/* =============================================================================
   iERA Hub — design system

   Inherited from the previous application so the system stays recognisable:
   the emerald primary, the dark green sidebar, Inter, the rounded-corner
   language. Those are deliberate carry-overs, not defaults.

   What is new is driven by what this application actually does — approve and
   track charity money across countries, often from a phone on a slow
   connection:

     1. Money is set in tabular numerals so amounts align in a column. A
        misread figure in this system is a misapproved payment.
     2. Status is one vocabulary used identically everywhere. The real content
        of this app is workflow state, so colour here carries meaning and is
        never decoration.
     3. Data tables become labelled stacked rows on narrow screens rather than
        scrolling sideways. Country staff are on phones.
     4. The approval chain has its own component. Three sequential approvers
        is the heart of the system.

   Section order: tokens, reset, typography, layout, components, utilities,
   responsive, print.
   ============================================================================= */

/* -----------------------------------------------------------------------------
   1. FONT
   Self-hosted: the Content-Security-Policy blocks external font sources, and
   this removes a third-party request from every page load.
   Drop inter-variable.woff2 into /assets/fonts/.
   -------------------------------------------------------------------------- */

@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 300 800;
    font-display: swap;
    src: url('../fonts/inter-variable.woff2') format('woff2');
}

/* -----------------------------------------------------------------------------
   2. TOKENS
   -------------------------------------------------------------------------- */

:root {
    /* Brand — iERA official palette, converted to HSL components.
       Stored as components rather than hex so the design system can derive
       hover and tint shades by adjusting lightness.

         Deep navy   #062A4A   208 85% 16%   anchor, navigation
         Blue        #087CC1   202 92% 39%   interactive
         Cyan        #00A3E0   196 100% 44%  highlight
         Gold        #F4C400    48 100% 48%  attention
         Magenta     #D41473   330 83% 45%   emphasis, decline
         Green       #36A64C   132 51% 43%   confirmation

       Navy is the anchor but not the interactive colour: buttons in near-black
       navy read as disabled. The brand blue carries actions instead, which
       also clears 4.5:1 against white for the text on top of it. */

    --primary:              202 92% 39%;
    --primary-hover:        202 92% 31%;
    --primary-fg:           0 0% 100%;
    --primary-soft:         202 90% 96%;
    --primary-soft-fg:      202 92% 26%;

    --brand-navy:           208 85% 16%;
    --brand-cyan:           196 100% 44%;
    --brand-gold:            48 100% 48%;
    --brand-magenta:        330 83% 45%;
    --brand-green:          132 51% 43%;

    /* Neutrals — cooled slightly towards the navy so greys sit with it
       rather than against it. */
    --bg:                   210 25% 98%;
    --surface:              0 0% 100%;
    --surface-sunken:       210 22% 96%;
    --ink:                  208 40% 13%;
    --ink-muted:            208 14% 42%;
    --ink-subtle:           208 12% 56%;
    --line:                 208 20% 90%;
    --line-strong:          208 18% 82%;

    /* Navigation — the navy anchor */
    --nav-bg:               208 85% 16%;
    --nav-fg:               205 25% 80%;
    --nav-fg-strong:        0 0% 100%;
    --nav-active:           207 70% 23%;
    --nav-accent:           196 100% 44%;
    --nav-line:             207 50% 26%;

    /* Status palette, drawn from the brand accents.
       These map to workflow states, not to a generic success/warning scale.
       Every status in the system resolves to exactly one of these.

       Magenta carries "rejected" because the brand has no red. It is distinct
       enough from green, gold and blue to be unmistakable, and using an
       off-brand red for the one thing people remember most would undo the
       palette everywhere else. */
    --st-neutral:           208 12% 46%;   /* Draft, Not started        */
    --st-neutral-bg:        210 22% 95%;
    --st-progress:          202 92% 39%;   /* Submitted, In review      */
    --st-progress-bg:       202 90% 96%;
    --st-waiting:            42 95% 34%;   /* Queried, Awaiting you     */
    --st-waiting-bg:         48 100% 94%;
    --st-good:              132 51% 34%;   /* Approved, Paid            */
    --st-good-bg:           132 45% 94%;
    --st-bad:               330 83% 42%;   /* Rejected, Overspend       */
    --st-bad-bg:            330 80% 97%;

    /* Type scale.
       A 1.2 ratio from a 15px base, rounded to whole pixels so text lands on
       the pixel grid rather than being resampled. Tight at the small end
       because finance screens are dense, opening up at the top where a page
       title needs presence. */
    --t-xs:     0.75rem;    /* 12px — table meta, helper text  */
    --t-sm:     0.8125rem;  /* 13px — labels, badges           */
    --t-base:   0.9375rem;  /* 15px — body, inputs, tables     */
    --t-md:     1.0625rem;  /* 17px — card titles              */
    --t-lg:     1.3125rem;  /* 21px — section headings         */
    --t-xl:     1.625rem;   /* 26px — page titles              */
    --t-2xl:    2rem;       /* 32px — figures, empty states    */

    /* Tracking. Large text needs negative tracking to stop looking loose;
       small text needs a touch of positive to stay legible. Uniform letter
       spacing is a tell that nobody looked at the type. */
    --track-tight:  -0.018em;
    --track-snug:   -0.008em;
    --track-normal:  0;
    --track-wide:    0.01em;

    /* Spacing — 4px base */
    --s-1: 0.25rem;  --s-2: 0.5rem;   --s-3: 0.75rem;  --s-4: 1rem;
    --s-5: 1.25rem;  --s-6: 1.5rem;   --s-8: 2rem;     --s-10: 2.5rem;
    --s-12: 3rem;    --s-16: 4rem;

    /* Radii, by role rather than by habit. One radius on everything
       flattens hierarchy: a badge and a page panel are not the same kind of
       object and should not share a corner. */
    --r-sm: 0.375rem;   /* badges, small controls */
    --r-md: 0.5rem;     /* inputs, buttons        */
    --r-lg: 0.625rem;   /* panels, cards          */
    --r-full: 999px;    /* pills, avatars         */

    /* Elevation is reserved for things that float — dialogs, the mobile
       drawer, the sign-in card. Panels sit flat on the page with a hairline
       border. Putting the same soft shadow under every card is the SaaS-kit
       default, and it makes twelve panels read as twelve equally urgent
       things. Shadows are tinted with the brand navy rather than pure black,
       so they sit in the palette instead of greying it. */
    --shadow-sm: 0 1px 2px hsl(208 40% 12% / 0.06);
    --shadow-md: 0 6px 16px -6px hsl(208 40% 12% / 0.14);
    --shadow-lg: 0 16px 40px -16px hsl(208 40% 12% / 0.26);

    --nav-w: 15rem;
    --topbar-h: 3.5rem;
}

/* -----------------------------------------------------------------------------
   3. RESET
   -------------------------------------------------------------------------- */

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

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: hsl(var(--bg));
    color: hsl(var(--ink));
    font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --font-mono: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
    font-size: var(--t-base);
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, p, figure { margin: 0; }
h1, h2, h3, h4 { font-weight: 600; line-height: 1.25; letter-spacing: -0.011em; }

img, svg { display: block; max-width: 100%; }

a {
    color: hsl(var(--primary));
    text-decoration: none;
    text-underline-offset: 0.2em;
}
a:hover { text-decoration: underline; }

/* Keyboard focus must always be visible. Removing outlines without a
   replacement makes the whole application unusable without a mouse.
   
   A white halo sits between the ring and the element, so the ring stays
   visible against a dark navigation panel as well as a white page — one rule
   that works on every surface, rather than a special case per context. */
:focus-visible {
    outline: 2px solid hsl(var(--primary));
    outline-offset: 2px;
    border-radius: var(--r-sm);
    box-shadow: 0 0 0 2px hsl(var(--surface));
}

.sidebar :focus-visible {
    outline-color: hsl(var(--nav-accent));
    box-shadow: 0 0 0 2px hsl(var(--nav-bg));
}

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

/* -----------------------------------------------------------------------------
   4. TYPOGRAPHY
   -------------------------------------------------------------------------- */

.page-title  { font-size: var(--t-xl); letter-spacing: var(--track-tight); }

/* Capped at 68 characters. Longer lines make the eye lose its place on the
   return sweep, and this text is frequently read in a second language. */
.page-lede {
    color: hsl(var(--ink-muted));
    margin-top: 0.375rem;
    max-width: 68ch;
    line-height: 1.55;
}
.section-title { font-size: var(--t-lg); }
.card-title  { font-size: var(--t-md); }
.text-muted  { color: hsl(var(--ink-muted)); }
.text-subtle { color: hsl(var(--ink-subtle)); font-size: var(--t-sm); }

/* Money and any figure that appears in a column.
   Tabular numerals keep digits the same width so amounts line up vertically.
   In a system where someone approves a five-figure sum, a column that does
   not align is a column that gets misread. */
.figure {
    font-variant-numeric: tabular-nums slashed-zero;
    font-feature-settings: 'tnum' 1, 'zero' 1;
    letter-spacing: var(--track-snug);
}

/* The one place this interface raises its voice. A five-figure sum is the
   most consequential thing on any screen it appears on, so it gets the
   weight, the tracking and the optical sizing. Everything around it stays
   quiet. */
.figure--lg {
    font-size: var(--t-2xl);
    font-weight: 680;
    letter-spacing: var(--track-tight);
    line-height: 1.1;
}

.figure--neg { color: hsl(var(--st-bad)); }

/* The currency code is held back so the number reads first. It qualifies the
   amount; it is not the information. */
.figure__unit {
    font-size: 0.68em;
    font-weight: 550;
    color: hsl(var(--ink-subtle));
    letter-spacing: var(--track-wide);
    margin-right: 0.28em;
    vertical-align: 0.06em;
}

/* -----------------------------------------------------------------------------
   5. APPLICATION SHELL
   -------------------------------------------------------------------------- */

.app { display: flex; min-height: 100vh; }

.sidebar {
    width: var(--nav-w);
    flex-shrink: 0;
    background: hsl(var(--nav-bg));
    color: hsl(var(--nav-fg));
    display: flex;
    flex-direction: column;
    padding: var(--s-4) var(--s-3);

    /* Stays put while the page scrolls.
       Sticky rather than fixed: fixed would take the sidebar out of the flow
       and the content beside it would slide underneath, needing a margin
       offset kept in step with --nav-w by hand — and that margin would then
       be wrong the moment the rail collapses. Sticky keeps it a flex child,
       so the column width and the rail toggle keep working on their own.
       On a narrow screen the media query below turns it into a fixed drawer,
       which overrides all of this. */
    position: sticky;
    top: 0;
    height: 100vh;
    /* Without this, the flex default of stretching to the container's height
       would make the sidebar as tall as the whole page — and an element the
       same height as its scroll container has nowhere to stick to, so sticky
       would silently do nothing. */
    align-self: flex-start;
    /* A long menu on a short screen has to be able to scroll inside itself,
       or its last items become unreachable. */
    overflow-y: auto;
    /* Reaching the end of the menu must not start scrolling the page behind
       it — that reads as the sidebar dragging the content with it, which is
       the behaviour being fixed here. */
    overscroll-behavior: contain;
}

.sidebar__brand {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    padding: var(--s-2) var(--s-2) var(--s-5);
    color: hsl(var(--nav-fg-strong));
    font-weight: 600;
}

/* Constrain an uploaded logo in both directions. Height alone lets a wide
   mark run past the sidebar; width alone lets a tall one tower over the nav.
   The sidebar is already navy, so no chip is needed here. */
.sidebar__brand img {
    height: 2rem;
    width: auto;
    max-width: 10rem;
    object-fit: contain;
}

.brand-mark {
    width: 2rem;
    height: 2rem;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: hsl(var(--primary));
    color: hsl(var(--primary-fg));
    display: grid;
    place-items: center;
    font-weight: 700;
    font-size: var(--t-sm);
    letter-spacing: -0.02em;
}

.sidebar__nav { display: flex; flex-direction: column; gap: 1px; }

/* A nav group heading, in sentence case. Not a tracked-out capitalised
   eyebrow — those appear on every generated interface regardless of subject,
   and they shout a label nobody needs to read twice. */
.sidebar__group {
    padding: var(--s-5) var(--s-3) 0.375rem;
    margin: 0;
    font-size: var(--t-xs);
    font-weight: 500;
    color: hsl(var(--nav-fg) / 0.5);
}

/* No gap above the first group — it would leave the menu starting with an
   empty band. */
.sidebar__group:first-of-type { padding-top: var(--s-4); }

/* The sidebar is taller than the viewport on small laptops once every group
   is shown, so it scrolls independently of the page. */
.sidebar__nav {
    overflow-y: auto;
    flex: 1;
    /* Room for the account block pinned beneath it. */
    padding-bottom: var(--s-4);
    scrollbar-width: thin;
}

.sidebar__link {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3);
    padding: 0.4375rem var(--s-3);
    border-radius: var(--r-md);
    color: hsl(var(--nav-fg));
    font-size: var(--t-base);
    text-decoration: none;
    transition: background-color 0.14s ease, color 0.14s ease;
}

.sidebar__link:hover {
    background: hsl(var(--nav-active) / 0.7);
    color: hsl(var(--nav-fg-strong));
    text-decoration: none;
}

.sidebar__link[aria-current='page'] {
    background: hsl(var(--nav-active));
    color: hsl(var(--nav-fg-strong));
    font-weight: 550;
}

/* The current-page marker is a short bar centred on the link rather than a
   full-height inset border. It reads as a pointer to this item, not as a
   left edge belonging to the panel. */
.sidebar__link[aria-current='page']::before {
    content: '';
    position: absolute;
    left: -0.75rem;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 1.125rem;
    border-radius: 0 2px 2px 0;
    background: hsl(var(--nav-accent));
}

/* Count of items waiting on this person. The one place the nav raises its
   voice, because "what needs me" is the question this system exists to
   answer. */
.sidebar__count {
    min-width: 1.35rem;
    padding: 0 0.4rem;
    border-radius: 999px;
    background: hsl(var(--nav-accent));
    color: hsl(152 40% 10%);
    font-size: var(--t-xs);
    font-weight: 650;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.sidebar__footer {
    margin-top: auto;
    padding-top: var(--s-4);
    border-top: 1px solid hsl(var(--nav-line));
    font-size: var(--t-sm);
    color: hsl(var(--nav-fg) / 0.75);
}

/* overflow-x here rather than on html or body: hidden makes an element a
   scroll container, and one on html or body silently stops position: sticky
   working inside it — which is what made the sidebar scroll away with the
   page. Wide tables scroll inside .table-wrap, which is where sideways
   movement belongs. */
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow-x: hidden; }

.topbar {
    height: var(--topbar-h);
    display: flex;
    align-items: center;
    gap: var(--s-2);
    padding: 0 var(--s-5);
    background: hsl(var(--surface) / 0.85);
    /* Frosted rather than opaque, so content scrolling underneath is
       suggested rather than hidden. Falls back to the flat colour where
       backdrop-filter is unsupported. */
    backdrop-filter: saturate(1.6) blur(10px);
    border-bottom: 1px solid hsl(var(--line));
    position: sticky;
    top: 0;
    z-index: 20;
}

@supports not (backdrop-filter: blur(1px)) {
    .topbar { background: hsl(var(--surface)); }
}

.topbar__title { font-size: var(--t-base); font-weight: 600; }
.topbar__spacer { margin-left: auto; }

.content { flex: 1; padding: var(--s-6); }
.content__inner { max-width: 78rem; margin: 0 auto; }

.page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--s-4);
    margin-bottom: var(--s-6);
}

/* Mobile navigation trigger — hidden on desktop where the sidebar is always
   visible. */
.nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    flex-shrink: 0;
    padding: 0;
    margin-right: var(--s-2);
    background: transparent;
    color: hsl(var(--ink-muted));
    border: 0;
    border-radius: var(--r-md);
    cursor: pointer;
    transition: background-color 0.14s ease, color 0.14s ease;
}

.nav-toggle:hover {
    background: hsl(var(--surface-sunken));
    color: hsl(var(--ink));
}

/* -----------------------------------------------------------------------------
   6. BUTTONS
   -------------------------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s-2);
    padding: 0.5625rem var(--s-4);
    font: inherit;
    font-size: var(--t-base);
    font-weight: 550;
    line-height: 1.2;
    letter-spacing: var(--track-snug);
    border: 1px solid transparent;
    border-radius: var(--r-md);
    cursor: pointer;
    white-space: nowrap;
    /* Transitions respond to what the person did. Nothing animates on its
       own, and nothing fades in on page load. */
    transition: background-color 0.14s ease, border-color 0.14s ease,
                box-shadow 0.14s ease, transform 0.06s ease;
}

/* A press should feel like a press. One pixel, not a bounce. */
.btn:active:not(:disabled) { transform: translateY(1px); }

.btn:disabled { opacity: 0.55; cursor: not-allowed; }

.btn--primary {
    background: hsl(var(--primary));
    color: hsl(var(--primary-fg));
    /* An inset highlight along the top edge. Reads as a raised surface
       rather than a flat rectangle, without a drop shadow underneath. */
    box-shadow: inset 0 1px 0 hsl(0 0% 100% / 0.14);
}

.btn--primary:hover:not(:disabled) { background: hsl(var(--primary-hover)); }

.btn--secondary {
    background: hsl(var(--surface));
    color: hsl(var(--ink));
    border-color: hsl(var(--line-strong));
    box-shadow: var(--shadow-sm);
}

.btn--secondary:hover:not(:disabled) {
    background: hsl(var(--surface-sunken));
    border-color: hsl(var(--ink-subtle));
}

.btn--ghost { background: transparent; color: hsl(var(--ink-muted)); }
.btn--ghost:hover:not(:disabled) { background: hsl(var(--surface-sunken)); color: hsl(var(--ink)); }

/* Reject, deactivate, delete. Outlined rather than filled: a destructive
   action should be reachable but should not be the brightest thing on screen. */
.btn--danger {
    background: hsl(var(--surface));
    color: hsl(var(--st-bad));
    border-color: hsl(var(--st-bad) / 0.35);
}
.btn--danger:hover:not(:disabled) { background: hsl(var(--st-bad-bg)); }

.btn--sm { padding: 0.375rem var(--s-3); font-size: var(--t-sm); }
.btn--block { width: 100%; }

.btn-row { display: flex; flex-wrap: wrap; gap: var(--s-3); }

/* -----------------------------------------------------------------------------
   7. FORMS
   -------------------------------------------------------------------------- */

.field { margin-bottom: var(--s-5); }

.field__label {
    display: block;
    font-weight: 500;
    font-size: var(--t-sm);
    color: hsl(var(--ink));
    margin-bottom: var(--s-2);
}

.field__optional { font-weight: 400; color: hsl(var(--ink-subtle)); }

.field__hint {
    display: block;
    font-size: var(--t-sm);
    color: hsl(var(--ink-muted));
    margin-top: var(--s-2);
}

.input, .select, .textarea {
    width: 100%;
    padding: 0.5625rem var(--s-3);
    font: inherit;
    font-size: var(--t-base);
    color: hsl(var(--ink));
    background: hsl(var(--surface));
    border: 1px solid hsl(var(--line-strong));
    border-radius: var(--r-md);
    /* A hairline inset, so a field reads as a recess rather than an outline
       drawn on the page. */
    box-shadow: inset 0 1px 2px hsl(208 30% 12% / 0.04);
    transition: border-color 0.14s ease, box-shadow 0.14s ease;
}

.input::placeholder, .textarea::placeholder {
    color: hsl(var(--ink-subtle));
}

.input:hover:not(:focus), .select:hover:not(:focus), .textarea:hover:not(:focus) {
    border-color: hsl(var(--ink-subtle));
}

.input:focus, .select:focus, .textarea:focus {
    outline: none;
    border-color: hsl(var(--primary));
    box-shadow: 0 0 0 3px hsl(var(--primary) / 0.16);
}

/* The native arrow differs across browsers and platforms, and on Windows it
   is a heavy grey block that does not belong here. */
.select {
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' stroke='%235a6b7a' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right var(--s-3) center;
    padding-right: var(--s-8);
}

.textarea { min-height: 7rem; resize: vertical; line-height: 1.55; }

.input--invalid, .select--invalid, .textarea--invalid {
    border-color: hsl(var(--st-bad));
}
.input--invalid:focus { border-color: hsl(var(--st-bad)); box-shadow: 0 0 0 3px hsl(var(--st-bad) / 0.14); }

/* Amount inputs align with the amounts they will become. */
.input--amount { font-variant-numeric: tabular-nums; text-align: right; }

/* One-time codes: widely spaced, centred, unmistakably a code field. */
.input--code {
    font-size: var(--t-xl);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.35em;
    text-align: center;
    padding-left: calc(var(--s-3) + 0.35em);
}

.field__error {
    display: block;
    color: hsl(var(--st-bad));
    font-size: var(--t-sm);
    margin-top: var(--s-2);
}

.field-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: var(--s-4);
}

.form-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3);
    align-items: center;
    padding-top: var(--s-5);
    margin-top: var(--s-5);
    border-top: 1px solid hsl(var(--line));
}

.form-links {
    display: flex;
    justify-content: flex-end;
    margin: calc(var(--s-2) * -1) 0 var(--s-5);
    font-size: var(--t-sm);
}

/* -----------------------------------------------------------------------------
   8. STATUS
   One vocabulary, used identically wherever a state appears. Colour here is
   information: a user learns it once and it means the same thing on every
   screen.
   -------------------------------------------------------------------------- */

.status {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.1875rem 0.5rem 0.1875rem 0.4375rem;
    /* Fully round. A status is a label attached to something, not a container
       — the pill shape says that without a word. */
    border-radius: var(--r-full);
    font-size: var(--t-sm);
    font-weight: 550;
    letter-spacing: var(--track-snug);
    white-space: nowrap;
    /* A hairline of the status colour, so a pale fill still has an edge on a
       white panel. */
    box-shadow: inset 0 0 0 1px currentColor;
}

.status::before {
    content: '';
    width: 0.4rem;
    height: 0.4rem;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
}

.status--draft     { background: hsl(var(--st-neutral-bg));  color: hsl(var(--st-neutral)); }
.status--submitted { background: hsl(var(--st-progress-bg)); color: hsl(var(--st-progress)); }
.status--queried   { background: hsl(var(--st-waiting-bg));  color: hsl(var(--st-waiting)); }
.status--approved  { background: hsl(var(--st-good-bg));     color: hsl(var(--st-good)); }
.status--rejected  { background: hsl(var(--st-bad-bg));      color: hsl(var(--st-bad)); }

/* -----------------------------------------------------------------------------
   9. APPROVAL CHAIN
   The system's central object: three sequential approvers, where only one is
   ever live. This is the one place the interface is allowed to be emphatic.
   -------------------------------------------------------------------------- */

.chain {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2);
    padding: 0;
    margin: 0;
    list-style: none;
}

.chain__step {
    flex: 1 1 12rem;
    padding: var(--s-3) var(--s-4);
    background: hsl(var(--surface));
    border: 1px solid hsl(var(--line));
    border-radius: var(--r-md);
    position: relative;
}

.chain__role { font-weight: 550; font-size: var(--t-sm); }
.chain__who  { color: hsl(var(--ink-muted)); font-size: var(--t-sm); margin-top: 0.1rem; }
.chain__when { color: hsl(var(--ink-subtle)); font-size: var(--t-xs); margin-top: var(--s-2); }

/* The step currently waiting on someone. Left border rather than a fill, so
   it reads as "here" without shouting over the content. */
.chain__step[data-state='current'] {
    border-color: hsl(var(--st-progress) / 0.4);
    background: hsl(var(--st-progress-bg));
    box-shadow: inset 3px 0 0 hsl(var(--st-progress));
}

.chain__step[data-state='approved'] { box-shadow: inset 3px 0 0 hsl(var(--st-good)); }
.chain__step[data-state='queried']  { box-shadow: inset 3px 0 0 hsl(var(--st-waiting)); }
.chain__step[data-state='rejected'] { box-shadow: inset 3px 0 0 hsl(var(--st-bad)); }

/* Steps that cannot run yet, because an earlier one has not cleared. */
.chain__step[data-state='pending'] { opacity: 0.6; }

/* -----------------------------------------------------------------------------
   10. CARDS AND PANELS
   Deliberately not one uniform card applied to everything. A panel groups
   related content; a stat presents a single figure; they look different
   because they do different jobs.
   -------------------------------------------------------------------------- */

.panel {
    background: hsl(var(--surface));
    border: 1px solid hsl(var(--line));
    border-radius: var(--r-lg);
    /* Flat. No shadow — see the elevation note in the tokens. */
}

.panel__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-4);
    padding: var(--s-4) var(--s-5);
    border-bottom: 1px solid hsl(var(--line));
    min-height: 3.25rem;
}

.panel__body { padding: var(--s-5); }

.panel__footer {
    padding: var(--s-3) var(--s-5);
    border-top: 1px solid hsl(var(--line));
    background: hsl(var(--surface-sunken));
    border-radius: 0 0 var(--r-lg) var(--r-lg);
    font-size: var(--t-sm);
}

.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
    gap: var(--s-4);
    margin-bottom: var(--s-6);
}

.stat {
    padding: var(--s-5);
    background: hsl(var(--surface));
    border: 1px solid hsl(var(--line));
    border-radius: var(--r-lg);
}

.stat__label {
    font-size: var(--t-sm);
    color: hsl(var(--ink-muted));
    font-weight: 500;
}

.stat__value { margin-top: 0.375rem; }

/* -----------------------------------------------------------------------------
   11. TABLES
   On narrow screens each row becomes a labelled block. Horizontal scrolling
   is how data tables fail on phones, and a significant share of this app's
   users are on one.
   -------------------------------------------------------------------------- */

.table-wrap { overflow-x: auto; }

.table { width: 100%; border-collapse: collapse; font-size: var(--t-base); }

.table th {
    text-align: left;
    font-weight: 550;
    font-size: var(--t-sm);
    color: hsl(var(--ink-muted));
    letter-spacing: var(--track-wide);
    padding: var(--s-3) var(--s-4);
    border-bottom: 1px solid hsl(var(--line-strong));
    white-space: nowrap;
    /* Sticky, so a finance officer scrolling forty invoices can still tell
       which column is which. Sentence case, not tracked-out capitals. */
    position: sticky;
    top: 0;
    background: hsl(var(--surface));
    z-index: 1;
}

.table td {
    padding: 0.625rem var(--s-4);
    border-bottom: 1px solid hsl(var(--line));
    vertical-align: middle;
}

.table tbody tr:last-child td { border-bottom: none; }

/* Hover only where a row leads somewhere. A row that highlights but does
   nothing is a promise the interface does not keep. */
.table tbody tr:has(a):hover { background: hsl(var(--surface-sunken)); }

.table .is-numeric { text-align: right; font-variant-numeric: tabular-nums; }

.table__primary { font-weight: 500; }
.table__meta { color: hsl(var(--ink-muted)); font-size: var(--t-sm); }

/* -----------------------------------------------------------------------------
   12. ALERTS AND EMPTY STATES
   -------------------------------------------------------------------------- */

.alert {
    display: flex;
    gap: var(--s-3);
    padding: var(--s-3) var(--s-4);
    border-radius: var(--r-md);
    border: 1px solid transparent;
    font-size: var(--t-base);
    margin-bottom: var(--s-5);
}

.alert--success { background: hsl(var(--st-good-bg));     color: hsl(152 55% 22%); border-color: hsl(152 40% 84%); }
.alert--error   { background: hsl(var(--st-bad-bg));      color: hsl(0 66% 36%);   border-color: hsl(0 70% 90%); }
.alert--warning { background: hsl(var(--st-waiting-bg));  color: hsl(38 84% 28%);  border-color: hsl(40 80% 84%); }
.alert--info    { background: hsl(var(--st-progress-bg)); color: hsl(214 75% 32%); border-color: hsl(214 70% 88%); }

/* An empty screen is an invitation to act, so it carries the action. */
.empty {
    padding: var(--s-12) var(--s-6);
    text-align: center;
    color: hsl(var(--ink-muted));
}
.empty__title { font-size: var(--t-md); color: hsl(var(--ink)); margin-bottom: var(--s-2); }
.empty__body  { max-width: 40ch; margin: 0 auto var(--s-5); }

/* -----------------------------------------------------------------------------
   13. AUTHENTICATION SCREENS

   Mobile first, because a new invitee opening their link is very often on a
   phone — the invitation arrives by email and email is read on a phone.

   The logo sits on a navy band, never on white. iERA's logo is white, so on a
   light card it disappears entirely. The band is not decoration: it is what
   makes the mark visible at all, and it matches the header used in emails, so
   the link and the page it opens look like the same organisation.

   Above 60rem a second column appears for the brand image. Below that there
   is one column and nothing is reserved for it.
   -------------------------------------------------------------------------- */

.auth-shell {
    min-height: 100vh;
    min-height: 100dvh;          /* dvh accounts for mobile browser chrome */
    display: grid;
    grid-template-columns: 1fr;
    background: hsl(var(--bg));
}

.auth-form-side {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* The navy band carrying the logo. Full-bleed on a phone, so it reads as a
   header rather than as a stray coloured box. */
/* The masthead sits on the page background, not on a band. iERA's logo is
   white, so the mark itself gets a compact navy chip — enough dark ground to
   make it readable, without a full-width bar dominating a screen whose job is
   a two-field form. */
.auth-masthead {
    padding: var(--s-6) var(--s-5) var(--s-5);
    padding-top: max(var(--s-6), calc(env(safe-area-inset-top) + var(--s-4)));
    display: flex;
    flex-direction: column;
    /* flex-start, not the default stretch. Stretch forces the logo to the
       full container width while its height stays fixed, which distorts it. */
    align-items: flex-start;
    gap: var(--s-4);
}

.auth-logo-chip {
    display: inline-flex;
    align-items: center;
    background: hsl(var(--brand-navy));
    border-radius: var(--r-md);
    padding: var(--s-3) var(--s-4);
}

.auth-masthead__logo {
    display: block;
    height: 2rem;
    width: auto;
    /* Both constraints matter: height alone lets a wide logo run off, width
       alone lets a tall one tower. contain keeps the proportions whichever
       shape the uploaded file turns out to be. */
    max-width: 11rem;
    object-fit: contain;
}

.auth-masthead__name {
    color: hsl(var(--ink));
    font-weight: 600;
    font-size: var(--t-md);
}

/* Short accent rule, matching the brand panel rather than spanning the page. */
.auth-accent-bar {
    display: flex;
    height: 3px;
    width: 7rem;
    border-radius: 2px;
    overflow: hidden;
}

.auth-accent-bar span { flex: 1; }
.auth-accent-bar span:nth-child(1) { background: hsl(var(--brand-cyan)); }
.auth-accent-bar span:nth-child(2) { background: hsl(var(--brand-gold)); }
.auth-accent-bar span:nth-child(3) { background: hsl(var(--brand-magenta)); }
.auth-accent-bar span:nth-child(4) { background: hsl(var(--brand-green)); }

.auth-masthead__tagline {
    color: hsl(var(--ink-muted));
    font-size: var(--t-sm);
    max-width: 32ch;
}

.auth-body {
    flex: 1;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: var(--s-2) var(--s-5) var(--s-10);
}

.auth-card { width: 100%; max-width: 24rem; }
.auth-card__body { padding: 0; }

.auth-card__footer {
    margin-top: var(--s-6);
    padding-top: var(--s-4);
    border-top: 1px solid hsl(var(--line));
    color: hsl(var(--ink-muted));
    font-size: var(--t-sm);
}

.auth-title { font-size: var(--t-xl); margin-bottom: var(--s-2); }
.auth-subtitle { color: hsl(var(--ink-muted)); margin-bottom: var(--s-6); }

/* Setup key for the authenticator app: grouped and monospaced so it can be
   read aloud or copied by hand without transcription errors. */
.setup-key {
    font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
    font-size: var(--t-base);
    letter-spacing: 0.08em;
    word-break: break-all;
    background: hsl(var(--surface-sunken));
    border: 1px solid hsl(var(--line));
    padding: var(--s-3);
    border-radius: var(--r-md);
}

.recovery-codes {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: var(--s-2);
    margin: var(--s-5) 0;
    padding: var(--s-4);
    background: hsl(var(--surface-sunken));
    border: 1px solid hsl(var(--line));
    border-radius: var(--r-md);
    font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
    font-size: var(--t-base);
}

/* The brand image column. Hidden until there is room for it — a decorative
   half-screen above a sign-in form would push the password field off the
   fold on a phone. */
.auth-brand-side { display: none; }

@media (min-width: 60rem) {
    .auth-shell { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

    .auth-form-side { justify-content: flex-start; }
    .auth-body { align-items: flex-start; padding: var(--s-4) var(--s-8) var(--s-10); }
    .auth-card { max-width: 25rem; }

    /* The band stays navy at every width. iERA's logo is white, so making
       this transparent on desktop — as an earlier version did — left the mark
       invisible against the card. A dark ground is not a style choice here;
       it is the only thing that makes the logo readable. */
    .auth-masthead { padding: var(--s-10) var(--s-8) var(--s-5); }
    /* The brand panel beside it already says this. */
    .auth-masthead__tagline { display: none; }

    .auth-brand-side {
        display: block;
        position: relative;
        background: hsl(var(--brand-navy));
        overflow: hidden;
    }

    .auth-brand-side__image {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
        /* Held back so white text stays legible over whatever image an
           administrator uploads — we cannot know what it will be. */
        opacity: 0.55;
    }

    .auth-brand-side__overlay {
        position: absolute;
        inset: 0;
        background: linear-gradient(
            180deg,
            hsl(var(--brand-navy) / 0.35) 0%,
            hsl(var(--brand-navy) / 0.85) 100%
        );
    }

    .auth-brand-side__content {
        position: relative;
        height: 100%;
        display: flex;
        flex-direction: column;
        justify-content: flex-end;
        gap: var(--s-3);
        padding: var(--s-10);
        color: #fff;
    }

    .auth-brand-side__title { font-size: var(--t-xl); font-weight: 600; max-width: 20ch; }
    .auth-brand-side__tagline { color: hsl(205 25% 85%); max-width: 32ch; }

    .auth-brand-side__bar {
        display: flex;
        height: 4px;
        width: 8rem;
        border-radius: 2px;
        overflow: hidden;
        margin-bottom: var(--s-2);
    }

    .auth-brand-side__bar span { flex: 1; }
    .auth-brand-side__bar span:nth-child(1) { background: hsl(var(--brand-cyan)); }
    .auth-brand-side__bar span:nth-child(2) { background: hsl(var(--brand-gold)); }
    .auth-brand-side__bar span:nth-child(3) { background: hsl(var(--brand-magenta)); }
    .auth-brand-side__bar span:nth-child(4) { background: hsl(var(--brand-green)); }
}

/* -----------------------------------------------------------------------------
   13b2. FORM BUILDER
   -------------------------------------------------------------------------- */

.builder-field {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    /* Wraps so the in-place editor can drop onto its own line beneath the
       row. Without this it becomes a third item in the flex line and is
       squeezed between the question and its buttons. */
    flex-wrap: wrap;
    gap: var(--s-4);
    padding: var(--s-4) 0;
    border-bottom: 1px solid hsl(var(--line));
}

.builder-field:last-child { border-bottom: none; }

/* Takes the whole width of the wrapped line rather than sharing it. */
.builder-field__edit { flex: 1 0 100%; }

/* Grows to fill what the handle and buttons leave, so a long question does
   not push the buttons off the row. */
.builder-field__body { flex: 1 1 12rem; min-width: 0; }
.builder-field__label { font-weight: 550; display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }

.builder-field__actions { display: flex; gap: var(--s-1); flex-shrink: 0; }

/* A block shown inside the editor. Scaled down and non-interactive — it is
   there to confirm which block is which, not to be used. */
.block-preview {
    margin-top: var(--s-2);
    padding: var(--s-3);
    background: hsl(var(--surface-sunken));
    border-radius: var(--r-md);
    max-height: 14rem;
    overflow: hidden;
    pointer-events: none;
    font-size: var(--t-sm);
}

.block-preview .block-stat__value { font-size: var(--t-lg); }
.block-preview .embed { aspect-ratio: 16 / 9; max-height: 9rem; }

/* Submission answers — a definition list, because that is what a question
   and its answer are. */
.answers { margin: 0; display: grid; gap: var(--s-3); }
.answer { display: grid; gap: 0.15rem; }
.answer__label { font-size: var(--t-sm); color: hsl(var(--ink-muted)); font-weight: 500; }
.answer__value { margin: 0; overflow-wrap: break-word; }

.event-thumb {
    width: 100%;
    max-width: 22rem;
    height: auto;
    border-radius: var(--r-md);
    border: 1px solid hsl(var(--line));
}

@media (max-width: 48rem) {
    .builder-field { flex-direction: column; gap: var(--s-3); }
    .builder-field__actions { width: 100%; }
    .builder-field__actions form { flex: 1; }
    .builder-field__actions .btn { width: 100%; }
}

/* -----------------------------------------------------------------------------
   13c. PUBLIC PAGES
   The only part of the system a member of the public sees. No sidebar, no
   navigation — a single thing to read and one thing to do.
   -------------------------------------------------------------------------- */

.public-shell { width: 100%; max-width: 44rem; margin: 0 auto; padding: 0 var(--s-5); }

.public-header {
    background: hsl(var(--brand-navy));
    padding: var(--s-5) 0;
}

.public-header__logo { height: 2.25rem; width: auto; max-width: 11rem; object-fit: contain; }
.public-header__name { color: #fff; font-weight: 600; font-size: var(--t-md); }

.auth-accent-bar--full { width: 100%; border-radius: 0; }

.public-body { padding: var(--s-10) var(--s-5) var(--s-16); }
.public-body--centred { text-align: center; }

.public-title { font-size: var(--t-2xl); margin-bottom: var(--s-3); }
.public-lede { color: hsl(var(--ink-muted)); font-size: var(--t-md); line-height: 1.6; }

.public-footer {
    padding: var(--s-6) 0;
    border-top: 1px solid hsl(var(--line));
    color: hsl(var(--ink-subtle));
    font-size: var(--t-sm);
    text-align: center;
}

/* The honeypot. Hidden from people without display:none, which some bots
   check for — off-screen and unfocusable instead. */
.honeypot {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* A choice row that is comfortable to tap. The label wraps the input so the
   whole row is the target, not the 13px circle. */
.choice {
    display: flex;
    align-items: flex-start;
    gap: var(--s-3);
    padding: var(--s-3) 0;
    min-height: 2.75rem;
    cursor: pointer;
}

.choice + .choice { border-top: 1px solid hsl(var(--line)); }
.choice input { margin-top: 0.2rem; flex-shrink: 0; }

/* ── Event pages ──────────────────────────────────────────────────────── */

.event-hero {
    position: relative;
    background: hsl(var(--brand-navy));
    color: #fff;
    overflow: hidden;
}

.event-hero__image {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0.45;
}

.event-hero__overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(
        180deg,
        hsl(var(--brand-navy) / 0.5) 0%,
        hsl(var(--brand-navy) / 0.9) 100%
    );
}

.event-hero__content { position: relative; padding: var(--s-12) var(--s-5); }
.event-hero__title { font-size: var(--t-2xl); margin: var(--s-3) 0 var(--s-2); }
.event-hero__subtitle { color: hsl(205 30% 85%); font-size: var(--t-md); max-width: 40ch; }
.event-hero__cta { margin-top: var(--s-6); width: auto; padding-inline: var(--s-8); }

.event-facts {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-6);
    margin-top: var(--s-6);
}

.event-fact {
    color: hsl(205 25% 88%);
    font-size: var(--t-sm);
    line-height: 1.5;
}

.event-fact strong { color: #fff; font-size: var(--t-base); }

.event-section { margin-top: var(--s-8); }
.event-section p { color: hsl(var(--ink)); line-height: 1.65; }

.event-cta-band {
    margin-top: var(--s-12);
    padding: var(--s-8) var(--s-6);
    background: hsl(var(--primary-soft));
    border-radius: var(--r-lg);
    text-align: center;
}

.event-cta-band__text {
    font-size: var(--t-md);
    font-weight: 600;
    color: hsl(var(--primary-soft-fg));
    margin-bottom: var(--s-4);
}

@media (max-width: 48rem) {
    .event-hero__content { padding: var(--s-8) var(--s-4); }
    .event-hero__title { font-size: var(--t-xl); }
    .public-body { padding: var(--s-8) var(--s-4) var(--s-12); }
    .public-shell { padding: 0 var(--s-4); }
    .event-hero__cta { width: 100%; }
}

/* -----------------------------------------------------------------------------
   13d. PAGE BLOCKS
   The pieces a landing page is built from. Generous vertical rhythm — this is
   reading matter, not an interface.
   -------------------------------------------------------------------------- */

.block + .block { margin-top: var(--s-8); }

.block-heading { font-size: var(--t-lg); margin-bottom: var(--s-3); }
.block-text { line-height: 1.7; margin-bottom: var(--s-4); }
.block-text:last-child { margin-bottom: 0; }

.block-image { width: 100%; height: auto; border-radius: var(--r-lg); display: block; }
.block-figure { margin: 0; }
.block-figure figcaption {
    margin-top: var(--s-2);
    color: hsl(var(--ink-muted));
    font-size: var(--t-sm);
}

.block-button { margin: var(--s-6) 0; }

.block-divider {
    border: 0;
    border-top: 1px solid hsl(var(--line));
    margin: var(--s-8) 0;
}

.block-quote {
    margin: 0;
    padding: var(--s-5) var(--s-6);
    border-left: 3px solid hsl(var(--brand-gold));
    background: hsl(var(--surface-sunken));
    border-radius: 0 var(--r-md) var(--r-md) 0;
}

.block-quote p { font-size: var(--t-md); line-height: 1.65; }
.block-quote cite {
    display: block;
    margin-top: var(--s-3);
    color: hsl(var(--ink-muted));
    font-size: var(--t-sm);
    font-style: normal;
}

.block-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: var(--s-5);
    padding: var(--s-6);
    background: hsl(var(--primary-soft));
    border-radius: var(--r-lg);
}

.block-stat { text-align: center; }
.block-stat__value {
    display: block;
    font-size: var(--t-2xl);
    font-weight: 700;
    color: hsl(var(--primary-soft-fg));
}
.block-stat__label {
    display: block;
    margin-top: var(--s-1);
    color: hsl(var(--ink-muted));
    font-size: var(--t-sm);
}

/* <details> rather than JavaScript: works everywhere, keeps a long list
   readable on a phone, and prints expanded. */
.block-faq__item {
    border-bottom: 1px solid hsl(var(--line));
    padding: var(--s-4) 0;
}

.block-faq__item summary {
    font-weight: 550;
    cursor: pointer;
    list-style: none;
    display: flex;
    justify-content: space-between;
    gap: var(--s-4);
}

.block-faq__item summary::-webkit-details-marker { display: none; }
.block-faq__item summary::after { content: '+'; color: hsl(var(--ink-muted)); }
.block-faq__item[open] summary::after { content: '−'; }
.block-faq__item p { margin-top: var(--s-3); color: hsl(var(--ink-muted)); line-height: 1.65; }

.block-columns { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-6); }

/* Aspect-ratio wrapper so an embed never causes layout shift as it loads. */
.embed {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: var(--r-lg);
    overflow: hidden;
    background: hsl(var(--surface-sunken));
}

.embed[data-ratio='4 / 3'] { aspect-ratio: 4 / 3; }
.embed[data-ratio='3 / 4'] { aspect-ratio: 3 / 4; }
.embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.block-html { line-height: 1.7; }
.block-html img { max-width: 100%; height: auto; border-radius: var(--r-md); }
.block-html table { width: 100%; border-collapse: collapse; }
.block-html th, .block-html td {
    padding: var(--s-2) var(--s-3);
    border: 1px solid hsl(var(--line));
    text-align: left;
}

.preview-banner {
    background: hsl(var(--brand-gold));
    color: hsl(42 95% 18%);
    padding: var(--s-3) var(--s-5);
    text-align: center;
    font-weight: 550;
    font-size: var(--t-sm);
}

@media (max-width: 48rem) {
    .block-columns { grid-template-columns: 1fr; }
    .block + .block { margin-top: var(--s-6); }
}

/* -----------------------------------------------------------------------------
   13e. EMBEDDED FORM
   A form rendered inside another site's page. Transparent background so it
   takes on whatever surface it lands in, rather than stamping a white box on
   somebody else's design.
   -------------------------------------------------------------------------- */

body.embedded { background: transparent; }
.embed-body { padding: var(--s-5); max-width: 36rem; margin: 0 auto; }

/* A form dropped into an iERA page by its short code. */
.block-form { margin: var(--s-6) 0; }

/* -----------------------------------------------------------------------------
   14. ERROR PAGES
   -------------------------------------------------------------------------- */

.error-page { min-height: 100vh; display: grid; place-items: center; padding: var(--s-8) var(--s-4); text-align: center; }
.error-page__status { font-size: var(--t-2xl); font-weight: 700; color: hsl(var(--primary)); font-variant-numeric: tabular-nums; }
.error-page__message { color: hsl(var(--ink-muted)); margin: var(--s-2) 0 var(--s-6); max-width: 34rem; }

/* -----------------------------------------------------------------------------
   12a. QUICK ACTIONS
   Shortcuts on the dashboard. Cards rather than a row of buttons, because
   each carries a reason as well as a label — "Start a budget" means little
   without "3 approved requests need one".
   -------------------------------------------------------------------------- */

.quick-actions {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: var(--s-3);
    margin-bottom: var(--s-6);
}

.quick-action {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: var(--s-4);
    min-height: 4.5rem;
    background: hsl(var(--surface));
    border: 1px solid hsl(var(--line));
    border-radius: var(--r-lg);
    color: hsl(var(--ink));
    text-decoration: none;
    transition: border-color 0.14s ease, background-color 0.14s ease;
}

.quick-action:hover {
    border-color: hsl(var(--primary) / 0.45);
    background: hsl(var(--primary-soft) / 0.5);
    text-decoration: none;
}

.quick-action__label {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    font-weight: 550;
    letter-spacing: var(--track-snug);
}

.quick-action__hint { color: hsl(var(--ink-muted)); font-size: var(--t-sm); }

/* Every quick action looks the same. A filled card among outlined ones was
   louder than the difference deserved — these are all things you might do
   next, and singling one out by colour implied a ranking that does not hold
   for every role. Order carries the emphasis instead: the most likely action
   is simply first. */

/* Something overdue. A left edge in the waiting colour, not a filled card —
   a reminder should be noticeable without competing with the main action. */
.quick-action--urgent {
    box-shadow: inset 3px 0 0 hsl(var(--st-waiting));
}

.quick-action__count {
    min-width: 1.375rem;
    padding: 0 0.375rem;
    border-radius: var(--r-full);
    background: hsl(var(--st-progress));
    color: #fff;
    font-size: var(--t-xs);
    font-weight: 650;
    line-height: 1.375rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

@media (max-width: 60rem) {
    .quick-actions { grid-template-columns: 1fr 1fr; gap: var(--s-2); }
    .quick-action { padding: var(--s-3); min-height: 4rem; }
}

@media (max-width: 30rem) {
    .quick-actions { grid-template-columns: 1fr; }
}

/* -----------------------------------------------------------------------------
   12b. CHARTS
   Rendered as inline SVG on the server. Colours come from the same custom
   properties as everything else, so a brand change in Administration moves
   the charts with it.
   -------------------------------------------------------------------------- */

.chart { width: 100%; height: auto; display: block; }
.chart--bar { height: 14px; border-radius: 7px; overflow: hidden; }
.chart--ring { width: 7rem; height: 7rem; }
.chart--spark { height: 2.5rem; }

.chart__label {
    font-size: 12px;
    fill: hsl(var(--ink-muted));
    font-family: inherit;
}

.chart__value {
    font-size: 12px;
    font-weight: 600;
    fill: hsl(var(--ink));
    text-anchor: end;
    font-family: inherit;
    font-variant-numeric: tabular-nums;
}

.chart__track { fill: hsl(var(--surface-sunken)); }
.chart__ring-track { stroke: hsl(var(--surface-sunken)); }

.chart__ring-value {
    font-size: 22px;
    font-weight: 650;
    fill: hsl(var(--ink));
    text-anchor: middle;
    dominant-baseline: central;
    font-family: inherit;
    font-variant-numeric: tabular-nums;
}

/* A key under a proportion bar. Without it the segments are colour with no
   meaning, which is decoration rather than information. */
.chart-key {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-4);
    margin-top: var(--s-3);
    font-size: var(--t-sm);
}

.chart-key__item { display: flex; align-items: center; gap: var(--s-2); }

.chart-key__swatch {
    width: 0.625rem;
    height: 0.625rem;
    border-radius: 2px;
    flex-shrink: 0;
}

/* Classes rather than inline styles: the CSP blocks style attributes, which
   is deliberate — they are one of the ways an XSS payload gets rendered. */
.chart-key__swatch--neutral  { background: hsl(var(--st-neutral)); }
.chart-key__swatch--progress { background: hsl(var(--st-progress)); }
.chart-key__swatch--good     { background: hsl(var(--st-good)); }
.chart-key__swatch--waiting  { background: hsl(var(--st-waiting)); }
.chart-key__swatch--bad      { background: hsl(var(--st-bad)); }

.chart-key__count {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* Ring paired with a caption. */
.ring-stat { display: flex; align-items: center; gap: var(--s-5); }
.ring-stat__body { min-width: 0; }

/* A direction indicator beside a figure. Arrow plus word, never colour
   alone — red and green are indistinguishable to a meaningful share of
   readers, and this is a finance screen. */
.trend {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: var(--t-sm);
    font-weight: 500;
}

.trend--up   { color: hsl(var(--st-good)); }
.trend--down { color: hsl(var(--st-bad)); }
.trend--flat { color: hsl(var(--ink-muted)); }

/* -----------------------------------------------------------------------------
   12c. TIMELINE
   Used for activity feeds, where sequence is the point.
   -------------------------------------------------------------------------- */

.timeline { list-style: none; margin: 0; padding: 0; position: relative; }

.timeline::before {
    content: '';
    position: absolute;
    left: 0.4375rem;
    top: 0.75rem;
    bottom: 0.75rem;
    width: 2px;
    background: hsl(var(--line));
}

.timeline__item {
    position: relative;
    padding: 0 0 var(--s-5) var(--s-6);
}

.timeline__item:last-child { padding-bottom: 0; }

.timeline__marker {
    position: absolute;
    left: 0;
    top: 0.25rem;
    width: 0.9375rem;
    height: 0.9375rem;
    border-radius: 50%;
    background: hsl(var(--surface));
    border: 2px solid hsl(var(--line-strong));
}

.timeline__item[data-state='approved'] .timeline__marker { border-color: hsl(var(--st-good)); background: hsl(var(--st-good)); }
.timeline__item[data-state='rejected'] .timeline__marker { border-color: hsl(var(--st-bad)); background: hsl(var(--st-bad)); }
.timeline__item[data-state='queried'] .timeline__marker  { border-color: hsl(var(--st-waiting)); background: hsl(var(--st-waiting)); }
.timeline__item[data-state='submitted'] .timeline__marker { border-color: hsl(var(--st-progress)); background: hsl(var(--st-progress)); }

.timeline__title { font-weight: 550; }
.timeline__meta { color: hsl(var(--ink-subtle)); font-size: var(--t-sm); }

/* -----------------------------------------------------------------------------
   12d. EMPTY STATES WITH ILLUSTRATION
   -------------------------------------------------------------------------- */

.empty__art {
    width: 4.5rem;
    height: 4.5rem;
    margin: 0 auto var(--s-4);
    color: hsl(var(--line-strong));
}

/* -----------------------------------------------------------------------------
   13b. BUDGET LINES
   -------------------------------------------------------------------------- */

.budget-line {
    padding: var(--s-4) var(--s-5);
    border-bottom: 1px solid hsl(var(--line));
}

.budget-line:last-child { border-bottom: none; }

.budget-line__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-4);
}

.budget-line__title { font-weight: 550; }
.budget-line__total { font-weight: 650; white-space: nowrap; }

/* The three quotes, side by side. Comparison is the point, so they sit on one
   row where the amounts can be read against each other. */
.quotes {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2);
    margin-top: var(--s-3);
}

.quote {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    padding: var(--s-2) var(--s-3);
    background: hsl(var(--surface-sunken));
    border: 1px solid hsl(var(--line));
    border-radius: var(--r-sm);
    font-size: var(--t-sm);
}

.quote--chosen {
    background: hsl(var(--st-good-bg));
    border-color: hsl(var(--st-good) / 0.35);
}

.quote__vendor { font-weight: 500; }
.quote__amount { color: hsl(var(--ink-muted)); }

@media (max-width: 48rem) {
    .budget-line__head { flex-direction: column; gap: var(--s-1); }
    .quotes { flex-direction: column; }
    .quote { justify-content: space-between; }
}

/* -----------------------------------------------------------------------------
   14a. NOTIFICATIONS
   -------------------------------------------------------------------------- */

.notification-list { display: flex; flex-direction: column; }

.notification {
    display: flex;
    gap: var(--s-3);
    padding: var(--s-4) var(--s-5);
    border-bottom: 1px solid hsl(var(--line));
    color: inherit;
    text-decoration: none;
    /* A whole-row target rather than a link inside a row — on a phone this is
       the difference between tapping it and tapping near it. */
    min-height: 3.5rem;
}

.notification:last-child { border-bottom: none; }
.notification:hover { background: hsl(var(--surface-sunken)); text-decoration: none; }

.notification__dot {
    width: 0.5rem;
    height: 0.5rem;
    margin-top: 0.5rem;
    border-radius: 50%;
    background: hsl(var(--primary));
    flex-shrink: 0;
}

.notification__body { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.notification__title { font-weight: 550; }
.notification__text { color: hsl(var(--ink-muted)); font-size: var(--t-sm); }
.notification__meta { color: hsl(var(--ink-subtle)); font-size: var(--t-xs); margin-top: 0.2rem; }

/* The bell in the top bar. */
.bell {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--r-md);
    color: hsl(var(--ink-muted));
    text-decoration: none;
}

.bell:hover { background: hsl(var(--surface-sunken)); color: hsl(var(--ink)); }

.bell__count {
    position: absolute;
    top: 0.25rem;
    right: 0.15rem;
    min-width: 1.1rem;
    padding: 0 0.25rem;
    border-radius: 999px;
    /* Magenta rather than the primary blue: this is the one thing in the top
       bar that should pull the eye, and blue on blue does not. */
    background: hsl(var(--brand-magenta));
    color: #fff;
    font-size: 0.6875rem;
    font-weight: 650;
    line-height: 1.1rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

/* -----------------------------------------------------------------------------
   14b. AVATARS
   -------------------------------------------------------------------------- */

.avatar {
    display: inline-grid;
    place-items: center;
    flex-shrink: 0;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    object-fit: cover;
    background: hsl(var(--surface-sunken));
}

.avatar--lg { width: 4.5rem; height: 4.5rem; }

/* Initials, when there is no photograph. Generated from the name in the
   template with |upper — a coloured circle with initials reads as a person,
   where a generic silhouette reads as missing data. */
.avatar--empty {
    background: hsl(var(--primary-soft));
    color: hsl(var(--primary-soft-fg));
    font-weight: 650;
    font-size: var(--t-md);
    letter-spacing: 0.02em;
}

/* -----------------------------------------------------------------------------
   15. UTILITIES
   -------------------------------------------------------------------------- */

.stack > * + * { margin-top: var(--s-4); }
.stack--tight > * + * { margin-top: var(--s-2); }
.row { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; }
.row--between { justify-content: space-between; }
.mt-6 { margin-top: var(--s-6); }
.w-auto { width: auto; }

.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* -----------------------------------------------------------------------------
   16. RESPONSIVE

   A significant share of this application's users are country staff on a
   phone, often on a slow connection. Mobile is not a degraded desktop here —
   for those people it is the whole product.

   Breakpoints:
     60rem (960px) and below — sidebar becomes a drawer, tables become cards
     48rem (768px) and below — tighter spacing, stacked headers
     30rem (480px) and below — full-width controls
   -------------------------------------------------------------------------- */

/* Nothing may cause sideways scroll. One over-wide element otherwise drags
   the entire page, and the user is left swiping to find content that is not
   there.

   The containment sits on .main, NOT on html or body. overflow-x: hidden makes
   an element a scroll container, and a scroll container on html or body is
   what silently stops position: sticky working anywhere inside it — which is
   how the sidebar came to scroll away with the page.

   Wide content is unaffected: tables already scroll inside .table-wrap, which
   is the only place sideways movement belongs. */
html, body { max-width: 100%; }

/* Long unbroken strings — email addresses, references, URLs — must wrap
   rather than force the page wider. */
.table td, .table__meta, .chain__who, .field__hint, .setup-key, .alert {
    overflow-wrap: break-word;
    word-break: break-word;
}

@media (max-width: 60rem) {

    /* ── Inputs: 16px minimum ────────────────────────────────────────────
       iOS Safari zooms the viewport whenever a focused input has a font
       smaller than 16px, and does not zoom back out afterwards. At 15px
       every tap on a field lurches the page sideways. This one rule is the
       difference between the app feeling broken and feeling normal. */
    .input, .select, .textarea {
        font-size: 1rem;
        padding: 0.6875rem var(--s-3);
    }

    /* ── Touch targets ───────────────────────────────────────────────────
       44px minimum. A 28px button is hittable with a mouse and a coin toss
       with a thumb. */
    .btn {
        min-height: 2.75rem;
        padding: 0.6875rem var(--s-4);
        font-size: 1rem;
    }

    .btn--sm {
        min-height: 2.5rem;
        padding: 0.5rem var(--s-3);
        font-size: 0.9375rem;
    }

    a { text-decoration-thickness: from-font; }

    /* ── Navigation drawer ──────────────────────────────────────────────── */
    .sidebar {
        position: fixed;
        inset: 0 auto 0 0;
        width: min(17rem, 82vw);
        z-index: 60;
        transform: translateX(-100%);
        transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1);
        box-shadow: var(--shadow-lg);
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        /* Clear of the notch and the home indicator on modern phones. */
        padding-top: max(var(--s-4), env(safe-area-inset-top));
        padding-bottom: max(var(--s-4), env(safe-area-inset-bottom));
    }

    .sidebar[data-open='true'] { transform: translateX(0); }

    /* Bigger tap targets in the drawer than on desktop — this is a thumb,
       not a cursor. */
    .sidebar__link {
        min-height: 2.75rem;
        padding: var(--s-3);
        font-size: 1rem;
    }

    .nav-toggle {
        width: 2.75rem;
        height: 2.75rem;
        color: hsl(var(--ink));
        border: 1px solid hsl(var(--line-strong));
    }

    .nav-scrim {
        position: fixed;
        inset: 0;
        z-index: 50;
        background: hsl(208 40% 10% / 0.5);
        border: 0;
        cursor: pointer;
        animation: scrim-in 0.18s ease;
    }

    @keyframes scrim-in { from { opacity: 0; } to { opacity: 1; } }

    /* Set on <body> while the drawer is open, so the page behind does not
       scroll under it — the classic "I closed the menu and lost my place". */
    body.nav-open { overflow: hidden; }

    /* ── Shell ──────────────────────────────────────────────────────────── */
    .topbar {
        gap: var(--s-3);
        padding: 0 var(--s-4);
        padding-top: env(safe-area-inset-top);
        height: calc(3.5rem + env(safe-area-inset-top));
    }

    .topbar__title {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .content {
        padding: var(--s-4) var(--s-4) max(var(--s-8), env(safe-area-inset-bottom));
    }

    .page-header { flex-direction: column; align-items: stretch; gap: var(--s-3); }
    .page-header .btn { width: 100%; }

    .panel__body,
    .panel__header,
    .panel__footer { padding-left: var(--s-4); padding-right: var(--s-4); }

    /* ── Tables become cards ─────────────────────────────────────────────
       Horizontal scrolling is how data tables fail on phones: the first
       column is visible and everything that matters is off-screen. Each row
       becomes a labelled block instead. */
    .table-wrap { overflow-x: visible; }

    .table thead { display: none; }

    .table, .table tbody, .table tr, .table td { display: block; width: 100%; }

    .table tr {
        background: hsl(var(--surface));
        border: 1px solid hsl(var(--line));
        border-radius: var(--r-md);
        margin-bottom: var(--s-3);
        padding: var(--s-2) var(--s-4);
    }

    .table tr:hover { background: hsl(var(--surface)); }
    .table tbody tr:last-child { margin-bottom: 0; }

    .table td {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: var(--s-4);
        padding: var(--s-3) 0;
        border-bottom: 1px solid hsl(var(--line));
        text-align: right;
    }

    .table td:last-child { border-bottom: none; }

    .table td::before {
        content: attr(data-label);
        font-size: var(--t-sm);
        font-weight: 500;
        color: hsl(var(--ink-muted));
        text-align: left;
        flex-shrink: 0;
        /* Caps the label so a long one cannot squeeze the value into a
           one-word-per-line column. */
        max-width: 45%;
    }

    /* A cell with no label — usually the actions column — should not leave
       a phantom gap where the label would have been. */
    .table td[data-label='']::before,
    .table td:not([data-label])::before { content: none; }

    .table td[data-label=''],
    .table td:not([data-label]) { justify-content: flex-end; }

    /* Cells holding form controls stack instead: a flex row squeezes an
       input down to its intrinsic width, which is unusable.
       
       Two selectors deliberately. :has() handles it automatically in current
       browsers; .table--form is an explicit opt-in for older Android
       WebViews, which some field staff are still on. The fallback costs one
       class in the markup and means the request form is not unusable for
       exactly the people most likely to be filling it in from a phone. */
    .table--form td,
    .table td:has(input),
    .table td:has(select),
    .table td:has(textarea) {
        flex-direction: column;
        align-items: stretch;
        gap: var(--s-2);
        text-align: left;
    }

    .table--form td::before,
    .table td:has(input)::before,
    .table td:has(select)::before,
    .table td:has(textarea)::before {
        max-width: none;
    }

    /* Each repeating row is a card of its own, so it is obvious where one
       activity ends and the next begins. */
    .table--form tr {
        background: hsl(var(--surface-sunken));
        padding: var(--s-3) var(--s-4);
    }

    .table--form td:last-child { padding-top: var(--s-3); }
    .table--form td:last-child .btn { width: 100%; }

    /* Action cells: buttons side by side, filling the row. */
    .table td .row { justify-content: flex-end; width: 100%; }
    .table td form { display: inline-flex; }

    .table .is-numeric { text-align: right; }

    /* ── Forms ──────────────────────────────────────────────────────────── */
    .field-row { grid-template-columns: 1fr; gap: 0; }

    .form-actions { flex-direction: column-reverse; align-items: stretch; }
    .form-actions .btn { width: 100%; }
    .form-actions a { text-align: center; padding: var(--s-2); }

    .btn-row { flex-direction: column; }
    .btn-row .btn { width: 100%; }

    /* Filter bars: the input takes the row, controls sit beneath it. */
    .row > .input,
    .row > .select { flex: 1 1 100%; }

    /* ── Auth ───────────────────────────────────────────────────────────── */
    .auth-body { padding: var(--s-2) var(--s-5) var(--s-10); }
    .auth-title { font-size: var(--t-lg); }

    .input--code {
        font-size: 1.5rem;
        letter-spacing: 0.25em;
        padding-left: calc(var(--s-3) + 0.25em);
    }

    /* ── Approval chain ─────────────────────────────────────────────────── */
    .chain { flex-direction: column; }
    .chain__step { flex: 1 1 auto; }

    /* ── Stats ──────────────────────────────────────────────────────────── */
    .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3); }
    .stat { padding: var(--s-3) var(--s-4); }
    .figure--lg { font-size: var(--t-xl); }
}

@media (max-width: 48rem) {
    .page-title { font-size: var(--t-lg); }
    .alert { flex-direction: column; gap: var(--s-2); }
    .recovery-codes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 30rem) {
    .content { padding: var(--s-3) var(--s-3) var(--s-8); }
    .panel__body, .panel__header, .panel__footer {
        padding-left: var(--s-3);
        padding-right: var(--s-3);
    }
    .table tr { padding: var(--s-2) var(--s-3); }

    /* One column: two stat cards at this width leave no room for the
       figures they exist to show. */
    .stat-grid { grid-template-columns: 1fr; }

    .auth-body { padding: var(--s-2) var(--s-4) var(--s-8); }
    .auth-masthead { padding: var(--s-5) var(--s-4) var(--s-4); }

    .input--code { font-size: 1.25rem; letter-spacing: 0.2em; }
}

/* -----------------------------------------------------------------------------
   17. PRINT
   Approval records get printed and filed for audit, so they should print
   like a document rather than like a screenshot of an application.
   -------------------------------------------------------------------------- */

@media print {
    .sidebar, .topbar, .nav-toggle, .btn, .form-actions { display: none !important; }

    body { background: #fff; font-size: 11pt; }
    .content { padding: 0; }
    .panel { border: 1px solid #999; break-inside: avoid; }
    .chain__step { break-inside: avoid; }

    a[href^='http']::after { content: ' (' attr(href) ')'; font-size: 9pt; color: #555; }
}

/* -----------------------------------------------------------------------------
   STATUS VARIANTS FOR PROJECTS, PAYMENTS, AUDIT AND RECRUITMENT
   The original five cover the approval vocabulary (draft, submitted, queried,
   approved, rejected). These modules introduce their own words for the same
   underlying states, so each maps onto an existing semantic colour rather
   than inventing a sixth. Nothing here defines a new colour.
   -------------------------------------------------------------------------- */

/* Neutral: nothing is happening yet, or it is over and uncontentious. */
.status--on-hold,
.status--closed,
.status--completed,
.status--open {
    background: hsl(var(--st-neutral-bg));
    color: hsl(var(--st-neutral));
}

/* In progress: somebody is working on it. */
.status--active,
.status--in-progress,
.status--filled,
.status--verified {
    background: hsl(var(--st-progress-bg));
    color: hsl(var(--st-progress));
}

/* Settled well. */
.status--resolved,
.status--rating-satisfactory {
    background: hsl(var(--st-good-bg));
    color: hsl(var(--st-good));
}

/* Needs attention, but not a failure. */
.status--accepted,
.status--rating-needs-improvement {
    background: hsl(var(--st-waiting-bg));
    color: hsl(var(--st-waiting));
}

/* Ended badly, or ended early. */
.status--terminated,
.status--rating-unsatisfactory {
    background: hsl(var(--st-bad-bg));
    color: hsl(var(--st-bad));
}

/* Severity is a scale, not a state, so it gets its own three. */
.status--severity-low {
    background: hsl(var(--st-neutral-bg));
    color: hsl(var(--st-neutral));
}
.status--severity-medium {
    background: hsl(var(--st-waiting-bg));
    color: hsl(var(--st-waiting));
}
.status--severity-high {
    background: hsl(var(--st-bad-bg));
    color: hsl(var(--st-bad));
}


/* -----------------------------------------------------------------------------
   DETAIL GRID
   Label-and-value pairs on a record page. Used where a form would be wrong
   because nothing is editable.
   -------------------------------------------------------------------------- */

.detail-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: var(--s-5);
    margin: 0;
}

.detail__label {
    font-size: var(--t-xs);
    text-transform: uppercase;
    letter-spacing: var(--track-wide);
    color: hsl(var(--ink-subtle));
    margin-bottom: 0.25rem;
}

.detail__value {
    margin: 0;
    font-weight: 500;
}


/* -----------------------------------------------------------------------------
   FINDINGS
   Each finding is a self-contained item with its own update form, so it needs
   a visible boundary without becoming a nested panel.
   -------------------------------------------------------------------------- */

.finding {
    padding: var(--s-5) 0;
    border-bottom: 1px solid hsl(var(--line));
}

.finding:last-child { border-bottom: 0; padding-bottom: 0; }
.finding:first-child { padding-top: 0; }

.finding__head {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3);
    align-items: flex-start;
    justify-content: space-between;
    margin-bottom: var(--s-3);
}

.finding__title {
    font-weight: 600;
    margin: 0;
}


/* -----------------------------------------------------------------------------
   ASSORTED
   -------------------------------------------------------------------------- */

/* Two or three decisions side by side, wrapping on a narrow screen rather
   than shrinking below a comfortable tap target. */
.decision-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3);
    margin-top: var(--s-5);
}

.page-header__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3);
}

/* A status dropdown that sits inside a table cell under its current status. */
.inline-form { margin-top: var(--s-2); }

.select--sm {
    padding: 0.25rem 1.75rem 0.25rem 0.5rem;
    font-size: var(--t-sm);
}

/* A ring chart tucked under a figure in a stat card. */
.stat__chart {
    margin-top: var(--s-3);
    max-width: 5rem;
}

.text-warn { color: hsl(var(--st-waiting)); }
.text-bad  { color: hsl(var(--st-bad)); }

/* -----------------------------------------------------------------------------
   RICH TEXT EDITOR
   Wraps a real textarea, which stays in the form and keeps its name. If the
   JavaScript fails, what is left is a plain textarea that still submits.
   -------------------------------------------------------------------------- */

.editor {
    border: 1px solid hsl(var(--line));
    border-radius: var(--r-md);
    background: hsl(var(--surface));
    overflow: hidden;
}

.editor:focus-within {
    border-color: hsl(var(--primary) / 0.55);
    box-shadow: 0 0 0 3px hsl(var(--primary) / 0.12);
}

.editor__toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.125rem;
    padding: var(--s-2);
    background: hsl(var(--surface-sunken));
    border-bottom: 1px solid hsl(var(--line));
}

.editor__tool {
    min-width: 2rem;
    height: 2rem;
    padding: 0 0.4rem;
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    color: hsl(var(--ink-muted));
    font-size: var(--t-sm);
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
}

.editor__tool:hover:not(:disabled) {
    background: hsl(var(--primary) / 0.1);
    color: hsl(var(--primary));
}

/* A pressed formatting button reflects the state of the cursor, not a
   preference — it turns on and off as the caret moves. */
.editor__tool.is-on {
    background: hsl(var(--primary) / 0.14);
    color: hsl(var(--primary));
}

.editor__tool:disabled { opacity: 0.35; cursor: default; }
.editor__tool--wide    { font-family: var(--font-mono, ui-monospace, monospace); }

.editor__divider {
    width: 1px;
    height: 1.25rem;
    margin: 0 0.25rem;
    background: hsl(var(--line));
}

.editor__spacer { flex: 1 1 auto; }

.editor__surface {
    min-height: 11rem;
    padding: var(--s-4);
    font-size: var(--t-base);
    line-height: 1.65;
    overflow-wrap: break-word;
}

.editor__surface:focus { outline: none; }

/* The editing surface should look like the thing it produces, or the author
   is composing against a preview that lies. */
.editor__surface h2 { font-size: 1.3rem; font-weight: 650; margin: 1.1em 0 0.4em; }
.editor__surface h3 { font-size: 1.1rem; font-weight: 600; margin: 1em 0 0.35em; }
.editor__surface p  { margin: 0 0 0.8em; }
.editor__surface ul,
.editor__surface ol { margin: 0 0 0.8em; padding-left: 1.4em; }
.editor__surface li { margin-bottom: 0.25em; }
.editor__surface a  { color: hsl(var(--primary)); text-decoration: underline; }

.editor__surface blockquote {
    margin: 0 0 0.8em;
    padding-left: var(--s-4);
    border-left: 3px solid hsl(var(--primary) / 0.4);
    color: hsl(var(--ink-muted));
}

.editor__surface.is-empty::before {
    content: attr(data-placeholder);
    color: hsl(var(--ink-subtle));
    pointer-events: none;
}

.editor__source {
    display: block;
    width: 100%;
    min-height: 11rem;
    padding: var(--s-4);
    border: 0;
    background: hsl(var(--surface-sunken));
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.8125rem;
    line-height: 1.6;
    resize: vertical;
}

.editor__source:focus { outline: none; }


/* -----------------------------------------------------------------------------
   CODE EDITOR
   A textarea with a line-number gutter. The gutter scrolls with the text, so
   the two must share a line height exactly.
   -------------------------------------------------------------------------- */

.code-editor {
    display: flex;
    border: 1px solid hsl(var(--line));
    border-radius: var(--r-md);
    background: hsl(var(--surface-sunken));
    overflow: hidden;
}

.code-editor:focus-within {
    border-color: hsl(var(--primary) / 0.55);
    box-shadow: 0 0 0 3px hsl(var(--primary) / 0.12);
}

.code-editor__gutter,
.code-editor__area {
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.8125rem;
    line-height: 1.6;
}

.code-editor__gutter {
    flex: 0 0 auto;
    padding: var(--s-4) 0.5rem var(--s-4) var(--s-3);
    border-right: 1px solid hsl(var(--line));
    color: hsl(var(--ink-subtle));
    text-align: right;
    white-space: pre;
    overflow: hidden;
    user-select: none;
}

.code-editor__area {
    flex: 1 1 auto;
    min-height: 14rem;
    padding: var(--s-4);
    border: 0;
    background: transparent;
    resize: vertical;
    white-space: pre;
    overflow-x: auto;
    tab-size: 2;
}

.code-editor__area:focus { outline: none; }

/* The in-place editor for an existing form question. Inset and tinted so it
   reads as belonging to the question above it rather than as a new section. */
.builder-field__edit {
    margin-top: var(--s-4);
    padding: var(--s-4);
    background: hsl(var(--surface-sunken));
    border-radius: var(--r-md);
    border: 1px solid hsl(var(--line));
}

.builder-field__edit[hidden] { display: none; }

/* A .field sets its own display, which would win over the hidden attribute.
   Conditional fields rely on hidden actually hiding them. */
.field[hidden],
[data-when-field][hidden] { display: none; }

/* The visibility rule inside the question editor. Set apart from the fields
   above it because it describes when the question appears, not what it asks. */
.condition {
    margin: var(--s-5) 0;
    padding: var(--s-4);
    background: hsl(var(--surface));
    border: 1px dashed hsl(var(--line-strong, var(--line)));
    border-radius: var(--r-md);
}

/* Marks a question in the builder list as conditional, so the rule is visible
   without opening each one. */
.builder-field__rule {
    font-size: var(--t-xs);
    color: hsl(var(--st-progress));
}

/* -----------------------------------------------------------------------------
   PROSE
   Author-written HTML from the rich editor, rendered on a page that otherwise
   resets margins. Without this, paragraphs and lists run together with no
   spacing, which is what "the editor did nothing" looks like from outside.
   Scoped to .prose so it cannot leak into the application's own layout.
   -------------------------------------------------------------------------- */

.prose > *:first-child { margin-top: 0; }
.prose > *:last-child  { margin-bottom: 0; }

.prose p {
    margin: 0 0 1em;
    line-height: 1.7;
}

.prose h1, .prose h2, .prose h3,
.prose h4, .prose h5, .prose h6 {
    margin: 1.5em 0 0.5em;
    line-height: 1.3;
    font-weight: 650;
}

.prose h1 { font-size: 1.6rem; }
.prose h2 { font-size: 1.35rem; }
.prose h3 { font-size: 1.15rem; }
.prose h4, .prose h5, .prose h6 { font-size: 1rem; }

.prose ul,
.prose ol {
    margin: 0 0 1em;
    padding-left: 1.5em;
}

.prose li { margin-bottom: 0.35em; }

.prose a {
    color: hsl(var(--primary));
    text-decoration: underline;
}

.prose blockquote {
    margin: 0 0 1em;
    padding: 0.25em 0 0.25em 1em;
    border-left: 3px solid hsl(var(--primary) / 0.4);
    color: hsl(var(--ink-muted));
}

.prose img {
    max-width: 100%;
    height: auto;
    border-radius: var(--r-md);
}

.prose table {
    width: 100%;
    margin: 0 0 1em;
    border-collapse: collapse;
}

.prose th,
.prose td {
    padding: 0.5em 0.75em;
    border: 1px solid hsl(var(--line));
    text-align: left;
}

.prose code {
    padding: 0.1em 0.35em;
    background: hsl(var(--surface-sunken));
    border-radius: var(--r-sm);
    font-family: var(--font-mono);
    font-size: 0.9em;
}

.prose pre {
    margin: 0 0 1em;
    padding: var(--s-4);
    background: hsl(var(--surface-sunken));
    border-radius: var(--r-md);
    overflow-x: auto;
}

.prose pre code { padding: 0; background: none; }

.prose hr {
    margin: 1.5em 0;
    border: 0;
    border-top: 1px solid hsl(var(--line));
}

/* .textarea and .input set their own display, which beats the hidden
   attribute. The editors hide the original control rather than removing it,
   so it has to actually disappear. */
.textarea[hidden],
.input[hidden],
.editor__source[hidden],
.editor__surface[hidden] { display: none; }

/* -----------------------------------------------------------------------------
   EMBEDDED WHOLE-PAGE BLOCK
   A pasted page renders in its own frame, sized by a height message from
   inside it. The starting height only has to be reasonable — it is replaced as
   soon as the frame reports, and it is what shows if the message never
   arrives.
   -------------------------------------------------------------------------- */

.block-frame {
    width: 100%;
    margin: var(--s-6) 0;
}

.block-frame__frame {
    display: block;
    width: 100%;
    height: 600px;
    border: 0;
    /* The frame scrolls itself only until the parent sizes it. Hiding overflow
       here would clip the content in the seconds before that happens. */
    background: transparent;
}

/* -----------------------------------------------------------------------------
   DRAG TO REORDER
   The handle is the only draggable part, so the text in a row stays
   selectable. The arrow buttons remain — dragging is not reachable by
   keyboard, so it can only ever be an addition to them, never a replacement.
   -------------------------------------------------------------------------- */

[data-sortable] { position: relative; }

.sortable__handle {
    flex: 0 0 auto;
    align-self: center;
    padding: 0 var(--s-3) 0 0;
    color: hsl(var(--ink-subtle));
    font-size: 1.1rem;
    line-height: 1;
    cursor: grab;
    /* Without this the browser claims the gesture for scrolling and the
       pointermove events never arrive on touch. */
    touch-action: none;
    user-select: none;
}

.sortable__handle:hover { color: hsl(var(--primary)); }

/* The row being dragged is lifted out of the flow and follows the pointer, so
   it needs a fixed position and a width pinned from its original size. */
.builder-field.is-dragging {
    position: fixed;
    z-index: 60;
    margin: 0;
    box-shadow: var(--shadow-lg, 0 12px 32px hsl(var(--ink) / 0.18));
    border-radius: var(--r-md);
    background: hsl(var(--surface));
    opacity: 0.97;
    pointer-events: none;
    cursor: grabbing;
}

/* The gap showing where the row will land. */
.sortable__placeholder {
    border: 2px dashed hsl(var(--primary) / 0.45);
    border-radius: var(--r-md);
    background: hsl(var(--primary) / 0.05);
    margin: var(--s-2) 0;
}

/* Whole-document while a drag is in progress: stops the pointer selecting
   text across the page as it moves. */
body.is-sorting {
    user-select: none;
    cursor: grabbing;
}

/* Dimmed while the new order is in flight, so a slow connection is visible
   rather than looking like the drag did nothing. */
[data-sortable].is-saving { opacity: 0.6; }

/* -----------------------------------------------------------------------------
   ROLE PREVIEW BANNER
   Sits above the topbar, full width, and does not scroll away. Somebody who
   forgets they are previewing will misread everything they see afterwards, so
   this has to stay visible and carry the way out with it.
   -------------------------------------------------------------------------- */

.view-as-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3);
    padding: var(--s-3) var(--s-5);
    background: hsl(var(--st-waiting-bg));
    color: hsl(var(--st-waiting));
    font-size: var(--t-sm);
    border-bottom: 1px solid hsl(var(--st-waiting) / 0.3);
}

.view-as-bar strong { font-weight: 650; }

.view-as-bar .btn {
    background: hsl(var(--st-waiting));
    color: hsl(var(--surface));
    border-color: transparent;
}

/* The topbar photo is smaller than the one in a people list, and sits in a
   row of icon-sized controls. Scoped rather than changing .avatar, which is
   sized for lists where the face is the point. */
.topbar .avatar {
    width: 1.75rem;
    height: 1.75rem;
    font-size: var(--t-xs);
}

/* -----------------------------------------------------------------------------
   FORM MEASURE
   A single input stretched across a 1200px panel is the clearest sign of a
   layout nobody looked at: the eye has to travel the whole width to read a
   value that is twenty characters long, and the label sits marooned at the
   far left. Capping a lone field at a readable measure fixes most of the
   forms in the application without touching a single template.

   Fields inside a .field-row are already sharing the width between them, so
   they keep filling their column.
   -------------------------------------------------------------------------- */

.field > .input,
.field > .select,
.field > .textarea,
.field > .editor,
.field > .code-editor {
    max-width: 46rem;
}

.field-row > .field > .input,
.field-row > .field > .select,
.field-row > .field > .textarea,
.field-row > .field > .editor,
.field-row > .field > .code-editor {
    max-width: none;
}

/* Hints and errors line up under the control they belong to rather than
   running the full width of the panel past it. */
.field > .field__hint,
.field > .field__error {
    max-width: 46rem;
}

/* -----------------------------------------------------------------------------
   ROLE PREVIEW
   -------------------------------------------------------------------------- */

.preview__tools { display: flex; align-items: center; gap: var(--s-2); }

/* The stage is a viewport onto the whole application, so it needs real height
   — a short pane makes every page inside it look broken. */
.preview__stage {
    border-top: 1px solid hsl(var(--line));
    border-bottom: 1px solid hsl(var(--line));
    background: hsl(var(--surface-sunken));
}

.preview__frame {
    display: block;
    width: 100%;
    height: min(72vh, 900px);
    border: 0;
    background: hsl(var(--surface));
}

/* Roles as cards rather than table rows. Each one carries a sentence, a
   count and a scope rule — that is a description, and descriptions do not
   read well squeezed into a column. */
.role-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
    gap: var(--s-4);
}

.role-card {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    padding: var(--s-4);
    border: 1px solid hsl(var(--line));
    border-radius: var(--r-lg);
    background: hsl(var(--surface));
}

.role-card--on {
    border-color: hsl(var(--st-waiting) / 0.55);
    background: hsl(var(--st-waiting-bg));
}

.role-card__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--s-2);
}

.role-card__name { font-weight: 600; margin: 0; }

.role-card__summary {
    margin: 0;
    color: hsl(var(--ink-muted));
    font-size: var(--t-sm);
    line-height: 1.5;
}

.role-card__meta {
    margin: 0;
    color: hsl(var(--ink-subtle));
    font-size: var(--t-xs);
}

/* Pushes the button to the bottom so cards of differing text length still
   line their actions up. */
.role-card form { margin-top: auto; }

/* -----------------------------------------------------------------------------
   ICONS
   One shared size and stroke. Drawn with currentColor so an icon takes the
   colour of the text beside it — no per-context variants to keep in step.
   -------------------------------------------------------------------------- */

.icon {
    width: 1.125rem;
    height: 1.125rem;
    flex: 0 0 auto;
    /* Sits on the text baseline rather than the line box, which is what stops
       an icon riding high next to its label. */
    vertical-align: -0.175em;
}

/* Inside a button, the icon and the word are one unit. */
.btn { gap: var(--s-2); }
.btn .icon { width: 1rem; height: 1rem; }


/* -----------------------------------------------------------------------------
   SIDEBAR ICONS AND THE COLLAPSIBLE RAIL
   -------------------------------------------------------------------------- */

.sidebar__link {
    /* Was space-between, which pushed the icon and its label to opposite ends
       of the row the moment an icon existed. */
    justify-content: flex-start;
}

.sidebar__icon {
    width: 1.125rem;
    height: 1.125rem;
    flex: 0 0 auto;
    /* Slightly quieter than the label. An icon at full strength competes with
       the word it is only there to support. */
    opacity: 0.75;
}

.sidebar__link[aria-current='page'] .sidebar__icon,
.sidebar__link:hover .sidebar__icon { opacity: 1; }

.sidebar__label { min-width: 0; }

/* Pushed to the right edge now that the row is packed from the left. */
.sidebar__count { margin-left: auto; }



/* ── The rail ──
   Icons only, at a width that still gives each one a comfortable target. */
.app--rail { --nav-w: 4.25rem; }

.app--rail .sidebar__label { display: none; }

.app--rail .sidebar__link {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
}

/* The group headings are words with no icon, so in a rail they would be
   fragments of text floating against the edge. The border keeps the grouping
   they provided without the words. */
.app--rail .sidebar__group {
    height: 1px;
    margin: var(--s-3) var(--s-3);
    padding: 0;
    background: hsl(var(--nav-fg) / 0.14);
    overflow: hidden;
}

/* A count has nowhere to sit beside a centred icon, so it becomes a dot on
   the corner of it — still visible, no longer pushing the icon off centre. */
.app--rail .sidebar__count {
    position: absolute;
    top: 0.15rem;
    right: 0.9rem;
    min-width: 0.5rem;
    height: 0.5rem;
    padding: 0;
    font-size: 0;
    line-height: 0;
    margin: 0;
}

.app--rail .sidebar__brand { justify-content: center; padding-bottom: var(--s-4); }
.app--rail .sidebar__brand span { display: none; }
.app--rail .sidebar__brand img { max-width: 2.25rem; }

/* The rail is a desktop idea. On a phone the sidebar is already a drawer,
   and a permanent strip of unlabelled icons down the edge of a small screen
   takes space from the content without replacing the drawer. */
@media (max-width: 60rem) {
    .app--rail { --nav-w: 15rem; }
    .app--rail .sidebar__label { display: initial; }
    .app--rail .sidebar__link { justify-content: flex-start; padding-left: var(--s-3); }
    .app--rail .sidebar__group { height: auto; background: none; margin: 0; }
    .app--rail .sidebar__count { position: static; font-size: var(--t-xs); line-height: 1.35rem; }
}

/* The alert icon holds its size and aligns with the first line of the message
   rather than centring against a paragraph that may run to three lines. */
.alert > .icon {
    margin-top: 0.15rem;
    align-self: flex-start;
}

/* The profile avatar in the sidebar footer sits between the topbar size and
   the list size: small enough not to dominate the footer, large enough to be
   the only thing left when the rail is collapsed. */
.sidebar__avatar {
    width: 1.75rem;
    height: 1.75rem;
    flex: 0 0 auto;
    font-size: var(--t-xs);
}

/* The menu icon is larger than an inline text icon — it is a standalone
   target, not a decoration beside a word. */
.nav-toggle .icon { width: 1.25rem; height: 1.25rem; vertical-align: 0; }

/* -----------------------------------------------------------------------------
   GOOGLE SIGN-IN

   Given its own treatment rather than reusing .btn--secondary. Google's mark
   is multicoloured and fixed, so a button carrying it has to be a light,
   neutral surface — on our navy or blue it turns muddy. The result also reads
   as "a different kind of thing from the form below", which is exactly what
   it is.
   -------------------------------------------------------------------------- */

.btn-google {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--s-3);
    width: 100%;
    min-height: 2.75rem;
    padding: 0.625rem var(--s-4);
    background: hsl(var(--surface));
    border: 1px solid hsl(var(--line-strong));
    border-radius: var(--r-md);
    color: hsl(var(--ink));
    font-family: inherit;
    font-size: var(--t-base);
    font-weight: 550;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.14s ease, border-color 0.14s ease;
}

.btn-google:hover {
    background: hsl(var(--surface-sunken));
    border-color: hsl(var(--ink-subtle));
}

.btn-google:focus-visible {
    outline: 2px solid hsl(var(--primary));
    outline-offset: 2px;
}

/* The mark keeps its own colours — the one exception to the currentColor rule
   the icon sprite follows, because Google's brand terms fix them. */
.btn-google__mark {
    width: 1.125rem;
    height: 1.125rem;
    flex-shrink: 0;
}

/* A labelled rule, so neither way in reads as a caption of the other. The
   label carries the instruction, which a bare "or" does not. */
.or-divider {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    margin: var(--s-5) 0;
    color: hsl(var(--ink-subtle));
    font-size: var(--t-sm);
    white-space: nowrap;
}

.or-divider::before,
.or-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: hsl(var(--line));
}

.auth-note {
    margin: var(--s-2) 0 0;
    color: hsl(var(--ink-subtle));
    font-size: var(--t-xs);
    text-align: center;
}

/* A grid of tick boxes — country authorisations, and anywhere else a long
   list has to be scannable rather than a single tall column. */
.choice-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    gap: var(--s-2) var(--s-4);
    margin-top: var(--s-2);
}

/* -----------------------------------------------------------------------------
   METRICS
   The row of figures at the top of the dashboard.

   Elevation IS used here, unlike the panels below, and the distinction is
   deliberate: these four sit above the page as a summary of it, where a panel
   is part of the page. The shadow is the one signal separating "this is about
   everything below" from "this is one of the things below". Giving every panel
   the same treatment is what makes a dashboard read as an undifferentiated
   wall of cards.
   -------------------------------------------------------------------------- */

.metric-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: var(--s-4);
    margin-bottom: var(--s-6);
}

.metric {
    display: flex;
    align-items: flex-start;
    gap: var(--s-4);
    padding: var(--s-5);
    background: hsl(var(--surface));
    border: 1px solid hsl(var(--line));
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-sm);
}

/* The icon is a tinted badge rather than a bare glyph. At this size a lone
   stroked icon beside a large number looks like a mistake; a filled shape
   reads as a deliberate marker. */
.metric__icon {
    display: inline-grid;
    place-items: center;
    flex-shrink: 0;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--r-md);
}

.metric__icon .icon { width: 1.25rem; height: 1.25rem; }

.metric__icon--pending { background: hsl(var(--st-progress-bg)); color: hsl(var(--st-progress)); }
.metric__icon--good    { background: hsl(var(--st-good-bg));     color: hsl(var(--st-good)); }
.metric__icon--warn    { background: hsl(var(--st-waiting-bg));  color: hsl(var(--st-waiting)); }
.metric__icon--neutral { background: hsl(var(--st-neutral-bg));  color: hsl(var(--st-neutral)); }

.metric__label {
    margin: 0;
    font-size: var(--t-sm);
    font-weight: 500;
    color: hsl(var(--ink-muted));
}

/* The only place in the application where type gets this large. Money and
   counts are what people come to read; everything else on the page is
   navigation towards them. */
.metric__value {
    margin: 0.15rem 0 0;
    font-size: 1.75rem;
    font-weight: 650;
    line-height: 1.15;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}

.metric__value .figure__unit {
    font-size: 0.875rem;
    font-weight: 500;
    color: hsl(var(--ink-subtle));
    letter-spacing: 0;
}

.metric__note {
    margin: 0.25rem 0 0;
    font-size: var(--t-xs);
    color: hsl(var(--ink-subtle));
}

@media (max-width: 40rem) {
    .metric-grid { grid-template-columns: 1fr 1fr; gap: var(--s-3); }
    .metric { flex-direction: column; gap: var(--s-3); padding: var(--s-4); }
    .metric__value { font-size: 1.375rem; }
}

/* -----------------------------------------------------------------------------
   NAVIGATION PROGRESS
   A bar at the very top edge. Deliberately thin and above everything: it must
   be visible over the topbar and any dialog without being something the eye
   has to work around.
   -------------------------------------------------------------------------- */

.progress-bar {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 200;
    width: 0;
    height: 2px;
    background: linear-gradient(90deg,
        hsl(var(--primary)) 0%,
        hsl(var(--brand-cyan, var(--primary))) 60%,
        hsl(var(--brand-gold, var(--primary))) 100%);
    opacity: 0;
    pointer-events: none;
    /* Width eases; opacity snaps. A bar that faded in would be invisible for
       the first moments of exactly the wait it exists to report. */
    transition: width 0.18s ease-out, opacity 0.18s ease;
}

.progress-bar.is-active {
    opacity: 1;
    /* A soft glow at the leading edge, so the bar reads as moving rather than
       as a static rule across the top of the page. */
    box-shadow: 0 0 8px hsl(var(--primary) / 0.5);
}

@media (prefers-reduced-motion: reduce) {
    .progress-bar { transition: opacity 0.18s ease; }
}


/* -----------------------------------------------------------------------------
   TYPE-TO-FILTER SELECT
   The native control stays in the form, visually hidden. It is not removed:
   it carries the name and the value, and it is what submits.
   -------------------------------------------------------------------------- */

.combo { position: relative; }

.combo__native {
    /* Not display:none — a hidden-but-present control keeps working with the
       form, and keeps browser autofill and validation attached to it. */
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

.combo__input { width: 100%; }

.combo__list {
    position: absolute;
    z-index: 40;
    top: calc(100% + 0.25rem);
    left: 0;
    right: 0;
    max-height: 16rem;
    margin: 0;
    padding: 0.25rem;
    overflow-y: auto;
    list-style: none;
    background: hsl(var(--surface));
    border: 1px solid hsl(var(--line-strong));
    border-radius: var(--r-md);
    box-shadow: var(--shadow-md);
}

.combo__list[hidden] { display: none; }

.combo__option {
    padding: 0.4375rem var(--s-3);
    border-radius: var(--r-sm);
    font-size: var(--t-base);
    cursor: pointer;
}

.combo__option:hover,
.combo__option.is-active {
    background: hsl(var(--primary) / 0.1);
    color: hsl(var(--primary));
}

.combo__option.is-selected { font-weight: 600; }

.combo__empty {
    padding: var(--s-3);
    color: hsl(var(--ink-subtle));
    font-size: var(--t-sm);
}

/* -----------------------------------------------------------------------------
   PAGINATION
   -------------------------------------------------------------------------- */

.pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3);
    margin-top: var(--s-4);
}

.pagination__count,
.pagination__position {
    margin: 0;
    color: hsl(var(--ink-muted));
    font-size: var(--t-sm);
    font-variant-numeric: tabular-nums;
}

.pagination__controls {
    display: flex;
    align-items: center;
    gap: var(--s-3);
}

/* Shown rather than removed at the ends of the range, so the controls do not
   jump sideways as you page through. */
.is-disabled {
    opacity: 0.45;
    pointer-events: none;
}

/* -----------------------------------------------------------------------------
   NEEDS ATTENTION
   The same card shape as the metrics above, tinted by severity.

   These were full-width stacked banners. Three of them pushed the figures and
   the approval queue below the fold, so the more urgent the state of the
   system, the less of it you could see — and an administrator who sees the
   same three banners every morning stops reading them anyway.

   As cards they sit in one row, stay scannable, and the whole card is the
   link: the target is the action, so the hit area should be too.
   -------------------------------------------------------------------------- */

.attention-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: var(--s-4);
    margin-bottom: var(--s-5);
}

.attention {
    display: flex;
    align-items: flex-start;
    gap: var(--s-3);
    padding: var(--s-4);
    background: hsl(var(--surface-sunken));
    border: 1px solid hsl(var(--line));
    border-radius: var(--r-lg);
    text-decoration: none;
    color: inherit;
    transition: transform 0.12s ease, box-shadow 0.12s ease;
}

.attention:hover {
    /* A lift rather than a colour change: the tint already carries the
       severity, and changing it on hover would suggest the severity changed. */
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
}

.attention__icon {
    display: inline-grid;
    place-items: center;
    flex-shrink: 0;
    width: 2rem;
    height: 2rem;
    border-radius: var(--r-md);
}

.attention__icon .icon { width: 1.125rem; height: 1.125rem; }

.attention__body { display: block; min-width: 0; }

.attention__title {
    display: block;
    font-weight: 600;
    font-size: var(--t-sm);
    line-height: 1.35;
}

.attention__detail {
    display: block;
    margin-top: 0.2rem;
    font-size: var(--t-xs);
    line-height: 1.5;
    color: hsl(var(--ink-muted));
    /* Long lists of country names are clipped to two lines. The card is a
       prompt to go and look, not the place to read the whole list. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.attention__action {
    display: inline-block;
    margin-top: 0.4rem;
    font-size: var(--t-xs);
    font-weight: 600;
    text-decoration: underline;
}

/* Severity carries the colour. Error is the brand magenta, since the palette
   has no red. */
.attention--error {
    background: hsl(var(--st-bad-bg));
    border-color: hsl(var(--st-bad) / 0.25);
}
.attention--error .attention__icon,
.attention--error .attention__action { color: hsl(var(--st-bad)); }
.attention--error .attention__icon   { background: hsl(var(--st-bad) / 0.14); }

.attention--warning {
    background: hsl(var(--st-waiting-bg));
    border-color: hsl(var(--st-waiting) / 0.28);
}
.attention--warning .attention__icon,
.attention--warning .attention__action { color: hsl(var(--st-waiting)); }
.attention--warning .attention__icon   { background: hsl(var(--st-waiting) / 0.16); }

@media (max-width: 40rem) {
    .attention-grid { grid-template-columns: 1fr; gap: var(--s-3); }
}

/* -----------------------------------------------------------------------------
   COUNTRY CARDS
   A grid rather than table rows: each country carries a name, a person, a
   count and a state, and four facts about one thing read better together in a
   block than spread across a row the eye has to track.
   -------------------------------------------------------------------------- */

.country-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    gap: var(--s-4);
}

.country-card {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    padding: var(--s-4);
    border: 1px solid hsl(var(--line));
    border-left: 3px solid hsl(var(--line));
    border-radius: var(--r-lg);
    background: hsl(var(--surface));
    text-decoration: none;
    color: inherit;
    transition: transform 0.12s ease, box-shadow 0.12s ease;
}

.country-card:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
}

/* The state lives on the left edge rather than in the card's background. A
   tinted card would make a country look like an alert; the edge marks it
   without shouting, and a whole grid of tinted cards would be unreadable. */
.country-card--blocked { border-left-color: hsl(var(--st-bad)); }
.country-card--partial { border-left-color: hsl(var(--st-waiting)); }
.country-card--off     { opacity: 0.6; }

.country-card__head {
    display: flex;
    align-items: center;
    gap: var(--s-3);
}

/* The ISO code as a badge. Two or three letters is a recognisable shorthand
   for people who work across countries daily, and it gives the card an anchor
   without needing a flag image for every country. */
.country-card__code {
    display: inline-grid;
    place-items: center;
    flex-shrink: 0;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--r-md);
    background: hsl(var(--primary) / 0.1);
    color: hsl(var(--primary));
    font-size: var(--t-xs);
    font-weight: 700;
    letter-spacing: 0.02em;
}

.country-card__name { margin: 0; font-weight: 600; }

.country-card__meta,
.country-card__head-name {
    margin: 0;
    font-size: var(--t-xs);
    color: hsl(var(--ink-muted));
}

.country-card__head-name { font-weight: 550; }

.country-card .status { align-self: flex-start; margin-top: auto; }

/* The running total under the activity lines. The number sits on the right,
   aligned with the pounds column above it, so it reads as the sum of that
   column rather than as a separate figure. */
.line-total {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-4);
    margin-top: var(--s-5);
    padding-top: var(--s-4);
    border-top: 1px solid hsl(var(--line));
}

.line-total__figure {
    display: flex;
    align-items: baseline;
    gap: var(--s-3);
}

.line-total__label {
    font-size: var(--t-sm);
    font-weight: 500;
    color: hsl(var(--ink-muted));
}

.line-total__value {
    font-size: 1.375rem;
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}
