/* ══════════════════════════════════════════════════════════════════════════
   T119 Nocturne foundation (phase 1). Ported from Creed's reference token
   sheet (docs/nocturne-tokens.reference.css on docs/t119-nocturne-design-spec,
   @ 484915e) - that file is the design deliverable and load-bearing for
   values/cascade; names here differ in places (--text-* vs its --t-*) but the
   numbers and structure match it, not re-derived independently. Corrections
   folded in before any screen consumes this, because retrofitting them once
   27 screens are drawn is the expensive path (D-Q1/D-Q3/D-Q4 §5a; R-Q6/R-Q8/
   R-Q10 + the control-border defect §5c/§5d):
     - light-mode accent: fixed oklch(0.48 0.125 <hue>), not the mirrored ramp
       (which fails contrast - measures 1.84:1 at its own pressed state)
     - 44px on anything tappable (.btn/.input/.seg-opt/.btn-icon)
     - type floor: nothing interactive/data-bearing below 13px, nothing at
       all below 11px
     - semantic quartet dark values corrected (Creed's own first answer was
       wrong - the shipped inks are light-page inks, 2.02-2.72:1 on this
       dark ground); light values unchanged and still read by DocxReportGenerator
     - --color-control-border: --color-divider bounds .input/.btn-secondary
       at 1.55:1 dark / 1.35:1 light, which fails 1.4.11's 3:1 for an operable
       control's boundary - dividers between rows keep --color-divider
     - every color-mix() gets a flat value declared immediately before it
       (Oscar's determination, §5d): an unsupported function drops the whole
       declaration, so without the fallback a pre-2023 browser inherits
       whatever came before - a contrast failure on muted text/control
       borders, not a cosmetic one. oklch() itself is NOT given this
       treatment: it is not load-bearing (Nocturne's own component layer
       ships flat hex), and if a floor ever excludes it, the nine accent
       ramp steps get computed server-side from the stored hue instead -
       same one-number branding model, no client-side OKLCH
     - Inter self-hosted (static/fonts/), not system-ui: Segoe UI has no 500
       weight and "headings at 500, never heavier" is Nocturne's one hard
       type rule

   This is a wholesale replacement, not a merge (§2.1: "do not translate
   names - one vocabulary, or we get both"), and the component layer below
   reuses Nocturne's own class names (.btn/.card/...), which the EXISTING
   app also happens to use for unrelated, not-yet-migrated styling further
   down this file. That collision is deliberate and temporary: cascade order
   keeps the legacy rules winning (they appear later in the file), so an
   unmigrated screen keeps its EXISTING STRUCTURE - its own layout, spacing,
   type sizes, all untouched - but adopts the new PALETTE, because the
   legacy rules' own colour tokens (--accent/--border/--surface/--ink/...)
   are bridged onto the new --color-* system below rather than left
   undeclared (Creed's fidelity review, F1 - an undeclared var() is invalid
   at computed-value time, not "keeps the old value"). A screen does not
   change appearance until its own T119 phase migrates it and removes its
   legacy rule ONLY in the sense that its structure and non-colour values
   hold; its colours already do change today, on purpose, because a light
   card floating on this file's new dark --color-bg would be broken
   regardless of whether anyone had "migrated" it yet.
   ══════════════════════════════════════════════════════════════════════ */
/* D-3a-6 (spec §7k): 3a's two-up branding preview forces one pane to each appearance regardless
   of the viewer's own setting, so it needs this whole dark-appearance token set on a NON-root
   element. Extending the selector list rather than copying the declarations - every value below
   that reads var(--brand-hue) (the whole accent ramp) re-derives PER ELEMENT against whichever
   --brand-hue is in scope, so a pane needs nothing but its own --brand-hue and this selector to
   render the entire dark palette correctly; a second copy of this block would be the exact
   duplication FrontendSourceGuardTest's lightAndAutoAppearanceBlocksStayDeclarationIdentical
   exists to catch on the light/auto pair, one file section down. */
:root, .theme-preview[data-appearance="dark"] {
    color-scheme: dark;

    --color-bg: #161826;
    --color-surface: #232532;
    --color-text: #e9e9ed;
    /* Every color-mix() is preceded by a flat value (Oscar's determination, spec §5d
       R-Q8): an unsupported function makes the DECLARATION invalid and it is dropped,
       so without this a 2023-or-older browser inherits whatever came before it - a
       contrast failure for muted text and control borders, not a cosmetic one. Windows
       8.1 caps Chrome at 109 (color-mix needs 111) and an iPhone 7 caps Safari below
       16.2; those two groups are the ones we control least. Keep each pair together -
       splitting them silently removes the fallback and it'll look fine on your machine. */
    --color-divider: #383946;
    --color-divider: color-mix(in srgb, var(--color-text) 16%, transparent);
    --color-text-muted: #aaaab1;                                        /* 7.64:1 */
    --color-text-muted: color-mix(in srgb, var(--color-text) 70%, transparent);

    --color-neutral-100: #f3f5fe;
    --color-neutral-200: #e4e7f5;
    --color-neutral-300: #cfd3e5;
    --color-neutral-400: #b2b6ca;
    --color-neutral-500: #9397ab;
    --color-neutral-600: #75798c;
    --color-neutral-700: #595d6c;
    --color-neutral-800: #3f424d;
    --color-neutral-900: #292b31;

    /* Per-supplier branding is one number (§2.2): every accent step below is
       generated from --brand-hue via oklch(), so switching a supplier's
       colour is switching this one custom property. Phase 2 wires this
       from the organisation; this default is the product's own Pro-accent
       hue (289 - Beacon RHS in the two live suppliers' worked examples).
       oklch() is not load-bearing here (spec §5d R-Q8/R-Q8 CLOSED): Nocturne's
       own component layer ships flat hex and needs color-mix(), which IS
       load-bearing and gets the fallback treatment above/below - but the
       branding ramp itself has a documented escape hatch if a browser floor
       ever excludes oklch(): compute the same nine steps server-side from the
       stored hue and inject them as plain hex custom properties instead. Same
       design, same one stored integer, no client-side OKLCH required. */
    --color-accent: oklch(0.660 0.125 var(--brand-hue));
    --color-accent-hover: var(--color-accent-400);
    --color-accent-pressed: var(--color-accent-300);
    --brand-hue: 289;
    --color-accent-100: oklch(0.975 0.020 var(--brand-hue));
    --color-accent-200: oklch(0.925 0.045 var(--brand-hue));
    --color-accent-300: oklch(0.860 0.090 var(--brand-hue));
    --color-accent-400: oklch(0.775 0.115 var(--brand-hue));
    --color-accent-500: oklch(0.660 0.125 var(--brand-hue));
    --color-accent-600: oklch(0.565 0.110 var(--brand-hue));
    --color-accent-700: oklch(0.460 0.090 var(--brand-hue));
    --color-accent-800: oklch(0.360 0.070 var(--brand-hue));
    --color-accent-900: oklch(0.280 0.055 var(--brand-hue));
    /* Nocturne's own stand-in "second accent" - kept only so both names
       resolve; the design system's own guidance is to treat it as one role
       with --color-accent, never a genuine second hue. */
    --color-accent-2-100: var(--color-accent-100);
    --color-accent-2-200: var(--color-accent-200);
    --color-accent-2-300: var(--color-accent-300);
    --color-accent-2-400: var(--color-accent-400);
    --color-accent-2-500: var(--color-accent-500);
    --color-accent-2-600: var(--color-accent-600);
    --color-accent-2-700: var(--color-accent-700);
    --color-accent-2-800: var(--color-accent-800);
    --color-accent-2-900: var(--color-accent-900);

    /* R-Q10: self-hosted Inter (static/fonts/), Latin subset, variable, woff2,
       font-display: swap - system-ui behind it never actually renders except
       during that swap. Nocturne's one hard type rule is headings at weight
       500, never heavier; Segoe UI (the Windows system stack) has 400 and 600
       but no 500, so a system-font heading would drop, jump, or get
       browser-synthesised - the one thing this rule asks not to happen. */
    --font-heading: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
    --font-heading-weight: 500;
    --font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;

    --space-1: 2.8px;
    --space-2: 5.6px;
    --space-3: 8.4px;
    --space-4: 11.2px;
    --space-6: 16.8px;
    --space-8: 22.4px;

    --radius-sm: 4px;
    --radius-md: 8px;
    --radius-lg: 14px;
    --radius-pill: 999px;

    --shadow-sm: 0 0 0 1px var(--color-neutral-800);
    --shadow-md: 0 0 0 1px var(--color-neutral-700), 0 6px 18px rgba(0, 0, 0, 0.55);
    --shadow-lg: 0 0 0 1px var(--color-neutral-500), 0 16px 40px rgba(0, 0, 0, 0.65);

    /* WCAG 2.2 AA 1.4.11, found implementing the color-mix fallbacks above (spec §5d
       "NEW"): Nocturne bounds .input/select/textarea/.btn-secondary with
       --color-divider, which is only 1.55:1 on this ground (1.58:1 on --color-surface) -
       so as shipped, every form field and secondary button has a boundary a low-vision
       user can't see. Not the same defect as the light-mode accent, and present in BOTH
       appearances. Fix is a dedicated token for boundaries that ARE the component,
       --color-divider stays exactly as-is for decoration between rows (1.4.11 doesn't
       apply there, and thickening every hairline would fight the whole system's
       quietness). Verified: 3.57:1 vs bg, 3.43:1 vs surface. */
    --color-control-border: #6f707a;
    --color-control-border: color-mix(in srgb, var(--color-text) 42%, transparent);

    /* D-Q4: nothing interactive or data-bearing below 13px, nothing at all
       below 11px. Body takes Nocturne's own 15px default unchanged. */
    --text-chrome: 11px;
    --text-data: 13px;
    --text-interactive: 14px;
    --text-body: 15px;
    --text-subheading: 17px;
    --text-heading: 19px;
    --text-title: 27px;

    /* Semantic quartet - not themeable, no equivalent in Nocturne (spec §5c R-Q6:
       Creed's own first answer here was wrong - the shipped inks are dark-page inks
       and measure 2.02-2.72:1 on Nocturne's dark ground). Dark-mode values below are
       the corrected, swept set: 9.57-11.89:1 on --color-bg, chips sit at Nocturne's own
       elevation step (surface vs bg = 1.16:1) so a tag reads as a raised surface, not a
       flood of colour - the mono palette's "never colour alone" rule still applies, so
       every consumer of these also carries a glyph or distinct text. */
    --error: #ffa8a8; --error-bg: #42201f;
    --ok: #7fe9b0; --ok-bg: #1b3a2a;
    --warn: #fbc76b; --warn-bg: #3d2f14;
    --info: #9ecbff; --info-bg: #1b2c50;
    --neutral: #c3c7d4; --neutral-bg: #292b31;
    --sent-back: #fdc498; --sent-back-bg: #3f2716;

    /* F1 (Creed's fidelity review): this commit deleted the ~20 tokens the 33
       not-yet-migrated screens still read. Cascade order keeps their rules winning
       (see the file-level banner above), but a winning declaration whose var() names
       an UNDECLARED custom property is invalid at computed-value time - not "keeps the
       old value", it resolves to unset (initial, for anything non-inherited). Rendered
       proof: unmigrated input/select/textarea came out border:0 none, background
       transparent (T124's 3:1 boundary silently gone), .card lost its padding/border/
       background entirely, .btn-row's gap collapsed to 0.
       Colour aliases onto the new system, because body already paints --color-bg/
       --color-text and an unmigrated light card floating on a dark page is broken
       regardless. Spacing/radius/type do NOT migrate - they keep their exact old
       literal values, because swapping in the 0.7x space scale or the new type ramp
       would re-lay-out 33 screens nobody has reviewed for it. Retire a line here only
       when the last legacy rule reading it has migrated to the --color-*/--space-*
       equivalent and been deleted. */
    --ink: var(--color-text);
    --surface: var(--color-surface);
    --border: var(--color-divider);
    --control-border: var(--color-control-border);
    --muted: var(--color-text-muted);
    --accent: var(--color-accent);
    /* The ink that sits ON an --accent fill, so it has to contrast with the ACCENT, not with
       the page. --color-text is the page ink and moves between appearances the same way the
       accent does, which left the RESTING .btn at 2.39:1 dark / 2.08:1 light - 360/360 hues
       failing WCAG 2.2 AA 1.4.3 in BOTH appearances. Regressed in T119 (#22), which swapped a
       fixed #1F2328 for var(--color-text) at the point --accent stopped being a pale fill.
       --color-bg is the same inversion #59 gives .btn:hover: an accent fill under
       page-background ink is the spec's locked accent-on-background floor read the other way
       round, and contrast is symmetric, so it inherits that guarantee rather than needing a
       new one - 5.31:1 dark / 5.06:1 light, 0/360. It also keeps rest and hover on one ink. */
    --accent-ink: var(--color-bg);
    /* Not arbitrary picks: the accent ramp MIRRORS between appearances, so step 900 is
       the pale step in light and the deep step in dark - --tint: accent-900 holds the
       same role (a quiet tinted fill) in both, and --accent-dark: accent-300 is the ink
       that sits legibly on that fill in both. Swept all 360 hues WITHIN each appearance
       (both tokens read in the SAME theme, which is the only comparison that corresponds
       to anything actually rendered): 9.19:1 dark / 6.27:1 light worst case. (Corrected
       during Creed's #59 review - the figure this replaced, 7.82:1 dark / 13.81:1 light,
       came from comparing accent-900 ACROSS themes rather than accent-300 against
       accent-900 within one theme; verified independently, twice, before changing it.) */
    --accent-dark: var(--color-accent-300);
    --tint: var(--color-accent-900);
    --radius: 10px;
    --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s7: 48px;
    --t-xs: 12px; --t-sm: 14px; --t-md: 16px; --t-lg: 20px;
}

/* Self-hosted per R-Q10 (spec §5c) - see static/fonts/README.md for provenance and how
   to update. font-weight is a RANGE because this is a variable font: the same file
   serves both 400 (body) and 500 (heading, Nocturne's ceiling) via the wght axis. */
@font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 400 500;
    font-display: swap;
    src: url("/fonts/inter-latin-variable-260c81a4759baf163c025001c4f27872.woff2") format("woff2-variations");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
        U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
        U+FEFF, U+FFFD;
}

/* Light appearance (§2.3). Nocturne itself ships no light mode - these are
   the values recovered from the handoff's own live-preview script (canvas
   JS, not styles.css), written here as a static block so there is never a
   flash of the wrong theme while a stylesheet loads.
   --color-accent is the one value NOT mirrored from the dark ramp (D-Q1):
   the mirror inverts which step reads as "one step past the base" and
   produces a 1.84:1 pressed state, so the accent instead takes a fixed
   lightness, hue still free, verified >=4.5:1 (text) and >=3:1 (the focus
   ring) at every hue an admin can pick. Everything else mirrors dark step
   N <-> step (1000-N) at the ground's own hue. */
/* D-3a-6 (spec §7k): same reasoning as the dark block's own comment above - extending the
   selector list, never copying the declarations, so 3a's forced-light preview pane gets this
   whole token set re-derived against its own --brand-hue for free. */
:root[data-appearance="light"], .theme-preview[data-appearance="light"] {
    color-scheme: light;
    /* THE FIX (D-Q1). Not the mirrored ramp: on a light ground the mirrored 600/700
       come out LIGHTER than the base and measure 1.84:1. Fixed lightness, hue free.
       0.509 is the exact ceiling for 4.5:1 at every hue; 0.48 leaves +0.56 of headroom
       (worst case 5.06:1 at hue ~190). Hover/pressed take explicit fixed-lightness
       values rather than reading var(--color-accent-600)/-700 from the ramp below -
       that's the trap this comment exists to flag. */
    --color-accent: oklch(0.48 0.125 var(--brand-hue));
    --color-accent-hover: oklch(0.42 0.115 var(--brand-hue));
    --color-accent-pressed: oklch(0.36 0.100 var(--brand-hue));

    --color-accent-100: oklch(0.280 0.055 var(--brand-hue));
    --color-accent-200: oklch(0.360 0.070 var(--brand-hue));
    --color-accent-300: oklch(0.460 0.090 var(--brand-hue));
    --color-accent-400: oklch(0.565 0.110 var(--brand-hue));
    --color-accent-500: oklch(0.660 0.125 var(--brand-hue));
    --color-accent-600: oklch(0.775 0.115 var(--brand-hue));
    --color-accent-700: oklch(0.860 0.090 var(--brand-hue));
    --color-accent-800: oklch(0.925 0.045 var(--brand-hue));
    --color-accent-900: oklch(0.975 0.020 var(--brand-hue));

    --color-neutral-100: oklch(0.280 0.014 265);
    --color-neutral-200: oklch(0.360 0.020 265);
    --color-neutral-300: oklch(0.460 0.024 265);
    --color-neutral-400: oklch(0.565 0.026 265);
    --color-neutral-500: oklch(0.660 0.026 265);
    --color-neutral-600: oklch(0.775 0.024 265);
    --color-neutral-700: oklch(0.860 0.020 265);
    --color-neutral-800: oklch(0.925 0.016 265);
    --color-neutral-900: oklch(0.975 0.012 265);

    --color-bg: oklch(0.955 0.009 265);
    --color-surface: oklch(0.995 0.004 265);
    --color-text: oklch(0.280 0.014 265);
    --color-divider: #cdd0d7;
    --color-divider: color-mix(in srgb, var(--color-text) 16%, transparent);
    --color-text-muted: #61656c;                                        /* 5.15:1 */
    --color-text-muted: color-mix(in srgb, var(--color-text) 70%, transparent);
    --color-control-border: #7d8187;                                    /* 3.45:1 vs bg, 3.58 vs surface */
    --color-control-border: color-mix(in srgb, var(--color-text) 56%, transparent);

    --shadow-sm: 0 0 0 1px oklch(0.86 0.02 265);
    --shadow-md: 0 0 0 1px oklch(0.90 0.016 265), 0 6px 18px oklch(0.28 0.014 265 / 0.10);
    --shadow-lg: 0 0 0 1px oklch(0.86 0.02 265), 0 16px 40px oklch(0.28 0.014 265 / 0.16);

    /* T234: the legacy bridge aliases (dark block's --ink through --tint) were declared ONLY
       there, whose selector list is ":root, .theme-preview[data-appearance=\"dark\"]". The plain
       ":root" half made every OUTER page self-heal regardless of appearance (--accent's own value
       is just the token "var(--color-accent)", and var() resolves PER ELEMENT - so on the real
       root, re-evaluating it against the root's own --color-accent already gives the right
       answer, appearance included). But ".theme-preview[data-appearance=\"light\"]" was never in
       that selector list, so the light preview pane never got its OWN --accent/--surface/etc.
       declared on itself - it fell through to INHERITING the already-resolved value from the page
       root instead, frozen at whatever hue/appearance the root happened to be, never the pane's
       own forced light appearance or its own live --brand-hue. The dark pane looked fine only
       because dark's selector list happens to already cover it; light's never did. Same defect
       shape as T186 (a bridge alias reachable by one appearance and not the other), recurring on
       a NESTED forced-appearance element rather than the root T186 fixed. Declaring the identical
       aliases here (matching the dark block's list exactly) gives the light pane the same
       self-healing per-element resolution the root already had - still pure var() aliases, never
       a resolved colour (D-3a-1 stays satisfied). Verified with a real browser: before this, the
       theme-form's light-mode preview button was stuck on the platform default hue no matter what
       the picker was set to; after, it tracks live like the dark pane always did. */
    --ink: var(--color-text);
    --surface: var(--color-surface);
    --border: var(--color-divider);
    --control-border: var(--color-control-border);
    --muted: var(--color-text-muted);
    --accent: var(--color-accent);
    --accent-ink: var(--color-bg);
    --accent-dark: var(--color-accent-300);
    --tint: var(--color-accent-900);

    /* The shipped, already-WCAG-checked light semantics - unchanged, and read by
       DocxReportGenerator, so don't "tidy" these into the dark set's numbers. */
    --error: #B91C1C; --error-bg: #FEE2E2;
    --ok: #166534; --ok-bg: #DCFCE7;
    --warn: #92400E; --warn-bg: #FEF3C7;
    --info: #1E40AF; --info-bg: #DBEAFE;
    --neutral: #374151; --neutral-bg: #EEF2F6;
    --sent-back: #9A3412; --sent-back-bg: #FDE7D8;
}

/* "auto" (spec §5c R-Q9): three states, matching how the tokens are structured
   anyway. Default for a NEW account (Phase 2 concern, not this file) - honouring
   whatever the user already set at the OS level is the accessible default (some
   people run dark for photophobia/migraine, others light for astigmatism), so a new
   account never lands on the wrong one by construction. This block and the explicit
   [data-appearance="light"] block above MUST stay declaration-for-declaration
   identical (FrontendSourceGuardTest enforces this) - an explicit choice and
   auto-on-a-light-OS have to resolve to the exact same thing, and drift between two
   copies of the same 20-odd tokens is exactly the kind of thing nobody notices
   until a colour looks subtly wrong in only one of the three states. */
@media (prefers-color-scheme: light) {
    :root[data-appearance="auto"] {
        color-scheme: light;
        --color-accent: oklch(0.48 0.125 var(--brand-hue));
        --color-accent-hover: oklch(0.42 0.115 var(--brand-hue));
        --color-accent-pressed: oklch(0.36 0.100 var(--brand-hue));

        --color-accent-100: oklch(0.280 0.055 var(--brand-hue));
        --color-accent-200: oklch(0.360 0.070 var(--brand-hue));
        --color-accent-300: oklch(0.460 0.090 var(--brand-hue));
        --color-accent-400: oklch(0.565 0.110 var(--brand-hue));
        --color-accent-500: oklch(0.660 0.125 var(--brand-hue));
        --color-accent-600: oklch(0.775 0.115 var(--brand-hue));
        --color-accent-700: oklch(0.860 0.090 var(--brand-hue));
        --color-accent-800: oklch(0.925 0.045 var(--brand-hue));
        --color-accent-900: oklch(0.975 0.020 var(--brand-hue));

        --color-neutral-100: oklch(0.280 0.014 265);
        --color-neutral-200: oklch(0.360 0.020 265);
        --color-neutral-300: oklch(0.460 0.024 265);
        --color-neutral-400: oklch(0.565 0.026 265);
        --color-neutral-500: oklch(0.660 0.026 265);
        --color-neutral-600: oklch(0.775 0.024 265);
        --color-neutral-700: oklch(0.860 0.020 265);
        --color-neutral-800: oklch(0.925 0.016 265);
        --color-neutral-900: oklch(0.975 0.012 265);

        --color-bg: oklch(0.955 0.009 265);
        --color-surface: oklch(0.995 0.004 265);
        --color-text: oklch(0.280 0.014 265);
        --color-divider: #cdd0d7;
        --color-divider: color-mix(in srgb, var(--color-text) 16%, transparent);
        --color-text-muted: #61656c;
        --color-text-muted: color-mix(in srgb, var(--color-text) 70%, transparent);
        --color-control-border: #7d8187;
        --color-control-border: color-mix(in srgb, var(--color-text) 56%, transparent);

        --shadow-sm: 0 0 0 1px oklch(0.86 0.02 265);
        --shadow-md: 0 0 0 1px oklch(0.90 0.016 265), 0 6px 18px oklch(0.28 0.014 265 / 0.10);
        --shadow-lg: 0 0 0 1px oklch(0.86 0.02 265), 0 16px 40px oklch(0.28 0.014 265 / 0.16);

        /* T234: mirrors the [data-appearance="light"] block above, byte-for-byte per this file's
           own rule (FrontendSourceGuardTest) - see that block's comment for why these nine exist. */
        --ink: var(--color-text);
        --surface: var(--color-surface);
        --border: var(--color-divider);
        --control-border: var(--color-control-border);
        --muted: var(--color-text-muted);
        --accent: var(--color-accent);
        --accent-ink: var(--color-bg);
        --accent-dark: var(--color-accent-300);
        --tint: var(--color-accent-900);

        --error: #B91C1C; --error-bg: #FEE2E2;
        --ok: #166534; --ok-bg: #DCFCE7;
        --warn: #92400E; --warn-bg: #FEF3C7;
        --info: #1E40AF; --info-bg: #DBEAFE;
        --neutral: #374151; --neutral-bg: #EEF2F6;
        --sent-back: #9A3412; --sent-back-bg: #FDE7D8;
    }
}

* { box-sizing: border-box; }
body {
    font-family: var(--font-body);
    font-size: var(--text-body);
    line-height: 1.55;
    margin: 0;
    color: var(--color-text);
    background: var(--color-bg);
}
h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    line-height: 1.2;
    letter-spacing: -0.01em;
    margin: 0 0 var(--space-2);
}
/* D-Q4: page title / section heading sizes, global (h3 keeps its own legacy size
   further down - see that rule's comment). */
h1 { font-size: var(--text-title); }
h2 { font-size: var(--text-heading); margin-top: var(--space-6); }

/* Skip link (FE-06): visually hidden until focused, first focusable element in <body>. */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    background: var(--color-accent);
    color: var(--color-bg);
    padding: var(--space-2) var(--space-3);
    border-radius: 0 0 8px 0;
    z-index: 100;
}
.skip-link:focus { left: 0; }

:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
/* Non-themeable focus indicator (FE-04), kept per T119 §3: the accent ring
   above is decoration on interactive elements that also carry it as a
   branded border; this outline is the real signal and must survive any
   supplier hue, including one that lands on --color-accent's own weak end.
   --color-text is appearance-aware but never brand-hue-derived, which is
   exactly the property this rule needs. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible, .checkbox-option:focus-within {
    outline: 2px solid var(--color-text);
    outline-offset: 2px;
    border-radius: 4px;
}
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* ══════════════════════════════════════════════════════════════════════════
   Nocturne component layer (phase 1 foundation) - ported from the design
   system's styles.css with the D-Q3 (44px touch target) and D-Q4 (type
   floor) corrections applied inline. Class names match the design system
   exactly (§2.1); see the file-level note above for why that collides with
   older rules further down and why that collision is temporary and safe.
   ══════════════════════════════════════════════════════════════════════ */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    cursor: pointer; text-decoration: none;
    font-family: var(--font-heading); font-weight: var(--font-heading-weight);
    font-size: var(--text-interactive); line-height: 1.2; color: var(--color-text);
    background: transparent; border: 1px solid transparent;
    min-height: 44px; padding: 0 calc(var(--space-3) * 1.2);
    border-radius: var(--radius-md);
}
.btn svg { display: block; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary { color: var(--color-accent); border-color: var(--color-accent); }
.btn-primary:hover { background: color-mix(in srgb, var(--color-accent) 12%, transparent); }
.btn-primary:active { background: color-mix(in srgb, var(--color-accent) 22%, transparent); }
/* T124 (WCAG 2.2 AA 1.4.11): --color-control-border, not --color-divider, on every
   boundary that IS an operable control - .input/.seg/.radio/.btn-secondary below,
   same distinction that commit drew for the old vocabulary. */
.btn-secondary { border-color: var(--color-control-border); }
.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }
.btn-ghost { color: var(--color-accent); padding-inline: var(--space-2); }
.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
.btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }
/* D-Q3: 44px on anything tappable - Nocturne draws .btn-icon at 36px. */
.btn-icon { width: 44px; height: 44px; padding: 0; }
.btn-block { width: 100%; margin-top: var(--space-2); }

.field > label {
    display: block; font-size: var(--text-data); margin-bottom: 5px;
    color: var(--color-text-muted);
}
.input {
    width: 100%; min-height: 44px; padding: 6px 10px; font: inherit;
    font-size: var(--text-interactive); color: var(--color-text); caret-color: var(--color-accent);
    background: var(--color-surface);
    border: 1px solid var(--color-control-border); border-radius: var(--radius-md);
}
.input:hover { border-color: var(--color-text-muted); }
.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
textarea.input { min-height: 90px; resize: vertical; }
.radio { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: var(--text-interactive); }
.radio input, .seg-opt input {
    position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;
}
.radio .dot {
    width: 16px; height: 16px; flex: none; border-radius: 50%;
    border: 1.5px solid var(--color-control-border);
}
.radio:hover .dot { border-color: var(--color-accent); }
.radio input:checked + .dot {
    border-color: var(--color-accent); background: var(--color-accent);
    box-shadow: inset 0 0 0 4px var(--color-bg);
}
.radio input:focus-visible + .dot { outline: 2px solid var(--color-accent); outline-offset: 2px; }
/* D-4a-2 (spec §7b): a coordinator allocating blind can't load-balance, so the visitor picker is a
   list of rows (name + current load), not a bare dropdown - a dropdown option can't carry a
   second field. .radio itself is inline-flex (one pill, sized to its label); stacking it into
   full-width, selectable rows is the only thing this adds - the dot/checked/focus states are
   unchanged. */
.radio-list { display: flex; flex-direction: column; gap: var(--s2); margin: var(--s2) 0 var(--s4); }
.radio-list .radio {
    display: flex; justify-content: space-between; width: 100%; min-height: 44px;
    padding: var(--s3) var(--s4); border: 1px solid var(--control-border); border-radius: 8px;
}
/* Creed's #75 review moved these off --accent-dark/--tint, which a legacy per-org inline <style>
   then overrode with fixed values for a branded organisation - so on a branded org in dark mode the
   selected row got a cream fill on the exact screen recording the allocation decision.

   T186 DELETED THAT BLOCK. The short tokens are no longer overridden and now mirror between
   appearances like everything else, so the reason this rule was moved has gone. The long names stay
   because they are equivalent here and churn is worse than nothing - NOT because --accent-dark or
   --tint are unsafe. Do not read this as a reason to avoid them, and do not spread the pattern:
   --color-accent (border, 4.58:1 dark / 5.68:1 light on --color-surface) and --color-accent-900
   (background) are simply the explicit ramp names. */
.radio-list .radio:hover { border-color: var(--color-accent); }
.radio-list .radio:has(input:checked) { border-color: var(--color-accent); background: var(--color-accent-900); }
.radio-list .load { color: var(--muted); font-variant-numeric: tabular-nums; }
.seg {
    display: inline-flex; overflow: hidden;
    border: 1px solid var(--color-control-border); border-radius: var(--radius-md);
}
/* D-Q3: 44px on anything tappable - Nocturne draws a seg option's own box shorter than that. */
.seg-opt {
    display: inline-flex; align-items: center; gap: 6px; min-height: 44px;
    padding: 7px 12px; font-size: var(--text-data); cursor: pointer;
}
.seg-opt + .seg-opt { border-left: 1px solid var(--color-divider); }
.seg-opt:has(input:checked) { color: var(--color-accent); box-shadow: inset 0 0 0 1px var(--color-accent); }
.seg-opt:not(:has(input:checked)):hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.seg-opt:has(input:focus-visible) { outline: 2px solid var(--color-accent); outline-offset: -2px; }

.card {
    display: flex; flex-direction: column; gap: var(--space-2);
    padding: var(--space-3); border-radius: var(--radius-md); background: var(--color-surface);
}
.card-kicker { font-size: var(--text-chrome); letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-accent); }
.card-title {
    font-family: var(--font-heading); font-weight: var(--font-heading-weight);
    font-size: var(--text-subheading); line-height: 1.2;
}
.card-body { margin: 0; font-size: var(--text-data); opacity: 0.8; flex: 1; }
.card-meta {
    display: flex; align-items: center; gap: 6px; font-size: var(--text-chrome);
    color: var(--color-text-muted);
}
.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }
.elev-lg { box-shadow: var(--shadow-lg); }

/* D-Q4: a status tag is data, not chrome - Nocturne draws .tag at 11px, this
   app's tags are frequently the most important word on a card. Pill radius per
   the reference token sheet (spec: docs/nocturne-tokens.reference.css). */
.tag {
    display: inline-flex; align-items: center; font-size: var(--text-data);
    letter-spacing: 0.02em; padding: 3px 10px;
    border-radius: var(--radius-pill);
}
.tag-accent { background: var(--color-accent-800); color: var(--color-accent-100); }
.tag-accent-2 { background: var(--color-accent-2-800); color: var(--color-accent-2-100); }
.tag-neutral { background: var(--color-neutral-800); color: var(--color-neutral-100); }
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }
/* Semantic tag modifiers - the mono palette carries no due/overdue/approved hues of
   its own (Q6), so these read off the semantic quartet, never off branding: an org's
   hue must never move what "overdue" means. A glyph or distinct text always
   accompanies colour here too (never colour alone). */
.tag-ok { color: var(--ok); background: var(--ok-bg); }
.tag-warn { color: var(--warn); background: var(--warn-bg); }
.tag-error { color: var(--error); background: var(--error-bg); }
.tag-info { color: var(--info); background: var(--info-bg); }
.tag-sent-back { color: var(--sent-back); background: var(--sent-back-bg); }
.tag-semantic-neutral { color: var(--neutral); background: var(--neutral-bg); }

.dialog-backdrop {
    position: fixed; inset: 0; display: grid; place-items: center;
    padding: var(--space-4);
    background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);
}
/*
  T173(1b): this shipped in T119's phase-1 foundation but had never been used until now - the
  bug it carried was invisible until the first real <dialog> element. A native <dialog> is
  display: none by default until it gains the `open` attribute (that IS how showModal() shows
  it); `display: flex` here, unconditional, is an AUTHOR rule that always beats the UA
  stylesheet's own display: none regardless of the attribute - the exact same bug shape as
  .btn[hidden] a few sections up (an author display: X rule beating [hidden]'s UA default), just
  with a native element's own default state this time instead of a JS-driven attribute. Explicit
  :not([open]) rather than relying on the .dialog-backdrop wrapper this was written for: this CSS
  now styles native <dialog> elements directly (see the app-wide note near .review's own CSS on
  why - native gives focus-trap/Escape/inert-background for free), and a native element's own
  hidden-by-default state deserves the same explicit guard [hidden] got, not an assumption.
*/
.dialog:not([open]) { display: none; }
.dialog {
    width: min(440px, 100%); display: flex; flex-direction: column; gap: var(--space-3);
    padding: var(--space-4); border-radius: var(--radius-lg);
    background: var(--color-surface); box-shadow: var(--shadow-lg);
}
.dialog-title {
    font-family: var(--font-heading); font-weight: var(--font-heading-weight);
    font-size: var(--text-heading);
}
.dialog-body { font-size: var(--text-interactive); opacity: 0.85; }
.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }

/* ══════════════════════════════════════════════════════════════════════════
   Nocturne shell (phase 1) - fragments/layout.html's sidebar + header.
   ══════════════════════════════════════════════════════════════════════ */
/* The sidebar and header are inserted by fragments/layout.html's "nav" fragment as a sibling
   before each page's own <main id="main"> - same insertion point the old <header> used - rather
   than as a wrapper, so no template's markup needs to change to adopt the shell. A fixed sidebar
   (not a grid column) is what makes that work: main's own left margin clears it below, with no
   shared parent element required between the two. */
.icon { width: 1em; height: 1em; fill: currentColor; flex: none; }
.shell-side {
    position: fixed; top: 0; left: 0; bottom: 0; width: 212px; overflow-y: auto;
    background: var(--color-neutral-900);
    box-shadow: inset -1px 0 0 color-mix(in srgb, var(--color-text) 8%, transparent);
    padding: var(--space-6) var(--space-4);
    display: flex; flex-direction: column; gap: var(--space-8);
}
.shell-brand { display: flex; align-items: center; gap: var(--space-3); padding: 0 var(--space-2); }
.shell-brand-mark {
    width: 22px; height: 22px; border-radius: 6px; flex: none;
    background: linear-gradient(140deg, var(--color-accent), var(--color-accent-700));
}
.shell-brand-name { font-size: var(--text-interactive); font-weight: 500; letter-spacing: -0.01em; }
/* Creed's review (spec 12d10e8): the box-shadow ring stays - it's a decorative hairline (10% of
   text, ~1.3:1) marking a grouping container, not a control-strength boundary (--color-control-border
   at 3:1+ is what a real control carries here), so it doesn't contradict "unwired chrome must not
   look like a control". The button-reset properties this used to carry (background/border/width/
   text-align/font/color, from when this was headed toward being a <button>) are gone - a plain <div>
   needs none of them: background/border already default to none, text-align/color/font are all
   naturally-inherited CSS properties regardless, and width stretches to fill .shell-side's column
   flex track by that container's own default align-items: stretch. */
.shell-org {
    padding: var(--space-3) var(--space-4); border-radius: var(--radius-md);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-text) 10%, transparent);
    display: flex; align-items: center; gap: var(--space-3);
}
.shell-org-label {
    display: block; font-size: var(--text-chrome); letter-spacing: 0.1em; text-transform: uppercase;
    color: var(--color-text-muted);
}
.shell-org-name {
    display: block; font-size: var(--text-data); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.shell-nav { display: flex; flex-direction: column; gap: 1px; }
.shell-nav-group {
    font-size: var(--text-chrome); letter-spacing: 0.1em; text-transform: uppercase;
    color: var(--color-neutral-400); padding: var(--space-6) var(--space-3) var(--space-2);
}
.shell-nav-group:first-child { padding-top: 0; }
.shell-nav a {
    display: flex; align-items: center; gap: var(--space-3); min-height: 44px;
    padding: 7px var(--space-3); border-radius: 6px; font-size: var(--text-data);
    color: var(--color-text-muted); text-decoration: none;
}
.shell-nav a:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.shell-nav a[aria-current] {
    color: var(--color-text);
    background: color-mix(in srgb, var(--color-accent) 14%, transparent);
    box-shadow: inset 2px 0 0 var(--color-accent);
}
.shell-nav a[aria-current] .icon { color: var(--color-accent); }
.shell-nav-count {
    margin-left: auto; font-size: var(--text-chrome); font-variant-numeric: tabular-nums;
    color: var(--color-text-muted);
}
.shell-nav a[aria-current] .shell-nav-count { color: var(--color-accent); }
.shell-user {
    margin-top: auto; display: flex; align-items: center; gap: var(--space-3);
    padding: var(--space-3) var(--space-2) 0;
    box-shadow: inset 0 1px 0 color-mix(in srgb, var(--color-text) 8%, transparent);
}
.shell-user-avatar {
    width: 26px; height: 26px; border-radius: 50%; background: var(--color-neutral-800);
    display: grid; place-items: center; font-size: var(--text-chrome); font-weight: 600; flex: none;
}
.shell-user-name { display: block; font-size: var(--text-data); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.shell-user-role { display: block; font-size: var(--text-chrome); color: var(--color-text-muted); }
.shell-user form { margin-left: auto; display: flex; }
.shell-user button {
    background: none; border: none; padding: 0; min-height: 44px; min-width: 44px;
    display: grid; place-items: center; cursor: pointer;
    color: var(--color-text-muted);
}
.shell-user button:hover { color: var(--color-text); }

.shell-header {
    display: flex; align-items: center; gap: var(--space-4);
    margin-left: 212px;
    padding: var(--space-4) var(--space-8);
    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--color-text) 8%, transparent);
}
.shell-header-actions { margin-left: auto; display: flex; align-items: center; gap: var(--space-4); }
/* T138 batch 1b (appearance) and 1c (reveal): both toggles are real <form>s (a POST + full page
   redirect, not a JS class swap - spec 2.3/2.5 need the whole page server-rendered fresh, the
   button's own icon/label included), sitting inline among .shell-toggle buttons that aren't
   wrapped in one. `display: contents` drops each form's own box from layout entirely, so its
   .shell-toggle child lays out exactly as if it were a direct flex child of .shell-header-actions -
   the form wrapper is otherwise invisible to the flex row it sits in. */
.shell-appearance-form, .shell-reveal-form { display: contents; }
.shell-toggle {
    display: inline-flex; align-items: center; gap: 6px; min-height: 44px;
    background: transparent;
    /* F2 (Creed's fidelity review): this used to be color-mix(...16% divider) inlined -
       --color-divider's own value, 1.55:1, the same 1.4.11 defect §5c fixes on
       .input/.btn-secondary, just not yet fixed here because these two buttons were
       disabled ("coming soon"). Phase 2 enables them, so fixing it now rather than
       carrying the defect forward. --color-control-border already has its own flat
       fallback (§5d), which the old inline color-mix() didn't - a pre-2023 browser
       dropped the whole border shorthand rather than falling back to anything. */
    border: 1px solid var(--color-control-border);
    border-radius: var(--radius-md); padding: 0 10px; color: var(--color-text);
    font: inherit; font-size: var(--text-data); cursor: pointer;
    /* T138 1c: the reveal control's "hide" state is a plain <a> (a safe, unaudited reload - see
       fragments/layout.html), the first non-<button> user of this class, so a link's own
       browser-default underline/blue needs resetting to match the button-shaped ones. */
    text-decoration: none;
}
.shell-toggle:disabled { opacity: 0.6; cursor: default; }
.shell-toggle .icon { color: var(--color-accent); }

/* D-1e-6 (spec §8n): the narrow-viewport panel's toggle - <details>'s native <summary>, so it
   works with JS off (D-1e-5). Hidden at every width by default; the media query below is the
   ONLY place that shows it, since the desktop rail needs no toggle at all. list-style/marker
   suppression mirrors .disclosure summary's own comment above almost verbatim - not restated
   here, that one explains the Firefox/Safari split this needs too. */
.shell-nav-toggle { display: none; list-style: none; }
.shell-nav-toggle::-webkit-details-marker { display: none; }
.shell-nav-toggle[hidden] { display: none; }

/* D-1e (spec §8n): the close control for the same panel. Not the discloser - a plain button,
   hidden by default via the hidden ATTRIBUTE (shell-nav-panel.js removes it on open) rather than
   a JS-added class, so a script that fails after load still leaves it correctly absent rather
   than stuck visible with nothing wired to it. The explicit [hidden] override is the same
   [hidden]-fighting shape .btn[hidden] and fieldset.step[hidden] already guard above - an
   unconditional `display: inline-flex` on .shell-nav-close would otherwise always beat the UA
   stylesheet's own display:none for [hidden], regardless of the attribute being present. */
.shell-nav-close {
    display: none; align-items: center; gap: 6px; min-height: 44px; align-self: flex-end;
    background: transparent; border: 1px solid var(--color-control-border);
    border-radius: var(--radius-md); padding: 0 10px; color: var(--color-text);
    font: inherit; font-size: var(--text-data); cursor: pointer;
}
.shell-nav-close[hidden] { display: none; }
.shell-nav-close:not([hidden]) { display: inline-flex; }

/* main is every page's own top-level content element (id="main", present on every template
   already) - these rules apply globally, sidebar or not, so login.html/error.html's sidebar-less
   .narrow variant explicitly resets the offset back to a centred, un-shifted column. */
main {
    margin-left: 212px;
    max-width: 1240px;
    padding: var(--space-8);
}
main.narrow { max-width: 420px; margin: var(--space-6) auto; }

@media (max-width: 900px) {
    /* D-1e (spec §8n): this used to be an unconditional `.shell-side { display: none }` - nothing
       replaced it, and that included .shell-user's log-out button, the only one in the app.
       Restored as a full-screen panel over the SAME markup (D-1e-3: "the pattern that reuses the
       DOM verbatim is the one in which the asset stays an asset" - no duplicated nav, no second
       copy of anything inside .shell-side to drift from the first).

       :has() reads .shell-nav-toggle's own native [open] state (fragments/layout.html) rather
       than .shell-side being nested inside that <details> as its disclosed content - nesting was
       rejected on Creed's own acceptance criterion: at desktop width .shell-side's presence must
       NEVER depend on open/closed, and a CSS rule that force-shows a closed <details>'s content
       can leave the sidebar visible while a screen reader still reports the region collapsed - a
       lie. Because .shell-side sits OUTSIDE the details entirely, that whole class of failure
       cannot occur: above this breakpoint neither rule below even applies, so the sidebar is
       governed purely by its own always-on styling above, with zero reference to [open] anywhere.

       display:none, never moved (D-1e-2, the one ruled most likely to be got wrong) - no
       transform, no left:-100%, no visibility transition. Any of those keeps the subtree in the
       tab order and the accessibility tree while only LOOKING closed, turning today's inert,
       complete, role-gated markup into a focus trap that no test would fail. */
    body:not(:has(.shell-nav-disclosure[open])) .shell-side { display: none; }
    body:has(.shell-nav-disclosure[open]) .shell-side {
        /* D-1e-3: full-screen, not a partial-width drawer - at 320px a usable drawer is nearly
           the full width anyway, and a partial width is what invites the transform animation
           D-1e-2 forbids. Overrides the base rule's fixed 212px rail width/positioning; overflow-y
           and the internal flex-column layout are inherited from that same base rule unchanged. */
        top: 0; right: 0; bottom: 0; left: 0; width: auto; height: auto; z-index: 100;
    }
    .shell-nav-toggle {
        display: inline-flex;
        /* Measured: without this, the open panel's z-index:100 paints over the header the toggle
           lives in, and re-clicking it to close - the ONLY close path D-1e-5 leaves without JS,
           since the trap/Escape/close-button are all enhancement - hits the panel instead and
           does nothing. shell-nav-panel.js additionally hides this once it can offer the real
           close button in its place (never both shown at once), but the no-JS case never runs
           that script, so the toggle must be able to stand on its own above the panel regardless. */
        position: relative; z-index: 101;
    }
    .shell-header { margin-left: 0; }
    main { margin-left: 0; }
}

/* ============================== layout ============================== */
/* main and h1/h2 are handled by the T119 shell layer above (D-Q4's page-title/
   section-heading sizes apply globally, same reasoning as main's sidebar-clearing
   margin) - h3 stays here unchanged: it is a specific "uppercase eyebrow" section
   label tied to existing card/stepper chrome (.card h3:first-child, fieldset.step
   legends), not a generic heading, so restyling it belongs with whichever screen's
   own T119 phase reworks that chrome, not the foundation. It currently renders
   against several now-retired tokens (--tint/--accent-dark/--t-xs) and will look
   plainer than before until then - expected, same as every other not-yet-migrated
   legacy rule in this file (see the file-level note at the top). */
h3, legend.section-title {
    margin: var(--s5) 0 var(--s2);
    padding: 0 0 var(--s1);
    border-bottom: 2px solid var(--tint);
    color: var(--accent-dark);
    font-size: var(--t-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.card h3:first-child, fieldset.step:first-of-type legend { margin-top: 0; }
.sub { color: var(--muted); font-size: var(--t-sm); margin: 0 0 var(--s4); }

.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: var(--s5);
    margin-bottom: var(--s4);
    box-shadow: 0 1px 3px rgba(20, 20, 20, 0.04);
}

/* ============================== forms ============================== */
label { display: block; margin-top: var(--s4); margin-bottom: 0.3rem; font-weight: 650; font-size: var(--t-sm); }
.req { color: var(--error); font-weight: 700; margin-left: 2px; }
.hint { font-size: var(--t-xs); color: var(--muted); margin: 0 0 var(--s2); max-width: 64ch; }
input, select, textarea {
    width: 100%;
    min-height: 44px;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--control-border);
    border-radius: 8px;
    font-size: var(--t-md);
    box-sizing: border-box;
    font-family: inherit;
    background: var(--surface);
    color: var(--ink);
}
textarea { min-height: 5.5rem; resize: vertical; }
input[type="color"].swatch-input { padding: 0.25rem; height: 3rem; cursor: pointer; }
/* T188. `outline: none` was here and is deliberately gone. Not because the ring was missing - it
   renders today, and Creed withdrew that claim (spec 6h) after we both walked the cascade: the
   element-qualified `input:focus-visible` block below is (0,1,1), the same as `input:focus`, and
   later in source, so it wins.

   THE DELETION CONVERTS A CORRECTNESS THAT DEPENDS ON SOURCE ORDER INTO ONE THAT DOES NOT.
   That block exists TWICE - here and once earlier, identical selector lists, --ink aliasing
   --color-text. Someone will dedupe them, and the obvious copy to delete is the later one, because
   the earlier reads as the original. Do that while `outline: none` is still here and the survivor
   sits BEFORE this rule: equal specificity, earlier loses, the outline is suppressed, and every text
   input in the app silently loses its focus ring. No test fails and no screenshot moves, because
   nothing in a static render is focused. With the suppression gone, either duplicate can go safely.

   The border turning accent-coloured stays as reinforcement, and the outline is what carries the AA
   obligation: 1.4.11 Non-text Contrast (AA) requires a focus indicator to reach 3:1 AGAINST ADJACENT
   COLOURS, which the 2px --color-text ring does at any brand hue and a border on the brand hue does
   not guarantee.

   Citation corrected against the published Recommendation rather than carried over: this said
   "2.4.11", which in WCAG 2.2 is Focus Not Obscured (Minimum) and is not about contrast at all. And
   the 1.47-1.79:1 figure is a FOCUSED-VS-UNFOCUSED measurement, which belongs to 2.4.13 Focus
   Appearance - AAA, not AA. 1.4.11 explicitly declines to require anything about the difference
   between the two states. So that ratio is the AAA cost of the hazard above, not the AA obligation
   and not a description of today - swapping the number's criterion without swapping its measurement
   would have been the right criterion attached to the wrong pair. */
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
/* Non-themeable focus indicator (FE-04): the branded border above is decoration; this outline is
   the real signal and survives any supplier colour, including a pale one that fails contrast. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible, .checkbox-option:focus-within {
    outline: 2px solid var(--ink);
    outline-offset: 2px;
    border-radius: 4px;
}

input.is-invalid, select.is-invalid, textarea.is-invalid {
    border-color: var(--error);
    border-width: 2px;
    /* Was #FFF7F7: a fixed pale-red fill under an ink that follows the appearance, i.e. the
       .tile.urgent bug on EVERY invalid field in the app, in the one state a user most needs
       to be able to read. Creed's sweep found the tiles; this came out of searching the shape. */
    background: var(--error-bg);
}
.field-error {
    display: flex;
    gap: 6px;
    align-items: flex-start;
    color: var(--error);
    font-size: var(--t-sm);
    font-weight: 650;
    margin: var(--s2) 0 0;
}
.field-error .icon { font-size: 11px; margin-top: 3px; }

/* T173(1b): was `background: #F6F7F9` - a light-page literal paired with the THEMED --ink colour
   it already correctly used, so in dark mode this rendered pale text (--ink resolves to #e9e9ed
   there) on that same pale literal background: 1.13:1, essentially invisible, on the reviewer's
   OWN readonly report view (this is review-form's only consumer of hardcoded-literal + themed-ink
   for this exact class; export/case-file-ready.html shares the class and the same live bug).
   Fixed to match real <input> exactly (same background/border/ink tokens two rules above) - a
   readonly value reads as "a normal field, just not editable" via its own lack of an interactive
   affordance, not via a different background shade from the fields around it. */
.readonly-val {
    background: var(--surface);
    border: 1px solid var(--control-border);
    border-radius: 8px;
    padding: 0.6rem 0.75rem;
    font-size: var(--t-md);
    color: var(--ink);
    min-height: 44px;
    white-space: pre-wrap;
}
.readonly-val.empty { color: var(--muted); font-style: italic; }
/* D-1b-2: 920px is a container width, not a measure - a free-text answer at 920px runs to
   ~123ch, roughly double comfortable reading. Caps the value slot only; dates, names and yes/no
   answers are already shorter than the cap, so this one rule covers the whole report without
   classifying individual fields. Spec names dl.detail dd for this - review-form's actual readonly
   markup (report-fields.html) uses .readonly-val instead (a pre-existing class, not one this
   ticket introduced), so applying the cap here achieves the identical stated outcome. Flagged to
   Creed as a factual note on the actual markup, not a design deviation. */
.readonly-val { max-width: min(100%, 66ch); }

fieldset { border: none; margin: 0; padding: 0; min-width: 0; }
fieldset.checkbox-fieldset { margin-top: var(--s4); }
fieldset.checkbox-fieldset > legend {
    display: block;
    font-weight: 650;
    font-size: var(--t-sm);
    padding: 0;
    margin: 0 0 0.3rem;
    border: none;
    color: var(--ink);
    text-transform: none;
    letter-spacing: normal;
}
.checkbox-group { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: 0.4rem; }
.checkbox-option {
    display: flex;
    align-items: center;
    gap: var(--s2);
    margin: 0;
    padding: 0.5rem var(--s3);
    border: 1px solid var(--control-border);
    border-radius: 8px;
    background: var(--surface);
    font-weight: 500;
    font-size: var(--t-sm);
    cursor: pointer;
    min-height: 44px;
}
/* F3: a checked chip's border takes accent-300, the same step as its ink, rather than
   the base accent. Not a contrast fix - the base accent measures 4.51:1 on the tint,
   which passes - but border and ink matching is how a selected chip should read, and
   accent-300 on accent-900 holds 9.19:1 dark / 6.27:1 light. The ramp MIRRORS between
   appearances, so one step number keeps the same role in both grounds.
   (Correction, 2026-09-04: Creed's original review called this a 1.4.11 failure at
   2.40:1 dark - that number came from a sweep with a double gamma-decode bug in its
   OKLCH-to-sRGB conversion. The corrected function reproduces Nocturne's own published
   accent hex to within one unit per channel. The CSS change itself was worth keeping
   regardless - see above - so nothing here reverted, only the false rationale.) */
.checkbox-option:has(input:checked) { border-color: var(--color-accent-300); background: var(--tint); color: var(--accent-dark); }
.checkbox-option input { width: auto; min-height: 0; margin: 0; }
.checkbox-option[aria-disabled="true"] { opacity: 0.55; }
.checkbox-option[aria-disabled="true"] input { cursor: not-allowed; }
.constraint-note { font-size: var(--t-xs); color: var(--muted); margin: var(--s2) 0 0; }

/* ============================== buttons ============================== */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s2);
    min-height: 44px;
    background: var(--accent);
    color: var(--accent-ink);
    border: 1px solid transparent;
    border-radius: 8px;
    padding: 0 var(--s5);
    font-size: var(--t-md);
    font-weight: 650;
    cursor: pointer;
    text-decoration: none;
    margin-top: var(--s5);
    margin-right: var(--s2);
    box-shadow: 0 1px 2px rgba(20, 20, 20, 0.06);
}
/* Creed's #59 review: --accent-dark is accent-300, which MIRRORS between themes (deep in light,
   PALE in dark) - so `color: #fff` was white text on a pale lilac fill in dark mode, 1.48-1.62:1,
   failing 1.4.3 at all 360 hues (worse than any of #48's four). Minimal fix, not a redesign: this
   legacy .btn family is scheduled for replacement by .btn-primary/.btn-secondary during the
   screen migrations, so the fix keeps the fill and only swaps the ink - var(--color-bg) measures
   10.85:1 dark / 5.87:1 light, 0/360 failures. */
.btn:hover { background: var(--accent-dark); color: var(--color-bg); }
.btn.secondary { background: var(--surface); color: var(--accent-dark); border-color: var(--accent-dark); box-shadow: none; }
.btn.secondary:hover { background: var(--tint); }
.btn.ghost { background: transparent; color: var(--muted); border-color: var(--control-border); box-shadow: none; }
.btn.ghost:hover { border-color: var(--accent-dark); color: var(--accent-dark); }
.btn.danger { background: var(--surface); color: var(--error); border-color: var(--error); box-shadow: none; }
.btn.danger:hover { background: var(--error-bg); }
/* D-1b-6: send-back is one of two legitimate review outcomes, not a destructive action - it
   creates work rather than destroying any (the report reopens to the visitor, nothing is lost),
   and red tells a reviewer choosing between two valid paths that one of them is dangerous. Not a
   new component, a modifier: same structure as .btn.secondary (outlined, not filled), --sent-back
   swapped in for the ink/border instead of --accent-dark - the token already means precisely this
   event (the status tag, the rail marker and the visitor's own banner all already carry it).
   Measured on the page background: 11.34:1 dark / 6.41:1 light, past 1.4.3 for the label and
   1.4.11 for the border. */
.btn.sent-back { background: var(--surface); color: var(--sent-back); border-color: var(--sent-back); box-shadow: none; }
.btn.sent-back:hover { background: var(--sent-back-bg); }
.btn.block { width: 100%; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* T235 / spec §8a (Creed, b5902ab): a button's external margin is a property of WHERE IT SITS,
   not of what it is. Legacy .btn (:930-ish, "margin-top: var(--s5)") carries the spacing between
   a form and its actions - but .btn-row rightly zeroes each child's own margin-top below (a flex
   row owns its own gap, and a stray top margin per child would misalign them), and until now
   never put the relationship back. So wrapping actions in the one container that spaces buttons
   correctly against EACH OTHER silently deleted their spacing against the form ABOVE them - the
   exact "no padding"/"sits flush against the input" the human reported on login, save-changes,
   and every add-home/add-child form. Not a new number: --s5 (24px) is what a lone .btn already
   said for this relationship, merely unreachable from inside .btn-row until now. It is also the
   BLOCK-AFTER-CONTENT rhythm elsewhere in this file (.table-wrap, .stack, two more) - --s4/16px is
   the WITHIN-FORM field-to-field rhythm (label), and an actions block must read as more separated
   than one more field. No double-spacing either path: a bare .btn outside a row keeps its own
   --s5; .btn-row .btn below zeroes it inside a row; both land on the same 24px. The two existing
   overrides (.srow .act .btn-row, .theme-preview .btn-row) already opt out the only two contexts
   that would be wrong under this rule - checked, still correct, left untouched.
   WHEN LEGACY .btn RETIRES: delete its margin-top rather than porting it - Nocturne's .btn has no
   external margin at all, correctly, and this rule is where the job lives from then on. */
.btn-row { display: flex; gap: var(--s3); flex-wrap: wrap; align-items: center; margin-top: var(--s5); }
.btn-row .btn { margin-top: 0; margin-right: 0; }

/* ============================== status / due / banners ============================== */
.status {
    display: inline-block;
    padding: 0.15rem 0.6rem;
    border-radius: 12px;
    font-size: var(--t-xs);
    font-weight: 700;
    /* Both halves were hard-coded, so this pair was at least self-consistent - but it ignored
       the appearance entirely, and every .status.* modifier below already takes a themed pair. */
    background: var(--neutral-bg);
    color: var(--neutral);
}
.status.REQUESTED { background: var(--warn-bg); color: var(--warn); }
.status.ALLOCATED, .status.SCHEDULED, .status.REPORT_SUBMITTED { background: var(--info-bg); color: var(--info); }
/* Was a hardcoded #FDE7D8/#9A3412 pair - the same bug shape as #48 (a light-page literal instead
   of the theme-aware token), just missed by that pass's grep because both sides were hardcoded
   rather than one hardcoded ink against a themed background. --sent-back/--sent-back-bg is the
   correct pair regardless: it's the same token .tag-sent-back and .tl .dot.back already use for
   this exact state (T155 batch 2), and D-1a-2's copy finding renamed this status's displayName to
   "Sent back" for the same reason - one vocabulary, one token, for one event. */
.status.REPORT_REJECTED { background: var(--sent-back-bg); color: var(--sent-back); }
.status.REPORT_APPROVED { background: var(--ok-bg); color: var(--ok); }
.status.CANCELLED { background: var(--error-bg); color: var(--error); }
/* Same literal-pair bug as REPORT_REJECTED above, but this one actually is a warning ("Consent not
   yet confirmed") - --warn/--warn-bg is the semantically correct token, not --sent-back. */
/* OrgStatus (T265). Not cosmetic, and not a follow-up to the vocabulary fix - the SAME change.
   All three org states fell through to base .status, so the colour carried NO information and the
   shouting-caps constant was the entire signal; fixing the case without these would have made the
   screen quieter without making it clearer. Pairs reuse existing precedents rather than inventing:
   a state awaiting an action reads like .status.REQUESTED, a verified-good state like
   .status.REPORT_APPROVED. */
.status.PENDING { background: var(--warn-bg); color: var(--warn); }
.status.ACTIVE { background: var(--ok-bg); color: var(--ok); }
/* Resolves to what base .status already gives, and is written out ANYWAY so the next person cannot
   read the absence as an oversight and "fix" it with a colour: archived should recede. Same reason
   InterviewStatusTransitions records CANCELLED's empty in-edge set explicitly rather than leaving
   it latent - a deliberate nothing has to be stated to survive. */
.status.ARCHIVED { background: var(--neutral-bg); color: var(--neutral); }
.status.warning { background: var(--warn-bg); color: var(--warn); margin-left: 0.4rem; }

/* Roadmap 2.1 due-state badges (ninja...interview.DueBadge). Never colour alone - each carries a
   glyph and duration text too. Colours reuse the semantic families above; no new red. */
.due {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: var(--t-xs);
    font-weight: 700;
    white-space: nowrap;
}
.due.overdue { background: var(--error-bg); color: var(--error); }
.due.soon { background: var(--warn-bg); color: var(--warn); }
.due.ontrack { background: var(--ok-bg); color: var(--ok); }
.due.noclock { background: var(--neutral-bg); color: var(--neutral); }

.error { color: var(--error); background: var(--error-bg); padding: var(--s4); border-radius: 8px; margin-bottom: var(--s4); }

.banner {
    border-radius: var(--radius);
    padding: var(--s4);
    margin-bottom: var(--s4);
    font-size: var(--t-sm);
    display: flex;
    gap: var(--s3);
    align-items: flex-start;
}
.banner strong { display: block; margin-bottom: 2px; font-size: var(--t-md); }
.banner p { margin: 0.35rem 0 0; white-space: pre-wrap; }
.banner .ic { font-size: var(--t-lg); line-height: 1.1; }
.banner ul { margin: var(--s2) 0 0; padding-left: 18px; }
.banner a { color: inherit; font-weight: 700; }
/* The borders derive from each variant's own token, so they follow the appearance the way the
   ink already does (#48). As light-mode literals they inverted in dark, turning a subtle
   1.31-1.47:1 boundary into a 7.70-8.99:1 ring around every banner - over-contrast rather than
   an AA failure, but four banners outlined like alerts. At 25% of their own token they measure
   1.47-1.50:1 light, visually unchanged, and 1.75-1.87:1 dark (spec cc3574c §5h). Per §5d each
   color-mix() is preceded by a flat value, and that value is `transparent` rather than the old
   literal: the literal is the light-page colour being removed, so falling back to it would hand
   a pre-2023 browser in dark mode the 8.99:1 ring this change exists to remove - the defect
   wearing a fallback's clothes. No border is honest degradation; the fill and the ink still
   identify the banner. Keeping `1px solid` holds the border box, so nothing reflows, and the
   banner's own background paints under it (background-clip is border-box by default). */
.banner.err { background: var(--error-bg); color: var(--error); border: 1px solid transparent; border-color: color-mix(in srgb, var(--error) 25%, transparent); }
.banner.warn { background: var(--warn-bg); color: var(--warn); border: 1px solid transparent; border-color: color-mix(in srgb, var(--warn) 25%, transparent); }
.banner.ok { background: var(--ok-bg); color: var(--ok); border: 1px solid transparent; border-color: color-mix(in srgb, var(--ok) 25%, transparent); }
.banner.info { background: var(--info-bg); color: var(--info); border: 1px solid transparent; border-color: color-mix(in srgb, var(--info) 25%, transparent); }
/* T156/T286: now consumed by visitor/report-form.html's REPORT_REJECTED banner, which used to
   borrow .banner.warn - the wrong vocabulary for the identical event D-1b-8's own
   .prior-send-back note and the rail marker already used correctly. --sent-back is the
   vocabulary all three surfaces share; the tokens themselves were locked earlier (D-1a-2a). */
.banner.sent-back { background: var(--sent-back-bg); color: var(--sent-back); border: 1px solid transparent; border-color: color-mix(in srgb, var(--sent-back) 25%, transparent); }

.muted { color: var(--muted); }
dl.detail dt { font-weight: 650; margin-top: var(--s3); font-size: var(--t-xs); color: var(--muted); }
dl.detail dd { margin: 0.15rem 0 0; white-space: pre-wrap; }

/* D-1a-1 (spec 1058511): never opacity for de-emphasis - it compounds with an already-muted
   token (0.7 x 0.5 = 2.81:1 dark / 2.07:1 light, both failing 1.4.3) and dims the focus ring of
   anything focusable inside it along with it. --color-text-muted on its own holds 6.89:1 dark /
   5.45:1 light; italic supplies the de-emphasis opacity was reaching for, without touching
   contrast. The question itself (dl.detail dt above) is never touched - a reviewer scans
   questions for gaps, so the thing skipped is exactly what must stay legible. */
.unanswered { color: var(--muted); font-style: italic; }
.section-count {
    font-weight: 400;
    text-transform: none;
    letter-spacing: normal;
    color: var(--muted);
    font-size: var(--t-sm);
    margin-left: var(--s2);
}

/* T173: a native <details>/<summary> disclosure, first use in the app - real request context
   (known risks, professionals, missing episode) available IN PLACE inside the report wizard
   instead of a link that navigated the visitor away and lost their unsaved progress (T174 covers
   actual autosave separately; this only stops the navigation). Deliberately native rather than a
   JS-driven expand/collapse: <summary> already carries the correct implicit role and exposes its
   own expanded/collapsed state to assistive tech, and focus stays on it after toggling with zero
   script - exactly the "don't build a widget the platform already gives you" reasoning D-1a-3
   used against inventing tablist markup, applied here in the direction where the native element
   genuinely IS the widget. summary:focus-visible is already styled globally (see :focus-visible
   rules above) - this component predates any <details> in the app, so nothing new to add there. */
.disclosure summary {
    display: flex; align-items: center; gap: var(--s2); cursor: pointer;
    list-style: none; font-weight: 650;
}
/* Suppresses the browser default triangle so only the custom caret icons show. `list-style:
   none` above already does this in modern Firefox for a `display: list-item` element like
   <summary> - no further ::marker override needed (and AnnouncedGlyphSourceGuardTest would flag
   one that set the CSS generated-content property directly on ::marker: it can't tell a
   marker-suppressing empty value from real generated text, so its correct default is to disallow
   that property outright). Safari/older Chromium still need the vendor-prefixed pseudo-element
   hidden explicitly. */
.disclosure summary::-webkit-details-marker { display: none; }
.disclosure[open] summary { margin-bottom: var(--s3); }
.disclosure .caret-open { display: none; }
.disclosure[open] .caret-open { display: block; }
.disclosure[open] .caret-closed { display: none; }
/* 1.4.11: a caret is a required graphical object (it's the only visual cue an open disclosure
   has beyond the content itself appearing) so it needs 3:1, not decorative-icon exemption -
   --muted (this screen's own name for --color-text-muted, see the bridge at :root) holds 6.89:1
   dark / 5.45:1 light, comfortably clear. */
.disclosure .icon { color: var(--muted); }
.disclosure dl.detail { margin: 0; }

/* ============================== tables ============================== */
.table-wrap { overflow-x: auto; border-radius: var(--radius); border: 1px solid var(--border); margin-top: var(--s5); }
table {
    width: 100%;
    border-collapse: collapse;
    background: var(--surface);
    font-size: var(--t-sm);
}
.table-wrap table { border: none; margin-top: 0; }
thead tr { background: var(--tint); }
th, td { text-align: left; padding: 0.7rem 0.85rem; border-bottom: 1px solid var(--border); }
th {
    color: var(--accent-dark);
    font-weight: 700;
    font-size: var(--t-xs);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
tbody tr:last-child td { border-bottom: 0; }
table caption { text-align: left; font-size: var(--t-xs); color: var(--muted); padding: 0 0 var(--s2); caption-side: top; }
.visually-hidden {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.rowlink { color: var(--accent-dark); font-weight: 650; text-decoration: none; display: inline-flex; align-items: center; min-height: 44px; margin-right: var(--s3); }
.rowlink:hover, .rowlink:focus-visible { text-decoration: underline; }
/* T165: the urgency-tier glyph is an aria-hidden <svg> in markup now, not a character inside the
   announced heading text - this keeps it on the text baseline in both the table and .stack views. */
.due-group-head { display: inline-flex; align-items: center; gap: 6px; }

/* stacked-card table - the mobile alternative to <table>, shown/hidden by the breakpoint below.
   T290: children/detail.html's own instance is now a <ul role="list"> (T253's rule - display:flex
   on a list strips list semantics in Safari/VoiceOver - this .stack never carried it before, and
   T253 never reached this older table-fallback component). list-style/padding reset matches every
   other <ul>-based component already in this file (.tl, .rail, .manifest, .not-kept); a no-op for
   the other two pages still using .stack as a plain <div>, which never had a marker or list
   padding to begin with. */
.stack { display: none; flex-direction: column; gap: var(--s3); margin-top: var(--s5); list-style: none; padding: 0; }
/* 6a (spec §7b): a page with no table at all (R-Q12 - cards for people, not a second table copy)
   needs its card list visible at every width, not just below the breakpoint that used to reveal
   it beside a hidden table. A distinct modifier rather than changing .stack itself, which every
   still-paired table+stack screen still depends on staying breakpoint-gated. */
.stack.solo { display: flex; }
.srow { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--s4); }
.srow .top { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--s3); margin-bottom: var(--s2); }
.srow .name { font-weight: 700; font-size: var(--t-md); }
.srow dl { display: grid; grid-template-columns: auto 1fr; gap: var(--s1) var(--s3); margin: var(--s3) 0 0; font-size: var(--t-sm); }
.srow dt { color: var(--muted); } .srow dd { margin: 0; }
.srow .act { margin-top: var(--s3); display: flex; gap: var(--s3); flex-wrap: wrap; }
.srow .act .btn, .srow .act .btn-row { margin-top: 0; }

.empty {
    padding: var(--s7) var(--s5);
    text-align: center;
    color: var(--muted);
    background: var(--surface);
    border: 1px dashed var(--border);
    border-radius: var(--radius);
    margin-top: var(--s5);
}

/* ============================== stepper chrome (visitor report) ============================== */
.steps {
    display: flex; align-items: center; gap: var(--s2);
    font-size: var(--t-xs); color: var(--muted); font-weight: 650;
    padding: var(--s3) var(--s4); background: var(--surface); border-bottom: 1px solid var(--border);
}
.dots { display: flex; gap: 5px; }
.dot { width: 9px; height: 9px; border-radius: 50%; background: var(--border); }
.dot.done { background: var(--ok); }
.dot.now { background: var(--accent); box-shadow: 0 0 0 3px var(--tint); }
.saved { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; color: var(--ok); font-weight: 700; }
.saved.pending { color: var(--muted); }
/* T174: a terminal autosave refusal is not a quieter version of "not yet saved" - the report can
   never accept this work again, and a muted grey reads as "still in progress". */
.saved.stopped { color: var(--error); }
.sticky-actions {
    position: sticky; bottom: 0; background: var(--surface); border-top: 1px solid var(--border);
    padding: var(--s3) var(--s4); display: flex; gap: var(--s2); align-items: center; margin-top: var(--s5);
}
.sticky-actions .btn { flex: 1; margin: 0; padding: 0 var(--s3); font-size: var(--t-sm); }
.sticky-actions .btn.ghost { flex: 0 0 auto; padding: 0 var(--s4); }
.offline {
    background: var(--warn-bg); color: var(--warn); font-size: var(--t-sm); font-weight: 650;
    padding: var(--s2) var(--s4); display: flex; gap: var(--s2); align-items: center; border-bottom: 1px solid #EBCF8A;
}
fieldset.step[hidden] { display: none; }
/* T173: report-stepper.js sets nextBtn.hidden on the terminal step, but .btn (both the legacy
   block above and the Nocturne one near the top of this file) unconditionally declares
   `display: inline-flex` - an author rule always beats the UA stylesheet's own `[hidden] {
   display: none}` regardless of specificity, so the Next button never actually disappeared; it
   sat there fully visible, and clicking it silently no-opped (report-stepper.js's own guard
   already refuses to advance past the last step). Same fix shape as the line above it - this
   class needed its own [hidden] override and never got one. */
.btn[hidden] { display: none; }

/* D-1c/1d (spec §8m): the step label becomes the disclosure for a section index (D-1d-1) - the
   sentence that already names the current position is what opens the panel, rather than a control
   living beside it. Reset to a plain button first (buttons inherit neither font nor color from
   their parent in most engines - without this it would silently stop matching .steps's own type). */
.step-label {
    font: inherit; color: inherit; background: none; border: none; cursor: pointer;
    display: inline-flex; align-items: center; padding: 2px 6px; margin: -2px -6px; border-radius: 6px;
}
.step-label:hover { background: var(--tint); }
/* Sighted affordance for the open/closed state - aria-expanded already carries it to a screen
   reader; this is the equivalent for everyone else, without a decorative icon (T165/AGSGT rules out
   CSS generated content, and nothing in D-1d-1 asks for one). */
.step-label[aria-expanded="true"] { background: var(--tint); color: var(--accent-dark); }

.step-panel { list-style: none; margin: 0; padding: var(--s2) 0; background: var(--surface); border-bottom: 1px solid var(--border); }
.step-panel-row {
    width: 100%; min-height: 44px; padding: 0 var(--s4); display: flex; align-items: center; gap: var(--s3);
    background: none; border: none; font: inherit; color: var(--color-text); text-align: left; cursor: pointer;
}
.step-panel-row:hover { background: var(--tint); }
.step-panel-marker { flex: 0 0 auto; width: 10px; height: 10px; border-radius: 50%; background: transparent; border: 2px solid var(--border); }
/* D-1d-2: "visited" is a solid marker, "current" is a filled one with the same halo .dot.now
   already uses immediately above - the two states share a family (both mean "has been on screen"),
   current just adds the ring that marks which one is happening now. */
.step-panel-row.visited .step-panel-marker { background: var(--ok); border-color: var(--ok); }
.step-panel-row.current .step-panel-marker { background: var(--accent); border-color: var(--accent); box-shadow: 0 0 0 3px var(--tint); }
.step-panel-row.current .step-panel-row-label { font-weight: 700; }
/* An override on top of whichever of the three position-states applies, never a fourth position -
   a step can be both "current" and "needs attention" at once. */
.step-panel-row.needs-attention .step-panel-marker { background: var(--warn); border-color: var(--warn); box-shadow: none; }
.step-panel-row-label { flex: 1; }
.step-panel-row-attention { flex: 0 0 auto; font-size: var(--t-xs); font-weight: 650; color: var(--warn); }

/* ============================== responsive (FE-03/FE-10/FE-25): 720px breakpoint ============================== */
@media (max-width: 720px) {
    main { padding: 0 var(--s4); margin: var(--s5) auto; }
    header { padding: var(--s3) var(--s4); }
    header a { margin-right: var(--s4); }
    .table-wrap.responsive { display: none; }
    .stack { display: flex; }
    .split { grid-template-columns: 1fr !important; }
    .rail { border-right: none !important; padding-right: 0 !important; border-bottom: 1px solid var(--border); padding-bottom: var(--s4); margin-bottom: var(--s4); }
}

/* ============================== audit history timeline (T38, V1) ============================== */
.provenance {
    display: flex; gap: var(--s2); align-items: center; font-size: var(--t-xs); color: var(--muted);
    background: var(--neutral-bg); border: 1px solid var(--border); border-radius: 8px;
    padding: var(--s2) var(--s3); margin-bottom: var(--s4);
}
.daysep {
    font-size: var(--t-xs); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--muted); margin: var(--s5) 0 var(--s3); padding-bottom: var(--s2); border-bottom: 1px solid var(--border);
}
.daysep:first-child { margin-top: 0; }
.tl { list-style: none; margin: 0; padding: 0; }
.tl li { position: relative; padding: 0 0 var(--s4) var(--s5); border-left: 2px solid var(--border); margin-left: 5px; }
.tl li:last-child { border-left-color: transparent; padding-bottom: 0; }
.tl .dot {
    position: absolute; left: -7px; top: 4px; width: 12px; height: 12px; border-radius: 50%;
    background: var(--neutral-bg); border: 2px solid var(--neutral);
}
.tl .dot.ok { background: var(--ok-bg); border-color: var(--ok); }
.tl .dot.info { background: var(--info-bg); border-color: var(--info); }
.tl .dot.back { background: var(--sent-back-bg); border-color: var(--sent-back); }
.tl .dot.err { background: var(--error-bg); border-color: var(--error); }
.tl .ev { display: flex; justify-content: space-between; gap: var(--s3); align-items: baseline; flex-wrap: wrap; }
.tl .what { font-weight: 650; font-size: var(--t-md); }
.tl .when { font-size: var(--t-xs); color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.tl .who { font-size: var(--t-sm); color: var(--muted); margin: 2px 0 0; }
.tl .who .role { font-weight: 600; }
.tl .detail {
    font-size: var(--t-sm); margin-top: var(--s2); display: inline-flex; align-items: center; gap: 6px;
    background: var(--neutral-bg); border-radius: 6px; padding: 3px 10px; color: var(--neutral); font-weight: 600;
}
.former { font-style: italic; }
@media print {
    .mk-nav, .btn { display: none !important; }
    .tl li { break-inside: avoid; page-break-inside: avoid; }
    .daysep { break-after: avoid; page-break-after: avoid; }
    .provenance { border: 1px solid #999; background: #fff; }
    .tl .dot { border-color: #333 !important; background: #fff !important; }
}

/* ============================== 1a: interview record (T155) ============================== */

/* D-1a-2/D-1a-2a: the status rail, one <ol> of five fixed positions (StatusRail.java is the only
   place that decides WHICH state a position is in - this only decides how each state looks).
   Horizontal, connector-first: each <li> draws its own leading connector as a ::before so the
   line runs continuously through the row rather than between the markers. */
.rail { list-style: none; display: flex; margin: var(--space-6) 0; padding: 0; }
.rail li {
    position: relative; flex: 1; display: flex; flex-direction: column; align-items: center;
    gap: var(--space-2); text-align: center; padding-top: 18px; font-size: var(--t-xs);
}
/*
  T165 fast-follow: this was a `.rail li::before` connector using the CSS generated-content
  property, which is exposed to the accessibility tree by the major screen readers
  (AnnouncedGlyphSourceGuardTest) - a purely decorative line must be real, aria-hidden markup
  instead, never CSS-generated. The template now renders `<span class="rail-connector"
  aria-hidden="true">` as the first child of every <li> but the first (th:unless="${stat.first}"),
  so no first-child exclusion rule is needed here - the element simply isn't in the DOM for it.
*/
.rail-connector {
    position: absolute; top: 8px; left: -50%; width: 100%; height: 2px;
    background: var(--color-divider); z-index: 0;
}
.rail-marker {
    position: relative; z-index: 1; width: 18px; height: 18px; display: flex; align-items: center;
    justify-content: center; border-radius: 50%; background: var(--surface); font-size: 16px;
}
.rail-label { font-weight: 600; color: var(--color-text-muted); }
.rail-when { color: var(--muted); font-variant-numeric: tabular-nums; }

.rail li.rail-COMPLETE .rail-marker, .rail li.rail-CURRENT .rail-marker { color: var(--accent); }
.rail li.rail-COMPLETE .rail-connector, .rail li.rail-CURRENT .rail-connector { background: var(--accent); }
.rail li.rail-COMPLETE .rail-label, .rail li.rail-CURRENT .rail-label { color: var(--color-text); }
.rail li.rail-CURRENT .rail-label { font-weight: 700; }
/* "in an outlined ring" (D-1a-2a) - the one marker that gets an explicit border, so CURRENT reads
   as distinct from COMPLETE at a glance and not only by which glyph it happens to carry. */
.rail li.rail-CURRENT .rail-marker { border: 2px solid var(--accent); }

.rail li.rail-UPCOMING .rail-marker { color: var(--control-border); }

/* SENT_BACK (REPORT_REJECTED at the REPORT_SUBMITTED position) - never colour alone, so the glyph
   (ph-arrow-u-up-left, "went back") plus the label from InterviewStatus.displayName ("Sent back")
   both carry the meaning; colour is the third signal, not the only one. */
.rail li.rail-SENT_BACK .rail-marker { color: var(--sent-back); background: var(--sent-back-bg); }
.rail li.rail-SENT_BACK .rail-connector { background: var(--sent-back); }
.rail li.rail-SENT_BACK .rail-label { color: var(--sent-back); font-weight: 700; }

/* CANCELLED (the position a cancelled request last reached) - a different glyph AND colour pair
   from SENT_BACK (D-1a-2a's table gives the two rows distinct treatments), so the two can never
   be confused for each other even at a glance. */
.rail li.rail-CANCELLED .rail-marker { color: var(--neutral); background: var(--neutral-bg); }
.rail li.rail-CANCELLED .rail-connector { background: var(--neutral); }
.rail li.rail-CANCELLED .rail-label { color: var(--neutral); font-weight: 700; }

/* NOT_APPLICABLE (after a cancelled request's last-reached position) - the connector itself turns
   dashed, not just muted colour: a shape change is what stops these reading as "still to come". */
.rail li.rail-NOT_APPLICABLE .rail-marker { color: var(--muted); }
.rail li.rail-NOT_APPLICABLE .rail-connector {
    background: none; border-top: 2px dashed var(--color-divider); height: 0;
}

/* D-1a-3: a section index, not tabs - pills that behave as jump links. `:has()` gives an active
   pill with no JS; it is progressive enhancement only (Chrome 105 / Safari 15.4 / Firefox 121,
   later than the color-mix() floor R-Q8 committed to) - unsupported browsers simply show no
   active pill, and every link still works, so nothing here may depend on it to be usable. */
.section-index {
    display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-6) 0;
    padding: var(--space-2) 0; border-block: 1px solid var(--border);
}
.section-index a {
    display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--space-4);
    border-radius: 999px; font-size: var(--text-interactive); font-weight: 600;
    color: var(--muted); text-decoration: none; background: transparent;
}
.section-index a:hover { background: var(--surface); color: var(--color-text); }
/* T188: --color-accent, not the legacy --accent alias. They resolve to the same colour today, so
   this changes nothing visually - it is here because this was the last focus rule still reading the
   token T186 overrides, and leaving one behind is how the next person concludes the migration was
   finished. The ring being brand-derived at all is the separate FE-04 question, flagged to Creed. */
.section-index a:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
body:has(#request:target) .section-index a[href="#request"],
body:has(#young-person:target) .section-index a[href="#young-person"],
body:has(#professionals:target) .section-index a[href="#professionals"],
body:has(#submitted-by:target) .section-index a[href="#submitted-by"],
body:has(#report-details:target) .section-index a[href="#report-details"],
body:has(#rhi:target) .section-index a[href="#rhi"],
body:has(#future-incidents:target) .section-index a[href="#future-incidents"],
body:has(#interviewer-comments:target) .section-index a[href="#interviewer-comments"],
body:has(#recommendations:target) .section-index a[href="#recommendations"],
body:has(#declaration:target) .section-index a[href="#declaration"],
body:has(#history:target) .section-index a[href="#history"] {
    /* Creed's review: white on the dark-mode accent is 2.89-3.31:1, failing 1.4.3 at all 360
       hues (light passes at 5.77-7.02, which is why this survives a light-mode eyeball).
       --color-bg is the fix, not an arbitrary swap - it measures 5.31:1 dark / 5.06:1 light,
       0/360 failures, because it's the spec's already-locked accent-on-background floor and
       contrast is symmetric: an accent fill with page-background ink is the exact inverse of a
       pair already swept. It inherits that guarantee for free. */
    background: var(--accent); color: var(--color-bg);
}
/* Every jump target: tabindex="-1" in the markup makes it focusable (a jump link moves scroll
   position but not the reading cursor otherwise - a screen-reader user activating a section link
   would see nothing happen); scroll-margin-top clears the sticky header so the heading lands
   below it rather than underneath it (D-1a-3). */
.card[id] { scroll-margin-top: calc(var(--space-8) + 12px); }
.card[id]:focus { outline: none; }

/* D-1a-4: the history column collapses at 1060px, not the shell's 900px - the report column's own
   width (viewport - 212 sidebar - 22.4 gap - 316 history) drops under a 66ch measure at ~1045px,
   well before the sidebar breakpoint. Below 1060px, #history becomes the section index's own last
   entry (D-1a-3's pattern) rather than a silent single-column stack. */
.detail-layout { display: grid; grid-template-columns: 1fr 316px; gap: var(--space-8); align-items: start; }
.detail-layout .history-col { min-width: 0; }
.section-index a[href="#history"] { display: none; }
@media (max-width: 1060px) {
    .detail-layout { display: block; }
    .section-index a[href="#history"] { display: inline-flex; }
}

/* ============================== 1b: the reviewer's decision surface (spec §6a) ============================== */
/* D-1b-2: single column, no .detail-layout grid - 1b has no second column. 920px is a container
   width, not a measure (see .readonly-val's own max-width above for the actual reading-width cap
   on free-text answers); this just keeps the report from stretching edge-to-edge on a wide
   reviewer monitor, matching main.narrow's own pattern immediately above for a different width. */
main.review { max-width: 920px; }
/* D-1b-4: numbered sections, matching the generated document - the number lives in the heading
   TEXT (see reviewerFields() in fragments/report-fields.html), never a CSS counter, because a
   counter isn't selectable and the send-back dialog asks the reviewer to CITE this exact number.
   Scoped to .review rather than added to the shared `h3, legend.section-title` rule above: this
   is 1b's own heading level (h2, per spec - review-form.html has no h3 sections), and scoping
   avoids touching every other h2 in the app (1a's own "History" card heading included). */
.review h2 {
    margin: var(--s5) 0 var(--s2);
    padding: 0 0 var(--s1);
    border-bottom: 2px solid var(--tint);
    color: var(--accent-dark);
    font-size: var(--t-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.review .card h2:first-child { margin-top: 0; }
/* D-1b-8 CLOSED (god + Creed, §6c/§6d): shown, at the top, ALONE - not a banner/alert (a
   resubmission isn't a fault; a report sent back and properly reworked is the system working),
   but not receding into .muted either, since a reviewer taking an irreversible decision must
   actually register it. Lifted out of body text via the --sent-back ink + weight alone, no
   border/background - "a clearly-distinct info note... not body-muted, not alarm-coloured".
   Same --sent-back vocabulary and ph-arrow-u-up-left glyph as the rail's exception state and the
   visitor's own banner, which is what makes the three surfaces read as one decision rather than
   three coincidences (D-1b-8's own point in the original spec). */
.prior-send-back { display: flex; align-items: flex-start; gap: var(--s2); color: var(--sent-back); font-weight: 650; margin-bottom: var(--s4); }
.prior-send-back .icon { flex: none; margin-top: 2px; }
.prior-send-back a { color: inherit; text-decoration-thickness: 1px; }
/* D-1b-5(a): position: sticky; bottom: 0 pins .sticky-actions OVER content scrolling beneath it -
   without this the page's last section could never fully clear the bar to be read. Approximate
   (bar padding + a 44px .btn, no exact measurement taken) but deliberately generous: over-padding
   costs a little blank space, under-padding hides real content, so the safe side to round on. */
.review { padding-bottom: 88px; }
/* D-1b-5(b)/D-1b-9: the native <dialog> element gives focus-trap, Escape-to-close and an inert
   background for free - the same "don't build a widget the platform already gives you" reasoning
   as D-1a-3's rejection of invented tablist markup, in the direction where a native element
   genuinely IS the widget this time. .dialog itself (padding/radius/surface/shadow) is unchanged
   from the shipped, previously-unused Nocturne definition above; only the backdrop tint needs a
   line of its own, since a native <dialog>'s ::backdrop is a different pseudo-element from the
   .dialog-backdrop wrapper div that CSS originally assumed - same tint value, new selector. */
dialog.dialog::backdrop { background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent); }
dialog.dialog { border: none; }

/* ============================== dashboard (roadmap 2.3) ============================== */
.zone {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    padding: var(--s5);
    margin-bottom: var(--s5);
}
.zone.live { border-left: 4px solid var(--accent); }
.zone-hd {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4); flex-wrap: wrap;
    margin-bottom: var(--s4); padding-bottom: var(--s3); border-bottom: 1px solid var(--border);
}
.zone-hd h2 { margin: 0; font-size: var(--t-lg); }
.zone-hd .when {
    font-size: var(--t-xs); font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
    color: var(--muted); white-space: nowrap;
}
.zone.live .zone-hd .when { color: var(--accent-dark); }

.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: var(--s4); }
.tile {
    display: flex; flex-direction: column; gap: 2px; text-decoration: none; color: inherit;
    border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface);
    padding: var(--s4); min-height: 44px;
}
.tile:hover, .tile:focus-visible { border-color: var(--accent-dark); }
.tile .lab { display: block; font-size: var(--t-xs); font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.tile .num { display: block; font-size: 30px; font-weight: 700; line-height: 1.15; margin: 4px 0 2px; font-variant-numeric: tabular-nums; }
.tile .den { display: block; font-size: var(--t-xs); color: var(--muted); }
.tile .go { display: block; font-size: var(--t-xs); font-weight: 700; color: var(--accent-dark); margin-top: var(--s2); }
/* LIVE DARK-MODE DEFECT, fixed (Creed's 2c review). Both rules used to set a hard-coded
   LIGHT background (#FFFAFA / #FFFDF6) and no ink at all, so .num / .lab / .den / .go kept
   inheriting --color-text - which is near-white in dark. Measured on dark: the .num, the
   30px COUNT ITSELF, at 1.17:1 and 1.19:1; .lab/.den 2.23-2.27:1; .go 1.43-1.59:1. These
   are the "needs attention" tiles, so the number that was invisible was the overdue count.
   Same class as the .due.overdue bug (#48), not cosmetic.

   The fix takes fill AND border from the semantic family and sets NO ink, so color:inherit
   keeps working in both appearances. The border derives at 25% (§5h) and its fallback for a
   browser without color-mix() is TRANSPARENT, never the old literal: a fallback degrades to
   ABSENCE, because #F3C0C0 is precisely the value that produces the bug this removes. */
.tile.urgent { background: var(--error-bg); border: 1px solid transparent; border-color: color-mix(in srgb, var(--error) 25%, transparent); }
.tile.warn { background: var(--warn-bg); border: 1px solid transparent; border-color: color-mix(in srgb, var(--warn) 25%, transparent); }

/* small flag chips (recurrence panel) - reuses the same neutral/semantic families as .due and .status */
.pill {
    display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: 999px;
    font-size: var(--t-xs); font-weight: 700; white-space: nowrap; background: var(--neutral-bg); color: var(--neutral);
}
.pill.flag-high { background: var(--error-bg); color: var(--error); }
.pill.flag-mid { background: var(--warn-bg); color: var(--warn); }

@media print {
    .no-print, .zone-hd form, .zone-hd select, .btn { display: none !important; }
    .zone, .tile, table { break-inside: avoid; }
}

/* ============================== split card (D-4b-5, spec §7e) ==============================
   A description block beside an action block, wrapping under a narrow viewport - not a one-off,
   it is the shape a card wants whenever it pairs "what this is" with "what to do about it" (4a's
   own summary wants it too). Replaces four inline style attributes that used to live in
   children/detail.html's case-file card. */
.split-card { display: flex; gap: var(--s5); align-items: flex-start; flex-wrap: wrap; }
.split-card-info { flex: 1 1 320px; }
.split-card-info h3 { margin-top: 0; }
.split-card-info > p { margin: 0; }
.split-card-info > p + p { margin-top: var(--s2); }
.split-card-actions { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-start; gap: var(--s2); }

/* ============================== audit feed (roadmap 2.5) ============================== */
.filters { display: flex; gap: var(--s3); flex-wrap: wrap; align-items: flex-end; margin: var(--s4) 0; }
.filters .f { display: flex; flex-direction: column; gap: var(--s1); }
.filters label { font-size: var(--t-xs); font-weight: 650; color: var(--muted); margin: 0; }
@media (max-width: 720px) { .filters .f { flex: 1 1 100%; } }

/* ============================== export consequence panel + manifest (roadmap 2.5) ============================== */
.consequence {
    border: 1px solid var(--border); border-left: 4px solid var(--accent); border-radius: 0 var(--radius) var(--radius) 0;
    background: var(--surface); padding: var(--s5); margin-bottom: var(--s4);
}
.manifest { list-style: none; margin: var(--s3) 0 0; padding: 0; font-size: var(--t-sm); }
.manifest li { display: flex; gap: var(--s3); align-items: flex-start; padding: 9px 0; border-bottom: 1px solid var(--border); }
.manifest li:last-child { border-bottom: 0; }
.manifest .mk-in { color: var(--ok); font-weight: 700; flex: 0 0 18px; }
.manifest .mk-out { color: var(--muted); font-weight: 700; flex: 0 0 18px; }
.manifest .lbl-wrap { flex: 1; }
.manifest .n { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.manifest li.out .lbl { color: var(--muted); }
.manifest .why { display: block; font-size: var(--t-xs); color: var(--muted); margin-top: 2px; }

.expires {
    display: inline-flex; align-items: center; gap: 6px; font-size: var(--t-xs); font-weight: 700;
    background: var(--warn-bg); color: var(--warn); padding: 3px 10px; border-radius: 999px;
}

/* ============================== 2a · queue filter chips ==============================
   D-2a-4: the chips are .seg/.seg-opt, the shipped single-choice treatment - the controller
   takes one `filter` param, so filtering IS single-choice, and .checkbox-option would promise
   a combination the backend cannot honour. The component ships keyed off `:has(input:checked)`
   because its first use was radio inputs; here the options are links, so selection arrives as
   aria-current instead. These two rules give a selected LINK the same accent ink plus inset
   ring - the ring is the point: it is what stops the active chip being colour alone.
   Additive only; the existing .seg-opt rules above are untouched. */
.seg-opt[aria-current] { color: var(--color-accent); box-shadow: inset 0 0 0 1px var(--color-accent); }
.seg-opt:not([aria-current]):hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
/* The chip row is a link group, so its options need the link ink reset and a wrap: a coordinator
   with a deep-linked filter on sees six chips, which will not fit one line on a narrow window. */
nav.seg { flex-wrap: wrap; text-decoration: none; }
nav.seg .seg-opt { color: inherit; text-decoration: none; }
/* Tabular figures so the counts do not jitter the chip widths between renders. */
.seg-count { font-variant-numeric: tabular-nums; opacity: 0.75; }

/* ============================== S-1/S-2 · the case card ==============================
   The row used by every list of PEOPLE OR CASES: the four interview lists through
   fragments/case-card.html, and 4d's user list directly on these classes. This said "every
   list of interview requests", which was true when it was written and stopped being true
   when 4d adopted the row - a scope clause is a claim in its own right, and this one would
   have gone on reading as a rule against reuse long after reuse was the intended thing. The
   columns are shared so a list scans down its own edges: identity, the screen's second
   fact, the two tags, one action with a MINIMUM width (D-2a-3 - 104px in the canvas is
   incidental; a fixed width truncates a longer label and breaks under translation, while
   a minimum still gives the list one aligned button edge, which is what that number was
   reaching for). Nothing above this block is edited. */
.case-group + .case-group { margin-top: var(--s5); }
.case-group-head {
    display: flex; align-items: center; gap: var(--s3);
    font-size: var(--t-md); margin: 0 0 var(--s3);
}
.case-group-count { color: var(--muted); font-weight: 400; font-variant-numeric: tabular-nums; }
/* T253 (spec 8f/9681e6d): seven card stacks across six templates were a visually-unambiguous list
   with no list in the markup (WCAG 2.2 1.3.1) - a <table> announced "table, N rows" and let a
   screen-reader user step through and skip it; a <div> stack announced nothing. The design (cards,
   not rows) is not in question - only that the container is now a <ul role="list"> and each card a
   <li role="listitem"> (fragments/case-card.html).
   THE ROLE ATTRIBUTES ARE LOAD-BEARING, NOT BELT-AND-BRACES. display:flex here overrides this
   element's list-item box generation, which is exactly the condition under which some engines stop
   inferring the implicit list/listitem role from the tag name - the same failure shape as
   list-style:none on a <ul> (the well-known Safari/VoiceOver case this pattern is named after).
   Swapping <div> for <ul>/<li> alone would look correct, pass review, and still announce nothing to
   the reader it was changed for. Do not let a future tidy-up remove role="list"/"listitem" as
   redundant with the tag - FrontendSourceGuardTest.everyFlexOrGridListHasAnExplicitListRole pins
   this mechanically, comments stripped first, so the removal fails a build rather than a review. */
.case-list { display: flex; flex-direction: column; gap: var(--s2); }
.case {
    display: flex; align-items: center; gap: var(--s4);
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); padding: var(--s3) var(--s4);
}
/* The link is the identity block, not the row: the whole-card target is deliberately
   rejected (S-1/D-2a-7), so the name is what carries the affordance. .rowlink already
   supplies the 44px minimum. */
.case-link { display: flex; align-items: center; gap: var(--s3); flex: 1.4 1 0; min-width: 0; }
.case-avatar {
    width: 38px; height: 38px; border-radius: 50%; flex: none;
    display: grid; place-items: center;
    background: var(--tint); color: var(--accent-dark);
    font-size: var(--t-sm); font-weight: 650;
}
.case-ident { min-width: 0; }
.case-name { display: block; font-size: var(--t-md); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.case-meta { display: block; font-size: var(--t-xs); font-weight: 400; color: var(--muted); }
.case-aside { flex: 1 1 0; min-width: 0; font-size: var(--t-sm); }
.case-aside .lbl {
    display: block; font-size: var(--t-xs); letter-spacing: 0.09em;
    text-transform: uppercase; color: var(--muted);
}
.case-tags { display: flex; align-items: center; gap: var(--s2); flex: none; }
.case-action { flex: none; display: flex; align-items: center; gap: var(--s2); min-width: 104px; }
.case-action .btn { margin-top: 0; width: 100%; }
/* The reason a card has no action (2d's self-review guard, D-2d-1): a sentence, never a
   disabled button - a disabled control is not focusable, so it cannot explain itself. */
.case-action .case-no-action { font-size: var(--t-xs); color: var(--muted); }

/* 4d additions. Deliberately NOT .case-action: that carries min-width:104px for a button, and
   an icon stretched to 104px is the width reaching for an aligned button edge on a row that
   has no button. */
.case-edit { flex: none; display: grid; place-items: center; color: var(--muted); }
.case-edit:hover { color: var(--accent-dark); }
/* T270: the human's report - "the orgs shift depending on the number of permissions". .case-tags
   is `flex: none` (intrinsic width), so a row with four role chips was WIDER than a row with one,
   and every flex sibling after it - .case-aside, the org/home column a person actually scans down
   - started further right. The defect was never the chips; it was that one column's horizontal
   POSITION depended on another column's CONTENT.
   A max-width alone is not enough - measured: it caps how wide the box CAN get, but below that
   cap a flex item with flex-basis:auto (what `flex: none` sets) still sizes to its own content, so
   a one-chip row and a four-chip row still differed even with both under a 200px ceiling. `width`
   sets the flex-basis directly with grow/shrink already at 0 from `flex: none`, so the box is the
   same size regardless of content, and overflow wraps to a second line WITHIN it - reusing the
   wrap this rule already had (below 720px), rather than a new overflow treatment. Scoped to
   .role-chips (used only here) so no other .case-list screen's typically-single status tag is
   affected. UserListRoleChipsUiTest pins the actual rendered position, not this declaration. */
.role-chips { flex-wrap: wrap; width: 200px; }
/* The username under the name, as a second identifier rather than a second name - monospace so
   a login key reads as one and cannot be mistaken for the person's own spelling of themselves. */
.case-username { font-family: ui-monospace, Menlo, Consolas, monospace; }
/* A disabled account is dimmed on the canvas. The dimming is reinforcement only - the "Disabled"
   chip carries the meaning - because opacity is a colour-only signal and 1.4.1 does not accept
   one alone. Held at .62 as drawn; the chip and the name stay above AA at that value because
   both sit on --surface rather than on the page ground. */
.case-inactive { opacity: 0.62; }

/* Below the shell's own breakpoint the row cannot hold five columns, so it stacks. The
   action goes full width rather than shrinking: it is the one thing on the card that is a
   tap target, and D-Q3's 44px is a floor, not a target to squeeze back towards. */
@media (max-width: 720px) {
    .case { flex-wrap: wrap; align-items: flex-start; }
    .case-link { flex: 1 1 100%; }
    .case-aside { flex: 1 1 100%; }
    .case-tags { flex: 1 1 100%; flex-wrap: wrap; }
    .case-action { flex: 1 1 100%; }
}
/* 2f's sent-back card: the reviewer's comment is what the visitor has to act on, so it
   sits on the card rather than one navigation away. A note breaks the row's single line,
   so the card becomes a two-row block; the label is what attributes the words to their
   author, without which "you must add the times" arrives unattributed. */
.case-with-note { flex-wrap: wrap; }
.case-note {
    flex: 1 1 100%;
    border-top: 1px solid var(--border);
    margin-top: var(--s2);
    padding-top: var(--s3);
}
.case-note .lbl {
    display: block; font-size: var(--t-xs); letter-spacing: 0.09em;
    text-transform: uppercase; color: var(--muted);
}
.case-note p { margin: var(--s1) 0 0; font-size: var(--t-sm); white-space: pre-wrap; }

/* ============================== 2c · compliance bars ==============================
   D-2c-1. The bar is decoration over a number the row already states in words, so it
   carries no ARIA and needs none - it is aria-hidden, and removing it loses nothing but
   the at-a-glance comparison between rows. It still takes 3:1 against its own track:
   an invisible encoding is not a safe encoding just because it is redundant. */
.rate { display: block; font-variant-numeric: tabular-nums; }
.bar {
    display: block; height: 6px; margin-top: 5px; min-width: 90px;
    border-radius: 999px; background: var(--neutral-bg); overflow: hidden;
}
.bar-fill { display: block; height: 100%; background: var(--accent-dark); border-radius: inherit; }

/* ============================== 2e · the raise-a-request form ==============================
   Four groups with a sticky rail. The rail is jump links, not tabs (D-1a-3's reasoning: a
   long form that is read and checked in full, and printed). scroll-margin-top on the
   headings so a jumped-to heading clears the sticky shell header instead of landing
   underneath it, and tabindex="-1" in the markup so the jump moves the READING CURSOR and
   not just the scroll position - without it a keyboard or screen-reader user is scrolled
   to a section their focus is nowhere near. */
.form-layout { display: grid; grid-template-columns: 180px minmax(0, 1fr); gap: var(--s5); align-items: start; }
.form-rail { position: sticky; top: 76px; display: flex; flex-direction: column; gap: 2px; }
.form-rail a {
    display: flex; align-items: center; min-height: 44px;
    padding: 0 var(--s3); border-radius: 6px;
    color: var(--muted); text-decoration: none; font-size: var(--t-sm);
}
.form-rail a:hover, .form-rail a:focus-visible { background: var(--tint); color: var(--accent-dark); }
.form-layout h3 { scroll-margin-top: 76px; }
/* The rail is a shortcut, not the only way through: below the breakpoint the form is one
   column and every section is a short scroll away, so the rail stops earning its width. */
@media (max-width: 900px) {
    .form-layout { grid-template-columns: 1fr; }
    .form-rail { display: none; }
}

/* ============================== 3a · branding live preview (spec §7j/§7k) ==============================
   Layout only - the appearance-driven token set each pane renders under comes from extending the
   existing [data-appearance="light"/"dark"] selector lists further up this file, not from anything
   declared here (D-3a-6). background/color are the one thing that HAS to be explicit: custom
   properties don't paint anything by themselves, and a nested pane's own --color-bg/--color-text
   would otherwise sit unused while body's ambient background showed through. */
.theme-preview-row { display: flex; gap: var(--s4); flex-wrap: wrap; margin: var(--s4) 0; }
.theme-preview {
    flex: 1 1 260px; padding: var(--s4); border-radius: var(--radius);
    background: var(--color-bg); color: var(--color-text);
}
.theme-preview-label {
    font-size: var(--t-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
    color: var(--color-text-muted); margin: 0 0 var(--s3);
}
.theme-preview h3 { margin: 0 0 var(--s2); }
.theme-preview p { margin: 0 0 var(--s3); }
.theme-preview .btn-row { margin-top: 0; }

/* ============================== 6e · the error page ==============================
   Centred, one card, no shell. The no-shell part is INHERITED, not decided here: error.html
   has never included fragments/layout's nav, and no note says why. It is left that way
   because the reason that suggests itself is a good one - a 403 is precisely the case where
   the sidebar's own links may be what the person cannot reach - but that is this author's
   reading of an existing choice, not a recorded one, and it should not harden into a rule
   by being written down confidently. Either way the page has to stand alone at every width. */
.error-page {
    min-height: 70vh; display: grid; place-items: center;
    padding: var(--s5); text-align: center;
}
.error-card {
    max-width: 46ch; display: flex; flex-direction: column;
    align-items: center; gap: var(--s3);
}
.error-icon { width: 2.5rem; height: 2.5rem; color: var(--muted); }
.error-card h1 { margin: 0; }
.error-body { margin: 0; color: var(--muted); line-height: 1.55; }
.error-actions { display: flex; gap: var(--s3); flex-wrap: wrap; justify-content: center; }
/* The status, demoted from the <h1> it used to be (FE-19). Small and muted because it is
   for quoting to someone else, not for reading first. */
.error-status { margin: var(--s2) 0 0; font-size: var(--t-xs); color: var(--muted); }
/* The 404's own message - which link was broken. Monospace because it usually contains an
   identifier, and an identifier read in prose type is one a person mis-transcribes. */
.error-detail {
    margin: 0; font-size: var(--t-sm); color: var(--muted);
    font-family: ui-monospace, Menlo, Consolas, monospace;
    overflow-wrap: anywhere;
}

/* ============================== 4e · the organisation tree ==============================
   Reuses the .case row from 4d/the interview lists rather than inventing a node component:
   a supplier and a care provider are both "one entity, its second fact, its tags", which is
   what that row already is. What the tree adds is only the INDENT and the rule that draws
   the relationship. */
.org-tree { display: flex; flex-direction: column; gap: var(--s5); margin-top: var(--s4); }
.org-branch { display: flex; flex-direction: column; gap: var(--s2); }
/* The children sit inside a left rule rather than on a bare margin, so the relationship is
   drawn rather than implied by whitespace - whitespace alone reads as a gap once the list is
   longer than one screen. --border, not the accent: the rule is structure, not emphasis. */
.org-children {
    display: flex; flex-direction: column; gap: var(--s2);
    margin-left: var(--s4); padding-left: var(--s4);
    border-left: 2px solid var(--border);
}
.org-children:empty { display: none; }
.org-supplier .case-avatar { background: var(--accent); color: var(--accent-ink); }
.org-icon { width: 1.25rem; height: 1.25rem; color: var(--muted); flex: none; }
.org-orphan-head { font-size: var(--t-md); margin: 0; }
/* The supplier and provider rows are not links - nothing is behind them yet. .case-link carries
   the layout, .rowlink carries the affordance, and only the second is omitted, so these read as
   information rather than as controls that do nothing (Creed's shell-org caret ruling). */
.org-tree .case-link { display: flex; align-items: center; gap: var(--s3); flex: 1.4 1 0; min-width: 0; }

@media (max-width: 720px) {
    .org-children { margin-left: var(--s2); padding-left: var(--s3); }
}

/* ============================== 6c · one audit event in full ==============================
   No new components: the record itself is a dl.detail, the absences are a plain list, and
   the permanence line is the only new piece because it is the only thing on the page that
   is an assurance rather than a field. */
.page-head { margin-bottom: var(--s4); }
.page-head h1 { margin: 0 0 var(--s1); }
.not-kept-head { font-size: var(--t-md); margin: 0 0 var(--s3); }
/* Dashes as list markers, matching the canvas: these rows are absences, and a bullet reads
   as a thing that is present. THE DASH IS aria-hidden MARKUP, NOT A ::before content STRING.
   I wrote the ::before first, reasoning that a pseudo-element is decorative by definition -
   it is not: AnnouncedGlyphSourceGuardTest caught it, and it is right that the major screen
   readers expose ::before content to the accessibility tree, where it is announced before the
   sentence it decorates. "Decorative" is a property of what the accessibility tree is told,
   not of which layer draws it. */
.not-kept { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s2); }
.not-kept li { display: flex; gap: var(--s3); color: var(--muted); font-size: var(--t-sm); }
.not-kept .mark { flex: none; }
.permanence {
    display: flex; align-items: flex-start; gap: var(--s3);
    margin: var(--s4) 0; padding: var(--s3) var(--s4);
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    font-size: var(--t-sm); color: var(--muted);
}
.permanence .icon { flex: none; margin-top: 2px; }
