﻿/* ============================================================
   Neuraplex theme — clinician-facing Web app
   ------------------------------------------------------------
   Modern, gradient-led but healthcare-appropriate. Layered on
   top of Bootstrap 5. WCAG 2.2 AA verified for every text-on-
   surface combination used.

   Brand spine: Neuraplex accent magenta — WCAG AAA palette.
   Per uicompliance/ui.design-system.md this is a third-party
   product — Neuraplex identity stays primary, NHS Service Manual
   informs *behaviour* (mandatory markers, error summary, focus
   indicators) not visual branding.

   Sections
     1.  Design tokens
     2.  Bootstrap variable bridge
     3.  Base + typography
     4.  Skip link
     5.  Header + nav (gradient)
     6.  Hero / surface system
     7.  Forms
     8.  Buttons (gradient)
     9.  Error summary + status panels
     10. Footer
     11. Utilities + motion + forced-colors
   ============================================================ */


/* 1. Design tokens
   ============================================================ */
:root {
    /* Brand spine — Neuraplex deep purple. WCAG AAA verified vs #FFFFFF:
         primary         #741F7B  9.48:1  buttons / UI components / large display
         hover           #581762  12.5:1  button hover
         active          #3D0F42  16.6:1  active / pressed / step tiles / strong text
         secondary       #4C2C92  9.40:1  inline accent text + links (AAA normal)
       Tints carry dark text only:
         very soft bg    #FBF7FC          page-level wash, hero gradients
         soft bg         #F7F0F8          eyebrow chips, subtle cards, sidebars
         medium tint     #EAD8EE          decorative separators (non-functional)
         strong border   #B57BB9  3.05:1  functional borders, chip outlines
       Cool neutral:
         neutral-100     #F2F1F4          inactive step backgrounds, idle chips */
    --np-color-brand-primary:        #741F7B;
    /* RGB triple of brand-primary, for rgba() washes/shadows so they follow the
       active theme. Kept in sync with --np-color-brand-primary by the preset. */
    --np-color-brand-primary-rgb:    116, 31, 123;
    --np-color-brand-primary-hover:  #581762;
    --np-color-brand-primary-active: #3D0F42;
    --np-color-brand-secondary:      #4C2C92;
    --np-color-brand-accent:         #B57BB9;
    --np-color-brand-tint-50:        #FBF7FC;
    --np-color-brand-tint-100:       #F7F0F8;
    --np-color-brand-tint-200:       #EAD8EE;
    --np-color-neutral-100:          #F2F1F4;

    /* Accent surfaces — kept available for gradients and decorative use.
       Purple tracks the brand primary RGB; indigo is the focus-fallback
       used on accent-filled or status surfaces where the purple would
       blend into the background. */
    --np-color-accent-magenta: #741F7B;
    --np-color-accent-indigo:  #0B3D91;

    /* Gradients — darker -> lighter at 100% so white text stays at least
       AA (4.5:1) across the full range. */
    --np-gradient-brand:        linear-gradient(135deg, #3D0F42 0%, #581762 55%, #741F7B 100%);
    --np-gradient-brand-soft:   linear-gradient(135deg, #581762 0%, #741F7B 100%);
    --np-gradient-button:       linear-gradient(180deg, #741F7B 0%, #581762 100%);
    --np-gradient-button-hover: linear-gradient(180deg, #581762 0%, #3D0F42 100%);
    --np-gradient-surface:      linear-gradient(180deg, #FFFFFF 0%, #FBF7FC 100%);
    --np-gradient-hero:
        radial-gradient(circle at 18% 18%, rgba(255, 255, 255, 0.18) 0%, rgba(255, 255, 255, 0) 45%),
        radial-gradient(circle at 82% 82%, rgba(var(--np-color-brand-primary-rgb), 0.30) 0%, rgba(var(--np-color-brand-primary-rgb), 0) 50%),
        linear-gradient(135deg, #3D0F42 0%, #581762 50%, #741F7B 100%);

    /* Neutrals — WCAG AAA verified vs #FFFFFF */
    --np-color-text-primary:   #3D0F42;   /* 16.6:1 vs white — AAA (brand-primary-active) */
    --np-color-text-secondary: #4C2C92;   /* 9.40:1 vs white — AAA (brand-secondary) */
    --np-color-text-muted:     #6B6474;   /* 5.68:1 vs white — AAA large / AA normal */
    --np-color-text-on-brand:  #FFFFFF;
    /* The page GROUND, distinct from a card's surface. In light they are both
       white and the distinction is invisible — which is exactly why it was
       never needed until now. In dark they must diverge: depth comes from
       surface lightness rather than shadow, so the ground goes darker than the
       cards sitting on it. Defined in both themes so `body` never has to be
       overridden per-theme. */
    --np-color-canvas:         #FFFFFF;
    /* The authenticated app shell's ground — the surface BEHIND the cards.
       Distinct from --np-color-canvas because the anonymous pages paint their
       own white/gradient ground.

       The brand's own very-soft wash, not a neutral grey. The shell is the
       ground BEHIND white cards: the old cool grey #E4E6EF was dark enough that
       every card read as a raised slab, and a neutral replacement left the app
       looking unbranded between the cards. #FBF7FC is the same tint the hero
       washes use, so white cards still separate cleanly while the ground itself
       stays on-brand. */
    --np-color-shell:          #FBF7FC;
    --np-color-surface:        #FFFFFF;
    --np-color-surface-muted:  #FBF7FC;   /* sidebars, sample cards, transcript panels */
    --np-color-surface-tinted: #F7F0F8;   /* eyebrow chips, subtle cards */
    --np-color-border:         #EAD8EE;   /* soft border — decorative separators */
    --np-color-border-strong:  #B57BB9;   /* strong border — functional UI, chip outlines */
    /* Search bars read better with a quieter, neutral outline than the purple
       functional border used by general form controls. Kept as dedicated tokens
       so the shared search styles below can stay search-only and theme-aware. */
    --np-color-search-border:          #D5D9E3;
    --np-color-search-border-hover:    #C6CCD8;
    --np-color-search-border-focus:    #B8C0CE;
    --np-color-search-border-disabled: #E4E7EE;
    /* Form-field outline — the ONE border every data-entry control draws (text,
       date, number, textarea, select, multi-select, token field, editor). The
       light brand tint the registration form set as the house look; it follows
       the organisation's brand preset through --np-color-brand-tint-200. Hover
       deepens to the accent and focus to the primary + halo, so the active field
       is still unmistakable. Change field borders HERE, never per control. */
    --np-color-field-border:          var(--np-color-brand-tint-200);
    --np-color-field-border-hover:    var(--np-color-brand-accent);
    /* The patient banner's own ground. A FILLED brand tint, not the white card
       wash: the banner is pinned to every patient screen and needs to read as
       chrome that belongs to the patient, distinct from the content scrolling
       under it. #EADBEE is the brand's medium tint — the same role
       --np-color-brand-tint-200 plays, and what each other preset supplies from
       its own ramp (ThemePreset.BuildCssVariables). */
    --np-color-banner-bg:      #EADBEE;
    /* Muted label colour ON that ground. --np-color-text-muted is derived
       against WHITE (5.68:1) and drops to 4.29:1 on #EADBEE — a fail at the
       12px label size (WCAG 1.4.3). Mixing the primary text INTO the ground
       keeps the muted step in the hierarchy while clearing AA, and because both
       operands are themed tokens the same declaration is correct in dark and on
       every preset — no per-theme value to keep in sync.
       Verified by PatientBannerContrastTests. */
    --np-color-banner-fg-muted: color-mix(in srgb, var(--np-color-text-primary) 75%, var(--np-color-banner-bg));

    /* Focus — brand purple on neutral surfaces (9.48:1, AAA).
       Use --np-color-focus-fallback (#0B3D91, 10.0:1) on accent-filled
       or status surfaces where the primary purple would lose contrast. */
    --np-color-focus:          #741F7B;
    --np-color-focus-fallback: #0B3D91;
    --np-color-focus-halo:     rgba(var(--np-color-brand-primary-rgb), 0.30);

    /* Status — paired with text + icon, never colour-only.
       Greens align with the sample's softer healthcare palette
       (#ECF8F0 / #174D2D / #B8DEC3); other tones retain proven AAA pairings. */
    /* One declaration per line — NOT cosmetic. ThemeTokenIntegrityTests scans
       for definitions with a line-anchored regex, so a second token sharing a
       line was invisible to it and got recorded as "pre-existing broken". Five
       of the six entries on that allowlist were this formatting artefact, not
       real defects. Keep one per line and the ratchet stays honest. */
    --np-color-info-bg:        #EAF1F8;
    --np-color-info-fg:        #103A6B;
    --np-color-success-bg:     #ECF8F0;
    --np-color-success-fg:     #174D2D;
    --np-color-success-border: #B8DEC3;
    --np-color-warning-bg:     #FFF6E0;
    --np-color-warning-fg:     #6B4F00;
    --np-color-error-bg:       #FBE7E5;
    --np-color-error-fg:       #B30000;

    /* Status FILLS — the same three meanings, for AREA marks: chart bars, band
       segments, progress fills. Deliberately separate from the -fg tokens above.
       Those are TEXT colours carrying a 7:1 target, which is why -warning-fg is a
       near-brown olive (#6B4F00) and -success-fg a near-black forest (#174D2D):
       correct as type, far too heavy as a bar. A large area mark only has to clear
       3:1 (WCAG 1.4.11 non-text contrast), and spending contrast it does not need
       is exactly what made the dashboard read as over-saturated — an olive bar
       where the eye expected amber.

       So: softer, and genuinely on-hue. Amber is amber rather than olive, danger
       is a brick rather than a signal red, success is a sage rather than a
       near-black. Each is still >= 3:1 against both --np-color-surface and
       --np-color-surface-tinted, which is the pair every card body uses.

       Like the -fg tokens, NO theme preset overrides these (§6.6): good, warning
       and danger must mean the same thing in every organisation, and every mark
       using them still ships with a dot + label, never colour alone. */
    --np-status-fill-success: #3D8A66;
    --np-status-fill-warning: #B4791A;
    --np-status-fill-danger:  #C0504A;
    /* The other two state hues the product's status vocabulary uses (§6.6):
       pending/in-progress blue and benign-neutral grey, at the same "area mark"
       weight as the three above — >= 3:1 against both card surfaces, softer
       than the -fg text tokens. Same rule: no preset overrides them, and every
       mark using them carries a label, never colour alone. */
    --np-status-fill-pending: #2E6DB8;
    --np-status-fill-neutral: #6E6875;

    /* Dashboard chart palette — the PRODUCT DEFAULT (Neuraplex Purple).
       Each theme preset overrides these via the inline :root block emitted by
       _NpThemeVars.cshtml; the defaults live here because that block is not
       emitted at all for an organisation on the default preset.

       s1-s6 are CATEGORICAL — used where the series ARE the subject (workload
       dimensions, activity lines). Assigned in order, never cycled: colour
       follows the entity, so filtering a series out must not repaint the rest.
       s1 is the brand lead; s2-s6 are fixed product-wide so identity encoding
       stays colour-blind-safe whatever the tenant's brand.

       o1-o6 are ORDINAL — one hue, light to dark, for ordered states (triage
       queue progression, waiting bands, funnel stages, age buckets).

       Status colours are NOT here. A chart segment that means good/warning/
       danger reads --np-color-success-* / -warning-* / -error-* so it means the
       same thing in every organisation (§6.6), and always ships with a dot +
       label — never colour alone.

       Generated in OKLCH and validated (lightness band, chroma floor, adjacent
       colour-blind separation, ramp monotonicity) by
       DashboardChartPaletteAccessibilityTests. Do not hand-edit a value here
       without re-running it. */
    --np-chart-s1: #A14DA8;  --np-chart-s2: #EB6834;  --np-chart-s3: #1BAF7A;
    --np-chart-s4: #EDA100;  --np-chart-s5: #2A78D6;  --np-chart-s6: #008300;
    --np-chart-o1: #E289E8;  --np-chart-o2: #CA73D0;  --np-chart-o3: #B35DB9;
    --np-chart-o4: #9C47A2;  --np-chart-o5: #85318C;  --np-chart-o6: #6F1976;

    /* Spacing */
    --np-space-1: 4px;
    --np-space-2: 8px;
    --np-space-3: 12px;
    --np-space-4: 16px;
    --np-space-5: 24px;
    --np-space-6: 32px;
    --np-space-7: 48px;
    --np-space-8: 64px;
    --np-space-9: 96px;

    /* Radii — generous, modern */
    --np-radius-1: 6px;
    --np-radius-2: 10px;
    --np-radius-3: 16px;
    --np-radius-pill: 999px;

    /* Shadows — multi-layer for depth without heaviness. The deep
       purple tint (61, 15, 66) matches brand-primary-active so card
       lift reads as "Neuraplex" rather than generic gray. */
    /* Elevation is carried by the 1px border, not by shadow. These stay defined
       (every surface references them) but at a weight that reads as a seam
       rather than a lift. --np-shadow-3 keeps real depth: it is only used by
       things that genuinely float — dialogs, popovers, drawers. */
    --np-shadow-1: 0 1px 1px rgba(61, 15, 66, 0.03);
    --np-shadow-2: 0 1px 3px rgba(61, 15, 66, 0.05);
    --np-shadow-3: 0 16px 40px rgba(61, 15, 66, 0.10), 0 4px 8px rgba(61, 15, 66, 0.05);
    --np-shadow-brand: 0 8px 24px rgba(var(--np-color-brand-primary-rgb), 0.22);

    /* Type */
    --np-font-sans: "Poppins", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif;
    --np-font-display: "Poppins", "Inter", "Segoe UI", system-ui, sans-serif;
    /* Normal text is 14px product-wide — the same size as Reminders, Notes, form
       controls, labels and buttons — so a screen reads at one text size. Headings
       keep their own steps below. The reader's Text size preference (np-text-size)
       still scales it up for anyone who needs larger text (WCAG 1.4.4). */
    --np-fs-body:    0.875rem;   /* 14px */
    --np-fs-small:   0.875rem;   /* 14px */
    /* Smallest body step. For SECONDARY metadata only (the muted second line in
       a dense table cell, compact status pills) — never for a value a clinician
       has to read to act. Stays usable at 200% zoom (WCAG 1.4.4). */
    --np-fs-xsmall:  0.75rem;    /* 12px */
    --np-fs-h1:      2.25rem;    /* 36px */
    --np-fs-h2:      1.625rem;   /* 26px */
    --np-fs-h3:      1.25rem;    /* 20px */
    --np-fs-display: 3rem;       /* 48px hero */

    --np-leading-body:   1.6;
    --np-leading-tight:  1.2;
    --np-tracking-tight: -0.015em;

    /* Legacy colour aliases — DEFINED, not decorative.
       ------------------------------------------------------------
       These names are referenced across ~200 sites in this file as
       `var(--np-color-danger-fg, #a41b17)` but were never actually declared,
       so every one of those calls silently resolved to its inline fallback.
       In light mode that was invisible — the fallbacks were hand-picked to
       look right. Under a theme switch it is fatal: a fallback is a literal,
       so those 200 sites would have stayed light-mode coloured on a dark page.

       Each is declared here at EXACTLY its existing fallback value, so light
       mode is byte-for-byte unchanged, and overridden in the dark block so all
       200 sites become theme-aware without touching a single call site.

       These are near-duplicates of the canonical tokens above (--np-color-danger-fg
       #a41b17 vs --np-color-error-fg #B30000, and so on). Reconciling them is a
       separate, visible change to light mode and deliberately NOT bundled into
       the dark-mode work — it needs its own review. Do not add new aliases here;
       new code uses the canonical tokens. */
    --np-color-text:            #0f172a;
    --np-color-link:            #1d4ed8;
    --np-color-primary:         #6b2f7a;
    --np-color-primary-50:      #f3e8f5;
    --np-color-primary-soft:    #e6f0ff;
    --np-color-brand-fg:        #5c1864;
    --np-color-surface-alt:     #f4f6f8;
    --np-color-surface-subtle:  transparent;
    --np-color-danger:          #c92420;
    --np-color-danger-bg:       #FBE7E9;
    --np-color-danger-fg:       #a41b17;
    --np-color-status-danger:   #C92420;
    --np-color-success:         #1f8a4c;
    --np-color-success-soft:    #e5f6ec;
    --np-color-success-strong:  #14663a;
}


/* 2. Bootstrap variable bridge — Bootstrap classes inherit Neuraplex theme.
   ============================================================ */
:root {
    --bs-primary: var(--np-color-brand-primary);
    --bs-primary-rgb: 116, 31, 123;
    --bs-link-color: var(--np-color-brand-secondary);
    --bs-link-hover-color: var(--np-color-brand-primary-hover);
    --bs-body-color: var(--np-color-text-primary);
    --bs-body-bg: var(--np-color-surface);
    --bs-border-color: var(--np-color-border);
    /* Bootstrap's own 16px body size follows the product's 14px normal text. */
    --bs-body-font-size: var(--np-fs-body);
    --bs-btn-font-size: var(--np-fs-body);
}

/* Bootstrap hard-codes 1rem (16px) on these controls rather than reading
   --bs-body-font-size; keep the few that remain in use on the 14px normal text. */
.form-control,
.form-select,
.input-group-text {
    font-size: var(--np-fs-body);
}


/* 2a. Dark appearance — TOKEN OVERRIDES ONLY
   ============================================================
   Applies when AppearancePreferenceResolver has stamped
   <html data-np-appearance="dark"> — server-side, before first paint
   (np-appearance-boot.js only corrects the "System" case). CLAUDE.md §441
   design record: .planning/Appearance-LightDark-Design.md.

   THE RULE FOR THIS BLOCK: it contains token redefinitions and nothing else.
   No component selectors, no `.np-card` in dark, no one-off fixes. If a
   component needs a rule here, that component has a hard-coded colour and the
   COMPONENT is what gets fixed. The moment this block grows component
   selectors it becomes a second stylesheet, and the two drift.

   Specificity note: this selector is (0,2,0) and the per-organisation brand
   block emitted inline by _NpThemeVars.cshtml is a bare `:root` at (0,1,0), so
   these win regardless of source order. That is correct for the default
   preset and WRONG for the other five — a non-default organisation currently
   loses its brand hue in dark mode. Emitting a dark variant per preset is
   slice 6; until then dark is verified against the default preset only.

   Contrast figures below are against this theme's own ground (#0F1116 canvas /
   #171A21 card), not against white. Every text token clears WCAG 2.2 AA at
   minimum; the body pair clears AAA.
   ============================================================ */
:root[data-np-appearance="dark"] {
    /* Brand spine, inverted the way the LIGHT theme already works rather than
       naively lightened. In light, -primary (#741F7B) is the FILL that white
       text sits on and -secondary (#4C2C92) is the TEXT colour for links. That
       split is what makes dark tractable:
         -primary   #9D4FA7  fill  — white text on it 5.1:1, and 3.3:1 against
                                     the card surface so the fill's own edge is
                                     a legible boundary (WCAG 1.4.11)
         -secondary #C89BD0  text  — 7.3:1 on the card surface, for links and
                                     inline accents
       Lightening -primary all the way to lavender would have looked right and
       broken every filled button, because white text cannot sit on it. */
    --np-color-brand-primary:        #9D4FA7;
    --np-color-brand-primary-rgb:    157, 79, 167;
    /* Hover goes LIGHTER here, where light mode goes darker. Reusing the light
       ramp is the classic dark-theme bug: the control visibly recedes on hover
       instead of coming forward. Capped at #A14DAB so white text stays >= 5:1. */
    --np-color-brand-primary-hover:  #A14DAB;
    --np-color-brand-primary-active: #B368BC;
    --np-color-brand-secondary:      #C89BD0;
    --np-color-brand-accent:         #B57BB9;

    /* Tints become the ELEVATION LADDER. In light these are washes on white;
       in dark they are progressively lighter surfaces, because depth here comes
       from lightness, not shadow. */
    --np-color-brand-tint-50:        #14171E;
    --np-color-brand-tint-100:       #1F2430;
    --np-color-brand-tint-200:       #2C303B;
    --np-color-neutral-100:          #232833;

    --np-color-accent-magenta: #9D4FA7;
    --np-color-accent-indigo:  #8FC0FF;

    /* Gradients sit in a 8-25% lightness band rather than light mode's 24-48%.
       A light-mode gradient on a dark page does not read as a header, it reads
       as a glowing slab. */
    --np-gradient-brand:        linear-gradient(135deg, #2A1430 0%, #3D1B45 55%, #52245C 100%);
    --np-gradient-brand-soft:   linear-gradient(135deg, #3D1B45 0%, #52245C 100%);
    --np-gradient-button:       linear-gradient(180deg, #9D4FA7 0%, #83398C 100%);
    --np-gradient-button-hover: linear-gradient(180deg, #A14DAB 0%, #8E4298 100%);
    --np-gradient-surface:      linear-gradient(180deg, #171A21 0%, #1C2029 100%);
    --np-gradient-hero:
        radial-gradient(circle at 18% 18%, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0) 45%),
        radial-gradient(circle at 82% 82%, rgba(var(--np-color-brand-primary-rgb), 0.25) 0%, rgba(var(--np-color-brand-primary-rgb), 0) 50%),
        linear-gradient(135deg, #2A1430 0%, #3D1B45 50%, #52245C 100%);

    /* Neutrals. Body text is #E6E4EA, NOT #FFFFFF — pure white on near-black is
       21:1, which sounds like a win and is punishing to read for a whole shift
       (halation). 13.6:1 is the comfortable band. */
    --np-color-text-primary:   #E6E4EA;   /* 13.6:1 on canvas */
    --np-color-text-secondary: #C6C1D0;   /*  9.9:1 */
    --np-color-text-muted:     #A29CAD;   /*  6.3:1 — still AA at body size */
    --np-color-text-on-brand:  #FFFFFF;
    /* The ground goes DARKER than the cards on it — the inversion of the light
       theme, where both are white. */
    --np-color-canvas:         #0F1116;
    /* Shell sits BELOW the cards on the elevation ladder, so it is the darkest
       surface in the theme — the inverse of light mode, where the shell grey is
       darker than the white cards for the same reason. */
    --np-color-shell:          #0B0D12;
    --np-color-surface:        #171A21;
    --np-color-surface-muted:  #1C2029;
    --np-color-surface-tinted: #232833;
    /* Borders carry MORE weight in dark, not less: a light-mode hairline simply
       disappears against a dark ground. */
    --np-color-border:         #2C303B;
    --np-color-border-strong:  #4A5060;   /* 3.2:1 — non-text minimum */
    --np-color-search-border:          #4A5060;
    --np-color-search-border-hover:    #5B6374;
    --np-color-search-border-focus:    #6A7386;
    --np-color-search-border-disabled: #3B404D;
    /* The dark brand tint is a near-black raised surface, so a field drawn in it
       would vanish; dark fields keep the quiet grey outline instead. */
    --np-color-field-border:          var(--np-color-border-strong);
    --np-color-field-border-hover:    #6A7386;
    /* Patient banner ground. Same ROLE as light's #EADBEE — the brand's medium
       tint — which on the dark ladder is a raised surface, not a wash. */
    --np-color-banner-bg:      #2C303B;
    /* DELIBERATELY the same expression as the light declaration, not a copied
       value. Both operands are redefined in this block, so the mix re-derives
       against the dark ground on its own; it is restated here because a colour
       token that appears only in light is exactly the mistake the dark-parity
       guard exists to catch, and silence would read as an oversight. If the
       percentage changes, change it in both places. */
    --np-color-banner-fg-muted: color-mix(in srgb, var(--np-color-text-primary) 75%, var(--np-color-banner-bg));

    --np-color-focus:          #D9B6DF;   /* 9.4:1 on canvas */
    --np-color-focus-fallback: #8FC0FF;
    --np-color-focus-halo:     rgba(var(--np-color-brand-primary-rgb), 0.45);

    /* Status — the §6.6 traffic light. MEANINGS DO NOT CHANGE: a Draft is blue
       in both themes, a not-started row is red in both. Only the rendered
       values move, so the -fg text clears 4.5:1 on its own -bg pill. Re-derived
       rather than inverted: inverting #B30000 gives a pink that reads as
       decorative, not as "nobody has touched this". */
    --np-color-info-bg:        #111F33;
    --np-color-info-fg:        #8FC0FF;
    --np-color-success-bg:     #12291D;
    --np-color-success-fg:     #7FD3A3;
    --np-color-success-border: #2F5C44;
    --np-color-warning-bg:     #2E240A;
    --np-color-warning-fg:     #E8BC55;
    --np-color-error-bg:       #331414;
    --np-color-error-fg:       #F58A80;

    /* Status FILLS — AREA marks (bars, band segments, progress fills), which
       carry a 3:1 non-text target, NOT the 7:1 the -fg text tokens carry. Same
       reasoning as the light set: spending contrast an area mark does not need
       is what makes a dashboard read as over-saturated. Each clears 3:1
       against both --np-color-surface and --np-color-surface-tinted. */
    --np-status-fill-success: #4CA87E;
    --np-status-fill-warning: #C9902E;
    --np-status-fill-danger:  #D2635B;
    --np-status-fill-pending: #4A87D0;
    --np-status-fill-neutral: #8A8494;

    /* Chart palettes are DELIBERATELY NOT overridden here.
       DefaultThemePresetCatalogue's own comment is the reason: every value is
       OKLCH-generated against a stated lightness band and re-checked on each
       build by DashboardChartPaletteAccessibilityTests (chroma floor, adjacent
       colour-blind separation, ramp monotonicity). Hand-picking six series and
       six ordinal steps here would pass review by eye and fail the generator.
       Slice 6 runs it against the dark band. Until then the light series render
       on dark surfaces — legible as area marks, but not yet verified. */

    /* Shadows go to true black and drop in spread. The purple-tinted lift that
       reads as "Neuraplex" on white is invisible on #0F1116; on dark, a shadow
       is a contact shadow, and the surface ladder above does the real work. */
    --np-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.40), 0 1px 1px rgba(0, 0, 0, 0.28);
    --np-shadow-2: 0 6px 18px rgba(0, 0, 0, 0.44), 0 1px 2px rgba(0, 0, 0, 0.30);
    --np-shadow-3: 0 16px 40px rgba(0, 0, 0, 0.52), 0 4px 8px rgba(0, 0, 0, 0.34);
    --np-shadow-brand: 0 8px 24px rgba(0, 0, 0, 0.45);

    /* Legacy colour aliases (see the light block for why they exist). Mapping
       them here is what makes ~200 existing `var(--np-color-…, #literal)` call
       sites theme-aware without editing any of them. */
    --np-color-text:            #E6E4EA;
    --np-color-link:            #8FC0FF;
    --np-color-primary:         #9D4FA7;
    --np-color-primary-50:      #1F2430;
    --np-color-primary-soft:    #232833;
    --np-color-brand-fg:        #C89BD0;
    --np-color-surface-alt:     #1C2029;
    --np-color-surface-subtle:  transparent;
    --np-color-danger:          #F58A80;
    --np-color-danger-bg:       #331414;
    --np-color-danger-fg:       #F58A80;
    --np-color-status-danger:   #F58A80;
    --np-color-success:         #7FD3A3;
    --np-color-success-soft:    #12291D;
    --np-color-success-strong:  #7FD3A3;

    /* Sidebar (np-aside). Its light-mode tokens are pinned to literal #FFFFFF
       and the legacy purple #741F7B, so without these the left nav stays a
       white slab against a near-black shell — the single most jarring thing in
       a dark build. The active item keeps WHITE text on a brand fill in both
       themes: that pairing is what makes the current page unmistakable, and it
       clears 5:1 against the dark fill. */
    --np-aside-bg:         #171A21;
    --np-aside-fg:         #C89BD0;
    --np-aside-fg-muted:   rgba(var(--np-color-brand-primary-rgb), 0.75);
    --np-aside-hover-bg:   rgba(var(--np-color-brand-primary-rgb), 0.18);
    --np-aside-active-bg:  #9D4FA7;
    --np-aside-active-fg:  #FFFFFF;
    --np-aside-divider:    rgba(var(--np-color-brand-primary-rgb), 0.28);

    /* Bootstrap bridge.
       --bs-primary-rgb is a literal triple, so unlike the other bridged vars it
       does not follow automatically.

       The rest of this block is the fix for the single widest dark-mode defect:
       views use Bootstrap UTILITY classes (`text-muted`, `border`, `bg-light`)
       alongside Neuraplex tokens — e.g. Users/Details renders every field label
       as `class="col-sm-4 text-muted"`. Those utilities resolve against
       Bootstrap's OWN variables, which section 2 never bridged because in light
       mode Bootstrap's defaults happened to look right. In dark they stay dark
       grey on a dark ground, which is why field labels across the master
       screens (Status, First name, Username, …) were unreadable while their
       values were fine.

       Bridging them here fixes every such screen at once, and keeps a view that
       reaches for a Bootstrap utility honest rather than silently broken. */
    --bs-primary-rgb:      157, 79, 167;
    --bs-secondary-color:  var(--np-color-text-muted);
    --bs-tertiary-color:   var(--np-color-text-muted);
    --bs-emphasis-color:   var(--np-color-text-primary);
    --bs-heading-color:    var(--np-color-text-primary);
    --bs-secondary-bg:     var(--np-color-surface-tinted);
    --bs-tertiary-bg:      var(--np-color-surface-muted);
    --bs-light:            var(--np-color-surface-tinted);
    --bs-light-rgb:        35, 40, 51;
    --bs-dark:             var(--np-color-text-primary);
    --bs-body-color-rgb:   230, 228, 234;
    --bs-body-bg-rgb:      23, 26, 33;
}


/* 3. Base + typography
   ============================================================ */
html {
    scroll-behavior: smooth;
}
body {
    background:
        radial-gradient(circle at top right, rgba(var(--np-color-brand-primary-rgb), 0.04) 0%, rgba(var(--np-color-brand-primary-rgb), 0) 35%),
        radial-gradient(circle at bottom left, rgba(var(--np-color-brand-primary-rgb), 0.04) 0%, rgba(var(--np-color-brand-primary-rgb), 0) 40%),
        var(--np-color-canvas);
    background-attachment: fixed;
    color: var(--np-color-text-primary);
    font-family: var(--np-font-sans);
    font-size: var(--np-fs-body);
    line-height: var(--np-leading-body);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--np-font-display);
    color: var(--np-color-text-primary);
    line-height: var(--np-leading-tight);
    letter-spacing: var(--np-tracking-tight);
    font-weight: 400;
}

/* ---------------------------------------------------------------------------
   Weight normalisation (product-wide).

   The product runs on a SINGLE weight: 400. Nothing is bolder, anywhere.
   Hierarchy is carried by SIZE and SPACING instead - h1/h2/h3 are 36/26/20px
   against 14px chrome, which separates them without reaching for weight. This
   is the product owner's call and it matches NHS Design System practice, where
   type scale rather than weight does the structural work.

   Component rules in this file all sit at 400, but three sources sit OUTSIDE
   them and reach every view in the product. They are why an earlier pass that
   fixed only component classes still left Details/Edit screens looking bold:

     - Bootstrap reboot   b, strong { font-weight: bolder }   (~420 elements)
     - Bootstrap reboot   dt        { font-weight: 700 }      (every dl-based
                                                               Details screen)
     - Bootstrap utils    .fw-bold / .fw-bolder / .fw-medium  (carry !important)
     - Bootstrap          .h1-.h6 { 500 } and .badge { 700 }  (the CLASS forms,
                                                               not the elements)

   bootstrap.min.css is vendored and loads BEFORE this file, so overriding here
   is the correct fix - never edit the vendored bundle, it is replaced wholesale
   on upgrade. The utility overrides carry !important only because Bootstrap's
   own declarations do.

   `th` is included for the browser default - UA stylesheets bold it, so a table
   header would otherwise be the one bold thing left on the page. Table headers
   now separate from body rows by their background tint and border rather than
   by weight; if that ever reads as too weak, change the TINT, not the weight.
   --------------------------------------------------------------------------- */
b, strong, dt, th, legend, optgroup {
    font-weight: 400;
}
.fw-bold, .fw-bolder, .fw-semibold,
.fw-medium, .fw-light, .fw-lighter, .fw-normal { font-weight: 400 !important; }
.h1, .h2, .h3, .h4, .h5, .h6 { font-weight: 400; }
.badge { font-weight: 400; }
h1 { font-size: var(--np-fs-h1); margin: 0 0 var(--np-space-5) 0; }
h2 { font-size: var(--np-fs-h2); margin: 0 0 var(--np-space-4) 0; }
h3 { font-size: var(--np-fs-h3); margin: 0 0 var(--np-space-3) 0; }

p { margin: 0 0 var(--np-space-4) 0; }
a { color: var(--np-color-brand-secondary); }
a:hover, a:focus { color: var(--np-color-brand-primary-hover); }

::selection { background: var(--np-color-brand-tint-200); color: var(--np-color-text-primary); }


/* 4. Skip link
   ============================================================ */
.np-skip-link {
    position: absolute;
    top: -48px;
    left: var(--np-space-3);
    padding: var(--np-space-3) var(--np-space-4);
    background: var(--np-color-brand-primary);
    color: var(--np-color-text-on-brand);
    text-decoration: none;
    border-radius: var(--np-radius-1);
    font-weight: 400;
    z-index: 1000;
    box-shadow: var(--np-shadow-2);
    transition: top 0.15s ease-in-out;
}
.np-skip-link:focus {
    top: var(--np-space-3);
    color: var(--np-color-text-on-brand);
    outline: 3px solid var(--np-color-focus);
    outline-offset: 3px;
}


/* 5. Header + nav (gradient spine)
   ============================================================ */
.np-header {
    background: var(--np-color-brand-tint-200);
    color: var(--np-color-brand-primary);
    position: relative;
    box-shadow: var(--np-shadow-brand);
}
.np-header::after {
    /* hairline highlight at the bottom edge */
    content: "";
    position: absolute;
    inset: auto 0 0 0;
    height: 1px;
    background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.35) 50%, rgba(255,255,255,0) 100%);
    pointer-events: none;
}
.np-header__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-4);
    padding: var(--np-space-5) 0;
}
.np-header__brand {
    color: var(--np-color-text-on-brand);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-3);
    font-weight: 400;
    font-size: 1.25rem;
    letter-spacing: -0.01em;
    transition: opacity 0.15s ease;
}
.np-header__brand:hover,
.np-header__brand:focus { color: var(--np-color-text-on-brand); opacity: 0.92; }
.np-header__brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    background: var(--np-color-brand-tint-200);
    color: var(--np-color-brand-primary);
    border: 1.5px solid rgba(255, 255, 255, 0.65);
    border-radius: var(--np-radius-2);
    font-weight: 400;
    letter-spacing: 0.02em;
    box-shadow: 0 4px 12px rgba(var(--np-color-brand-primary-rgb), 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

.np-nav {
    background: rgba(0, 0, 0, 0.18);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
.np-nav__list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-1);
    list-style: none;
    margin: 0;
    padding: var(--np-space-2) 0;
}
.np-nav__link {
    display: inline-block;
    padding: var(--np-space-2) var(--np-space-4);
    color: rgba(255, 255, 255, 0.92);
    text-decoration: none;
    font-weight: 400;
    border-radius: var(--np-radius-pill);
    transition: background-color 0.15s ease, color 0.15s ease;
}
.np-nav__link:hover,
.np-nav__link:focus {
    background: rgba(255, 255, 255, 0.12);
    color: var(--np-color-text-on-brand);
}
.np-nav__link[aria-current="page"] {
    background: var(--np-color-surface);
    color: var(--np-color-brand-primary);
    font-weight: 400;
}

/* Universal focus indicator — brand magenta on neutral surfaces (4.63:1). */
:focus-visible {
    outline: 3px solid var(--np-color-focus);
    outline-offset: 2px;
    border-radius: 4px;
}

/* Focus fallback — deep indigo (#0B3D91, 10.0:1) on accent-filled or
   status surfaces where the brand magenta would lose contrast.
   Note: .np-aside is intentionally NOT in this list — the aside surface
   is white, so the regular magenta focus indicator stays correct there. */
.np-header :focus-visible,
.np-hero :focus-visible,
.np-btn--primary:focus-visible,
.np-error-summary :focus-visible,
.np-error-summary:focus-visible {
    outline-color: var(--np-color-focus-fallback);
}


/* 6. Hero + surface system
   ============================================================ */
.np-hero {
    position: relative;
    background: var(--np-color-brand-tint-200);
    color: var(--np-color-brand-primary);
    border-radius: var(--np-radius-3);
    padding: var(--np-space-8) var(--np-space-7);
    margin-bottom: var(--np-space-7);
    overflow: hidden;
    box-shadow: var(--np-shadow-3);
}
.np-hero::before {
    /* subtle dot-grid pattern, only visible at full opacity in browsers
       that render radial-gradient masks well — purely decorative. */
    content: "";
    position: absolute;
    inset: 0;
    background-image: radial-gradient(rgba(255, 255, 255, 0.08) 1px, transparent 1px);
    background-size: 22px 22px;
    pointer-events: none;
}
.np-hero__content { position: relative; max-width: 720px; }
.np-hero__eyebrow {
    display: inline-block;
    background: rgba(255, 255, 255, 0.14);
    border: 1px solid rgba(255, 255, 255, 0.25);
    color: rgba(255, 255, 255, 0.95);
    padding: var(--np-space-1) var(--np-space-3);
    border-radius: var(--np-radius-pill);
    font-size: var(--np-fs-small);
    font-weight: 400;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    margin-bottom: var(--np-space-4);
}
.np-hero__title {
    font-size: var(--np-fs-display);
    color: var(--np-color-text-on-brand);
    margin: 0 0 var(--np-space-4) 0;
    line-height: 1.1;
    letter-spacing: -0.02em;
}
.np-hero__lede {
    font-size: 1.125rem;
    color: rgba(255, 255, 255, 0.92);
    margin: 0 0 var(--np-space-6) 0;
    max-width: 56ch;
}

.np-card {
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-3);
    padding: var(--np-space-6);
    box-shadow: var(--np-shadow-1);
    margin-bottom: var(--np-space-5);
}
.np-card--bleed { padding: 0; }


/* 7. Forms
   ============================================================ */
.np-form-required-note {
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
    margin-bottom: var(--np-space-5);
}
.np-form-section {
    border: 0;
    padding: 0;
    margin: 0 0 var(--np-space-7) 0;
}
.np-form-section legend {
    font-size: var(--np-fs-h3);
    font-weight: 400;
    margin-bottom: var(--np-space-4);
    padding: 0;
    color: var(--np-color-text-primary);
    width: 100%;
    border-bottom: 1px solid var(--np-color-border);
    padding-bottom: var(--np-space-2);
}
.np-form-group { margin-bottom: var(--np-space-4); }

/* Adaptive multi-column form grid — lays .np-form-group fields side by side to
   cut a long single-column form's vertical length (and the scrolling it forces)
   and to stop each input stretching the full page width. Apply as a modifier on
   a .np-form-section (or any wrapper of .np-form-group items).

   The column count is NOT fixed: auto-fit + minmax lets the grid fit as many
   ~320px columns as the card width allows, so the SAME utility gives a dense
   form (Appointment, User) three-to-four columns while a short form (Diagnosis,
   Role) shows one or two — no per-screen breakpoints to maintain.

   Accessibility / responsiveness:
   - min(100%, 320px) as the track minimum keeps a single column (never an
     overflow) once the container is narrower than one field, satisfying
     WCAG 2.2 1.4.10 (reflow — no horizontal scroll at 320px / 400% zoom).
   - align-items: start with any field hint rendered BELOW its input keeps
     every input on a row starting at the same height, so a field that carries
     a two-line hint no longer pushes its own input down out of alignment.
   - Source order == visual order (plain row auto-flow, no track placement),
     so the reading and Tab sequence stay meaningful (WCAG 1.3.2 / 2.4.3).
   - The column gap gives the fields clear separation; the row gap replaces
     each group's own bottom margin so spacing stays even. */
.np-form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
    column-gap: var(--np-space-5);
    row-gap: var(--np-space-3);
    align-items: start;
}
/* Fixed-column variants — use these when a section has a KNOWN small number of
   fields (and especially when it also has full-width rows like address lines or
   a textarea). The auto-fit default can't collapse trailing tracks once a
   full-width item is present, which leaves a short row of fields aligned left
   with blank space on the right; a fixed column count makes every row fill
   edge-to-edge (and the columns wider). They collapse responsively. */
.np-form-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.np-form-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.np-form-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1200px) {
    .np-form-grid--4 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
    .np-form-grid--3,
    .np-form-grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
    .np-form-grid--2,
    .np-form-grid--3,
    .np-form-grid--4 { grid-template-columns: 1fr; }
}
.np-form-grid > .np-form-group { margin-bottom: 0; }
/* Legend and any field that must keep the full width (wide textareas, multi-
   selects, info panels, conditional reveals) span every column. */
.np-form-grid > legend,
.np-form-grid > .np-form-grid__full { grid-column: 1 / -1; }
/* A wide control (e.g. a multi-select) that shares its row with one partner
   field in a 3/4-column grid: it takes two tracks so the row closes with no
   blank column. Collapses to the full row once the grid drops to 1–2 columns. */
.np-form-grid--3 > .np-form-grid__span-2,
.np-form-grid--4 > .np-form-grid__span-2 { grid-column: span 2; }
@media (max-width: 900px) {
    .np-form-grid--3 > .np-form-grid__span-2 { grid-column: 1 / -1; }
}
@media (max-width: 640px) {
    .np-form-grid--4 > .np-form-grid__span-2 { grid-column: 1 / -1; }
}
/* Hints render beneath the input in the grid (see markup), so they take a top
   margin instead of the default bottom margin and never sit between label and
   input where they would break input alignment across a row. */
.np-form-grid .np-hint { margin-top: var(--np-space-1); margin-bottom: 0; }

/* Multi-column form card. Fills the content width up to a generous ceiling so
   normal and large monitors are used fully while ultrawide displays don't
   stretch fields awkwardly. It also trims the vertical dead space a single
   long form otherwise accumulates. Used product-wide on master-data forms. */
.np-card--form { max-width: 1600px; }
/* Narrow variant for short forms (a handful of fields): a comfortable reading
   width so one or two fields sit sensibly instead of stretching across the
   page. Combine with .np-card--form. */
.np-card--form.np-card--form-narrow { max-width: 760px; }
/* Slightly reduced type scale — controls drop from 16px to 15px and
   labels/hints from 14px to 13px, keeping the form compact while staying above
   the readable floor for clinical use. */
.np-card--form .np-input,
.np-card--form .np-select,
.np-card--form textarea.np-input { font-size: 0.9375rem; } /* 15px */
.np-card--form .np-label,
.np-card--form .np-hint { font-size: 0.8125rem; }          /* 13px */
/* Collapse the gap between the LAST field section and the action bar: drop only
   the final section's bottom margin (multi-section forms keep the spacing
   BETWEEN their sections) and tighten the action bar's own top spacing so the
   buttons sit just below the fields, not a screen-height away. */
.np-card--form .np-form-section { margin-bottom: var(--np-space-3); }
.np-card--form .np-form-section:last-of-type { margin-bottom: 0; }
.np-card--form .np-form-actions {
    margin-top: var(--np-space-4);
    padding-top: var(--np-space-4);
}
/* Section header shares one line: title on the left, the required-fields note
   on the right (saves a whole row versus a separate note paragraph). The
   bottom margin is trimmed 25% (space-4 -> space-3) to tighten the gap between
   the divider line and the first field row. */
.np-card--form .np-form-section > legend {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    column-gap: var(--np-space-4);
    row-gap: var(--np-space-1);
    margin-bottom: var(--np-space-3);
}
.np-card--form .np-form-legend-note {
    font-weight: 400;
    font-size: 0.8125rem;
    color: var(--np-color-text-muted);
}
/* The legend is a space-between flex row (title left, "Fields marked *" right), so
   a help icon dropped into it lands against the far edge, detached from the title
   it belongs to. Absorbing the free space keeps it beside the words. */
.np-card--form .np-form-section > legend > .np-field-help,
.np-form-section > legend > .np-field-help {
    margin-right: auto;
}

.np-label {
    display: block;
    font-weight: 400;
    margin-bottom: var(--np-space-1);
    color: var(--np-color-text-primary);
    font-size: var(--np-fs-small);
    letter-spacing: 0.01em;
}
.np-hint {
    display: block;
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
    margin-bottom: var(--np-space-2);
}

/* Field help disclosure — the ⓘ beside a label (_NpFieldHelp.cshtml).
   Carries CONTEXT ("how is this value used later?"). Input rules, error text and
   clinical-safety warnings stay as a visible .np-hint above: a reader must not
   have to discover a format or a warning by pointing at something.
   Behaviour (hover / click / keyboard) lives in np-field-help.js. */
/* Sized to the LABEL'S OWN LINE BOX (16px against a 14px label), with
   line-height:0 so the inline icon cannot make one label taller than its
   neighbours. A label that grows by even 2px pushes its input down and breaks
   the row alignment of every field beside it in the form grid (§6.10). */
.np-field-help {
    position: relative;
    display: inline-flex;
    vertical-align: middle;
    margin-left: var(--np-space-1);
    line-height: 0;
}
.np-field-help__btn {
    width: 16px;
    height: 16px;
    padding: 0;
    display: inline-grid;
    place-items: center;
    /* ONE ring: this border, in the theme's LIGHT PRIMARY (brand accent) — in
       every state. The SVG inside draws only the "i". */
    border: 1px solid var(--np-color-brand-accent);
    border-radius: var(--np-radius-pill);
    background: var(--np-color-surface);
    color: var(--np-color-brand-primary);
    cursor: pointer;
    line-height: 0;
}
/* Belt and braces for the single ring: any copy of the icon that still draws its
   own circle inside the SVG (a hand-built one, or a page rendered from an older
   build of the partial) loses it here, so two outlines can never show. */
.np-field-help__btn svg circle { display: none; }
/* Hover / open: only a pale brand tint appears behind the "i". The ring stays
   the same light primary — not a darker border, and not a solid brand fill. */
.np-field-help__btn:hover,
.np-field-help__btn[aria-expanded="true"] {
    background: var(--np-color-surface-tinted);
    border-color: var(--np-color-brand-accent);
    color: var(--np-color-brand-primary);
}
/* Collapsed, but NOT display:none and NOT [hidden] — the field's
   aria-describedby points at this element, so it has to stay in the
   accessibility tree. Clipped rather than removed. */
.np-field-help__pop {
    position: absolute;
    top: calc(100% + 6px);
    left: -4px;
    z-index: 40;
    width: 1px;
    height: 1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    font-size: var(--np-fs-small);
    font-weight: 400;
    line-height: 1.5;
    color: var(--np-color-text-primary);
    text-align: left;
    white-space: normal;
}
/* Tone comes from the tinted ground and a border on ALL FOUR sides — never a
   left accent stripe (ui.design-system.md → "No Left-Accent Cards"). */
.np-field-help.is-open > .np-field-help__pop {
    width: min(300px, 76vw);
    height: auto;
    overflow: visible;
    clip-path: none;
    padding: var(--np-space-3);
    background: var(--np-color-surface-tinted);
    border: 1px solid var(--np-color-border-strong);
    border-radius: var(--np-radius-1);
    box-shadow: var(--np-shadow-3);
}
/* The right-hand column of a form grid would otherwise push the panel off-screen. */
.np-field-help.is-open > .np-field-help__pop--right {
    left: auto;
    right: -4px;
}
@media print {
    /* On paper there is no interaction left to recover the text with, so every
       explanation prints — the same decision the dashboard card info takes. */
    .np-field-help__pop {
        position: static;
        width: auto;
        height: auto;
        overflow: visible;
        clip-path: none;
        display: block;
        padding: 0;
        color: var(--np-color-text-muted);
    }
    .np-field-help__btn { display: none; }
}

/* A value the product supplies rather than the author (e.g. the template Name for
   a fixed product slot). Deliberately NOT a disabled <input>: a disabled control
   invites clicking, is skipped by the keyboard, and posts nothing — this is text.
   Sized to sit on the same baseline as the inputs beside it in a form grid. */
.np-static-value {
    display: block;
    margin: 0 0 var(--np-space-2);
    padding: 10px 0;
    font-weight: 400;
    color: var(--np-color-text-primary);
    min-height: 24px;
}

/* Inline label hint — keeps a field's short sub-description on the SAME line
   as its label instead of pushing it onto its own row. Apply the modifier to
   the <label> and put the descriptor in a trailing .np-label__hint span; it
   wraps gracefully on narrow viewports. Reusable across every form. */
.np-label--inline-hint {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: var(--np-space-2);
    row-gap: var(--np-space-1);
}
.np-label__hint {
    font-weight: 400;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
}
/* Input group — a unit or suffix shown INSIDE the right edge of an input
   ("2 | tablets", "5 | mL"). The suffix is text, not a control: it never takes
   a click (the input underneath does) and is associated with the input through
   aria-describedby, so a screen reader announces the unit with the value.
   Light grey from the theme's muted token, so it reads as a label for the
   value rather than part of it, and follows dark mode with the token.
   Reusable for any value with a fixed unit (mg, kg, %, days). */
.np-input-group {
    position: relative;
    display: block;
}
.np-input-group > .np-input {
    padding-right: calc(var(--np-space-3) + 5.5em);
}
.np-input-group__suffix {
    position: absolute;
    top: 50%;
    right: var(--np-space-3);
    transform: translateY(-50%);
    max-width: 5.5em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--np-fs-small);
    line-height: 1;
    color: var(--np-color-text-muted);
    pointer-events: none;
}
.np-input-group__suffix[hidden] { display: none; }
.np-input,
.np-select {
    display: block;
    width: 100%;
    padding: 10px var(--np-space-3);
    font-size: var(--np-fs-small);
    line-height: 1.5;
    color: var(--np-color-text-primary);
    background-color: var(--np-color-surface);
    border: 1px solid var(--np-color-field-border);
    border-radius: var(--np-radius-2);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.np-input:hover,
.np-select:hover { border-color: var(--np-color-field-border-hover); }
.np-input:focus,
.np-select:focus {
    outline: none;
    border-color: var(--np-color-brand-primary);
    box-shadow: 0 0 0 4px var(--np-color-focus-halo);
}

/* Shared search inputs (Patients, Enquiries, Users, masters, notification logs,
   DMS search, etc.). The layout still comes from .np-input / component-specific
   padding; only the border colour family changes from the default purple to a
   quieter neutral grey. */
input[type="search"].np-input:not(.np-input--error):not(.input-validation-error),
.np-dms__search-input {
    border-color: var(--np-color-search-border);
}
input[type="search"].np-input:not(.np-input--error):not(.input-validation-error):hover,
.np-dms__search-input:hover {
    border-color: var(--np-color-search-border-hover);
}
input[type="search"].np-input:not(.np-input--error):not(.input-validation-error):focus,
input[type="search"].np-input:not(.np-input--error):not(.input-validation-error):active,
.np-dms__search-input:focus,
.np-dms__search-input:active {
    border-color: var(--np-color-search-border-focus);
}
input[type="search"].np-input:disabled:not(.np-input--error):not(.input-validation-error),
.np-dms__search-input:disabled {
    border-color: var(--np-color-search-border-disabled);
}
form[role="search"] .np-select,
form[role="search"] .np-btn--secondary,
.np-calendar__nav .np-btn--secondary,
.np-calendar__filterbar .np-select,
.np-mycal__filterbar .np-select,
.np-dash__filters .np-select,
.np-mycal__filterbar .np-btn--secondary,
.np-mycal__datepicker {
    border-color: var(--np-color-search-border);
}
.np-input--error,
.np-select--error {
    border-color: var(--np-color-error-fg);
    box-shadow: 0 0 0 4px rgba(179, 0, 0, 0.12);
}
/* Locked input/select: value is fixed by a chosen slot (appointment booking).
   Still posts (readOnly / hidden mirror) but cannot be interacted with. */
.np-input--locked,
.np-select--locked {
    background-color: var(--np-color-neutral-100);
    color: var(--np-color-text-muted);
    cursor: not-allowed;
    pointer-events: none;
}
.np-input--error:focus,
.np-select--error:focus {
    box-shadow: 0 0 0 4px rgba(179, 0, 0, 0.18);
}
.np-field-error {
    display: block;
    color: var(--np-color-error-fg);
    font-weight: 400;
    font-size: var(--np-fs-small);
    margin-top: var(--np-space-2);
    padding-left: var(--np-space-3);
    border-left: 4px solid var(--np-color-error-fg);
}

/* ASP.NET Core unobtrusive-validation bridge.
   The framework auto-applies these classes when a model property fails
   validation. We mirror the np-input--error / np-field-error treatment
   so server- AND client-side validation looks identical without every
   view needing to remember the np-* error modifiers. */
.np-input.input-validation-error,
.np-select.input-validation-error,
input.input-validation-error,
select.input-validation-error,
textarea.input-validation-error {
    border-color: var(--np-color-error-fg);
    box-shadow: 0 0 0 4px rgba(179, 0, 0, 0.12);
}
.np-input.input-validation-error:focus,
.np-select.input-validation-error:focus,
input.input-validation-error:focus,
select.input-validation-error:focus,
textarea.input-validation-error:focus {
    box-shadow: 0 0 0 4px rgba(179, 0, 0, 0.18);
    outline: none;
}
.field-validation-error {
    display: block;
    color: var(--np-color-error-fg);
    font-weight: 400;
    font-size: var(--np-fs-small);
    margin-top: var(--np-space-2);
    padding-left: var(--np-space-3);
    border-left: 4px solid var(--np-color-error-fg);
}
/* Bootstrap default validation-summary container — match np-error-summary. */
.validation-summary-errors {
    border: 2px solid var(--np-color-error-fg);
    background: var(--np-color-error-bg);
    padding: var(--np-space-4) var(--np-space-5);
    margin-bottom: var(--np-space-5);
    border-radius: var(--np-radius-2);
    color: var(--np-color-error-fg);
    box-shadow: var(--np-shadow-1);
}
.validation-summary-errors ul {
    margin: 0;
    padding-left: var(--np-space-5);
}
.validation-summary-errors li { margin-bottom: var(--np-space-1); }
.validation-summary-valid { display: none; }

.np-checkbox-group {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-3) var(--np-space-5);
    padding: var(--np-space-3) 0;
}
.np-checkbox {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    cursor: pointer;
    padding: var(--np-space-2) var(--np-space-3);
    border-radius: var(--np-radius-1);
    transition: background-color 0.15s ease;
}
.np-checkbox:hover { background: var(--np-color-brand-tint-50); }
.np-checkbox input[type="checkbox"] {
    width: 20px;
    height: 20px;
    accent-color: var(--np-color-brand-primary);
}

/* ============================================================
   App-wide themed checkbox / radio (both states follow the theme)

   Native checkboxes/radios paint an unthemed grey/black box — `accent-color`
   only tints the CHECKED fill, never the unchecked border. Here we render the
   box from the brand tokens while keeping the real <input> (appearance:none),
   so keyboard, focus, indeterminate and form-posting are unchanged.

   Wrapped in :where() so it has ZERO specificity: it is a base every component
   (np-checkbox, np-role-assign__check, the .np-pre form tick-items, …) overrides
   for size/border without needing !important, and it never fights an existing
   custom control (a visually-hidden radio stays hidden; a sized checkbox keeps
   its size). Status colours are untouched (CLAUDE.md §6.6).
   ============================================================ */
:where(input[type="checkbox"], input[type="radio"]) {
    appearance: none;
    -webkit-appearance: none;
    box-sizing: border-box;
    width: 16px;
    height: 16px;
    margin: 0;
    border: 1.5px solid var(--np-color-border-strong);
    background: var(--np-color-surface, #fff) no-repeat center / 68% 68%;
    cursor: pointer;
    vertical-align: middle;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}
:where(input[type="checkbox"]) { border-radius: 4px; }
:where(input[type="radio"]) { border-radius: 50%; }
:where(input[type="checkbox"]:checked) {
    border-color: var(--np-color-brand-primary);
    background-color: var(--np-color-brand-primary);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23fff' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round' d='M3.5 8.5l3 3 6-6.5'/%3E%3C/svg%3E");
}
:where(input[type="checkbox"]:indeterminate) {
    border-color: var(--np-color-brand-primary);
    background-color: var(--np-color-brand-primary);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-width='2.4' stroke-linecap='round' d='M4 8h8'/%3E%3C/svg%3E");
}
:where(input[type="radio"]:checked) {
    border-color: var(--np-color-brand-primary);
    background: radial-gradient(circle at center, var(--np-color-brand-primary) 0 42%, #fff 47% 100%);
}
:where(input[type="checkbox"], input[type="radio"]):focus-visible {
    outline: 2px solid var(--np-color-focus, var(--np-color-brand-primary));
    outline-offset: 2px;
}
:where(input[type="checkbox"]:disabled, input[type="radio"]:disabled) {
    opacity: 0.5;
    cursor: not-allowed;
}


/* 8. Buttons (gradient, with hover lift)
   ============================================================ */
.np-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--np-space-2);
    /* Proportioned to the 14px text: 16px sides (24px read as a padded slab
       around a 14px label) and a 40px height that lines up with the ~42px inputs
       beside it. Still far above the WCAG 2.2 AA target minimum (24px, SC 2.5.8);
       touch screens get the full 44px below. */
    padding: 8px var(--np-space-4);
    font-family: inherit;
    font-size: var(--np-fs-small);
    font-weight: 400;
    line-height: 1.25;
    border-radius: var(--np-radius-2);
    border: 1.5px solid transparent;
    cursor: pointer;
    text-decoration: none;
    min-height: 40px;
    transition: transform 0.12s ease, box-shadow 0.15s ease, background 0.15s ease, color 0.15s ease;
    white-space: nowrap;
}
/* Tablets / phones: a finger needs the full 44px target (WCAG 2.5.5). The --xs
   inline-link variant keeps its own zero height. */
@media (pointer: coarse) {
    .np-btn:not(.np-btn--xs) { min-height: 44px; }
}
.np-btn:active { transform: translateY(1px); }

/* Flat brand fill. The gradient + glow + 1px lift read as a marketing CTA; on a
   clinical screen the primary action should be unmistakable but quiet. Hover
   darkens the fill, which is a state change the eye reads just as fast and does
   not move the target under the pointer. */
.np-btn--primary {
    background: var(--np-color-brand-primary);
    color: var(--np-color-text-on-brand);
    border-color: var(--np-color-brand-primary);
    box-shadow: none;
}
.np-btn--primary:hover,
.np-btn--primary:focus {
    background: var(--np-color-brand-primary-hover);
    border-color: var(--np-color-brand-primary-hover);
    color: var(--np-color-text-on-brand);
    box-shadow: none;
}
.np-btn--primary:active { background: var(--np-color-brand-primary-active); }

.np-btn--secondary {
    background: var(--np-color-surface);
    color: var(--np-color-brand-primary);
    border-color: var(--np-color-brand-primary);
}
.np-btn--secondary:hover,
.np-btn--secondary:focus {
    background: var(--np-color-brand-tint-50);
    color: var(--np-color-brand-primary-hover);
    border-color: var(--np-color-brand-primary-hover);
    box-shadow: var(--np-shadow-1);
}
.np-btn--link {
    background: transparent;
    border-color: transparent;
    color: var(--np-color-brand-secondary);
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 4px;
    min-height: auto;
    padding: var(--np-space-1) var(--np-space-2);
}
.np-btn--link:hover,
.np-btn--link:focus { color: var(--np-color-brand-primary-hover); }


/* 9. Error summary + status panels
   ============================================================ */
.np-error-summary {
    border: 2px solid var(--np-color-error-fg);
    background: var(--np-color-error-bg);
    padding: var(--np-space-5);
    margin-bottom: var(--np-space-6);
    border-radius: var(--np-radius-2);
    box-shadow: var(--np-shadow-1);
}
.np-error-summary__title {
    color: var(--np-color-error-fg);
    margin: 0 0 var(--np-space-3) 0;
    font-size: var(--np-fs-h3);
    font-weight: 400;
}
.np-error-summary__list { list-style: none; padding: 0; margin: 0; }
.np-error-summary__list li { margin-bottom: var(--np-space-2); }
.np-error-summary__list a {
    color: var(--np-color-error-fg);
    font-weight: 400;
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 3px;
}
.np-error-summary:focus { outline: 3px solid var(--np-color-focus); outline-offset: 3px; }

/* Borderless by design (updated design language): a panel is a clean tinted
   background with spacing — no left accent stripe. Tone comes from the
   background + text colour pair, never a border. Do NOT reintroduce a
   border-left here or on a variant; the one sanctioned exception is
   --prominent below (a panel that demands a decision), which must stay an
   explicit opt-in. The border-color on each variant exists solely so
   --prominent knows what colour its border takes. */
.np-panel {
    padding: var(--np-space-4) var(--np-space-5);
    border-radius: var(--np-radius-2);
    margin-bottom: var(--np-space-5);
    box-shadow: var(--np-shadow-1);
}
.np-panel--success { background: var(--np-color-success-bg); border-color: var(--np-color-success-fg); color: var(--np-color-success-fg); }
.np-panel--info    { background: var(--np-color-info-bg);    border-color: var(--np-color-info-fg);    color: var(--np-color-info-fg); }
.np-panel--warning { background: var(--np-color-warning-bg); border-color: var(--np-color-warning-fg); color: var(--np-color-warning-fg); }
.np-panel--danger  { background: #fde7e5; border-color: var(--np-color-danger-fg, #a41b17); color: var(--np-color-danger-fg, #a41b17); }

/* Full-border emphasis for panels that demand a decision (e.g. reorder confirmation).
   The base .np-panel is borderless; --prominent boxes it on all sides — the one
   sanctioned bordered-panel treatment. */
.np-panel--prominent { border-style: solid; border-width: 2px; }
.np-panel--prominent:focus { outline: 3px solid var(--np-color-focus); outline-offset: 3px; }


/* 10. Footer (multi-column professional layout)
   ============================================================ */
.np-footer {
    background: var(--np-color-surface-tinted);
    color: var(--np-color-text-secondary);
    border-top: 1px solid var(--np-color-border);
    margin-top: auto;
}
.np-footer__inner {
    display: grid;
    grid-template-columns: 2fr 1.2fr 1fr;
    gap: var(--np-space-7);
    padding: var(--np-space-7) var(--np-space-6) var(--np-space-5);
    max-width: 1440px;
    margin: 0 auto;
    width: 100%;
}
.np-footer__col { min-width: 0; }
.np-footer__col--brand { padding-right: var(--np-space-5); }
.np-footer__product {
    margin: 0 0 var(--np-space-2);
    font-weight: 400;
    font-size: 1.0625rem;
    color: var(--np-color-text-primary);
}
.np-footer__tagline {
    margin: 0 0 var(--np-space-3);
    color: var(--np-color-text-secondary);
    max-width: 42ch;
}
.np-footer__heading {
    margin: 0 0 var(--np-space-3);
    font-weight: 400;
    color: var(--np-color-text-primary);
    font-size: var(--np-fs-small);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.np-footer__body {
    margin: 0 0 var(--np-space-2);
    color: var(--np-color-text-secondary);
}
.np-footer__list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--np-space-1); }
.np-footer__link {
    color: var(--np-color-brand-secondary);
    text-decoration: none;
    font-weight: 400;
}
.np-footer__link:hover,
.np-footer__link:focus { color: var(--np-color-brand-primary-hover); text-decoration: underline; }
.np-footer__meta {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
}
/* The NP product mark, bottom right of the footer — the sidebar's top slot
   belongs to the customer's logo. Same tile as the old sidebar mark, scaled
   to sit on one line of footer text. */
.np-footer__mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    flex: 0 0 22px;
    border-radius: var(--np-radius-2);
    background: var(--np-color-brand-primary);
    color: #FFFFFF;
    font-size: var(--np-fs-xsmall);
    letter-spacing: 0.02em;
    margin-top: 5px;
}
.np-footer__bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-3);
    padding: var(--np-space-4) var(--np-space-6);
    border-top: 1px solid var(--np-color-border);
    background: var(--np-color-surface-tinted);
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
    max-width: 1440px;
    margin: 0 auto;
    width: 100%;
}
@media (max-width: 991.98px) {
    .np-footer__inner { grid-template-columns: 1fr 1fr; gap: var(--np-space-5); padding: var(--np-space-6) var(--np-space-5) var(--np-space-4); }
    .np-footer__col--brand { grid-column: 1 / -1; padding-right: 0; }
}
@media (max-width: 575px) {
    .np-footer__inner { grid-template-columns: 1fr; gap: var(--np-space-5); }
    .np-footer__bar { flex-direction: column; align-items: flex-start; }
}


/* 11. Layout, utilities, motion, forced-colors
   ============================================================ */
.np-container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 var(--np-space-5);
}
.np-main { padding: var(--np-space-7) 0 var(--np-space-9) 0; }

.np-form-actions {
    display: flex;
    gap: var(--np-space-3);
    flex-wrap: wrap;
    margin-top: var(--np-space-6);
    padding-top: var(--np-space-5);
    border-top: 1px solid var(--np-color-border);
}

.np-eyebrow {
    display: inline-block;
    color: var(--np-color-brand-secondary);
    font-size: var(--np-fs-small);
    font-weight: 400;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    margin-bottom: var(--np-space-2);
}

/* One-line supporting hint under a page's h1 — muted and small so it reads as
   secondary guidance, never competing with the heading. Reusable on any page
   that needs a quiet usage note beside its title.

   Hidden by default and revealed when the page-head is hovered/focused so the
   heading stays clean while the hint remains discoverable. */
.np-page-hint {
    display: none;
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
    margin: calc(-1 * var(--np-space-2)) 0 var(--np-space-4);
}
.np-page-head:hover > .np-page-hint,
.np-page-head:focus-within > .np-page-hint {
    display: block;
}

/* Page-title row: the h1 and its supporting hint share one line, aligned on the
   h1's text baseline so the hint reads as part of the same heading line. Wraps
   on narrow viewports rather than truncating either text. */
.np-page-head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    column-gap: var(--np-space-3);
    margin-bottom: var(--np-space-5);
}
.np-page-head > h1 { margin-bottom: 0; }
.np-page-head > .np-page-hint { margin: 0; }

.np-feature-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: var(--np-space-5);
    margin-top: var(--np-space-5);
}
.np-feature-card {
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-3);
    padding: var(--np-space-5);
    box-shadow: var(--np-shadow-1);
    transition: transform 0.15s ease, box-shadow 0.2s ease;
}
.np-feature-card:hover { transform: translateY(-2px); box-shadow: var(--np-shadow-2); }
.np-feature-card__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: var(--np-radius-2);
    background: var(--np-color-brand-tint-100);
    color: var(--np-color-brand-primary);
    font-weight: 400;
    margin-bottom: var(--np-space-3);
}

/* Tables — refined to match the modern surface system. */
.table { color: var(--np-color-text-primary); }
.table > thead { background: var(--np-color-brand-tint-50); }
.table > thead > tr > th {
    color: var(--np-color-text-secondary);
    font-weight: 400;
    font-size: var(--np-fs-small);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border-bottom: 1px solid var(--np-color-border-strong);
    /* A header is one short uppercase line: it must not inherit the body's 1.6
       reading line-height, which (with 12px padding) stretched every header row
       to ~46px. Tight leading + 10px padding keeps it a compact ~38px strip. */
    line-height: 1.3;
    padding: 10px var(--np-space-4);
    vertical-align: middle;
}
/* Body cells sit on the SAME step as the column headers (14px), not the 16px
   page body. A list is scanned down a column, not read as prose, and the two
   were a step apart for no reason — the header already carries its emphasis
   through uppercase and letter-spacing, so matching the sizes costs no
   hierarchy and buys back a row of height on every long list. */
.table > tbody > tr > td {
    padding: var(--np-space-4);
    border-color: var(--np-color-border);
    font-size: var(--np-fs-small);
}
/* Hover tint applied to every cell rather than the row, because Bootstrap
   sets an implicit --bs-table-bg on td which would otherwise paint over
   any tr-level background. Tinting each td keeps every column highlighted,
   including the sticky Actions column (which retains its own white
   background by default for the scroll-shadow effect). */
.table > tbody > tr:hover > td { background: var(--np-color-brand-tint-50); }


/* ---------- Sortable column header
   The <th> is rendered by _NpSortHeader.cshtml and exposes aria-sort
   so screen-readers announce the active sort state. The link inside
   spans the header cell so the whole column heading is clickable. */
.np-sort-th { white-space: nowrap; }

.np-sort__link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 6px;
    margin: -4px -6px;
    color: inherit;
    text-decoration: none;
    border-radius: var(--np-radius-1);
    transition: background-color 0.12s ease, color 0.12s ease;
}
.np-sort__link:hover,
.np-sort__link:focus {
    background: rgba(var(--np-color-brand-primary-rgb), 0.10);
    color: var(--np-color-brand-primary);
    text-decoration: none;
}
.np-sort__link:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 2px;
}

.np-sort__label { line-height: 1.2; }

.np-sort__indicator {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    color: var(--np-color-text-muted);
    transition: color 0.12s ease, transform 0.12s ease;
}
.np-sort__indicator svg { width: 12px; height: 12px; }
.np-sort__indicator--asc,
.np-sort__indicator--desc { color: var(--np-color-brand-primary); }
.np-sort__indicator--idle { opacity: 0.55; }
.np-sort__link:hover .np-sort__indicator--idle,
.np-sort__link:focus .np-sort__indicator--idle {
    color: var(--np-color-brand-primary);
    opacity: 1;
}

.np-sort-th--active {
    color: var(--np-color-brand-primary) !important;
    background: var(--np-color-brand-tint-200) !important;
    box-shadow: inset 0 -2px 0 var(--np-color-brand-primary);
}

@media (forced-colors: active) {
    .np-sort__link { border: 1px solid CanvasText; }
    .np-sort-th--active { outline: 2px solid CanvasText; }
}


/* Motion preferences — disable transitions for users who request it. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        transition-duration: 0.001s !important;
        animation-duration: 0.001s !important;
    }
    html { scroll-behavior: auto; }
}

/* Forced-colors / Windows high contrast — keep semantics visible */
@media (forced-colors: active) {
    .np-btn,
    .np-header,
    .np-nav,
    .np-card,
    .np-feature-card,
    .np-hero { border: 1px solid; }
    .np-btn--primary { background: ButtonFace; color: ButtonText; }
    .np-hero { background: Canvas; color: CanvasText; }
    :focus-visible { outline: 3px solid CanvasText; }
}

/* Small screens */
@media (max-width: 575px) {
    h1 { font-size: 1.625rem; }
    h2 { font-size: 1.25rem; }
    .np-hero { padding: var(--np-space-6) var(--np-space-5); border-radius: var(--np-radius-2); }
    .np-hero__title { font-size: 2rem; }
    .np-hero__lede { font-size: var(--np-fs-small); }
    .np-card { padding: var(--np-space-5); border-radius: var(--np-radius-2); }
    .np-nav__list { padding: var(--np-space-1) 0; }
}


/* 12. Shell + left aside navigation
   ============================================================
   The shell replaces the legacy top-header layout with a fixed
   left aside (NavRegistry-driven) and a main column that owns
   topbar / content / footer. On screens <992px the aside slides
   in from the left as a drawer over a backdrop.
   ============================================================ */
:root {
    /* Sidebar — white surface with legacy Neuraplex purple (#741F7B) text.
       Active item inverts: solid purple bg + white text for clear focus. */
    --np-aside-width: 256px;
    --np-aside-bg: #FFFFFF;
    --np-aside-fg: #741F7B;
    --np-aside-fg-muted: rgba(var(--np-color-brand-primary-rgb), 0.65);
    --np-aside-hover-bg: rgba(var(--np-color-brand-primary-rgb), 0.08);
    --np-aside-active-bg: #741F7B;
    --np-aside-active-fg: #FFFFFF;
    --np-aside-divider: rgba(var(--np-color-brand-primary-rgb), 0.12);
    --np-topbar-height: 56px;
}

.np-shell-body {
    margin: 0;
    min-height: 100vh;
    /* Was a literal #E4E6EF, which stayed light grey in dark mode and left the
       whole authenticated shell on a light ground. The app shell sits BEHIND
       the cards, so it takes the page ground, not the card surface. */
    /*background: var(--np-color-shell);*/
    background: #eef0f8;
    background-attachment: fixed;
}
.np-shell-body--locked { overflow: hidden; }

.np-shell {
    display: flex;
    min-height: 100vh;
}

.np-aside {
    flex: 0 0 var(--np-aside-width);
    width: var(--np-aside-width);
    background: var(--np-aside-bg);
    color: var(--np-aside-fg);
    /* A seam, not a cast shadow: the rail is flush with the page, so it should
       be divided from it rather than floating over it. */
    border-right: 1px solid var(--np-aside-divider);
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
    z-index: 30;
}

.np-aside__brand {
    /* Exactly as tall as the topbar, so the two bottom borders form ONE
       continuous line across the shell instead of a step. The block was
       24px + 38px mark + 24px = 87px against a 48px header, which read as a
       heavy slab and pushed the whole menu down by ~39px.

       Height comes from the shared token rather than a literal, so changing the
       topbar height keeps the seam aligned. min-height (not height) so a longer
       organisation name wraps rather than being clipped. */
    display: flex;
    align-items: center;
    min-height: var(--np-topbar-height);
    /* Aligns the brand mark with the nav icons below it: the nav's own padding
       plus each item's padding is the column every icon starts at. */
    padding: 0 calc(var(--np-space-2) + var(--np-space-3));
    border-bottom: 1px solid var(--np-aside-divider);
}
.np-aside__brand-link {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-3);
    color: var(--np-aside-fg);
    font-weight: 400;
    font-size: 1.125rem;
    letter-spacing: -0.01em;
    text-decoration: none;
}
.np-aside__brand-link:hover,
.np-aside__brand-link:focus { color: var(--np-aside-fg); opacity: 0.85; }
.np-aside__brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 32px leaves 8px of clearance inside the 48px bar. At the old 38px the
       mark filled the bar edge to edge with nothing to breathe. */
    width: 32px;
    height: 32px;
    flex: 0 0 32px;
    background: var(--np-color-brand-primary);
    border: 0;
    border-radius: var(--np-radius-2);
    color: #FFFFFF;
    font-weight: 400;
    letter-spacing: 0.02em;
    box-shadow: none;
}

/* Customer logo at the top of the menu (_SidebarBrand). The shared placement
   box fits any artwork; in the menu it reads from the left edge like the nav
   below it, and the name-only wordmark takes the menu's own text colour so it
   stays legible on the dark sidebar too. */
.np-aside__brand-logo { justify-content: flex-start; }
.np-aside__brand-logo .np-brand-logo__img { object-position: left center; }
.np-aside__brand .np-brand-logo--wordmark {
    color: var(--np-aside-fg);
    text-align: left;
}
/* The square mark / initials, shown only in the collapsed 72px rail. */
.np-aside__brand-compact { display: none; }
.np-aside--collapsed .np-aside__brand-logo { display: none; }
.np-aside--collapsed .np-aside__brand-compact { display: inline-flex; }

.np-aside__nav { padding: var(--np-space-3) var(--np-space-2) var(--np-space-5); }
.np-aside__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* ---- Section band heading (CONFIGURATION) ----
   Splits the primary clinical band from the set-up screens. Rendered as plain
   text rather than a heading element: the sidebar precedes the page <h1> in DOM
   order, so an <h2> here would put the document outline out of sequence for a
   reader browsing by heading. The band's <ul> carries aria-labelledby pointing
   at this text instead, so it still announces as "Configuration, list".
   Uppercasing is CSS-only, keeping the underlying string sentence-cased for
   assistive technology. */
.np-aside__section {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    margin: var(--np-space-4) 0 var(--np-space-2);
    padding: 0 var(--np-space-3);
    color: var(--np-aside-fg-muted);
    font-size: 0.6875rem;
    font-weight: 400;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.np-aside__section-label { flex: 0 0 auto; }
.np-aside__section-rule {
    flex: 1 1 auto;
    height: 1px;
    background: var(--np-aside-divider);
}

/* ---- LEAF top-level link (Dashboard, My Profile, Patients, ...) ---- */
.np-aside__link {
    display: flex;
    align-items: center;
    gap: var(--np-space-3);
    padding: 10px var(--np-space-3);
    border-radius: var(--np-radius-2);
    color: var(--np-aside-fg);
    text-decoration: none;
    font-weight: 400;
    transition: background-color 0.15s ease, color 0.15s ease;
}
.np-aside__link:hover,
.np-aside__link:focus-visible {
    background: var(--np-aside-hover-bg);
    color: var(--np-aside-fg);
    outline: none;
}
.np-aside__link--active,
.np-aside__link[aria-current="page"] {
    background: var(--np-aside-active-bg);
    color: var(--np-aside-active-fg);
    font-weight: 400;
    /* The solid fill IS the indicator — a glow under it only blurs its edge. */
    box-shadow: none;
}
.np-aside__link--active:hover,
.np-aside__link[aria-current="page"]:hover {
    background: var(--np-aside-active-bg);
    color: var(--np-aside-active-fg);
}

.np-aside__icon {
    flex: 0 0 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.np-aside__icon svg { width: 22px; height: 22px; }
.np-aside__label { flex: 1 1 auto; min-width: 0; }
.np-aside__chip {
    flex: 0 0 auto;
    font-size: 0.6875rem;
    font-weight: 400;
    padding: 2px 8px;
    border-radius: var(--np-radius-pill);
    background: rgba(var(--np-color-brand-primary-rgb), 0.10);
    color: var(--np-aside-fg);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.np-aside__link--active .np-aside__chip {
    background: rgba(255, 255, 255, 0.22);
    color: var(--np-aside-active-fg);
}

/* ---- GROUP toggle (parent of a sub-list — Administration, Master) ---- */
/* Rendered as a <button> so click-to-expand semantics are native and
   accessible. The chevron rotates with aria-expanded — driven by
   np-aside.js. Active style (group contains the current page) uses a
   soft brand tint rather than the solid bar reserved for leaf links;
   the visual weight stays under the actual destination (the child). */
.np-aside__group-toggle {
    display: flex;
    align-items: center;
    gap: var(--np-space-3);
    width: 100%;
    padding: 10px var(--np-space-3);
    background: transparent;
    border: 0;
    border-radius: var(--np-radius-2);
    color: var(--np-aside-fg);
    font: inherit;
    font-weight: 400;
    text-align: left;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease;
}
.np-aside__group-toggle:hover,
.np-aside__group-toggle:focus-visible {
    background: var(--np-aside-hover-bg);
    color: var(--np-aside-fg);
    outline: none;
}
.np-aside__group-toggle--active {
    background: rgba(var(--np-color-brand-primary-rgb), 0.06);
    font-weight: 400;
}
.np-aside__group-toggle--active:hover {
    background: rgba(var(--np-color-brand-primary-rgb), 0.10);
}

.np-aside__chevron {
    flex: 0 0 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--np-aside-fg-muted);
    transition: transform 0.2s ease, color 0.15s ease;
}
.np-aside__chevron svg { width: 12px; height: 12px; }
.np-aside__group-toggle[aria-expanded="true"] .np-aside__chevron {
    transform: rotate(90deg);
    color: var(--np-aside-fg);
}

/* ---- Sub-list (children of a group) ---- */
/* Animated max-height + opacity collapse. Server-side aria-expanded
   gives the no-JS default; np-aside.js layers the click-to-toggle and
   the one-group-open accordion. The vertical rail uses a 1px border
   that follows the indent — the active child overrides the rail at
   its row with a thicker accent bar (see __sublink--active::before). */
.np-aside__sublist {
    list-style: none;
    margin: 2px 0 var(--np-space-2) calc(var(--np-space-3) + 14px);
    padding: 0;
    border-left: 1px solid var(--np-aside-divider);
    overflow: hidden;
    max-height: 0;
    opacity: 0;
    transition: max-height 0.22s ease-out, opacity 0.18s ease-out,
                margin-top 0.18s ease-out, margin-bottom 0.18s ease-out;
}
.np-aside__group-toggle[aria-expanded="true"] + .np-aside__sublist {
    /* The ceiling must clear the TALLEST a group can ever get, because the list
       is overflow:hidden — exceed it and the last rows are silently clipped with
       no scrollbar to hint at it. Rows are ~36px (7+7 padding, ~20px line, 2px
       margin). Master data is the worst case: 18 children plus the "Show N more"
       row = 19 rows ~= 690px, and that is with the disclosure OPEN, since opening
       it grows this same container. 1600px leaves room for the group to roughly
       double before anyone has to think about this again.

       The old value was 800px, with a comment claiming Master had 7 children. It
       had grown to 18 — about 690px — so the list was one master table away from
       clipping. */
    max-height: 1600px;
    opacity: 1;
}

.np-aside__subitem { margin: 1px 0; }

.np-aside__sublink {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px var(--np-space-3) 7px 14px;
    color: var(--np-aside-fg-muted);
    text-decoration: none;
    font-size: 0.875rem;
    border-radius: 0 var(--np-radius-2) var(--np-radius-2) 0;
    position: relative;
    transition: background-color 0.12s ease, color 0.12s ease;
}

/* Small dot indicator — sits just inside the rail so each row reads as
   a discrete item even before hover. Grows + brightens on hover and on
   the active row. */
.np-aside__sub-dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--np-aside-divider);
    flex: 0 0 5px;
    transition: background-color 0.12s ease, transform 0.12s ease;
}
.np-aside__sublink:hover,
.np-aside__sublink:focus-visible {
    background: var(--np-aside-hover-bg);
    color: var(--np-aside-fg);
    outline: none;
}
.np-aside__sublink:hover .np-aside__sub-dot,
.np-aside__sublink:focus-visible .np-aside__sub-dot {
    background: var(--np-aside-fg);
    transform: scale(1.4);
}

.np-aside__sublink--active,
.np-aside__sublink[aria-current="page"] {
    background: rgba(var(--np-color-brand-primary-rgb), 0.10);
    color: var(--np-aside-fg);
    font-weight: 400;
}
.np-aside__sublink--active::before,
.np-aside__sublink[aria-current="page"]::before {
    /* Brand-color accent bar that replaces the rail at the active row.
       3px wide so it reads clearly without dominating the row. */
    content: '';
    position: absolute;
    left: -1px;
    top: 6px;
    bottom: 6px;
    width: 3px;
    background: var(--np-aside-fg);
    border-radius: 2px;
}
.np-aside__sublink--active .np-aside__sub-dot,
.np-aside__sublink[aria-current="page"] .np-aside__sub-dot {
    background: var(--np-aside-fg);
    transform: scale(1.5);
}

.np-aside__sublabel { flex: 1 1 auto; min-width: 0; }
.np-aside__chip--sub {
    font-size: 0.625rem;
    padding: 1px 6px;
}

/* ---- "Show N more" disclosure inside a long sub-list ----
   A native <details>/<summary>, so the hidden links still open with scripting
   off and the browser supplies the keyboard handling and the
   expanded/collapsed announcement. Padding-left aligns the label with the
   sub-link labels above it (14px indent + 5px dot + 10px gap = 29px), so the
   toggle reads as part of the list rather than a control bolted under it. */
.np-aside__overflow-toggle {
    display: flex;
    align-items: center;
    padding: 7px var(--np-space-3) 7px 29px;
    color: var(--np-aside-fg-muted);
    font-size: 0.8125rem;
    font-weight: 400;
    cursor: pointer;
    border-radius: 0 var(--np-radius-2) var(--np-radius-2) 0;
    transition: background-color 0.12s ease, color 0.12s ease;
    /* Drop the default disclosure triangle — the label carries the state in
       words. Both properties are needed for full browser coverage. */
    list-style: none;
}
.np-aside__overflow-toggle::-webkit-details-marker { display: none; }
.np-aside__overflow-toggle:hover {
    background: var(--np-aside-hover-bg);
    color: var(--np-aside-fg);
}
/* No outline:none here, unlike the sub-links: <summary> is a native control and
   keeps its focus ring. */
.np-aside__overflow-toggle:focus-visible {
    background: var(--np-aside-hover-bg);
    color: var(--np-aside-fg);
}
/* One label per state. The accessible name is the visible text, so the closed
   and open wordings can differ without an aria-label that would be wrong in one
   of the two states. */
.np-aside__overflow-less { display: none; }
.np-aside__overflow[open] .np-aside__overflow-more { display: none; }
.np-aside__overflow[open] .np-aside__overflow-less { display: inline; }
.np-aside__overflow-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Collapsed (icon-only) sidebar — both group sublists and the
   chevron + label hide. The group-toggle becomes an icon button. */
.np-aside--collapsed .np-aside__sublist {
    max-height: 0 !important;
    opacity: 0 !important;
    margin: 0 !important;
    border-left: 0;
}
.np-aside--collapsed .np-aside__chevron,
.np-aside--collapsed .np-aside__label,
.np-aside--collapsed .np-aside__chip {
    display: none;
}
.np-aside--collapsed .np-aside__group-toggle {
    justify-content: center;
}

.np-aside__backdrop {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(15, 12, 24, 0.55);
    border: 0;
    padding: 0;
    z-index: 25;
    cursor: pointer;
}
.np-aside__backdrop--visible { display: block; }

.np-shell__main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

/* Header (always visible, all breakpoints).
   Three regions: [left: toggle + title] [right: actions]. */
.np-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-4);
    min-height: var(--np-topbar-height);
    padding: 0 var(--np-space-5);
    background: var(--np-color-surface);
    border-bottom: 1px solid var(--np-color-border);
    /* Border only. A sticky bar that also casts a shadow smears the top of every
       scrolled page; the rule is enough to separate it from the content. */
    box-shadow: none;
    position: sticky;
    top: 0;
    z-index: 20;
}
.np-topbar__left {
    display: flex;
    align-items: center;
    gap: var(--np-space-3);
    min-width: 0;
}
.np-topbar__actions {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
}
.np-topbar__toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    background: transparent;
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    color: var(--np-color-text-primary);
    cursor: pointer;
}
.np-topbar__toggle:hover,
.np-topbar__toggle:focus { background: var(--np-color-surface-muted); }
.np-topbar__toggle:focus-visible { outline: 2px solid var(--np-color-focus); outline-offset: 2px; }
.np-topbar__toggle-icon { width: 20px; height: 20px; }

/* Which of the three glyphs is showing.

   Below the shell breakpoint the control opens a DRAWER, which is what a
   hamburger has always meant, so that is the icon there. At desktop width it
   collapses the rail to icons instead — a different action that deserves a
   different glyph, and one that says which direction it is about to go. The
   state comes from .np-shell-body--collapsed, which shell.js already maintains,
   so nothing here needs a second source of truth. */
.np-topbar__toggle-icon--collapse,
.np-topbar__toggle-icon--expand { display: none; }

@media (min-width: 992px) {
    .np-topbar__toggle-icon--menu { display: none; }
    .np-topbar__toggle-icon--collapse { display: block; }
    .np-shell-body--collapsed .np-topbar__toggle-icon--collapse { display: none; }
    .np-shell-body--collapsed .np-topbar__toggle-icon--expand { display: block; }
}

/* The leaf crumb — where you are. One step up the scale from the ancestor
   crumbs beside it, and sitting on the SAME tint the sidebar uses for a hovered
   nav item, so "the page I am on" is painted in the colour the reader already
   associates with "the thing I am pointing at". Deliberately the hover tint and
   not the solid active pill: the sidebar's active item is the section, this is
   the page inside it, and two solid pills on one screen compete. */
.np-topbar__title {
    margin: 0;
    display: inline-block;
    max-width: 100%;
    padding: 3px var(--np-space-3);
    border-radius: var(--np-radius-2);
    background: var(--np-aside-hover-bg);
    font-size: 1.125rem;
    font-weight: 400;
    color: var(--np-color-text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* An ellipsis needs a block box, and a block box in a flex row with
       overflow:hidden needs this to actually shrink. */
    min-width: 0;
}
@media (forced-colors: active) {
    /* The tint is not painted, so the leaf would lose its only distinction from
       the ancestor crumbs. */
    .np-topbar__title { border: 1px solid CanvasText; }
}

/* Topbar navigation path: Administration / Roles. Ancestor crumbs are muted
   and de-emphasised so the leaf (the page's <h1>) still reads as the heading. */
.np-topbar__trail { min-width: 0; }
.np-crumbs {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    margin: 0;
    padding: 0;
    list-style: none;
    min-width: 0;
}
.np-crumbs__item {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    min-width: 0;
}
.np-crumbs__item--current { min-width: 0; }
.np-crumbs__sep {
    color: var(--np-color-border-strong, var(--np-color-border));
    font-size: 0.875rem;
    line-height: 1;
}
.np-crumbs__link,
.np-crumbs__text {
    font-size: 0.8125rem;
    font-weight: 400;
    color: var(--np-color-text-muted);
    white-space: nowrap;
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
}
.np-crumbs__link { text-decoration: none; }
.np-crumbs__link:hover,
.np-crumbs__link:focus { color: var(--np-color-text-primary); text-decoration: underline; }
.np-crumbs__link:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 2px;
    border-radius: var(--np-radius-1, 3px);
}

/* Narrow viewports: keep the leaf only — ancestors are wayfinding, not
   identity, and the sidebar already answers "where am I" on mobile. */
@media (max-width: 767.98px) {
    .np-crumbs__item:not(.np-crumbs__item--current) { display: none; }
    .np-crumbs__item--current .np-crumbs__sep { display: none; }
}

/* Header icon button (help, notifications). */
.np-iconbtn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--np-radius-2);
    color: var(--np-color-text-secondary);
    cursor: pointer;
}
.np-iconbtn:hover,
.np-iconbtn:focus {
    background: var(--np-color-surface-muted);
    color: var(--np-color-brand-primary);
    border-color: var(--np-color-border);
}
.np-iconbtn:focus-visible { outline: 2px solid var(--np-color-focus); outline-offset: 2px; }
.np-iconbtn svg { width: 20px; height: 20px; }
.np-iconbtn__badge {
    position: absolute;
    top: 4px;
    right: 4px;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    background: var(--np-color-brand-primary);
    color: #fff;
    font-size: 0.6875rem;
    font-weight: 400;
    line-height: 18px;
    text-align: center;
    border-radius: 999px;
    box-shadow: 0 0 0 2px var(--np-color-surface);
}

/* Header user button. */
.np-userbtn {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    padding: 4px 10px 4px 4px;
    background: transparent;
    border: 1px solid var(--np-color-border);
    border-radius: 999px;
    color: var(--np-color-text-primary);
    cursor: pointer;
    max-width: 240px;
}
.np-userbtn:hover,
.np-userbtn:focus { background: var(--np-color-surface-muted); }
.np-userbtn:focus-visible { outline: 2px solid var(--np-color-focus); outline-offset: 2px; }
.np-userbtn__avatar {
    flex: 0 0 32px;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--np-color-brand-tint-200);
    color: var(--np-color-brand-primary);
    font-weight: 400;
    font-size: 0.8125rem;
    border-radius: 50%;
    letter-spacing: 0.02em;
    position: relative;
    overflow: hidden;
}

/* Photo avatar — the <img> fills the circular container and covers the
   initials underneath once it loads (shell.js toggles visibility). Shared by
   the header user button and the dropdown identity block. */
.np-avatar__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 50%;
}

/* Larger circular avatar used in the user-menu dropdown header. */
.np-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--np-color-brand-tint-200);
    color: var(--np-color-brand-primary);
    font-weight: 400;
    border-radius: 50%;
    letter-spacing: 0.02em;
    position: relative;
    overflow: hidden;
}
.np-avatar--lg {
    flex: 0 0 48px;
    width: 48px;
    height: 48px;
    font-size: 1.0625rem;
}

/* User-menu dropdown identity row: avatar + name/email/role stack. */
.np-popover__identity {
    display: flex;
    align-items: center;
    gap: var(--np-space-3);
    min-width: 0;
}
.np-popover__identity-meta { min-width: 0; }
.np-popover__sub--ellipsis {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 220px;
}
.np-userbtn__meta {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    line-height: 1.1;
    min-width: 0;
}
.np-userbtn__name {
    font-weight: 400;
    font-size: 0.875rem;
    max-width: 140px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.np-userbtn__role {
    color: var(--np-color-text-muted);
    font-size: 0.75rem;
}
.np-userbtn__chev { width: 14px; height: 14px; color: var(--np-color-text-muted); }

@media (max-width: 575px) {
    .np-userbtn__meta { display: none; }
    .np-userbtn { padding: 4px; }
    .np-topbar__title { font-size: var(--np-fs-small); }
}

/* Header dropdowns (help, notifications, user). */
.np-popover { position: relative; }
.np-popover__panel {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    min-width: 260px;
    max-width: 320px;
    padding: var(--np-space-2);
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    box-shadow: var(--np-shadow-2);
    z-index: 40;
}
.np-popover__panel--wide { min-width: 320px; }
.np-popover__panel--end { right: 0; }
.np-popover__header {
    padding: var(--np-space-3) var(--np-space-3) var(--np-space-2);
    border-bottom: 1px solid var(--np-color-border);
    margin-bottom: var(--np-space-2);
}
.np-popover__title {
    margin: 0;
    font-weight: 400;
    color: var(--np-color-text-primary);
}
.np-popover__sub {
    margin: 2px 0 0;
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
}
.np-popover__item {
    display: flex;
    align-items: center;
    gap: var(--np-space-3);
    padding: var(--np-space-2) var(--np-space-3);
    border-radius: var(--np-radius-1);
    color: var(--np-color-text-primary);
    text-decoration: none;
    background: transparent;
    border: 0;
    width: 100%;
    text-align: left;
    cursor: pointer;
    font-size: 0.9375rem;
}
.np-popover__item:hover,
.np-popover__item:focus { background: var(--np-color-surface-muted); color: var(--np-color-brand-primary); }
.np-popover__item:focus-visible { outline: 2px solid var(--np-color-focus); outline-offset: -2px; }
.np-popover__item--primary {
    background: var(--np-color-brand-primary);
    color: #fff;
    justify-content: center;
    font-weight: 400;
}
.np-popover__item--primary:hover,
.np-popover__item--primary:focus {
    background: var(--np-color-brand-primary-hover);
    color: #fff;
}
.np-popover__item--danger { color: #b00020; }
.np-popover__item--danger:hover,
.np-popover__item--danger:focus { background: #fdecef; color: #870016; }
.np-popover__icon {
    flex: 0 0 36px;
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--np-color-surface-muted);
    color: var(--np-color-brand-primary);
    border-radius: var(--np-radius-2);
}
.np-popover__icon svg { width: 18px; height: 18px; }
.np-popover__item-label {
    display: block;
    font-weight: 400;
    line-height: 1.2;
}
.np-popover__item-meta {
    display: block;
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
    line-height: 1.2;
}
.np-popover__empty {
    margin: 0;
    padding: var(--np-space-4) var(--np-space-3);
    color: var(--np-color-text-muted);
    text-align: center;
    font-size: var(--np-fs-small);
}
.np-popover__sep {
    margin: var(--np-space-2) var(--np-space-1);
    border: 0;
    border-top: 1px solid var(--np-color-border);
}
.np-popover__form { margin: 0; }

/* Shell main column owns the page chrome (header + content + footer)
   and lays them out vertically so the footer sticks to the bottom on
   short pages. The content area is FLUID — module screens (lists,
   tables, forms) get the whole available width, with a consistent
   gutter for breathing room. Marketing components (.np-hero) self-cap
   internally so they remain readable on wide screens. */
.np-shell__content {
    width: 100%;
    max-width: 1440px;
    margin: 0 auto;
    padding: 0 var(--np-space-6);
}
@media (max-width: 991.98px) {
    .np-shell__content { padding: 0 var(--np-space-5); }
}
@media (max-width: 575px) {
    .np-shell__content { padding: 0 var(--np-space-4); }
}

/* Tighten the previous over-padded main wrapper. */
.np-main { padding: var(--np-space-5) 0 var(--np-space-7) 0; }

/* Desktop collapsed sidebar — icon-only at 72px. */
.np-aside { transition: flex-basis 0.2s ease, width 0.2s ease; }
.np-aside--collapsed {
    flex: 0 0 72px;
    width: 72px;
}
/* Same seam in the icon rail. Vertical padding is dropped for the same reason
   as the expanded brand — min-height owns the height now, so keeping it here
   would make the collapsed rail's divider sit lower than the expanded one's. */
.np-aside--collapsed .np-aside__brand { padding: 0 var(--np-space-3); justify-content: center; }
.np-aside--collapsed .np-aside__brand-text,
.np-aside--collapsed .np-aside__label,
.np-aside--collapsed .np-aside__section-label,
.np-aside--collapsed .np-aside__chip { display: none; }
/* Icon rail: the band heading collapses to its rule alone, which is all the
   separation a 72px column needs (and all it has room for). */
.np-aside--collapsed .np-aside__section { padding: 0 var(--np-space-2); margin: var(--np-space-3) 0; }
.np-aside--collapsed .np-aside__brand-link { justify-content: center; gap: 0; }
.np-aside--collapsed .np-aside__nav { padding: var(--np-space-3) var(--np-space-2); }
.np-aside--collapsed .np-aside__link,
.np-aside--collapsed .np-aside__group-toggle {
    justify-content: center;
    padding: var(--np-space-3) 0;
}
.np-aside--collapsed .np-aside__icon { flex: 0 0 auto; }
/* Tooltip-on-hover for collapsed labels — read the text from aria-label
   if the link author set one, otherwise fall back to title. Browsers
   render the native title tooltip on hover. */
.np-aside--collapsed .np-aside__link::after,
.np-aside--collapsed .np-aside__group-toggle::after {
    content: attr(data-label);
    position: absolute;
    left: calc(100% + 8px);
    top: 50%;
    transform: translateY(-50%);
    background: var(--np-color-text-primary);
    color: #fff;
    padding: 4px 10px;
    border-radius: var(--np-radius-1);
    font-size: var(--np-fs-small);
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease;
}
.np-aside--collapsed .np-aside__link:hover::after,
.np-aside--collapsed .np-aside__link:focus::after,
.np-aside--collapsed .np-aside__group-toggle:hover::after,
.np-aside--collapsed .np-aside__group-toggle:focus-visible::after { opacity: 1; }
.np-aside--collapsed .np-aside__link,
.np-aside--collapsed .np-aside__group-toggle { position: relative; }

/* Mobile drawer behaviour: collapse aside, slide-in from left on toggle. */
@media (max-width: 991.98px) {
    .np-aside {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        height: 100vh;
        transform: translateX(-100%);
        transition: transform 0.2s ease;
        flex-basis: var(--np-aside-width);
        width: var(--np-aside-width);
    }
    .np-aside--open { transform: translateX(0); }
    .np-aside--collapsed {
        /* Collapsed-on-desktop state does not apply on mobile —
           the drawer either fully opens or stays off-screen. */
        flex-basis: var(--np-aside-width);
        width: var(--np-aside-width);
    }
    .np-aside--collapsed .np-aside__brand-text,
    .np-aside--collapsed .np-aside__label,
    .np-aside--collapsed .np-aside__section-label,
    .np-aside--collapsed .np-aside__chip { display: inline; }
    /* The drawer is full width, so it keeps the full customer logo. */
    .np-aside--collapsed .np-aside__brand-logo { display: inline-flex; }
    .np-aside--collapsed .np-aside__brand-compact { display: none; }
    /* Below this breakpoint --collapsed is a full-width drawer, not a 72px rail,
       so the brand goes back to reading from the left edge with the nav. */
    .np-aside--collapsed .np-aside__brand {
        padding: 0 calc(var(--np-space-2) + var(--np-space-3));
        justify-content: flex-start;
    }
    .np-aside--collapsed .np-aside__section { padding: 0 var(--np-space-3); margin: var(--np-space-4) 0 var(--np-space-2); }
    .np-aside--collapsed .np-aside__link,
    .np-aside--collapsed .np-aside__group-toggle { justify-content: flex-start; padding: var(--np-space-3); }
    .np-aside--collapsed .np-aside__link::after,
    .np-aside--collapsed .np-aside__group-toggle::after { display: none; }
    .np-shell { display: block; }
    .np-shell__main { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
    .np-aside { transition: none; }
}

@media (forced-colors: active) {
    .np-aside,
    .np-topbar { border: 1px solid; }
    .np-aside__link--active,
    .np-aside__link[aria-current="page"] { outline: 2px solid CanvasText; }
}

/* ---------- Row action icon button ----------
   Compact icon-only button used in list-view Actions columns
   (Edit, View, Delete, Reset password, etc.). Sits inside an
   `np-row-actions` flex container so multiple actions group
   neatly with consistent gaps.

   Accessibility:
     - Always pair with `aria-label` AND `title` (aria-label is the
       authoritative accessible name; title gives sighted hover users
       the same hint without inventing a separate tooltip system).
     - `--danger` variant is for destructive actions; never use it
       to indicate clinical-safety state (that's `np-status--danger`). */
.np-row-actions {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    justify-content: flex-end;
}

.np-row-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--np-radius-1);
    color: var(--np-color-text-secondary);
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.np-row-action:hover,
.np-row-action:focus {
    background: var(--np-color-surface-muted);
    border-color: var(--np-color-border);
    color: var(--np-color-brand-primary);
    text-decoration: none;
}

.np-row-action:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 1px;
}

.np-row-action svg {
    width: 16px;
    height: 16px;
}

.np-row-action:disabled,
.np-row-action[disabled] {
    opacity: 0.35;
    cursor: not-allowed;
    pointer-events: none;
}

/* An action that is refused for THIS row but explains why when pressed (e.g.
   removing an in-progress assessment). Dimmed like a disabled control, but still
   focusable and clickable — a truly disabled button cannot tell a tablet user
   anything, because its only explanation would be a hover title. */
.np-row-action--blocked { opacity: 0.45; }

.np-row-action--danger:hover,
.np-row-action--danger:focus {
    color: #8A1F1F;
    background: #FBE7E9;
    border-color: #F0C4C8;
}

/* Labelled variant — icon PLUS a text label, for the single PRIMARY action of a
   modal or detail table (see CLAUDE.md §6.7). A bare icon is ambiguous when the
   action is the whole point of the row and there is no adjacent context to read it
   against: "Send" and "Resend" are different commitments to a patient, and an icon
   cannot distinguish them at a glance.

   List-view rows stay icon-only — there the column is dense, repeated per row, and
   the surrounding cells supply the context. Do not use this to relabel Edit/Delete
   back into text links; that is the pattern §6.7 exists to prevent.

   Keeps the 32px control height (so it lines up with icon-only actions in the same
   row) and still requires aria-label + title, because the visible label is short
   and omits the row identity a screen-reader user needs. */
.np-row-action--labelled {
    width: auto;
    gap: 6px;
    padding: 0 10px;
    border-color: var(--np-color-border);
    background: var(--np-color-surface);
    color: var(--np-color-brand-primary);
    font-size: var(--np-fs-small);
    font-weight: 400;
    white-space: nowrap;
}

.np-row-action--labelled:hover,
.np-row-action--labelled:focus {
    background: var(--np-color-surface-tinted);
    border-color: var(--np-color-brand-primary);
}

@media (forced-colors: active) {
    .np-row-action { border: 1px solid CanvasText; }
}

/* ---------- Status pill (filled, with leading dot) ----------
   Standard for any list view's Status column. Tones map to semantic
   states; do not introduce ad-hoc colours per screen.
     Active    — operational                 (green)
     Inactive  — disabled / off-boarded       (grey, benign)
     Locked    — security state, attention    (amber)
     Pending   — awaiting an action           (blue)
     Warning   — soft alert                   (amber)
     Danger    — critical / clinical safety   (red)
   Each tone uses a tinted background + saturated text + matching dot,
   so the colour is never the only signal (WCAG 1.4.1). */
.np-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px 3px 8px;
    border-radius: 999px;
    font-size: 0.8125rem;
    font-weight: 400;
    line-height: 1.4;
    white-space: nowrap;
    letter-spacing: 0.01em;
    border: 1px solid transparent;
}

/* Compact variant — for a pill that ANNOTATES another value in the same cell
   (the priority badge under a patient's name), not one that is the cell's whole
   content. Same tone vocabulary and the same dot + label pair, just smaller, so
   it never competes with the value it qualifies (§6.6). */
.np-status--sm {
    padding: 1px 8px 1px 6px;
    font-size: var(--np-fs-xsmall, 0.75rem);
    gap: 5px;
}
.np-status--sm .np-status__dot {
    width: 6px;
    height: 6px;
}

.np-status__dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: currentColor;
    flex: 0 0 auto;
}

.np-status__label { display: inline-block; }

.np-status--active {
    background: var(--np-color-success-bg);
    color: var(--np-color-success-fg);
    border-color: var(--np-color-success-border);
}
.np-status--inactive {
    background: var(--np-color-surface-muted);
    color: var(--np-color-text-secondary);
    border-color: var(--np-color-border);
}
.np-status--locked {
    background: #FFF5E0;
    color: #855A00;
    border-color: #F0DCA0;
}
.np-status--pending {
    background: #E5F0FB;
    color: #1F4F8A;
    border-color: #C4DAF0;
}
.np-status--progress {
    background: #F2EAFB;
    color: #5B21B6;
    border-color: #DDCBF5;
}
.np-status--warning {
    background: #FFF5E0;
    color: #855A00;
    border-color: #F0DCA0;
}
.np-status--danger {
    background: #FBE7E9;
    color: #8A1F1F;
    border-color: #F0C4C8;
}
.np-status--neutral {
    background: var(--np-color-surface-muted);
    color: var(--np-color-text-secondary);
    border-color: var(--np-color-border);
}

@media (forced-colors: active) {
    .np-status { border: 1px solid CanvasText; }
}

/* Inline status line — an np-status pill followed by trailing meta text
   (e.g. "· last submitted 02 Jul 2026"), used in the workspace read-only
   form summaries. Keeps the pill vertically centred with the text. */
.np-status-line {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

/* ---------- Select-assessments: filter row + assigned chips ----------
   The Assign-assessments picker filter row (framework + multi-select
   Assessment type / Verbal ability + the Load button) and the assigned
   templates rendered as light, wrapping pill/chips (replaces the table
   for a compact, scannable read). */
/* Three equal columns (col-md-4 each): filter 1, filter 2, and the action
   buttons — so each filter gets a full third of the row (wide enough that
   selected chips read clearly) and the row uses the full width. Collapses to a
   single stacked column on narrow screens. */
.np-assign-filters {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    align-items: end;
    gap: var(--np-space-4);
}
@media (max-width: 768px) {
    .np-assign-filters {
        grid-template-columns: 1fr;
    }
}
/* Each filter fills its column (no width cap) and drops its default bottom
   margin so the controls bottom-align with the buttons. min-width:0 lets the
   grid track shrink instead of overflowing. */
.np-assign-filters > .np-form-group {
    margin-bottom: 0;
    min-width: 0;
}
/* The action column: Filter + Clear filter side by side, bottom-aligned with
   the filter controls. */
.np-assign-filters__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--np-space-3);
}
/* Consistent control height across the native select, the two multiselect
   controls and the action buttons so the row reads as one aligned strip. */
.np-assign-filters .np-select,
.np-assign-filters .np-multiselect__control,
.np-assign-filters__actions .np-btn {
    min-height: 44px;
}
.np-assign-filters__actions .np-btn {
    display: inline-flex;
    align-items: center;
}

/* Assigned-assessments block — a divider plus a small top gap so it reads as a
   distinct group beneath the filter controls (was cramped against them). */
.np-assigned {
    margin-top: 10px;
    padding-top: var(--np-space-4);
    border-top: 1px solid var(--np-color-border);
}

.np-assign-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}
.np-assign-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    max-width: 100%;
    padding: 5px var(--np-space-3);
    background: var(--np-color-surface-muted);
    border: 1px solid var(--np-color-border);
    border-radius: 999px;
}
.np-assign-chip__name {
    font-weight: 400;
    font-size: 0.875rem;
    line-height: 1.3;
    color: var(--np-color-text-primary);
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
@media (forced-colors: active) {
    .np-assign-chip { border: 1px solid CanvasText; }
}

/* ---------- Two-box picker (np-duallist.js) ----------
   Select assessments: "Not assigned" | move buttons | "Assigned", in one row.
   Both boxes are the same height and scroll inside, so a long template list never
   pushes Save off screen. A selected item takes the organisation's brand-primary
   fill AND shows a tick — the tick is the non-colour signal (WCAG 1.4.1). Stacks on
   narrow screens, where the arrows turn to point down / up. */
.np-duallist {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: stretch;
    gap: var(--np-space-4);
    margin-bottom: var(--np-space-3);
}
.np-duallist__box {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.np-duallist__title {
    font-size: 0.9375rem;
    font-weight: 400;
    margin: 0 0 var(--np-space-2);
    color: var(--np-color-text-primary);
}
.np-duallist__count {
    color: var(--np-color-text-secondary);
}
.np-duallist__list {
    list-style: none;
    margin: 0;
    padding: var(--np-space-1);
    height: 280px;
    overflow-y: auto;
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-field-border);
    border-radius: var(--np-radius-2);
}
.np-duallist__list:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 2px;
}
.np-duallist__item {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    padding: 8px var(--np-space-3);
    margin-bottom: 2px;
    border-radius: var(--np-radius-1);
    font-size: 0.875rem;
    line-height: 1.35;
    color: var(--np-color-text-primary);
    cursor: pointer;
    user-select: none;
}
.np-duallist__item:hover {
    background: var(--np-color-surface-tinted);
}
.np-duallist__item:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: -2px;
}
.np-duallist__item[aria-selected="true"] {
    background: var(--np-color-brand-primary);
    color: var(--np-color-text-on-brand);
}
/* The tick holds its space when hidden so labels don't shift on select. */
.np-duallist__tick {
    display: inline-flex;
    flex: 0 0 14px;
    visibility: hidden;
}
.np-duallist__item[aria-selected="true"] .np-duallist__tick {
    visibility: visible;
}
.np-duallist__label {
    min-width: 0;
    overflow-wrap: anywhere;
}
.np-duallist__item--locked {
    cursor: not-allowed;
    color: var(--np-color-text-secondary);
}
.np-duallist__item--locked:hover {
    background: transparent;
}
.np-duallist__note {
    margin-left: auto;
    flex: 0 0 auto;
    font-size: 0.75rem;
    color: var(--np-color-text-secondary);
}
.np-duallist__empty {
    margin: var(--np-space-2) 0 0;
    font-size: 0.875rem;
}
/* Move buttons sit between the boxes, Assign above Un-assign, centred vertically. */
.np-duallist__moves {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--np-space-3);
    padding-top: 1.75rem;
}
.np-duallist__move {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
}
.np-duallist__status:empty {
    display: none;
}
@media (max-width: 768px) {
    .np-duallist {
        grid-template-columns: minmax(0, 1fr);
    }
    .np-duallist__list {
        height: 220px;
    }
    .np-duallist__moves {
        flex-direction: row;
        padding-top: 0;
    }
    .np-duallist__move svg {
        transform: rotate(90deg);
    }
}
@media (forced-colors: active) {
    .np-duallist__item[aria-selected="true"] {
        background: Highlight;
        color: HighlightText;
        forced-color-adjust: none;
    }
}

/* ---------- Multiselect (chips + inline search dropdown) ----------
   Used for any multi-valued enum or lookup picker (Roles, Trained-in,
   etc). Progressive-enhancement of a hidden <select multiple>; if JS
   is off, the user sees and can use the native control. */
.np-multiselect { position: relative; }

/* FOUC guard — .np-js is set on <html> by np-boot.js (loaded in <head>,
   before first paint). While the multiselect is still un-enhanced (its
   select has not yet received .np-multiselect__native), the raw multi-row
   <select> is cloaked and its box is held at the enhanced control's 44px
   min-height so enhancement causes no layout shift. Failsafe: if
   np-multiselect.js never runs (script blocked/failed), the animation
   reveals the fully functional native select after 4s. Without JS, .np-js
   is never set and the native select shows immediately. */
.np-js .np-multiselect > select[multiple]:not(.np-multiselect__native) {
    display: block;
    width: 100%;
    height: 44px;
    visibility: hidden;
    animation: np-multiselect-reveal 0s 4s forwards;
}
@keyframes np-multiselect-reveal {
    to { visibility: visible; }
}

/* Hide the native select once the chip UI is in play. The script flips
   aria-hidden=true on the select, but width:0 / height:0 keeps it out
   of the layout and pointer flow. */
.np-multiselect__native {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.np-multiselect__control {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-1);
    min-height: 44px;
    padding: 4px var(--np-space-2);
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-field-border);
    border-radius: var(--np-radius-1);
    cursor: text;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.np-multiselect__control:hover { border-color: var(--np-color-field-border-hover); }
.np-multiselect__control:focus-within {
    border-color: var(--np-color-brand-primary);
    box-shadow: 0 0 0 4px var(--np-color-focus-halo);
    outline: none;
}

.np-multiselect__chips {
    display: contents;
}
/* Read-only variant (disabled field): the selected values as static chips, framed
   like a read-only input. Standalone (no parent control), so it lays out its own
   flex row rather than display:contents. */
.np-multiselect__chips--readonly {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-1);
    min-height: 44px;
    padding: 4px var(--np-space-2);
    background: var(--np-color-surface-muted, #F7F5FA);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2, 8px);
}
.np-multiselect__chips--readonly .np-multiselect__chip {
    padding: 2px 10px; /* symmetric — no × remove affordance in read-only */
}
.np-multiselect__empty { color: var(--np-color-text-muted); }
.np-multiselect__chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 4px 2px 10px;
    background: var(--np-color-brand-tint-100);
    color: var(--np-color-brand-primary);
    border-radius: 999px;
    font-size: var(--np-fs-small);
    font-weight: 400;
    line-height: 1.4;
    /* Never let a long label (e.g. "Dr Jane Doe - Clinical Psychologist") push the
       chip past the control's edge — cap it and ellipsize; chips wrap to new rows. */
    max-width: 100%;
    min-width: 0;
}
.np-multiselect__chip-label {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
.np-multiselect__chip-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: 50%;
    color: var(--np-color-brand-primary);
    font-size: var(--np-fs-small);
    line-height: 1;
    cursor: pointer;
}
.np-multiselect__chip-remove:hover,
.np-multiselect__chip-remove:focus {
    background: rgba(var(--np-color-brand-primary-rgb), 0.12);
}
.np-multiselect__chip-remove:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 1px;
}

.np-multiselect__input {
    flex: 1 1 120px;
    min-width: 80px;
    padding: 4px 6px;
    background: transparent;
    border: 0;
    color: var(--np-color-text-primary);
    font-size: var(--np-fs-small);
    line-height: 1.4;
}
.np-multiselect__input:focus { outline: none; }

/* The dropdown panel owns the popup chrome and the positioning (np-multiselect.js
   swaps it to `position: fixed` so it escapes ancestor `overflow: hidden`). The
   option list scrolls inside it while the footer stays pinned — the list stays
   open after each pick, so "Done" must always be reachable without scrolling. */
.np-multiselect__panel {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 30;
    display: flex;
    flex-direction: column;
    max-height: 280px;
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-1);
    box-shadow: var(--np-shadow-2);
    overflow: hidden;
}
.np-multiselect__listbox {
    flex: 1 1 auto;
    min-height: 0;              /* lets the list, not the panel, do the scrolling */
    margin: 0;
    padding: var(--np-space-1);
    list-style: none;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}
.np-multiselect__panel-footer {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-2);
    padding: var(--np-space-2) var(--np-space-2) var(--np-space-2) var(--np-space-3);
    background: var(--np-color-surface-muted);
    border-top: 1px solid var(--np-color-border);
}
.np-multiselect__count {
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
}
.np-multiselect__done {
    min-height: 32px;
    padding: var(--np-space-1) var(--np-space-4);
    background: var(--np-color-brand-tint-200);
    color: var(--np-color-brand-primary);
    border: 1px solid var(--np-color-brand-primary-hover);
    border-radius: var(--np-radius-1);
    font: inherit;
    font-size: var(--np-fs-small);
    font-weight: 400;
    line-height: 1.2;
    cursor: pointer;
}
.np-multiselect__done:hover,
.np-multiselect__done:focus {
    background: var(--np-color-brand-tint-100);
    color: var(--np-color-brand-primary);
}
.np-multiselect__done:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 2px;
}
/* Touch: "Done" is the deliberate dismissal affordance on tablet/mobile, where
   there is no hover and "tap outside" is easy to miss — give it a full target. */
@media (pointer: coarse) {
    .np-multiselect__panel-footer { padding: var(--np-space-2) var(--np-space-3); }
    .np-multiselect__done {
        min-height: 44px;
        padding-left: var(--np-space-5);
        padding-right: var(--np-space-5);
    }
    .np-multiselect__option { padding: var(--np-space-3); }
}
.np-multiselect__option {
    padding: var(--np-space-2) var(--np-space-3);
    border-radius: var(--np-radius-1);
    color: var(--np-color-text-primary);
    cursor: pointer;
    font-size: 0.9375rem;
}
.np-multiselect__option:hover,
.np-multiselect__option--active {
    background: var(--np-color-surface-muted);
    color: var(--np-color-brand-primary);
}
.np-multiselect__option--all {
    font-weight: 400;
    color: var(--np-color-brand-primary);
    border-bottom: 1px solid var(--np-color-border);
    border-radius: 0;
    margin-bottom: var(--np-space-1);
}
.np-multiselect__empty {
    padding: var(--np-space-3);
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
    text-align: center;
}

/* ---------- List footer (page-size selector + pager) ---------- */
/* Wraps the shared "Show N per page" selector and the pager on one row.
   Owns the single top separator so the nested pager drops its own. */
.np-list-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-3);
    margin-top: var(--np-space-4);
    padding-top: var(--np-space-3);
    border-top: 1px solid var(--np-color-border);
}

.np-list-footer .np-pager {
    flex: 1 1 auto;
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}

.np-pagesize {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    flex: 0 0 auto;
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
}

.np-pagesize__select {
    width: auto;
    min-width: 4.5rem;
}

.np-pagesize__label,
.np-pagesize__suffix {
    white-space: nowrap;
}

/* ---------- Pager (shared list pagination) ---------- */
.np-pager {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-3);
    align-items: center;
    justify-content: space-between;
    margin-top: var(--np-space-4);
    padding-top: var(--np-space-3);
    border-top: 1px solid var(--np-color-border);
}

.np-pager__summary {
    margin: 0;
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
}

/* Friendly hint appended to the summary when more than one page exists —
   tells the user there are more records and how to reach them. */
.np-pager__hint {
    color: var(--np-color-text-secondary);
}

.np-pager__list {
    display: inline-flex;
    flex-wrap: wrap;
    gap: var(--np-space-1);
    list-style: none;
    margin: 0;
    padding: 0;
}

.np-pager__link {
    display: inline-flex;
    align-items: center;
    min-width: 2.25rem;
    height: 2.25rem;
    padding: 0 var(--np-space-3);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-1);
    background: var(--np-color-surface);
    color: var(--np-color-brand-primary);
    font-size: var(--np-fs-small);
    line-height: 1;
    text-decoration: none;
    justify-content: center;
}

.np-pager__link:hover,
.np-pager__link:focus {
    background: var(--np-color-surface-muted);
    color: var(--np-color-brand-primary-hover);
    text-decoration: none;
}

.np-pager__link:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 2px;
}

.np-pager__link--current {
    background: var(--np-color-brand-primary);
    border-color: var(--np-color-brand-primary);
    color: #fff;
    cursor: default;
}

.np-pager__link--current:hover,
.np-pager__link--current:focus {
    background: var(--np-color-brand-primary);
    color: #fff;
}

.np-pager__link--disabled {
    color: var(--np-color-text-muted);
    background: var(--np-color-surface-muted);
    cursor: not-allowed;
    pointer-events: none;
}

@media (forced-colors: active) {
    .np-pager__link { border: 1px solid CanvasText; }
    .np-pager__link--current { outline: 2px solid CanvasText; }
}


/* ============================================================
   Landing / sign-in page (anonymous shell)
   ------------------------------------------------------------
   Two-column hero + login panel rendered by Account/Login.cshtml
   under _LayoutAnon. Responsive cascade:
     ≥1100px  desktop       2-column hero + panel
     768–1099 tablet         single column, hero on top
     480–767  small tablet   single column, features 2-up
     <480     phone          everything stacks, full-width logos
   All gaps + paddings use clamp() so transitions stay smooth.
   Reuses the global Neuraplex tokens — no new colour values.
   ============================================================ */
/* Patient / public-facing pages read at 16px normal text (NHS design guidance for
   public readers — often a parent on a phone). Staff screens, including the staff
   sign-in pages, stay on the product's 14px. Set by _LayoutAnon on every page
   except Account. Re-declared here rather than inherited from :root because a
   custom property that references another is resolved where it is declared. */
.np-public-body {
    --np-fs-body: 1rem;          /* 16px */
    --bs-body-font-size: 1rem;
    --bs-btn-font-size: 1rem;
}
.np-anon-body {
    margin: 0;
    min-height: 100vh;
    min-height: 100dvh;          /* honours mobile dynamic viewport */
    overflow-x: clip;            /* prevents 1px horizontal scrollbars from rounding */
    color: var(--np-color-text-primary);
    /* Every stop is a token. The middle stop used to be a literal #ffffff,
       which in dark mode produced a dark -> WHITE -> dark sweep: the "silver
       metallic" background that made the sign-in page unreadable. A literal in
       a gradient whose other stops are tokens is the exact shape of that bug —
       it survives review because it looks correct in the theme it was written
       in. The tint radial is brand-derived for the same reason. */
    background:
        radial-gradient(circle at top left, rgba(var(--np-color-brand-primary-rgb), 0.18), transparent 36%),
        linear-gradient(135deg,
            var(--np-color-brand-tint-50) 0%,
            var(--np-color-canvas) 48%,
            var(--np-color-brand-tint-100) 100%);
    /* `background-attachment: fixed` is intentionally omitted —
       it's expensive on mobile and broken on iOS. The gradient is
       large enough that scroll feels natural without it. */
}

.np-anon-shell {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
}

.np-landing {
    flex: 1;
    display: flex;
    flex-direction: column;
}

/* ---------- Topbar (brand cluster + help link) ---------- */
.np-landing__topbar {
    width: 100%;
    max-width: 1440px;
    margin: 0 auto;
    padding: clamp(var(--np-space-3), 2vw, var(--np-space-5))
             clamp(var(--np-space-4), 4vw, var(--np-space-6));
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-3) var(--np-space-4);
}

.np-landing__brand-cluster {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--np-space-3);
    min-width: 0;
    flex: 1 1 auto;
}

.np-landing__product-logo,
.np-landing__customer-logo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--np-radius-2);
    font-weight: 400;
    letter-spacing: -0.02em;
    height: 40px;
    padding: 0 var(--np-space-4);
    box-shadow: var(--np-shadow-1);
    max-width: 100%;
}
.np-landing__product-logo {
    color: var(--np-color-brand-primary);
    background: var(--np-color-brand-tint-200);
    border: 0;
    box-shadow: var(--np-shadow-brand);
    font-size: var(--np-fs-small);
    flex: 0 0 auto;
    white-space: nowrap;
}
.np-landing__customer-logo {
    color: var(--np-color-text-secondary);
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    font-size: 0.875rem;
    font-weight: 400;
    /* Allow long trust names to wrap rather than push the layout offscreen. */
    flex: 0 1 auto;
    min-width: 0;
    white-space: normal;
    line-height: 1.2;
    text-align: center;
}

/* ---------- Customer brand logo (_NpBrandLogo.cshtml) ----------

   ONE rule set for every customer, every placement. The box is fixed per
   placement and the artwork is fitted inside it with object-fit: contain —
   so a wide wordmark, a tall crest and a circular badge all render correctly
   with no per-tenant CSS anywhere (§3.14).

   Height/width are BOTH set on the box but the <img> only ever contains
   itself inside them; setting both on the image is what produces stretched,
   cheap-looking logos, so the image is never given a fixed aspect. */
.np-brand-logo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    max-width: 100%;
}

.np-brand-logo__img {
    display: block;
    width: 100%;
    height: 100%;
    /* The whole trick: never crop, never stretch, always centre. */
    object-fit: contain;
    object-position: center;
}

/* Wordmark fallback when no asset is configured — reads as intentional. */
.np-brand-logo--wordmark {
    font-weight: 400;
    letter-spacing: -0.02em;
    line-height: 1.15;
    color: var(--np-color-brand-primary);
    text-align: center;
    /* A long trust name must wrap inside the box rather than overflow it. */
    overflow-wrap: anywhere;
}

/* Sign-in hero — the highest-impact placement in the product. */
.np-brand-logo--signin { width: 280px; height: 70px; }
.np-brand-logo--signin.np-brand-logo--wordmark { font-size: 1.375rem; }

/* Persistent chrome. */
.np-brand-logo--topbar { width: 160px; height: 40px; }
.np-brand-logo--topbar.np-brand-logo--wordmark { font-size: 0.9375rem; }

/* Collapsed sidebar / square mark. */
.np-brand-logo--compact { width: 32px; height: 32px; }
.np-brand-logo--compact.np-brand-logo--wordmark { font-size: 0.875rem; }

/* Footer — deliberately lighter than the topbar so one screen never shows
   the same logo twice at equal weight. */
.np-brand-logo--footer { width: 140px; height: 35px; }
.np-brand-logo--footer.np-brand-logo--wordmark { font-size: 0.8125rem; }

/* Phones: the sign-in logo may exceed the viewport on the narrowest devices,
   so let the box shrink while keeping its ratio. */
@media (max-width: 359.98px) {
    .np-brand-logo--signin { width: 100%; max-width: 280px; }
}

/* Customer logo in the application topbar, beside the page title. */
.np-topbar__brand {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
}

.np-topbar__divider {
    width: 1px;
    height: 24px;
    background: var(--np-color-border);
    margin: 0 var(--np-space-3);
    flex: 0 0 auto;
}

/* Below tablet the page title needs the room more than the logo does. The
   sidebar still carries the product mark, and the customer's identity is
   established at sign-in, so nothing is lost. */
@media (max-width: 767.98px) {
    .np-topbar__brand,
    .np-topbar__divider { display: none; }
}

.np-landing__help-link {
    color: var(--np-color-brand-primary);
    text-decoration: none;
    font-weight: 400;
    padding: var(--np-space-2) var(--np-space-3);
    border-radius: var(--np-radius-1);
    flex: 0 0 auto;
}
.np-landing__help-link:hover,
.np-landing__help-link:focus {
    background: rgba(var(--np-color-brand-primary-rgb), 0.08);
    color: var(--np-color-brand-primary-hover);
    text-decoration: underline;
}

/* ---------- Two-column body (hero + login panel) ---------- */
.np-landing__grid {
    flex: 1;
    display: grid;
    /* Right column is bounded so it never starves the hero column.
       At small widths the @media block below collapses to a single column. */
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 460px);
    gap: clamp(var(--np-space-5), 4vw, var(--np-space-7));
    align-items: center;
    padding: clamp(var(--np-space-4), 3vw, var(--np-space-6))
             clamp(var(--np-space-4), 4vw, var(--np-space-6))
             clamp(var(--np-space-5), 4vw, var(--np-space-7));
    max-width: 1440px;
    margin: 0 auto;
    width: 100%;
}

/* Single-column variant used by the post-login MFA challenge page — drops
   the marketing hero so the verification panel sits centred and prominent. */
.np-landing__grid--centered {
    grid-template-columns: minmax(0, 480px);
    justify-content: center;
}

/* Product wordmark on the sign-in card. The product owns this box; the artwork
   is fitted inside it with object-fit: contain, exactly as customer logos are —
   so swapping the asset can never stretch it or shift the card's layout. */
.np-landing__panel-productmark {
    display: block;
    width: 100%;
    max-width: 240px;
    height: 96px;
    margin: 0 auto;
    object-fit: contain;
    object-position: center;
}

@media (max-width: 575.98px) {
    .np-landing__panel-productmark { height: 76px; max-width: 200px; }
}

/* ---------- Terms & conditions dialog (_NpTermsDialog.cshtml) ----------

   A read-only notice, so the body is a scroll region of its own rather than a
   growing dialog: the Close button must stay reachable without scrolling the
   dialog off the bottom of a short viewport. tabindex="0" on the region makes it
   keyboard-scrollable, which a scrolling container is not by default. */
.np-terms-doc {
    max-height: min(48vh, 420px);
    overflow-y: auto;
    padding-right: var(--np-space-2);
    text-align: left;
}

.np-terms-doc__section + .np-terms-doc__section { margin-top: var(--np-space-4); }

.np-terms-doc__heading {
    margin: 0 0 var(--np-space-1);
    font-size: var(--np-fs-small);
    font-weight: 400;
    color: var(--np-color-brand-primary);
}

.np-terms-doc p {
    margin: 0;
    font-size: var(--np-fs-small);
    line-height: 1.6;
    color: var(--np-color-text-secondary);
}

.np-terms-doc:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}

/* ---------- MFA challenge page ---------- */
.np-mfa__lock-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    border-radius: 999px;
    background: var(--np-color-brand-tint-100, #F7F0F8);
    color: var(--np-color-brand-primary, #741F7B);
    margin-bottom: var(--np-space-3);
}

.np-mfa__input-row { margin-top: var(--np-space-4); }

.np-mfa__code-input {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 1.5rem;
    letter-spacing: 0.4em;
    text-align: center;
    padding: var(--np-space-3);
}

.np-mfa__timer {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--np-color-text-muted, #6F6879);
    font-size: 0.875rem;
    margin: var(--np-space-2) 0 var(--np-space-4);
}
.np-mfa__timer-icon { display: inline-flex; }
.np-mfa__timer--expired { color: var(--np-color-danger, #c92420); }
.np-mfa__timer--expired .np-mfa__timer-icon { display: none; }

.np-mfa__verify-btn { width: 100%; }

.np-mfa__resend-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin: var(--np-space-4) 0 var(--np-space-2);
    font-size: 0.9375rem;
}

.np-mfa__help {
    margin-top: var(--np-space-3);
    font-size: 0.875rem;
    color: var(--np-color-text-muted, #6F6879);
}

.np-landing__centered {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: clamp(var(--np-space-5), 5vw, var(--np-space-7))
             clamp(var(--np-space-4), 4vw, var(--np-space-6));
}

.np-landing__hero {
    min-width: 0;
    max-width: 720px;
}

.np-landing__eyebrow {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    padding: 6px 12px;
    border-radius: var(--np-radius-pill);
    background: rgba(var(--np-color-brand-primary-rgb), 0.08);
    color: var(--np-color-brand-primary-active);
    font-weight: 400;
    font-size: var(--np-fs-small);
    margin-bottom: var(--np-space-4);
    letter-spacing: 0.02em;
}
.np-landing__eyebrow-dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: #027A48;
    box-shadow: 0 0 0 4px rgba(2, 122, 72, 0.12);
    flex: 0 0 auto;
}

.np-landing__title {
    /* Tuned for legibility — caps at 56px on a 1440px viewport. */
    font-size: clamp(1.875rem, 3.5vw + 0.5rem, 3.5rem);
    line-height: 1.05;
    margin: 0 0 var(--np-space-4);
    letter-spacing: -0.04em;
    color: var(--np-color-text-primary);
    font-weight: 400;
    overflow-wrap: anywhere;
}
.np-landing__title-accent {
    /* Was gradient-clipped text. A pale tint cannot paint a glyph and stay
       readable, so the accent is solid brand ink. */
    color: var(--np-color-brand-primary);
}

.np-landing__lede {
    font-size: clamp(0.95rem, 0.6vw + 0.85rem, 1.125rem);
    line-height: 1.6;
    color: var(--np-color-text-secondary);
    max-width: 60ch;
    margin: 0 0 var(--np-space-5);
}

.np-landing__features {
    display: grid;
    /* auto-fit reflows naturally at any width without media queries. */
    grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
    gap: var(--np-space-3);
    max-width: 720px;
}
.np-landing__feature {
    /* Translucent over the page gradient — the glassy look is deliberate, so
       it is kept, but derived from the surface token rather than hard white.
       The solid declaration first is the fallback for engines without
       color-mix(); both themes stay readable either way. */
    background: var(--np-color-surface);
    background: color-mix(in srgb, var(--np-color-surface) 78%, transparent);
    border: 1px solid var(--np-color-brand-tint-200);
    border-radius: var(--np-radius-2);
    padding: var(--np-space-4);
    box-shadow: var(--np-shadow-1);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    min-width: 0;
}
.np-landing__feature strong {
    display: block;
    color: var(--np-color-brand-primary-active);
    margin-bottom: var(--np-space-1);
    font-size: 0.95rem;
    font-weight: 400;
}
.np-landing__feature p {
    margin: 0;
    color: var(--np-color-text-muted);
    font-size: 0.875rem;
    line-height: 1.5;
}

/* ---------- Sign-in panel ---------- */
.np-landing__panel {
    background: var(--np-color-surface);
    background: color-mix(in srgb, var(--np-color-surface) 94%, transparent);
    border: 1px solid var(--np-color-brand-tint-200);
    border-radius: var(--np-radius-3);
    box-shadow: var(--np-shadow-3);
    padding: clamp(var(--np-space-4), 3vw, var(--np-space-6));
    position: relative;
    overflow: hidden;
    min-width: 0;
    width: 100%;
    max-width: 480px;
    justify-self: end;
}
.np-landing__panel::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 5px;
    background: var(--np-color-brand-tint-200);
}
/* Opt out of the gradient rule. The sign-in page uses this: the product mark
   directly above already carries the brand, so the bar read as a second,
   competing accent rather than a frame. */
.np-landing__panel--flush::before {
    content: none;
}

/* Customer logo above the sign-in heading. Generous clear space below it —
   a cramped logo reads as cheaper than a small one. */
.np-landing__panel-brand {
    display: flex;
    justify-content: center;
    margin-bottom: var(--np-space-5);
    padding-bottom: var(--np-space-4);
    border-bottom: 1px solid var(--np-color-border);
}

.np-landing__panel-header {
    margin-bottom: var(--np-space-4);
}
.np-landing__panel-header h2 {
    margin: 0 0 var(--np-space-2);
    font-size: clamp(1.375rem, 0.6vw + 1.2rem, 1.625rem);
    letter-spacing: -0.03em;
    color: var(--np-color-text-primary);
}
.np-landing__panel-header p {
    margin: 0;
    color: var(--np-color-text-muted);
    line-height: 1.5;
    font-size: 0.9375rem;
}

/* Mandatory-field marker — the ONE required-field indicator product-wide
   (uicompliance/ui.design-system.md → "Required Field Marker"). Views write a
   trailing "*" on the visible label; RequiredMarkerTagHelper wraps it in this
   span so every screen renders the same red asterisk. aria-hidden on the span
   keeps the glyph out of the control's accessible name — `required` /
   `aria-required` carry the semantics — and the asterisk itself is the visual
   cue, so colour is reinforcement and never the sole signal (WCAG 1.4.1). */
.np-required {
    color: var(--np-color-error-fg);
    font-weight: 400;
    margin-left: 2px;
}

/* The form-level note ("Fields marked with * are required.") carries its
   asterisk mid-sentence, so the tag helper deliberately leaves it alone.
   Colour it here instead, so both asterisks on a form read the same red. */
.np-form-required-note strong,
.np-form-legend-note strong {
    color: var(--np-color-error-fg);
}

/* Mandatory-field indicator, EXTERNAL (patient-facing) forms — the word, not the
   glyph (uicompliance/ui.design-system.md → "Required Field Marker"). Registration,
   pre-assessment and the anonymous DOB gates are filled in by patients and carers,
   not by staff who have been taught what a red asterisk means; a legend-free label
   that says what it means removes that learned convention. Internal clinician
   screens keep `.np-required` — see the "two contexts" table in the design system.

   Deliberately a SEPARATE class, not a `.np-required--tag` modifier: the two
   presentations must never collide on specificity (`.npf-form .np-required` in
   np-public-form.css would otherwise repaint this red), and a distinct class keeps
   each context's guard test unambiguous.

   Defined here rather than in np-public-form.css because the DOB challenge gates
   use `_LayoutAnon` WITHOUT the public-form stylesheet — np-theme.css is the only
   sheet every external page loads.

   A soft brand-tinted PILL, not the error red: a required field is a normal
   state, and reserving red for genuine validation failures is what keeps an
   actual error legible when one appears. Brand primary on tint-100 is ~9.5:1
   (AAA), and every colour here is a brand token, so the pill re-colours with the
   per-organisation theme rather than pinning Neuraplex purple onto a tenant's
   form (CLAUDE.md §3.14 configuration-over-code).

   The WORD is the signal; the tint and the pill shape are reinforcement, so this
   stays clean under WCAG 1.4.1 — remove all colour and it still reads "REQUIRED".
   rem sizing so it tracks the user's np-text-size choice. */
.np-required-tag {
    display: inline-block;
    margin-left: 0.6em;
    padding: 0.25em 0.7em;
    border: 1px solid var(--np-color-brand-tint-200);
    border-radius: 999px;
    background: var(--np-color-brand-tint-100);
    color: var(--np-color-brand-primary);
    font-size: 0.58rem;
    font-weight: 400;
    line-height: 1.5;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    white-space: nowrap;      /* wrap the pill as a unit, never mid-word */
    vertical-align: middle;   /* centres against the label text, not its baseline */
}

/* The form-level note that explains the pill. It shows the real pill inline, so the
   sentence and the marker cannot drift apart, and it is NEVER aria-hidden — this
   sentence is the explanation. Reset the inline copy's left margin: mid-sentence the
   surrounding text already supplies a word space, and the label offset would read
   as a gap. */
.np-form-required-note .np-required-tag {
    margin-left: 0;
}

.np-form-actions-inline {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--np-space-3);
    margin: var(--np-space-2) 0 var(--np-space-4);
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
    flex-wrap: wrap;
}

/* Single-child variant — keeps a lone action on the right, where it sits when
   the row also carries a note on the left. */
.np-form-actions-inline--end {
    justify-content: flex-end;
}

.np-landing__terms {
    display: flex;
    align-items: flex-start;
    gap: var(--np-space-3);
    padding: var(--np-space-3) var(--np-space-4);
    border-radius: var(--np-radius-2);
    background: var(--np-color-surface-tinted);
    border: 1px solid var(--np-color-border);
    margin-bottom: var(--np-space-2);
}
.np-landing__terms input[type="checkbox"] {
    margin-top: 3px;
    width: 18px;
    height: 18px;
    accent-color: var(--np-color-brand-primary);
    flex: 0 0 auto;
}
.np-landing__terms label {
    margin: 0;
    font-weight: 400;
    line-height: 1.5;
    color: var(--np-color-text-secondary);
    font-size: 0.875rem;
    flex: 1 1 auto;
    min-width: 0;
}
.np-landing__terms a {
    color: var(--np-color-brand-primary);
    font-weight: 400;
}

.np-landing__submit {
    width: 100%;
    margin-top: var(--np-space-3);
}

.np-landing__support {
    margin: var(--np-space-4) 0 0;
    text-align: center;
    font-size: 0.875rem;
    color: var(--np-color-text-muted);
    line-height: 1.5;
}

.np-landing__small-note { color: var(--np-color-text-muted); }

/* ---------- Footer ---------- */
.np-landing__footer {
    width: 100%;
    max-width: 1440px;
    margin: 0 auto;
    padding: var(--np-space-4) clamp(var(--np-space-4), 4vw, var(--np-space-6));
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-3) var(--np-space-4);
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
    border-top: 1px solid var(--np-color-brand-tint-200);
    background: var(--np-color-surface);
    background: color-mix(in srgb, var(--np-color-surface) 50%, transparent);
    flex-wrap: wrap;
}
/* Footer carrying the link row alone. space-between only distributes when there
   are two children; with one it strands the links hard left, so this variant
   centres them under the sign-in card. */
.np-landing__footer--links-only { justify-content: center; }

.np-landing__legal {
    max-width: 720px;
    line-height: 1.5;
    min-width: 0;
    flex: 1 1 320px;
}
.np-landing__footer-links {
    display: flex;
    gap: var(--np-space-3) var(--np-space-4);
    flex-wrap: wrap;
}
.np-landing__footer-links a {
    color: var(--np-color-brand-secondary);
    text-decoration: none;
    font-weight: 400;
}
.np-landing__footer-links a:hover,
.np-landing__footer-links a:focus {
    color: var(--np-color-brand-primary-hover);
    text-decoration: underline;
}

/* ---------- Responsive cascade ----------
   Below 1100px the right column gets squeezed (it needs ~460px and
   the hero needs room for the title), so we collapse to a single
   stacked column. */
@media (max-width: 1099.98px) {
    .np-landing__grid {
        grid-template-columns: 1fr;
        align-items: start;
    }
    .np-landing__hero { max-width: none; }
    .np-landing__panel {
        max-width: 480px;
        margin: 0 auto;
        justify-self: stretch;
    }
}

/* Tablet portrait — slightly tighter type and padding so the
   hero+panel pair fits comfortably above the fold on iPad-class
   devices without scrolling. */
@media (max-width: 767.98px) {
    .np-landing__grid {
        gap: var(--np-space-5);
        padding-top: var(--np-space-3);
    }
    .np-landing__title {
        font-size: clamp(1.625rem, 4.5vw + 0.5rem, 2.25rem);
    }
    .np-landing__lede { margin-bottom: var(--np-space-4); }
}

/* Phones — full-width logos, footer + topbar stack vertically,
   form actions rebreak so "Forgot password?" doesn't crowd the
   helper text. */
@media (max-width: 479.98px) {
    .np-landing__topbar,
    .np-landing__footer {
        flex-direction: column;
        align-items: flex-start;
    }
    .np-landing__brand-cluster { width: 100%; }
    .np-landing__product-logo,
    .np-landing__customer-logo {
        width: 100%;
        justify-content: flex-start;
        text-align: left;
    }
    .np-landing__features { grid-template-columns: 1fr; }
    .np-form-actions-inline {
        flex-direction: column;
        align-items: flex-start;
    }
    .np-landing__panel {
        border-radius: var(--np-radius-2);
        padding: var(--np-space-4);
    }
    .np-landing__terms { padding: var(--np-space-3); }
}

@media (forced-colors: active) {
    .np-landing__panel,
    .np-landing__feature { border: 1px solid CanvasText; }
    .np-landing__title-accent {
        color: CanvasText;
        -webkit-text-fill-color: CanvasText;
    }
}


/* ============================================================
   Role × Module assignments
   ------------------------------------------------------------
   - .np-assignment-indicator  status icon shown in the Roles list
                               "Assignments" column. Green tick when
                               at least one RoleModuleAssignment row
                               exists; grey "+" otherwise. Click-target
                               navigates to the editor.
   - .np-role-assign__grid     table layout for the assignments editor
                               (Role × Module CRUD checkboxes).
   ============================================================ */
.np-assignment-indicator {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 999px;
    text-decoration: none;
    border: 1px solid transparent;
    transition: background-color 0.12s ease,
                border-color 0.12s ease,
                color 0.12s ease,
                transform 0.12s ease;
}
.np-assignment-indicator svg { width: 18px; height: 18px; }
.np-assignment-indicator:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 2px;
}

.np-assignment-indicator--assigned {
    background: var(--np-color-success-bg);
    color: var(--np-color-success-fg);
    border-color: var(--np-color-success-border);
}
.np-assignment-indicator--assigned:hover,
.np-assignment-indicator--assigned:focus {
    background: #D7EEDE;
    color: #0E3E22;
    border-color: #9DCDA8;
    transform: translateY(-1px);
}

.np-assignment-indicator--empty {
    background: var(--np-color-surface-muted);
    color: var(--np-color-text-muted);
    border-color: var(--np-color-border);
}
.np-assignment-indicator--empty:hover,
.np-assignment-indicator--empty:focus {
    background: var(--np-color-brand-tint-100);
    color: var(--np-color-brand-primary);
    border-color: var(--np-color-brand-tint-200);
    transform: translateY(-1px);
}

@media (forced-colors: active) {
    .np-assignment-indicator { border: 1px solid CanvasText; }
}


/* Header strip on the assignments editor — role name + status pill on
   the left, "assignments saved / no assignments yet" pill on the
   right. Wraps cleanly on phones. */
.np-role-assign__header {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-3) var(--np-space-4);
    align-items: center;
    justify-content: space-between;
    padding-bottom: var(--np-space-4);
    margin-bottom: var(--np-space-4);
    border-bottom: 1px solid var(--np-color-border);
}
.np-role-assign__role {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-3);
    flex-wrap: wrap;
    min-width: 0;
}
.np-role-assign__role strong {
    font-size: 1.0625rem;
    color: var(--np-color-text-primary);
}


/* Assignments grid — hierarchical, sticky-header, horizontally-scrollable
   permission editor. Module column (and the header row) stay pinned while the
   action columns scroll, so the column a checkbox belongs to is always visible.

   Structure:
     .np-role-assign__legend   collapsible glossary of the action columns
     .np-role-assign__toolbar  expand/collapse-all controls
     .np-role-assign__scroll   the scroll viewport (sticky context)
       .np-role-assign__grid   the table
   Rows: --group (parent w/ children, collapsible) / --child (indented) /
   --single (childless top-level). */

/* Collapsible action glossary */
.np-role-assign__legend {
    margin-bottom: var(--np-space-3);
    font-size: var(--np-fs-small);
}
.np-role-assign__legend > summary {
    cursor: pointer;
    color: var(--np-color-brand-primary);
    font-weight: 400;
}
.np-role-assign__legend-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: var(--np-space-2) var(--np-space-4);
    margin: var(--np-space-3) 0 0;
}
.np-role-assign__legend-list > div { display: flex; gap: var(--np-space-2); }
.np-role-assign__legend-list dt { font-weight: 400; color: var(--np-color-text-primary); min-width: 6rem; }
.np-role-assign__legend-list dd { margin: 0; color: var(--np-color-text-muted); }

/* Toolbar */
.np-role-assign__toolbar {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    margin-bottom: var(--np-space-2);
}
.np-role-assign__toolbar-label { font-size: var(--np-fs-small); color: var(--np-color-text-muted); }

/* Scroll viewport — horizontal scroll only, for the wide action columns. The
   table flows at its natural height so the page scrolls as one (like every
   other list view); a fixed `max-height` here created a nested vertical
   scrollbar AND, on tall viewports, left the page shorter than the screen so
   the footer floated up with dead space beneath it. The sticky first column
   still pins because this is a horizontal scroll container. */
.np-role-assign__scroll {
    overflow-x: auto;
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
}
.np-role-assign__scroll:focus-visible { outline: 2px solid var(--np-color-focus); outline-offset: 2px; }

.np-role-assign__grid {
    width: 100%;
    border-collapse: separate;   /* required for sticky cells to paint borders */
    border-spacing: 0;
    min-width: 620px;            /* below this the viewport scrolls horizontally */
}

/* Sticky header row */
.np-role-assign__grid thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--np-color-brand-tint-50);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.7rem;
    font-weight: 400;
    color: var(--np-color-text-secondary);
    border-bottom: 2px solid var(--np-color-border-strong);
    padding: var(--np-space-3);
    white-space: nowrap;
    text-align: center;
}

/* Sticky module column (left). Opaque background so scrolled cells don't
   bleed through. */
.np-role-assign__col-module {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--np-color-surface);
    text-align: left;
    min-width: 240px;
    max-width: 360px;
    border-right: 1px solid var(--np-color-border);
}
/* Top-left corner sits above both sticky axes */
.np-role-assign__grid thead th.np-role-assign__col-module {
    z-index: 3;
    background: var(--np-color-brand-tint-50);
    text-align: left;
}

.np-role-assign__col-act { width: 92px; text-align: center; }

.np-role-assign__grid tbody th,
.np-role-assign__grid tbody td {
    padding: var(--np-space-3);
    border-bottom: 1px solid var(--np-color-border);
    vertical-align: top;
}

/* Row hover — keep the sticky module cell opaque on hover too */
.np-role-assign__grid tbody tr:hover td { background: var(--np-color-brand-tint-50); }
.np-role-assign__grid tbody tr:hover .np-role-assign__col-module { background: var(--np-color-brand-tint-50); }

.np-role-assign__module { font-weight: 400; color: var(--np-color-text-primary); }
.np-role-assign__module-head { display: flex; align-items: flex-start; gap: var(--np-space-2); }
.np-role-assign__module-text { display: flex; flex-direction: column; min-width: 0; }
.np-role-assign__module-name { font-weight: 400; line-height: 1.3; }
.np-role-assign__module-desc {
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
    line-height: 1.4;
    margin-top: 2px;
}

/* Group header row (parent with children) — tinted + slightly larger title.
   The hover rule carries `tbody tr` so it outranks the generic row-hover rule
   above (otherwise the group cell flips to the lighter tint-50 on hover). */
.np-role-assign__row--group > .np-role-assign__col-module { background: var(--np-color-brand-tint-100); }
.np-role-assign__grid tbody tr.np-role-assign__row--group:hover .np-role-assign__col-module { background: var(--np-color-brand-tint-100); }
.np-role-assign__row--group .np-role-assign__module-name { font-size: 0.95rem; }

/* Child rows — indented under their parent */
.np-role-assign__row--child .np-role-assign__module-head { padding-left: 1.85rem; }
.np-role-assign__row--child .np-role-assign__module-name { font-weight: 400; }

/* Collapsed children */
.np-role-assign__row--hidden { display: none; }

/* Caret toggle */
.np-role-assign__caret {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    margin-top: 1px;
    background: transparent;
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-1);
    color: var(--np-color-text-secondary);
    cursor: pointer;
}
.np-role-assign__caret svg { width: 11px; height: 11px; transform: rotate(90deg); transition: transform .15s ease; }
.np-role-assign__caret.is-collapsed svg { transform: rotate(0deg); }
.np-role-assign__caret:focus-visible { outline: 2px solid var(--np-color-focus); outline-offset: 1px; }

/* Per-group select all / clear */
.np-role-assign__group-tools {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    margin-top: var(--np-space-2);
    padding-left: 1.85rem;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
}

/* "Not applicable" dash — action has no meaning for this module */
.np-role-assign__na { color: var(--np-color-text-muted); opacity: .55; }

/* Checkbox */
.np-role-assign__check {
    width: 20px;
    height: 20px;
    accent-color: var(--np-color-brand-primary);
    cursor: pointer;
    vertical-align: middle;
}
.np-role-assign__check:focus-visible { outline: 2px solid var(--np-color-focus); outline-offset: 2px; }

/* Compact link buttons used in the toolbar / group tools */
.np-btn--sm { padding: 2px 6px; font-size: var(--np-fs-small); }
.np-btn--xs { padding: 0; font-size: var(--np-fs-small); min-height: 0; }

@media (max-width: 640px) {
    .np-role-assign__col-module { min-width: 170px; max-width: 220px; }
    .np-role-assign__col-act { width: 60px; }
    .np-role-assign__grid thead th,
    .np-role-assign__grid tbody th,
    .np-role-assign__grid tbody td { padding: var(--np-space-2); }
    .np-role-assign__row--child .np-role-assign__module-head,
    .np-role-assign__group-tools { padding-left: 1.4rem; }
}


/* ============================================================
   Patient list tab strip — horizontal pill nav matching the
   legacy mental model (Recent / Active / Autism / ADHD …).
   Tabs whose data source has not migrated yet carry the .--soon
   chip and a muted treatment so the deferred state reads
   without colour-only signalling.
   ============================================================ */
.np-tabs {
    margin: var(--np-space-5) 0 var(--np-space-4);
    overflow-x: auto;
}
.np-tabs__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    gap: var(--np-space-2);
    flex-wrap: wrap;
}
.np-tabs__item { display: inline-flex; }

/* Single-row strip that scrolls sideways instead of wrapping — for a strip with
   more tabs than fit (the patient list's). .np-tabs already scrolls on x; this
   stops the list wrapping and stops the pills shrinking. The padding keeps the
   active pill's shadow and the focus ring from being clipped by the scroll box.
   np-tabs-scroll.js brings the selected tab into view on load.

   The scrollbar is INVISIBLE at rest and a pale brand tint while the pointer is
   over the strip (or focus is inside it). It is recoloured rather than removed,
   so its height stays reserved and nothing below jumps when it appears. */
.np-tabs--scroll {
    padding: 2px 2px var(--np-space-2);
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
}
.np-tabs-scroller:hover .np-tabs--scroll,
.np-tabs-scroller:focus-within .np-tabs--scroll {
    /* Thumb in the brand's light accent on a tinted track. The soft border
       colour was tried first and was too pale to see against the page. */
    scrollbar-color: var(--np-color-brand-accent) var(--np-color-surface-tinted);
}
.np-tabs--scroll .np-tabs__list { flex-wrap: nowrap; }
.np-tabs--scroll .np-tabs__item { flex: 0 0 auto; }

/* Wrapper for a scrolling strip: carries the strip's margin and anchors the two
   arrow buttons over its ends. The arrows appear only while the pointer is over
   the strip, and only on the side that still has tabs to reveal
   (np-tabs-scroll.js toggles [hidden] at each end). */
.np-tabs-scroller {
    position: relative;
    margin: var(--np-space-3) 0 var(--np-space-3);
}
.np-tabs-scroller > .np-tabs { margin: 0; }
.np-tabs-scroller__btn {
    position: absolute;
    top: 2px;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-pill);
    background: var(--np-color-surface);
    color: var(--np-color-brand-primary);
    box-shadow: var(--np-shadow-1);
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease, background-color 0.15s ease;
}
.np-tabs-scroller__btn[hidden] { display: none; }
.np-tabs-scroller__btn--prev { left: 0; }
.np-tabs-scroller__btn--next { right: 0; }
.np-tabs-scroller:hover .np-tabs-scroller__btn {
    opacity: 1;
    pointer-events: auto;
}
.np-tabs-scroller__btn:hover { background: var(--np-color-surface-tinted); }
@media (prefers-reduced-motion: reduce) {
    .np-tabs-scroller__btn { transition: none; }
}

.np-tabs__link {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    padding: var(--np-space-2) var(--np-space-4);
    border-radius: var(--np-radius-pill);
    background: var(--np-color-surface-muted);
    color: var(--np-color-text-secondary);
    text-decoration: none;
    font-size: var(--np-fs-xsmall);
    font-weight: 400;
    border: 1px solid transparent;
    white-space: nowrap;
    transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.np-tabs__link:hover,
.np-tabs__link:focus {
    background: var(--np-color-surface-tinted);
    color: var(--np-color-text-primary);
    border-color: var(--np-color-border);
}
.np-tabs__link--active,
.np-tabs__link[aria-selected="true"] {
    background: var(--np-color-brand-primary);
    color: var(--np-color-text-on-brand);
    border-color: var(--np-color-brand-primary);
    box-shadow: var(--np-shadow-1);
}
.np-tabs__link--active:hover,
.np-tabs__link[aria-selected="true"]:hover {
    background: var(--np-color-brand-primary-hover);
    color: var(--np-color-text-on-brand);
}
.np-tabs__chip {
    font-size: 0.6875rem;
    font-weight: 400;
    padding: 1px 6px;
    border-radius: var(--np-radius-pill);
    background: var(--np-color-border);
    color: var(--np-color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.np-tabs__link--active .np-tabs__chip,
.np-tabs__link[aria-selected="true"] .np-tabs__chip {
    background: rgba(255, 255, 255, 0.25);
    color: var(--np-color-text-on-brand);
}

/* Secondary tab strip — a SECOND level of tabs inside a card (the Assessments
   tab's per-module sub-tabs).

   Deliberately quieter than the primary strip: repeating the filled brand pill
   one row below the real tabs produced two competing "you are here" signals and
   read as one broken control. The sub-strip therefore uses an outline for the
   active pill and a lighter resting state, so the hierarchy is obvious at a
   glance — primary tab = solid, sub-tab = outlined.

   Sits directly under the list caption, above the toolbar, because it selects
   WHICH list the toolbar then filters. */
.np-tabs--sub {
    margin: 0 0 var(--np-space-4);
    padding-bottom: var(--np-space-3);
    border-bottom: 1px solid var(--np-color-border);
}
.np-tabs--sub .np-tabs__link {
    background: transparent;
    border-color: var(--np-color-border);
    padding: var(--np-space-1) var(--np-space-3);
}
.np-tabs--sub .np-tabs__link:hover,
.np-tabs--sub .np-tabs__link:focus {
    background: var(--np-color-surface-muted);
    color: var(--np-color-text-primary);
}
.np-tabs--sub .np-tabs__link--active,
.np-tabs--sub .np-tabs__link[aria-selected="true"] {
    /* Outlined, not filled — the colour AND the weight change, so the active
       sub-tab is not signalled by colour alone (WCAG 1.4.1). */
    background: var(--np-color-surface-tinted);
    color: var(--np-color-brand-primary);
    border-color: var(--np-color-brand-primary);
    font-weight: 400;
    box-shadow: none;
}
.np-tabs--sub .np-tabs__link--active:hover,
.np-tabs--sub .np-tabs__link[aria-selected="true"]:hover {
    background: var(--np-color-surface-tinted);
    color: var(--np-color-brand-primary-hover);
}

/* Patient count beside a sub-tab label ("Autism (18)"). A pill rather than bare
   parentheses so the number reads as a distinct datum at a glance, and so it keeps
   its shape when the count reaches four digits. */
.np-tabs__count {
    display: inline-block;
    margin-left: var(--np-space-2);
    padding: 0 6px;
    border-radius: var(--np-radius-pill);
    background: var(--np-color-surface-muted);
    color: var(--np-color-text-secondary);
    font-size: var(--np-fs-small);
    font-weight: 400;
    line-height: 1.5;
}
/* On the selected tab the count belongs to the active label, so it takes the brand
   colour rather than staying muted and reading as a separate, inactive control. */
.np-tabs__link--active .np-tabs__count,
.np-tabs__link[aria-selected="true"] .np-tabs__count {
    background: var(--np-color-surface);
    border-color: var(--np-color-brand-primary);
    color: var(--np-color-brand-primary);
}

/* NOTE: .np-confirm--wide is NOT defined here. It lives with the rest of the
   np-confirm rules further down this file, because it overrides .np-confirm's own
   max-width at EQUAL specificity — one class each — so it only wins if it comes
   later in source order. Defined here it silently lost, and every "wide" modal
   rendered at the 40rem prose width. Keep it next to the rule it overrides. */

/* Progress meter inside the "Processing" modal. Paired with a text count beside
   it — a bar alone encodes the value in length only, which is not readable by a
   screen reader or at a glance. */
/* Themed, not the browser's default bar (a bordered grey groove with a system-
   blue fill). A slim line, no border, the organisation's primary for what is
   done and a light grey track for what is left. The grey is mixed from the text colour so it stays a visible neutral on
   both the light and the dark surface without a second token.

   <progress> is styled through vendor pseudo-elements; they cannot share one
   selector list, because a browser drops a whole rule on a pseudo it does not
   recognise. */
.np-progress {
    --np-progress-track: color-mix(in srgb, var(--np-color-text-primary) 12%, transparent);
    -webkit-appearance: none;
    appearance: none;
    display: block;
    width: 100%;
    max-width: 10rem;
    height: 0.5rem;
    margin: 4px 0 2px;
    border: 0;
    border-radius: 999px;
    overflow: hidden;
    background: var(--np-progress-track);       /* Firefox paints the track here */
    color: var(--np-color-brand-primary);       /* legacy fill colour fallback */
}
.np-progress::-webkit-progress-bar {
    background: var(--np-progress-track);
    border-radius: 999px;
}
.np-progress::-webkit-progress-value {
    background: var(--np-color-brand-primary);
    border-radius: 999px;
}
.np-progress::-moz-progress-bar {
    background: var(--np-color-brand-primary);
    border-radius: 999px;
}
/* ------------------------------------------------------------
   Reports modal — report row + its send history as ONE visual group.

   Kept deliberately light: white rows, no accent bars, no tinted report row and as
   few borders as the grouping can survive on. The earlier version painted every
   report row and drew a 3px brand rule down the side of both it and its history,
   which read as five heavy cards rather than a list and buried the status pills —
   the signals a clinician is actually scanning for — in decoration.

   The grouping now rests on two quiet signals instead: the report row drops its
   bottom border so it and its history read as one block, and the history sits in a
   softly tinted, rounded panel so the nested table never mixes with the outer one.
   A gap row with a hairline closes each group.
   ------------------------------------------------------------ */

/* The report row itself: white, like every other list row in the product. Its bottom
   border is dropped so the history below is visibly attached to it. */
.np-report-table > tbody > tr.np-report-row > td {
    border-bottom: 0;
}

/* Expandable send history. Sits in a full-width row under its report, indented so
   the hierarchy is legible without a rule to draw it. */
.np-report-history__row > td {
    padding-top: 0;
    padding-left: var(--np-space-4);
    border-top: 0;
    border-bottom: 0;
}
.np-report-history > summary {
    cursor: pointer;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
    padding: var(--np-space-1) 0;
}
.np-report-history > summary:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
    border-radius: var(--np-radius-1);
}
.np-report-history[open] > summary { color: var(--np-color-text-primary); }

/* The opened history sits in a softly tinted, rounded panel. The tint is what stops
   the nested table blurring into the white one around it — WITHOUT a border box, so
   the modal keeps its light, low-clutter look. Its own rows then stay white, which
   is the inversion that makes the sub-table readable at a glance. */
.np-report-history__panel {
    margin: var(--np-space-2) 0;
    border-radius: var(--np-radius-2, 6px);
    background: var(--np-color-surface-muted, #fbf7fc);
    overflow: hidden;
}
.np-report-history__panel > table { margin-bottom: 0; background: transparent; }
/* Quiet header: the nested table is supporting detail, so its column labels must not
   compete with the real table's. */
.np-report-history__panel thead th {
    border-bottom: 1px solid var(--np-color-border, #ead8ee);
    font-size: var(--np-fs-xsmall, 0.75rem);
    color: var(--np-color-text-muted);
    text-transform: none;
}
/* Row separators inside the panel only — the outer table already has its own. */
.np-report-history__panel tbody td {
    border-top: 0;
    border-bottom: 1px solid var(--np-color-border, #ead8ee);
    background: transparent;
}
.np-report-history__panel tbody tr:last-child td { border-bottom: 0; }

/* Closes one group before the next begins. A spacer row rather than a margin,
   because margins do not apply between table rows. The hairline is the ONLY border
   between two reports, which is what keeps a long list scannable without boxing each
   entry. aria-hidden in the markup, so it is never announced as an empty row. */
.np-report-table > tbody > tr.np-report-gap > td {
    height: var(--np-space-3);
    padding: 0;
    border: 0;
    border-bottom: 1px solid var(--np-color-border, #ead8ee);
    background: transparent;
}
/* Nothing follows the last group, so a rule there would just underline the table. */
.np-report-table > tbody > tr.np-report-gap:last-child > td {
    height: 0;
    border-bottom: 0;
}

/* The Report Type column's short-lived .np-more* rules are GONE — that cell now
   renders through the shared .np-overflow pattern like every other overflow cell.
   It was a third implementation of one idea and behaved differently again: it kept
   "+N more" on screen beside the values it had just revealed. */

/* The "what this table is not showing" note (design D3 / H7). Deliberately quiet:
   it qualifies the table rather than warning about it, and a loud panel under every
   list is a panel that stops being read. */
.np-reconciliation-note {
    display: flex;
    align-items: flex-start;
    gap: var(--np-space-2);
    margin: var(--np-space-3) 0 0;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
}
.np-reconciliation-note__icon {
    flex: 0 0 auto;
    width: 1rem;
    height: 1rem;
    margin-top: 0.15rem;
}

/* ── Assessment dashboard: clickable count cells ──────────────────────────
   Narrow numeric columns on the patient list's Assessments tab. Centred and
   tabular-figure aligned so a column of counts can be scanned vertically —
   proportional digits make 1 and 7 sit at different widths and the column
   stops reading as a column. */
.np-count-cell {
    text-align: center;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* The count itself. A real <button> (keyboard-operable, announced as a
   control) styled as a filled badge — NOT a bare <span> with a click
   handler, which is unreachable without a mouse.

   The badge's TONE follows the §6.6 status vocabulary (green = done, blue =
   in progress, amber = waiting on someone, grey = a total). Colour is
   reinforcement only: the column header and the number carry the meaning, so
   the cell still reads correctly in monochrome (WCAG 1.4.1). The affordance is
   likewise not the colour — it is the badge's border, its hover lift and its
   focus ring, all of which survive a colour-blind or high-contrast viewer. */
.np-count-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 2px;
    /* WCAG 2.2 AA 2.5.8 (target size, minimum) is 24x24 CSS px. Set explicitly
       rather than left to inherited line-height, which a type-scale change could
       quietly drop below the floor. */
    min-width: 2.25rem;
    min-height: 1.75rem;
    padding: 2px var(--np-space-2);
    border: 1px solid transparent;
    border-radius: var(--np-radius-pill);
    background: transparent;
    color: var(--np-color-brand-primary);
    font: inherit;
    font-variant-numeric: tabular-nums;
    font-weight: 400;
    cursor: pointer;
    transition: filter 0.15s ease, box-shadow 0.15s ease;
}
/* Hover deepens the badge's own tint rather than swapping it for a shared
   surface colour — the tone must stay legible while the pointer is on it. */
.np-count-link:hover {
    filter: brightness(0.96);
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.10);
}
/* :focus-visible on every custom control (uicompliance/ui.accessibility.md). */
.np-count-link:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}

/* ── Count badge tones ────────────────────────────────────────────────────
   Same palette as the np-status pills, so a green count and a green status
   pill on one screen mean related things. Backgrounds are the shared tokens
   where they exist; the blue and amber are the literal np-status values
   (those tones predate the token set and are inlined there too). */
.np-count-link--neutral {
    background: var(--np-color-surface-muted);
    border-color: var(--np-color-border);
    color: var(--np-color-text-secondary);
    /* A total, not outstanding work — normal body weight so the actionable
       columns lead the eye (D9). */
    font-weight: 400;
}
.np-count-link--active {
    background: var(--np-color-success-bg);
    border-color: var(--np-color-success-border);
    color: var(--np-color-success-fg);
}
.np-count-link--pending {
    background: #E5F0FB;
    border-color: #C4DAF0;
    color: #1F4F8A;
}
.np-count-link--warning {
    background: #FFF5E0;
    border-color: #F0DCA0;
    color: #855A00;
}
/* Not started / not sent — the traffic light's bottom rung (§6.6). Reuses the
   shared error tokens, the same palette npb-badge--danger and np-status--danger
   already draw from, so red means one thing across pills, badges and counts. */
.np-count-link--danger {
    background: var(--np-color-error-bg);
    border-color: #F5C2C7;
    color: var(--np-color-error-fg);
}
/* Inherits the badge's tone colour at a lighter weight — a separate muted grey
   would fight the tint it sits inside. */
.np-count-link__sep {
    opacity: 0.6;
    font-weight: 400;
}

/* Zero and not-applicable. Both are muted and NOT interactive — there is
   nothing to open. They are visually distinct from each other because they say
   different things: "0" is a real count, "—" means the column does not apply
   to this row at all. */
.np-count-cell__zero {
    color: var(--np-color-text-muted);
    font-variant-numeric: tabular-nums;
}
.np-count-cell__na {
    color: var(--np-color-text-muted);
    cursor: help;
}

/* ── Clinicians cell: "first clinician +N" disclosure ─────────────────────
   A <details>/<summary>, so the full list opens by keyboard and touch — hover
   alone would leave it unreachable for both. */
/* The Clinicians column's own .np-clinicians* rules are GONE — the cell renders
   through the shared .np-overflow pattern below. Duplicating the markup here is
   what let the two drift apart: this copy re-listed the clinician already shown
   in the summary, so "Dr Senthil +1" expanded to Dr Senthil twice. */

/* ── Multi-value cell: the ONE "+N" overflow pattern ──────────────────────
   Used by every overflow cell in the product (Report Type, Clinicians, the
   Unassigned tab's Assessments / Who-should-assess, and anything added later).

   A <details>/<summary> so the overflow opens by click, touch AND keyboard —
   "+N" as plain text with a title tooltip is reachable by neither of the last
   two, and needs no JavaScript to work.

   Expanding REPLACES the collapsed view rather than adding to it: the lead value
   and the "+N" chip are hidden, the full list is shown once, and the summary
   becomes a Collapse control moved visually BELOW the list with flex `order`.
   DOM order is untouched (summary stays first), and the summary is the only
   focusable node in the cell, so visual order cannot desynchronise from focus
   order. */
.np-overflow { display: inline-block; }
.np-overflow__summary {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--np-space-2);
    cursor: pointer;
    list-style: none;
}
/* Suppress the native disclosure triangle — the "+N" chip IS the affordance. */
.np-overflow__summary::-webkit-details-marker { display: none; }
.np-overflow__summary::marker { content: ""; }
.np-overflow__summary:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
    border-radius: var(--np-radius-1);
}
.np-overflow__more {
    flex: none;
    padding: 0 6px;
    border-radius: var(--np-radius-pill);
    background: var(--np-color-surface-muted);
    border: 1px solid var(--np-color-border);
    color: var(--np-color-text-secondary);
    font-size: var(--np-fs-small);
    font-weight: 400;
    white-space: nowrap;
}
.np-overflow__list {
    margin: var(--np-space-2) 0 0;
    padding-left: var(--np-space-4);
    font-size: var(--np-fs-small);
    color: var(--np-color-text-secondary);
}

/* Collapsed: the Collapse control does not exist yet. */
.np-overflow:not([open]) .np-overflow__collapse { display: none; }

/* Open: flip the two states, and put the control after the list.
   `display: flex` is applied ONLY on [open] — restyling a closed <details> is
   what breaks the browser's native hiding of its non-summary children. */
.np-overflow[open] {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}
.np-overflow[open] > .np-overflow__list { order: 1; margin-top: 0; }
.np-overflow[open] > .np-overflow__summary { order: 2; }
/* The lead value is now the first row of the list — showing it here too is the
   duplication this pattern exists to remove. */
.np-overflow[open] .np-overflow__lead,
.np-overflow[open] .np-overflow__more { display: none; }
.np-overflow__collapse {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-top: var(--np-space-1);
    color: var(--np-color-text-secondary);
    font-size: var(--np-fs-small);
    font-weight: 400;
    white-space: nowrap;
}
.np-overflow__summary:hover .np-overflow__collapse,
.np-overflow__summary:hover .np-overflow__more { color: var(--np-color-brand-primary); }
.np-overflow__caret { flex: none; }

/* Muted in-cell text that is a STATEMENT, not missing data ("Unassigned").
   Distinct from an empty cell, which reads as absent information. */
.np-cell__muted {
    color: var(--np-color-text-muted);
    font-style: italic;
}

/* Task list: the patient-record link under a task's subject. Secondary text,
   so it does not compete with the subject it sits beneath. */
.np-task-row__patient {
    font-size: var(--np-fs-small);
    margin-top: 2px;
}
.np-task-row__patient a {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

/* One-line caption at the top of a list card — says what the list below
   contains (e.g. the active tab's purpose). Lives INSIDE the card: on the page
   background between the tab pills and the card it read as an orphaned
   fragment with nothing to anchor it. Muted and small so it captions the list
   rather than competing with the page heading. */
.np-list-caption {
    margin: 0 0 var(--np-space-3);
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
    line-height: 1.4;
}

/* ============================================================
   Wide data tables — sticky header + sticky first column.
   ------------------------------------------------------------
   Used by the patient list, where the row carries more columns
   than a laptop viewport fits.

   Accessibility note (WCAG): the table stays on the design
   system's SMALL text scale (14px) — it is not shrunk further to
   squeeze columns in. 1.4.4 (resize text to 200%) and 1.4.12
   (text spacing) both bite well before a font size small enough
   to fit nine composite columns on a 1366px screen, and dense
   clinical data is exactly the content that must stay legible.
   Horizontal scrolling is the documented fallback instead.

   The container is focusable (tabindex="0" in the markup) with
   role="region" so a keyboard-only user can scroll it — a
   scrollable region that cannot receive focus is a 2.1.1 failure.
   ============================================================ */
.np-table-scroll {
    overflow: auto;
    /* A bounded height is what makes the sticky header stick: sticky
       positions against the nearest scrolling ancestor, so the table needs
       its own vertical scroll viewport rather than the page's. */
    max-height: calc(100vh - 21rem);
    min-height: 12rem;
    border-radius: var(--np-radius-md, 8px);
}
.np-table-scroll:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}
.np-table-scroll > .table {
    margin-bottom: 0;
}

/* Dense variant — the small type scale the design system already uses for
   data-heavy tables, with tighter cell padding. */
.np-table--dense {
    font-size: var(--np-fs-small);
}
.np-table--dense > thead > tr > th,
.np-table--dense > tbody > tr > td {
    padding-top: var(--np-space-2);
    padding-bottom: var(--np-space-2);
    white-space: nowrap;
}

/* Serial-number ("#") column — the row's position in the whole list, not on the
   page. Shrinks to its content, right-aligned with tabular figures so 9, 10 and
   501 line up. Deliberately NOT sticky: the patient name beside it is the pinned
   column, and this one scrolls away underneath it. */
.np-serial-col {
    width: 1%;
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--np-color-text-muted);
}

/* Header row pinned during vertical scroll. Opaque background is mandatory —
   a transparent sticky header lets rows show through as they pass under it. */
.np-table-sticky thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--np-color-surface, #fff);
    box-shadow: 0 1px 0 0 var(--np-color-border);
}

/* First column pinned during horizontal scroll, so a row stays identifiable
   however far right the reader scrolls. The right-hand shadow signals
   "content is scrolled under here". */
.np-table-sticky .np-table-sticky__col {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--np-color-surface, #fff);
    box-shadow: 8px 0 8px -8px rgba(15, 23, 42, 0.12);
}
/* The header's first cell is sticky on BOTH axes, so it must outrank each. */
.np-table-sticky thead th.np-table-sticky__col {
    z-index: 3;
}

/* LAST column pinned to the right edge — the mirror of np-table-sticky__col, for a
   table whose action belongs at the end of the row rather than the start (the
   assessment bucket modals). The shadow points the other way: it signals "content is
   scrolled under here" from the right.

   Note this wrapper class is np-table-sticky-end, NOT np-table-sticky: a table that
   pins its trailing column does not necessarily pin its leading one, and combining
   both on a narrow viewport would leave almost no scrollable middle. */
.np-table-sticky-end thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--np-color-surface, #fff);
    box-shadow: 0 1px 0 0 var(--np-color-border);
}
.np-table-sticky-end .np-table-sticky__col-end {
    position: sticky;
    right: 0;
    z-index: 1;
    background: var(--np-color-surface, #fff);
    box-shadow: -8px 0 8px -8px rgba(15, 23, 42, 0.12);
}
.np-table-sticky-end thead th.np-table-sticky__col-end {
    z-index: 3;
}
.np-table-sticky-end tbody tr:hover > .np-table-sticky__col-end {
    background: var(--np-color-surface-tinted, #faf7fb);
}
/* Same mobile release as the first-column pin: on a narrow screen the pinned cell
   eats too much of the viewport to be worth its cost. */
@media (max-width: 767.98px) {
    .np-table-sticky-end thead th { position: static; box-shadow: none; }
    .np-table-sticky-end .np-table-sticky__col-end { position: static; box-shadow: none; }
}
/* Row hover must repaint the sticky cell too, otherwise the pinned column
   stays white while the rest of the row highlights. */
.np-table-sticky tbody tr:hover > .np-table-sticky__col {
    background: var(--np-color-surface-tinted, #faf7fb);
}

/* Returning to a list from a record (the row link carries #p-{id}) scrolls the
   row into view; this marks WHICH row so the clinician re-orients without
   re-reading the page. The left rule is the signal — the tint alone would be
   indistinguishable from the hover state, and colour alone is not a signal
   (WCAG 1.4.1). Fades out so it never reads as a persistent selection. */
.np-table-sticky tbody tr:target > td {
    background: var(--np-color-surface-tinted, #faf7fb);
    animation: np-row-target-fade 2.4s ease-out 1.2s forwards;
}
/* The sticky cell restates its own right-hand scroll shadow alongside the
   marker — this rule out-specifies the sticky-column rule, so omitting it would
   drop that shadow on the one row we just highlighted. */
.np-table-sticky tbody tr:target > td.np-table-sticky__col {
    box-shadow: inset 3px 0 0 0 var(--np-color-primary, #6b2f7a),
                8px 0 8px -8px rgba(15, 23, 42, 0.12);
    animation: np-row-target-marker-fade 2.4s ease-out 1.2s forwards;
}
@keyframes np-row-target-fade {
    to { background: transparent; }
}
@keyframes np-row-target-marker-fade {
    to { box-shadow: inset 0 0 0 0 transparent,
                     8px 0 8px -8px rgba(15, 23, 42, 0.12); }
}
/* No fade for readers who ask for reduced motion — the marker simply stays. */
@media (prefers-reduced-motion: reduce) {
    .np-table-sticky tbody tr:target > td,
    .np-table-sticky tbody tr:target > td.np-table-sticky__col { animation: none; }
}

/* Badge line inside a composite cell (e.g. the priority pill under a patient
   name). Absent entirely when there is nothing to show — never an empty row. */
.np-cell__badges {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 4px;
}

/* A composite cell's primary line carrying its own action beside the value —
   e.g. the Pre-Triage list's "Registration: [Sent] [send icon]", where the send
   control belongs to that column rather than to a shared Actions bucket. The
   icon button keeps its 32px hit target; the row just lays the two out.

   A two-track grid, not a flex row: the pill sits in a track as wide as the
   longest state ("Submitted"), so the action lands in the SAME column on every
   row. In a flex row it trailed the pill and zig-zagged down the list with each
   row's state, which made a column of send buttons hard to run an eye down. */
.np-cell__action-row {
    display: grid;
    grid-template-columns: minmax(7rem, max-content) 32px;
    align-items: center;
    gap: var(--np-space-2);
    width: max-content;
}
.np-cell__action-row > .np-status { justify-self: start; }
/* A disabled in-cell action (e.g. no care episode, so no pre-assessment to
   send) stays visible and keeps its tooltip — vanishing would leave a gap the
   reader has to interpret. */
.np-cell__action-row .np-row-action:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* Secondary (muted) line in a composite cell sits one step below the primary
   value, so the eye lands on the identifier first. Scoped to the dense table:
   in it the primary value is already at the small scale, which left the two
   lines the same size and the hierarchy flat. 12px is the design system's
   smallest body step and stays above the WCAG-usable floor at 200% zoom. */
.np-table--dense .np-cell__secondary {
    font-size: var(--np-fs-xsmall, 0.75rem);
}

/* ------------------------------------------------------------
   List toolbar — search + filters + actions on ONE row.
   The search input is the only flexible element, so the buttons
   and filter selects keep their natural width and the row never
   wraps until the viewport genuinely cannot hold it.
   ------------------------------------------------------------ */
.np-list-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-2);
    margin-bottom: var(--np-space-4);
}
/* Takes the leftover width. min-width:0 lets it shrink below its intrinsic
   size instead of forcing the row to wrap (the classic flex-item overflow). */
.np-list-toolbar__search {
    flex: 1 1 240px;
    min-width: 0;
}
.np-list-toolbar__search .np-input { width: 100%; }
.np-list-toolbar__filter { flex: 0 0 auto; }
.np-list-toolbar__filter .np-select { width: auto; }
/* Pushed to the far end so the primary action always sits at the row's edge,
   wherever the filters end. */
.np-list-toolbar__add { margin-left: auto; }

@media (max-width: 575.98px) {
    /* Single column on a phone — an "all on one row" toolbar at 360px would
       squeeze the search box to uselessness. */
    .np-list-toolbar > * { flex: 1 1 100%; }
    .np-list-toolbar__add { margin-left: 0; text-align: center; }
}

/* On small screens the bounded height fights the on-screen keyboard and short
   viewports; let the page scroll instead and keep only the sticky column. */
@media (max-width: 767.98px) {
    .np-table-scroll { max-height: none; }
    .np-table-sticky thead th { position: static; box-shadow: none; }
}

/* ------------------------------------------------------------
   Responsive tables — one row + an expandable details row (np-responsive-table.js).

   The documented answer for a wide table used to be horizontal scrolling only.
   That is defensible on a laptop and unusable on a phone: a nine-column clinical
   list becomes a narrow porthole dragged sideways, and the reader loses the row
   they were on. WCAG 2.2 1.4.10 (Reflow) also expects content to work at 320px
   without two-dimensional scrolling.

   Below the table breakpoint the script keeps the identity column and Actions,
   and folds every other column into a <dl> underneath, revealed by a toggle. The
   classes below are inert until it adds them, so a table renders exactly as
   before if the script fails to load — horizontal scroll remains the fallback.
   ------------------------------------------------------------ */

/* The script sets this on the header cell AND the body cell of a folded column,
   so the two can never disagree about how many columns the row has. */
.np-rt-hidden { display: none; }

/* The toggle sits before the identity value, where the eye starts. A real
   <button> (§3.11) — it is keyboard-reachable, announces its own expanded state
   and names its own row. */
.np-rt__toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* WCAG 2.2 2.5.8 minimum target size — this is the primary control on a
       touch screen, so it is not shrunk to match the desktop icon scale. */
    width: 28px;
    height: 28px;
    margin-right: var(--np-space-2);
    padding: 0;
    vertical-align: middle;
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-1);
    background: var(--np-color-surface);
    color: var(--np-color-text-secondary);
    cursor: pointer;
}
.np-rt__toggle:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}
.np-rt__toggle svg { transition: transform 120ms ease; }
.np-rt__toggle[aria-expanded="true"] {
    background: var(--np-color-surface-muted);
    color: var(--np-color-brand-primary);
}
.np-rt__toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
    .np-rt__toggle svg { transition: none; }
}

/* The details row. Tinted and inset so it reads as belonging to the row above
   rather than as another record in the list. */
.np-rt__child > td {
    padding: var(--np-space-3) var(--np-space-3) var(--np-space-3) calc(var(--np-space-3) + 36px);
    background: var(--np-color-surface-muted);
    /* .np-table--dense sets nowrap on every cell; a details row is prose-shaped
       and must wrap or it reintroduces the horizontal scroll it exists to remove. */
    white-space: normal;
}
.np-rt__list {
    display: grid;
    grid-template-columns: minmax(7rem, 38%) 1fr;
    gap: var(--np-space-2) var(--np-space-3);
    margin: 0;
}
.np-rt__list > dt {
    margin: 0;
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-xsmall, 0.75rem);
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    align-self: start;
}
.np-rt__list > dd {
    margin: 0;
    min-width: 0;
    word-break: break-word;
}
/* A folded column with nothing in it still gets its label, so the reader can tell
   "no value recorded" from "this table has no such column". */
.np-rt__list > dd:empty::after {
    content: "—";
    color: var(--np-color-text-muted);
}
/* The identity cell now carries a control, so it must not be clipped to one line. */
.np-rt-active .np-rt__row > td { white-space: normal; }
/* A pinned first column is pointless once the row is two columns wide, and its
   shadow reads as a stray rule down the middle of a phone screen. */
.np-rt-active .np-table-sticky__col {
    position: static;
    box-shadow: none;
}


/* ============================================================
   Compact-layout overrides — reduce wasted whitespace between
   aside / topbar / content / footer.
   ------------------------------------------------------------
   Earlier iterations of the shell were tuned for a marketing
   landing page (generous breathing room). For dense
   clinician-facing list / form screens that pushed the table
   below the fold and left wide empty bands on either side.
   These overrides pull the content tighter:
   - np-main:   trim top/bottom padding
   - np-shell__content: fluid up to 1600px, smaller gutters
   - np-card:   keep margin-bottom but tighten internal padding
                on the first card of a screen
   - np-topbar: slimmer
   ============================================================ */
.np-main { padding: 0 0 var(--np-space-5) 0; }

.np-shell__content {
    max-width: 1600px;
    padding: 0 var(--np-space-5);
}
@media (max-width: 991.98px) {
    .np-shell__content { padding: 0 var(--np-space-4); }
}
@media (max-width: 575px) {
    .np-shell__content { padding: 0 var(--np-space-3); }
}

/* 12px of breathing room under the topbar — as PADDING on the content, not a
   margin on the first child, because the first child is now often a display:none
   page-head element (see "Page head compaction" below) and a margin on a hidden
   box collapses to nothing, leaving the first visible row flush to the topbar. */
.np-shell__content { padding-top: var(--np-space-3); }
.np-shell__content > :first-child { margin-top: 0; }

/* Page head.
   Every screen carries a visible head block — eyebrow (the module it belongs to),
   <h1> (the screen), and an intro line where the screen has something worth saying:

       PATIENT MANAGEMENT
       Patients
       All clinicians, administrators, and support users in this organisation.

   The markup already lives in the views; this block only sets the head's rhythm.

   It previously HID all three (`display: none` on the eyebrow, on a list page's
   <h1>, and on the intro paragraph) on the argument that the topbar path said the
   same thing in less space. That took the page heading off ~190 screens, which is
   not a trade the topbar crumb can cover — the crumb is navigation chrome, the head
   is the page. The suppression is deleted rather than commented out; if above-the-
   fold space needs winning back, compact the head, don't remove it.

   The topbar's breadcrumb leaf is a <span>, not the page <h1> (see _Header.cshtml),
   so restoring these does not leave two <h1>s on a page. */
.np-shell__content > h1 {
    margin: 0 0 var(--np-space-4);
}
/* When an intro line follows, the heading hands straight over to it — the pair is
   one block, so the gap belongs below the pair, not inside it. */
.np-shell__content > h1:has(+ p.text-muted),
.np-shell__content > h1:has(+ p.np-lead) {
    margin-bottom: var(--np-space-2);
}

/* Intro line under the heading: one compact muted line, not a paragraph block. */
.np-shell__content > h1 + p.text-muted,
.np-shell__content > h1 + p.np-lead,
.np-shell__content--list > p.text-muted:first-of-type,
.np-shell__content--list > p.np-lead:first-of-type {
    margin-bottom: var(--np-space-4);
}


/* ---------- LIST screens: drop the duplicated page head ----------
   On a list screen the topbar path already reads "Administration / Users", so
   the eyebrow ("User management"), the <h1> ("Users") and the intro line
   ("All clinicians, administrators, and support users…") say the same thing a
   third time and push the actual table below the fold. Product-owner decision,
   #441 follow-up: on LIST screens only, the table is the page.

   Scoped to --list deliberately. Details / Create / Edit keep their full head,
   because there the <h1> carries information the path does not — a patient's
   name, "Modify …" — and the intro line is often the only place a field's
   constraints are explained.

   ONE CORRECTION to the request, and it is not negotiable: the <h1> is
   VISUALLY hidden, not removed. Deleting it leaves the page with no <h1> at
   all, and the topbar's breadcrumb leaf is a <span>, not a heading (see
   _Header.cshtml). A screen-reader user navigating by heading — the primary way
   they move around a page — would land on a list screen with nothing to orient
   against, and "skip to main content" would drop them into an unlabelled table.
   Hidden-but-announced costs zero pixels and keeps the document outline intact
   (WCAG 2.2 SC 1.3.1 / 2.4.6).

   Implemented with the same clip-rect pattern as .visually-hidden rather than
   display:none, because display:none removes it from the accessibility tree
   too — which would be the deletion this comment exists to prevent. */
.np-shell__content--list > .np-eyebrow,
.np-shell__content--list > h1 + p.text-muted,
.np-shell__content--list > h1 + p.np-lead,
.np-shell__content--list > p.text-muted:first-of-type,
.np-shell__content--list > p.np-lead:first-of-type,
/* Patients wraps its heading in .np-page-head with a hint line beside it
   ("Hover on each tab for more information"). display:none rather than clipped,
   because the hint describes a HOVER affordance — advice a screen-reader or
   keyboard user cannot act on, so removing it from the accessibility tree is
   the correct outcome rather than a side effect. The tab pills keep their own
   title/aria descriptions, which is where that information actually belongs. */
.np-shell__content--list .np-page-hint {
    display: none;
}

/* Same clip treatment for a heading nested in a page-head wrapper. Clipping the
   WRAPPER rather than the h1 inside it also collapses the flex row's own
   margins, which a hidden child would otherwise leave behind as a gap. */
.np-shell__content--list > .np-page-head,
.np-shell__content--list > h1 {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Tabs: pull closer to the heading. */
.np-tabs { margin: var(--np-space-3) 0 var(--np-space-3); }

/* Cards: tighter internal padding for screens that pack a lot in. */
.np-card { padding: var(--np-space-5); margin-bottom: var(--np-space-4); }
.np-shell__content > .np-card:last-child { margin-bottom: var(--np-space-3); }

/* Topbar: slimmer + lighter. */
:root { --np-topbar-height: 48px; }
.np-topbar { padding: 0 var(--np-space-4); }

/* Footer footprint: keep the brand but drop the giant top-margin
   it inherited from the marketing layout. */
/*.np-shell__main .np-footer {
    margin-top: auto;
    padding: var(--np-space-3) 0;
}*/
.np-shell__main .np-footer__bar { padding-top: 0; }


/* Composite list cells — two-line cell where the first line is the
   primary value (e.g. MRN, legal name, formatted DoB) and the second
   line is a supporting value (e.g. NHS Number, preferred name, age).
   Used in lists where collapsing two related columns into one keeps
   the table compact and the related values visually paired. */
.np-cell__primary {
    display: block;
    color: var(--np-color-text-primary);
    font-weight: 400;
    line-height: 1.3;
}
.np-cell__secondary {
    display: block;
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-xsmall);
    line-height: 1.3;
    margin-top: 2px;
}

/* MRN and NHS Number are rendered as <code> in the patient lists because they are
   exact identifiers a clinician reads back character by character — the ELEMENT is
   the right semantic. Its default UA styling is not: the browser drops in its own
   monospace family at ~0.85em, so the MRN was the one cell in the table not in the
   product's typeface, sitting visibly smaller than the name beside it. Take the
   typography from the surrounding cell and leave the semantics alone. Same fix, same
   reason, as `.npb__fact-value--code` in the patient banner. */
code.np-cell__primary {
    font-family: inherit;
    font-size: inherit;
}
code.np-cell__secondary {
    font-family: inherit;
    font-size: var(--np-fs-small);
}

/* Two-line person cell (_NpPersonName.cshtml): name above, profession beneath.
   Block so the two child spans stack — the children already carry the type scale,
   so this adds nothing else. */
.np-person {
    display: block;
}

/* Link that wraps a composite cell's primary value (e.g. Name in the
   patient list opens the workspace in a new tab). Carries the brand
   underline-on-hover so the cell still looks like a link without
   blowing up the secondary value's typography. */
.np-cell__link {
    text-decoration: none;
    color: inherit;
}
.np-cell__link:hover .np-cell__primary,
.np-cell__link:focus-visible .np-cell__primary {
    color: var(--np-color-brand-primary);
    text-decoration: underline;
    text-underline-offset: 3px;
}
.np-cell__link:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
    border-radius: 4px;
}

/* HIGH CLINICAL PRIORITY — carried by the patient's NAME rather than by a pill
   beside it (see TriagePriorityDisplay for the why). A badge on every triaged
   row put a second coloured object next to every name, and on a 200-row tab the
   one row that mattered was lost among the rows saying "Normal".

   The colour does not travel alone: the link's hover text reads
   "High Priority — <name>", and the cell carries a visually-hidden
   "— High Priority" so assistive technology announces it as part of the row.
   (It is NOT reinforced by weight — the product runs on a single 400 weight
   product-wide, guarded by TypographyScaleSweepTests, so a bolder name is not
   available as a signal here.) --np-color-danger-fg is the same red the danger
   status pill uses and is theme-aware, so this reads correctly in dark mode too.

   The hover/focus rules restate the colour because the base .np-cell__link hover
   repaints .np-cell__primary brand-purple; the underline it adds is kept — only
   the colour is held. */
.np-cell__link--priority .np-cell__primary {
    color: var(--np-color-danger-fg, #a41b17);
}
.np-cell__link--priority:hover .np-cell__primary,
.np-cell__link--priority:focus-visible .np-cell__primary {
    color: var(--np-color-danger-fg, #a41b17);
}
.np-cell__link--priority:focus-visible {
    outline-color: var(--np-color-danger-fg, #a41b17);
}

/* Inline "Read more" trigger (e.g. truncated message in a list cell). Renders
   as brand-coloured text, not a button, so it reads as a link inside the cell. */
.np-readmore {
    background: none;
    border: 0;
    padding: 0;
    margin-left: 4px;
    font: inherit;
    font-weight: 400;
    color: var(--np-color-brand-primary);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
    white-space: nowrap;
}
.np-readmore:hover,
.np-readmore:focus-visible {
    text-decoration: none;
}
.np-readmore:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
    border-radius: 4px;
}

/* Enquiry change log. The details card shows the latest entry inline; the
   green indicator (reused from the Roles "Assignments" column) opens a popup
   listing the full history in a table. */
.np-changelog__entry {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.np-changelog__message { color: var(--np-color-text); }
.np-changelog__time {
    font-size: 0.85rem;
    color: var(--np-color-text-muted);
}
/* Popup is much wider than the standard confirm dialog so the change-log
   table reads like the enquiry list; the body scrolls when history is long. */
.np-changelog-dialog {
    width: min(98vw, 80rem);
    max-width: min(98vw, 80rem);
}
.np-changelog-dialog__body {
    max-height: 65vh;
    overflow-y: auto;
    margin-bottom: var(--np-space-5, 20px);
}
/* Message column carries the most text — give it room. */
.np-changelog-dialog__body td:first-child,
.np-changelog-dialog__body th:first-child { width: 55%; }
/* Keep "Updated by" and "Updated on" on a single line each so the date never
   wraps down the row; align them to the top of the (possibly 2-line) message. */
.np-changelog-dialog__body td:nth-child(2),
.np-changelog-dialog__body th:nth-child(2),
.np-changelog-dialog__body td:nth-child(3),
.np-changelog-dialog__body th:nth-child(3) {
    white-space: nowrap;
    vertical-align: top;
}

/* Message cell clamps to two lines (like the enquiry list) until "Read more"
   expands it inline. Full column width — no narrow max-width here. */
.np-changelog-dialog__message {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-word;
}

/* Truncated message preview in a list cell — clamps to two lines with an
   ellipsis so rows keep a uniform height regardless of message length. The
   full text is reachable via the adjacent "Read more" trigger. */
.np-cell__message {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    max-width: 260px;
    line-height: 1.4;
    word-break: break-word;
}

/* ============================================================
   Patient workspace — Notes section (_PatientNotes.cshtml).
   Toolbar, search row, inline add-note form, the per-row Source
   pill, and the Alert / Reminder type badges.
   ============================================================ */
.np-notes__toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-2);
    margin-bottom: var(--np-space-3);
}

.np-notes__toolbar-title {
    font-size: var(--np-fs-h3);
    margin: 0;
}

/* A one-line status on the left of the toolbar (the empty-state message or a
   count) with its ⓘ at the end of the same line — so the icon never sits alone
   on a row above the text it explains (_PatientTasks). Muted: it is state, not a
   heading. */
.np-notes__toolbar-status {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--np-space-1, 4px) var(--np-space-2);
    margin: 0;
    color: var(--np-color-text-muted);
}

/* Action buttons sit on the right (Show deleted / Print / Add new / Refresh). */
.np-notes__toolbar-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-2);
    margin-left: auto;
}

.np-notes__search {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-2);
    margin-bottom: var(--np-space-3);
}

.np-notes__search .np-input {
    flex: 1 1 16rem;
    min-width: 12rem;
}

/* Inline filter dropdown in a search row — auto width so it sits beside the
   search box + button instead of stretching full width. */
.np-notes__search .np-select {
    flex: 0 0 auto;
    width: auto;
    min-width: 9rem;
}

.np-notes__add {
    margin-bottom: var(--np-space-4);
}

/* ── Important reminders accordion (_PatientReminders.cshtml) ──
   Reminder-marked notes surfaced at the top of the workspace. Each item shows
   the note text in its highlight colour with the created-on / author beneath. */
.np-reminders {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Simple divided rows — no separate boxes, just a light rule between items.
   Tight vertical rhythm so the list stays compact. */
.np-reminders__item {
    padding: var(--np-space-2) 0;
    border-bottom: 1px solid var(--np-color-border);
}

.np-reminders__item:first-child {
    padding-top: 0;
}

.np-reminders__item:last-child {
    padding-bottom: 0;
    border-bottom: 0;
}

.np-reminders__text {
    font-weight: 400;
    white-space: pre-wrap;
    word-break: break-word;
}

.np-reminders__meta {
    margin-top: var(--np-space-1);
    font-size: 0.8125rem;
    color: var(--np-color-text-secondary);
}

.np-reminders__hint {
    margin: var(--np-space-3) 0 0;
    font-size: 0.8125rem;
}

/* Inline checkbox — the registration np-checkbox styling (20px brand-accent box)
   without the form-row padding / hover, so it sits tight in a table cell. */
.np-checkbox--inline {
    padding: 0;
    gap: 0;
}

.np-checkbox--inline:hover {
    background: transparent;
}

/* Row action cluster for notes — colour picker + icon buttons on one line. */
.np-notes__actions {
    flex-wrap: nowrap;
}

/* Per-note colour picker — a compact swatch matching the 32px row-action size.
   Same native control the template-master editor uses. */
/* Per-note colour picker — clickable swatches restricted to Black / Red.
   Reads as a small inline colour picker, not a dropdown. The active colour
   carries a brand-coloured ring. */
.np-note-swatches {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    vertical-align: middle;
}

.np-note-swatch {
    width: 20px;
    height: 20px;
    padding: 0;
    border: 2px solid var(--np-color-surface);
    border-radius: 50%;
    box-shadow: 0 0 0 1px var(--np-color-border);
    cursor: pointer;
    transition: box-shadow 0.12s ease, transform 0.12s ease;
}

.np-note-swatch--black { background: #000000; }
.np-note-swatch--red { background: #ff0000; }

.np-note-swatch:hover { transform: scale(1.1); }

.np-note-swatch.is-active {
    box-shadow: 0 0 0 2px var(--np-color-brand-primary);
}

.np-note-swatch:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 2px;
}

/* Source legend — explains the Patient / Enquiry colour key above the list. */
.np-notes__legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-2) var(--np-space-4);
    margin-bottom: var(--np-space-3);
    font-size: 0.8125rem;
}

.np-notes__legend-title {
    font-weight: 400;
    color: var(--np-color-text-secondary);
}

.np-notes__legend-item {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
}

.np-notes__legend-text {
    color: var(--np-color-text-secondary);
}

/* Friendly paging hint shown beneath the pager. */
.np-notes__add-actions {
    display: flex;
    gap: var(--np-space-2);
    margin-top: var(--np-space-2);
}

/* ── Price Quote section ──────────────────────────────────────────────────
   Small layout helpers for the quote accordion body (status override, the
   Send-quote reveal form, and the details dialog). Reuses the shared form /
   list primitives; these only handle spacing. */
.np-quote__status-form {
    margin-bottom: var(--np-space-4);
}

/* Status row — the radio group and the Submit button sit on one line. Each radio
   option is an inline-flex label so the control and its text keep a comfortable,
   consistent gap (the raw native radio sits flush otherwise). */
.np-quote__status-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-4);
}

.np-quote__status-row .np-checkbox-group {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-4);
    margin: 0;
}

.np-quote__status-row .np-checkbox {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    margin: 0;
    cursor: pointer;
}

/* "Recipient opened the quote" indicator, sat at the end of the status row. */
.np-quote__viewed {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-1);
}

/* The Send Price Quote modal matches the template-preview dialog width (720px)
   so the pop-up sizing is consistent across the app, with room for the rich-text
   Content editor. */
.np-quote__send-dialog {
    min-width: min(94vw, 40rem);
    max-width: min(94vw, 720px);
}

/* The per-assessment price adjustment: the amount and its unit (% or currency)
   are one control, so they sit on a row with a real gap rather than stacked flush
   against each other. Wraps to two lines on a narrow dialog, keeping the gap. */
.np-quote__adjust {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--np-space-2);
}

.np-quote__adjust .quote-price-kind { width: auto; min-width: 5.5rem; }

/* The amount box carries its own unit symbol (£ before, % after) so the number is
   never ambiguous once the row scrolls away from the column heading. The wrapper
   draws the border and the input sits inside it borderless, so symbol and value
   read as one control. */
.np-quote__adjust-field {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
    width: 8rem;
    padding: 0 0.55rem;
    border: 1px solid var(--np-color-border);
    border-radius: 6px;
    background: #fff;
}

.np-quote__adjust-field:focus-within {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 1px;
}

.np-quote__adjust-field .quote-price-input {
    flex: 1 1 auto;
    min-width: 0;
    width: auto;
    border: none;
    background: transparent;
    box-shadow: none;
    padding: 0.375rem 0;
}

.np-quote__adjust-field .quote-price-input:focus,
.np-quote__adjust-field .quote-price-input:focus-visible { outline: none; box-shadow: none; }

.np-quote__adjust-symbol {
    color: var(--np-color-text-muted, #6b7280);
    font-weight: 400;
    flex: 0 0 auto;
}

/* The varied-price summary in the confirmation dialog. np-confirm__body zeroes
   the list's own padding, so the bullets need it back or they hang outside the
   dialog's inset. */
.np-quote__adjust-summary {
    padding-left: var(--np-space-6, 24px);
    list-style: disc;
}

.np-quote__adjust-summary li { margin-bottom: var(--np-space-2, 8px); }
.np-quote__adjust-summary li:last-child { margin-bottom: 0; }

/* Assessment checkboxes inside the modal — space the options and give each a
   comfortable gap between the box and its label (native checkbox sits flush). */
.np-quote__send-dialog .np-checkbox-group {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-4);
}

.np-quote__send-dialog .np-checkbox {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    margin: 0;
    cursor: pointer;
}

/* Details dialog definition list — label / value rows. */
.np-quote__details {
    margin: 0 0 var(--np-space-3);
}

.np-quote__details dt {
    font-weight: 400;
    margin-top: var(--np-space-2);
}

.np-quote__details dd {
    margin: 0 0 var(--np-space-1);
}

/* Source pill — Patient vs Enquiry origin. Filled pill + leading dot, styled
   like the status pill so it reads as a coloured category. It intentionally
   uses brand / info hues — NOT the green/amber/red clinical-safety vocabulary
   reserved for true Status columns (§6.6) — so it can't be mistaken for a
   safety signal. */
.np-source-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px 3px 8px;
    border-radius: 999px;
    font-size: 0.8125rem;
    font-weight: 400;
    line-height: 1.4;
    white-space: nowrap;
    letter-spacing: 0.01em;
    border: 1px solid transparent;
}

.np-source-tag__dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: currentColor;
    flex: 0 0 auto;
}

/* Patient — recorded directly. Brand magenta tint. */
.np-source-tag--patient {
    background: #F6E9F4;
    color: #8A1F6E;
    border-color: #E6C4DD;
}

/* Enquiry — carried over from an enquiry. Blue/info tint. */
.np-source-tag--enquiry {
    background: #E5F0FB;
    color: #1F4F8A;
    border-color: #C4DAF0;
}

@media (forced-colors: active) {
    .np-source-tag { border: 1px solid CanvasText; }
}

/* Alert / Reminder badge shown under the Created-on date. */
.np-note-badge {
    display: inline-block;
    margin-top: 2px;
    padding: 1px 7px;
    border-radius: 999px;
    font-size: 0.6875rem;
    font-weight: 400;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    line-height: 1.5;
    white-space: nowrap;
}

.np-note-badge--alert {
    background: #FBE7E9;
    color: #8A1F1F;
}

.np-note-badge--reminder {
    background: #EDE7FB;
    color: #4B2F8A;
}

/* ============================================================
   Patient banner — shared partial _NpPatientBanner.cshtml.

   Layout: a single full-width surface card with two stacked
   regions — the "primary" row (avatar + identity + identifiers
   + demographics) and the "status row" (clinical-safety flag
   pills). The card uses the surface-tinted brand background so
   it reads as a clinical "patient header" distinct from the
   regular np-card surfaces below it.
   ============================================================ */

.np-patient-banner {
    background: linear-gradient(180deg,
        var(--np-color-brand-tint-50) 0%,
        var(--np-color-surface) 100%);
    /* A subtle edge. The banner is the widest element on every patient screen,
       so a saturated outline around it draws the eye to the frame instead of to
       the identifiers inside it — and it is the identifiers that carry the
       clinical-safety weight (right patient, right record). The tint fill is
       what separates the banner from the cards below; the border only has to
       close it, not announce it. */
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-3, 12px);
    padding: var(--np-space-5, 20px) var(--np-space-6, 24px);
    margin-bottom: var(--np-space-4, 16px);
    box-shadow: none;
}

/* Inactive ribbon — surfaces above the primary row when the patient
   record is marked inactive (soft-deleted in a future state, or
   IsActive=false). Reads as a strong contextual warning without
   reaching for the danger palette (which is reserved for clinical-
   safety hazards per CLAUDE.md §6.6). */
.np-patient-banner--inactive {
    /* The greyed background + the ribbon carry it — no left accent stripe
       (a coloured left border is not part of this design system). */
    border-color: var(--np-color-border);
    background: linear-gradient(180deg, #FAFAFA 0%, var(--np-color-surface) 100%);
}
.np-patient-banner__ribbon {
    display: flex;
    align-items: center;
    gap: var(--np-space-2, 8px);
    padding: var(--np-space-2, 8px) var(--np-space-3, 12px);
    margin-bottom: var(--np-space-3, 12px);
    background: #FFF3CD;
    color: #664D03;
    border-radius: var(--np-radius-2, 8px);
    font-size: var(--np-fs-small);
}
.np-patient-banner__ribbon svg {
    width: 16px;
    height: 16px;
    flex: 0 0 16px;
}

/* Internal registration host — the app-shell header bar above the embedded
   registration wizard (Views/PatientRegistration/Form.cshtml). */
.np-reg-host__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-3, 12px);
    flex-wrap: wrap;
    margin-bottom: var(--np-space-3, 12px);
}
/* Cancel button + its autosave indicator, stacked so "Saves automatically"
   sits directly below Cancel (right-aligned). Internal-only — the external
   form keeps the savebar inside the nav card. */
.np-reg-host__cancel {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--np-space-1, 4px);
}
.np-reg-host__savebar {
    padding: 0;
    justify-content: flex-end;
    min-height: 18px;
}
.np-reg-host__title {
    font-size: var(--np-fs-h2, 24px);
    margin: 2px 0 0;
}
.np-reg-host__sub {
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
}
/* The wizard's `.app` column is full-viewport when standalone (external); when
   embedded in the app shell the surrounding shell owns the height, so drop the
   100vh min and let the form size to its content. */
.np-reg-host .app {
    min-height: 0;
    /* External keeps the narrow 760px reading column; internal staff have the
       app content area to work in, so let the form use the full width. All of
       these overrides are scoped under `.np-reg-host` (internal-only) — the
       external `body.np-reg` form is never affected. */
    max-width: none;
    margin: 0;
    /* The external wizard fills `.app` with `--brand-tint-50` (public-registration.css)
       — patient-facing chrome. Internal forms sit directly on the app-shell background
       for a clean, minimal look consistent with the rest of the application, so clear
       the tint here (see skills.md → "Internal Form Appearance"). */
    background: transparent;
}
/* The registration wizard has TWO stacked backgrounds: the `.np-reg` scope
   (`--np-color-neutral-100`, public-registration.css) and the `.app` column
   (`--brand-tint-50`). Clearing only `.app` above reveals the neutral-100 panel
   beneath, so also clear `.np-reg` internally — the internal form then sits
   directly on the app-shell background, matching the internal pre-assessment.
   Scoped to `.np-reg-host` so the external `body.np-reg` form keeps both fills. */
.np-reg-host .np-reg { background: transparent; }

/* ---- Internal registration: card-based visual hierarchy ------------------
   Align the internal registration layout with the internal pre-assessment (the
   reference implementation): the section navigation and the section header each
   sit in their own white card on the app-shell background, with the same surface
   / border / radius / shadow tokens the shared design system uses. Scoped to
   `.np-reg-host` so the external public form is untouched. See skills.md →
   "Internal Form Appearance". */

/* Section-navigation card (savebar + stepper + progress bar) — mirrors the
   pre-assessment `.progress-chart` card. The wrapper is display:contents on the
   external form, so promote it back to a block here before painting the card. */
.np-reg-host .reg-nav {
    display: block;
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-3);
    box-shadow: var(--np-shadow-1);
    padding: 14px 18px 16px;
    margin: var(--np-space-2, 8px) 0 var(--np-space-5, 20px);
}
/* The card owns the horizontal padding now, so drop the children's own gutters
   (they were spacing against the tinted `.app` column on the external form). */
.np-reg-host .reg-nav .savebar,
.np-reg-host .reg-nav .steps { padding-left: 0; padding-right: 0; }
.np-reg-host .reg-nav .pbar { margin-left: 0; margin-right: 0; }

/* Section card (Part X of Y · title · lede · questions · nav) — one full card
   per section, matching the pre-assessment form. Internal uses the app-shell
   card tokens so it sits with the rest of the workspace. */
.np-reg-host .sec-card {
    background: var(--np-color-surface);
    border-color: var(--np-color-border);
    border-radius: var(--np-radius-3);
    box-shadow: var(--np-shadow-1);
    padding: 20px 24px;
}
/* Questions: internal and external render the SAME two-column
   (label | control) row — `.field` in public-registration.css, which is loaded by
   both hosts. The clinician form previously stacked the label above the control;
   that made the same question read differently depending on who opened it, so the
   override is gone rather than duplicated. `.field-block` (public-preassessment.css)
   carries the identical rule for pre-assessment / initial assessment. */
/* Inside that card the head is plain — it is no longer a card of its own. */
.np-reg-host .sec-card .sec-head {
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
    margin: 0 0 12px;
}
/* Standalone section header — the review screen, which is not inside a
   `.sec-card` — keeps its own card. */
.np-reg-host .sec-head {
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-3);
    box-shadow: var(--np-shadow-1);
    padding: 20px 24px;
    margin: 0 0 var(--np-space-4, 16px);
}

/* Stepper — spread the steps evenly across the (now wider) internal form
   instead of bunching them centred. flex:1 gives each step an equal share so
   they read as [1]   [2]   [3]   … and stay responsive as the column resizes.
   The shared mobile breakpoint still hides the labels on narrow widths. */
.np-reg-host .steps {
    justify-content: space-between;
    gap: var(--np-space-2, 8px);
    overflow-x: visible;
}
.np-reg-host .step {
    flex: 1 1 0;
    min-width: 0;
}
.np-reg-host .step .lbl {
    max-width: none;
}

/* Nav buttons — the external (narrow, mobile-first) form uses a full-width
   `flex:1` primary button, but on the wider internal form that reads as a
   stretched, oversized "Continue". Match the pre-assessment: keep the primary
   at its natural width and pin it to the right, with Back / Save-later grouped
   on the left. Scoped to `.np-reg-host` so the external form is unaffected. */
.np-reg-host .np-reg .btn-primary {
    flex: 0 0 auto;
    margin-left: auto;
}
.np-patient-banner__primary {
    display: flex;
    align-items: flex-start;
    gap: var(--np-space-5, 20px);
}

/* Care-episode actions — pinned top-right of the banner's primary row. The
   identity block flexes to fill, pushing the actions to the right edge. Can hold
   two buttons (Reopen + Open new), so lay them out in a wrapping flex row. */
.np-patient-banner__actions {
    flex: 0 0 auto;
    margin-left: auto;
    align-self: flex-start;
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2, 8px);
    justify-content: flex-end;
}
@media (max-width: 640px) {
    .np-patient-banner__actions {
        margin-left: 0;
        width: 100%;
    }
    .np-patient-banner__actions .d-inline,
    .np-patient-banner__actions .np-btn { width: 100%; }
}

/* Avatar — 72px circle, brand tint background, initials in brand
   primary. Photo when supplied, initials fallback when not. */
.np-patient-banner__avatar {
    flex: 0 0 72px;
    width: 72px;
    height: 72px;
    border-radius: 50%;
    background: var(--np-color-brand-tint-100);
    border: 2px solid var(--np-color-surface);
    box-shadow: 0 0 0 1px var(--np-color-brand-tint-200);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}
.np-patient-banner__avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.np-patient-banner__initials {
    font-size: 28px;
    font-weight: 400;
    color: var(--np-color-brand-primary);
    line-height: 1;
    letter-spacing: 0.5px;
}

/* Identity block — name, preferred, identifiers, demographics stack */
.np-patient-banner__identity {
    flex: 1 1 auto;
    min-width: 0; /* allow long names to wrap */
}

.np-patient-banner__name-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--np-space-4, 16px);
    row-gap: var(--np-space-2, 8px);
    margin-bottom: var(--np-space-2, 8px);
}
.np-patient-banner__name {
    font-size: var(--np-fs-h2, 24px);
    font-weight: 400;
    color: var(--np-color-text-primary);
    margin: 0;
    line-height: 1.2;
}
.np-patient-banner__age {
    color: var(--np-color-text-secondary, #5A5963);
    font-size: var(--np-fs-small);
    display: inline-flex;
    align-items: baseline;
    gap: var(--np-space-2, 8px);
    flex-wrap: wrap;
}
.np-patient-banner__age-label {
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small, 14px);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.np-patient-banner__age-value {
    font-weight: 400;
}
.np-patient-banner__pronouns {
    color: var(--np-color-text-muted);
}

.np-patient-banner__preferred {
    margin-bottom: var(--np-space-2, 8px);
    color: var(--np-color-text-secondary, #5A5963);
    font-size: var(--np-fs-small);
}
.np-patient-banner__preferred-name {
    font-weight: 400;
    color: var(--np-color-text-primary);
}

.np-patient-banner__identifiers {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-5, 20px);
    margin-bottom: var(--np-space-2, 8px);
}
.np-patient-banner__id-group {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2, 8px);
}
.np-patient-banner__id-value {
    font-size: var(--np-fs-small);
    background: var(--np-color-surface);
    padding: 2px 8px;
    border-radius: 4px;
    border: 1px solid var(--np-color-brand-tint-200);
    color: var(--np-color-text-primary);
}

/* NHS Number validity badge — green ✓ when valid, amber on check
   when the Mod-11 fails. Hidden when no number is supplied. */
.np-patient-banner__nhs-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--np-fs-small, 14px);
    font-weight: 400;
    padding: 2px 8px;
    border-radius: 12px;
}
.np-patient-banner__nhs-badge svg {
    width: 14px;
    height: 14px;
}
.np-patient-banner__nhs-badge--valid {
    background: #D1F2DD;
    color: #0F5132;
}
.np-patient-banner__nhs-badge--invalid {
    background: #FFE5D9;
    color: #7C2D12;
}

.np-patient-banner__demographics {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-2, 8px) var(--np-space-3, 12px);
    color: var(--np-color-text-secondary, #5A5963);
    font-size: var(--np-fs-small, 14px);
}
.np-patient-banner__demographic {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.np-patient-banner__inline-icon {
    width: 14px;
    height: 14px;
    flex: 0 0 14px;
    color: var(--np-color-text-muted);
}
.np-patient-banner__age-inline {
    color: var(--np-color-text-muted);
    margin-left: 4px;
}
.np-patient-banner__separator {
    color: var(--np-color-text-muted);
    padding: 0 4px;
}

/* Shared label style for "MRN:", "NHS:", "DOB:", "Preferred:" */
.np-patient-banner__field-label {
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small, 14px);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 400;
}

/* ─── Status row ───
   Clinical-safety flags. Always present so the banner's vertical
   rhythm stays stable; placeholder pills render when backing data
   is absent (default state today).

   Colour vocabulary follows CLAUDE.md §6.6 — danger is reserved
   for clinical-safety hazards; warning is amber for "needs attention";
   ok is green for affirmed-good; neutral-strong is the brand muted
   surface for informational (e.g. confidential address); placeholder
   is the most-muted variant so unfilled flags read as "not yet
   recorded" rather than "no concern".
*/
.np-patient-banner__status-row {
    margin-top: var(--np-space-4, 16px);
    padding-top: var(--np-space-3, 12px);
    border-top: 1px dashed var(--np-color-brand-tint-200);
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2, 8px);
}
.np-patient-banner__flag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: 999px;
    font-size: var(--np-fs-small, 14px);
    font-weight: 400;
    line-height: 1.3;
    border: 1px solid transparent;
}
.np-patient-banner__flag svg {
    width: 14px;
    height: 14px;
    flex: 0 0 14px;
}
.np-patient-banner__flag-label {
    line-height: 1;
}
.np-patient-banner__flag--warning {
    background: #FFF3CD;
    color: #664D03;
    border-color: #FFE69C;
}
.np-patient-banner__flag--danger {
    background: #FFE5E5;
    color: #842029;
    border-color: #F5C2C7;
}
.np-patient-banner__flag--ok {
    background: #D1F2DD;
    color: #0F5132;
    border-color: #A3E0BB;
}
.np-patient-banner__flag--neutral-strong {
    background: var(--np-color-brand-tint-100);
    color: var(--np-color-brand-secondary);
    border-color: var(--np-color-brand-tint-200);
}
.np-patient-banner__flag--placeholder {
    background: var(--np-color-surface);
    color: var(--np-color-text-muted);
    border-color: var(--np-color-brand-tint-200);
    border-style: dashed;
}

/* Responsive — at narrow widths, the avatar drops above the identity
   block (stack), and the demographics row wraps naturally. The banner
   keeps the same hierarchy on phones — no information is hidden. */
@media (max-width: 640px) {
    .np-patient-banner__primary {
        flex-direction: column;
        align-items: stretch;
        gap: var(--np-space-3, 12px);
    }
    .np-patient-banner__avatar {
        flex: 0 0 56px;
        width: 56px;
        height: 56px;
    }
    .np-patient-banner__initials {
        font-size: 22px;
    }
    .np-patient-banner__name {
        font-size: var(--np-fs-h3, 20px);
    }
}

/* Print — flatten the gradient to ink-saving white, keep the brand
   accent on the left so the banner still scans as a header on paper. */
@media print {
    .np-patient-banner {
        background: #FFFFFF;
        box-shadow: none;
    }
    .np-patient-banner__flag {
        border: 1px solid #888;
    }
}

/* Forced-colors / Windows high contrast — ensure pill borders are
   visible (the brand tint backgrounds disappear in this mode). */
@media (forced-colors: active) {
    .np-patient-banner__flag {
        border: 1px solid CanvasText;
    }
    .np-patient-banner__nhs-badge {
        border: 1px solid CanvasText;
    }
}

/* ============================================================
   Patient workspace accordion — Workspace.cshtml.

   Vertical stack of <details> sections matching the legacy
   per-patient screen one-for-one. Each section header carries
   an icon, label, action buttons (Send/Fill/Add/etc.), and a
   chevron that rotates on expand.

   Native <details>/<summary> handles state without JS. The
   action <button>s use stopPropagation in markup so clicking
   one does NOT toggle the section (legacy parity).
   ============================================================ */

.np-workspace-accordion {
    display: flex;
    flex-direction: column;
    gap: var(--np-space-2, 8px);
    margin-bottom: var(--np-space-4, 16px);
}

/* Borderless card (legacy parity). A 1px outline on every row read as noise once
   a dozen sections stack; the white surface on the tinted app background plus a
   hairline shadow already separates one section from the next. The border stays
   in the box model as `transparent` so nothing reflows when forced-colors mode
   paints it back in (see the @media (forced-colors: active) block below). */
.np-section {
    background: var(--np-color-surface);
    border: 1px solid transparent;
    border-radius: var(--np-radius-2, 8px);
    box-shadow: 0 1px 2px rgba(var(--np-color-brand-primary-rgb), 0.06);
    overflow: hidden;  /* keeps the expanded body inside the rounded corner */
    /* Anchored deep-links (#section-…) land the header below the sticky topbar
       rather than hidden beneath it. */
    scroll-margin-top: 88px;
}
.np-section[open] {
    box-shadow: 0 2px 8px rgba(var(--np-color-brand-primary-rgb), 0.10);
}
/* Always-visible (non-collapsible) section — e.g. the Patient log tabbed panel.
   Reuses the card chrome but is never toggled, so it carries the open shadow
   permanently and its header is plain (no pointer/hover/chevron). */
.np-section--static {
    box-shadow: 0 2px 8px rgba(var(--np-color-brand-primary-rgb), 0.10);
}
.np-section__header--static {
    cursor: default;
}
.np-section__header--static:hover {
    background: var(--np-color-surface);
}

/* Header — flex row: icon | label (grow) | actions | chevron.
   Compact by design (legacy parity): a patient workspace stacks a dozen of these,
   so every extra pixel of header padding costs a section of visible page. The row
   still clears the WCAG 2.2 (2.5.8, AA) 24px target minimum with room to spare. */
.np-section__header {
    display: flex;
    align-items: center;
    gap: var(--np-space-3, 12px);
    min-height: 46px;
    padding: 6px var(--np-space-4, 16px);
    cursor: pointer;
    list-style: none;     /* hide the default <summary> marker */
    user-select: none;
    background: var(--np-color-surface);
    transition: background-color 0.15s ease;
}
.np-section__header::-webkit-details-marker { display: none; }
.np-section__header:hover {
    background: var(--np-color-brand-tint-50);
}
.np-section__header:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: -2px;
}

.np-section__icon {
    flex: 0 0 20px;
    width: 20px;
    height: 20px;
    color: var(--np-color-brand-primary);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.np-section__icon svg {
    width: 20px;
    height: 20px;
}

.np-section__label {
    flex: 1 1 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2, 8px);
    font-size: var(--np-fs-small);
    font-weight: 400;
    color: var(--np-color-text-primary);
    min-width: 0;        /* allow chip / dot to compete for width */
}

/* Pulse-dot for sections that need attention (legacy "fa-circle text-danger"
   on unsent forms). Solid magenta with a subtle pulse so it draws the eye
   without blinking aggressively. */
.np-section__attention-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--np-color-brand-primary);
    box-shadow: 0 0 0 0 rgba(var(--np-color-brand-primary-rgb), 0.75);
    /* Kept a touch quicker than the section ring (np-accordion-pulse, 2.8s) so the
       two read as one family rather than two unrelated rhythms. */
    animation: np-section-pulse 1.9s ease-out infinite;
}
@keyframes np-section-pulse {
    0%   { box-shadow: 0 0 0 0    rgba(var(--np-color-brand-primary-rgb), 0.75); }
    70%  { box-shadow: 0 0 0 10px rgba(var(--np-color-brand-primary-rgb), 0);    }
    100% { box-shadow: 0 0 0 0    rgba(var(--np-color-brand-primary-rgb), 0);    }
}
@media (prefers-reduced-motion: reduce) {
    .np-section__attention-dot { animation: none; }
}

/* ── Strong attention treatment (reminders present) ──
   Brand-purple cue that grabs the eye immediately: the header reminder icon
   pulses, a count badge shows how many, and the header carries a soft pulsing
   accent. No separate dot — the icon + badge + accent already signal it. */

/* Header reminder (bell) icon — brand purple. The attention pulse lives on the
   whole accordion body (below), NOT on the icon or header. */
.np-section__icon--ringing {
    color: var(--np-color-brand-primary);
}

/* Count badge — how many reminders, next to the label. */
.np-section__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    margin-left: 6px;
    border-radius: 999px;
    background: var(--np-color-brand-primary);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 400;
    line-height: 1;
}

/* OUTSTANDING WORK: the pulse radiates OUTSIDE the whole accordion card (legacy
   pulse-icon effect, applied to the outer body — not the header). The box-shadow
   follows the section's own border-radius and grows beyond it.

   ONE ring, four sections. Reminders raised it first, and it now carries the same
   meaning everywhere it appears in the workspace — "nothing has happened here
   yet": an un-triaged episode, an episode with no assessment selected, one with
   no clinician assigned, and an urgent task nobody has picked up. A reader learns
   the signal once. Adding a second colour or a second speed per section is what
   this single class exists to prevent. */
/* Brand token, not a frozen hex: the ring is one of the few places a tenant's own
   primary has to show through, and the literal purple was the app's, not theirs. */
/* Deliberately QUIET — but no longer TOO quiet. The original 16px ring at 0.7
   alpha on a 1.6s loop read as an alarm on a page a clinician sits with for a
   whole shift, and was cut to 5px / 0.28 / 3.6s. Now that the same ring annotates
   four sections rather than one, that setting proved too faint to find by eye and
   slow enough to be missed between glances, so it is 0.42 on a 2.8s loop: darker,
   a little quicker, still a hint rather than a highlight. (Oct 2026: 2.4s, at
   the product owner's request — the ring now also marks the section icons and
   the Tasks / Enquiry badges, all on this one rhythm.) The ring WIDTH is
   unchanged at 5px — width is what made the old treatment dominate the card it
   was annotating, and that part of the earlier argument still stands. Do not take
   it wider, faster than ~2.4s, or past ~0.45 alpha without making the case the
   other way, as both of these changes did. */
.np-section--attention {
    animation: np-accordion-pulse 2.4s ease-in-out infinite;
}
/* The "Next step" section takes the SAME whole-card ring (product owner, Oct
   2026): it is the one section the pathway is waiting on, and a ring on a 20px
   icon was too easy to miss in a long workspace. Its own rule rather than a
   second selector on the one above, so each can be found and tuned by name —
   but the keyframes, colour and rhythm are shared, never restated. Only the
   ring: no header tint and no border, which read as a stuck frame on a section
   that can stay "next" for days. */
.np-section--next {
    animation: np-accordion-pulse 2.4s ease-in-out infinite;
}
@keyframes np-accordion-pulse {
    0%   { box-shadow: 0 0 0 0   rgba(var(--np-color-brand-primary-rgb), 0.42); }
    70%  { box-shadow: 0 0 0 5px rgba(var(--np-color-brand-primary-rgb), 0);    }
    100% { box-shadow: 0 0 0 0   rgba(var(--np-color-brand-primary-rgb), 0);    }
}

/* Soft brand tint on the header (framing only — no pulse, no left rule). */
.np-section--attention > .np-section__header {
    background: rgba(var(--np-color-brand-primary-rgb), 0.06);
}

@media (prefers-reduced-motion: reduce) {
    .np-section--attention { animation: none; }
    /* The "Next step" pill in the header still marks the section. */
    .np-section--next { animation: none; }
}

/* "Next step" — the ONE workspace section the patient pathway is waiting on
   (PatientWorkspaceViewModel.NextStepSectionKey). It pulses the whole card, like
   Reminders (above); no permanent coloured border, which read as a stuck frame
   on a section that can stay "next" for days (e.g. Diagnosis). */
/* Post-report diagnosis dialog (PatientReports/_DiagnosisPrompt) — a standard
   np-confirm dialog; only the existing-diagnoses lead-in needs its own tone. */
.np-dx-prompt__existing {
    display: block;
    margin-bottom: var(--np-space-1);
    font-size: var(--np-fs-body);
    color: var(--np-color-brand-primary);
}

/* ── Pulse ring on the section icon (waiting sections other than the two above) ─
   Product owner, Sep 2026: "the accordion itself remains completely fixed", and
   then (Oct 2026) the ICON stays still too — a pulse RING radiates around it in
   the same colour as the Reminders pulse. The whole-card ring above belongs to
   Reminders and the Next step; every OTHER section that is waiting on the
   clinician (Triage, Select assessments, Assign clinician, Appointments,
   Diagnosis, an urgent task) rings its left icon and nothing else — no
   background, border, label, container or glyph change. That split is
   deliberate: the card ring says "start here", the icon ring says "this too".

   :not(.np-section--next) — a waiting section that is ALSO the Next step takes
   the card ring only. Both at once is two pulses on one row, out of phase.

   The ring is the icon's own ::after: a circle just outside the 20px glyph whose
   box-shadow runs the SAME np-accordion-pulse keyframes as Reminders, so the two
   signals share one colour, alpha and rhythm. It fits inside the 46px header and
   its 16px side padding, so the card's overflow:hidden never clips it. */
.np-section--breathe:not(.np-section--next) > .np-section__header .np-section__icon {
    position: relative;
}
.np-section--breathe:not(.np-section--next) > .np-section__header .np-section__icon::after {
    content: "";
    position: absolute;
    inset: -4px;
    border-radius: 50%;
    pointer-events: none;
    animation: np-accordion-pulse 2.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
    /* No motion: a steady faint ring keeps the marker visible. */
    .np-section--breathe:not(.np-section--next) > .np-section__header .np-section__icon::after {
        animation: none;
        box-shadow: 0 0 0 2px rgba(var(--np-color-brand-primary-rgb), 0.42);
    }
}
.np-section__next {
    display: inline-flex;
    align-items: center;
    flex: none;
}

/* "Soon" chip — re-uses np-tabs__chip styling for consistency */
.np-section__chip {
    display: inline-block;
    padding: 1px 8px;
    font-size: var(--np-fs-tiny, 12px);
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--np-color-text-muted);
    background: var(--np-color-surface-muted, #FBF7FC);
    border: 1px solid var(--np-color-brand-tint-200);
    border-radius: 999px;
    line-height: 1.5;
}

/* Action buttons group — right-aligned, hugs the chevron.
   `stopPropagation` in the markup keeps clicks from toggling the section. */
.np-section__actions {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
    justify-content: flex-end;
}

/* Status badge slot — sits at the far right of the header, AFTER the action
   buttons and just before the chevron, so the status (Submitted / Draft / Soon)
   reads in the same place on every accordion regardless of how many actions the
   section has. The label grows (flex:1) and pushes this + the chevron right. */
.np-section__status {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2, 8px);
}

/* Stacked variant — a status pill with one or two short provenance lines under
   it (the Allergies header: "Source: pre-assessment", "Last reviewed: …"). The
   pill stays right-aligned with every other accordion's status slot. */
.np-section__status--stacked {
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
}

.np-section__status-note {
    font-size: 0.75rem;
    font-weight: 400;
    line-height: 1.3;
    color: var(--np-color-text-muted);
    text-align: right;
}

/* Header control — a compact outlined chip (legacy parity). Every control in the
   set carries the same hairline box, so an accordion row reads as one tidy strip
   of actions instead of a mix of bare text links and filled buttons. `<a>` and
   `<button>` render identically, hence the explicit text-decoration reset.
   30px tall clears the WCAG 2.2 (2.5.8, AA) 24px target minimum. */
.np-section-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    min-height: 30px;
    padding: 4px 11px;
    font-size: var(--np-fs-small, 14px);
    font-weight: 400;
    line-height: 1.3;
    border-radius: var(--np-radius-2, 8px);
    border: 1px solid var(--np-color-brand-tint-200);
    background: var(--np-color-surface);
    color: var(--np-color-brand-primary);
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.np-section-action:hover:not(:disabled):not([aria-disabled="true"]) {
    background: var(--np-color-brand-tint-50);
    border-color: var(--np-color-brand-primary);
    color: var(--np-color-brand-primary-hover);
    text-decoration: none;
}
.np-section-action:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}
.np-section-action:disabled,
.np-section-action[aria-disabled="true"] {
    cursor: not-allowed;
    opacity: 0.6;
}
.np-section-action__icon {
    width: 14px;
    height: 14px;
    flex: 0 0 14px;
}
.np-section-action__label {
    line-height: 1;
}

/* Tone variants — same colour vocabulary as np-status (CLAUDE.md §6.6).
   Primary = brand magenta (Send, Resend, Fill, Add) — most common.
   Success = green (Submit, Save final, Confirm).
   Neutral = grey (View, Print, Refresh, Show hidden — utility actions). */
.np-section-action--primary {
    color: var(--np-color-brand-primary);
    border-color: var(--np-color-brand-tint-200);
    background: var(--np-color-brand-tint-50);
}
.np-section-action--primary:hover:not(:disabled):not([aria-disabled="true"]) {
    background: var(--np-color-brand-tint-100);
    border-color: var(--np-color-brand-tint-100);
}
.np-section-action--success {
    color: #0F5132;
    border-color: #A3E0BB;
    background: #D1F2DD;
}
.np-section-action--success:hover:not(:disabled):not([aria-disabled="true"]) {
    background: #BCEACE;
}
.np-section-action--neutral {
    color: var(--np-color-text-secondary, #5A5963);
    border-color: var(--np-color-brand-tint-200);
    background: var(--np-color-surface);
}
.np-section-action--neutral:hover:not(:disabled):not([aria-disabled="true"]) {
    color: var(--np-color-brand-primary);
}

/* Chevron — rotates on expand. Pure CSS, no JS. */
.np-section__chevron {
    flex: 0 0 20px;
    width: 20px;
    height: 20px;
    color: var(--np-color-text-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: transform 0.2s ease;
}
.np-section__chevron svg {
    width: 18px;
    height: 18px;
}
.np-section[open] .np-section__chevron {
    transform: rotate(180deg);
}
@media (prefers-reduced-motion: reduce) {
    .np-section__chevron { transition: none; }
}

/* Expanded body — sits inside the same rounded card frame. The hairline is the
   ONLY rule left on the card now that the outer border is gone, so it still reads
   as "header above, content below" without re-boxing the section. */
.np-section__body {
    padding: var(--np-space-3, 12px) var(--np-space-4, 16px) var(--np-space-4, 16px);
    border-top: 1px solid var(--np-color-brand-tint-100);
    background: var(--np-color-surface);
}

/* Responsive — on narrow screens, action labels collapse to icon-only
   to keep the header on one line. The icons retain their tooltips
   (title attribute) so the meaning is still discoverable. */
@media (max-width: 720px) {
    .np-section-action__label {
        position: absolute;
        width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
        clip: rect(0,0,0,0); white-space: nowrap; border: 0;
    }
    /* Icon-only: keep an explicit 30×30 box so the control still clears the
       WCAG 2.2 (2.5.8, AA) 24px minimum target once the label is removed. */
    .np-section-action {
        padding: 4px;
        min-width: 30px;
    }
}

/* Print — flatten interactive affordances, show content open.
   Useful when a clinician prints the patient record. */
@media print {
    .np-section {
        page-break-inside: avoid;
        border: 1px solid #888;
        box-shadow: none;
    }
    .np-section__chevron,
    .np-section__actions {
        display: none;
    }
    .np-section[open] .np-section__body,
    .np-section .np-section__body {
        display: block;  /* show all section bodies on paper */
    }
    /* Never clip the activity log when printing — show the whole timeline. */
    .np-activity-scroll {
        max-height: none;
        overflow: visible;
    }
}

/* Forced-colors / Windows high contrast — ensure section borders and
   action button outlines are visible (the brand tint backgrounds drop
   out in this mode). */
@media (forced-colors: active) {
    .np-section { border: 1px solid CanvasText; }
    .np-section-action { border: 1px solid CanvasText; }
    .np-section__attention-dot {
        background: CanvasText;
        animation: none;
    }
}

/* ──────────────────────────────────────────────────────────────────
   Registration wizard — the step-based form rendered inside the
   Registration accordion (Views/Patients/_RegistrationAccordion.cshtml).
   Progressive enhancement: the step rail, per-step nav and one-panel-at-a-
   time behaviour only activate once np-registration-wizard.js adds
   `.np-wizard--enhanced`. Without JS the panels render as one scroll.
   ────────────────────────────────────────────────────────────────── */
.np-wizard__steps,
.np-wizard__toolbar,
.np-wizard__progressbar,
.np-wizard__nav { display: none; }

.np-wizard--enhanced .np-wizard__toolbar { display: flex; }
.np-wizard--enhanced .np-wizard__nav { display: flex; }
.np-wizard--enhanced .np-wizard__steps { display: flex; }
.np-wizard--enhanced .np-wizard__progressbar { display: block; }

/* Progress bar — fills as the user advances through the steps. */
.np-wizard__progressbar {
    height: 8px;
    border-radius: 999px;
    background: var(--np-color-brand-tint-100);
    overflow: hidden;
    margin-bottom: var(--np-space-3, 12px);
}
.np-wizard__progressbar-fill {
    display: block;
    height: 100%;
    width: 0;
    border-radius: 999px;
    background: var(--np-color-brand-primary);
    transition: width 0.25s ease;
}
@media (prefers-reduced-motion: reduce) {
    .np-wizard__progressbar-fill { transition: none; }
}

/* Step rail — clickable indicators across the top. */
.np-wizard__steps {
    list-style: none;
    margin: 0 0 var(--np-space-4, 16px);
    padding: 0 0 var(--np-space-2, 8px);
    gap: var(--np-space-2, 8px);
    flex-wrap: wrap;
    border-bottom: 1px solid var(--np-color-brand-tint-100);
}
.np-wizard__step-item { margin: 0; }
.np-wizard__step {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    border: 1px solid var(--np-color-brand-tint-200);
    border-radius: 999px;
    background: var(--np-color-surface);
    color: var(--np-color-text-muted);
    font: inherit;
    font-size: var(--np-fs-sm, 0.85rem);
    cursor: pointer;
}
.np-wizard__step:hover { background: var(--np-color-brand-tint-50); }
.np-wizard__step-index {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px; height: 22px;
    border-radius: 50%;
    background: var(--np-color-brand-tint-100);
    color: var(--np-color-brand-secondary);
    font-size: 0.78rem;
    font-weight: 400;
    flex: none;
}
.np-wizard__step.is-current {
    border-color: var(--np-color-brand-primary);
    color: var(--np-color-brand-primary);
    font-weight: 400;
}
.np-wizard__step.is-current .np-wizard__step-index,
.np-wizard__step.is-done .np-wizard__step-index {
    background: var(--np-color-brand-primary);
    color: #fff;
}
.np-wizard__step.is-done { color: var(--np-color-text); }

/* Toolbar — live progress + persistent Save draft. */
.np-wizard__toolbar {
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-3, 12px);
    margin-bottom: var(--np-space-3, 12px);
}
.np-wizard__progress {
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-sm, 0.85rem);
    font-weight: 400;
}

.np-wizard__panel-head { margin-bottom: var(--np-space-3, 12px); }
.np-wizard__panel-head h3 { margin: 0 0 4px; font-size: var(--np-fs-h3, 1.1rem); }
.np-wizard__panel-head p { margin: 0; }

/* Footer — per-step Back/Continue and the always-available submit row.
   All step action buttons sit on the right-hand side for a consistent
   alignment across every step (matches the workspace module patterns). */
.np-wizard__footer {
    margin-top: var(--np-space-4, 16px);
    padding-top: var(--np-space-3, 12px);
    border-top: 1px solid var(--np-color-brand-tint-100);
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-3, 12px);
    align-items: center;
    justify-content: flex-end;
}
.np-wizard__nav { gap: var(--np-space-2, 8px); justify-content: flex-end; }
/* The submit row is nested inside the footer (which already owns the divider),
   so drop its own top border / spacing to avoid a doubled rule. */
.np-wizard__footer .np-form-actions {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
    justify-content: flex-end;
}

/* Review summary — read-only label/value list built by the wizard JS. */
.np-wizard__review-group { margin-bottom: var(--np-space-4, 16px); }
.np-wizard__review-group h4 {
    margin: 0 0 var(--np-space-2, 8px);
    font-size: var(--np-fs-small);
    color: var(--np-color-brand-primary);
}
.np-wizard__review-row {
    display: flex;
    gap: var(--np-space-3, 12px);
    padding: 5px 0;
    border-bottom: 1px solid var(--np-color-brand-tint-50);
    font-size: var(--np-fs-sm, 0.9rem);
}
/* Fixed label column so every answer in every group starts at the same x. A
   long label wraps inside its own column instead of growing and pushing its
   value right (which left the rows visibly ragged between groups). */
.np-wizard__review-label {
    flex: 0 0 240px;
    width: 240px;
    color: var(--np-color-text-muted);
}
.np-wizard__review-value { color: var(--np-color-text); font-weight: 400; }
/* Safety-flag row (risk-assessment "Yes", allergy "Yes"): the whole row — label AND
   value — reads danger-red + bold so a captured risk stands out as a hazard in the
   read-only summary (§6.6). */
.np-wizard__review-value--danger { color: var(--np-color-danger, #C62828); font-weight: 400; }
.np-wizard__review-row--danger .np-wizard__review-label { color: var(--np-color-danger, #C62828); font-weight: 400; }

@media (max-width: 720px) {
    .np-wizard__review-row { flex-direction: column; gap: 2px; }
    /* Stacked: the basis would become a height, so release it. */
    .np-wizard__review-label { flex: 0 0 auto; width: auto; }
}

/* Workspace overview — definition-list grid (legacy; kept for any
   later screen that wants a label/value pair list). */
.np-overview-grid {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--np-space-2, 8px) var(--np-space-4, 16px);
    margin: 0;
}
.np-overview-grid > dt {
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small, 14px);
    font-weight: 400;
}
.np-overview-grid > dd {
    margin: 0;
    color: var(--np-color-text-primary);
}
@media (max-width: 480px) {
    .np-overview-grid {
        grid-template-columns: 1fr;
        gap: 2px var(--np-space-4, 16px);
    }
    .np-overview-grid > dt {
        margin-top: var(--np-space-2, 8px);
    }
}

/* ============================================================================
   Reusable confirmation dialog (np-confirm)
   ----------------------------------------------------------------------------
   Themed replacement for window.confirm() — see _NpConfirmDialog.cshtml +
   js/np-confirm.js. Three tones: --info, --warning (default), --danger.
   ============================================================================ */
.np-confirm {
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-3);
    padding: 0;
    background: var(--np-color-surface);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
    color: var(--np-color-text);
    min-width: min(90vw, 32rem);
    max-width: min(90vw, 40rem);
}

/* A confirm-style dialog carrying a TABLE rather than a sentence — the assessment
   count modals. The base width above is sized for prose; a bucket modal carries up
   to seven columns (Action, Assessment, Status, Assigned/Completed, Progress, Last
   sent, Assigned by) and was being forced into a horizontal scroll at every
   viewport, which is the usability complaint this variant exists to answer.

   MUST stay after .np-confirm — same specificity, so source order decides. See the
   note beside the assessment-dashboard rules earlier in this file.

   min-width is reset to 0: the inherited min(90vw, 32rem) fights max-width on small
   viewports, where the dialog should simply shrink to the gutter. */
.np-confirm--wide {
    min-width: 0;
    width: calc(100vw - 3rem);
    max-width: min(96rem, calc(100vw - 3rem));
}

/* ONE scroller, not two.
   The table already owns a scroll viewport (.np-table-scroll — that bounded height
   is what makes its header stick). The <dialog> then scrolled as well, because a
   dialog is `overflow: auto` with a UA max-height and the heading + description +
   60vh of table + actions together overflowed it. The result was two nested
   scrollbars racing each other and a band of dead space under the Close button
   wherever the two heights failed to agree.

   Fixed by giving the OPEN dialog a bounded height and a flex column, so the table
   region absorbs whatever is left after the heading and the actions and is the only
   thing that scrolls. The dialog itself never does.

   Scoped to [open] deliberately: a bare `display: flex` on the dialog would
   out-specify the UA's `dialog:not([open]) { display: none }` and leave the modal
   on screen while it is closed. */
.np-confirm--wide[open] {
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 3rem);
    overflow: hidden;
}
.np-confirm--wide[open] > .np-confirm__form,
.np-confirm--wide[open] > .np-confirm__panel {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    /* Without min-height:0 a flex item refuses to shrink below its content, so the
       table would push the dialog past its max-height and put the scrollbar back on
       the dialog — the exact bug this rule removes. */
    min-height: 0;
}
/* The injected fragment's wrapper is a flex item too, or the table inside it has
   nothing to measure against. */
.np-confirm--wide[open] [data-np-bucket-body] {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}
/* max-height is released here on purpose: the flex parent now decides the height,
   and a fixed 60vh on top of it is what left the dead space. The 12rem floor goes
   too — inside a bounded dialog it can only force the content back past the cap and
   out of reach on a short viewport, and it exists to stop a near-empty PAGE table
   looking odd beside its pager, which is not this situation. */
.np-confirm--wide[open] .np-table-scroll {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
}
/* Errors and the action bar keep their natural height at the bottom of the column. */
.np-confirm--wide[open] > .np-confirm__form > .np-confirm__actions,
.np-confirm--wide[open] > .np-confirm__panel > .np-confirm__actions {
    flex: none;
    margin-top: var(--np-space-4, 16px);
}

/* Pin every modal to the VIEWPORT, not to the document.
   A <dialog> is `position: absolute` by default, so `margin: auto` centres it in
   the initial containing block — the whole DOCUMENT. On a phone, where a patient
   list is far wider than the screen, that centred the dialog over the document's
   width and pushed it mostly off-screen to the right: the modal opened somewhere
   the user could not reach and could not scroll to, because the backdrop blocks
   the page scroll it would have needed.
   Scoped to [open] so a closed dialog keeps the UA's display:none. */
.np-confirm[open] {
    position: fixed;
    inset: 0;
    margin: auto;
}

.np-confirm::backdrop {
    background: rgba(15, 23, 42, 0.45);
    backdrop-filter: blur(2px);
}

/* Phones: claim the screen. 3rem of gutter is a sixth of a 320px viewport spent
   on nothing, and the base dialog's 32rem min-width is wider than the device. */
@media (max-width: 575.98px) {
    .np-confirm {
        min-width: 0;
        width: calc(100vw - 1rem);
        max-width: calc(100vw - 1rem);
    }
    .np-confirm--wide {
        width: calc(100vw - 1rem);
        max-width: calc(100vw - 1rem);
    }
    .np-confirm--wide[open] { max-height: calc(100vh - 1rem); }
    .np-confirm__form,
    .np-confirm__panel { padding: var(--np-space-4, 16px); }
    /* Full-width, stacked buttons — side-by-side they fall below the 2.5.8 target
       size on a narrow screen, and the primary action wraps to its own line anyway. */
    .np-confirm__actions { flex-direction: column-reverse; align-items: stretch; }
    .np-confirm__actions > .np-btn { width: 100%; }
}
/* .np-confirm itself is padding:0 — the inset belongs to the content wrapper so
   the backdrop and border hug the dialog. __form is that wrapper when the dialog
   posts something; __panel is the same wrapper for a dialog that only confirms
   and re-submits another form. Without one of the two, the content sits flush
   against the dialog edge. */
.np-confirm__form,
.np-confirm__panel {
    padding: var(--np-space-6, 24px);
    margin: 0;
}
.np-confirm__title {
    font-size: var(--np-fs-h3, 1.25rem);
    margin: 0 0 var(--np-space-3, 12px) 0;
    line-height: 1.3;
}
/* The email-preview dialog puts initial focus on its heading (tabindex="-1") so
   no input field is highlighted on open. That focus is programmatic, never
   keyboard-driven, so it must not paint a focus ring — :focus-visible on the
   genuinely interactive controls is untouched. */
.np-confirm__title[tabindex]:focus { outline: none; }
.np-confirm__body {
    margin: 0 0 var(--np-space-5, 20px) 0;
    color: var(--np-color-text-muted, #4b5563);
    line-height: 1.5;
}
.np-confirm__actions {
    display: flex;
    gap: var(--np-space-3, 12px);
    justify-content: flex-end;
    flex-wrap: wrap;
    margin-top: 5px;
}
.np-confirm--warning .np-confirm__title { color: var(--np-color-warning-fg, #92400e); }
.np-confirm--danger  .np-confirm__title { color: var(--np-color-danger-fg, #a41b17); }

/* Standalone .np-btn--danger for the dialog's "destructive" confirm button.
   Kept here (not in the row-action group) so dialogs can use it without
   pulling in row-action-specific styling. */
.np-btn--danger {
    background: #C92420;
    color: #fff;
    border-color: #8C1612;
}
.np-btn--danger:hover,
.np-btn--danger:focus {
    background: #A41B17;
    color: #fff;
}

/* Success / confirm — the affirmative-action green, matched to the offer email's
   Accept button (#174D2D). Used for the patient "Confirm appointment" button so
   the on-screen confirm reads as the same action as the email's accept. */
.np-btn--success {
    background: #1F7A44;
    color: #fff;
    border-color: #123D24;
}
.np-btn--success:hover,
.np-btn--success:focus {
    background: #174D2D;
    color: #fff;
}

/* ── Disabled ────────────────────────────────────────────────────────
   Declared AFTER every variant on purpose: .np-btn--primary:hover and
   .np-btn:disabled carry the same specificity, so a disabled rule placed
   with the base class would lose the tie and a disabled primary button
   would keep its full-strength fill — reading as clickable, and leaving a
   user to click something that can never respond.

   Matches .np-row-action:disabled, which has always dimmed this way. No
   pointer-events:none: a disabled <button> dispatches nothing regardless,
   and suppressing pointer events would also suppress the not-allowed
   cursor that explains why. */
.np-btn:disabled,
.np-btn[disabled],
.np-btn:disabled:hover,
.np-btn[disabled]:hover,
.np-btn:disabled:focus,
.np-btn[disabled]:focus {
    opacity: 0.45;
    cursor: not-allowed;
    box-shadow: none;
    transform: none;
}

/* A select the clinician cannot answer yet — the folder picker with no folders
   to offer. Dimmed to match, so "you cannot use this" is one visual language. */
.np-select:disabled,
.np-input:disabled {
    background: var(--np-color-neutral-100);
    color: var(--np-color-text-muted);
    cursor: not-allowed;
}

/* ============================================================================
   List view — pipe-delimited row actions + sticky Actions column.
   ----------------------------------------------------------------------------
   Note: this is a DELIBERATE exception to CLAUDE.md §6.7 (which mandates
   icon-only row actions). Approved per product review on 2026-05-27 —
   the Users list opted into pipe-delimited text links for clarity at the
   cost of column width. Other masters keep the icon pattern unless
   product approves a similar exception for them.
   ============================================================================ */
.np-row-actions--pipe {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-3, 12px);
    white-space: nowrap;
}
.np-row-actions--pipe a,
.np-row-actions--pipe button {
    padding: 0;
    color: var(--np-color-link, #1d4ed8);
    text-decoration: none;
    background: none;
    border: 0;
    font: inherit;
    cursor: pointer;
}
.np-row-actions--pipe a:hover,
.np-row-actions--pipe a:focus,
.np-row-actions--pipe button:hover,
.np-row-actions--pipe button:focus {
    text-decoration: underline;
}
.np-row-actions--pipe .np-row-action--danger {
    color: #A41B17;
}
.np-row-actions--pipe .np-row-actions__sep {
    color: var(--np-color-border, #cbd5e1);
    user-select: none;
    aria-hidden: true;
}

/* Sticky Actions column for horizontal scroll on dense tables. The
   .table-responsive wrapper provides the horizontal-scroll viewport;
   position: sticky on the last cell keeps it visible. Subtle left
   shadow signals "more content scrolled under here". */
.table-responsive .np-actions-col {
    position: sticky;
    right: 0;
    background: var(--np-color-surface, #fff);
    box-shadow: -8px 0 8px -8px rgba(15, 23, 42, 0.12);
    z-index: 1;
}
.table-responsive tr:hover .np-actions-col {
    background: var(--np-color-brand-tint-50, #f1f5ff);
}

/* List-view toolbar layout — search box + status filter + primary action.
   Wraps cleanly on narrow viewports. */
.np-list-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: var(--np-space-3, 12px);
    margin-bottom: var(--np-space-4, 16px);
}
.np-list-toolbar__search {
    display: flex;
    gap: var(--np-space-2, 8px);
    flex: 1 1 22rem;
    min-width: 0;
    align-items: end;
}
.np-list-toolbar__search input[type="search"] { flex: 1 1 auto; min-width: 0; }
.np-list-toolbar__filter { flex: 0 0 auto; }
.np-list-toolbar__primary { flex: 0 0 auto; margin-left: auto; }

/* A filter that needs a VISIBLE label. Almost all of them do not — a select
   whose first option reads "All statuses" says what it is. A date input does
   not: it renders as an empty "mm/dd/yyyy" with nothing to say which end of the
   range it is, so the two ends are indistinguishable without a label.
   Stacks label over control; the toolbar's align-items:end keeps the control
   itself on the same baseline as its unlabelled neighbours. */
.np-list-toolbar__labelled {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.np-list-toolbar__labelled > .np-label {
    margin: 0;
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-muted);
    white-space: nowrap;
}

@media (max-width: 600px) {
    .np-list-toolbar__primary { margin-left: 0; width: 100%; }
    .np-list-toolbar__primary .np-btn { width: 100%; }
}

/* ============================================================================
   Progress bar — thin animated indicator at the top of the viewport.
   Renders once in _Layout.cshtml. Driven by np-busy.js — width is animated
   via CSS transform for sub-pixel smoothness without forcing layout.
   ============================================================================ */
.np-progress-bar {
    position: fixed;
    top: 0;
    left: 0;
    height: 3px;
    width: 100%;
    background: var(--np-color-brand-primary);
    transform-origin: 0% 50%;
    transform: scaleX(0);
    transition: transform 200ms ease-out;
    z-index: 9999;
    pointer-events: none;
    opacity: 0;
    box-shadow: 0 0 8px rgba(var(--np-color-brand-primary-rgb), 0.45);
}
.np-progress-bar--running { opacity: 1; }
.np-progress-bar--done {
    opacity: 0;
    transition: transform 200ms ease-out, opacity 200ms ease-out 100ms;
}

@media (prefers-reduced-motion: reduce) {
    .np-progress-bar { transition: opacity 100ms linear; }
}

/* ============================================================================
   Spinner — used inline inside a busy button. Pure CSS, no images.
   ============================================================================ */
.np-spinner {
    display: inline-block;
    width: 1em;
    height: 1em;
    margin-right: 0.5em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    vertical-align: -0.15em;
    animation: np-spinner-rotate 0.6s linear infinite;
}

@keyframes np-spinner-rotate {
    100% { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .np-spinner {
        animation: none;
        border-right-color: currentColor;
        opacity: 0.5;
    }
}

/* ============================================================================
   Button busy state — applied by np-busy.js when a submit-once form is
   submitting. Keeps the button at its original size so the surrounding
   layout doesn't reflow.
   ============================================================================ */
.np-btn--busy {
    cursor: progress;
    opacity: 0.85;
}
.np-btn--busy[disabled] {
    /* Browsers default to a lower opacity for [disabled]; restore so the
       spinner and label remain readable. */
    opacity: 0.85;
}
.np-btn__busy-label {
    vertical-align: middle;
}

/* ============================================================================
   Toast — top-right slide-in notification stack. Rendered by _NpToast.cshtml
   reading TempData["Flash"] / "FlashError" / "FlashWarning" / "FlashInfo",
   or pushed programmatically via window.NpToast.show({...}).
   ============================================================================ */
.np-toast-region {
    position: fixed;
    top: var(--np-space-4, 16px);
    right: var(--np-space-4, 16px);
    display: flex;
    flex-direction: column;
    gap: var(--np-space-3, 12px);
    z-index: 9998;
    pointer-events: none;
    max-width: min(420px, calc(100vw - 32px));
}
.np-toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: var(--np-space-3, 12px);
    padding: var(--np-space-3, 12px) var(--np-space-4, 16px);
    background: var(--np-color-surface, #fff);
    /* Tone comes from the FULL border + the icon, never a left accent stripe
       (a coloured left border is not part of this design system). */
    border: 1px solid var(--np-color-info-fg, #0369a1);
    border-radius: var(--np-radius-3, 6px);
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.16);
    animation: np-toast-enter 200ms ease-out;
}
.np-toast--leaving {
    animation: np-toast-leave 200ms ease-in forwards;
}
@keyframes np-toast-enter {
    from { transform: translateX(110%); opacity: 0; }
    to   { transform: translateX(0);    opacity: 1; }
}
@keyframes np-toast-leave {
    from { transform: translateX(0);    opacity: 1; }
    to   { transform: translateX(110%); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .np-toast,
    .np-toast--leaving {
        animation: none;
        opacity: 1;
    }
    .np-toast--leaving { opacity: 0; transition: opacity 100ms linear; }
}

.np-toast__icon {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    margin-top: 2px;
}
.np-toast__icon svg { width: 100%; height: 100%; }

.np-toast__body {
    flex: 1 1 auto;
    min-width: 0;
}
.np-toast__title {
    margin: 0 0 2px 0;
    font-size: 0.95rem;
    font-weight: 400;
    color: var(--np-color-text, #0f172a);
}
.np-toast__message {
    margin: 0;
    font-size: 0.9rem;
    color: var(--np-color-text-muted, #475569);
    line-height: 1.4;
    word-break: break-word;
}

.np-toast__dismiss {
    flex: 0 0 auto;
    background: transparent;
    border: 0;
    padding: 4px;
    margin: -4px -4px -4px 0;
    cursor: pointer;
    color: var(--np-color-text-muted, #64748b);
    border-radius: 4px;
}
.np-toast__dismiss:hover,
.np-toast__dismiss:focus {
    color: var(--np-color-text, #0f172a);
    background: var(--np-color-brand-tint-50, #f1f5ff);
}
.np-toast__dismiss svg { width: 16px; height: 16px; display: block; }

/* Kind variants — borrow the same fg tokens used by np-panel. The variant colour
   goes on the WHOLE border (plus the icon), never a left stripe. */
.np-toast--success {
    border-color: var(--np-color-success-fg, #15803d);
}
.np-toast--success .np-toast__icon { color: var(--np-color-success-fg, #15803d); }

.np-toast--info {
    border-color: var(--np-color-info-fg, #0369a1);
}
.np-toast--info .np-toast__icon { color: var(--np-color-info-fg, #0369a1); }

.np-toast--warning {
    border-color: var(--np-color-warning-fg, #92400e);
}
.np-toast--warning .np-toast__icon { color: var(--np-color-warning-fg, #92400e); }

.np-toast--danger {
    border-color: var(--np-color-danger-fg, #a41b17);
}
.np-toast--danger .np-toast__icon { color: var(--np-color-danger-fg, #a41b17); }

@media (max-width: 600px) {
    .np-toast-region {
        top: auto;
        bottom: var(--np-space-4, 16px);
        left: var(--np-space-4, 16px);
        right: var(--np-space-4, 16px);
        max-width: none;
    }
    @keyframes np-toast-enter {
        from { transform: translateY(110%); opacity: 0; }
        to   { transform: translateY(0);    opacity: 1; }
    }
    @keyframes np-toast-leave {
        from { transform: translateY(0);    opacity: 1; }
        to   { transform: translateY(110%); opacity: 0; }
    }
}

/* ============================================================================
   Session-expiry warning dialog
   ----------------------------------------------------------------------------
   Themed alertdialog rendered by _NpSessionExpiryDialog.cshtml. Reuses the
   .np-confirm chrome (backdrop, padding, action row) and layers a countdown
   ring + large digits on top. Warning palette — NOT danger; the dialog is
   informational ("you're idle"), not an error.
   ============================================================================ */
.np-session-dialog {
    text-align: center;
}
.np-session-dialog .np-confirm__form {
    padding-top: var(--np-space-6, 24px);
}
.np-session-dialog__visual {
    position: relative;
    width: 120px;
    height: 120px;
    margin: 0 auto var(--np-space-4, 16px);
}
.np-session-dialog__ring {
    display: block;
    width: 100%;
    height: 100%;
}
.np-session-dialog__ring-track {
    stroke: var(--np-color-border, #e2e8f0);
}
.np-session-dialog__ring-progress {
    stroke: var(--np-color-success-fg, #15803d);
    transition: stroke-dashoffset 250ms linear, stroke 200ms ease-out;
}
.np-session-dialog__ring-progress--warn {
    stroke: var(--np-color-warning-fg, #b45309);
}
.np-session-dialog__ring-progress--danger {
    stroke: var(--np-color-danger-fg, #a41b17);
}

.np-session-dialog__countdown {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.75rem;
    font-weight: 400;
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    color: var(--np-color-text, #0f172a);
    letter-spacing: -0.02em;
}

.np-session-dialog .np-confirm__title {
    color: var(--np-color-warning-fg, #92400e);
    font-size: 1.25rem;
    margin-bottom: var(--np-space-3, 12px);
}

.np-session-dialog .np-confirm__body {
    text-align: left;
    color: var(--np-color-text-muted, #475569);
    line-height: 1.5;
    margin-bottom: var(--np-space-4, 16px);
}

.np-session-dialog__warning {
    display: flex;
    align-items: flex-start;
    gap: var(--np-space-3, 12px);
    padding: var(--np-space-3, 12px) var(--np-space-4, 16px);
    background: var(--np-color-warning-bg, #fef3c7);
    border: 1px solid var(--np-color-warning-fg, #b45309);
    border-radius: var(--np-radius-2, 4px);
    color: var(--np-color-warning-fg, #92400e);
    font-size: 0.875rem;
    line-height: 1.4;
    text-align: left;
    margin-bottom: var(--np-space-5, 20px);
}
.np-session-dialog__warning svg {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    margin-top: 2px;
}
.np-session-dialog__warning span {
    flex: 1 1 auto;
}

.np-session-dialog .np-confirm__actions {
    justify-content: center;
}

/* Reduced-motion respect — disable the ring's smooth transition; the
   stroke-dashoffset still updates each second, just snapping. */
@media (prefers-reduced-motion: reduce) {
    .np-session-dialog__ring-progress {
        transition: none;
    }
}

/* ── np-charcount ─────────────────────────────────────────────────────────
   Live "used / max" counter rendered by np-charcount.js after any [maxlength]
   field. Sits bottom-right of the field; amber near the limit, red at it. */
.np-charcount {
    display: block;
    text-align: right;
    font-size: 0.8125rem;
    color: var(--np-color-text-muted, #6b7280);
    margin-top: var(--np-space-1, 4px);
}
.np-charcount--warn { color: var(--np-color-warning-fg, #b45309); }
.np-charcount--limit { color: var(--np-color-danger-fg, #a41b17); font-weight: 400; }

/* ── np-sortable ──────────────────────────────────────────────────────────
   Drag-to-reorder handle + dragging state, driven by np-sortable.js. The
   handle is the only drag affordance; up/down buttons stay for keyboard. */
.np-drag-handle {
    cursor: grab;
    user-select: none;
    color: var(--np-color-text-muted, #6b7280);
    font-size: 1.1rem;
    line-height: 1;
    padding: 0 var(--np-space-1, 4px);
    display: inline-flex;
    align-items: center;
}
.np-drag-handle:active { cursor: grabbing; }
.np-dragging { opacity: 0.5; }
[data-sortable-item][draggable="true"] .np-drag-handle { touch-action: none; }

/* ── np-mergefields ───────────────────────────────────────────────────────
   The "Variables" helper rendered by _NpMergeFields.cshtml. A labelled set of
   token chips an author clicks to insert a merge field into the field above.
   Sits directly beneath its target field inside the same .np-form-group. */
.np-mergefields {
    margin-top: var(--np-space-2, 8px);
    padding: var(--np-space-2, 8px) var(--np-space-3, 12px);
    border: 1px dashed var(--np-color-border, #d1d5db);
    border-radius: var(--np-radius, 8px);
    background: var(--np-color-surface-muted, #f9fafb);
}
.np-mergefields__title {
    display: block;
    font-size: var(--np-fs-small);
    font-weight: 400;
    color: var(--np-color-text-primary);
}
.np-mergefields__hint {
    display: block;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
    margin-bottom: var(--np-space-2, 8px);
}
.np-mergefields__chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-1, 4px);
}
.np-mergefields__chip {
    font-family: var(--np-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.8125rem;
    line-height: 1.4;
    padding: 2px var(--np-space-2, 8px);
    border: 1px solid var(--np-color-border, #d1d5db);
    border-radius: 999px;
    background: var(--np-color-surface, #fff);
    color: var(--np-color-text-primary);
    cursor: grab;
    transition: background-color 0.12s ease, border-color 0.12s ease, opacity 0.12s ease;
}
.np-mergefields__chip:hover {
    background: var(--np-color-primary-50, #f3e8f5);
    border-color: var(--np-color-primary, #741f7b);
}
.np-mergefields__chip:active { cursor: grabbing; }
.np-mergefields__chip:focus-visible {
    outline: 2px solid var(--np-color-primary, #741f7b);
    outline-offset: 2px;
}
.np-mergefields__chip--dragging { opacity: 0.5; }

/* ── np-tokenfield ─────────────────────────────────────────────────────────
   Contenteditable editor (np-tokenfield.js) that renders {{variables}} as
   atomic chips. Mirrors .np-input; the hidden source <textarea>/<input>
   carries the posted value. */
.np-tokenfield {
    display: block;
    width: 100%;
    min-height: 2.75rem;
    padding: 10px var(--np-space-3);
    font-size: var(--np-fs-small);
    line-height: 1.7;
    color: var(--np-color-text-primary);
    background-color: var(--np-color-surface);
    border: 1px solid var(--np-color-field-border);
    border-radius: var(--np-radius-2);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    cursor: text;
}
.np-tokenfield:hover { border-color: var(--np-color-field-border-hover); }
.np-tokenfield:focus {
    outline: none;
    border-color: var(--np-color-brand-primary);
    box-shadow: 0 0 0 4px var(--np-color-focus-halo);
}
.np-tokenfield--single {
    white-space: nowrap;
    overflow-x: auto;
}

/* The variable chip rendered inside the editor — light grey to read as a
   distinct, removable unit within the purple theme. */
.np-token {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin: 0 2px;
    padding: 1px 4px 1px 10px;
    background: var(--np-color-surface-tinted, #F7F0F8);
    border: 1px solid var(--np-color-border-strong, #B57BB9);
    border-radius: var(--np-radius-pill, 999px);
    font-family: var(--np-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.85em;
    line-height: 1.4;
    white-space: nowrap;
    user-select: none;
    vertical-align: baseline;
    transition: border-color 0.12s ease, background-color 0.12s ease;
}
.np-token:hover {
    background: var(--np-color-surface-muted, #FBF7FC);
    border-color: var(--np-color-brand-primary, #741F7B);
}
.np-token__label { color: var(--np-color-text-primary); }
.np-token__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.15em;
    height: 1.15em;
    padding: 0;
    margin: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--np-color-text-muted);
    font-size: 1.05em;
    line-height: 1;
    cursor: pointer;
}
.np-token__remove:hover,
.np-token__remove:focus {
    color: var(--np-color-danger-fg, #A41B17);
    background: rgba(0, 0, 0, 0.06);
}


/* ============================================================================
   Clinical Event Viewer — filter form + audit modal + JSON side-by-side
   ----------------------------------------------------------------------------
   Used only by /ClinicalAudit/Index and the _DetailsPanel partial. Scoped
   with the np-audit-* prefix so the audit viewer's specific affordances
   don't collide with the general filter / modal patterns.
   ============================================================================ */

/* Filter form -------------------------------------------------------------- */

.np-audit-filter {
    display: flex;
    flex-direction: column;
    gap: var(--np-space-4, 16px);
    margin-bottom: var(--np-space-5, 20px);
}

.np-audit-filter__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--np-space-3, 12px) var(--np-space-4, 16px);
}

.np-audit-filter__field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.np-audit-filter__field .np-input,
.np-audit-filter__field .np-select {
    width: 100%;
}

.np-audit-filter__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--np-space-3, 12px);
    flex-wrap: wrap;
}

/* Table row affordances ---------------------------------------------------- */

.np-audit-entity-link {
    color: var(--np-color-brand-fg, #5c1864);
    text-decoration: none;
    font-weight: 400;
}
.np-audit-entity-link:hover,
.np-audit-entity-link:focus-visible {
    text-decoration: underline;
}

.np-audit-eventtype {
    font-size: 0.85em;
    color: var(--np-color-text-muted, #475569);
}

/* Modal dialog (native <dialog>) ------------------------------------------ */

.np-audit-modal {
    width: min(1100px, calc(100vw - 32px));
    max-width: 1100px;
    max-height: calc(100vh - 64px);
    padding: 0;
    border: 0;
    border-radius: var(--np-radius-3, 8px);
    background: var(--np-color-surface, #fff);
    color: var(--np-color-text, #0f172a);
    box-shadow: 0 24px 64px rgba(15, 23, 42, 0.24);
    overflow: hidden;
}
.np-audit-modal::backdrop {
    background: rgba(15, 23, 42, 0.55);
}

.np-audit-modal__chrome {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--np-space-3, 12px) var(--np-space-4, 16px);
    border-bottom: 1px solid var(--np-color-border, #e2e8f0);
    background: var(--np-color-brand-tint-50, #f7f0fa);
}

.np-audit-modal__title {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 400;
    color: var(--np-color-text, #0f172a);
}

.np-audit-modal__close {
    flex: 0 0 auto;
    background: transparent;
    border: 0;
    padding: 6px;
    cursor: pointer;
    color: var(--np-color-text-muted, #64748b);
    border-radius: 4px;
}
.np-audit-modal__close:hover,
.np-audit-modal__close:focus-visible {
    color: var(--np-color-text, #0f172a);
    background: rgba(15, 23, 42, 0.08);
}
.np-audit-modal__close svg { width: 18px; height: 18px; display: block; }

.np-audit-modal__body {
    padding: var(--np-space-4, 16px) var(--np-space-5, 20px) var(--np-space-5, 20px);
    overflow-y: auto;
    max-height: calc(100vh - 64px - 48px); /* viewport minus offsets minus chrome height */
    position: relative;
}

/* Detail content ----------------------------------------------------------- */

.np-audit-detail {
    display: flex;
    flex-direction: column;
    gap: var(--np-space-4, 16px);
    position: relative;
    z-index: 1;
}

/* PHI watermark — large, faded label confirming the reviewer is
   inside an audited surface. Sits behind the content (z-index 0) so
   it doesn't interfere with scrolling. */
.np-audit-detail__watermark {
    position: absolute;
    inset: 0;
    pointer-events: none;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 4rem;
    font-weight: 400;
    color: rgba(var(--np-color-brand-primary-rgb), 0.06);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    transform: rotate(-12deg);
    user-select: none;
    z-index: 0;
}

.np-audit-detail__header {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: var(--np-space-4, 16px) var(--np-space-6, 24px);
    padding: var(--np-space-3, 12px) var(--np-space-4, 16px);
    background: var(--np-color-brand-tint-50, #f7f0fa);
    border-radius: var(--np-radius-2, 4px);
}

.np-audit-detail__meta-row {
    display: grid;
    grid-template-columns: 140px 1fr;
    gap: var(--np-space-3, 12px);
    align-items: baseline;
    padding: 2px 0;
    font-size: 0.875rem;
}

.np-audit-detail__hash {
    word-break: break-all;
    font-size: 0.75rem;
}

.np-audit-detail__split {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--np-space-4, 16px);
}

@media (max-width: 900px) {
    .np-audit-detail__split { grid-template-columns: 1fr; }
}

.np-audit-detail__pane {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.np-audit-detail__pane-title {
    margin: 0 0 var(--np-space-2, 8px) 0;
    font-size: var(--np-fs-small);
    font-weight: 400;
    color: var(--np-color-text, #0f172a);
    display: flex;
    align-items: baseline;
    gap: var(--np-space-2, 8px);
}

.np-audit-detail__pane-flag {
    font-size: 0.8rem;
    font-weight: 400;
    color: var(--np-color-text-muted, #64748b);
}

.np-audit-detail__json {
    margin: 0;
    padding: var(--np-space-3, 12px);
    background: #0f172a;
    color: #e2e8f0;
    border-radius: var(--np-radius-2, 4px);
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.8rem;
    line-height: 1.5;
    white-space: pre;
    overflow: auto;
    max-height: 50vh;
}

/* ---- Property-level diff (object-shaped JSON payloads) ------------------- */

.np-audit-diff {
    border: 1px solid var(--np-color-border, #e2e8f0);
    border-radius: var(--np-radius-2, 4px);
    background: #fff;
    overflow: hidden;
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.82rem;
    line-height: 1.55;
    max-height: 50vh;
    overflow-y: auto;
}

.np-audit-diff__line {
    display: grid;
    grid-template-columns: 22px minmax(0, auto) 8px minmax(0, 1fr);
    align-items: baseline;
    column-gap: 4px;
    padding: 2px var(--np-space-3, 12px);
    border-bottom: 1px solid #f1f5f9;
    word-break: break-word;
}
.np-audit-diff__line:last-child { border-bottom: 0; }

.np-audit-diff__marker {
    font-weight: 400;
    text-align: center;
    color: #64748b;
}

.np-audit-diff__prop {
    color: #1e293b;
    font-weight: 400;
}

.np-audit-diff__colon { color: #64748b; }

.np-audit-diff__val {
    color: #0f172a;
    white-space: pre-wrap;
    word-break: break-word;
}

/* Modified — soft amber wash on both sides at the same row */
.np-audit-diff__line--modified {
    background: #fef9c3;
}
.np-audit-diff__line--modified .np-audit-diff__marker { color: #92400e; }


/* ============================================================
   Role selection (/Account/SelectRole)
   ------------------------------------------------------------
   Vertical stack of selectable role cards for multi-role users.
   Native radio group (keyboard arrow-nav + space/enter) with the
   real input visually hidden; the card is the visual control.
   Selection is conveyed by the check pill (sibling selector — works
   without :has) and enhanced with brand border/fill where :has is
   supported, so it degrades gracefully.
   ============================================================ */
.np-role-select__main {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    max-width: 1440px;
    margin: 0 auto;
    overflow-y: auto;
    padding: clamp(var(--np-space-5), 4vw, var(--np-space-7)) clamp(var(--np-space-4), 4vw, var(--np-space-6));
}

.np-role-select {
    width: 100%;
    max-width: 34rem;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: var(--np-space-5);
}

.np-role-select__head { display: flex; flex-direction: column; gap: var(--np-space-2); }
.np-role-select__title { margin: 0; }
.np-role-select__lead { margin: 0; max-width: 48ch; color: var(--np-color-text-muted); }

.np-role-select__fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }

/* Scrollport — caps height so a long role list scrolls inside the card
   instead of pushing the Continue button off-screen. */
.np-role-select__list {
    display: flex;
    flex-direction: column;
    gap: var(--np-space-3);
    max-height: min(56vh, 28rem);
    overflow-y: auto;
    padding: 4px;                 /* room so hover-lift + focus ring aren't clipped */
    scrollbar-width: thin;
    scrollbar-color: var(--np-color-border-strong) transparent;
}
.np-role-select__list::-webkit-scrollbar { width: 8px; }
.np-role-select__list::-webkit-scrollbar-thumb {
    background: var(--np-color-border-strong);
    border-radius: var(--np-radius-pill);
}
.np-role-select__list::-webkit-scrollbar-track { background: transparent; }
.np-role-select__list:focus { outline: none; }

/* The card label */
.np-role-select__option {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--np-space-4);
    padding: var(--np-space-4);
    background: var(--np-color-surface);
    border: 1.5px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    cursor: pointer;
    transition: border-color .15s ease, box-shadow .15s ease, background .15s ease, transform .15s ease;
}
.np-role-select__option:hover {
    border-color: var(--np-color-border-strong);
    box-shadow: var(--np-shadow-2);
    transform: translateY(-1px);
}
/* Guaranteed-visible keyboard focus (works without :has). */
.np-role-select__option:focus-within {
    border-color: var(--np-color-focus);
    box-shadow: 0 0 0 3px var(--np-color-focus-halo);
}
/* Selected highlight (enhancement — the check pill conveys it without :has). */
.np-role-select__option:has(.np-role-select__radio:checked) {
    border-color: var(--np-color-brand-primary);
    background: var(--np-color-brand-tint-50);
    box-shadow: var(--np-shadow-2);
}

/* Real radio — hidden but focusable + in the a11y tree. */
.np-role-select__radio {
    position: absolute;
    top: var(--np-space-4);
    left: var(--np-space-4);
    width: 1px;
    height: 1px;
    margin: 0;
    opacity: 0;
    pointer-events: none;
}

/* Monogram avatar */
.np-role-select__media {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--np-radius-2);
    border: 1px solid var(--np-color-border);
    background: var(--np-color-surface-tinted);
    color: var(--np-color-brand-primary);
    font-weight: 400;
    font-size: 0.95rem;
    letter-spacing: 0.02em;
    transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.np-role-select__option:has(.np-role-select__radio:checked) .np-role-select__media {
    background: var(--np-color-brand-primary);
    border-color: transparent;
    color: var(--np-color-text-on-brand);
}

.np-role-select__body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.np-role-select__name {
    font-weight: 400;
    line-height: 1.3;
    color: var(--np-color-text-primary);
    overflow-wrap: anywhere;
}
.np-role-select__meta { display: flex; align-items: center; gap: var(--np-space-2); min-width: 0; }
.np-role-select__hint { font-size: var(--np-fs-small); color: var(--np-color-text-muted); }
.np-role-select__badge {
    display: inline-flex;
    align-items: center;
    padding: 2px 10px;
    border-radius: var(--np-radius-pill);
    border: 1px solid var(--np-color-success-border);
    background: var(--np-color-success-bg);
    color: var(--np-color-success-fg);
    font-size: var(--np-fs-small);
    font-weight: 400;
    white-space: nowrap;
}

/* Selection check pill — toggled by the sibling radio's :checked. */
.np-role-select__check {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--np-radius-pill);
    border: 2px solid var(--np-color-border-strong);
    color: transparent;
    background: transparent;
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.np-role-select__check svg { width: 16px; height: 16px; opacity: 0; transition: opacity .15s ease; }
.np-role-select__radio:checked ~ .np-role-select__check {
    border-color: var(--np-color-brand-primary);
    background: var(--np-color-brand-primary);
    color: var(--np-color-text-on-brand);
}
.np-role-select__radio:checked ~ .np-role-select__check svg { opacity: 1; }

.np-role-select__actions { margin: 0; }
.np-btn--block { display: flex; width: 100%; justify-content: center; }

@media (max-width: 480px) {
    .np-role-select__option { padding: var(--np-space-3); gap: var(--np-space-3); }
    .np-role-select__media { width: 38px; height: 38px; font-size: 0.85rem; }
    .np-role-select__check { width: 22px; height: 22px; }
    .np-role-select__check svg { width: 14px; height: 14px; }
    .np-role-select__list { max-height: 62vh; }
}

@media (prefers-reduced-motion: reduce) {
    .np-role-select__option,
    .np-role-select__media,
    .np-role-select__check,
    .np-role-select__check svg { transition: none; }
    .np-role-select__option:hover { transform: none; }
}

/* Windows High Contrast / forced colours — keep selection perceivable. */
@media (forced-colors: active) {
    .np-role-select__option:focus-within { outline: 2px solid Highlight; }
    .np-role-select__option:has(.np-role-select__radio:checked) { border-color: Highlight; }
    .np-role-select__radio:checked ~ .np-role-select__check {
        background: Highlight;
        border-color: Highlight;
        color: HighlightText;
    }
}


/* ============================================================
   Switch-role modal (account menu)
   ------------------------------------------------------------
   Native <dialog> reusing the np-role-select option cards for the
   role list. Opened by ~/js/np-switch-role.js (hoisted to <body>
   on load so the account popover closing can't hide it).
   ============================================================ */
.np-switch-dialog {
    width: min(92vw, 32rem);
    max-width: 32rem;
    padding: 0;
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-3);
    box-shadow: var(--np-shadow-3);
    background: var(--np-color-surface);
    color: var(--np-color-text-primary);
}
.np-switch-dialog::backdrop {
    background: rgba(61, 15, 66, 0.45);
    backdrop-filter: blur(2px);
}
.np-switch-dialog__form {
    display: flex;
    flex-direction: column;
    gap: var(--np-space-4);
    padding: var(--np-space-6);
    max-height: min(86vh, 40rem);
}
.np-switch-dialog__head { display: flex; flex-direction: column; gap: var(--np-space-2); }
.np-switch-dialog__title { margin: 0; font-size: var(--np-fs-h2); }
.np-switch-dialog__desc { margin: 0; color: var(--np-color-text-muted); }
.np-switch-dialog__fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
.np-switch-dialog__list { max-height: min(48vh, 22rem); }
.np-switch-dialog__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--np-space-3);
    flex-wrap: wrap;
}
@media (max-width: 480px) {
    .np-switch-dialog__form { padding: var(--np-space-4); }
    .np-switch-dialog__actions .np-btn { flex: 1 1 auto; }
}
@media (prefers-reduced-motion: reduce) {
    .np-switch-dialog::backdrop { backdrop-filter: none; }
}
.np-audit-diff__line--modified .np-audit-diff__val { font-weight: 400; }

/* Added — green tint on the After side only */
.np-audit-diff__line--added {
    background: #dcfce7;
}
.np-audit-diff__line--added .np-audit-diff__marker { color: #15803d; }
.np-audit-diff__line--added .np-audit-diff__val { font-weight: 400; }

/* Removed — red tint on the Before side only */
.np-audit-diff__line--removed {
    background: #fee2e2;
    text-decoration: line-through;
    text-decoration-color: rgba(164, 27, 23, 0.5);
}
.np-audit-diff__line--removed .np-audit-diff__marker { color: #a41b17; }
.np-audit-diff__line--removed .np-audit-diff__val { color: #7f1d1d; }

/* Placeholder — empty row holding vertical alignment with the
   opposite-side change. Dimmed so it visually recedes. */
.np-audit-diff__line--placeholder {
    background: #fafafa;
    opacity: 0.55;
}
.np-audit-diff__line--placeholder .np-audit-diff__val::before {
    content: "—";
    color: #94a3b8;
}

/* Diff summary banner above the panes — quick "X fields changed" tag */
.np-audit-diff-summary {
    padding: var(--np-space-2, 8px) var(--np-space-3, 12px);
    background: var(--np-color-info-bg, #e0f2fe);
    color: var(--np-color-info-fg, #0369a1);
    border-radius: var(--np-radius-2, 4px);
    /* Borderless by design — matches the np-panel treatment (no accent stripe). */
    font-size: 0.875rem;
}

/* Legend strip under the panes */
.np-audit-diff-legend {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--np-space-3, 12px);
    margin-top: var(--np-space-3, 12px);
    padding: var(--np-space-2, 8px) var(--np-space-3, 12px);
    background: #f8fafc;
    border-radius: var(--np-radius-2, 4px);
    font-size: 0.8rem;
    color: var(--np-color-text-muted, #475569);
}
.np-audit-diff-legend__swatch {
    width: 14px;
    height: 14px;
    border-radius: 3px;
    display: inline-block;
    border: 1px solid rgba(15, 23, 42, 0.08);
}
.np-audit-diff-legend__swatch--modified { background: #fef9c3; }
.np-audit-diff-legend__swatch--added    { background: #dcfce7; }
.np-audit-diff-legend__swatch--removed  { background: #fee2e2; }
.np-audit-diff-legend__label { margin-right: var(--np-space-2, 8px); }


/* Template composer (in-house, jQuery-free)
   ============================================================ */
.np-composer {
    display: block;
}

/* Variables panel — sits BELOW the editor, full width */
.np-variables {
    margin-top: var(--np-space-4);
    background: var(--np-color-surface-tinted);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    padding: var(--np-space-4);
}
.np-variables__title {
    font-size: var(--np-fs-small);
    margin: 0 0 var(--np-space-2);
}
.np-variables__hint {
    margin: 0 0 var(--np-space-3);
    color: var(--np-color-text-secondary, #6B5570);
    font-size: 0.85rem;
}
.np-variables__list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2);
}

/* Editors (contenteditable) — only visible once JS enhances the textareas */
.np-composer__editor-wrap {
    position: relative;
    border: 1px solid var(--np-color-field-border);
    border-radius: var(--np-radius-1);
    background: var(--np-color-surface);
    overflow: hidden;
}
.np-composer__editor img { max-width: 100%; height: auto; }
.np-composer__toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    padding: var(--np-space-2);
    background: var(--np-color-surface-tinted);
    border-bottom: 1px solid var(--np-color-border);
}
.np-composer__tool {
    min-width: 34px;
    padding: 4px 9px;
    border: 1px solid var(--np-color-border-strong);
    border-radius: var(--np-radius-1);
    background: var(--np-color-surface);
    color: var(--np-color-text-primary);
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
}
.np-composer__tool:hover { background: var(--np-color-surface-tinted); }
.np-composer__tool:focus-visible {
    outline: 3px solid var(--np-color-focus);
    outline-offset: 1px;
}
.np-composer__tool--active {
    background: var(--np-color-brand-tint-200, #EAD8EE);
    border-color: var(--np-color-border-strong);
}
.np-composer__tool-select { min-width: auto; padding: 4px 6px; }
.np-composer__tool-color {
    width: 34px;
    padding: 2px;
    cursor: pointer;
}
.np-composer__tool-sep {
    width: 1px;
    align-self: stretch;
    margin: 2px 4px;
    background: var(--np-color-border);
}

/* Raw-HTML code view (toggled from the toolbar) */
.np-composer__codeview {
    width: 100%;
    min-height: 280px;
    border: none;
    padding: var(--np-space-3);
    font-family: var(--np-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.875rem;
    line-height: 1.5;
    resize: vertical;
}
.np-composer__codeview:focus { outline: none; box-shadow: inset 0 0 0 2px var(--np-color-focus-halo); }

/* Fullscreen editing */
.np-composer__editor-wrap--fullscreen {
    position: fixed;
    inset: 0;
    z-index: 1050;
    display: flex;
    flex-direction: column;
    border-radius: 0;
    background: var(--np-color-surface);
}
.np-composer__editor-wrap--fullscreen .np-composer__editor,
.np-composer__editor-wrap--fullscreen .np-composer__codeview {
    flex: 1 1 auto;
    max-height: none;
}
body.np-composer-fs-lock { overflow: hidden; }

/* Inserted tables + videos (apply in the editor and the Details preview) */
.np-composer__table {
    border-collapse: collapse;
    width: 100%;
    margin: var(--np-space-2) 0;
}
.np-composer__table td,
.np-composer__table th {
    border: 1px solid var(--np-color-border-strong);
    padding: 6px 8px;
    min-width: 2rem;
}
.np-composer__video {
    position: relative;
    display: block;
    max-width: 480px;
    margin: var(--np-space-2) 0;
    aspect-ratio: 16 / 9;
}
.np-composer__video iframe {
    width: 100%;
    height: 100%;
    border: 0;
}

/* Insert-media dialog (image / video — device file or URL) */
.np-editor-dialog-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1100; /* above fullscreen editor (1050) */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--np-space-5);
    overflow: auto;
    background: rgba(0, 0, 0, 0.45);
}
.np-editor-dialog {
    width: 100%;
    max-width: 440px;
    background: var(--np-color-surface);
    border-radius: var(--np-radius-2);
    box-shadow: var(--np-shadow-3);
    padding: var(--np-space-4);
}
.np-editor-dialog h4 { margin: 0 0 var(--np-space-3); }
.np-editor-dialog__label {
    display: block;
    font-weight: 400;
    margin: var(--np-space-3) 0 4px;
}
.np-editor-dialog__or {
    text-align: center;
    color: var(--np-color-text-secondary, #6B5570);
    font-size: 0.85rem;
    margin: var(--np-space-3) 0;
}
.np-editor-dialog__error {
    margin: var(--np-space-3) 0 0;
    color: var(--np-color-status-danger, #C92420);
    font-size: 0.85rem;
}
.np-editor-dialog input.np-input + .np-hint { display: block; margin-top: 4px; }

/* Template preview modal */
.np-editor-dialog--preview { max-width: 720px; }
.np-preview__meta {
    margin: 0 0 var(--np-space-3);
    color: var(--np-color-text-secondary, #6B5570);
    font-size: 0.85rem;
}
.np-preview__subject {
    padding-bottom: var(--np-space-2);
    margin-bottom: var(--np-space-3);
    border-bottom: 1px solid var(--np-color-border);
}
/* One-line note above a framed preview ("links are not active here"). Sits
   between the Message heading and the frame, so the reader is told before they
   try rather than after nothing happens. */
.np-preview__note {
    margin: 0 0 var(--np-space-2);
    font-size: var(--np-fs-small);
}
.np-preview__content {
    max-height: 60vh;
    overflow: auto;
    padding: var(--np-space-3);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-1);
    line-height: 1.5;
}
/* The email preview renders inside an iframe so the wrapped document keeps its own
   <head> and the application stylesheet cannot bleed into it. The frame owns the
   scrolling, so the container drops its own padding and overflow. The modifier is
   applied by np-editor.js rather than matched with :has(), which is unsupported on
   the older browsers still in use on NHS desktops. */
.np-preview__content--framed { padding: 0; overflow: hidden; }
.np-preview__frame {
    display: block;
    width: 100%;
    height: 60vh;
    border: 0;
    background: #ffffff;
}
.np-preview__content img { max-width: 100%; height: auto; }
.np-preview__content table { border-collapse: collapse; }
.np-preview__content td, .np-preview__content th { border: 1px solid var(--np-color-border-strong); padding: 6px 8px; }
/* Resolved token values — subtle highlight so reviewers see what was merged */
.np-preview__resolved { background: #E8F5E9; border-radius: 3px; padding: 0 3px; }
.np-editor-dialog__actions {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
    margin-top: var(--np-space-4);
}
.np-composer__editor {
    padding: var(--np-space-3);
    line-height: 1.5;
    overflow: auto;
}
.np-composer__editor--line { min-height: 44px; }
/* Fixed visible height; long content scrolls inside the editor (overflow:auto
   above). Fullscreen overrides max-height to fill the screen. */
.np-composer__editor--rich { min-height: 280px; max-height: 460px; }
.np-composer__editor:focus { outline: none; box-shadow: inset 0 0 0 2px var(--np-color-focus-halo); }
.np-composer__editor--dragover {
    box-shadow: inset 0 0 0 2px var(--np-color-focus);
    background: var(--np-color-surface-tinted);
}

/* Read-only body viewer used by the "Preview email" dialogs: a non-editable, true
   representation of the sent email. It adds NO styling of its own (no background,
   no badges) — the email's own markup renders verbatim. The only normalisation is
   the application theme font, which was an explicit product requirement so every
   email preview reads consistently regardless of the email's inline font styles
   (CLAUDE.md §3.11 / email-preview font rule). */
.np-composer__editor--readonly {
    min-height: 180px;
    max-height: 360px;
    cursor: default;
}
.np-composer__editor--readonly:focus { outline: none; box-shadow: none; }
/* The read-only preview shows the full toolbar for visual parity with the compose
   editor, but every control is disabled and the whole bar is non-interactive. */
.np-composer__toolbar--disabled {
    opacity: 0.6;
    pointer-events: none;
    cursor: default;
}
.np-composer__toolbar--disabled .np-composer__tool,
.np-composer__toolbar--disabled .np-composer__tool-select,
.np-composer__toolbar--disabled .np-composer__tool-color { cursor: default; }
.np-composer__editor--readonly,
.np-composer__editor--readonly * {
    font-family: var(--np-font-sans) !important;
}
/* Token spans carried over from the template render as PLAIN TEXT here — the sent
   email has no np-theme.css, so the recipient never sees a pill/highlight. Strip
   the editor-only chrome so the preview matches the final email exactly. */
.np-composer__editor--readonly .np-token {
    display: inline;
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    border-radius: 0;
    color: inherit;
    font: inherit;
}
.np-composer__editor--readonly .np-token__x { display: none; }

/* Token chips placed inside an editor — light lavender pill, monospace */
.np-token {
    display: inline-block;
    background: var(--np-color-brand-tint-200, #EAD8EE);
    border: 1px solid var(--np-color-border, #EAD8EE);
    color: var(--np-color-brand-primary, #741F7B);
    border-radius: var(--np-radius-pill);
    padding: 1px 9px;
    margin: 0 2px;
    font-family: var(--np-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.85em;
    white-space: nowrap;
    cursor: grab;
}
.np-token:active { cursor: grabbing; }
.np-token__x {
    margin-left: 6px;
    color: var(--np-color-text-secondary, #6B5570);
    cursor: pointer;
    font-weight: 400;
}
.np-token__x:hover { color: var(--np-color-status-danger, #C92420); }

/* Variable picker chips — white pill, soft border, monospace {{token}} */
.np-composer__props-group {
    width: 100%;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--np-color-text-secondary, #6B5570);
    font-weight: 400;
    margin: var(--np-space-2) 0;
}
.np-composer__props-empty { color: var(--np-color-text-secondary, #6B5570); margin: 0; }
.np-composer__chip {
    display: inline-flex;
    align-items: center;
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border-strong, #B57BB9);
    color: var(--np-color-text-primary);
    border-radius: var(--np-radius-pill);
    padding: 4px 12px;
    font-family: var(--np-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.8rem;
    cursor: pointer;
    -webkit-user-select: none;
    user-select: none;
}
.np-composer__chip:hover { background: var(--np-color-surface-tinted); box-shadow: var(--np-shadow-1); }
.np-composer__chip:active { cursor: grabbing; }
.np-composer__chip:focus-visible {
    outline: 3px solid var(--np-color-focus);
    outline-offset: 1px;
}

/* Trash zone */
.np-composer__trash {
    margin-top: var(--np-space-4);
    border: 2px dashed var(--np-color-status-danger, #C92420);
    border-radius: var(--np-radius-2);
    padding: var(--np-space-3);
    text-align: center;
    color: var(--np-color-status-danger, #C92420);
    font-size: 0.85rem;
}
.np-composer__trash--over { background: #FBEAE8; }

/* Read-only render of stored body markup (Details view) */
.np-composer-preview {
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-1);
    background: var(--np-color-surface);
    padding: var(--np-space-3);
    line-height: 1.5;
    overflow-wrap: anywhere;
}
.np-composer-preview > :last-child { margin-bottom: 0; }

/* ── Patient activity log timeline (workspace "Patient log" section) ──
   A vertical timeline of read-only activity entries. Each row pairs the shared
   _NpStatus pill (colour + dot + label, per §6.6) with the message and meta. */
.np-activity-wrap { position: relative; }

/* JS-free segmented filter: visually-hidden radios drive the active state and
   the "care episodes only" view via CSS sibling rules. */
.np-activity-filter__radio {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    opacity: 0;
}
/* Tab strip — underline tabs (legacy "nav-tabs" look), driven by the hidden
   radios above via CSS sibling rules. */
.np-activity-tabs {
    display: flex;
    gap: var(--np-space-4);
    margin-bottom: var(--np-space-3);
    border-bottom: 1px solid var(--np-color-border);
}
.np-activity-tab {
    margin: 0 0 -1px;
    padding: 8px 2px;
    font-size: var(--np-fs-small);
    font-weight: 400;
    line-height: 1.4;
    color: var(--np-color-text-muted);
    cursor: pointer;
    user-select: none;
    border-bottom: 2px solid transparent;
}
.np-activity-tab:hover { color: var(--np-color-text-primary); }
/* Active tab. */
#np-logfilter-all:checked ~ .np-activity-tabs label[for="np-logfilter-all"],
#np-logfilter-episodes:checked ~ .np-activity-tabs label[for="np-logfilter-episodes"] {
    color: var(--np-color-brand-primary);
    border-bottom-color: var(--np-color-brand-primary);
}
/* Keyboard focus ring on the focused tab. */
#np-logfilter-all:focus-visible ~ .np-activity-tabs label[for="np-logfilter-all"],
#np-logfilter-episodes:focus-visible ~ .np-activity-tabs label[for="np-logfilter-episodes"] {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}
/* Fixed-height scroll body so a long log stays inside the panel. */
.np-activity-scroll {
    max-height: 420px;
    overflow-y: auto;
    padding-right: var(--np-space-2);
}
.np-activity-scroll:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}
/* "Care episodes only" hides the patient-level rows + reveals the empty note. */
#np-logfilter-episodes:checked ~ .np-activity-scroll .np-activity__item--patient { display: none; }
.np-activity__empty-filtered { display: none; }
#np-logfilter-episodes:checked ~ .np-activity-scroll .np-activity__empty-filtered { display: block; }

/* Type filter — a compact select on the right of the tab strip, driven by
   np-activity-log.js. Rows it excludes get --type-hidden; when the combined
   filters leave nothing, the JS reveals the empty-type note. */
.np-activity-type-select {
    margin-left: auto;
    align-self: center;
    margin-bottom: 6px;
    width: auto;
    max-width: 250px;
    font-size: var(--np-fs-small);
    padding-top: 4px;
    padding-bottom: 4px;
}
.np-activity__item.np-activity__item--type-hidden { display: none; }
.np-activity__empty-type { display: none; }
.np-activity__empty-type.is-visible { display: block; }

.np-activity {
    list-style: none;
    margin: 0;
    padding: 0;
}
.np-activity__item {
    position: relative;
    display: flex;
    gap: var(--np-space-3);
    padding: 0 0 var(--np-space-4) var(--np-space-1);
}
.np-activity__item:last-child { padding-bottom: 0; }
/* Connecting line running down the timeline behind the markers. */
.np-activity__item::before {
    content: "";
    position: absolute;
    left: 5px;
    top: 6px;
    bottom: 0;
    width: 2px;
    background: var(--np-color-border);
}
.np-activity__item:last-child::before { display: none; }
.np-activity__marker {
    flex: 0 0 auto;
    width: 10px;
    height: 10px;
    margin-top: 5px;
    border-radius: 50%;
    background: var(--np-color-brand-primary);
    box-shadow: 0 0 0 3px var(--np-color-surface);
    z-index: 1;
}
.np-activity__body { flex: 1 1 auto; min-width: 0; }
.np-activity__head {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    flex-wrap: wrap;
}
.np-activity__episode {
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
}
.np-activity__message {
    margin: var(--np-space-1) 0 0;
    color: var(--np-color-text-primary);
    overflow-wrap: anywhere;
}
/* The document name in a "Document uploaded" entry (PatientActivityMessageRenderer). */
.np-activity__doclink {
    color: var(--np-color-link, var(--np-color-primary));
    text-decoration: underline;
    text-underline-offset: 2px;
}
.np-activity__doclink:focus-visible {
    outline: 2px solid var(--np-color-focus, currentColor);
    outline-offset: 2px;
}
.np-activity__meta {
    margin-top: 2px;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
}
.np-activity__sep { margin: 0 4px; }


/* ── Patient workspace records tabs (Activity logs / Documents / Timeline) ──
   Pinned beneath the workspace accordion. JS-free tabbed panel: three
   visually-hidden radios drive both the active pill and which panel shows, via
   CSS sibling rules — same mechanism as the activity-filter tabs above. */
.np-wstabs { margin-top: var(--np-space-5); position: relative; }
.np-wstabs__radio {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    opacity: 0;
}
.np-wstabs__nav { margin: 0 0 var(--np-space-4); }
.np-wstabs__tab { cursor: pointer; }
/* Active pill — mirror .np-tabs__link--active for the checked radio's label. */
#np-wstab-assessments:checked ~ .np-wstabs__nav .np-wstabs__tab--assessments,
#np-wstab-activity:checked ~ .np-wstabs__nav .np-wstabs__tab--activity,
#np-wstab-documents:checked ~ .np-wstabs__nav .np-wstabs__tab--documents,
#np-wstab-notifications:checked ~ .np-wstabs__nav .np-wstabs__tab--notifications {
    background: var(--np-color-brand-primary);
    color: var(--np-color-text-on-brand);
    border-color: var(--np-color-brand-primary);
    box-shadow: var(--np-shadow-1);
}
#np-wstab-assessments:checked ~ .np-wstabs__nav .np-wstabs__tab--assessments .np-tabs__chip,
#np-wstab-activity:checked ~ .np-wstabs__nav .np-wstabs__tab--activity .np-tabs__chip,
#np-wstab-documents:checked ~ .np-wstabs__nav .np-wstabs__tab--documents .np-tabs__chip,
#np-wstab-notifications:checked ~ .np-wstabs__nav .np-wstabs__tab--notifications .np-tabs__chip {
    background: rgba(255, 255, 255, 0.25);
    color: var(--np-color-text-on-brand);
}
/* Keyboard focus ring — the radio is focusable (Tab/arrow keys); surface the
   ring on its label so the active tab is visible to keyboard users. */
#np-wstab-assessments:focus-visible ~ .np-wstabs__nav .np-wstabs__tab--assessments,
#np-wstab-activity:focus-visible ~ .np-wstabs__nav .np-wstabs__tab--activity,
#np-wstab-documents:focus-visible ~ .np-wstabs__nav .np-wstabs__tab--documents,
#np-wstab-notifications:focus-visible ~ .np-wstabs__nav .np-wstabs__tab--notifications {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}
/* Panels — only the checked tab's panel shows. Each panel carries the same
   card chrome (surface + border + radius + shadow) as the accordion sections
   above so the tab body reads as a panel, not loose text on the page. */
.np-wstabs__panel {
    display: none;
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-brand-tint-100);
    border-radius: var(--np-radius-2, 8px);
    padding: var(--np-space-5);
    box-shadow: 0 1px 3px rgba(var(--np-color-brand-primary-rgb), 0.08);
}
/* (The per-panel subtitle was removed: each tab now carries its own description
   as a title + visually-hidden hint, so the sentence is readable BEFORE the tab
   is opened instead of taking a line off the top of the panel afterwards.) */
/* A panel whose content IS the card — it draws its own toolbar bar and a
   tinted rail edge-to-edge, so the panel must contribute no padding of its
   own. Used by the Documents tab; see the np-dms block below. */
.np-wstabs__panel--flush {
    padding: 0;
    overflow: hidden;
    border-radius: var(--np-radius-3);
}
#np-wstab-assessments:checked ~ .np-wstabs__body .np-wstabs__panel--assessments,
#np-wstab-activity:checked ~ .np-wstabs__body .np-wstabs__panel--activity,
#np-wstab-documents:checked ~ .np-wstabs__body .np-wstabs__panel--documents,
#np-wstab-notifications:checked ~ .np-wstabs__body .np-wstabs__panel--notifications {
    display: block;
}
/* Empty state inside a records-tab panel — plain copy inside the panel card,
   centred, no nested border. */
.np-wstabs__empty {
    padding: var(--np-space-5) var(--np-space-3);
    text-align: center;
}
.np-wstabs__empty-title {
    font-size: var(--np-fs-h3);
    margin: 0 0 var(--np-space-2);
    color: var(--np-color-text-primary);
}
.np-wstabs__empty-text {
    margin: 0;
    color: var(--np-color-text-muted);
}
/* ───────────────────────────────────────────────────────────────────────
   Patient Documents (DMS) — the Documents records tab.

   ONE flush card: a toolbar bar across the top, a tinted folder rail down
   the left, and the document table filling the rest. The rail and the
   toolbar are edge-to-edge inside the card (which is why the tab panel it
   sits in carries np-wstabs__panel--flush and contributes no padding of
   its own) — a rail that floats inside 24px of white reads as a second
   card and loses the containment that makes the two panes one surface.

   BORDERS ARE STRUCTURAL HERE, NOT DECORATIVE. Exactly four lines exist:
   under the toolbar, down the right of the rail, under the table header,
   and between rows. No column rules, no outer table box, no zebra, and
   nothing under the last row. A clinical list is read by scanning names
   down a column, and every extra horizontal rule is one more stop the eye
   has to cross.

   Density is deliberate: a 40px file-type badge sets the row height, the
   name sits beside it, and everything else in the cell is a step quieter.
   Whitespace separates rows; type hierarchy separates the facts in them.

   Every colour is a token, so the whole panel re-themes with the app.
   ─────────────────────────────────────────────────────────────────────── */

.np-dms {
    /* The card IS this element — the tab panel around it is flush. */
    background: var(--np-color-surface);
    border-radius: var(--np-radius-3);
    overflow: hidden;
}

/* ── Toolbar ───────────────────────────────────────────────────────────
   Case scope, then search (which takes all the slack), then the two
   create actions. Wraps as a whole rather than collapsing the search,
   because a search box narrower than its own placeholder is worse than
   a second row. */
.np-dms__toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-3);
    padding: var(--np-space-4) var(--np-space-5);
    border-bottom: 1px solid var(--np-color-brand-tint-100);
}

/* The case scope. A bordered pill rather than a bare <select> so the
   label and the value read as one control — "CASE ▸ CASE-0052" — which is
   what makes it obvious this narrows everything below it. */
.np-dms__case {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    padding: 6px 8px 6px var(--np-space-3);
    border: 1px solid var(--np-color-brand-tint-200);
    border-radius: var(--np-radius-2);
    background: var(--np-color-brand-tint-50);
}
.np-dms__case-label {
    font-size: 0.625rem;
    font-weight: 400;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--np-color-text-muted);
}
.np-dms__case-select {
    border: 0;
    background: transparent;
    padding: 2px 0;
    max-width: 24ch;
    font-family: inherit;
    font-size: var(--np-fs-xsmall);
    font-weight: 400;
    color: var(--np-color-text-primary);
    cursor: pointer;
}
.np-dms__case-select:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
    border-radius: var(--np-radius-1);
}

.np-dms__search {
    position: relative;
    flex: 1 1 240px;
    min-width: 200px;
    display: flex;
    align-items: center;
}
.np-dms__search-icon {
    position: absolute;
    left: var(--np-space-3);
    display: grid;
    place-items: center;
    color: var(--np-color-text-muted);
    pointer-events: none;
}
.np-dms__search-icon svg { width: 16px; height: 16px; }
/* Padded left for the icon that sits on top of it. Not .np-input: this one
   is a toolbar field, sized to the bar rather than to a form grid. */
.np-dms__search-input {
    width: 100%;
    padding: 9px var(--np-space-3) 9px 34px;
    border: 1px solid var(--np-color-search-border);
    border-radius: var(--np-radius-2);
    background: var(--np-color-brand-tint-50);
    font-family: inherit;
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-primary);
}
.np-dms__search-input::placeholder { color: var(--np-color-text-muted); }
.np-dms__search-input:focus {
    outline: 0;
    border-color: var(--np-color-search-border-focus);
    background: var(--np-color-surface);
    box-shadow: 0 0 0 3px var(--np-color-focus-halo);
}
.np-dms__search-input:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 1px;
}

/* Toolbar buttons sit a step tighter than a form button — they are chrome,
   not the page's primary action. */
.np-dms__toolbar-actions {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    flex: 0 0 auto;
}
.np-dms__toolbar .np-btn {
    padding: 9px var(--np-space-4);
    /*font-size: var(--np-fs-xsmall);*/
    white-space: nowrap;
}
.np-dms__toolbar .np-btn svg { width: 16px; height: 16px; }

/* ── Layout ───────────────────────────────────────────────────────────
   align-items: stretch so the rail's tint and its right hairline run the
   full height of the panel however short the list is. */
.np-dms__layout {
    display: grid;
    /* Wide enough that "Initial assessment" fits without an ellipsis. A folder
       name truncated to "Initial assess…" costs the reader the one word that
       tells two similar folders apart, which is the whole job of the rail. */
    grid-template-columns: 292px minmax(0, 1fr);
    align-items: stretch;
}

/* ── Folder rail ────────────────────────────────────────────────────── */
.np-dms__rail {
    background: var(--np-color-brand-tint-50);
    border-right: 1px solid var(--np-color-brand-tint-100);
    padding: var(--np-space-4) var(--np-space-3) var(--np-space-5);
    min-width: 0;
}
.np-dms__rail-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--np-space-2);
    padding: 0 var(--np-space-2) var(--np-space-3);
}
.np-dms__rail-title {
    margin: 0;
    font-size: 0.66rem;
    font-weight: 400;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--np-color-text-muted);
}
.np-dms__rail-count {
    font-size: 0.7rem;
    color: var(--np-color-text-muted);
}

/* The line between the combined view and the folders themselves.
   "All documents" is NOT a folder — it is every folder in scope at once — so
   the rail states where the real folders begin rather than letting the first
   card imply it. A rule to the right of the label, drawn with a flex filler so
   it stretches to the rail edge whatever the label says. */
.np-dms__rail-divider {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    margin: var(--np-space-4) 0 var(--np-space-2);
    padding: 0 var(--np-space-2);
    font-size: 0.66rem;
    font-weight: 400;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--np-color-text-muted);
}
.np-dms__rail-divider::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background: var(--np-color-brand-tint-100);
}

.np-dms__folders {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--np-space-2);
}

.np-dms__folder-row { position: relative; }

/* Each folder is its own card so the active one can lift off the rail rather
   than merely tint. Roomy on purpose: the rail is a short list a clinician
   aims at repeatedly, and the name plus its one line of context is what they
   are aiming for. */
.np-dms__folder {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    padding: var(--np-space-3) var(--np-space-3) var(--np-space-3) 14px;
    border: 1px solid var(--np-color-brand-tint-100);
    border-radius: var(--np-radius-2);
    background: var(--np-color-surface);
    text-decoration: none;
    color: inherit;
    /* Clips the active row's left bar to the rounded edge, so it reads as part
       of the row rather than a tab stuck to the side of it. */
    overflow: hidden;
    transition: background 0.15s ease, border-color 0.15s ease;
}
/* The combined view sits above the divider, outside the folder list. */
.np-dms__folder--all { display: flex; }
.np-dms__folder:hover {
    border-color: var(--np-color-brand-accent);
    text-decoration: none;
}
.np-dms__folder:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}
/* Active state carries THREE signals — background, a left bar, and a
   filled icon tile — so it survives greyscale and colour-blindness
   (WCAG 1.4.1). aria-current carries it to assistive tech. */
.np-dms__folder--active {
    background: var(--np-color-brand-tint-100);
    border-color: var(--np-color-brand-accent);
    box-shadow: var(--np-shadow-1);
}
.np-dms__folder::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: transparent;
}
.np-dms__folder--active::before { background: var(--np-color-brand-primary); }

.np-dms__folder-icon {
    flex: none;
    width: 34px;
    height: 34px;
    border-radius: var(--np-radius-2);
    display: grid;
    place-items: center;
    background: var(--np-color-neutral-100);
    color: var(--np-color-text-muted);
}
.np-dms__folder-icon svg { width: 18px; height: 18px; }
.np-dms__folder--active .np-dms__folder-icon {
    background: var(--np-color-brand-primary);
    color: var(--np-color-text-on-brand);
}

.np-dms__folder-body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}
.np-dms__folder-name {
    font-size: var(--np-fs-small);
    font-weight: 400;
    color: var(--np-color-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.np-dms__folder-sub {
    font-size: 0.7rem;
    color: var(--np-color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.np-dms__folder-count {
    flex: none;
    min-width: 26px;
    text-align: center;
    padding: 2px 7px;
    border-radius: var(--np-radius-1);
    background: var(--np-color-neutral-100);
    font-size: 0.75rem;
    font-weight: 400;
    color: var(--np-color-text-muted);
}
.np-dms__folder--active .np-dms__folder-count {
    background: var(--np-color-brand-primary);
    color: var(--np-color-text-on-brand);
}

/* ── Main pane ─────────────────────────────────────────────────────── */
.np-dms__main {
    min-width: 0;
    padding: var(--np-space-4) var(--np-space-5) var(--np-space-5);
}
.np-dms__main-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--np-space-3);
    padding-bottom: var(--np-space-3);
}
.np-dms__main-headings { min-width: 0; }
.np-dms__main-title {
    margin: 0;
    font-size: 1.2rem;
    font-weight: 400;
    letter-spacing: var(--np-tracking-tight);
    color: var(--np-color-text-primary);
}
.np-dms__main-sub {
    margin: 2px 0 0;
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-muted);
}

/* Folder-level actions, beside the name of the folder they act on. Their
   position IS the disambiguation: at the head of the panel they read as
   "this folder", where the same pair inside a table row would read as
   "this document". */
.np-dms__main-actions {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    flex: none;
}
/* A notch smaller than the toolbar's buttons: these act on the folder being
   shown, and at full weight they out-shouted the folder name they sit beside. */
.np-dms__main-actions .np-btn {
    min-height: 34px;
    padding: 6px var(--np-space-3);
    font-size: var(--np-fs-small);
}
.np-dms__main-actions .np-btn svg { width: 16px; height: 16px; }
/* Destructive, but not shouted: the button is quiet until the pointer is on
   it, and the confirmation dialog (§6.9) is what actually guards the action. */
.np-dms__danger-btn:hover,
.np-dms__danger-btn:focus {
    border-color: var(--np-color-danger-fg);
    color: var(--np-color-danger-fg);
}

/* ── Table ──────────────────────────────────────────────────────────
   The shell owns the outline and the radius; the table draws no box of
   its own. The header band rounds its own top corners rather than being
   clipped by overflow:hidden on the shell — overflow there would also
   clip the row menu on the no-JavaScript path, where the panel has not
   been promoted to position:fixed. */
.np-dms__table-shell {
    border: 1px solid var(--np-color-brand-tint-100);
    border-radius: var(--np-radius-2);
}
.np-dms__table-wrap { overflow-x: auto; }

.np-dms__table {
    width: 100%;
    margin: 0;
    border-collapse: collapse;
    background: var(--np-color-surface);
}
/* Column 1 absorbs every spare pixel; the rest shrink to their content.
   width:1% + nowrap is the auto-layout idiom for that — it beats fixed
   pixel widths because "Uploaded by" is a name of unknown length. */
.np-dms__table th,
.np-dms__table td {
    width: 1%;
    white-space: nowrap;
}
/* max-width:0 alongside width:100% is the table idiom that makes an inner
   text-overflow:ellipsis actually engage: without it the cell grows to its
   content's min-content width and the whole table overflows sideways instead
   of the name truncating. */
.np-dms__table th:first-child,
.np-dms__table td:first-child {
    width: 100%;
    max-width: 0;
    white-space: normal;
}

.np-dms__table thead th {
    padding: var(--np-space-2) var(--np-space-4);
    background: var(--np-color-brand-tint-50);
    border-bottom: 1px solid var(--np-color-brand-tint-100);
    font-size: 0.66rem;
    font-weight: 400;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--np-color-text-muted);
    text-align: left;
    vertical-align: middle;
}
.np-dms__table thead th:first-child { border-top-left-radius: calc(var(--np-radius-2) - 1px); }
.np-dms__table thead th:last-child {
    text-align: right;
    border-top-right-radius: calc(var(--np-radius-2) - 1px);
}
.np-dms__table thead .np-sort__link {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-1);
    color: inherit;
    text-decoration: none;
}
.np-dms__table thead .np-sort__link:hover { color: var(--np-color-brand-primary); }

.np-dms__table > tbody > tr > td {
    padding: var(--np-space-3) var(--np-space-4);
    border-bottom: 1px solid var(--np-color-brand-tint-100);
    vertical-align: middle;
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-primary);
}
/* No rule under the last row — the shell's own border already closes the
   table, and a doubled line there is the commonest table smell there is. */
.np-dms__table tbody tr:last-child td { border-bottom: 0; }
.np-dms__table tbody tr:hover { background: var(--np-color-brand-tint-50); }

/* ── The document cell ───────────────────────────────────────────────
   Name, then one quiet line of provenance. No file-type tile: the type is
   a column now, and a coloured square that restates the word beside it
   earns nothing in a list read by scanning names. The NAME is the row's
   subject and the only thing at full contrast. */
.np-dms__doc {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}
.np-dms__name {
    font-size: 0.845rem;
    font-weight: 400;
    line-height: 1.35;
    color: var(--np-color-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* The name as the way into the document (preview or download). It keeps the
   row's quiet weight at rest — a column of blue underlined names is loud in a
   dense list — and signals the link on hover and focus with the brand colour
   AND an underline, so the cue is never colour alone (WCAG 1.4.1). */
.np-dms__name--link {
    display: block;
    text-decoration: none;
}
.np-dms__name--link:hover {
    color: var(--np-color-brand-primary);
    text-decoration: underline;
}
.np-dms__name--link:focus-visible {
    color: var(--np-color-brand-primary);
    text-decoration: underline;
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
    border-radius: 2px;
}

/* The provenance line: chips first, then muted text. Wraps rather than
   truncating, because a clipped "Prepared by" is worse than a second row. */
.np-dms__subline {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--np-space-2);
    min-width: 0;
}
.np-dms__tag {
    flex: none;
    padding: 2px 7px;
    border-radius: 5px;
    background: var(--np-color-brand-tint-100);
    font-size: 0.625rem;
    font-weight: 400;
    color: var(--np-color-brand-primary);
    white-space: nowrap;
}
/* Live external link. Amber = sent, not yet actioned by whoever received
   it (§6.6). Carries its own word, never colour alone (WCAG 1.4.1). */
.np-dms__tag--shared {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: var(--np-color-warning-bg);
    color: var(--np-color-warning-fg);
}
.np-dms__tag--shared svg { width: 11px; height: 11px; }
.np-dms__meta {
    /* Shrinks but never GROWS: with flex-grow the provenance line would absorb
       the whole cell and shove the chips against the far right edge, reading as
       two unrelated things rather than one line of context. */
    flex: 0 1 auto;
    min-width: 0;
    font-size: 0.7rem;
    line-height: 1.4;
    color: var(--np-color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Size and uploader, folded into the provenance line ONLY at the narrow
   breakpoint where their own columns are hidden. Declared here, BEFORE the
   media queries that switch it on — a bare rule after them would win the
   cascade at every width and the fallback would never appear. */
.np-dms__meta-item--compact { display: none; }
.np-dms__desc {
    font-size: 0.7rem;
    line-height: 1.4;
    color: var(--np-color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* A standalone muted note (share dialogs, the log's loading state). NOT
   .np-dms__meta — that one lives inside a flex row. */
.np-dms__note {
    margin: 0;
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-muted);
}
/* A qualifier sitting under a value in the sharing log — "Single use",
   "opened 3 times". A step below the value it qualifies, never instead
   of it. */
.np-dms__source {
    display: block;
    font-size: 0.7rem;
    color: var(--np-color-text-muted);
}
/* Cells whose value must not wrap mid-value — a timestamp, a size. */
.np-nowrap { white-space: nowrap; }

/* ── Uploader + timestamp columns ─────────────────────────────────── */
/* Date over time, so the column scans by date and the precise instant is
   there when it is wanted. Both are Instants in the VIEWER's zone; only
   the lower line repeats the zone label (§3.5). */
.np-dms__when {
    display: flex;
    flex-direction: column;
    gap: 1px;
    line-height: 1.35;
}
.np-dms__when-time {
    font-size: 0.7rem;
    color: var(--np-color-text-muted);
}

/* ── Row action menu ─────────────────────────────────────────────────
   The trigger is a bordered icon button; the panel is promoted to
   position: fixed by np-dms.js because the table scrolls horizontally and
   an absolutely-positioned panel would be clipped by that overflow. */
.np-rowmenu { position: relative; display: inline-block; }
.np-rowmenu__trigger {
    width: 30px;
    height: 30px;
    display: grid;
    place-items: center;
    padding: 0;
    border: 1px solid var(--np-color-brand-tint-200);
    border-radius: var(--np-radius-1);
    background: var(--np-color-surface);
    color: var(--np-color-text-muted);
    cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.np-rowmenu__trigger svg { width: 16px; height: 16px; }
.np-rowmenu__trigger:hover {
    border-color: var(--np-color-brand-accent);
    color: var(--np-color-brand-primary);
}
.np-rowmenu__trigger:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}
.np-rowmenu__trigger[aria-expanded="true"] {
    border-color: var(--np-color-brand-primary);
    background: var(--np-color-brand-tint-100);
    color: var(--np-color-brand-primary);
}

.np-rowmenu__panel {
    position: absolute;
    right: 0;
    top: calc(100% + 4px);
    z-index: 60;
    width: 224px;
    padding: var(--np-space-1);
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-brand-tint-200);
    border-radius: var(--np-radius-2);
    box-shadow: var(--np-shadow-3);
    display: flex;
    flex-direction: column;
    gap: 1px;
    text-align: left;
}
.np-rowmenu__panel[hidden] { display: none; }
.np-rowmenu__panel--fixed { position: fixed; right: auto; top: auto; }

.np-rowmenu__item {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    width: 100%;
    padding: 7px var(--np-space-2);
    border: 0;
    border-radius: var(--np-radius-1);
    background: transparent;
    font-family: inherit;
    font-size: var(--np-fs-xsmall);
    font-weight: 400;
    line-height: 1.3;
    text-align: left;
    text-decoration: none;
    color: var(--np-color-text-primary);
    cursor: pointer;
}
.np-rowmenu__item svg {
    flex: none;
    width: 15px;
    height: 15px;
    opacity: 0.7;
}
.np-rowmenu__item:hover {
    background: var(--np-color-brand-tint-100);
    color: var(--np-color-text-primary);
    text-decoration: none;
}
.np-rowmenu__item:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: -2px;
}
.np-rowmenu__item--danger { color: var(--np-color-danger-fg); }
.np-rowmenu__item--danger:hover {
    background: var(--np-color-danger-bg);
    color: var(--np-color-danger-fg);
}
.np-rowmenu__sep {
    height: 1px;
    margin: var(--np-space-1) 0;
    background: var(--np-color-brand-tint-100);
}

/* ── Pager ────────────────────────────────────────────────────────── */
.np-dms__main .np-pager { margin-top: var(--np-space-4); }

/* ── Dialog helpers ───────────────────────────────────────────────── */
.np-dialog--wide { max-width: 660px; width: calc(100vw - 2rem); }

/* A dialog's title block: heading plus one line saying what this dialog is
   about — the document, the case, the recipient. Separated from the body so the
   heading is never read as part of the first field. */
.np-dialog__head { margin-bottom: var(--np-space-4); }
.np-dialog__head .np-confirm__title { margin-bottom: var(--np-space-1); }
.np-dialog__lead {
    margin: 0;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
}

/* The action row, pushed clear of whatever precedes it and separated by a rule.
   A Submit button sitting hard against the bottom edge of a textarea is the
   commonest way a form gets clicked before it is finished — the gap is the
   affordance, not decoration. */
.np-confirm__actions.np-dialog__actions {
    margin-top: var(--np-space-5);
    padding-top: var(--np-space-4);
    border-top: 1px solid var(--np-color-brand-tint-100);
}

/* What a secure share actually does, stated before the clinician is asked
   anything. Three facts with room to breathe — a clinician who reads only this
   block still knows what the link they are about to create will do. */
.np-sharefacts {
    list-style: none;
    margin: 0 0 var(--np-space-5);
    padding: var(--np-space-4);
    border-radius: var(--np-radius-2);
    background: var(--np-color-brand-tint-50);
    display: flex;
    flex-direction: column;
    gap: var(--np-space-3);
}
.np-sharefacts__item {
    display: flex;
    align-items: flex-start;
    gap: var(--np-space-3);
    font-size: var(--np-fs-small);
    line-height: 1.5;
    color: var(--np-color-text-primary);
}
.np-sharefacts__icon {
    flex: none;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: var(--np-radius-1);
    background: var(--np-color-surface);
    color: var(--np-color-brand-primary);
}
.np-sharefacts__icon svg { width: 16px; height: 16px; }

/* ── The document details card ────────────────────────────────────
   A record, so it reads as one: grouped definition lists, label left, value
   right, nothing competing with the values. The groups are the point — "who
   wrote it" and "who filed it" are different questions and a flat list of
   fifteen rows invites them to be read as one. */
.np-docdetails {
    display: flex;
    flex-direction: column;
    gap: var(--np-space-5);
}
.np-docdetails__group-title {
    margin: 0 0 var(--np-space-2);
    font-size: 0.66rem;
    font-weight: 400;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--np-color-text-muted);
}
.np-docdetails__list {
    margin: 0;
    border-top: 1px solid var(--np-color-brand-tint-100);
}
.np-docdetails__row {
    display: grid;
    grid-template-columns: minmax(140px, 34%) minmax(0, 1fr);
    gap: var(--np-space-3);
    padding: var(--np-space-2) 0;
    border-bottom: 1px solid var(--np-color-brand-tint-100);
}
.np-docdetails__row dt {
    margin: 0;
    font-size: var(--np-fs-xsmall);
    font-weight: 400;
    color: var(--np-color-text-muted);
}
.np-docdetails__row dd {
    margin: 0;
    min-width: 0;
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-primary);
    overflow-wrap: anywhere;
}
.np-docdetails__value--lead { font-weight: 400; }
/* The original filename — long, hyphenated and not prose, so it breaks
   anywhere rather than forcing the column wide. */
.np-docdetails__value--file {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    word-break: break-all;
}
/* "Not recorded" is a fact, and a different fact from a blank — muted and
   italic so it is legible as an absence rather than mistaken for a value. */
.np-docdetails__absent {
    color: var(--np-color-text-muted);
    font-style: italic;
}
.np-docdetails__note {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--np-space-2);
    margin: 0;
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-primary);
}

/* On a phone the label/value split stops earning its width — stack it. */
@media (max-width: 560px) {
    .np-docdetails__row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
}

/* The ONE place the physical filename is shown — the edit dialog, where
   the file itself is the subject. Everywhere else DisplayName wins. */
.np-dms__original {
    margin: 0;
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-muted);
    word-break: break-all;
}
.np-dms__original-label {
    display: block;
    font-weight: 400;
    color: var(--np-color-text-primary);
}

.np-dms__copyrow {
    display: flex;
    gap: var(--np-space-2);
    align-items: center;
}
.np-dms__copyfield {
    flex: 1 1 auto;
    min-width: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--np-fs-xsmall);
}

.np-sharelog__empty {
    margin: 0;
    padding: var(--np-space-4) 0;
    text-align: center;
    color: var(--np-color-text-muted);
}

/* A quiet table for the sharing log inside a dialog — same restraint as
   the document table, no shell. */
.np-table--quiet { width: 100%; border-collapse: collapse; }
.np-table--quiet thead th {
    padding: var(--np-space-2);
    text-align: left;
    font-size: 0.66rem;
    font-weight: 400;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--np-color-text-muted);
    border-bottom: 1px solid var(--np-color-border);
    white-space: nowrap;
}
.np-table--quiet > tbody > tr > td {
    padding: var(--np-space-2);
    vertical-align: middle;
    font-size: var(--np-fs-xsmall);
    border-bottom: 1px solid var(--np-color-brand-tint-100);
}
.np-table--quiet tbody tr:last-child td { border-bottom: 0; }

/* A row action that posts (Delete, Revoke) needs its own form; keep it
   inline and strip the standalone-form top margin so it sits level with
   its siblings. */
.np-dms__table .np-inline-form,
.np-dms__main-head .np-inline-form,
.np-table--quiet .np-inline-form {
    display: inline;
    margin-top: 0;
}
/* Inside the menu the form is a full-width row, not an inline fragment —
   its button is a menu item and must fill the panel like its siblings. */
.np-rowmenu__panel .np-inline-form {
    display: block;
    margin-top: 0;
}

/* ── Responsive ──────────────────────────────────────────────────────
   Three steps. The rail narrows, then it stops being a rail, then the
   columns that the row's own subline can carry drop out of the table
   (and the subline picks them up via np-dms__meta-item--compact). */
@media (max-width: 1180px) {
    .np-dms__layout { grid-template-columns: 244px minmax(0, 1fr); }
}

@media (max-width: 900px) {
    /* The rail becomes a horizontal strip above the table. A 216px rail
       beside five columns is unusable on a tablet, and stacking beats
       squeezing. */
    .np-dms__layout { grid-template-columns: minmax(0, 1fr); }
    .np-dms__rail {
        border-right: 0;
        border-bottom: 1px solid var(--np-color-brand-tint-100);
        padding: var(--np-space-3) var(--np-space-4);
    }
    .np-dms__folders {
        flex-direction: row;
        flex-wrap: wrap;
    }
    .np-dms__folder { min-width: 180px; flex: 1 1 180px; }
    .np-dms__main { padding: var(--np-space-4); }
}

@media (max-width: 720px) {
    .np-dms__toolbar { padding: var(--np-space-3); }
    .np-dms__case,
    .np-dms__search,
    .np-dms__toolbar-actions { flex: 1 1 100%; }
    .np-dms__toolbar-actions .np-btn { flex: 1 1 auto; justify-content: center; }
    /* Size and Uploaded-by leave the table; the row's own subline has the
       room for them and repeats nothing. */
    .np-dms__table th:nth-child(2),
    .np-dms__table td:nth-child(2),
    .np-dms__table th:nth-child(3),
    .np-dms__table td:nth-child(3) { display: none; }
    .np-dms__meta-item--compact { display: inline; }
    .np-dms__folder { min-width: 0; flex: 1 1 100%; }
}

/* ───────────────────────────────────────────────────────────────────────
   In-house calendar + date/time picker (Appointments module).
   Built with the design tokens above — no external library.
   ─────────────────────────────────────────────────────────────────────── */

.np-calendar__toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-3);
    margin-bottom: var(--np-space-4);
}
.np-calendar__nav { display: inline-flex; gap: var(--np-space-2); }
.np-calendar__title {
    flex: 1 1 auto;
    margin: 0;
    text-align: center;
    font-size: var(--np-fs-h3);
    color: var(--np-color-text-primary);
}
/* The date in view, as a light badge with a calendar glyph — the heading is
   the reader's "where am I" on a screen whose grid looks alike on every day. */
.np-calendar__title-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    padding: 6px 16px;
    border: 1px solid var(--np-color-border-strong);
    border-radius: 999px;
    background: var(--np-color-surface-tinted);
    color: var(--np-color-brand-primary);
}
.np-calendar__title-badge svg { flex: 0 0 auto; }
.np-calendar__views {
    display: inline-flex;
    border: 1.5px solid var(--np-color-border-strong);
    border-radius: var(--np-radius-2);
    overflow: hidden;
}
.np-calendar__view-btn {
    border: 0;
    background: var(--np-color-surface);
    color: var(--np-color-text-secondary);
    padding: var(--np-space-2) var(--np-space-4);
    font: inherit;
    cursor: pointer;
}
.np-calendar__view-btn + .np-calendar__view-btn { border-left: 1.5px solid var(--np-color-border); }
.np-calendar__view-btn.is-active {
    background: var(--np-color-brand-primary);
    color: var(--np-color-text-on-brand);
}
.np-calendar__legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-4);
    margin-bottom: var(--np-space-3);
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
}
.np-calendar__legend-item { display: inline-flex; align-items: center; gap: var(--np-space-2); }
.np-calendar__dot { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.np-calendar__dot--pending   { background: var(--np-color-info-fg); }
.np-calendar__dot--active    { background: var(--np-color-success-fg); }
.np-calendar__dot--completed { background: #0F766E; }
.np-calendar__dot--arrived   { background: #6D28D9; }
.np-calendar__dot--warning  { background: var(--np-color-warning-fg); }
.np-calendar__dot--inactive { background: var(--np-color-text-muted); }
.np-calendar__dot--danger   { background: var(--np-color-error-fg); }
.np-calendar__status { margin: var(--np-space-3) 0 0; font-size: var(--np-fs-small); color: var(--np-color-text-muted); }
.np-calendar__filterbar {
    display: flex;
    align-items: center;
    gap: var(--np-space-3);
    flex-wrap: wrap;
    margin-bottom: var(--np-space-4);
}
.np-calendar__filterbar .np-select { max-width: 22rem; }

/* Month grid */
.np-cal-month__head,
.np-cal-month__grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 1px;
}
.np-cal-month__weekday {
    padding: var(--np-space-2);
    text-align: center;
    font-size: var(--np-fs-small);
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--np-color-brand-primary);
}
.np-cal-month__grid {
    background: var(--np-color-border);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    overflow: hidden;
}
.np-cal-month__cell {
    background: var(--np-color-surface);
    min-height: 132px;
    padding: var(--np-space-2);
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.np-cal-month__cell.is-outside { background: var(--np-color-surface-muted); }
.np-cal-month__cell.is-today { background: var(--np-color-brand-tint-50); }
.np-cal-month__cellhead { display: flex; align-items: center; justify-content: space-between; gap: 4px; margin-bottom: 2px; }
.np-cal-month__pending {
    font-size: 0.58rem;
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--np-color-warning-fg);
    background: var(--np-color-warning-bg);
    padding: 1px 6px;
    border-radius: var(--np-radius-pill);
    white-space: nowrap;
}
.np-cal-month__daynum.is-today {
    background: var(--np-color-brand-primary);
    color: var(--np-color-text-on-brand);
    min-width: 26px;
    height: 26px;
    border-radius: 999px;
    padding: 0 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.np-cal-month__daynum.is-today:hover { background: var(--np-color-brand-primary-hover); }
.np-cal-event--struck { opacity: 0.75; }
.np-cal-event--struck .np-cal-event__title { text-decoration: line-through; }
.np-cal-month__daynum {
    align-self: flex-start;
    border: 0;
    background: transparent;
    font: inherit;
    font-weight: 400;
    color: var(--np-color-text-secondary);
    cursor: pointer;
    border-radius: var(--np-radius-1);
    padding: 0 4px;
}
.np-cal-month__daynum:hover { background: var(--np-color-brand-tint-100); }
.np-cal-month__more {
    border: 0;
    background: transparent;
    font: inherit;
    font-size: var(--np-fs-small);
    color: var(--np-color-brand-primary);
    cursor: pointer;
    text-align: left;
    padding: 0 4px;
}

/* Event chip (shared by month + time-grid) */
.np-cal-event {
    display: block;
    font-size: var(--np-fs-small);
    line-height: 1.25;
    padding: 2px 6px;
    border-radius: var(--np-radius-1);
    border-left: 3px solid var(--np-color-text-muted);
    background: var(--np-color-surface-tinted);
    color: var(--np-color-text-primary);
    text-decoration: none;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}
.np-cal-event:hover { filter: brightness(0.97); }
.np-cal-event__time { font-weight: 400; }
.np-cal-event__line1 { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.np-cal-event__title { font-weight: 400; }
.np-cal-event__meta {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 0.72rem;
    opacity: 0.8;
}
.np-cal-event--pending   { background: var(--np-color-info-bg);    border-left-color: var(--np-color-info-fg); }
.np-cal-event--active    { background: var(--np-color-success-bg); border-left-color: var(--np-color-success-fg); }
.np-cal-event--completed { background: #F0FDFA;                    border-left-color: #0F766E; }
.np-cal-event--arrived   { background: #F3EDFC;                    border-left-color: #6D28D9; }
.np-cal-event--warning  { background: var(--np-color-warning-bg); border-left-color: var(--np-color-warning-fg); }
.np-cal-event--inactive { background: var(--np-color-neutral-100); border-left-color: var(--np-color-text-muted); }
.np-cal-event--danger   { background: var(--np-color-error-bg);   border-left-color: var(--np-color-error-fg); }

/* Week / day time-grid */
.np-cal-grid { overflow-x: auto; }              /* Week scrolls horizontally on narrow screens */
.np-cal-grid__header {
    display: grid;
    grid-template-columns: 56px 1fr;
    border-bottom: 1px solid var(--np-color-border);
}
/* Wider day columns in Week so patient / type / venue stay readable. */
.np-cal-grid--week .np-cal-grid__header { grid-template-columns: 56px repeat(7, minmax(130px, 1fr)); }
.np-cal-grid__dayhead {
    padding: var(--np-space-2);
    text-align: center;
    font-size: var(--np-fs-small);
    border-left: 1px solid var(--np-color-border);
}
.np-cal-grid__dayhead.is-today { background: var(--np-color-brand-tint-100); }
.np-cal-grid__dayname { color: var(--np-color-text-muted); text-transform: uppercase; letter-spacing: 0.04em; font-size: 0.72rem; }
.np-cal-grid__daynum { font-weight: 400; color: var(--np-color-text-primary); }
.np-cal-grid__body {
    display: grid;
    grid-template-columns: 56px 1fr;
    position: relative;
}
.np-cal-grid--week .np-cal-grid__body { grid-template-columns: 56px repeat(7, minmax(130px, 1fr)); }
.np-cal-grid__gutter { display: flex; flex-direction: column; }
.np-cal-grid__hour {
    height: 84px;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
    text-align: right;
    padding-right: var(--np-space-2);
    transform: translateY(-8px);
}
.np-cal-grid__col {
    position: relative;
    border-left: 1px solid var(--np-color-border);
}
.np-cal-grid__line {
    position: absolute;
    left: 0;
    right: 0;
    border-top: 1px solid var(--np-color-border);
}
.np-cal-grid__event {
    position: absolute;
    box-sizing: border-box;
    z-index: 1;
    white-space: normal;
}

/* Compact Week strip — one line: time + patient name. Reuses the .np-cal-block--*
   tone rules for the coloured bar + background. */
.np-cal-strip {
    display: block;
    text-decoration: none;
    color: var(--np-color-text-primary);
    border-left: 3px solid var(--np-color-text-muted);
    border-radius: var(--np-radius-1);
    padding: 2px 6px;
    font-size: 0.72rem;
    line-height: 1.35;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    box-shadow: var(--np-shadow-1);
}
.np-cal-strip:hover { filter: brightness(0.98); }
.np-cal-strip__time { font-weight: 400; color: var(--np-color-text-muted); }
.np-cal-strip__title { font-weight: 400; }

/* Week as a 7-column day list — appointments stacked one after another. */
.np-cal-weeklist {
    display: grid;
    grid-template-columns: repeat(7, minmax(165px, 1fr));
    gap: 1px;
    background: var(--np-color-border);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    overflow-x: auto;
}
.np-cal-weeklist__col {
    background: var(--np-color-surface);
    padding: var(--np-space-2);
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-height: 260px;
}
.np-cal-weeklist__col.is-today { background: var(--np-color-brand-tint-50); }
.np-cal-weeklist__head {
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding-bottom: var(--np-space-2);
    margin-bottom: 2px;
    border-bottom: 1px solid var(--np-color-border);
    cursor: pointer;
}
.np-cal-weeklist__dayname {
    font-size: 0.72rem;
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--np-color-brand-primary);
}
.np-cal-weeklist__daynum { font-weight: 400; color: var(--np-color-text-primary); }
.np-cal-weeklist__empty { color: var(--np-color-text-muted); font-size: var(--np-fs-small); padding: var(--np-space-2) 0; }

/* Rich Day appointment block — patient name, time, TYPE pill, venue. Diary
   reference styling: coloured left bar, soft card, hover lift. */
.np-cal-block {
    display: block;
    text-decoration: none;
    color: var(--np-color-text-primary);
    border-left: 4px solid var(--np-color-text-muted);
    border-radius: var(--np-radius-1);
    padding: 6px 10px;
    overflow: hidden;
    background: var(--np-color-surface-tinted);
    box-shadow: var(--np-shadow-1);
    transition: transform .15s, box-shadow .15s;
}
.np-cal-block:hover { transform: translateX(2px); box-shadow: var(--np-shadow-2); }
.np-cal-block__title {
    display: block;
    font-weight: 400;
    font-size: var(--np-fs-small);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.np-cal-block__row { display: flex; align-items: center; gap: 6px; margin-top: 2px; min-width: 0; }
.np-cal-block__time { flex: 0 0 auto; font-size: 0.72rem; font-weight: 400; color: var(--np-color-text-muted); }
.np-cal-block__badge {
    flex: 0 0 auto;
    max-width: 55%;
    font-size: 0.6rem;
    font-weight: 400;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    padding: 1px 7px;
    border-radius: var(--np-radius-pill);
    background: rgba(255, 255, 255, 0.75);
    color: var(--np-color-text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.np-cal-block__desc {
    min-width: 0;
    font-size: 0.72rem;
    color: var(--np-color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.np-cal-block--struck { opacity: 0.8; }
.np-cal-block--struck .np-cal-block__title { text-decoration: line-through; }
/* Solid tone cards — crisp side-by-side blocks when appointments overlap
   (no right-fade that makes them look like they bleed together). */
.np-cal-block--pending   { border-left-color: var(--np-color-info-fg);    background: var(--np-color-info-bg); }
.np-cal-block--active    { border-left-color: var(--np-color-success-fg); background: var(--np-color-success-bg); }
.np-cal-block--completed { border-left-color: #0F766E;                    background: #F0FDFA; }
.np-cal-block--warning  { border-left-color: var(--np-color-warning-fg); background: var(--np-color-warning-bg); }
.np-cal-block--inactive { border-left-color: var(--np-color-text-muted); background: var(--np-color-neutral-100); }
.np-cal-block--danger   { border-left-color: var(--np-color-error-fg);   background: var(--np-color-error-bg); }
.np-cal-block--neutral  { border-left-color: var(--np-color-text-muted); }

/* Date / time picker popup */
.np-picker-pop {
    position: absolute;
    z-index: 50;
    margin-top: 4px;
    background: var(--np-color-surface);
    border: 1.5px solid var(--np-color-border-strong);
    border-radius: var(--np-radius-2);
    box-shadow: var(--np-shadow-2);
    padding: var(--np-space-3);
    width: 280px;
}
.np-picker__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--np-space-2);
}
.np-picker__label { font-weight: 400; color: var(--np-color-text-primary); }
.np-picker__nav {
    border: 1px solid var(--np-color-border);
    background: var(--np-color-surface);
    border-radius: var(--np-radius-1);
    width: 32px;
    height: 32px;
    cursor: pointer;
    font-size: 1.1rem;
    line-height: 1;
}
.np-picker__nav:hover { background: var(--np-color-brand-tint-100); }
.np-picker__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.np-picker__wd { text-align: center; font-size: 0.75rem; color: var(--np-color-text-muted); padding: 2px 0; }
.np-picker__day {
    border: 0;
    background: transparent;
    font: inherit;
    padding: 6px 0;
    border-radius: var(--np-radius-1);
    cursor: pointer;
    color: var(--np-color-text-primary);
}
.np-picker__day:hover { background: var(--np-color-brand-tint-100); }
.np-picker__day.is-outside { color: var(--np-color-text-muted); }
.np-picker__day.is-today { box-shadow: inset 0 0 0 1.5px var(--np-color-brand-accent); }
.np-picker__day.is-selected { background: var(--np-color-brand-primary); color: var(--np-color-text-on-brand); }
.np-picker-pop--time { width: 140px; }
.np-picker__times { max-height: 240px; overflow-y: auto; display: flex; flex-direction: column; }
.np-picker__time {
    border: 0;
    background: transparent;
    font: inherit;
    padding: 6px var(--np-space-2);
    text-align: left;
    cursor: pointer;
    border-radius: var(--np-radius-1);
    color: var(--np-color-text-primary);
}
.np-picker__time:hover { background: var(--np-color-brand-tint-100); }
.np-picker__time.is-selected { background: var(--np-color-brand-primary); color: var(--np-color-text-on-brand); }

/* Calendar "Needs attention" summary cards (for the visible period) */
.np-cal-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--np-space-4);
    margin-bottom: var(--np-space-5);
}
.np-cal-stat {
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    padding: var(--np-space-4);
    box-shadow: var(--np-shadow-1);
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.np-cal-stat--alert {
    border-top: 3px solid var(--np-color-warning-fg);
    background: var(--np-color-warning-bg);
}
.np-cal-stat__label { font-size: var(--np-fs-small); font-weight: 400; color: var(--np-color-text-secondary); }
.np-cal-stat__value { font-size: 2rem; font-weight: 400; line-height: 1.1; color: var(--np-color-text-primary); }
.np-cal-stat__sub { font-size: var(--np-fs-small); color: var(--np-color-text-muted); }
.np-cal-stat__chip {
    align-self: flex-start;
    margin-top: var(--np-space-1);
    font-size: 0.75rem;
    padding: 2px 10px;
    border-radius: var(--np-radius-pill);
    background: var(--np-color-brand-tint-100);
    color: var(--np-color-brand-secondary);
}
.np-cal-stat--alert .np-cal-stat__chip { background: rgba(255, 255, 255, 0.6); color: var(--np-color-warning-fg); }

/* Calendar follow-up list */
.np-cal-followup { margin-top: var(--np-space-5); }
.np-cal-followup__head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--np-space-2);
    margin-bottom: var(--np-space-3);
}
.np-cal-followup__title { font-size: var(--np-fs-h3); margin: 0; color: var(--np-color-text-primary); }
.np-cal-followup__hint { font-size: var(--np-fs-small); color: var(--np-color-text-muted); }
.np-cal-followup__empty {
    color: var(--np-color-text-muted);
    padding: var(--np-space-4);
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
}
.np-cal-followup__list {
    display: flex;
    flex-direction: column;
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    overflow: hidden;
}
.np-cal-followup__row {
    display: flex;
    align-items: center;
    gap: var(--np-space-4);
    padding: var(--np-space-3) var(--np-space-4);
    text-decoration: none;
    color: inherit;
    border-bottom: 1px solid var(--np-color-border);
}
.np-cal-followup__row:last-child { border-bottom: 0; }
.np-cal-followup__row:hover { background: var(--np-color-brand-tint-50); }
.np-cal-followup__badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 64px;
    padding: 6px 10px;
    flex: 0 0 auto;
    border-radius: var(--np-radius-2);
    background: var(--np-color-brand-tint-100);
    color: var(--np-color-brand-secondary);
    font-size: var(--np-fs-small);
    font-weight: 400;
    text-align: center;
    white-space: nowrap;
}
.np-cal-followup__main { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.np-cal-followup__name { font-weight: 400; color: var(--np-color-text-primary); }
.np-cal-followup__meta { font-size: var(--np-fs-small); color: var(--np-color-text-muted); }
.np-cal-followup__pill { flex: 0 0 auto; display: inline-block; }
.np-cal-followup__chev { flex: 0 0 auto; color: var(--np-color-text-muted); font-size: 1.5rem; line-height: 1; }

/* ═══════════════════════════════════════════════════════════════════════
   My Calendar — dedicated clinician workspace (np-mycalendar.js)
   Reuses the calendar status tones; adds the read-only clinician label,
   filter bar, working-hours frame, and rich appointment blocks.
   ═══════════════════════════════════════════════════════════════════════ */
.np-mycal-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--np-space-3);
    margin-bottom: var(--np-space-2);
}
.np-mycal-head h1 { margin: 0; }
.np-mycal-clinician {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    padding: var(--np-space-1) var(--np-space-3);
    border: 1px solid var(--np-color-brand-tint-200);
    background: var(--np-color-brand-tint-50);
    color: var(--np-color-brand-primary);
    border-radius: var(--np-radius-pill);
    font-size: var(--np-fs-small);
}
.np-mycal-clinician svg { flex: 0 0 auto; }

.np-mycal__toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--np-space-3);
    justify-content: space-between;
    margin-bottom: var(--np-space-3);
}
.np-mycal__datepicker { width: auto; }

.np-mycal__filterbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--np-space-3);
    padding: var(--np-space-3);
    background: var(--np-color-brand-tint-50);
    border: 1px solid var(--np-color-brand-tint-100);
    border-radius: var(--np-radius-2);
    margin-bottom: var(--np-space-3);
}
.np-mycal__filter { display: flex; flex-direction: column; gap: 4px; min-width: 150px; }
.np-mycal__filter .np-label { margin: 0; }
.np-mycal__filter--grow { flex: 1 1 220px; }
.np-mycal__hours { display: none; font-size: var(--np-fs-small); margin: 0 0 var(--np-space-3); }
.np-mycal:hover .np-mycal__hours,
.np-mycal:focus-within .np-mycal__hours { display: block; }
.np-mycal__hours .text-muted { margin-left: var(--np-space-2); }
.np-mycal__foot { font-size: var(--np-fs-small); margin: var(--np-space-3) 0 0; }

/* Status pill (icon + text) — colour is never the only signal. */
.np-mycal-status {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.75rem;
    font-weight: 400;
}
.np-mycal-status svg { flex: 0 0 auto; }
.np-mycal-status--pending   { color: var(--np-color-info-fg); }
.np-mycal-status--active    { color: var(--np-color-success-fg); }
.np-mycal-status--completed { color: #0F766E; }
.np-mycal-status--arrived   { color: #6D28D9; }
.np-mycal-status--warning   { color: var(--np-color-warning-fg); }
.np-mycal-status--inactive  { color: var(--np-color-text-muted); }
.np-mycal-status--danger    { color: var(--np-color-error-fg); }
.np-mycal-status--neutral   { color: var(--np-color-text-muted); }

/* Time-grid (Day / Week) — mirrors the org calendar grid, framed to working hours. */
.np-mycal-grid { overflow-x: auto; }
.np-mycal-grid__header,
.np-mycal-grid__body { display: grid; }
.np-mycal-grid__header { position: sticky; top: 0; z-index: 2; background: var(--np-color-surface, #fff); }
/* Top padding so the first hour label (nudged up 0.6em to sit on its line) clears
   the sticky header instead of being clipped beneath it. Gutter + columns are both
   grid items of the body, so they shift down together and stay aligned. */
.np-mycal-grid__body { padding-top: 0.9em; }
.np-mycal-grid__gutter-head { border-bottom: 1px solid var(--np-color-border, #e5e5e5); }
.np-mycal-grid__dayhead {
    padding: 10px var(--np-space-2);
    text-align: center;
    border-bottom: 1px solid var(--np-color-border, #e5e5e5);
    border-left: 1px solid var(--np-color-border, #e5e5e5);
    font-size: var(--np-fs-small);
}
/* Today's column header — tinted band + brand colour, matching the mockup. */
.np-mycal-grid__dayhead.is-today {
    color: var(--np-color-brand-primary);
    font-weight: 400;
    background: var(--np-color-brand-tint-50);
}
.np-mycal-grid__dayhead-label { font-weight: 400; font-size: 0.9rem; color: var(--np-color-text-primary); }
.np-mycal-grid__dayhead.is-today .np-mycal-grid__dayhead-label { color: var(--np-color-brand-primary); }
/* Legacy stacked day/number spans (kept for any other caller). */
.np-mycal-grid__dayname { display: block; text-transform: uppercase; color: var(--np-color-text-muted); }
.np-mycal-grid__daynum { font-size: 1.1rem; font-weight: 400; }
.np-mycal-grid__gutter { position: relative; }
.np-mycal-grid__hour {
    height: 84px;
    font-size: 0.75rem;
    color: var(--np-color-text-muted);
    text-align: right;
    padding-right: var(--np-space-2);
    transform: translateY(-0.6em);
}
.np-mycal-grid__col { position: relative; border-left: 1px solid var(--np-color-border, #e5e5e5); }
.np-mycal-grid__line { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--np-color-border, #eee); }
.np-mycal-grid__empty {
    position: absolute; top: 8px; left: 0; right: 0;
    text-align: center; font-size: var(--np-fs-small); color: var(--np-color-text-muted);
}

/* Org Day view — stacked column lists (one wide column per clinician, cards
   listed by time; no time-axis). */
.np-mycal-collist { display: flex; gap: var(--np-space-4); align-items: flex-start; overflow-x: auto; padding-top: var(--np-space-2); }
.np-mycal-collist__col { flex: 1 1 0; min-width: 320px; }
.np-mycal-collist__col:not(:first-child) { border-left: 1px solid var(--np-color-border, #eee); padding-left: var(--np-space-4); }
.np-mycal-collist__head {
    text-align: center;
    padding: var(--np-space-2) var(--np-space-2) var(--np-space-3);
    border-bottom: 2px solid var(--np-color-border, #e5e5e5);
    margin-bottom: var(--np-space-3);
}
.np-mycal-collist__body { display: flex; flex-direction: column; gap: var(--np-space-3); }
.np-mycal-collist__item { position: relative; }
.np-mycal-collist__item .np-mycal-block { padding: 10px 34px 10px 12px; gap: 3px; }
.np-mycal-collist__item .np-mycal-kebab { top: 8px; right: 8px; background: transparent; }
.np-mycal-collist__none { color: var(--np-color-text-muted); font-size: 0.8125rem; padding: var(--np-space-2); }
.np-mycal-collist__empty { color: var(--np-color-text-muted); padding: var(--np-space-4); text-align: center; width: 100%; }

/* Org Week view — clinician rows × day columns. */
.np-mycal-week {
    display: grid;
    grid-template-columns: 150px repeat(7, minmax(150px, 1fr));
    overflow-x: auto;
    border-top: 1px solid var(--np-color-border, #e5e5e5);
    border-left: 1px solid var(--np-color-border, #e5e5e5);
}
.np-mycal-week__corner {
    position: sticky; left: 0; z-index: 3;
    background: #fff;
    border-right: 1px solid var(--np-color-border, #e5e5e5);
    border-bottom: 1px solid var(--np-color-border, #e5e5e5);
}
.np-mycal-week__dayhead {
    position: sticky; top: 0; z-index: 1;
    padding: 8px 6px;
    text-align: center;
    background: #fff;
    border-right: 1px solid var(--np-color-border, #e5e5e5);
    border-bottom: 1px solid var(--np-color-border, #e5e5e5);
}
.np-mycal-week__dayname { text-transform: uppercase; color: var(--np-color-text-muted); font-size: 0.75rem; }
.np-mycal-week__daynum { font-weight: 400; font-size: 0.8125rem; }
.np-mycal-week__dayhead.is-today { color: var(--np-color-brand-primary); }
.np-mycal-week__rowlabel {
    position: sticky; left: 0; z-index: 1;
    padding: 10px 8px;
    background: #fff;
    border-right: 1px solid var(--np-color-border, #e5e5e5);
    border-bottom: 1px solid var(--np-color-border, #e5e5e5);
}
.np-mycal-week__cell {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-height: 92px;
    padding: 6px;
    border-right: 1px solid var(--np-color-border, #e5e5e5);
    border-bottom: 1px solid var(--np-color-border, #e5e5e5);
}
.np-mycal-week__cell.is-today { background: var(--np-color-brand-tint-50); }
.np-mycal-week__item { position: relative; }
.np-mycal-week__item .np-mycal-block { padding: 6px 26px 6px 10px; gap: 1px; }
.np-mycal-week__none {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 4px; height: 100%; min-height: 72px;
    color: var(--np-color-text-muted); font-size: 0.75rem; text-align: center;
}
.np-mycal-week-card .np-mycal-block__type { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }

/* Rich appointment block (a normal card by default; the time-grid wrapper
   positions it absolutely — see .np-mycal-grid__event .np-mycal-block). */
.np-mycal-block {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 1px;
    overflow: hidden;
    padding: 4px 8px;
    text-decoration: none;
    color: var(--np-color-text-primary);
    /* Pin the application sans font so the card text always matches the rest of the
       app (never inherits a browser default in any embedding context). */
    font-family: var(--np-font-sans);
    border-left: 4px solid var(--np-color-text-muted);
    border-radius: var(--np-radius-1);
    box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
.np-mycal-block:hover { filter: brightness(0.98); box-shadow: 0 2px 6px rgba(0,0,0,0.14); }
/* Compact block type scale matching the Day-calendar mockup: muted 12px time,
   13px semibold type + bold patient. */
/* Time + status share the top row. */
.np-mycal-block__head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.np-mycal-block__status-slot { flex: 0 1 auto; min-width: 0; overflow: hidden; }
.np-mycal-block__status-slot .np-mycal-status { min-width: 0; }
.np-mycal-block__status-slot .np-mycal-status__text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.np-mycal-block__time { font-size: 0.75rem; font-weight: 400; color: var(--np-color-text-muted); flex: 0 0 auto; }
.np-mycal-block__type { font-size: 0.8125rem; font-weight: 400; }
.np-mycal-block__patient { font-size: 0.8125rem; font-weight: 400; }
.np-mycal-block--struck { opacity: 0.8; }
.np-mycal-block--struck .np-mycal-block__patient { text-decoration: line-through; }
/* A cancelled appointment whose slot was NOT re-booked: shown as a bookable free
   slot (dashed, success-tinted) with a "book this slot" hover message. */
.np-mycal-block--free {
    border-left-style: dashed;
    border-left-color: var(--np-color-success-fg);
    background: var(--np-color-success-bg);
}
.np-mycal-block--free .np-mycal-block__patient { text-decoration: none; }
.np-mycal-block--pending   { border-left-color: var(--np-color-info-fg);    background: var(--np-color-info-bg); }
.np-mycal-block--active    { border-left-color: var(--np-color-success-fg); background: var(--np-color-success-bg); }
.np-mycal-block--completed { border-left-color: #0F766E;                    background: #F0FDFA; }
.np-mycal-block--arrived   { border-left-color: #6D28D9;                    background: #F3EDFC; }
.np-mycal-block--warning   { border-left-color: var(--np-color-warning-fg); background: var(--np-color-warning-bg); }
.np-mycal-block--inactive  { border-left-color: var(--np-color-text-muted); background: var(--np-color-neutral-100); }
.np-mycal-block--danger    { border-left-color: var(--np-color-error-fg);   background: var(--np-color-error-bg); }
.np-mycal-block--neutral   { border-left-color: var(--np-color-text-muted); }

/* Month chip */
.np-mycal-chip {
    display: block;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    text-decoration: none;
    color: var(--np-color-text-primary);
    font-family: var(--np-font-sans);
    font-size: 0.75rem;
    padding: 1px 6px;
    margin-top: 2px;
    border-left: 3px solid var(--np-color-text-muted);
    border-radius: var(--np-radius-1);
}
.np-mycal-chip:hover { filter: brightness(0.97); }
.np-mycal-chip__time { font-weight: 400; color: var(--np-color-text-muted); }
.np-mycal-chip--pending   { border-left-color: var(--np-color-info-fg);    background: var(--np-color-info-bg); }
.np-mycal-chip--active    { border-left-color: var(--np-color-success-fg); background: var(--np-color-success-bg); }
.np-mycal-chip--completed { border-left-color: #0F766E;                    background: #F0FDFA; }
.np-mycal-chip--arrived   { border-left-color: #6D28D9;                    background: #F3EDFC; }
.np-mycal-chip--warning   { border-left-color: var(--np-color-warning-fg); background: var(--np-color-warning-bg); }
.np-mycal-chip--inactive  { border-left-color: var(--np-color-text-muted); background: var(--np-color-neutral-100); }
.np-mycal-chip--danger    { border-left-color: var(--np-color-error-fg);   background: var(--np-color-error-bg); }
.np-mycal-chip--neutral   { border-left-color: var(--np-color-text-muted); }

/* Month view — per-day status-grouped summary lines (dot + coloured count). */
.np-mycal-monthsum {
    display: flex; align-items: center; gap: 6px;
    width: 100%; text-align: left;
    border: 0; background: none; cursor: pointer;
    padding: 1px 2px; margin-top: 3px;
    font-size: 0.75rem; font-weight: 400;
    color: var(--np-color-text-primary);
}
.np-mycal-monthsum:hover .np-mycal-monthsum__text { text-decoration: underline; }
.np-mycal-monthsum__dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
.np-mycal-monthsum__text { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.np-mycal-monthsum--pending   { color: var(--np-color-info-fg); }
.np-mycal-monthsum--active    { color: var(--np-color-success-fg); }
.np-mycal-monthsum--completed { color: #0F766E; }
.np-mycal-monthsum--arrived   { color: #6D28D9; }
.np-mycal-monthsum--warning   { color: var(--np-color-warning-fg); }
.np-mycal-monthsum--inactive  { color: var(--np-color-text-muted); }
.np-mycal-monthsum--danger    { color: var(--np-color-error-fg); }
.np-mycal-monthsum--neutral   { color: var(--np-color-text-muted); }

/* Positioned grid event wrapper (holds the block link + the three-dot button). */
.np-mycal-grid__event { position: absolute; }
.np-mycal-grid__event .np-mycal-block {
    position: static;
    width: 100%;
    height: 100%;
    /* Extra right padding so the head-row status pill clears the ⋮ button. */
    padding: 6px 30px 6px 12px;
    gap: 1px;
}
/* Tight line-heights so all three lines (time / type / patient) fit a compact
   card without clipping. */
.np-mycal-grid__event .np-mycal-block__time,
.np-mycal-grid__event .np-mycal-block__type,
.np-mycal-grid__event .np-mycal-block__patient { line-height: 1.25; }
/* Type/patient may be clipped by the card height on very short slots — keep them
   to one line with an ellipsis rather than cutting a line in half. */
.np-mycal-grid__event .np-mycal-block__type,
.np-mycal-grid__event .np-mycal-block__patient {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* Three-dot (kebab) action button — top-right of a block. */
.np-mycal-kebab {
    position: absolute;
    top: 2px;
    right: 2px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 0;
    border-radius: var(--np-radius-1);
    background: transparent;
    color: var(--np-color-text-muted);
    cursor: pointer;
}
.np-mycal-kebab:hover { color: var(--np-color-brand-primary); }
.np-mycal-kebab:focus-visible { outline: 2px solid var(--np-color-brand-primary); outline-offset: 1px; }

/* Kebab dropdown menu (appended to <body>, positioned by JS). */
.np-mycal-menu {
    position: absolute;
    z-index: 1200;
    min-width: 190px;
    padding: 4px;
    background: #fff;
    border: 1px solid var(--np-color-border, #e5e5e5);
    border-radius: var(--np-radius-2);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
}
.np-mycal-menu__item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    text-align: left;
    padding: 9px 14px;
    border: 0;
    background: none;
    border-radius: var(--np-radius-1);
    font-size: var(--np-fs-small);
    color: var(--np-color-text-primary);
    cursor: pointer;
    white-space: nowrap;
}
.np-mycal-menu__icon { flex: 0 0 auto; color: var(--np-color-text-muted); }
.np-mycal-menu__label { flex: 1 1 auto; }
.np-mycal-menu__item:hover,
.np-mycal-menu__item:focus-visible { background: var(--np-color-brand-tint-50); outline: none; }
.np-mycal-menu__item:hover .np-mycal-menu__icon,
.np-mycal-menu__item:focus-visible .np-mycal-menu__icon { color: var(--np-color-brand-primary); }
/* Action items use the same theme colours as the rest of the menu (no red). */
.np-mycal-menu__item--danger .np-mycal-menu__icon { color: var(--np-color-brand-primary); }
/* Divider between the action group and the navigation group. */
.np-mycal-menu__sep { height: 1px; margin: 4px 0; background: var(--np-color-border, #e5e5e5); }
/* Non-interactive info line (e.g. last-reminder-sent date). */
.np-mycal-menu__info { padding: 2px 14px 6px 40px; font-size: 0.72rem; color: var(--np-color-text-muted); }
/* Persistent calendar action-warning banner. */
.np-mycal__warning { margin-bottom: var(--np-space-3); }

/* Clinician multi-select ("Select clinician(s)") — org Calendar only. */
.np-mycal-clinsel { position: relative; }
.np-mycal-clinsel__btn { text-align: left; cursor: pointer; min-width: 190px; }
.np-mycal-clinsel__btn::after { content: "▾"; float: right; color: var(--np-color-text-muted); }
.np-mycal-clinsel__panel {
    position: absolute;
    z-index: 1100;
    top: calc(100% + 4px);
    left: 0;
    min-width: 240px;
    max-height: 320px;
    overflow-y: auto;
    padding: 4px;
    background: #fff;
    border: 1px solid var(--np-color-border, #e5e5e5);
    border-radius: var(--np-radius-2);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
}
.np-mycal-clinsel__opt {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    padding: 6px 10px;
    border-radius: var(--np-radius-1);
    font-size: var(--np-fs-small);
    cursor: pointer;
}
.np-mycal-clinsel__opt:hover { background: var(--np-color-brand-tint-50); }
.np-mycal-clinsel__opt--all { border-bottom: 1px solid var(--np-color-border, #eee); font-weight: 400; margin-bottom: 2px; }
.np-mycal-clinsel__list { display: block; }

/* Day-view clinician column headers. */
.np-mycal-colhead__name { font-weight: 400; font-size: 0.875rem; }
.np-mycal-colhead__you { color: var(--np-color-brand-primary); font-weight: 400; font-size: 0.75rem; }
.np-mycal-colhead__hours { display: block; color: var(--np-color-text-muted); font-size: 0.75rem; }

/* Confirm-dialog fact list (patient + appointment identifiers). */
.np-mycal-facts { margin: 0 0 var(--np-space-3); }
.np-mycal-facts > div { display: flex; gap: var(--np-space-3); padding: 2px 0; }
.np-mycal-facts dt { flex: 0 0 96px; color: var(--np-color-text-muted); font-size: var(--np-fs-small); margin: 0; }
.np-mycal-facts dd { margin: 0; font-weight: 400; }

/* ── UK bank holidays ──────────────────────────────────────────────────────
   Display-only markers. Neutral/info tint (never a clinical-safety colour — the
   red/amber vocabulary is reserved for §6.6 status). Applied in Month cells,
   Week/Day headers, and the org Day banner. */
.np-cal-month__cell.is-holiday { background: var(--np-color-info-bg); }
.np-cal-month__cell.is-holiday.is-today { background: var(--np-color-brand-tint-50); }
.np-cal-holiday-name {
    font-size: 0.72rem;
    font-weight: 400;
    line-height: 1.25;
    color: var(--np-color-info-fg);
    background: transparent;
    padding: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
/* Holiday tag under a Week/Day column header. */
.np-mycal-grid__dayhead.is-holiday,
.np-mycal-week__dayhead.is-holiday { color: var(--np-color-info-fg); }
.np-cal-holiday-tag {
    display: block;
    margin-top: 3px;
    font-size: 0.78rem;
    font-weight: 400;
    line-height: 1.2;
    color: var(--np-color-info-fg);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
/* Org Day-view holiday banner (its header is clinician columns). */
.np-cal-holiday-banner {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 var(--np-space-2);
    padding: 6px 12px;
    border-radius: var(--np-radius-2);
    background: var(--np-color-info-bg);
    color: var(--np-color-info-fg);
    border: 1px solid var(--np-color-info-fg);
    font-size: var(--np-fs-small);
}
.np-cal-holiday-banner__text { font-weight: 400; font-size: 0.9rem; }

/* ── Resource unavailability / organisation closures (#356) ──────────────────
   "Away" bands painted BENEATH appointment blocks in Day/Week time grids, and
   as compact markers in Month/Week cells. A band can never be misread as a
   booking: it is non-interactive (pointer-events:none) and sits below events.
   Tone follows the category — leave/closure = neutral grey; sickness = warning
   amber; training/other = info blue — always paired with a text label (§6.6). */
.np-mycal-band {
    position: absolute;
    left: 2px; right: 2px;
    z-index: 0;                 /* below .np-mycal-grid__event (see below) */
    border-radius: var(--np-radius-1, 4px);
    border-left: 3px solid transparent;
    padding: 3px 6px;
    overflow: hidden;
    pointer-events: none;       /* never blocks a click on the grid/appointment */
    font-size: 0.72rem;
    line-height: 1.2;
}
.np-mycal-band__label {
    display: block;
    font-weight: 400;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Keep appointment blocks above the shaded bands. */
.np-mycal-grid__event { z-index: 1; }

.np-mycal-band--neutral { background: rgba(107, 114, 128, 0.14); border-left-color: #6B7280; color: var(--np-color-text-muted); }
.np-mycal-band--info    { background: var(--np-color-info-bg);    border-left-color: var(--np-color-info-fg);    color: var(--np-color-info-fg); }
.np-mycal-band--warning { background: var(--np-color-warning-bg); border-left-color: var(--np-color-warning-fg); color: var(--np-color-warning-fg); }

/* Month-cell away line (marker above the appointment summary). */
.np-cal-month__cell.is-unavailable { box-shadow: inset 3px 0 0 rgba(107, 114, 128, 0.4); }
.np-mycal-away-line {
    font-size: 0.72rem;
    font-weight: 400;
    line-height: 1.25;
    margin: 1px 0 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
.np-mycal-away-line--neutral { color: var(--np-color-text-muted); }
.np-mycal-away-line--info    { color: var(--np-color-info-fg); }
.np-mycal-away-line--warning { color: var(--np-color-warning-fg); }

/* Week-cell away tag (org Week matrix). */
.np-mycal-week__cell.is-unavailable { box-shadow: inset 3px 0 0 rgba(107, 114, 128, 0.4); }
.np-mycal-away-tag {
    display: block;
    margin: 0 0 4px;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 0.72rem;
    font-weight: 400;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.np-mycal-away-tag--neutral { background: rgba(107, 114, 128, 0.14); color: var(--np-color-text-muted); }
.np-mycal-away-tag--info    { background: var(--np-color-info-bg);    color: var(--np-color-info-fg); }
.np-mycal-away-tag--warning { background: var(--np-color-warning-bg); color: var(--np-color-warning-fg); }

/* Legend swatch for the shaded "away" band. */
.np-calendar__band-key {
    display: inline-block;
    width: 22px;
    height: 12px;
    border-radius: 3px;
    background: rgba(107, 114, 128, 0.14);
    border-left: 3px solid #6B7280;
    vertical-align: -1px;
}

/* ── Slot availability grid (§5A slots / §5B core hours) ─────────────────────
   Computed availability for the selected clinic, painted BENEATH both the away
   bands and the appointment blocks so a booked slot still shows its appointment
   card on top. Like the away bands these are non-interactive: booking into a
   slot needs a patient chosen first, so nothing here is a click target.

   Status is carried by THREE signals, never colour alone (§6.6): the fill/border
   colour, a distinct left-edge treatment (solid / double / dashed), and the
   occupancy chip's text. A clinician reading this in greyscale still sees which
   slots are open. */
.np-mycal-slot {
    position: absolute;
    left: 2px; right: 2px;
    z-index: 0;                 /* with .np-mycal-band, below .np-mycal-grid__event */
    border-radius: var(--np-radius-1, 4px);
    border: 1px solid transparent;
    border-left-width: 3px;
    pointer-events: none;
    overflow: hidden;
}
/* Occupancy chip ("2/3"), pinned right so it stays readable beside an
   appointment card sitting on top of the slot. */
.np-mycal-slot__chip {
    position: absolute;
    top: 2px; right: 4px;
    padding: 0 5px;
    border-radius: 999px;
    font-size: 0.68rem;
    font-weight: 400;
    line-height: 1.5;
    white-space: nowrap;
}
.np-mycal-slot--open {
    background: var(--np-color-success-bg);
    border-color: var(--np-color-success-fg);
}
.np-mycal-slot--open .np-mycal-slot__chip {
    background: var(--np-color-success-bg);
    color: var(--np-color-success-fg);
}
/* Over-book available — double left edge marks "past comfortable capacity". */
.np-mycal-slot--over {
    background: var(--np-color-warning-bg);
    border-color: var(--np-color-warning-fg);
    border-left-style: double;
    border-left-width: 5px;
}
.np-mycal-slot--over .np-mycal-slot__chip {
    background: var(--np-color-warning-bg);
    color: var(--np-color-warning-fg);
}
/* Full — flat grey, no accent. Full is not a hazard, so it must not read red
   (§6.6: red is reserved for clinical-safety signalling). */
.np-mycal-slot--full {
    background: rgba(107, 114, 128, 0.10);
    border-color: rgba(107, 114, 128, 0.55);
}
.np-mycal-slot--full .np-mycal-slot__chip {
    background: rgba(107, 114, 128, 0.18);
    color: var(--np-color-text-muted);
}
/* Blocked — dashed edge, the same visual grammar as a disabled region. */
.np-mycal-slot--blocked {
    background: repeating-linear-gradient(
        135deg,
        rgba(107, 114, 128, 0.08) 0 6px,
        rgba(107, 114, 128, 0.14) 6px 12px);
    border-color: rgba(107, 114, 128, 0.45);
    border-left-style: dashed;
}
.np-mycal-slot--blocked .np-mycal-slot__chip { display: none; }

/* ── Merged booking in the calendar lane ──
   The appointment card above already spans the whole hour, because cards are placed
   by time. The availability lane beneath is computed PER SLOT, so without this an
   hour-long booking would sit on two separate blocks each chipped "1/1" and read as
   two bookings under one card. The run is painted as one block with a faint seam at
   each internal slot boundary: one booking, still visibly made of two slots. */
.np-mycal-slot--merged .np-mycal-slot__chip { align-self: flex-start; }
.np-mycal-slot__seam {
    position: absolute;
    left: 0;
    right: 0;
    height: 0;
    border-top: 1px dashed currentColor;
    opacity: 0.45;
    pointer-events: none;
}

/* Free-style core hours — the soft working frame (§5B). Deliberately the
   faintest layer: it says "the clinic is open", not "this is bookable". */
.np-mycal-core {
    position: absolute;
    left: 0; right: 0;
    z-index: 0;
    pointer-events: none;
    background: linear-gradient(135deg,
        rgba(0, 94, 184, 0.045),
        rgba(0, 94, 184, 0.085));
    border-top: 1px solid rgba(0, 94, 184, 0.14);
    border-bottom: 1px solid rgba(0, 94, 184, 0.14);
}

/* Month / week-matrix availability rollup. A month cell cannot hold individual
   slot blocks, so it carries the counts instead. */
.np-mycal-slotsum {
    display: block;
    margin: 0 0 3px;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 0.72rem;
    font-weight: 400;
    line-height: 1.25;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
.np-mycal-slotsum--open { background: var(--np-color-success-bg); color: var(--np-color-success-fg); }
.np-mycal-slotsum--over { background: var(--np-color-warning-bg); color: var(--np-color-warning-fg); }
.np-mycal-slotsum--full { background: rgba(107, 114, 128, 0.16); color: var(--np-color-text-muted); }

/* Slot legend swatches — mirror each block's edge treatment so the key teaches
   the shape signal, not just the colour. */
.np-calendar__legend--slots { margin-top: var(--np-space-2); }
.np-slot-key {
    display: inline-block;
    width: 22px;
    height: 12px;
    border-radius: 3px;
    border: 1px solid transparent;
    border-left-width: 3px;
    vertical-align: -1px;
}
.np-slot-key--open {
    background: var(--np-color-success-bg);
    border-color: var(--np-color-success-fg);
}
.np-slot-key--over {
    background: var(--np-color-warning-bg);
    border-color: var(--np-color-warning-fg);
    border-left-style: double;
    border-left-width: 5px;
}
.np-slot-key--full {
    background: rgba(107, 114, 128, 0.10);
    border-color: rgba(107, 114, 128, 0.55);
}
.np-slot-key--core {
    background: linear-gradient(135deg, rgba(0, 94, 184, 0.05), rgba(0, 94, 184, 0.09));
    border-color: rgba(0, 94, 184, 0.25);
    border-left-color: rgba(0, 94, 184, 0.35);
}

/* ── Bank-holidays list page (whole-year professional view) ───────────────── */
.np-holidays-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-3);
    margin-bottom: var(--np-space-4);
}
.np-holidays-yearnav { display: flex; align-items: center; gap: var(--np-space-2); }
.np-holidays-yearnav__year {
    min-width: 5.5ch;
    text-align: center;
    font-size: 1.5rem;
    font-weight: 400;
    color: var(--np-color-text-primary);
    letter-spacing: 0.01em;
}
.np-holidays-jump { display: flex; align-items: center; gap: var(--np-space-2); }
.np-holidays-jump input { width: 7rem; }
.np-holidays-stats { display: flex; flex-wrap: wrap; gap: var(--np-space-2); }
.np-holidays-stat {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    padding: 6px 14px;
    border-radius: var(--np-radius-pill);
    background: var(--np-color-surface-muted);
    border: 1px solid var(--np-color-border, #e5e5e5);
    font-size: var(--np-fs-small);
    color: var(--np-color-text-secondary);
}
.np-holidays-stat strong { font-size: 1.05rem; color: var(--np-color-text-primary); }
.np-holidays-stat--region { background: var(--np-color-info-bg); border-color: var(--np-color-info-fg); color: var(--np-color-info-fg); }
.np-holidays-stat--region strong { color: var(--np-color-info-fg); }

/* Month band separating groups in the year table. */
.np-holidays-month td {
    background: var(--np-color-surface-muted);
    font-weight: 400;
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--np-color-text-secondary);
    padding-top: 10px;
    padding-bottom: 10px;
}
.np-holidays-row--today td { background: var(--np-color-brand-tint-50); }
.np-holidays-row--past td { color: var(--np-color-text-muted); }
.np-holidays-name { font-weight: 400; }
.np-holidays-date { white-space: nowrap; }
.np-holidays-day { color: var(--np-color-text-secondary); }

/* ══════════════════════════════════════════════════════════════════════
   Patient banner (np-patient-banner.js)

   The standard sticky patient header. Zone 1 is always visible and sticks
   just below the app topbar; Zone 2 expands/collapses (grid-rows animation,
   no layout jump) and auto-collapses on scroll. Colours, radii, shadows and
   spacing all come from the theme tokens so it reads as native Neuraplex.
   ══════════════════════════════════════════════════════════════════════ */
.npb {
    position: sticky;
    top: var(--np-topbar-height);
    z-index: 15; /* below the topbar (20), above content */
    /*background: var(--np-color-banner-bg);*/
    background: #fff;
    /* Every muted label inside the banner (Zone 1 fact labels, Zone 2 field
       labels, the PDS freshness note) now sits on the tinted ground rather than
       on white, where the page-level muted token no longer clears 4.5:1 at the
       12px label size. Re-point the token ONCE here and every descendant is
       correct — a per-rule colour override would have to be repeated on each
       and would be missed by the next label someone adds. */
    --np-color-text-muted: var(--np-color-banner-fg-muted);
    /* No left accent stripe — a coloured left border is not part of this design
       system (the previously commented-out rule is deleted, not disabled). */
    /* The soft border token IS the banner tint on every preset, so a hairline in
       it would be invisible now the banner is filled. The strong token keeps the
       block's edge readable against the shell without becoming a rule. */
    border: 2px solid var(--np-color-brand-tint-200);
    border-radius: var(--np-radius-3);
    box-shadow: var(--np-shadow-2);
    margin-bottom: var(--np-space-3);
    padding: var(--np-space-2) var(--np-space-4);
    /* The banner is pinned inside the viewport, so the browser may pick a node
       inside it as the scroll anchor — then move the page when the drawer opens
       and that node shifts. Exclude it; content below is the stable thing to
       anchor to. This is hygiene, NOT the fix for "More detail does nothing when
       scrolled down": the compensating scroll for the banner's own growth is
       driven by the anchor in the CONTENT, so it cannot be suppressed from here.
       np-patient-banner.js owns that fix (it mutes the auto-collapse handler
       across the toggle). */
    overflow-anchor: none;
}

/* The patient banner stays pinned under the topbar on EVERY patient page,
   including the Patient workspace — it is the wrong-patient safeguard, so the
   identity must stay in view while the clinician scrolls the accordions. (A
   workspace-only `position: static` override that let it scroll away was
   removed; do not scope it back out per page.) */
/* Sit the banner tight under the topbar rather than inheriting the page's top
   gutter — Zone 1 is chrome, not content, and every pixel here is one fewer of
   the workspace visible. Guarded to `:first-child` so any page that renders
   something above the banner keeps its normal spacing. */
/* The second selector covers a page that opens with a visually-hidden <h1> (the
   Patient Workspace): it is out of flow, so the banner is still the first thing
   drawn and should still sit tight under the topbar. */
.np-shell__content > .npb:first-child,
.np-shell__content > .npb__back-link:first-child + .npb,
.np-shell__content > .visually-hidden:first-child + .npb,
.np-shell__content > .visually-hidden:first-child + .npb__back-link + .npb { margin-top: calc(-1 * var(--np-space-3)); }
/* An inactive record drops the brand tint for the cool neutral: the banner still
   reads as the patient's chrome, but the colour says "this record is not live"
   before a word is read. Token-based so it follows the theme. */
.npb--inactive {
    border-color: var(--np-color-border);
    background: var(--np-color-neutral-100);
    --np-color-text-muted: color-mix(in srgb, var(--np-color-text-primary) 75%, var(--np-color-neutral-100));
}

/* ── Zone 1 — always visible. Deliberately compact (≈52px): it is pinned under
      the topbar on every patient screen, so its height is a permanent tax on the
      workspace below. Every identifier a clinician needs to confirm they have the
      right patient (name, DOB, age, gender, MRN, NHS number) plus the safety
      badges (allergy, high priority, deceased, restricted) stay in this row —
      compacting the chrome must never cost an identifier. ── */
.npb__zone1 {
    display: flex;
    align-items: center;
    gap: var(--np-space-3);
    min-height: 52px;
}
/* No avatar in the banner: an initials disc carries no identifying information a
   clinician can check a patient against, and it cost 40px of a row that is pinned
   to every patient screen. The name is the identity anchor. */
.npb__identity { flex: 1 1 auto; min-width: 0; }
.npb__name-row {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--np-space-1) var(--np-space-3);
    margin-bottom: 3px;   /* breathing room between the name row and the facts row */
}
.npb__name {
    /* One step down from --np-fs-h3 (20px) to keep Zone 1 compact. Still the
       largest thing in the banner, so the patient's name remains the first thing
       read — the identity check must not get quieter than the chrome around it. */
    font-size: 1rem;
    font-weight: 400;
    color: var(--np-color-text-primary);
    line-height: 1.2;
}
/* Forename (and middle name) stay at normal weight; only the family name is bold,
   so the surname is what the eye locks onto in a list-to-banner identity check. */
.npb__name-given  { font-weight: 400; }
.npb__name-family { font-weight: 700; }
.npb__preferred {
    color: var(--np-color-text-secondary);
    font-size: var(--np-fs-small);
}

/* Identifier facts row — wraps on narrow widths, never overflows.

   SPACING IS DERIVED, NOT EYEBALLED. Two custom properties own it:
     --npb-fact-gap  the space between a fact's own label and value
     --npb-fact-sep  the space on EACH side of the "|" divider
   The separator is a ::before inside the next fact, so the parent's column-gap
   lands before it and the fact's own flex gap lands after it. Setting the
   pseudo's margin to (sep - gap) makes the two sides equal — without it the
   divider sat 12px from the fact on its left and 18px from the one on its
   right, which is what read as "uneven spacing" across the row.

   Baseline (not centre) alignment: a value that wraps to a second line must
   keep its label and its neighbours on the FIRST line, with the overflow
   hanging below — centring would float short facts to the middle of a tall
   one. */
.npb__facts {
    --npb-fact-gap: 6px;
    --npb-fact-sep: var(--np-space-3);

    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    row-gap: var(--np-space-2);
    column-gap: var(--npb-fact-sep);
    font-size: var(--np-fs-small);
}
.npb__fact {
    display: inline-flex;
    align-items: baseline;
    gap: var(--npb-fact-gap);
    min-width: 0;
}
/* Pipe separator between identifier facts — DOB | Age | Gender | MRN | NHS | …
   Rendered from CSS so no view has to hand-place a divider and none is ever left
   dangling at the end of a wrapped row. aria-hidden by construction (generated
   content is not exposed as text to assistive tech in this position). */
.npb__fact + .npb__fact::before {
    content: "|";
    color: var(--np-color-border-strong);
    margin-right: calc(var(--npb-fact-sep) - var(--npb-fact-gap));
}
.npb__fact-label {
    color: var(--np-color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 400;
    font-size: 0.75rem;
}
/* A long value WRAPS onto a second line inside its own fact; it never stretches
   the row or slides under its neighbour. `anywhere` (not `break-word`) so it can
   also break a single unbroken token — a 40-character address with no spaces —
   and, just as importantly, so the flex item's min-content width collapses and
   the fact can actually shrink instead of forcing an overflow. */
.npb__fact-value {
    color: var(--np-color-text-primary);
    font-weight: 400;
    min-width: 0;
    overflow-wrap: anywhere;
}
.npb__contact-owner {
    position: relative;
    display: inline-flex;
    min-width: 0;
    vertical-align: baseline;
}
.npb__contact-trigger {
    appearance: none;
    border: 0;
    padding: 0;
    margin: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    line-height: inherit;
    text-align: left;
    cursor: help;
}
.npb__contact-trigger:hover {
    text-decoration: underline;
    text-underline-offset: 2px;
}
.npb__contact-trigger:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
    border-radius: 4px;
}
.npb__contact-tooltip {
    position: absolute;
    left: 50%;
    bottom: calc(100% + 8px);
    transform: translateX(-50%);
    min-width: max-content;
    max-width: min(24rem, calc(100vw - 2rem));
    padding: var(--np-space-2) var(--np-space-3);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    background: var(--np-color-surface);
    box-shadow: var(--np-shadow-2);
    color: var(--np-color-text-primary);
    font-size: 0.8125rem;
    line-height: 1.4;
    white-space: normal;
    z-index: 20;
    pointer-events: none;
}
/* MRN / NHS number. Rendered as <code> because they are exact identifiers a
   clinician reads back character by character — but <code> brings the browser's
   monospace family, which stood out as a different typeface in the middle of the
   banner. The boxed treatment is the "this is an identifier" cue; the FONT should be
   the banner's own. */
.npb__fact-value--code {
    font-family: inherit;
    background: var(--np-color-surface);
    padding: 1px 7px;
    border-radius: 4px;
    border: 1px solid var(--np-color-brand-tint-200);
    font-size: 0.8125rem;
    letter-spacing: 0.01em;
    /* The one value that must NOT wrap. An MRN or NHS number is read back
       character by character; splitting it across two lines invites a
       transcription error, and it is short enough to never need the room. */
    white-space: nowrap;
    overflow-wrap: normal;
}

/* Right rail of Zone 1: the action row, and under it the next appointment.
   A column so the appointment reads as a caption to the actions rather than
   competing with them for the row. */
.npb__zone1-right {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--np-space-2);
    align-self: flex-start;
    margin-left: auto;
}
.npb__back-link {
    display: inline-flex;
    align-items: center;
    margin-bottom: var(--np-space-2);
    font-weight: 700;   /* product-owner request; same weight as .npb__name-family */
}
/* Badge row — the bottom row of Zone 1 and the ONLY place a banner badge renders
   (status, High Priority, Deceased, Restricted, PDS verification, allergy status,
   and any badge added later). A dashed hairline separates it from the identity
   rows above; the pills wrap onto a second line on narrow widths rather than
   stretching the banner. */
.npb__badges {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-2);
    margin-top: var(--np-space-2);
    padding-top: var(--np-space-2);
    border-top: 1px dashed var(--np-color-border);
}
.npb__badges .npb-badge {
    white-space: nowrap;
}

/* Next appointment — a quiet, tappable panel. Quiet because it is reference, not
   an action: it must not out-shout Edit. The whole panel is the link (44px+ tall,
   so it clears the touch-target minimum) and it carries its own focus ring. */
.npb__next {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    padding: 4px var(--np-space-3) 4px var(--np-space-2);
    border: 1px solid var(--np-color-brand-tint-200);
    border-radius: var(--np-radius-pill);
    background: var(--np-color-surface);
    text-decoration: none;
    color: inherit;
    max-width: 100%;
}
.npb__next:hover {
    border-color: var(--np-color-brand-primary);
    background: var(--np-color-brand-tint-100);
}
.npb__next:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}
.npb__next-icon { display: inline-flex; color: var(--np-color-brand-primary); }
.npb__next-icon svg { width: 18px; height: 18px; }
/* ONE line, one fact: the date and time. The "Next appointment" eyebrow, the
   venue and the booking status all moved to the tooltip and the accessible name
   — each was a real detail, but this panel is pinned to every patient screen and
   they were costing a second line plus a status pill of width for information
   nobody reads at a glance. The calendar glyph says what the value is. */
.npb__next-value {
    color: var(--np-color-text-primary);
    font-weight: 400;
    font-size: var(--np-fs-small);
    white-space: nowrap;
}

/* Row-1 action set (Edit · More detail). The controls themselves are the
   shared `np-section-action` used on accordion headers — nothing is restyled
   here, so the banner and the workspace sections stay in lockstep. The row is
   pinned to the TOP of Zone 1 so it sits on the name line and never stretches
   the banner. */
.npb__zone1-actions {
    flex: 0 0 auto;
    align-self: flex-start;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--np-space-2);
    justify-content: flex-end;
}
.npb__zone1-primary-actions {
    display: inline-flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: var(--np-space-2);
    min-width: max-content;
}
.npb__zone1-actions .np-section-action { text-decoration: none; }

/* Chevron on the expand/collapse control — rotates with the drawer. */
.npb__toggle-chevron { transition: transform 250ms ease; }
.npb[data-expanded="true"] .npb__toggle-chevron { transform: rotate(180deg); }

/* ── Zone 2 — collapsible detail. grid-rows 0fr → 1fr animates height with
      no fixed max-height guesswork and no layout jump. ── */
.npb__zone2 {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 250ms ease;
}
.npb[data-expanded="true"] .npb__zone2 { grid-template-rows: 1fr; }
.npb__zone2-inner { overflow: hidden; min-height: 0; }
.npb__grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--np-space-3) var(--np-space-5);
    padding-top: var(--np-space-4);
    margin-top: var(--np-space-3);
    border-top: 1px dashed var(--np-color-brand-tint-200);
}
.npb__col { display: flex; flex-direction: column; gap: var(--np-space-3); min-width: 0; }
.npb__field { display: flex; align-items: flex-start; gap: var(--np-space-2); min-width: 0; }
.npb__field-icon {
    flex: 0 0 auto;
    display: inline-flex;
    color: var(--np-color-text-muted);
}
.npb__field-icon svg { width: 16px; height: 16px; }
.npb__field-body { display: flex; flex-direction: column; min-width: 0; }
.npb__field-label {
    color: var(--np-color-text-muted);
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 400;
}
.npb__field-value {
    color: var(--np-color-text-primary);
    font-size: var(--np-fs-small);
    word-break: break-word;
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    flex-wrap: wrap;
}
.npb__episode {
    background: var(--np-color-surface);
    padding: 1px 7px;
    border-radius: 4px;
    border: 1px solid var(--np-color-brand-tint-200);
    font-size: 0.8125rem;
}

/* Badge row — wraps freely; only rendered when flags exist. */
.npb__badges {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2);
    margin-top: var(--np-space-4);
}
.npb-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border-radius: var(--np-radius-pill);
    font-size: var(--np-fs-small);
    font-weight: 400;
    line-height: 1.3;
    border: 1px solid transparent;
}
.npb-badge svg { width: 14px; height: 14px; flex: 0 0 14px; }
.npb-badge--sm { padding: 1px 8px; font-size: 0.75rem; }
/* Icon-only badge (the allergy indicator). Square-ish pill so the glyph is not
   squeezed; the label lives on aria-label/title. Never colour-alone — the glyph
   itself changes with the state. */
.npb-badge--icon {
    padding: 3px;
    width: 24px;
    height: 24px;
    justify-content: center;
    border-radius: 50%;
}
.npb-badge--icon svg { width: 16px; height: 16px; flex: 0 0 16px; }
/* The allergy pill sits in the identifier run, on the contact/email line. It
   keeps the row's derived label↔value gap (so the "|" divider stays centred like
   every other fact) but never splits its own wording: the row wraps BETWEEN
   facts, which moves the whole pill to the next line rather than breaking
   "Allergies recorded" across two. */
.npb__fact--allergy .npb-badge { white-space: nowrap; }
/* Dot-style badge — a small coloured dot instead of an icon (keeps the badge
   compact). The dot inherits the badge's tone colour via currentColor. */
.npb-badge--dot { gap: 7px; }
.npb-badge__dot {
    width: 7px;
    height: 7px;
    flex: 0 0 7px;
    border-radius: 50%;
    background: currentColor;
}
.npb-badge--ok       { background: var(--np-color-success-bg); color: var(--np-color-success-fg); border-color: var(--np-color-success-border); }
.npb-badge--warning  { background: var(--np-color-warning-bg); color: var(--np-color-warning-fg); border-color: #FFE69C; }
.npb-badge--danger   { background: var(--np-color-error-bg);   color: var(--np-color-error-fg);   border-color: #F5C2C7; }
.npb-badge--info     { background: var(--np-color-info-bg);    color: var(--np-color-info-fg);    border-color: #BBD3EC; }
.npb-badge--neutral  { background: var(--np-color-brand-tint-100); color: var(--np-color-brand-secondary); border-color: var(--np-color-brand-tint-200); }

/* Allergy read-out for a prescribing surface (`_NpAllergyStatus.cshtml`) — the
   panel above the first field of the medication form. Layout only: the tone
   comes from the shared .np-panel--* variants, so nothing here introduces a
   colour, and the glyph is the same shape-per-state set the banner draws. */
.np-allergy-status {
    display: flex;
    align-items: flex-start;
    gap: var(--np-space-3, 12px);
}
.np-allergy-status__icon { flex: 0 0 24px; line-height: 0; margin-top: 1px; }
.np-allergy-status__body { min-width: 0; }
.np-allergy-status__body > p { margin: 0; }
.np-allergy-status__body > p + p { margin-top: var(--np-space-1, 4px); }
/* "Allergy status" / "Allergy information" — the quiet term that names the value
   beside it, so the panel reads without relying on the icon. */
.np-allergy-status__term {
    display: inline-block;
    margin-right: 6px;
    font-size: 0.75rem;
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    opacity: 0.85;
}
/* Clinician-entered free text — wrap rather than overflow the panel. */
.np-allergy-status__detail { overflow-wrap: anywhere; }
.np-allergy-status__note { font-size: var(--np-fs-small, 0.875rem); opacity: 0.9; }
/* "Last reviewed" line. Same weight as the detail above it: whether the list
   was checked is part of reading the list, not a footnote to it. */
.np-allergy-status__review { font-size: var(--np-fs-small, 0.875rem); }

/* Break-glass "Reveal" affordance — sits immediately after the Restricted badge.
   A compact amber (caution) pill with an eye icon, so it reads as a deliberate,
   consequential action rather than a plain grey button. Hover/focus fills solid. */
.npb-reveal {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 11px;
    font-size: var(--np-fs-small);
    font-weight: 400;
    line-height: 1.5;
    color: var(--np-color-warning-fg);
    background: var(--np-color-warning-bg);
    border: 1px solid #FFE69C;
    border-radius: 999px;
    cursor: pointer;
    transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.npb-reveal svg { width: 13px; height: 13px; flex: 0 0 13px; }
.npb-reveal:hover,
.npb-reveal:focus-visible {
    background: var(--np-color-warning-fg);
    border-color: var(--np-color-warning-fg);
    color: #fff;
}
.npb-reveal:focus-visible { outline: 2px solid var(--np-color-warning-fg); outline-offset: 2px; }

/* Care-episode action row — lives at the top of Zone 2 (revealed with the detail
   drawer). Open / Close / Reopen wrap onto one row with consistent spacing and a
   divider from the fields grid below. */
.npb__ce-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2);
    padding-bottom: var(--np-space-4);
    margin-bottom: var(--np-space-1);
    border-bottom: 1px dashed var(--np-color-brand-tint-200);
}
.npb__ce-actions + .npb__grid { border-top: none; margin-top: var(--np-space-1); padding-top: var(--np-space-2); }

/* Care-episode buttons (Open / Close / Reopen) in Zone 2 keep the compact pill
   sizing. Zone-1's controls are `np-section-action` and are sized by that
   component — they are deliberately NOT included here. Scoped to the banner so
   the global .np-btn / .np-btn--sm used elsewhere are untouched. */
.npb__ce-actions .np-btn {
    min-height: 34px;
    padding: 6px 14px;
    font-size: var(--np-fs-small);
    font-weight: 400;
    line-height: 1.2;
    border-radius: var(--np-radius-pill);
}
/* Drop the heavy primary glow inside the banner so filled and outlined buttons
   sit at the same visual weight in the compact row. */
.npb__ce-actions .np-btn--primary { box-shadow: none; }
.npb__ce-actions .np-btn--primary:hover,
.npb__ce-actions .np-btn--primary:focus { box-shadow: var(--np-shadow-1); transform: none; }

/* Email fact can be long, so it is capped — but it must NOT grow: as a flex:1
   item it swallowed the rest of the row and left a corridor of white space
   after it. It takes only the width it needs, up to the cap.

   It WRAPS at the cap rather than truncating with an ellipsis. An address a
   clinician cannot finish reading is not information, and "j.smith-…" hides
   exactly the part that distinguishes two similar addresses. The generic
   .npb__fact-value wrap rule does the work; nothing email-specific is needed
   beyond the cap. */
.npb__fact--email { min-width: 0; flex: 0 1 auto; max-width: 32ch; }

/* ── Responsive ── */
@media (max-width: 991.98px) {
    .npb__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
    .npb__zone1 { flex-wrap: wrap; }
    .npb__zone1-right { width: 100%; align-items: flex-start; margin-left: 0; }
    .npb__zone1-actions { width: 100%; justify-content: flex-start; align-self: auto; }
    .npb__grid { grid-template-columns: 1fr; }
    /* Labels collapse to icon-only below 720px via the shared
       .np-section-action__label rule — nothing banner-specific needed. */
}

/* Respect reduced-motion — no sliding, instant show/hide. */
@media (prefers-reduced-motion: reduce) {
    .npb__zone2,
    .npb__toggle-chevron { transition: none; }
}



/* Venue cell — leading icon (video-call camera / location pin) + text. */
.np-venue { display: inline-flex; align-items: center; gap: 6px; }
.np-venue__icon { flex: 0 0 auto; color: var(--np-color-text-muted); }
.np-venue__text { min-width: 0; }

/* Appointment email review modal — wider than the standard confirm so the
   rich editor and composed email body have room. */
/* Patient-facing appointment accept / cancel card title — smaller than the app
   h1 so the name-bearing title sits comfortably in the narrow anonymous card. */
.np-anon-title { font-size: var(--np-fs-h2); }

.np-appt-email { max-width: 760px; width: 92vw; }
.np-appt-email .np-confirm__form { text-align: left; }
.np-appt-email .np-form-group { margin-bottom: var(--np-space-3); }
/* Rendered email body shown in the review / offer preview (read-only). Shared by
   the Details "Send email" modal and the Create offer-email preview so both read
   as a clean email draft. */
.np-appt-email__preview {
    max-height: 46vh;
    overflow-y: auto;
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-1, 6px);
    padding: var(--np-space-3, 12px);
    background: #fff;
}
/* Separate the action bar from the content so the buttons don't crowd the body. */
.np-appt-email .np-confirm__actions {
    margin-top: var(--np-space-4, 16px);
    padding-top: var(--np-space-4, 16px);
    border-top: 1px solid var(--np-color-border);
}

/* ── Triage accordion — clinical summary (≈60%) + reserved decision rail (≈40%).
   Left: five stacked cards (surfaced-for-attention + acknowledgement gate, reasons for
   referral, areas of need, history, full form record) read-projected from Registration +
   Pre-Assessment. Right: reserved for the triage decision workflow (next phase). ── */
/* Soften every structural border inside the triage panel to a barely-there tint —
   redefine the border token once on the container so cards, domains, pills, ref-text,
   form-links and Q/A separators all inherit it (tone borders set their own colour and
   are unaffected). */
.np-triage { --np-color-border: var(--np-color-brand-tint-100); }
.np-triage__wrap {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);   /* ≈60% / ≈40% */
    gap: var(--np-space-4, 16px);
    align-items: start;
}
@media (max-width: 1100px) {
    .np-triage__wrap { grid-template-columns: 1fr; }
}
/* BASIC Triage renders the decision on its own — the intake column is not there to sit
   beside (see _TriageSummary.cshtml). One column, and the rail stops being sticky because
   there is nothing left to stay level with. Capped and centred: a two-question form run
   across a 27" monitor is harder to fill in, not easier — the same reason every other
   single-purpose form in the product has a max width. */
.np-triage__wrap--solo { grid-template-columns: minmax(0, 1fr); }
.np-triage__wrap--solo .np-triage__rail {
    position: static;
    width: 100%;
    max-width: 720px;
    margin-inline: auto;
}
.np-triage__main { display: flex; flex-direction: column; gap: var(--np-space-2, 8px); min-width: 0; }
/* Desktop: the intake column scrolls independently so the decision rail stays put and
   the page itself does not grow to the height of the whole pre-assessment. Below the
   two-column breakpoint the column flows with the page as before. */
@media (min-width: 1101px) {
    .np-triage__main {
        max-height: var(--np-triage-main-h, calc(100vh - 140px));
        overflow-y: auto;
        /* Scroll chaining stays ON (`auto`, the initial value) deliberately: once the
           intake column reaches its end the wheel/trackpad gesture must continue on to
           the page, or the clinician gets stuck mid-screen with no way to reach the
           workspace content below without moving the pointer off the panel first.
           Do NOT restore `overscroll-behavior: contain` here — a dead-end scroll on the
           densest screen in the product reads as a broken page. */
        overscroll-behavior-y: auto;
        padding-right: var(--np-space-2, 8px);
    }
    /* Section headers stay put while their card scrolls, so the card title and its
       "n to review" counter remain visible as the clinician scans. */
    .np-triage__main .np-triage__card-head {
        position: sticky;
        top: 0;
        z-index: 1;
        background: var(--np-color-surface, #fff);
        padding-top: 2px;
    }
}
/* Flex column + gap so the decision-history card and the active (wizard / start) card
   never touch — the rail now stacks more than one card. */
.np-triage__rail { position: sticky; top: var(--np-space-4, 16px); min-width: 0; display: flex; flex-direction: column; gap: var(--np-space-4, 16px); }
@media (max-width: 1100px) { .np-triage__rail { position: static; } }

/* Compact by design: the intake column carries a lot of sections, so the cards trade
   generous padding for density — same surface, border, radius and type scale, less air.
   The vertical padding is deliberately tighter than the horizontal: rows read fine close
   together, but text running to the card edge does not. The rail keeps its roomier
   treatment below — it is where the clinician WRITES, not where they scan. */
.np-triage__card {
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-3, 16px);
    padding: var(--np-space-2, 8px) var(--np-space-4, 16px) var(--np-space-3, 12px);
    background: var(--np-color-surface, #fff);
}
/*.np-triage__card--rail { background: var(--np-color-brand-tint-50); }*/
.np-triage__card-head { display: flex; align-items: center; gap: var(--np-space-2, 8px); margin-bottom: 6px; }
.np-triage__card-ic {
    width: 26px; height: 26px; border-radius: var(--np-radius-2, 8px);
    background: var(--np-color-brand-tint-100); color: var(--np-color-brand-primary);
    display: inline-flex; align-items: center; justify-content: center; flex: none;
}
.np-triage__card-ic svg { width: 15px; height: 15px; }
.np-triage__card-title { font-size: var(--np-fs-small); font-weight: 400; margin: 0; }
/* The rail keeps the roomier treatment — it holds one decision at a time, not a stack
   of intake sections, and it is where the clinician writes rather than scans. */
.np-triage__card--rail { padding: var(--np-space-4, 16px) var(--np-space-5, 20px); }
.np-triage__card--rail .np-triage__card-head { margin-bottom: var(--np-space-2, 8px); }
.np-triage__card--rail .np-triage__card-title { font-size: var(--np-fs-h3, 20px); }
.np-triage__card--rail .np-triage__card-ic { width: 34px; height: 34px; }
.np-triage__card--rail .np-triage__card-ic svg { width: 18px; height: 18px; }

/* Review state — a quiet outline chip so "not reviewed" reads at a glance without
   competing with the clinical-safety tones used by the surfaced indicators. */
.np-triage__rev {
    flex: none; font-size: var(--np-fs-small); font-weight: 400; white-space: nowrap;
    border-radius: 999px; padding: 1px 8px;
    color: var(--np-color-text-muted);
    background: var(--np-color-surface, #fff);
    border: 1px dashed var(--np-color-brand-tint-200);
}
.np-triage__rev--done {
    color: var(--np-color-brand-primary);
    background: var(--np-color-brand-tint-50);
    border-style: solid;
    border-color: var(--np-color-brand-tint-100);
}
.np-triage__rev-count {
    margin-left: auto; flex: none; font-size: var(--np-fs-small); font-weight: 400;
    white-space: nowrap; border-radius: 999px; padding: 1px 9px;
    color: var(--np-color-text-muted);
    background: var(--np-color-brand-tint-50);
    border: 1px dashed var(--np-color-brand-tint-200);
}
.np-triage__src-tag {
    margin-left: auto; font-size: var(--np-fs-small); font-weight: 400;
    color: var(--np-color-brand-primary); background: var(--np-color-brand-tint-100);
    border-radius: 999px; padding: 3px 10px; white-space: nowrap;
}

/* ── Decision history (the signed-decision sequence, newest first) ────────────── */
/* Each decision is a collapsible accordion row; the list scrolls once it grows past
   a few entries so the card never balloons down the page. */
/* No height cap: an opened decision grows to its full content so its actions (letter
   send, "Read more") are visible without an inner scroller. */
.np-triage__dhist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--np-space-2, 8px); }
.np-triage__dhist-item { border: 1px solid var(--np-color-border); border-radius: var(--np-radius-2, 10px); background: var(--np-color-surface, #fff); }
.np-triage__dhist-item--latest { border-color: var(--np-color-brand-tint-200, var(--np-color-brand-primary)); }
.np-triage__dhist-item > details > summary {
    display: flex; align-items: center; flex-wrap: wrap; gap: var(--np-space-2, 8px);
    padding: var(--np-space-2, 8px) var(--np-space-3, 12px); cursor: pointer; list-style: none;
    border-radius: inherit;
}
.np-triage__dhist-item > details > summary::-webkit-details-marker { display: none; }
.np-triage__dhist-item > details > summary:focus-visible { outline: 2px solid var(--np-color-brand-primary); outline-offset: 2px; }
.np-triage__dhist-seq { font-weight: 400; color: var(--np-color-text, #111); white-space: nowrap; }
.np-triage__dhist-title { font-weight: 400; color: var(--np-color-text-muted, #555); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 6rem; }
.np-triage__dhist-latest {
    font-size: var(--np-fs-small); font-weight: 400; color: #fff;
    background: var(--np-color-brand-primary); border-radius: 999px; padding: 1px 8px; white-space: nowrap;
}
.np-triage__dhist-date { font-size: var(--np-fs-small); color: var(--np-color-text-muted, #777); white-space: nowrap; }
.np-triage__dhist-sum .np-triage__chev { margin-left: auto; transition: transform .15s ease; flex: none; }
.np-triage__dhist-sum .np-triage__chev svg { width: 16px; height: 16px; }
.np-triage__dhist-item > details[open] > summary .np-triage__chev { transform: rotate(180deg); }
.np-triage__dhist-body { padding: 0 var(--np-space-3, 12px) var(--np-space-3, 12px); border-top: 1px solid var(--np-color-border); margin-top: var(--np-space-1, 4px); }

/* ── Completed decision: accordion + "Read more" modal ──
   A signed decision collapses to a one-line header (decision type + "Latest"); the body holds
   the headline facts and the full record lives in the modal, so a case with several decisions
   costs a few lines rather than a screen each. Native <details> — no JS. */
.np-triage__dacc {
    border: 1px solid var(--np-color-border); border-radius: var(--np-radius-2, 10px);
    background: var(--np-color-surface, #fff); overflow: hidden;
}
.np-triage__dacc--latest { border-color: var(--np-color-brand-tint-200, var(--np-color-brand-primary)); }
.np-triage__dacc-head {
    display: flex; align-items: center; gap: var(--np-space-2, 8px);
    padding: var(--np-space-2, 8px) var(--np-space-3, 12px);
    cursor: pointer; list-style: none; font-weight: 400;
}
.np-triage__dacc-head::-webkit-details-marker { display: none; }
.np-triage__dacc-head:focus-visible { outline: 2px solid var(--np-color-brand-primary); outline-offset: -2px; }
.np-triage__dacc-chev { display: inline-flex; flex: none; transition: transform .15s ease; color: var(--np-color-text-muted, #667); }
.np-triage__dacc-chev svg { width: 16px; height: 16px; }
.np-triage__dacc[open] > .np-triage__dacc-head .np-triage__dacc-chev { transform: rotate(180deg); }
.np-triage__dacc-seq { flex: none; white-space: nowrap; color: var(--np-color-text, #111); }
.np-triage__dacc-title {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-weight: 400; color: var(--np-color-text-muted, #555);
}
.np-triage__dacc-title::before { content: "· "; }
/* Header badge group — pinned to the header's right edge, priority first then "Latest". */
.np-triage__dacc-badges {
    margin-left: auto; flex: none; display: inline-flex; align-items: center; gap: 6px;
}
/* "Latest" badge — Success theme. */
.np-triage__dacc-badge {
    flex: none; white-space: nowrap;
    font-size: var(--np-fs-small); font-weight: 400;
    background: var(--np-color-success-bg); color: var(--np-color-success-fg);
    border: 1px solid var(--np-color-success-border); border-radius: 999px; padding: 1px 10px;
}
/* Review-gate prompt above "Start the triage decision" — informational, not an error:
   nothing has gone wrong, there is simply reading left to do. */
.np-triage__review {
    margin-bottom: 8px; padding: 8px 10px;
    /* Tinted background + hairline border, no left accent stripe. */
    border: 1px solid var(--np-color-brand-tint-200);
    border-radius: var(--np-radius-2, 8px); background: var(--np-color-brand-tint-50);
}
.np-triage__review-msg { margin: 0; font-size: var(--np-fs-small); }

/* High clinical priority — Danger theme (§6.6: a clinical-urgency signal, not a status).
   Spacing and right-edge alignment come from the .np-triage__dacc-badges group. */
.np-triage__dacc-badge--high {
    background: #FBE7E9; color: #8A1F1F; border-color: #F0C4C8;
}
.np-triage__dacc-body {
    padding: var(--np-space-3, 12px); border-top: 1px solid var(--np-color-border);
    display: flex; flex-direction: column; gap: var(--np-space-3, 12px);
}
.np-triage__dacc-letters { display: flex; flex-direction: column; gap: 4px; }
/* Rail decision summary — the rail is narrow, so the header qualifiers, the rows, their
   pills/chips and the actions step down to the xsmall scale. At 14px the assessment chips
   and the "· by <clinician>" trail wrapped and the actions sat below the fold. */
.np-triage__dacc-title,
.np-triage__dacc-badge { font-size: var(--np-fs-xsmall); }
.np-triage__dacc-body .np-triage__dsum-grid--ruled > dt,
.np-triage__dacc-body .np-triage__dsum-grid--ruled > dd {
    font-size: var(--np-fs-xsmall); padding: 6px 0;
    /* Label and value centre on one line even when the value is a row of chips/pills;
       the items still stretch, so the dotted rule under each row stays continuous. */
    display: flex; align-items: center;
}
.np-triage__dacc-body .np-triage__dsum-grid--ruled > dd { flex-wrap: wrap; justify-content: flex-end; column-gap: 4px; }
.np-triage__dacc-body .np-triage__dsum-grid--ruled > dd:last-of-type { border-bottom: 0; }
.np-triage__dacc-body .np-triage__dsum-grid--ruled > dt:last-of-type { border-bottom: 0; }
.np-triage__dacc-body .np-triage__dsum-by { margin-left: 0; }
.np-triage__dacc-body .np-triage__chips { gap: 4px; }
.np-triage__dacc-body .np-triage__chip { font-size: var(--np-fs-xsmall); padding: 1px 9px; }
.np-triage__dacc-body .np-status { font-size: var(--np-fs-xsmall); padding: 1px 8px 1px 6px; gap: 5px; }
.np-triage__dacc-body .np-status__dot { width: 6px; height: 6px; }
.np-triage__dacc-body .np-triage__next { margin: 0; padding: var(--np-space-2, 8px) var(--np-space-3, 12px); }
.np-triage__dacc-body .np-triage__next-label { font-size: var(--np-fs-xsmall); }
.np-triage__dacc-body .np-btn { font-size: var(--np-fs-xsmall); }
@media (max-width: 40rem) {
    .np-triage__dacc-body .np-triage__dsum-grid--ruled > dd { justify-content: flex-start; }
}
@media (prefers-reduced-motion: reduce) { .np-triage__dacc-chev { transition: none; } }

.np-triage__dsum-grid {
    display: grid; grid-template-columns: minmax(7rem, auto) 1fr;
    gap: 4px var(--np-space-3, 12px); margin: 0;
}
.np-triage__dsum-grid dt { font-size: var(--np-fs-small); color: var(--np-color-text-muted, #667); font-weight: 400; }
.np-triage__dsum-grid dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
/* Deep-link target after signing (#triage-decisions) — clear the sticky topbar + banner,
   matching the .np-section anchor offset. */
#triage-decisions { scroll-margin-top: 140px; }
/* "· by <clinician>" trailing the decision instant on the same row — secondary to the time. */
.np-triage__dsum-by { margin-left: 4px; color: var(--np-color-text-muted, #667); }
.np-triage__dsum-actions { display: flex; flex-wrap: wrap; gap: var(--np-space-2, 8px); }
/* Single column on narrow rails — a 7rem label column squeezes the value too hard. */
@media (max-width: 40rem) {
    .np-triage__dsum-grid { grid-template-columns: 1fr; gap: 2px; }
    .np-triage__dsum-grid dd { margin-bottom: var(--np-space-2, 8px); }
}

/* Native <dialog> holding the full decision record.

   40rem (640px) — the width this modal has always actually RENDERED at, and the one the
   record was judged to read best at. It briefly went to 64rem to allow two label/value
   pairs per row; that was reverted (see .np-triage__rec) because one field per row scans
   better, and without the 2-up layout the extra width only stretches short values across
   an empty gap. Still viewport-bounded, so on a tablet or phone it is simply "the screen
   minus a margin" and never overflows.

   NOTE the `dialog` element selector. The shared `.np-modal` rule further down this file
   also sets max-width (640px) at the SAME specificity, so on source order it wins — an
   unqualified `.np-triage__modal` here does nothing at all, which is how the original
   44rem silently rendered at 640px. Qualifying by element (0,1,1 beats 0,1,0) keeps this
   width authoritative regardless of order: 40rem is now stated, not inherited by accident. */
dialog.np-triage__modal { border: 0; padding: 0; max-width: 40rem; width: min(40rem, calc(100vw - 2rem)); }
/* Guidance-style modals are prose, and prose past ~70ch is harder to read, not easier.
   Kept as its own variant (rather than folded into the rule above) so a future width
   change to the record modal cannot silently widen the guidance text with it. */
dialog.np-triage__modal--prose { max-width: 40rem; width: min(40rem, calc(100vw - 2rem)); }
.np-triage__modal::backdrop { background: rgba(0, 0, 0, 0.45); }
.np-triage__modal .np-modal__head {
    display: flex; align-items: flex-start; gap: var(--np-space-3, 12px);
    padding: var(--np-space-4, 16px); border-bottom: 1px solid var(--np-color-border);
}
.np-triage__modal .np-modal__title { flex: 1 1 auto; font-size: 1.05rem; }
.np-triage__modal .np-modal__close {
    flex: none; border: 0; background: transparent; font-size: 1.5rem; line-height: 1;
    cursor: pointer; color: var(--np-color-text-muted, #667); padding: 0 4px;
}
.np-triage__modal .np-modal__close:focus-visible { outline: 2px solid var(--np-color-brand-primary); outline-offset: 2px; }
.np-triage__modal .np-modal__body { padding: var(--np-space-4, 16px); max-height: 65vh; overflow-y: auto; }
.np-triage__modal .np-modal__foot {
    display: flex; justify-content: flex-end; padding: var(--np-space-3, 12px) var(--np-space-4, 16px);
    border-top: 1px solid var(--np-color-border);
}

/* Correspondence trail inside the full record. */
.np-triage__letters { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.np-triage__letters li { font-size: var(--np-fs-small); color: var(--np-color-text-muted, #667); }
.np-triage__letters-to { display: block; overflow-wrap: anywhere; }

/* Wizard footer: Sign sits first, Cancel (close without recording) beside it. */
.np-tw__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--np-space-2, 8px); margin-top: 6px; }
.np-tw__cancel { display: inline-flex; align-items: center; gap: 6px; }
.np-tw__cancel-ic { display: inline-flex; width: 15px; height: 15px; flex: none; }
.np-tw__cancel-ic svg { width: 15px; height: 15px; }
/* Carries only the antiforgery token + episode id; the Cancel button targets it by id. */
.np-tw__discard-form { display: none; }

/* Post-sign success confirmation on the refreshed panel. */
.np-triage__flash { margin-bottom: var(--np-space-3, 12px); }

/* Delivery tick boxes in a "Preview email" dialog (Email patient / Email GP). */
/* The standard "Send to" recipient picker (_NpEmailRecipientPicker + np-email-recipients.js)
   on every Patient Workspace email preview. One bordered row per person on the record;
   the ticked rows carry the brand edge so who-gets-it scans at a glance, and the tick
   itself (not colour alone) is the state. */
.np-email-recipients { border: 0; padding: 0; min-width: 0; }
.np-email-recipients__list { display: flex; flex-direction: column; gap: 6px; }
.np-email-recipients__option {
    display: flex; align-items: flex-start; gap: var(--np-space-2);
    padding: 8px var(--np-space-3);
    border: 1px solid var(--np-color-field-border);
    border-radius: var(--np-radius-1, 6px);
    background: var(--np-color-surface);
    cursor: pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}
.np-email-recipients__option:hover { border-color: var(--np-color-field-border-hover); }
.np-email-recipients__option:has(input:checked) {
    border-color: var(--np-color-brand-primary);
    background: var(--np-color-brand-tint-50, #f7f4fb);
}
.np-email-recipients__option input { margin-top: 3px; flex: 0 0 auto; }
.np-email-recipients__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.np-email-recipients__role { color: var(--np-color-text-primary); }
.np-email-recipients__detail {
    font-size: var(--np-fs-small);
    color: var(--np-color-text-secondary);
    overflow-wrap: anywhere;
}
.np-email-recipients__status {
    margin: 6px 0 0;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-secondary);
}
.np-email-recipients__status:empty { display: none; }
.np-email-recipients__status--error { color: var(--np-color-error-fg); }

/* Busy state for a submit that is mid-round-trip (triage sign, letter send). */
.np-btn--busy, .np-btn.is-busy { opacity: .75; cursor: progress; }
.np-btn__spinner {
    display: inline-block; width: 14px; height: 14px; margin-right: 8px; vertical-align: -2px;
    border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
    animation: np-btn-spin .7s linear infinite;
}
@keyframes np-btn-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .np-btn__spinner { animation-duration: 2.4s; }
}

/* Start / record-another affordance */
.np-triage__start-form { margin-top: var(--np-space-2, 8px); }
.np-triage__start-form .np-btn { width: 100%; }

/* Intake prerequisite (Registration → Pre-assessment → Triage).
   The "go to the missing form" buttons sit inline and wrap; the override is a
   closed-by-default <details> so proceeding without the forms is a deliberate
   second step rather than a single click. */
.np-triage__card .np-panel .np-btn--sm { margin: var(--np-space-1, 4px) var(--np-space-2, 8px) 0 0; }

.np-triage__override { margin-top: var(--np-space-3, 12px); }
.np-triage__override > summary {
    cursor: pointer; padding: var(--np-space-2, 8px) 0;
    font-size: var(--np-fs-small); font-weight: 400;
    color: var(--np-color-text-muted);
}
.np-triage__override > summary:focus-visible {
    outline: 2px solid var(--np-color-focus); outline-offset: 2px; border-radius: var(--np-radius-1, 6px);
}
.np-triage__override[open] > summary { margin-bottom: var(--np-space-2, 8px); }
.np-triage__override textarea.np-input { width: 100%; margin-bottom: var(--np-space-1, 4px); }

/* Recorded override — stays on the case so a reader never mistakes the intake
   summary for a complete one. */
.np-triage__intake-override { margin-bottom: var(--np-space-3, 12px); }

/* Acknowledgement gate banner (H1) */
.np-triage__gate {
    display: flex; align-items: center; gap: var(--np-space-2, 8px);
    border: 1px solid; border-radius: var(--np-radius-2, 10px);
    padding: var(--np-space-3, 12px); margin-bottom: var(--np-space-3, 12px);
    font-size: var(--np-fs-small);
}
.np-triage__gate--clear   { background: var(--np-color-success-bg); border-color: var(--np-color-success-border); color: var(--np-color-success-fg); }
.np-triage__gate--pending { background: var(--np-color-warning-bg); border-color: var(--np-color-warning-fg); color: var(--np-color-warning-fg); }
.np-triage__gate-box { display: inline-flex; flex: none; width: 18px; height: 18px; }
.np-triage__gate-box svg { width: 18px; height: 18px; }

/* Surfaced-for-attention pills */
.np-triage__pills { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
/* Fixed height showing ~4 items before scrolling, so the panel does not balloon. */
.np-triage__pills--scroll { max-height: 17rem; overflow-y: auto; padding-right: 4px; }
.np-triage__pills--scroll::-webkit-scrollbar { width: 8px; }
.np-triage__pills--scroll::-webkit-scrollbar-thumb { background: var(--np-color-brand-tint-200); border-radius: 999px; }
.np-triage__pills--scroll { scrollbar-width: thin; scrollbar-color: var(--np-color-brand-tint-200) transparent; }
.np-triage__pill {
    display: flex; align-items: flex-start; gap: var(--np-space-2, 8px);
    border: 1px solid var(--np-color-border); border-radius: var(--np-radius-2, 10px);
    padding: var(--np-space-2, 8px) var(--np-space-3, 12px);
}
.np-triage__pill--urgent    { background: #fde7e5; border-color: #f0c1b8; }
.np-triage__pill--attention { background: var(--np-color-warning-bg); border-color: #ecd9a6; }
.np-triage__pill--info      { background: var(--np-color-info-bg); border-color: #c7d8ec; }
.np-triage__pill-ic { flex: none; width: 30px; height: 30px; border-radius: var(--np-radius-2, 8px); background: rgba(255,255,255,.6); display: inline-flex; align-items: center; justify-content: center; }
.np-triage__pill-ic svg { width: 17px; height: 17px; }
.np-triage__pill--urgent    .np-triage__pill-ic { color: var(--np-color-danger-fg, #a41b17); }
.np-triage__pill--attention .np-triage__pill-ic { color: var(--np-color-warning-fg); }
.np-triage__pill--info      .np-triage__pill-ic { color: var(--np-color-info-fg); }
/* Acknowledged — the box turns success-green with a tick regardless of the original level
   (declared AFTER the level rules so it wins on equal specificity). */
.np-triage__pill--acked              { background: var(--np-color-success-bg, #e6f4ea); border-color: #b6ddc0; }
.np-triage__pill--acked .np-triage__pill-ic { color: var(--np-color-success-fg); background: rgba(255,255,255,.7); }
.np-triage__pill-body { flex: 1 1 auto; min-width: 0; }
.np-triage__pill-title { display: flex; align-items: center; gap: var(--np-space-2, 8px); flex-wrap: wrap; }
.np-triage__pill-label { font-weight: 400; }
.np-triage__pill-source {
    margin: 4px 0 0;
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-muted);
}
.np-triage__pill-detail { margin: 6px 0 0; white-space: pre-wrap; }
.np-triage__pill-ack { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.np-triage__ack-form { margin: 0; }
.np-triage__acked { display: inline-flex; align-items: center; gap: 4px; font-size: var(--np-fs-small); font-weight: 400; color: var(--np-color-success-fg); }
.np-triage__acked-ic { display: inline-flex; width: 15px; height: 15px; }
.np-triage__acked-ic svg { width: 15px; height: 15px; }
.np-triage__acked-meta { font-size: var(--np-fs-small); color: var(--np-color-text-muted); text-align: right; }
/* Inline acknowledge error (e.g. no permission) — shown beside the pill, never a page redirect. */
.np-triage__ack-error { margin: 4px 0 0; font-size: var(--np-fs-small); font-weight: 400; color: var(--np-color-danger-fg, #a41b17); text-align: right; max-width: 220px; }

.np-triage__note, .np-triage__attrib { display: flex; align-items: center; gap: 6px; font-size: var(--np-fs-xsmall); color: var(--np-color-text-muted); margin: var(--np-space-2, 8px) 0 0; }
.np-triage__note-ic, .np-triage__attrib-ic { flex: none; width: 15px; height: 15px; }
.np-triage__note-ic svg, .np-triage__attrib-ic svg { width: 15px; height: 15px; }

/* Reasons for referral now renders through the shared np-triage__domain accordion —
   see .np-triage__domain-body--verbatim below, which carries the tinted reading panel
   the old .np-triage__ref-text box provided. The three .np-triage__ref* rules that lived
   here had no remaining markup and were removed rather than left to rot. */
.np-triage__empty { color: var(--np-color-text-muted); margin: 0; }
.np-triage__legend { font-size: var(--np-fs-small); color: var(--np-color-text-muted); margin: 0 0 var(--np-space-3, 12px); }

/* Subtle "Source: …" subheading — smaller, muted, sits directly under the card title
   (or, --inset, at the top of an accordion section body). */
.np-triage__source { font-size: var(--np-fs-xsmall); color: var(--np-color-text-muted); font-weight: 400; margin: -2px 0 6px; }
.np-triage__source--inset { margin: 0 0 6px; font-weight: 400; letter-spacing: .01em; }

/* Sub-heading INSIDE a card, below the card title and any "Source: …" line — names a
   column of values in the rows underneath (e.g. "Self Reported Severity" over the
   np-triage__meter bars). Same muted, small type as np-triage__source so the card keeps
   one subheading voice; heavier weight because this one titles content rather than
   attributing it. */
.np-triage__subhead {
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-muted);
    font-weight: 400;
    letter-spacing: .01em;
    margin: 0 0 6px;
}

/* Domain collapsibles (reasons for referral / areas of need / history) */
.np-triage__domain { border: 1px solid var(--np-color-border); border-radius: var(--np-radius-2, 10px); margin-bottom: 4px; overflow: hidden; }
/* Open state: the same 1.5px border-strong an .np-input draws, so the expanded section
   reads as the one "live" box on the column without any heavier device — no shadow, no
   fill, no accent stripe. Only one section can be open at a time (np-triage-review.js),
   so exactly one of these is ever on screen. */
.np-triage__domain[open] {
    border: 1.5px solid var(--np-color-border-strong);
}
/* An unopened section keeps a soft tint so the unread rows stand out as a block while
   scanning; opening it returns the row to the plain surface. */
.np-triage__domain:not([open]) > summary { background: var(--np-color-brand-tint-50); }
    .np-triage__domain > summary {
        list-style: none;
        cursor: pointer;
        display: flex;
        align-items: center;
        gap: var(--np-space-2, 8px);
        padding: 5px var(--np-space-3, 12px);
        min-height: 32px;
        background: var(--np-color-brand-tint-50);
    }
.np-triage__domain > summary::-webkit-details-marker { display: none; }
.np-triage__domain > summary:focus-visible { outline: 2px solid var(--np-color-brand-primary); outline-offset: -2px; }
.np-triage__domain-ic { flex: none; width: 24px; height: 24px; border-radius: var(--np-radius-2, 8px); background: var(--np-color-brand-tint-100); color: var(--np-color-brand-primary); display: inline-flex; align-items: center; justify-content: center; }
.np-triage__domain-ic svg { width: 14px; height: 14px; }
.np-triage__domain-name { font-weight: 400; flex: 1 1 auto; min-width: 0; }
.np-triage__meter { display: inline-flex; gap: 3px; align-items: center; flex: none; }
.np-triage__seg { width: 14px; height: 8px; border-radius: 3px; background: var(--np-color-brand-tint-200); }
.np-triage__seg--on { background: var(--np-color-brand-primary); }
.np-triage__rater { font-size: var(--np-fs-small); font-weight: 400; color: var(--np-color-brand-primary); background: var(--np-color-brand-tint-100); border-radius: 999px; padding: 2px 9px; white-space: nowrap; flex: none; }
.np-triage__chev { width: 16px; height: 16px; color: var(--np-color-text-muted); flex: none; transition: transform .15s ease; }
.np-triage__chev svg { width: 16px; height: 16px; }
.np-triage__domain[open] > summary .np-triage__chev { transform: rotate(180deg); }
.np-triage__domain-body { padding: var(--np-space-2, 8px) var(--np-space-3, 12px); white-space: pre-line; font-size:var(--np-fs-xsmall)}
/* Reasons for referral: the family's own words. Keeps the tinted reading panel the old
   np-triage__ref-text box gave them (the "verbatim" signal), now as the accordion body
   so there is one border instead of two. pre-wrap, not pre-line — the paragraph breaks
   the family typed are part of what they said. */
.np-triage__domain-body--verbatim {
    /* 2px of top margin, unlike the plain body's flush-to-summary treatment: this one
       has a visible tinted edge, and an edge touching the summary rule reads as a
       rendering fault rather than a panel. */
    margin: 2px var(--np-space-2, 8px) var(--np-space-2, 8px);
    padding: var(--np-space-2, 8px) var(--np-space-3, 12px);
    border-radius: var(--np-radius-2, 10px);
    white-space: pre-wrap;
    line-height: 1.55;
}
/* Match the tight content padding of "Areas of need": the first row sits directly under the
   summary (no top gap) and rows are lightly spaced rather than 8px apart. */
.np-triage__qa { display: grid; grid-template-columns: minmax(120px, 34%) 1fr; gap: 2px 12px; padding: 3px 0; border-top: 1px dashed var(--np-color-border); }
.np-triage__qa:first-child { border-top: none; padding-top: 0; }
.np-triage__qa-q { color: var(--np-color-text-muted); font-weight: 400; }
.np-triage__qa-a { white-space: pre-wrap; }

/* Full form record */
.np-triage__forms { display: flex; flex-direction: column; gap: var(--np-space-2, 8px); }
.np-triage__form-link { display: flex; align-items: center; gap: var(--np-space-3, 12px); border: 1px solid var(--np-color-border); border-radius: var(--np-radius-2, 10px); padding: var(--np-space-3, 12px); text-decoration: none; color: inherit; }
.np-triage__form-link:hover { background: var(--np-color-brand-tint-50); border-color: var(--np-color-brand-primary); }
.np-triage__form-link:focus-visible { outline: 2px solid var(--np-color-brand-primary); outline-offset: 2px; }
.np-triage__form-ic { flex: none; width: 30px; height: 30px; border-radius: var(--np-radius-2, 8px); background: var(--np-color-brand-tint-100); color: var(--np-color-brand-primary); display: inline-flex; align-items: center; justify-content: center; }
.np-triage__form-ic svg { width: 16px; height: 16px; }
.np-triage__form-name { font-weight: 400; flex: 1 1 auto; }
.np-triage__form-open { width: 16px; height: 16px; color: var(--np-color-text-muted); flex: none; }
.np-triage__form-open svg { width: 16px; height: 16px; }

/* ── Triage decision (right rail) ── */
/* Decision form */
.np-triage__decision-form { display: flex; flex-direction: column; gap: var(--np-space-4, 16px); }
.np-triage__field { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--np-space-2, 8px); min-width: 0; }
.np-triage__field-label { font-weight: 400; font-size: var(--np-fs-small); padding: 0; }
/* Parenthetical hint beside a label ("(choose all that apply)"). MUTED, never
   red: this is guidance, not a mandatory marker. The wizard used to paint these
   with `.np-triage__req` — the same red as its asterisks — which made a hint look
   like a requirement. The required marker is `.np-required` and nothing else. */
.np-triage__opt { font-weight: 400; color: var(--np-color-text-muted); }
.np-triage__radios, .np-triage__checks { display: flex; flex-direction: column; gap: 6px; }
.np-triage__radio, .np-triage__check { display: flex; align-items: flex-start; gap: 8px; cursor: pointer; padding: 8px 10px; border: 1px solid var(--np-color-border); border-radius: var(--np-radius-2, 8px); background: var(--np-color-surface, #fff); }
.np-triage__radio:hover, .np-triage__check:hover { background: var(--np-color-brand-tint-50); }
.np-triage__radio:has(input:checked), .np-triage__check:has(input:checked) { border-color: var(--np-color-brand-primary); background: var(--np-color-brand-tint-50); }
.np-triage__radio input, .np-triage__check input { margin-top: 2px; accent-color: var(--np-color-brand-primary); width: 16px; height: 16px; flex: none; }
.np-triage__radio input:focus-visible, .np-triage__check input:focus-visible, .np-triage__attest input:focus-visible { outline: 2px solid var(--np-color-brand-primary); outline-offset: 2px; }
.np-triage__accepted-fields { display: flex; flex-direction: column; gap: var(--np-space-4, 16px); }
.np-triage__accepted-fields[hidden] { display: none; }
.np-triage__control { width: 100%; box-sizing: border-box; border: 1px solid var(--np-color-border); border-radius: var(--np-radius-2, 8px); padding: 8px 10px; font: inherit; background: var(--np-color-surface, #fff); resize: vertical; }
.np-triage__control:focus-visible { outline: 2px solid var(--np-color-brand-primary); outline-offset: 0; border-color: var(--np-color-brand-primary); }
.np-triage__attest { display: flex; align-items: flex-start; gap: 10px; padding: 11px 13px; border: 1px solid var(--np-color-border); border-radius: var(--np-radius-2, 10px); background: var(--np-color-brand-tint-50); }
.np-triage__attest input { margin-top: 2px; accent-color: var(--np-color-brand-primary); width: 18px; height: 18px; flex: none; }
.np-triage__attest span { font-size: var(--np-fs-small); line-height: 1.45; }
.np-triage__sign { align-self: flex-start; }

/* Decided card */
.np-triage__decision { display: flex; flex-direction: column; gap: var(--np-space-3, 12px); }
.np-triage__decision-head { display: flex; align-items: center; gap: var(--np-space-2, 8px); flex-wrap: wrap; }
.np-triage__decision-priority { font-size: var(--np-fs-small); font-weight: 400; color: var(--np-color-brand-primary); background: var(--np-color-brand-tint-100); border-radius: 999px; padding: 2px 10px; }
.np-triage__chips { display: flex; flex-wrap: wrap; gap: 6px; }
.np-triage__chip { font-size: var(--np-fs-small); font-weight: 400; color: var(--np-color-brand-primary); background: var(--np-color-brand-tint-100); border: 1px solid var(--np-color-brand-tint-200); border-radius: 999px; padding: 3px 11px; }
/* "Who should assess" holds PROFESSIONS, not people. A distinct neutral chip (the brand
   chip reads as a selected assessment) keeps that visually obvious alongside the hint text. */
.np-triage__chip--profession {
    color: var(--np-color-text, #111); background: var(--np-color-surface-muted, #f4f6f8);
    border-color: var(--np-color-border);
}
.np-triage__decision-detail { margin: 0; white-space: pre-wrap; }
/* The FULL decision record (the "Read more" modal). One definition list: a fixed label
   column on the left, the value taking the rest, and a hairline between rows — the reader
   scans straight down the labels instead of hopping over stacked label/value pairs. */
.np-triage__rec { display: grid; grid-template-columns: 11rem minmax(0, 1fr); margin: 0; }
.np-triage__rec > dt,
.np-triage__rec > dd { padding: 9px 0; border-top: 1px solid var(--np-color-border); }
/* The first pair needs no rule above it. */
.np-triage__rec > dt:first-of-type,
.np-triage__rec > dt:first-of-type + dd { border-top: 0; padding-top: 0; }
.np-triage__rec > dt {
    font-size: var(--np-fs-small); font-weight: 400; color: var(--np-color-text-muted, #667);
    padding-right: var(--np-space-3, 12px);
}
.np-triage__rec > dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
/* Free-text values keep their authored line breaks. Only ever applied to a <dd> whose whole
   content is a single @value — Razor indentation would otherwise render as leading space. */
.np-triage__pre { white-space: pre-wrap; }
.np-triage__reclist { margin: 0; padding-left: 1.1rem; display: flex; flex-direction: column; gap: 2px; }

/* DELIBERATELY one label/value pair per row at every width — there is no 2-up variant.
   A wide modal briefly ran two pairs per row; it was reverted because the record stopped
   scanning. With pairs side by side the eye has to hop left-right-left down the list, the
   hairline rules break off half-way whenever a free-text field claims a full row, and a
   field's value no longer sits where the previous field's value did. One column keeps a
   single vertical run of labels with the values aligned beside them, which is what makes
   a long clinical record readable at a glance. If this modal is ever widened again, widen
   the VALUE column — do not pair fields up. */

/* Narrow viewports: the 11rem column starves the value, so stack label over value. */
@media (max-width: 34rem) {
    .np-triage__rec { grid-template-columns: 1fr; }
    .np-triage__rec > dt { padding-bottom: 0; }
    .np-triage__rec > dd { padding-top: 2px; border-top: 0; }
    .np-triage__rec > dt:first-of-type + dd { padding-top: 2px; }
}
.np-triage__decision-reason { display: flex; flex-direction: column; gap: 4px; }
.np-triage__decision-reason p { margin: 0; white-space: pre-wrap; }
.np-triage__decision-label { font-size: var(--np-fs-small); font-weight: 400; color: var(--np-color-text-muted); }
.np-triage__decision-sign { display: flex; align-items: center; gap: 6px; margin: 0; font-size: var(--np-fs-small); color: var(--np-color-text-muted); }
.np-triage__decision-ic { display: inline-flex; width: 15px; height: 15px; color: var(--np-color-success-fg); flex: none; }
.np-triage__decision-ic svg { width: 15px; height: 15px; }
.np-triage__amend, .np-triage__defer { border-top: 1px solid var(--np-color-border); padding-top: var(--np-space-3, 12px); }
.np-triage__amend > summary, .np-triage__defer > summary { cursor: pointer; font-weight: 400; font-size: var(--np-fs-small); color: var(--np-color-brand-primary); }
.np-triage__amend > summary:focus-visible, .np-triage__defer > summary:focus-visible { outline: 2px solid var(--np-color-brand-primary); outline-offset: 2px; }
.np-triage__amend-body { margin-top: var(--np-space-3, 12px); }

/* Field hint (below an input) */
.np-triage__hint {
    margin: 0;
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-muted);
}

/* Held-for-MDT card + the MDT deferral form */
.np-triage__held { display: flex; flex-direction: column; gap: var(--np-space-3, 12px); }
.np-triage__held-row { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; font-size: var(--np-fs-small); }
.np-triage__held-record { border-top: 1px solid var(--np-color-border); padding-top: var(--np-space-3, 12px); display: flex; flex-direction: column; gap: var(--np-space-3, 12px); }
.np-triage__defer-form { display: flex; flex-direction: column; gap: var(--np-space-4, 16px); }

/* ── Triage decision wizard (np-tw) ── */
.np-tw { display: flex; flex-direction: column; gap: var(--np-space-4, 16px); }
.np-tw__step {
    border: 0;
    margin: 0;
    margin-bottom: 5px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--np-space-2, 8px);
    min-width: 0;
}
.np-tw__step[hidden], .np-tw__reveal[hidden], [data-reveal][hidden], [data-branch][hidden], [data-preset-ctx][hidden] { display: none; }
.np-tw__lab { font-weight: 400; font-size: var(--np-fs-small); padding: 0; margin: 0; color: var(--np-color-text); }
.np-tw__rl { font-weight: 400; font-size: var(--np-fs-small); margin-top: 6px; }
.np-tw__seg { display: flex; gap: 8px; }
.np-tw__seg--stack { flex-direction: column; }
.np-tw__segbtn { flex: 1 1 auto; display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 9px 12px; border: 1px solid var(--np-color-border); border-radius: var(--np-radius-2, 8px); background: var(--np-color-surface, #fff); font-size: var(--np-fs-xsmall); }
.np-tw__segbtn input { position: absolute; opacity: 0; width: 0; height: 0; }
.np-tw__segbtn:hover { background: var(--np-color-brand-tint-50); }
.np-tw__segbtn.on { border-color: var(--np-color-brand-primary); background: var(--np-color-brand-tint-50); font-weight: 400; }
.np-tw__segbtn:has(input:focus-visible) { outline: 2px solid var(--np-color-brand-primary); outline-offset: 2px; }
.np-tw__opts { display: flex; flex-direction: column; gap: 6px; }
.np-tw__opt { display: flex; align-items: center; gap: 9px; cursor: pointer; padding: 9px 11px; border: 1px solid var(--np-color-border); border-radius: var(--np-radius-2, 8px); background: var(--np-color-surface, #fff); }
.np-tw__opt input { accent-color: var(--np-color-brand-primary); width: 16px; height: 16px; flex: none; }
.np-tw__opt:hover { background: var(--np-color-brand-tint-50); }
.np-tw__opt.sel { border-color: var(--np-color-brand-primary); background: var(--np-color-brand-tint-50); }
.np-tw__opt--danger.sel { border-color: #f0c1b8; background: #fde7e5; }
/* Options, checks and chips use the xsmall scale — the rail is narrow and at 14px the
   longer answers wrapped onto two lines. */
.np-tw__opt-t { font-size: var(--np-fs-xsmall); font-weight: 400; }
.np-tw__check { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: var(--np-fs-xsmall); padding: 5px 2px; }
.np-tw__check input { accent-color: var(--np-color-brand-primary); width: 16px; height: 16px; flex: none; margin: 0; }
.np-tw__reveal { display: flex; flex-direction: column; gap: 6px; padding: 11px; border: 1px solid var(--np-color-border); border-radius: var(--np-radius-2, 8px); background: var(--np-color-brand-tint-50); }
.np-tw__reveal--danger { background: #fdeeeb; border-color: #f0c9b3; }
.np-tw__chips { display: flex; flex-wrap: wrap; gap: 6px; }
.np-tw__chip { cursor: pointer; font-size: var(--np-fs-xsmall); border: 1px solid var(--np-color-border); border-radius: 999px; padding: 4px 10px; background: var(--np-color-surface, #fff); }
.np-tw__chip input { position: absolute; opacity: 0; width: 0; height: 0; }
.np-tw__chip.on { border-color: var(--np-color-brand-primary); background: var(--np-color-brand-tint-100); color: var(--np-color-brand-primary); font-weight: 400; }
/* Fixed two EQUAL columns for preset chips (triage notes, priority reasons). A wrapping
   flex row sized each chip to its label, producing ragged rows that are hard to scan;
   the grid gives every chip the same width and a predictable reading order. Chips wrap
   their own text rather than truncating, so no reason is hidden from the clinician. */
.np-tw__chips--cols {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px;
}
.np-tw__chips--cols .np-tw__chip { text-align: center; white-space: normal; }
@media (max-width: 560px) { .np-tw__chips--cols { grid-template-columns: minmax(0, 1fr); } }

/* Guidance ("what each reason means") — a LINK, not a boxed control: it is a side note
   about the chips above it, and a bordered panel gave it the same visual weight as the
   fields the clinician must actually complete. Opens the guidance modal. */
/* Label + guidance link share one line, the link pushed right. flex-wrap means a narrow
   rail (or a zoomed page) drops the link onto its own line instead of squashing either. */
.np-tw__rl-row {
    display: flex; flex-wrap: wrap; align-items: baseline;
    justify-content: space-between; gap: 4px 12px;
}
.np-tw__rl-row .np-tw__rl { margin-top: 0; }

.np-tw__guide-link {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 0; border: 0; background: none; cursor: pointer;
    font-size: var(--np-fs-small); font-weight: 400; color: var(--np-color-brand-primary);
    text-decoration: none;
}
.np-tw__guide-link:hover, .np-tw__guide-link:focus-visible { text-decoration: underline; color: var(--np-color-brand-primary); }
.np-tw__guide-link:focus-visible { outline: 2px solid var(--np-color-brand-primary); outline-offset: 2px; border-radius: 2px; }
.np-tw__guide-ic svg { width: 16px; height: 16px; display: block; }
.np-tw__guide-list { margin: 0; font-size: var(--np-fs-small); }
.np-tw__guide-list dt { font-weight: 400; margin-top: 8px; }
.np-tw__guide-list dd { margin: 2px 0 0; color: var(--np-color-text-muted, #555); }
.np-tw__attest { margin-top: 4px; }
.np-tw__sign { margin-top: 0; }
.np-tw__mdt-hold { margin: 4px 0 0; }
.np-tw__error { margin: 6px 0 0; font-size: var(--np-fs-small); font-weight: 400; color: var(--np-color-danger-fg, #a41b17); }
.np-tw__saved { margin: 6px 0 0; font-size: var(--np-fs-small); color: var(--np-color-text-muted); min-height: 1em; }
.np-tw__saved--err { color: var(--np-color-danger-fg, #a41b17); font-weight: 400; }
.np-tw__reqlist { display: flex; flex-direction: column; gap: 4px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--np-color-border); }
.np-tw__reqhead { font-size: var(--np-fs-small); font-weight: 400; color: var(--np-color-text); }
.np-tw__reqitem { display: flex; align-items: center; gap: 7px; font-size: var(--np-fs-small); color: var(--np-color-text-muted); }
.np-tw__reqitem.ok { color: var(--np-color-success-fg); }
.np-tw__reqdot { width: 9px; height: 9px; border-radius: 50%; border: 2px solid currentColor; flex: none; }
.np-tw__reqitem.ok .np-tw__reqdot { background: var(--np-color-success-fg); border-color: var(--np-color-success-fg); }

/* Module lock panel (Assign clinician / Select assessments / Medication until decided) */
.np-triage-lock { display: flex; flex-direction: column; align-items: flex-start; gap: var(--np-space-3, 12px); }
.np-triage-lock p { margin: 0; }

/* ── Waiting Well ──────────────────────────────────────────────────────────────
   The accordion reuses the triage card vocabulary (np-triage__card, __card-head,
   __dsum-grid, __chips) so the two neighbouring sections read as one family.
   Only the pieces triage has no equivalent for are defined here. */

/* "What happens next" row on the latest accepted triage decision card. */
.np-triage__next {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--np-space-2, 8px) var(--np-space-3, 12px);
    margin: var(--np-space-3, 12px) 0 0; padding: var(--np-space-3, 12px) var(--np-space-4, 16px);
    border: 1px solid var(--np-color-border, #d9dee3); border-radius: var(--np-radius-2, 10px);
    background: var(--np-color-surface-alt, #f7f8f9);
}
.np-triage__next-label { font-weight: 400; font-size: var(--np-fs-small, 14px); }
.np-triage__next .np-btn { margin-left: auto; }

/* The Waiting Well body: one column of cards, sized to the panel.

   --np-ww-answer-col is THE SECTION'S ANSWER COLUMN, and every label/value row in
   Waiting Well takes it: the triage decision card, the record card, each check-in
   card, and the areas of need. The label sits at the left edge; the answer sits in a
   fixed-width column pinned to the RIGHT, so the whole accordion reads down one
   vertical edge instead of each card starting its values wherever its own longest
   label happened to end (7rem on the decision card, 17rem on the areas card — two
   cards, two columns, sitting one above the other).

   Pinned right rather than at a fixed offset from the left, because the labels are
   not one kind of thing: "Priority" is two syllables and "Routines & repetitive
   behaviours" is a phrase. A left-anchored column has to clear the longest of them
   and then strands the short ones behind a void; a right-anchored one lets the label
   track absorb the difference and still lands every answer on the same x. */
.np-ww {
    display: flex; flex-direction: column; gap: var(--np-space-4, 16px);
    --np-ww-reading-col: 8.5rem;
}

/* ── THE WAITING WELL ROW ──────────────────────────────────────────────────────
   One shape for every label/value pair in the section — the triage decision card,
   the record card, each check-in card, the five answers, and the areas of need:

       Label ·············································· Answer
       Label ·············································· Answer

   Label at the left edge, answer against the RIGHT edge, a dotted rule between
   rows. Both edges are then straight and the rule carries the eye across the gap,
   which is what makes a long run of facts scannable — the answer column is where
   a reader's eye lands, so it is the one that has to be predictable, and the labels
   vary in length far more than the answers do.

   The label track is sized to the label and the answer takes what is left, which
   is the design's `flex: none` label + `justify-content: space-between` expressed
   as a grid: a two-word label and a five-word one both leave their answer on the
   same edge, and neither imposes a column width on the other.

   Alignment and rhythm ONLY. The design sets its values in 700; this product runs
   on a single weight (see TypographyScaleSweepTests — an off-scale weight is a
   build failure), so the value/detail hierarchy is carried by colour, as it
   already is: value in the text colour, the trailing "· by …" muted.

   Applied by SCOPE inside Waiting Well (every grid in .np-ww takes it) and by an
   explicit OPT-IN elsewhere: .np-triage__dsum-grid--ruled. The opt-in exists so the
   triage decision accordion can read identically to the "Most recent triage decision"
   card that mirrors it in Waiting Well — the same facts, so the same shape — without
   the shape being copied into a second place that can drift. Discharge and the
   care-episode lifecycle share the .np-triage__dsum-grid vocabulary and keep their
   own layout: they do not carry the modifier. */
.np-ww .np-triage__dsum-grid,
.np-ww .np-ww__answers,
.np-triage__dsum-grid--ruled {
    grid-template-columns: auto minmax(0, 1fr);
    /* Zero column gap: the rule has to cross the row unbroken, and a grid gap is a
       hole no element paints in. The breathing room moves onto the label's padding. */
    column-gap: 0;
    row-gap: 0;
}
.np-ww .np-triage__dsum-grid > dt,
.np-ww .np-triage__dsum-grid > dd,
.np-ww .np-ww__answers > dt,
.np-ww .np-ww__answers > dd,
.np-triage__dsum-grid--ruled > dt,
.np-triage__dsum-grid--ruled > dd {
    padding: var(--np-space-2, 8px) 0;
    border-bottom: 1px dotted var(--np-color-border);
}
.np-ww .np-triage__dsum-grid > dt,
.np-ww .np-ww__answers > dt,
.np-triage__dsum-grid--ruled > dt { padding-right: var(--np-space-3, 12px); }
.np-ww .np-triage__dsum-grid > dd,
.np-ww .np-ww__answers > dd,
.np-triage__dsum-grid--ruled > dd { text-align: right; }
/* Chips are flex items, so text-align cannot reach them. */
.np-ww .np-triage__dsum-grid > dd .np-triage__chips,
.np-ww .np-ww__answers > dd .np-triage__chips,
.np-triage__dsum-grid--ruled > dd .np-triage__chips { justify-content: flex-end; }
/* The opt-out, for the one value that is not a fact but a paragraph. Right-aligned
   prose has a ragged left edge and is measurably slower to read; a clinical note is
   written to be read, not scanned, so it keeps the reading edge. */
.np-ww .np-triage__dsum-grid > dd.np-ww__dd--prose { text-align: left; }

/* The support list is a COLUMN — one chip per line, every one ending on the card's right
   edge like every other value in it. It stays in the answer column: this row is a value like
   the rest, and taking the full width made it the one row that started on the left.

   A COLUMN, NOT A WRAPPING ROW, and that is the whole fix for the overlap. A wrapping row
   packs what fits and breaks where it must, so the list had two chips on one line and one on
   the next, each line a different width — and the folded remainder was a second wrapping row
   INSIDE the first, sizing itself from its own widest line rather than from the line it was
   given, which is what put its chips over the ones above. A column has no lines to fit and
   nothing to break, so there is nothing left to overflow. */
.np-ww .np-triage__dsum-grid > dd.np-ww__dd--chips .np-triage__chips {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: flex-end;
    gap: var(--np-space-2, 8px);
}

/* Two columns cannot survive a phone. Stacked, the answer goes back under its label
   and only the answer carries the rule — a border on both would double every line. */
@media (max-width: 40rem) {
    .np-ww .np-triage__dsum-grid,
    .np-ww .np-ww__answers,
    .np-triage__dsum-grid--ruled { grid-template-columns: minmax(0, 1fr); }
    .np-ww .np-triage__dsum-grid > dt,
    .np-ww .np-ww__answers > dt,
    .np-triage__dsum-grid--ruled > dt { border-bottom: 0; padding-bottom: 0; }
    .np-ww .np-triage__dsum-grid > dd,
    .np-ww .np-ww__answers > dd,
    .np-triage__dsum-grid--ruled > dd { text-align: left; padding-top: 2px; }
}
/* ── The pair: record card | check-ins ─────────────────────────────────────────
   On a wide screen the Waiting Well record and the check-in list sit side by side,
   with everything else — the letters trail, the dialogs — full width beneath them.

   Placement rather than a wrapper element, because the two panes are rendered by
   two different partials (the record by _WaitingWellSummary, the pane by
   _WaitingWellContacts) and a wrapper would have to be opened in one file and
   closed in the other. Every child runs full width by default and the two panes
   claim row 1; anything added later lands under them without touching this rule.

   The check-ins pane is capped to the record card's height by np-waiting-well.js
   and scrolls inside itself, so the two cards finish level however long the
   history is. Its head is outside the scroller, so "Check-ins · N recorded" stays
   put. With the script absent the pane is simply full height, as it was before. */
@media (min-width: 992px) {
    .np-ww--paired {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        align-items: start;
    }
    .np-ww--paired > * { grid-column: 1 / -1; }
    .np-ww--paired > .np-ww__pane--record { grid-column: 1; grid-row: 1; }
    .np-ww--paired > .np-ww__pane--checkins { grid-column: 2; grid-row: 1; }
    /* Only ever scrolls while paired — at one column the page's own scroll is the
       right one, and a scroller inside it would trap the history. */
    .np-ww--paired .np-ww__pane-scroll {
        overflow-y: auto;
        padding-right: var(--np-space-2, 8px);
        margin-right: calc(var(--np-space-2, 8px) * -1);
    }
}
/* The badge in a Waiting Well card head sits on the RIGHT edge, so every card in the
   section is titled the same way. .np-triage__src-tag ("3 recorded", "Decision 1") already
   carries margin-left:auto of its own; a status pill does not, so it sat against the title
   on the one card that uses one. Named rather than :last-child on purpose — a head with no
   badge would otherwise push its own title across the card. */
.np-ww__card > .np-triage__card-head > .np-status { margin-left: auto; }

.np-ww__pane--checkins { display: flex; flex-direction: column; min-height: 0; }
.np-ww__pane-scroll { flex: 1 1 auto; min-height: 0; }

/* A support chip is as wide as its own name and wraps to its own line when it will not fit
   beside the one before it — it is never squeezed to share a line. Two chips side by side in
   this column came out cut mid-word ("Communication and b…", "Social interaction and …"),
   which reads as clutter and costs the reader the one thing a chip is for. `flex: 0 0 auto`
   stops the shrink; the width cap moves onto the column itself, so a name longer than the
   whole column still cannot set the pane's width and still truncates — with the full name in
   the chip's title, and in the Update support dialog, exactly as before.

   Chips STACK here where elsewhere they sit on one line, so their gap is row rhythm rather
   than separation: at 6px five supports read as one block of text. */
.np-ww .np-triage__chips { gap: var(--np-space-2, 8px); }
.np-ww .np-triage__chip {
    /* NOT an inline box. A chip carries vertical padding, and an INLINE element's padding
       does not grow its line box — so two chips on consecutive lines of inline text overlap,
       which is what happened the moment they landed anywhere that was not a flex container.
       inline-flex gives the pill a box of its own, so it cannot overlap its neighbour
       wherever it is rendered, flex parent or not. */
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    max-width: 100%;
    padding: 5px 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* The remainder, behind a "+N" toggle. One support shows; the rest are one click away, so a
   record with eight of them does not push the card down a screen.

   A NATIVE <details>, not a scripted show/hide: keyboard-operable for free, works with
   JavaScript off, and the folded chips stay real text in the page — found by Ctrl-F and read
   by a screen reader either way. Nothing is removed from the record, only from the first look.

   A COLUMN, like the list it belongs to. It used to be a wrapping row nested inside a wrapping
   row, which is what made the chips overlap: the inner container sized itself from its own
   widest line rather than from the line the outer one gave it, so once opened its chips ran
   over the ones above them. There are no lines to fit in a column.

   `order` puts the toggle AFTER the chips it reveals, so the column reads "…chips… Show less"
   rather than the reverse — a <summary> must be its parent's first child, which is a DOM rule,
   not a visual one. */
.np-ww__chips-more {
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: flex-end;
    gap: var(--np-space-2, 8px);
}
/* THE CHIPS' OWN CONTAINER, and the reason they stopped overlapping. A browser wraps a
   <details>'s content in a ::details-content box of its own, so the column set on the
   <details> above reaches THAT box — not the chips inside it. They fell back to inline
   layout, where a span's vertical padding does not grow its line box, so two pills on
   consecutive wrapped lines sat on top of each other. Owning the container removes the
   question: this is what the chips are laid out by, wherever the browser puts its box. */
.np-ww__chips-more__list {
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: flex-end;
    gap: var(--np-space-2, 8px);
}
/* Shaped like the chips it sits under — same pill, same height — but in the brand colour and
   carrying an icon, so it reads as the control among the values rather than a sixth support. */
.np-ww__chips-more > summary {
    order: 2;
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-1, 4px);
    padding: 5px 12px;
    border: 1px solid var(--np-color-brand-tint-200, #e8d6ec);
    border-radius: 999px;
    background: transparent;
    font-size: var(--np-fs-small, 14px);
    color: var(--np-color-brand-primary, #741f7b);
    white-space: nowrap;
}
.np-ww__chips-more > summary:hover { background: var(--np-color-brand-tint-50, #faf6fb); }
.np-ww__chips-more > summary::-webkit-details-marker { display: none; }
.np-ww__chips-more > summary:focus-visible {
    outline: 2px solid var(--np-color-brand-primary, #741f7b);
    outline-offset: 2px;
}
/* The icon is the affordance the label spells out — a plus to open, a minus to close. Drawn,
   never written: a "+" in the text would land in the accessible name, where "plus 3 more"
   is not what a screen reader should read out. */
.np-ww__chips-more__icon { flex: 0 0 auto; }
.np-ww__chips-more--shut,
.np-ww__chips-more--open { display: inline-flex; align-items: center; gap: var(--np-space-1, 4px); }
.np-ww__chips-more--open { display: none; }
.np-ww__chips-more[open] .np-ww__chips-more--shut { display: none; }
.np-ww__chips-more[open] .np-ww__chips-more--open { display: inline-flex; }

.np-ww__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--np-space-4, 16px); }
@media (min-width: 992px) { .np-ww__grid--2 { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.np-ww__card { padding: var(--np-space-4, 16px) var(--np-space-5, 20px); }
.np-ww__lede { margin: 0 0 var(--np-space-3, 12px); color: var(--np-color-text-muted, #5b6470); }
.np-ww__note { margin: var(--np-space-2, 8px) 0 0; font-size: var(--np-fs-small, 14px); color: var(--np-color-text-muted, #5b6470); }

/* Areas of need — label, reported impact as text + a five-segment meter. Never colour
   alone: the number is always printed beside the meter. */
/* The rule between rows lives on the row, so the list itself carries no gap. */
.np-ww__areas { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
/* The same row as every other in the section: label at the left edge, answer against the
   right, a dotted rule between. The difference is that the answer is TWO things — a meter
   and the words for it — and they have to hold together.

   This was a wrapping flex row whose label carried `min-width: 12rem` — a floor, not a
   column. Five labels fit inside it and lined up; "Routines & repetitive behaviours" ran
   past it and carried its own meter ~60px right of the rest, which is the one row a
   clinician most needs to compare against the others.

   So the answer cell hugs the right (`auto`, not a share of the row) and its own two
   tracks are the meter and a FIXED reading column. Fixed is what keeps the meters in
   line: every row's reading track is the same width whatever that row's words are, so
   the meter in front of it lands on the same x, and the words still finish on the
   section's answer edge. The label track is then simply what is left over — a long label
   uses more of it, a short one less, and neither moves the meter. */
.np-ww__area {
    display: grid;
    grid-template-columns: minmax(8rem, 1fr) auto;
    align-items: center;
    column-gap: var(--np-space-3, 12px);
    row-gap: var(--np-space-1, 4px);
    padding: var(--np-space-2, 8px) 0;
    border-bottom: 1px dotted var(--np-color-border);
}
.np-ww__area-label { font-weight: 400; }
.np-ww__area-answer {
    display: grid;
    grid-template-columns: auto minmax(0, var(--np-ww-reading-col));
    align-items: center;
    column-gap: var(--np-space-3, 12px);
    justify-items: end;
    text-align: right;
}
/* An unanswered area has no meter, so its words take the whole cell rather than sitting
   in the meter's track directly under the bars above, reading as a rating they are not. */
.np-ww__area-answer > :only-child { grid-column: 1 / -1; }
/* A check-in reads its rating against the pre-assessment one — "Now Moderate 3 of 5 · was
   A lot at pre-assessment · down 1" — so those words need a column of their own size. */
.np-ww__contact-body .np-ww__area { --np-ww-reading-col: 20rem; }
@media (max-width: 640px) {
    /* A phone has no room for a label column and an answer column side by side. Stack,
       and the answer keeps its own line under its label, reading from the left again. */
    .np-ww__area { grid-template-columns: minmax(0, 1fr); }
    .np-ww__area-answer { justify-items: start; text-align: left; }
}
.np-ww__meter { display: inline-flex; gap: 3px; }
.np-ww__meter i { width: 14px; height: 8px; border-radius: 2px; background: var(--np-color-border, #d9dee3); }
.np-ww__meter i.on { background: var(--np-color-brand-primary, #741f7b); }
.np-ww__area-impact { font-size: var(--np-fs-small, 14px); color: var(--np-color-text-muted, #5b6470); }

/* The number behind the word. It is what makes two check-ins comparable, so it
   stays on the page — one step quieter than the word, which is what a reader
   actually understands. */
.np-ww__area-figure { font-size: var(--np-fs-xsmall, 12px); color: var(--np-color-text-muted, #5b6470); }
/* A sub-heading inside a Waiting Well card body. Sits between two blocks of
   answers, so it needs to read as a divider without competing with the card
   title above it — small, muted, with room only above. The product is a single
   weight (TypographyScaleSweepTests), so size and colour do the work. */
.np-ww__subhead {
    font-size: var(--np-fs-small, 14px);
    font-weight: 400;
    color: var(--np-color-text-muted, #5b6470);
    margin: var(--np-space-4, 16px) 0 var(--np-space-2, 8px);
}
.np-ww__contact-body > .np-ww__subhead:first-child { margin-top: 0; }

/* The block of severity scales inside the record-response dialog. Its own
   spacing rather than the dialog's, because each scale is already a fieldset
   with internal padding and the default stack reads as one undifferentiated
   run of boxes. */
.np-ww__areas-ask { margin-bottom: var(--np-space-4, 16px); }

/* ─────────────────────────────────────────────────────────────────────────
   np-ww__support-review — "Support to issue now"
   (_WaitingWellSupportReview.cshtml)

   The panel under the support picker. The picker deals in NAMES; this deals in
   the WORDS the family receives, one collapsible block per chosen support.

   It reads as a nested surface — tinted ground, no shadow — because it is
   subordinate to the picker above it and must not compete with the card it sits
   inside. Native <details>, so the disclosure is keyboard-operable and works
   with JavaScript off; the script only decides which blocks are shown.
   ───────────────────────────────────────────────────────────────────────── */
.np-ww__support-review {
    margin: 0 0 var(--np-space-4, 16px);
    padding: var(--np-space-3, 12px) var(--np-space-4, 16px) var(--np-space-4, 16px);
    background: var(--np-color-brand-tint-50, #faf6fb);
    border: 1px solid var(--np-color-brand-tint-200, #e6d4e9);
    border-radius: var(--np-radius-2, 10px);
}

.np-ww__support-review-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-2, 8px);
}

.np-ww__support-review-head .np-ww__subhead { margin: 0; }

.np-ww__support-empty { margin: var(--np-space-2, 8px) 0 0; font-size: var(--np-fs-small, 14px); }

.np-ww__support {
    margin-top: var(--np-space-2, 8px);
    background: var(--np-color-surface, #fff);
    border: 1px solid var(--np-color-brand-tint-200, #e6d4e9);
    border-radius: var(--np-radius-1, 6px);
}

/* The script hides unchosen blocks with the `hidden` property. Stated
   explicitly rather than left to the UA sheet, where `details { display: block }`
   sits close enough to `[hidden]` to be worth not relying on. */
.np-ww__support[hidden] { display: none; }

.np-ww__support-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-2, 8px);
    padding: var(--np-space-2, 8px) var(--np-space-3, 12px);
    min-height: 44px;         /* WCAG 2.2 SC 2.5.8 */
    cursor: pointer;
    list-style: none;         /* the default triangle, replaced below */
    font-size: var(--np-fs-small, 14px);
}

.np-ww__support-head::-webkit-details-marker { display: none; }

/* A chevron that rotates on open — a disclosure needs a state signal that is not
   the position of the content it discloses. */
.np-ww__support-head::before {
    content: "";
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-right: 1.75px solid var(--np-color-text-muted, #5b6470);
    border-bottom: 1.75px solid var(--np-color-text-muted, #5b6470);
    transform: rotate(-45deg);
    transition: transform 120ms ease;
}

.np-ww__support[open] > .np-ww__support-head::before { transform: rotate(45deg); }

.np-ww__support-head:focus-visible {
    outline: 2px solid var(--np-color-brand-primary, #741f7b);
    outline-offset: -2px;
    border-radius: var(--np-radius-1, 6px);
}

/* Reads as the heading of its block against the muted flags beside it — by colour,
   not weight: the product runs on 400 alone (TypographyScaleSweepTests). */
.np-ww__support-name { color: var(--np-color-text-primary, #1c2430); }

/* Whether this support carries wording at all — pushed to the right so the flags
   line up down the list and can be scanned in one pass. "Name only" is not an
   error, so it is muted rather than red (§6.6: red is for a state that needs
   acting on, and this one is the administrator's to act on, not the reader's). */
.np-ww__support-flag {
    margin-left: auto;
    font-size: var(--np-fs-xsmall, 12px);
    color: var(--np-color-brand-primary, #741f7b);
    background: var(--np-color-brand-tint-100, #f3e9f5);
    border: 1px solid var(--np-color-brand-tint-200, #e6d4e9);
    border-radius: 999px;
    padding: 2px 9px;
}

.np-ww__support-flag--bare {
    color: var(--np-color-text-muted, #5b6470);
    background: var(--np-color-surface-alt, #f5f6f8);
    border-color: var(--np-color-border, #d9dee3);
}

.np-ww__support-body {
    padding: 0 var(--np-space-3, 12px) var(--np-space-3, 12px);
    border-top: 1px solid var(--np-color-border, #d9dee3);
    padding-top: var(--np-space-3, 12px);
}

.np-ww__support-areas {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-2, 8px);
    margin: 0 0 var(--np-space-2, 8px);
}

/* The administrator's own paragraph breaks, kept — WITHOUT accepting any markup.
   The description is rendered as text (Razor encodes it) and pre-line is what
   turns a typed blank line back into a blank line. */
.np-ww__support-text {
    margin: 0;
    white-space: pre-line;
    font-size: var(--np-fs-small, 14px);
    color: var(--np-color-text-primary, #1c2430);
}

/* The per-area dialog renders its supports as plain blocks rather than disclosures —
   the dialog was opened TO read them, so there is nothing left to fold away. */
.np-ww__support--flat { display: block; }
.np-ww__support--flat > .np-ww__support-head { cursor: default; }
.np-ww__support--flat > .np-ww__support-head::before { content: none; }

/* ─────────────────────────────────────────────────────────────────────────
   np-ww__start — THE GUIDED START (_WaitingWellSummary.cshtml, not-started card).

   One question at a time: start it, is support going out now, which areas, how
   often do we check in, complete. The steps are SERVER-RENDERED in one form;
   np-waiting-well.js only hides the ones not reached yet. With JavaScript off
   nothing here is hidden and the card is one long form — which is exactly what it
   was before it was stepped.

   [hidden] is honoured explicitly on the fieldset steps: a <fieldset> carries a
   UA display value that a bare [hidden] does not always beat.
   ───────────────────────────────────────────────────────────────────────── */
.np-ww__step { margin: 0 0 var(--np-space-4, 16px); }
.np-ww__step:last-child { margin-bottom: 0; }
.np-ww__step[hidden] { display: none; }

/* A fieldset step carries no border or padding of its own — the card is the frame,
   and a box inside a box reads as two levels of grouping where there is one. */
fieldset.np-ww__step,
.np-ww__areas-choice {
    border: 0;
    padding: 0;
    margin-inline: 0;
    min-width: 0;
}

.np-ww__areas-choice { margin: 0; }

/* The question itself. A legend rather than a heading: it names the controls under
   it, which is what a screen reader announces with each one.

   ON THE SECTION'S OWN SCALE, not the page's. A <legend> inherits the body size
   (16px) unless told otherwise, and this card runs at 14px throughout —
   np-triage__card-title, np-label, np-hint, np-select, np-btn and np-ww__note are
   every one of them --np-fs-small. The same applies to the two controls below,
   which are labels wrapping a native input and would otherwise inherit 16px too.
   Nothing custom here: the shared token, matching what surrounds it. */
.np-ww__step-legend {
    display: block;
    width: 100%;
    padding: 0;
    margin: 0 0 var(--np-space-3, 12px);
    font-size: var(--np-fs-small, 14px);
    color: var(--np-color-text-primary, #1c2430);
}

.np-ww__step--ask + .np-ww__step,
.np-ww__step--areas + .np-ww__step {
    padding-top: var(--np-space-4, 16px);
    border-top: 1px solid var(--np-color-border, #d9dee3);
}

/* The two answers to "would you like to issue support now?". Stacked, because they
   are full sentences and a side-by-side pair truncates the longer one first.
   Sized here rather than on .np-radio: that component is shared with the Clinics,
   Rooms and Schedule-builder screens, which sit on the page scale and must stay
   there — this is the Waiting Well card matching its own surroundings. */
.np-ww__choices { display: flex; flex-direction: column; gap: var(--np-space-2, 8px); }
.np-ww__choices .np-radio { font-size: var(--np-fs-small, 14px); }

/* ── The areas of need, as ticks ──
   A per-patient list, not a closed-list picker (§6.5): each row carries the impact
   the family reported and its own "read what will be sent" action, neither of which
   fits in a chip. Native checkbox in its label — role, state and keyboard come from
   the platform (ui.accessibility → multiple values from a closed list). */
.np-ww__choice-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--np-space-2, 8px);
}

.np-ww__choice {
    display: flex;
    align-items: center;
    gap: var(--np-space-2, 8px);
}

/* The label takes the row so the whole strip is the hit target for the tick, and the
   view action sits outside it — clicking "read this" must never also tick it. On the
   card's 14px scale for the same reason as the legend above: .np-checkbox declares no
   size of its own and is shared with screens that sit on the page scale. */
.np-ww__choice-label {
    flex: 1 1 auto;
    min-width: 0;
    align-items: flex-start;
    font-size: var(--np-fs-small, 14px);
}

.np-ww__choice-text {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--np-space-1, 4px) var(--np-space-3, 12px);
    min-width: 0;
}

.np-ww__choice-name { color: var(--np-color-text-primary, #1c2430); }

.np-ww__choice-meta {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2, 8px);
}

.np-ww__choice .np-row-action { flex: 0 0 auto; }

/* ─────────────────────────────────────────────────────────────────────────
   np-sevscale — THE 1–5 severity control (_NpSeverityScale.cshtml).

   The answerable twin of the read-only meters above (np-ww__meter,
   np-triage__meter). It deliberately looks like them: a value entered here is
   read back there, and a control that does not resemble its own read-only
   form is a control people mis-read.

   NATIVE RADIOS UNDERNEATH. The boxes are <label>s wrapping real
   <input type="radio">, so arrow keys, roving focus and posting without
   JavaScript all come from the platform (§3.11). The input itself is
   visually hidden — NOT display:none, which removes it from the
   accessibility tree and from keyboard focus — so the label is the whole hit
   target and the focus ring lands on it.

   THE VALUE IS NEVER CARRIED BY COLOUR ALONE (§6.6 / WCAG 1.4.1). Three
   independent signals: every box shows its own number; the chosen box is
   ringed and its number goes bold; and the fill is cumulative, which is a
   position signal a reader gets from the shape of the row.
   ───────────────────────────────────────────────────────────────────────── */
.np-sevscale {
    border: 0;
    padding: 0;
    margin: 0 0 var(--np-space-4, 16px);
    min-width: 0;   /* fieldsets default to min-content; this lets them flex */
}

/* Beat `.np-form-section legend` (a descendant selector that draws a full-width
   rule) without !important — the same fix .np-radio-set__legend applies. */
.np-form-section .np-sevscale__legend,
.np-sevscale__legend {
    display: block;
    width: auto;
    font-size: var(--np-fs-small, 14px);
    font-weight: 400;
    color: var(--np-color-text-primary, #1c2430);
    border-bottom: 0;
    margin: 0 0 var(--np-space-2, 8px) 0;
    padding: 0;
}

.np-sevscale__scale {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--np-space-2, 8px) var(--np-space-3, 12px);
}

/* The five options as ONE ROW that wraps, sized by their words rather than by a
   fixed square: the widest word ("A great deal") sets the track and the rest
   match it, so the row reads as one scale instead of five ragged buttons.
   `flex: 1 1 0` over a min-width lets five options share a wide field evenly and
   fall to two rows on a narrow one, never to a horizontal scroll. */
.np-sevscale__boxes {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2, 8px);
    width: 100%;
}

.np-sevscale__box {
    position: relative;
    display: inline-flex;
    /* CENTRE, not baseline. The box is taller than its text — min-height 44px against a
       single 14px line — and baseline alignment puts the line box at the top of the free
       space and leaves the remainder underneath, so every step sat visibly high in its own
       border. Baseline was there to line the number up with the word beside it; both are
       --np-fs-small, so centring aligns them identically and also centres the pair in the
       box. It is what "I'd rather not say" and the Yes/No options in the same card already
       do, which is why those two looked right and the five steps did not. */
    align-items: center;
    justify-content: center;
    gap: 6px;
    flex: 1 1 0;
    /* Small enough that five fit one row in a modal, large enough that the longest
       word ("A great deal") wraps to two lines rather than to five. */
    min-width: 6.5rem;
    min-height: 44px;         /* WCAG 2.2 SC 2.5.8 — a 44px target, on a phone */
    padding: var(--np-space-2, 8px) var(--np-space-3, 12px);
    border: 1px solid var(--np-color-border, #d9dee3);
    border-radius: var(--np-radius-1, 6px);
    background: var(--np-color-surface, #fff);
    color: var(--np-color-text-primary, #1c2430);
    cursor: pointer;
    line-height: 1.3;
    text-align: center;
}

/* Visually hidden, NOT display:none — the radio must stay focusable and stay
   in the accessibility tree, or the control loses keyboard operation and its
   checked state entirely. */
.np-sevscale__box input[type="radio"],
.np-sevscale__none input[type="radio"] {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
}

/* The number stays, and stays quiet: it is the join back to the read-only meters
   (np-ww__meter, np-triage__meter) and to the stored value, but the WORD is what
   a person reads to answer. */
.np-sevscale__num {
    font-weight: 400;
    font-size: var(--np-fs-small, 14px);
    color: var(--np-color-text-muted, #5b6470);
}

/* The separator is decoration, so it is drawn rather than written — it must not
   land in the accessible name, which is built from the number and the word. */
.np-sevscale__num::after { content: "\00b7"; padding-left: 6px; }

.np-sevscale__word { font-size: var(--np-fs-small, 14px); }

/* ONE step is marked, because one step is the answer. The fill used to be
   cumulative to match the read-only meters; with a word on every option that
   read as three answers to a single-answer question. Three independent signals
   carry the choice, so none of it rests on colour (§6.6 / WCAG 1.4.1): the fill,
   the ring, and the weight of the word.

   KEYED ON :checked, NOT ON A SERVER-STAMPED CLASS. This painted
   .np-sevscale__box--chosen, which the partial writes from the value the record
   HELD when the page was rendered. A class cannot follow a click: choosing a step
   moved :checked and left the class where it was, so the scale sat inert under the
   pointer — no fill on the step just chosen, and the old one still lit. The "I'd
   rather not say" option never had the bug because it was always keyed on
   :has(input:checked), which is why that one alone turned purple.

   The radio still carries `checked` from the server, so :checked is ALSO the
   stored state on first paint — one selector for both, and no way for the two to
   disagree. */
.np-sevscale__box:has(input:checked) {
    background: var(--np-color-brand-primary, #741f7b);
    border-color: var(--np-color-brand-primary, #741f7b);
    color: #fff;
    box-shadow: 0 0 0 2px var(--np-color-surface, #fff), 0 0 0 4px var(--np-color-brand-primary, #741f7b);
}

/* The number inherits the filled box's own colour, or it stays muted grey on
   purple and drops below contrast. */
.np-sevscale__box:has(input:checked) .np-sevscale__num { color: inherit; }

.np-sevscale__box:hover:not(:has(input:disabled)) { border-color: var(--np-color-brand-primary, #741f7b); }

/* :focus-visible only — :focus-within also matches a mouse click and would
   leave a permanent ring on whichever box was last clicked. */
.np-sevscale__box:has(input:focus-visible),
.np-sevscale__none:has(input:focus-visible) {
    outline: 2px solid var(--np-color-brand-primary, #741f7b);
    outline-offset: 3px;
    border-radius: var(--np-radius-1, 6px);
}

.np-sevscale__none {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2, 8px);
    margin-top: var(--np-space-2, 8px);
    padding: var(--np-space-1, 4px) var(--np-space-2, 8px);
    border: 1px solid var(--np-color-border, #d9dee3);
    border-radius: var(--np-radius-1, 6px);
    cursor: pointer;
    font-size: var(--np-fs-small, 14px);
    color: var(--np-color-text-muted, #5b6470);
}

/* Selected reads as selected by the RING around it — a shape, not a hue — as well as
   by the filled border and the checked radio itself, never by colour alone. (Weight
   would be the obvious signal; the product runs on 400 alone.) */
.np-sevscale__none:has(input:checked) {
    border-color: var(--np-color-brand-primary, #741f7b);
    color: var(--np-color-text-primary, #1c2430);
    box-shadow: 0 0 0 2px var(--np-color-surface, #fff), 0 0 0 4px var(--np-color-brand-primary, #741f7b);
}

/* Read-only copy of a submitted form: the whole thing sits inside a disabled
   <fieldset>, so this styles the disabled state rather than adding a class.
   pointer-events off so a greyed control cannot be clicked and look live. */
.np-sevscale__box:has(input:disabled),
.np-sevscale__none:has(input:disabled) {
    cursor: not-allowed;
    pointer-events: none;
    opacity: 0.75;
}

@media (max-width: 640px) {
    /* Five labelled options do not fit a phone in portrait side by side. They
       stack full width and left-align — a centred word in a full-width row
       leaves the eye no column to run down. The targets keep their height and
       give up width, never the other way round: height is the dimension a thumb
       misses. */
    .np-sevscale__box {
        flex: 1 1 100%;
        justify-content: flex-start;
        text-align: left;
    }
}


/* Actions row under the record summary. */
.np-ww__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--np-space-2, 8px); margin-top: var(--np-space-3, 12px); }
/* "Close — no response" is a POST, so its button sits inside a <form>. Without this the
   form is a block-level flex item with its own line box, and the button inside it lands a
   few pixels off the two beside it. Flattened so all three align on one baseline. */
.np-ww__actions .np-inline-form { display: inline-flex; margin: 0; }

/* The Waiting Well dialogs compose the house <dialog> chrome (`np-modal np-triage__modal`)
   and inherit its 40rem width. Field spacing is the house's own: .np-form-group carries a
   bottom margin, and .np-form-grid zeros it in favour of the grid gap. A dialog-level
   `.np-form-group + .np-form-group { margin-top }` once lived here and leaked into the
   two-up grid, dropping the right-hand cell 12px below the left. Do not add one back. */
/* The Record-response dialog carries nine questions plus the record check; it earns a wider
   column so the two-up selects sit side by side and the radio rows do not wrap.

   ELEMENT-QUALIFIED on purpose. `dialog.np-triage__modal` sets max-width AND width at
   (0,1,1), and an unqualified `.np-ww__dialog--wide` (0,1,0) loses to it regardless of
   source order — which is exactly how this dialog rendered at 640px while declaring 760.
   Same specificity, later in the file, so this one wins. Still viewport-bounded: on a phone
   it is the screen minus a margin, and the body scrolls inside the chrome. */
dialog.np-ww__dialog--wide { max-width: 56rem; width: min(56rem, calc(100vw - 2rem)); }
.np-ww__question + .np-ww__question { margin-top: var(--np-space-3, 12px); }
.np-ww__record-check { margin-top: var(--np-space-4, 16px); }
.np-ww__record-check > summary {
    cursor: pointer; font-weight: 400; font-size: var(--np-fs-small, 14px);
    padding: var(--np-space-2, 8px) 0;
}
.np-ww__record-check-body { padding-top: var(--np-space-2, 8px); }

/* ── The numbered check-in loop ──
   The current contact opens and earlier ones stay shut, so the history can only be
   read one way. Closed contacts take the surface-alt ground — the "archive colour"
   that tells a reader at a glance which one is live. */
.np-ww__contact-list { display: flex; flex-direction: column; gap: var(--np-space-2, 8px); margin-top: var(--np-space-4, 16px); }
.np-ww__contact {
    border: 1px solid var(--np-color-border, #d9dee3);
    border-radius: var(--np-radius-2, 10px);
    background: var(--np-color-surface-alt, #f7f8f9);
}
/* The open one is the live row, and says so with a brand-tinted GROUND and a border on
   all four sides — never a left accent stripe (ui.design-system → "No Left-Accent Cards").
   The status pill inside it carries the meaning; this only says "read me first". */
.np-ww__contact--open {
    background: var(--np-color-brand-tint-50, #f6f1f7);
    border: 1px solid var(--np-color-brand-primary, #741f7b);
    margin-top: var(--np-space-3, 12px);
    /* No padding of its own: it is a <details> like every other check-in, and the
       summary and body carry their own. */
}
.np-ww__contact-summary {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--np-space-2, 8px) var(--np-space-3, 12px);
    padding: var(--np-space-3, 12px) var(--np-space-4, 16px);
    min-height: 44px;         /* WCAG 2.2 SC 2.5.8 */
    cursor: pointer; list-style: none;
}
.np-ww__contact-summary::-webkit-details-marker { display: none; }
/* A chevron that rotates on open — the same disclosure signal .np-ww__support-head
   uses. A collapsible card with no marker reads as a static one, which is exactly
   what the open check-in used to be. */
.np-ww__contact-summary::before {
    content: "";
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-right: 1.75px solid var(--np-color-text-muted, #5b6470);
    border-bottom: 1.75px solid var(--np-color-text-muted, #5b6470);
    transform: rotate(-45deg);
    transition: transform 120ms ease;
}
.np-ww__contact[open] > .np-ww__contact-summary::before { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) {
    .np-ww__contact-summary::before { transition: none; }
}
.np-ww__contact-summary:focus-visible {
    outline: 2px solid var(--np-color-brand-primary, #741f7b);
    outline-offset: -2px; border-radius: var(--np-radius-2, 10px);
}
.np-ww__contact-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--np-space-3, 12px); }
.np-ww__contact-title { margin: 0; font-size: var(--np-fs-small); font-weight: 400; }
.np-ww__contact-meta { font-size: var(--np-fs-small, 14px); color: var(--np-color-text-muted, #5b6470); }
.np-ww__contact-body { padding: 0 var(--np-space-4, 16px) var(--np-space-4, 16px); }

/* The five answers. A two-column definition list so the question and its answer sit on
   one line — a clinician reads down the answers, not across the questions. */
.np-ww__answers {
    display: grid; grid-template-columns: minmax(0, auto) minmax(0, 1fr);
    gap: var(--np-space-1, 4px) var(--np-space-4, 16px);
    margin: 0 0 var(--np-space-3, 12px);
}
.np-ww__answers dt { font-size: var(--np-fs-small, 14px); color: var(--np-color-text-muted, #5b6470); }
.np-ww__answers dd { margin: 0; }
/* A Yes is the answer that routes, so it is the one that has to be findable. Weight
   AND colour — never colour alone (§6.6). */
.np-ww__answer--yes { font-weight: 400; color: var(--np-color-danger-fg, #8a1f11); }
/* The TERM carries it too, so the question and its answer read as one flagged fact rather
   than a red value sitting beside a muted grey label. Element-qualified because
   `.np-ww__answers dt` (0,1,1) sets the muted colour and would otherwise win over the
   plain class (0,1,0); this selector is (0,2,1) and takes it back. */
.np-ww__answers dt.np-ww__answer--yes { font-weight: 400; color: var(--np-color-danger-fg, #8a1f11); }

/* The family's own words. Quoted, and captioned so a reader knows it is verbatim. */
.np-ww__quote { margin: 0 0 var(--np-space-3, 12px); }
.np-ww__quote blockquote {
    margin: 0; padding: var(--np-space-3, 12px) var(--np-space-4, 16px);
    border: 1px solid var(--np-color-border, #d9dee3);
    background: var(--np-color-surface-alt, #f7f8f9);
    border-radius: var(--np-radius-2, 10px);
}
.np-ww__quote figcaption {
    margin-top: var(--np-space-1, 4px);
    font-size: var(--np-fs-small, 14px); color: var(--np-color-text-muted, #5b6470);
}

/* The correspondence trail. */
.np-ww__messages { margin-bottom: 0; }
.np-ww__messages td { vertical-align: top; }

/* Appointment email previews are edited in the standard editable editor (not the
   read-only viewer), so normalise the rendered body to the application theme font
   — every appointment email preview reads consistently regardless of the email's
   own inline font styles (CLAUDE.md §3.11 / email-preview font rule). */
.np-appt-email .np-composer__editor,
.np-appt-email .np-composer__editor * {
    font-family: var(--np-font-sans) !important;
}

/* Dynamic merge values and the Accept / Reject buttons are locked in the preview:
   the clinician may edit the template's own wording, but never the patient-specific
   data merged into it. Locked regions are non-editable (contenteditable=false) and
   greyed so it is obvious they cannot be changed. The np-locked wrappers are stripped
   server-side before the email is sent, so the recipient sees clean values. */
.np-appt-email .np-locked {
    background: var(--np-color-surface-tinted, #f3f4f6);
    color: var(--np-color-text-secondary, #55606a);
    border-radius: var(--np-radius-1, 4px);
    padding: 0 3px;
    cursor: default;
    -webkit-user-select: none;
    user-select: none;
}
/* An Accept / Reject button inside a locked region reads as a disabled, greyed
   affordance in the preview (it is live only in the sent email). */
.np-appt-email .np-locked a {
    pointer-events: none;
    opacity: 0.6;
}

/* Non-editable header fields (Subject, recipient) are greyed to match the locked
   body regions — the clinician cannot change who the email is to or its subject. */
.np-appt-email .np-input[readonly] {
    background: var(--np-color-surface-tinted, #f3f4f6);
    color: var(--np-color-text-secondary, #55606a);
    cursor: not-allowed;
}

/* ============================================================
   Schedule builder (Scheduling Settings → Schedules)
   ============================================================ */
.np-info-panel {
    background: var(--np-color-info-bg);
    /* Borderless by design — matches the np-panel treatment (no accent stripe). */
    color: var(--np-color-info-fg);
    padding: var(--np-space-3, 12px) var(--np-space-4, 16px);
    border-radius: var(--np-radius-1, 4px);
    margin: var(--np-space-3, 12px) 0;
}
.np-radio-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: var(--np-space-3, 12px);
}
.np-radio {
    display: flex;
    align-items: flex-start;
    gap: var(--np-space-2, 8px);
    cursor: pointer;
    padding: var(--np-space-3, 12px);
    border: 2px solid var(--np-color-border);
    border-radius: var(--np-radius-1, 4px);
    transition: border-color 0.15s ease, background-color 0.15s ease;
}
.np-radio:hover { background: var(--np-color-brand-tint-50); }
.np-radio:has(input:checked) {
    border-color: var(--np-color-brand-primary);
    background: var(--np-color-brand-tint-50);
}
.np-radio input[type="radio"] {
    width: 20px;
    height: 20px;
    margin-top: 2px;
    accent-color: var(--np-color-brand-primary);
}
.np-day-toggles {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2, 8px);
}
.np-day-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2, 8px);
    padding: var(--np-space-2, 8px) var(--np-space-3, 12px);
    border: 2px solid var(--np-color-border);
    border-radius: var(--np-radius-1, 4px);
    cursor: pointer;
    user-select: none;
}
.np-day-toggle:has(input:checked) {
    border-color: var(--np-color-brand-primary);
    background: var(--np-color-brand-tint-50);
    font-weight: 400;
}
.np-day-toggle input[type="checkbox"] {
    width: 18px;
    height: 18px;
    accent-color: var(--np-color-brand-primary);
}
.np-break-row {
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-1, 4px);
    padding: var(--np-space-3, 12px);
    margin-bottom: var(--np-space-3, 12px);
}

/* Pick-a-slot booking picker */
.np-slot-picker {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2, 8px);
    margin-top: var(--np-space-2, 8px);
}
.np-slot-btn { padding: var(--np-space-2, 8px) var(--np-space-3, 12px); }
.np-slot-btn--overbook { border-color: #8a5a00; color: #8a5a00; }
.np-slot-btn--selected {
    background: var(--np-color-brand-primary);
    color: #fff;
    border-color: var(--np-color-brand-primary);
}

/* Locked manual fields when a slot-based day requires picking a slot */
.np-locked {
    background: var(--np-color-neutral-100, #eee);
    pointer-events: none;
    opacity: 0.7;
}

/* Next-availability modal */
.np-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--np-space-4, 16px);
    z-index: 1000;
}
.np-modal {
    background: var(--np-color-surface, #fff);
    color: inherit;
    border-radius: var(--np-radius-1, 4px);
    padding: var(--np-space-4, 16px);
    max-width: 640px;
    width: 100%;
    max-height: 85vh;
    overflow: auto;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}
.np-modal__title { margin: 0 0 var(--np-space-2, 8px); }
.np-modal__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--np-space-2, 8px);
    margin-top: var(--np-space-3, 12px);
}
.np-na-day { margin-bottom: var(--np-space-3, 12px); }
.np-na-day__label { font-weight: 400; margin-bottom: var(--np-space-2, 8px); }
.np-na-slots { display: flex; flex-wrap: wrap; gap: var(--np-space-2, 8px); }

/* ── Availability CALENDAR (week grid): dates across columns, times down rows ── */
.np-modal--wide { max-width: 960px; }

.np-week-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-2, 8px);
    margin: var(--np-space-2, 8px) 0 var(--np-space-3, 12px);
}
.np-week-nav__label { font-weight: 400; text-align: center; flex: 1 1 auto; }

/* Horizontal scroll on narrow screens — the page body must never scroll. */
.np-week-grid-wrap { overflow-x: auto; }

.np-week-grid {
    border-collapse: collapse;
    width: 100%;
    min-width: 640px;
    table-layout: fixed;
}
.np-week-grid th,
.np-week-grid td {
    border: 1px solid var(--np-color-border, #d9dee5);
    text-align: center;
    padding: var(--np-space-1, 4px);
    vertical-align: middle;
}
.np-week-grid__corner,
.np-week-grid__time {
    width: 68px;
    background: var(--np-color-surface-alt, #f4f6f8);
    font-weight: 400;
    white-space: nowrap;
    position: sticky;
    left: 0;
    z-index: 1;
}
.np-week-grid__day { background: var(--np-color-surface-alt, #f4f6f8); line-height: 1.2; }
.np-week-grid__wd { display: block; font-size: var(--np-fs-small, 0.8rem); color: var(--np-color-text-muted, #5a6572); }
.np-week-grid__dt { display: block; font-weight: 400; }
.np-week-grid__day--today { background: var(--np-color-primary-soft, #e6f0ff); }
.np-week-grid__day--today .np-week-grid__wd { color: var(--np-color-primary, #0b5cad); }
.np-week-grid__day--past { opacity: 0.5; }

.np-week-grid__cell { padding: 3px; }
.np-week-grid__cell--empty { background: repeating-linear-gradient(135deg, transparent, transparent 5px, rgba(0,0,0,0.03) 5px, rgba(0,0,0,0.03) 6px); }

.np-week-cell {
    display: block;
    width: 100%;
    border: 1px solid var(--np-color-success, #1f8a4c);
    background: var(--np-color-success-soft, #e5f6ec);
    color: var(--np-color-success-strong, #14663a);
    border-radius: var(--np-radius-1, 4px);
    padding: var(--np-space-1, 4px) 2px;
    font-size: var(--np-fs-small, 0.8rem);
    font-weight: 400;
    cursor: pointer;
    line-height: 1.15;
}
.np-week-cell:hover { background: var(--np-color-success, #1f8a4c); color: #fff; }
.np-week-cell:focus-visible { outline: 2px solid var(--np-color-primary, #0b5cad); outline-offset: 1px; }
.np-week-cell__occ { display: block; }
.np-week-cell--overbook {
    border-color: #8a5a00;
    background: #fff3e0;
    color: #8a5a00;
}
.np-week-cell--overbook:hover { background: #8a5a00; color: #fff; }
.np-week-cell--selected { outline: 2px solid var(--np-color-primary, #0b5cad); outline-offset: 1px; }

/* ── Merged slot selection (booking picker) ──
   Two or more CONSECUTIVE slots taken as ONE appointment, for a patient who needs
   longer than the slot length. Every cell in the run is marked, so the run is
   visible as a run rather than as one selected cell with a longer duration
   silently attached to it. Selection is never signalled by colour alone: the cells
   carry aria-pressed, and the bar below states the span in words. */
.np-week-cell--merged {
    outline-color: var(--np-color-brand-primary);
    box-shadow: inset 0 0 0 1px var(--np-color-brand-primary);
}

/* Summary + extend/shrink bar under the availability grid. Hidden entirely for a
   clinic that books one slot at a time, so that picker is untouched. */
.np-week-merge {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-3);
    margin-top: var(--np-space-3);
    padding: var(--np-space-2) var(--np-space-3);
    border: 1px solid var(--np-color-brand-tint-200);
    border-radius: var(--np-radius-md, 8px);
    background: var(--np-color-brand-tint-100);
}
.np-week-merge__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.np-week-merge__summary { font-weight: 400; color: var(--np-color-text-primary); }
/* Why the next slot can or can't be added — shown, not just hovered. A disabled
   button with no visible reason is the commonest dead end in a picker. */
.np-week-merge__hint { color: var(--np-color-text-secondary); font-size: var(--np-fs-small); }
.np-week-merge__actions { display: inline-flex; gap: var(--np-space-2); flex-wrap: wrap; }

/* ============================================================
   Patient list → Report tab
   ============================================================
   Recipient chips (_ReportRecipientChips.cshtml). Each chip carries THREE
   signals — a dot (shape), the state word (text) and a colour — so it reads
   correctly in greyscale and at 200% zoom. The legacy tab used colour alone
   (a purple vs grey envelope), which is a WCAG 1.4.1 failure and the reason
   this component exists. Tones follow the §6.6 vocabulary: green = delivered,
   amber = awaiting an action, red = clinical-safety (a report nobody
   received), grey = not applicable. */
.np-report-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin: 0;
    padding: 0;
    list-style: none;
}
.np-report-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 1px 7px;
    border: 1px solid transparent;
    border-radius: 999px;
    font-size: 0.72rem;
    line-height: 1.5;
    white-space: nowrap;
}
.np-report-chip__dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    flex: 0 0 auto;
}
.np-report-chip__kind { font-weight: 400; }
.np-report-chip__state { opacity: 0.85; }

.np-report-chip--sent {
    background: #e8f5ed;
    border-color: #b7dfc7;
    color: #1f6b3f;
}
/* Both reds, and deliberately the SAME red: in each case this recipient does not
   have the report (§6.6 — no action taken). They keep separate class names because
   they are fixed by different actions, and the chip's own text ("Not sent" vs
   "Failed") is what distinguishes them — colour is never the only signal. */
.np-report-chip--failed,
.np-report-chip--notsent {
    background: #fdecec;
    border-color: #f2bcbc;
    color: #a11b1b;
}
/* No address on record: an ADDRESS-QUALITY problem, not a delivery one. Amber
   like "awaiting an action", but dashed so it is visibly a different kind of
   outstanding item from "not sent yet" — pressing Send cannot resolve it. */
.np-report-chip--noaddress {
    background: #fff5e6;
    border-color: #8a5a00;
    border-style: dashed;
    color: #8a5a00;
}
.np-report-chip--na {
    background: var(--np-color-surface-muted, #f4f5f7);
    border-color: var(--np-color-border, #d9dde3);
    color: var(--np-color-text-muted, #5b6472);
}

/* Report-tab cell accents: the "· Closed" episode flag beside the age, and the
   version stamp beside a report name. Both are secondary to the value they
   annotate, never a status pill in their own right (§6.6). */
.np-cell__flag {
    font-weight: 400;
    color: var(--np-color-text-muted, #5b6472);
}
.np-cell__version {
    font-weight: 400;
    color: var(--np-color-text-muted, #5b6472);
}

/* Filter chips (Report tab toolbar). Checkbox + label + count in one control,
   so the count a user reads is attached to the control that applies it. */
.np-filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0 0 var(--np-space-3, 12px);
    padding: 0;
    list-style: none;
}
.np-filter-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border: 1px solid var(--np-color-border, #d9dde3);
    border-radius: 999px;
    background: var(--np-color-surface, #fff);
    font-size: 0.8rem;
    cursor: pointer;
}
.np-filter-chip:hover { background: var(--np-color-brand-tint-50, #f1f5ff); }
.np-filter-chip:focus-within {
    outline: 2px solid var(--np-color-primary, #0b5cad);
    outline-offset: 1px;
}
.np-filter-chip--on {
    border-color: var(--np-color-primary, #0b5cad);
    background: var(--np-color-brand-tint-50, #f1f5ff);
    font-weight: 400;
}
.np-filter-chip__count {
    color: var(--np-color-text-muted, #5b6472);
    font-variant-numeric: tabular-nums;
}
.np-filter-chip--on .np-filter-chip__count { color: inherit; }

/* ─────────────────────────────────────────────────────────────────────────
   Task notification badge (sidebar)
   .planning/TaskManagement-Design.md §11

   Shows how many tasks are outstanding for the signed-in user. Sits beside
   the nav label, not on top of the icon, so it survives the collapsed rail.

   ACCESSIBILITY — three things this must never do:
     1. Signal by COLOUR ALONE (§6.6 / WCAG 1.4.1). The badge always carries
        the count as text and an aria-label spelling out what it means.
     2. Animate for users who have asked it not to — the urgent pulse is
        switched off under prefers-reduced-motion (vestibular safety). The
        badge itself STAYS; only the movement goes.
     3. Announce loudly. aria-live="polite" (set on the element) queues the
        update rather than interrupting.

   The urgent variant reuses the np-status--danger palette so red means the
   same thing here as it does in every status column.
   ───────────────────────────────────────────────────────────────────────── */
.np-aside__badge {
    flex: 0 0 auto;
    min-width: 1.375rem;
    margin-left: auto;
    padding: 2px 7px;
    font-size: 0.6875rem;
    font-weight: 400;
    line-height: 1.4;
    text-align: center;
    border-radius: var(--np-radius-pill);
    background: rgba(var(--np-color-brand-primary-rgb), 0.16);
    color: var(--np-aside-fg);
    /* Pulse RING (product owner, Oct 2026): the same ring, colour and rhythm as
       the workspace section icons and Reminders (np-accordion-pulse). The pill
       itself stays still — only the ring radiates from its edge, so the count
       never moves under the reader's eye. A badge renders only when there is
       outstanding work (NavBadgeModel.HasAny), so every badge rings. */
    animation: np-accordion-pulse 2.4s ease-in-out infinite;
}

.np-aside__link--active .np-aside__badge {
    background: rgba(255, 255, 255, 0.24);
    color: var(--np-aside-active-fg);
    /* On the brand-filled active link a brand ring would vanish — white instead. */
    animation-name: np-badge-pulse-light;
}

/* Urgent — red, and its ring is red, until every urgent task has been picked
   up (requirement item 4). Same rhythm and width as every other ring; red is
   the np-status--danger palette, so it means the same here as in a status
   column. */
.np-aside__badge--urgent {
    background: #FBE7E9;
    color: #8A1F1F;
    animation-name: np-task-pulse;
}

.np-aside__link--active .np-aside__badge--urgent {
    background: #F0C4C8;
    color: #6E1717;
    animation-name: np-task-pulse;
}

@keyframes np-task-pulse {
    0%   { box-shadow: 0 0 0 0   rgba(138, 31, 31, 0.42); }
    70%  { box-shadow: 0 0 0 5px rgba(138, 31, 31, 0);    }
    100% { box-shadow: 0 0 0 0   rgba(138, 31, 31, 0);    }
}
@keyframes np-badge-pulse-light {
    0%   { box-shadow: 0 0 0 0   rgba(255, 255, 255, 0.6); }
    70%  { box-shadow: 0 0 0 5px rgba(255, 255, 255, 0);   }
    100% { box-shadow: 0 0 0 0   rgba(255, 255, 255, 0);   }
}

/* Vestibular safety — keep the badge, drop the movement. */
@media (prefers-reduced-motion: reduce) {
    .np-aside__badge,
    .np-aside__link--active .np-aside__badge,
    .np-aside__badge--urgent,
    .np-aside__link--active .np-aside__badge--urgent {
        animation: none;
    }
}

/* Collapsed rail: the label is hidden, so pin the badge to the icon corner
   rather than letting it collapse to zero width. */
.np-aside--collapsed .np-aside__badge {
    position: absolute;
    top: 4px;
    right: 6px;
    margin-left: 0;
    min-width: 1.125rem;
    padding: 1px 5px;
    font-size: 0.625rem;
}

/* ─────────────────────────────────────────────────────────────────────────
   Radio set — a labelled group of native radios.

   WHY THIS EXISTS: `np-checkbox--inline` was being reused for radios. That
   modifier sets `gap: 0` because it is built for a TABLE CELL, where the label
   is visually-hidden and there is nothing to space away from the control. Used
   with a visible label the text jams against the radio.

   It also fixes a second bug: `.np-form-section legend` sets a full-width
   h3-sized `border-bottom`, and that DESCENDANT selector also matched the
   <legend> of any nested <fieldset> — so every sub-group drew a heavy rule
   across the card. `.np-radio-set__legend` resets it back to label weight.

   Native <input type="radio"> throughout — an interactive control is never a
   bare div (CLAUDE.md §3.11), so keyboard, arrow-key navigation within the
   group, and form posting all come free from the platform.

   NAMING — every option class is scoped `np-radio-set__*`, NOT the bare
   `.np-radio`. `.np-radio` is already taken by the schedule builder's
   radio-CARDS (line ~8586), which are bordered 2px boxes. A generic `.np-radio`
   here collided with it: these options inherited a card border, and the
   schedule builder would have inherited this component's rules back. Two
   components, two names.
   ───────────────────────────────────────────────────────────────────────── */
.np-radio-set {
    border: 0;
    padding: 10px;
    margin: 0;
    min-width: 0;   /* fieldsets default to min-content; this lets them flex */
}

/* …but NOT when it is one field of a form grid. There the padding is measured
   against the sibling fields either side of it, and 10px of it sits the radio
   set's legend a visible step below their <label>s — the row then reads as
   three controls at three heights. The grid's own gap already separates the
   cells, so the padding has nothing left to do. Scoped to a direct grid child
   so a standalone radio set (a dialog, a filter bar) keeps its breathing room. */
.np-form-grid > .np-form-group > .np-radio-set { padding: 0; }

/* Beat `.np-form-section legend` (descendant selector) without !important. */
.np-form-section .np-radio-set__legend,
.np-radio-set__legend {
    display: block;
    width: auto;
    font-size: var(--np-fs-small);
    font-weight: 400;
    color: var(--np-color-text-primary);
    border-bottom: 0;
    margin: 0 0 var(--np-space-2) 0;
    padding: 0;
}

.np-radio-set__options {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2) var(--np-space-4);
    align-items: center;
}

/* Stacked variant — one option per line, for options that carry help text. */
.np-radio-set__options--stacked {
    flex-direction: column;
    align-items: stretch;
    gap: var(--np-space-1);
}

/* NO BORDER and no filled background — this is a plain inline radio, not a
   selectable card. The only decoration is the gap beside the control. */
.np-radio-set__option {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);          /* the fix — real space beside the control */
    cursor: pointer;
    padding: var(--np-space-1) 0;
    margin-right: var(--np-space-5);
    border: 0;
    background: none;
    line-height: 1.35;
}

/* Keyboard focus ring on the WRAPPER, so tabbing lights up the whole hit target
   rather than an 18px circle.

   :focus-visible ONLY — never :focus-within. :focus-within also matches a MOUSE
   click (clicking a radio focuses it), which drew a permanent purple ring around
   whichever option you had just selected and read as a border on the selected
   row. :focus-visible is the browser's "this user is navigating by keyboard"
   signal, which is exactly when the ring is wanted. */
.np-radio-set__option:has(input:focus-visible) {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 3px;
    border-radius: var(--np-radius-1);
}

.np-radio-set__option input[type="radio"] {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
}

.np-radio-set__label { line-height: 1.35; }

/* Selected option reads as selected by WEIGHT as well as by the filled dot —
   never colour alone (§6.6 / WCAG 1.4.1). */
.np-radio-set__option:has(input:checked) .np-radio-set__label {
    font-weight: 400;
}

.np-radio-set__meta {
    color: var(--np-color-text-muted);
    font-weight: 400;
}

.np-radio-set__option input[type="radio"]:disabled { cursor: not-allowed; opacity: 0.5; }
.np-radio-set__option:has(input:disabled) { cursor: not-allowed; opacity: 0.65; }

/* Nested sub-options — a left rail makes "these belong to the option above"
   visible rather than implied by proximity alone. */
.np-radio-set--nested {
    margin: var(--np-space-2) 0 0 var(--np-space-4);
    padding-left: var(--np-space-4);
    border-left: 2px solid var(--np-color-border);
}

/* The reveal panel for whichever option needs a picker. */
.np-radio-reveal {
    margin: var(--np-space-3) 0 0 var(--np-space-4);
    padding-left: var(--np-space-4);
    border-left: 2px solid var(--np-color-border);
}

/* Plain-language summary of who a task will reach. */
.np-assign-summary {
    margin-top: var(--np-space-4);
    padding: var(--np-space-3) var(--np-space-4);
    border-radius: var(--np-radius-1);
    background: var(--np-color-brand-tint-50);
    /* Tinted background + hairline border — no left accent stripe. */
    border: 1px solid var(--np-color-brand-tint-200);
    font-size: var(--np-fs-small);
}

/* [hidden] must win inside the flex option list — a flex container's children
   would otherwise keep their box when the UA's display:none is overridden. */
.np-radio-set__options > [hidden],
.np-radio-reveal[hidden] { display: none; }

/* ─────────────────────────────────────────────────────────────────────────
   np-qrows — QUESTION ROWS: the question on one line, its options on the next,
   in a card, with every option the same width.

   THE CARD IS THE POINT. A questionnaire read as a stack of "legend, then a row
   of options, then the next legend" gives the eye nothing to run down: each
   answer starts at whatever x the one above it happened to end at, and the only
   thing separating one question from the next is white space. Ruled rows inside
   a single surface turn the same questions into a table — a column of questions,
   a column of answers, and a line between every pair.

   TWO LINES, NOT TWO COLUMNS. This first put the question and its options side
   by side. Side by side they fight for the same width: the option tracks have a
   fixed minimum and the question column does not, so on anything narrower than a
   wide monitor the options won, the question column collapsed toward nothing and
   the boxes ended up sitting on top of the text. Stacked, the question gets the
   full width and so do the options — and because the options then divide that
   same width every time, they line up in columns down the whole card no matter
   how long any one question runs.

   THE ANSWERS ARE BOXES, NOT BARE RADIOS. Underneath, each box is still a native
   <input type="radio"> in a native <label> (§3.11): the input is CLIPPED, never
   display:none, so it keeps keyboard focus, arrow-key navigation within the
   group, the checked state a screen reader announces, and posting with
   JavaScript off. Same technique np-sevscale has always used for its steps —
   this extends it to the yes/no answers beside them so one form has one look.

   SELECTED IS THE FILL. Hover is the tint, chosen is the brand fill, and there
   is no outer ring: a ring on top of the fill read as a second border and made
   the chosen box look like a different component from its neighbours.

   THE COLUMNS ARE GATED ON THERE BEING ROOM. Below 56rem six labelled options
   cannot divide a line without clipping their own words, so the card, the rules
   and the boxes stay and only the option row falls back to the control's own
   wrapping layout.
   ───────────────────────────────────────────────────────────────────────── */
.np-qrows {
    background: var(--np-color-surface, #fff);
    border: 1px solid var(--np-color-border, #ead8ee);
    border-radius: var(--np-radius-2, 10px);
    margin: 0 0 var(--np-space-4, 16px);
}

/* Every row: the same padding, and a rule that runs the FULL width of the card.
   The padding is on the row rather than the card precisely so the rule can —
   a separator that stops short of the edge reads as a border on the content
   instead of a division between two questions. */
.np-qrows > .np-sevscale,
.np-qrows > .np-radio-set,
.np-qrows > .np-form-group,
.np-qrows > .np-qrows__stack,
.np-qrows > .np-qrows__head {
    margin: 0;
    padding: var(--np-space-3, 12px) var(--np-space-4, 16px);
    border-bottom: 1px solid var(--np-color-border, #ead8ee);
}

/* The last row's rule would sit on the card's own border. */
.np-qrows > :last-child { border-bottom: 0; }

/* ── The heading block ─────────────────────────────────────────────────────
   The section's heading and its instruction are the card's FIRST row. Outside
   the card they sat hard against the page margin while every question below
   them was indented by the row padding, so the block read as two things that
   had drifted apart. In the card they take the same padding as the questions
   and the same rule beneath, and the whole section lines up on one left edge. */
.np-qrows__head > :last-child { margin-bottom: 0; }

/* ── The answer boxes ────────────────────────────────────────────────────── */
.np-qrows .np-radio-set__option {
    position: relative;              /* anchors the clipped radio */
    justify-content: center;
    gap: 0;
    margin: 0;
    padding: var(--np-space-2, 8px);
    min-height: 44px;                /* WCAG 2.2 SC 2.5.8 — a 44px target */
    border: 1px solid var(--np-color-border, #ead8ee);
    border-radius: var(--np-radius-2, 10px);
    background: var(--np-color-surface, #fff);
    font-size: var(--np-fs-small, 14px);
    text-align: center;
    white-space: nowrap;
}

/* Clipped, NOT display:none — the radio must stay focusable and stay in the
   accessibility tree, or the control is operable by mouse only. */
.np-qrows .np-radio-set__option input[type="radio"] {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
}

/* The scale's own boxes take the same shape as the answers beside them. */
.np-qrows .np-sevscale__box {
    padding: var(--np-space-2, 8px);
    border-radius: var(--np-radius-2, 10px);
    white-space: nowrap;
}

/* "Not answered" is the last box of the scale, not a footnote under it. It is
   one more choice in the same single-answer question and only reads as one when
   it is the same size and on the same line as the other five. */
.np-qrows .np-sevscale__none {
    justify-content: center;
    margin: 0;
    padding: var(--np-space-2, 8px);
    min-height: 44px;
    border-radius: var(--np-radius-2, 10px);
    color: var(--np-color-text-primary, #1c2430);
    text-align: center;
    white-space: nowrap;
}

/* SELECTED IS THE FILL, and nothing else — the house segmented-control state.
   The non-colour signal is the checked <input type="radio"> underneath, which
   is clipped rather than removed and so is still reported to assistive tech.
   Weight would be the obvious visual one; the product runs on 400 alone. */
.np-qrows .np-radio-set__option:has(input:checked),
.np-qrows .np-sevscale__box:has(input:checked),
.np-qrows .np-sevscale__none:has(input:checked) {
    background: var(--np-color-brand-primary, #741f7b);
    border-color: var(--np-color-brand-primary, #741f7b);
    color: #fff;
    box-shadow: none;
}

/* The number and its separator invert with the box, or they stay muted grey on
   purple and drop below contrast. */
.np-qrows .np-sevscale__box:has(input:checked) .np-sevscale__num { color: inherit; }

.np-qrows .np-radio-set__option:hover:not(:has(input:disabled)),
.np-qrows .np-sevscale__box:hover:not(:has(input:disabled)):not(:has(input:checked)),
.np-qrows .np-sevscale__none:hover:not(:has(input:disabled)):not(:has(input:checked)) {
    background: var(--np-color-brand-tint-50, #faf6fb);
    border-color: var(--np-color-brand-primary, #741f7b);
}

/* :focus-visible only — :focus-within also matches a mouse click and would
   leave a permanent ring on whichever box was last clicked. */
.np-qrows .np-radio-set__option:has(input:focus-visible) {
    outline: 2px solid var(--np-color-brand-primary, #741f7b);
    outline-offset: 3px;
}

/* Read-only copy of a submitted form: the whole thing sits inside a disabled
   <fieldset>, so this styles the disabled state rather than adding a class. */
.np-qrows .np-radio-set__option:has(input:disabled) {
    cursor: not-allowed;
    pointer-events: none;
    opacity: 0.75;
}

/* ── The question line ─────────────────────────────────────────────────────
   The legend is one line of its own, and the hint sits under it. Both take the
   full width of the row; nothing shares it with them. */
.np-qrows .np-sevscale__legend,
.np-qrows .np-radio-set__legend {
    display: flex;
    gap: var(--np-space-2, 8px);
    margin: 0;
    color: var(--np-color-text-primary, #1c2430);
}

/* The question number, drawn beside the question rather than written into it.
   The five are referred to BY NUMBER on the phone, in the letter and on the
   clinician's own copy of the form, so the number is navigation: it is
   aria-hidden in the markup, leaving the accessible name the question exactly
   as written. */
.np-qrows__num {
    flex: 0 0 auto;
    color: var(--np-color-text-muted, #5b6470);
}

/* A row whose answer is a whole control — a picker, a narrative box — rather
   than a set of options. Same card, same padding, same rule. */
.np-qrows > .np-qrows__stack > .np-label,
.np-qrows > .np-qrows__stack > .field-label { display: block; }

/* The remark box a question opens when it is answered Yes is `.np-radio-reveal`
   — the house panel for exactly this, already carrying the nesting rail that
   says "this belongs to the option above" and already exempt from the
   left-accent sweep as a rail rather than a card. It needs nothing here but its
   place in the row; `.np-radio-reveal[hidden]` is handled with the rest of the
   radio set above.

   The read-back of one: the follow-up prompt is a quieter row label than the
   question it hangs off, so a reader's eye goes to the answers first and picks
   up the explanation on the way past rather than reading five prompts as five
   more questions. */
.np-ww__answer-remark-label { color: var(--np-color-text-muted, #5b6470); }
.np-ww__answer-remark { white-space: pre-line; }

/* Several questions ACROSS one row, for answers that are a <select>: stacked,
   three of those made a section twice the height of the questions it belongs
   with. Every label here is ONE LINE, which is what makes the controls line up
   — the grid's own `align-items: start` then puts every select at the same
   height with no further help. An earlier version grew the label to fill the
   cell instead, which pushed each select down by however much text happened to
   sit below it and left the three at three different heights. */
.np-qrows__fields > .np-form-group > .np-label {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

@media (min-width: 56rem) {
    /* THE OPTION LINE. Every option divides the same full-width row, so a
       section's answers form columns however long its questions run. */
    .np-qrows > .np-sevscale,
    .np-qrows > .np-radio-set {
        display: grid;
        align-items: center;
        column-gap: var(--np-space-2, 8px);
        row-gap: 0;
    }

    /* Six equal columns, and `display: contents` twice over lifts each box out
       of its two wrappers into that grid — so the boxes ARE the columns, with
       "not answered" as the sixth. */
    .np-qrows > .np-sevscale { grid-template-columns: repeat(6, minmax(0, 1fr)); }

    .np-qrows .np-sevscale__scale,
    .np-qrows .np-sevscale__boxes { display: contents; }

    .np-qrows .np-sevscale__box,
    .np-qrows .np-sevscale__none {
        width: auto;
        min-width: 0;
        margin-top: var(--np-space-2, 8px);
    }

    .np-qrows > .np-radio-set { grid-template-columns: minmax(0, 1fr); }

    /* One track per answer, all equal, filling the line — two answers halve it,
       three take a third each, and both land on the same right edge. */
    .np-qrows .np-radio-set__options {
        display: grid;
        grid-auto-flow: column;
        grid-auto-columns: minmax(0, 1fr);
        gap: var(--np-space-2, 8px);
        margin-top: var(--np-space-2, 8px);
    }

    .np-qrows .np-sevscale__legend,
    .np-qrows .np-radio-set__legend,
    .np-qrows .np-sevscale > .np-hint,
    .np-qrows .np-radio-set > .np-hint,
    .np-qrows .np-radio-set__options,
    .np-qrows .np-radio-reveal { grid-column: 1 / -1; }

    .np-qrows .np-sevscale > .np-hint,
    .np-qrows .np-radio-set > .np-hint { margin: 2px 0 0; }
}

/* Assigned-clinician roster shown on the create-task form, so "Everyone on the
   team" is never an unknown quantity — the clinician sees exactly who will be
   notified before sending. Read-only; selection happens in the picker below. */
.np-assign-roster {
    margin: var(--np-space-2) 0 var(--np-space-3) var(--np-space-4);
    padding: var(--np-space-3) var(--np-space-4);
    border-left: 2px solid var(--np-color-border);
    background: var(--np-color-surface-subtle, transparent);
}

.np-assign-roster__title {
    display: block;
    font-size: var(--np-fs-small);
    font-weight: 400;
    color: var(--np-color-text-primary);
    margin-bottom: var(--np-space-2);
}

.np-assign-roster__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2) var(--np-space-3);
}

.np-assign-roster__item {
    display: inline-flex;
    align-items: baseline;
    gap: var(--np-space-2);
    padding: var(--np-space-1) var(--np-space-3);
    border-radius: var(--np-radius-pill);
    background: var(--np-color-brand-tint-50);
    font-size: var(--np-fs-small);
}

.np-assign-roster__name { font-weight: 400; }

.np-assign-roster__meta {
    color: var(--np-color-text-muted);
    font-weight: 400;
}

/* "Clear all (N)" — the undo for Select all. Styled as a quiet text action, not
   a chip, so it never reads as a selected value. Deliberately not in the
   dropdown: with everything selected the list is empty and a row there would be
   unreachable.

   PINNED to the control's top-right corner rather than trailing the last chip:
   trailing, it shifted with every chip added or removed and could land under the
   pointer of someone clearing chips one at a time. The control reserves a right
   gutter for it (--has-clear) so no chip or the search box runs underneath. */
.np-multiselect__control { position: relative; }
.np-multiselect__control--has-clear { padding-right: 124px; }
.np-multiselect__clear-all[hidden] { display: none; }
.np-multiselect__clear-all {
    position: absolute;
    top: 8px;
    right: var(--np-space-2);
    padding: 2px var(--np-space-2);
    border: 0;
    background: none;
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-small);
    font-weight: 400;
    text-decoration: underline;
    cursor: pointer;
    border-radius: var(--np-radius-1);
}

.np-multiselect__clear-all:hover {
    color: var(--np-color-danger-fg, #8A1F1F);
    background: var(--np-color-danger-bg, #FBE7E9);
    text-decoration: none;
}

.np-multiselect__clear-all:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
    text-decoration: none;
}

/* ── GP practice picker (np-gp-practice-picker.js) ─────────────────────────
   Typeahead over the GP practice master. Selection state is never colour
   alone: the active option carries a background AND aria-selected, and a
   closed practice is labelled in text, not tinted (§6.6 / WCAG 1.4.1). */
.np-gp-picker { position: relative; }

/* The live region that announces "N practices found." / "Selected …". It is for
   screen readers only — sighted users can see the list itself — so it carries
   the clip-rect here rather than depending on a utility class from another
   stylesheet (the control ships to the anonymous public form as well as the
   authenticated app, and must not assume Bootstrap is loaded). */
.np-gp-picker__status {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.np-gp-picker__results {
    position: absolute;
    z-index: 30;
    left: 0;
    right: 0;
    margin: 2px 0 0;
    padding: 4px 0;
    list-style: none;
    max-height: 18rem;
    overflow-y: auto;
    background: var(--np-color-surface, #fff);
    border: 1px solid var(--np-color-border, #d5dae2);
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(16, 24, 40, .12);
}

.np-gp-picker__option {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 12px;
    cursor: pointer;
}

.np-gp-picker__option:hover,
.np-gp-picker__option.is-active {
    background: var(--np-color-surface-alt, #eef2f7);
}

/* Keyboard users must see the active option as clearly as mouse users. */
.np-gp-picker__option:focus-visible {
    outline: 2px solid var(--np-color-focus, #1c64f2);
    outline-offset: -2px;
}

.np-gp-picker__name { font-weight: 400; }

.np-gp-picker__meta {
    font-size: .8125rem;
    color: var(--np-color-text-muted, #5b6472);
}

/* Closed practices stay selectable — a patient may still be registered there —
   but are labelled so nobody picks a defunct practice by accident. */
.np-gp-picker__closed {
    font-size: .75rem;
    font-weight: 400;
    color: var(--np-color-text-muted, #5b6472);
}

.np-gp-picker__origin {
    font-size: .75rem;
    color: var(--np-color-text-muted, #5b6472);
}

.np-gp-picker__empty,
.np-gp-picker__add {
    padding: 8px 12px;
    font-size: .875rem;
    color: var(--np-color-text-muted, #5b6472);
}

.np-gp-picker__add {
    cursor: pointer;
    font-weight: 400;
    color: var(--np-color-primary, #1c64f2);
}

.np-gp-picker__add:hover { background: var(--np-color-surface-alt, #eef2f7); }

/* ============================================================================
   ADMINISTRATOR DASHBOARD
   ----------------------------------------------------------------------------
   Component styles for the dashboard shell and its cards.

   Every colour here is a TOKEN. Nothing states a hex, which is what lets the
   whole surface — chrome, icons, highlights and charts alike — follow the
   organisation's theme preset with no per-widget work: _NpThemeVars.cshtml
   swaps the token block and the page re-colours on the next load.

   Chart marks read --np-chart-s1..s6 (categorical) and --np-chart-o1..o6
   (ordinal). Anything that means good/warning/danger reads the STATUS tokens
   instead, which no preset overrides (§6.6) — so "send failed" is the same red
   in every organisation, and always carries a dot + label, never colour alone.
   ============================================================================ */

/* Waiting-band colours: which side of the 18-week standard a case is on, and
   nothing else. Mixed from the STATUS tokens rather than stated as hex, so they
   stay in step with every other good/late signal in the product and are not
   overridden by an organisation's theme preset (§6.6).

   The soft steps exist only so two adjacent bands on the same side of the line
   have a visible boundary — same meaning, different end of it. */
:root {
    /* TWO tokens, not four. The soft steps were only there because two adjacent
       bands of one colour had no visible boundary inside a segmented bar; the
       waiting-time profile is columns now, so position and the axis labels do
       that job — and both soft mixes sat around 2:1 against the card, under the
       3:1 a graphical object needs to be perceivable (WCAG 1.4.11). */
    --np-band-ok:        var(--np-status-fill-success);
    --np-band-late:      var(--np-status-fill-danger);
}

/* space-5 between sections, not space-7. The tier heading already carries a rule
   and an accent bar, so it separates its own section without needing a third of a
   screen of air above it — and that air was costing roughly one card row per
   section on a laptop viewport. */
.np-dash { display: flex; flex-direction: column; gap: var(--np-space-5); }

/* ── header ───────────────────────────────────────────────────────────────── */
/* A white banner card rather than bare text on the page background.

   The dashboard opens on a wall of white cards, and a plain heading above them
   gave the page no anchor — the eye landed in the middle of the grid. Giving the
   header the same card treatment as everything below it gives the page a top
   edge and makes the controls read as belonging to the page rather than to the
   first card.

   Built ENTIRELY from surface/border tokens, so it follows each tenant's
   configured theme instead of freezing a colour into the dashboard (§3.14). */
.np-dash__head {
    display: flex; flex-wrap: wrap; gap: var(--np-space-3) var(--np-space-4);
    /* Centre, not flex-end. With the heading gone this row is a scope line on
       the left and its controls on the right; baseline-aligning a 13px sentence
       to a 34px control just floats the sentence off the bottom of the row. */
    align-items: center; justify-content: space-between;
    position: relative;
    padding: var(--np-space-4) var(--np-space-5);
    /* Rounded on all four corners, like every other control and card on the
       page. It used to be square-bottomed because the tab rail tucked under it;
       the rail is its own card now, so the banner has no reason to be a
       half-shape. */
    border-radius: var(--np-radius-3);
    /* Plain card surface — not the hero gradient this once carried. A
       full-strength brand fill on the widest element of the busiest screen
       out-shouted the data underneath it: on a dashboard the numbers are the
       content and the header is only a label for them. Being a normal surface
       also means everything on the banner uses ordinary page text tokens, so
       nothing here has to be restated for a dark fill. */
    background: var(--np-color-surface);
    color: var(--np-color-text-primary);
    /* Card treatment, so the banner reads as the first surface of the page
       rather than a coloured band above it — the same white + hairline every
       panel below it uses. */
    border: 1px solid var(--np-color-border);
    overflow: hidden;
}
.np-dash__head .np-dash__title { color: var(--np-color-text-primary); }
.np-dash__head .np-dash__meta { color: var(--np-color-text-secondary); }

@media (max-width: 767.98px) {
    .np-dash__head { padding: var(--np-space-4); }
}
@media (forced-colors: active) {
    /* The tint is not painted here, so the banner needs a real edge or it merges
       into the page. */
    .np-dash__head { border: 1px solid CanvasText; color: CanvasText; }
    .np-dash__head .np-dash__meta { color: CanvasText; }
}

/* The banner is a light tinted surface, so the scope toggle and the clinician
   filter keep their ordinary light-surface styling — there are deliberately no
   on-brand overrides here. Restoring a dark fill would mean restoring the glass
   treatment (translucent track, white labels, inverted active pill) with it. */

.np-dash__title { font-size: var(--np-fs-h2); margin: 0; letter-spacing: -0.02em; }
.np-dash__head-titles { min-width: 0; flex: 1 1 16rem; }
.np-dash__meta {
    /* No top margin any more: the h1 above it is visually hidden, so the margin
       was pushing this line off the row centre line for no visible heading. */
    margin: 0; font-size: var(--np-fs-small); color: var(--np-color-text-secondary);
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--np-space-2);
}

/* Scope toggle. Rendered only when the caller genuinely holds both scopes.
   Segmented control: the recessed track IS the grouping, so the outline the
   track used to carry is redundant with it. */
.np-dash__scope { display: inline-flex; gap: 3px; padding: 3px; border-radius: var(--np-radius-pill);
    background: var(--np-color-surface-tinted); }
.np-dash__scope-btn {
    padding: 7px 16px; border-radius: var(--np-radius-pill); text-decoration: none;
    font-size: var(--np-fs-small); font-weight: 400; color: var(--np-color-text-secondary);
    transition: background .16s ease, color .16s ease, box-shadow .16s ease;
}
.np-dash__scope-btn:hover { color: var(--np-color-text-primary); background: var(--np-color-surface); }
.np-dash__scope-btn.is-active {
    background: var(--np-color-brand-primary); color: var(--np-color-text-on-brand);
    font-weight: 400; box-shadow: var(--np-shadow-1);
}

/* ── header controls: scope + filter, one row ─────────────────────────────── */
/* ONE row of controls, scoping everything below it — never inside a card and
   never per-card, because a per-chart filter lets two charts on one page
   describe different populations.

   It sits INSIDE the header rather than in a band of its own. A full-width
   filter card cost ~90px of first-screen height to hold one dropdown, and the
   first screen is the whole point of a dashboard. Grouped with the scope toggle
   it also reads correctly: these are the page's controls, not content. */
/* One control group, right-aligned, every control on the same height so the
   filter, the Clear link and the scope toggle share a single baseline. Mixed
   control heights on one row is what made this read as three parts bolted
   together rather than one bar. */
.np-dash__controls {
    display: flex; flex-wrap: wrap; align-items: center;
    justify-content: flex-end; gap: var(--np-space-2) var(--np-space-3);
    flex: 0 1 auto;
}
.np-dash__controls > * { flex: 0 0 auto; }
.np-dash__filters { display: flex; align-items: center; gap: var(--np-space-2); }
/* Sized to its CONTENT, capped so a long clinician name cannot push the scope
   toggle off the row. The label is visually hidden — "All clinicians" as the
   resting option says what the control does without spending a line on it. */
.np-dash__filters .np-select--compact {
    /* 34px tall to match .np-dash__scope (3px padding + a 28px pill), so the two
       controls sit as a matched pair rather than two near-misses. */
    min-width: 11rem; max-width: 15rem;
    height: 34px; padding-top: 0; padding-bottom: 0;
    font-size: var(--np-fs-small);
}
.np-dash__filter-clear {
    display: inline-flex; align-items: center; height: 34px;
    font-size: var(--np-fs-small); color: var(--np-color-text-secondary);
    text-decoration: underline; white-space: nowrap;
}
.np-dash__filter-clear:hover { color: var(--np-color-text-primary); }
.np-dash__filter-clear:focus-visible {
    outline: 2px solid var(--np-color-focus); outline-offset: 2px; border-radius: var(--np-radius-1);
}

/* ── tab strip ────────────────────────────────────────────────────────────── */
/* One tab per section, replacing a page the reader had to scroll end to end to
   find any one of six groups.

   Underline tabs rather than the pill treatment used by the scope toggle above
   them, and that difference is deliberate: the two controls sit within ~60px of
   each other and do completely different jobs. The scope toggle changes WHICH
   RECORDS are counted; the tabs change WHICH SECTION is shown. Rendering both as
   the same segmented pill would read as one four-or-seven-way control.

   The active tab is marked by weight, colour AND a 2px underline — the underline
   is the shape signal, so the selection survives greyscale, print and
   forced-colors where colour alone would not (WCAG 1.4.1). */
/* An elevated white RAIL that overlaps the banner's lower edge, rather than a
   line of links on the page background. Two things come from the overlap: the
   banner and the grid read as one composition instead of two stacked blocks, and
   the rail gets a surface of its own to sit the active pill on. */
.np-dash__tabs {
    display: flex;
    gap: var(--np-space-1, 4px);
    position: relative;
    z-index: 1;
    margin: 0 var(--np-space-4);
    padding: var(--np-space-2);
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-3);
    /* Hairline, not a double shadow. The rail no longer floats over the banner,
       so the heavy lift it needed to read as "on top" is now just haze. */
    box-shadow: none;
    /* Narrow viewports scroll the rail sideways rather than wrapping it to a
       second row that pushes every card down the page. */
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
}
/* The overlap is scoped to the ADJACENT case, and that is the whole point of the
   selector rather than a flat negative margin.

   The rail only tucks under the banner when it actually follows it. When
   something is wrong, the pinned "Urgent attention" section renders BETWEEN the
   two — and an unconditional negative margin pulled the rail up over the alert
   card instead, sitting the tab pills on top of a clinical-risk figure. The
   sibling combinator makes that structurally impossible: no adjacency, no pull.

   The alert section stays in the DOM while hidden, so this correctly declines to
   overlap on a page that HAS alerts pending. If they all report "nothing to
   show", np-dashboard.js removes the section outright, the rail becomes adjacent
   and the overlap applies — the layout follows the DOM rather than a guess made
   when the page was served.

   The parent (.np-dash) is a flex column with a 24px gap, and that gap applies
   regardless of margins, so the offset cancels it first and then overlaps:
   -(gap + 18px) nets an 18px overlap. */
/* No negative pull. The banner is a closed rounded card now, so tucking the
   rail under its bottom edge would clip a corner radius against a straight one.
   Both sit in the .np-dash flex column and its gap does the spacing. */

@media (max-width: 575.98px) {
    .np-dash__tabs { margin-left: var(--np-space-2); margin-right: var(--np-space-2); }
}
@media (forced-colors: active) {
    .np-dash__tabs { border: 1px solid CanvasText; }
}
.np-dash__tab {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    padding: 9px var(--np-space-4);
    border-radius: var(--np-radius-2);
    text-decoration: none;
    white-space: nowrap;
    font-size: var(--np-fs-small);
    font-weight: 400;
    color: var(--np-color-text-secondary);
    position: relative;
    transition: color .16s ease, background .16s ease, box-shadow .16s ease;
}
.np-dash__tab:hover {
    color: var(--np-color-brand-primary);
    background: var(--np-color-surface-tinted);
}

/* The active tab is a TINTED pill with a brand ring, not an underlined label.

   The fill plus the ring is the shape signal - it survives greyscale and print
   where a colour-only difference would not (WCAG 1.4.1), and is reinforced by
   the brand-coloured label. An underline was the earliest treatment and was too
   quiet; a solid dark-purple fill replaced it and read as too heavy against a
   page of white cards, so the pill now carries the same tint as the patient
   banner. brand-primary on brand-tint-200 is ~7:1 on the default preset. Dark
   appearance overrides the label colour below - see the note there. */
.np-dash__tab.is-active {
    color: var(--np-color-brand-primary);
    background: var(--np-color-brand-tint-200);
    /* Inset ring, not a border: a real border would add 2px to the pill and
       shift every tab beside it. The ring is the non-colour signal that keeps
       the active tab distinguishable in greyscale and print (WCAG 1.4.1),
       now that the fill is a pale tint rather than a solid brand block. */
    
}
.np-dash__tab.is-active:hover {
    color: var(--np-color-brand-primary);
    background: var(--np-color-brand-tint-100);
}
/* Dark appearance: tint-200 is a dark slate on every preset, so brand-primary
   ink on it lands at ~2.6:1 - below AA. Every surface that takes the tint-200
   fill therefore takes the primary text colour in dark instead. Keep this list
   in step with the tint-200 fills above. */
:root[data-np-appearance="dark"] .np-header,
:root[data-np-appearance="dark"] .np-header__brand-mark,
:root[data-np-appearance="dark"] .np-hero,
:root[data-np-appearance="dark"] .np-userbtn__avatar,
:root[data-np-appearance="dark"] .np-avatar,
:root[data-np-appearance="dark"] .np-multiselect__done,
:root[data-np-appearance="dark"] .np-multiselect__done:hover,
:root[data-np-appearance="dark"] .np-multiselect__done:focus,
:root[data-np-appearance="dark"] .np-landing__product-logo,
:root[data-np-appearance="dark"] .np-dash-card__icon,
:root[data-np-appearance="dark"] .np-appearance__apply,
:root[data-np-appearance="dark"] .np-dash__tab.is-active,
:root[data-np-appearance="dark"] .np-dash__tab.is-active:hover {
    color: var(--np-color-text-primary);
}
.np-dash__tab:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 2px;
}

/* The glyph is what makes a tab findable at a glance once the rail is muscle
   memory - the reader stops reading labels and goes straight to the calendar or
   the hourglass.

   Idle glyphs take brand-SECONDARY, not brand-accent: accent is only guaranteed
   3:1 on white (the non-text UI threshold), whereas secondary is guaranteed AA
   for normal text on every preset, which leaves headroom. On the active pill the
   glyph inherits the inverted text colour. */
.np-dash__tab-icon {
    display: grid; place-items: center;
    width: 20px; height: 20px; flex: 0 0 auto;
    color: var(--np-color-brand-secondary);
    opacity: .8;
    transition: color .16s ease, opacity .16s ease;
}
.np-dash__tab:hover .np-dash__tab-icon { color: var(--np-color-brand-primary); opacity: 1; }
.np-dash__tab.is-active .np-dash__tab-icon { color: currentColor; opacity: 1; }

@media (forced-colors: active) {
    /* Gradients are not painted in this mode, so the pill would vanish and the
       selection with it. */
    .np-dash__tab.is-active {
        background: Highlight;
        color: HighlightText;
        forced-color-adjust: none;
    }
    .np-dash__tab-icon { color: CanvasText; }
    .np-dash__tab.is-active .np-dash__tab-icon { color: HighlightText; }
}

/* Below tablet the labels alone overflow the strip and force a scroll on every
   viewport, so the inactive tabs show their icon only — that is what they are
   recognised by at that width — and the ACTIVE tab keeps its label, so the strip
   always says in words which section is open.

   Clipped, NOT display:none. A hidden label is removed from the accessibility
   tree, and since the icon beside it is aria-hidden that would leave the tab
   with no accessible name at all — a row of unnamed tabs to a screen-reader
   user. Clipping keeps the name and only takes the pixels back. */
@media (max-width: 575.98px) {
    .np-dash__tab { padding: 10px var(--np-space-2); }
    .np-dash__tab .np-dash__tab-label {
        position: absolute; width: 1px; height: 1px;
        margin: -1px; padding: 0; overflow: hidden;
        clip-path: inset(50%); white-space: nowrap; border: 0;
    }
    .np-dash__tab.is-active .np-dash__tab-label {
        position: static; width: auto; height: auto;
        margin: 0; overflow: visible; clip-path: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .np-dash__tab::after { transition: none; }
}

/* A panel is focusable (the tabs pattern moves focus into it), but it is a
   region rather than a control — an outline ring around the whole section on a
   mouse click would be noise. :focus-visible only. */
.np-dash__panel:focus { outline: none; }
.np-dash__panel:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 4px;
    border-radius: var(--np-radius-2);
}

/* The tab already names the section, so a panel prints only the description.
   Without the accent bar and heading beside it, it needs its own top margin to
   sit clear of the strip. */
.np-dash__panel-sub { margin-top: 0; }

@media (forced-colors: active) {
    /* Gradients are not rendered under forced-colors, so the active marker has
       to be repainted in a system colour or the selection vanishes entirely. */
    .np-dash__tab.is-active::after { background: Highlight; }
    .np-dash__tab.is-active .np-dash__tab-icon { background: Highlight; color: HighlightText; }
    .np-dash__tab-icon { color: CanvasText; }
}

/* ── tiers ────────────────────────────────────────────────────────────────── */
/* The section header has to survive being scrolled past at speed. Four signals,
   none of them relying on the others:
     * a brand accent bar — a SHAPE, so the section marker survives greyscale,
       print and forced-colors, where colour alone would not;
     * a heading a full step larger than the card titles under it;
     * the description on its own line rather than trailing the title, so the
       heading edge is a clean vertical the eye can run down;
     * a hairline the full width of the grid, which closes the section above and
       opens the one below.
   Together they read as a section title rather than a line of bold text. */
.np-dash__tier { display: flex; flex-direction: column; gap: var(--np-space-3); }
.np-dash__tier-head {
    display: flex; align-items: flex-start; gap: var(--np-space-3);
    padding-bottom: var(--np-space-2); border-bottom: 1px solid var(--np-color-border);
}
/* The section marker is not painted. Product decision: the left accent rail
   read as a status indicator — the same shape the calendar uses to encode
   appointment state — when it only ever marked a section. The rule under the
   heading already separates one tier from the next.

   The element is still emitted by _DashSection so nothing has to change per
   section if it is ever reinstated; here it simply takes no space. */
.np-dash__tier-accent { display: none; }
.np-dash__tier-titles { min-width: 0; flex: 1 1 auto; }
.np-dash__tier-title {
    font-size: 1.25rem; font-weight: 400; margin: 0; letter-spacing: -0.015em;
    line-height: 1.25; color: var(--np-color-text-primary);
}
.np-dash__tier-sub {
    display: none;
    margin: 3px 0 0; font-size: var(--np-fs-small); color: var(--np-color-text-muted);
}
.np-dash__tier:hover .np-dash__tier-sub,
.np-dash__tier:focus-within .np-dash__tier-sub {
    display: block;
}

/* ── grid ─────────────────────────────────────────────────────────────────── */
/* A tier renders one grid per BAND — a run of cards that are all the same size.
   The 12-column track is the fallback shape only; every band the server emits
   carries --even and an explicit column count. See DashboardViewModel.BandsFor.

   grid-auto-flow: dense so a small card backfills the gap a hidden larger one
   left behind — an unlicensed module must leave no hole. */
.np-dash__grid {
    display: grid; grid-template-columns: repeat(12, 1fr);
    gap: var(--np-space-3); grid-auto-flow: dense;
    /* Cards in the same row share a height so their tops and bottoms line up;
       the content inside sits at the top rather than stretching to fill. */
    align-items: stretch;
}
.np-dash__slot { grid-column: span var(--np-dash-span, 4); }

/* Equal-column band. The column count is chosen server-side so the card count
   divides into it evenly — six queue tiles as 3 + 3 rather than 4 + 2, a short
   trailing row being the thing that reads as two cards that failed to load. */
.np-dash__grid--even { grid-template-columns: repeat(var(--np-dash-cols, 4), 1fr); }
.np-dash__grid--even > .np-dash__slot { grid-column: span 1; }

/* A lone trailing CHART takes the whole row rather than leaving half of it blank.
   Never a KPI tile: one number stretched across the page is not a wide card, it
   is a mistake. See DashboardBand.IsRowFiller. */
.np-dash__grid--even > .np-dash__slot.is-row-filler { grid-column: span var(--np-dash-cols, 1); }

/* A ROW sizes to its own content, and the cards in it stretch to match each other
   (align-items: stretch, above). Tops and bottoms still line up across a row, which
   is what makes a row read as a set.

   It was `1fr` on grid-auto-rows — ONE height for every row of the band. That is a
   different and much more expensive claim: the tallest card anywhere in the band
   set the height of every row in it, so a six-card band laid out 3 + 3 gave its
   second row the depth of the deepest card in the first, and a chart card sitting
   below a five-row list card was mostly empty surface. Equal rows are worth having;
   equal to the deepest card on the section is not what they cost. */
.np-dash__grid--even { grid-auto-rows: auto; }

/* And every card in a ROW is one size (align-items: stretch, above).
   -------------------------------------------------------------------------
   Cards that end at different depths in the same row read as a layout that went
   wrong rather than as cards sized to their content — the tops line up, the
   bottoms do not, and the eye finds the ragged edge before it finds the data.
   A row is a set, and a set matches.

   The height that was actually wasted came from the rule ABOVE this one, which
   gave every row in the band the depth of the deepest card ANYWHERE in it. Row by
   row, a row is only ever as deep as its own tallest card. */

/* The card carries NO outline. Elevation and the surface change do the
   separating, which is what stops 23 cards reading as 23 boxes; the hairline
   returns only under forced-colors, where shadows are not rendered at all. */
.np-dash-card {
    display: flex; flex-direction: column; gap: var(--np-space-3);
    margin-bottom: 0; border: 0; padding: var(--np-space-4);
    box-shadow: var(--np-shadow-1);
    transition: box-shadow .18s ease, transform .18s ease;
    /* Anchors the info popover (__info-pop) to the card. */
    position: relative;
}
/* Only a card that DOES something on click gets the lift — a chart nobody can
   drill into must not advertise an interaction it does not have. */
/* Only a card that DOES something on click gets the lift — a chart nobody can
   drill into must not advertise an interaction it does not have.

   The hover shadow is the brand-tinted one, which the preset derives from its
   own darkest shade, so the lift reads as the tenant's colour rather than as
   generic grey. */
.np-dash-card:has(a):hover {
    box-shadow: var(--np-shadow-2), 0 10px 28px rgba(var(--np-color-brand-primary-rgb), 0.10);
    transform: translateY(-2px);
}
.np-dash-card:has(a):active { box-shadow: var(--np-shadow-1); transform: translateY(0); }
.np-dash-card:focus-within { box-shadow: var(--np-shadow-2); }

/* Cards arrive one fetch at a time, so they land in a ragged order. A short
   rise-in turns that from "things popping in" into "the page settling", which is
   most of the difference between a dashboard that feels assembled and one that
   feels finished. Keyed off the class np-dashboard.js already sets on a
   successful load, so it costs no extra script. */
.np-dash__slot.is-loaded .np-dash-card__body,
.np-dash-card.is-loaded .np-dash-card__body {
    animation: np-dash-rise .28s ease-out both;
}
@keyframes np-dash-rise {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
    .np-dash__slot.is-loaded .np-dash-card__body,
    .np-dash-card.is-loaded .np-dash-card__body { animation: none; }
    .np-dash-card:has(a):hover { transform: none; }
}

.np-dash-card__head { display: flex; align-items: flex-start; gap: var(--np-space-3); }
/* Tinted chip with a hairline of brand at its edge. The flat tint read as grey
   at a glance across 20-odd cards; the soft wash plus the ring gives each card a
   spot of brand without any of them shouting. */
.np-dash-card__icon {
    flex: 0 0 auto; width: 34px; height: 34px; border-radius: var(--np-radius-2);
    /* SOLID brand fill, always - not a tint that only fills on hover. Across a
       grid of twenty white cards the tinted chip read as grey, which is what made
       the page look monochrome no matter which preset was selected. Filled, each
       card carries a spot of the tenant's colour and the grid reads as branded.

       White glyph on the fill is safe on every preset: PrimaryHex is AAA against
       white, and --np-gradient-button's lighter stop IS PrimaryHex. */
    background: var(--np-color-brand-tint-200);
    color: var(--np-color-brand-primary);
    box-shadow: 0 2px 8px rgba(var(--np-color-brand-primary-rgb), 0.30);
    display: grid; place-items: center;
    transition: transform .18s ease, box-shadow .18s ease;
}
.np-dash-card:has(a):hover .np-dash-card__icon {
    transform: scale(1.06);
    box-shadow: 0 4px 14px rgba(var(--np-color-brand-primary-rgb), 0.42);
}
@media (prefers-reduced-motion: reduce) {
    .np-dash-card:has(a):hover .np-dash-card__icon { transform: none; }
}
@media (forced-colors: active) {
    .np-dash-card__icon { background: Highlight; color: HighlightText; forced-color-adjust: none; }
}
.np-dash-card__titles { min-width: 0; }
/* text-wrap: balance was making a two-word title break into two lines to
   "balance" it. At the widened KPI span these titles fit on one line, and
   pretty leaves them there. */
.np-dash-card__title {
    font-size: var(--np-fs-small);
    font-weight: 400;
    margin: 0;
    text-wrap: pretty;
    line-height: 1.35;
}
.np-dash-card__sub {
    display: none;
    margin: 2px 0 0; font-size: var(--np-fs-xsmall); color: var(--np-color-text-muted); line-height: 1.4;
}
.np-dash-card:hover .np-dash-card__sub,
.np-dash-card:focus-within .np-dash-card__sub {
    display: block;
}

/* The ⓘ disclosure that holds the card's explanation. A real button — role,
   state (aria-expanded) and keyboard come from the element; :focus-visible is
   restated because the control is custom-styled (uicompliance rule). */
.np-dash-card__info {
    flex: 0 0 auto; margin-left: auto;
    width: 26px; height: 26px; border: 0; border-radius: 50%; padding: 0;
    display: grid; place-items: center; cursor: pointer;
    background: transparent; color: var(--np-color-text-muted);
    transition: background .16s ease, color .16s ease;
}
.np-dash-card__info:hover,
.np-dash-card__info[aria-expanded="true"] {
    background: var(--np-color-surface-tinted); color: var(--np-color-brand-secondary);
}
.np-dash-card__info:focus-visible {
    outline: 2px solid var(--np-color-brand-primary); outline-offset: 2px;
}

/* The explanation itself. Overlays rather than reflowing — a card must not
   change height because someone asked what it measures. Opens ABOVE the card,
   right-aligned with the button that opened it (product owner, Oct 2026): below,
   it ran into the next row or section — the tab strip under the alert strip
   covered it — so on every card it rises over the space above instead. */
.np-dash-card__info-pop {
    position: absolute; top: auto; bottom: calc(100% + 6px); right: var(--np-space-4); z-index: 8;
    max-width: min(320px, calc(100% - var(--np-space-4) * 2));
    padding: var(--np-space-3) var(--np-space-4);
    background: var(--np-color-surface); border-radius: var(--np-radius-2);
    box-shadow: var(--np-shadow-2);
    /* Shadows vanish under forced-colors; the hairline is what keeps the popover
       separable from the card there. */
    border: 1px solid var(--np-color-border);
}
/* While a card's explanation is open, the WHOLE CARD is lifted above its
   neighbours and above the tab strip (.np-dash__tabs, z-index 1).
   The popover's own z-index is not enough: a hovered card carries a transform,
   which makes the card a stacking context and traps the popover inside it — so
   the cards after it, and the tab strip above the first row, painted over the
   explanation. Lifting the card takes the popover with it. */
.np-dash-card:has([data-np-card-info][aria-expanded="true"]) { z-index: 30; }
/* The alert strip's body is its own stacking context (its clip-path), so it is
   lifted as a whole for the same reason. */
.np-dash-alerts__body:has([data-np-card-info][aria-expanded="true"]) {
    position: relative;
    z-index: 30;
}
/* The body takes the slack in a stretched row, so cards in a row end level
   without their content being pushed apart. */
.np-dash-card__body { display: flex; flex-direction: column; gap: var(--np-space-3); flex: 1 1 auto; }
.np-dash-card__link { font-size: var(--np-fs-small); font-weight: 400; align-self: flex-start; }

/* The card whose list is open below. Marked on the card, not just the panel, so
   it is obvious which of six tiles the table belongs to. */
.np-dash__slot.is-drilled,
/* Restated for :hover — `.np-dash-card:has(a):hover` above is one point more
   specific (the :has argument counts), so without this the ring disappears the
   moment the pointer lands on the very card the open list belongs to. */
.np-dash__slot.is-drilled:hover {
    box-shadow: var(--np-shadow-2), inset 0 0 0 2px var(--np-color-brand-primary);
    transform: none;
}

.np-dash__none { text-align: center; }
.np-dash__none-title { font-size: var(--np-fs-h3); }

/* ── KPI ──────────────────────────────────────────────────────────────────── */
/* The whole tile is the link. A number-sized click target on a card this big is
   a miss waiting to happen. */
/* The figure and its two supporting lines sit on their own tinted panel, the way
   the empty state does. Two things come of it: the card's DATA is visibly one
   block rather than three loose lines against the card, and a populated card and
   an empty one now have the same structure — head, panel, caveat — so a row reads
   as a set whatever each card happens to be reporting.

   flex: 1 1 auto makes the panel take the slack in a stretched row, so panels
   line up across a row even when one card's caveat runs longer than another's. */
/* ONE PANEL ANATOMY, every card, every state:

       ┌───────────────────────────────────────┐
       │  head row     …     trailing element  │   ← baseline-aligned
       │  one supporting line                  │   ← optional
       └───────────────────────────────────────┘

   The figure and its action used to STACK — the number on one line and
   "Work your tasks →" right-aligned on the next — so a row of five tiles had its
   numbers on one line and its links on another, at two different left edges.
   They share a row now, baseline to baseline, which is the alignment a reader
   expects of a figure and its verb.

   flex: 0 0 auto, NOT 1 1 auto. The panel used to grow to fill whatever height
   the row was equalised to, which is how a two-digit figure ended up floating in
   a 160px tinted block.

   The min-height and the centring are what make a MIXED row read as one
   component: a tile showing a figure and a tile showing "Nothing is overdue" are
   the same panel at the same depth, each with its content centred in it, rather
   than two different shapes that happen to share a row. */
.np-dash-kpi,
.np-dash-empty {
    min-height: 4.75rem;
    padding: var(--np-space-3);
    border-radius: var(--np-radius-2);
}
/* The figure panel carries a soft brand wash and nothing else. It used to also
   draw a left accent rail; removed with the section accent above, for the same
   reason — a coloured left edge reads as a status indicator everywhere else in
   the product, and on a KPI tile it indicated nothing. */
.np-dash-kpi {
    display: flex; flex-direction: column; justify-content: center;
    gap: var(--np-space-2); flex: 0 0 auto;
    position: relative; overflow: hidden;
    background: rgba(var(--np-color-brand-primary-rgb), 0.06);
    text-decoration: none; color: inherit;
    transition: background .16s ease, box-shadow .16s ease;
}
/* The figure and the action, on one row. Baseline rather than centre: a 2rem
   numeral and a 12px label centred against each other look off by a couple of
   pixels in a way nobody can name but everybody sees. */
.np-dash-kpi__head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--np-space-3);
}
/* tint-200 is the next step UP the brand ramp. Neither of the obvious choices
   works: surface-muted (#FBF7FC) is LIGHTER than the panel's surface-tinted
   (#F7F0F8) and would fade it on hover, and brand-tint-100 is the same value as
   surface-tinted, so the hover would do nothing at all. */
.np-dash-card:hover .np-dash-kpi {
    background: rgba(var(--np-color-brand-primary-rgb), 0.13);
}
/* The panel is now a distinct surface, so its focus ring has to read against it
   rather than against the card. */
.np-dash-kpi:focus-visible {
    outline: 2px solid var(--np-color-brand-primary); outline-offset: 2px;
}
.np-dash-kpi__value {
    /* Proportional figures, deliberately NOT tabular-nums: equal-width digits
       make a display-size number look loose. */
    font-size: 2.125rem; font-weight: 400; line-height: 1; letter-spacing: -0.03em;
    /* The tenant's colour at REST. Near-black-on-tint made every figure on the
       page the same ink; the brand shade is AAA on white and on the tint above
       it, so the number can carry the colour without costing legibility. */
    color: var(--np-color-brand-primary); transition: color .14s ease;
}
.np-dash-kpi:hover .np-dash-kpi__value { color: var(--np-color-brand-primary); }
/* Its own line under the head row, never squeezed in beside the action. It is a
   sentence ("Oldest waiting 12 days"), and a sentence competing with a link for
   the right-hand half of a 220px tile wraps to two lines and pushes the panel out
   of line with every other tile on the row. */
.np-dash-kpi__sub {
    font-size: var(--np-fs-small); color: var(--np-color-text-muted); line-height: 1.35;
}
.np-dash-kpi__sub--warn { color: var(--np-color-warning-fg); font-weight: 400; }
/* Clinical-safety cards. The tinted panel and the figure take the danger tone —
   never as the only signal (§6.6): the card's own title says what the number
   counts, and the colour only reinforces it. */
.np-dash-kpi--danger { background: var(--np-color-error-bg); }
.np-dash-card:hover .np-dash-kpi--danger { background: var(--np-color-error-bg); filter: brightness(0.97); }
.np-dash-kpi--danger .np-dash-kpi__value { color: var(--np-color-error-fg); }
.np-dash-card:hover .np-dash-kpi--danger .np-dash-kpi__value { color: var(--np-color-error-fg); }
.np-dash-kpi--danger .np-dash-kpi__sub { color: var(--np-color-error-fg); }
.np-dash-kpi--danger .np-dash-kpi__go { color: var(--np-color-error-fg); }
.np-dash-card:hover .np-dash-kpi--danger .np-dash-kpi__go { color: var(--np-color-error-fg); }

/* Always on screen, not revealed on hover — a hover-only affordance does not
   exist on a touch device and is invisible in print. It brightens on hover
   instead, which says the same thing without hiding the control. */
.np-dash-kpi__go {
    font-size: var(--np-fs-xsmall); font-weight: 400; color: var(--np-color-brand-secondary);
    white-space: nowrap; margin-left: auto;
    transition: color .16s ease, transform .16s ease;
}
.np-dash-card:hover .np-dash-kpi__go,
.np-dash-kpi:focus-visible .np-dash-kpi__go {
    color: var(--np-color-brand-primary); transform: translateX(2px);
}

/* What the figure is MEASURED FROM. It used to be a permanently visible two-line
   footnote clamped at the foot of every card; it now lives inside the header's ⓘ
   popover (__info-pop), so the sentence no longer competes with the figure it
   qualifies and needs neither the clamp nor the reserved height.

   A card whose honest sentence depends on what LOADED — "the 25 longest-waiting",
   "showing 10 of 14 clinicians" — still renders its own inside the body marked
   --data; np-dashboard.js moves it into the popover after each load, where it
   supersedes the widget's static sentence (one footnote per card, as before). */
.np-dash-card__basis {
    margin: 0;
    font-size: var(--np-fs-xsmall); line-height: 1.5;
    color: var(--np-color-text-muted); text-wrap: pretty;
}
/* Print is where a figure gets reported upward, and there is no interaction
   available there to open the popover — so it prints, in place, on every card. */
@media print {
    .np-dash-card__info { display: none; }
    .np-dash-card__info-pop,
    .np-dash-card__info-pop[hidden] {
        display: block; position: static; max-width: none;
        border: 0; box-shadow: none; padding: var(--np-space-3) 0 0;
    }
    .np-dash-card__info-pop [data-np-info-static][hidden] { display: none; }
}

/* A finding, not a basis — "3 have never been sent a reminder". It reads like the
   footnote and sits with it, but it is about THIS data rather than about how the
   card is measured, so it never replaces the card's own statement. */
.np-dash-card__note {
    margin: var(--np-space-3) 0 0;
    font-size: var(--np-fs-xsmall); line-height: 1.5;
    color: var(--np-color-text-secondary); text-wrap: pretty;
}

/* ── stacked bar + legend ─────────────────────────────────────────────────── */
/* A 2px surface gap separates segments rather than a border drawn around each. */
/* ── vertical column chart ────────────────────────────────────────────────── */
/* Compare categories that do NOT sum to a whole. Every column starts from one
   baseline, which is what makes them comparable at a glance — and, unlike a
   stacked bar, the shape claims nothing about the numbers adding up.

   Fixed track height so a card's depth does not change with its data: a chart
   that grows when a number does makes every card below it move. */

/* The chart row: the value axis, then the plot. The axis sits OUTSIDE the scroll
   container so the scale holds its place while a chart with more groups than the
   card is wide scrolls sideways under it.

   Three measurements live here as custom properties because two elements have to
   agree on them: the axis is positioned against the TRACK, which is a sibling two
   levels down inside the scroller, so the only way to align them is to derive both
   from the same numbers. --np-cols-value-h is the printed figure above each column
   (1rem at line-height 1), --np-cols-gap the space under it. */
.np-dash-cols__chart {
    display: flex; align-items: flex-start; gap: var(--np-space-2);
    --np-cols-track-h: 96px;
    --np-cols-value-h: 1rem;
    --np-cols-gap: 6px;
}
/* The plot scrolls sideways rather than dividing the card between however many
   columns it happens to have. A chart whose groups come from the tenant's
   licensed pathways can be three columns or eighteen; eighteen slivers with
   unreadable labels is not a chart. Columns hold a floor width (below) and the
   overflow lands here.

   Nothing scrolls while the columns fit, which is why every chart shares this
   container instead of opting in to a scrolling variant. */
/* overflow-y: hidden is not decoration. A box with overflow-x: auto computes its
   other axis to auto as well, and on a machine set to "always show scrollbars"
   (Windows accessibility) that paints a permanent vertical scrollbar and its
   arrows down the side of every chart — a control that scrolls nothing, on a card
   with nothing to scroll. The plot only ever overflows sideways. */
.np-dash-cols__scroll {
    overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
    flex: 1 1 auto; min-width: 0;
}

/* THE VALUE AXIS. Exactly as tall as a track, and pushed down by the height of the
   value row so its 0 lands on the axis line rather than somewhere near it. */
.np-dash-cols__axis {
    position: relative; flex: 0 0 auto;
    height: var(--np-cols-track-h);
    margin-top: calc(var(--np-cols-value-h) + var(--np-cols-gap));
    min-width: 1.25rem;
}
/* Centred ON its value rather than sitting above or below it — a tick label whose
   baseline is the gridline reads half a step high. text-muted: the scale is the
   quietest thing on the card, under both the figures and the labels. */
.np-dash-cols__tick {
    position: absolute; right: 0; transform: translateY(50%);
    font-size: var(--np-fs-xsmall); line-height: 1;
    color: var(--np-color-text-muted); font-variant-numeric: tabular-nums;
}

.np-dash-cols { display: flex; align-items: stretch; gap: var(--np-space-4); min-width: min-content; }

/* A GROUP of columns under one caption. Two groups is how a chart states a
   boundary its numbers cannot — "open now" against "closed in the last 30 days",
   "within 18 weeks" against "past" — instead of repeating the qualifier in the
   smallest type on the card, once per axis label.

   flex-grow is set inline from the group's column count, so every column on the
   chart is the same width whichever group it sits in. */
.np-dash-cols__group {
    display: flex; flex-direction: column; gap: 7px;
    /* min-content, not 0: a group must never be squeezed narrower than the
       columns inside it, or they overflow their own group instead of the
       scroll container and the divider lands in the middle of a column. */
    flex: 1 1 0; min-width: min-content;
}
/* Dashed, because it marks a boundary WITHIN one chart rather than dividing two
   panels. On the waiting-times card that boundary is the 18-week standard, which
   is the only line on that chart anyone is looking for; on the assessment card it
   is simply where one pathway's columns end and the next begins. */
.np-dash-cols__group + .np-dash-cols__group {
    padding-left: var(--np-space-4);
    border-left: 1px dashed var(--np-color-border);
}
/* A SERIES repeats the same columns under every caption, so there is no change of
   basis between one group and the next to draw — and a dashed rule between every
   pair of them is a picket fence through the middle of the chart. The gap between
   groups is the separation, which is what a grouped column chart uses. */
.np-dash-cols--series .np-dash-cols__group + .np-dash-cols__group {
    padding-left: 0; border-left: 0;
}
/* A SERIES GROUPS ITS BARS instead of spreading them.
   -------------------------------------------------------------------------
   Everywhere else a column takes an equal share of the card, because each one is
   its own category and the label under it needs the width. In a series the
   categories are the GROUPS: the three bars under "ADHD" are one reading, and
   spreading them across half the card puts as much space between two bars of the
   same pathway as between the pathways themselves — which is the shape that made
   this chart hard to read. Fixed narrow columns, centred in the group's share of
   the width, are what a grouped column chart looks like.

   No label under them to fit (the legend carries the names), so the width only
   has to hold the printed value — 42px takes three digits at 1rem bold. */
.np-dash-cols--series .np-dash-cols__plot { justify-content: center; }
.np-dash-cols--series .np-dash-cols__col { flex: 0 0 auto; min-width: 42px; }

/* ONE AXIS LINE, across the whole plot.
   The per-column rule butts up group by group but breaks at the gap between them,
   which is right where a divider states a change of basis and wrong here, where
   nothing changes between one pathway and the next. Drawn at the tracks' own
   bottom edge (hence the -1px, so it lands ON their border rather than a pixel
   under it and doubles the rule), spanning the scrolled content rather than the
   card, so it follows the plot sideways. */
.np-dash-cols--series { position: relative; }
.np-dash-cols--series::after {
    content: ""; position: absolute; left: 0; right: 0;
    top: calc(var(--np-cols-value-h) + var(--np-cols-gap) + var(--np-cols-track-h) - 1px);
    border-top: 1px solid var(--np-color-border);
}

/* No fixed height on the plot: the TRACK carries it now (below), so every track on
   the chart is the same known height whether the labels under it wrap to one line
   or two — which is the only way the value axis beside them can line up. */
.np-dash-cols__plot { display: flex; align-items: stretch; }

/* No horizontal padding, deliberately: the tracks butt against each other so
   their bottom borders form ONE continuous axis line under the group. The bars
   are separated by their own width cap, which is how a column chart separates
   columns. */
/* The floor width. Enough for a two-word axis label to wrap to two lines and
   still be read; below it the plot overflows and np-dash-cols__scroll takes
   over. Columns still GROW to fill a card with room to spare. */
.np-dash-cols__col {
    flex: 1 1 0; min-width: 54px;
    display: flex; flex-direction: column; align-items: center; gap: var(--np-cols-gap, 6px);
    text-decoration: none; color: inherit;
    border-radius: var(--np-radius-1);
    transition: background .14s ease;
}
/* The hit target is the WHOLE column — value, bar and label — not the painted
   bar, which is a sliver on a near-zero category. */
a.np-dash-cols__col:hover { background: var(--np-color-surface-tinted); }
a.np-dash-cols__col:focus-visible { outline: 2px solid var(--np-color-focus); outline-offset: -1px; }
a.np-dash-cols__col:hover .np-dash-cols__value { color: var(--np-color-brand-primary); }

/* Above the column, never inside it: an in-bar label has to be dropped or
   clipped the moment the bar is short, and this one never is. A step larger than
   the axis label so the figure leads and the category follows. */
.np-dash-cols__value {
    font-size: var(--np-fs-small); font-weight: 400; line-height: 1;
    color: var(--np-color-text-primary); font-variant-numeric: tabular-nums;
    transition: color .14s ease;
}
/* THE AXIS LINE. It earns its place: with a rule under every column an empty
   column reads as "none" rather than as a bar that failed to render, which is
   what the separate zero stub used to be for. */
.np-dash-cols__track {
    flex: 0 0 auto; width: 100%; height: var(--np-cols-track-h, 96px);
    display: flex; align-items: flex-end; justify-content: center;
    border-bottom: 1px solid var(--np-color-border);
}
/* A SLIM mark. Capped hard, so a two-column chart does not render two slabs half
   the card wide and a grouped chart reads as a set of marks rather than a wall of
   colour. The column's hit target is the full width regardless — the bar is the
   ink, not the control. */
.np-dash-cols__stack {
    width: 100%; max-width: 22px; display: flex; flex-direction: column; gap: 2px;
    border-radius: 3px 3px 0 0; overflow: hidden;
}
.np-dash-cols__seg { min-height: 4px; transition: filter .12s ease; }
a.np-dash-cols__col:hover .np-dash-cols__seg { filter: brightness(1.08); }
/* text-secondary, not muted: this is the label that says WHAT the figure above it
   counts, and at 12px a 5.7:1 grey is the weakest link on the card. 9.4:1 costs
   nothing and is the difference between scanning the chart and reading it. */
.np-dash-cols__label {
    font-size: var(--np-fs-xsmall); font-weight: 400;
    color: var(--np-color-text-secondary);
    text-align: center; line-height: 1.3; text-wrap: pretty; padding: 0 2px;
}
.np-dash-cols__note {
    font-size: var(--np-fs-xsmall); color: var(--np-color-warning-fg);
    font-weight: 400; line-height: 1.2; text-align: center;
}
/* The group caption. Set apart from the axis labels above it by CASE and
   tracking rather than by size — two rows of small grey text at the same weight
   would read as one wrapped label. */
.np-dash-cols__caption {
    font-size: var(--np-fs-xsmall); font-weight: 400;
    text-transform: uppercase; letter-spacing: .05em;
    color: var(--np-color-text-muted); text-align: center; line-height: 1.2;
}
/* A caption that IS the drill-down. Underlined without hover, matching the KPI
   "go" rule — no hover-only affordances. */
a.np-dash-cols__caption--link {
    /* Shrink-to-fit rather than filling the group, so the hit target and the
       focus ring hug the caption instead of a band of empty space beside it. */
    align-self: center;
    text-decoration: underline; text-underline-offset: 3px;
}
a.np-dash-cols__caption--link:hover { color: var(--np-color-brand-primary); }
a.np-dash-cols__caption--link:focus-visible {
    outline: 2px solid var(--np-color-focus); outline-offset: 2px; border-radius: 2px;
}
/* A hue-agnostic key, for a distinction a swatch cannot state honestly. */
.np-dash-cols__key {
    margin: 0; font-size: var(--np-fs-xsmall); color: var(--np-color-text-muted);
}

.np-dash-bar { display: flex; gap: 2px; height: 28px; border-radius: var(--np-radius-1); overflow: hidden; }
.np-dash-bar__seg { min-width: 3px; transition: filter .12s ease; }
.np-dash-bar__seg:hover { filter: brightness(1.08); }

.np-dash-legend { display: flex; flex-wrap: wrap; gap: var(--np-space-2) var(--np-space-4); margin: 0; padding: 0; list-style: none; }
.np-dash-legend__item { display: flex; align-items: center; gap: 7px; font-size: var(--np-fs-small); color: var(--np-color-text-secondary); }
.np-dash-legend__swatch { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
/* The column chart's legend: centred under the plot and pushed to the bottom of
   the card. A modifier rather than a change to the base class — the flow charts
   (appointment activity, outcomes, cancellation reasons, workload) render their
   legend beside their own content, where a centred one would float away from the
   rows it names.

   margin-top: auto works because np-dash-card__body is a flex column that fills
   the card: cards in a row share one height, so without it one card's key sits
   under its chart while its neighbour's sits 60px lower. */
.np-dash-legend--center { justify-content: center; margin-top: auto; }
.np-dash-legend__n { font-weight: 400; color: var(--np-color-text-primary); font-variant-numeric: tabular-nums; }
/* A legend entry that IS a drill-down link (tasks-by-status live segments):
   same colour as its inert siblings, underlined so the affordance is visible
   without hover (matching the KPI "go" rule — no hover-only affordances). */
a.np-dash-legend__label { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
a.np-dash-legend__label:hover { color: var(--np-color-text-primary); }
a.np-dash-legend__label:focus-visible { outline: 2px solid var(--np-color-focus); outline-offset: 2px; border-radius: 2px; }

/* ── priority rows (tasks-by-priority) ────────────────────────────────────────
   Two labelled bar rows sharing one scale; each whole row is the link. The bar
   reuses np-dash-bar, with a transparent remainder segment padding the shorter
   row so lengths stay comparable. */
.np-dash-priority { display: flex; flex-direction: column; gap: var(--np-space-3); margin: 0; padding: 0; list-style: none; }
.np-dash-priority__link { display: block; text-decoration: none; color: inherit; border-radius: var(--np-radius-1); }
.np-dash-priority__link:focus-visible { outline: 2px solid var(--np-color-focus); outline-offset: 2px; }
.np-dash-priority__link:hover .np-dash-priority__label { color: var(--np-color-text-primary); text-decoration: underline; text-underline-offset: 2px; }
.np-dash-priority__head {
    display: flex; justify-content: space-between; align-items: baseline;
    gap: var(--np-space-2); margin-bottom: 4px;
}
.np-dash-priority__label { font-size: var(--np-fs-small); color: var(--np-color-text-secondary); }
.np-dash-priority__n { font-weight: 400; color: var(--np-color-text-primary); font-variant-numeric: tabular-nums; font-size: var(--np-fs-small); }
.np-dash-priority__late { font-weight: 400; color: var(--np-color-text-secondary); }

/* ── grouped horizontal bars — one labelled row per category ──────────────────
   Horizontal because category labels are prose of varying length; rotated axis
   text is slower to read and wraps unpredictably. The label is its own grid
   column so bars start on a common left edge whatever the label's width. */
.np-dash-rows { display: flex; flex-direction: column; gap: var(--np-space-2); }
.np-dash-rows__row {
    display: grid; grid-template-columns: minmax(6rem, 9rem) 1fr auto;
    align-items: center; gap: var(--np-space-2);
}
.np-dash-rows__label {
    font-size: var(--np-fs-small); color: var(--np-color-text-secondary);
    text-decoration: none; overflow-wrap: anywhere;
}
/* Scoped to anchors: most labels on this chart are plain spans, and a span that
   changes colour under the pointer promises a link that is not there. */
a.np-dash-rows__label:hover, a.np-dash-rows__label:focus-visible {
    color: var(--np-color-brand-primary); text-decoration: underline;
}
.np-dash-rows__track { min-width: 0; }
/* The bar's WIDTH encodes the row total against the widest row; the segments
   inside divide that width. Two encodings, one geometry. */
.np-dash-rows__bar { display: flex; height: 14px; border-radius: var(--np-radius-1); overflow: hidden; min-width: 2px; }
/* 2px of surface between fills so adjacent segments read as separate quantities
   rather than one blended block. */
.np-dash-rows__seg { min-width: 2px; }
.np-dash-rows__seg + .np-dash-rows__seg { box-shadow: -2px 0 0 0 var(--np-color-surface); }
.np-dash-rows__n {
    font-size: var(--np-fs-small); font-weight: 400; font-variant-numeric: tabular-nums;
    color: var(--np-color-text-primary); min-width: 2.5ch; text-align: right;
}

/* ── split rows — one line PER STATUS, optionally grouped under a category ────
   Used by "Assessment forms outstanding" and "Report dispatch queue". Both drew
   their states stacked inside one bar. A stack gives exactly one of its
   quantities a baseline and makes every other one a length that has to be
   measured against its neighbours — and on both cards the state a reader is
   actually chasing (nothing has happened to it yet) sat LAST, so it was the one
   that could never be read. Splitting the states onto their own lines gives each
   a baseline, a length and a number, and retires the legend both cards carried:
   a named row is its own legend entry.

   The rail behind each bar is the SCALE made visible. Without it a short bar and
   a bar that failed to render look identical, and a zero has nothing to be zero
   against. */
.np-dash-rows--split { gap: var(--np-space-4); }
.np-dash-rows--split .np-dash-rows__row { gap: var(--np-space-2); }
.np-dash-rows__group { display: flex; flex-direction: column; gap: 5px; }
/* The category and its total, above the rows it owns. */
.np-dash-rows__group-head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--np-space-2); margin-bottom: 2px;
}
.np-dash-rows__group-label {
    font-size: var(--np-fs-small); font-weight: 400; overflow-wrap: anywhere;
    color: var(--np-color-text-primary); text-decoration: none;
}
.np-dash-rows__group-label:hover, .np-dash-rows__group-label:focus-visible {
    color: var(--np-color-brand-primary); text-decoration: underline; text-underline-offset: 2px;
}
.np-dash-rows__group-n {
    font-size: var(--np-fs-xsmall); color: var(--np-color-text-muted);
    font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Ties a row to its bar at a glance. Never the only signal — the row is named in
   words immediately beside it (§6.6). */
.np-dash-rows__key { width: 9px; height: 9px; border-radius: 2px; flex: 0 0 auto; }
.np-dash-rows--split .np-dash-rows__label { display: flex; align-items: center; gap: 7px; }
.np-dash-rows--split .np-dash-rows__track {
    background: var(--np-color-neutral-100); border-radius: var(--np-radius-1);
}
/* One fill, not a stack — so no min-width floor. A zero has to draw nothing, or
   every empty state shows a stub that reads as "one or two". */
.np-dash-rows--split .np-dash-rows__bar { min-width: 0; height: 12px; }

/* ── grouped bars — four SEPARATE quantities per row, never stacked ───────────
   Stacking would imply the four add up to something meaningful; a report plus an
   appointment is not a number. Four thin bars share the row instead. */
/* The four-thin-bars variant these classes served went with the flat workload
   chart it was built for — that chart could not survive an organisation with 100+
   clinicians and was replaced by the profession drill-down below. Nothing renders
   np-dash-rows__group / --grouped / __stack / __mini any more, so they are gone
   rather than left as a set of classes a future reader would have to test to
   discover were dead.
*/

/* ── drill-down bar chart ─────────────────────────────────────────────────────
   The workload chart, at both levels. Every row IS the control — the bar is what
   a reader points at, so the hit target is the whole row rather than a link at
   the end of it.

   Grid: label | plot | chevron. The plot column is what makes the bars share a
   left edge whatever the labels do, which is the whole reason a bar chart can be
   read at a glance.

   Marks are 10px and the fill carries a 4px radius on its data-end only — the
   baseline end stays square so a short bar still reads as anchored to zero
   rather than floating. */
.np-dash-chart { display: flex; flex-direction: column; gap: var(--np-space-1); }

.np-dash-chart__row {
    display: grid; grid-template-columns: minmax(6rem, 11rem) 1fr auto;
    align-items: center; gap: var(--np-space-3);
    width: 100%; text-align: left; background: none; border: 0;
    padding: var(--np-space-2); margin: 0 calc(var(--np-space-2) * -1);
    border-radius: var(--np-radius-2); cursor: pointer;
    font: inherit; color: inherit; text-decoration: none;
    transition: background .14s ease;
}
.np-dash-chart__row:hover { background: var(--np-color-surface-tinted); }
.np-dash-chart__row:focus-visible {
    outline: 2px solid var(--np-color-brand-primary); outline-offset: 1px;
}

.np-dash-chart__label {
    font-size: var(--np-fs-small); color: var(--np-color-text-secondary);
    overflow-wrap: anywhere; min-width: 0;
}
.np-dash-chart__row:hover .np-dash-chart__label { color: var(--np-color-text-primary); }

/* The distinct-patient count under a clinician's name. Deliberately its own line
   and its own number: it is NOT the length of the bar beside it, which counts
   assessment forms and therefore runs longer. */
.np-dash-chart__sub {
    display: block; font-size: var(--np-fs-xsmall); color: var(--np-color-text-muted);
    font-variant-numeric: tabular-nums;
}

/* The plot column. The value sits AFTER the bar rather than inside it, so it is
   legible on a short bar and never fights the fill for contrast — and it wears a
   text token, never the series colour. */
.np-dash-chart__plot { display: flex; align-items: center; gap: var(--np-space-2); min-width: 0; }

.np-dash-chart__bar,
.np-dash-chart__stack {
    height: 10px; min-width: 3px; border-radius: 0 4px 4px 0;
    display: flex; overflow: hidden;
    /* The bar grows into place when a level loads. Motion is the cheapest signal
       that the card responded to the click. */
    transition: width .28s cubic-bezier(.2, .7, .3, 1);
}
.np-dash-chart__row:hover .np-dash-chart__bar,
.np-dash-chart__row:hover .np-dash-chart__stack { filter: brightness(1.06); }

/* 2px of surface between fills so adjacent segments read as separate quantities
   rather than one blended block. */
.np-dash-chart__seg { min-width: 2px; }
.np-dash-chart__seg + .np-dash-chart__seg { box-shadow: -2px 0 0 0 var(--np-color-surface); }

.np-dash-chart__value {
    font-size: var(--np-fs-small); font-weight: 400; font-variant-numeric: tabular-nums;
    color: var(--np-color-text-primary); min-width: 2.5ch; flex: 0 0 auto;
}

/* Always on screen rather than revealed on hover — a hover-only affordance does
   not exist on touch and is invisible in print. It moves on hover instead. */
.np-dash-chart__go {
    color: var(--np-color-text-muted); font-size: 1.1rem; line-height: 1;
    transition: color .14s ease, transform .14s ease;
}
.np-dash-chart__row:hover .np-dash-chart__go {
    color: var(--np-color-brand-primary); transform: translateX(2px);
}

@media (prefers-reduced-motion: reduce) {
    .np-dash-chart__bar,
    .np-dash-chart__stack,
    .np-dash-chart__go { transition: none; }
}

/* The way back up a level. */
.np-dash-crumb {
    display: flex; align-items: center; gap: var(--np-space-2);
    margin: 0 0 var(--np-space-3); font-size: var(--np-fs-small);
}
.np-dash-crumb__up {
    background: none; border: 0; padding: 0; cursor: pointer; font: inherit;
    font-weight: 400; color: var(--np-color-brand-secondary);
}
.np-dash-crumb__up:hover { color: var(--np-color-brand-primary); text-decoration: underline; }
.np-dash-crumb__up:focus-visible {
    outline: 2px solid var(--np-color-brand-primary); outline-offset: 2px;
    border-radius: var(--np-radius-1);
}
.np-dash-crumb__sep { color: var(--np-color-text-muted); }
.np-dash-crumb__here { color: var(--np-color-text-primary); font-weight: 400; }

/* ── funnel — the NARROWING is the information ────────────────────────────────
   Bars start on a COMMON LEFT EDGE, the same as np-dash-rows above and every
   other chart on the dashboard. They were centred (`margin: 0 auto`), on the
   theory that a funnel is read from its silhouette — but a centred bar has no
   fixed edge to measure from, so comparing two stages meant judging two lengths
   that begin in different places, and the short stages floated mid-card looking
   like decoration rather than data. The narrowing still reads; it now reads
   against a baseline.

   Every stage direct-labelled: five labels is few enough that a legend would be
   an extra lookup for no saving. */
.np-dash-funnel { display: flex; flex-direction: column; gap: var(--np-space-2); }
.np-dash-funnel__stage {
    display: grid; grid-template-columns: 5.5rem 1fr 2.5rem;
    align-items: center; gap: var(--np-space-2); text-decoration: none;
}
.np-dash-funnel__stage:hover .np-dash-funnel__bar { filter: brightness(0.94); }
.np-dash-funnel__label { font-size: var(--np-fs-small); color: var(--np-color-text-secondary); }
/* The rail is the scale made visible, so a short stage reads as short rather
   than as a bar that did not render — the same reason np-dash-rows--split has
   one. */
.np-dash-funnel__track {
    display: block; min-width: 0;
    background: var(--np-color-neutral-100); border-radius: var(--np-radius-1);
}
.np-dash-funnel__bar { height: 18px; border-radius: var(--np-radius-1); margin: 0; display: block; }
.np-dash-funnel__n {
    font-size: var(--np-fs-small); font-weight: 400; text-align: right;
    font-variant-numeric: tabular-nums; color: var(--np-color-text-primary);
}

/* ── a table inside a card ────────────────────────────────────────────────────
   Uses the product's own .table / .np-table--dense; only the card-specific
   overflow container and the two cell modifiers live here. Wide content scrolls
   inside its own box so the card never forces the page sideways. */
/* overflow-y: hidden for the same reason as np-dash-cols__scroll: auto on one
   axis makes the other auto, which paints a dead vertical scrollbar down the side
   of every in-card table where the OS is set to always show them. A dashboard
   table scrolls sideways or not at all — its rows are paged, never scrolled. */
.np-dash-table { overflow-x: auto; overflow-y: hidden; }
.np-dash-table > .table { margin-bottom: 0; }
.np-dash-table__sub {
    display: block; font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-muted); font-variant-numeric: tabular-nums;
}
/* Digits that line up in a column get tabular figures — the one place equal-width
   digits genuinely help, unlike a display-size KPI where they read as loose. */
.np-dash-table__num { text-align: right; font-variant-numeric: tabular-nums; font-weight: 400; }

/* Every column centred, header and cell together. Wins over the numeric column's
   right alignment above — inside this variant the whole grid is centred, and one
   column keeping its own alignment is the ragged edge the variant exists to
   remove. The tabular figures stay: they are what keeps a centred column of
   numbers from shuffling as the digits change. */
.np-dash-table--center th,
.np-dash-table--center td,
.np-dash-table--center .np-dash-table__num { text-align: center; }
/* Block children (the MRN sub-line, a status pill) centre with their cell rather
   than sitting at its left edge. */
.np-dash-table--center .np-dash-table__sub { text-align: center; }
/* Patient name column (first td) should be left-aligned for readability */
.np-dash-table--center table td:first-child,
.np-dash-table--center table td:first-child .np-dash-table__sub { text-align: left; }

/* ── applied-filter notice ────────────────────────────────────────────────────
   For a filter the reader did NOT set — arriving from a dashboard tile into a
   list that is already narrowed. Without it, a subset reads as the whole set,
   and someone concludes the pathway has three patients in it. The chip states
   what is applied and removes it in one click. */
.np-list-applied {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--np-space-2) var(--np-space-3);
    margin: 0 0 var(--np-space-4);
}
.np-list-applied__chip {
    display: inline-flex; align-items: center; gap: var(--np-space-2);
    padding: 5px 6px 5px 12px; border-radius: var(--np-radius-pill);
    background: var(--np-color-brand-tint-100); color: var(--np-color-brand-secondary);
    font-size: var(--np-fs-small); font-weight: 400;
}
.np-list-applied__clear {
    display: inline-flex; align-items: center; gap: 2px;
    padding: 2px 10px; border-radius: var(--np-radius-pill);
    background: var(--np-color-surface); color: var(--np-color-brand-primary);
    font-size: var(--np-fs-xsmall); font-weight: 400; text-decoration: none;
    transition: background .16s ease, color .16s ease;
}
.np-list-applied__clear:hover, .np-list-applied__clear:focus-visible {
    background: var(--np-color-brand-primary); color: var(--np-color-text-on-brand);
}
.np-list-applied__note { font-size: var(--np-fs-xsmall); color: var(--np-color-text-muted); }

/* ── in-card mini list — N rows of a set the card already holds ───────────────
   Search + paging over rows that are ALREADY in the DOM (np-dash-minilist.js).
   The height is pinned to the page size so the card cannot grow with the
   caseload and push everything below it down the page — which is what a
   dashboard table that renders every row it loaded does. */
.np-dash-minilist { display: flex; flex-direction: column; gap: var(--np-space-3); }

.np-dash-minilist__search { position: relative; }
.np-dash-minilist__search-icon {
    position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
    display: inline-flex; color: var(--np-color-text-muted); pointer-events: none;
}
.np-dash-minilist__input {
    padding-left: 32px; height: 34px; font-size: var(--np-fs-small);
}

/* Sized from the page size rather than a magic pixel value, so changing the rows
   per page cannot leave the card the wrong height. */
.np-dash-minilist__body { min-height: calc(var(--np-minilist-rows, 5) * 2.65rem + 2.25rem); }
.np-dash-minilist__none {
    margin: var(--np-space-4) 0 0; font-size: var(--np-fs-small);
    color: var(--np-color-text-muted); text-wrap: pretty;
}

.np-dash-minilist__foot {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--np-space-3); margin-top: auto;
}
.np-dash-minilist__status {
    margin: 0; font-size: var(--np-fs-xsmall); color: var(--np-color-text-muted);
    font-variant-numeric: tabular-nums;
}
.np-dash-minilist__pager { display: flex; gap: var(--np-space-1); }
.np-dash-minilist__step {
    display: inline-grid; place-items: center; width: 30px; height: 30px;
    border: 0; border-radius: var(--np-radius-1); cursor: pointer;
    background: var(--np-color-surface-tinted); color: var(--np-color-text-secondary);
    transition: background .16s ease, color .16s ease;
}
.np-dash-minilist__step:hover:not(:disabled) {
    background: var(--np-color-brand-primary); color: var(--np-color-text-on-brand);
}
.np-dash-minilist__step:disabled { opacity: 0.4; cursor: default; }

/* np-dash-pair (two tinted figure panels side by side) removed — no view rendered
   it, and a third panel shape competing with np-dash-kpi is exactly the kind of
   near-duplicate that gets reached for and makes a row look assembled rather than
   designed. The KPI panel is the one figure component. */

/* ── day rail — a timeline, because the GAPS are the information ──────────────
   One row per appointment rather than a shared lane: overlaps are normal (two
   clinicians at 10:00) and stacking them would hide the clash a reader is
   looking for. */
.np-dash-rail { position: relative; margin-top: var(--np-space-2); }
.np-dash-rail__hours { position: relative; height: 1.25rem; border-bottom: 1px solid var(--np-color-border); }
.np-dash-rail__hour {
    position: absolute; transform: translateX(-50%);
    font-size: var(--np-fs-xsmall); color: var(--np-color-text-muted);
    font-variant-numeric: tabular-nums; white-space: nowrap;
}
.np-dash-rail__list {
    list-style: none; margin: 0; padding: 0;
    display: flex; flex-direction: column; gap: 3px;
    max-height: 13rem; overflow-y: auto;
}
.np-dash-rail__row { position: relative; height: 22px; }
.np-dash-rail__block {
    position: absolute; top: 0; bottom: 0; min-width: 3.5rem;
    display: flex; align-items: center; gap: var(--np-space-1);
    padding: 0 var(--np-space-1); border-radius: var(--np-radius-1);
    font-size: var(--np-fs-xsmall); text-decoration: none; overflow: hidden;
    background: var(--np-color-brand-tint-100); color: var(--np-color-text-primary);
    /* Colour is never the only signal — a left rule gives every block a second,
       non-colour cue, and the status is spelled out in the accessible name. */
    border-left: 3px solid var(--np-color-brand-primary);
}
.np-dash-rail__block:hover, .np-dash-rail__block:focus-visible { filter: brightness(0.96); }
.np-dash-rail__block.is-past { opacity: 0.62; }
.np-dash-rail__block--active   { border-left-color: var(--np-color-success-fg); }
.np-dash-rail__block--completed{ border-left-color: var(--np-color-success-fg); }
.np-dash-rail__block--pending  { border-left-color: var(--np-color-info-fg); }
.np-dash-rail__block--warning  { border-left-color: var(--np-color-warning-fg); }
.np-dash-rail__block--danger   { border-left-color: var(--np-color-error-fg); }
.np-dash-rail__block--inactive { border-left-color: var(--np-color-text-muted); }
.np-dash-rail__time { font-weight: 400; font-variant-numeric: tabular-nums; flex: none; }
.np-dash-rail__type { color: var(--np-color-text-secondary); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }

/* ── trend lines — one axis, always ─────────────────────────────────────────── */
.np-dash-trend { width: 100%; height: 7.5rem; display: block; margin-top: var(--np-space-2); overflow: visible; }
.np-dash-trend__axis {
    display: flex; justify-content: space-between;
    font-size: var(--np-fs-xsmall); color: var(--np-color-text-muted);
    padding-top: var(--np-space-1); border-top: 1px solid var(--np-color-border);
}

/* np-dash-kpi--inline (value, sentence and link all on one row) removed. One card
   used it, and beside the standard panel on the same tier it read as a different
   component — which is the whole problem this section was rebuilt to fix. Every
   panel is now head row + supporting line, no variants. */

.np-dash-card__meta {
    margin: 0 0 var(--np-space-2); font-size: var(--np-fs-small);
    color: var(--np-color-text-secondary);
}

/* Upcoming confirmed appointments — a figure that opens its own patient list.
   The <summary> IS the figure panel (np-dash-kpi), so the card matches its
   neighbours while closed; the list opens inside the card and scrolls there,
   like the day rail's, instead of growing the row. */
.np-dash-upcoming { display: flex; flex-direction: column; gap: var(--np-space-2); }
.np-dash-upcoming__summary { cursor: pointer; list-style: none; }
.np-dash-upcoming__summary::-webkit-details-marker { display: none; }
.np-dash-upcoming__close,
.np-dash-upcoming[open] .np-dash-upcoming__open { display: none; }
.np-dash-upcoming[open] .np-dash-upcoming__close { display: inline; }
.np-dash-upcoming__meta { margin-top: var(--np-space-2); }
.np-dash-upcoming__list {
    list-style: none; margin: 0 0 var(--np-space-2); padding: 0;
    max-height: 16rem; overflow-y: auto;
}
.np-dash-upcoming__row {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--np-space-3); padding: 6px 0;
    border-top: 1px solid var(--np-color-border);
    font-size: var(--np-fs-small);
}
.np-dash-upcoming__row:first-child { border-top: 0; }
.np-dash-upcoming__patient { min-width: 0; text-decoration: none; }
.np-dash-upcoming__when { flex: 0 0 auto; text-align: right; text-decoration: none; color: var(--np-color-text-primary); }
.np-dash-upcoming__patient:hover,
.np-dash-upcoming__when:hover { text-decoration: underline; }
.np-dash-upcoming__patient:focus-visible,
.np-dash-upcoming__when:focus-visible {
    outline: 2px solid var(--np-color-brand-primary); outline-offset: 2px;
}
.np-dash-upcoming__mrn,
.np-dash-upcoming__type {
    display: block; font-size: var(--np-fs-xsmall); font-weight: 400;
    color: var(--np-color-text-secondary);
}

/* Promoted incident row — an outcome the system knows went wrong. */
/* A hairline box in the error colour, NOT a left accent stripe (a coloured left
   border is not part of this design system). The border is still the non-colour
   cue the tinted fill needs — WCAG 1.4.1 is satisfied by a border, not by which
   side it is on — and 1px keeps it quieter than the old 3px rule. */
.np-dash-alert {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--np-space-3);
    padding: var(--np-space-3); text-decoration: none;
    background: var(--np-color-error-bg); border: 1px solid var(--np-color-error-fg);
    border-radius: var(--np-radius-2);
    transition: filter .16s ease, transform .16s ease;
}
.np-dash-alert__text { font-size: var(--np-fs-small); color: var(--np-color-error-fg); }
.np-dash-alert:hover, .np-dash-alert:focus-visible { filter: brightness(0.97); transform: translateX(2px); }
.np-dash-alert__icon { display: inline-flex; color: var(--np-color-error-fg); flex: none; }
.np-dash-alert__body { display: flex; align-items: baseline; gap: var(--np-space-2); flex: 1 1 12rem; }
.np-dash-alert__value {
    font-size: 1.75rem; font-weight: 400; line-height: 1;
    color: var(--np-color-error-fg); font-variant-numeric: tabular-nums; flex: none;
}
.np-dash-alert__go {
    font-size: var(--np-fs-xsmall); font-weight: 400; color: var(--np-color-error-fg);
    margin-left: auto; white-space: nowrap;
}

/* ── empty / error ────────────────────────────────────────────────────────────
   A STATEMENT, not a void.

   This was a centred column in the middle of the card, which on a half-width card
   sharing a row with a full table meant an icon and two lines of text floating in
   several hundred pixels of white — indistinguishable from a card that had failed
   to load. It is now a compact tinted panel at the TOP of the card body: same
   information, but it reads as the card's answer rather than as its absence.

   margin-bottom: auto keeps it at the top while the card still stretches to its
   row's height, so the row stays level without the panel growing with it. */
/* THE SAME ANATOMY as the KPI panel above — head row, then one supporting line —
   so a row that mixes "3 unassigned" with "Nothing is overdue" reads as one
   component in two states rather than as two components.

   The icon used to sit in its own column beside the WHOLE text block, so on a
   card with a two-line hint a 32px circle floated at the top-left of a 50px
   column with nothing beside its lower half. It now sits on the message's own
   row, centred against it, and the hint runs the full width beneath. */
.np-dash-empty {
    display: flex; flex-direction: column; justify-content: center;
    gap: var(--np-space-2); flex: 0 0 auto;
    margin-bottom: auto;
    text-align: left;
    background: var(--np-color-surface-tinted);
}
.np-dash-empty__head { display: flex; align-items: center; gap: var(--np-space-2); }
.np-dash-empty--good { background: var(--np-color-success-bg); }
.np-dash-empty--error { background: var(--np-color-error-bg); }
.np-dash-empty__icon {
    width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
    flex: 0 0 auto; background: var(--np-color-surface);
}
.np-dash-empty--good .np-dash-empty__icon { background: var(--np-color-success-bg); color: var(--np-color-success-fg); }
.np-dash-empty--neutral .np-dash-empty__icon,
.np-dash-empty--start .np-dash-empty__icon { background: var(--np-color-surface-muted); color: var(--np-color-text-muted); }
.np-dash-empty--error .np-dash-empty__icon { background: var(--np-color-error-bg); color: var(--np-color-error-fg); }
.np-dash-empty__msg { margin: 0; min-width: 0; font-size: var(--np-fs-small); font-weight: 400; line-height: 1.3; }
.np-dash-empty--good .np-dash-empty__msg { color: var(--np-color-success-fg); }
.np-dash-empty__hint { margin: 0; font-size: var(--np-fs-xsmall); color: var(--np-color-text-muted); line-height: 1.45; }
.np-dash-empty__action {
    align-self: flex-start; font-size: var(--np-fs-small); font-weight: 400;
    color: var(--np-color-brand-primary); background: none; border: 0; cursor: pointer; padding: 0;
}
.np-dash-empty__action:hover { text-decoration: underline; }

/* ── Compact queue cards ─────────────────────────────────────────────────────
   Every queue tile (_QueueKpi) is a short row rather than a big tile:

       [icon]  New referrals           ⓘ
               3                     37d

   ~45% of the old height: a smaller icon, no tinted inner panel, no verb line,
   the oldest wait shortened to "37d" on the right. The whole card is the link.
   "Oldest waiting 37 days" and the next action open in a small panel under the
   card on hover or keyboard focus — also in the link's accessible name, so the
   panel is never the only place they exist (§6.12).

   A band made only of queue tiles fills as a dense strip: as many ~172px cards
   per row as fit, instead of the 3–5 equal columns a big tile needs. Placed after
   the base card, KPI and empty rules so it overrides them. */
.np-dash__grid--even.np-dash__grid--queue {
    grid-template-columns: repeat(auto-fill, minmax(172px, 1fr));
    gap: var(--np-space-2);
}
.np-dash-card--queue {
    display: grid;
    grid-template-columns: 30px minmax(0, 1fr) auto;
    grid-template-areas:
        "icon title info"
        "icon body  body";
    align-items: start;
    column-gap: 10px;
    row-gap: 2px;
    padding: 10px 12px;
    border: 1px solid var(--np-color-border);
    box-shadow: none;
}
/* The header's children join the card grid directly. */
.np-dash-card--queue > .np-dash-card__head { display: contents; }
.np-dash-card--queue .np-dash-card__icon {
    grid-area: icon;
    width: 30px;
    height: 30px;
    box-shadow: none;
}
.np-dash-card--queue .np-dash-card__icon svg { width: 16px; height: 16px; }
.np-dash-card--queue .np-dash-card__titles { grid-area: title; }
.np-dash-card--queue .np-dash-card__title { font-size: var(--np-fs-xsmall); line-height: 1.3; }
.np-dash-card--queue .np-dash-card__sub,
.np-dash-card--queue:hover .np-dash-card__sub,
.np-dash-card--queue:focus-within .np-dash-card__sub { display: none; }
/* Above the stretched link, so the ⓘ stays its own control. */
.np-dash-card--queue .np-dash-card__info {
    grid-area: info;
    position: relative;
    z-index: 2;
    width: 24px;
    height: 24px;
    margin: -3px -4px 0 0;
}
.np-dash-card--queue .np-dash-card__body { grid-area: body; gap: 0; }
.np-dash-card--queue .np-dash-skeleton { gap: var(--np-space-1); }
.np-dash-card--queue .np-dash-skeleton__bar--line { display: none; }
/* No lift on this density — the border darkens instead — and the card rises
   above its neighbours so its detail panel can overlap the row below. */
.np-dash-card--queue:has(a):hover {
    transform: none;
    box-shadow: var(--np-shadow-1);
    border-color: rgba(var(--np-color-brand-primary-rgb), 0.35);
}
.np-dash-card--queue:hover,
.np-dash-card--queue:focus-within { z-index: 6; }

.np-dash-kpi--queue {
    position: static;
    min-height: 0;
    padding: 0;
    gap: 0;
    overflow: visible;
    background: none;
}
.np-dash-card:hover .np-dash-kpi--queue { background: none; }
/* Stretched link: the whole card is the click target. */
.np-dash-kpi--queue::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: var(--np-radius-3);
}
.np-dash-kpi--queue:focus-visible { outline: none; }
.np-dash-kpi--queue:focus-visible::after {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}
.np-dash-kpi--queue .np-dash-kpi__value { font-size: 1.375rem; }
.np-dash-kpi__age {
    margin-left: auto;
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-muted);
    white-space: nowrap;
}
.np-dash-kpi__age--warn { color: var(--np-color-warning-fg); }

/* The detail panel under the card. */
.np-dash-kpi__more {
    display: none;
    position: absolute;
    top: calc(100% - 2px);
    left: -1px;
    right: -1px;
    z-index: 7;
    min-width: 200px;
    flex-direction: column;
    gap: 6px;
    padding: 10px 12px;
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    border-radius: 0 0 var(--np-radius-2) var(--np-radius-2);
    box-shadow: var(--np-shadow-2);
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-muted);
}
.np-dash-card--queue:hover .np-dash-kpi__more,
.np-dash-kpi--queue:focus-visible .np-dash-kpi__more { display: flex; }
/* Never over the list the card has opened, or over its own ⓘ explanation. */
.np-dash__slot.is-drilled .np-dash-kpi__more,
.np-dash-card--queue:has([data-np-card-info][aria-expanded="true"]) .np-dash-kpi__more { display: none; }
.np-dash-kpi__more-row {
    display: flex;
    justify-content: space-between;
    gap: var(--np-space-3);
}
.np-dash-kpi__more-value { color: var(--np-color-text); }
.np-dash-kpi__more-row--warn .np-dash-kpi__more-value { color: var(--np-color-warning-fg); }
.np-dash-kpi__more-go {
    align-self: flex-end;
    color: var(--np-color-brand-secondary);
}

/* An empty queue: one quiet line, not a tinted panel. */
.np-dash-card--queue .np-dash-empty {
    min-height: 0;
    padding: 2px 0 0;
    gap: 2px;
    background: none;
}
.np-dash-card--queue .np-dash-empty__icon { width: 18px; height: 18px; }
.np-dash-card--queue .np-dash-empty__icon svg { width: 12px; height: 12px; }
.np-dash-card--queue .np-dash-empty__msg { font-size: var(--np-fs-xsmall); }
.np-dash-card--queue .np-dash-empty__hint { display: none; }

@media print {
    .np-dash-kpi__more { display: none; }
}

/* ── loading ──────────────────────────────────────────────────────────────── */
/* The skeleton occupies the content's final footprint, so the swap causes no
   reflow. Cards load independently — one slow query never blocks the page. */
.np-dash-skeleton { display: flex; flex-direction: column; gap: var(--np-space-3); }
.np-dash-skeleton__bar {
    display: block; border-radius: var(--np-radius-1);
    background: linear-gradient(90deg,
        var(--np-color-surface-tinted) 25%,
        var(--np-color-surface-muted) 50%,
        var(--np-color-surface-tinted) 75%);
    background-size: 220% 100%;
    animation: np-dash-shimmer 1.5s linear infinite;
}
.np-dash-skeleton__bar--value { height: 40px; width: 68px; }
.np-dash-skeleton__bar--line { height: 14px; width: 60%; }
@keyframes np-dash-shimmer { from { background-position: 120% 0; } to { background-position: -120% 0; } }

/* ── responsive ───────────────────────────────────────────────────────────── */
@media (max-width: 1199px) {
    .np-dash__grid { grid-template-columns: repeat(6, 1fr); }
    /* Everything wider than a KPI becomes full width on a 6-column grid. */
    .np-dash__slot { grid-column: span min(var(--np-dash-span, 4), 6); }

    /* The even band has to be restated at every breakpoint. Its cards span ONE
       column by rule, so without this it inherits the 6-column track above and a
       band of queue tiles lands as six cards across a tablet — each about a
       sixth of the viewport, with the title and the footnote both wrapping to
       nothing readable.

       Three for a band of TILES, two for anything wider. A tile is one figure
       and a short line, so it survives a third of a tablet; a chart does not. */
    .np-dash__grid--even { grid-template-columns: repeat(min(var(--np-dash-cols, 4), 2), 1fr); }
    .np-dash__grid--even[data-np-cols-fill="true"] {
        grid-template-columns: repeat(min(var(--np-dash-cols, 4), 3), 1fr);
    }
    /* The row-filler is a desktop refinement: at two or three columns a short
       final row is already most of the width. */
    .np-dash__grid--even > .np-dash__slot.is-row-filler { grid-column: span 1; }
}
@media (max-width: 767px) {
    .np-dash__grid { grid-template-columns: repeat(2, 1fr); }
    .np-dash__slot { grid-column: span 2; }
    /* One card per row, matching what span 2 of 2 does to every other tier. */
    .np-dash__grid--even,
    .np-dash__grid--even[data-np-cols-fill="true"] { grid-template-columns: 1fr; }
    .np-dash-kpi__value { font-size: 1.75rem; }
    .np-dash__head { flex-direction: column; align-items: stretch; }
    /* One card per row already, so the controls get the full width rather than
       squeezing a select and a two-option toggle onto one line. */
    .np-dash__controls { flex-direction: column; align-items: stretch; }
    .np-dash__filters .np-select--compact { max-width: none; flex: 1 1 auto; }
    /* The 40px stack cap that used to live here is gone: the base cap is 22px, so
       this would have made the bars WIDER on a phone than on a desktop. Columns
       get thinner before they get shorter, and at 22px they already are — the
       plot height is what makes the comparison readable and it is the last thing
       to give. */
    /* Two groups side by side leaves four columns about 35px wide on a phone.
       Stacked, each group keeps its full width and the threshold rule becomes a
       horizontal one — which reads the same way. */
    .np-dash-cols { flex-direction: column; gap: var(--np-space-3); }
    .np-dash-cols__group + .np-dash-cols__group {
        padding-left: 0; border-left: 0;
        padding-top: var(--np-space-3); border-top: 1px dashed var(--np-color-border);
    }
    .np-dash-cols__chart { --np-cols-track-h: 76px; }
    /* Stacked groups: one axis down the left would sit beside the first group and
       lie about the rest. Every value is printed above its own column, so nothing
       is lost by dropping it. */
    .np-dash-cols__axis { display: none; }
    .np-dash-cols--series .np-dash-cols__group + .np-dash-cols__group {
        padding-top: var(--np-space-3); border-top: 0;
    }
}

/* ── drill-down panel — the list, under the card that opened it ───────────────
   Master-detail: a card's drill-down renders the SAME patient list the Patients
   page renders (same action, same partials, same sort keys), injected below the
   tier it belongs to. The point is to stop administrators bouncing out to the
   list page and back for every card they want to check.

   One panel open at a time, deliberately: the injected fragment carries the
   list's dialogs, whose ids would collide if two panels were open at once — and
   two tables under two different tiers is not a master-detail layout, it is a
   second dashboard. */
.np-dash__detail {
    /* A SIBLING of the grid, not a cell in it — see the note in np-dashboard.js.
       Inside the grid it joined the row flow, and on an even grid (which still
       equalises every row) that sized every card row to this panel's height. The
       margin replaces the grid gap it used to inherit, so it sits the same
       distance below the cards. */
    margin-top: var(--np-space-4);
    background: var(--np-color-surface); border-radius: var(--np-radius-3);
    box-shadow: var(--np-shadow-2);
    /* The panel belongs to the card above it — the brand rule ties the two
       together without drawing a connector. */
    border-top: 3px solid var(--np-color-brand-primary);
    overflow: hidden;
    animation: np-dash-detail-in .22s ease both;
}
@keyframes np-dash-detail-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
}
.np-dash__detail-head {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--np-space-3);
    padding: var(--np-space-4) var(--np-space-5);
    background: var(--np-color-surface-tinted);
}
.np-dash__detail-titles { min-width: 0; flex: 1 1 16rem; }
.np-dash__detail-title { margin: 0; font-size: var(--np-fs-small); font-weight: 400; }
.np-dash__detail-sub { margin: 2px 0 0; font-size: var(--np-fs-xsmall); color: var(--np-color-text-muted); }
.np-dash__detail-actions { display: flex; align-items: center; gap: var(--np-space-2); margin-left: auto; }
/* The escape hatch. The panel is for REVIEWING; anything that leaves the page —
   or any action whose dialog the dashboard does not host — belongs on the full
   list, and it must always be one click away. */
.np-dash__detail-open {
    font-size: var(--np-fs-small); font-weight: 400; white-space: nowrap;
    color: var(--np-color-brand-primary); text-decoration: none;
}
.np-dash__detail-open:hover { text-decoration: underline; }
.np-dash__detail-close {
    display: inline-grid; place-items: center; width: 32px; height: 32px;
    border: 0; border-radius: var(--np-radius-1); cursor: pointer;
    background: transparent; color: var(--np-color-text-muted);
    transition: background .16s ease, color .16s ease;
}
.np-dash__detail-close:hover { background: var(--np-color-surface-muted); color: var(--np-color-text-primary); }
.np-dash__detail-body { padding: var(--np-space-5); }
/* The list renders inside its own .np-card on the Patients page. Here the panel
   IS the card, so the inner one drops its chrome rather than nesting a second
   raised box inside the first. */
.np-dash__detail-body > .np-card {
    box-shadow: none; border: 0; padding: 0; margin-bottom: 0; background: transparent;
}
.np-dash__detail-busy { display: flex; justify-content: center; padding: var(--np-space-6); }

@media (max-width: 767px) {
    .np-dash__detail-head, .np-dash__detail-body { padding: var(--np-space-4); }
}

@media (prefers-reduced-motion: reduce) {
    .np-dash-skeleton__bar { animation: none; background: var(--np-color-surface-tinted); }
    .np-dash__detail { animation: none; }
    .np-dash-kpi__go, .np-dash-kpi__value, .np-dash-bar__seg, .np-dash-cols__seg,
    .np-dash-cols__col, .np-dash-cols__value,
    .np-dash-card, .np-dash-card__icon, .np-dash-alert { transition: none; }
    /* Movement is the part reduced-motion asks us to drop; the elevation change
       that says "this is interactive" stays. */
    .np-dash-card:has(a):hover, .np-dash-alert:hover { transform: none; }
}
@media (forced-colors: active) {
    /* Every data-fill class, not just the segmented bar's. The horizontal-bar
       rows (assessment progress, awaiting information, cancellation reasons),
       the workload bars, the funnel and the trend polylines are the same kind
       of mark — stripping their fills in high-contrast mode erases the chart
       while its legend survives. Each mark also carries its values as text, so
       keeping the authored colours only ever ADDS information here. */
    .np-dash-bar__seg, .np-dash-legend__swatch,
    .np-dash-rows__bar, .np-dash-rows__seg,
    .np-dash-chart__bar, .np-dash-chart__seg,
    .np-dash-cols__seg,
    .np-dash-funnel__bar,
    .np-dash-trend polyline { forced-color-adjust: none; }
    /* The axis line and the group threshold are STRUCTURE, not decoration: with
       the fills forced flat they are what still says where zero is and where one
       basis ends. */
    .np-dash-cols__track { border-bottom-color: CanvasText; }
    .np-dash-cols__group + .np-dash-cols__group { border-left-color: CanvasText; }
    /* Shadows are not rendered in forced-colors, so the outline the cards
       dropped has to come back here or every card loses its edge. */
    .np-dash-card { border: 1px solid CanvasText; }
    .np-dash__detail { border: 1px solid CanvasText; }
}


/* ============================================================================
   Care-episode lifecycle (Phase 5)
   ============================================================================
   Three small components used by the workspace lifecycle card, the held-episode
   worklist and the discharge report.

   np-inline-details is a native <details> disclosure styled to read as a
   secondary action. It is NOT a JavaScript-toggled panel on purpose: every
   transition behind it needs a mandatory reason, so the control has to expand
   into a form anyway, and <details> keeps that working with JavaScript
   disabled. */

.np-inline-links {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-3);
    margin-bottom: var(--np-space-3);
}

.np-inline-details {
    margin-top: var(--np-space-3);
    border-top: 1px solid var(--np-color-border);
    padding-top: var(--np-space-3);
}

.np-inline-details__summary {
    cursor: pointer;
    font-weight: 400;
    color: var(--np-color-brand-primary);
    list-style: none;
    padding: var(--np-space-1) 0;
}

.np-inline-details__summary::-webkit-details-marker { display: none; }

.np-inline-details__summary::before {
    content: "\25B8";
    display: inline-block;
    margin-right: var(--np-space-2);
    transition: transform 120ms ease;
}

.np-inline-details[open] > .np-inline-details__summary::before {
    transform: rotate(90deg);
}

/* The summary is the interactive control here, so it carries the focus ring
   the button it replaces would have had (accessibility: :focus-visible on
   every custom control). */
.np-inline-details__summary:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 2px;
    border-radius: var(--np-radius-1);
}

.np-inline-form {
    margin-top: var(--np-space-3);
}

@media (prefers-reduced-motion: reduce) {
    .np-inline-details__summary::before { transition: none; }
}

/* ============================================================================
   Slide-over drawer (np-drawer)
   ----------------------------------------------------------------------------
   A native <dialog> pinned to the right edge and sized full-height, so the
   browser still owns the focus trap, Escape-to-close and the inert background
   while the panel reads as a sliding window. See _NpSupportDrawer.cshtml +
   js/np-support-drawer.js.

   The dialog element itself is the positioning shell (no padding, no border);
   .np-drawer__panel is the visible surface. Clicking the shell outside the
   panel closes the drawer — that hit area is why the two are separate.
   ============================================================================ */
.np-drawer {
    border: 0;
    padding: 0;
    margin: 0 0 0 auto;      /* pin to the right edge */
    max-width: none;
    max-height: none;
    width: min(100vw, 26rem);
    height: 100vh;
    background: transparent;
    overflow: hidden;
    color: var(--np-color-text);
}
.np-drawer::backdrop {
    background: rgba(0, 0, 0, 0.4);
}

.np-drawer__panel {
    display: flex;
    flex-direction: column;
    height: 100%;
    background: var(--np-color-surface);
    border-left: 1px solid var(--np-color-border);
    box-shadow: -12px 0 32px rgba(0, 0, 0, 0.18);
    /* Scoped to [open] so the closed dialog is not animated into view on load. */
    animation: np-drawer-in 180ms ease-out;
}

@keyframes np-drawer-in {
    from { transform: translateX(100%); }
    to   { transform: translateX(0); }
}

.np-drawer__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-3);
    padding: var(--np-space-4);
    border-bottom: 1px solid var(--np-color-border);
    flex: 0 0 auto;
}

.np-drawer__title {
    margin: 0;
    font-size: var(--np-fs-h3, 1.125rem);
    font-weight: 400;
}

.np-drawer__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--np-radius-1);
    background: transparent;
    color: var(--np-color-text-muted);
    cursor: pointer;
    flex: 0 0 auto;
}
.np-drawer__close:hover {
    background: var(--np-color-brand-tint-100, rgba(0, 0, 0, 0.05));
    color: var(--np-color-text);
}
.np-drawer__close:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 2px;
}

.np-drawer__body {
    padding: var(--np-space-4);
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: 0;
}

.np-drawer__intro,
.np-drawer__empty {
    margin: 0 0 var(--np-space-4);
    color: var(--np-color-text-muted);
    line-height: 1.5;
}
.np-drawer__empty { margin-bottom: 0; }

.np-drawer__table {
    width: 100%;
    margin: 0;
}
.np-drawer__table th {
    width: 40%;
    white-space: nowrap;
    font-weight: 400;
    vertical-align: top;
}
.np-drawer__table td {
    word-break: break-word;
}

@media (prefers-reduced-motion: reduce) {
    .np-drawer__panel { animation: none; }
}

@media (forced-colors: active) {
    .np-drawer__panel { border-left: 1px solid CanvasText; }
}

/* ==========================================================================
   PDS candidate picker  (Patients → Find NHS number on PDS)
   --------------------------------------------------------------------------
   A radio group over PEOPLE. Two things have to hold at once: the rows must be
   comparable at a glance (the clinician's whole task here is telling the right
   person from a similar wrong one), and the selected row must be unmistakable
   before they commit — picking the wrong candidate attaches another patient's
   NHS number to this record.

   Follows the np-role-select pattern: a real radio, visually hidden but
   focusable and present in the accessibility tree, with the label carrying the
   affordance. Selection is signalled by border, ground AND the radio's own
   checked state — never colour alone (WCAG 1.4.1).
   ========================================================================== */
.np-candidate-list {
    display: grid;
    gap: var(--np-space-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.np-candidate {
    position: relative;
    display: block;
    padding: var(--np-space-4);
    background: var(--np-color-surface);
    border: 1.5px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    cursor: pointer;
    transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
}
.np-candidate:hover {
    border-color: var(--np-color-border-strong);
    box-shadow: var(--np-shadow-2);
}
/* Guaranteed-visible keyboard focus (works without :has). */
.np-candidate:focus-within {
    border-color: var(--np-color-focus);
    box-shadow: 0 0 0 3px var(--np-color-focus-halo);
}
.np-candidate:has(.np-candidate__radio:checked) {
    border-color: var(--np-color-brand-primary);
    background: var(--np-color-brand-tint-50);
    box-shadow: var(--np-shadow-2);
}

/* Real radio — hidden but focusable and in the a11y tree. */
.np-candidate__radio {
    position: absolute;
    top: var(--np-space-4);
    left: var(--np-space-4);
    width: 1px;
    height: 1px;
    margin: 0;
    opacity: 0;
}

.np-candidate__title {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    flex-wrap: wrap;
    font-weight: 400;
    margin-bottom: var(--np-space-2);
}

/* Identity fields. Tabular figures so NHS numbers and dates line up down the
   list — a column that jitters is a column that is hard to compare. */
.np-candidate__meta {
    display: flex;
    gap: var(--np-space-4);
    flex-wrap: wrap;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
    font-variant-numeric: tabular-nums;
}

@media (forced-colors: active) {
    .np-candidate:has(.np-candidate__radio:checked) { border: 2px solid Highlight; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Password policy panel (_NpPasswordPolicy)

   Shows the organisation's effective password rules next to a password field
   and ticks each one off as the user types. Rule state is carried by the
   icon (shape), the "Met"/"Not met" text, AND the colour — never colour
   alone (WCAG 1.4.1). The strength meter is decorative: its label carries
   the meaning, which is why the bar itself is aria-hidden.
   ───────────────────────────────────────────────────────────────────────── */
.np-password-policy {
    background: var(--np-color-surface-muted);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    padding: var(--np-space-4);
    margin-bottom: var(--np-space-4);
}

.np-password-policy__title {
    font-size: var(--np-fs-small);
    margin: 0 0 var(--np-space-3) 0;
}

.np-password-policy__rules {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--np-space-2);
}

.np-password-policy__rule {
    display: grid;
    grid-template-columns: 20px 1fr auto;
    align-items: center;
    gap: var(--np-space-2);
    color: var(--np-color-text-muted);
}

/* Unmet: hollow ring. Met: filled tick. The SHAPE changes, so the state
   survives greyscale, forced-colors, and colour-blindness. */
.np-password-policy__icon {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: 2px solid currentColor;
    display: inline-block;
    position: relative;
}

.np-password-policy__rule--met {
    color: var(--np-status-fill-success);
}

.np-password-policy__rule--met .np-password-policy__icon {
    background: var(--np-status-fill-success);
    border-color: var(--np-status-fill-success);
}

.np-password-policy__rule--met .np-password-policy__icon::after {
    content: "";
    position: absolute;
    left: 4px;
    top: 1px;
    width: 4px;
    height: 8px;
    border: solid #FFFFFF;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/* Unmet AFTER typing: red ring + cross. Shape again carries the state, so it
   survives greyscale and colour-blindness; red is spent only once the rule has
   actually been failed, never on a pristine form. */
.np-password-policy__rule--unmet {
    color: var(--np-status-fill-danger);
}

.np-password-policy__rule--unmet .np-password-policy__icon {
    border-color: var(--np-status-fill-danger);
}

.np-password-policy__rule--unmet .np-password-policy__icon::before,
.np-password-policy__rule--unmet .np-password-policy__icon::after {
    content: "";
    position: absolute;
    left: 5px;
    top: 1px;
    width: 2px;
    height: 10px;
    border-radius: 1px;
    background: var(--np-status-fill-danger);
}

.np-password-policy__rule--unmet .np-password-policy__icon::before { transform: rotate(45deg); }
.np-password-policy__rule--unmet .np-password-policy__icon::after  { transform: rotate(-45deg); }

/* The per-rule state is now shown by the icon. The text stays in the DOM,
   visually hidden, so screen-reader users still hear "Met" / "Not met" and the
   checklist is never colour-alone. */
.np-password-policy__state {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* "Checked on save" is NOT a state the icon can express, so it stays visible. */
.np-password-policy__state--server {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip: auto;
    font-size: var(--np-fs-small);
    font-style: italic;
    white-space: nowrap;
}

.np-password-policy__strength {
    margin-top: var(--np-space-4);
}

.np-password-policy__meter {
    height: 6px;
    border-radius: var(--np-radius-pill);
    background: var(--np-color-border);
    overflow: hidden;
}

.np-password-policy__meter-fill {
    display: block;
    height: 100%;
    width: 0;
    border-radius: var(--np-radius-pill);
    transition: width 120ms linear;
}

.np-password-policy__meter-fill--weak { background: var(--np-status-fill-danger); }
.np-password-policy__meter-fill--medium { background: var(--np-status-fill-warning); }
.np-password-policy__meter-fill--strong { background: var(--np-status-fill-success); }

.np-password-policy__strength-label {
    margin: var(--np-space-2) 0 0 0;
    font-size: var(--np-fs-small);
}

.np-password-policy__source {
    margin: var(--np-space-3) 0 0 0;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
}

@media (prefers-reduced-motion: reduce) {
    .np-password-policy__meter-fill { transition: none; }
}

/* In forced-colors the custom fills are dropped by the platform, so lean on
   the border + the text state, which are already the primary signals. */
@media (forced-colors: active) {
    .np-password-policy__meter-fill { background: Highlight; }
    .np-password-policy__rule--met .np-password-policy__icon { background: Highlight; }
    .np-password-policy__rule--unmet .np-password-policy__icon::before,
    .np-password-policy__rule--unmet .np-password-policy__icon::after { background: CanvasText; }
}

/* ===========================================================================
   Waitlist stage chips (patient list — Waitlist tab)
   ===========================================================================
   LAYOUT ONLY. Deliberately no colours, no dots and no tone modifiers: every
   chip's status is a shared _NpStatus pill rendered INSIDE it, so the colour
   vocabulary stays the one closed palette in np-status (§6.6). A chip variant
   that painted itself would be a second status language for the same facts —
   which is the divergence np-report-chip--* had to be pinned to the same four
   rungs to avoid.

   The chip supplies the ASSESSMENT NAME beside the pill, because a pill alone
   ("Submitted") cannot say which of a patient's pathways it describes. */
.np-stage-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.np-stage-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 1px 6px 1px 7px;
    border: 1px solid var(--np-color-border, #d9dde3);
    border-radius: 999px;
    background: var(--np-color-surface-muted, #f4f5f7);
    font-size: 0.72rem;
    line-height: 1.5;
    white-space: nowrap;
}

.np-stage-chip__name {
    font-weight: 400;
    color: var(--np-color-text, #1f2430);
}

/* The overflow marker. Muted and italic so it reads as a count of what is not
   shown rather than as another assessment with a status of its own. Its full
   contents are in the title attribute and in visually-hidden text beside it —
   never only in the tooltip, which a keyboard or screen-reader user may not
   reach. */
.np-stage-chip--more {
    font-style: italic;
    font-weight: 400;
    color: var(--np-color-text-muted, #5b6472);
    background: transparent;
    border-style: dashed;
}


/* ============================================================
   Appearance control (_NpAppearance.cshtml) — #441
   ------------------------------------------------------------
   A three-way segmented switch: System / Light / Dark. Native radios in a
   fieldset (§3.11), so radiogroup semantics, arrow-key navigation and the
   no-JS form post all come for free — this file styles them and nothing more.

   TOKENS ONLY in here, no literal colours: the control has to look right in
   BOTH themes, and it is the one component a viewer is looking straight at
   while the theme changes underneath it.
   ============================================================ */
.np-appearance {
    display: block;
}

.np-appearance__fieldset {
    border: 0;
    margin: 0;
    padding: 0;
    min-width: 0;   /* fieldsets refuse to shrink inside flex/grid without this */
}

.np-appearance__legend {
    display: block;
    float: none;
    width: auto;
    padding: 0;
    margin: 0 0 var(--np-space-2) 0;
    font-size: var(--np-fs-small);
    font-weight: 400;
    color: var(--np-color-text-primary);
}

.np-appearance__options {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--np-space-1);
    padding: var(--np-space-1);
    background: var(--np-color-surface-tinted);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
}

.np-appearance__option {
    position: relative;
    margin: 0;
    cursor: pointer;
}

/* The System option cannot resolve prefers-color-scheme without JavaScript, so
   it is hidden until np-boot.js sets .np-js on <html> (before first paint, so
   there is no flash of an option appearing). Offering a control that silently
   does nothing is worse than offering two that work. */
.np-appearance__option--js-only { display: none; }
.np-js .np-appearance__option--js-only { display: block; }

/* Visually hidden but focusable — the label body carries the visible state. */
.np-appearance__input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.np-appearance__body {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--np-space-1);
    padding: var(--np-space-3) var(--np-space-2);
    border-radius: var(--np-radius-1);
    border: 1px solid transparent;
    background: transparent;
    color: var(--np-color-text-muted);
    font-size: var(--np-fs-xsmall);
    font-weight: 400;
    line-height: 1.2;
    text-align: center;
    transition: background-color 0.12s ease-in-out, color 0.12s ease-in-out;
}

.np-appearance__icon { flex: none; }

/* The tick is the SHAPE half of the checked signal. Colour carries the same
   message, but never alone (WCAG 1.4.1) — and neither does position, which is
   why each option also has a distinct icon. */
.np-appearance__tick {
    opacity: 0;
    margin-top: calc(var(--np-space-1) * -1);
}

.np-appearance__option:hover .np-appearance__body {
    background: var(--np-color-surface);
    color: var(--np-color-text-primary);
}

.np-appearance__input:checked + .np-appearance__body {
    background: var(--np-color-surface);
    border-color: var(--np-color-border-strong);
    color: var(--np-color-brand-secondary);
    box-shadow: var(--np-shadow-1);
}

.np-appearance__input:checked + .np-appearance__body .np-appearance__tick {
    opacity: 1;
}

/* :focus-visible on the label body, driven by the hidden input — every custom
   control needs a visible focus state (§3.11). */
.np-appearance__input:focus-visible + .np-appearance__body {
    outline: 3px solid var(--np-color-focus);
    outline-offset: 2px;
}

/* No-JS commit button. np-boot.js sets .np-js before first paint, so this is
   hidden without ever having been seen when JavaScript is available. */
.np-appearance__apply {
    margin-top: var(--np-space-2);
    width: 100%;
    padding: var(--np-space-2) var(--np-space-3);
    font-size: var(--np-fs-small);
    font-weight: 400;
    color: var(--np-color-brand-primary);
    background: var(--np-color-brand-tint-200);
    border: 0;
    border-radius: var(--np-radius-1);
    cursor: pointer;
}
.np-js .np-appearance__apply { display: none; }

/* Inside the user-menu popover the control is the full width of the panel and
   sits under the ACCESSIBILITY heading. */
.np-popover__prefs {
    padding: var(--np-space-3) var(--np-space-4);
}

.np-popover__group-label {
    margin: 0 0 var(--np-space-2) 0;
    font-size: var(--np-fs-xsmall);
    font-weight: 400;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--np-color-text-muted);
}

/* The popover panel is a menu; the control inside it is a form. Reset the
   inherited menu-item spacing so the segmented switch is not stretched. */
.np-popover__prefs .np-appearance__legend {
    font-size: var(--np-fs-xsmall);
    color: var(--np-color-text-muted);
    font-weight: 400;
}

@media (prefers-reduced-motion: reduce) {
    .np-appearance__body { transition: none; }
}

/* Windows High Contrast: system colours must win over both themes. The tick and
   the border carry the state; the background fill does not survive here. */
@media (forced-colors: active) {
    .np-appearance__options { border: 1px solid CanvasText; }
    .np-appearance__input:checked + .np-appearance__body {
        border-color: Highlight;
        color: Highlight;
        forced-color-adjust: none;
    }
    .np-appearance__input:focus-visible + .np-appearance__body { outline-color: Highlight; }
}

/* ── Operational announcements ────────────────────────────────────────────
   Estate-wide notices (planned maintenance, upgrades, incidents) shown at the
   top of every authenticated page. Tones come from the existing closed palette
   (§6.6) — an announcement is not a new colour language, and Warning here means
   what Warning means everywhere else in the product. */
.np-announcements {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-bottom: 1rem;
}
.np-announcement {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.7rem 0.9rem;
    border: 1px solid;
    border-left-width: 4px;
    border-radius: 6px;
    font-size: 0.9rem;
}
.np-announcement__body {
    display: flex;
    flex-wrap: wrap;
    gap: 0.15rem 0.6rem;
    flex: 1 1 auto;
    min-width: 0;
}
.np-announcement__title { font-weight: 400; }
.np-announcement__text { color: inherit; }

/* Borders use currentColor rather than a hex literal so each tone stays legible
   when the dark token block swaps the foreground. A hard-coded border renders a
   light-mode colour on a dark page. */
.np-announcement--info {
    background: var(--np-color-info-bg);
    color: var(--np-color-info-fg);
    border-color: currentColor;
}
.np-announcement--warning {
    background: var(--np-color-warning-bg);
    color: var(--np-color-warning-fg);
    border-color: currentColor;
}
.np-announcement--danger {
    background: var(--np-color-error-bg);
    color: var(--np-color-error-fg);
    border-color: currentColor;
}

.np-announcement__dismiss {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 4px;
    color: inherit;
    cursor: pointer;
}
.np-announcement__dismiss svg { width: 16px; height: 16px; }
.np-announcement__dismiss:hover { border-color: currentColor; }
.np-announcement__dismiss:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 2px;
}

/* ── Interrupting broadcast modal ─────────────────────────────────────────
   A native <dialog>, so focus trapping, background inertness and the backdrop
   come from the browser rather than from hand-rolled JS that usually gets one
   of them wrong. Tones reuse the closed palette; nothing new is introduced. */
.np-broadcast {
    max-width: 34rem;
    width: calc(100% - 2rem);
    border: 1px solid var(--np-color-border);
    border-top: 4px solid currentColor;
    border-radius: 8px;
    padding: 1.5rem;
    background: var(--np-color-surface);
    color: var(--np-color-text);
    box-shadow: 0 18px 48px rgba(0, 0, 0, 0.22);
}
.np-broadcast::backdrop {
    background: rgba(15, 23, 32, 0.55);
    /* Blur keeps the page visible enough to reassure the reader nothing was
       lost, while making it unmistakable that the dialog owns the screen. */
    backdrop-filter: blur(2px);
}
.np-broadcast__head {
    display: flex;
    align-items: flex-start;
    gap: 0.7rem;
    margin-bottom: 0.75rem;
}
.np-broadcast__icon { flex: 0 0 auto; line-height: 0; margin-top: 0.1rem; }
.np-broadcast__title {
    margin: 0;
    font-size: 1.15rem;
    font-weight: 400;
    line-height: 1.3;
    color: var(--np-color-text);
}
.np-broadcast__body {
    margin: 0 0 0.9rem;
    font-size: 0.95rem;
    line-height: 1.55;
    color: var(--np-color-text);
    /* Operator free text, so it must wrap rather than force the dialog wide. */
    overflow-wrap: anywhere;
}
.np-broadcast__meta {
    margin: 0 0 1.25rem;
    font-size: 0.8rem;
    color: var(--np-color-text-muted);
}
.np-broadcast__actions { display: flex; justify-content: flex-end; gap: 0.5rem; }

/* Tone lives on the top rule and the icon (via currentColor on the border),
   never on the body text, which stays at full reading contrast. */
.np-broadcast--info    { color: var(--np-color-info-fg); }
.np-broadcast--warning { color: var(--np-color-warning-fg); }
.np-broadcast--danger  { color: var(--np-color-error-fg); }

.np-broadcast-host:empty { display: none; }

@media (prefers-reduced-motion: no-preference) {
    .np-broadcast[open] { animation: np-broadcast-in 140ms ease-out; }
    @keyframes np-broadcast-in {
        from { opacity: 0; transform: translateY(-6px); }
        to   { opacity: 1; transform: none; }
    }
}

/* ---------- Clinic matching: requirement chips, match reasons ----------
   Used by Find a clinic and the matching coverage view. Kept here rather than
   inline per screen so the vocabulary stays consistent (§6.6 rationale: a
   colour must mean the same thing on every screen).

   The reason list is where the THREE-STATE rule becomes visible to a reader:
   met, not met, and "nobody has recorded this" are three different marks, never
   two. Colour is always paired with a glyph and with text, never used alone
   (WCAG 1.4.1). */

.np-chip-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2);
    list-style: none;
    margin: 0 0 var(--np-space-4) 0;
    padding: 0;
}

.np-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--np-space-2);
    padding: 5px 12px;
    border-radius: var(--np-radius-pill);
    border: 1.5px solid var(--np-color-border-strong);
    background: var(--np-color-surface-muted);
    color: var(--np-color-text-primary);
    font-size: var(--np-fs-small);
    font-weight: 400;
    line-height: 1.4;
}

/* A must-have. Stronger border, not a different hue — the distinction is
   emphasis within one family, and the word "Must have" above the list carries
   the meaning for anyone who cannot see the difference. */
.np-chip--strong {
    border-color: var(--np-color-brand-primary);
    background: var(--np-color-surface-tinted);
    font-weight: 400;
}

.np-chip--muted {
    border-style: dashed;
    border-color: var(--np-color-border);
    color: var(--np-color-text-muted);
}

/* A card nested inside another card — one result inside the results list. */
.np-card--inset {
    box-shadow: none;
    background: var(--np-color-surface);
    margin-bottom: var(--np-space-4);
}

.np-split {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--np-space-4);
}

.np-reason-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
    gap: var(--np-space-2) var(--np-space-5);
    list-style: none;
    margin: var(--np-space-4) 0 0 0;
    padding: 0;
}

.np-reason {
    position: relative;
    padding-left: 26px;
    font-size: var(--np-fs-small);
    line-height: 1.5;
}

/* The glyph is the shape signal that carries the meaning without colour. */
.np-reason::before {
    position: absolute;
    left: 0;
    top: 0;
    font-weight: 400;
    line-height: 1.5;
}

.np-reason--met::before {
    content: "\2713";                          /* check */
    color: var(--np-color-success-fg);
}

.np-reason--notmet::before {
    content: "\2715";                          /* cross */
    color: var(--np-color-error-fg);
}

/* Deliberately NOT a cross and NOT a tick: nobody has answered this, which is a
   third state and must not read as either. */
.np-reason--notconfirmed::before {
    content: "?";
    color: var(--np-color-warning-fg);
}

.np-reason--preferencemissed::before {
    content: "\2013";                          /* en dash */
    color: var(--np-color-text-muted);
}

.np-reason--notmet .np-reason__text {
    font-weight: 400;
}

.np-reason--notconfirmed .np-reason__text {
    font-weight: 400;
    color: var(--np-color-warning-fg);
}

.np-reason--preferencemissed .np-reason__text {
    color: var(--np-color-text-muted);
}

.np-reason__detail {
    display: block;
    font-size: var(--np-fs-xsmall);
}

/* ---------- Matching coverage: metric tiles ---------- */

.np-coverage-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
    gap: var(--np-space-4);
    margin-bottom: var(--np-space-5);
}

.np-coverage {
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    padding: var(--np-space-4);
    background: var(--np-color-surface);
    display: flex;
    flex-direction: column;
    gap: var(--np-space-3);
}

.np-coverage__value {
    font-size: 1.75rem;
    font-weight: 400;
    line-height: 1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}

.np-coverage__value span {
    font-size: var(--np-fs-small);
    font-weight: 400;
    color: var(--np-color-text-muted);
}

.np-coverage__bar {
    height: 6px;
    border-radius: var(--np-radius-pill);
    background: var(--np-color-neutral-100);
    overflow: hidden;
}

.np-coverage__fill {
    height: 100%;
    background: var(--np-status-fill-success);
}

.np-coverage__fill--weakening { background: var(--np-status-fill-warning); }
.np-coverage__fill--blocking  { background: var(--np-status-fill-danger); }

/* A horizontal run of radios — the tri-state capability answer (Yes / No / Not
   recorded). Laid out with flex + gap so the group survives direct edits and
   wraps cleanly on a phone. */
.np-radio-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--np-space-2) var(--np-space-3);
    align-items: center;
}

/* ---------- Capability answer row (clinic / room "what it can offer") ----------
   One question per row: the capability on the left, its three answers on the
   right, so a reader scans the column of answers rather than hunting for each
   control. Collapses to stacked on narrow screens.

   Deliberately a div with role="radiogroup" rather than a nested
   fieldset/legend: a legend inside .np-form-section inherits that rule's
   full-width + bottom-border treatment, which pushed the answers into a narrow
   column at the far right. aria-labelledby names the group just as well. */
.np-answer {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: var(--np-space-5);
    row-gap: var(--np-space-1);
    padding: var(--np-space-3) 0;
    border-bottom: 1px solid var(--np-color-surface-tinted);
}

.np-answer:last-of-type {
    border-bottom: 0;
}

.np-answer__label {
    grid-column: 1;
    font-weight: 400;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-primary);
}

.np-answer .np-hint {
    grid-column: 1;
    margin: 0;
}

/* The answers span both label and hint rows so they stay vertically centred
   against a question that needed two lines to explain itself. */
.np-answer__choices {
    grid-column: 2;
    grid-row: 1 / -1;
    flex-wrap: nowrap;
}

/* Compact enough that three sit comfortably on one line beside the question. */
.np-answer .np-radio {
    padding: var(--np-space-2) var(--np-space-3);
    white-space: nowrap;
}

@media (max-width: 48rem) {
    .np-answer {
        grid-template-columns: minmax(0, 1fr);
    }

    .np-answer__choices {
        grid-column: 1;
        grid-row: auto;
        flex-wrap: wrap;
        margin-top: var(--np-space-2);
    }
}

/* Suggested appointment times on a Find-a-clinic result. */
.np-offered {
    margin-top: var(--np-space-4);
    padding-top: var(--np-space-4);
    border-top: 1px solid var(--np-color-border);
    display: flex;
    flex-direction: column;
    gap: var(--np-space-2);
}

/* Why a time is being suggested ahead of an earlier one. Sits inside the chip
   so the reason travels with the time rather than needing a legend. */
.np-chip__note {
    font-size: var(--np-fs-xsmall);
    font-weight: 400;
    color: var(--np-color-text-muted);
}

/* A chip that is a link — a suggested appointment time you can click to open
   the booking form with it filled in. Reads as actionable rather than as a
   label: pointer, hover lift, and a visible focus ring for keyboard users. */
a.np-chip--action {
    text-decoration: none;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}

a.np-chip--action:hover {
    border-color: var(--np-color-brand-primary);
    background: var(--np-color-surface-tinted);
    text-decoration: none;
    box-shadow: var(--np-shadow-2);
}

a.np-chip--action:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 2px;
}

/* ---------- Find a clinic: context widgets and clinician grouping ----------
   Two expandable widgets side by side above the results — how urgent the case
   is, and what the patient needs. Both open by default: they are the facts a
   booker needs BEFORE choosing anything, so hiding them behind a click would
   optimise the wrong thing. They collapse for someone who already knows. */

.np-widget-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
    gap: var(--np-space-4);
    margin-bottom: var(--np-space-5);
}

.np-widget {
    background: var(--np-color-surface);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-3);
    box-shadow: var(--np-shadow-1);
    overflow: hidden;
}

.np-widget__summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-3);
    padding: var(--np-space-4) var(--np-space-5);
    cursor: pointer;
    list-style: none;
    font-weight: 400;
}

/* The default disclosure marker is replaced by a chevron on the right, so the
   title and the status pill can share one clean row. */
.np-widget__summary::-webkit-details-marker { display: none; }

.np-widget__summary::after {
    content: "";
    width: 8px;
    height: 8px;
    border-right: 2px solid var(--np-color-text-muted);
    border-bottom: 2px solid var(--np-color-text-muted);
    transform: rotate(45deg);
    transition: transform 0.15s ease;
    flex: 0 0 auto;
    margin-left: auto;
}

.np-widget[open] > .np-widget__summary::after { transform: rotate(-135deg); }

.np-widget__summary:hover { background: var(--np-color-surface-muted); }

.np-widget__summary:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: -2px;
}

.np-widget__title {
    font-size: var(--np-fs-small);
    color: var(--np-color-text-primary);
}

.np-widget__body {
    padding: 0 var(--np-space-5) var(--np-space-5);
    border-top: 1px solid var(--np-color-surface-tinted);
    padding-top: var(--np-space-4);
}

.np-widget__list {
    display: grid;
    grid-template-columns: minmax(0, 10rem) minmax(0, 1fr);
    gap: var(--np-space-2) var(--np-space-4);
    margin: 0;
}

.np-widget__list dt {
    font-size: var(--np-fs-small);
    color: var(--np-color-text-muted);
    font-weight: 400;
}

.np-widget__list dd {
    margin: 0;
    font-size: var(--np-fs-small);
}

@media (max-width: 34rem) {
    .np-widget__list { grid-template-columns: minmax(0, 1fr); }
    .np-widget__list dd { margin-bottom: var(--np-space-2); }
}

/* One clinician's free times within a clinic. */
.np-clinician {
    padding: var(--np-space-3) 0;
    border-top: 1px solid var(--np-color-surface-tinted);
    display: flex;
    flex-direction: column;
    gap: var(--np-space-2);
}

.np-clinician:first-of-type { border-top: 0; }

.np-clinician__name {
    font-weight: 400;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-primary);
}

/* Centred form heading — used where a page has no hero beside it and the form
   is the whole page (account activation). */
.np-landing__form-title--centered {
    text-align: center;
}

/* Practical notes on a Find-a-clinic result — parking, which floor, quirks.
   Deliberately UNDERSTATED. An earlier version used a status pill beside the
   real ones, which borrowed the status vocabulary for something that is only
   context and made the header cluttered. This sits on the clinic's own metadata
   line instead: a quiet toggle that reads as "there is more to know here", not
   as a verdict on the clinic. */

.np-clinic-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--np-space-2) var(--np-space-3);
    margin: 0;
}

.np-notes {
    display: inline-block;
}

.np-notes__toggle {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    cursor: pointer;
    list-style: none;
    color: var(--np-color-brand-primary);
    font-size: var(--np-fs-xsmall);
    font-weight: 400;
    line-height: 1.4;
    padding: 2px 2px 2px 0;
    border-radius: var(--np-radius-1);
    user-select: none;
}

.np-notes__toggle::-webkit-details-marker { display: none; }

.np-notes__toggle:hover { text-decoration: underline; }

.np-notes__toggle:focus-visible {
    outline: 2px solid var(--np-color-focus);
    outline-offset: 2px;
}

/* The info glyph stays put — it says WHAT this is, and rotating a circle
   signals nothing. A small caret carries the open/closed state instead. */
.np-notes__toggle::after {
    content: "";
    width: 5px;
    height: 5px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg) translate(-1px, -1px);
    transition: transform 0.15s ease;
    margin-left: 2px;
    flex: 0 0 auto;
}

.np-notes[open] > .np-notes__toggle::after {
    transform: rotate(-135deg) translate(-1px, -1px);
}

@media (prefers-reduced-motion: reduce) {
    .np-notes__toggle::after { transition: none; }
}

/* Full width once open, breaking out of the inline metadata row so the text
   gets a readable measure rather than a narrow column beside the code. */
.np-notes[open] { display: block; width: 100%; }

.np-notes__body {
    margin-top: var(--np-space-2);
    padding: var(--np-space-3) var(--np-space-4);
    background: var(--np-color-surface-muted);
    border: 1px solid var(--np-color-border);
    border-radius: var(--np-radius-2);
    max-width: 72ch;
}

/* Free text somebody typed: keep their line breaks, and wrap long unbroken
   strings rather than forcing the card to scroll sideways. */
.np-notes__text {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-size: var(--np-fs-small);
    color: var(--np-color-text-primary);
    margin: 0 0 var(--np-space-2) 0;
}

.np-notes__text:last-child { margin-bottom: 0; }

/* The room's name runs into its own note rather than sitting on a heading line
   of its own — a one-line note under a heading is mostly heading. */
.np-notes__room {
    font-weight: 400;
    margin-right: 6px;
}

.np-notes__room::after {
    content: "4";
    margin-left: 6px;
    color: var(--np-color-text-muted);
    font-weight: 400;
}

/* ── Dashboard "Alerts (N)" strip ─────────────────────────────────────────────
   Product owner, Oct 2026: the pinned alert tier is ONE compact card — a header
   row (bell · "Alerts" · how many are showing · collapse chevron) over a single
   row of alert ITEMS, each one line: icon · title · count · | Review →.

   Rendered by _DashSection (header + body) and _WidgetSlot (each item keeps the
   ordinary slot markup, so np-dashboard.js loads, reveals and removes it exactly
   as before); _AttentionKpi / _SendFailures supply the count and the link.

     * Every item starts ONE line high. A title too long for its share of the row
       ends in "…"; on hover OR keyboard focus it wraps onto a second line to show
       in full (never hover alone — §6.12; the full title is also always the
       item's heading text for a screen reader).
     * The count and "Review →" never shrink; only the title gives way.
     * Items share the row equally and wrap on a narrow screen. The band grids
       are flattened (display: contents) so items of different widget sizes still
       sit on one row. Dividers are each item's left edge; the body clips its own
       left edge so a row never STARTS with a divider. Item explanations (ⓘ)
       open ABOVE the item, so nothing clips them. */
.np-dash-alerts {
    gap: var(--np-space-2);
    padding: var(--np-space-3) var(--np-space-4);
    background: var(--np-color-surface);
    border-radius: var(--np-radius-3);
    box-shadow: var(--np-shadow-1);
}
.np-dash-alerts__head {
    display: flex;
    align-items: center;
    gap: var(--np-space-2);
    min-height: 32px;
}
.np-dash-alerts__bell {
    display: inline-flex;
    color: var(--np-color-brand-primary);
}
.np-dash-alerts__title { font-size: 1.125rem; }
.np-dash-alerts__count {
    display: inline-grid;
    place-items: center;
    min-width: 24px;
    height: 24px;
    padding: 0 7px;
    border-radius: var(--np-radius-pill);
    background: var(--np-color-surface-tinted);
    color: var(--np-color-brand-primary);
    font-size: var(--np-fs-small);
    line-height: 1;
}
.np-dash-alerts__toggle {
    margin-left: auto;
    width: 32px;
    height: 32px;
    border: 0;
    border-radius: 50%;
    padding: 0;
    display: grid;
    place-items: center;
    background: transparent;
    color: var(--np-color-brand-primary);
    cursor: pointer;
    transition: background .16s ease;
}
.np-dash-alerts__toggle svg { transition: transform .18s ease; }
.np-dash-alerts__toggle:hover { background: var(--np-color-surface-tinted); }
.np-dash-alerts__toggle:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: 2px;
}
.np-dash-alerts__toggle[aria-expanded="false"] svg { transform: rotate(180deg); }

/* The items' row. */
.np-dash-alerts__body {
    display: flex;
    flex-wrap: wrap;
    row-gap: var(--np-space-1);
    /* Hides ONLY the 1px divider an item draws on its own left edge when it
       STARTS a row. Open above, right and below, so an item's ⓘ explanation —
       which opens above the item — is never cut off. */
    clip-path: inset(-100vh -100vw -100vh 0);
}
.np-dash-alerts__body[hidden] { display: none; }
.np-dash-alerts .np-dash__grid { display: contents; }

.np-dash-alerts .np-dash__slot.np-dash-card {
    flex: 1 1 260px;
    min-width: 0;
    flex-direction: row;
    align-items: center;
    gap: var(--np-space-2);
    margin: 0 0 0 -1px;
    padding: var(--np-space-1) var(--np-space-3);
    border-radius: 0;
    background: transparent;
    box-shadow: -1px 0 0 var(--np-color-border);
    transform: none;
}
.np-dash-alerts .np-dash__slot.np-dash-card[hidden] { display: none; }
.np-dash-alerts .np-dash-card:has(a):hover,
.np-dash-alerts .np-dash-card:focus-within {
    background: var(--np-color-surface-tinted);
    box-shadow: -1px 0 0 var(--np-color-border);
    transform: none;
}

.np-dash-alerts .np-dash-card__head { display: contents; }
.np-dash-alerts .np-dash-card__icon {
    order: 1;
    width: 32px;
    height: 32px;
    box-shadow: none;
}
.np-dash-alerts .np-dash-card:has(a):hover .np-dash-card__icon { transform: none; box-shadow: none; }
.np-dash-alerts .np-dash-card__titles {
    order: 2;
    flex: 0 1 auto;
    min-width: 0;
}
.np-dash-alerts .np-dash-card__title {
    font-size: 0.9375rem;
    line-height: 1.35;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* The full title, on a second line, for a mouse AND a keyboard. */
.np-dash-alerts .np-dash-card:hover .np-dash-card__title,
.np-dash-alerts .np-dash-card:focus-within .np-dash-card__title {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
}
/* The subtitle stays for a screen reader only — the row has no room for it. */
.np-dash-alerts .np-dash-card__sub,
.np-dash-alerts .np-dash-card:hover .np-dash-card__sub,
.np-dash-alerts .np-dash-card:focus-within .np-dash-card__sub {
    display: block;
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Count and Review: one link, never shrinking — only the title gives way. */
.np-dash-alerts .np-dash-card__body {
    order: 3;
    flex: 1 0 auto;
    display: flex;
    gap: 0;
}
.np-dash-alert-item__link {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--np-space-3);
    min-height: 36px;
    text-decoration: none;
    border-radius: var(--np-radius-1, 4px);
}
.np-dash-alert-item__link:focus-visible {
    outline: 2px solid var(--np-color-brand-primary);
    outline-offset: -2px;
}
.np-dash-alert-item__count {
    font-size: 1.0625rem;
    line-height: 1;
    color: var(--np-color-error-fg);
}
.np-dash-alert-item__go {
    padding-left: var(--np-space-3);
    border-left: 1px solid var(--np-color-border);
    font-size: var(--np-fs-small);
    color: var(--np-color-brand-primary);
    white-space: nowrap;
}
.np-dash-alert-item__link:hover .np-dash-alert-item__go { text-decoration: underline; }

/* The item's ⓘ sits last; its explanation opens above the item like every
   card's (see .np-dash-card__info-pop). */
.np-dash-alerts .np-dash-card__info { order: 4; margin-left: 0; width: 24px; height: 24px; }
.np-dash-alerts .np-dash-card__info-pop { right: 0; }

/* A failed alert stays visible (with its retry) but on one line. */
.np-dash-alerts .np-dash-empty { flex-direction: row; align-items: center; gap: var(--np-space-2); padding: 0; background: transparent; }
.np-dash-alerts .np-dash-empty__hint { display: none; }

@media (prefers-reduced-motion: reduce) {
    .np-dash-alerts__toggle svg { transition: none; }
}
@media (max-width: 767px) {
    .np-dash-alerts .np-dash__slot.np-dash-card { flex-basis: 100%; }
}
