/* Matrix Vision brand tokens — merged from the PRODUCT-2617 design handoff
   (design/design_handoff_matrix_vision/css/matrix-vision.css), which supersedes the earlier
   MatrixTheme.axaml translation (docs/VISION-DESIGN.md §6). Per the ticket's decision 2, the
   handoff values are adopted EXCEPT where our a11y-darkened colours already cleared WCAG AA —
   those are kept (each carries its pre-fix value + measured ratio in a comment, verified by
   test/load/a11y-check.mjs). */
:root {
    /* ---- brand core ---- */
    --brand-pink: #FA5CDC;
    --brand-magenta: #C13AE0;
    --brand-purple: #9342DC;
    --brand-indigo: #4A20CA;
    --brand-indigo-deep: #3A18A8;
    --brand-ink: #1E1A34;
    --brand-ink-2: #1C1735;
    --brand-primary: #7A2FD8;        /* balanced solid primary ("brand violet") */
    --brand-primary-hover: #6A28C6;
    --brand-primary-active: #591FB0;
    /* Foreground accent for links/text/icons (distinct from --brand-primary, which is a button FILL
       behind white text). Split so dark mode can lighten the text accent without breaking fills. */
    --accent: #7A2FD8;
    --focus-ring: rgba(122, 47, 216, 0.45);

    /* ---- gradients (handoff angles — follow the mark's flow) ---- */
    --brand-gradient: linear-gradient(115deg, var(--brand-pink) 0%, var(--brand-purple) 48%, var(--brand-indigo) 100%);
    --brand-gradient-vivid: linear-gradient(115deg, #FF6FE0 0%, #A24BE6 45%, #5A2BE0 100%);
    --brand-gradient-ink: linear-gradient(150deg, #2A2350 0%, var(--brand-ink) 60%, #17122B 100%);
    --brand-gradient-lilac: linear-gradient(135deg, #D8C8F4 0%, #BCA6EE 100%);

    /* ---- neutrals (cool, tinted toward the ink) — the LIGHT-mode source palette.
       Components use the semantic tokens below, never these directly: the neutrals do not flip
       in dark mode, the semantic tokens do. ---- */
    --neutral-0:  #FFFFFF;
    --neutral-50: #F7F6FB;
    --neutral-100: #EFEDF5;
    --neutral-200: #E2DEEC;
    --neutral-300: #CEC9DD;
    --neutral-400: #A9A2BE;
    --neutral-500: #7C7691;
    --neutral-600: #574F6E;
    --neutral-700: #3B3452;
    --neutral-800: #262038;
    --neutral-900: #1E1A34;

    /* ---- brand tints ---- */
    --violet-50: #F5F0FE;
    --violet-100: #ECE2FC;
    --violet-200: #D9C6F6;
    --pink-50: #FEF0FB;
    --pink-100: #FBDDF4;

    /* ---- surfaces ---- */
    --surface-page: #F7F6FB;
    --surface-card: #FFFFFF;
    --surface-sunken: #EFEDF5;
    --surface-chip: #F7F6FB;
    --surface-ink: var(--brand-ink);
    --surface-brand-soft: var(--violet-50);

    /* ---- text ---- */
    --text-strong: #1E1A34;
    --text-body: #3B3452;
    /* Muted/faint darkened to clear WCAG AA (4.5:1) at their small rendered sizes — was
       #7C7691 (4.02:1) / #A9A2BE (2.27:1) on the page bg (a11y-check.mjs). §6, board a11y pass.
       The handoff ships the failing originals (neutral-500/400); decision 2 keeps ours. */
    --text-muted: #5C5772;
    --text-faint: #6D6883;
    --text-on-brand: #FFFFFF;
    --text-on-accent: #FFFFFF;

    --border-subtle: #E2DEEC;
    --border-default: #CEC9DD;
    --border-strong: #A9A2BE;
    /* Scrollbar thumbs are translucent rather than a border token, so one value reads correctly
       over both the card and whatever content sits under it. */
    --scrollbar-thumb: rgba(23, 18, 43, .22);
    --scrollbar-thumb-hover: rgba(23, 18, 43, .40);

    /* ---- semantic status ----
       Backgrounds are the handoff values. Foreground danger/success darkened to clear AA (4.5:1)
       as text on white AND on their own tinted bg (badges/toasts); still used as fills behind
       WHITE text (segmented control), where darker only raises contrast.
       Was #E23B5D (4.19:1) / #1F9D6B (3.45:1) — the handoff re-ships exactly those; decision 2
       keeps ours. Warning is new with the handoff and darkened the same way on arrival:
       was #E39A2B (2.35:1); #8A5A06 is 5.9:1 on white, 5.3:1 on its bg (the handoff itself
       drops to a darker amber for badge text, conceding the raw hue fails). */
    --danger: #C21D41;
    --danger-bg: #FBE4E9;
    --success: #12744C;
    --success-bg: #E4F5EE;
    --warning: #8A5A06;
    --warning-bg: #FBF0DC;
    --info: #4A20CA;
    --info-bg: #ECE2FC;

    /* ---- type ---- */
    /* Brand font (design §6, OQ#7). Manrope — a geometric humanist sans (SIL Open Font Licence, self-hosted
       below) chosen as a Gilroy-alike while Gilroy's licence is unconfirmed (PRODUCT-2617: files received,
       licence still unconfirmed — shipping Manrope stands). Gilroy is kept at the head of the stack so that
       dropping licensed Gilroy woff2 into /fonts/ + adding its @font-face picks it up with no other change.
       Keep in sync with the literal stack in site.css (loaded standalone on the Error page). */
    --font-brand: 'Gilroy', 'Manrope', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
    --font-display: var(--font-brand);   /* handoff aliases — display + body are both the brand face */
    --font-body: var(--font-brand);
    --font-mono: ui-monospace, 'SF Mono', Menlo, monospace;

    --fw-regular: 400; --fw-medium: 500; --fw-semibold: 600; --fw-bold: 700;

    /* Handoff px scale — the target for screens built to the new spec. The rem --fs-* scale below
       is the legacy scale still referenced by existing components; migrate per screen, then retire. */
    --text-xs: 12px; --text-sm: 14px; --text-base: 16px; --text-md: 18px;
    --text-lg: 20px; --text-xl: 24px; --text-2xl: 30px; --text-3xl: 38px;
    --text-4xl: 48px; --text-5xl: 64px; --text-6xl: 84px;

    --leading-none: 1; --leading-tight: 1.12; --leading-snug: 1.28;
    --leading-normal: 1.5; --leading-relaxed: 1.65;

    --tracking-tight: -0.02em; --tracking-normal: 0;
    --tracking-wide: 0.04em; --tracking-label: 0.14em;   /* uppercase eyebrows */

    /* Legacy rem scale (pre-handoff), still in use. */
    --fs-display: 1.75rem;   /* page hero (login/getting-started) */
    --fs-h1: 1.4rem;         /* page titles (<h1> / .vz-h2) */
    --fs-h2: 1.2rem;         /* major section titles */
    --fs-h3: 1.02rem;        /* sub-section titles — below the page title, above body */
    --fs-body: .95rem;
    --fs-small: .82rem;
    --lh-tight: 1.2;
    --lh-body: 1.55;

    /* ---- spacing (4px grid) ---- */
    --space-1: 4px;  --space-2: 8px;  --space-3: 12px;  --space-4: 16px;
    --space-5: 20px; --space-6: 24px; --space-8: 32px;  --space-10: 40px;
    --space-12: 48px; --space-16: 64px; --space-20: 80px; --space-24: 96px;

    /* ---- layout (handoff shell metrics — consumed by the package-2 shell rework) ---- */
    --container-max: 1200px;
    --content-max: 1180px;
    --sidebar-w: 264px;
    --sidebar-w-collapsed: 76px;
    --topbar-h: 68px;
    /* The chats rail mirrors the nav sidebar on the right; collapsed it keeps a strip, not zero. */
    --chat-rail-width: 280px;
    --chat-rail-width-collapsed: 56px;

    /* ---- radius ---- */
    --radius-xs: 4px;
    --radius-sm: 6px;
    --radius-md: 10px;   /* inputs, buttons */
    --radius-lg: 14px;   /* default card */
    --radius-xl: 20px;
    --radius-2xl: 28px;
    --radius-pill: 999px;

    /* ---- shadows (cool navy tint — never pure black) ---- */
    --shadow-xs: 0 1px 2px rgba(30, 26, 52, 0.06);
    --shadow-sm: 0 2px 6px rgba(30, 26, 52, 0.08);
    --shadow-md: 0 8px 24px rgba(30, 26, 52, 0.10);
    --shadow-lg: 0 18px 48px rgba(30, 26, 52, 0.14);
    --shadow-xl: 0 30px 70px rgba(30, 26, 52, 0.18);
    --shadow-brand: 0 12px 30px rgba(122, 47, 216, 0.30);   /* primary/gradient CTAs only */
    --shadow-inset: inset 0 1px 0 rgba(255, 255, 255, 0.6);

    /* ---- motion (quick ease-out, no bounce) ---- */
    --ease-standard: cubic-bezier(0.22, 0.61, 0.36, 1);
    --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
    --dur-fast: 120ms; --dur-base: 200ms; --dur-slow: 320ms;
}

/* Manrope — SIL Open Font Licence 1.1. Files: wwwroot/fonts/manrope/ (latin subset, see OFL.txt). */
@font-face { font-family: 'Manrope'; src: url('../fonts/manrope/manrope-400.woff2') format('woff2'); font-weight: 400; font-display: swap; font-style: normal; }
@font-face { font-family: 'Manrope'; src: url('../fonts/manrope/manrope-500.woff2') format('woff2'); font-weight: 500; font-display: swap; font-style: normal; }
@font-face { font-family: 'Manrope'; src: url('../fonts/manrope/manrope-600.woff2') format('woff2'); font-weight: 600; font-display: swap; font-style: normal; }
@font-face { font-family: 'Manrope'; src: url('../fonts/manrope/manrope-700.woff2') format('woff2'); font-weight: 700; font-display: swap; font-style: normal; }
@font-face { font-family: 'Manrope'; src: url('../fonts/manrope/manrope-800.woff2') format('woff2'); font-weight: 800; font-display: swap; font-style: normal; }

body {
    background: var(--surface-page);
    color: var(--text-body);
    font-family: var(--font-brand);
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Links carry the brand accent (handoff base rule; was a stale template blue in site.css).
   Colour only — decoration stays Bootstrap's until each screen is restyled to the handoff. */
a, .btn-link { color: var(--accent); }

/* Keyboard focus — a single visible ring for every interactive control, including the custom/borderless
   ones (chips, chat-toolbar buttons, segmented control) that otherwise had no focus affordance at all.
   :focus-visible keeps it keyboard-only (no ring on mouse click). WCAG 2.4.7. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible,
[tabindex]:focus-visible, label[role="button"]:focus-visible,
.tj-chip:focus-visible, .vz-seg button:focus-visible, .vz-btn:focus-visible {
    outline: 2px solid var(--brand-primary);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}
/* On the dark ink rails the purple ring is low-contrast — use a light ring there instead. */
.vision-sidebar a:focus-visible, .vision-sidebar button:focus-visible,
.chat-rail a:focus-visible, .chat-rail button:focus-visible {
    outline-color: #EDE9F7;
}

/* Heading hierarchy — geometric sans reads best slightly heavier and tighter than browser defaults. */
h1, h2, h3, h4, .vz-h2 { font-family: var(--font-brand); color: var(--text-strong); line-height: var(--lh-tight); }
h1, .vz-h2 { font-size: var(--fs-h1); font-weight: 800; letter-spacing: -.02em; }   /* page title — the largest */
h2 { font-size: var(--fs-h2); font-weight: 700; letter-spacing: -.01em; }
/* h3/h4 previously had NO font-size and inherited Bootstrap's 1.75rem/1.5rem, so <h3> section
   headers rendered LARGER than the <h2> page title (inverted hierarchy). Pin them to the scale. */
h3 { font-size: var(--fs-h3); font-weight: 700; letter-spacing: -.01em; }
h4 { font-size: var(--fs-body); font-weight: 700; }

/* App shell — cap the height at the viewport so content areas scroll INTERNALLY (fixed sidebar +
   scrolling content). Chat (Chat.razor §5.4) relies on this cap: without a bounded height its flex:1
   message list can't scroll internally and the "Ask Tjoppie" input panel is pushed off-screen.
   100dvh (not vh) avoids the mobile address-bar height jump. min-height:0 on the flex columns lets
   the inner scroller actually shrink below content size. */
.vz-shell { display: flex; height: 100dvh; }
.vz-main-col { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.vz-main { flex: 1; background: var(--surface-page); min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; }
.vz-topbar { display: none; }                 /* desktop: no top bar */
.vz-nav-toggle { position: absolute; opacity: 0; pointer-events: none; }   /* hidden checkbox (CSS drawer) */
.vz-nav-scrim { display: none; }
.vision-sidebar {
    background: var(--brand-gradient-ink);
    color: #EDE9F7;
    height: 100%;   /* fill the now-bounded .vz-shell (was min-height:100vh, which broke internal scroll) */
    width: var(--sidebar-w);
    flex: none;
    position: relative;
    /* overflow stays visible: the scope menu pops out past the collapsed rail's width. The decorative
       motion mark is clipped by its own .vision-mark-clip wrapper instead. */
    transition: width var(--dur-slow) var(--ease-standard);
}
@media (prefers-reduced-motion: reduce) { .vision-sidebar { transition: none; } }
.vision-mark-clip { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.vision-mark-bleed { position: absolute; right: -90px; bottom: -80px; width: 280px; opacity: .12; }

/* Narrow screens: sidebar becomes an off-canvas drawer behind a top-bar hamburger. */
@media (max-width: 900px) {
    .vz-topbar { display: flex; align-items: center; gap: .75rem; background: var(--brand-ink); color: #fff;
        padding: .55rem 1rem; position: sticky; top: 0; z-index: 40; }
    .vz-hamburger { font-size: 1.35rem; line-height: 1; cursor: pointer; padding: .1rem .3rem; }
    .vz-topbar-brand { font-weight: 800; letter-spacing: -.01em; display: inline-flex; align-items: center; gap: .45rem; }
    .vz-topbar-logo { height: 22px; width: auto; flex: none; display: block; }
    .vision-sidebar { position: fixed; top: 0; left: 0; height: 100vh; z-index: 60;
        transform: translateX(-100%); transition: transform .2s ease; box-shadow: 0 0 40px rgba(0,0,0,.4); }
    .vz-nav-toggle:checked ~ .vz-shell .vision-sidebar { transform: translateX(0); }
    .vz-nav-toggle:checked ~ .vz-shell .vz-nav-scrim { display: block; position: fixed; inset: 0;
        background: rgba(0,0,0,.45); z-index: 50; }
}
/* Sidebar brand row (design §4): the static lockup (expanded) or the logomark alone (collapsed rail),
   plus the collapse toggle at the right. */
.vision-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 4px 0 20px;
    position: relative;
    z-index: 1;
}
.vision-brand-lockup { height: 24px; width: auto; flex: none; display: block; }
.vision-brand-logo { height: 26px; width: auto; flex: none; display: none; }   /* collapsed rail only */
/* Collapse/expand toggle — lives in the brand row (design §4), keeps its historical class name
   because rail-check.mjs clicks it. */
.vision-rail-toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; flex: none; margin-left: auto;
    border: none; border-radius: var(--radius-sm); cursor: pointer;
    background: rgba(255, 255, 255, .07); color: rgba(255, 255, 255, .7);
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.vision-rail-toggle:hover { background: rgba(255, 255, 255, .14); color: #fff; }
/* Sidebar inner shell. Full-height flex column bounded to the viewport-tall .vision-sidebar so the
   ink gradient always covers it. The nav region below is the only scroller; overflow stays visible
   here so the scope menu can pop past the collapsed rail. */
.vision-sidebar-inner { padding: 20px 16px 18px; display: flex; flex-direction: column; height: 100%; position: relative; }
/* Nav is the only scrollable region: it flexes to fill the space between the brand and the pinned
   footer, and scrolls internally when the menu (e.g. Admin expanded) is taller than the viewport.
   min-height:0 lets a flex item shrink below its content so overflow-y actually engages. */
.vision-nav { flex: 1 1 auto; min-height: 0; overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(201, 194, 224, .35) transparent; }
.vision-nav::-webkit-scrollbar { width: 6px; }
.vision-nav::-webkit-scrollbar-thumb { background: rgba(201, 194, 224, .35); border-radius: 3px; }
/* Footer (profile/logout/theme/collapse) is pinned at the bottom and never shrinks. */
.vision-nav-foot { flex: 0 0 auto; padding-top: 2rem; }

/* Nav rows (design §4): 18px Lucide icon, 14px label, quiet rest state, white-on-soft-white active
   with a pink accent bar in the padding gutter. */
.vision-nav a {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    color: rgba(255, 255, 255, .62);
    font-size: var(--text-sm);
    text-decoration: none;
    position: relative;
    white-space: nowrap;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.vision-nav a:hover { background: rgba(255, 255, 255, .08); color: #fff; }
.vision-nav a.active { background: rgba(255, 255, 255, .10); color: #fff; font-weight: 600; }
.vision-nav a.active::before {
    content: ""; position: absolute; left: -16px; top: 8px; bottom: 8px;
    width: 3px; border-radius: 3px; background: var(--brand-pink);
}
.vision-nav-divider { height: 1px; background: rgba(255, 255, 255, .10); margin: 11px 8px; flex: none; }
/* Inert "coming soon" nav item — matches the link row shape but is dimmed and non-interactive. */
.vision-nav-disabled {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    color: rgba(255, 255, 255, .42);
    font-size: var(--text-sm);
    cursor: default;
    pointer-events: none;
    white-space: nowrap;
}
.vision-soon-pill {
    margin-left: auto; padding: 3px 8px; border-radius: var(--radius-pill);
    border: 1px solid rgba(255, 255, 255, .2);
    font-size: 9px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
    color: rgba(255, 255, 255, .5); white-space: nowrap;
}

/* Collapsible section header (Admin) — a real button for keyboard + aria-expanded. */
.vision-nav-group {
    display: flex;
    align-items: center;
    gap: .35rem;
    width: 100%;
    margin-top: .9rem;
    padding: .35rem .9rem;
    background: none;
    border: none;
    color: #8B84A6;
    font: inherit;
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    text-align: left;
    cursor: pointer;
    border-radius: var(--radius-sm);
}
.vision-nav-group:hover { color: #C9C2E0; }
.vision-nav-group .chevron {
    display: inline-block;
    font-size: .6rem;
    transition: transform .15s ease;
}
.vision-nav-group.open .chevron { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
    .vision-nav-group .chevron { transition: none; }
}

/* Footer (design §4): read-only disclaimer card, user chip → /profile, quiet Sign out. */
.vision-disclaimer {
    display: flex; gap: 9px; margin: 12px 0; padding: 11px 12px;
    border-radius: var(--radius-md);
    background: rgba(255, 255, 255, .06); border: 1px solid rgba(255, 255, 255, .14);
}
.vision-disclaimer-icon { display: inline-flex; flex: none; margin-top: 1px; color: rgba(255, 255, 255, .62); }
.vision-disclaimer-text { font-size: 11px; line-height: 1.55; color: rgba(255, 255, 255, .62); }
.vision-user-chip {
    display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px;
    border: none; border-top: 1px solid rgba(255, 255, 255, .10); border-radius: 0;
    background: transparent; color: inherit; text-decoration: none; cursor: pointer;
    transition: background var(--dur-fast) var(--ease-standard);
}
.vision-user-chip:hover { background: rgba(255, 255, 255, .10); }
.vision-user-avatar {
    width: 32px; height: 32px; flex: none; border-radius: 50%;
    background: var(--brand-gradient); color: #fff;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: var(--text-sm); font-weight: 600;
}
.vision-user-meta { min-width: 0; text-align: left; line-height: 1.2; }
.vision-user-name { display: block; font-size: var(--text-sm); font-weight: 600; color: #fff;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vision-user-role { display: block; font-size: var(--text-xs); color: rgba(255, 255, 255, .5); }
.vision-user-chev { display: inline-flex; flex: none; margin-left: auto; color: rgba(255, 255, 255, .5); }
.vision-signout {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    width: 100%; margin-top: 8px; padding: 9px; border: none;
    background: transparent; border-radius: var(--radius-sm); cursor: pointer;
    font-family: inherit; font-size: var(--text-xs); font-weight: 500; color: rgba(255, 255, 255, .5);
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.vision-signout:hover { color: #fff; background: rgba(255, 255, 255, .08); }
.vision-signout-icon { display: none; }   /* collapsed rail only */

/* --- Collapsed icon-rail (desktop only; mobile keeps the off-canvas drawer) --- */
@media (min-width: 901px) {
    :root[data-rail="collapsed"] .vision-sidebar { width: var(--sidebar-w-collapsed); }
    :root[data-rail="collapsed"] .vision-sidebar-inner { padding: 20px 12px 18px; }
    /* Hide every text label; icons remain. */
    :root[data-rail="collapsed"] .nav-label { display: none; }
    /* Center the icon-only rows. */
    :root[data-rail="collapsed"] .vision-nav a,
    :root[data-rail="collapsed"] .vision-nav-group,
    :root[data-rail="collapsed"] .vision-nav-disabled { justify-content: center; gap: 0; padding-left: 0; padding-right: 0; }
    /* Brand row: the lockup is hidden (.nav-label), the logomark shows, the toggle wraps below it. */
    :root[data-rail="collapsed"] .vision-brand { justify-content: center; flex-wrap: wrap; padding-bottom: 18px; }
    :root[data-rail="collapsed"] .vision-brand-logo { display: block; height: 24px; max-width: 100%; }
    :root[data-rail="collapsed"] .vision-rail-toggle { margin-left: 0; }
    /* Active accent bar sits in the narrower gutter. */
    :root[data-rail="collapsed"] .vision-nav a.active::before { left: -12px; }
    /* Scope trigger shrinks to the building icon; the menu pops out at a fixed width. */
    :root[data-rail="collapsed"] .vision-scope-btn { justify-content: center; padding: 11px 0; }
    :root[data-rail="collapsed"] .vision-scope-mark { display: inline-flex; }
    :root[data-rail="collapsed"] .vision-scope-menu { right: auto; min-width: 196px; }
    /* Footer: icon-only disclaimer (full text stays as its tooltip), centred chip, icon sign-out. */
    :root[data-rail="collapsed"] .vision-disclaimer { justify-content: center; padding: 9px 0; }
    :root[data-rail="collapsed"] .vision-user-chip { justify-content: center; }
    :root[data-rail="collapsed"] .vision-signout-icon { display: inline-flex; }
    /* Theme toggle stacks to two centered icon buttons. */
    :root[data-rail="collapsed"] .vz-theme-toggle { flex-direction: column; }
    :root[data-rail="collapsed"] .vz-theme-toggle button { justify-content: center; }
    :root[data-rail="collapsed"] .vz-theme-toggle button + button { border-left: none; border-top: 1px solid #4A4368; }
    /* The admin section header keeps just its disclosure chevron. */
    :root[data-rail="collapsed"] .vision-nav-group .chevron { margin: 0; }
}

/* --- Site scope selector (sidebar, design §4). Trigger on the ink gradient; the menu is a light
   surface-card popover shared with the page theme. Behaviour lives in Shared/ScopeSelector.razor. --- */
.vision-scope { position: relative; z-index: 3; margin-bottom: 18px; }
.vision-scope-btn {
    width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 10px 12px; border: 1px solid rgba(255, 255, 255, .18); border-radius: var(--radius-md);
    background: rgba(255, 255, 255, .07); cursor: pointer; text-align: left; font-family: inherit;
    transition: background var(--dur-fast) var(--ease-standard);
}
.vision-scope-btn:hover { background: rgba(255, 255, 255, .12); }
.vision-scope-text { min-width: 0; }
.vision-scope-eyebrow {
    display: block; font-size: 10px; font-weight: 600; letter-spacing: var(--tracking-label);
    text-transform: uppercase; color: rgba(255, 255, 255, .5); margin-bottom: 5px;
}
.vision-scope-label {
    display: block; font-size: var(--text-sm); font-weight: 600; color: #fff;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vision-scope-mark { display: none; color: rgba(255, 255, 255, .75); }   /* collapsed rail only */
.vision-scope-chev { display: inline-flex; flex: none; color: rgba(255, 255, 255, .6); }
/* Transparent click-away layer: any outside click commits the selection and closes the menu. */
.vision-scope-scrim { position: fixed; inset: 0; z-index: 4; background: transparent; }
.vision-scope-menu {
    position: absolute; top: 100%; left: 0; right: 0; margin-top: 6px; padding: 6px; z-index: 5;
    background: var(--surface-card); border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md); box-shadow: var(--shadow-xl);
}
.vision-scope-menu-eyebrow {
    display: block; padding: 4px 10px 8px; font-size: 10px; font-weight: 600;
    letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint);
}
.vision-scope-row {
    display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
    padding: 8px 10px; border: none; border-radius: var(--radius-sm); cursor: pointer;
    background: transparent; font-family: inherit; font-size: var(--text-sm); font-weight: 500;
    color: var(--text-body); white-space: nowrap;
}
.vision-scope-row:hover { background: var(--surface-sunken); }
.vision-scope-row.on { background: var(--violet-50); color: var(--brand-primary); }
.vision-scope-box {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 16px; height: 16px; border-radius: var(--radius-xs);
    border: 1.5px solid var(--border-default); background: var(--surface-card); color: #fff;
}
.vision-scope-box.on { border-color: transparent; background: var(--brand-primary); }
.vision-scope-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.vision-scope-count { font-size: 11px; color: var(--text-faint); }
.vision-scope-divider { display: block; height: 1px; margin: 5px 8px; background: var(--border-subtle); }
.vision-scope-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 2px 10px 4px; }
.vision-scope-sum { font-size: 11px; line-height: 1.4; color: var(--text-muted); }
.vision-scope-done {
    flex: none; padding: 5px 12px; border: none; border-radius: var(--radius-sm);
    background: var(--brand-gradient); color: #fff;
    font-family: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer;
}

/* --- Shell topbar (design §4): page title/subtitle left (published via PageHeader → TopbarState),
   actions + the site-scope badge right. Sits above the only page scroller (.vz-main). --- */
.vision-topbar {
    flex: none; height: var(--topbar-h);
    display: flex; align-items: center; justify-content: space-between; gap: 16px;
    padding: 0 28px;
    background: var(--surface-card); border-bottom: 1px solid var(--border-subtle);
}
.vision-topbar-heading { min-width: 0; }
.vision-topbar h1 {
    margin: 0; font-size: var(--text-xl); font-weight: 500; letter-spacing: var(--tracking-tight);
    line-height: 1.15; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* FocusOnNavigate lands keyboard/SR focus on the page title after each navigation (good), but the
   generic [tabindex]:focus-visible ring outlines the heading on every nav (noisy). Headings are not
   interactive — suppress the visual ring, keep the programmatic focus. */
.vision-topbar h1:focus, .vision-topbar h1:focus-visible { outline: none; }
.vision-topbar-sub {
    margin-top: 2px; font-size: var(--text-xs); color: var(--text-muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vision-topbar-actions { display: flex; align-items: center; gap: 10px; flex: none; }
.vision-topbar-badge {
    display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px;
    border-radius: var(--radius-pill); background: var(--success-bg); color: var(--success);
    font-size: 11px; font-weight: 600; white-space: nowrap;
}
.vision-topbar-badge .dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }

/* Chat answer blocks — differentiated by type, not one uniform white card (design review). */
/* position:relative belongs on the answer itself, not on each host page's layout: an answer's
   absolutely-positioned descendants (the .tj-sr-only chart table) must anchor inside it, or they
   anchor to <html> and grow the document into empty scrollable space. */
/* No width cap here. The reading measure belongs to the prose alone (see .tj-prose): capping the
   answer capped tables, charts and diagrams with it, so a wide result sat in an 820px column with
   the rest of the card unused beside it. */
.tj-answer { position: relative; }
.tj-answer > * { margin-bottom: 1rem; }
.tj-answer > *:last-child { margin-bottom: 0; }

/* Narrative flows as prose — no card. `anywhere` rather than `break-word` because only `anywhere`
   reduces min-content width, which is what stops a long token widening the column. */
.tj-prose { color: var(--text-body); line-height: var(--lh-body); overflow-wrap: anywhere; max-width: 820px; }
.tj-prose > :first-child { margin-top: 0; }
.tj-prose > :last-child { margin-bottom: 0; }
.tj-prose p { margin: 0 0 .6rem; }
.tj-prose ul, .tj-prose ol { margin: .3rem 0 .6rem; padding-left: 1.3rem; }
.tj-prose li { margin: .15rem 0; }
.tj-prose strong { color: var(--text-strong); font-weight: 700; }
.tj-prose h1, .tj-prose h2, .tj-prose h3 { margin: .6rem 0 .35rem; }
.tj-prose code { background: var(--surface-sunken); padding: .05rem .3rem; border-radius: var(--radius-sm); font-size: .85em; overflow-wrap: anywhere; }

/* Data objects — tables, charts, diagrams — get the framed card. */
.tj-object { background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); padding: 1rem 1.25rem; }
.tj-object > h4 { color: var(--text-strong); margin: 0 0 .75rem; font-size: .95rem; font-weight: 700; }
/* Long result tables (e.g. a 46-row monthly series) scroll inside the card instead of dumping the
   whole thing inline. Short tables stay their natural height (content < max-height ⇒ no scrollbar). */
.tj-object-scroll { overflow: auto; max-height: 380px; }
/* Charts and diagrams for non-table objects. X-only and no max-height: they set their own height, so
   capping it would add a second vertical scrollbar inside the card. */
.tj-object-xscroll { overflow-x: auto; }
/* A diagram sizes itself, so a narrow one would otherwise sit against the left edge of a full-width
   card. `safe` is not optional: plain centring pushes a diagram wider than the card off its left edge,
   where a scroller cannot reach it. Only the diagram needs it: DxChart already spans 100%, and making
   the scroller itself a flex container would collapse that. */
pre.mermaid { display: flex; justify-content: safe center; }
.tj-empty { color: var(--text-muted); font-size: .85rem; padding: .6rem 0; }
/* A site's non-answer in an all-sites view. Muted prose reads as information ("nothing here"); the
   amber callout reads as a problem, so a failed read is never mistaken for an empty one. */
.tj-site-status.no-data { color: var(--text-muted); font-size: .85rem; padding: .4rem 0; }
/* A question put back to the user: set apart from an answer by the accent edge and the soft brand surface,
 which both themes define. */
.tj-clarification {
    background: var(--surface-brand-soft); border: 1px solid var(--border-subtle);
    border-left: 3px solid var(--accent); border-radius: var(--radius-sm); padding: .7rem .9rem;
}
.tj-clarification-label { display: flex; align-items: center; gap: .4rem; color: var(--accent); font-size: .75rem; font-weight: 600; margin-bottom: .3rem; }
.tj-clarification-question { color: var(--text-strong); font-weight: 600; margin: 0; overflow-wrap: anywhere; }
.tj-clarification-choices { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .6rem; }
.tj-clarification-choice {
    display: inline-flex; align-items: center; gap: .5rem; min-height: 44px; padding: .45rem 1rem;
    background: var(--surface-card); color: var(--accent); border: 1.5px solid var(--accent);
    border-radius: var(--radius-pill); font: inherit; font-size: 15px; font-weight: var(--fw-semibold);
    line-height: 1.3; text-align: left; overflow-wrap: anywhere; cursor: pointer;
}
.tj-clarification-choice:hover:not(:disabled) { background: var(--violet-50); }
/* Greyed by colour rather than faded, so the text of an answered question back stays readable. */
.tj-clarification-choice:disabled { background: transparent; color: var(--text-muted); border: 1.5px dashed var(--border-default); cursor: default; }
.tj-clarification-choice.picked { background: var(--accent); color: var(--text-on-accent); border: 1.5px solid var(--accent); }
.tj-clarification-hint { margin: .5rem 0 0; font-size: 13px; color: var(--text-muted); }
.chat-root-pwa .tj-clarification-choices { flex-direction: column; }
.chat-root-pwa .tj-clarification-choice { width: 100%; }
.tj-site-status.unavailable {
    display: inline-flex; align-items: center; gap: .4rem;
    color: var(--warning); background: var(--warning-bg);
    border: 1px solid var(--border-subtle); border-radius: var(--radius-sm);
    padding: .5rem .7rem; font-size: .85rem;
}
/* Screen-reader-only: visually hidden but announced. Used for the chart data table (charts alone
   aren't SR-friendly — design §5.4 / §8). WCAG 1.1.1.
   Put this on a wrapping div, never on the <table> itself: a table box floors at its min-content
   width, so it ignores the 1px and lays out full-size while clip hides it — invisible bulk that
   still counts as scrollable overflow. A div honours the 1px. */
.tj-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
.tj-dl { background: none; border: none; color: var(--accent); cursor: pointer; font-size: .75rem; padding: .35rem 0 0; display: inline-flex; align-items: center; gap: .3rem; }
/* Answer-toolbar action (Save / Please review / export) — icon + label, borderless, inline. */
.tj-tool { display: inline-flex; align-items: center; gap: .35rem; background: none; border: none; padding: 0; font-size: .8rem; font-family: inherit; color: var(--accent); cursor: pointer; }
.tj-tool:hover { text-decoration: underline; }
span.tj-tool { cursor: default; }
span.tj-tool:hover { text-decoration: none; }
/* Attribute-qualified, so the confirmation outranks the fill of whatever shape it sits in. */
.tj-copy[data-copy-state="done"] { color: var(--success); background: var(--success-bg); }
.tj-copy[data-copy-state="failed"] { color: var(--danger); background: var(--danger-bg); }
/* Replaces .tj-dl's top-only .35rem, which spaces the row from the block above rather than padding a
   label. The margin-top looks inert beside a CSV sibling but holds the offset in the SQL panel. */
/* Slightly more on the icon side: the glyph's ink fills its box edge to edge, so equal padding reads
   tighter on the left than the lettering does on the right. */
.tj-dl.tj-copy { padding: .25rem .9rem .25rem 1rem; margin-top: .1rem; margin-left: 0.5rem; border-radius: var(--radius-pill); }
/* Exactly one label is in flow at a time, so a control can never read as two states at once. */
.tj-copy .tj-copy-done, .tj-copy .tj-copy-failed { display: none; }
.tj-copy[data-copy-state="done"] .tj-copy-idle,
.tj-copy[data-copy-state="failed"] .tj-copy-idle { display: none; }
.tj-copy[data-copy-state="done"] .tj-copy-done { display: inline; }
.tj-copy[data-copy-state="failed"] .tj-copy-failed { display: inline; }

/* General-purpose card + text used across admin screens (Dashboard, Usage, Review, Chats, Getting Started). */
.tj-block { background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); padding: 1rem 1.25rem; margin-bottom: 1rem; }
.tj-block h4 { color: var(--text-strong); margin: 0 0 .75rem; font-size: 1rem; }
.tj-md { color: var(--text-body); line-height: var(--lh-body); }

/* KPI text styles — shared by chat tiles and the Usage/Dashboard cards. */
.tj-kpi .label { color: var(--text-muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .03em; }
.tj-kpi .value { color: var(--text-strong); font-size: 1.7rem; font-weight: 800; letter-spacing: -.01em; }
.tj-kpi .delta { color: var(--success); font-weight: 600; margin-left: .5rem; font-size: .9rem; }
/* Chat KPI tile box — flows compactly in a row (Usage supplies its own .tj-block box). */
.tj-tile { display: inline-block; vertical-align: top; min-width: 148px; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); padding: .7rem 1rem; margin-right: .6rem; }

.tj-table { width: 100%; border-collapse: collapse; }
.tj-table th, .tj-table td { text-align: left; padding: .45rem .6rem; border-bottom: 1px solid var(--border-subtle); }
.tj-table th { color: var(--text-muted); font-weight: 600; font-size: .85rem; }
.tj-table tbody tr:hover, .tj-table tbody tr:hover { background: var(--surface-page); }
.tj-table th.num, .tj-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.tj-table th.measure, .tj-table td.measure { font-variant-numeric: tabular-nums; }
.tj-audit { color: var(--text-muted); font-size: .85rem; }
.tj-audit summary { cursor: pointer; }
.tj-audit pre { background: var(--surface-sunken); padding: .6rem .8rem; border-radius: var(--radius-sm); overflow-x: auto; }
.tj-exploratory { display: inline-block; background: var(--violet-50); color: var(--accent); border: 1px solid var(--border-default); border-radius: 999px; padding: .1rem .6rem; font-size: .75rem; font-weight: 600; }
/* Which flow answered. Same pill as the one above so the pair sits on one line and neither wraps on a
   phone, but quiet: it is on every reply and the answer is what should be read first. */
.tj-flow { display: inline-block; background: var(--surface-sunken); color: var(--text-muted); border: 1px solid var(--border-default); border-radius: 999px; padding: .1rem .6rem; font-size: .75rem; font-weight: 600; }
.tj-exploratory + .tj-flow, .tj-flow + .tj-flow { margin-left: .35rem; }

/* Conversation layout — user turns right (brand bubble, design §6), Tjoppie left with a small mark. */
.tj-turn-user { display: flex; justify-content: flex-end; align-items: center; gap: .35rem; margin-bottom: 1rem; }
/* Permanent per-message delete (the sensitive-paste escape hatch). Quiet until the turn is hovered;
   kept faintly visible rather than opacity:0 so touch devices (the PWA) can still reach it. */
.tj-msg-delete { flex: none; order: -1; background: none; border: none; cursor: pointer;
    color: var(--text-muted); opacity: .35; padding: .3rem; border-radius: var(--radius-sm); }
.tj-turn-user:hover .tj-msg-delete, .tj-msg-delete:focus-visible { opacity: 1; }
.tj-msg-delete:hover { color: var(--danger); }
/* Design §6 wants the FULL brand gradient behind white text, but its pink start (#FA5CDC) puts white
   text at 1.7:1 — far below AA. Starting the ramp at brand-purple holds the brand feel at ≥5:1, the
   same accessibility-over-exact-colour call as the darkened status tokens (ticket decision 2). */
.tj-turn-user .bubble { background: linear-gradient(115deg, var(--brand-purple) 0%, var(--brand-indigo) 100%);
    border: none; color: #fff;
    border-radius: var(--radius-lg) var(--radius-lg) var(--radius-sm) var(--radius-lg); padding: .55rem .95rem; max-width: 75%;
    /* A flex item's default min-width:auto floors it at its longest token, overriding max-width. */
    min-width: 0; overflow-wrap: anywhere; }
.tj-turn-agent { margin-bottom: 1.25rem; }
.tj-agent-label { display: flex; align-items: center; gap: .45rem; color: var(--text-muted);
    font-size: .68rem; font-weight: 600; margin-bottom: .45rem;
    text-transform: uppercase; letter-spacing: var(--tracking-label); }
.tj-agent-dot { width: 18px; height: 18px; border-radius: 50%; background: var(--brand-gradient); flex: none; }
/* Tighter gap inside a group than between them, so the row reads as answer actions then exports. */
.tj-toolbar { display: flex; align-items: center; gap: .55rem .85rem; margin: 1rem 0 1.25rem; max-width: 860px; flex-wrap: wrap; }
.tj-toolbar-divider { color: var(--border-default); font-size: .8rem; margin: 0 .35rem; }
.tj-toolbar-group-label { color: var(--text-muted); font-size: .8rem; align-self: center; }
/* Applied alongside .tj-tool, on the answer actions only — .tj-tool is shared with the export controls. */
.tj-answer-action { border: 1px solid var(--border-default); background: var(--surface-chip);
    color: var(--text-body); border-radius: var(--radius-md); padding: .4rem .75rem; line-height: 1.2; }
.tj-answer-action.done { background: transparent; border-color: var(--border-subtle); color: var(--text-muted); }
button.tj-answer-action:hover { text-decoration: none; border-color: var(--border-strong); }
/* Without this the global button focus ring reshapes the chip to --radius-sm. */
.tj-answer-action:focus-visible { border-radius: var(--radius-md); }
/* Thinking indicator — pulsing dots in an assistant bubble (design §6). */
.tj-thinking { display: inline-flex; align-items: center; gap: 9px; padding: 12px 14px; align-self: flex-start;
    background: var(--surface-sunken); border-radius: var(--radius-lg);
    color: var(--text-muted); font-size: .75rem; font-weight: 500; }
.vz-dots span { animation: vzPulse 1.2s infinite; }
.vz-dots span:nth-child(2) { animation-delay: .2s; }
.vz-dots span:nth-child(3) { animation-delay: .4s; }

/* Empty state (design §6): greeting + scope line + full-width suggestion buttons. */
.tj-empty-state { max-width: 620px; }
.tj-empty-state .greet { display: flex; align-items: center; gap: .6rem; margin-bottom: .5rem; }
.tj-empty-state .greet .tj-agent-dot { width: 28px; height: 28px; }
.tj-empty-state h3 { font-size: var(--text-lg); font-weight: 500; letter-spacing: var(--tracking-tight); margin: 0; }
.tj-empty-state p { color: var(--text-muted); font-size: var(--text-xs); margin: 0 0 1rem; }
.tj-chips { display: flex; flex-direction: column; gap: 8px; width: 100%; }
.tj-chip { display: flex; align-items: center; gap: 10px; width: 100%; padding: 12px 14px;
    background: var(--surface-card); border: 1.5px solid var(--border-default); border-radius: var(--radius-md);
    font-size: .85rem; color: var(--text-body); cursor: pointer; text-align: left;
    transition: border-color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard); }
.tj-chip:hover { border-color: var(--brand-primary); background: var(--violet-50); }
.tj-chip .lead { display: inline-flex; flex: none; color: var(--brand-primary); }
.tj-chip .tj-chip-text { flex: 1; min-width: 0; }
.tj-chip .trail { display: inline-flex; flex: none; color: var(--text-faint); }
/* The phone shell keeps its compact chip pills (the PWA is deliberately not restyled — decision 5). */
.chat-root-pwa .tj-chips { flex-direction: row; flex-wrap: wrap; }
.chat-root-pwa .tj-chip { width: auto; border-width: 1px; border-radius: 999px; padding: .45rem .9rem; }
.chat-root-pwa .tj-chip .lead, .chat-root-pwa .tj-chip .trail { display: none; }

/* --- Chat layout (design §6): full-height conversation card, centred in the space the two rails
   leave between them (the nav sidebar on the left, the chats rail on the right). --- */
/* Centred at the content cap rather than widened. width:100% is required alongside the auto margins:
   as a flex item of .vz-main, auto cross-axis margins cancel stretch and size the surface to content. */
/* flex:1 + min-height:0 claim the whole bounded shell height; stretch then passes that height to the
   card, so scrolling happens inside it rather than on the page. */
.chat-root-desktop { display: flex; align-items: stretch; padding: 28px 34px 40px;
    flex: 1; min-height: 0; width: 100%; max-width: var(--content-max); margin-inline: auto; }
.chat-root-pwa { display: flex; flex: 1; min-height: 0; }
/* position:relative anchors the .tj-jump pill below: inside .chat-scroll it would be positioned
   against the scroller's padding box and drift out of view with the thread. */
.chat-main { position: relative; flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.chat-card { background: var(--surface-card); border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg); overflow: hidden; }
/* position:relative keeps absolutely-positioned descendants anchored inside the scroller rather than
   to <html>, where they would reintroduce page scroll (VISION-DESIGN §5.4). An answer's own hidden
   boxes are already held by .tj-answer; this covers the surface's other children. */
/* overflow-x is explicit because a box cannot be `visible` on one axis and a scroller on the other:
   overflow-y:auto alone resolves it to `auto`, letting one wide object scroll the whole thread. */
.chat-scroll { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 34px 40px; position: relative; }
.chat-root-pwa .chat-scroll { padding: 1rem .9rem; }
/* Scrollbars inside an answer and on the thread itself: a translucent pill, no track, inset from the
   edge by a transparent border so the thumb reads narrower than the space it occupies. The thread
   scroller is desktop-only, since the phone shell keeps its native overlay bar (decision 5 — the PWA
   is not restyled); the answer objects are one component wherever they render, so they are not
   scoped. Firefox takes the two standard properties; Chromium ignores them once ::-webkit-scrollbar
   is styled, which is the same pairing .vision-nav and .chat-rail-list already use. */
.chat-root-desktop .chat-scroll,
.tj-object-scroll,
.tj-object-xscroll,
.tj-audit pre { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb) transparent; }
.chat-root-desktop .chat-scroll::-webkit-scrollbar,
.tj-object-scroll::-webkit-scrollbar,
.tj-object-xscroll::-webkit-scrollbar,
.tj-audit pre::-webkit-scrollbar { width: 10px; height: 10px; }
.chat-root-desktop .chat-scroll::-webkit-scrollbar-track,
.tj-object-scroll::-webkit-scrollbar-track,
.tj-object-xscroll::-webkit-scrollbar-track,
.tj-audit pre::-webkit-scrollbar-track { background: transparent; }
.chat-root-desktop .chat-scroll::-webkit-scrollbar-thumb,
.tj-object-scroll::-webkit-scrollbar-thumb,
.tj-object-xscroll::-webkit-scrollbar-thumb,
.tj-audit pre::-webkit-scrollbar-thumb { background-color: var(--scrollbar-thumb);
    border: 2px solid transparent; background-clip: content-box; border-radius: var(--radius-pill); }
.chat-root-desktop .chat-scroll::-webkit-scrollbar-thumb:hover,
.tj-object-scroll::-webkit-scrollbar-thumb:hover,
.tj-object-xscroll::-webkit-scrollbar-thumb:hover,
.tj-audit pre::-webkit-scrollbar-thumb:hover { background-color: var(--scrollbar-thumb-hover); }
/* Both axes can be live in one object (a wide, long table), so the corner between them must not fall
   back to the default grey square. */
.tj-object-scroll::-webkit-scrollbar-corner,
.tj-object-xscroll::-webkit-scrollbar-corner { background: transparent; }
.chat-input-row { flex: none; display: flex; gap: 10px; padding: 14px 40px;
    border-top: 1px solid var(--border-subtle); background: var(--surface-card); }
.chat-root-pwa .chat-input-row { padding: .6rem .9rem; }
.chat-input-row .form-control { flex: 1; height: 44px; }

/* "Back to latest" pill (design §6). Hidden until the scroll watcher stamps data-chatjump on <html>,
   which keeps the state outside Blazor's render tree so scrolling never reaches the circuit. */
.tj-jump { display: none; position: absolute; left: 50%; bottom: 88px; transform: translateX(-50%);
    z-index: 2; align-items: center; gap: 7px; height: 36px; padding: 0 15px;
    border: 1px solid var(--border-subtle); border-radius: var(--radius-pill);
    background: var(--surface-card); color: var(--text-body);
    font-family: inherit; font-size: var(--text-sm); white-space: nowrap;
    box-shadow: var(--shadow-md); cursor: pointer;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard); }
:root[data-chatjump="1"] .tj-jump { display: inline-flex; }
/* The hover label is --accent, the foreground token, not --brand-primary, which is a fill behind white
   text and never lightens: on dark --violet-50 it measures 2.30:1. --accent is 5.82:1 light, 5.72:1 dark. */
.tj-jump:hover { background: var(--violet-50); color: var(--accent); }

/* Chats rail — the mirror of the nav sidebar: pinned to the right of the viewport, viewport-tall so
   it runs alongside the topbar as the nav does, and carrying the same ink gradient in both themes so
   the two rails frame the page. It is fixed rather than in-flow because its markup renders inside
   .vz-main, which starts below the topbar and so cannot reach the full height; .vz-main-col reserves
   the width instead (below), so no content sits underneath it. */
.chat-rail {
    position: fixed; top: 0; right: 0; bottom: 0; z-index: 20;
    width: var(--chat-rail-width); overflow: hidden;
    background: var(--brand-gradient-ink); color: #EDE9F7;
    transition: width var(--dur-slow) var(--ease-standard);
}
@media (prefers-reduced-motion: reduce) { .chat-rail { transition: none; } }
/* Inner shell, mirroring .vision-sidebar-inner: the flex column the groups scroll inside. */
.chat-rail-inner { height: 100%; display: flex; flex-direction: column; gap: 12px; min-height: 0;
    padding: 20px 16px 18px; }
/* Header row — the collapse toggle sits at the rail's inner edge, as the nav's does at its own. */
.chat-rail-top { flex: none; display: flex; align-items: center; gap: 10px; }
.chat-rail-top .chat-rail-label { font-size: .68rem; font-weight: 600; color: rgba(255, 255, 255, .5);
    text-transform: uppercase; letter-spacing: var(--tracking-label); white-space: nowrap; }
.chat-rail-toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; flex: none;
    border: none; border-radius: var(--radius-sm); cursor: pointer;
    background: rgba(255, 255, 255, .07); color: rgba(255, 255, 255, .7);
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.chat-rail-toggle:hover { background: rgba(255, 255, 255, .14); color: #fff; }
/* Both glyphs are rendered and swapped in CSS, so the collapse costs no render round-trip. */
.chat-rail-icon-collapse, .chat-rail-icon-expand { display: inline-flex; }
.chat-rail-icon-expand { display: none; }
.chat-rail-new { flex: none; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    width: 100%; height: 44px; border: none; border-radius: var(--radius-md);
    background: var(--brand-gradient); color: #fff; font-family: inherit; font-size: var(--text-sm);
    font-weight: 600; cursor: pointer; box-shadow: var(--shadow-brand);
    transition: filter var(--dur-base) var(--ease-standard); }
.chat-rail-new:hover { filter: brightness(1.07); }
.chat-rail-head { display: flex; align-items: center; gap: 6px; width: 100%; padding: 4px 2px;
    background: none; border: none; cursor: pointer; text-align: left; font-family: inherit;
    color: rgba(255, 255, 255, .5); font-size: .68rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: var(--tracking-label); }
.chat-rail-head:hover { color: #C9C2E0; }
.chat-rail-head .chev { display: inline-flex; transition: transform var(--dur-fast) var(--ease-standard); }
.chat-rail-head .chev.open { transform: rotate(90deg); }
.chat-rail-count { margin-left: auto; min-width: 20px; padding: 1px 7px; border-radius: var(--radius-pill);
    background: rgba(255, 255, 255, .10); color: rgba(255, 255, 255, .62); font-size: .7rem; text-align: center; }
/* Saved is sized to its rows and may claim at most half the rail; Recent takes whatever is left
   (flex-basis 0), so an empty Saved group reserves no space it cannot use. */
.chat-rail-group { display: flex; flex-direction: column; min-height: 0; max-height: 50%; }
.chat-rail-group.fill { flex: 1 1 0; max-height: none; }
/* Each group scrolls in its own box, which is what keeps New chat, both group headers and the
   history link in view. It wraps the rows only — on the group it would scroll the header away.
   The scrollbar takes the nav's ink-rail tint now that the rail carries the ink gradient too. */
.chat-rail-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px;
    flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; padding-right: 2px;
    scrollbar-width: thin; scrollbar-color: rgba(201, 194, 224, .35) transparent; }
.chat-rail-list::-webkit-scrollbar { width: 6px; }
.chat-rail-list::-webkit-scrollbar-thumb { background: rgba(201, 194, 224, .35); border-radius: 3px; }
.chat-rail-empty { color: rgba(255, 255, 255, .5); font-size: .78rem; padding: 6px 2px; }
.chat-rail-row { display: flex; align-items: stretch; gap: 2px; border-radius: var(--radius-md); }
.chat-rail-row:hover { background: rgba(255, 255, 255, .08); }
.chat-rail-row.on { background: rgba(255, 255, 255, .10); }
.chat-rail-row.on .chat-rail-title { color: #fff; font-weight: 600; }
.chat-rail-open { flex: 1; min-width: 0; display: block; padding: 8px 4px 8px 10px;
    background: none; border: none; cursor: pointer; text-align: left; font-family: inherit; }
.chat-rail-title { display: block; font-size: .85rem; font-weight: 500; color: rgba(255, 255, 255, .62);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* .5 is the dimmest white this gradient carries at AA for small text (4.9:1 against its lightest
   stop); the nav's quiet rows use the same value. */
.chat-rail-date { display: block; color: rgba(255, 255, 255, .5); font-size: .7rem; margin-top: 2px; }
/* A chat waiting for its answer, shown in the rail so a user reading another conversation can still see
   the turn is running. Two shapes, and they differ because a skeleton should only ever stand in for text
   that is coming: a brand-new chat has no title yet, so it is a pair of skeleton lines whose sweep reads
   as progress rather than a disabled row; a listed chat already has its title and swaps its date for the
   state in words, whose dots type themselves in and clear. Reduced motion stills both below. */
.chat-rail-row.waiting { background: rgba(255, 255, 255, .06); }
.chat-rail-date.waiting { color: rgba(255, 255, 255, .62); animation: vzRailStateIn .3s ease-out both; }
.chat-rail-dots span { opacity: 0; animation: vzRailType 1.5s ease-in-out infinite; }
.chat-rail-dots span:nth-child(2) { animation-delay: .18s; }
.chat-rail-dots span:nth-child(3) { animation-delay: .36s; }
@keyframes vzRailStateIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes vzRailType {
    0%, 12% { opacity: 0; }
    24%, 72% { opacity: 1; }
    88%, 100% { opacity: 0; }
}
.chat-rail-skeleton-line { display: block; height: .6rem; border-radius: 999px;
    background-color: rgba(255, 255, 255, .10);
    background-image: linear-gradient(90deg, rgba(255, 255, 255, 0) 20%, rgba(255, 255, 255, .20) 50%, rgba(255, 255, 255, 0) 80%);
    background-repeat: no-repeat; background-size: 260% 100%;
    animation: vzRailShimmer 1.5s ease-in-out infinite; }
.chat-rail-skeleton-line.title { width: 72%; margin: .3rem 0; }
.chat-rail-skeleton-line.date { width: 34%; height: .5rem; margin: .24rem 0; animation-delay: .18s; }
@keyframes vzRailShimmer { from { background-position: 160% 0; } to { background-position: -60% 0; } }
@media (prefers-reduced-motion: reduce) {
    .chat-rail-skeleton-line { animation: none; background-image: none; background-color: rgba(255, 255, 255, .16); }
    .chat-rail-date.waiting { animation: none; }
    .chat-rail-dots span { animation: none; opacity: 1; }
}

.chat-rail-bookmark { flex: none; display: inline-flex; align-items: center; padding: 0 10px;
    background: none; border: none; cursor: pointer; color: rgba(255, 255, 255, .5); border-radius: var(--radius-md); }
.chat-rail-bookmark:hover { color: #fff; }
.chat-rail-bookmark.on { color: var(--brand-pink); }
/* Delete sits after the bookmark on every row (design handoff §6: "every rail row carries a delete
   action"). Same quiet white as the bookmark; hover goes white, not --danger — the dark rail's
   danger red fails contrast, and the dialog is where the destructive choice is actually made. */
.chat-rail-delete { flex: none; display: inline-flex; align-items: center; padding: 0 10px 0 4px;
    background: none; border: none; cursor: pointer; color: rgba(255, 255, 255, .5); border-radius: var(--radius-md); }
.chat-rail-delete:hover { color: #fff; }
.chat-rail-foot { flex: none; display: block; text-align: center; font-size: .78rem; font-weight: 600;
    margin-top: 2px; color: rgba(255, 255, 255, .62); }
.chat-rail-foot:hover { color: #fff; }

/* --- Collapsed chats rail (desktop only; the phone shell hides the rail entirely) --- */
@media (min-width: 901px) {
    /* The shell column gives up exactly the rail's width, which is what keeps the topbar clear of the
       rail and leaves the conversation surface centred between the two rails as the width changes. */
    .vz-main-col:has(.chat-root-desktop) {
        padding-right: var(--chat-rail-width);
        transition: padding-right var(--dur-slow) var(--ease-standard);
    }
    :root[data-chatrail="collapsed"] { --chat-rail-width: var(--chat-rail-width-collapsed); }
    /* The strip keeps the toggle and New chat; every label goes, as it does on the nav rail. */
    :root[data-chatrail="collapsed"] .chat-rail-label { display: none; }
    :root[data-chatrail="collapsed"] .chat-rail-top,
    :root[data-chatrail="collapsed"] .chat-rail-new { justify-content: center; gap: 0; }
    :root[data-chatrail="collapsed"] .chat-rail-inner { padding: 20px 12px 18px; }
    :root[data-chatrail="collapsed"] .chat-rail-icon-collapse { display: none; }
    :root[data-chatrail="collapsed"] .chat-rail-icon-expand { display: inline-flex; }
    /* Chat titles are not shown in the strip. visibility is what takes them out of the tab order —
       width and overflow do not — and it is delayed to the end of the collapse so the rail does not
       empty before it has finished narrowing. */
    .chat-rail-group, .chat-rail-foot {
        transition: opacity var(--dur-slow) var(--ease-standard), visibility 0s linear 0s;
    }
    :root[data-chatrail="collapsed"] .chat-rail-group,
    :root[data-chatrail="collapsed"] .chat-rail-foot {
        opacity: 0; visibility: hidden;
        transition: opacity var(--dur-slow) var(--ease-standard), visibility 0s linear var(--dur-slow);
    }
    @media (prefers-reduced-motion: reduce) {
        .vz-main-col:has(.chat-root-desktop) { transition: none; }
        .chat-rail-group, .chat-rail-foot,
        :root[data-chatrail="collapsed"] .chat-rail-group,
        :root[data-chatrail="collapsed"] .chat-rail-foot { transition: none; }
    }
}

@media (max-width: 900px) {
    /* Phones: the rail hides (past chats stay reachable via History in the nav drawer) and the
       conversation card goes full-height edge to edge. */
    .chat-rail { display: none; }
    .chat-root-desktop { padding: 0; gap: 0; display: flex; flex: 1; min-height: 0; }
    .chat-card { height: auto; flex: 1; border: none; border-radius: 0; }
    .tj-turn-user .bubble { max-width: 88%; }
    .vz-page { padding: 1.25rem 1rem; }
    /* Topbar heading compacts on phones (the ink hamburger bar already carries the brand). */
    .vision-topbar { padding: 0 1rem; }
}

/* Getting Started (design §5): welcome ink banner + three ORDERED step cards with persisted
   completion (numbered gradient circle → green check + Done badge). */
.gs-welcome { position: relative; overflow: hidden; padding: 24px; margin-bottom: 18px;
    background: var(--brand-gradient-ink); border-radius: var(--radius-lg); }
.gs-welcome .bleed { position: absolute; right: -70px; top: -60px; width: 260px; opacity: .14; pointer-events: none; }
.gs-welcome h2 { margin: 0 0 6px; color: #fff; font-size: var(--text-lg); font-weight: 500; letter-spacing: var(--tracking-tight); }
.gs-welcome p { margin: 0; color: rgba(255, 255, 255, .7); font-size: .85rem; }
.gs-welcome p b { color: #fff; font-weight: 600; }
.gs-step { display: flex; gap: 14px; align-items: flex-start; background: var(--surface-card);
    border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); padding: 20px; margin-bottom: 12px; }
.gs-step-num { width: 30px; height: 30px; flex: none; border-radius: 50%;
    background: var(--brand-gradient); color: #fff;
    display: inline-flex; align-items: center; justify-content: center; font-size: .9rem; font-weight: 600; }
.gs-step.done .gs-step-num { background: var(--success); }
.gs-step-body { flex: 1; min-width: 0; }
.gs-step-title { display: flex; align-items: center; gap: 8px; color: var(--text-strong);
    font-size: var(--text-base); font-weight: 600; }
.gs-step p { color: var(--text-muted); font-size: 13px; margin: .35rem 0 .8rem; }

/* User-facing Insights (PRODUCT-2618, design §13): metric cards — title, headline number, delta
   badge, 7-bar trend, "Open in Tjoppie". Every colour is a token, so the cards need no dark-block
   overrides. */
.ins-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }
.ins-card { display: flex; flex-direction: column; background: var(--surface-card);
    border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); padding: 18px 20px; }
.ins-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.ins-card-title { color: var(--text-muted); font-size: .85rem; font-weight: 600; margin: 0; }
.ins-delta { font-size: .8rem; font-weight: 700; white-space: nowrap; }
.ins-delta.up { color: var(--success); }
.ins-delta.down { color: var(--danger); }
.ins-delta.flat, .ins-delta.new { color: var(--text-muted); }
.ins-value { color: var(--text-strong); font-size: 1.7rem; font-weight: 700; letter-spacing: var(--tracking-tight); margin: 2px 0 12px; }
.ins-bars { display: flex; align-items: flex-end; gap: 6px; height: 56px; margin-bottom: 14px; }
.ins-bars span { flex: 1; min-height: 4px; border-radius: 6px; background: var(--violet-200); }
.ins-bars span.hi { background: var(--accent); }
.ins-card .vz-btn { width: 100%; }
.ins-asof { color: var(--text-faint); font-size: .78rem; margin-top: 14px; }

/* --- Admin screens (Users, Roles & permissions) --- */
.vz-page { padding: 28px 34px 40px; max-width: var(--content-max); }
.vz-h2 { color: var(--text-strong); margin: 0 0 .25rem; font-size: var(--fs-h1); font-weight: 800; letter-spacing: -.02em; }
.vz-sub { color: var(--text-muted); margin: 0 0 1.25rem; }
.vz-card { background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); padding: 1.1rem 1.25rem; margin-bottom: 1rem; }
.vz-card-title { color: var(--text-strong); font-weight: 700; font-size: 1rem; margin: 0 0 .1rem; }
.vz-card-hint { color: var(--text-muted); font-size: .82rem; margin: 0 0 .9rem; }
/* One of the TARGET conventions in .claude/rules/vision-design-system.md — a colour never goes inline. */
.vz-muted { color: var(--text-muted); }

/* Buttons */
/* One button system. .vz-btn = base (secondary/outline); +.vz-btn-primary = the single primary
   treatment (solid brand). inline-flex + no underline so <a> styled as a button matches <button>. */
.vz-btn { display: inline-flex; align-items: center; justify-content: center; gap: .4rem; border: 1px solid var(--border-default); background: var(--surface-card); color: var(--text-body); border-radius: var(--radius-md); padding: .45rem 1rem; font-size: .85rem; font-family: inherit; font-weight: 600; line-height: 1.2; text-decoration: none; cursor: pointer; }
.vz-btn:hover:not(:disabled) { background: var(--surface-sunken); text-decoration: none; }
/* A disabled button reads as flat grey in every variant. Without the explicit background/colour the
   gradient variant kept its white label over the hover surface, which is white text on near-white. */
.vz-btn:disabled { background: var(--surface-sunken); border-color: var(--border-default); color: var(--text-muted); box-shadow: none; cursor: default; }
.vz-btn-primary { background: var(--brand-primary); border-color: var(--brand-primary); color: #fff; }
.vz-btn-primary:hover { background: var(--brand-primary-hover); color: #fff; }
.vz-btn-danger { border-color: var(--danger); color: var(--danger); }
.vz-btn-danger:hover { background: var(--danger-bg); }
/* Gradient CTA (design: primary CTAs carry the brand gradient + brand shadow). */
/* border-box on the shorthand: the gradient must span the full border-box, or it tiles under the
   1px transparent border and the wrapped ends show as purple/pink edge lines. The border stays so
   the button keeps the same size as its .vz-btn siblings. */
.vz-btn-gradient { background: var(--brand-gradient) border-box; border-color: transparent; color: #fff; box-shadow: var(--shadow-brand); }
/* Re-assert the gradient on hover — the base .vz-btn:hover (higher specificity) would otherwise
   swap it for flat --surface-sunken under the white text. */
.vz-btn-gradient:hover:not(:disabled) { background: var(--brand-gradient) border-box; filter: brightness(1.07); color: #fff; }

/* Status / effect badges */
.vz-badge { display: inline-flex; align-items: center; gap: 6px; padding: .12rem .55rem; border-radius: 999px; font-size: .72rem; font-weight: 600; }
.vz-badge.allow { background: var(--success-bg); color: var(--success); }
.vz-badge.block { background: var(--danger-bg); color: var(--danger); }
.vz-badge.neutral { background: var(--surface-sunken); color: var(--text-muted); }
/* Database Dashboard health states (design §5.1). */
.vz-badge.online { background: var(--success-bg); color: var(--success); }
.vz-badge.lagging { background: var(--warning-bg); color: var(--warning); }
.vz-badge.offline { background: var(--danger-bg); color: var(--danger); }
.vz-badge-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }

/* Tri-state segmented control (Inherit / Grant / Deny) */
.vz-seg { display: inline-flex; border: 1px solid var(--border-default); border-radius: var(--radius-md); overflow: hidden; }
.vz-seg button { border: none; background: var(--surface-card); color: var(--text-body); padding: .3rem .7rem; font-size: .78rem; cursor: pointer; line-height: 1.2; }
.vz-seg button + button { border-left: 1px solid var(--border-subtle); }
.vz-seg button.active { background: var(--brand-primary); color: #fff; font-weight: 600; }
.vz-seg button.active.grant { background: var(--success); }
.vz-seg button.active.deny { background: var(--danger); }
.vz-seg button:disabled { opacity: .5; cursor: not-allowed; }

/* Permission list rows */
.vz-group-title { color: var(--text-muted); font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; margin: 1.1rem 0 .35rem; }
.vz-perm-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .6rem 0; border-bottom: 1px solid var(--border-subtle); }
.vz-perm-row:last-child { border-bottom: none; }
.vz-perm-meta { flex: 1; min-width: 0; }
.vz-perm-name { color: var(--text-strong); font-weight: 600; font-size: .9rem; }
.vz-perm-name .kind { color: var(--text-muted); font-weight: 500; font-size: .72rem; margin-left: .4rem; }
.vz-perm-desc { color: var(--text-muted); font-size: .78rem; }
.vz-perm-controls { display: flex; align-items: center; gap: .75rem; white-space: nowrap; }
.vz-source { color: var(--text-muted); font-size: .72rem; min-width: 82px; text-align: right; }
.vz-notice { background: var(--violet-50); border: 1px solid var(--border-default); color: var(--accent); border-radius: var(--radius-md); padding: .6rem .85rem; font-size: .82rem; margin-bottom: .9rem; }
.vz-toast { border-radius: var(--radius-md); padding: .5rem .85rem; font-size: .85rem; margin-bottom: .75rem; }
.vz-toast.ok { background: var(--success-bg); color: var(--success); }
.vz-toast.err { background: var(--danger-bg); color: var(--danger); }

/* Confirmation modal (ConfirmDialog.razor) — destructive-action guard, themed via tokens. */
.vz-modal-scrim { position: fixed; inset: 0; z-index: 100; background: rgba(15, 12, 30, .5);
    display: flex; align-items: center; justify-content: center; padding: 1rem; }
.vz-modal { background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
    padding: 1.25rem 1.4rem; width: 100%; max-width: 420px; box-shadow: 0 20px 60px rgba(0, 0, 0, .3); }
.vz-modal-title { margin: 0 0 .5rem; font-size: 1.05rem; font-weight: 700; color: var(--text-strong); }
.vz-modal-body { margin: 0 0 1.25rem; color: var(--text-body); font-size: .9rem; line-height: var(--lh-body); }
.vz-modal-actions { display: flex; justify-content: flex-end; gap: .6rem; }

/* --- Auth screens (login / 2FA / reset / access request) — the centred 460px card on the page
   surface with the motion mark bleeding off the top-left (design handoff §1/§3). Markup shell:
   Pages/Account/_AuthLayout.cshtml. --- */
.auth-bleed { position: fixed; left: -110px; top: -90px; width: 320px; opacity: .14;
    pointer-events: none; user-select: none; }
.auth-page { min-height: 100vh; display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: 14px; padding: 40px 16px; position: relative; }
.auth-card { width: 100%; max-width: 460px; background: var(--surface-card);
    border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg); padding: 36px; }
/* Animated brand mark — transparent background, never on a white pill (design §1). */
.auth-logo { height: 30px; width: auto; display: block; margin: 0 auto 22px; }
.auth-title { margin: 0 0 4px; font-size: var(--text-lg); font-weight: 500; line-height: 1.2;
    color: var(--text-strong); letter-spacing: var(--tracking-tight); }
.auth-sub { margin: 0 0 22px; font-size: 13px; line-height: 1.6; color: var(--text-muted); }
.auth-label { display: block; color: var(--text-strong); font-size: var(--text-xs); font-weight: 600; margin-bottom: 7px; }
.auth-field { margin-bottom: 14px; }
.auth-card .form-control { height: 46px; border: 1.5px solid var(--border-default);
    border-radius: var(--radius-md); font-size: var(--text-sm); }
.auth-card .form-control:focus { border-color: var(--brand-primary); box-shadow: 0 0 0 3px var(--focus-ring); }
.auth-card textarea.form-control { height: auto; min-height: 84px; }
.auth-pw { position: relative; }
.auth-pw .form-control { padding-right: 46px; }
.auth-eye { position: absolute; right: 5px; top: 5px; width: 36px; height: 36px;
    display: inline-flex; align-items: center; justify-content: center;
    border: none; background: none; color: var(--text-faint); cursor: pointer; border-radius: var(--radius-sm); }
.auth-eye:hover { color: var(--text-body); }
.auth-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 4px 0 18px; }
.auth-remember { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-body); margin: 0; }
.auth-remember input { width: 16px; height: 16px; accent-color: var(--brand-primary); }
.auth-link { font-size: 12.5px; font-weight: 600; }
.auth-submit { width: 100%; height: 46px; display: inline-flex; align-items: center; justify-content: center;
    gap: 9px; border: none; border-radius: var(--radius-md); background: var(--brand-gradient); color: #fff;
    font-family: inherit; font-size: var(--text-sm); font-weight: 600; cursor: pointer;
    box-shadow: var(--shadow-brand); transition: filter var(--dur-base) var(--ease-standard); }
.auth-submit:hover:not(:disabled) { filter: brightness(1.07); }
.auth-submit:disabled { opacity: .55; cursor: not-allowed; }
@keyframes vzSpin { to { transform: rotate(360deg); } }
.auth-spinner { width: 18px; height: 18px; border-radius: 50%; border: 2px solid rgba(255, 255, 255, .35);
    border-top-color: #fff; animation: vzSpin 640ms linear infinite; display: none; }
.auth-submit.loading .auth-spinner { display: inline-block; }
.auth-helper { margin: 22px 0 0; text-align: center; font-size: 12.5px; color: var(--text-muted); }
.auth-helper a { font-weight: 600; }
.auth-error-field { display: flex; align-items: center; gap: 6px; margin-top: 6px;
    font-size: var(--text-xs); font-weight: 500; color: var(--danger); }
.auth-banner { display: flex; align-items: flex-start; gap: 11px; padding: 13px 15px;
    border-radius: var(--radius-md); font-size: var(--text-xs); font-weight: 500; line-height: 1.55;
    margin-bottom: 18px; }
.auth-banner-error { background: var(--danger-bg); color: var(--danger); border: 1px solid rgba(226, 59, 93, .28); }
.auth-banner-success { background: var(--success-bg); color: var(--success); border: 1px solid rgba(31, 157, 107, .28); }
.auth-back { display: inline-flex; align-items: center; gap: 6px; margin-bottom: 16px;
    font-size: var(--text-sm); font-weight: 600; text-decoration: none; }
.auth-confirm { text-align: center; padding: 8px 0; }
.auth-confirm-icon { width: 56px; height: 56px; margin: 0 auto 16px; border-radius: var(--radius-pill);
    display: flex; align-items: center; justify-content: center; }
.auth-confirm-icon.ok { background: var(--success-bg); color: var(--success); }
.auth-confirm-icon.mail { background: var(--info-bg); color: var(--brand-primary); }
.auth-confirm h2 { font-size: var(--text-md); font-weight: 500; margin: 0 0 6px; letter-spacing: var(--tracking-tight); }
.auth-confirm p { margin: 0 auto 14px; max-width: 32ch; font-size: 13px; color: var(--text-muted); }
.auth-ref { display: inline-flex; align-items: center; gap: 8px; padding: 7px 14px;
    border-radius: var(--radius-pill); background: var(--surface-sunken);
    font-size: var(--text-xs); font-weight: 600; color: var(--text-body); white-space: nowrap; }
.auth-ref span { color: var(--brand-primary); }
.auth-foot { margin: 0; font-size: var(--text-xs); color: var(--text-faint); }

/* --- Branded 404, retail-scan treatment (design §9): full-screen ink, headline + till-lookup card.
   Fixed ink colours on purpose — a till slip does not theme-flip (the card stays --neutral-0). --- */
.vz-404 { min-height: 100dvh; display: flex; align-items: center; justify-content: center; gap: 56px;
    flex-wrap: wrap; padding: 48px 32px; background: var(--brand-gradient-ink); position: relative; overflow: hidden; }
.vz-404-bleed { position: absolute; right: -90px; top: -70px; width: 320px; opacity: .12; pointer-events: none; }
.vz-404-left { max-width: 440px; position: relative; z-index: 1; }
.vz-404-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 18px;
    color: rgba(255, 255, 255, .75); font-size: 11px; font-weight: 600;
    letter-spacing: var(--tracking-label); text-transform: uppercase; }
.vz-404-brand img { height: 22px; width: auto; }
.vz-404-code { font-size: clamp(72px, 12vw, 120px); font-weight: 700; line-height: 1;
    letter-spacing: var(--tracking-tight); margin-bottom: 10px;
    background: var(--brand-gradient); -webkit-background-clip: text; background-clip: text; color: transparent; }
.vz-404-left h1 { color: #fff; font-size: var(--text-2xl); font-weight: 500;
    letter-spacing: var(--tracking-tight); line-height: 1.2; margin: 0 0 14px; }
.vz-404-left p { color: rgba(255, 255, 255, .7); font-size: var(--text-sm); line-height: 1.65; margin: 0 0 24px; }
.vz-404-actions { display: flex; gap: 12px; flex-wrap: wrap; }
.vz-404-retry { background: rgba(255, 255, 255, .07); border-color: rgba(255, 255, 255, .25); color: #fff; }
.vz-404-retry:hover { background: rgba(255, 255, 255, .14); color: #fff; }
.vz-404-card { width: 100%; max-width: 400px; background: var(--neutral-0); border-radius: var(--radius-lg);
    overflow: hidden; box-shadow: var(--shadow-xl); position: relative; z-index: 1; }
.vz-404-card-head { background: var(--brand-gradient); color: #fff; padding: 12px 18px;
    font-size: 11px; font-weight: 600; letter-spacing: var(--tracking-label); text-transform: uppercase; }
.vz-404-card-body { padding: 18px; }
.vz-404-matched { background: var(--neutral-900); border-radius: var(--radius-md); padding: 14px 16px; margin-bottom: 16px; }
.vz-404-matched-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.vz-404-matched .lbl { color: rgba(255, 255, 255, .6); font-size: 10.5px; font-weight: 600;
    letter-spacing: var(--tracking-label); text-transform: uppercase; }
.vz-404-matched .status { display: inline-flex; align-items: center; gap: 6px; color: var(--brand-pink);
    font-size: 10.5px; font-weight: 600; letter-spacing: var(--tracking-label); text-transform: uppercase; }
.vz-404-matched .status .dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.vz-404-matched .zero { color: #fff; font-size: 40px; font-weight: 700; line-height: 1.1; }
/* The scanned barcode — drawn with a repeating gradient, no image. */
.vz-404-barcode { height: 44px; width: 60%; margin-bottom: 16px;
    background: repeating-linear-gradient(90deg,
        var(--neutral-900) 0 2px, transparent 2px 4px, var(--neutral-900) 4px 7px, transparent 7px 9px,
        var(--neutral-900) 9px 10px, transparent 10px 14px, var(--neutral-900) 14px 18px, transparent 18px 20px); }
.vz-404-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
    padding: 10px 0; border-top: 1px solid var(--neutral-200); font-size: var(--text-sm); }
.vz-404-row span { color: var(--neutral-500); }
.vz-404-row b { color: var(--neutral-900); font-variant-numeric: tabular-nums; }
.vz-404-row i { color: var(--neutral-500); }
.vz-404-note { margin-top: 12px; padding: 10px 12px; border-radius: var(--radius-md);
    background: var(--neutral-100); color: var(--neutral-600); font-size: var(--text-xs); line-height: 1.5; }

/* --- Coming-soon panel (design §8): the ONE place looping decorative motion is intentional.
   prefers-reduced-motion stills the ticker, tiles and progress fill. --- */
.cs-panel { position: relative; overflow: hidden; background: var(--brand-gradient-ink);
    border-radius: var(--radius-xl); box-shadow: var(--shadow-xl); }
.cs-bleed { position: absolute; width: 260px; opacity: .14; pointer-events: none; }
.cs-bleed.tl { left: -80px; top: -70px; }
.cs-bleed.br { right: -80px; bottom: -70px; }
.cs-ticker { overflow: hidden; border-bottom: 1px solid rgba(255, 255, 255, .08); padding: 8px 0; }
.cs-ticker-track { display: inline-block; white-space: nowrap; animation: csTicker 22s linear infinite;
    color: rgba(255, 255, 255, .42); font-size: 10.5px; font-weight: 600;
    letter-spacing: var(--tracking-label); text-transform: uppercase; }
@keyframes csTicker { to { transform: translateX(-50%); } }
.cs-body { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center;
    gap: 22px; padding: 48px 28px 56px; text-align: center; }
.cs-pill { display: inline-flex; align-items: center; gap: 8px; padding: 6px 14px;
    border-radius: var(--radius-pill); background: rgba(250, 92, 220, .14);
    border: 1px solid rgba(250, 92, 220, .4); color: #fff; font-size: var(--text-xs); font-weight: 600; }
.cs-pill .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--brand-pink);
    animation: csBlink 1.2s ease-in-out infinite; }
@keyframes csBlink { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
.cs-tiles { display: flex; align-items: flex-end; gap: 8px; height: 58px; }
.cs-tiles span { width: 22px; border-radius: var(--radius-sm); background: var(--brand-gradient);
    animation: csBob 1.5s ease-in-out infinite; }
.cs-tiles span:nth-child(1) { height: 22px; }
.cs-tiles span:nth-child(2) { height: 34px; animation-delay: 140ms; }
.cs-tiles span:nth-child(3) { height: 46px; animation-delay: 280ms; }
.cs-tiles span:nth-child(4) { height: 34px; animation-delay: 420ms; }
.cs-tiles span:nth-child(5) { height: 22px; animation-delay: 560ms; }
@keyframes csBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-12px); } }
.cs-name { font-size: clamp(44px, 7vw, 88px); font-weight: 700; line-height: 1.05;
    letter-spacing: var(--tracking-tight);
    background: var(--brand-gradient); -webkit-background-clip: text; background-clip: text; color: transparent; }
.cs-progress { width: min(420px, 80%); height: 8px; border-radius: var(--radius-pill);
    background: rgba(255, 255, 255, .12); overflow: hidden; }
.cs-progress .bar { height: 100%; width: 72%; border-radius: var(--radius-pill);
    background: var(--brand-gradient); animation: csFill 2.6s var(--ease-standard) both; }
@keyframes csFill { from { width: 0; } to { width: 72%; } }
.cs-progress-label { display: inline-flex; align-items: center; gap: 6px;
    color: rgba(255, 255, 255, .7); font-size: var(--text-xs); font-weight: 500; }
@media (prefers-reduced-motion: reduce) {
    .cs-ticker-track, .cs-pill .dot, .cs-tiles span, .cs-progress .bar { animation: none; }
}

/* --- Routing interstitial ("Taking you to Tjoppie", design §2): full-screen ink, animated mark,
   pulsing dots. Shown only while the target route is genuinely loading — no artificial delay. --- */
.routing-body { background: var(--brand-gradient-ink); }
.routing-screen { min-height: 100vh; display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: 22px; }
.routing-mark { height: 60px; width: auto; }
.routing-text { color: #fff; font-size: var(--text-lg); font-weight: 500; letter-spacing: var(--tracking-tight); }
@keyframes vzPulse { 0%, 100% { opacity: .2; } 50% { opacity: 1; } }
.routing-dots span { animation: vzPulse 1.2s infinite; }
.routing-dots span:nth-child(2) { animation-delay: .2s; }
.routing-dots span:nth-child(3) { animation-delay: .4s; }

/* ============================================================================
   Dark theme (design §6 — "support light + dark; the token set is light-first,
   derive a dark ramp"). Only the surface/text/border/semantic/accent tokens are
   re-pointed; brand hues, radii, fonts and the ink sidebar gradient are shared.
   Applied when the OS prefers dark (auto) unless the user forced light, OR when
   the user explicitly toggles dark. Contrast for every text pair verified ≥5:1
   (a11y-check.mjs --dark). DevExpress components are themed via the blazing-dark
   stylesheet swap in _Layout.cshtml.
   ============================================================================ */
/* CSS cannot express "these two selectors share a body" across a media-query boundary, so the
   auto block (OS prefers dark, no explicit choice) and the explicit block (data-theme="dark")
   MUST stay byte-identical declaration-for-declaration. a11y-check.mjs fails if they diverge.
   A new colour-bearing token needs its dark value added to BOTH blocks. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --accent: #B98BF0;
        --brand-primary-hover: #8B44E4;
        --surface-page: #14111E;
        --surface-card: #211C33;
        --surface-sunken: #0E0C17;
        --surface-chip: #2C2542;
        --violet-50: #2A2145;
        --violet-100: #3A2E5B;
        --violet-200: #4A3B72;
        --pink-50: #3A1830;
        --pink-100: #45203C;
        --text-strong: #F4F1FB;
        --text-body: #D9D4E8;
        --text-muted: #ABA4C2;
        --text-faint: #918AA8;
        --text-on-accent: #1E1A34;
        --border-subtle: #322C48;
        --border-default: #423B5C;
        --border-strong: #5C5479;
        --scrollbar-thumb: rgba(237, 233, 247, .22);
        --scrollbar-thumb-hover: rgba(237, 233, 247, .40);
        --danger: #FF7A93;
        --danger-bg: #3A1220;
        --success: #46D9A0;
        --success-bg: #123528;
        --warning: #F0B45C;
        --warning-bg: #3A2A12;
        --info: #B98BF0;
        --info-bg: #2A2145;
    }
}
:root[data-theme="dark"] {
    --accent: #B98BF0;
    --brand-primary-hover: #8B44E4;
    --surface-page: #14111E;
    --surface-card: #211C33;
    --surface-sunken: #0E0C17;
    --surface-chip: #2C2542;
    --violet-50: #2A2145;
    --violet-100: #3A2E5B;
    --violet-200: #4A3B72;
    --pink-50: #3A1830;
    --pink-100: #45203C;
    --text-strong: #F4F1FB;
    --text-body: #D9D4E8;
    --text-muted: #ABA4C2;
    --text-faint: #918AA8;
    --text-on-accent: #1E1A34;
    --border-subtle: #322C48;
    --border-default: #423B5C;
    --border-strong: #5C5479;
    --scrollbar-thumb: rgba(237, 233, 247, .22);
    --scrollbar-thumb-hover: rgba(237, 233, 247, .40);
    --danger: #FF7A93;
    --danger-bg: #3A1220;
    --success: #46D9A0;
    --success-bg: #123528;
    --warning: #F0B45C;
    --warning-bg: #3A2A12;
    --info: #B98BF0;
    --info-bg: #2A2145;
}

/* Token-drive Bootstrap inputs so they adapt to the theme (they ship white-bg/dark-text baked in,
   which would stay white in dark mode). Visually identical in light; correct in dark. */
.form-control, .form-select {
    background-color: var(--surface-card);
    color: var(--text-body);
    border-color: var(--border-default);
}
.form-control:focus, .form-select:focus {
    background-color: var(--surface-card);
    color: var(--text-body);
    border-color: var(--brand-primary);
    box-shadow: 0 0 0 .15rem rgba(122, 47, 216, .25);
}
.form-control::placeholder { color: var(--text-faint); }

/* Sidebar theme toggle (light/dark segmented control, on the ink gradient). */
.vz-theme-toggle { display: inline-flex; border: 1px solid #4A4368; border-radius: var(--radius-md); overflow: hidden; margin-top: 1.1rem; }
.vz-theme-toggle button { display: inline-flex; align-items: center; gap: .35rem; background: none; border: none; color: #C9C2E0; padding: .35rem .7rem; font-size: .78rem; font-family: inherit; line-height: 1; cursor: pointer; }
.vz-theme-toggle button + button { border-left: 1px solid #4A4368; }
.vz-theme-toggle button.on { background: rgba(122, 47, 216, .5); color: #fff; }

/* ============================================================================
   PWA shell (/pwa) — the installable mobile shell: fixed top bar, chat/history
   content, bottom tab bar. All colours come from tokens, so light/dark follows
   the same theme system as the desktop shell; the top bar sits on the fixed ink
   gradient (like .vz-topbar) so it never flips light. env(safe-area-inset-*)
   keeps the bars clear of phone notches/home indicators in standalone display.
   ============================================================================ */
.pwa-shell { display: flex; flex-direction: column; height: 100dvh; background: var(--surface-page); }
.pwa-topbar { flex: none; display: flex; align-items: center; justify-content: space-between; gap: .75rem; padding: .55rem .9rem; padding-top: calc(.55rem + env(safe-area-inset-top)); background: var(--brand-gradient-ink); color: #fff; }
.pwa-brand { display: flex; align-items: center; gap: .5rem; font-family: var(--font-brand); font-weight: 700; font-size: 1.02rem; }
.pwa-brand img { height: 22px; width: auto; }
.pwa-topbar-actions { display: flex; align-items: center; gap: .15rem; }
.pwa-topbar-actions form { display: flex; }
.pwa-topbar-actions button { background: none; border: none; color: #fff; padding: .45rem; border-radius: var(--radius-sm); cursor: pointer; display: flex; align-items: center; }
.pwa-main { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.pwa-tabbar { flex: none; display: flex; border-top: 1px solid var(--border-subtle); background: var(--surface-card); padding-bottom: env(safe-area-inset-bottom); }
.pwa-tabbar a { flex: 1; display: flex; flex-direction: column; align-items: center; gap: .15rem; padding: .5rem 0 .4rem; font-size: .7rem; font-weight: 600; color: var(--text-muted); text-decoration: none; }
.pwa-tabbar a.active { color: var(--accent); }

/* Compact PWA chat top bar (new chat + scope tag box). The compact scroll/input paddings live with
   the .chat-* layout rules above — no !important needed since the inline styles were promoted. */
.pwa-chat-top { display: flex; align-items: center; gap: .5rem; padding: .6rem .9rem; border-bottom: 1px solid var(--border-subtle); background: var(--surface-card); }

/* PWA history — tap-to-open card list. */
.pwa-history { flex: 1; overflow-y: auto; padding: 1rem .9rem; }
.pwa-kpis { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; margin-bottom: 1rem; }
.pwa-history-card { display: block; width: 100%; text-align: left; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: .6rem .75rem; margin-bottom: .5rem; cursor: pointer; font-family: inherit; }
.pwa-history-card .title { color: var(--text-body); font-size: .9rem; font-weight: 600; margin-bottom: .2rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pwa-history-card .meta { display: flex; flex-wrap: wrap; gap: .25rem .75rem; color: var(--text-muted); font-size: .72rem; }

/* PWA saved — a history card plus a trailing unpin button per row. */
.pwa-saved-row { display: flex; align-items: stretch; gap: .4rem; }
.pwa-saved-row .pwa-history-card { flex: 1; min-width: 0; }
.pwa-saved-unpin { flex: none; margin-bottom: .5rem; padding: 0 .7rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); color: var(--text-muted); cursor: pointer; }

/* Circuit-loss overlay: blazor.server.js toggles components-reconnect-* classes on the
   #components-reconnect-modal element in PwaLayout; vision-pwa.js reloads on failed/rejected. */
.pwa-reconnect { display: none; }
.pwa-reconnect.components-reconnect-show,
.pwa-reconnect.components-reconnect-failed,
.pwa-reconnect.components-reconnect-rejected { display: flex; position: fixed; inset: 0; z-index: 2000; align-items: center; justify-content: center; background: rgba(30, 26, 52, .55); }
.pwa-reconnect-card { display: flex; align-items: center; gap: .5rem; background: var(--surface-card); color: var(--text-body); border-radius: var(--radius-md); padding: .8rem 1.1rem; font-weight: 600; }
