﻿
/* This rule fires whether or not the theme is on, so nothing branded may live in it -
   it is byte-identical to main's first rule, minus the background-color that rule also
   carried. Sora is applied by the gated rule below instead; setting it here put the brand
   face on every flag-off page.

   DO NOT re-add the background-color. main paired it with !important, which beats
   MudBlazor's own body { background-color: var(--mud-palette-background) } and would pin a
   themed page light grey no matter what the palette says. The flag-off grey comes back
   below at a specificity that needs no !important. */
html, body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

/* Sora is the brand face and must not reach a flag-off instance, so it hangs off the same
   data-theme gate as the rest of the theme. (0,1,1)/(0,1,2) beats the rule above at
   (0,0,1), so the flag-on rendering is exactly what it was. */
html[data-theme],
html[data-theme] body {
    font-family: 'Sora', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

/* The gold theme is gated on enable_new_admin_home_page, the same flag that picks
   NavMenuGrouped in LoggedInLayout.razor:44. With the flag off nothing stamps
   data-theme, MudBlazor stays on its stock palette, and its Background is white -
   not the #f5f5f5 main forced. Keep main's grey for that flag-off path only.

   No !important, unlike main: this selector is (0,1,2) and already out-ranks MudBlazor's
   body rule at (0,0,1), and leaving it off is what keeps the themed path free to paint its
   own background. For this to match at all, _Layout.cshtml must not emit data-theme when
   the flag is off - see the note there about Razor and data-* attributes. */
html:not([data-theme]) body {
    background-color: #f5f5f5;
}

/* ----- Brand constants (gold theme) ---------------------------------------
   C:/Users/AlexGoldschmidt/Downloads/adminhomemockupproptech.html is the approved
   spec (token blocks at lines 50-62 :root, 66-93 dark, 97-124 light). It replaces
   admin-home-gold-variants/01-gold-rail.html, whose chrome was navy in both modes.

   NAME MAP - ours on the left, the mockup's on the right, so the two files stay
   diffable by eye. Our --pa- prefix is kept because these tokens sit on :root and
   share a document with .pg-hub's generic names.

     --pa-gold            --gold           #F2CD7A both
     --pa-gold-bright     --gold-bright    #FFDA72 both
     --pa-gold-deep       --gold-deep      #CD9650 both
     --pa-gold-dim        (none)           #94763A both - hero-only holdover, see below
     --pa-gold-fg         --gold-text      #8C6413 light / #F2CD7A dark
     --pa-gold-ic         --gold-ic        #B07F22 light / #F2CD7A dark
     --pa-gold-ic-soft    --gold-ic-soft   rgba(176,127,34,.62) / rgba(242,205,122,.55)
     --pa-gold-wash       --gold-wash      rgba(176,127,34,.10) / rgba(242,205,122,.12)
     --pa-gold-border     --border-gold    rgba(176,127,34,.30) / rgba(242,205,122,.16)
     --pa-border-soft     --border-soft    rgba(16,24,40,.10)   / rgba(255,255,255,.08)
     --pa-card            --card           #FFF->#FCFCFE        / white .04->.06
     --pa-card-flat       --card-flat      rgba(16,24,40,.035)  / rgba(255,255,255,.04)
     --pa-sidebar-bg      --sidebar-bg     #FFF->#F8F9FD        / #0A1220->#07101D
     --pa-app-glow        (--app-bg's two radials, minus the flat colour under them)
     --pa-shadow          --shadow
     --pa-radius          --radius         14px both
     --pa-radius-sm       --radius-sm      10px both
     --pa-on-gold         --on-gold        #1B1B3E both
     --pa-grad-gold       --grad-gold
     --pa-nav-active-wash --nav-active
     --pa-nav-active-text (the mockup inlines --text on .nav-link.active)

   The mockup's --text / --text-muted / --text-dim are NOT mirrored here: they are
   exactly the palette's TextPrimary / TextSecondary / TextDisabled, so the rules
   below use --mud-palette-text-* and there is one copy of each value, in
   AdminWeb/Theme/AdminTheme.cs.

   Two golds, named by ROLE and not by shade, and they are not interchangeable:
   --pa-gold-fg is the gold allowed to BE text (5.32:1 on white) and --pa-gold-ic
   is the gold for icon glyphs and UI boundaries (3.56:1 - clears WCAG 1.4.11's
   3:1 for a non-text indicator, fails the 4.5:1 body-text bar). Champagne
   (--pa-gold) is a FILL you put dark ink on, 1.5:1 on white. In dark mode all
   three collapse onto champagne, which is legible as ink on #07101D (12.5:1).

   --pa-gold-dim survives at Gold Rail's value on purpose: the signed-off home-page
   hero still draws its gradient CTA's hairline with it, and the hero is carved out
   of this pass. Nothing outside .ah-home's hero rules may use it.

   The --mud-palette-* variables are emitted by MudThemeProvider from
   AdminWeb/Theme and must never be declared in this file. */
:root {
    --pa-gold: #F2CD7A;
    --pa-gold-bright: #FFDA72;
    --pa-gold-deep: #CD9650;
    --pa-gold-dim: #94763A;
    --pa-on-gold: #1B1B3E;
    --pa-grad-gold: linear-gradient(135deg, var(--pa-gold-bright) 0%, var(--pa-gold) 45%, var(--pa-gold-deep) 100%);
    --pa-radius: 14px;
    --pa-radius-sm: 10px;

    /* Light values. The dark block below re-values every one of them. */
    --pa-gold-fg: #8C6413;
    --pa-gold-ic: #B07F22;
    /* DELIBERATE DEVIATION from the mockup's rgba(176,127,34,.62) - do not restore it.
       "Soft" on a DARK ground means receding toward the ground and still reading; on the
       white drawer it means fading INTO it, and .62 composites to #CEB076 = 2.10:1,
       under 1.4.11's 3:1 and worse than the plain grey icons it replaces. The hue tops
       out at 3.56:1 even at alpha 1, so no alpha rescues it. In light mode the resting
       glyph is therefore the full --pa-gold-ic; the resting/hover distinction is already
       carried by the row wash, the rail and the label weight. Dark keeps the soft step,
       where it genuinely measures 4.41:1. */
    --pa-gold-ic-soft: #B07F22;
    --pa-gold-wash: rgba(176, 127, 34, .10);
    --pa-gold-border: rgba(176, 127, 34, .30);
    --pa-border-soft: rgba(16, 24, 40, .10);
    --pa-card: linear-gradient(180deg, #FFFFFF, #FCFCFE);
    --pa-card-flat: rgba(16, 24, 40, .035);
    --pa-sidebar-bg: linear-gradient(180deg, #FFFFFF 0%, #F8F9FD 100%);
    --pa-app-glow: radial-gradient(1200px 600px at 10% -10%, rgba(242, 205, 122, .30), transparent 62%),
                   radial-gradient(900px 500px at 110% 10%, rgba(99, 95, 234, .10), transparent 60%);
    --pa-shadow: 0 18px 40px -24px rgba(93, 68, 12, .35);
    --pa-nav-active-text: #0E1524;
    --pa-nav-active-wash: linear-gradient(90deg, rgba(176, 127, 34, .13), rgba(176, 127, 34, .02));
}

html[data-theme="dark"] {
    --pa-gold-fg: #F2CD7A;
    --pa-gold-ic: #F2CD7A;
    --pa-gold-ic-soft: rgba(242, 205, 122, .55);
    --pa-gold-wash: rgba(242, 205, 122, .12);
    --pa-gold-border: rgba(242, 205, 122, .16);
    --pa-border-soft: rgba(255, 255, 255, .08);
    --pa-card: linear-gradient(180deg, rgba(255, 255, 255, .04), rgba(255, 255, 255, .06));
    --pa-card-flat: rgba(255, 255, 255, .04);
    --pa-sidebar-bg: linear-gradient(180deg, #0A1220 0%, #07101D 100%);
    --pa-app-glow: radial-gradient(1200px 600px at 10% -10%, rgba(242, 205, 122, .07), transparent 60%),
                   radial-gradient(900px 500px at 110% 10%, rgba(99, 95, 234, .08), transparent 60%);
    --pa-shadow: 0 18px 40px -22px rgba(242, 205, 122, .25);
    --pa-nav-active-text: #FCFCFF;
    --pa-nav-active-wash: linear-gradient(90deg, rgba(242, 205, 122, .14), rgba(242, 205, 122, .02));
    /* First-paint floor, and the one place a raw palette hex belongs in this file:
       _Layout.cshtml stamps data-theme server-side, but MudThemeProvider's
       --mud-palette-* style element does not exist until the circuit is up, so
       without this the page flashes white before Blazor connects. */
    background-color: #07101D;
    /* Native chrome follows the mode: scrollbars, and the five raw <textarea> elements in the
       admin that carry no background of their own and would otherwise stay white boxes in a dark
       page. MudBlazor's own inputs are unaffected - it already sets color-scheme on .mud-input
       from --mud-native-html-color-scheme - and AdminWeb has no raw select/date/number/checkbox
       controls for this to reach. */
    color-scheme: dark;
}

/* Light mode gets the same floor for the same reason - the previous palette's
   Background was a cream that read as "white enough" unlit, and #F4F5F9 does not. */
html[data-theme="light"] {
    background-color: #F4F5F9;
    color-scheme: light;
}

/* Belt and braces for the certificate and email-preview iframes: their documents did not inherit
   the scheme in testing, but a preview that renders dark-on-dark would be unreadable and this
   costs one declaration. */
html[data-theme] iframe {
    color-scheme: light;
    /* color-scheme on its own is not enough, and this is the second half of the same fix. An embedded
       document that declares no background of its own keeps a TRANSPARENT canvas and composites over
       the parent, so in dark mode the #07101D ground shows through while the document's default ink
       stays black - invisible. The trigger QA hit is EmailController's unpopulated-template
       placeholder (Controllers/EmailController.cs:33 and :47), which returns a bare <h1> fragment
       with no document around it; a populated template is unaffected when its HTML carries a <body>
       background of its own, which is why the report reads "only when no template is populated".
       White is the correct floor for both previews regardless of mode - a certificate and an email
       are documents that get printed or read on white. */
    background-color: #FFFFFF;
}

/* ----- Scrollbars ------------------------------------------------------------
   TWO BLOCKS THAT MUST NEVER MEET, and the usual advice about them is backwards.
   Measured in Chrome 152/Windows: default gutter 15px; ::-webkit-scrollbar{width:8px} alone gives
   8px; scrollbar-width:thin alone gives 10px; BOTH together give 10px, in either source order.
   ANY non-initial value of scrollbar-width OR scrollbar-color switches the element off the webkit
   path entirely - and scrollbar-color INHERITS, so a single declaration on <html> would silently
   kill every ::-webkit-scrollbar rule in the document, including MudBlazor's own textarea one.
   Hence the @supports fence: selector(::-webkit-scrollbar) is true in Chromium and false in
   Firefox, and an engine too old to parse selector() drops the block and falls through to the
   webkit path, which is the safe direction. DO NOT "tidy" the two blocks together.

   WIDTH IS TWO DECLARATIONS, NOT ONE. MudBlazor hard-codes .scroll-locked{padding-right:8px} onto
   <body> whenever a dialog or popover opens, to stand in for the scrollbar it has just hidden.
   That number is theirs and is not derived from ours, so any width other than 8px has to restate
   it in the .scroll-locked rule below - otherwise the compensation no longer matches the gutter it
   compensates for and the page shifts by the difference on every open. Change the two together.
   Verified at 4px: content width identical with and without .scroll-locked applied.

   --pa-gold-ic-soft resting, --pa-gold-fg on hover: one declaration each, correct in both modes
   with no dark override, and role-correct - a thumb is a boundary, so it takes the icon gold, not
   the text gold. NEVER alpha the light thumb: --pa-gold-ic at 70% over the page background is
   2.21:1, under the 3:1 a boundary needs, and the hue tops out at 3.56:1 even at full opacity.
   Subtlety in light mode comes from the width and the transparent track - which is also what keeps
   the thumb compositing over the real surface, and what those contrast numbers assume. */

/* THIS RESET IS LOAD-BEARING - without it nothing below has any effect at all.
   Elsewhere the admin ships a component that renders, into <body>, a style block ending in
   `html, body * { scrollbar-color: #c4c4c4 transparent; scrollbar-width: thin; }`. Per the note
   above, ANY non-initial value of either property takes that element off the ::-webkit-scrollbar
   implementation - so that one declaration disables every webkit rule in the document, its own
   included, and every scroller falls back to a 10px grey native bar.
   It is not a specificity fight we can win by out-ranking the thumb colour; the whole webkit path
   is switched off. Resetting the two properties to `auto` with higher specificity is what hands
   it back. Verified on staging: descendant scroller gutter 10px -> 8px with this in place.
   `html, body *` is (0,0,2), so `html[data-theme] body *` at (0,1,2) beats it. */
html[data-theme],
html[data-theme] body * {
    scrollbar-color: auto;
    scrollbar-width: auto;
}

html[data-theme] ::-webkit-scrollbar {
    width: 4px;
    height: 4px;
}

html[data-theme] ::-webkit-scrollbar-track {
    background: transparent;
}

/* Radius is half the width, which keeps the thumb a pill rather than a rounded bar. */
html[data-theme] ::-webkit-scrollbar-thumb {
    background: var(--pa-gold-ic-soft);
    border-radius: 2px;
}

html[data-theme] ::-webkit-scrollbar-thumb:hover {
    background: var(--pa-gold-fg);
}

html[data-theme] ::-webkit-scrollbar-corner {
    background: transparent;
}

/* Holds MudBlazor's scroll-lock compensation equal to the width above - see the WIDTH note.
   (0,2,1) beats its own (0,1,0), so no !important is needed. The Firefox block restates this a
   third time with Firefox's own thin-gutter value, and wins there on source order. */
html[data-theme] .scroll-locked,
html[data-theme] .scroll-locked .mud-layout .mud-appbar,
html[data-theme] .scroll-locked .mud-layout .mud-main-content .mud-scroll-to-top {
    padding-right: 4px;
}

@supports not selector(::-webkit-scrollbar) {
    /* Firefox has no ::-webkit-scrollbar at all, so here the standard properties are the only
       lever and the reset above has to be put back.

       Both selectors are repeated verbatim from that reset so this matches its specificity exactly
       and wins on source order alone. The root is listed SEPARATELY rather than relying on the
       descendant selector for two reasons: `body *` cannot match <html>, and only the ROOT's
       scrollbar-width propagates to the viewport - miss it and the page scrollbar stays ~17px
       while the compensation below assumes 11px, under-padding every dialog open by ~6px. */
    html[data-theme],
    html[data-theme] body * {
        scrollbar-color: var(--pa-gold-ic-soft) transparent;
        scrollbar-width: thin;
    }

    /* scrollbar-width:thin drops Firefox's real gutter from ~17px to ~11px, which turns
       MudBlazor's currently-correct compensation into a ~6px OVER-pad. (0,2,1) beats its (0,1,0),
       so no !important is needed. */
    html[data-theme] .scroll-locked,
    html[data-theme] .scroll-locked .mud-layout .mud-appbar,
    html[data-theme] .scroll-locked .mud-layout .mud-main-content .mud-scroll-to-top {
        padding-right: 11px;
    }
}

/* The mockup's --app-bg is two radial glows over a flat ground. The ground is the
   palette's Background, which MudBlazor already paints as body's background-COLOR,
   so only the glows live here and they layer over it as body's background-IMAGE.
   Fixed attachment because they are lighting on the viewport, not on the document -
   scrolling a long grid past them would otherwise smear them.

   They have to sit on body rather than html: the two rules above give html a
   background-color, which stops body's background propagating to the canvas, and a
   glow on html would then be painted over by body's own opaque colour. The cost of
   that is that body's BOX, not the viewport, is the painting area - hence
   min-height, so a short page still gets lit to the bottom of the screen. */
html[data-theme] body {
    background-image: var(--pa-app-glow);
    background-attachment: fixed;
    background-repeat: no-repeat;
    min-height: 100vh;
}

h1:focus {
    outline: none;
}

a, .btn-link {
    color: #0077cc;
}

/* #0077cc is only 4.10:1 on the dark page background, so links fall back to the
   palette accent (champagne, 12.09:1) there. Light mode keeps the old blue. */
html[data-theme="dark"] a,
html[data-theme="dark"] .btn-link {
    color: var(--mud-palette-primary);
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.content {
    padding-top: 1.1rem;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid red;
}

.validation-message {
    color: red;
}

/* The ink is explicit because the plate is not: `lightyellow` is fixed while the
   inherited body colour follows the palette, so in dark mode this banner was white
   on pale yellow (1.01:1) - an error notice nobody could read. */
#blazor-error-ui {
    background: lightyellow;
    color: #1B1B3E;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;   
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }


.search-bar {
    background-color: rgb(255,255,255,.15)
}

.custom-search .mud-input {
    height: 42px !important;
}

.custom-search.mud-input-control {
    background-color: rgba(255,255,255,.15);
    margin-bottom: 5px;
    height: 42px;
    border-radius: var(--mud-default-borderradius);
    color: white;
}

    .custom-search.mud-input-control .mud-input-root {
        color: white;
    }

    .custom-search.mud-input-control .mud-icon-default {
        color:white;
    }

/* ----- Initial page loader (shown until Blazor circuit is ready) ----- */
#initial-loader {
    position: fixed;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: #f5f5f5;
    z-index: 99999;
    transition: opacity 0.25s ease-out;
}

#initial-loader.app-loaded {
    opacity: 0;
    pointer-events: none;
}

#initial-loader .app-spinner {
    width: 48px;
    height: 48px;
    border: 4px solid rgba(0,0,0,.08);
    border-top-color: #1b6ec2;
    border-radius: 50%;
    animation: appLoaderSpin .8s linear infinite;
}

#initial-loader .app-loading-text {
    margin-top: 16px;
    color: rgba(0,0,0,.55);
    font-size: 14px;
    letter-spacing: 0.02em;
}

@keyframes appLoaderSpin { to { transform: rotate(360deg); } }

/* The loader paints before Blazor connects, so --mud-palette-* does not exist yet
   and these have to be literals. The values are the spec's dark Background,
   Primary and TextSecondary. */
html[data-theme="dark"] #initial-loader {
    background: #07101D;
}

html[data-theme="dark"] #initial-loader .app-spinner {
    border-color: rgba(255,255,255,.12);
    border-top-color: #F2CD7A;
}

html[data-theme="dark"] #initial-loader .app-loading-text {
    color: #A7A7A7;
}

/* ----- App chrome: top bar + drawer -----------------------------------------
   Everything here is gated on html[data-theme], which _Layout.cshtml only stamps
   when enable_new_admin_home_page is on, so a flag-off instance keeps stock
   MudBlazor chrome untouched.

   The load-bearing change in this pass: in light mode the app bar and drawer are
   WHITE, not navy. AppbarBackground and DrawerBackground carry the colour from the
   palette; what they cannot carry is a gradient or a blur, which is what is left
   below. */
html[data-theme] .mud-appbar {
    /* --topbar-bg is translucent by design, so the bar needs the blur or the page
       scrolls through it as a smear. */
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    /* The mockup draws the bar's bottom edge with a hairline instead of a drop
       shadow, and MudAppBar is set to Elevation=25 in LoggedInLayout.razor - a
       shadow tuned for a navy bar that reads as a dark smudge under a white one. */
    box-shadow: none;
    border-bottom: 1px solid var(--pa-border-soft);
}

/* --sidebar-bg. background-IMAGE over the palette's flat drawer colour, so a
   browser that drops the gradient still lands on the right top stop. */
html[data-theme] .mud-drawer {
    background-image: var(--pa-sidebar-bg);
    /* Same reason the app bar above drops its shadow, and the same MudBlazor knob:
       MudDrawer is set to Elevation=25 in LoggedInLayout.razor, whose
       --mud-elevation-25 is one array shared by both modes and was tuned for a navy
       drawer. Against a white drawer on a #F4F5F9 ground it is a dark smear down the
       page. The mockup's sidebar (line 154) draws its edge with a hairline only. */
    box-shadow: none;
    border-right: 1px solid var(--pa-border-soft);
}

/* ----- Global search (plain-input replacement for MudAutocomplete) ----- */
.global-search-wrap {
    position: relative;
    width: 783px;
    max-width: 60vw;
    margin-bottom: 5px;
}

.global-search-input {
    width: 100%;
    height: 42px;
    box-sizing: border-box;
    padding: 0 36px 0 36px;
    background-color: rgba(255,255,255,.15);
    border: 1px solid rgba(255,255,255,.3);
    border-radius: var(--mud-default-borderradius);
    color: white;
    font: inherit;
    outline: none;
}

.global-search-input::placeholder {
    color: rgba(255,255,255,.75);
}

.global-search-input:focus {
    border-color: rgba(255,255,255,.6);
    background-color: rgba(255,255,255,.22);
}

.global-search-icon,
.global-search-spinner {
    position: absolute;
    top: 50%;
    left: 10px;
    transform: translateY(-50%);
    color: white;
    pointer-events: none;
    width: 18px;
    height: 18px;
}

.global-search-spinner {
    display: none;
    border: 2px solid rgba(255,255,255,.3);
    border-top-color: white;
    border-radius: 50%;
    box-sizing: border-box;
    animation: gsSpin .8s linear infinite;
}

.global-search-wrap.is-loading .global-search-icon { display: none; }
.global-search-wrap.is-loading .global-search-spinner { display: inline-block; }

@keyframes gsSpin { to { transform: translateY(-50%) rotate(360deg); } }

/* The eight rules above are the ORIGINAL dark-app-bar styling and they stay as the
   flag-off path: with no data-theme the bar is stock MudBlazor's violet Primary, so
   white-on-translucent-white is right there.

   With the flag on it is wrong in light mode - the bar is now WHITE, and
   rgba(255,255,255,.15) on white with white ink and a white-ish placeholder is an
   invisible control. This block re-points the field to the mockup's .topbar .search
   (lines 217-224): --card-flat plate, --border-soft hairline, --radius-sm corners,
   --text ink, --text-dim placeholder, --gold-ic-soft glyph, --border-gold on focus. */
/* LinesInputs rather than the mockup's --border-soft: the plate is only 1.07:1
   against the white bar, so this hairline is the control's ONLY boundary and has to
   clear the 3:1 of WCAG 1.4.11 on its own. --border-soft measures 1.23:1; LinesInputs
   is 3.37:1 light / 3.79:1 dark and already exists in the palette for this exact job. */
html[data-theme] .global-search-input {
    background-color: var(--pa-card-flat);
    border: 1px solid var(--mud-palette-lines-inputs);
    border-radius: var(--pa-radius-sm);
    color: var(--mud-palette-text-primary);
}

/* text-secondary, not text-disabled: this placeholder is the field's only accessible
   name (the input has no label), so it is enabled content and owes 4.5:1 - disabled
   would be 2.89:1 and exempt from nothing. */
html[data-theme] .global-search-input::placeholder {
    color: var(--mud-palette-text-secondary);
}

/* Still needed on plain :focus - the ungated rule above swaps the plate to
   rgba(255,255,255,.24), which is the dark-bar behaviour and goes white-on-white here. */
html[data-theme] .global-search-input:focus {
    background-color: var(--pa-card-flat);
    border-color: var(--pa-gold-ic);
}

/* A real ring, not a border-colour nudge. The ungated block kills the outline for the
   flag-off dark bar; on the white bar the mockup's border swap moves the control by
   1.13:1, which is invisible - so focus has to be re-stated here. */
html[data-theme] .global-search-input:focus-visible {
    outline: 2px solid var(--pa-gold-ic);
    outline-offset: 2px;
}

html[data-theme] .global-search-icon {
    color: var(--pa-gold-ic-soft);
}

html[data-theme] .global-search-spinner {
    border-color: var(--pa-border-soft);
    border-top-color: var(--pa-gold-ic);
}

/* Hand-rolled popover, so it gets none of MudPopover's theming for free: every
   colour below was a light-only literal and is now the palette equivalent. */
.global-search-dropdown {
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    right: 0;
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-primary);
    border-radius: var(--mud-default-borderradius);
    box-shadow: var(--mud-elevation-8);
    max-height: 50vh;
    overflow-y: auto;
    z-index: 1300;
}

.global-search-dropdown .gs-item {
    padding: 10px 14px;
    cursor: pointer;
    border-bottom: 1px solid var(--mud-palette-divider);
    font-size: 0.92rem;
}

.global-search-dropdown .gs-item:last-child { border-bottom: none; }

.global-search-dropdown .gs-item:hover,
.global-search-dropdown .gs-item.gs-active {
    background-color: var(--mud-palette-action-default-hover);
}

.global-search-dropdown .gs-empty {
    padding: 10px 14px;
    color: var(--mud-palette-text-secondary);
    font-style: italic;
    font-size: 0.9rem;
}

/* The panel beneath the field follows it: same corner radius, and an edge of its
   own. It needs the edge in BOTH modes now - the panel is Surface white and it
   drops out of a white app bar onto a #F4F5F9 ground, so the 1-in-25 luminance
   step is all that separates it. */
html[data-theme] .global-search-dropdown {
    border: 1px solid var(--pa-border-soft);
    border-radius: var(--pa-radius-sm);
}

/* LANDMINE B in miniature: --mud-elevation-* is one array shared by both modes,
   so the drop shadow above is invisible against a dark page. Dark mode leans
   entirely on the edge, so it gets the stronger INPUT line rather than
   --pa-border-soft (rgba(255,255,255,.08)), which is a between-rows hairline and
   reads as nothing as a floating panel's boundary. */
html[data-theme="dark"] .global-search-dropdown {
    border-color: var(--mud-palette-lines-inputs);
}

.mud-table-container {
    min-height: 0;
    max-height: 75vh !important;
}

.mud-main-content {
}

.mud-tab {
    text-transform: none !important;
}

.justify-content-center {
    justify-content: center;
}

.menu-label-count
{
    float:right;
    font-weight: bold;
}

.red-button
{
    background-color: #B40000;
    color: white;
}

.w-full {
    width: 100%;
}

.max-w-full {
    max-width: 100%;
}

.flex {
    display: flex;
}

.flex-col {
    flex-direction: column;
}

.gap-2 {
    gap: 0.5rem;
}

.tooltip-custom .mud-popover {
    max-width: 300px;
    text-align: left;
    padding: 8px; 
    white-space: normal; 
    word-wrap: break-word; 
    box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2); 
}

 .mud-tab-slider.mud-tab-slider-horizontal {
     height: 0;
 }
 
.mud-tab.mud-tab-active {
    background-color: var(--mud-palette-primary-hover);
    color: var(--mud-palette-primary);
}
/* Was indigo-on-#f5f5f5, i.e. invisible in dark mode. The detail row of six
   discount-code grids, so it has to follow the palette. */
.expanded-row {
    border-left: 4px solid var(--mud-palette-primary);
    background-color: var(--mud-palette-background-gray);
}

.mud-list-item.no-checkbox .mud-list-item-icon {
    display: none;
}

#account-status-report .mud-table-head .header-centered th {
    text-align: center;
}

.no-interaction-item {
    cursor: text;
}

.copy-zone,
.copy-zone * {
    -webkit-user-select: text !important;
    user-select: text !important;
    cursor: text;
}


.json-placeholder textarea::placeholder {
    white-space: pre-wrap;
    font-family: monospace;
}

.mud-tooltip.custom-tooltip-wrapper {
    background-color: #1e2235 !important;
    color: #e8eaf2 !important;
    border: none !important;
    box-shadow: 0 4px 12px rgba(0,0,0,0.45) !important;
    border-radius: 6px !important;
    padding: 0 !important;
    z-index: 99999 !important;
}

.mud-tooltip.custom-tooltip-wrapper.mud-tooltip-arrow::after {
    border-top-color: #1e2235 !important;
}

.custom-tooltip-content {
    max-width: 220px;
    overflow-y: auto;
    padding: 9px 12px;
    color: #e8eaf2;
    background: transparent;
    font-size: 12px;
    line-height: 1.55;
    font-weight: 400;
    white-space: normal;
    letter-spacing: 0;
    text-align: left;
    border-radius: 6px;
}

.custom-tooltip-content-wide {
    max-width: 550px;
}

.custom-tooltip-root {
    width: 100% !important;
}

.help-tooltip-trigger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: help;
    vertical-align: middle;
}

.help-tooltip-icon {    
    font-size: 18px !important;
    opacity: 0.8;
}

.help-tooltip-icon:hover {
    opacity: 1 !important;
}

.ml-1 {
    margin-left: 6px;
}

.mud-nav-link .help-tooltip-trigger {
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s ease;
}

.mud-nav-link:hover .help-tooltip-trigger {
    opacity: 1;
    visibility: visible;
}

.field-tooltip-right {
    display: inline-flex;
    align-items: center;
    margin-left: 8px;
    flex: 0 0 auto;
}

.mud-paper.d-flex.align-center > :first-child {
    flex: 1 1 auto;
    min-width: 0;
}


.editor-tooltip-top {
    align-self: flex-start;
    margin-top: 10px;
}

.mud-table-cell .mud-button .help-tooltip-trigger {
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s ease;
}


.mud-table-cell .mud-button:hover .help-tooltip-trigger {
    opacity: 1;
    visibility: visible;
}

.field-tooltip-right-with-validator {
    display: flex;
    align-items: center;
    margin-right: 8px;
    flex: 0 0 auto;
    height: 56px;
    position: relative;
}

.field-tooltip-right-with-validator .mud-popover-cascading-value {
    position: absolute !important;
    width: 0 !important;
    height: 0 !important;
    overflow: visible !important;
}

/* Edit Affiliate Form*/
.affiliate-header-item {
    width: var(--affiliate-tier-col, 150px);
    max-width: var(--affiliate-tier-col, 150px);
    min-width: var(--affiliate-tier-col, 150px);
    display: flex;
    align-items: flex-end;
}

.affiliate-header-text {
    width: var(--affiliate-tier-col, 150px);
    min-height: 42px;
    display: flex;
    align-items: flex-end;
    justify-content: flex-end;
    text-align: right;
    line-height: 1.2;
}

/* Dashboard */
.dashboard-banner {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 16px;
    background: linear-gradient(90deg, #f59e0b, #fbbf24);
    color: #1f2937;
    border-radius: var(--mud-default-borderradius);
    font-size: 0.875rem;
    font-weight: 500;
}

.dashboard-banner-text {
    flex: 1;
}

.dashboard-banner-icon {
    font-size: 1rem;
    flex-shrink: 0;
}

.dashboard-banner-close {
    color: inherit !important;
    margin: -4px -8px -4px 0;
    flex-shrink: 0;
}

.dashboard-banner-link {
    font-weight: 700;
    text-decoration: underline;
    cursor: pointer;
    color: inherit;
}

.dashboard-greeting {
    font-weight: 700;
    letter-spacing: -0.01em;
}

.dashboard-greeting em {
    font-family: 'Cormorant Garamond', serif;
    font-weight: 600;
    font-style: italic;
    font-size: 32px;
}

.dashboard-date {
    white-space: nowrap;
    padding-top: 6px;
    margin-left: 24px;
}

.dashboard-stat-header {
    font-weight: 600 !important;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.dashboard-link-sm {
    font-size: 0.8rem;
}

.dashboard-stat-value {
    font-weight: 700;
    margin-top: 2px;
}

.dashboard-stat-sublabel {
    font-size: 0.7rem !important;
    line-height: 1.2;
}

.dashboard-section-title {
    font-weight: 700 !important;
}

.dashboard-studio-branding {
    background: linear-gradient(135deg, #0f766e 0%, #059669 100%) !important;
    color: white !important;
    min-height: 170px;
}

.dashboard-studio-web {
    background: linear-gradient(135deg, #1e293b 0%, #334155 100%) !important;
    color: white !important;
    min-height: 170px;
}

.dashboard-studio-header {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 12px;
}

.dashboard-studio-icon {
    font-size: 1.8rem !important;
    color: white !important;
}

.dashboard-studio-name {
    font-weight: 700 !important;
    color: white !important;
}

.dashboard-studio-desc {
    color: rgba(255, 255, 255, 0.85) !important;
    font-size: 0.875rem;
    line-height: 1.55;
    margin-bottom: 20px;
}

.dashboard-studio-btn {
    background: rgba(255, 255, 255, 0.2) !important;
    color: white !important;
    border: 1px solid rgba(255, 255, 255, 0.4) !important;
}

.dashboard-activity-row {
    gap: 12px;
}

/* --mud-palette-action-hover is not a variable MudBlazor emits, so this rule has
   been dead since it was written. The real name is --mud-palette-action-default-hover. */
.dashboard-activity-row:hover {
    background-color: var(--mud-palette-action-default-hover);
}

.dashboard-activity-avatar {
    flex-shrink: 0;
}

.dashboard-activity-content {
    flex: 1;
    min-width: 0;
}

.dashboard-activity-title {
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dashboard-activity-user {
    white-space: nowrap;
    font-size: 0.72rem !important;
}

.dashboard-skeleton-row {
    gap: 12px;
}

.dashboard-studio-actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    align-items: center;
}

.dashboard-studio-btn-import {
    color: rgba(255, 255, 255, 0.55) !important;
    font-size: 0.75rem !important;
    padding: 0 4px !important;
    min-width: unset !important;
    text-transform: none !important;
}

.dashboard-studio-btn-import:hover {
    color: rgba(255, 255, 255, 0.85) !important;
    background: transparent !important;
    text-decoration: underline !important;
}

.genie-import-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 32px 0;
    gap: 16px;
}

.genie-import-loading-label {
    font-weight: 500;
}

.genie-import-result {
    white-space: pre-wrap;
    word-break: break-word;
}

.dashboard-stat-card {
    height: 100%;
}

/* Force every tier-grid cell to the same width so headers and data align
   regardless of which optional columns (Revenue Threshold, Multi-Level
   Pass Up, etc.) are visible. */
.affiliate-tier-grid > .mud-grid-item {
    flex: 0 0 var(--affiliate-tier-col, 150px);
    width: var(--affiliate-tier-col, 150px);
    min-width: var(--affiliate-tier-col, 150px);
    max-width: var(--affiliate-tier-col, 150px);
}

/* The tier rows are one wrapping flex container split by MudFlexBreak, so a container narrower than
   the full column set silently pushes the last column onto its own line and knocks the headers out
   of alignment. Reserve exactly the width the columns need (--affiliate-tier-cols is set by the
   component) and scroll the block horizontally when the popup is too narrow to give us that much. */
.affiliate-tier-scroll {
    overflow-x: auto;
    /* Room for the negative margin MudGrid's spacing applies, so the scroll box doesn't clip it. */
    padding-left: 8px;
    margin-left: -8px;
}

.affiliate-tier-grid {
    min-width: calc(var(--affiliate-tier-cols, 0) * var(--affiliate-tier-col, 150px));
}

/* Edit Account popup: nudge help-tooltip icons up 5px (popup only) */
.account-edit-dialog .help-tooltip-trigger {
    vertical-align: middle;
}
.account-edit-dialog .help-tooltip-icon {
    position: relative;
    top: -5px;
}

/* Account Detail page: nudge help-tooltip icons up 5px (page only) */
.account-detail-page .help-tooltip-icon {
    position: relative;
    top: -5px;
}

/* Account Detail action buttons: the page rule above lifts every icon 5px, which is too high inside a button — push button icons 5px back down so they sit centered in the button */
.account-detail-page .mud-button .help-tooltip-icon {
    top: 0;
}

/* Account Detail status-badge (InfoTag) tooltips: pull icon closer to the chip (left + up) */
.account-infotag-tip .help-tooltip-icon {
    position: relative;
    left: -5px;
    top: -6px;
}

/* ============================================================================
   PropGenie tools hub — Branding page (design: propgenie-admin-portal-v3)
   Every selector is scoped under .pg-hub on purpose: the design uses very
   generic class names (.card, .panel, .help, .tools…) that must never leak
   into the rest of the admin.
   ============================================================================ */
.pg-hub {
    --pg-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --pg-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
    /* --pp is the hub's brand accent, and --pp-line names the lavender that used to
       be hardcoded into the two hover borders further down. These are the ORIGINAL
       violet values and they must stay here: this block is ungated, so it is what a
       flag-off instance renders, and with the gold theme off the rest of the admin
       is still MudBlazor violet - a gold hub would be the only odd page.
       The gold re-point lives in the html[data-theme] block below. */
    --pp: #5C4AE4;
    --pp-soft: #EFEDFD;
    --pp-line: #C9C2F6;
    --ink: #1B2233;
    --ink-2: #41495E;
    --muted: #6B7488;
    --faint: #98A0B3;
    --line: #E6E8EF;
    --line-soft: #F0F2F7;
    --pg-radius: 18px;
    --pg-shadow-sm: 0 1px 2px rgba(27, 34, 51, .04);
    --pg-shadow: 0 14px 36px rgba(27, 34, 51, .10);
    font-family: var(--pg-sans);
    font-size: 14px;
    line-height: 1.5;
    color: var(--ink);
    /* No max-width: the MudPaper panels below this grid are full-width, so a cap here
       made the cards read as cramped against the rest of the page. */
    padding-bottom: 8px;
    -webkit-font-smoothing: antialiased;
}

/* The hub's violet is the one colour the stakeholder rejected, so with the gold
   theme on it tracks the gold system instead - otherwise the admin reads as half
   purple. Gated on html[data-theme], which _Layout.cshtml only stamps when
   enable_new_admin_home_page is on, so turning the flag off really does put the
   Branding page back to violet. All three are aliases of tokens that already flip,
   so this covers dark mode too and the dark block below never touches them. */
html[data-theme] .pg-hub {
    --pp: var(--pa-gold-fg);
    --pp-soft: var(--pa-gold-wash);
    --pp-line: var(--pa-gold-border);
}

.pg-hub a {
    color: inherit;
    text-decoration: none;
}

/* one colour family per module — its features carry the same colour */
.pg-hub .fam-web {
    --fam: #2563EB;
    --fa: #EFF5FF;
    --fb: #DCE9FE;
    --fr: #C7DBFD;
    --fg: rgba(37, 99, 235, .26);
    --g1: #4F8CF7;
    --g2: #1D4ED8;
}

.pg-hub .fam-dash {
    --fam: #0E9F6E;
    --fa: #EDFBF4;
    --fb: #D3F5E4;
    --fr: #BCEDD5;
    --fg: rgba(14, 159, 110, .24);
    --g1: #34C48C;
    --g2: #07875C;
}

.pg-hub .fam-brand {
    --fam: #7C3AED;
    --fa: #F5F2FF;
    --fb: #E9E2FE;
    --fr: #DCD2FC;
    --fg: rgba(124, 58, 237, .26);
    --g1: #A063F5;
    --g2: #6323CE;
}

.pg-hub .fam-mkt {
    --fam: #E11D67;
    --fa: #FEF1F5;
    --fb: #FCDDE8;
    --fr: #FBCEDE;
    --fg: rgba(225, 29, 103, .24);
    --g1: #F5589B;
    --g2: #C71156;
}

/* Amber, and it is the one hue the suite had left: blue, green, violet and rose are
   spoken for, and gold is what a certificate seal is. --fam is used as TEXT in four
   places below, so it is the 700 step (5.0:1 on white) rather than the brighter 600 —
   the gradient carries the brightness instead, where it is decoration. */
.pg-hub .fam-cert {
    --fam: #B45309;
    --fa: #FFFBEB;
    --fb: #FEF3C7;
    --fr: #FDE68A;
    --fg: rgba(180, 83, 9, .24);
    --g1: #F59E0B;
    --g2: #92400E;
}

.pg-hub .phead {
    margin: 0 0 20px;
}

.pg-hub .phead h1 {
    margin: 0;
    font-size: 24px;
    font-weight: 800;
    letter-spacing: -.7px;
    color: var(--ink);
}

/* ================= TOOL CARDS ================= */
.pg-hub .tools {
    display: grid;
    /* auto-fit rather than a fixed column count: a card is unreadable below ~260px
       (its title and feature labels start wrapping to three lines), so the row reflows
       on its own at every width and empty tracks collapse when a card is disabled.
       The 420px cap is the point of the max: with 1fr a tenant that has only one
       module enabled got a single card stretched across the whole page. */
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 420px));
    gap: 18px;
    align-items: stretch;
}

.pg-hub .card {
    border: 1px solid var(--fr);
    border-left: 3px solid var(--fam);
    border-radius: var(--pg-radius);
    background: #fff;
    overflow: hidden;
    position: relative;
    display: flex;
    flex-direction: column;
    box-shadow: var(--pg-shadow-sm);
    transition: box-shadow .22s, border-color .22s, transform .22s;
}

.pg-hub .card:hover {
    box-shadow: 0 18px 42px var(--fg);
    transform: translateY(-4px);
}

/* The illustration plate matches the artwork's own 264:144 ratio, so a correctly-proportioned
   image fills it edge to edge. Keep new artwork on that ratio: anything else letterboxes against
   the gradient below, which is what made the Web Builder plate look like it had a border. */
.pg-hub .art {
    aspect-ratio: 264/144;
    min-height: 132px;
    flex: 0 0 auto;
    padding: 0;
    overflow: hidden;
    background: linear-gradient(165deg, var(--fb) 0%, var(--fa) 55%, #fff 100%);
    border-bottom: 1px solid var(--fr);
}

.pg-hub .art img,
.pg-hub .art svg {
    width: 100%;
    height: 100%;
    display: block;
}

/* contain, not cover: these are illustrations with content out at the edges (globes, orbits),
   so a plate that ever stops matching 264:144 should letterbox rather than silently crop the
   artwork. It is also what the SVG-art cards do via preserveAspectRatio, so both kinds of plate
   behave the same. */
.pg-hub .art img {
    object-fit: contain;
}

.pg-hub .card-body {
    padding: 16px 17px 0;
    flex: 1;
    display: flex;
    flex-direction: column;
}

.pg-hub .card-body h3 {
    margin: 0;
    font-size: 16.5px;
    font-weight: 780;
    letter-spacing: -.4px;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.pg-hub .kicker {
    font-size: 9.5px;
    font-weight: 800;
    letter-spacing: .13em;
    text-transform: uppercase;
    color: var(--faint);
    margin-top: 6px;
}

.pg-hub .card-body p {
    margin: 11px 0 0;
    color: var(--muted);
    font-size: 12.6px;
    line-height: 1.55;
}

/* capability bullets */
.pg-hub .bul {
    list-style: none;
    margin: 14px 0 0;
    padding: 0;
}

.pg-hub .bul li {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    font-size: 12.2px;
    color: var(--ink-2);
    margin-bottom: 9px;
}

.pg-hub .bul li .bi {
    width: 17px;
    height: 17px;
    flex: 0 0 auto;
    border-radius: 6px;
    background: var(--fa);
    border: 1px solid var(--fr);
    display: grid;
    place-items: center;
    margin-top: 1px;
}

.pg-hub .bul li .bi svg {
    width: 10px;
    height: 10px;
    color: var(--fam);
}

/* features nested under their parent module */
.pg-hub .feats {
    margin-top: 14px;
    /* equal-height feature rows: row 1 is the label, every feature row after it
       stretches to the tallest one so a two-line description doesn't shrink its sibling */
    display: grid;
    grid-template-rows: auto;
    grid-auto-rows: 1fr;
    gap: 8px;
}

.pg-hub .feats-lbl {
    font-size: 11px;
    font-weight: 700;
    color: var(--ink-2);
    margin-bottom: 1px;
}

.pg-hub .feat {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 10px;
    border-radius: 12px;
    background: var(--fa);
    border: 1px solid var(--fr);
    cursor: pointer;
    transition: .15s;
}

.pg-hub .feat:hover {
    background: var(--fb);
}

.pg-hub .feat .fi {
    width: 30px;
    height: 30px;
    flex: 0 0 auto;
    border-radius: 9px;
    display: grid;
    place-items: center;
    background: #fff;
    border: 1px solid var(--fr);
    box-shadow: 0 2px 6px var(--fg);
}

.pg-hub .feat .fi svg {
    width: 15px;
    height: 15px;
    color: var(--fam);
}

.pg-hub .feat .fn {
    min-width: 0;
    flex: 1;
}

.pg-hub .feat .fn b {
    display: block;
    font-size: 12.3px;
    font-weight: 680;
    letter-spacing: -.1px;
}

.pg-hub .feat .fn span {
    display: block;
    font-size: 10.8px;
    color: var(--muted);
    margin-top: 1px;
}

.pg-hub .feat .fgo {
    flex: 0 0 auto;
    color: var(--fam);
    display: grid;
    place-items: center;
}

.pg-hub .feat .fgo svg {
    width: 14px;
    height: 14px;
}

/* footer */
.pg-hub .card-foot {
    /* padding-top keeps the button off the card body */
    padding: 14px 17px 16px;
    margin-top: auto;
}

.pg-hub .open-row {
    display: flex;
    /* five columns on a wide screen leave a card ~195px wide, which is narrower than
       "Open Web Builder" + "Import from Genie" side by side. Wrap to a stacked pair
       rather than let nowrap clip the primary label. */
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
}

.pg-hub .btn-open {
    flex: 1 1 120px;
    min-width: 0;
    font-family: var(--pg-sans);
    font-weight: 680;
    font-size: 12px;
    letter-spacing: -.1px;
    white-space: nowrap;
    text-align: center;
    border: 0;
    border-radius: 11px;
    padding: 11px 8px;
    color: #fff;
    cursor: pointer;
    background: linear-gradient(135deg, var(--g1) 0%, var(--g2) 100%);
    box-shadow: 0 4px 12px var(--fg), inset 0 1px 0 rgba(255, 255, 255, .25);
    transition: box-shadow .16s, transform .16s, filter .16s;
}

.pg-hub a.btn-open {
    display: block;
    color: #fff;
}

.pg-hub .btn-open:hover {
    box-shadow: 0 9px 22px var(--fg), inset 0 1px 0 rgba(255, 255, 255, .3);
    transform: translateY(-1px);
    filter: saturate(1.08);
}

.pg-hub .btn-open:disabled,
.pg-hub .btn-open[disabled] {
    opacity: .5;
    cursor: not-allowed;
    box-shadow: none;
    transform: none;
    filter: none;
}

/* secondary action next to the primary Open button (e.g. "Import from Genie") */
.pg-hub .btn-ghost {
    flex: 1 1 115px;
    font-family: var(--pg-sans);
    font-weight: 650;
    font-size: 11.5px;
    white-space: nowrap;
    border: 1px solid var(--fr);
    border-radius: 11px;
    padding: 10px 12px;
    background: #fff;
    color: var(--fam);
    cursor: pointer;
    transition: .15s;
}

.pg-hub .btn-ghost:hover {
    background: var(--fa);
}

/* ================= LOWER PANELS ================= */
.pg-hub .lower {
    display: grid;
    grid-template-columns: 1.6fr 1fr;
    gap: 18px;
    margin-top: 20px;
    align-items: start;
}

.pg-hub .panel {
    border: 1px solid var(--line);
    border-radius: var(--pg-radius);
    background: #fff;
    box-shadow: var(--pg-shadow-sm);
    overflow: hidden;
}

.pg-hub .panel-head {
    padding: 14px 17px;
    font-weight: 720;
    font-size: 13.5px;
    letter-spacing: -.2px;
    border-bottom: 1px solid var(--line-soft);
}

.pg-hub .act {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 11px 17px;
    border-bottom: 1px solid var(--line-soft);
    font-size: 12.2px;
}

.pg-hub .act .ad {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex: 0 0 auto;
}

.pg-hub .act .an {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--ink-2);
}

.pg-hub .act .at {
    margin-left: auto;
    font-size: 10.8px;
    color: var(--faint);
    white-space: nowrap;
    flex: 0 0 auto;
}

.pg-hub .view-all {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 13px 17px;
    font-size: 12px;
    font-weight: 680;
    color: var(--pp);
    cursor: pointer;
}

.pg-hub .view-all svg {
    width: 13px;
    height: 13px;
}

.pg-hub .links {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
}

.pg-hub .link-card {
    border: 1px solid var(--line);
    border-radius: 16px;
    background: #fff;
    padding: 15px;
    position: relative;
    cursor: pointer;
    box-shadow: var(--pg-shadow-sm);
    transition: .16s;
}

.pg-hub .link-card:hover {
    border-color: var(--pp-line);
    box-shadow: var(--pg-shadow);
    transform: translateY(-2px);
}

.pg-hub .link-card .li {
    width: 32px;
    height: 32px;
    border-radius: 10px;
    display: grid;
    place-items: center;
}

.pg-hub .link-card .li svg {
    width: 16px;
    height: 16px;
}

.pg-hub .link-card b {
    display: block;
    font-size: 12.8px;
    font-weight: 700;
    margin-top: 12px;
    letter-spacing: -.15px;
}

.pg-hub .link-card span {
    display: block;
    font-size: 11.2px;
    color: var(--muted);
    margin-top: 4px;
    line-height: 1.45;
}

.pg-hub .link-card .arw {
    position: absolute;
    top: 15px;
    right: 15px;
    color: var(--faint);
}

.pg-hub .link-card .arw svg {
    width: 14px;
    height: 14px;
}

.pg-hub .help {
    border: 1px solid var(--line);
    border-radius: 16px;
    background: #fff;
    box-shadow: var(--pg-shadow-sm);
    padding: 16px 17px;
    margin-top: 16px;
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: nowrap;
}

.pg-hub .help > div:first-child {
    min-width: 0;
}

.pg-hub .help b {
    display: block;
    font-size: 13.2px;
    font-weight: 720;
}

.pg-hub .help span {
    display: block;
    font-size: 11.5px;
    color: var(--muted);
    margin-top: 3px;
}

.pg-hub .btn-help {
    margin-top: 11px;
    font-family: var(--pg-sans);
    font-weight: 680;
    font-size: 12px;
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 8px 14px;
    background: #fff;
    color: var(--ink-2);
    cursor: pointer;
    transition: .15s;
}

.pg-hub .btn-help:hover {
    border-color: var(--pp-line);
    color: var(--pp);
}

.pg-hub .avs {
    margin-left: auto;
    display: flex;
    align-items: center;
    flex: 0 0 auto;
}

.pg-hub .avs i {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    border: 2.5px solid #fff;
    margin-left: -11px;
    font-style: normal;
    display: grid;
    place-items: center;
    color: #fff;
    font-weight: 700;
    font-size: 12px;
    box-shadow: 0 2px 6px rgba(27, 34, 51, .14);
}

@media (max-width: 1000px) {
    .pg-hub .lower {
        grid-template-columns: 1fr;
    }

    .pg-hub .lower .third {
        grid-column: auto;
    }
}

@media (max-width: 560px) {
    .pg-hub .links {
        grid-template-columns: 1fr;
    }

    /* Phone: a card is its artwork, its name and the things you can press. The kicker,
       the pitch paragraph and the capability bullets are read-only copy — five cards of
       it stacked is a screenful of scrolling before the first button, so it only appears
       from 561px up. The feature rows stay: they are buttons, and hiding them would be
       the only way to reach the Creative/Content Studio from a phone. */
    .pg-hub .kicker,
    .pg-hub .card-body p,
    .pg-hub .bul,
    .pg-hub .feats-lbl {
        display: none;
    }

    .pg-hub .card-body {
        padding-bottom: 0;
    }

    /* grid-template-rows: auto exists to keep the "Included features" label out of the
       equalised 1fr rows. With the label hidden the first button would inherit that auto
       row and come out shorter than its sibling, so drop the explicit row here. */
    .pg-hub .feats {
        margin-top: 12px;
        grid-template-rows: none;
    }

    /* every button clears the 44px touch target */
    .pg-hub .btn-open,
    .pg-hub .btn-ghost {
        padding-top: 13px;
        padding-bottom: 13px;
    }
}

/* ----------------------------------------------------------------------------
   PropGenie hub - dark mode.
   The 590 rules above are already fully tokenised, so re-pointing the tokens does
   almost all of the work; what follows the token block is the short list of
   places the design hardcoded #fff instead of going through one.
   ---------------------------------------------------------------------------- */
html[data-theme="dark"] .pg-hub {
    --ink: rgba(255, 255, 255, .92);
    --ink-2: rgba(255, 255, 255, .78);
    --muted: rgba(255, 255, 255, .62);
    --faint: rgba(255, 255, 255, .48);
    --line: rgba(255, 255, 255, .14);
    --line-soft: rgba(255, 255, 255, .08);
    --pg-shadow-sm: 0 1px 2px rgba(0, 0, 0, .55);
    --pg-shadow: 0 14px 36px rgba(0, 0, 0, .60);
}

/* The five categorical families keep their hues - they are semantic category
   colours, not brand chrome. What changes is only what has to: --fa/--fb/--fr are
   near-white tints that glare on a dark card, so they become low-alpha washes of
   the same hue, and --fam moves from the 600/700 step (picked for 5:1 on white)
   to the 400 step, which clears 6:1 on the dark Surface card. */
html[data-theme="dark"] .pg-hub .fam-web {
    --fam: #7FB0FB;
    --fa: rgba(37, 99, 235, .16);
    --fb: rgba(37, 99, 235, .28);
    --fr: rgba(127, 176, 251, .30);
    --fg: rgba(37, 99, 235, .45);
}

html[data-theme="dark"] .pg-hub .fam-dash {
    --fam: #45D19C;
    --fa: rgba(14, 159, 110, .16);
    --fb: rgba(14, 159, 110, .28);
    --fr: rgba(69, 209, 156, .30);
    --fg: rgba(14, 159, 110, .45);
}

html[data-theme="dark"] .pg-hub .fam-brand {
    --fam: #B69CFC;
    --fa: rgba(124, 58, 237, .18);
    --fb: rgba(124, 58, 237, .30);
    --fr: rgba(182, 156, 252, .30);
    --fg: rgba(124, 58, 237, .45);
}

html[data-theme="dark"] .pg-hub .fam-mkt {
    --fam: #F783B0;
    --fa: rgba(225, 29, 103, .16);
    --fb: rgba(225, 29, 103, .28);
    --fr: rgba(247, 131, 176, .30);
    --fg: rgba(225, 29, 103, .45);
}

html[data-theme="dark"] .pg-hub .fam-cert {
    --fam: #F5C24B;
    --fa: rgba(180, 83, 9, .18);
    --fb: rgba(180, 83, 9, .30);
    --fr: rgba(245, 194, 75, .30);
    --fg: rgba(180, 83, 9, .45);
}

/* The one thing the tokens cannot reach: every plate in this design is
   `background: #fff` rather than a var, so each has to be re-pointed by hand.
   This list must cover ALL of them - a plate left out keeps a white card while
   --ink flips to near-white, i.e. white text on white. There are exactly eight:
   grep this file for `^\.pg-hub` rules declaring a white background and the count
   must match the selectors below. .wallet-cta was the one that got missed, because
   it was added to the page after this block was written. */
html[data-theme="dark"] .pg-hub .card,
html[data-theme="dark"] .pg-hub .panel,
html[data-theme="dark"] .pg-hub .link-card,
html[data-theme="dark"] .pg-hub .help,
html[data-theme="dark"] .pg-hub .btn-ghost,
html[data-theme="dark"] .pg-hub .btn-help,
html[data-theme="dark"] .pg-hub .wallet-cta,
html[data-theme="dark"] .pg-hub .feat .fi {
    background: var(--mud-palette-surface);
}

/* Same gradient, but ending in the card it sits on instead of in white. */
html[data-theme="dark"] .pg-hub .art {
    background: linear-gradient(165deg, var(--fb) 0%, var(--fa) 55%, transparent 100%);
}

/* The avatar stack's separator ring is the card colour, not literally white. */
html[data-theme="dark"] .pg-hub .avs i {
    border-color: var(--mud-palette-surface);
}

/* ============================================================================
   New admin Home page - dashboard hero + launcher tile grid.
   adminhomemockupproptech.html is the approved visual spec for the TILES;
   01-gold-rail.html is the superseded Gold Rail reading of the same layout.

   THE HERO FOLLOWS A DIFFERENT SPEC: adminhomemockuppropgenie1.html, the PropGenie banner. It
   is a dark stage in BOTH modes because the 1600x900 raster at its centre is lit for a dark
   ground, so every colour inside .hero is a literal rather than a mode-flipping token - that is
   the reason for the pinning, not a carve-out from this pass.

   Every selector is scoped under .ah-home on purpose, exactly as .pg-hub above:
   the design uses very generic class names (.grid, .tile, .hero, .empty) that
   must never leak into the other ~85 pages.

   --card-radius is a scoped token rather than a MudTheme override, which would
   re-round every card, input and dialog in the admin.

   The five .dashboard-* classes reused here (.dashboard-greeting,
   .dashboard-greeting em, .dashboard-date, .dashboard-section-title,
   .dashboard-link-sm) are orphans from the deleted Branding page. Their base
   rules still apply; what follows only adds the sizing the mockup pins.
   ============================================================================ */
/* The home page's own token layer, declared on .ah-home exactly as --card-radius
   already was: these are surface treatments for one page, not palette slots, so
   they must not sit on :root where every other page would inherit them.

   The tile tokens alias --pa-* tokens that flip on their own, so the dark
   override below restates only the band - which is all of it, since the banner
   became one raster per mode.

   LANDMINE, and the reason the tiles no longer rest on an elevation at all:
   --mud-elevation-* comes from the single MudTheme.Shadows array and is
   byte-identical in both modes, so a shadow tuned for a white page reads as
   nothing against a dark one. The mockup solves it the way we now do - no resting
   shadow, a --border-soft hairline in both modes, and --pa-shadow (which IS
   per-mode) only on hover. */
.ah-home {
    --card-radius: var(--pa-radius);

    /* ---- HERO BAND ----------------------------------------------------------
       There is one raster per mode now, so the band follows the mode and EVERY colour inside
       .hero is a token declared here at its light value and restated dark below. Nothing in the
       band may be a literal in its own rule: a literal is a colour with only one mode, and the
       failure that produces - text invisible in exactly one of them - is silent.

       --ah-hero-stage is CHANNELS, not a colour. .hero and all four stops of .hero-scrim derive
       from this one value so the band and its scrim can never drift apart. Writing it as a hex or
       an rgb() breaks every rgb(var(--ah-hero-stage) / a) consumer, and custom-property
       substitution fails at computed-value time with NO console error - the band and the scrim
       simply stop painting and the raw art shows through the copy.

       --ah-hero-art is the raster's own backdrop, painted on .hero-bg. The svg is xMaxYMid meet,
       so the art NEVER fills the band: at 1200x500 it covers the right 74%, and under 860px it is
       letterboxed inside a 230px strip with the scrim switched off entirely. Whatever the art does
       not cover shows this colour, so matching it to the file makes art and surround one surface -
       which is what lets the stage be chosen for legibility instead of being pinned to the
       artwork.

       A flat fill can only hide that join if the FILE's outer edge is that same flat colour, and
       neither supplied render was: the dark one's lower-left smoke reached the frame edge at
       #4B4B5B, 59 levels off, which drew a hard vertical line down the right of the letterbox.
       Both files are therefore feathered into their own ground - 6% on the left, which is the edge
       that shows at any width down to 409px, and 2% top and bottom, which are the edges `meet`
       exposes below that. A replacement raster must be re-feathered the same way; sampling one
       edge pixel is what produced the bug, because no edge here has a single colour.

       GOLD IS NOT INK ON THE LIGHT BAND. The light raster's backdrop is a flat mid-grey (#818181)
       and the scrim lifts the copy zone to about #F2F2F5, where --pa-gold-fg measures 4.2-4.5:1 -
       at or under the 4.5:1 body-text bar - and champagne is 1.9:1. Small text in light mode is
       therefore --ah-hero-ink on a gold PLATE. Gold is spent only where it clears its own bar:
       the large accent headline, fills, and boundaries. */
    --ah-hero-stage: 247 247 250;
    --ah-hero-art: #818181;
    /* The WHOLE scrim, not just its alphas: the two modes need different stop POSITIONS, not only
       different opacities, and positions cannot be tokenised one at a time without four more
       tokens that only ever move together.

       The light band has two jobs that pull apart. Under the copy it must be an opaque plate,
       because the art there is mid-grey and anything bleeding through drags the plate down toward
       the ink. Over the genie it must be GONE - he is a render lit for his own ground, and white
       laid over him does not soften him, it desaturates him. There is room for both: the art box
       starts ~28% across a wide band (the svg is xMaxYMid meet, so it is contained and right-
       pinned), .hero-copy ends by ~42%, and the genie's outstretched hand does not begin until
       ~62%. So the plate holds through the copy and is fully clear before the art that matters.

       An earlier revision ran .9 at 52% and .34 at 70% and measurably washed him: 25% white on the
       costume, 32% on the wisp, 44% on the gold ribbon, sampled off a real render.

       Dark keeps the original shape. Its art is near-black, so bleed-through only darkens an
       already-dark ground and costs nothing.

       Stop percentages run along the 95deg gradient LINE, not the band's width, so they are close
       to but not exactly x-positions. Re-measure against a render; do not read them as x. */
    --ah-hero-scrim: linear-gradient(95deg,
        rgb(var(--ah-hero-stage) / 1) 0%,
        rgb(var(--ah-hero-stage) / .97) 26%,
        rgb(var(--ah-hero-stage) / .84) 42%,
        rgb(var(--ah-hero-stage) / .28) 54%,
        transparent 66%);
    --ah-hero-ink: #0E1524;
    --ah-hero-ink-dim: rgba(14, 21, 36, .78);
    /* The headline's gradient. Champagne over the pale band is 1.5:1, so light takes a deep-gold
       pair - 3.55:1 at its bright stop and 4.07:1 at its dark one, against a 30px display face's
       3:1. The bright stop is deepened from --pa-gold-ic rather than being it: that token's
       documented 3.56:1 is measured on WHITE, and this plate is the scrim's #EBEBEE, which costs
       enough to land it on 2.99:1. NOT a re-valued --pa-grad-gold - that token has four other
       consumers, one of them outside this page entirely. */
    --ah-hero-accent: linear-gradient(135deg, #9C6F1E, #8C6413);
    --ah-hero-sheen: rgba(176, 127, 34, .07);
    --ah-hero-ready-glow: 0 18px 46px -20px rgba(176, 127, 34, .45);
    /* Ghost CTA = the mockup's .hero .btn-ghost, a 13px bold label owing 4.5:1. Dark puts
       champagne on a white wash; light inverts the roles - dark ink on a GOLD wash, 13.5:1 -
       because no gold in the palette clears 4.5:1 at this size on the lifted band. The edge is
       the SOLID --pa-gold-ic: alpha cannot rescue this hue on a pale ground (see :root), and a
       boundary still owes 3:1. */
    --ah-cta-bg: rgba(176, 127, 34, .14);
    --ah-cta-fg: #0E1524;
    --ah-cta-border: var(--pa-gold-ic);
    --ah-cta-border-hover: var(--pa-gold-fg);

    /* ---- EYEBROW, STEP CHIPS, READINESS BAR ---------------------------------
       Light keeps gold on the eyebrow because it is the band's one brand accent, but only by
       putting it on a near-WHITE plate: --pa-gold-fg is 4.66:1 there and 4.04:1 on the gold wash
       the dark mode uses, so the plate is what buys the pass, not the hue.

       The chips' white-on-translucent-white fills do not merely soften on a pale ground, they
       INVERT - a 6% white lift over a near-white band is no shape at all. Light restates every
       one of them as translucent INK. */
    --ah-eyebrow-fg: var(--pa-gold-fg);
    --ah-eyebrow-bg: rgba(255, 255, 255, .72);
    --ah-eyebrow-line: var(--pa-gold-ic);
    /* Channels, because @keyframes sit outside the cascade: no [data-theme] selector can reach
       ah-pulse, but a var() inside it resolves against the animating element, which .ah-home does
       reach. All three stops must ride this token - the two alpha-0 ones never paint but they set
       what the pulse ramps TOWARD. */
    --ah-pulse-rgb: 176 127 34;
    --ah-step-fg: rgba(14, 21, 36, .80);
    --ah-step-bg: rgba(255, 255, 255, .72);
    --ah-step-line: rgba(16, 24, 40, .28);
    --ah-step-done-fg: #0E1524;
    --ah-step-done-bg: rgba(176, 127, 34, .16);
    --ah-step-done-line: rgba(176, 127, 34, .55);
    --ah-tick-line: rgba(14, 21, 36, .50);
    /* The done tick's disc cancels its own border, so the FILL is the only thing left drawing its
       edge. Champagne over the light done-chip is 1.2:1 and the disc dissolves; the deep-gold pair
       holds 2.8-3.9:1 against it, and carries white at 3.7-5.1:1. */
    --ah-tick-fill: linear-gradient(135deg, #9C6F1E, #8C6413);
    --ah-tick-ink: #FFFFFF;
    --ah-ready-fg: rgba(14, 21, 36, .76);
    /* A WHITE track, matching the chips beside it, not a dark one: the bar's state is carried by
       filled-vs-unfilled, and a dark track against a deep-gold fill is 2.6:1. Inverting the track
       puts that pair at 3.5:1 without touching the fill. */
    --ah-ready-track: rgba(255, 255, 255, .78);
    --ah-ready-track-line: rgba(16, 24, 40, .22);
    --ah-ready-fill: linear-gradient(135deg, #9C6F1E, #8C6413);

    /* ---- BANNER OVERLAY -----------------------------------------------------
       The fume is emissive and was lit for near-black. Mid-grey is the worst ground it could
       land on - champagne reads 2.5:1 there and the deep golds read 1.1:1, BELOW champagne,
       because that hue sits at almost exactly the grey's luminance. So light does not darken the
       magic, it brightens it and adds a dark halo to separate it, which is how a glow is drawn
       over a mid tone.

       The four <defs> gradients are deliberately NOT retinted: their ids go document-global the
       moment this renders in the admin shell, so a second set risks url(#id) resolving to the
       wrong one, and champagne over the art still reads. They get opacity instead. */
    --ah-rub-filter: contrast(1.06) saturate(1.12);
    --ah-spark: #4E5E85;
    --ah-spark-op: .42;
    --ah-spark-lit: #FFE9A8;
    --ah-star: #FFF8E4;
    --ah-star-glow: rgba(74, 52, 6, .5);
    --ah-ring: #FFFFFF;
    --ah-halo: drop-shadow(0 0 3px rgba(40, 28, 4, .55));
    --ah-fog-op: .26;
    --ah-fog-op-rub: .44;
    --ah-fog-op-still: .16;
    --ah-billow-op-still: .35;

    /* ---- TILES - the mockup's .tile / .tile-ico, lines 422-437 --------------
       All four alias --pa-* tokens that already flip, which is why the dark block
       below no longer restates them. --ah-card-bg is the mockup's --card gradient
       rather than the flat palette Surface; --ah-tile-chip is its radial gold wash
       over --card-flat. */
    --ah-card-bg: var(--pa-card);
    --ah-card-line: var(--pa-border-soft);
    --ah-card-line-hover: var(--pa-gold-border);
    --ah-tile-chip: radial-gradient(120% 120% at 30% 20%, var(--pa-gold-wash), transparent 70%), var(--pa-card-flat);
    --ah-tile-chip-line: var(--pa-gold-border);
}

/* The band's dark half, and the ONLY place dark is described. Custom properties only: a media
   query adds no specificity, so a layout declaration at this selector's (0,2,1) would outrank the
   860/960px rules at (0,2,0) at every width, which is the same trap the pair at the foot of this
   file already documents.

   Written as the dark OVERRIDE rather than a light one to match :root/html[data-theme="dark"]
   above. html:not([data-theme="dark"]) would be wrong for the light half either way: _Layout.cshtml
   emits themeMode as null with the flag off and a null Razor attribute is omitted entirely, so that
   selector would also match the flag-off document. */
html[data-theme="dark"] .ah-home {
    /* rgb(16,16,32) is propgenie-banner-dark.webp's own backdrop, measured, not chosen. It is NOT
       the old 14 14 26 - that matched the raster this one replaced, and leaving it would have put
       a 2-level step between the band and its own art.

       --ah-hero-art repeats the stage here rather than aliasing it: the two are equal in dark only
       because this raster happens to sit on the same ground the band wants. In light they differ
       by design, which is the case the token exists for. */
    --ah-hero-stage: 16 16 32;
    --ah-hero-art: #101020;
    --ah-hero-scrim: linear-gradient(95deg,
        rgb(var(--ah-hero-stage) / .96) 0%,
        rgb(var(--ah-hero-stage) / .88) 30%,
        rgb(var(--ah-hero-stage) / .5) 52%,
        rgb(var(--ah-hero-stage) / .1) 70%,
        transparent 84%);
    --ah-hero-ink: #FFFFFF;
    --ah-hero-ink-dim: rgba(226, 232, 246, .78);
    --ah-hero-accent: var(--pa-grad-gold);
    --ah-hero-sheen: rgba(255, 218, 114, .09);
    --ah-hero-ready-glow: 0 18px 46px -20px rgba(242, 205, 122, .45);
    /* #FFD983 on a white-.07 plate over this stage is 12.06:1. */
    --ah-cta-bg: rgba(255, 255, 255, .07);
    --ah-cta-fg: #FFD983;
    --ah-cta-border: rgba(242, 205, 122, .45);
    --ah-cta-border-hover: #FFD983;

    --ah-eyebrow-fg: #FFD983;
    --ah-eyebrow-bg: rgba(242, 205, 122, .15);
    --ah-eyebrow-line: rgba(242, 205, 122, .34);
    --ah-pulse-rgb: 242 205 122;
    --ah-step-fg: rgba(226, 232, 246, .76);
    --ah-step-bg: rgba(255, 255, 255, .06);
    --ah-step-line: rgba(255, 255, 255, .16);
    --ah-step-done-fg: #FFF;
    --ah-step-done-bg: rgba(242, 205, 122, .16);
    --ah-step-done-line: rgba(242, 205, 122, .42);
    --ah-tick-line: rgba(226, 232, 246, .45);
    --ah-tick-fill: var(--pa-grad-gold);
    --ah-tick-ink: var(--pa-on-gold);
    --ah-ready-fg: rgba(226, 232, 246, .70);
    --ah-ready-track: rgba(255, 255, 255, .1);
    --ah-ready-track-line: rgba(255, 255, 255, .18);
    --ah-ready-fill: var(--pa-grad-gold);

    --ah-rub-filter: brightness(1.06) saturate(1.08);
    --ah-spark: #C8D6F0;
    --ah-spark-op: .2;
    --ah-spark-lit: #FFD983;
    --ah-star: #FFF2CE;
    --ah-star-glow: rgba(255, 214, 120, .9);
    --ah-ring: #FFD983;
    --ah-halo: none;
    --ah-fog-op: .19;
    --ah-fog-op-rub: .36;
    --ah-fog-op-still: .12;
    /* Deliberately equal to its light value: the billow gradient is pale in both modes and lands
       on the art rather than under the copy, so it needs no per-mode alpha. It stays a token
       anyway - its three siblings above do move, and a lone literal among them is exactly the
       kind of thing a sweep for hardcoded colours skips. */
    --ah-billow-op-still: .35;
}

/* -- greeting row ---------------------------------------------------------- */
.ah-home .greetrow {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 16px;
    margin: 16px 0 20px;
    flex-wrap: wrap;
}

.ah-home .dashboard-greeting {
    font-size: 1.5rem;
    line-height: 1.334;
    margin: 0;
}

.ah-home .dashboard-greeting em {
    line-height: 1;
}

.ah-home .greet-sub {
    margin: 4px 0 0;
    font-size: 14px;
    color: var(--mud-palette-text-secondary);
}

.ah-home .dashboard-date {
    font-size: 0.75rem;
    color: var(--mud-palette-text-secondary);
}

/* -- hero band --------------------------------------------------------------
   The brand's own banner, composed with the genie right of centre and clear space on the left -
   so the copy sits ON the art rather than beside it, and a scrim keeps it legible. One raster per
   mode, so every colour in here reads a token from the pair above and NONE is a literal.
   --pa-shadow, not --mud-elevation-2: the elevation array is ONE array shared by both modes, so
   a light-tuned shadow reads as nothing on dark. */
.ah-home .hero {
    position: relative;
    display: flex;
    align-items: center;
    min-height: 500px;
    overflow: hidden;
    background: rgb(var(--ah-hero-stage));
    border: 1px solid var(--pa-gold-border);
    border-radius: var(--card-radius);
    box-shadow: var(--pa-shadow);
    margin-bottom: 32px;
    transition: border-color .4s ease, box-shadow .4s ease;
}

/* Server-rendered from the step count, never from JS. The glow is tokenised even though the
   border beside it already flips on its own: the two sit twelve lines apart on one element, and
   this rule only renders once all four setup steps are done, so a half-flipped pair would not
   show up in casual testing. */
.ah-home .hero.is-ready {
    border-color: var(--pa-gold-ic);
    box-shadow: var(--ah-hero-ready-glow);
}

/* The raster's own backdrop, under the art rather than beside it. The svg is xMaxYMid meet, so
   the art is contained and right-pinned and never covers the whole band - without this the stage
   shows through wherever it does not reach and the two grounds meet at a seam, worst below 860px
   where the art becomes a letterboxed strip and the scrim that would have hidden it is gone. */
.ah-home .hero-bg {
    position: absolute;
    inset: 0;
    z-index: 0;
    background: var(--ah-hero-art);
}

/* Which raster is showing, for the genie and the uploaded-banner placeholder alike. Opacity, not
   display:none: display:none would drop the outgoing image from the render tree, so every theme
   toggle would re-rasterise it. The swap itself is instant - nothing transitions opacity. The light
   rasters need no light-mode rule - the default opacity 1 is already correct, including on a
   document with no data-theme at all. */
.ah-home .genie-dark,
.ah-home .placeholder-dark {
    opacity: 0;
}

html[data-theme="dark"] .ah-home .genie-dark,
html[data-theme="dark"] .ah-home .placeholder-dark {
    opacity: 1;
}

html[data-theme="dark"] .ah-home .genie-light,
html[data-theme="dark"] .ah-home .placeholder-light {
    opacity: 0;
}

/* The svg's inline transform is written by admin-home-banner.js (parallax), so nothing
   Razor-bound may ever live on this element. */
.ah-home .hero-bg svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    transition: transform .35s cubic-bezier(.2, .7, .3, 1);
}

/* A build that uploaded its own banner paints the Marketing Hub placeholder here instead of the
   genie, and it is the only thing in the band: AdminHome renders no copy and no scrim on that path.
   This rule is therefore the imgs' whole layout, and it has to be - .hero-bg is an absolute
   inset:0 box that sizes no child, so without it each image lands at its intrinsic size in the
   top-left corner. Both rasters share it and stack exactly; the pair above picks which one shows.

   cover, and it crops nothing: the band takes the art's own shape on this path (below), so cover
   only absorbs sub-pixel rounding. contain would letterbox instead, and the light raster's edges
   are a grey ramp no flat --ah-hero-art can meet without a visible seam.

   Nothing here rounds the image. .hero already carries the radius and overflow:hidden, so the img
   is clipped by the band and the corner treatment stays defined in exactly one place - which is
   why the rounded corners baked into the supplied screenshots were filled with ground, not kept. */
.ah-home .hero-custom-art {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
}

/* The band takes the placeholder's own 1582x642 instead of the PropGenie band's 500px min-height:
   at a fixed height every band narrower than 1232px - every viewport under 1520px - crops the
   art's own headline, and every wider one crops its top label. Nothing in the band is in flow on
   this path, so the ratio alone sets the height. A replacement raster must keep this ratio, or
   change it here; AdminHomeBannerAssetsTest pins the two together. */
.ah-home .hero.has-own-banner {
    min-height: 0;
    aspect-ratio: 1582 / 642;
}

/* Stays an overlay below 860px, where .hero-bg otherwise becomes a fixed 230px strip under the
   copy. There is no copy on this path, and a fixed strip height would crop the art again. (0,4,0),
   so it beats that block's (0,2,0) regardless of source order. */
.ah-home .hero.has-own-banner .hero-bg {
    position: absolute;
    height: auto;
}

.ah-home .hero-scrim {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: var(--ah-hero-scrim);
}

/* Slow gold sheen. z-index 2, UNDER .hero-copy's 3: the mockup puts it above the text, where a
   translucent wash sweeps across live copy every 9s and costs every string contrast - and since
   reduced-motion users lose it entirely, they would be the best-served group. */
.ah-home .hero::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 2;
    background: linear-gradient(105deg, transparent 40%, var(--ah-hero-sheen) 48%, transparent 56%);
    animation: ah-sheen 9s ease-in-out infinite;
}

@keyframes ah-sheen {
    0%, 70% { transform: translateX(-65%); }
    100% { transform: translateX(65%); }
}

/* Off on this path along with the copy and scrim: the placeholder is finished art, headline
   included, and the PropGenie band's chrome is not part of it. Killed by specificity ((0,3,1) over
   the base rule's (0,2,1)), so it does not depend on source order. */
.ah-home .hero.has-own-banner::after {
    content: none;
}

.ah-home .hero-copy {
    position: relative;
    z-index: 3;
    padding: 34px 38px;
    max-width: min(580px, 56%);
}

/* Routed through --ah-hero-ink rather than inheriting the page's text tokens. Both follow the
   mode now, so that is no longer the reason - the reason is that this sits on ART with its own
   ground (mid-grey light, near-black dark), and --mud-palette-text-* was measured against the
   page surface, not against either of those. */
.ah-home .hero-head {
    margin: 0;
    font-size: 24px;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.25;
    color: var(--ah-hero-ink);
}

/* The band flips again, so the mockup's deep-gold gradient is restored as the LIGHT half of
   --ah-hero-accent - it was only ever wrong because there was no light band to put it on. The two
   are not interchangeable: champagne runs 14.16 -> 7.36:1 on the dark stage and 1.5:1 on the pale
   one, deep gold the reverse. */
.ah-home .hero-head .accent {
    display: block;
    font-family: 'Cormorant Garamond', serif;
    font-style: italic;
    font-weight: 600;
    font-size: 30px;
    line-height: 1.2;
    margin-top: 2px;
    background: var(--ah-hero-accent);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.ah-home .hero-copy p {
    margin: 12px 0 20px;
    font-size: 13px;
    line-height: 1.6;
    color: var(--ah-hero-ink-dim);
    max-width: 52ch;
}

/* -- eyebrow, step chips, readiness bar -------------------------------------
   The plate under all of these is the stage, so all of them move with it. */
.ah-home .eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ah-eyebrow-fg);
    background: var(--ah-eyebrow-bg);
    border: 1px solid var(--ah-eyebrow-line);
    padding: 4px 11px;
    border-radius: 999px;
    margin-bottom: 14px;
}

.ah-home .eyebrow .dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgb(var(--ah-pulse-rgb));
    flex: none;
    animation: ah-pulse 2.2s ease-out infinite;
}

/* The one keyframe in this file carrying a COLOUR rather than an opacity, and keyframes sit
   outside the cascade - html[data-theme="..."] @keyframes is invalid and dropped without a word.
   A var() inside one resolves against the animating element instead, which is how --ah-pulse-rgb
   reaches it. The two alpha-0 stops ride the token as well: they never paint, but they are what
   the ring ramps toward, and champagne leaks back in if they are left literal. */
@keyframes ah-pulse {
    0% { box-shadow: 0 0 0 0 rgb(var(--ah-pulse-rgb) / .6); }
    70% { box-shadow: 0 0 0 7px rgb(var(--ah-pulse-rgb) / 0); }
    100% { box-shadow: 0 0 0 0 rgb(var(--ah-pulse-rgb) / 0); }
}

.ah-home .steps {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 16px 0 14px;
    padding: 0;
    list-style: none;
}

/* No :hover and no transition: these are li readouts, not the mockup's buttons, and a hover
   response on a non-interactive element reads as a control that has broken. */
.ah-home .step {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 13px 6px 7px;
    border-radius: 999px;
    font-size: 12.5px;
    font-weight: 500;
    color: var(--ah-step-fg);
    background: var(--ah-step-bg);
    border: 1px solid var(--ah-step-line);
}

.ah-home .step.is-done {
    color: var(--ah-step-done-fg);
    background: var(--ah-step-done-bg);
    border-color: var(--ah-step-done-line);
}

/* THE TICK IS LOAD-BEARING, not decoration: done and not-done differ by only 1.23:1 in fill, so
   the hollow-ring / filled-check SHAPE change is what carries the distinction without relying on
   colour. Do not simplify it away. */
.ah-home .step .tick {
    width: 19px;
    height: 19px;
    border-radius: 50%;
    flex: none;
    display: grid;
    place-items: center;
    border: 1.5px solid var(--ah-tick-line);
}

.ah-home .step .tick .mud-icon-root {
    font-size: 13px;
    color: transparent;
}

/* border-color:transparent is a COLOUR decision, not a reset: it hands the job of drawing this
   disc's edge to the fill alone. That only holds while the fill contrasts with the chip under it,
   which is why --ah-tick-fill is deep gold in light mode and champagne in dark - swap either and
   the done tick dissolves into its own chip. */
.ah-home .step.is-done .tick {
    background: var(--ah-tick-fill);
    border-color: transparent;
}

.ah-home .step.is-done .tick .mud-icon-root {
    color: var(--ah-tick-ink);
}

.ah-home .ready {
    display: flex;
    align-items: center;
    gap: 11px;
    margin-bottom: 20px;
    font-size: 11px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ah-ready-fg);
}

.ah-home .ready .bar {
    flex: 0 1 230px;
    height: 6px;
    border-radius: 99px;
    background: var(--ah-ready-track);
    border: 1px solid var(--ah-ready-track-line);
    overflow: hidden;
}

.ah-home .ready .bar i {
    display: block;
    height: 100%;
    background: var(--ah-ready-fill);
}

.ah-home .ready > span:last-child {
    white-space: nowrap;
}

.ah-home .cta-row {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
}

/* THE HERO'S PRIMARY CTA - Gold Rail's gradient fill with NAVY text, and the one
   element in the design that is byte-identical in light and dark: the fill is
   bright enough that --pa-on-gold clears 6.4:1 even at the gradient's darkest
   stop, so there is nothing for a dark override to improve.
   The radius is --pa-radius-sm (10px), the mockup's .btn value at line 309, NOT the
   999px pill the superseded Gold Rail reading used. Both hero CTAs carry it so they
   read as a matched pair. Scoped to .cta-row so it cannot reach any other Filled
   Primary button - every other filled button in the admin keeps MudBlazor's 4px - and
   skipped while disabled so the PropGenie-not-ready state still reads as unavailable.
   The hairline is --pa-gold-dim rather than the mode-dependent --pa-gold-border,
   because a mode-dependent edge would break the "identical in both modes" claim. */
/* Shape is unconditional; only the gold FILL is state-dependent. Keeping the radius in
   the :not(:disabled) rule below would leave a disabled CTA at MudBlazor's 4px, so the
   two hero buttons would stop matching precisely when one of them is unavailable. */
.ah-home .cta-row .mud-button-root {
    border-radius: var(--pa-radius-sm);
}

.ah-home .cta-row .mud-button-filled-primary:not(:disabled):not([disabled]) {
    background: var(--pa-grad-gold);
    color: var(--pa-on-gold);
    border: 1px solid var(--pa-gold-dim);
}

.ah-home .cta-row .mud-button-filled-primary:not(:disabled):not([disabled]):hover {
    filter: brightness(1.05);
}

/* Secondary CTA, the mockup's .hero .btn-ghost: a SOLID plate with a gold hairline, so it reads
   as a control rather than dissolving into the artwork the way a plain Variant.Outlined MudButton
   does - which is the whole reason this rule exists. The class name is a misnomer in both modes:
   the plate is a white wash on the dark stage and a gold one on the pale band, never white.

   These carry !important, so they beat MudBlazor's disabled styling too - this button looks
   enabled while disabled today, in both modes. Changing the tokens does not change that; removing
   the !important would. */
.ah-home .ah-cta-white {
    background: var(--ah-cta-bg) !important;
    color: var(--ah-cta-fg) !important;
    border: 1px solid var(--ah-cta-border);
    /* Matches the primary CTA beside it. Without this the button falls back to
       MudBlazor's 4px default and the pair reads as two different components. */
    border-radius: var(--pa-radius-sm);
}

/* --ah-cta-border-hover, not --pa-gold-ic directly: the ghost CTA sits on the stage, not on a
   page surface, so its hover edge is measured with the rest of the band. Light happens to resolve
   it to --pa-gold-fg, a step DARKER than the resting --pa-gold-ic edge; dark goes brighter. Both
   are "more contrast on hover", in opposite directions. */
.ah-home .ah-cta-white:hover {
    border-color: var(--ah-cta-border-hover);
}

/* -- banner layers ----------------------------------------------------------
   admin-home-banner.js toggles `rubbing` on .hero-bg, NOT on .hero: .hero-bg's Razor class is a
   static literal so the renderer never writes it, while .hero's carries the server-rendered
   is-ready. A changed class attribute is a whole-value setAttribute, so a JS-added token on an
   interpolated class would be deleted on the next render. Every one of the mockup's
   `.hero.rubbing` selectors targets an element inside .hero-bg, so this rehoming is lossless.

   The paint servers themselves (stroke/fill/filter = url(#...)) are SVG presentation attributes
   in AdminHomeBanner.razor, not CSS: a bare fragment in an EXTERNAL stylesheet is a relative URL
   under <base href> and fails SILENTLY. Only animation, opacity and transform live here. */
.ah-home .genie-img {
    transition: filter .5s ease;
}

/* A 6% brightness lift is a multiply, so it reads as a surge against near-black and as almost
   nothing against the light raster's mid-grey, where every channel is already near the middle of
   the range. Light carries the same beat with contrast and saturation instead. */
.ah-home .hero-bg.rubbing .genie-img {
    filter: var(--ah-rub-filter);
}

/* No cursor:pointer: the subtree is aria-hidden, so it must not advertise itself as a control. */
.ah-home .lamp-hit {
    fill: transparent;
}

.ah-home .ribbon {
    fill: none;
    stroke-linecap: round;
    stroke-dasharray: 90 340;
    animation: ah-flow 7s linear infinite;
}

@keyframes ah-flow {
    to { stroke-dashoffset: -430; }
}

.ah-home .hero-bg.rubbing .ribbon {
    animation-duration: 2.4s;
}

.ah-home .fog {
    opacity: var(--ah-fog-op);
    transform-box: fill-box;
    transform-origin: center;
    animation: ah-drift var(--t, 36s) ease-in-out infinite alternate;
}

@keyframes ah-drift {
    from { transform: translate(0, 0) scale(1); }
    to { transform: translate(var(--dx, 60px), -22px) scale(1.14); }
}

.ah-home .hero-bg.rubbing .fog {
    opacity: var(--ah-fog-op-rub);
}

.ah-home .billow {
    opacity: 0;
    transform-box: fill-box;
    transform-origin: center;
    animation: ah-rise var(--d, 6s) ease-out infinite;
}

@keyframes ah-rise {
    0% { opacity: 0; transform: translate(0, 0) scale(.28); }
    18% { opacity: .8; }
    60% { opacity: .45; }
    100% { opacity: 0; transform: translate(var(--dx, 40px), var(--dy, -190px)) scale(2.6); }
}

.ah-home .hero-bg.rubbing .billow {
    animation-duration: calc(var(--d, 6s) * .45);
}

/* The second plume is parked until the lamp is rubbed: paused at 0%, which is opacity 0. */
.ah-home .billow.extra {
    animation-play-state: paused;
}

.ah-home .ribbon.extra {
    opacity: 0;
    animation-play-state: paused;
    transition: opacity .45s ease;
}

.ah-home .hero-bg.rubbing .billow.extra {
    animation-play-state: running;
    animation-duration: var(--d, 4s);
}

.ah-home .hero-bg.rubbing .ribbon.extra {
    opacity: 1;
    animation-play-state: running;
    animation-duration: 2.4s;
}

/* One spark per setup step, lit as that step's module comes on - this is the artwork's copy of
   the readiness count, so the lit/unlit difference has to survive both grounds. On the light
   raster's mid-grey neither hue has much room, so light widens the gap with opacity and a dark
   halo rather than with colour alone. */
.ah-home .spark {
    fill: var(--ah-spark);
    opacity: var(--ah-spark-op);
    transition: opacity .4s ease, fill .4s ease;
}

.ah-home .wish.lit .spark {
    fill: var(--ah-spark-lit);
    opacity: .95;
    filter: var(--ah-halo);
    animation: ah-twinkle 2.8s ease-in-out infinite;
}

@keyframes ah-twinkle {
    0%, 100% { opacity: .55; }
    50% { opacity: 1; }
}

.ah-home .emblem-glow {
    transform-box: fill-box;
    transform-origin: center;
    animation: ah-emblem-pulse 3.4s ease-in-out infinite;
}

@keyframes ah-emblem-pulse {
    0%, 100% { opacity: .34; transform: scale(.9); }
    50% { opacity: .6; transform: scale(1.06); }
}

.ah-home .hero-bg.rubbing .emblem-glow {
    opacity: .9;
    animation-duration: 1.1s;
}

/* The drop-shadow inverts with the mode: on the dark stage it is a gold BLOOM around a near-white
   star, on the pale one a dark halo that separates the same star from a mid-grey ground. An
   additive bloom over light art is invisible. */
.ah-home .magic-star {
    fill: var(--ah-star);
    opacity: 0;
    transform-box: fill-box;
    transform-origin: center;
    filter: drop-shadow(0 0 6px var(--ah-star-glow));
}

.ah-home .hero-bg.rubbing .magic-star {
    animation: ah-pop 1.5s ease-out infinite;
}

@keyframes ah-pop {
    0% { opacity: 0; transform: scale(.2) rotate(0deg); }
    35% { opacity: 1; transform: scale(1.15) rotate(40deg); }
    100% { opacity: 0; transform: scale(.45) rotate(90deg); }
}

.ah-home .spark-ring {
    fill: none;
    stroke: var(--ah-ring);
    stroke-width: 1.4;
    opacity: 0;
    transform-box: fill-box;
    transform-origin: center;
}

.ah-home .hero-bg.rubbing .wish.lit .spark-ring {
    animation: ah-spark-pop 1.1s ease-out;
}

@keyframes ah-spark-pop {
    0% { opacity: .7; transform: scale(.4); }
    100% { opacity: 0; transform: scale(3); }
}

/* The scene still reads, it just holds still. filter:none goes further than the mockup's
   animation:none - without it the five feTurbulence/feDisplacementMap chains are still evaluated
   every paint, just statically, on exactly the low-end machines most likely to set this. */
@media (prefers-reduced-motion: reduce) {
    .ah-home .hero::after {
        display: none;
    }

    .ah-home .eyebrow .dot,
    .ah-home .ribbon,
    .ah-home .billow,
    .ah-home .fog,
    .ah-home .emblem-glow,
    .ah-home .magic-star,
    .ah-home .wish .spark,
    .ah-home .spark-ring {
        animation: none !important;
    }

    /* The fog's third opacity site and the billow's second, both far enough from the others to be
       missed by a visual sweep. filter:none also strips the displacement, so the fog here is plain
       ellipses rather than tendrils and takes its own alpha rather than a copy of the animated
       one. The billow's value is deliberately equal in both modes - see the token. */
    .ah-home .fog {
        filter: none;
        opacity: var(--ah-fog-op-still);
    }

    .ah-home .billow {
        opacity: var(--ah-billow-op-still);
    }

    /* The second plume is normally hidden by being PAUSED at its 0% keyframe, so killing the
       animation above would otherwise reveal it - the rub-only fume would sit there permanently. */
    .ah-home .billow.extra,
    .ah-home .ribbon.extra {
        opacity: 0;
    }

    .ah-home .hero-bg svg {
        transition: none;
    }
}

/* Visually-hidden text, for the step chips' enabled/not-enabled state. Neither site.css nor
   MudBlazor 7.16.0 ships a utility for this. */
.ah-home .ah-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* -- tile grid ------------------------------------------------------------- */
.ah-home .sectionhead {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 16px;
    margin: 0 0 16px;
}

/* !important is required to win against .dashboard-section-title's own
   font-weight: 700 !important in the orphaned block above. */
.ah-home .dashboard-section-title {
    margin: 0;
    font-size: 1.25rem;
    font-weight: 500 !important;
    line-height: 1.6;
}

.ah-home .dashboard-link-sm {
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--mud-palette-primary);
}

.ah-home .dashboard-link-sm:hover {
    text-decoration: underline;
}

/* 12-col track so a partial last row can widen to stay flush with the row
   above. A row of k tiles gives each tile span 12/k - 4 up = 3, 3 up = 4,
   2 up = 6, 1 up = 12. --span-lg is set per tile from the VISIBLE count
   (AdminHomeTiles.GridColumnSpanFor), so gated tiles reflow correctly. */
.ah-home .grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 18px;
}

/* The mockup's .tile (lines 422-427) verbatim, with two additions it does not need
   and we do: height:100% so a short tile still fills its grid row, and the
   --span-lg reflow. Centred, not left-aligned: the icon chip, the heading, the copy
   and the arrow all sit on the tile's axis now. */
.ah-home .tile {
    grid-column: span 3;
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    height: 100%;
    padding: 22px 18px 18px;
    background: var(--ah-card-bg);
    border: 1px solid var(--ah-card-line);
    border-radius: var(--card-radius);
    overflow: hidden;
    transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}

@media (min-width: 1101px) {
    .ah-home .tile {
        grid-column: span var(--span-lg, 3);
    }
}

/* Three things move together on hover and each carries the state in a mode where
   the others are weak: the lift, the border stepping from --border-soft to
   --border-gold, and --pa-shadow - which, unlike --mud-elevation-*, is a separate
   value per mode (a warm bronze cast in light, a gold bloom in dark). */
.ah-home .tile:hover {
    transform: translateY(-2px);
    border-color: var(--ah-card-line-hover);
    box-shadow: var(--pa-shadow);
}

/* The mockup marks keyboard focus with the border alone. That is a 3.56:1 hairline
   against the card and it is the ONLY indicator, so the outline stays on top of it:
   MudBlazor's defaults do not cover a custom clickable card and the project has no
   focus-ring convention to inherit. */
.ah-home .tile:has(.tile-link:focus-visible) {
    border-color: var(--pa-gold-ic);
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

/* Whole-tile target via a stretched real anchor, so keyboard and screen readers
   get link semantics. border-radius:inherit keeps the ring on the card shape. */
.ah-home .tile-link {
    position: absolute;
    inset: 0;
    border-radius: inherit;
}

.ah-home .tile-ico {
    position: relative;
    width: 44px;
    height: 44px;
    border-radius: 13px;
    display: grid;
    place-items: center;
    background: var(--ah-tile-chip);
    border: 1px solid var(--ah-tile-chip-line);
    margin-bottom: 14px;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.ah-home .tile:hover .tile-ico {
    border-color: var(--pa-gold-ic);
    box-shadow: 0 0 0 3px var(--pa-gold-wash);
}

/* THE GLYPH IS A FLAT --pa-gold-ic, NOT the mockup's gold gradient, and that is a
   technique limit rather than a shortcut. The mockup's icons are Material Symbols
   FONT ligatures, so it paints them with background-image + background-clip:text;
   ours are MudBlazor <MudIcon> SVGs (.mud-icon-root.mud-svg-icon { fill:currentColor }),
   and background-clip:text has no effect on an SVG's fill. The alternatives all cost
   markup we do not own: an SVG <linearGradient> paint server needs a <defs> block on
   the page and fill:url(#id), which would mean either duplicating the defs in every
   tile or editing AdminHome.razor. --pa-gold-ic is the mockup's own token for icon
   glyphs and the fallback the brief sanctions; the gradient still appears on this
   tile, in the chip's radial --pa-gold-wash behind the glyph. */
.ah-home .tile-ico .mud-icon-root {
    font-size: 23px;
    color: var(--pa-gold-ic);
}

.ah-home .tile h3 {
    margin: 0 0 5px;
    font-size: 15px;
    font-weight: 600;
    letter-spacing: -0.01em;
    line-height: 1.4;
}

.ah-home .tile p {
    margin: 0;
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--mud-palette-text-secondary);
}

/* margin-top:auto pins the footer to the bottom of the tallest tile in the row, so
   a one-line description and a three-line one still line their arrows up. */
.ah-home .tile-foot {
    display: flex;
    align-items: center;
    justify-content: center;
    margin-top: auto;
    padding-top: 14px;
}

.ah-home .tile-arrow {
    font-size: 19px;
    color: var(--pa-gold-ic);
    transition: transform 0.15s ease;
}

.ah-home .tile:hover .tile-arrow {
    transform: translateX(3px);
}

/* Rendered only when zero tiles survive gating, so it is always visible when
   present - unlike the mockup, where JS toggles it. */
.ah-home .empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 48px;
    background: var(--ah-card-bg);
    border: 1px solid var(--ah-card-line);
    box-shadow: none;
    border-radius: var(--card-radius);
    text-align: center;
}

/* --pa-gold-ic-soft, not text-disabled: the mockup's empty state keeps the gold
   family even when there is nothing to launch. */
.ah-home .empty .mud-icon-root {
    font-size: 46px;
    color: var(--pa-gold-ic-soft);
    margin-bottom: 12px;
}

.ah-home .empty h4 {
    margin: 0 0 4px;
    font-size: 16px;
    font-weight: 600;
    color: var(--mud-palette-text-primary);
}

.ah-home .empty p {
    margin: 0;
    font-size: 13px;
    color: var(--mud-palette-text-secondary);
}

/* -- responsive ------------------------------------------------------------ */
@media (max-width: 1100px) {
    .ah-home .tile {
        grid-column: span 6;
    }
}

/* Narrow: the genie would sit under the text, so the banner becomes a strip BENEATH the copy
   and the scrim comes off - .hero-bg stops being an absolute overlay and becomes a flex item. */
@media (max-width: 860px) {
    .ah-home .hero {
        flex-direction: column;
        min-height: 0;
        /* Not inherited from the base rule: once the axis flips, its align-items:center is
           CROSS-axis and therefore horizontal, which shrink-wraps .hero-copy to the width of its
           widest chip row and floats it off the left edge. .hero-bg escapes only because it pins
           width:100%. */
        align-items: stretch;
    }

    .ah-home .hero-bg {
        position: relative;
        order: 2;
        height: 230px;
        width: 100%;
    }

    .ah-home .hero-scrim {
        display: none;
    }

    .ah-home .hero-copy {
        order: 1;
        max-width: none;
        padding: 24px 22px 18px;
    }
}

/* Explicitly bounded to 861-960px. Both blocks select .ah-home .hero-copy at the same
   specificity and a media query adds none, so an unbounded max-width:960px sitting after the
   860px block would win for the whole range below it and silently kill the narrow padding. */
@media (max-width: 960px) and (min-width: 861px) {
    .ah-home .hero-copy {
        padding: 26px 26px 12px;
    }
}

/* 640px is the single phone breakpoint for this page: the banner scales its type and goes
   full-width CTA here, and the tile grid already did. Only the greeting row still keeps
   MudBlazor's 599px sm below, which is where it was tuned. */
@media (max-width: 640px) {
    .ah-home .grid {
        gap: 14px;
    }

    .ah-home .tile {
        grid-column: span 12;
    }

    .ah-home .hero-copy {
        padding: 20px 18px 8px;
    }

    .ah-home .hero-head {
        font-size: 21px;
    }

    .ah-home .hero-head .accent {
        font-size: 25px;
    }

    .ah-home .hero-copy p {
        font-size: 12.5px;
        margin: 10px 0 16px;
    }

    .ah-home .eyebrow {
        margin-bottom: 12px;
    }

    .ah-home .steps {
        gap: 6px;
        margin: 14px 0 12px;
    }

    .ah-home .step {
        font-size: 12px;
        padding: 5px 12px 5px 6px;
        gap: 6px;
    }

    .ah-home .step .tick {
        width: 17px;
        height: 17px;
    }

    .ah-home .ready {
        gap: 9px;
        margin-bottom: 16px;
    }

    .ah-home .ready .bar {
        flex: 1 1 auto;
    }

    .ah-home .cta-row .mud-button-root {
        width: 100%;
    }
}

@media (max-width: 599px) {
    .ah-home .greetrow {
        margin-bottom: 16px;
    }

    .ah-home .dashboard-greeting {
        font-size: 1.25rem;
    }

    .ah-home .dashboard-greeting em {
        font-size: 26px;
    }
}

/* ============================================================================
   Drawer: nav rows, gold icons, active rail, grouped section headers.
   adminhomemockupproptech.html lines 172-200 (.nav-link / .nav-section .label /
   .nav-children / .menu-label-count) is the visual spec. The colours here are all
   palette or --pa-* variables, so the drawer follows the theme with no dark
   override.

   SCOPING, two layers of it. Everything colour-bearing is gated behind
   html[data-theme], which _Layout.cshtml only emits when
   enable_new_admin_home_page is on - and that is the same flag that picks
   NavMenuGrouped over the legacy NavMenu in LoggedInLayout.razor:66. So a
   flag-off instance renders the legacy NavMenu with no data-theme on <html>, none
   of these rules match, and the drawer is byte-identical to stock MudBlazor.
   The second layer is the .mudblazor-navmenu class, shared by both menus, which
   keeps the rules off MudNavMenu instances anywhere else in the admin.

   The one genuinely new nav primitive here: MudBlazor has no non-collapsible
   section header, only MudNavGroup, which collapses. Safe as a descendant
   wrapper: every .mud-navmenu rule in MudBlazor 7.16.0 is a descendant selector,
   not a direct-child one, so putting links inside a .nav-section div does not
   detach them from their own styling.
   ============================================================================ */

/* 8px of inline padding on the LIST, not margin on the links. MudNavLink is
   width:100%, so a margin would push 16px of it past the drawer's edge; padding on
   the container shrinks the links' containing block instead and shifts every
   nesting level by the same 8px, which is what keeps the indentation steps intact. */
html[data-theme] .mudblazor-navmenu {
    padding-inline: 8px;
}

/* .nav-link, lines 175-180. Only padding-INLINE is left at MudBlazor's own values
   (16px top-level, 36px inside a MudNavGroup) - that axis is what encodes the
   nesting depth and the mockup's flat 12px would erase it. padding-block is free to
   take the mockup's 9px, and does, together with its 1.3 line-height. */
html[data-theme] .mudblazor-navmenu .mud-nav-link {
    border-radius: var(--pa-radius-sm);
    color: var(--mud-palette-text-secondary);
    font-size: 14px;
    font-weight: 500;
    line-height: 1.3;
    padding-block: 9px;
}

/* THE ASK: resting nav icons are gold, not grey. The palette already carries most
   of this - DrawerIcon is --gold-ic-soft in AdminTheme.cs, and MudBlazor paints
   .mud-nav-link-icon from it - so this rule exists for the transition and to beat
   MudBlazor's own (0,4,0) .mud-nav-link-icon-default selector when a variant class
   would otherwise win. */
html[data-theme] .mudblazor-navmenu .mud-nav-link .mud-nav-link-icon {
    /* Line 181's 19px. MudNavLink hard-codes its MudIcon at Size.Medium, so without
       this the glyph is MudBlazor's 1.5rem; .mud-icon-root is width/height 1em, so
       font-size is what sizes an SVG icon. */
    font-size: 19px;
    color: var(--pa-gold-ic-soft);
    transition: color 0.18s ease;
}

/* Hover steps the glyph up to the full --gold-ic and the plate to --card-flat,
   replacing MudBlazor's grey --mud-palette-action-default-hover. */
html[data-theme] .mudblazor-navmenu .mud-nav-link:hover:not(.mud-nav-link-disabled) {
    background-color: var(--pa-card-flat);
    color: var(--mud-palette-text-primary);
}

html[data-theme] .mudblazor-navmenu .mud-nav-link:hover:not(.mud-nav-link-disabled) .mud-nav-link-icon {
    color: var(--pa-gold-ic);
}

/* MudBlazor's only focus treatment for these rows is a background tint off
   action-default-hover, which composites to 1.13:1 here - a keyboard user tabbing the
   drawer gets no usable cue across ~84 links. The offset is negative so the ring sits
   inside the 10px row radius instead of fighting the 2px inset active rail. */
html[data-theme] .mudblazor-navmenu .mud-nav-link:focus-visible {
    outline: 2px solid var(--pa-gold-ic);
    outline-offset: -2px;
}

/* MudNavGroup's chevron is drawn from --mud-palette-drawer-icon too, which is now
   soft gold. The mockup keeps it in the dim text family so it does not compete with
   the row's own icon. */
html[data-theme] .mudblazor-navmenu .mud-nav-link .mud-nav-link-expand-icon {
    font-size: 18px;
    /* An icon, so the bar is 3:1 - which the disabled slot misses (2.97:1) on the lower
       half of the drawer gradient where most groups actually sit. */
    color: var(--mud-palette-text-secondary);
}

/* The active row. MudBlazor's own Color.Default rule paints the whole active label
   with --mud-palette-primary; the mockup keeps the LABEL in the page's --text and
   lets gold carry the state through the rail, the icon and the wash instead - which
   is also what keeps it legible, since gold-as-a-label is 3.56:1 at best.

   font-weight needs !important to beat MudBlazor's own
   `.mud-nav-link.active { font-weight:500 !important }`.

   The rail is an inset box-shadow rather than a border-inline-start because
   MudBlazor sets padding-inline-start per nesting level (16px top-level, 36px
   inside a MudNavGroup); a real border would eat 2px off whichever level it was
   tuned against and shift every row's text. */
html[data-theme] .mudblazor-navmenu .mud-nav-link.active:not(.mud-nav-link-disabled) {
    color: var(--pa-nav-active-text);
    background: var(--pa-nav-active-wash);
    font-weight: 600 !important;
    box-shadow: inset 2px 0 0 0 var(--pa-gold-ic);
}

/* The active row has to restate its own wash for the interaction states, and that is
   a specificity fight rather than a style choice. MudBlazor ships
   `.mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(...):hover:not(...)`
   and a matching :focus-visible/:active pair, both (0,6,0) and both setting the
   background-COLOR longhand - which outranks the (0,5,1) `background` shorthand
   above for that one longhand, leaving grey showing under the gold gradient the
   moment the current row is hovered or focused. These selectors are (0,6,1). */
html[data-theme] .mudblazor-navmenu .mud-nav-link.active:not(.mud-nav-link-disabled):hover:not(.mud-nav-link-disabled),
html[data-theme] .mudblazor-navmenu .mud-nav-link.active:not(.mud-nav-link-disabled):focus-visible:not(.mud-nav-link-disabled),
html[data-theme] .mudblazor-navmenu .mud-nav-link.active:not(.mud-nav-link-disabled):active:not(.mud-nav-link-disabled) {
    background: var(--pa-nav-active-wash);
}

html[data-theme] .mudblazor-navmenu .mud-nav-link.active:not(.mud-nav-link-disabled) .mud-nav-link-icon {
    color: var(--pa-gold-ic);
}

/* .nav-children .nav-link, line 199: a nested row is smaller and dimmer than its
   parent. The .mud-nav-group ancestor is what marks "inside a group" - .mud-nav-item
   alone will not do it, MudNavLink wraps EVERY link in one including top-level ones,
   and only MudNavGroup's own title link escapes it (it is a direct child of
   .mud-nav-group, which is how MudBlazor's own 36px indent rule tells them apart). */
html[data-theme] .mudblazor-navmenu .mud-nav-group .mud-nav-item .mud-nav-link {
    font-size: 13px;
}

/* :not(.active) is load-bearing, not decoration: this selector and the active-row
   one above score the same (0,5,1) and this one comes later, so without it a
   selected child row would lose its active ink to the dim one. */
/* text-secondary, not the mockup's dim: these are ENABLED child routes, so they owe
   4.5:1 (disabled would be 3.12:1, and 2.97:1 further down the drawer gradient). It
   also keeps them distinguishable from rows MudBlazor genuinely disables, which it
   paints from the text-disabled slot with !important. The nesting is already legible
   from the 13px size step and the indent - the colour does not have to carry it. */
html[data-theme] .mudblazor-navmenu .mud-nav-group .mud-nav-item .mud-nav-link:not(.active) {
    color: var(--mud-palette-text-secondary);
}

html[data-theme] .mudblazor-navmenu .mud-nav-group .mud-nav-item .mud-nav-link:hover:not(.mud-nav-link-disabled) {
    color: var(--mud-palette-text-primary);
}

/* .menu-label-count, line 190: a monospace pill in the gold family. Gated the same
   way as everything else here because the legacy NavMenu uses the same class and
   must keep its plain bold float.

   Scoped to .nav-count, NOT to .menu-label-count itself. That wrapper is doing double
   duty in NavMenuGrouped: three rows use it for a count, six use it purely to host a
   hover-revealed tooltip icon and never hold a number - and the three that do hold one
   are set to "" whenever the count is zero or counts are switched off. Painting the
   wrapper therefore drew a bordered, filled, padded badge around nothing on most rows. */
html[data-theme] .mudblazor-navmenu .nav-count {
    font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
    font-size: 11px;
    font-weight: 500;
    color: var(--pa-gold-fg);
    background: var(--pa-gold-wash);
    border: 1px solid var(--pa-gold-border);
    padding: 1px 7px;
    border-radius: 999px;
}

.mudblazor-navmenu .nav-section {
    margin-top: 4px;
}

/* .nav-section .label, line 173. The inline padding is 16px so the label's text
   lines up with the links' text now that the list carries 8px of its own. */
.mudblazor-navmenu .nav-section-header {
    display: block;
    padding: 16px 16px 6px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    /* Same reason as the child rows: static headings are enabled content, and at 11px
       with 0.14em tracking the disabled slot's 3.12:1 is the hardest text in the app. */
    color: var(--mud-palette-text-secondary);
}

/* A rule between sections, but never above the first one. */
.mudblazor-navmenu .nav-section + .nav-section .nav-section-header {
    border-top: 1px solid var(--mud-palette-divider);
    margin-top: 4px;
    padding-top: 16px;
}

/* ================= WALLET SHORTCUT (top right) ================= */
/* One pill above the tool cards, pushed to the right: the token balance and a button into
   the Wallet page. Purple (--pp) rather than a module family colour because the wallet
   pays for every module, not one of them. */
.pg-hub .topbar {
    display: flex;
    justify-content: flex-end;
    margin: 0 0 16px;
}

.pg-hub .wallet-cta {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    padding: 6px 6px 6px 12px;
    border: 1px solid var(--line);
    border-radius: 14px;
    background: #fff;
    box-shadow: var(--pg-shadow-sm);
    color: var(--ink);
    transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

.pg-hub .wallet-cta:hover,
.pg-hub .wallet-cta:focus-visible {
    border-color: var(--pp);
    box-shadow: 0 6px 18px rgba(92, 74, 228, .14);
    transform: translateY(-1px);
    outline: none;
}

.pg-hub .wallet-cta-icon {
    display: grid;
    place-items: center;
    flex: none;
    width: 32px;
    height: 32px;
    border-radius: 10px;
    background: var(--pp-soft);
    color: var(--pp);
}

.pg-hub .wallet-cta-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.25;
}

.pg-hub .wallet-cta-label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .3px;
    text-transform: uppercase;
    color: var(--muted);
}

.pg-hub .wallet-cta-amount {
    font-family: var(--pg-mono);
    font-size: 15px;
    font-weight: 700;
    letter-spacing: -.2px;
    white-space: nowrap;
    color: var(--ink);
}

.pg-hub .wallet-cta-amount small {
    margin-left: 4px;
    font-family: var(--pg-sans);
    font-size: 11px;
    font-weight: 600;
    color: var(--muted);
}

/* Loading, Not connected, Unavailable */
.pg-hub .wallet-cta-amount.is-muted {
    font-family: var(--pg-sans);
    font-weight: 600;
    color: var(--faint);
}

.pg-hub .wallet-cta-go {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 9px 12px 9px 14px;
    border-radius: 10px;
    font-size: 12px;
    font-weight: 680;
    letter-spacing: -.1px;
    white-space: nowrap;
    color: #fff;
    background: linear-gradient(135deg, #7B6CF0 0%, var(--pp) 100%);
}

.pg-hub .wallet-cta-go svg {
    width: 14px;
    height: 14px;
}

/* The base rule above fills this button with linear-gradient(135deg, #7B6CF0, var(--pp))
   and labels it #fff. --pp is re-pointed to the gold family for the themed admin, so in
   DARK mode the gradient ends at champagne and the white label lands at 1.52:1 - the
   "Manage wallet" text disappears. Light mode only survives because --pa-gold-fg is the
   dark bronze #8C6413 there, which is why the bug looks mode-specific but is really a
   hardcoded-ink bug.

   Fixed by giving it the same treatment as every other primary CTA - gold fill, navy
   ink - which is mode-independent (6.34:1 even at the gradient's darkest stop) and
   retires the last hardcoded violet on this page. Gated, so a flag-off instance keeps
   the original violet gradient and its white label untouched. */
html[data-theme] .pg-hub .wallet-cta-go {
    background: var(--pa-grad-gold);
    color: var(--pa-on-gold);
}

/* Same leftover, lower stakes: the hover lift was tinted with the old violet. */
html[data-theme] .pg-hub .wallet-cta:hover,
html[data-theme] .pg-hub .wallet-cta:focus-visible {
    box-shadow: 0 6px 18px var(--pa-gold-wash);
}

@media (max-width: 640px) {
    .pg-hub .wallet-cta {
        width: 100%;
    }

    .pg-hub .wallet-cta-text {
        flex: 1;
    }
}

/* ============================================================================
   Wallet page — balance, usage, history and recharge (Pages/Wallet.razor).
   Scoped under .wallet-page. Colours come from the MudBlazor palette variables so
   the page follows the admin theme (purple primary on white paper). Layout: a
   full-width hero (balance left, usage per tool right), then History (2/3) beside
   Recharge (1/3) from md up; a single column below that.
   ============================================================================ */

/* The admin container grows to 1920px and beyond on big monitors; the page keeps the
   width of the design instead and sits centred, so the cards stay readable. The bottom
   padding keeps the History card's last row (or its "Show more") off the edge of the
   scroll area: the layout gives the content no spacing of its own below the last page. */
.wallet-page {
    max-width: 1160px;
    margin: 0 auto;
    padding-bottom: 32px;
}

.wallet-page .wallet-title-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 16px;
}

/* ---- hero: the one deliberately loud element on the page ---- */
.wallet-page .wallet-hero {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
    padding: 24px 32px;
    background: linear-gradient(135deg, var(--mud-palette-primary-darken) 0%, var(--mud-palette-primary) 60%, var(--mud-palette-primary-lighten) 100%);
    color: var(--mud-palette-primary-text);
    border-radius: 12px;
}

.wallet-page .wallet-hero .mud-typography {
    color: inherit;
}

.wallet-page .wallet-hero .mud-icon-button {
    color: inherit;
    padding: 4px;
}

.wallet-page .wallet-hero .mud-skeleton {
    background-color: rgba(255, 255, 255, .28);
}

@media (min-width: 960px) {
    .wallet-page .wallet-hero {
        grid-template-columns: minmax(300px, 1fr) minmax(0, 2fr);
        gap: 32px;
    }

    .wallet-page .wallet-hero-usage {
        border-left: 1px solid rgba(255, 255, 255, .28);
        padding-left: 32px;
    }
}

@media (max-width: 959px) {
    .wallet-page .wallet-hero-usage {
        border-top: 1px solid rgba(255, 255, 255, .28);
        padding-top: 20px;
    }
}

.wallet-page .wallet-balance-label {
    display: block;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    opacity: .8;
}

.wallet-page .wallet-balance-figure {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 10px;
    margin: 2px 0 8px;
}

.wallet-page .wallet-balance-amount {
    font-size: 44px;
    font-weight: 800;
    letter-spacing: -1.5px;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

/* Hidden until the count-up script takes the figure over (Pages/Wallet.razor.cs, js/wallet.js): the final
   balance must never be on screen before the count starts from zero. Visibility, not display, so the
   line keeps its height and nothing jumps when the figure appears.
   Failsafe: should nothing ever reveal it (script not loaded, stalled, or an old copy), the figure shows
   itself after the delay below rather than staying hidden. A zero-length animation whose end state
   sticks (fill-mode forwards) is how CSS alone flips a property at a point in time; removing the class
   cancels it, so the normal path is unaffected. */
.wallet-page .wallet-balance-amount.counting {
    visibility: hidden;
    animation: wallet-balance-reveal 0s linear 2.5s forwards;
}

@keyframes wallet-balance-reveal {
    to {
        visibility: visible;
    }
}

.wallet-page .wallet-balance-unit {
    font-size: 16px;
    opacity: .8;
}

.wallet-page .wallet-balance-error {
    opacity: .9;
}

.wallet-page .wallet-balance-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: -4px;
    opacity: .9;
}

/* usage per tool: three columns inside the hero, white on purple */
.wallet-page .wallet-hero-usage {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 28px;
    align-content: center;
}

/* a note across the usage grid: nothing spent yet, or why the columns could not be refreshed */
.wallet-page .wallet-usage-note {
    grid-column: 1 / -1;
    opacity: .9;
}

.wallet-page .wallet-usage-name {
    font-weight: 500;
    opacity: .9;
}

.wallet-page .wallet-usage-figure {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px;
    margin: 2px 0 8px;
}

.wallet-page .wallet-usage-amount {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -.4px;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
}

.wallet-page .wallet-usage-unit {
    font-size: 12px;
    opacity: .8;
}

.wallet-page .wallet-usage-bar.mud-progress-linear {
    background-color: rgba(255, 255, 255, .25);
}

.wallet-page .wallet-usage-bar .mud-progress-linear-bar {
    background-color: #fff;
}

.wallet-page .wallet-usage-share {
    display: block;
    margin-top: 6px;
    opacity: .8;
}

/* ---- the two cards under the hero ---- */
.wallet-page .wallet-columns {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
    align-items: start;
}

@media (min-width: 960px) {
    .wallet-page .wallet-columns {
        grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    }
}

/* ---- history: head and filters carry the padding; the rows rule edge to edge ---- */
.wallet-page .wallet-history-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px;
    padding: 20px 24px 12px;
}

.wallet-page .wallet-history-head-right {
    display: flex;
    align-items: center;
    gap: 4px;
}

.wallet-page .wallet-history-count {
    color: var(--mud-palette-text-secondary);
}

.wallet-page .wallet-history-count.filtered {
    color: var(--mud-palette-primary);
}

.wallet-page .wallet-history-filters {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr) minmax(0, 2fr);
    gap: 12px;
    padding: 0 24px 16px;
}

/* the filters have no label, so the dense outlined field can drop its label allowance */
.wallet-page .wallet-history-filters .mud-input-control {
    margin-top: 0;
}

@media (max-width: 700px) {
    .wallet-page .wallet-history-filters {
        grid-template-columns: minmax(0, 1fr);
    }
}

.wallet-page .wallet-history-row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 12px 24px;
    border-top: 1px solid var(--mud-palette-lines-default);
}

.wallet-page .wallet-history-empty,
.wallet-page .wallet-history-error {
    padding: 16px 24px 20px;
    border-top: 1px solid var(--mud-palette-lines-default);
}

.wallet-page .wallet-history-more {
    display: flex;
    justify-content: center;
    padding: 10px 24px;
    border-top: 1px solid var(--mud-palette-lines-default);
}

/* avatar: two letters in the service's colour, neutral for rows without a service, green plus for credits */
.wallet-page .wallet-history-avatar {
    background-color: var(--mud-palette-background-gray);
    color: var(--mud-palette-text-secondary);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .02em;
}

.wallet-page .wallet-history-avatar.tint-0 {
    background-color: rgba(var(--mud-palette-info-rgb), .14);
    color: var(--mud-palette-info);
}

.wallet-page .wallet-history-avatar.tint-1 {
    background-color: rgba(var(--mud-palette-tertiary-rgb), .16);
    color: var(--mud-palette-tertiary-darken);
}

.wallet-page .wallet-history-avatar.tint-2 {
    background-color: rgba(var(--mud-palette-warning-rgb), .18);
    color: var(--mud-palette-warning-darken);
}

.wallet-page .wallet-history-avatar.tint-3 {
    background-color: rgba(var(--mud-palette-secondary-rgb), .14);
    color: var(--mud-palette-secondary-darken);
}

.wallet-page .wallet-history-avatar.tint-4 {
    background-color: rgba(var(--mud-palette-primary-rgb), .14);
    color: var(--mud-palette-primary);
}

.wallet-page .wallet-history-avatar.credit {
    background-color: rgba(var(--mud-palette-success-rgb), .16);
    color: var(--mud-palette-success-darken);
}

.wallet-page .wallet-history-text {
    flex: 1;
    min-width: 0;
}

.wallet-page .wallet-history-title {
    line-height: 1.3;
}

.wallet-page .wallet-history-amount {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.wallet-page .wallet-history-amount.credit {
    color: var(--mud-palette-success);
}

/* ---- recharge: package tiles behave like a radio group and look like one ---- */
.wallet-page .wallet-recharge {
    container-type: inline-size;
}

.wallet-page .wallet-packages {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* placeholder tiles while the packages load; same footprint as a real tile */
.wallet-page .wallet-package-skeleton {
    border-radius: var(--mud-default-borderradius);
}

.wallet-page .wallet-package {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    border-radius: var(--mud-default-borderradius);
    border: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-surface);
    cursor: pointer;
    user-select: none;
    transition: border-color .15s, background-color .15s;
}

.wallet-page .wallet-package:hover {
    border-color: var(--mud-palette-primary);
}

.wallet-page .wallet-package:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

.wallet-page .wallet-package.selected {
    background: var(--mud-palette-primary-hover);
    border-color: var(--mud-palette-primary);
}

.wallet-page .wallet-package-radio {
    flex: none;
    position: relative;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: 2px solid var(--mud-palette-lines-inputs);
    background: var(--mud-palette-surface);
}

.wallet-page .wallet-package.selected .wallet-package-radio {
    border-color: var(--mud-palette-primary);
}

.wallet-page .wallet-package.selected .wallet-package-radio::after {
    content: "";
    position: absolute;
    inset: 3px;
    border-radius: 50%;
    background: var(--mud-palette-primary);
}

.wallet-page .wallet-package-text {
    flex: 1;
    min-width: 0;
}

.wallet-page .wallet-package-title {
    font-weight: 600;
    line-height: 1.3;
}

.wallet-page .wallet-package-price {
    font-weight: 700;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* the label carries the price and the token count; in a narrow column it may need two lines */
.wallet-page .wallet-pay .mud-button-label {
    white-space: normal;
    line-height: 1.3;
}

/* while the order is being placed the label is a spinner and a word; the spinner takes the icon's slot */
.wallet-page .wallet-pay .wallet-pay-spinner {
    width: 20px;
    height: 20px;
    margin-right: 8px;
    vertical-align: middle;
}

@container (max-width: 320px) {
    .wallet-page .wallet-package-title {
        font-size: 15px;
    }

    .wallet-page .wallet-pay .mud-button-label {
        font-size: 13px;
        letter-spacing: .01em;
    }
}

@media (max-width: 600px) {
    .wallet-page .wallet-hero {
        padding: 20px;
    }

    .wallet-page .wallet-balance-amount {
        font-size: 38px;
    }

    .wallet-page .wallet-hero-usage {
        grid-template-columns: minmax(0, 1fr);
        gap: 16px;
    }

    .wallet-page .wallet-history-head,
    .wallet-page .wallet-history-filters,
    .wallet-page .wallet-history-row,
    .wallet-page .wallet-history-empty,
    .wallet-page .wallet-history-error,
    .wallet-page .wallet-history-more {
        padding-left: 16px;
        padding-right: 16px;
    }
}
