a, .btn-link {
    color: var(--visitor-secondary, #3B82F6);
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 2px var(--visitor-surface, #fff), 0 0 0 4px color-mix(in srgb, var(--visitor-secondary, #3B82F6) 55%, transparent);
}

.content {
    padding-top: 1.1rem;
}

/* LOAD-BEARING — not template cruft, do not tidy away.
   Routes.razor carries <FocusOnNavigate Selector="h1" />, so the framework gives the h1
   tabindex="-1" and focuses it on every routed load for assistive tech. The heading then matches
   :focus-visible, and Chrome's UA rule (:focus-visible { outline: auto 1px }) draws a black box
   around it — including around the oversized product noun on every pre-auth page, on first paint,
   with nothing focusable inside it. This rule is the only thing suppressing that.

   It is easy to assume Vahary.Branding covers this. It does not, and cannot: 0.6.0 scoped the auth
   shell's own 2px violet ring to interactive elements, which removes the PACKAGE's ring, but a
   package can never remove a UA ring. Measured with this rule disabled at runtime, the focused noun
   comes back outline-style:auto / 1px / rgb(16,16,16). visitor-auth-supplement.css used to carry a
   duplicate suppression; it was removed once this was shown to be doing the work, so there is now
   exactly one. (Upstream fix pending: h1[tabindex="-1"]:focus{outline:none} in the shared sheet.) */
h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--v-color-status-healthy);
}

.invalid {
    outline: 1px solid var(--v-color-status-fault);
}

.validation-message {
    color: var(--v-color-status-fault);
}

.blazor-error-boundary {
    background: var(--v-color-status-fault);
    padding: 1rem;
    color: var(--v-color-status-fault-on);
}

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

.darker-border-checkbox.form-check-input {
    border-color: var(--v-color-border-strong);
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}
/* ===== VMS design system — rebound onto @vahary/tokens (vahary.css → --v-*) =====
   The --visitor-* names stay (components + per-tenant ThemeCss overrides keep working) but their
   DEFAULT values now come from the portfolio tokens. --v-* flip light/dark via data-v-theme,
   which App.razor keeps in lockstep with VIM's data-theme — so the old per-theme --visitor-* blocks
   are gone; dark values come from the tokens. Status scheme is classic-green (see App.razor). */
:root {
    /* Brand (still overridden per-tenant by ThemeCss). */
    --visitor-primary: var(--v-color-interactive-accent);
    --visitor-secondary: var(--v-color-text-link);
    --visitor-accent: var(--v-color-interactive-accent);
    /* The ink for text/icons sitting ON a --visitor-primary fill. It was never defined here, so every
       `var(--visitor-on-primary, #fff)` in this file fell through to the literal white — invisible for
       as long as the accent stayed dark. It no longer does: the token set moved the DARK-theme accent
       from #8B5CF6 to #A78BFA, and white on #A78BFA is 2.72:1 (it was 4.23:1). --v-color-text-on-accent
       is the token that answers this pairing and flips with the theme — #F2F3F1 on the light accent
       (8.07:1) and #181E24 on the dark one (6.17:1). Tenant themes still override this variable (and
       --visitor-primary with it) from TenantThemeCss.Build, whose ReadableText picks the ink per tenant
       colour; that block is emitted through HeadOutlet, i.e. after this stylesheet, so it still wins.
       The `, #fff` fallbacks at the call sites are now dead code kept only as belt-and-braces. */
    --visitor-on-primary: var(--v-color-text-on-accent);
    --visitor-sidebar: #181E24;          /* brand ink chrome — theme-invariant dark rail */
    --visitor-sidebar-text: #F2F3F1;
    --visitor-font: var(--v-font-body);

    /* Surfaces. */
    --visitor-bg: var(--v-color-surface-base);
    --visitor-surface: var(--v-color-surface-raised);
    --visitor-surface-2: var(--v-color-surface-base);   /* subtle raised/zebra/hover fill */
    --visitor-surface-3: var(--v-color-surface-sunken); /* table header / inset panels */
    --visitor-border: var(--v-color-border-hairline);
    --visitor-border-strong: var(--v-color-border-strong);
    --visitor-text: var(--v-color-text-primary);
    --visitor-muted: var(--v-color-text-secondary);
    /* Emphasis text/number colour — the accent used AS TEXT, never as a fill. That is what
       --v-color-interactive-accent-text exists for: it is the accent variant held to the 4.5:1 body-text
       bar, where the bare -accent token is only ever held to the 3:1 UI bar because its job is to be a
       fill. They happen to carry the same value in both themes today (#5B21B6 / #A78BFA), so this is a
       repoint with no visual change — but it is now pinned to the token that cannot drift away from
       readable-as-text. */
    --visitor-brand-ink: var(--v-color-interactive-accent-text);

    /* Semantic colours (foreground / tinted background) → canonical status tokens. */
    --visitor-success: var(--v-color-status-healthy); --visitor-success-bg: var(--v-color-status-healthy-subtle);
    --visitor-warning: var(--v-color-status-attention); --visitor-warning-bg: var(--v-color-status-attention-subtle);
    --visitor-danger:  var(--v-color-status-fault);   --visitor-danger-bg:  var(--v-color-status-fault-subtle);
    --visitor-info:    var(--v-color-status-neutral);  --visitor-info-bg:    var(--v-color-status-neutral-subtle);

    /* Text ON a tinted background. The bare --visitor-warning / --visitor-danger are the STRONG
       colours, meant for borders, icons and marks on the page background — put them on their own
       subtle tint and they fail contrast: #D97706 on #FEF3C7 is 2.86:1, below even the large-text
       floor, and #DC2626 on #FEE2E2 is 3.95:1. Seventeen rules did exactly that, and between them they
       cover every "act now" state in the product: pending, escalated, overdue, denied, expired,
       flagged, blocked, expiring certification, restricted. The states a front-desk operator must not
       miss were the least readable text on the screen, in the default theme.
       The design system already had the answer — -on variants exist for precisely this pairing
       (#92400E on #FEF3C7 = 6.37:1, #991B1B on #FEE2E2 = 6.80:1) and were simply never mapped here.
       (CC-12.) */
    --visitor-warning-on: var(--v-color-status-attention-on);
    --visitor-danger-on:  var(--v-color-status-fault-on);
    --visitor-success-on: var(--v-color-status-healthy-on);
    --visitor-info-on:    var(--v-color-status-neutral-on);

    /* Type scale. */
    --text-xs: .75rem; --text-sm: .8125rem; --text-base: .9375rem; --text-md: 1rem;
    --text-lg: 1.125rem; --text-xl: 1.375rem; --text-2xl: 1.75rem; --text-3xl: 2.25rem; --text-4xl: 3rem;

    /* Radius scale. */
    --radius-sm: .375rem; --radius-md: .5rem; --radius-lg: .75rem; --radius-xl: 1rem; --radius-pill: 999px;

    /* Elevation — layered, slightly cool-tinted. */
    --shadow-sm: 0 1px 2px rgba(15,23,42,.06), 0 1px 1px rgba(15,23,42,.04);
    --shadow-md: 0 1px 2px rgba(15,23,42,.06), 0 4px 12px rgba(15,23,42,.08);
    --shadow-lg: 0 4px 12px rgba(15,23,42,.08), 0 16px 32px rgba(15,23,42,.12);
    --shadow-xl: 0 12px 32px rgba(15,23,42,.14), 0 32px 64px rgba(15,23,42,.18);

    --visitor-transition: 160ms cubic-bezier(.4, 0, .2, 1);
    --visitor-skeleton-base: var(--v-color-surface-sunken); --visitor-skeleton-hi: var(--v-color-surface-raised);
    color-scheme: light;
}

/* Native control colour-scheme follows the resolved theme (tokens carry the palette). */
@media (prefers-color-scheme: dark) { :root:not([data-v-theme="light"]) { color-scheme: dark; } }
:root[data-v-theme="dark"] { color-scheme: dark; }
:root[data-v-theme="light"] { color-scheme: light; }

* { box-sizing: border-box; }
body { font-family: var(--visitor-font); color: var(--visitor-text); background: var(--visitor-bg);
    -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; font-feature-settings: "cv11", "ss01"; }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
}

.visitor-app { display: flex; min-height: 100vh; }
.visitor-sidebar { width: 244px; color: var(--visitor-sidebar-text); display: flex; flex-direction: column;
    background: linear-gradient(180deg, color-mix(in srgb, var(--visitor-sidebar) 88%, #fff) 0%, var(--visitor-sidebar) 55%, color-mix(in srgb, var(--visitor-sidebar) 82%, #000) 100%);
    box-shadow: inset -1px 0 0 rgba(255,255,255,.06), var(--shadow-md); }
.visitor-brand { font-weight: 800; font-size: 1.2rem; letter-spacing: -.01em; padding: 1.1rem 1.25rem; border-bottom: 1px solid rgba(255,255,255,.12); }
/* --visitor-panic-top: the admin shell's topbar is sticky at top:0, so the alarm banner sticks
   directly beneath it rather than over the global search and sign-out. The guard shell has no
   sticky header and inherits the 0 default. */
.visitor-main { flex: 1; display: flex; flex-direction: column; min-width: 0; --visitor-panic-top: 3.75rem; }
.visitor-topbar { display: flex; align-items: center; gap: 1rem; padding: .75rem 1.25rem; background: color-mix(in srgb, var(--visitor-surface) 88%, transparent);
    backdrop-filter: saturate(140%) blur(8px); border-bottom: 1px solid var(--visitor-border); position: sticky; top: 0; z-index: 40; }
.visitor-spacer { flex: 1; }
.visitor-user { color: var(--visitor-muted); font-size: var(--text-sm); font-weight: 500; }
.visitor-content { padding: 1.75rem 2rem; max-width: 1680px; width: 100%; margin: 0 auto; }

/* Skip-to-content link — visible only when focused. */
.visitor-skip { position: absolute; left: .5rem; top: -3rem; z-index: 2000; background: var(--visitor-primary); color: var(--visitor-on-primary, #fff);
    padding: .5rem .9rem; border-radius: var(--radius-md); text-decoration: none; transition: top var(--visitor-transition); }
.visitor-skip:focus { top: .5rem; }

/* Mobile nav: off-canvas sidebar toggled by a real button (body.visitor-nav-open via window.vmsNav). */
.visitor-hamburger { display: none; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem; font-size: 1.3rem; line-height: 1;
    cursor: pointer; border: 1px solid var(--visitor-border); border-radius: var(--radius-md); background: var(--visitor-surface); color: var(--visitor-text); }
.visitor-hamburger:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--visitor-secondary) 35%, transparent); }
.visitor-nav-backdrop { display: none; border: 0; padding: 0; }
@media (max-width: 768px) {
    .visitor-hamburger { display: inline-flex; }
    .visitor-sidebar { position: fixed; top: 0; bottom: 0; left: 0; z-index: 1100; transform: translateX(-100%); transition: transform .2s ease; }
    body.visitor-nav-open .visitor-sidebar { transform: translateX(0); box-shadow: var(--shadow-xl); }
    body.visitor-nav-open .visitor-nav-backdrop { display: block; position: fixed; inset: 0; z-index: 1050; background: rgba(0,0,0,.45); }
}
.visitor-muted { color: var(--visitor-muted); }

/* Desktop collapsible rail (html.visitor-rail) — icons-only sidebar, persisted in localStorage. */
.visitor-brand { display: flex; align-items: center; }
.visitor-brand-mark { display: none; }
.visitor-rail-toggle { margin-top: auto; display: flex; align-items: center; gap: .65rem; padding: .55rem .85rem; margin: .5rem;
    border: 0; background: rgba(255,255,255,.06); color: var(--visitor-sidebar-text); border-radius: var(--radius-md);
    cursor: pointer; opacity: .75; font: inherit; font-size: var(--text-sm); transition: background var(--visitor-transition), opacity var(--visitor-transition); }
.visitor-rail-toggle:hover { background: rgba(255,255,255,.14); opacity: 1; }
.visitor-rail-chevron { display: inline-block; font-size: 1.2rem; line-height: 1; transition: transform var(--visitor-transition); }
@media (min-width: 769px) {
    html.visitor-rail .visitor-sidebar { width: 68px; }
    html.visitor-rail .visitor-brand-text,
    html.visitor-rail .visitor-nav-section,
    html.visitor-rail .visitor-navlink > span:not(.visitor-nav-icon),
    html.visitor-rail .visitor-rail-label { display: none; }
    html.visitor-rail .visitor-brand { justify-content: center; padding: 1.1rem 0; }
    html.visitor-rail .visitor-brand-mark { display: inline; font-size: 1.4rem; font-weight: 800; }
    html.visitor-rail .visitor-navlink { justify-content: center; padding: .55rem 0; }
    html.visitor-rail .visitor-navlink.active::before { left: 0; }
    html.visitor-rail .visitor-rail-toggle { justify-content: center; }
    html.visitor-rail .visitor-rail-chevron { transform: rotate(180deg); }
}
.visitor-theme-toggle { display: inline-flex; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem;
    border: 1px solid var(--visitor-border); border-radius: var(--radius-md); background: var(--visitor-surface); color: var(--visitor-text);
    font-size: 1rem; cursor: pointer; transition: background var(--visitor-transition), border-color var(--visitor-transition); }
.visitor-theme-toggle:hover { background: var(--visitor-surface-2); border-color: var(--visitor-border-strong); }
.visitor-theme-toggle:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--visitor-secondary) 35%, transparent); }

.visitor-nav { display: flex; flex-direction: column; padding: .6rem .5rem; gap: .1rem; }
.visitor-navlink { position: relative; display: flex; align-items: center; gap: .65rem; color: var(--visitor-sidebar-text); opacity: .8;
    text-decoration: none; padding: .55rem .85rem; border-radius: var(--radius-md); font-size: var(--text-base); font-weight: 500;
    transition: background var(--visitor-transition), opacity var(--visitor-transition); }
.visitor-navlink:hover { opacity: 1; background: rgba(255,255,255,.1); }
.visitor-navlink.active { opacity: 1; background: rgba(255,255,255,.16); font-weight: 600; }
.visitor-navlink.active::before { content: ""; position: absolute; left: -.5rem; top: 50%; transform: translateY(-50%);
    width: 3px; height: 1.3rem; border-radius: 0 3px 3px 0; background: var(--visitor-accent); }
/* Base for all inline SVG icons (the Icon component). Sizes to text by default. */
.visitor-icon { width: 1em; height: 1em; display: inline-block; vertical-align: -0.125em; flex: none; }
.visitor-nav-icon { width: 1.2rem; height: 1.2rem; opacity: .8; transition: opacity var(--visitor-transition), color var(--visitor-transition); }
.visitor-navlink:hover .visitor-nav-icon { opacity: 1; }
.visitor-navlink.active .visitor-nav-icon { opacity: 1; color: var(--visitor-accent); }
/* Was rgba(255,255,255,.45) = 3.73:1 against the lightest stop of the sidebar gradient (#34393E, the
   binding case since the text is near-white). These are 10.88px uppercase — well under the 18.66px
   large-text threshold — so they need the full 4.5:1, and they are the labels telling you which part
   of the product you are in. .62 gives 5.61:1 without turning a quiet divider into a heading that
   competes with the nav items themselves. (CC-12.) */
.visitor-nav-section { color: rgba(255,255,255,.62); font-size: .68rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .08em; padding: .9rem .85rem .3rem; }

/* ---- the auth surfaces are NOT styled here ----------------------------------------------------
   Everything that used to live in this block — the /logout and /auth/refresh interstitials, the
   Wave 1 additions (arrival notices, SSO entry, password rules, field hints) and the auth-scoped
   validation treatment — moved to wwwroot/visitor-auth-supplement.css when the vendored
   vahary-auth-shell.css was replaced by the Vahary.Branding package.

   Two stylesheets now dress /login, /logout, /auth/refresh, /forgot-password and /reset-password,
   in this order: _content/Vahary.Branding/vahary-auth-shell.css (the shared shell) and the
   supplement. Both are loaded per-route by App.razor, so their weight never rides along on console
   pages. Do not add a third opinion here — a rule in app.css would apply to the console too, and
   the whole point of the supplement is that it is the ONE place Visitor's auth additions live.
   ----------------------------------------------------------------------------------------------- */

.visitor-field { display: block; margin-bottom: 1rem; }
.visitor-field > span { display: block; font-size: var(--text-sm); color: var(--visitor-muted); margin-bottom: .3rem; font-weight: 500; }
.visitor-input { width: 100%; padding: .55rem .7rem; border: 1px solid var(--visitor-border); border-radius: var(--radius-md); font: inherit;
    color: var(--visitor-text); background: var(--visitor-surface); transition: border-color var(--visitor-transition), box-shadow var(--visitor-transition); }
.visitor-input::placeholder { color: var(--visitor-muted); opacity: .8; }
.visitor-input:focus, .visitor-input:focus-visible { outline: none; border-color: var(--visitor-secondary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--visitor-secondary) 25%, transparent); }
.visitor-input:hover:not(:focus) { border-color: var(--visitor-border-strong); }

.visitor-btn { display: inline-flex; align-items: center; justify-content: center; gap: .45rem; padding: .55rem 1rem; border: 1px solid transparent;
    border-radius: var(--radius-md); font: inherit; font-weight: 600; line-height: 1.2; cursor: pointer; white-space: nowrap;
    transition: background var(--visitor-transition), box-shadow var(--visitor-transition), transform var(--visitor-transition), border-color var(--visitor-transition); }
.visitor-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--visitor-secondary) 35%, transparent); }
.visitor-btn:active { transform: translateY(1px); }
.visitor-btn-primary { background: var(--visitor-primary); color: var(--visitor-on-primary, #fff); width: 100%; box-shadow: var(--shadow-sm); }
.visitor-btn-primary:hover { background: color-mix(in srgb, var(--visitor-primary) 88%, #000); box-shadow: var(--shadow-md); }
.visitor-link { color: var(--visitor-secondary); text-decoration: none; font-weight: 500; transition: color var(--visitor-transition); }
.visitor-link:hover { text-decoration: underline; }

.visitor-alert { padding: .75rem 1rem; border-radius: var(--radius-md); margin-bottom: 1rem; border: 1px solid transparent;
    border-left-width: 3px; font-size: var(--text-base); }
.visitor-alert-info { background: var(--visitor-info-bg); border-color: color-mix(in srgb, var(--visitor-info) 30%, transparent); border-left-color: var(--visitor-info); color: var(--visitor-info-on); }
.visitor-alert-success { background: var(--visitor-success-bg); border-color: color-mix(in srgb, var(--visitor-success) 30%, transparent); border-left-color: var(--visitor-success); color: var(--visitor-success-on); }
.visitor-alert-warning { background: var(--visitor-warning-bg); border-color: color-mix(in srgb, var(--visitor-warning) 30%, transparent); border-left-color: var(--visitor-warning); color: var(--visitor-warning-on); }
.visitor-alert-error { background: var(--visitor-danger-bg); border-color: color-mix(in srgb, var(--visitor-danger) 30%, transparent); border-left-color: var(--visitor-danger); color: var(--visitor-danger-on); }

.visitor-empty { text-align: center; padding: 3rem 1.5rem; color: var(--visitor-muted); border: 1px dashed var(--visitor-border-strong);
    border-radius: var(--radius-lg); background: color-mix(in srgb, var(--visitor-surface-2) 60%, transparent); }
.visitor-empty-icon { font-size: 2.5rem; line-height: 1; margin-bottom: .75rem; opacity: .55; }
.visitor-empty-title { font-weight: 700; font-size: var(--text-lg); color: var(--visitor-text); margin-bottom: .35rem; }
/* VisitorEmptyState renders this for every Message it is given, and it was defined nowhere — so the
   explanatory line under every empty state in the product inherited .visitor-empty's muted colour at
   the default body size, with no measure limit. Found by the class-parity test below, not by the
   audit. Sits between title and actions, so its spacing is what separates them. */
/* The floor-plan preview thumbnail on the wayfinding admin page. Undefined, so a full-resolution
   floor plan rendered at its natural size and blew the table cell apart. Bounded, not stretched:
   floor plans are not a fixed aspect ratio. */
.visitor-way-thumb { max-width: 8rem; max-height: 6rem; width: auto; height: auto; object-fit: contain;
    border: 1px solid var(--visitor-border); border-radius: var(--radius-sm); background: var(--visitor-surface-2); }
.visitor-empty-message { font-size: var(--text-sm); line-height: 1.5; max-width: 42ch; margin: 0 auto; }
.visitor-empty-actions { margin-top: 1.25rem; display: flex; gap: .5rem; justify-content: center; flex-wrap: wrap; }

.visitor-skeleton-row { height: 1rem; margin: .5rem 0; border-radius: var(--radius-sm);
    background: linear-gradient(90deg, var(--visitor-skeleton-base) 25%, var(--visitor-skeleton-hi) 37%, var(--visitor-skeleton-base) 63%);
    background-size: 400% 100%; animation: visitor-shimmer 1.4s ease infinite; }
@keyframes visitor-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* ===== Dashboard (Phase 1) ===== */
.visitor-page-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
.visitor-page-head h1 { margin: 0; }
.visitor-select { width: auto; min-width: 200px; }
.visitor-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 1rem; }
.visitor-tile { position: relative; overflow: hidden; background: var(--visitor-surface); border: 1px solid var(--visitor-border);
    border-radius: var(--radius-lg); padding: 1.2rem 1.3rem; box-shadow: var(--shadow-sm); }
/* The status rule is a flat 3px of the tile's own colour (UIC-09). It was a left-to-right fade to
   50% transparent, which read as a highlight rather than as a status band -- and a band whose right
   half is half-transparent is a band whose colour depends on what is behind it. */
.visitor-tile::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
    background: var(--visitor-tile-color, var(--visitor-secondary)); }
.visitor-tile-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.visitor-tile-icon { width: 2.1rem; height: 2.1rem; flex: none; display: grid; place-items: center; border-radius: var(--radius-md); font-size: 1.1rem;
    color: var(--visitor-tile-color, var(--visitor-secondary)); background: color-mix(in srgb, var(--visitor-tile-color, var(--visitor-secondary)) 14%, var(--visitor-surface)); }
.visitor-tile-accent { border-color: color-mix(in srgb, var(--visitor-tile-color, var(--visitor-accent)) 55%, var(--visitor-border)); }
.visitor-tile-flag { color: var(--visitor-tile-color, var(--visitor-accent)); font-size: .7rem; margin-left: auto; animation: visitor-flag-pulse 2s ease-in-out infinite; }
/* The drill-down affordance a linked tile shows AT REST (VIS-DELTA-8). Auto margins hand the head's
   free space to whichever of flag/arrow comes first, so the pair hugs the right edge together. */
.visitor-tile-go { margin-left: auto; color: var(--visitor-muted); font-size: .95rem; line-height: 1;
    transition: color var(--visitor-transition), transform var(--visitor-transition); }
.visitor-tile-flag + .visitor-tile-go { margin-left: 0; }
.visitor-tile-link:hover .visitor-tile-go { color: var(--visitor-tile-color, var(--visitor-secondary)); transform: translateX(3px); }
@keyframes visitor-flag-pulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
.visitor-tile-value { font-size: var(--text-3xl); font-weight: 800; line-height: 1.05; letter-spacing: -.02em; color: var(--visitor-text); margin-top: .65rem; }
.visitor-tile-label { color: var(--visitor-muted); margin-top: .3rem; font-size: var(--text-base); font-weight: 500; }
.visitor-tile-sub { color: var(--visitor-muted); margin-top: .25rem; font-size: var(--text-sm); }
.visitor-tile-link { display: block; text-decoration: none; color: inherit; transition: box-shadow var(--visitor-transition), transform var(--visitor-transition), border-color var(--visitor-transition); }
.visitor-tile-link:hover { box-shadow: var(--shadow-lg); transform: translateY(-3px); border-color: color-mix(in srgb, var(--visitor-tile-color, var(--visitor-secondary)) 45%, var(--visitor-border)); }
.visitor-dash-controls { display: flex; justify-content: flex-end; margin-bottom: 1rem; }
.visitor-dash-hero { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); gap: 1rem; align-items: start; margin-bottom: 1rem; }
.visitor-dash-hero .visitor-occupancy { margin-top: 0; }
@media (max-width: 880px) { .visitor-dash-hero { grid-template-columns: 1fr; } }
/* The dashboard's headline occupancy card. Only its -value child was ever defined, so the card
   itself was a plain .visitor-card and the "primary" emphasis the name promises did not exist. */
.visitor-dash-primary { display: flex; flex-direction: column; justify-content: center; }
.visitor-dash-primary-value { font-size: clamp(2.4rem, 5vw, 3.2rem); font-weight: 800; line-height: 1; letter-spacing: -.02em; color: var(--visitor-text); margin: .35rem 0 .25rem; }

.visitor-section-head { margin: 1.75rem 0 .75rem; font-size: var(--text-lg); font-weight: 700; color: var(--visitor-text); }
.visitor-num { text-align: right; font-variant-numeric: tabular-nums; }
.visitor-insight-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); gap: 1rem; align-items: start; }
.visitor-insight-grid .visitor-card-head h3 { margin: 0; font-size: var(--text-base); font-weight: 700; }
.visitor-noshow { display: flex; align-items: center; gap: 1rem; }
.visitor-noshow > .visitor-chart-wrap { flex: 1; min-width: 0; }
.visitor-noshow-rate { font-size: clamp(1.8rem, 4vw, 2.6rem); font-weight: 800; line-height: 1; color: var(--visitor-text); white-space: nowrap; }
.visitor-noshow-rate span { font-size: .5em; color: var(--visitor-muted); margin-left: .1em; }

.visitor-onsite { margin-top: 1.5rem; }
.visitor-board-controls { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: .75rem; }
.visitor-board-chips { display: flex; gap: .4rem; flex-wrap: wrap; }
.visitor-board-types { margin-bottom: .85rem; }
.visitor-board-search { max-width: 18rem; }
.visitor-chip-n { display: inline-block; min-width: 1.25rem; text-align: center; margin-left: .35rem; padding: 0 .35rem;
    border-radius: var(--radius-pill); background: color-mix(in srgb, currentColor 16%, transparent); font-size: .8em; font-weight: 700; }
.visitor-chip-sm { padding: .3rem .65rem; font-size: var(--text-sm); }
.visitor-occupancy { margin-top: 1.5rem; }
.visitor-occupancy-total { font-size: var(--text-2xl); font-weight: 800; color: var(--visitor-brand-ink); line-height: 1; }
.visitor-occupancy-total.visitor-live { animation: visitor-occ-pulse 1.2s ease-out; }
/* UI-VIM-001: a failed occupancy fetch reads as unknown/stale, never a calm zero. */
.visitor-occupancy-total.visitor-occupancy-err { color: var(--visitor-danger); }
.visitor-occupancy-total.visitor-stale { color: var(--visitor-warning); }
.visitor-occupancy-err-msg { color: var(--visitor-danger); }
.visitor-occupancy-stale-msg { color: var(--visitor-warning); }
@keyframes visitor-occ-pulse { 0% { color: var(--visitor-accent); } 100% { color: var(--visitor-primary); } }
.visitor-occupancy-list { list-style: none; margin: 0; padding: 0; }
.visitor-occupancy-list li { display: flex; justify-content: space-between; padding: .45rem 0; border-bottom: 1px solid var(--visitor-border); }
.visitor-occupancy-list li:last-child { border-bottom: 0; }
.visitor-occupancy-count { font-weight: 700; color: var(--visitor-brand-ink); }
.visitor-card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
.visitor-card-head h2 { margin: 0; font-size: var(--text-lg); font-weight: 700; letter-spacing: -.01em; }
.visitor-linkbtn { background: none; border: 0; cursor: pointer; padding: 0; font: inherit; }

/* ===== Visitors (Phase 2) ===== */
.visitor-page-actions { display: flex; gap: .5rem; }
.visitor-btn-secondary { background: var(--visitor-secondary); color: var(--visitor-on-secondary, #fff); width: auto; box-shadow: var(--shadow-sm); }
.visitor-btn-secondary:hover { background: color-mix(in srgb, var(--visitor-secondary) 88%, #000); box-shadow: var(--shadow-md); }
.visitor-btn-ghost { background: var(--visitor-surface); color: var(--visitor-text); border: 1px solid var(--visitor-border); width: auto; }
.visitor-btn-ghost:hover { background: var(--visitor-surface-2); border-color: var(--visitor-border-strong); }
.visitor-btn-primary { width: auto; }
.visitor-btn:disabled { opacity: .5; cursor: not-allowed; transform: none; box-shadow: none; }

.visitor-toolbar { display: flex; flex-wrap: wrap; gap: .6rem; margin-bottom: 1rem; }
.visitor-toolbar .visitor-input { width: auto; }
.visitor-toolbar input[type=search] { min-width: 240px; flex: 1; }

.visitor-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; border: 1px solid var(--visitor-border);
    border-radius: var(--radius-lg); background: var(--visitor-surface); box-shadow: var(--shadow-sm); }
.visitor-table-wrap .visitor-table { border: 0; border-radius: 0; box-shadow: none; min-width: 100%; }
.visitor-table { width: 100%; border-collapse: collapse; background: var(--visitor-surface); border: 1px solid var(--visitor-border); border-radius: var(--radius-lg); overflow: hidden; }
.visitor-table th, .visitor-table td { text-align: left; padding: .7rem .9rem; border-bottom: 1px solid var(--visitor-border); white-space: nowrap; }
.visitor-table th { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: .04em; font-weight: 700; color: var(--visitor-muted);
    background: var(--visitor-surface-3); position: sticky; top: 0; z-index: 1; }
.visitor-table tbody tr { transition: background var(--visitor-transition); }
.visitor-table tbody tr:last-child td { border-bottom: 0; }
.visitor-row-link { cursor: pointer; }
.visitor-row-link:hover { background: var(--visitor-surface-2); }
.visitor-row-link:focus-visible { outline: 2px solid var(--visitor-brand-ink); outline-offset: -2px; background: var(--visitor-surface-2); }
.visitor-card.visitor-report-card:focus-visible, a.visitor-guard-action:focus-visible { outline: 2px solid var(--visitor-brand-ink); outline-offset: 2px; }
.visitor-th-sort { background: none; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; text-transform: inherit;
    letter-spacing: inherit; cursor: pointer; display: inline-flex; align-items: center; gap: .3rem; }
.visitor-th-sort:hover { color: var(--visitor-brand-ink); }
.visitor-cell-strong { font-weight: 600; }
.visitor-cell-sub { color: var(--visitor-muted); font-size: var(--text-sm); }

.visitor-badge { display: inline-block; padding: .15rem .55rem; border-radius: 999px; font-size: .78rem; font-weight: 600; background: var(--visitor-info-bg); color: var(--visitor-info-on); }
.visitor-badge-ok { background: var(--visitor-success-bg); color: var(--visitor-success-on); }
.visitor-toolbar-spread { justify-content: space-between; align-items: center; }
.visitor-row-selected { background: var(--visitor-surface-2, #F8FAFC); }
.visitor-doc-card { margin-bottom: 1rem; }
.visitor-choice-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: .75rem; }
.visitor-choice { display: flex; flex-direction: column; gap: .35rem; align-items: flex-start; text-align: left; cursor: pointer; padding: 1.25rem; }
.visitor-choice:hover { border-color: var(--visitor-primary, #4F46E5); }
.visitor-choice-title { font-weight: 600; font-size: 1.05rem; }
.visitor-radio-list { display: flex; flex-direction: column; gap: .25rem; margin: .5rem 0 1rem; }
.visitor-radio-row { display: flex; align-items: baseline; gap: .6rem; padding: .5rem .6rem; border-radius: .5rem; cursor: pointer; }
.visitor-radio-row:hover { background: var(--visitor-surface-2, #F8FAFC); }
@media (max-width: 640px) { .visitor-choice-grid { grid-template-columns: 1fr; } }
.visitor-doc-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
.visitor-doc-body { white-space: pre-wrap; max-height: 9rem; overflow-y: auto; padding: .6rem .75rem; margin: .5rem 0; border: 1px solid var(--visitor-border, #E2E8F0); border-radius: .5rem; background: var(--visitor-surface-2, #F8FAFC); font-size: .9rem; }
.visitor-status-active, .visitor-status-checkedin { background: var(--visitor-success-bg); color: var(--visitor-success-on); }
/* Neutral (--visitor-info) pills split by NON-COLOR channel (colorblind-safe): in-progress /
   awaiting-next-step states stay FILLED (solid --visitor-info-bg); terminal / done / inactive
   states use the OUTLINE treatment (near-transparent wash + 1px --visitor-info border). Same hue,
   different fill — recovers the pre-token in-progress(blue) vs done(gray) distinction. */
.visitor-status-invited, .visitor-status-preregistered { background: var(--visitor-info-bg); color: var(--visitor-info-on); }
.visitor-status-escortpending { background: var(--visitor-warning-bg); color: var(--visitor-warning-on); }
.visitor-status-checkedout { background: color-mix(in srgb, var(--visitor-info-bg) 45%, transparent); color: var(--visitor-info); border: 1px solid var(--visitor-info); }
.visitor-status-denied, .visitor-status-expired { background: var(--visitor-danger-bg); color: var(--visitor-danger-on); }
.visitor-status-cancelled { background: color-mix(in srgb, var(--visitor-info-bg) 45%, transparent); color: var(--visitor-info); border: 1px solid var(--visitor-info); }

.visitor-pager { display: flex; align-items: center; justify-content: space-between; margin-top: 1rem; }
.visitor-pager-info { color: var(--visitor-muted); font-size: .85rem; }
.visitor-pager-controls { display: flex; gap: .5rem; }

.visitor-detail-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 1rem; align-items: start; }
@media (max-width: 800px) { .visitor-detail-grid { grid-template-columns: 1fr; } }
.visitor-card { background: var(--visitor-surface); border: 1px solid var(--visitor-border); border-radius: var(--radius-lg); padding: 1.35rem; box-shadow: var(--shadow-sm); }
.visitor-card h2 { margin: 0 0 1rem; font-size: var(--text-lg); font-weight: 700; letter-spacing: -.01em; }
.visitor-card h3 { margin: 1rem 0 .35rem; font-size: var(--text-base); font-weight: 600; }
.visitor-dl { display: grid; grid-template-columns: 9rem 1fr; gap: .4rem .75rem; margin: 0; }
.visitor-dl dt { color: var(--visitor-muted); }
.visitor-dl dd { margin: 0; }
.visitor-notes { white-space: pre-wrap; margin: 0; }

.visitor-action-stack { display: flex; flex-wrap: wrap; gap: .5rem; }
/* The bulk bar on the muster roster (CC-5). Applied alongside .visitor-action-stack and never
   defined, so the selection count and its actions sat flush against the table with no separation
   from the rows they act on — during an evacuation. */
/* Roll-call controls: find a name, or cut the list to the people still missing. Sits directly above
   the roster it filters, so the count line and the table read as one thing. */
.visitor-muster-filters { align-items: center; gap: .75rem; margin-bottom: .6rem; }

/* The read-only roster rendered by vmsMusterSnapshot (App.razor) when the Blazor circuit dies during
   an evacuation. Built in plain JS from localStorage, because no .NET runs at that point — so these
   rules are the only styling it can get. Above the connectivity banner's own z-index: the banner says
   "reconnecting", this says who is still in the building. */
.visitor-muster-snapshot { position: fixed; inset: auto 0 0 0; max-height: 70vh; overflow-y: auto; z-index: 3100;
    background: var(--visitor-surface); border-top: 3px solid var(--visitor-danger); box-shadow: 0 -4px 20px rgba(0,0,0,.3);
    padding: 1rem 1.25rem; color: var(--visitor-text); }
.visitor-muster-snapshot-head { font-weight: 700; color: var(--visitor-danger); margin-bottom: .6rem; line-height: 1.4; }
.visitor-muster-snapshot-list { list-style: none; margin: 0; padding: 0; display: grid;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: .2rem .75rem; }
.visitor-muster-snapshot-missing { font-weight: 700; color: var(--visitor-danger); }
.visitor-muster-snapshot-ok { color: var(--visitor-muted); }
.visitor-muster-bulk { align-items: center; gap: .6rem; padding: .6rem .75rem; margin-bottom: .6rem;
    background: var(--visitor-surface-2); border: 1px solid var(--visitor-border); border-radius: var(--radius-md); }
.visitor-action-form { margin-top: 1rem; display: flex; flex-direction: column; gap: .5rem; }

.visitor-form-card { max-width: 720px; }
.visitor-form-row { display: flex; gap: 1rem; }
.visitor-form-row .visitor-field { flex: 1; }
.visitor-label { display: block; font-size: .85rem; color: var(--visitor-muted); margin-bottom: .25rem; }
.visitor-form-card .visitor-select { width: 100%; }

/* ===== Approvals + Escort (Phase 3) ===== */
.visitor-check { display: inline-flex; align-items: center; gap: .4rem; color: var(--visitor-text); font-size: .9rem; }
/* Applied by ConfigLobbyGroups to a location already claimed by another group. It was applied and
   never defined, so the only tell that the row was unavailable was the native disabled attribute —
   which greys the box but leaves the label at full contrast, reading as an ordinary choice. */
.visitor-check-disabled { color: var(--visitor-muted); cursor: not-allowed; }
/* Two-column master/detail used by ConfigCompanies. Also applied-but-never-defined, so the admins
   panel stacked below the whole companies table: on a 30-company tenant, clicking "Admins" on row 4
   opened a panel 26 rows further down with no scroll-to and no focus move. Mirrors
   .visitor-detail-grid's ratio and breakpoint deliberately — it is the same layout. */
.visitor-report-layout { display: grid; grid-template-columns: 2fr 1fr; gap: 1rem; align-items: start; }
@media (max-width: 800px) { .visitor-report-layout { grid-template-columns: 1fr; } }
.visitor-btn-sm { padding: .3rem .6rem; font-size: .82rem; }
.visitor-input-sm { width: auto; padding: .3rem .5rem; font-size: .82rem; }
.visitor-inline-form { display: inline-flex; gap: .35rem; align-items: center; }
.visitor-row-panel { background: var(--visitor-surface-2); }
.visitor-status-pending { background: var(--visitor-warning-bg); color: var(--visitor-warning-on); }
/* Escalated-as-missing on a roll-call. Deliberately louder than pending: "we have not called their
   name yet" is amber, "we have searched and told the fire marshal" is the one row on a 300-person
   roster that needs a person to act. (CC-12: the act-now state must be the most readable, not the
   least.) */
.visitor-status-escalated { background: var(--visitor-danger-bg); color: var(--visitor-danger-on); font-weight: 700; }
.visitor-status-approved, .visitor-status-confirmed { background: var(--visitor-success-bg); color: var(--visitor-success-on); }
.visitor-status-denied, .visitor-status-timeout, .visitor-status-overdue { background: var(--visitor-danger-bg); color: var(--visitor-danger-on); }
.visitor-status-escalated { background: var(--visitor-warning-bg); color: var(--visitor-warning-on); }
.visitor-status-autoresolved, .visitor-status-reassigned, .visitor-status-notified, .visitor-status-etaprovided, .visitor-status-assigned { background: color-mix(in srgb, var(--visitor-info-bg) 45%, transparent); color: var(--visitor-info); border: 1px solid var(--visitor-info); }

.visitor-thread { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; }
.visitor-thread-msg { padding: .55rem .7rem; border-radius: .5rem; border: 1px solid var(--visitor-border); }
.visitor-thread-inbound { background: var(--visitor-surface-2); }
.visitor-thread-outbound { background: var(--visitor-info-bg); border-color: color-mix(in srgb, var(--visitor-info) 30%, transparent); }
.visitor-thread-meta { font-size: .75rem; color: var(--visitor-muted); margin-bottom: .2rem; }

/* ===== Watchlist (Phase 4) ===== */
.visitor-list-nav { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .25rem; }
.visitor-list-nav-item { padding: .55rem .7rem; border-radius: .45rem; cursor: pointer; border: 1px solid transparent; }
.visitor-list-nav-item:hover { background: var(--visitor-surface-2); }
.visitor-list-nav-item.active { background: color-mix(in srgb, var(--visitor-secondary) 14%, var(--visitor-surface)); border-color: color-mix(in srgb, var(--visitor-secondary) 35%, transparent); }
.visitor-entry-form { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: 1rem; align-items: center; }
.visitor-screen-clear { background: var(--visitor-success-bg); color: var(--visitor-success-on); }
.visitor-screen-flagforreview { background: var(--visitor-warning-bg); color: var(--visitor-warning-on); }
.visitor-screen-block { background: var(--visitor-danger-bg); color: var(--visitor-danger-on); }

/* ===== Reports (Phase 5) ===== */
.visitor-report-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 1rem; }
.visitor-report-card { display: block; text-decoration: none; color: inherit; transition: box-shadow var(--visitor-transition), transform var(--visitor-transition), border-color var(--visitor-transition); }
.visitor-report-card:hover { box-shadow: var(--shadow-lg); transform: translateY(-3px); border-color: color-mix(in srgb, var(--visitor-secondary) 40%, var(--visitor-border)); }
.visitor-report-card h2 { margin: 0 0 .35rem; font-size: var(--text-md); font-weight: 700; color: var(--visitor-brand-ink); }
.visitor-inline-field { display: inline-flex; align-items: center; gap: .35rem; font-size: .85rem; color: var(--visitor-muted); }
/* Wrapper around the export format select + button in ReportExportButton. Undefined, so the two
   controls sat as inline elements with a whitespace gap instead of a laid-out pair. */
.visitor-export { display: inline-flex; gap: .35rem; align-items: center; }
.visitor-export-error { margin-left: .5rem; color: var(--visitor-danger); }

.visitor-chart { width: 100%; height: auto; max-height: 260px; display: block; }
.visitor-chart-axis { stroke: var(--visitor-border-strong); stroke-width: 1; }
.visitor-chart-grid { stroke: var(--visitor-border); stroke-width: 1; stroke-dasharray: 3 4; opacity: .7; }
.visitor-chart-bar { transition: opacity var(--visitor-transition); }
.visitor-chart-bar:hover { opacity: .8; }
.visitor-chart-line { fill: none; stroke: var(--visitor-secondary); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.visitor-chart-dot { fill: var(--visitor-surface); stroke: var(--visitor-secondary); stroke-width: 2; }
.visitor-chart-xlabels { display: flex; justify-content: space-between; font-size: var(--text-xs); color: var(--visitor-muted); margin-top: .4rem; }

/* ----- Charting engine (D0): legend, tooltip, target, donut, sparkline, heatmap ----- */
.visitor-chart-wrap { position: relative; }
.visitor-chart-legend { display: flex; flex-wrap: wrap; gap: .75rem; margin-bottom: .4rem; font-size: var(--text-xs); color: var(--visitor-muted); }
.visitor-chart-legend-item { display: inline-flex; align-items: center; gap: .35rem; }
.visitor-chart-swatch { width: .7rem; height: .7rem; border-radius: 2px; display: inline-block; }
.visitor-chart-target { stroke: var(--visitor-warning, #D97706); stroke-width: 1.5; stroke-dasharray: 5 4; opacity: .85; }
.visitor-chart-click { cursor: pointer; }
.visitor-chart-bar.visitor-chart-click:hover, .visitor-chart-slice.visitor-chart-click:hover { opacity: .82; }
.visitor-chart-dot.visitor-chart-click:hover { r: 5; }
/* Heatmap cells are clickable once the dashboard wires OnCellSelected (VIS-DELTA-2); an outline
   reads on both a nearly-empty cell (where an opacity change would be invisible) and a full one. */
.visitor-heatmap rect.visitor-chart-click:hover { stroke: var(--visitor-secondary); stroke-width: 1.5; }
.visitor-chart-slice { transition: opacity var(--visitor-transition); }
.visitor-chart-donut-total { fill: var(--visitor-text); font-size: 22px; font-weight: 700; }
.visitor-chart-tip { position: absolute; transform: translate(-50%, -120%); background: var(--visitor-text); color: var(--visitor-surface);
    padding: .25rem .5rem; border-radius: .4rem; font-size: var(--text-xs); white-space: nowrap; pointer-events: none; z-index: 5; box-shadow: 0 4px 14px rgba(0,0,0,.25); }
.visitor-spark { width: 100%; height: 28px; display: block; }
.visitor-spark-line { fill: none; stroke: var(--visitor-secondary); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* Metric-tile delta + sparkline */
.visitor-tile-delta { margin-top: .25rem; font-size: var(--text-sm); font-weight: 600; color: var(--visitor-muted); }
.visitor-delta-up { color: #1D9E75; }
.visitor-delta-down { color: #D4537E; }
.visitor-tile-spark { margin-top: .5rem; }

/* Heatmap */
.visitor-heatmap { width: 100%; height: auto; max-height: 240px; display: block; }
.visitor-heatmap-axis { fill: var(--visitor-muted); font-size: 10px; }

/* Date-range control */
.visitor-daterange { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin-bottom: 1rem; }
.visitor-daterange-presets { display: inline-flex; gap: .25rem; }
.visitor-chip { border: 1px solid var(--visitor-border); background: var(--visitor-surface); color: var(--visitor-text); border-radius: 999px;
    padding: .25rem .7rem; font-size: var(--text-sm); cursor: pointer; }
.visitor-chip:hover { border-color: var(--visitor-secondary); }
.visitor-chip-on { background: var(--visitor-secondary); color: #fff; border-color: var(--visitor-secondary); }

.visitor-cert-valid { background: var(--visitor-success-bg); color: var(--visitor-success-on); }
.visitor-cert-expiringsoon { background: var(--visitor-warning-bg); color: var(--visitor-warning-on); }
.visitor-cert-expired { background: var(--visitor-danger-bg); color: var(--visitor-danger-on); }
.visitor-cert-missing { background: var(--visitor-warning-bg); color: var(--visitor-warning-on); }

/* ===== Modules (Phase 6) ===== */
.visitor-grow { flex: 1; min-width: 140px; }
.visitor-plain-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .4rem; }
.visitor-plain-list li { padding: .4rem 0; border-bottom: 1px solid var(--visitor-border); }
.visitor-plain-list li:last-child { border-bottom: 0; }
.visitor-muster-head { display: flex; align-items: center; gap: .75rem; margin-bottom: 1rem; }
/* Roll-call action: large touch target for use under stress (fire drill), gloves, tablets. */
.visitor-muster-account { min-height: 3rem; min-width: 11rem; padding: .75rem 1.25rem; font-size: 1.05rem; font-weight: 600; }
.visitor-status-inactive, .visitor-status-suspended { background: var(--visitor-danger-bg); color: var(--visitor-danger-on); }
.visitor-status-received, .visitor-status-notificationsent { background: var(--visitor-info-bg); color: var(--visitor-info-on); }
.visitor-status-pickedup { background: var(--visitor-success-bg); color: var(--visitor-success-on); }
.visitor-status-returned { background: color-mix(in srgb, var(--visitor-info-bg) 45%, transparent); color: var(--visitor-info); border: 1px solid var(--visitor-info); }
.visitor-status-draft { background: color-mix(in srgb, var(--visitor-info-bg) 45%, transparent); color: var(--visitor-info); border: 1px solid var(--visitor-info); }
.visitor-status-published { background: var(--visitor-success-bg); color: var(--visitor-success-on); }
.visitor-status-completed { background: var(--visitor-info-bg); color: var(--visitor-info-on); }

/* ===== Healthcare / Patients (Phase 7) ===== */
.visitor-privacy-standard { background: var(--visitor-success-bg); color: var(--visitor-success-on); }
.visitor-privacy-restricted { background: var(--visitor-warning-bg); color: var(--visitor-warning-on); }
.visitor-privacy-confidential, .visitor-privacy-vip { background: var(--visitor-danger-bg); color: var(--visitor-danger-on); }
.visitor-sev-informational { background: color-mix(in srgb, var(--visitor-info-bg) 45%, transparent); color: var(--visitor-info); border: 1px solid var(--visitor-info); }
.visitor-sev-caution { background: var(--visitor-warning-bg); color: var(--visitor-warning-on); }
.visitor-sev-alert { background: var(--visitor-danger-bg); color: var(--visitor-danger-on); }

/* ===== Configuration (Phase 8) ===== */
.visitor-swatch { display: inline-block; width: .9rem; height: .9rem; border-radius: 3px; border: 1px solid var(--visitor-border); vertical-align: middle; margin-right: .3rem; }
.visitor-checkgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: .4rem .75rem; }
.visitor-perm-group { margin: .5rem 0; }
.visitor-perm-group-head { font-weight: 600; font-size: .82rem; text-transform: capitalize; color: var(--visitor-muted); margin-bottom: .25rem; }

/* ===== Cadence / filter chips ===== */
.visitor-chips { display: flex; flex-wrap: wrap; gap: .4rem; }
.visitor-chip { padding: .4rem .8rem; border: 1px solid var(--visitor-border); border-radius: var(--radius-pill); background: var(--visitor-surface);
    color: var(--visitor-text); font: inherit; font-size: var(--text-sm); cursor: pointer; transition: background var(--visitor-transition), border-color var(--visitor-transition); }
.visitor-chip:hover { background: var(--visitor-surface-2); border-color: var(--visitor-border-strong); }
.visitor-chip-on { background: color-mix(in srgb, var(--visitor-secondary) 14%, var(--visitor-surface)); border-color: var(--visitor-secondary); color: var(--visitor-brand-ink); font-weight: 600; }

/* ===== Workflow builder ===== */
.visitor-wf-rule { border-left: 3px solid var(--visitor-secondary); }
.visitor-wf-rule h3 { margin: 1rem 0 .4rem; font-size: var(--text-sm); text-transform: uppercase; letter-spacing: .04em; color: var(--visitor-muted); }
.visitor-wf-match { display: flex; align-items: center; gap: .5rem; font-size: var(--text-sm); color: var(--visitor-muted); margin-bottom: .5rem; }
.visitor-wf-match .visitor-input-sm { width: auto; }
.visitor-wf-cond { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; margin-bottom: .4rem; }
.visitor-wf-cond .visitor-input-sm { width: auto; min-width: 9rem; }
.visitor-wf-step { border: 1px solid var(--visitor-border); border-radius: var(--radius-md); padding: .6rem .7rem; margin-bottom: .5rem; background: var(--visitor-surface-2); }
.visitor-wf-step-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.visitor-wf-step-head .visitor-input-sm { width: auto; min-width: 12rem; }
.visitor-wf-step-tools { display: inline-flex; align-items: center; gap: .15rem; flex-shrink: 0; }
.visitor-wf-step-tools .visitor-btn-sm { padding: .2rem .45rem; }
.visitor-wf-params { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: .5rem .75rem; margin-top: .5rem; }
.visitor-wf-params .visitor-label { margin-bottom: .15rem; }

/* ===== Deploy (Phase 9) ===== */
.visitor-mono { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: .82rem; }
.visitor-health-healthy, .visitor-health-ok { background: var(--visitor-success-bg); color: var(--visitor-success-on); }
.visitor-health-degraded { background: var(--visitor-warning-bg); color: var(--visitor-warning-on); }
/* Offline had no rule at all, so the one PACS status that means "the doors are not being controlled"
   rendered as an uncoloured default badge. It reads as a fault, like the other two that mean the same
   thing operationally. */
.visitor-health-unhealthy, .visitor-health-down, .visitor-health-offline { background: var(--visitor-danger-bg); color: var(--visitor-danger-on); }

/* The "remove the stored secret on save" tick beside a write-only field. Sits under its input as a
   quiet consequence, not as a second control competing with it. */
.visitor-clear-secret { margin-top: .35rem; font-size: var(--text-sm); color: var(--visitor-danger); }
.visitor-health-unknown { background: color-mix(in srgb, var(--visitor-info-bg) 45%, transparent); color: var(--visitor-info); border: 1px solid var(--visitor-info); }

/* ===== Kiosk/guard connectivity resilience (VIM-011) ===== */
/* On the kiosk & guard surfaces we show our own calm banner (created in JS) and suppress the raw
   stock Blazor reconnect modal, which freezes the screen and reads as an error to a visitor. */
body[data-visitor-surface] #components-reconnect-modal { display: none !important; }
.visitor-conn-banner {
    position: fixed; inset: 0 0 auto 0; z-index: 3000;
    padding: 1rem 1.25rem; text-align: center; font-size: 1.05rem; font-weight: 600;
    color: #fff; box-shadow: 0 2px 14px rgba(0,0,0,.28);
}
.visitor-conn-offline { background: #b91c1c; }
.visitor-conn-reconnecting { background: #b45309; }

/* ===== Kiosk surface (Phase 10) =====
   MUST STAY IN SYNC with Vahary.Visitor.Kiosk.Ui/wwwroot/visitor-kiosk.css. One renderer (KioskHome,
   in the RCL) draws the kiosk for both hosts, but each host brings its own stylesheet: the native MAUI
   kiosk loads visitor-kiosk.css, and Vahary.Visitor.Web loads this file and never that one.

   They had already drifted, and silently. Every rule the design vocabulary needs — the --kiosk-*
   custom properties BrandCss emits, the centered/split/hero variants, the entry-mode choice grids —
   existed only in the RCL copy. So a tenant who chose Glass, or Hero, or a background image got them
   on a paired tablet and got the original hardcoded navy at /kiosk, with no error anywhere. The
   designer disagreed with one of the two screens it claims to preview.

   KioskSurfaceParityTests asserts every class and token the renderer can emit is present in BOTH
   files, so the next addition to one cannot quietly skip the other. */
.visitor-kiosk {
    min-height: 100vh; display: grid; place-items: center; padding: 2rem;
    background: var(--kiosk-backdrop, linear-gradient(160deg, var(--visitor-primary) 0%, var(--visitor-secondary) 100%));
    font-family: var(--kiosk-font, inherit);
    color: #fff;
}
.visitor-kiosk-panel {
    width: 28rem; max-width: 92vw;
    background: var(--kiosk-panel-bg, var(--visitor-surface));
    /* Glass needs the blur; solid and flat pass "none". -webkit- first for iPadOS Safari. */
    -webkit-backdrop-filter: var(--kiosk-panel-blur, none);
    backdrop-filter: var(--kiosk-panel-blur, none);
    border: var(--kiosk-panel-border, none);
    /* Literal, not --visitor-text: that token turns near-white under prefers-color-scheme: dark,
       and this panel is light on every surface because --kiosk-panel-bg is white or a white
       translucency in all three. Following the page theme here put #E7EDF5 on a white card. */
    color: #0F172A;
    border-radius: var(--kiosk-radius, 1rem);
    padding: 2.5rem;
    box-shadow: var(--kiosk-panel-shadow, 0 20px 60px rgba(0,0,0,.25));
    text-align: center;
}

/* Layout variants. `centered` is the default and needs no rule. */
/* split — the tenant's brand beside the form. Two columns only where there is room for two: below
   60rem the grid rule does not apply and the aside simply stacks above the panel, which is the right
   answer on a portrait tablet rather than two columns too narrow to use.
   The aside is rendered by KioskLayout (not the page) so it persists across every stage; KioskHome
   suppresses its own logo/heading on the attract screen while split is on, or the brand draws twice. */
.visitor-kiosk-brandside { text-align: center; color: #fff; max-width: 34rem; }
.visitor-kiosk-brandside-logo { max-height: 7rem; max-width: 22rem; margin: 0 auto 1.5rem; display: block; object-fit: contain; }
.visitor-kiosk-brandside-heading { font-size: 3.25rem; line-height: 1.05; margin: 0 0 .5rem; }
.visitor-kiosk-brandside-location { font-size: 1.5rem; opacity: .9; margin: 0 0 .5rem; }
.visitor-kiosk-brandside-sub { font-size: 1.125rem; color: rgba(255,255,255,.85); margin: 0; }

@media (min-width: 60rem) {
    .visitor-kiosk-split { grid-template-columns: 1fr 1fr; place-items: center stretch; gap: 3rem; padding: 3rem; }
    .visitor-kiosk-split .visitor-kiosk-panel { width: 100%; max-width: 34rem; justify-self: center; }
    /* The attract stage is a bare CTA in this layout, so centre it against the brand rather than
       letting it sit at the top of its column. */
    .visitor-kiosk-split .visitor-kiosk-attract { align-self: center; }
    .visitor-kiosk-split .visitor-kiosk-brandside { justify-self: center; text-align: left; }
    .visitor-kiosk-split .visitor-kiosk-brandside-logo { margin-left: 0; }
}
.visitor-kiosk-hero { align-content: end; padding-bottom: 6vh; }
.visitor-kiosk-hero .visitor-kiosk-panel { width: 34rem; padding: 3rem; }

/* Entry-mode choices — the grid gives up columns before it gives up touch-target size. */
.visitor-kiosk-choices { display: grid; gap: .75rem; margin-top: 1.25rem; width: min(34rem, 92vw); }
.visitor-kiosk-choice { min-height: 4rem; font-size: 1.125rem; border-radius: var(--kiosk-control-radius, 14px); padding: 1rem 1.25rem; }
.visitor-kiosk-choices-2   { grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
.visitor-kiosk-choices-3   { grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); width: min(44rem, 94vw); }
.visitor-kiosk-choices-4   { grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));  width: min(52rem, 96vw); }
.visitor-kiosk-choices-2x2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* The face on the confirm screen, above the greeting it belongs to. */
.visitor-kiosk-face { display: flex; justify-content: center; margin-bottom: 1rem; }
.visitor-kiosk-title { margin: 0 0 .5rem; font-size: 1.8rem; }
.visitor-kiosk-sub { color: var(--visitor-muted); margin: .25rem 0 1.5rem; }
.visitor-kiosk-section { text-align: left; margin: 0 0 1.25rem; }
.visitor-kiosk-doc { border-top: 1px solid var(--visitor-border, #E2E8F0); padding-top: 1rem; }
.visitor-kiosk-docbody { white-space: pre-wrap; max-height: 8rem; overflow-y: auto; padding: .6rem .75rem; margin: .4rem 0; border: 1px solid var(--visitor-border, #E2E8F0); border-radius: .5rem; background: var(--visitor-surface-2, #F8FAFC); font-size: .9rem; }
.visitor-kiosk-code { text-align: center; letter-spacing: .35em; text-transform: uppercase; font-size: 1.6rem;
    font-family: ui-monospace, "Cascadia Code", Consolas, monospace; }
.visitor-btn-lg { width: 100%; padding: .9rem 1.25rem; font-size: 1.1rem; margin-top: 1rem; }
.visitor-kiosk-attract { text-align: center; color: #fff; }
.visitor-kiosk-logo { max-height: 5rem; max-width: 18rem; margin: 0 auto 1.25rem; display: block; object-fit: contain; }
.visitor-kiosk-welcome { font-size: 3rem; margin: 0 0 .5rem; }
.visitor-kiosk-location { font-size: 1.5rem; opacity: .9; margin: 0 0 1rem; }
.visitor-kiosk-attract .visitor-kiosk-sub { color: rgba(255,255,255,.85); }
/* The tenant's accent, finally. BrandCss emitted --kiosk-accent from the day the designer offered
   the swatch and no stylesheet ever read it, so the accent changed the preview and not the screen.
   Ink is computed from the accent's luminance (KioskStyleMap.AccentInk) rather than fixed, because
   the picker accepts any hex; the default accent is white, so an existing kiosk is unchanged. */
.visitor-kiosk-cta { width: auto; min-width: 16rem; background: var(--kiosk-accent, #fff);
    color: var(--kiosk-accent-ink, var(--visitor-primary)); }
.visitor-kiosk-back { width: 100%; margin-top: .5rem; }
.visitor-kiosk-steps { text-align: left; margin: .5rem 0 1rem; padding-left: 1.25rem; color: var(--visitor-text); }
.visitor-kiosk-steps li { margin: .2rem 0; text-transform: capitalize; }
/* Staff exit. This was a visible lock button that opened a credentials dialog on one tap, so the one
   control a visitor must never reach was the one control on screen at all times. It is now an
   unlabelled corner target that needs five taps in three seconds (KioskHome.OnExitZoneTap): no
   background, no border, no cursor change, nothing to notice or hover. Same envelope the lock
   occupied, anchored into the corner so it cannot sit over a control. */
.visitor-kiosk-exit-zone { position: fixed; bottom: 0; right: 0; width: 3.5rem; height: 3.5rem; background: transparent; }
.visitor-kiosk-overlay { position: fixed; inset: 0; display: grid; place-items: center; background: rgba(0,0,0,.55); }
.visitor-kiosk-overlay .visitor-input { margin-bottom: .6rem; }
.visitor-kiosk-walkin { margin-top: .85rem; color: var(--visitor-muted); width: 100%; }
.visitor-kiosk-fieldlabel { display: block; text-align: left; margin-bottom: .25rem; }
.visitor-kiosk-panel .visitor-input, .visitor-kiosk-panel .visitor-select { margin-bottom: .6rem; }
.visitor-kiosk-panel .visitor-select { margin-bottom: .8rem; }
/* ---- Wayfinding: facility map + building directory (founder-scoped 2026-08-07) -------------------
   The inline plan is a button because a floor plan scaled into a 28rem panel is unreadable from the
   three feet a visitor stands at; the overlay is where it becomes legible. Zoom is a width on the
   image inside a scrolling stage rather than pinch-zoom, because the native kiosk ships
   user-scalable=no (Vahary.Visitor.Kiosk/wwwroot/index.html:6) and a pinch does nothing there. */
.visitor-kiosk-way { margin-top: 1.5rem; text-align: left; }
.visitor-kiosk-way-heading { font-size: 1.1rem; font-weight: 600; margin: 0 0 .6rem; }
.visitor-kiosk-attract .visitor-kiosk-way-heading { color: #fff; text-align: center; }

.visitor-kiosk-way-floors { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .6rem; }
.visitor-kiosk-way-floor { min-height: 2.75rem; padding: .5rem .9rem; font: inherit; font-weight: 600; cursor: pointer;
    border: 1px solid var(--visitor-border, #E2E8F0); border-radius: var(--kiosk-control-radius, 14px);
    background: var(--visitor-surface, #fff); color: var(--visitor-text, #0F172A); }
.visitor-kiosk-way-floor-on { background: var(--visitor-primary, #5B21B6); border-color: var(--visitor-primary, #5B21B6); color: var(--visitor-on-primary, #fff); }

.visitor-kiosk-way-thumb { display: block; width: 100%; padding: 0; border: 1px solid var(--visitor-border, #E2E8F0);
    border-radius: var(--kiosk-radius, 1rem); overflow: hidden; background: #fff; cursor: pointer; position: relative; }
.visitor-kiosk-way-thumb img { display: block; width: 100%; max-height: 40vh; object-fit: contain; background: #fff; }
.visitor-kiosk-way-enlarge { display: flex; align-items: center; justify-content: center; gap: .4rem;
    padding: .55rem; font-size: .9rem; font-weight: 600; background: var(--visitor-surface-2, #F8FAFC); color: var(--visitor-text, #0F172A); }
.visitor-kiosk-way-dirbtn { width: 100%; margin-top: .6rem; }

/* Overlay. Column layout with the stage taking the slack, so the directory below stays reachable. */
.visitor-kiosk-way-overlay { padding: 1.5rem; }
.visitor-kiosk-way-sheet { display: flex; flex-direction: column; gap: .75rem; width: min(60rem, 96vw); max-height: 92vh;
    overflow-y: auto; padding: 1.5rem; text-align: left;
    background: var(--visitor-surface, #fff); color: var(--visitor-text, #0F172A); border-radius: var(--kiosk-radius, 1rem); }
.visitor-kiosk-way-bar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.visitor-kiosk-way-bar .visitor-kiosk-way-heading { margin: 0; font-size: 1.4rem; }
.visitor-kiosk-way-stage { overflow: auto; max-height: 55vh; border: 1px solid var(--visitor-border, #E2E8F0);
    border-radius: .5rem; background: #fff; -webkit-overflow-scrolling: touch; }
.visitor-kiosk-way-stage img { display: block; max-width: none; }
.visitor-kiosk-way-zoom { display: flex; align-items: center; gap: .5rem; }
.visitor-kiosk-way-zoom .visitor-btn { min-width: 3rem; min-height: 2.75rem; font-size: 1.1rem; }
.visitor-kiosk-way-zoomlevel { min-width: 4rem; text-align: center; font-variant-numeric: tabular-nums; color: var(--visitor-muted, #64748B); }

.visitor-kiosk-way-searchlabel { display: block; font-size: .9rem; font-weight: 600; margin-top: .25rem; }
.visitor-kiosk-way-none { color: var(--visitor-muted, #64748B); }
.visitor-kiosk-way-dir { display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); gap: 1rem 1.5rem; }
.visitor-kiosk-way-grouphead { font-size: .8rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
    color: var(--visitor-muted, #64748B); margin: 0 0 .35rem; }
.visitor-kiosk-way-list { list-style: none; margin: 0; padding: 0; }
.visitor-kiosk-way-entry { display: flex; justify-content: space-between; gap: 1rem; padding: .45rem 0;
    border-bottom: 1px solid var(--visitor-border, #E2E8F0); }
.visitor-kiosk-way-name { font-weight: 600; }
.visitor-kiosk-way-where { color: var(--visitor-muted, #64748B); white-space: nowrap; }

.visitor-pairing-code { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 1.25rem;
    letter-spacing: .25em; font-weight: 700; margin-left: .35rem; }

/* ===== Builder split layout (editor + sticky preview) ===== */
.visitor-split-grid { display: grid; grid-template-columns: minmax(0,1fr) 24rem; gap: 1.5rem; align-items: start; }
@media (max-width: 1100px) { .visitor-split-grid { grid-template-columns: 1fr; } }
.visitor-split-preview { position: sticky; top: 1rem; }
.visitor-fieldrow { display: flex; align-items: center; gap: .75rem; padding: .55rem .7rem; border: 1px solid var(--visitor-border);
    border-radius: var(--radius-md); margin-bottom: .4rem; background: var(--visitor-surface); }
.visitor-fieldrow.visitor-fieldrow-off { opacity: .55; }
.visitor-fieldrow-grip { display: flex; flex-direction: column; gap: .15rem; }
.visitor-fieldrow-grip button { border: 0; background: var(--visitor-surface-2); border-radius: 4px; cursor: pointer; line-height: 1;
    padding: .1rem .35rem; color: var(--visitor-muted); font-size: .7rem; }
.visitor-fieldrow-grip button:disabled { opacity: .35; cursor: default; }
.visitor-fieldrow-main { flex: 1; min-width: 0; }
.visitor-fieldrow-name { font-weight: 500; }
.visitor-fieldrow-meta { font-size: .72rem; color: var(--visitor-muted); }
.visitor-fieldrow-toggles { display: flex; gap: 1rem; align-items: center; white-space: nowrap; }
.visitor-tag { display: inline-block; font-size: .65rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
    padding: .1rem .4rem; border-radius: var(--radius-pill); background: var(--visitor-surface-2); color: var(--visitor-muted); margin-left: .4rem; }

/* ===== Kiosk designer ===== */
.visitor-section-title { font-size: 1rem; font-weight: 600; margin: 0 0 .75rem; color: var(--visitor-text); }
.visitor-field-hint { display: block; margin-top: .25rem; font-size: .78rem; color: var(--visitor-muted); }
.visitor-progressbar { height: .5rem; background: var(--visitor-surface-2); border-radius: var(--radius-pill); overflow: hidden; margin-top: .5rem; }
.visitor-progressbar-fill { height: 100%; background: var(--visitor-primary); border-radius: var(--radius-pill); transition: width .3s ease; }
.visitor-kdes-grid { display: grid; grid-template-columns: minmax(0,1fr) 27rem; gap: 1.5rem; align-items: start; }
@media (max-width: 1100px) { .visitor-kdes-grid { grid-template-columns: 1fr; } }
.visitor-kdes-controls { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
.visitor-kdes-preview { position: sticky; top: 1rem; }
.visitor-kdes-tabs { display: flex; gap: .35rem; margin-bottom: .75rem; flex-wrap: wrap; }
.visitor-kdes-note { margin-top: .6rem; text-align: center; }

/* Device frame + screen */
.visitor-kdes-device { border-radius: 2rem; padding: .7rem; background: linear-gradient(160deg,#2a2a30,#141417);
    box-shadow: 0 30px 80px rgba(0,0,0,.45), inset 0 0 0 1px rgba(255,255,255,.06); }
/* Backdrop and face come from the kiosk token set, the same two declarations .visitor-kiosk makes,
   rather than being inlined on the element — so the preview and the device read one source. */
.visitor-kdes-screen { position: relative; border-radius: 1.5rem; overflow: hidden;
    aspect-ratio: 10 / 13; display: grid; place-items: center; padding: 1.6rem; text-align: center; color: #fff;
    background: var(--kiosk-backdrop); font-family: var(--kiosk-font, inherit); }
.visitor-kdes-attract { display: flex; flex-direction: column; align-items: center; gap: .25rem; }
.visitor-kdes-logo { max-height: 3rem; max-width: 12rem; margin: 0 auto 1rem; display: block; object-fit: contain;
    filter: drop-shadow(0 2px 8px rgba(0,0,0,.3)); }
.visitor-kdes-welcome { font-size: 2.1rem; font-weight: 800; letter-spacing: -.02em; margin: 0 0 .25rem; text-shadow: 0 2px 18px rgba(0,0,0,.25); }
.visitor-kdes-location { font-size: 1.05rem; opacity: .92; margin: 0 0 .35rem; font-weight: 500; }
.visitor-kdes-subtle { font-size: .92rem; opacity: .9; margin: 0 0 1.1rem; max-width: 22rem; }
.visitor-kdes-cta { background: var(--kiosk-accent); color: var(--kiosk-accent-ink, #0b1020); border: 0; border-radius: var(--kiosk-control-radius);
    padding: .8rem 1.9rem; font-weight: 700; font-size: 1.05rem; cursor: default; box-shadow: 0 10px 30px rgba(0,0,0,.28); }
.visitor-kdes-ghost { margin-top: .9rem; font-size: .9rem; opacity: .9; text-decoration: underline; }
.visitor-kdes-done-check { width: 4rem; height: 4rem; border-radius: 50%; display: grid; place-items: center; margin: 0 auto 1rem;
    background: var(--kiosk-accent); color: var(--kiosk-accent-ink, #0b1020); font-size: 2rem; font-weight: 800; box-shadow: 0 10px 30px rgba(0,0,0,.3); }

/* Cards. These are the six declarations .visitor-kiosk-panel makes, reading the same tokens — the
   surface treatment is now KioskStyleMap.Surface()'s answer on both sides instead of a second,
   hand-written glass/solid/flat set that had drifted away from it. */
/* The ink is a literal, NOT --visitor-text. In this stylesheet that token resolves to
   --v-color-text-primary, which is near-white when the admin console is in dark mode — and the panel
   is light on every surface, because --kiosk-panel-bg is white or a white translucency in all three.
   A theme-following ink would put #F2F3F1 on a white card whenever the operator's console is dark. */
.visitor-kdes-card { width: 88%; border-radius: var(--kiosk-radius); padding: 1.4rem; text-align: left;
    background: var(--kiosk-panel-bg); color: #0b1020;
    -webkit-backdrop-filter: var(--kiosk-panel-blur, none); backdrop-filter: var(--kiosk-panel-blur, none);
    border: var(--kiosk-panel-border, none); box-shadow: var(--kiosk-panel-shadow, none); }
.visitor-kdes-cardtitle { font-size: 1.25rem; font-weight: 700; margin-bottom: .3rem; }
.visitor-kdes-cardsub { font-size: .9rem; margin-bottom: 1rem; opacity: .8; }
.visitor-kdes-codebox { border-radius: calc(var(--kiosk-radius) * .5); padding: .7rem; text-align: center;
    letter-spacing: .18em; text-transform: uppercase; font-size: .9rem; margin-bottom: .7rem; }
.visitor-kdes-btn { display: block; width: 100%; border: 0; border-radius: var(--kiosk-control-radius); padding: .75rem;
    font-weight: 700; font-size: 1rem; color: var(--kiosk-accent-ink, #0b1020); background: var(--kiosk-accent); cursor: default; margin-top: .3rem;
    box-shadow: 0 8px 22px rgba(0,0,0,.25); }
/* The per-surface card overrides that used to sit here are gone. They were a second definition of
   glass/solid/flat that had drifted from KioskStyleMap.Surface(): glass was a 13%-white veil with
   white text here and a 72%-white panel with dark text on the device, so the one control a tenant
   is most likely to touch previewed as a different card than it shipped. The card now takes its
   fill, blur, border and shadow from the tokens, which means all three surfaces are light panels
   with dark text — as they already were in the lobby.
   Card sub-text and the code box follow that dark-on-light ground for every surface. */
.visitor-kdes-cardsub { color: #475569; }
.visitor-kdes-codebox { border: 1px solid #cbd5e1; color: #64748b; }

/* Composition — mirrors the live kiosk rules in visitor-kiosk.css. `centered` needs no rule; it is
   what .visitor-kdes-screen already does. */
.visitor-kdes-layout-hero { align-content: end; padding-bottom: 2.4rem; }

/* split — brand beside the form. The frame turns landscape with it, because that is the shape the
   layout needs on a device (it stacks below 960px) and a portrait preview of a landscape composition
   would be the designer promising a screen the kiosk will not draw. */
.visitor-kdes-device-landscape .visitor-kdes-screen { aspect-ratio: 13 / 10; }
.visitor-kdes-layout-split { grid-template-columns: 1fr 1fr; place-items: center; gap: 1.2rem; padding: 1.2rem; }
.visitor-kdes-layout-split .visitor-kdes-card { width: 100%; }
.visitor-kdes-brandside { text-align: left; }
.visitor-kdes-brandside .visitor-kdes-logo { margin-left: 0; }
.visitor-kdes-brandside .visitor-kdes-welcome { font-size: 1.7rem; }
.visitor-kdes-brandside .visitor-kdes-location { font-size: .95rem; }

/* Entry-mode choice buttons. Same "columns before size" rule as the device: auto-fit with a minimum
   width so a narrow preview collapses to fewer columns rather than drawing unhittable buttons. */
.visitor-kdes-choices { display: grid; gap: .4rem; margin-top: .9rem; width: 100%; }
.visitor-kdes-choice { border: 1px solid rgba(255,255,255,.45); border-radius: var(--kiosk-control-radius);
    padding: .55rem .6rem; font-size: .82rem; font-weight: 600; background: rgba(255,255,255,.12); color: #fff;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.visitor-kdes-choices-two-across   { grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr)); }
.visitor-kdes-choices-three-across { grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr)); }
.visitor-kdes-choices-four-across  { grid-template-columns: repeat(auto-fit, minmax(3.6rem, 1fr)); }
.visitor-kdes-choices-two-by-two   { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* A greyed chip must read as "not available", not as "not selected". */
.visitor-chip:disabled { opacity: .45; cursor: not-allowed; text-decoration: line-through; }

/* Template chips */
.visitor-kdes-templates { display: grid; grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr)); gap: .6rem; }
.visitor-kdes-tpl { display: flex; flex-direction: column; gap: .45rem; text-align: left; border: 1px solid var(--visitor-border);
    border-radius: var(--radius-md); padding: .55rem; cursor: pointer; background: var(--visitor-surface); color: var(--visitor-text);
    transition: border-color .12s, box-shadow .12s, transform .12s; }
.visitor-kdes-tpl:hover { border-color: var(--visitor-primary); box-shadow: var(--shadow-md); transform: translateY(-2px); }
.visitor-kdes-tpl-chip { height: 2.4rem; border-radius: var(--radius-sm); box-shadow: inset 0 0 0 1px rgba(0,0,0,.08); }
.visitor-kdes-tpl-name { font-weight: 600; font-size: .82rem; }
/* Sibling of -tpl-name inside the same card and defined nowhere, so a role template's description
   rendered at body size in body colour and competed with the name above it. */
.visitor-kdes-tpl-desc { font-size: .78rem; line-height: 1.4; color: var(--visitor-muted); }

/* Colour-theme swatches */
.visitor-kdes-themes { display: grid; grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr)); gap: .55rem; }
.visitor-kdes-theme { position: relative; border: 2px solid transparent; border-radius: var(--radius-md); padding: .3rem;
    cursor: pointer; background: var(--visitor-surface); display: flex; flex-direction: column; gap: .3rem; transition: border-color .12s, transform .12s; }
.visitor-kdes-theme:hover { transform: translateY(-2px); }
.visitor-kdes-theme-on { border-color: var(--visitor-primary); }
/* The theme chip this kiosk's design came from. Mirrors -theme-on's treatment so "selected" reads
   the same in both pickers on the page. */
.visitor-kdes-tpl-on { border-color: var(--visitor-primary); box-shadow: 0 0 0 1px var(--visitor-primary); }
.visitor-kdes-theme-grad { height: 2.1rem; border-radius: var(--radius-sm); position: relative; }
.visitor-kdes-theme-dot { position: absolute; top: .5rem; right: .6rem; width: .85rem; height: .85rem; border-radius: 50%; border: 2px solid rgba(255,255,255,.8); }
.visitor-kdes-theme-name { font-size: .72rem; color: var(--visitor-muted); text-align: center; }

.visitor-kdes-toggle { display: flex; gap: .35rem; flex-wrap: wrap; }
.visitor-kdes-lookups { display: flex; gap: .4rem; flex-wrap: wrap; justify-content: center; margin-top: .7rem; }
/* These chips live inside the card, which every surface now renders as a light panel, so they take
   the dark-on-light treatment unconditionally — the white-on-glass variant that used to be the base
   only ever looked right against the 13% veil this stylesheet no longer paints. */
.visitor-kdes-lookups span { font-size: .8rem; padding: .35rem .7rem; border-radius: 999px;
    border: 1px solid rgba(0,0,0,.15); background: rgba(0,0,0,.04); color: #334155; }
.visitor-color { padding: .15rem; height: 2.4rem; }

/* ===== Guard mobile (Phase 11) ===== */
.visitor-guard { max-width: 30rem; margin: 0 auto; min-height: 100vh; background: var(--visitor-bg); }
.visitor-guard-bar { position: sticky; top: 0; display: flex; align-items: center; justify-content: space-between;
    padding: .9rem 1rem; background: var(--visitor-primary); color: var(--visitor-on-primary, #fff); }
.visitor-guard-brand { font-weight: 700; }
.visitor-guard-exit { color: var(--visitor-on-primary, #fff); opacity: .9; font-size: .9rem; }
/* One-tap route to the roll-call from every guard screen. Sits left of Sign out and is deliberately
   more prominent than it — an officer reaches for this under alarm conditions. (CC-5.) */
.visitor-guard-emergency {
    color: var(--visitor-on-primary, #fff);
    font-size: .9rem;
    font-weight: 600;
    margin-left: auto;
    margin-right: 1rem;
    padding: .35rem .75rem;
    border: 1px solid rgba(255, 255, 255, .5);
    border-radius: 999px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
}
.visitor-guard-emergency:hover, .visitor-guard-emergency:focus-visible { background: rgba(255, 255, 255, .15); }
.visitor-guard-main { padding: 1rem; }
.visitor-guard-tiles { display: grid; gap: 1rem; }
.visitor-guard-count { text-align: center; padding: 1.25rem; background: var(--visitor-surface);
    border: 1px solid var(--visitor-border); border-radius: .75rem; }
.visitor-guard-count-value { display: block; font-size: 2.6rem; font-weight: 800; color: var(--visitor-brand-ink); line-height: 1; }
.visitor-guard-count-label { color: var(--visitor-muted); }
.visitor-guard-action { display: flex; align-items: center; gap: .75rem; padding: 1.1rem 1.25rem; font-size: 1.15rem;
    font-weight: 600; background: var(--visitor-surface); border: 1px solid var(--visitor-border); border-radius: .75rem; color: var(--visitor-text); }
.visitor-guard-action-icon { font-size: 1.6rem; }
.visitor-guard-field { margin-bottom: .75rem; }
.visitor-guard-result { padding: 1rem; border: 1px solid var(--visitor-border); border-radius: .75rem; background: var(--visitor-surface); margin-top: 1rem; }
.visitor-guard-result h2 { margin: 0 0 .35rem; }

/* ===== Notification bell (Phase 14) ===== */
.visitor-bell { position: relative; }
.visitor-bell-btn { position: relative; background: transparent; border: 0; font-size: 1.2rem; cursor: pointer; padding: .25rem; }
.visitor-bell-badge { position: absolute; top: -.2rem; right: -.4rem; background: var(--visitor-accent); color: #fff;
    border-radius: 999px; font-size: .65rem; font-weight: 700; padding: .05rem .3rem; min-width: 1rem; text-align: center; }
.visitor-bell-panel { position: absolute; right: 0; top: 2rem; width: 22rem; max-width: 90vw; z-index: 50;
    background: var(--visitor-surface); border: 1px solid var(--visitor-border); border-radius: .6rem; box-shadow: 0 12px 40px rgba(0,0,0,.18); }
.visitor-bell-head { display: flex; justify-content: space-between; align-items: center; padding: .6rem .8rem; border-bottom: 1px solid var(--visitor-border); }
.visitor-bell-empty { padding: 1rem .8rem; }
.visitor-bell-list { list-style: none; margin: 0; padding: 0; max-height: 24rem; overflow-y: auto; }
.visitor-bell-item { padding: .55rem .8rem; border-bottom: 1px solid var(--visitor-border); cursor: pointer; }
.visitor-bell-item:hover { background: var(--visitor-surface-2); }
.visitor-bell-unread { border-left: 3px solid var(--visitor-accent); }
.visitor-bell-item-title { font-weight: 600; font-size: .9rem; }
.visitor-bell-count { margin-left: .4rem; padding: 0 .35rem; border-radius: .6rem; background: var(--visitor-surface-2);
    border: 1px solid var(--visitor-border); color: var(--visitor-muted); font-size: .7rem; font-weight: 600; }
.visitor-bell-item-time { color: var(--visitor-muted); font-size: .75rem; margin-top: .15rem; }

/* ===== Branding editor (Phase 14) ===== */
.visitor-color-row { display: flex; flex-wrap: wrap; gap: 1rem; margin: .5rem 0 1rem; }
.visitor-color { display: flex; flex-direction: column; gap: .25rem; font-size: .82rem; color: var(--visitor-muted); }
.visitor-color input[type=color] { width: 3rem; height: 2rem; border: 1px solid var(--visitor-border); border-radius: .3rem; background: none; padding: 0; cursor: pointer; }

/* ===== Public surface (Phase 12) ===== */
.visitor-public { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 1rem; padding: 2rem 1rem; background: var(--visitor-bg); }
.visitor-public-card { width: 32rem; max-width: 94vw; background: var(--visitor-surface); border: 1px solid var(--visitor-border);
    border-radius: .9rem; padding: 2rem; box-shadow: 0 12px 40px rgba(0,0,0,.08); }
.visitor-public-title { margin: 0 0 .25rem; }
/* Tenant brand header on public pages (VIS-H1) — logo preferred, company name fallback. */
.visitor-public-brand { margin-bottom: 1rem; display: flex; justify-content: center; }
.visitor-public-brand img { max-height: 3rem; max-width: 16rem; object-fit: contain; }
.visitor-public-brand-name { font-size: 1.15rem; font-weight: 700; color: var(--visitor-text); }
.visitor-public-footer { color: var(--visitor-muted); font-size: .78rem; }
.visitor-public-done { text-align: center; }
.visitor-public-code { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 1.1rem; font-weight: 700;
    letter-spacing: .05em; margin: 1rem 0; padding: .6rem; background: var(--visitor-bg); border-radius: .5rem; word-break: break-all; }
/* The arrival pass (CC-7). Always on white regardless of theme — a QR needs light quiet-zone contrast
   to scan, and a dark-mode phone rendering it on a dark card is a code a reader cannot read. Sized so
   it still scans held at arm's length across a desk. */
.visitor-public-qr {
    display: block;
    margin: 1.25rem auto;
    width: 220px;
    height: 220px;
    padding: 12px;
    background: #fff;
    border-radius: .75rem;
    box-shadow: var(--shadow-md);
    image-rendering: pixelated; /* keep module edges crisp when the browser scales it */
}

/* The typed code is a fallback, not the headline — collapsed until someone needs to read it out. */
.visitor-public-codefallback { margin: .75rem 0; }
.visitor-public-codefallback > summary { cursor: pointer; color: var(--visitor-muted); font-size: .875rem; }

.visitor-prereg-doc { padding: .6rem 0; border-bottom: 1px solid var(--visitor-border); }
.visitor-prereg-doc-content { max-height: 8rem; overflow-y: auto; white-space: pre-wrap; margin: .35rem 0; }
.visitor-sigpad-canvas { border: 1px dashed var(--visitor-border); border-radius: .4rem; background: #fff; touch-action: none; display: block; }
.visitor-sigpad-actions { display: flex; gap: .5rem; margin-top: .4rem; }
.visitor-sigpad-error { color: var(--visitor-danger); }

.visitor-th-arrow { margin-left: .25rem; font-size: .8em; color: var(--visitor-brand-ink); }

/* ===== Row quick actions + group actions on lists (FD-5) ===== */

/* Row actions deliberately do NOT reuse .visitor-btn-sm. That variant computes to 27.3px, which fails
   WCAG 2.5.5 on touch, and this grid would instantly become the product's highest-frequency instance
   of it. 2rem/32px on a fine pointer, 2.75rem/44px on a coarse one (the media rule below). */
.visitor-btn-row { width: auto; min-height: 2rem; padding: .3rem .7rem; font-size: .82rem; }
.visitor-row-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; }
/* Never hover-only: a keyboard operator tabbing the grid must be able to see what they can reach. */
.visitor-row-actions-cell { white-space: nowrap; }
.visitor-row-msg { display: block; margin-top: .25rem; font-size: var(--text-sm); white-space: normal; max-width: 16rem; }
.visitor-row-msg-ok { color: var(--visitor-success-on, var(--visitor-success)); }
.visitor-row-msg-err { color: var(--visitor-danger); }

/* The checkbox is the whole target — the cell around it is not clickable, because a row-level click
   opens the visit. */
.visitor-select-cell input[type=checkbox] { width: 1.15rem; height: 1.15rem; }

.visitor-selection-bar { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin-bottom: 1rem;
    padding: .6rem .85rem; border: 1px solid var(--visitor-border-strong, var(--visitor-border));
    border-radius: var(--radius-lg); background: var(--visitor-surface-2); }
.visitor-selection-count { font-weight: 700; }
.visitor-selection-grow { flex: 1; }

.visitor-progress { display: flex; align-items: center; gap: .75rem; }
.visitor-progress-track { flex: 1; height: .5rem; min-width: 8rem; border-radius: 999px; background: var(--visitor-surface-3); overflow: hidden; }
.visitor-progress-fill { height: 100%; background: var(--visitor-primary); transition: width .15s linear; }
.visitor-bulk-failures { list-style: none; margin: .5rem 0 0; padding: 0; }
.visitor-bulk-failures li { padding: .2rem 0; }
.visitor-bulk-failure-name { font-weight: 600; }
.visitor-bulk-pages { list-style: none; margin: .5rem 0 0; padding: 0; column-width: 14rem; }

/* ===== In-app dialog (FD-5) ===== */
/* The product had NO modal component: naming a saved view used window.prompt and every confirmation
   used window.confirm — OS chrome inside a designed application, unthemeable, untranslatable, and
   suppressible by browser configuration, which would silently turn "confirm before a bulk operation"
   into "run it". */
.visitor-dialog-backdrop { position: fixed; inset: 0; z-index: 200; display: flex; align-items: center; justify-content: center;
    padding: 1rem; background: rgba(15, 23, 42, .45); }
.visitor-dialog { width: 30rem; max-width: 100%; max-height: 90vh; overflow-y: auto; background: var(--visitor-surface);
    border: 1px solid var(--visitor-border); border-radius: var(--radius-lg); box-shadow: 0 18px 50px rgba(0,0,0,.28); padding: 1.35rem; }
.visitor-dialog-title { margin: 0 0 .6rem; font-size: var(--text-lg); font-weight: 700; letter-spacing: -.01em; }
.visitor-dialog-body p { margin: 0 0 .6rem; }
/* The target of a destructive action, emphasised: naming what will be destroyed is what stops a
   confirmation being reflex-clicked, so it must not read as body copy. (UX-5 / WP-D1.) */
.visitor-dialog-target { font-weight: 600; color: var(--visitor-text); }
.visitor-dialog-detail { list-style: none; margin: 0 0 .75rem; padding: 0; color: var(--visitor-muted); }
.visitor-dialog-detail li { padding: .1rem 0; }
.visitor-dialog-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: .5rem; margin-top: 1rem; }

/* --- Touch targets: enlarge small icon controls on touch devices (WCAG 2.5.5) --- */
@media (pointer: coarse) {
    .visitor-hamburger, .visitor-theme-toggle, .visitor-bell-btn { min-width: 2.75rem; min-height: 2.75rem; }
    .visitor-bell-btn { padding: .6rem; }
    /* FD-5: row and group action controls are the highest-frequency controls on the busiest screen in
       the product, so they are enlarged here rather than left out of this rule as every other control
       on that screen currently is. */
    .visitor-btn-row { min-height: 2.75rem; padding: .5rem .9rem; }
    .visitor-selection-bar .visitor-btn { min-height: 2.75rem; }
    .visitor-dialog-actions .visitor-btn { min-height: 2.75rem; }
    .visitor-select-cell input[type=checkbox] { width: 1.5rem; height: 1.5rem; margin: .625rem; }
}

/* --- Accessible form fields (VisitorField) --- */
.visitor-required { color: var(--visitor-danger); margin-left: .15rem; }
.visitor-field-help { display: block; font-size: var(--text-sm); color: var(--visitor-muted); margin-top: .25rem; }
.visitor-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; }

/* --- Reception stepper + ID scan --- */
.visitor-stepper { display: flex; flex-wrap: wrap; gap: 1.25rem; list-style: none; margin: 0 0 1.25rem; padding: 0; }
.visitor-step { display: inline-flex; align-items: center; gap: .5rem; color: var(--visitor-muted); font-size: var(--text-sm); }
.visitor-step-num { width: 1.6rem; height: 1.6rem; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
    background: var(--visitor-surface-3); color: var(--visitor-muted); border: 1px solid var(--visitor-border); font-weight: 700; font-size: var(--text-xs); }
.visitor-step-active .visitor-step-num { background: var(--visitor-primary); color: var(--visitor-on-primary, #fff); border-color: var(--visitor-primary); }
.visitor-step-active .visitor-step-label { color: var(--visitor-text); font-weight: 600; }
.visitor-step-done .visitor-step-num { background: var(--visitor-success); color: #fff; border-color: var(--visitor-success); }
.visitor-idscan { margin-bottom: 1rem; }
.visitor-idscan-input { font-family: ui-monospace, monospace; }
.visitor-photocap { display: flex; flex-direction: column; gap: .6rem; align-items: flex-start; }
.visitor-photocap-video, .visitor-photocap-preview { width: 320px; max-width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
    background: var(--visitor-surface-3); border: 1px solid var(--visitor-border); border-radius: var(--radius-md); }
.visitor-hidden { display: none; }

/* --- Global topbar search --- */
.visitor-search { position: relative; flex: 1 1 22rem; max-width: 26rem; }
.visitor-search-icon { position: absolute; left: .65rem; top: 50%; transform: translateY(-50%); color: var(--visitor-muted); width: 1rem; height: 1rem; pointer-events: none; }
.visitor-search-input { width: 100%; padding-left: 2.1rem; }
.visitor-search-panel { position: absolute; top: calc(100% + .35rem); left: 0; right: 0; z-index: 60; background: var(--visitor-surface);
    border: 1px solid var(--visitor-border); border-radius: var(--radius-md); box-shadow: var(--shadow-lg); overflow: hidden; }
.visitor-search-empty { padding: .75rem 1rem; color: var(--visitor-muted); font-size: var(--text-sm); }
.visitor-search-results { list-style: none; margin: 0; padding: .25rem; max-height: 22rem; overflow-y: auto; }
.visitor-search-result { display: flex; flex-direction: column; gap: .15rem; width: 100%; text-align: left; background: none; border: 0;
    padding: .5rem .65rem; border-radius: var(--radius-sm); cursor: pointer; font: inherit; color: var(--visitor-text); }
.visitor-search-result:hover { background: var(--visitor-surface-2); }
.visitor-search-result-name { font-weight: 600; }
.visitor-search-result-meta { font-size: var(--text-sm); color: var(--visitor-muted); display: flex; align-items: center; gap: .35rem; }
.visitor-search-all { width: 100%; text-align: left; background: none; border: 0; border-top: 1px solid var(--visitor-border); margin-top: .25rem;
    padding: .55rem .65rem; cursor: pointer; font: inherit; font-size: var(--text-sm); color: var(--visitor-secondary); }
.visitor-search-all:hover { background: var(--visitor-surface-2); }
@media (max-width: 768px) { .visitor-search { display: none; } }

/* --- Nav count badges --- */
.visitor-nav-badge { margin-left: auto; min-width: 1.25rem; height: 1.25rem; padding: 0 .35rem; border-radius: 999px;
    background: var(--visitor-secondary); color: #fff; font-size: var(--text-xs); font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center; line-height: 1; }
.visitor-nav-badge-alert { background: var(--visitor-danger); }

/* --- Breadcrumbs --- */
.visitor-breadcrumbs { margin-bottom: 1rem; }
.visitor-breadcrumbs ol { list-style: none; display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin: 0; padding: 0; }
.visitor-breadcrumbs li { display: inline-flex; align-items: center; gap: .35rem; font-size: var(--text-sm); }
.visitor-breadcrumbs a { color: var(--visitor-muted); text-decoration: none; }
.visitor-breadcrumbs a:hover { color: var(--visitor-text); text-decoration: underline; }
.visitor-breadcrumbs [aria-current="page"] { color: var(--visitor-text); font-weight: 600; }
.visitor-breadcrumb-sep { color: var(--visitor-muted); }

/* --- Panic / duress alarms --- */
.visitor-btn-danger { background: var(--visitor-danger); color: #fff; width: auto; box-shadow: var(--shadow-sm); }
.visitor-btn-danger:hover { background: color-mix(in srgb, var(--visitor-danger) 88%, #000); box-shadow: var(--shadow-md); }
.visitor-user { text-decoration: none; }
.visitor-user:hover { color: var(--visitor-text); }

.visitor-guard-action-danger { background: var(--visitor-danger-bg); border-color: var(--visitor-danger); color: var(--visitor-danger); }

/* Sticky, because it sat in normal document flow: an officer part-way down an incident form had
   scrolled the alarm indicator off the screen and had no persistent tell that an alarm was live.
   Sticky rather than fixed keeps it in flow, which the layouts' own comments say is the intent —
   it still occupies its space and pushes content down, it just stops scrolling away.

   The offset is a token because the two layouts differ: MainLayout parks it under the sticky
   topbar (.visitor-main sets the offset below), while the guard shell's header scrolls away and
   the banner belongs at the very top. z-index sits above the sticky table headers (:387) and the
   search panel (:1097) and below the modal backdrop (:200) — a scrolled grid must not cover the
   one banner that must never be covered, and a dialog the officer opened still owns the screen. */
.visitor-panic-banner {
    position: sticky; top: var(--visitor-panic-top, 0); z-index: 70;
    background: var(--visitor-danger-bg); border: 2px solid var(--visitor-danger); border-radius: var(--radius-md);
    padding: .75rem 1rem; margin-bottom: 1rem; box-shadow: var(--shadow-md);
    animation: visitor-panic-pulse 1.6s ease-in-out infinite;
}
.visitor-panic-banner-head { display: flex; align-items: center; gap: .5rem; color: var(--visitor-danger); font-size: var(--text-md); margin-bottom: .4rem; }
.visitor-panic-siren { font-size: 1.25rem; }
.visitor-panic-mute { margin-left: auto; background: none; border: 0; cursor: pointer; font-size: 1.05rem; line-height: 1;
    padding: .15rem .35rem; border-radius: var(--radius-sm); }
.visitor-panic-mute:hover { background: color-mix(in srgb, var(--visitor-danger) 12%, transparent); }
/* The two states the banner could previously be in without saying so: sound off, and not live.
   Both sit under the head rather than beside the count — they qualify the alarm, they don't
   compete with it. */
.visitor-panic-mute-note { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin: 0 0 .5rem;
    font-size: var(--text-sm); color: var(--visitor-danger); }
.visitor-panic-stale { margin-left: .5rem; padding: .1rem .4rem; border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--visitor-danger) 12%, transparent); color: var(--visitor-danger);
    font-size: var(--text-xs); font-weight: 600; white-space: nowrap; }
/* The location + meta pair inside an alarm row. Undefined, so the row's flex put it beside the
   acknowledge button with no growth and the two lines never claimed the space they needed. */
.visitor-panic-info { display: flex; flex-direction: column; gap: .1rem; min-width: 0; flex: 1 1 auto; }
.visitor-panic-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .4rem; }
.visitor-panic-row { display: flex; align-items: center; justify-content: space-between; gap: .75rem; flex-wrap: wrap;
    background: var(--visitor-surface); border-radius: var(--radius-sm); padding: .5rem .65rem; }
.visitor-panic-row-urgent { border-left: 4px solid var(--visitor-danger); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--visitor-danger) 30%, transparent); }
.visitor-resolve-inline { display: flex; flex-direction: column; gap: .4rem; min-width: 16rem; }
.visitor-panic-where { font-weight: 600; color: var(--visitor-text); }
.visitor-panic-meta { display: block; color: var(--visitor-muted); font-size: var(--text-sm); }
.visitor-panic-ack { color: var(--visitor-success); }
.visitor-panic-actions { display: flex; gap: .4rem; }
@keyframes visitor-panic-pulse {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--visitor-danger) 35%, transparent); }
    50% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--visitor-danger) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .visitor-panic-banner { animation: none; } }

/* Blazor's error banner is hidden until the framework unhides it on a circuit
   failure. That rule used to live ONLY in MainLayout.razor.css — a SCOPED
   stylesheet, so it applied to nothing outside MainLayout. Every other layout
   (Kiosk, Guard, Public, Greeter) renders the same #blazor-error-ui div and so
   shipped it unstyled: permanently in the DOM, read out by screen readers, and
   visible as stray "An unhandled error has occurred" text on any light surface —
   on an unattended lobby kiosk, of all places. Global, unscoped, so it covers
   every layout including ones added later. */
#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
    color: #111;
}

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

/* ---- Visitor avatar (CC-4 / FD-4) -------------------------------------------------------------
   The face, or a designed fallback. Sizes are deliberate rather than inherited: sm keeps a table row
   at its existing height (the row is .7rem padding + a ~20px line box, so 24px fits without changing
   the grid's density), md suits cards and detail headers, lg is the greeter's "is this them?" moment.
   Photos are cropped to fill so a portrait and a landscape capture read the same in a list.        */
/* Name-and-face row at a decision point (reception issue step). */
.visitor-issue-head { display: flex; align-items: center; gap: .75rem; }
.visitor-issue-head p { margin: 0; }

.visitor-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    border-radius: 50%;
    overflow: hidden;
    font-weight: 600;
    line-height: 1;
    letter-spacing: .01em;
    user-select: none;
    background: var(--visitor-surface-3);
    color: var(--visitor-muted);
}

.visitor-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

.visitor-avatar-sm { width: 24px; height: 24px; font-size: .625rem; }
.visitor-avatar-md { width: 40px; height: 40px; font-size: .875rem; }
.visitor-avatar-lg { width: 128px; height: 128px; font-size: 2.5rem; box-shadow: 0 0 0 4px var(--visitor-surface-2); }

/* Pairs the avatar with the name it belongs to without disturbing the cell's existing type. */
.visitor-identity { display: inline-flex; align-items: center; gap: .55rem; min-width: 0; }
.visitor-identity > div { min-width: 0; }

/* A matched watchlist entry at a block: the reference photo large enough to compare a face against,
   with the identifying detail beside it. Wraps on a narrow reception screen rather than shrinking the
   photo, since the photo is the point. */
.visitor-match-card { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; }
.visitor-match-detail { min-width: 12rem; flex: 1 1 12rem; display: flex; flex-direction: column; gap: .2rem; }

/* ---- Live indicator (CC-8) ---------------------------------------------------------------------
   Says whether a screen is actually receiving updates. A list that looks current but is not is worse
   than one that admits it is stale — especially on a guard console answering "who is in the building
   right now". Deliberately quiet: it is a status, not an alert.                                    */
.visitor-live {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    font-size: var(--text-xs);
    color: var(--visitor-muted);
    white-space: nowrap;
}

.visitor-live-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: currentColor;
    flex: 0 0 auto;
}

.visitor-live-on { color: var(--v-color-status-ok, #15803D); }
.visitor-live-on .visitor-live-dot { animation: visitor-live-pulse 2.4s ease-in-out infinite; }

/* Not-live is muted, not red: losing the socket is a degraded state, not a fault the user caused. */
.visitor-live-off { color: var(--visitor-muted); }

@keyframes visitor-live-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: .35; }
}

@media (prefers-reduced-motion: reduce) {
    .visitor-live-on .visitor-live-dot { animation: none; }
}

/* ---- Camera QR scanning (CC-7) -----------------------------------------------------------------
   The stage is capped and centred so a tablet held at a desk shows a viewfinder rather than a
   full-bleed video wall. object-fit: cover keeps the preview honest when the camera's aspect ratio
   doesn't match the box — a letterboxed preview makes people aim at the wrong part of the frame.  */
/* Root of the QR scanner component (CC-7). Every child was defined and the container was not, so
   the stage, bar and hint stacked with no shared spacing. */
.visitor-qr { display: flex; flex-direction: column; gap: .6rem; align-items: center; }
.visitor-qr-stage {
    position: relative;
    width: 100%;
    max-width: 420px;
    aspect-ratio: 4 / 3;
    margin: 0 auto;
    border-radius: var(--radius-md);
    overflow: hidden;
    background: #000;
}

.visitor-qr-video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Tells the visitor where to hold the pass. Purely decorative — the decoder reads the whole frame,
   so a code outside the reticle still scans; this is aiming guidance, not a crop. */
.visitor-qr-reticle {
    position: absolute;
    inset: 50% auto auto 50%;
    transform: translate(-50%, -50%);
    width: 62%;
    aspect-ratio: 1;
    border: 3px solid rgba(255, 255, 255, .9);
    border-radius: var(--radius-md);
    box-shadow: 0 0 0 100vmax rgba(0, 0, 0, .35);
}

.visitor-qr-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    max-width: 420px;
    margin: .6rem auto 0;
}

.visitor-qr-hint { color: var(--visitor-muted); font-size: var(--text-sm); }

/* ---- Mud shell adjustments (D-VIS-UX-1, 4a) -------------------------------------------------- */
/* The product's own icon set rides inside MudNavLink content; give it the gap MudNavLink's Icon
   parameter would have provided, and keep the section headers/badges from the old rail. */
.mud-nav-link .visitor-nav-icon { margin-right: .6rem; flex: none; }
.visitor-drawer-brand { padding-top: .9rem; padding-bottom: .4rem; }
.visitor-appbar-user { font-size: .9rem; opacity: .9; }

/* ---- /visits on MudTable (D-VIS-UX-1, 4b) ---------------------------------------------------- */
.visitor-toolbar-mud { align-items: flex-start; }
.visitor-toolbar-mud .visitor-filter { max-width: 11rem; min-width: 8.5rem; }
.visitor-toolbar-mud .visitor-filter-search { max-width: 16rem; }
/* VIS-DELTA-3's density: 14px data text, one predictable row height. */
.visitor-visits-table .mud-table-cell { font-size: .875rem; }
.visitor-visits-table tbody tr { cursor: pointer; }
.visitor-row-name { color: inherit; text-decoration: none; }
.visitor-row-name:hover { text-decoration: underline; }
.visitor-col-item { display: flex; align-items: center; gap: .25rem; }

/* ---- Dashboard on the Mud shell (D-VIS-UX-1, 4b) --------------------------------------------- */
/* Three children now share the head: title left, sub pushed right, expand after it — the auto
   margin sits on the SUB so two-child heads keep their old justify-between look untouched. */
.visitor-card-head .visitor-cell-sub { margin-left: auto; }
.visitor-card-expand { margin-left: .35rem; opacity: .55; }
.visitor-card:hover .visitor-card-expand { opacity: 1; }
/* Inside the expand dialog the chart owns the width; lift the inline heatmap's height cap so the
   cells and axis labels finally render at a legible size (the whole point of VIS-DELTA-2). */
.visitor-expand-body { padding: .5rem 0 1rem; }
.visitor-expand-body .visitor-heatmap { max-height: none; }
.visitor-expand-body .visitor-chart { max-height: none; }
/* The dashboard visitor board matches the /visits grid's 14px density. */
.visitor-onsite .visitor-table td { font-size: .875rem; }

/* Shared dense Mud list table for the desk surfaces (approvals/contractors/delivery/events),
   the same 14px density the /visits grid set (4b). */
.visitor-mud-table .mud-table-cell { font-size: .875rem; }
.visitor-mud-table tbody tr { cursor: pointer; }

/* ---- The front-desk live board (VIS-DELTA-5) ------------------------------------------------- */
.visitor-desk-main { min-width: 0; }
.visitor-desk { display: grid; grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); gap: 1rem; align-items: start; }
@media (max-width: 1100px) { .visitor-desk { grid-template-columns: 1fr; } }
.visitor-desk-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: .75rem; }
.visitor-desk-card { display: flex; gap: .75rem; align-items: center; padding: .75rem; border-radius: var(--radius-md);
    background: var(--visitor-surface); border: 1px solid var(--visitor-border); border-left: 4px solid var(--visitor-ok, #10B981);
    color: inherit; text-decoration: none; transition: box-shadow var(--visitor-transition), transform var(--visitor-transition); }
.visitor-desk-card:hover { box-shadow: var(--shadow-lg); transform: translateY(-2px); }
.visitor-desk-escortpending { border-left-color: var(--visitor-warn, #F59E0B); }
.visitor-desk-card-body { min-width: 0; }
.visitor-desk-card-body .visitor-cell-strong { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.visitor-desk-card-meta { display: flex; gap: .5rem; align-items: center; margin-top: .3rem; flex-wrap: wrap; }
.visitor-desk-badge { font-family: var(--font-mono, monospace); font-size: .7rem; padding: .1rem .35rem;
    border: 1px solid var(--visitor-border); border-radius: .3rem; color: var(--visitor-muted); }
/* Recognition-grade face for the board (the component already accepts any size token). */
.visitor-avatar-xl { width: 64px; height: 64px; font-size: 1.4rem; flex: none; }
/* A genuinely new arrival slides in once; everything else stays put. */
@keyframes visitor-desk-arrive { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.visitor-desk-new { animation: visitor-desk-arrive .45s ease-out; }
@media (prefers-reduced-motion: reduce) { .visitor-desk-new { animation: none; } }
.visitor-desk-side { display: flex; flex-direction: column; gap: 1rem; }
.visitor-desk-expected { list-style: none; margin: 0; padding: 0; }
.visitor-desk-expected li { display: flex; align-items: center; gap: .5rem; padding: .45rem 0; border-bottom: 1px solid var(--visitor-border); }
.visitor-desk-expected li:last-child { border-bottom: 0; }
.visitor-desk-expected-name { display: flex; flex-direction: column; min-width: 0; flex: 1; color: inherit; text-decoration: none; }
.visitor-desk-expected-name:hover .visitor-cell-strong { text-decoration: underline; }
.visitor-desk-attention { list-style: none; margin: 0; padding: 0; }
.visitor-desk-attention li { display: flex; justify-content: space-between; align-items: center; padding: .45rem 0; border-bottom: 1px solid var(--visitor-border); }
.visitor-desk-attention li:last-child { border-bottom: 0; }
.visitor-desk-attn-n { font-weight: 700; color: var(--visitor-muted); }
.visitor-desk-attn-on .visitor-desk-attn-n { color: var(--visitor-warn, #F59E0B); }
.visitor-desk-attn-alert .visitor-desk-attn-n { color: var(--visitor-danger, #EF4444); }

/* ---- 4c bridge: the bespoke primitives render in the Mud visual language ---------------------- */
/* The six desk surfaces are REAL Mud components now; the ~50-page admin/report/detail tail still
   carries .visitor-table/.visitor-btn/.visitor-input markup. This bridge closes the visual gap —
   the same 14px density, uppercase-free headers and compact padding the Mud tables render — so a
   user crossing from /visits to /config/users sees one product, while per-page component
   conversion continues opportunistically. Metrics mirror .visitor-mud-table, not invented. */
.visitor-table th, .visitor-table td { font-size: .875rem; padding: .5rem .75rem; }
.visitor-table th { text-transform: none; letter-spacing: 0; font-size: .875rem; font-weight: 600;
    color: var(--visitor-fg, inherit); background: var(--visitor-surface); }
.visitor-btn { font-size: .8125rem; padding: .45rem .9rem; text-transform: uppercase; letter-spacing: .02em; }
.visitor-btn-sm { font-size: .75rem; padding: .3rem .7rem; }
.visitor-input, .visitor-select { font-size: .875rem; }
.visitor-filter-narrow { max-width: 7rem; }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   AUTHENTICATED ART DIRECTION (VIS-UX-VIBRANT)

   Founder, 2026-08-22: "a lot of flat color buttons… doesn't match the pizzazz of the other
   software… I want modern, vibrant, visually appealing."

   THE MEASUREMENT THAT DEFINED THE WORK. Taken in a browser across two signed-in consoles rather
   than by eye: Visitor rendered 0 gradients, 0 backdrop blurs, a median box-shadow blur of 2px and
   1 of 20 visible buttons carrying any shadow. Uptime measured the same. Every product's SIGN-IN
   screen, meanwhile, carries the Monument art direction — 5-6 gradients, 4 blurred layers, real
   motion. The estate had a visual language; it stopped at the login boundary. That is why adopting
   MudBlazor did not fix this: it made Visitor consistent with siblings that are themselves stock
   Material. There was no authenticated art direction to adopt. This is it.

   WHY IT LIVES IN THE TOKEN LAYER. Visitor is ~109 pages. Restyling them one at a time is how a
   redesign spends its budget on the first fifteen screens and ships two products in one binary.
   Everything here attaches to MudBlazor's own class names and to the bespoke primitives the 4c
   bridge already covers, so every page lifts together and a screen nobody has opened since Wave 0
   gets the same treatment as /visits.

   THE RULE NOTHING BREAKS: no brand colour is written down. Depth, sheen, wash and glow are mixed
   from --visitor-accent and from each tile's own --visitor-tile-color, which come from the TENANT's
   theme claims and from the status of the metric. Meridian renders this in its amber (#f59e0b),
   Kingsmere in Vahary violet, St. Auriol in teal — one stylesheet, and a hardcoded violet here
   would have quietly un-branded every paying tenant.

   THEMING IS THREE-STATE, NOT TWO. data-v-theme is absent until the viewer explicitly picks a side
   (verified at runtime: both theme attributes read null on a default load, and the dark palette is
   arriving from prefers-color-scheme). So the light values are the bare :root default, dark is
   applied by the media query GUARDED against an explicit light choice, and again by the explicit
   dark attribute. Writing dark as the default — the first thing I tried — hands dark-amplitude
   shadows to a light-OS viewer who never chose anything, and they turn to mud on a white surface.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

:root {
    /* Elevation is a two-part ramp: a tight contact shadow plus a wide ambient one. The single 1px
       grey shadow this product used to carry reads as a border, not as height — which is precisely
       why every surface looked pasted onto the page instead of resting above it. */
    --vx-e1: 0 1px 2px rgba(16,24,40,.06), 0 2px 8px -3px rgba(16,24,40,.10);
    --vx-e2: 0 2px 4px rgba(16,24,40,.06), 0 10px 24px -8px rgba(16,24,40,.14);
    --vx-e3: 0 4px 10px rgba(16,24,40,.08), 0 22px 48px -14px rgba(16,24,40,.18);

    /* The top-lit sheen that separates a raised surface from the page. Deliberately weak: at these
       values it reads as light falling across a panel; at 15% it reads as a gradient somebody
       applied to a panel. */
    /* --vx-sheen and --vx-sheen-btn were retired under UIC-09: Machined Truth forbids gradients,
       and they were the last ones in this file. Deliberately not left defined-but-unused -- a token
       nothing reads is an invitation to reach for it again. */
    --vx-inner-hi: inset 0 1px 0 rgba(255,255,255,.35);

    /* The page's light source. One soft accent wash behind the content instead of a flat field —
       the cheapest thing on this list and the one that stops a dark UI reading as a black rectangle. */

    --vx-radius: 14px;
    --vx-radius-sm: 10px;
    --vx-ease: 180ms cubic-bezier(.4, 0, .2, 1);
}

/* Dark: applied by preference UNLESS the viewer explicitly chose light, and by the explicit dark
   attribute. Both blocks carry the same values — a dark surface absorbs a heavier shadow, and the
   white sheen that lifts a dark panel would blow out a light one. */
@media (prefers-color-scheme: dark) {
    :root:not([data-v-theme="light"]) {
        --vx-e1: 0 1px 2px rgba(0,0,0,.28), 0 2px 10px -3px rgba(0,0,0,.30);
        --vx-e2: 0 2px 6px rgba(0,0,0,.32), 0 12px 30px -8px rgba(0,0,0,.44);
        --vx-e3: 0 6px 14px rgba(0,0,0,.36), 0 26px 60px -14px rgba(0,0,0,.58);
        --vx-inner-hi: inset 0 1px 0 rgba(255,255,255,.14);
    }
}
:root[data-v-theme="dark"] {
    --vx-e1: 0 1px 2px rgba(0,0,0,.28), 0 2px 10px -3px rgba(0,0,0,.30);
    --vx-e2: 0 2px 6px rgba(0,0,0,.32), 0 12px 30px -8px rgba(0,0,0,.44);
    --vx-e3: 0 6px 14px rgba(0,0,0,.36), 0 26px 60px -14px rgba(0,0,0,.58);
    --vx-inner-hi: inset 0 1px 0 rgba(255,255,255,.14);
}

/* Glow is the accent's own colour thrown as light, and it is the single biggest contributor to
   "vibrant". Derived, never picked, so it follows the tenant. */
:root {
    --vx-glow: 0 6px 20px -8px color-mix(in srgb, var(--visitor-accent) 70%, transparent);
    --vx-glow-lg: 0 10px 34px -10px color-mix(in srgb, var(--visitor-accent) 80%, transparent);
    --vx-ring: 0 0 0 3px color-mix(in srgb, var(--visitor-accent) 34%, transparent);
}

/* ---- Links stay readable on the surface they land on ------------------------------------------ */
/* TenantThemeCss emits the tenant's own link colour lifted for each theme; the fallback is the raw
   secondary, so a tenant with no stored theme renders exactly as it did before this existed. */
:root { --visitor-link: var(--visitor-link-on-light, var(--visitor-secondary)); }
@media (prefers-color-scheme: dark) {
    :root:not([data-v-theme="light"]) { --visitor-link: var(--visitor-link-on-dark, var(--visitor-secondary)); }
}
:root[data-v-theme="dark"] { --visitor-link: var(--visitor-link-on-dark, var(--visitor-secondary)); }
.visitor-link { color: var(--visitor-link); }

/* ---- The page gets a light source ------------------------------------------------------------ */
/* UIC-09: the ambient wash is gone. A 460px radial gradient in the tenant's hue sat across the top
   of EVERY page in this product -- the largest gradient in the estate, and the one nobody names when
   they say "Visitor looks different", because it is too diffuse to point at. Machined Truth: flat
   fields. The content region is now the surface colour, as it is in every other console. */
.visitor-main, .mud-main-content { position: relative; }

/* ---- Surfaces --------------------------------------------------------------------------------- */
/* UIC-09: the sheen is gone from every primitive it was applied to -- cards, buttons, table heads
   and chips, as well as the KPI tiles below. It was a top-down white gradient (55% in light, 5.5% in
   dark) layered as a background-IMAGE so it could never fight a tenant's brand, which was careful
   and correct, and still a gradient. Machined Truth forbids them: flat fields.

   Depth and geometry are NOT touched -- radius, elevation and the accent glow on a primary action
   stay, because those are not gradients and are not what this rule is about. What goes is the
   painted-on light. */
.visitor-card, .mud-paper {
    border-radius: var(--vx-radius);
    box-shadow: var(--vx-e1);
    transition: box-shadow var(--vx-ease), transform var(--vx-ease), border-color var(--vx-ease);
}
.mud-paper { border: 1px solid var(--visitor-border); }
.mud-paper.mud-elevation-0 { box-shadow: none; }
.mud-paper.mud-elevation-2, .mud-paper.mud-elevation-3 { box-shadow: var(--vx-e2); }
.mud-paper.mud-elevation-4, .mud-paper.mud-elevation-5,
.mud-paper.mud-elevation-6, .mud-paper.mud-elevation-8 { box-shadow: var(--vx-e3); }
.mud-dialog { border-radius: var(--vx-radius); box-shadow: var(--vx-e3); }

/* ---- The app bar: flat, like the rest of the estate -------------------------------------------- */
/* UIC-09 / D-UIC-3. This block used to make the bar GLASS -- a sheen gradient, saturate(165%)
   blur(14px), its own border and a --vx-e1 shadow -- on the argument that "content sliding under a
   translucent bar is the strongest single 'this is current software' signal available."

   It is a good argument and it lost to a better one. Machined Truth is explicit: hard margins,
   hairline rules, flat fields, no gradients. Shell 0.4.0 made a flat bar with a 1px rule the estate
   default, and this rule was quietly defeating it -- with Shell 0.4.0 the bar still computed
   `rgba(0,0,0,.28) 0 1px 2px, rgba(0,0,0,.3) 0 2px 10px -3px` in dark, because a product stylesheet
   beats a shell default. Correct cascade, wrong outcome: it meant Visitor was the one console that
   did not go flat, and nobody could see that without opening a browser and reading a computed style.

   The border is deliberately NOT restated here. VaharyAppShell sets it as an inline Style from
   --mud-palette-lines-default, which follows the theme (including dark) exactly as MudBlazor's own
   dividers do. A second border declaration would be a second thing to keep in step. */

/* ---- KPI tiles: flat, and still sorted by colour ----------------------------------------------- */
/* UIC-09, second pass. This block used to light each tile with its own status colour -- a --vx-sheen
   gradient over the surface, a --vx-e1 elevation shadow, a radial corner wash at 26% of the tile's
   hue, a gradient on the 3px rule, and a coloured glow under the icon and on hover. The argument was
   that it "lets the eye sort five tiles by hue before reading a single number", and that argument is
   correct and survives intact -- what carried it was never the gradients.

   Machined Truth is explicit: hard margins, hairline rules, FLAT FIELDS, gradients forbidden. Every
   other console in the estate uses flat surfaces with a hairline. These tiles were the last thing on
   a Visitor screen that no other product does, once the app bar stopped being a brand-coloured slab.

   WHAT IS DELIBERATELY KEPT. The colour-coding itself, because it is information rather than
   decoration: the 3px top rule and the icon's tinted field still come from --visitor-tile-color, so
   five tiles still sort by hue at a glance. They are FLAT now -- one colour, no fade. And the rule
   the block below the app bar states still holds here: no brand colour is written down anywhere in
   this file. Every value is mixed from the tenant's own theme, so Meridian still renders amber,
   Kingsmere violet and St. Auriol teal.

   The 3px rule is flattened at its BASE declaration too (~line 305), not overridden here. An
   override that cancels a gradient with a gradient is two things to keep in step; there is now one
   flat declaration and nothing arguing with it. */
.visitor-tile-link:hover {
    /* Hover feedback stays -- it is an affordance, not brand decoration -- minus the coloured glow.
       The lift and the border-colour change in the base rule carry it on their own. */
    box-shadow: var(--shadow-lg);
}
/* A gradient numeral was tried here and removed. Measured off painted pixels at 14.27:1 it was not
   a contrast regression — but the effect is almost invisible when it works, and its lower stop is
   mixed from a colour the CUSTOMER picks, so how legible it lands varies by tenant. The one element
   on the page whose entire job is to be read at a glance from across a reception desk does not get a
   decoration with a tenant-dependent floor. The energy stays in the washes and glows, where it reads. */

/* ---- Buttons: the "flat colour" complaint, answered ------------------------------------------- */
/* Three things separate a control from a coloured rectangle: a fill with a light source, an inner
   top highlight implying a bevelled edge, and a coloured shadow so it sits ABOVE the page rather
   than being painted onto it. */
.mud-button-root, .visitor-btn {
    border-radius: var(--vx-radius-sm);
    text-transform: none;              /* SHOUTING CAPS was most of what dated this UI */
    letter-spacing: .01em;
    font-weight: 600;
    transition: box-shadow var(--vx-ease), transform var(--vx-ease), filter var(--vx-ease);
}
.mud-button-filled, .visitor-btn-primary, .visitor-btn-danger {
    box-shadow: var(--vx-e1), var(--vx-inner-hi);
}
/* Only the primary action on a screen is allowed to glow. Spend it once. */
.mud-button-filled.mud-button-filled-primary,
.mud-button-filled.mud-button-filled-secondary,
.visitor-btn-primary {
    box-shadow: var(--vx-glow), var(--vx-e1), var(--vx-inner-hi);
}
.mud-button-filled:hover, .visitor-btn-primary:hover, .visitor-btn-danger:hover {
    transform: translateY(-1px);
    filter: brightness(1.07) saturate(1.05);
    box-shadow: var(--vx-glow-lg), var(--vx-e2), var(--vx-inner-hi);
}
.mud-button-filled:active, .visitor-btn:active { transform: translateY(0); filter: brightness(.97); }

.mud-button-outlined, .visitor-btn-secondary, .visitor-btn-ghost {
    transition: background-color var(--vx-ease), border-color var(--vx-ease), box-shadow var(--vx-ease);
}
.mud-button-outlined:hover, .visitor-btn-secondary:hover, .visitor-btn-ghost:hover {
    border-color: color-mix(in srgb, var(--visitor-accent) 62%, transparent);
    background-color: color-mix(in srgb, var(--visitor-accent) 12%, transparent);
    box-shadow: var(--vx-e1);
}

/* Keyboard focus is a designed state, not a browser default — and it is the same accent ring the
   hover glow uses, so the two read as one system. */
.mud-button-root:focus-visible, .visitor-btn:focus-visible,
.mud-nav-link:focus-visible, .visitor-input:focus-visible, .visitor-select:focus-visible {
    outline: none;
    box-shadow: var(--vx-ring), var(--vx-e1);
}

/* ---- Navigation: the active item is lit, not merely tinted ------------------------------------ */
.mud-nav-link, .visitor-nav-link {
    border-radius: var(--vx-radius-sm);
    transition: background-color var(--vx-ease), color var(--vx-ease), box-shadow var(--vx-ease);
}
.mud-nav-link:hover, .visitor-nav-link:hover {
    background-color: color-mix(in srgb, var(--visitor-accent) 12%, transparent);
}
/* The active item is a FLAT tint plus the 3px accent edge (UIC-09). It was a left-to-right fade from
   28% to 6% of the accent, which is a gradient doing a job a flat tint does at least as well -- the
   inset edge and the weight are what actually say "you are here". */
.mud-nav-link.active, .mud-nav-link.mud-nav-link-active,
.visitor-nav-link.active {
    background-color: color-mix(in srgb, var(--visitor-accent) 16%, transparent);
    box-shadow: inset 3px 0 0 var(--visitor-accent);
    font-weight: 600;
}

/* ---- Tables, inputs, chips -------------------------------------------------------------------- */
.mud-table, .visitor-table-wrap { border-radius: var(--vx-radius); overflow: hidden; box-shadow: var(--vx-e1); }
/* The head row separates from the body by the table's own border, not by a painted highlight. */
.mud-table-row:hover, .visitor-table tbody tr:hover {
    background-color: color-mix(in srgb, var(--visitor-accent) 7%, transparent);
}
.visitor-input, .visitor-select, .mud-input-control .mud-input-slot { border-radius: var(--vx-radius-sm); }
.mud-chip, .visitor-badge { border-radius: 999px; font-weight: 600; }

/* ---- Motion, and the promise to switch it off -------------------------------------------------- */
/* Every transform above is decoration. Someone who has asked the OS for less motion is not asking
   for a lesser product, so this removes the movement and keeps every colour, shadow and glow. */
@media (prefers-reduced-motion: reduce) {
    .visitor-card, .mud-paper, .visitor-chart-wrap, .mud-button-root, .visitor-btn,
    .mud-nav-link, .visitor-nav-link, .visitor-tile, .visitor-tile-link { transition: none !important; }
    .mud-button-filled:hover, .visitor-btn-primary:hover, .visitor-btn-danger:hover,
    .visitor-tile-link:hover { transform: none !important; }
}
