/* HIRMS component styles — every value comes from docs/design-tokens.css.
   Design-system rules baked in here rather than per screen:
   · DataGrid collapses to cards ≤ 820px
   · SidePanel becomes a bottom sheet on mobile
   · touch targets ≥ 44px on kiosk/portal density
   · accent green is reserved for financial confirmations */

@import url('tokens.css');
@import url('hsl-brand.css');   /* HSL corporate identity overlay */

*, *::before, *::after { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    font-family: var(--f-ui);
    font-size: var(--t-md);
    line-height: var(--lh-base);
    color: var(--ink);
    /* The design's 24px grid wash on the page canvas (dc.html:94). --grid-line
       was declared and consumed nowhere; this is its one consumer. */
    background: var(--paper);
    background-image: linear-gradient(var(--grid-line) 1px, transparent 1px),
                      linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
    background-size: 24px 24px;
    -webkit-font-smoothing: antialiased;
}

/* dc.html consistently sets every heading to --w-bold (700), not --w-semi
   (600) — confirmed at dc.html:150,159,203,227,283,315,330,369,398,446,482,
   503,528,543,576,685,767 etc. UI-DESIGN-SYSTEM-AUDIT §4.9. */
h1, h2, h3, h4 { font-family: var(--f-disp); font-weight: var(--w-bold); line-height: var(--lh-tight); margin: 0; }
h1 { font-size: var(--t-2xl); }
h2 { font-size: var(--t-xl); }
h3 { font-size: var(--t-lg); }
a { color: var(--brand); text-decoration: none; }
/* The offset keeps the rule clear of descenders, so an underlined link stays
   legible rather than having its g's and y's struck through (dc.html:84). */
a:hover { text-decoration: underline; text-underline-offset: 3px; }
code, .mono { font-family: var(--f-mono); font-size: var(--t-2xs); letter-spacing: .02em; white-space: nowrap; }

/* ── Ellipsis idiom (V1-V6 fix, docs/WORKBENCH-AUDIT-2026-08-06.md) ─────────
   The design system's own repeated pattern (dc.html:462-464 et al.) — put on
   the INNERMOST text-bearing element, never a table cell wholesale, since
   many cells intentionally stack two lines (a name + a mono id, for
   instance). Pair with a `title="@fullValue"` attribute at the call site so
   the full value stays reachable, not just less-clipped. Requires a bounded
   width from somewhere up the tree (an explicit column Width, a fixed
   sidebar track, a flex child with min-width:0) — this rule alone does
   nothing without one. */
.hs-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* ── Money atom (§3.41) ────────────────────────────────────────────────────
   Tabular numerals are guaranteed only here — the design-system rule for
   stopping KPI figures jittering as they update. Tone maps to the --pos/--neg
   tokens, defined and previously unused. */
.hs-money { font-variant-numeric: tabular-nums; }
.hs-money.pos { color: var(--pos); }
.hs-money.neg { color: var(--neg); }

/* ── The accessibility floor ─────────────────────────────────────────────────
   Four base rules the design system states as non-negotiable (dc.html:1081-84),
   kept together because they are the ones that apply to everything and so
   belong to no component in particular. */

/* Visible focus, everywhere, once (dc.html:81). Before this, the whole codebase
   granted focus in two places — the form fields below and .hs-landing-card — so
   every nav link, button, chip, tab, sortable header and pager control gave a
   keyboard user no sign of where they were. In a workbench an assessor drives
   from the keyboard through a queue of hundreds, that is not a rough edge; it
   is the difference between usable and not. The universal selector is the whole
   point, and the design says so in as many words: focus belongs "on every
   focusable element, not 8 isolated rules" (dc.html:1081). A per-component
   focus rule is a per-component omission waiting to happen.

   Note the border-radius: it applies to the focused element, not just the
   outline, so a pill-shaped .hs-chip squares off to 3px while focused. That is
   the design's own rule copied faithfully; if the corner-popping reads badly on
   the round controls, drop the border-radius line rather than the selector. */
*:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: var(--r-xs);
}

/* Interactive controls that remove their own border/background need an explicit
   focus ring too — a chip, tab, sort header or nav link that turns invisible on
   focus is a keyboard user's dead end (DESIGN-HANDOFF §P2.9). The global rule
   above already catches most; these restate it for elements that paint their own
   focus target (pill corners pop to 3px, matching the design file's rule). */
.hs-chip:focus-visible,
.hs-tab:focus-visible,
.hs-th-sort:focus-visible,
.hs-nav a:focus-visible,
.hs-appbar a:focus-visible,
.hs-bell-btn:focus-visible,
.hs-pager button:focus-visible,
.hs-btn:focus-visible,
.hs-landing-card:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: var(--r-xs);
}

/* Placeholder text. The browser default is the input's colour at reduced
   opacity, which lands somewhere unpredictable and usually too faint; naming
   the colour and forcing opacity to 1 makes it a deliberate value we can
   contrast-check (dc.html:82). */
::placeholder { color: var(--ink-3); opacity: 1; }

/* First tab stop on every shell: a way past the navigation. Without it a
   keyboard or screen-reader user tabs through the whole sidebar on every page
   before reaching the thing they came for. Parked off-screen with a negative
   offset rather than display:none, because a hidden element cannot be focused
   and this one has to be (dc.html:102). */
.hs-skip {
    position: absolute; left: -9999px; top: 8px; z-index: 99;
    padding: 10px 16px;
    background: var(--ink); color: var(--paper);
    border-radius: var(--r-sm); font-weight: var(--w-semi);
}
.hs-skip:focus { left: 12px; }

/* Available to assistive technology, absent from the page. For the heading a
   route needs but the design does not draw — the kiosk's h1, for instance,
   where the visible heading is the step title but a screen-reader user still
   has to be told what they have landed on. Not display:none and not
   visibility:hidden: both remove the element from the accessibility tree, which
   is the one thing this must not do. */
.hs-visually-hidden {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

/* Some people are made ill by movement — vestibular disorders, migraine — and
   say so at the operating system. Honour it: near-zero rather than zero, so
   transitionend and animationend still fire and nothing that waits on them
   hangs (dc.html:91). */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        transition-duration: .01ms !important;
    }
}

/* ── App shell ─────────────────────────────────────────────────────────────
   The design file's workbench frame (dc.html:549-624): a masthead across the
   top, a sidebar nav on the left, content to the right. Three grid rows — the
   top bar is sticky, the nav and main share the remaining row.

   height (not min-height) + overflow:hidden pins the shell to exactly one
   viewport: the mast's row is auto-sized off the top, and the nav/main row
   gets exactly what's left. Without a definite height there neither the mast
   nor the nav can stay put — the grid just keeps growing and the whole page
   scrolls under a "sticky" mast that has no further ancestor to stick against.
   Restored to the old page-scrolling behaviour below 820px, where the nav
   becomes a horizontal strip and a fixed-height shell would clip it. */
.hs-shell { display: grid; grid-template-rows: auto 1fr; grid-template-columns: 194px 1fr; height: 100vh; overflow: hidden; }
.hs-mast { grid-column: 1 / -1; }

/* ── Masthead ──────────────────────────────────────────────────────────────
   The design file's sticky masthead (dc.html:105): glass, with the hamburger,
   the H+ mark + HSLOnline, a search box, then the system controls. The
   notification bell and theme toggle live here, at the top.

   Classes match AppMasthead.razor's actual markup (hs-mast-*). This block
   used to style hs-topbar-* — a class family nothing in the codebase renders
   (grep over every .razor file under src turns up zero hits) — so the masthead rendered
   fully unstyled, and without grid-column:1/-1 the shell's grid auto-placed
   the nav beside it in row 1 and shoved main content into the 248px column. */
.hs-mast {
    position: sticky; top: 0; z-index: 40;
    display: flex; align-items: center; gap: var(--s4);
    /* Three independent right-pushes, so no single failure can bunch the
       cluster against the brand: the spacer grows (flex:1 below), the bell
       carries margin-left:auto, and space-between spreads first/last as a
       final fallback. The live-VM "header bunched left" reports all traced to
       a browser holding an older stylesheet; these make the layout correct
       even against one. */
    justify-content: space-between;
    padding: var(--s3) var(--s5);
    /* Two product-owner asks, 2026-08-09, both landing on this one property.
       "Make background white": the old value was the glass treatment —
       `color-mix(in srgb, var(--paper) 55%, transparent)` over whatever sat
       behind it (the aurora, the page) — which read as a dull grey scrim,
       not white, on the screenshot that prompted the ask. --paper at full
       strength IS this design's white (`--i-25` #FBFBF9 in light mode, the
       same token every opaque card/panel calls "paper"), so this stays a
       token reference rather than a hand-picked #fff, and it is opaque in
       BOTH themes — dark mode's --paper is a near-black opaque, not a
       translucent one either, and the dark-mode logo backing-chip rule two
       blocks down already assumed a non-white ground, so it is unaffected.
       backdrop-filter is dropped with it: nothing shows through an opaque
       bar for it to blur any more.
       "Add a splash of teal", pointing at the top-right corner near the
       language/theme/bell/account cluster: a second background-image layer
       UNDER the opaque colour, using the multi-layer form of the `background`
       shorthand (comma-separated layers paint image-over-color, and only the
       LAST, colour-only layer may set background-color) — so --paper stays
       the trailing solid layer and the bar stays opaque, while the gradient
       shows through only where its own stops are not fully transparent.
       Uses --brand — the app's one teal token (tokens.css: "le teal 600
       porte les actions") — at low alpha via color-mix, so it re-derives
       correctly in dark mode from this one declaration instead of needing a
       second, hand-tuned dark value.
       Widened 2026-08-10: a plain `circle` sized to the corner's own
       farthest-corner radius faded out well before reaching the bar's own
       middle (the empty flex-spacer between the wordmark and the settings
       cluster), so that whole stretch read as plain white against a splash
       that only ever touched the far-right icons. A wide, short `ellipse`
       hugging the top edge reaches across the middle without growing tall
       enough to compete with the logo/wordmark on the left; alpha nudged
       down slightly (16%→13%) since it now covers more of the bar. */
    background: radial-gradient(ellipse 820px 150px at 100% 0%,
        color-mix(in srgb, var(--brand) 13%, transparent), transparent 100%),
        var(--paper);
    border-bottom: 1px solid var(--rule);
}
.hs-mast-icon {
    display: grid; place-items: center;
    width: var(--ctl); height: var(--ctl); flex: none;
    border: none; background: transparent; color: var(--ink-2);
    border-radius: var(--r-sm); cursor: pointer;
}
.hs-mast-icon:hover { background: var(--surface-2); }
/* dc.html draws the hamburger with no onClick and no collapsed/rail sidebar
   in any frame — it only has real work below 820px, where the sidebar
   becomes a drawer. Shown again in the 820px media query below. */
.hs-mast-toggle { display: none; }
.hs-mast-brand { display: flex; align-items: center; gap: var(--s3); flex: none; }
.hs-mast-logo-wrap { display: flex; align-items: center; flex: none; }
/* 28px → 35px (product-owner ask, 2026-08-09: "increase logo size by 25%" —
   28 * 1.25 = 35 exactly). This is a few px taller than every other control
   in the bar (.hs-mast-icon/.hs-mast-avatar are --ctl 32px / 28px), so it is
   now what sets the bar's own height — the bar has no explicit height of its
   own, only `align-items:center` + padding around its tallest child, and
   that child changed. It grows the measured ~54-57px bar dc.html:553-564
   specs by roughly the same few px, not shrinks it — the alternative was
   capping the increase at 32px (level with the icon buttons) to hold the bar
   height exactly, but that stops short of the literal "25%" ask for a
   difference too small to read as a broken spec. `.hs-nav`'s sticky
   `top`/`max-height` below are measured against the bar's real height, not
   this literal number, and were re-measured and updated alongside this. */
.hs-mast-logo { display: block; height: 35px; width: auto; }
/* CI manual do-nots rule out recolouring the mark; the manual's own fix for a
   colour logo on a non-white ground is a white backing chip
   (hsl-brand.css .hsl-logo-on-brand) — applied here only in dark mode, where
   the masthead's glass background is no longer light enough for contrast. */
:root[data-theme="dark"] .hs-mast-logo-wrap { background: #fff; border-radius: var(--r-sm); padding: 3px 6px; }
.hs-mast-word { font-family: var(--f-disp); font-weight: var(--w-bold); font-size: var(--t-sm); letter-spacing: -.3px; }
.hs-mast-spacer { flex: 1; }
/* Language + Theme: an inline toolbar row down to 481px, a gear-icon dropdown
   below that. The gear stays hidden until the row itself would be cramped —
   see the ≤480px override at the bottom of this file, which is the ONLY
   thing that changes; .hs-mast-inline-settings holds the exact same
   LangToggle/ThemeToggle markup at every width. */
.hs-mast-settings { position: relative; }
.hs-mast-settings-toggle { display: none; }
.hs-mast-inline-settings { display: flex; align-items: center; gap: var(--s3); }
.hs-mast-account { position: relative; }
.hs-mast-avatar {
    width: 28px; height: 28px; border-radius: var(--r-full);
    background: var(--brand-soft); color: var(--brand);
    display: grid; place-items: center; font-weight: var(--w-semi); font-size: 10.5px;
    flex: none; border: 1px solid transparent; cursor: pointer;
}
.hs-mast-avatar:hover, .hs-mast-avatar.active { border-color: var(--brand-line); }
.hs-mast-initials { pointer-events: none; }
/* Same anchored-dropdown shape as .hs-bell-panel — the account menu is the
   masthead's other overlay and should read as the same pattern. */
.hs-mast-panel {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
    width: min(280px, calc(100vw - var(--s6)));
    background: var(--paper); border: 1px solid var(--rule);
    border-radius: var(--r-lg); box-shadow: var(--e-3);
    padding: var(--s4); display: flex; flex-direction: column; gap: var(--s3);
}
.hs-mast-who { font-size: var(--t-sm); font-weight: var(--w-semi); }
.hs-mast-detail { font-size: var(--t-xs); color: var(--ink-2); margin-top: 2px; }
.hs-mast-panel-section { padding-top: var(--s3); border-top: 1px solid var(--rule); display: flex; flex-direction: column; gap: var(--s2); }

/* ── Sidebar nav ───────────────────────────────────────────────────────────
   The design file's nav (dc.html:566-571): a 194px column, items with a 3px
   tick on the left, an active item filled with brand-soft + brand tick, and a
   badge riding the right edge. Glass, like the masthead.

   Two layers, not one, since 2026-08-10. `.hs-nav` is a transparent sticky/
   scroll SHELL only — it owns the positioning math (sticky offset, max-
   height, the top gap) but paints nothing. `.hs-nav-inner` (added in
   MainLayout.razor, wrapping the Capture button + nav rows) is the actual
   visible glass panel — background, border, the row list. Splitting them is
   what makes the top gap read as EMPTY PAGE rather than a blank rectangle of
   the sidebar's own surface colour: before this split, `.hs-nav` painted its
   background across the whole padded box including the gap, so the "move
   the sidebar down" fix from the day before looked like an unstyled box
   floating above the real content instead of the panel hanging mid-page —
   direct product-owner correction on a live screenshot. */
.hs-nav {
    /* Top padding raised from --s3 to --s10 (product-owner annotation,
       2026-08-09: "move the sidebar panel down" — an arrow from Capture a
       visit toward Overview). Unpadded, the nav's top (the Capture button)
       sits level with the masthead, while .hs-main's own --s6 padding plus
       its eyebrow/title stack put the page heading noticeably lower — so the
       two columns start at visibly different rows even though they read as
       one page. --s10 is a token off the same spacing scale .hs-main and
       .hs-page-head already use, chosen as a page-agnostic approximation of
       that heading's start (measured against Overview's eyebrow+h1 stack,
       one of the shorter ones) rather than a hardcoded pixel value tuned to
       any single page's exact title height — some pages add a breadcrumb row
       below the title and sit a little lower still; this stays a close, not
       exact, match everywhere, which the product owner's own sketch (a
       modest nudge, not pixel-perfect ruler alignment) calls for. Reset to
       0 in the ≤820px row below — the nav is a horizontal strip under the
       mast there, not a column beside main, so there is nothing for a top
       gap to align against and it would just be dead space over the row of
       links. */
    padding-top: var(--s10);
    overflow-y: auto;
    /* Pinned under the masthead instead of scrolling away with the page —
       the masthead is sticky at top:0, so the nav sticks just below it, not
       at 0, or the two would overlap. Bounded to the viewport height minus
       that offset so a nav taller than the window still scrolls internally
       rather than spilling under the fold.
       60px, not the 54px dc.html:553-564 specs and AppMasthead's own header
       comment cite: raising .hs-mast-logo 28px→35px (25% bigger, product-
       owner ask 2026-08-09) made the logo the bar's tallest child in place
       of the 32px icon buttons, which grows the bar's own auto height by
       the same few px. Re-measured in headless Chrome after that change
       rather than computed by hand, since the bar's height was never an
       authored constant to begin with — only padding + tallest-child, same
       as before. Keep this in step with .hs-mast-logo's height; a future
       change to either without the other reintroduces the overlap this
       exists to prevent. */
    position: sticky; top: 60px; align-self: start;
    max-height: calc(100vh - 60px);
}
.hs-nav-inner {
    background: var(--surface);
    border-right: 1px solid var(--rule);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    padding: 0 var(--s2) var(--s3);
    display: flex; flex-direction: column; gap: 2px;
}
.hs-brand { font-family: var(--f-disp); font-size: var(--t-lg); font-weight: var(--w-bold); color: var(--brand-lo); margin-bottom: var(--s4); padding: 0 var(--s2); }
.hs-brand small { display: block; font-family: var(--f-ui); font-size: var(--t-2xs); font-weight: var(--w-reg); color: var(--ink-3); letter-spacing: .08em; text-transform: uppercase; }
.hs-nav-group { font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: .1em; color: var(--ink-3); margin: var(--s4) var(--s2) var(--s1); }
.hs-nav a {
    display: flex; align-items: center; gap: var(--s3);
    min-height: 36px; padding: 0 var(--s3);
    border-radius: var(--r-sm);
    color: var(--ink-2); font-size: var(--t-sm); font-weight: var(--w-med);
    text-decoration: none;
}
.hs-nav a:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.hs-nav a.active { background: var(--brand-soft); color: var(--brand); font-weight: var(--w-semi); }
/* MainLayout.razor documents this rule as already existing for "Capturer une
   visite" — it wasn't; the class+element+class selector is needed (0,2,1) to
   outrank .hs-nav a's (0,1,1), a bare .hs-nav-action rule would lose. Without
   it the button rendered with --ink-2 body text on the brand background
   instead of --on-brand, reading as washed-out on every masthead. */
.hs-nav a.hs-nav-action { color: var(--on-brand); font-weight: var(--w-semi); }
.hs-nav a.hs-nav-action:hover { color: var(--on-brand); }
/* The 3px tick — the design file's status edge on the active nav item.
   Idle rows carry a dim brand tint rather than flat --rule-2 grey: the tick
   is the row's identity mark even at rest, and a flat grey column of eight
   reads as decoration nobody finished, not eight distinct destinations. */
.hs-nav-tick {
    width: 3px; height: 14px; border-radius: var(--r-full);
    background: var(--brand-soft); flex: none;
}
.hs-nav a:hover .hs-nav-tick { background: var(--brand-line); }
.hs-nav a.active .hs-nav-tick { background: var(--brand); }

/* The label and the count badge — dc.html:1209-12 builds the row as
   [3px tick] label [badge] where the badge is mono, 9px, pill-shaped and
   pushed to the row's end with margin-left:auto. The design's active-row badge
   fills brand/on-brand; the idle badge sits on --surface-3 in --ink-2. The
   component renders the badge span only when a count is positive, so no
   display:none workaround is needed for empty badges. */
/* min-width:0 lets the label actually shrink inside the flex row instead of
   pushing the badge (flex:none) past the sidebar's edge — the default
   flex-shrink basis is the text's own intrinsic width, which is wider than
   the sidebar on items like "Console anti-fraude". */
.hs-nav-label { flex: 1 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.hs-nav-badge {
    margin-left: auto;
    font-family: var(--f-mono); font-size: 9px; font-weight: var(--w-semi);
    padding: 2px 6px; border-radius: var(--r-full);
    background: var(--surface-3); color: var(--ink-2);
    flex: none;
}
.hs-nav a.active .hs-nav-badge { background: var(--brand); color: var(--on-brand); }

/* ── Aurora backdrop ────────────────────────────────────────────────────────
   The design file's signature wash (dc.html:96-100): three fixed radial blobs,
   blurred hard, drifting on 28–40s loops behind every surface. Deliberately
   almost invisible — it gives the glass something to refract. `--grid-line`
   (tokens.css:55) is the graph-paper wash that sits on the same layer; the
   aurora adds the colour. prefers-reduced-motion already zeroes the drift via
   the global rule at the top of this file. */
.hs-aurora {
    position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
}
.hs-aurora .blob {
    position: absolute; border-radius: var(--r-full); filter: blur(70px);
}
.hs-aurora .blob-1 {
    top: -240px; left: -180px; width: 840px; height: 840px;
    background: radial-gradient(circle at 35% 35%, rgba(0,137,182,.32), transparent 62%);
    animation: hxAur1 28s var(--ez-io) infinite;
}
.hs-aurora .blob-2 {
    top: 8%; right: -260px; width: 780px; height: 780px;
    background: radial-gradient(circle at 60% 40%, rgba(140,198,63,.24), transparent 60%);
    animation: hxAur2 34s var(--ez-io) infinite;
}
.hs-aurora .blob-3 {
    bottom: -280px; left: 28%; width: 920px; height: 720px;
    background: radial-gradient(ellipse at 50% 50%, rgba(92,185,217,.22), transparent 62%);
    animation: hxAur1 40s var(--ez-io) infinite reverse;
}
@keyframes hxAur1 { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(70px, -50px) scale(1.14); } }
@keyframes hxAur2 { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(-80px, 60px) scale(1.08); } }

/* ── Glassmorphism ──────────────────────────────────────────────────────────
   `--glass-blur` was defined in tokens and never consumed (DESIGN-HANDOFF
   §P3.13). The shells and overlays that float over content — the sticky
   appbar, the workbench nav, the side panel, the bell, the picker — get the
   backdrop blur + the inset specular highlight, per dc.html:105. The surface
   tokens are already translucent; the blur is what makes them read as glass
   rather than washed-out. */
.hs-appbar, .hs-nav-inner, .hs-panel, .hs-bell-panel, .hs-picker-list, .hs-confirm, .hs-modal {
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
}

/* ── Notched cards ──────────────────────────────────────────────────────────
   The design file's signature clipped corner on KPI tiles and the member card
   (dc.html:403, 848). `--notch` is the kill switch: 0px flattens every notch
   at once, which is what a printer or a tenant wanting a softer corner sets.
   The notch clips the hover shadow too, so the shadow sits on an unclipped
   wrapper instead — .hs-kpi-link already provides that wrapper. */
.hs-kpi, .hs-membercard {
    clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
}

/* ── Status edge ticks ──────────────────────────────────────────────────────
   The 3px coloured edge that marks a row's status before the pill is read
   (dc.html:459). Applied to DataGrid rows via RowStatus (hs-row-*), nav items
   and claim cards. A tick is the pill's colour standing at the row's edge. */
.hs-tick {
    align-self: stretch; width: 3px; border-radius: var(--r-full); flex: none;
    background: var(--rule-2); opacity: .55;
}
.hs-tick.is-pend, .hs-tick.is-held { background: var(--st-pend); }
.hs-tick.is-eval { background: var(--st-eval); }
.hs-tick.is-redy { background: var(--st-redy); }
.hs-tick.is-rejt, .hs-tick.is-flagged { background: var(--st-rejt); }
.hs-tick.is-paid { background: var(--st-paid); }
.hs-tick.is-brand { background: var(--brand); opacity: 1; }
/* A statused row's rail is the design's first track (dc.html:459,611) — full
   colour, not the faded neutral a no-status row shows. held/flagged are the
   semantic aliases several pages' RowStatus already passes (Finance,
   FraudConsole, Invoices, OcrJobs, PreAuthQueue, TobBrowser) — without them
   here this rule only ever matched pend/eval/redy/rejt/paid, so every one of
   those pages' tick was rendering as the faded default above, not the
   coloured rail its row-level inset shadow already showed. */
.hs-tick.is-pend, .hs-tick.is-held, .hs-tick.is-eval, .hs-tick.is-redy,
.hs-tick.is-rejt, .hs-tick.is-flagged, .hs-tick.is-paid { opacity: 1; }
/* The one scrolling region in the shell: .hs-mast's row is auto-sized and
   .hs-nav scrolls its own overflow (hirms.css .hs-nav), so main is what's left
   to carry page-length content within the shell's fixed viewport height. */
.hs-main {
    padding: var(--s6) var(--s8); max-width: 1600px; overflow-y: auto;
    /* "Add a splash of teal around this region of space for all background"
       (product-owner ask, 2026-08-09), a second, larger annotation on the
       same screenshot covering the KPI-tile row and reaching down over the
       content beneath it.
       MainLayout.razor already renders <Aurora /> here — three fixed, blurred
       teal/lime blobs behind the whole shell (hirms.css .hs-aurora) — and
       .hs-main itself was, and stays, transparent so that wash can show
       through its gutters. It is not what this ask is pointing at, though:
       measured in headless Chrome, .hs-aurora's blobs sit correctly and
       .hs-main really is transparent over them, but between .hs-aurora's own
       low alpha (.22–.32) and its 70px blur further softening it, plus the
       KPI tiles' and panels' own opaque/near-opaque card surfaces sitting on
       top, nothing of it reads as a "splash" over precisely the region
       circled — it is closer to imperceptible there than subtle. Rather than
       retune .hs-aurora's shared blob geometry — Kiosk and Member portal
       both key their own washes off the same three blobs, and a change big
       enough to read as a splash on THIS surface would very likely be too
       strong on theirs — this adds a second, purpose-built wash scoped to
       .hs-main alone.
       Two soft pools, not one centred blob: a single wash reading as a
       circle behind a rectangular KPI row looks like a stray spotlight,
       where two loosely bracketing the row's left and right ends reads as
       "the top of this page has some colour" without a shape anyone would
       describe. Positioned in px, not %, and background-attachment:local —
       CSS resolves a percentage `at` position against the FULL scrollable
       content box, not the viewport, and .hs-main routinely holds page-
       length content, so a percentage position would drift toward the
       bottom of a long page; `local` (rather than the default `scroll`,
       which is fixed to the viewport of the element and would instead
       follow the user down the page like a stuck highlight) scrolls the
       wash away with the KPI row once that row scrolls past — it belongs to
       the top of the page, not to whatever happens to be on screen.
       color-mix over --brand, same token the masthead's own corner wash
       uses (hirms.css .hs-mast), at low single-digit alpha — a back-office
       queue screen, not a marketing hero; low enough that it never competes
       with the KPI values or the status colours sitting on top of it, and it
       re-derives correctly in dark mode from this one declaration. */
    background:
        radial-gradient(620px 420px at 60px 0px, color-mix(in srgb, var(--brand) 9%, transparent), transparent 70%),
        radial-gradient(560px 380px at calc(100% - 60px) 40px, color-mix(in srgb, var(--brand) 6%, transparent), transparent 70%),
        transparent;
    background-repeat: no-repeat;
    background-attachment: local;
}
/* Teal, not near-black — the page-head rule reads as an accent line under the
   title now, matching the brand colour every primary action already uses,
   not a plain structural divider. Product-owner ask, 2026-08-06. */
.hs-page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s4); margin-bottom: var(--s5); flex-wrap: wrap; border-bottom: 2px solid var(--brand); padding-bottom: var(--s4); }
.hs-page-head p { margin: var(--s1) 0 0; color: var(--ink-2); font-size: var(--t-sm); max-width: 68ch; }
.hs-page-head-norule { border-bottom: none; padding-bottom: 0; }
.hs-page-head-actions { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.hs-page-head-crumbs { margin-top: var(--s3); }
/* The V1 eyebrow — mono, tracked, uppercase, brand. */
.hs-eyebrow {
    font-family: var(--f-mono); font-size: var(--t-2xs); letter-spacing: 2px;
    text-transform: uppercase; color: var(--brand);
    margin-bottom: var(--s1);
}
.hs-page-head h1 { max-width: 22ch; }

/* ── Wave 8 · Domain components ──────────────────────────────────────────── */

/* Timeline — date+status rail on the left, headline+detail on the right, a
   hairline between rows. */
.hs-spine-row {
    display: flex; gap: var(--s4); padding: var(--s3) 0; border-bottom: 1px solid var(--rule);
}
.hs-spine-row:last-child { border-bottom: none; }
.hs-spine-index { min-width: 132px; display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }
.hs-spine-stage { font-size: var(--t-xs); color: var(--ink-2); }
.hs-spine-body { flex: 1; font-size: var(--t-sm); }
.hs-spine-detail { white-space: pre-wrap; margin: 4px 0 0; font-size: var(--t-2xs); }
.hs-spine.dense .hs-spine-row { padding: var(--s2) 0; }

/* HsKv — semantic dl/dt/dd pairs. */
.hs-kv { margin: 0; }
.hs-kv-row { display: flex; justify-content: space-between; gap: var(--s4); padding: var(--s2) 0; border-bottom: 1px solid var(--rule); }
.hs-kv-row:last-child { border-bottom: none; }
.hs-kv dt { color: var(--ink-2); font-size: var(--t-xs); }
.hs-kv dd { margin: 0; font-size: var(--t-sm); text-align: right; }
.hs-kv.right .hs-kv-row { justify-content: flex-start; }
.hs-kv.right dd { margin-left: auto; }

/* HsDisclosure — summary button with caret, aria-expanded. */
.hs-disclosure-summary {
    display: inline-flex; align-items: center; gap: var(--s2);
    background: none; border: none; padding: 0; cursor: pointer;
    font-family: var(--f-ui); font-size: var(--t-sm); font-weight: var(--w-med); color: var(--ink);
}
.hs-disclosure-caret { transition: transform var(--d-fast) var(--ez-out); color: var(--ink-2); }
.hs-disclosure.open .hs-disclosure-caret { transform: rotate(90deg); }
.hs-disclosure-body { margin-top: var(--s2); }

/* ResultList — a real list surface (not .hs-picker-item, which is a floating
   dropdown style). */
.hs-resultlist-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s1); }
.hs-resultlist-item {
    width: 100%; text-align: left; background: none; border: none; padding: 0; cursor: pointer;
    display: block;
}
.hs-resultlist-item:disabled { cursor: not-allowed; }
.hs-resultlist-item:focus-visible { outline: 2px solid var(--brand-line); border-radius: var(--r-sm); }
.hs-resultlist-item-main { display: flex; flex-direction: column; gap: 2px; padding: var(--s2) var(--s3); border: 1px solid var(--rule); border-radius: var(--r-md); }
.hs-resultlist li.disabled { opacity: .55; }

/* LineRepeater — repeating rows with add/remove. */
.hs-linerepeater-row { display: flex; align-items: flex-end; gap: var(--s2); margin-bottom: var(--s2); }
.hs-linerepeater-row > :first-child { flex: 1; }
.hs-linerepeater-remove { flex: none; }
.hs-linerepeater-add { margin-top: var(--s1); }

/* Invoices.razor review — one card per line instead of a 7-column table
   (2026-08-09): a code search whose dropdown is only as wide as a 220px
   table cell showed 4-5 characters of every match, and Description had to
   fit in whatever width five other columns left it. */
.hs-review-lines { display: flex; flex-direction: column; gap: var(--s3); }
.hs-review-line {
    border: 1px solid var(--rule); border-radius: var(--r-md); padding: var(--s3) var(--s4);
    background: var(--surface-2);
}
.hs-review-line.hs-row-held { box-shadow: inset 3px 0 0 var(--st-pend); }
.hs-review-line-top { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s3); }
.hs-review-line-grid {
    display: grid; align-items: start; gap: var(--s3);
    /* Code and Resolved-to get the most room — a search result and a
       resolved benefit name are both prose, not a number. Qty/Unit/Total
       are fixed enough to not need to flex. */
    grid-template-columns: minmax(200px, 1.4fr) minmax(200px, 1.4fr) 76px 96px 108px;
}
.hs-review-line-code, .hs-review-line-resolved, .hs-review-line-num { display: flex; flex-direction: column; gap: var(--s1); }
.hs-review-line-evidence {
    margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--rule);
}
/* Same breakpoint the panel itself switches to a bottom sheet at (§1281) —
   below it, five columns racing for space is the same crowding this whole
   redesign exists to fix. Two columns: Code+Resolved (the prose fields)
   keep a full row each; Qty/Unit/Total share the second row, which is
   still plenty of room for a 2-3 digit number. */
@media (max-width: 820px) {
    .hs-review-line-grid { grid-template-columns: 1fr 1fr 1fr; }
    .hs-review-line-code, .hs-review-line-resolved { grid-column: 1 / -1; }
}

/* ── Surfaces ────────────────────────────────────────────────────────────── */
/* Cards rest at --e-1 and rise to --e-2 on hover (dc.html:49-50) — the build
   rested them at --e-2, so the hover affordance had nowhere to go. */
.hs-card {
    position: relative;
    /* var(..., fallback), not a --card-bg/--card-blur declared in every theme
       block: the tokens exist only in the light block (tokens.css), so dark
       mode falls straight through to the plain --surface/no-blur it always
       had, guaranteed by the fallback rather than a second override to keep
       in step. */
    background: var(--card-bg, var(--surface)); border: 1px solid var(--rule); border-radius: var(--r-lg);
    box-shadow: var(--e-1); padding: var(--pad-card);
    backdrop-filter: var(--card-blur, none);
    -webkit-backdrop-filter: var(--card-blur, none);
    transition: box-shadow var(--d-fast) var(--ez-out);
}
.hs-card:hover { box-shadow: var(--e-2); }
/* The KPI tile's coloured 3px top-accent border (.hs-kpi-top, TopColor-driven)
   generalized to every card sitewide, not just KpiTile. Plain cards get the
   brand colour; a page can still request a status colour per-instance with
   style="--card-accent:var(--st-rejt)" the same way KpiTile passes TopColor. */
.hs-card::before {
    content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    background: var(--card-accent, var(--brand));
}
.hs-card-flush::before { left: 1px; right: 1px; }
.hs-card + .hs-card { margin-top: var(--s4); }
.hs-card-glass {
    background: color-mix(in srgb, var(--surface) 66%, transparent);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
}
.hs-card-flush { padding: 0; }
.hs-card-aside { display: flex; align-items: center; gap: var(--s2); margin-left: auto; }
.hs-card-foot { padding-top: var(--s3); margin-top: var(--s3); border-top: 1px solid var(--rule); }
/* flex-wrap so a long right-hand status readout (e.g. "N held for missing
   pre-auth") drops to its own line instead of clipping at the card edge —
   it's a number worth reading in full, not an identifier worth truncating. */
.hs-card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s2) var(--s3); margin-bottom: var(--s3); flex-wrap: wrap; }
.hs-card-title { font-family: var(--f-ui); font-weight: var(--w-semi); font-size: var(--t-md); margin: 0; }
.hs-muted { color: var(--ink-2); font-size: var(--t-sm); }
.hs-dim { color: var(--ink-3); font-size: var(--t-xs); }

/* The reconciliation delta on the invoice-group queue — red when non-zero
   (docs/08 §6: "shown red before anything else"). */
.hs-delta-bad { color: var(--neg); font-weight: var(--w-semi); }

/* ── Buttons ─────────────────────────────────────────────────────────────── */
/* Weight --w-semi per dc.html:1281-1296 (the build used --w-med). Height is
   --ctl (32px, the compact control grid dc.html:1038 builds btn() on) — the
   build hardcoded a 38px comfort height instead. UI-DESIGN-SYSTEM-AUDIT §4.4. */
.hs-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
    min-height: var(--ctl); padding: 0 var(--s4);
    border: 1px solid var(--rule-2); border-radius: var(--r-md);
    background: var(--surface); color: var(--ink); font-family: var(--f-ui); font-size: var(--t-sm); font-weight: var(--w-semi);
    cursor: pointer; transition: background var(--d-fast) var(--ez-out), border-color var(--d-fast) var(--ez-out);
}
/* Hover is one step along the surface ladder, not a fixed grey. --i-50 was a
   literal step off the light ink ramp, so it stayed near-white in dark and
   hovering a button lit a hole in the page. --surface-2 moves the right way in
   both themes: darker than --surface on a light ground, lighter on a dark one. */
.hs-btn:hover:not(:disabled) { background: var(--surface-2); }
.hs-btn:disabled { opacity: .5; cursor: not-allowed; }
.hs-btn-primary { background: var(--brand); border-color: var(--brand); color: var(--on-brand); box-shadow: var(--e-1); }
.hs-btn-primary:hover:not(:disabled) { background: var(--brand-hi); }
/* accent green = financial confirmations only */
.hs-btn-accent { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
/* Danger is FILLED per dc.html:1285 (btnDanger = --st-rejt bg, #fff text) —
   the build had it as an outline, which read as a secondary action for the
   one button that destroys state. */
.hs-btn-danger { background: var(--st-rejt); border-color: var(--st-rejt); color: #fff; }
.hs-btn-danger:hover:not(:disabled) { background: var(--st-rejt); filter: brightness(.92); }
/* Soft — the design's row action ("Évaluer"/Assess), dc.html:1283. */
.hs-btn-soft { background: var(--brand-soft); border-color: var(--brand-line); color: var(--brand); }
.hs-btn-soft:hover:not(:disabled) { background: var(--brand-soft); filter: brightness(.97); }
.hs-btn-ghost { background: transparent; border-color: transparent; color: var(--brand); }
.hs-btn-sm { min-height: 30px; padding: 0 var(--s3); font-size: var(--t-xs); }
/* Icon-only control (refresh, theme toggle) — square, not the text button's
   horizontal padding. 2026-08-06: "Refresh" text buttons across the workbench
   became icon+tooltip on product-owner ask; this is the one rule both share. */
/* min-height alone isn't enough: a text .hs-btn's line-height content pushes
   it a few px past --ctl's floor (measured 36px, not 32, in the same button
   row) — an icon-only button has no text content to do that, so it settled
   at exactly the --ctl floor and sat visibly shorter than its siblings
   (product-owner screenshot, 2026-08-06: the refresh icon on /invoices'
   action row). Explicit height matches what its siblings actually render at. */
.hs-btn-icon-only { min-width: var(--ctl); height: 36px; padding: 0; gap: 0; }
.hs-btn-icon-only.hs-btn-sm { min-width: 30px; height: 30px; }
/* Raised, not flat-ghost: a card resting at --e-1, lifting to --e-2 on
   hover — the same elevation language cards already use, so the refresh
   control reads as a distinct pressable surface rather than blending into
   the header text beside it. 2026-08-06, product-owner ask. */
.hs-btn-raised { box-shadow: var(--e-1); }
.hs-btn-raised:hover:not(:disabled) { box-shadow: var(--e-2); }
@keyframes hxSpin { to { transform: rotate(360deg); } }
.hs-btn-icon-spin svg { animation: hxSpin 0.8s linear infinite; }
@media (prefers-reduced-motion: reduce) { .hs-btn-icon-spin svg { animation: none; } }
.hs-btn-row { display: flex; gap: var(--s2); flex-wrap: wrap; }

/* ── Accent colour: teal checkboxes/radios per dc.html:358 (the design's
   non-negotiable). Without this every control shipped browser-default blue. */
.hs-app input[type="checkbox"], .hs-app input[type="radio"],
input[type="checkbox"], input[type="radio"] { accent-color: var(--brand); width: 16px; height: 16px; cursor: pointer; }

/* ── Forms ───────────────────────────────────────────────────────────────── */
/* Field label signature per the design system §4.2: mono + uppercase + tracked,
   not the semibold sans it used to be. gap s2 (6px) per the design's field
   rhythm, not s1. */
.hs-field { display: flex; flex-direction: column; gap: var(--s2); margin-bottom: var(--s3); }
.hs-label {
    font-family: var(--f-mono); font-size: var(--t-2xs); letter-spacing: .8px;
    text-transform: uppercase; color: var(--ink-2);
}
.hs-label-wrap { display: flex; flex-direction: column; gap: var(--s1); }
.hs-input, .hs-select, .hs-textarea {
    /* --ctl (32px), matching dc.html's selectStyle/inputMono/inputError
       (dc.html:1297-1299) — the build hardcoded 38px. */
    min-height: var(--ctl); padding: var(--s2) var(--s3);
    border: 1px solid var(--rule-2); border-radius: var(--r-md);
    background: var(--surface); font-family: var(--f-ui); font-size: var(--t-sm); color: var(--ink); width: 100%;
}
/* A field gets more than the global focus ring, and gets it on :focus rather
   than :focus-visible — a text box you have clicked into should look active to
   a mouse user too, because "where am I typing" is a question everyone asks. */
.hs-input:focus, .hs-select:focus, .hs-textarea:focus { outline: 2px solid var(--brand-line); border-color: var(--brand); }
.hs-textarea { min-height: 84px; resize: vertical; }
/* Select wrapper — the design file draws the chevron as an overlay (dc.html:347),
   so the native arrow is suppressed and replaced with a themed one. The native
   control stays, keeping the listbox keyboard behaviour. */
.hs-select-wrap { position: relative; display: inline-flex; align-items: center; }
.hs-select-wrap .hs-select { appearance: none; -webkit-appearance: none; padding-right: var(--s8); }
.hs-select-block { display: flex; }
.hs-select-block .hs-select { width: 100%; }
.hs-select-chevron { position: absolute; right: 12px; color: var(--ink-2); pointer-events: none; }
.hs-select-wrap:has(.hs-select:disabled) .hs-select-chevron { opacity: .5; }

/* ── Wave 4 · Combobox (Select) ─────────────────────────────────────────── */
.hs-combo { position: relative; }
.hs-combo .hs-select-wrap { width: 100%; }
.hs-combo .hs-input { width: 100%; padding-right: var(--s8); }
.hs-combo-list {
    position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 50;
    margin: 0; padding: var(--s1); list-style: none;
    background: var(--paper); border: 1px solid var(--rule);
    border-radius: var(--r-md); box-shadow: var(--e-3);
    max-height: 320px; overflow-y: auto;
}
.hs-combo-item {
    padding: var(--s2) var(--s3); border-radius: var(--r-sm);
    cursor: pointer; display: flex; flex-direction: column; gap: 2px;
    font-size: var(--t-sm); color: var(--ink);
}
.hs-combo-item.active { background: var(--brand-soft); }
.hs-combo-item.selected { font-weight: var(--w-semi); }
.hs-combo-state { padding: var(--s3); font-size: var(--t-sm); color: var(--ink-2); }
.hs-combo-error { color: var(--neg); display: flex; flex-direction: column; gap: var(--s2); align-items: flex-start; }
.hs-picker-item-main { display: flex; flex-direction: column; gap: 2px; }
.hs-picker-why { font-size: var(--t-2xs); color: var(--ink-2); }

/* ── Wave 4 · Checkbox / Radio / Switch ─────────────────────────────────── */
.hs-check { display: inline-flex; align-items: center; gap: var(--s2); cursor: pointer; }
.hs-check-input { width: 17px; height: 17px; accent-color: var(--brand); cursor: pointer; }
.hs-check-label { font-size: var(--t-sm); color: var(--ink); }
.hs-switch { display: inline-flex; align-items: center; gap: var(--s2); cursor: pointer; }
.hs-switch-input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.hs-switch-track {
    position: relative; width: 36px; height: 20px; border-radius: var(--r-full);
    background: var(--rule-2); transition: background var(--d-fast) var(--ez-out); flex: none;
}
.hs-switch-track::after {
    content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px;
    border-radius: var(--r-full); background: var(--paper);
    box-shadow: var(--e-1); transition: transform var(--d-fast) var(--ez-out);
}
.hs-switch-input:checked + .hs-switch-track { background: var(--brand); }
.hs-switch-input:checked + .hs-switch-track::after { transform: translateX(16px); }
.hs-switch-input:focus-visible + .hs-switch-track { outline: 2px solid var(--brand-line); }
.hs-switch-input:disabled + .hs-switch-track { opacity: .5; }
.hs-switch-label { font-size: var(--t-sm); color: var(--ink); }

/* ── Wave 4 · OTP (single input over segmented cells) ───────────────────── */
.hs-otp-field { display: flex; flex-direction: column; gap: var(--s2); }
.hs-otp { position: relative; }
.hs-otp-input {
    position: relative; z-index: 1;
    width: 100%; min-height: 56px;
    background: transparent; border: 1px solid var(--rule-2); border-radius: var(--r-md);
    font-family: var(--f-mono); font-size: 16px; letter-spacing: .6em; text-align: center;
    /* The typed digits render in the backdrop's cells, one per box, not here —
       centring a whole growing string over fixed-width cells would drift out
       of alignment with every keystroke. This input stays invisible on
       purpose; only its caret (the real typing position) shows. */
    color: transparent; caret-color: var(--brand);
}
.hs-otp-input:focus { outline: 2px solid var(--brand-line); border-color: var(--brand); }
.hs-otp-cells {
    position: absolute; inset: 0; z-index: 0;
    display: grid; gap: 2px;
    pointer-events: none;
}
.hs-otp-cell {
    margin: 2px 0;
    border: 1.5px solid var(--rule-2); border-radius: var(--r-sm);
    background: var(--paper);
    display: grid; place-items: center;
    font-family: var(--f-mono); font-weight: var(--w-semi); font-size: var(--t-xl); color: var(--ink);
}
.hs-otp-cell.filled { border-color: var(--brand-line); background: var(--brand-soft); }
.hs-otp-cell.current {
    border-color: var(--brand); background: var(--paper);
    box-shadow: var(--ring);
}
.hs-otp-timer { font-size: var(--t-xs); color: var(--ink-2); }
.hs-otp-expired { color: var(--st-pend); font-weight: var(--w-semi); }
.hs-otp-attempts { font-size: var(--t-xs); color: var(--neg); }
.hs-otp-locked { display: flex; flex-direction: column; gap: var(--s2); font-size: var(--t-sm); color: var(--neg); }
.hs-otp-field.expired .hs-otp-cell { border-color: var(--st-pend-line); background: var(--st-pend-bg); }
@keyframes hs-shake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-6px); }
    75% { transform: translateX(6px); }
}
.hs-otp-field.shake { animation: hs-shake 200ms var(--ez-out); }
.hs-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s3); }
.hs-grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s3); }
.hs-grid-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s4); }

/* ── StatusPill ──────────────────────────────────────────────────────────── */
.hs-pill {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 3px 10px; border-radius: var(--r-full);
    font-size: var(--t-xs); font-weight: var(--w-semi); letter-spacing: .02em; white-space: nowrap;
    border: 1px solid transparent;
}
/* "Ready to pay (partial)" cannot wrap under the global nowrap in a narrow
   card cell — this modifier opts a pill out of nowrap for card mode. */
.hs-pill-wrap { white-space: normal; }
/* The design's StatusPill has no leading dot (dc.html: statuses) — the 6px
   ::before the build added widened every pill ~12px in the status column.
   Removed per UI-DESIGN-SYSTEM-AUDIT §4.5. */
.hs-pill-pend { color: var(--st-pend); background: var(--st-pend-bg); border-color: var(--st-pend-line); }
.hs-pill-eval { color: var(--st-eval); background: var(--st-eval-bg); border-color: var(--st-eval-line); }
.hs-pill-redy { color: var(--st-redy); background: var(--st-redy-bg); border-color: var(--st-redy-line); }
.hs-pill-rejt { color: var(--st-rejt); background: var(--st-rejt-bg); border-color: var(--st-rejt-line); }
.hs-pill-paid { color: var(--st-paid); background: var(--st-paid-bg); border-color: var(--st-paid-line); }
/* --surface-3, not the --i-100 literal that was here. Two reasons, and the
   second is the surprise: the ramp step never moved in dark, and --ink-2 on
   --i-100 measured 4.44:1 — under AA in the *light* theme it was written for.
   --surface-3 carries the same weight by eye and lands at 4.54:1 light,
   6.39:1 dark. Same fix, same pair, as .hs-split-member below. */
.hs-pill-neutral { color: var(--ink-2); background: var(--surface-3); border-color: var(--rule); }

/* ── DataGrid ────────────────────────────────────────────────────────────── */
.hs-grid-wrap { border: 1px solid var(--rule); border-radius: var(--r-lg); background: var(--surface); overflow: hidden; }
/* Fixed layout is what makes DataGridColumn's Width authoritative instead of
   a hint content can still widen past — without it a nowrap identifier
   column just grows the table, and the crowded neighbour wraps messily
   instead of the design's single-line cells (WORKBENCH-AUDIT-2026-08-06
   V1-V4). Columns with no Width share the remainder per the browser's own
   fixed-layout default, which is fine for the common case (one flexible
   description column beside a couple of narrow ones). */
.hs-table { width: 100%; border-collapse: collapse; font-size: var(--t-sm); table-layout: fixed; }
/* Opt-in per column (DataGridColumn.Truncate) — never the table default: a
   narrative column (the audit trail's "What happened") is meant to read as
   a sentence, and blanket ellipsis would hide the point of it. */
.hs-table .hs-truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hs-table thead th {
    text-align: left; font-size: 9.5px; text-transform: uppercase; letter-spacing: 1px;
    color: var(--ink-2); font-weight: var(--w-semi); font-family: var(--f-mono);
    padding: var(--s3) var(--s4); background: var(--surface-2); border-bottom: 1px solid var(--rule);
    white-space: nowrap;
}
/* table-layout:fixed sizes columns from the header, not the content — a
   long unbroken token (an invoice number, a mono code) in a cell that never
   opted into .hs-truncate then overflowed the column instead of wrapping.
   overflow-wrap:anywhere breaks it at any point once it has nowhere left to
   wrap at a normal word boundary. */
.hs-table tbody td { padding: var(--cell-y) var(--s4); border-bottom: 1px solid var(--rule); vertical-align: middle; overflow-wrap: anywhere; }
.hs-table tbody tr:last-child td { border-bottom: none; }
.hs-table tbody tr { cursor: pointer; }
/* Colour-role fix (UI-DESIGN-SYSTEM-AUDIT §4.1): the design sets hover =
   --surface-2, selected = --brand-soft. Both were --brand-soft, so hovering a
   row made it look selected. */
.hs-table tbody tr:hover { background: var(--surface-2); }
.hs-table tbody tr.selected { background: var(--brand-soft); box-shadow: inset 3px 0 0 var(--brand); }
.hs-table.compact tbody td { padding: 8px var(--s4); }
.hs-num { text-align: right; font-variant-numeric: tabular-nums; }
.hs-cards { display: none; }
.hs-empty { padding: var(--s10) var(--s4); text-align: center; color: var(--ink-3); font-size: var(--t-sm); }

/* ── EmptyState (dc.html:484-489) ────────────────────────────────────────── */
.hs-empty-state {
    display: flex; flex-direction: column; align-items: center; gap: var(--s2);
    border: 1px dashed var(--rule-2); border-radius: var(--r-md);
    padding: var(--s5); text-align: center;
}
.hs-empty-badge {
    width: 36px; height: 36px; border-radius: var(--r-full);
    background: var(--brand-soft); color: var(--brand);
    display: grid; place-items: center; margin-bottom: var(--s2);
}
.hs-empty-title { font-size: var(--t-sm); font-weight: var(--w-semi); }
.hs-empty-body { font-size: var(--t-xs); color: var(--ink-2); margin: 4px 0 var(--s3); }

/* ── Skeleton (dc.html:490-493) — shimmer bars matching the real layout ──── */
@keyframes hxShim { 0% { background-position: -360px 0; } 100% { background-position: 360px 0; } }
.hs-skeleton { display: flex; flex-direction: column; gap: var(--s2); padding: var(--s4); }
.hs-skeleton-bar {
    height: 13px; border-radius: var(--r-xs);
    background: linear-gradient(90deg, var(--surface-3) 25%, var(--surface-2) 37%, var(--surface-3) 63%);
    background-size: 360px 100%; animation: hxShim 1.3s linear infinite;
}

/* Bulk bar (S3.1) */
.hs-bulkbar {
    display: flex; align-items: center; gap: var(--s3);
    padding: var(--s2) var(--s4); background: var(--brand-soft);
    border-bottom: 1px solid var(--brand-line); font-size: var(--t-sm);
}

/* Filter chips */
.hs-chips { display: flex; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s3); }
.hs-chip {
    min-height: 32px; padding: 0 var(--s3); display: inline-flex; align-items: center; gap: 6px;
    border: 1px solid var(--rule-2); border-radius: var(--r-full); background: var(--surface);
    font-size: var(--t-xs); font-weight: var(--w-med); color: var(--ink-2); cursor: pointer;
    transition: background var(--d-fast) var(--ez-out), border-color var(--d-fast) var(--ez-out);
}
.hs-chip:hover:not(.active) { background: var(--surface-2); border-color: var(--rule); }
.hs-chip.active { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
/* A filter chip with zero results must not look identical to one with hundreds
   — de-emphasis, not removal, so the row layout does not jump. */
.hs-chip[data-count="0"] { opacity: .5; }

/* Masthead presentation — ink/mono/uppercase for the shell's segmented
   controls (FR/EN, CONFORT/COMPACT), per §4.1's two chip styles. */
.hs-chips-masthead .hs-chip {
    min-height: 28px; padding: 0 var(--s2);
    background: transparent; border-color: transparent;
    font-family: var(--f-mono); font-size: var(--t-2xs); letter-spacing: .08em;
    text-transform: uppercase; color: var(--ink-2);
}
.hs-chips-masthead .hs-chip:hover:not(.active) { background: var(--surface-2); border-color: transparent; }
.hs-chips-masthead .hs-chip.active { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }

/* The FR|EN switch in the masthead: the shared .hs-chips brings a bottom margin
   meant for in-page chip groups; the masthead bar is taller than the chips
   (60px vs. 32px — see .hs-mast-logo), so the margin must go and the row must
   centre regardless of the bar's exact height. The design's masthead
   language control (dc.html:563) is the same segmented pair as the theme
   chips, only compacted to two letters. */
.hs-mast .hs-lang-toggle { margin-bottom: 0; align-items: center; }
.hs-mast .hs-lang-toggle .hs-chip { min-height: 28px; padding: 0 var(--s2); }

/* Language dropdown — topbar-cluster recommendation 1b (2026-08-10): one
   button (globe + current code + chevron) instead of a segmented pill, so
   the control doesn't keep growing as CultureSwitcher.Choices does (it
   already anticipates a third, Portuguese, entry). Sized and shaped like
   the theme/bell icon buttons either side of it (36px tall, r-full) rather
   than introducing a new button language; the panel reuses .hs-mast-panel's
   own anchored-dropdown shape for the same reason. */
.hs-lang-dd { position: relative; }
.hs-lang-dd-toggle {
    display: flex; align-items: center; gap: 6px;
    height: 36px; padding: 0 var(--s3);
    border: 1px solid var(--rule); border-radius: var(--r-full);
    background: var(--paper); color: var(--ink-2);
    font-family: var(--f-ui); font-size: var(--t-xs); font-weight: var(--w-semi);
    cursor: pointer;
}
.hs-lang-dd-toggle:hover, .hs-lang-dd-toggle[aria-expanded="true"] { background: var(--surface-2); color: var(--ink); }
.hs-lang-dd-panel {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
    min-width: 160px;
    background: var(--paper); border: 1px solid var(--rule);
    border-radius: var(--r-lg); box-shadow: var(--e-3);
    padding: var(--s2); display: flex; flex-direction: column; gap: 2px;
}
.hs-lang-dd-item {
    text-align: left; border: none; background: transparent;
    padding: var(--s2) var(--s3); border-radius: var(--r-sm);
    font-family: var(--f-ui); font-size: var(--t-sm); color: var(--ink);
    cursor: pointer;
}
.hs-lang-dd-item:hover { background: var(--surface-2); }
.hs-lang-dd-item.active { background: var(--brand-soft); color: var(--brand); font-weight: var(--w-semi); }

/* Theme dropdown — same 1b call, same shape as .hs-lang-dd* right above.
   Icon-only toggle (36px circle, not a labelled pill like language) since
   the active theme is a glyph, not a two-letter code — matches the plain
   icon buttons (bell etc.) either side of it on the bar. */
.hs-theme-dd { position: relative; }
.hs-theme-dd-toggle {
    display: flex; align-items: center; gap: 4px;
    width: 36px; height: 36px; padding: 0;
    border: 1px solid var(--rule); border-radius: var(--r-full);
    background: var(--paper); color: var(--ink-2);
    cursor: pointer; justify-content: center;
}
.hs-theme-dd-toggle:hover, .hs-theme-dd-toggle[aria-expanded="true"] { background: var(--surface-2); color: var(--ink); }
.hs-theme-dd-panel {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
    min-width: 160px;
    background: var(--paper); border: 1px solid var(--rule);
    border-radius: var(--r-lg); box-shadow: var(--e-3);
    padding: var(--s2); display: flex; flex-direction: column; gap: 2px;
}
.hs-theme-dd-item {
    display: flex; align-items: center; gap: var(--s2);
    text-align: left; border: none; background: transparent;
    padding: var(--s2) var(--s3); border-radius: var(--r-sm);
    font-family: var(--f-ui); font-size: var(--t-sm); color: var(--ink);
    cursor: pointer;
}
.hs-theme-dd-item:hover { background: var(--surface-2); }
.hs-theme-dd-item.active { background: var(--brand-soft); color: var(--brand); font-weight: var(--w-semi); }

/* Theme toggle's compact masthead presentation — icon-only chips, sized like
   the language switch beside it. .hs-chip-icon needs no margin of its own
   here since there is no label sibling to space from. */
.hs-theme-toggle { margin-bottom: 0; align-items: center; }
.hs-mast .hs-theme-toggle .hs-chip { min-height: 28px; padding: 0 var(--s2); }

/* ── DocumentViewer (S8.2) ───────────────────────────────────────────────── */
.hs-docview { margin: 0; }
.hs-docview img { max-width: 100%; border: 1px solid var(--rule); border-radius: var(--r-md); }
.hs-docview figcaption { margin-top: var(--s2); }
.hs-docview-empty, .hs-docview-link { border: 1px dashed var(--rule-2); border-radius: var(--r-md); padding: var(--s4); display: flex; flex-direction: column; gap: var(--s3); align-items: flex-start; }

/* ── SidePanel → bottom sheet ≤ 820px ────────────────────────────────────── */
/* Black, not rgba(20,23,25,…). The design system specifies no scrim at all —
   it has no modal overlay, so its dark block has nothing to say here and this
   is HIRMS's own value. It could not stay as it was: (20,23,25) is *lighter*
   than dark --paper #0C0E10, so the scrim would have faintly lit the page it
   is there to dim, which is the exact opposite of a scrim's job. Black at the
   same .35 darkens in both themes and is imperceptibly different from the old
   value on a light ground. Alpha deliberately unchanged. */
.hs-scrim { position: fixed; inset: 0; background: rgba(0,0,0,.35); backdrop-filter: blur(2px); z-index: 40; }
.hs-panel {
    position: fixed; top: 0; right: 0; bottom: 0; width: min(760px, 96vw);
    /* Glass per design §5.2 — the panel is chrome, not a solid surface; the
       queue shows through the blur so the user keeps their place in it. */
    background: color-mix(in srgb, var(--paper) 66%, transparent);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    border-left: 1px solid var(--rule); box-shadow: var(--e-4);
    z-index: 41; display: flex; flex-direction: column;
    animation: hs-slide var(--d-base) var(--ez-out);
}
@keyframes hs-slide { from { transform: translateX(24px); opacity: 0; } to { transform: none; opacity: 1; } }
.hs-panel-sm { width: min(420px, 96vw); }
.hs-panel-lg { width: min(1040px, 96vw); }
/* The bottom-sheet grab handle — visible only in the ≤820px presentation,
   where the panel becomes a sheet rising from the bottom. */
.hs-panel-grab {
    display: none; width: 40px; height: 4px; border-radius: var(--r-full);
    background: var(--rule-2); margin: var(--s3) auto 0;
}
.hs-panel-head {
    padding: var(--s5) var(--s6) var(--s4); border-bottom: 1px solid var(--rule);
    display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s4);
    background: var(--surface-2);
}
/* A flex item's default min-width is auto (its content's intrinsic width),
   not 0 — a long unbroken title (an IntakeRef, an invoice number) forced the
   whole head to overflow the panel instead of wrapping. min-width:0 lets it
   shrink to the row's actual space; overflow-wrap breaks the token itself
   once it has nowhere left to shrink to. */
.hs-panel-head > div:first-child { min-width: 0; overflow-wrap: break-word; }
.hs-panel-head-aside { display: flex; align-items: center; gap: var(--s2); }
.hs-panel-body { padding: var(--s5) var(--s6); overflow-y: auto; flex: 1; }
.hs-panel-foot {
    padding: var(--s4) var(--s6); border-top: 1px solid var(--rule); background: var(--surface-2);
    display: flex; justify-content: space-between; gap: var(--s3); flex-wrap: wrap;
}

/* ── Modal (general overlay; HsConfirmDialog is its specialisation) ──────── */
.hs-modal {
    position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: min(640px, 94vw); max-height: 90vh;
    background: var(--paper); border: 1px solid var(--rule);
    border-radius: var(--r-lg); box-shadow: var(--e-4); z-index: 41;
    display: flex; flex-direction: column;
    animation: hs-fade var(--d-base) var(--ez-out);
}
.hs-modal-sm { width: min(460px, 94vw); }
.hs-modal-lg { width: min(880px, 94vw); }
.hs-modal-head {
    padding: var(--s4) var(--s5); border-bottom: 1px solid var(--rule);
    display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
}
.hs-modal-body { padding: var(--s5); overflow-y: auto; }
.hs-modal-foot {
    padding: var(--s4) var(--s5); border-top: 1px solid var(--rule);
    display: flex; justify-content: flex-end; gap: var(--s2);
}
@keyframes hs-fade { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } }

/* ── Loading overlay (LoadingOverlayService / LoadingOverlayHost) ─────────── */
/* Glass in BOTH themes, not light-only like .hs-card (2026-08-08): this is
   chrome sitting over the page, the same category as .hs-panel/.hs-scrim
   above, which already go glass unconditionally — the light-only rule was
   specific to page-content cards, not overlays. */
.hs-loading-scrim { backdrop-filter: blur(4px); z-index: 60; }
.hs-loading-overlay {
    position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
    z-index: 61; width: min(420px, 90vw);
    background: color-mix(in srgb, var(--paper) 66%, transparent);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    border: 1px solid var(--rule); border-radius: var(--r-lg); box-shadow: var(--e-4);
    padding: var(--s6) var(--s5);
    display: flex; flex-direction: column; align-items: center; gap: var(--s4);
    text-align: center;
    animation: hs-fade var(--d-base) var(--ez-out);
}
.hs-loading-spinner { display: flex; gap: 6px; }
.hs-loading-spinner span {
    width: 8px; height: 8px; border-radius: var(--r-full); background: var(--brand);
    animation: hs-loading-pulse 1.1s var(--ez-io) infinite;
}
.hs-loading-spinner span:nth-child(2) { animation-delay: .15s; }
.hs-loading-spinner span:nth-child(3) { animation-delay: .3s; }
@keyframes hs-loading-pulse {
    0%, 80%, 100% { opacity: .3; transform: scale(.8); }
    40% { opacity: 1; transform: scale(1); }
}
/* The "streaming" read: each line clips in from zero width as if typed. @key
   on the line in LoadingOverlayHost forces a fresh element per line, which is
   what re-triggers this animation on every advance rather than playing once. */
.hs-loading-line {
    font-family: var(--f-ui); font-size: var(--t-sm); color: var(--ink-2);
    min-height: 1.4em; max-width: 100%;
    overflow: hidden; white-space: nowrap;
    border-right: 2px solid transparent;
    animation: hs-loading-type .6s steps(30, end), hs-loading-caret .8s step-end 4;
}
@keyframes hs-loading-type { from { max-width: 0; } to { max-width: 100%; } }
@keyframes hs-loading-caret { 50% { border-color: var(--brand); } }
@media (prefers-reduced-motion: reduce) {
    .hs-loading-line { animation: none; }
    .hs-loading-spinner span { animation: none; opacity: .7; }
}

/* ── Meter (member balances) ─────────────────────────────────────────────── */
.hs-meter + .hs-meter { margin-top: var(--s4); }
.hs-meter-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3); margin-bottom: 6px; }
.hs-meter-label { font-size: var(--t-sm); font-weight: var(--w-med); }
/* Mono, per dc.html:385 ({{m.value}} is rendered in --f-mono with tnum) — the
   build left this in --f-ui. UI-DESIGN-SYSTEM-AUDIT §3.4. */
.hs-meter-value { font-family: var(--f-mono); font-size: var(--t-xs); color: var(--ink-2); font-variant-numeric: tabular-nums; font-feature-settings: 'tnum'; }
/* Segmented tick-track, per dc.html:385-388 — twelve segments with 2px gaps,
   not a smooth bar. The design file renders the meter as discrete ticks and
   fills them left to right; the last filled tick is the same brand/accent
   colour the smooth bar used. Each segment is a 1px-radius span on the grid. */
.hs-meter-track {
    display: flex; gap: 2px; height: 10px;
}
.hs-meter-track .tick {
    flex: 1; border-radius: 1px; background: var(--surface-3);
    transition: background var(--d-slow) var(--ez-out);
}
.hs-meter-track .tick.on { background: var(--brand); }
.hs-meter-track .tick.warn { background: var(--st-pend); }
.hs-meter-track .tick.over { background: var(--st-rejt); }
.hs-meter-note { font-size: var(--t-2xs); color: var(--st-pend); margin-top: 4px; }

/* A KPI that answers a question should take you to the answer. */
.hs-kpi-link { display: block; text-decoration: none; color: inherit; }
.hs-kpi-link:hover { text-decoration: none; }
.hs-kpi-link:hover .hs-kpi {
    border-color: var(--brand-line);
    box-shadow: var(--e-2);
    transform: translateY(-1px);
}
.hs-kpi-link .hs-kpi { transition: border-color var(--d-fast) var(--ez-out), box-shadow var(--d-fast) var(--ez-out), transform var(--d-fast) var(--ez-out); }

/* ── KpiTile / ChartCard ─────────────────────────────────────────────────── */
/* Label per dc.html:584 — mono 9px uppercase tracked --ink-2, not --f-ui.
   The notch (--notch, 18px) belongs on KpiTile + MemberCard only (audit §1). */
.hs-kpi {
    background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-lg);
    padding: var(--s4); box-shadow: var(--e-1);
    clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
}
.hs-kpi-label { font-family: var(--f-mono); font-size: 9px; letter-spacing: .8px; text-transform: uppercase; color: var(--ink-2); }
.hs-kpi-value { font-family: var(--f-disp); font-size: var(--t-2xl); font-weight: var(--w-bold); line-height: var(--lh-1); margin: var(--s2) 0 4px; display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s2); font-feature-settings: 'tnum'; }
.hs-kpi-value.accent { color: var(--accent); }
.hs-kpi-sub { font-size: var(--t-xs); color: var(--ink-2); }
/* The design file's finance KPI — a 3px coloured top border (dc.html:773). */
.hs-kpi--top { position: relative; padding-top: var(--s4); }
.hs-kpi-top {
    position: absolute; top: 0; left: 0; right: 0; height: 3px;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
}
.hs-bars { display: flex; align-items: flex-end; gap: var(--s3); height: 180px; padding-top: var(--s4); }
.hs-bar-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; height: 100%; justify-content: flex-end; }
.hs-bar { width: 100%; max-width: 56px; border-radius: var(--r-sm) var(--r-sm) 0 0; background: var(--brand); min-height: 2px; }
.hs-bar-label { font-size: var(--t-2xs); color: var(--ink-3); text-align: center; }
.hs-bar-value { font-size: var(--t-2xs); font-weight: var(--w-semi); color: var(--ink-2); font-variant-numeric: tabular-nums; }

/* ── Stepper (kiosk flow) ────────────────────────────────────────────────── */
/* Per dc.html:509-516,1227-1232: done = green (--accent) filled + white ✓,
   current = teal filled + mono numeral, future = --surface-3 outline; the
   label sits UNDER the dot (a centred column, not a row beside it), and the
   connecting bar runs dot-centre to dot-centre via an absolutely positioned
   span (top:13px = half the 26px dot), not a fixed-width line beside the
   label. UI-DESIGN-SYSTEM-AUDIT §3.2/§3.12/§4.7. */
.hs-stepper { display: flex; align-items: flex-start; margin-bottom: var(--s5); }
.hs-step { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; position: relative; }
.hs-step-dot {
    width: 26px; height: 26px; border-radius: var(--r-full); border: 1px solid var(--rule-2);
    display: grid; place-items: center; font-family: var(--f-mono); font-size: 10.5px; font-weight: var(--w-semi);
    background: var(--surface-3); color: var(--ink-3); z-index: 1;
}
.hs-step.done .hs-step-dot { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.hs-step.current .hs-step-dot { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
.hs-step-label { font-size: 10px; color: var(--ink-2); text-align: center; line-height: var(--lh-snug); }
.hs-step-bar {
    position: absolute; top: 13px; left: 50%; right: -50%; height: 2px;
    background: var(--rule); z-index: 0;
}
.hs-step-bar.done { background: var(--accent); }

/* ── Rule citation & alerts ──────────────────────────────────────────────── */
.hs-rule {
    border-left: 3px solid var(--brand-line); background: var(--brand-soft);
    padding: var(--s2) var(--s3); border-radius: 0 var(--r-md) var(--r-md) 0;
    font-size: var(--t-xs); color: var(--brand-lo);
}
.hs-rule .quote { font-style: italic; }
/* Alert — the §4.13 left-edge geometry: 3px border-left, squared-off on the
   accent side (border-radius only on the right). The old rule was a uniform
   1px box wrapping a single child with no icon slot. */
.hs-alert {
    padding: var(--s3) var(--s4);
    border: 1px solid; border-left: 3px solid; border-radius: 0 var(--r-md) var(--r-md) 0;
    font-size: var(--t-sm); display: flex; gap: var(--s3); align-items: flex-start;
}
.hs-alert-icon { flex: none; line-height: 1.4; }
/* min-width:0, not just flex:1 — a flex item's default min-width is auto,
   which means "never shrink narrower than my content wants to be". A short
   filename never exposed this; a genuinely long one (UploadInvoices.razor's
   job rows: raw upload filenames, sometimes 60+ characters) grew the whole
   body past the row instead of the inner span's own overflow:hidden ever
   getting a narrower box to truncate against — text-overflow:ellipsis on a
   descendant does nothing if nothing upstream is actually constrained.
   Harmless for short content: this only lowers how far the item CAN shrink,
   it does not force it to. */
.hs-alert-body { flex: 1; min-width: 0; }
.hs-alert-actions { margin-top: var(--s2); display: flex; gap: var(--s2); flex-wrap: wrap; }
.hs-alert-dismissible { padding-right: var(--s2); }
.hs-alert-block { color: var(--st-rejt); background: var(--st-rejt-bg); border-color: var(--st-rejt-line); }
.hs-alert-warn { color: var(--st-pend); background: var(--st-pend-bg); border-color: var(--st-pend-line); }
.hs-alert-info { color: var(--st-eval); background: var(--st-eval-bg); border-color: var(--st-eval-line); }
.hs-alert-ok { color: var(--st-redy); background: var(--st-redy-bg); border-color: var(--st-redy-line); }
/* The permission/restricted-access shell §4.13 specifies and no tone covered:
   neutral ink on --surface-3 with a --rule-2 edge. */
.hs-alert-restricted { color: var(--ink-2); background: var(--surface-3); border-color: var(--rule-2); }
/* A clickable hs-alert acting as a list row (UploadInvoices.razor's job
   list) needs its own selected state on top of whichever tone is active —
   a ring rather than a colour swap, so "selected" and "ready/failed/etc."
   stay two independent signals instead of fighting over the same border. */
.hs-alert.selected { box-shadow: 0 0 0 2px var(--brand); }

/* ── Cover split bar ─────────────────────────────────────────────────────── */
.hs-split { display: flex; height: 26px; border-radius: var(--r-full); overflow: hidden; border: 1px solid var(--rule); }
/* The one #fff in this file that is deliberately NOT a token, and --on-brand is
   the specific wrong answer. The two segments below are painted with literal
   ramp steps — var(--b-600), var(--b-400) — not var(--brand), because a cover
   split is categorical data: insurer and employer must stay distinguishable
   from each other, so they are pinned to the scale rather than following the
   theme. Those two backgrounds are therefore identical in light and dark.
   --on-brand is not: it flips to #04222B when --brand becomes a pale teal. Pair
   the two and dark mode gets near-black text on #00718F at 2.96:1 — a fail
   introduced by tokenising, on the widget that tells a member what they owe.
   White on those two fixed steps measures 5.59:1 and 3.08:1 in both themes.

   The 3.08:1 is the EMPLOYER segment, and it failed AA at 11px — worse than the
   --i-400 case the member fix below caught, and missed by it.

   It does NOT need the ramp re-picked. Walking the scale shows white passes AA
   at no step that is still visibly distinct from --b-600 (b-500 is 4.00:1,
   b-700 is 7.72:1 but reads as the same colour as its neighbour). Turning the
   TEXT over instead costs nothing and measures 5.85:1: --ink on --b-400. The
   backgrounds stay exactly where the design put them, so the three segments
   remain as separable as they ever were.

   The bar therefore reads light-on-dark, dark-on-mid, dark-on-pale — which is
   not an inconsistency but what any value scale does, flipping its ink at the
   luminance crossover. The member segment below already does it. */
.hs-split-seg { display: grid; place-items: center; font-size: var(--t-2xs); font-weight: var(--w-semi); color: #fff; min-width: 2px; overflow: hidden; }
.hs-split-insurer { background: var(--b-600); }
/* 5.85:1. Must stay below .hs-split-seg to beat its #fff on source order. */
.hs-split-employer { background: var(--b-400); color: var(--ink); }
/* White on --i-400 measured 3.3:1 — below the 4.5:1 that 11px text needs, on a
   widget whose whole job is telling a member what they owe. The design does not
   fight the mid-grey with white text; it steps the segment back to a pale
   surface and puts dark ink on it (dc.html:656), which reads at 4.5:1 and has
   the side effect of saying the right thing: the member's share is the quiet
   one, not a third brand colour. Overrides the #fff from .hs-split-seg on
   source order, so this rule must stay below it. */
.hs-split-member { background: var(--surface-3); color: var(--ink-2); }
.hs-split-legend { display: flex; gap: var(--s4); margin-top: var(--s2); font-size: var(--t-xs); color: var(--ink-2); flex-wrap: wrap; }
.hs-split-legend span::before { content: ''; display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 6px; }
.hs-split-legend .i::before { background: var(--b-600); }
.hs-split-legend .e::before { background: var(--b-400); }
/* Follows the segment above. A key that names a colour the bar no longer uses
   is worse than no key at all — and the pale swatch needs an edge, or an 8px
   square of --surface-3 on a card of --surface is just nothing. */
.hs-split-legend .m::before { background: var(--surface-3); border: 1px solid var(--rule-2); }

/* ── Member card (kiosk/portal) ──────────────────────────────────────────── */
.hs-membercard {
    background: linear-gradient(135deg, var(--brand-lo), var(--brand));
    /* --r-lg, not --r-xl — dc.html:848 draws the member card at --r-lg.
       UI-DESIGN-SYSTEM-AUDIT §3.2/§3.13. */
    color: var(--on-brand); border-radius: var(--r-lg); padding: var(--s5) var(--s6); box-shadow: var(--e-3);
    clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
}
.hs-membercard .name { font-family: var(--f-disp); font-size: var(--t-xl); font-weight: var(--w-bold); }
.hs-membercard .mso { font-family: var(--f-mono); font-size: var(--t-sm); opacity: .85; letter-spacing: .04em; }
.hs-membercard .row { display: flex; justify-content: space-between; gap: var(--s4); margin-top: var(--s4); font-size: var(--t-xs); opacity: .92; flex-wrap: wrap; }
/* Photo frame + name/MSO + QR, in one row — photo and QR are both optional
   (QR only when the host passes the Qr slot; the photo frame always renders,
   silhouette or real portrait, so the layout doesn't jump the day a photo
   pipeline lands). */
.hs-membercard-head { display: flex; align-items: center; gap: var(--s3); }
.hs-membercard-id { min-width: 0; flex: 1; }
.hs-membercard-photo {
    width: 44px; height: 44px; border-radius: var(--r-full); flex: none;
    background: rgba(255,255,255,.16); border: 1px solid rgba(255,255,255,.35);
    display: grid; place-items: center; overflow: hidden; color: var(--on-brand);
}
.hs-membercard-photo img { width: 100%; height: 100%; object-fit: cover; }
.hs-membercard.compact .hs-membercard-photo { width: 36px; height: 36px; }

/* The design file's phone member card (dc.html:848-864): the notched gradient
   plate with the QR in a white box on the right. */
.hs-membercard--phone { position: relative; overflow: hidden; }
.hs-membercard-glow {
    position: absolute; right: -36px; bottom: -40px; width: 130px; height: 130px;
    border-radius: var(--r-full); background: rgba(255,255,255,.07);
}
.hs-membercard-main { display: flex; align-items: flex-start; gap: var(--s3); position: relative; }
.hs-membercard-kicker {
    font-family: var(--f-mono); font-size: 9px; letter-spacing: 1.4px; text-transform: uppercase; opacity: .78;
}
.hs-membercard-qr { width: 72px; height: 72px; background: #fff; border-radius: var(--r-sm); padding: 5px; flex: none; }
.hs-membercard-qr .hs-qr { gap: 0; }

/* ── Phone-style layout (member portal) ──────────────────────────────────── */
.hs-phone-head { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s5); }
.hs-avatar {
    width: 38px; height: 38px; border-radius: var(--r-full);
    background: var(--brand-soft); color: var(--brand);
    display: grid; place-items: center; font-weight: var(--w-bold); font-size: var(--t-sm); flex: none;
}
.hs-phone-icon {
    display: grid; place-items: center; width: 44px; height: 44px;
    border: 1px solid var(--rule); background: var(--surface); color: var(--ink-2);
    border-radius: var(--r-md); cursor: pointer; flex: none;
}
.hs-phone-section { margin-bottom: var(--s5); }
.hs-phone-section-title {
    font-family: var(--f-mono); font-size: 9.5px; font-weight: var(--w-semi);
    text-transform: uppercase; letter-spacing: 1px; color: var(--ink-2); margin-bottom: var(--s3);
}
.hs-phone-actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); margin-bottom: var(--s5); }
.hs-phone-action {
    display: flex; flex-direction: column; align-items: flex-start; gap: 7px;
    min-height: 76px; padding: var(--s3);
    border: 1px solid var(--rule); background: var(--surface-2); color: var(--ink);
    border-radius: var(--r-md); font: inherit; font-size: var(--t-sm); font-weight: var(--w-semi);
    cursor: pointer; text-align: left;
}
.hs-phone-action-icon {
    width: 26px; height: 26px; border-radius: var(--r-sm);
    display: grid; place-items: center; font-size: var(--t-md); flex: none;
}
.hs-phone-action--brand { background: var(--brand-soft); color: var(--brand); }
.hs-phone-action--pend { background: var(--st-pend-bg); color: var(--st-pend); }
/* Not --accent: green is reserved for financial confirmations (CLAUDE.md
   conventions) and "Find a provider" is a directory lookup, not one. */
.hs-phone-action--neutral { background: var(--surface-3); color: var(--ink-2); }
.hs-phone-action--eval { background: var(--st-eval-bg); color: var(--st-eval); }
.hs-phone-list { border: 1px solid var(--rule); border-radius: var(--r-md); overflow: hidden; }
.hs-phone-list-item {
    display: flex; align-items: center; gap: var(--s3);
    padding: var(--s3); border-bottom: 1px solid var(--rule); min-height: 56px;
    background: var(--surface);
}
.hs-phone-list-item:last-child { border-bottom: none; }
.hs-phone-list-item .hs-tick { align-self: stretch; }
/* A row that opens a detail view (Claims) is a <button>, not a <div>, for
   keyboard and screen-reader operability — this resets the browser's own
   button chrome back to plain list-item styling. */
.hs-phone-list-item--btn {
    width: 100%; border: none; text-align: left; font: inherit; color: inherit;
    cursor: pointer;
}
.hs-phone-list-item--btn:hover { background: var(--surface-2); }
.hs-phone-tabbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    display: grid; grid-template-columns: repeat(4, 1fr);
    border-top: 1px solid var(--rule); background: color-mix(in srgb, var(--paper) 66%, transparent);
    backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
}
.hs-phone-tab {
    display: flex; flex-direction: column; align-items: center; gap: 4px;
    min-height: 58px; padding: var(--s2) 0;
    border: none; background: transparent; font: inherit; font-size: 10px; font-weight: var(--w-semi);
    color: var(--ink-3); cursor: pointer;
}
.hs-phone-tab--active { color: var(--brand); }
/* Room for the fixed tabbar so the last list item is not hidden behind it. */
.hs-main-narrow { padding-bottom: 76px; }

/* ── Kiosk hero (dc.html:906-935) ───────────────────────────────────────────
   The design file's ProviderKiosk idle screen: a centred card with an eyebrow,
   a display headline, the dashed scan target, body copy, two large buttons and
   the online/offline dot. The scan target is a button when the camera exists,
   so the whole thing is one tap target on a clinic tablet. */
.hs-kiosk-hero {
    max-width: 640px; margin: 0 auto; padding: var(--s8) var(--s6);
    display: flex; flex-direction: column; gap: var(--s5); align-items: center; text-align: center;
    border: 1px solid var(--rule-2); border-radius: var(--r-xl);
    background: var(--surface); box-shadow: var(--e-3);
}
.hs-kiosk-eyebrow {
    font-family: var(--f-mono); font-size: var(--t-2xs); letter-spacing: 2px;
    text-transform: uppercase; color: var(--ink-2);
}
.hs-kiosk-title {
    font-family: var(--f-disp); font-size: var(--t-2xl); font-weight: var(--w-bold);
    letter-spacing: -1.4px; max-width: 24ch; margin: 0;
}
.hs-kiosk-scan {
    width: 190px; height: 190px; border-radius: var(--r-lg);
    border: 2px dashed var(--brand-line); background: var(--brand-soft);
    position: relative; display: grid; place-items: center;
    cursor: pointer;
}
.hs-kiosk-scan:hover { border-color: var(--brand); background: var(--brand-soft); }
.hs-kiosk-native {
    position: absolute; top: -11px; left: 50%; transform: translateX(-50%);
    font-family: var(--f-mono); font-size: 9px; font-weight: var(--w-semi); letter-spacing: .7px;
    padding: 3px 8px; border-radius: var(--r-full);
    background: var(--st-pend-bg); color: var(--st-pend); border: 1px solid var(--st-pend-line);
    white-space: nowrap;
}
.hs-kiosk-body { margin: 0; font-size: var(--t-sm); color: var(--ink-2); max-width: 46ch; }
.hs-kiosk-actions { display: flex; gap: var(--s3); flex-wrap: wrap; justify-content: center; }
.hs-kiosk-offline {
    display: flex; gap: var(--s2); align-items: center;
    font-family: var(--f-mono); font-size: 10px; color: var(--ink-3);
}
.hs-kiosk-offline-dot { width: 6px; height: 6px; border-radius: var(--r-full); background: var(--accent); flex: none; }

/* ── Network graph (S6.3) ────────────────────────────────────────────────── */
.hs-graph { width: 100%; height: 460px; border: 1px solid var(--rule); border-radius: var(--r-lg); background: var(--surface-2); }
.hs-graph-legend { display: flex; gap: var(--s4); margin-top: var(--s2); font-size: var(--t-xs); color: var(--ink-2); flex-wrap: wrap; }

/* ── Kiosk / portal density: touch targets ≥ 44px ────────────────────────── */

/* ── Responsive: grid → cards, panel → bottom sheet ──────────────────────── */
@media (max-width: 820px) {
    /* Below 820px the nav is a horizontal strip under the mast, not a sidebar
       beside it — a fixed-height, single-scroll-region shell doesn't fit that
       shape, so this reverts to one normal, whole-page scroll (.hs-mast's
       own position:sticky still keeps it pinned as that page scrolls). */
    .hs-shell { grid-template-columns: 1fr; height: auto; overflow: visible; }
    /* padding-top back to 0: below 820px the nav is a horizontal strip
       sitting ABOVE main, not a column running beside it, so the desktop
       offset that aligns the sidebar's top with main's page title (added
       above) has nothing to align against here and would just be dead space
       over the row of links. The transparent-gap/glass-panel split above is
       a desktop-only concern for the same reason — .hs-nav-inner just fills
       .hs-nav edge to edge at this width. */
    .hs-nav { padding-top: 0; }
    .hs-nav-inner { flex-direction: row; overflow-x: auto; border-right: none; border-bottom: 1px solid var(--rule); padding: var(--s3) var(--s2); }
    .hs-nav a { min-height: 40px; }
    .hs-nav-group, .hs-brand small, .hs-brand { display: none; }
    .hs-mast { padding: var(--s2) var(--s3); gap: var(--s2); }
    .hs-mast-toggle { display: grid; }
    /* The brand wordmark steps out first — the logo mark is the identity that
       survives at this width. */
    .hs-mast-word { display: none; }
    .hs-main { padding: var(--s4); overflow-y: visible; }
    .hs-table { display: none; }
    .hs-cards { display: flex; flex-direction: column; gap: var(--s3); padding: var(--s3); }
    .hs-rowcard { position: relative; border: 1px solid var(--rule); border-radius: var(--r-md); padding: var(--s3); background: var(--surface); }
    /* The checkbox rides alongside the card's headline rather than above it, so
       a selectable card and an unselectable one are the same height. 44px is the
       touch floor — this is a thumb on a phone, not a pointer. */
    .hs-rowcard:has(.hs-rowcard-select) { display: grid; grid-template-columns: 44px 1fr; align-items: start; }
    .hs-rowcard:has(.hs-rowcard-select) dl { grid-column: 2; }
    .hs-rowcard-select {
        grid-row: 1; display: grid; place-items: center;
        min-width: 44px; min-height: 44px; margin: calc(var(--s3) * -1) 0 0 calc(var(--s3) * -1);
        cursor: pointer;
    }
    .hs-rowcard dl { display: grid; grid-template-columns: auto 1fr; gap: 4px var(--s3); margin: var(--s2) 0 0; font-size: var(--t-xs); }
    .hs-rowcard dt { color: var(--ink-3); }
    .hs-rowcard dd { margin: 0; text-align: right; }
    .hs-panel {
        top: auto; left: 0; right: 0; bottom: 0; width: 100%; height: 92vh;
        border-left: none; border-top: 1px solid var(--rule);
        border-radius: var(--r-xl) var(--r-xl) 0 0;
        animation-name: hs-sheet;
    }
    .hs-panel-grab { display: block; }
    .hs-grid-2, .hs-grid-3, .hs-grid-4 { grid-template-columns: 1fr; }
    @keyframes hs-sheet { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }
}

/* Below 481px the masthead's language + theme controls (five small chip
   targets) crowd the refresh/bell/avatar cluster in the same strip — reported
   2026-08-08 against a 400px viewport. Collapsed behind one gear icon; the
   dropdown itself reuses the account panel's exact anchored-overlay shape. */
@media (max-width: 480px) {
    .hs-mast-settings-toggle { display: grid; }
    .hs-mast-inline-settings {
        display: none;
        position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
        flex-direction: column; align-items: stretch; gap: var(--s3);
        width: min(220px, calc(100vw - var(--s6)));
        background: var(--paper); border: 1px solid var(--rule);
        border-radius: var(--r-lg); box-shadow: var(--e-3);
        padding: var(--s4);
    }
    .hs-mast-inline-settings.open { display: flex; }
}

/* ── Block B components ──────────────────────────────────────────────────── */

/* Pager — sits in the grid footer so it scrolls with the table it belongs to. */
.hs-pager {
    display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap;
    padding: var(--s3) var(--s4); border-top: 1px solid var(--rule); background: var(--surface-2);
    font-size: var(--t-xs); color: var(--ink-2);
}
.hs-pager-range { font-variant-numeric: tabular-nums; }
.hs-pager-controls { display: flex; align-items: center; gap: var(--s2); margin-left: auto; }
.hs-pager-page { font-variant-numeric: tabular-nums; padding: 0 var(--s2); white-space: nowrap; }
.hs-pager-size { max-width: 150px; min-height: 30px; font-size: var(--t-xs); }

/* Sortable headers. The arrow is part of the button so the hit target includes it. */
.hs-th-sort {
    background: none; border: none; padding: 0; cursor: pointer; font: inherit;
    color: inherit; text-transform: inherit; letter-spacing: inherit;
    display: inline-flex; align-items: center; gap: 4px;
}
.hs-th-sort:hover { color: var(--brand); }
.hs-th-arrow { color: var(--brand); font-size: var(--t-xs); }

/* Grid states. A list has four, not one. */
.hs-grid-state {
    display: flex; align-items: center; gap: var(--s3);
    padding: var(--s8) var(--s4); color: var(--ink-2); font-size: var(--t-sm);
    justify-content: center;
}
/* Left accent + squared-off corner on the accent side, per dc.html:493
   ("Service indisponible" state) — the build had a flat tinted block. */
.hs-grid-state-error {
    color: var(--st-rejt); background: var(--st-rejt-bg);
    border-left: 3px solid var(--st-rejt); justify-content: flex-start; text-align: left;
}
.hs-grid-foot { border-top: 1px solid var(--rule); }
.hs-spinner {
    width: 16px; height: 16px; border-radius: var(--r-full);
    border: 2px solid var(--rule-2); border-top-color: var(--brand);
    /* The one duration that stays hardcoded, and linear with it: a spinner
       turns at a constant rate forever, so it has no arrival to ease into and
       nothing to do with the three-step motion scale. The design leaves its
       own spin unscaled for the same reason. */
    animation: hs-spin .7s linear infinite; display: inline-block;
}
@keyframes hs-spin { to { transform: rotate(360deg); } }

/* Row status modifiers — a held claim should be visible before it is read.
   The five canonical tones (tokens.css --st-*): eval/pend/redy/rejt/paid.
   held and flagged are semantic aliases for pend/rejt kept for the callers
   already using them; eval/pend/redy/rejt are also exposed by their own tone
   names so a page can pass RowStatus straight from a status enum's tone
   (StatusPill.razor's own Claim/Requisition/PreAuth switch) without
   re-deriving a second vocabulary. */
.hs-table tbody tr.hs-row-eval { box-shadow: inset 3px 0 0 var(--st-eval); }
.hs-table tbody tr.hs-row-pend, .hs-table tbody tr.hs-row-held { box-shadow: inset 3px 0 0 var(--st-pend); }
.hs-table tbody tr.hs-row-redy { box-shadow: inset 3px 0 0 var(--st-redy); }
.hs-table tbody tr.hs-row-rejt, .hs-table tbody tr.hs-row-flagged { box-shadow: inset 3px 0 0 var(--st-rejt); }
.hs-table tbody tr.hs-row-paid { opacity: .72; }
/* The status edge tick — a 3px coloured column at the row's left edge, so the
   state is readable before the pill is (dc.html:459). Sits in its own narrow
   cell so it does not compress the first data column. */
.hs-row-tick { width: 6px; padding: 0 !important; }
.hs-row-tick .hs-tick { display: block; width: 3px; height: 100%; min-height: var(--row-h); }
/* Card-mode tick — anchored to the card's left edge, spanning its height. */
.hs-rowcard-tick { position: absolute; left: 0; top: 0; bottom: 0; height: auto; opacity: 1; }
.hs-hide-narrow { }

/* Tabs */
.hs-tabs { display: flex; gap: var(--s1); border-bottom: 1px solid var(--rule); margin-bottom: var(--s4); overflow-x: auto; }
.hs-tab {
    background: none; border: none; border-bottom: 2px solid transparent;
    padding: var(--s3) var(--s4); cursor: pointer;
    font-family: var(--f-ui); font-size: var(--t-sm); font-weight: var(--w-med); color: var(--ink-2);
    display: inline-flex; align-items: center; gap: var(--s2); min-height: 42px;
    white-space: nowrap;
}
.hs-tab:hover { color: var(--brand); }
.hs-tab.active { color: var(--brand-lo); border-bottom-color: var(--brand); font-weight: var(--w-semi); }
.hs-tab-badge {
    background: var(--surface-3); border-radius: var(--r-full); padding: 1px 7px;
    font-size: var(--t-2xs); font-variant-numeric: tabular-nums;
}
.hs-tab.active .hs-tab-badge { background: var(--brand-soft); color: var(--brand-lo); }
/* Badge tones — "3 errors" (rejt) must not look like "3 lines" (neutral). */
.hs-tab-badge-rejt { background: var(--st-rejt-bg); color: var(--st-rejt); }
.hs-tab-badge-pend { background: var(--st-pend-bg); color: var(--st-pend); }
.hs-tab-badge-redy { background: var(--st-redy-bg); color: var(--st-redy); }

/* Toasts */
.hs-toast-host {
    position: fixed; right: var(--s5); bottom: var(--s5); z-index: 60;
    display: flex; flex-direction: column; gap: var(--s2); max-width: min(420px, 92vw);
}
.hs-toast {
    display: flex; gap: var(--s3); align-items: flex-start;
    /* --paper, not --surface: a toast floats over arbitrary page content, so it
       has to be opaque. --surface is translucent in both themes and would let
       the table underneath show through the text. Matches .hs-confirm,
       .hs-bell-panel and .hs-picker-list, the other three overlays. */
    background: var(--paper); border: 1px solid var(--rule); border-left-width: 4px;
    border-radius: var(--r-md); box-shadow: var(--e-3);
    padding: var(--s3) var(--s4); font-size: var(--t-sm);
    animation: hs-toast-in var(--d-base) var(--ez-out);
}
.hs-toast > div:first-child { flex: 1; }
.hs-toast-redy { border-left-color: var(--st-redy); }
.hs-toast-eval { border-left-color: var(--st-eval); }
.hs-toast-pend { border-left-color: var(--st-pend); }
.hs-toast-rejt { border-left-color: var(--st-rejt); }
@keyframes hs-toast-in { from { opacity: 0; transform: translateY(6px); } }

/* Confirm dialog */
.hs-confirm {
    position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 61;
    width: min(460px, 92vw); background: var(--paper);
    border: 1px solid var(--rule); border-radius: var(--r-lg); box-shadow: var(--e-4);
    padding: var(--s5) var(--s6);
}
.hs-confirm h3 { margin-bottom: var(--s2); }
.hs-confirm-body { margin: var(--s3) 0; }
.hs-confirm-actions { display: flex; justify-content: flex-end; gap: var(--s2); margin-top: var(--s5); }

/* Field validation — the label goes negative with the control, and the border
   is 1.5px (not 1px) so an invalid field reads before the message does. */
.hs-field-invalid .hs-label { color: var(--neg); }
.hs-field-invalid .hs-input,
.hs-field-invalid .hs-select,
.hs-field-invalid .hs-textarea,
.hs-field-invalid .hs-select-wrap .hs-select { border-color: var(--neg); border-width: 1.5px; }
/* The error line carries the design's 14px round badge before the sentence —
   a marker a colour-blind clerk can still find, because the colour alone is
   not the signal. */
.hs-field-error {
    display: flex; align-items: center; gap: var(--s2);
    font-size: var(--t-xs); color: var(--neg);
}
.hs-field-error::before {
    content: "!"; flex: none;
    width: 14px; height: 14px; border-radius: var(--r-full);
    background: var(--neg); color: var(--paper);
    display: grid; place-items: center;
    font-size: 10px; font-weight: var(--w-bold); line-height: 1;
}
.hs-required { color: var(--neg); margin-left: 3px; }

/* Chips with counts */
.hs-chip-count {
    background: var(--surface-3); border-radius: var(--r-full); padding: 0 6px;
    font-size: var(--t-2xs); font-variant-numeric: tabular-nums;
}
/* On an active chip the count sits on --brand, so it is tinted with the ink
   that chip already uses rather than a fixed white wash. rgba(255,255,255,.25)
   was right only while --brand stayed dark; in dark --brand is a pale teal and
   a white wash on it disappears. Mixing --on-brand keeps the wash on the
   opposite side of the chip from its background in both themes — white at 22%
   in light, near-black at 22% in dark — which is the same effect, not a new
   colour. color-mix is the design system's own idiom (dc.html:96). */
.hs-chip.active .hs-chip-count { background: color-mix(in srgb, var(--on-brand) 22%, transparent); color: inherit; }

/* Breadcrumb */
.hs-breadcrumb {
    display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
    font-size: var(--t-xs); color: var(--ink-3); margin-bottom: var(--s3);
}
.hs-breadcrumb-current { color: var(--ink-2); font-weight: var(--w-med); }
.hs-breadcrumb-sep { color: var(--rule-2); }

/* Filter bar — the row of controls above a grid. */
.hs-filters {
    display: flex; gap: var(--s3); flex-wrap: wrap; align-items: flex-end;
    padding: var(--s4); background: var(--surface-2);
    border: 1px solid var(--rule); border-radius: var(--r-lg); margin-bottom: var(--s4);
}
/* Targets .hs-filter-field, a real wrapper div each page adds around its own
   HsField — never .hs-field, which HsField (Hirms.Rcl/Components/HsField.razor)
   never renders: it has no wrapping element at all, label/control/hint render
   as flat siblings. A selector naming .hs-field silently matches nothing, and
   .hs-filters' flex-wrap then wraps at the individual label/input/hint level
   instead of per logical field — confirmed live on Claims.razor/
   FraudConsole.razor, matching the bug TobBrowser.razor's .hs-filters-compact
   variant already carries this exact comment about. 2026-08-08. */
.hs-filters .hs-filter-field { margin-bottom: 0; min-width: 170px; }
.hs-filters-actions { margin-left: auto; display: flex; gap: var(--s2); align-items: center; }

/* TOB's filter bar has five fields (search + insurer/plan/category/coverage)
   where every other page's has at most two or three — at the default
   170px-per-field minimum that wraps to two rows on anything narrower than a
   very wide monitor. Narrower fields here keep it on one row at normal
   widths; the search box (the one field worth reading, not just picking from
   a short list) keeps the most room. 2026-08-06, product-owner ask.
   .hs-filter-field, not .hs-field: HsField (the Razor component) renders no
   wrapping element at all — label, control and hint render as flat siblings
   — so a selector targeting ".hs-field" never matched anything and the
   fields wrapped per label/input/hint, not per logical field. TobBrowser.razor
   wraps each one in a real div now. */
.hs-filters-compact { gap: var(--s2); }
.hs-filters-compact .hs-filter-field { display: flex; flex-direction: column; gap: var(--s2); min-width: 128px; }
.hs-filters-compact .hs-filter-field:first-child { min-width: 220px; flex: 1 1 220px; }

/* Member portal sections (B4) */
.hs-section { border: 1px solid var(--rule); border-radius: var(--r-lg); background: var(--surface); margin-bottom: var(--s3); overflow: hidden; }
.hs-section-head {
    display: flex; align-items: center; gap: var(--s4); width: 100%;
    padding: var(--s4) var(--pad-card); background: none; border: none; cursor: pointer;
    font-family: var(--f-ui); text-align: left; min-height: 56px;
}
.hs-section-head:hover { background: var(--brand-soft); }
.hs-section-title { font-weight: var(--w-semi); font-size: var(--t-md); }
.hs-section-sub { font-size: var(--t-xs); color: var(--ink-2); }
.hs-section-meter { flex: 1; max-width: 240px; margin-left: auto; }
.hs-section-caret { color: var(--ink-3); transition: transform var(--d-fast) var(--ez-out); }
.hs-section-caret.open { transform: rotate(90deg); }
.hs-section-body { padding: 0 var(--pad-card) var(--s4); }

@media (max-width: 820px) {
    .hs-hide-narrow { display: none; }
    .hs-pager { flex-direction: column; align-items: stretch; gap: var(--s2); }
    .hs-pager-controls { margin-left: 0; justify-content: space-between; }
    .hs-toast-host { right: var(--s3); left: var(--s3); bottom: var(--s3); max-width: none; }
    .hs-section-meter { max-width: none; margin-left: 0; }
    /* On the member portal the fixed tabbar hugs the bottom of the screen, so
       the toast rack must clear it or a success lands underneath a thumb. */
    .hs-main-narrow ~ .hs-toast-host,
    .hs-app .hs-toast-host { bottom: calc(76px + var(--s3)); }
    /* A filter bar is a toolbar, not a column: on a narrow workbench the
       actions still want to sit beside the last control rather than dropping a
       row below it. */
    .hs-filters-actions { margin-left: 0; flex-wrap: wrap; }
}

/* Funnel */
.hs-funnel { display: flex; flex-direction: column; gap: var(--s4); }
.hs-funnel-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 4px; }
.hs-funnel-label { font-size: var(--t-sm); font-weight: var(--w-med); }
.hs-funnel-value { font-variant-numeric: tabular-nums; font-weight: var(--w-semi); }
.hs-funnel-track { height: 10px; background: var(--surface-3); border-radius: var(--r-full); overflow: hidden; }
/* Matches .hs-meter-fill deliberately. Both are a bar travelling to a new
   width, and two widgets doing the same thing at different speeds is a thing
   people notice without being able to say why. */
.hs-funnel-fill { height: 100%; background: var(--brand); border-radius: var(--r-full); transition: width var(--d-slow) var(--ez-out); }
.hs-funnel-stage.clickable { cursor: pointer; }
.hs-funnel-stage.clickable:hover .hs-funnel-fill { background: var(--brand-lo); }

/* Code picker */
.hs-picker { position: relative; }
.hs-picker-list {
    position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 4px);
    background: var(--paper); border: 1px solid var(--rule); border-radius: var(--r-md);
    box-shadow: var(--e-3); max-height: 320px; overflow-y: auto;
}
.hs-picker-item {
    display: flex; flex-direction: column; gap: 2px; width: 100%; text-align: left;
    background: none; border: none; border-bottom: 1px solid var(--rule);
    padding: var(--s3) var(--s4); cursor: pointer; font: inherit;
}
.hs-picker-item:last-child { border-bottom: none; }
.hs-picker-item:hover { background: var(--brand-soft); }
.hs-picker-state { padding: var(--s4); font-size: var(--t-sm); color: var(--ink-2); display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }

/* File upload — the dashed drop zone (dc.html §6.7, HSL.Platform HxFileUpload).
   The native input stretches invisibly across the whole zone, so click,
   keyboard and OS drag+drop all hit the same control. */
.hs-upload {
    border: 1.5px dashed var(--brand-line);
    border-radius: var(--r-lg);
    background: var(--brand-soft);
    padding: var(--s5);
    position: relative;
    transition: border-color var(--d-fast) var(--ez-out), background var(--d-fast) var(--ez-out);
}
.hs-upload--drag { border-color: var(--brand); background: var(--brand-line); }
.hs-upload--busy { opacity: .8; cursor: progress; }
.hs-upload__zone {
    display: flex; flex-direction: column; align-items: center; gap: var(--s2);
    text-align: center; color: var(--ink-2);
}
.hs-upload__input {
    position: absolute; inset: 0; width: 100%; height: 100%;
    opacity: 0; cursor: pointer;
}
.hs-upload:focus-within { outline: 2px solid var(--brand-line); outline-offset: 2px; border-radius: var(--r-lg); }
.hs-upload__badge {
    width: 36px; height: 36px; border-radius: var(--r-full);
    background: var(--brand); color: var(--on-brand);
    display: grid; place-items: center; flex: none;
}
.hs-upload__label { font-weight: var(--w-semi); color: var(--ink); }
.hs-upload__hint { font-size: var(--t-xs); color: var(--ink-3); }
.hs-upload__browse {
    display: inline-flex; align-items: center;
    height: var(--ctl-sm); padding: 0 var(--s3);
    border: 1px solid var(--brand-line); background: var(--surface);
    color: var(--brand); border-radius: var(--r-sm);
    font-size: var(--t-xs); font-weight: var(--w-semi);
}
.hs-upload__errors {
    display: flex; flex-direction: column; gap: var(--s1);
    margin-top: var(--s2); font-size: var(--t-xs); color: var(--st-rejt);
}
.hs-upload__error { display: inline-flex; align-items: center; gap: 6px; }
.hs-upload__error-badge {
    width: 14px; height: 14px; border-radius: var(--r-full);
    background: var(--st-rejt-bg); color: var(--st-rejt);
    display: inline-grid; place-items: center; font-weight: var(--w-bold); font-size: 9px;
    flex: none;
}

/* QR */
.hs-qr { display: inline-flex; flex-direction: column; align-items: center; gap: var(--s2); }
/* Stays #fff in both themes, and this is not an oversight. A QR code's quiet
   zone and light modules are a scanner requirement, not a surface: the symbol
   is read by a camera thresholding dark modules against a light field, and
   inverting it on a dark page makes the code unreadable on a large share of
   handsets. The white plate is the widget, so it is drawn, not themed. */
.hs-qr svg { border-radius: var(--r-sm); background: #fff; }
.hs-qr-value { font-size: var(--t-xs); color: var(--ink-2); }
.hs-qr-fallback { padding: var(--s4); border: 1px dashed var(--rule-2); border-radius: var(--r-md); }

/* Member card variant */
.hs-membercard.compact { padding: var(--s4); }
.hs-membercard.compact .name { font-size: var(--t-lg); }
.hs-membercard-inactive { color: var(--st-rejt); font-weight: var(--w-semi); }

/* Meter compact — bar only, for section headers */
.hs-meter.compact { margin: 0; }
.hs-meter.compact .hs-meter-track { height: 6px; }
/* Blazor's crash bar. Hidden until the runtime reveals it; when it does show,
   it should look like part of the product, not unstyled text. The PWA loads no
   host-specific css, so this lives in the RCL where both apps get it. */
#blazor-error-ui {
    display: none;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 100;
    background: var(--st-pend-bg, #fff3cd); color: var(--ink, #1f2321);
    border-top: 3px solid var(--st-pend, #b98a00);
    padding: var(--s3) var(--s5); font-size: var(--t-sm);
    box-shadow: var(--e-3);
}
#blazor-error-ui .reload { font-weight: var(--w-semi); margin-left: var(--s2); }
#blazor-error-ui .dismiss { cursor: pointer; float: right; }

/* Sign-in — the first screen either installable app shows. Centred, generous,
   and the only place a large button is right: it is the single thing to do. */
.hs-signin {
    max-width: 460px; margin: 8vh auto; padding: var(--s8) var(--s6);
    background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-lg);
    box-shadow: var(--e-2); text-align: center;
}
/* hsl-brand.css makes .hsl-logo `display:block; margin:2px 0 …` — a block box
   ignores the parent's text-align, so on the member sign-in card (unlike the
   landing page, which already carries this same fix at .hs-landing-head
   .hsl-logo) the mark sat hard left under a centred heading. margin:0 auto
   centres it; height bumped 40px → 56px per product ask. */
.hs-signin .hsl-logo { height: 56px; margin: 0 auto var(--s5); }
.hs-signin h1 { font-size: var(--t-xl); margin-bottom: var(--s2); }
.hs-signin p { margin: 0 0 var(--s5); }
.hs-signin .hs-alert { margin-bottom: var(--s5); }
.hs-signin-foot { margin-top: var(--s6); padding-top: var(--s4); border-top: 1px solid var(--rule); }
.hs-btn-lg { min-height: 48px; padding: 0 var(--s6); font-size: var(--t-md); width: 100%; }

/* ── Notification bell + inbox ───────────────────────────────────────────── */
.hs-bell { position: relative; }
/* The masthead's right cluster (bell → avatar) is pushed to the edge by the
   spacer, but belt-and-braces: margin-left:auto on the first cluster element
   does the same push on its own, so a build where the spacer is missing or
   its flex-grow is defeated cannot bunch the cluster against the brand. */
.hs-mast .hs-bell { margin-left: auto; }
.hs-bell-btn {
    position: relative; background: none; border: 1px solid transparent;
    border-radius: var(--r-md); min-height: var(--ctl); min-width: var(--ctl);
    cursor: pointer; font-size: var(--t-md); line-height: 1;
    display: inline-flex; align-items: center; justify-content: center;
}
.hs-bell-btn:hover, .hs-bell-btn.active { background: var(--brand-soft); border-color: var(--brand-line); }
.hs-bell-count {
    position: absolute; top: 2px; right: 2px;
    min-width: 17px; height: 17px; padding: 0 4px;
    /* The status pair, used the other way up. Everywhere else --st-rejt is ink
       on --st-rejt-bg; a filled count badge inverts that, so the two tokens
       swap roles and the badge follows the theme for free — off-white on deep
       red in light (5.36:1), near-black on salmon in dark (8.21:1). Plain #fff
       was right only while --st-rejt stayed dark, and in dark it is #F2A99C. */
    background: var(--st-rejt); color: var(--st-rejt-bg);
    border-radius: var(--r-full); font-size: var(--t-2xs); font-weight: var(--w-semi);
    display: inline-flex; align-items: center; justify-content: center;
    font-variant-numeric: tabular-nums;
}
.hs-scrim-quiet { background: transparent; }   /* click-away without dimming the page */

.hs-bell-panel {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
    width: min(400px, calc(100vw - var(--s6)));
    background: var(--paper); border: 1px solid var(--rule);
    border-radius: var(--r-lg); box-shadow: var(--e-3);
    max-height: min(560px, 70vh); display: flex; flex-direction: column;
}
.hs-bell-head {
    display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
    padding: var(--s3) var(--s4); border-bottom: 1px solid var(--rule);
}
/* ── Camera scanner overlay ───────────────────────────────────────────────────
   The kiosk used to append a bare <video> at inset:0 with no cancel, no reticle
   and no copy: a clinic tablet went black-with-a-picture-of-the-room for twenty
   seconds with nothing to press. Built by camera.js, styled here — a literal in
   that file could not be themed and would be a white hole in dark mode.

   Fixed, not absolute, so it covers the viewport rather than the nearest
   positioned ancestor. The ink here is deliberately NOT --paper on --ink: a
   camera preview wants a true black surround in both themes, because the
   picture is the content and anything lighter reads as a frame around it. */
.hs-scanner {
    position: fixed; inset: 0; z-index: 200;
    display: grid; place-items: center;
    background: #000;
}
.hs-scanner-video {
    width: 100%; height: 100%; object-fit: cover;
    position: absolute; inset: 0;
}
/* 190px, the design system's scan-target geometry (§6.7). Corner brackets
   rather than a full rectangle: a closed border reads as a frame around the
   whole picture, four corners read as somewhere to aim. */
.hs-scanner-frame {
    position: relative; width: 190px; height: 190px;
    border-radius: var(--r-lg);
}
.hs-scanner-corner {
    position: absolute; width: 28px; height: 28px;
    border: 3px solid var(--paper);
}
.hs-scanner-corner.tl { top: 0; left: 0; border-right: 0; border-bottom: 0; border-top-left-radius: var(--r-lg); }
.hs-scanner-corner.tr { top: 0; right: 0; border-left: 0; border-bottom: 0; border-top-right-radius: var(--r-lg); }
.hs-scanner-corner.bl { bottom: 0; left: 0; border-right: 0; border-top: 0; border-bottom-left-radius: var(--r-lg); }
.hs-scanner-corner.br { bottom: 0; right: 0; border-left: 0; border-top: 0; border-bottom-right-radius: var(--r-lg); }
/* Decoded. The brackets turn brand, and that is the whole confirmation the
   clerk gets before the overlay tears down — it has to be unmissable. */
.hs-scanner-frame.found .hs-scanner-corner { border-color: var(--brand); }
.hs-scanner-foot {
    position: absolute; left: 0; right: 0; bottom: 0;
    display: flex; align-items: center; justify-content: center;
    gap: var(--s4); flex-wrap: wrap;
    padding: var(--s5);
    background: linear-gradient(to top, rgba(0,0,0,.75), transparent);
}
.hs-scanner-copy { color: var(--paper); font-size: var(--t-md); margin: 0; }
.hs-scanner-countwrap { color: var(--paper); font-size: var(--t-sm); opacity: .8; }
/* 44px: this is the only way out of a full-screen modal on a touch device. */
.hs-scanner-cancel { min-height: 44px; padding: 0 var(--s6); }

/* ── Kiosk stage (S1, Home.razor) ─────────────────────────────────────────────
   The single-stage design (Kiosk-1/2 in the design reference) had markup and
   behaviour but no CSS backing before this — every class below rendered on
   bare browser defaults. Centred column, not full-width: a kiosk is read from
   arm's length, and a line that stretches wall to wall is not that. */
.hs-stage, .hs-screen, .hs-done {
    max-width: 620px; margin: var(--s8) auto 0; padding: 0 var(--s5);
}
/* The idle stage (Kiosk-1 in the design reference) is the one screen that is
   fully centred — eyebrow, title, scan target, copy, actions and the status
   line all sit on the column's centre line. The screens after it (MSO/OTP/
   PIN/Reason entry) stay left-aligned: they are forms, and a form reads
   left-to-right, not centre-out. */
.hs-stage { text-align: center; }
/* .hsl-logo is display:block with margin:2px 0 (hsl-brand.css), which ignores
   a centred parent's text-align — same fix as .hs-signin/.hs-landing-head. */
.hs-stage .hsl-logo { height: 56px; margin: 0 auto var(--s4); }
.hs-stage-eyebrow {
    font-family: var(--f-mono); font-size: var(--t-2xs); font-weight: var(--w-semi);
    text-transform: uppercase; letter-spacing: .1em; color: var(--ink-3);
    margin: 0 0 var(--s2);
}
.hs-stage-title, .hs-screen-title, .hs-done-title {
    font-family: var(--f-disp); font-weight: var(--w-bold); font-size: var(--t-3xl);
    line-height: var(--lh-tight); color: var(--ink); margin: 0 0 var(--s3);
}
.hs-stage-body, .hs-screen-hint {
    font-size: var(--t-md); color: var(--ink-2); line-height: var(--lh-base);
    max-width: 46ch; margin: 0 0 var(--s5);
}
.hs-stage .hs-stage-body { margin-left: auto; margin-right: auto; }
.hs-stage-actions { display: flex; flex-wrap: wrap; gap: var(--s3); margin-bottom: var(--s5); }
.hs-stage .hs-stage-actions { justify-content: center; }
.hs-actions-start { justify-content: flex-start; }
.hs-btn-stage {
    min-height: 56px; padding: 0 var(--s6); font-size: var(--t-md); font-weight: var(--w-semi);
    border-radius: var(--r-md);
}

/* The idle-stage scan target — a promise the browser has already confirmed it
   can keep (Home.razor draws this only once Camera.Availability says so). */
.hs-scan-target {
    position: relative; width: 190px; height: 190px;
    display: flex; align-items: center; justify-content: center;
    margin: 0 auto var(--s5);
    border: 2px dashed var(--brand-line); border-radius: var(--r-xl);
    background: var(--brand-soft); color: var(--brand);
}
/* The camera-bump notch on the frame's top edge (Kiosk-1). Decorative only —
   a real element would be one more thing a screen reader has to skip. */
.hs-scan-target::before {
    content: ""; position: absolute; top: -5px; left: 50%; transform: translateX(-50%);
    width: 28px; height: 9px; border-radius: var(--r-full);
    background: var(--st-pend-bg); border: 1px solid var(--st-pend-line);
}
.hs-scan-badge {
    position: absolute; top: var(--s2); right: var(--s2);
    font-family: var(--f-mono); font-size: var(--t-2xs); font-weight: var(--w-semi);
    text-transform: uppercase; letter-spacing: .06em;
    padding: 2px var(--s2); border-radius: var(--r-full);
    background: var(--surface-3); color: var(--ink-2);
}

/* The status/settings line. Now the only connectivity tell on the kiosk — see
   MainLayout.razor, which no longer draws a masthead over it. Plain text with
   a dot, not a chip: Kiosk-1 draws no border or fill around it, and a button
   wearing a full pill outline reads as louder than "everything is fine"
   should be. */
.hs-kioskbar {
    display: inline-flex; align-items: center; gap: var(--s2);
    padding: var(--s2) var(--s3); border: 1px solid transparent; border-radius: var(--r-full);
    background: transparent; font-family: var(--f-ui); font-size: var(--t-xs); color: var(--ink-2);
    cursor: pointer;
}
.hs-kioskbar:hover { background: var(--surface-2); border-color: var(--rule); }
.hs-kioskbar-dot { width: 8px; height: 8px; border-radius: var(--r-full); background: var(--i-400); flex: none; }
.hs-kioskbar-dot.online { background: var(--pos); }
.hs-kioskbar-dot.offline { background: var(--neg); }
.hs-kioskbar-edit {
    padding-left: var(--s2); margin-left: var(--s1); border-left: 1px solid var(--rule);
    color: var(--brand); font-weight: var(--w-med);
}

.hs-screen-form { margin-top: var(--s2); }
.hs-screen-cost { margin-bottom: var(--s4); }
.hs-capture-title { margin-top: var(--s6); }
.hs-capture-foot { margin-top: var(--s4); }

/* Presets — the Pareto 20% (Home.razor's own note on why these exist). */
.hs-presets { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: var(--s2); margin-bottom: var(--s5); }
.hs-preset {
    display: flex; flex-direction: column; gap: var(--s1); align-items: flex-start;
    padding: var(--s3); border: 1px solid var(--rule); border-radius: var(--r-md);
    background: var(--surface); text-align: left; cursor: pointer;
    transition: background var(--d-fast) var(--ez-out);
}
.hs-preset:hover { background: var(--surface-2); border-color: var(--brand-line); }
.hs-preset-label { font-weight: var(--w-semi); color: var(--ink); }
.hs-preset-desc { font-size: var(--t-xs); color: var(--ink-3); }
.hs-capture-line { margin-top: var(--s4); }

/* Captured-act rows. */
.hs-actrow { padding: var(--s3) 0; border-bottom: 1px solid var(--rule); }
.hs-actrow:last-child { border-bottom: none; }
.hs-actrow-main { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s3); }
.hs-actrow-tags { display: flex; gap: var(--s2); flex: none; }

/* The pre-auth gate — PENDING tone (amber), not rejected/red: the act is
   awaiting a decision, not refused. */
.hs-gate {
    margin-top: var(--s3); padding: var(--s4);
    border: 1px solid var(--st-pend-line); border-left: 3px solid var(--st-pend);
    border-radius: var(--r-md); background: var(--st-pend-bg);
}
.hs-gate-head { display: flex; gap: var(--s3); align-items: flex-start; }
.hs-gate-icon { font-size: var(--t-lg); flex: none; line-height: 1; }
.hs-gate-title { font-weight: var(--w-semi); color: var(--ink); margin: 0 0 var(--s1); }
.hs-gate-act { color: var(--ink-2); font-size: var(--t-sm); margin: 0; }
.hs-gate-plan { color: var(--ink-3); font-size: var(--t-xs); margin: var(--s2) 0 0; }
.hs-gate-next { margin-top: var(--s3); }
.hs-gate-next-title { font-weight: var(--w-semi); font-size: var(--t-sm); margin: 0 0 var(--s1); }
.hs-gate-next ol { margin: 0; padding-left: var(--s5); color: var(--ink-2); font-size: var(--t-sm); }
.hs-gate-wait { margin-top: var(--s3); font-size: var(--t-sm); color: var(--ink-2); }
.hs-gate-wait strong { color: var(--ink); display: block; margin-bottom: var(--s1); }

/* Done screen — full stop, centred. Accent green is the sanctioned financial
   confirmation; .neutral is the closed-with-nothing-billed case. */
.hs-done { text-align: center; padding-top: var(--s10); }
.hs-done-mark {
    width: 64px; height: 64px; margin: 0 auto var(--s4);
    display: flex; align-items: center; justify-content: center;
    border-radius: var(--r-full); background: var(--accent); color: var(--on-accent);
    font-size: var(--t-2xl); font-weight: var(--w-bold);
}
.hs-done-mark.neutral { background: var(--surface-3); color: var(--ink-2); }
.hs-done-amount { font-family: var(--f-mono); font-size: var(--t-2xl); font-weight: var(--w-bold); color: var(--ink); margin: 0 0 var(--s2); }
.hs-done-sub { color: var(--ink-2); margin: 0 0 var(--s2); }
.hs-done-ref { color: var(--ink-3); font-size: var(--t-sm); margin: 0 0 var(--s5); }
.hs-done .hs-stage-actions { justify-content: center; }

.hs-countdown { max-width: 280px; margin: 0 auto var(--s5); }
.hs-countdown-track { height: 4px; border-radius: var(--r-full); background: var(--rule); overflow: hidden; margin-bottom: var(--s2); }
.hs-countdown-fill { height: 100%; background: var(--brand); transition: width var(--d-base) linear; }
.hs-countdown-label { font-size: var(--t-xs); color: var(--ink-3); margin: 0; }

.hs-stage-note { margin-bottom: var(--s4); }

/* A plain vertical list of rows, each with a trailing action — the kiosk's
   offline queue sheet is the first user. Named generically on purpose: this is
   the THIRD list-ish pattern in this file, after .hs-bell-* and .hs-picker-*,
   and the three differ only in padding and hover. They should converge here
   rather than a fourth being added the next time something needs a list. */
.hs-rowlist { list-style: none; margin: 0; padding: 0; }
.hs-rowlist-item {
    display: flex; gap: var(--s3); align-items: center; justify-content: space-between;
    padding: var(--s3) var(--pad-card); border-bottom: 1px solid var(--rule);
}
.hs-rowlist-item:last-child { border-bottom: none; }

.hs-bell-list { list-style: none; margin: 0; padding: 0; overflow-y: auto; }
.hs-bell-item {
    display: flex; gap: var(--s3); align-items: flex-start;
    padding: var(--s3) var(--s4); border-bottom: 1px solid var(--rule);
    cursor: pointer;
}
.hs-bell-item:last-child { border-bottom: none; }
.hs-bell-item:hover { background: var(--brand-soft); }
.hs-bell-item.unread { box-shadow: inset 3px 0 0 var(--brand); }
.hs-bell-item.unread .hs-bell-subject { font-weight: var(--w-semi); }
.hs-bell-body { min-width: 0; }
.hs-bell-subject { font-size: var(--t-sm); }
.hs-bell-meta { font-size: var(--t-2xs); margin-top: 2px; }
.hs-bell-foot {
    display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
    padding: var(--s3) var(--s4); border-top: 1px solid var(--rule); background: var(--surface-2);
}

/* Kiosk/Member have no masthead (Kiosk-1/dc.html:826 draw no top chrome — see
   the doc comments on each app's MainLayout.razor), so .hs-bell cannot sit in
   a masthead's right cluster the way it does on the workbench. Fixed, not
   absolute: it has to stay put regardless of scroll on a long phone-portal
   page. Below .hs-scanner's z-index (200) on purpose — the camera overlay
   covers it during a scan, which is correct; above ordinary content (the
   masthead's own .hs-bell-panel is z-index 60, so 55 sits just under it,
   consistent rather than fighting for the same layer). */
.hs-bell-corner { position: fixed; top: var(--s4); right: var(--s4); z-index: 55; }

/* Kiosk-only: the corner holds the LangToggle alongside the bell, since this
   shell has no masthead to put either in otherwise (see MainLayout.razor). */
.hs-corner-cluster { display: flex; align-items: center; gap: var(--s3); }

/* .hs-app .hs-chip's 44px touch floor (below) is correct for a repeatedly-
   tapped control (a filter, an act preset) on a gloved-thumb clinic tablet —
   but the corner's FR|EN switch is an occasional preference, tapped once
   per shift at most, and at 44px it visually dwarfed the bell beside it
   (product-owner feedback, live review). ~30% down from 44px, landing on
   30px rather than a rounder number so it still clears the 24px AA-relaxed
   floor (WCAG 2.5.5) with margin, just not the 44px "AAA / gloved thumb"
   one this component doesn't need. */
.hs-corner-cluster .hs-lang-toggle { gap: 6px; }
.hs-corner-cluster .hs-lang-toggle .hs-chip { min-height: 30px; padding: 0 10px; font-size: var(--t-2xs); }

/* Cold-boot loading screen (Kiosk + Member index.html — plain HTML, not
   Razor, since it renders before Blazor exists to render anything). Fires on
   every fresh visit AND every LangToggle switch (CultureSwitcher force-
   reloads so the resolved culture is set before RunAsync — see its own doc
   comment) — worth making legible rather than a bare "Loading…" line, since
   a clerk mid-shift sees it every time they change language. The bar's
   width is driven by boot-progress.js against Blazor's own
   loadBootResource — real fetch progress, not a fake timer. */
.hs-boot {
    display: flex; flex-direction: column; align-items: center; gap: var(--s4);
    height: 100vh; justify-content: center; padding: 0 var(--s5);
    font-family: system-ui, sans-serif;
}
.hs-boot-logo { width: 96px; height: auto; opacity: .9; }
.hs-boot-track {
    width: min(280px, 70vw); height: 6px; border-radius: var(--r-full);
    background: var(--surface-2); overflow: hidden;
}
.hs-boot-bar {
    height: 100%; width: 5%; border-radius: var(--r-full); background: var(--brand);
    transition: width .25s var(--ez-out);
}
.hs-boot-row {
    display: flex; align-items: center; gap: var(--s3);
    font-size: var(--t-sm); color: var(--ink-2); min-height: 1.4em;
}
.hs-boot-row #hs-boot-pct { font-variant-numeric: tabular-nums; color: var(--ink-3); }

@media (max-width: 560px) {
    /* On a phone the panel is the screen — a 400px dropdown anchored to a bell
       in the corner is unusable at that width. */
    .hs-bell-panel {
        position: fixed; inset: auto var(--s3) var(--s3) var(--s3);
        width: auto; max-height: 70vh;
    }
    /* ── Phone tier: workbench masthead ───────────────────────────────────────
       The H+ mark keeps the product name next to the hamburger; the wordmark
       is already gone at the 820px tier. */
    .hs-mast { padding: var(--s2) var(--s3); gap: var(--s2); }
    /* ── Phone tier: member portal ────────────────────────────────────────────
       The tabbar is fixed; the narrow column is the one main that lives above
       it, so the existing bottom padding is enforced there and only there. */
    .hs-main-narrow { padding-bottom: 76px; }
    .hs-phone-actions { gap: var(--s2); }
    /* ── Phone tier: kiosk hero ───────────────────────────────────────────────
       The 640px card is the whole screen on a phone. Padding steps down, the
       scan target shrinks from the design's 190px tablet geometry, and the two
       large buttons stop sitting side by side — they are the two things a clerk
       does at the front desk, each thumb-tall and full-width. */
    .hs-kiosk-hero { padding: var(--s6) var(--s4); }
    .hs-kiosk-scan { width: 150px; height: 150px; }
    .hs-kiosk-actions { flex-direction: column; align-items: stretch; width: 100%; }
    .hs-kiosk-actions .hs-btn { width: 100%; }
    /* ── Phone tier: toasts ───────────────────────────────────────────────────
       Full-width and pinned to the bottom — above the tabbar where one exists
       (76px), at the very edge otherwise. */
    .hs-toast-host { right: var(--s3); left: var(--s3); bottom: var(--s3); max-width: none; }
    .hs-main-narrow ~ .hs-toast-host,
    .hs-app .hs-toast-host { bottom: calc(76px + var(--s3)); }
}

/* ── App shell for the two installable apps ──────────────────────────────────
   The workbench uses .hs-shell (a sidebar); a phone and a clinic tablet get an
   app bar instead. These classes were referenced by both apps' layouts and
   defined nowhere, so the shells were rendering unstyled. */
/* The grid wash (html/body above) is a workbench page-canvas treatment; the
   kiosk and member-portal stage sit on plain paper in the design reference
   (Kiosk-1/2, Foundation), so .hs-app covers it with an opaque background
   rather than the wash showing through from body. */
.hs-app { min-height: 100vh; display: flex; flex-direction: column; background: var(--paper); }

.hs-appbar {
    display: flex; align-items: center; gap: var(--s4);
    padding: var(--s3) var(--s5);
    background: var(--surface-3); border-bottom: 1px solid var(--rule);
    position: sticky; top: 0; z-index: 20;
}
.hs-appbar .hsl-logo { height: 26px; flex: none; }
.hs-appbar-title {
    font-family: var(--f-disp); font-weight: var(--w-semi); font-size: var(--t-md);
    color: var(--brand-lo);
}
.hs-appbar-actions { margin-left: auto; display: flex; align-items: center; gap: var(--s3); }
.hs-appbar a, .hs-appbar .nav-link {
    color: var(--ink-2); font-size: var(--t-sm); font-weight: var(--w-med);
    padding: var(--s2) var(--s3); border-radius: var(--r-md);
}
.hs-appbar a:hover { background: var(--brand-soft); color: var(--brand-lo); text-decoration: none; }
.hs-appbar a.active { background: var(--brand); color: var(--on-brand); }

/* One column, thumb-reachable. A member's cover is read top to bottom; there is
   nothing to put beside it. */
.hs-main-narrow { max-width: 640px; margin: 0 auto; width: 100%; }

@media (max-width: 560px) {
    .hs-appbar { padding: var(--s2) var(--s3); gap: var(--s3); }
    .hs-main { padding: var(--s4) var(--s3); }
    /* The 44px touch floor reaches the workbench on a phone (UI-DESIGN-SYSTEM-
       AUDIT §1 density seam). .hs-app owns it for the PWAs; the workbench's
       canvas is .hs-main, so it gets the same floor at the phone tier. */
    .hs-main .hs-btn { min-height: 44px; padding: 0 var(--s5); }
    .hs-main .hs-chip { min-height: 44px; }
    .hs-main .hs-input, .hs-main .hs-select, .hs-main .hs-textarea { min-height: 44px; }
}
/* Touch floors for the installable apps. Density decides how much fits; a
   finger is the same size either way, so these are minimums layered on top of
   the compact tokens rather than a competing density. */
.hs-app .hs-btn { min-height: 44px; padding: 0 var(--s5); font-size: var(--t-md); }
.hs-app .hs-input, .hs-app .hs-select, .hs-app .hs-textarea { min-height: 44px; font-size: var(--t-md); }
/* 44, not 40. This sat inside the block that puts a 44px floor under everything
   else and quietly undercut it by four pixels — which is the sort of thing that
   survives review precisely because it is nearly right. The design states ≥44px
   for kiosk and mobile as non-negotiable (dc.html:274), and a chip on a clinic
   tablet is a filter someone taps with a gloved thumb. */
.hs-app .hs-chip { min-height: 44px; }
.hs-app .hs-table tbody td { padding: var(--s3) var(--s4); }

/* ── Landing page (/welcome) ──────────────────────────────────────────────────
   The front door, and the only page in the workbench an anonymous visitor sees.
   Three destinations for three populations, because "the platform" means
   something different to staff, to a member and to a provider.

   Compact still applies: the cards are generous relative to a data grid, not
   relative to a marketing page. This is a signpost, not a brochure. */
.hs-plain { min-height: 100vh; padding: var(--s6); }
/* Content sits above the fixed aurora layer (z-index 0). */
.hs-plain { position: relative; z-index: 1; }

/* ── Error page ────────────────────────────────────────────────────────────
   The server error page (Error.razor), rendered on EmptyLayout. Centred and
   quiet, with the brand mark and a working way back — an error is a dead end
   only if it offers none. */
.hs-error {
    max-width: 560px; margin: 10vh auto; padding: var(--s8) var(--s6);
    background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-lg);
    box-shadow: var(--e-2); text-align: center;
}
.hs-error-card h1 { font-family: var(--f-disp); font-size: var(--t-2xl); margin: var(--s4) 0 var(--s2); }
.hs-error-mark {
    width: 52px; height: 52px; margin: 0 auto; border-radius: var(--r-md);
    background: var(--brand); color: var(--on-brand);
    display: grid; place-items: center;
    font-family: var(--f-disp); font-weight: var(--w-black); font-size: var(--t-lg);
    letter-spacing: -1px; box-shadow: var(--e-2);
}
.hs-error .hs-btn-row { justify-content: center; }

.hs-landing { max-width: 980px; margin: 0 auto; padding: 6vh 0 var(--s10); }

.hs-landing-head { text-align: center; margin-bottom: var(--s10); }
/* margin-inline:auto, not text-align. hsl-brand.css makes .hsl-logo a BLOCK
   with `margin: 2px 0 …`, and a block box ignores the parent's text-align — so
   the mark sat hard left under a centred heading. */
.hs-landing-head .hsl-logo { height: 44px; margin: 0 auto var(--s6); }
.hs-landing-head h1 {
    font-family: var(--f-disp); font-size: var(--t-2xl);
    line-height: var(--lh-tight); margin: 0 0 var(--s3);
}
.hs-landing-head p {
    max-width: 46ch; margin: 0 auto; color: var(--ink-2);
    font-size: var(--t-md); line-height: var(--lh-base);
}

/* auto-fit rather than a fixed count: three cards on a desk, one per row on a
   phone, and no media query to keep in step with the card width. */
.hs-landing-grid {
    display: grid; gap: var(--s4);
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}

.hs-landing-card {
    display: flex; flex-direction: column; gap: var(--s2);
    padding: var(--s6); text-decoration: none; color: inherit;
    background: var(--surface); border: 1px solid var(--rule);
    border-radius: var(--r-lg); box-shadow: var(--e-1);
    transition: border-color var(--d-fast) var(--ez-out), box-shadow var(--d-fast) var(--ez-out), transform var(--d-fast) var(--ez-out);
}
.hs-landing-card:hover {
    border-color: var(--brand-line); box-shadow: var(--e-2); transform: translateY(-1px);
}
/* The accessibility floor. A card is a link; a keyboard user must be able to
   see which one they are on. */
.hs-landing-card:focus-visible {
    outline: 2px solid var(--brand); outline-offset: 2px;
}

.hs-landing-card-primary { border-color: var(--brand-line); background: var(--brand-soft); }

.hs-landing-card-kicker {
    font-size: var(--t-xs); font-weight: var(--w-semi); letter-spacing: .06em;
    text-transform: uppercase; color: var(--brand);
}
.hs-landing-card-title {
    font-family: var(--f-disp); font-size: var(--t-lg); line-height: var(--lh-snug);
}
.hs-landing-card-desc {
    color: var(--ink-2); font-size: var(--t-sm); line-height: var(--lh-base);
    flex: 1;   /* pushes the call to action to the foot, so all three align */
}
.hs-landing-card-go {
    margin-top: var(--s2); font-size: var(--t-sm);
    font-weight: var(--w-semi); color: var(--brand);
}

.hs-landing-foot {
    margin-top: var(--s10); padding-top: var(--s5);
    border-top: 1px solid var(--rule);
    display: flex; justify-content: space-between; align-items: center;
    gap: var(--s4); flex-wrap: wrap; font-size: var(--t-sm);
}
.hs-landing-foot a { color: var(--brand); font-weight: var(--w-semi); text-decoration: none; }
.hs-landing-foot a:hover { text-decoration: underline; }

/* Blazor's enhanced navigation moves focus to the page heading for screen-reader
   users, and Chrome then paints its default focus ring across it — a black
   rectangle the full width of the content, on a page nobody has interacted with.
   The focus MOVE is correct and must stay: removing tabindex would silently cost
   a screen-reader user their landmark. Only the ring is suppressed, and only for
   a heading, which is not an interactive control a keyboard user tabs to. */
h1[tabindex="-1"]:focus,
h1[tabindex="-1"]:focus-visible,
.hs-landing-head h1:focus,
.hs-landing-head h1:focus-visible {
    outline: none;
}
