/* ============================================================================
   FDS — Fitmentry Design System · v0.7.1 "Ion"

   Nothing here names a product. The wordmark is a slot the consumer fills.

   FOUR AXES. A component reads roles and knows about none of them.

     <html data-theme="day|night">          omit -> follows the OS
           data-contrast="standard|high"    omit -> standard
           data-density="desk|shop"         omit -> desk
           data-account="active|locked"     omit -> active

   LAYER ORDER — never skipped inside a component:
     1. primitives   --fdsx-*   raw ramps.  A component may never name one.
     2. semantic     --fds-*    role-named. Flips with theme.
     3. contrast     --fds-*    overlays the theme.
     4. density      --fds-*    overlays ergonomics.

   Every ratio quoted in a comment is measured, not estimated.
   ==========================================================================*/


/* ============================================================================
   RELATIVE SIZING — the rule the whole sheet obeys

   Type, spacing, control heights, icons and measures are in `rem`. They scale
   with the browser's text-size setting, which somebody in a shop office has
   set to 125% and is not going to change for us.

   FDS never sets a font-size on `html`. The root is the person's, not ours.

   What stays in px, and why:
     · border widths and hairlines — a 1px rule is a device-pixel decision, and
       a 1.25px rule renders as a smear
     · corner radii — a 12px corner is a 12px corner at any text size; scaling
       it makes large-text mode look inflated rather than larger
     · shadows — physical distance from a surface, unrelated to text
     · touch floors — `max(2.75rem, 44px)`, so shrinking the text can never
       shrink a target below what a gloved thumb needs

   Fluid marketing sizes are `clamp(rem, rem + vw, rem)`. The middle term keeps
   a rem base on purpose: `clamp(38px, 7vw, 72px)` ignores the text-size
   setting completely, which is a 1.4.4 failure dressed up as responsiveness.
   ==========================================================================*/


/* --- 0 · FACES -----------------------------------------------------------
   Self-hosted. A third party staying up is a dependency of a different kind
   from a licence fee, and it is why OFL faces were chosen. Serve `fonts/`
   beside this file.

   The axes are declared unclamped. `wdth` on Archivo and Martian Mono is what
   the density switch drives — clamp it and the switch silently stops working
   while everything still renders.
   ------------------------------------------------------------------------*/
@font-face{font-family:'Archivo';src:url('fonts/Archivo[wdth,wght].woff2') format('woff2');
  font-weight:100 900;font-stretch:62% 125%;font-style:normal;font-display:swap}
@font-face{font-family:'Archivo';src:url('fonts/Archivo-Italic[wdth,wght].woff2') format('woff2');
  font-weight:100 900;font-stretch:62% 125%;font-style:italic;font-display:swap}
@font-face{font-family:'Newsreader';src:url('fonts/Newsreader[opsz,wght].woff2') format('woff2');
  font-weight:200 800;font-style:normal;font-display:swap}
@font-face{font-family:'Martian Mono';src:url('fonts/MartianMono[wdth,wght].woff2') format('woff2');
  font-weight:100 800;font-stretch:75% 112.5%;font-style:normal;font-display:swap}


/* --- 1 · PRIMITIVES ------------------------------------------------------*/
:root{

  /* Graphite — the day neutral. Near-neutral with a trace of cool, which is
     what keeps a white card from going yellow next to a grey page. */
  --fdsx-g-000:#FFFFFF; --fdsx-g-025:#FAFBFC; --fdsx-g-050:#F1F3F5;
  --fdsx-g-100:#E7EAEC; --fdsx-g-200:#DCE0E4; --fdsx-g-300:#BFC4C9;
  --fdsx-g-400:#8C9196; --fdsx-g-500:#6E7378; --fdsx-g-550:#4A4E52;
  --fdsx-g-600:#55595E; --fdsx-g-750:#2E3134; --fdsx-g-900:#16181B;
  --fdsx-g-999:#0A0A0A; --fdsx-g-black:#000000;

  /* Carbon — the night neutral, and the only reason the system carries two
     ramps. A cast invisible at day lightnesses is dominant at night ones.
     `300` is #A6ACB2, not #9AA0A6: the older value passed on the page and on
     a card and fell to 4.31:1 on a raised surface, which is exactly where
     secondary text inside a card lives. */
  --fdsx-n-050:#E8EAED; --fdsx-n-150:#C3C7CB; --fdsx-n-300:#A6ACB2;
  --fdsx-n-400:#8A9096; --fdsx-n-450:#838991; --fdsx-n-500:#6B7075;
  --fdsx-n-600:#43474C; --fdsx-n-700:#35383C; --fdsx-n-800:#2A2C2F;
  --fdsx-n-900:#202124; --fdsx-n-925:#24272B; --fdsx-n-950:#1A1C1F;
  --fdsx-n-975:#16181B; --fdsx-n-999:#0D0E10;

  /* Cyan — the action colour. One hue means "you may press this". A bright
     fill and a deep fill do not take the same label: 100–400 are pressed with
     near-black, 700–900 with white. Nothing between works with either, which
     is why the ramp has a hole in the middle. The hole is the honest part. */
  --fdsx-c-050:#DCF3F8; --fdsx-c-100:#6FF0FF; --fdsx-c-200:#0FDCF5;
  --fdsx-c-400:#00B8D9; --fdsx-c-700:#0A7185; --fdsx-c-800:#0A5F70;
  --fdsx-c-900:#145C73; --fdsx-c-tint-n:#1D3F45; --fdsx-c-tint-hc-n:#10262B;
  --fdsx-c-tint-hc-d:#F2FAFC; --fdsx-c-ink:#08222A;

  /* Volt — emphasis, and the one role that is a fill and never a mark on day.
     Lime cannot hold 3:1 against white at any usable saturation. */
  --fdsx-v-050:#EFF8E1; --fdsx-v-200:#CCFF14; --fdsx-v-300:#DDFF63;
  --fdsx-v-400:#9BE317; --fdsx-v-500:#4A7A05; --fdsx-v-700:#33520E;
  --fdsx-v-tint-n:#3C4521; --fdsx-v-tint-hc-n:#2E3A16; --fdsx-v-tint-hc-d:#F6FBEE;

  /* Status. Each carries a fill and a mark. The fill is a solid chip, bright
     enough to find across a bay. The mark is the same status as text, a dot,
     a 1px rule — which the fill cannot do, because a colour that survives at
     20px of solid area does not survive at 1px of stroke.

     Night marks for info and danger are lifted off their fills: #A06BFF reads
     3.39:1 and #FF5C7A reads 3.96:1 as text on a raised surface. Both fail. */
  --fdsx-ok-100:#62F5C0; --fdsx-ok-300:#10E8A0; --fdsx-ok-400:#00C27A;
  --fdsx-ok-600:#047857; --fdsx-ok-700:#065F46; --fdsx-ok-050:#DCF4EC;
  --fdsx-ok-tint-n:#1D4138; --fdsx-ok-tint-hc-n:#16332B; --fdsx-ok-tint-hc-d:#F0FBF6;

  --fdsx-wn-100:#FFCF4D; --fdsx-wn-300:#FFBA0A; --fdsx-wn-400:#FFB800;
  --fdsx-wn-600:#A65A06; --fdsx-wn-700:#8C3D0B; --fdsx-wn-050:#FBF3DE;
  --fdsx-wn-tint-n:#443920; --fdsx-wn-tint-hc-n:#3A3018; --fdsx-wn-tint-hc-d:#FDF8EC;

  --fdsx-dg-100:#FF93A6; --fdsx-dg-200:#FF8FA3; --fdsx-dg-300:#FF5C7A;
  --fdsx-dg-500:#E80B45; --fdsx-dg-600:#BE0B3C; --fdsx-dg-700:#9A0F35;
  --fdsx-dg-050:#F8DEE6;
  --fdsx-dg-tint-n:#442A32; --fdsx-dg-tint-hc-n:#2E1A1F; --fdsx-dg-tint-hc-d:#FDF2F5;

  --fdsx-in-100:#C9AEFF; --fdsx-in-200:#B98CFF; --fdsx-in-300:#A06BFF;
  --fdsx-in-500:#7B2FF7; --fdsx-in-600:#6A21D9; --fdsx-in-700:#48189A;
  --fdsx-in-050:#EBE3FB;
  --fdsx-in-tint-n:#342D47; --fdsx-in-tint-hc-n:#221C36; --fdsx-in-tint-hc-d:#F6F2FE;

  /* Focus — deliberately not any status hue and not the action hue. Blue is
     what is left once cyan is the action colour, so a blue ring can only mean
     "the keyboard is here". */
  --fdsx-fc-200:#ADC7FB; --fdsx-fc-300:#7FA5F7;
  --fdsx-fc-500:#2563EB; --fdsx-fc-700:#163FA3;

  /* --- Families --- */
  --fds-font-sans: 'Archivo','Helvetica Neue',Arial,system-ui,sans-serif;
  --fds-font-serif:'Newsreader',Georgia,'Times New Roman',serif;
  --fds-font-mono: 'Martian Mono',ui-monospace,'SF Mono',Menlo,Consolas,monospace;

  /* --- Type steps ---
     One ramp, in rem, quoted against a 16px root so the intent stays legible.
     Tiers name steps; components name tiers. Nothing names a rem directly. */
  --fds-fs-100:0.6875rem;  /* 11 */   --fds-fs-200:0.75rem;    /* 12 */
  --fds-fs-300:0.8125rem;  /* 13 */   --fds-fs-400:0.875rem;   /* 14 */
  --fds-fs-500:0.9375rem;  /* 15 */   --fds-fs-600:1rem;       /* 16 */
  --fds-fs-700:1.0625rem;  /* 17 */   --fds-fs-800:1.1875rem;  /* 19 */
  --fds-fs-900:1.3125rem;  /* 21 */   --fds-fs-1000:1.5rem;    /* 24 */
  --fds-fs-1100:1.5625rem; /* 25 */   --fds-fs-1200:1.6875rem; /* 27 */
  --fds-fs-1300:2rem;      /* 32 */   --fds-fs-1400:2.125rem;  /* 34 */
  --fds-fs-1500:2.375rem;  /* 38 */   --fds-fs-1600:2.25rem;   /* 36 */

  /* --- Leading and tracking ---
     Tracking is in `em` so it tracks the size it is applied to. Tracking in px
     is the commonest way a type scale falls apart at 200% zoom. */
  --fds-lh-flat:1.02; --fds-lh-tight:1.16; --fds-lh-snug:1.3;
  --fds-lh-body:1.5;  --fds-lh-prose:1.7;
  --fds-tr-hero:-.032em; --fds-tr-display:-.024em; --fds-tr-heading:-.012em;
  --fds-tr-eyebrow:.09em; --fds-tr-kicker:.13em;

  /* --- Space, in rem so the layout breathes when the text does --- */
  --fds-sp-1:0.125rem; --fds-sp-2:0.25rem; --fds-sp-3:0.375rem; --fds-sp-4:0.5rem;
  --fds-sp-5:0.75rem;  --fds-sp-6:1rem;    --fds-sp-7:1.25rem;  --fds-sp-8:1.5rem;
  --fds-sp-9:2rem;     --fds-sp-10:2.5rem; --fds-sp-11:3rem;    --fds-sp-12:4rem;
  --fds-sp-13:5rem;    --fds-sp-14:6rem;

  /* --- Corners — in px, deliberately.
     A component asks for the radius of the thing it *is*, not for a step on a
     scale. That is what stops a card and a button drifting apart because two
     people picked different steps six weeks apart.

     Controls carry a radius proportional to their height, because a constant
     9px reads tight on a 30px button and slack on a 56px one. Same optical
     curvature at every size is the point, not the same number.

     NESTING RULE: inner radius = outer radius − the gap between them. A 12px
     card with 8px of padding holds a 4px inner corner. Concentric corners look
     machined; equal corners look pasted. */
  --fds-r-sharp:0;
  --fds-r-mark:4px;
  --fds-r-ctl-sm:7px; --fds-r-ctl-md:9px; --fds-r-ctl-lg:11px; --fds-r-ctl-xl:13px;
  --fds-r-control:var(--fds-r-ctl-md);
  --fds-r-field:var(--fds-r-ctl-md);
  --fds-r-card:12px;
  --fds-r-panel:16px;
  --fds-r-sheet:20px;
  --fds-r-pill:999px;   /* chips, and nothing else. A pill on a solid accent
                           fill dates an interface. */

  /* --- Surface treatment ---
     Depth is one specular line, not a wash: a gradient wide enough to see is
     wide enough to band, and it bands visibly on the 6-bit panels most shop
     tablets ship with.

     `sheen` lightens upward and belongs on a fill with a dark label.
     `sheen-inv` darkens downward and belongs on a fill with a white one. The
     rule under both: the gradient moves *away* from the label colour, so the
     flat token stays the worst case and every measured ratio still holds.

     Alpha stays at .05. Above about .10 the overlay eats the chroma and the
     whole palette reads pastel. */
  --fds-sheen:         linear-gradient(180deg,rgba(255,255,255,.05) 0%,rgba(255,255,255,0) 55%);
  --fds-sheen-inv:     linear-gradient(180deg,rgba(0,0,0,.05) 0%,rgba(0,0,0,0) 55%);
  --fds-sheen-flip:    linear-gradient(0deg,rgba(255,255,255,.05) 0%,rgba(255,255,255,0) 55%);
  --fds-sheen-flip-inv:linear-gradient(0deg,rgba(0,0,0,.05) 0%,rgba(0,0,0,0) 55%);

  /* --- Motion ---
     Five durations and five curves. More than that and nobody remembers which
     to use, so everything quietly becomes 200ms ease. */
  --fds-dur-instant:90ms;  --fds-dur-fast:120ms;   --fds-dur-base:200ms;
  --fds-dur-slow:320ms;    --fds-dur-slower:480ms; --fds-dur-stagger:28ms;

  --fds-ease-standard:  cubic-bezier(.2,.8,.2,1);    /* the default            */
  --fds-ease-emphasized:cubic-bezier(.2,0,0,1);      /* a screen-level change  */
  --fds-ease-entrance:  cubic-bezier(.2,.9,.24,1.08);/* arrives with a settle  */
  --fds-ease-exit:      cubic-bezier(.4,0,1,1);      /* leaves without ceremony*/
  --fds-ease-data:      cubic-bezier(.4,0,.2,1);     /* monotonic, no overshoot:
                                                        a number must never pass
                                                        its own value and come back */
  --fds-hover-in:110ms; --fds-hover-out:0ms;

  /* --- Strokes, in px --- */
  --fds-border-w:1px; --fds-border-thick:2px; --fds-focus-w:2px; --fds-depress:0.5px;

  /* --- Icons, in rem: an icon is a piece of type and grows with it --- */
  --fds-ic-xs:0.875rem; --fds-ic-sm:1rem; --fds-ic-md:1.25rem;
  --fds-ic-lg:1.5rem;   --fds-ic-xl:2rem; --fds-ic-stroke:1.8;

  /* Optical stroke compensation. A stroke declared in the 24-unit viewBox
     scales with the icon, so 1.8 becomes 1.05 device px at xs and 2.4 at xl.
     Proportionally correct, optically wrong in both directions: the small one
     thins out against the text and the large one reads bold beside Archivo 400.
     Each size therefore carries its own stroke, chosen so the rendered weight
     stays roughly constant against the type it sits beside. */
  --fds-ic-stroke-xs:2.2; --fds-ic-stroke-sm:2;   --fds-ic-stroke-md:1.8;
  --fds-ic-stroke-lg:1.7; --fds-ic-stroke-xl:1.5;

  /* --- Stacking. One scale, no ad-hoc numbers, ever. --- */
  --fds-z-base:0; --fds-z-sticky:100; --fds-z-dropdown:300; --fds-z-overlay:400;
  --fds-z-drawer:500; --fds-z-modal:600; --fds-z-toast:700; --fds-z-tooltip:800;
}


/* --- 2 · SEMANTIC : DAY --------------------------------------------------*/
:root, [data-theme="day"]{
  color-scheme:light;
  --fds-page:var(--fdsx-g-050); --fds-canvas:var(--fdsx-g-025);
  --fds-surface:var(--fdsx-g-050); --fds-raised:var(--fdsx-g-000);
  --fds-floating:var(--fdsx-g-000); --fds-sunk:var(--fdsx-g-050);

  --fds-border:var(--fdsx-g-200);          /* hairline. Separation, not meaning */
  --fds-border-control:var(--fdsx-g-400);  /* 3.18:1 — inputs, pressables       */
  --fds-hairline:var(--fdsx-g-100);

  --fds-text:var(--fdsx-g-900);            /* 17.17:1 on canvas          */
  --fds-text-muted:var(--fdsx-g-600);      /*  6.81 canvas / 7.05 raised */
  --fds-text-disabled:var(--fdsx-g-400);   /* exempt. Never meaning.     */

  --fds-accent:var(--fdsx-c-400); --fds-accent-ink:var(--fdsx-g-999);
  --fds-accent-mark:var(--fdsx-c-700); --fds-accent-tint:var(--fdsx-c-050);
  --fds-volt:var(--fdsx-v-400); --fds-volt-ink:var(--fdsx-g-999);
  --fds-volt-mark:var(--fdsx-v-500); --fds-volt-tint:var(--fdsx-v-050);
  --fds-ok:var(--fdsx-ok-400); --fds-ok-ink:var(--fdsx-g-999);
  --fds-ok-mark:var(--fdsx-ok-600); --fds-ok-tint:var(--fdsx-ok-050);
  --fds-wn:var(--fdsx-wn-400); --fds-wn-ink:var(--fdsx-g-999);
  --fds-wn-mark:var(--fdsx-wn-600); --fds-wn-tint:var(--fdsx-wn-050);
  --fds-dg:var(--fdsx-dg-500); --fds-dg-ink:var(--fdsx-g-000);
  --fds-dg-mark:var(--fdsx-dg-600); --fds-dg-tint:var(--fdsx-dg-050);
  --fds-in:var(--fdsx-in-500); --fds-in-ink:var(--fdsx-g-000);
  --fds-in-mark:var(--fdsx-in-600); --fds-in-tint:var(--fdsx-in-050);

  /* Neutral tone, for buttons that must not claim the action hue. */
  --fds-neutral:var(--fdsx-g-900); --fds-neutral-ink:var(--fdsx-g-000);
  --fds-neutral-mark:var(--fdsx-g-600); --fds-neutral-tint:var(--fdsx-g-100);

  --fds-focus:var(--fdsx-fc-500);          /* 4.99:1 on canvas */
  --fds-focus-soft:rgba(37,99,235,.18);
  --fds-fill-sheen:var(--fds-sheen); --fds-fill-sheen-pressed:var(--fds-sheen-flip);
  --fds-scrim:rgba(22,24,27,.44);
  --fds-hover-wash:rgba(22,24,27,.045); --fds-press-wash:rgba(22,24,27,.075);
  --fds-skeleton:linear-gradient(90deg,var(--fdsx-g-100) 0%,var(--fdsx-g-050) 40%,var(--fdsx-g-100) 80%);

  /* Elevation. On day, depth is carried by shadow first: the surfaces are
     already near-white and cannot step up much further. */
  --fds-e1:0 1px 2px rgba(22,24,27,.07);
  --fds-e2:0 6px 16px rgba(22,24,27,.10),0 1px 3px rgba(22,24,27,.08);
  --fds-e3:0 14px 32px rgba(22,24,27,.14),0 2px 6px rgba(22,24,27,.08);
  --fds-e4:0 26px 60px rgba(22,24,27,.20),0 4px 10px rgba(22,24,27,.10);
  --fds-e-drag:0 18px 36px rgba(22,24,27,.22),0 3px 8px rgba(22,24,27,.12);
  --fds-e-edge:0 0 0 0 transparent;
}

/* --- 3 · SEMANTIC : NIGHT ------------------------------------------------*/
[data-theme="night"]{
  color-scheme:dark;
  --fds-page:var(--fdsx-n-975); --fds-canvas:var(--fdsx-n-900);
  --fds-surface:var(--fdsx-n-800); --fds-raised:var(--fdsx-n-700);
  --fds-floating:var(--fdsx-n-700); --fds-sunk:var(--fdsx-n-950);

  --fds-border:var(--fdsx-n-600);
  --fds-border-control:var(--fdsx-n-450);  /* 4.56:1 on canvas */
  --fds-hairline:var(--fdsx-n-700);

  --fds-text:var(--fdsx-n-050);            /* 13.36:1 on canvas          */
  --fds-text-muted:var(--fdsx-n-300);      /*  7.03 canvas / 5.14 raised */
  --fds-text-disabled:var(--fdsx-n-400);

  --fds-accent:var(--fdsx-c-200); --fds-accent-ink:var(--fdsx-c-ink);
  --fds-accent-mark:var(--fdsx-c-200); --fds-accent-tint:var(--fdsx-c-tint-n);
  --fds-volt:var(--fdsx-v-200); --fds-volt-ink:var(--fdsx-g-999);
  --fds-volt-mark:var(--fdsx-v-200); --fds-volt-tint:var(--fdsx-v-tint-n);
  --fds-ok:var(--fdsx-ok-300); --fds-ok-ink:var(--fdsx-g-999);
  --fds-ok-mark:var(--fdsx-ok-300); --fds-ok-tint:var(--fdsx-ok-tint-n);
  --fds-wn:var(--fdsx-wn-300); --fds-wn-ink:var(--fdsx-g-999);
  --fds-wn-mark:var(--fdsx-wn-300); --fds-wn-tint:var(--fdsx-wn-tint-n);
  --fds-dg:var(--fdsx-dg-300); --fds-dg-ink:var(--fdsx-g-999);
  --fds-dg-mark:var(--fdsx-dg-200); --fds-dg-tint:var(--fdsx-dg-tint-n);
  --fds-in:var(--fdsx-in-300); --fds-in-ink:var(--fdsx-g-999);
  --fds-in-mark:var(--fdsx-in-200); --fds-in-tint:var(--fdsx-in-tint-n);

  --fds-neutral:var(--fdsx-n-050); --fds-neutral-ink:var(--fdsx-n-900);
  --fds-neutral-mark:var(--fdsx-n-300); --fds-neutral-tint:var(--fdsx-n-700);

  --fds-focus:var(--fdsx-fc-300);          /* 6.61:1 on canvas */
  --fds-focus-soft:rgba(127,165,247,.22);
  --fds-fill-sheen:var(--fds-sheen); --fds-fill-sheen-pressed:var(--fds-sheen-flip);
  --fds-scrim:rgba(13,14,16,.64);
  --fds-hover-wash:rgba(255,255,255,.05); --fds-press-wash:rgba(255,255,255,.085);
  --fds-skeleton:linear-gradient(90deg,var(--fdsx-n-800) 0%,var(--fdsx-n-700) 40%,var(--fdsx-n-800) 80%);

  /* On night, depth is carried by surface lightness first and shadow second.
     A shadow on a charcoal page is a smudge; a lighter surface is a step. The
     `-edge` inset is the top specular line that tells the eye which way is up
     once the shadow has stopped doing that job. */
  --fds-e1:0 1px 2px rgba(0,0,0,.40);
  --fds-e2:0 6px 18px rgba(0,0,0,.50),0 1px 3px rgba(0,0,0,.40);
  --fds-e3:0 14px 34px rgba(0,0,0,.55),0 2px 6px rgba(0,0,0,.42);
  --fds-e4:0 28px 64px rgba(0,0,0,.66),0 4px 10px rgba(0,0,0,.46);
  --fds-e-drag:0 20px 40px rgba(0,0,0,.58),0 3px 8px rgba(0,0,0,.44);
  --fds-e-edge:inset 0 1px 0 rgba(255,255,255,.06);
}

@media (prefers-color-scheme:dark){
  :root:not([data-theme]){
    color-scheme:dark;
    --fds-page:#16181B; --fds-canvas:#202124; --fds-surface:#2A2C2F;
    --fds-raised:#35383C; --fds-floating:#35383C; --fds-sunk:#1A1C1F;
    --fds-border:#43474C; --fds-border-control:#838991; --fds-hairline:#35383C;
    --fds-text:#E8EAED; --fds-text-muted:#A6ACB2; --fds-text-disabled:#8A9096;
    --fds-accent:#0FDCF5; --fds-accent-ink:#08222A; --fds-accent-mark:#0FDCF5; --fds-accent-tint:#1D3F45;
    --fds-volt:#CCFF14; --fds-volt-ink:#0A0A0A; --fds-volt-mark:#CCFF14; --fds-volt-tint:#3C4521;
    --fds-ok:#10E8A0; --fds-ok-ink:#0A0A0A; --fds-ok-mark:#10E8A0; --fds-ok-tint:#1D4138;
    --fds-wn:#FFBA0A; --fds-wn-ink:#0A0A0A; --fds-wn-mark:#FFBA0A; --fds-wn-tint:#443920;
    --fds-dg:#FF5C7A; --fds-dg-ink:#0A0A0A; --fds-dg-mark:#FF8FA3; --fds-dg-tint:#442A32;
    --fds-in:#A06BFF; --fds-in-ink:#0A0A0A; --fds-in-mark:#B98CFF; --fds-in-tint:#342D47;
    --fds-neutral:#E8EAED; --fds-neutral-ink:#202124; --fds-neutral-mark:#A6ACB2; --fds-neutral-tint:#35383C;
    --fds-focus:#7FA5F7; --fds-focus-soft:rgba(127,165,247,.22); --fds-scrim:rgba(13,14,16,.64);
    --fds-hover-wash:rgba(255,255,255,.05); --fds-press-wash:rgba(255,255,255,.085);
    --fds-skeleton:linear-gradient(90deg,#2A2C2F 0%,#35383C 40%,#2A2C2F 80%);
    --fds-e1:0 1px 2px rgba(0,0,0,.40);
    --fds-e2:0 6px 18px rgba(0,0,0,.50),0 1px 3px rgba(0,0,0,.40);
    --fds-e3:0 14px 34px rgba(0,0,0,.55),0 2px 6px rgba(0,0,0,.42);
    --fds-e4:0 28px 64px rgba(0,0,0,.66),0 4px 10px rgba(0,0,0,.46);
    --fds-e-drag:0 20px 40px rgba(0,0,0,.58),0 3px 8px rgba(0,0,0,.44);
    --fds-e-edge:inset 0 1px 0 rgba(255,255,255,.06);
  }
}


/* --- 4 · CONTRAST OVERLAY ------------------------------------------------
   High contrast converges fill and mark into one value per accent, because at
   7:1 there is no room left for a punchy fill and a legible label to be two
   different colours. High-contrast day is deliberately a quieter interface —
   that is the cost of AAA, not a fault in the tints.
   ------------------------------------------------------------------------*/
[data-contrast="high"][data-theme="day"], [data-contrast="high"]:not([data-theme="night"]){
  --fds-canvas:#FFFFFF; --fds-surface:#FFFFFF; --fds-raised:#FFFFFF;
  --fds-floating:#FFFFFF; --fds-page:#F1F3F5; --fds-sunk:#FFFFFF;
  --fds-border:var(--fdsx-g-550); --fds-border-control:var(--fdsx-g-550);
  --fds-hairline:var(--fdsx-g-300);
  --fds-text:#000000; --fds-text-muted:var(--fdsx-g-750); --fds-text-disabled:var(--fdsx-g-500);
  --fds-accent:var(--fdsx-c-900); --fds-accent-mark:var(--fdsx-c-900); --fds-accent-ink:#FFFFFF; --fds-accent-tint:var(--fdsx-c-tint-hc-d);
  --fds-volt:var(--fdsx-v-700); --fds-volt-mark:var(--fdsx-v-700); --fds-volt-ink:#FFFFFF; --fds-volt-tint:var(--fdsx-v-tint-hc-d);
  --fds-ok:var(--fdsx-ok-700); --fds-ok-mark:var(--fdsx-ok-700); --fds-ok-ink:#FFFFFF; --fds-ok-tint:var(--fdsx-ok-tint-hc-d);
  --fds-wn:var(--fdsx-wn-700); --fds-wn-mark:var(--fdsx-wn-700); --fds-wn-ink:#FFFFFF; --fds-wn-tint:var(--fdsx-wn-tint-hc-d);
  --fds-dg:var(--fdsx-dg-700); --fds-dg-mark:var(--fdsx-dg-700); --fds-dg-ink:#FFFFFF; --fds-dg-tint:var(--fdsx-dg-tint-hc-d);
  --fds-in:var(--fdsx-in-700); --fds-in-mark:var(--fdsx-in-700); --fds-in-ink:#FFFFFF; --fds-in-tint:var(--fdsx-in-tint-hc-d);
  --fds-neutral:#000000; --fds-neutral-ink:#FFFFFF; --fds-neutral-mark:var(--fdsx-g-750); --fds-neutral-tint:var(--fdsx-g-100);
  --fds-focus:var(--fdsx-fc-700);
  --fds-fill-sheen:none; --fds-fill-sheen-pressed:none;
}

[data-contrast="high"][data-theme="night"]{
  --fds-page:#000000; --fds-canvas:var(--fdsx-n-999); --fds-surface:var(--fdsx-n-950);
  --fds-raised:var(--fdsx-n-925); --fds-floating:var(--fdsx-n-925); --fds-sunk:#000000;
  --fds-border:var(--fdsx-n-500); --fds-border-control:var(--fdsx-n-150);
  --fds-hairline:var(--fdsx-n-600);
  --fds-text:#FFFFFF; --fds-text-muted:var(--fdsx-n-150); --fds-text-disabled:var(--fdsx-n-400);
  --fds-accent:var(--fdsx-c-100); --fds-accent-mark:var(--fdsx-c-100); --fds-accent-ink:#0A0A0A; --fds-accent-tint:var(--fdsx-c-tint-hc-n);
  --fds-volt:var(--fdsx-v-300); --fds-volt-mark:var(--fdsx-v-300); --fds-volt-ink:#0A0A0A; --fds-volt-tint:var(--fdsx-v-tint-hc-n);
  --fds-ok:var(--fdsx-ok-100); --fds-ok-mark:var(--fdsx-ok-100); --fds-ok-ink:#0A0A0A; --fds-ok-tint:var(--fdsx-ok-tint-hc-n);
  --fds-wn:var(--fdsx-wn-100); --fds-wn-mark:var(--fdsx-wn-100); --fds-wn-ink:#0A0A0A; --fds-wn-tint:var(--fdsx-wn-tint-hc-n);
  --fds-dg:var(--fdsx-dg-100); --fds-dg-mark:var(--fdsx-dg-100); --fds-dg-ink:#0A0A0A; --fds-dg-tint:var(--fdsx-dg-tint-hc-n);
  --fds-in:var(--fdsx-in-100); --fds-in-mark:var(--fdsx-in-100); --fds-in-ink:#0A0A0A; --fds-in-tint:var(--fdsx-in-tint-hc-n);
  --fds-neutral:#FFFFFF; --fds-neutral-ink:#000000; --fds-neutral-mark:var(--fdsx-n-150); --fds-neutral-tint:var(--fdsx-n-700);
  --fds-focus:var(--fdsx-fc-200);
  --fds-fill-sheen:none; --fds-fill-sheen-pressed:none;
}


/* --- 5 · DENSITY ---------------------------------------------------------
   DESK is a mouse and a chair. SHOP is gloves, an open bay door and a queue.

   Density drives control heights, row rhythm, the interface type scale and the
   mono width axis — a wider face on the shop floor, because the number is
   being read at arm's length.

   Heights are `max(rem, px)`. The rem term grows the control when someone has
   set larger text; the px term is the floor a gloved thumb needs regardless,
   and shrinking the text must never shrink a target under it.
   ------------------------------------------------------------------------*/
:root, [data-density="desk"]{
  --fds-h-sm:max(1.875rem,26px);  --fds-h-md:max(2.375rem,34px);
  --fds-h-lg:max(2.875rem,40px);  --fds-h-xl:max(3.5rem,48px);
  --fds-row-pad:var(--fds-sp-5); --fds-card-pad:var(--fds-sp-6);
  --fds-tap:max(2.125rem,32px); --fds-mono-wdth:87.5;

  --fds-ui-display:var(--fds-fs-1400);   /* 34 */
  --fds-ui-title:var(--fds-fs-1100);     /* 25 */
  --fds-ui-heading:var(--fds-fs-800);    /* 19 */
  --fds-ui-body-size:var(--fds-fs-500);  /* 15 */
  --fds-ui-small-size:var(--fds-fs-300);
  --fds-ui-caption-size:var(--fds-fs-200);
  --fds-ui-micro-size:var(--fds-fs-100);
}
[data-density="shop"]{
  --fds-h-sm:max(2.25rem,36px);   --fds-h-md:max(2.75rem,44px);
  --fds-h-lg:max(3.25rem,50px);   --fds-h-xl:max(3.875rem,58px);
  --fds-row-pad:var(--fds-sp-7); --fds-card-pad:1.125rem;
  --fds-tap:max(2.75rem,44px); --fds-mono-wdth:100;

  --fds-ui-display:var(--fds-fs-1500);   /* 38 */
  --fds-ui-title:var(--fds-fs-1200);     /* 27 */
  --fds-ui-heading:var(--fds-fs-900);    /* 21 */
  --fds-ui-body-size:var(--fds-fs-700);  /* 17 */
  --fds-ui-small-size:var(--fds-fs-400);
  --fds-ui-caption-size:var(--fds-fs-300);
  --fds-ui-micro-size:var(--fds-fs-200);
}


/* --- 6 · ELEVATION -------------------------------------------------------
   Five levels, and a rule for which one a thing gets: elevation answers "does
   this float above the page, and how far", not "how important is it".

     e0  resting     flat, hairline only. Cards, rows, panels.
     e1  hover       an interactive card under the pointer. Sticky headers.
     e2  floating    menus, popovers, autosuggest lists, tooltips.
     e3  overlay     toasts, drawers, sheets.
     e4  dialog      modals. The only level that also dims the page.
     drag            a thing currently under a finger or a cursor.

   Importance is carried by type, position and colour. A card does not become
   more urgent by rising 4px; it becomes harder to read against its neighbours.
   ------------------------------------------------------------------------*/
.fds-e0{box-shadow:none}
.fds-e1{box-shadow:var(--fds-e1),var(--fds-e-edge)}
.fds-e2{box-shadow:var(--fds-e2),var(--fds-e-edge);background:var(--fds-floating)}
.fds-e3{box-shadow:var(--fds-e3),var(--fds-e-edge);background:var(--fds-floating)}
.fds-e4{box-shadow:var(--fds-e4),var(--fds-e-edge);background:var(--fds-floating)}


/* --- 7 · BASE ------------------------------------------------------------
   FDS does not reset the page and never sets a font-size on `html`. It sets
   what it owns and leaves the rest, so it can be dropped into an Odoo view
   without fighting Odoo's own CSS.
   ------------------------------------------------------------------------*/
.fds *,.fds *::before,.fds *::after{box-sizing:border-box}
.fds{
  background:var(--fds-canvas);color:var(--fds-text);
  font-family:var(--fds-font-sans);
  font-size:var(--fds-ui-body-size);line-height:var(--fds-lh-body);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
}
.fds :focus-visible{
  outline:var(--fds-focus-w) solid var(--fds-focus);outline-offset:2px;
  border-radius:var(--fds-r-mark);
}
/* The `hidden` attribute means hidden. A component class that sets `display`
   outranks the UA rule for `[hidden]`, so the attribute is restated here at a
   specificity that survives it. */
.fds [hidden]{display:none}
@media (prefers-reduced-motion:reduce){
  .fds *,.fds *::before,.fds *::after{
    animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;scroll-behavior:auto !important;
  }
}


/* --- 8 · TYPOGRAPHY ------------------------------------------------------
   Three faces, three jobs, no overlap.

     Archivo        the interface. Every label, control and sentence.
     Martian Mono   quantities and identifiers — bays, counts, rack positions,
                    plates, addresses. Anything read back aloud or checked
                    character by character. Never a paragraph.
     Newsreader     the guarantee. Only where the product makes a promise about
                    the customer's records. It never labels a control.

   And three TIERS, because the same face at the same size does three different
   jobs in three different places. A tier is chosen once per surface, not per
   element. Mixing tiers on one page is the tell that nobody chose.

     .fds-mk-*   MARKETING. Someone who has not agreed to anything yet. Fluid,
                 large, tight tracking, short measure. Built to be scanned while
                 deciding whether to keep reading.

     .fds-wb-*   WEBSITE & CONTENT. Docs, onboarding, help, release notes.
                 Someone who has agreed and now needs to understand. Fixed,
                 comfortable, long measure, generous leading. Built to be read
                 for minutes at a time.

     .fds-ui-*   INTERFACE & TOOLS. Someone with a queue behind them. Density-
                 driven, tight leading, no measure cap because the container
                 already has one. Built to be found, not read.

   Every size is rem. Every measure is `ch` — a font-relative unit, so a line
   holds its length when the text scales. Every tracking value is `em`. There
   is no px in this section and there should never be.
   ------------------------------------------------------------------------*/

/* -- Marketing ---------------------------------------------------------- */
.fds-mk-hero{font-size:clamp(2.375rem,1.6rem + 3.6vw,4.5rem);font-weight:600;
  letter-spacing:var(--fds-tr-hero);line-height:var(--fds-lh-flat);max-width:18ch;margin:0}
.fds-mk-display{font-size:clamp(1.875rem,1.35rem + 2.4vw,2.875rem);font-weight:600;
  letter-spacing:-.026em;line-height:1.1;max-width:24ch;margin:0}
.fds-mk-h2{font-size:clamp(1.5rem,1.2rem + 1.4vw,2rem);font-weight:600;
  letter-spacing:-.022em;line-height:var(--fds-lh-tight);max-width:28ch;margin:0}
.fds-mk-h3{font-size:clamp(1.125rem,1.05rem + .35vw,1.3125rem);font-weight:500;
  letter-spacing:var(--fds-tr-heading);line-height:var(--fds-lh-snug);margin:0}
.fds-mk-lede{font-size:clamp(1rem,.95rem + .28vw,1.1875rem);line-height:1.62;
  color:var(--fds-text-muted);max-width:56ch;margin:0}
.fds-mk-body{font-size:var(--fds-fs-600);line-height:1.65;max-width:62ch;margin:0}
.fds-mk-kicker{font-size:var(--fds-fs-100);letter-spacing:.15em;text-transform:uppercase;
  color:var(--fds-accent-mark);margin:0}
.fds-mk-pull{font-family:var(--fds-font-serif);font-weight:300;
  font-size:clamp(1.375rem,1.1rem + 1.2vw,2rem);line-height:1.4;
  color:var(--fds-text-muted);max-width:26ch;margin:0}
.fds-mk-pull b,.fds-mk-pull strong{color:var(--fds-text);font-weight:400}

/* -- Website & content -------------------------------------------------- */
.fds-wb-h1{font-size:var(--fds-fs-1600);font-weight:600;letter-spacing:var(--fds-tr-display);
  line-height:var(--fds-lh-tight);margin:0}
.fds-wb-h2{font-size:var(--fds-fs-1200);font-weight:600;letter-spacing:-.018em;line-height:1.24;margin:0}
.fds-wb-h3{font-size:var(--fds-fs-900);font-weight:500;letter-spacing:-.01em;line-height:1.34;margin:0}
.fds-wb-h4{font-size:var(--fds-fs-600);font-weight:600;line-height:1.4;margin:0}
.fds-wb-body{font-size:var(--fds-fs-600);line-height:var(--fds-lh-prose);max-width:68ch;margin:0}
.fds-wb-small{font-size:var(--fds-fs-400);line-height:1.65;color:var(--fds-text-muted);max-width:68ch;margin:0}
.fds-wb-eyebrow{font-size:var(--fds-fs-200);letter-spacing:.1em;text-transform:uppercase;
  color:var(--fds-text-muted);margin:0}
.fds-wb-prose > * + *{margin-top:1em}
.fds-wb-prose h2{margin-top:1.9em}
.fds-wb-prose h3{margin-top:1.5em}
.fds-wb-prose li + li{margin-top:.4em}

/* -- Interface & tools --------------------------------------------------- */
.fds-ui-display{font-size:var(--fds-ui-display);font-weight:600;letter-spacing:-.022em;line-height:1.08;margin:0}
.fds-ui-title{font-size:var(--fds-ui-title);font-weight:600;letter-spacing:-.02em;line-height:var(--fds-lh-tight);margin:0}
.fds-ui-heading{font-size:var(--fds-ui-heading);font-weight:500;letter-spacing:-.008em;line-height:var(--fds-lh-snug);margin:0}
.fds-ui-body{font-size:var(--fds-ui-body-size);line-height:var(--fds-lh-body);margin:0}
.fds-ui-small{font-size:var(--fds-ui-small-size);line-height:1.5;margin:0}
.fds-ui-caption{font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted);line-height:1.5;margin:0}
.fds-ui-micro{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);line-height:1.55;margin:0}
.fds-ui-eyebrow{font-size:var(--fds-ui-micro-size);letter-spacing:var(--fds-tr-eyebrow);
  text-transform:uppercase;color:var(--fds-text-muted);margin:0}
.fds-ui-kicker{font-size:var(--fds-ui-micro-size);letter-spacing:var(--fds-tr-kicker);
  text-transform:uppercase;color:var(--fds-accent-mark);margin:0}

/* -- Cross-tier ---------------------------------------------------------- */
.fds-mono{font-family:var(--fds-font-mono);font-variation-settings:'wdth' var(--fds-mono-wdth);
  font-variant-numeric:tabular-nums;letter-spacing:-.01em}
.fds-num{font-family:var(--fds-font-mono);font-variation-settings:'wdth' var(--fds-mono-wdth);
  font-variant-numeric:tabular-nums;font-weight:500;letter-spacing:-.02em}
.fds-promise{font-family:var(--fds-font-serif);font-weight:300;
  font-size:calc(var(--fds-ui-body-size) + .125rem);line-height:1.5;
  color:var(--fds-text-muted);max-width:44ch;margin:0}
.fds-promise b,.fds-promise strong{color:var(--fds-text);font-weight:400}
.fds-muted{color:var(--fds-text-muted)}
.fds-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}


/* --- 9 · ICONS -----------------------------------------------------------
   Library: TABLER ICONS. MIT, 6,184 icons, one 24×24 grid, one 2px stroke, one
   author. The only free set simultaneously big enough never to need a second
   source and consistent enough that a rare icon does not look imported. No
   attribution required, commercial use unrestricted.

   Second choice is Lucide (ISC, ~1,600) — cleaner drawing, too small a set:
   the gaps land exactly on trade vocabulary, which is where an off-style icon
   costs the most.

   Rules:
     · Stroke is set here at 1.8, not Tabler's 2. Tabler is drawn against a
       heavier UI face; 1.8 sits correctly beside Archivo 400.
     · Size comes from the tokens, never from the SVG's own width/height, and
       the tokens are rem — an icon is a piece of type and grows with it.
     · An icon is decorative until proven otherwise: aria-hidden="true" by
       default, and every icon-only control carries an aria-label.
     · Six glyphs the library lacks are drawn in-house in `icons/domain/`:
       tire, rim, rack bay, tread depth, VIN plate, hand-off. Drawn on Tabler's
       grid at Tabler's stroke so nobody can tell which came from where.
   ------------------------------------------------------------------------*/
.fds-ic{width:var(--fds-ic-md);height:var(--fds-ic-md);flex-shrink:0;
  stroke-width:var(--fds-ic-stroke);fill:none;stroke:currentColor;
  stroke-linecap:round;stroke-linejoin:round;display:block}
.fds-ic--xs{width:var(--fds-ic-xs);height:var(--fds-ic-xs);stroke-width:var(--fds-ic-stroke-xs)}
.fds-ic--sm{width:var(--fds-ic-sm);height:var(--fds-ic-sm);stroke-width:var(--fds-ic-stroke-sm)}
.fds-ic--lg{width:var(--fds-ic-lg);height:var(--fds-ic-lg);stroke-width:var(--fds-ic-stroke-lg)}
.fds-ic--xl{width:var(--fds-ic-xl);height:var(--fds-ic-xl);stroke-width:var(--fds-ic-stroke-xl)}
/* An icon inside a control belongs to that control's type, not to the page's,
   so it is sized in `em` off the label and takes no size token at all. */
.fds-btn .fds-ic,.fds-chip .fds-ic{width:1.15em;height:1.15em;stroke-width:1.9}
.fds-dot{width:.5rem;height:.5rem;border-radius:50%;background:currentColor;
  flex-shrink:0;display:inline-block}

/* --- 10 · COMPOSITION ----------------------------------------------------
   The rules that decide where a thing sits, how far it is from its neighbour,
   and what happens to it when the window changes. Components below assume
   these; a screen that ignores them will look wrong while every component in
   it is correct.

   ── ALIGNMENT ───────────────────────────────────────────────────────────
   LEFT is the default and needs no justification. A left edge is a ruler the
   eye returns to on every line; the rag belongs on the right where it costs
   nothing.

   CENTRE is allowed only when all three are true:
     1. the block is short — a heading of one or two lines, three items or fewer
     2. it is symmetrical around a focal object — an empty state, a success
        seal, a confirmation, a single stat
     3. nothing continues below it in the same column
   A centred paragraph over three lines has two ragged edges and costs a
   re-scan on every line. Centre a heading over a centred illustration; do not
   centre the body underneath it out of politeness to the heading.

   RIGHT is for two things and nothing else:
     · numbers that are compared down a column, so their units line up
     · the primary action in a footer, because reading order should end on it
   Right-aligned prose does not exist here. Right-aligned labels against left
   values do not either — the label column becomes the rag and the eye loses
   the ruler.

   IN A ROW: label left, value right. The label edge is the ruler; the value
   edge is the comparison. In a card, one alignment per card — a card with a
   centred heading over a left body reads as two cards that failed to separate.

   ICON + TEXT: the icon aligns to the first line's optical centre, never to
   the centre of a multi-line block. `align-items:flex-start` with a small
   top offset, not `center`, whenever the text can wrap.

   ── GAP VERSUS PADDING ──────────────────────────────────────────────────
   PADDING is the distance from a container's edge to its content, and belongs
   to the container. GAP is the distance between siblings, and belongs to the
   parent layout. Children never carry margins for layout — a child that owns
   its own outside spacing cannot be reused anywhere else.

   THE ONE RULE THAT MATTERS: gap must be smaller than the padding enclosing
   it, by at least one step. When gap ≥ padding, the eye stops reading the
   group as one card's contents and starts reading it as separate blocks that
   happen to share a border.

   Grouping is done by proximity, not by rules and boxes. Items inside a group
   take one step less gap than the space between groups. If you have reached
   for a divider, first try increasing the gap by one step.

   Working rhythm:
     within a block      sp-4  (8)   ·  sp-5 (12)
     between blocks      sp-8  (24)  ·  sp-9 (32)
     between sections    sp-11 (48)  ·  sp-13 (80)

   ── PADDING BY CONTAINER ────────────────────────────────────────────────
   Padding scales with the container's radius, not with its content. A 12px
   corner wants ~16px of padding; give it 8 and the corner eats the content,
   give it 32 and the corner disappears.

     chip     3px / 9px      fixed — it is a label, not a container
     control  derived        horizontal ≈ 0.55 × height, floor sp-5
     card     --fds-card-pad 16 desk / 18 shop
     panel    sp-7           a container that holds cards
     sheet    sp-6           plus the safe-area inset

   ── RESIZING ────────────────────────────────────────────────────────────
   Containers grow; content reflows; TEXT NEVER SCALES TO FIT. If it does not
   fit, it wraps or it truncates. Shrinking type below its tier step to win an
   argument with a container is how a system stops being one.

   Width is content-driven up to a measure cap. Prose caps at 68ch (website)
   and 56ch (marketing lede); the interface tier has no cap because its
   container already has one. `ch` and not px, so the cap survives text zoom.

   Grids are `repeat(auto-fit, minmax(<min>, 1fr))`. Fixed column counts per
   breakpoint assume a device catalogue; a shop tablet held in portrait is not
   a phone and will get the phone layout every time.

   `min-width:0` on any flex child containing text. Without it, truncation
   silently does nothing and the row grows instead.

   Buttons hug their content. They fill the width only in a sheet footer, a
   form footer, or when stacked one-per-row on a narrow screen.

   Truncation: one line with an ellipsis for identifiers; two-line clamp for
   descriptions; NEVER truncate a number, a name, or a rack position — anything
   a person has to read back gets wrapped instead, however ugly.

   Media takes an `aspect-ratio`, never a fixed height.

   ── CORNERS IN COMPOSITION ──────────────────────────────────────────────
   Inner radius = outer radius − the gap between them. A 12px card with 16px
   of padding holds an inner element at 12 − 16 → 0, i.e. square: that is
   correct, and it is why a field inside a tight card should be square-cornered
   rather than "9px because it is a field".

   A radius never exceeds half the shortest side. Past that it is a pill and
   should be declared as one.

   Anything reaching a screen edge loses its radius on that edge only.
   ------------------------------------------------------------------------*/

/* Layout primitives. These are the only spacing mechanisms components use. */
.fds-stack{display:flex;flex-direction:column;gap:var(--fds-sp-5)}
.fds-stack--tight{gap:var(--fds-sp-4)}
.fds-stack--loose{gap:var(--fds-sp-8)}
.fds-cluster{display:flex;flex-wrap:wrap;align-items:center;gap:var(--fds-sp-4)}
.fds-cluster--end{justify-content:flex-end}
.fds-cluster--between{justify-content:space-between;width:100%}
.fds-grid{display:grid;gap:var(--fds-sp-6);
  grid-template-columns:repeat(auto-fit,minmax(min(var(--fds-col-min,16rem),100%),1fr))}
.fds-sidebar{display:flex;flex-wrap:wrap;gap:var(--fds-sp-8)}
.fds-sidebar > :first-child{flex-basis:var(--fds-side-w,18rem);flex-grow:1}
.fds-sidebar > :last-child{flex-basis:0;flex-grow:999;min-width:min(60%,32rem)}

.fds-measure{max-width:68ch}
.fds-measure--short{max-width:56ch}
.fds-measure--label{max-width:44ch}
.fds-truncate{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fds-clamp-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.fds-numcol{text-align:right;font-variant-numeric:tabular-nums}
.fds-min0{min-width:0}
/* Icon beside text that can wrap: align to the first line, not the block. */
.fds-lead{display:flex;align-items:flex-start;gap:var(--fds-sp-5)}
.fds-lead > .fds-ic{margin-top:.15em}
.fds-center{text-align:center;display:flex;flex-direction:column;align-items:center;
  gap:var(--fds-sp-5);max-width:44ch;margin-inline:auto}


/* --- 11 · BUTTONS --------------------------------------------------------
   Three variants × four tones × four sizes, and one rule about how many of
   each may appear together: ONE solid button per view. Solid is "the thing
   this screen is for". Two solids on one screen means the screen has two
   purposes and should be two screens.

     solid    the action the screen exists for
     outline  a real alternative to the solid one
     text     a way out, a secondary path, anything in a row of many

   Tones: accent (default), neutral, danger, volt. Volt is emphasis and is
   never the primary action — it marks something as new or notable, and a
   person should not be pressing "notable".

   Sizes map to the control heights, and the radius moves with the height so
   the optical curvature stays constant. An icon-only button is square at its
   size and always carries an aria-label.

   A control says exactly what happens when it is used, and keeps the same name
   for the whole flow: the button that says "Give it back" produces a row that
   says "Given back".
   ------------------------------------------------------------------------*/
.fds-btn{
  --_h:var(--fds-h-md); --_r:var(--fds-r-ctl-md);
  --_fill:var(--fds-accent); --_ink:var(--fds-accent-ink); --_mark:var(--fds-accent-mark);
  display:inline-flex;align-items:center;justify-content:center;gap:var(--fds-sp-4);
  height:var(--_h);min-height:var(--_h);padding-inline:calc(var(--_h) * .55);
  font-family:var(--fds-font-sans);font-size:var(--fds-ui-body-size);font-weight:500;
  line-height:1.2;white-space:nowrap;text-decoration:none;cursor:pointer;
  border:var(--fds-border-w) solid transparent;border-radius:var(--_r);
  background-color:var(--_fill);background-image:var(--fds-fill-sheen);color:var(--_ink);
  transition:transform var(--fds-dur-fast) var(--fds-ease-standard),
             filter var(--fds-dur-fast) var(--fds-ease-standard),
             background-color var(--fds-dur-fast) var(--fds-ease-standard),
             border-color var(--fds-dur-fast) var(--fds-ease-standard);
}
.fds-btn:hover{filter:brightness(1.06)}
/* The pressed state flips the specular line from the top edge to the bottom,
   which is what a physical key does. The flat token stays the worst case in
   both gradients, so the measured label ratio holds pressed and unpressed. */
.fds-btn:active{transform:translateY(var(--fds-depress)) scale(.985);
  background-image:var(--fds-fill-sheen-pressed);filter:brightness(.93)}
.fds-btn:disabled,.fds-btn[aria-disabled="true"]{opacity:.42;cursor:not-allowed;
  transform:none;filter:none;pointer-events:none}

.fds-btn--sm{--_h:var(--fds-h-sm);--_r:var(--fds-r-ctl-sm);font-size:var(--fds-ui-small-size)}
.fds-btn--md{--_h:var(--fds-h-md);--_r:var(--fds-r-ctl-md)}
.fds-btn--lg{--_h:var(--fds-h-lg);--_r:var(--fds-r-ctl-lg);font-size:var(--fds-fs-600)}
.fds-btn--xl{--_h:var(--fds-h-xl);--_r:var(--fds-r-ctl-xl);font-size:var(--fds-fs-700)}

.fds-btn--neutral{--_fill:var(--fds-neutral);--_ink:var(--fds-neutral-ink);--_mark:var(--fds-neutral-mark)}
.fds-btn--danger{--_fill:var(--fds-dg);--_ink:var(--fds-dg-ink);--_mark:var(--fds-dg-mark)}
.fds-btn--volt{--_fill:var(--fds-volt);--_ink:var(--fds-volt-ink);--_mark:var(--fds-volt-mark)}

.fds-btn--outline{background-color:transparent;background-image:none;
  color:var(--_mark);border-color:var(--fds-border-control)}
.fds-btn--outline:hover{background-color:var(--fds-hover-wash);filter:none;
  border-color:var(--_mark)}
.fds-btn--outline:active{background-color:var(--fds-press-wash);filter:none}

.fds-btn--text{background-color:transparent;background-image:none;border-color:transparent;
  color:var(--_mark);padding-inline:var(--fds-sp-4)}
.fds-btn--text:hover{background-color:var(--fds-hover-wash);filter:none}
.fds-btn--text:active{background-color:var(--fds-press-wash);filter:none}

.fds-btn--icon{padding-inline:0;width:var(--_h);aspect-ratio:1}
.fds-btn--block{display:flex;width:100%}
.fds-btn .fds-ic{width:1.15em;height:1.15em}

/* Loading. The label stays in place and keeps its width so the button does not
   resize under the finger that just pressed it. */
.fds-btn[data-loading="true"]{pointer-events:none;position:relative;color:transparent}
.fds-btn[data-loading="true"]::after{
  content:'';position:absolute;width:1.1em;height:1.1em;border-radius:50%;
  border:2px solid currentColor;border-top-color:transparent;color:var(--_ink);
  animation:fds-spin .7s linear infinite}
.fds-btn--outline[data-loading="true"]::after,.fds-btn--text[data-loading="true"]::after{color:var(--_mark)}

/* A group of equals. Radius collapses between members so the group reads as
   one control with several answers. */
.fds-btngroup{display:inline-flex}
.fds-btngroup > .fds-btn{border-radius:0;margin-inline-start:calc(var(--fds-border-w) * -1)}
.fds-btngroup > .fds-btn:first-child{border-start-start-radius:var(--fds-r-ctl-md);
  border-end-start-radius:var(--fds-r-ctl-md);margin-inline-start:0}
.fds-btngroup > .fds-btn:last-child{border-start-end-radius:var(--fds-r-ctl-md);
  border-end-end-radius:var(--fds-r-ctl-md)}
.fds-btngroup > .fds-btn:focus-visible{z-index:1}


/* --- 12 · FORM FIELDS ----------------------------------------------------
   One wrapper, one label, one hint, one error, and a control. The wrapper owns
   the spacing so no field ever carries its own margin.

   Label above, always. Placeholder is not a label: it disappears exactly when
   the person needs it, and it fails every test of a form filled in twice.

   Hint text sits under the control and describes the rule before it is broken.
   Error text replaces it and describes what happened and how to fix it — never
   "invalid input", which tells a person nothing they did not already suspect.

   Required is marked on the label. Optional is marked when most fields are
   required. Marking both is noise.
   ------------------------------------------------------------------------*/
.fds-field{display:block;margin:0}
.fds-field + .fds-field{margin-top:var(--fds-sp-6)}
.fds-label{display:block;font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted);
  margin:0 0 var(--fds-sp-3) 2px;font-weight:400}
.fds-label__req{color:var(--fds-dg-mark);margin-inline-start:2px}
.fds-hint{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);line-height:1.5;
  margin:var(--fds-sp-3) 2px 0;max-width:60ch}
.fds-error{display:flex;align-items:flex-start;gap:var(--fds-sp-3);
  font-size:var(--fds-ui-micro-size);color:var(--fds-dg-mark);line-height:1.5;
  margin:var(--fds-sp-3) 2px 0;max-width:60ch}

/* Shared control skin. Every text-like control wears exactly this. */
.fds-input,.fds-textarea,.fds-select,.fds-datefield{
  --_h:var(--fds-h-md);
  width:100%;min-height:var(--_h);
  background:var(--fds-sunk);color:var(--fds-text);
  border:var(--fds-border-w) solid var(--fds-border-control);
  border-radius:var(--fds-r-field);
  font-family:var(--fds-font-sans);
  /* max(1rem, 16px): below 16 rendered px, iOS zooms the page on focus and the
     person loses their place mid-form. The rem term still lets it grow. */
  font-size:max(var(--fds-ui-body-size),16px);
  padding:0 var(--fds-sp-5);
  transition:border-color var(--fds-dur-fast) var(--fds-ease-standard),
             box-shadow var(--fds-dur-fast) var(--fds-ease-standard);
}
.fds-textarea{padding:var(--fds-sp-5);min-height:6.5rem;resize:vertical;line-height:1.55}
.fds-input:focus,.fds-textarea:focus,.fds-select:focus,.fds-datefield:focus-within{
  outline:0;border-color:var(--fds-accent-mark);box-shadow:0 0 0 3px var(--fds-focus-soft)}
.fds-input:disabled,.fds-textarea:disabled,.fds-select:disabled{
  color:var(--fds-text-disabled);background:var(--fds-surface);cursor:not-allowed}
.fds-input::placeholder,.fds-textarea::placeholder{color:var(--fds-text-disabled)}
.fds-input[aria-invalid="true"],.fds-textarea[aria-invalid="true"],.fds-select[aria-invalid="true"]{
  border-color:var(--fds-dg-mark)}
.fds-input--mono{font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-variant-numeric:tabular-nums}
.fds-input--sm,.fds-select--sm{--_h:var(--fds-h-sm);font-size:max(var(--fds-ui-small-size),16px)}
.fds-input--lg,.fds-select--lg{--_h:var(--fds-h-lg)}
/* A number is compared down a column, so it is right-aligned and tabular. */
.fds-input--num{text-align:right;font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-variant-numeric:tabular-nums;
  -moz-appearance:textfield}
.fds-input--num::-webkit-outer-spin-button,.fds-input--num::-webkit-inner-spin-button{
  -webkit-appearance:none;margin:0}

/* Adornments: a prefix, a suffix, or a control living inside the field. */
.fds-inputgroup{position:relative;display:flex;align-items:center}
.fds-inputgroup > .fds-input{padding-inline-start:var(--_pad-s,var(--fds-sp-5));
  padding-inline-end:var(--_pad-e,var(--fds-sp-5))}
.fds-inputgroup--lead{--_pad-s:2.5rem}
.fds-inputgroup--trail{--_pad-e:2.75rem}
.fds-inputgroup__lead,.fds-inputgroup__trail{position:absolute;display:flex;align-items:center;
  color:var(--fds-text-muted);pointer-events:none}
.fds-inputgroup__lead{inset-inline-start:var(--fds-sp-5)}
.fds-inputgroup__trail{inset-inline-end:var(--fds-sp-4);pointer-events:auto}
.fds-inputgroup__btn{display:flex;align-items:center;justify-content:center;
  width:2rem;height:2rem;border:0;background:none;border-radius:var(--fds-r-ctl-sm);
  color:var(--fds-text-muted);cursor:pointer}
.fds-inputgroup__btn:hover{background:var(--fds-hover-wash);color:var(--fds-text)}

/* Number stepper. Buttons flank the field at shop density where a caret is not
   a realistic target through a glove. */
.fds-stepper{display:inline-flex;align-items:center;gap:var(--fds-sp-4)}
.fds-stepper .fds-input{width:5.5rem}
.fds-stepper__big{display:flex;align-items:center;justify-content:center;gap:var(--fds-sp-8)}
.fds-stepper__big button{width:3.6rem;height:3.6rem;border-radius:50%;
  border:var(--fds-border-w) solid var(--fds-border-control);background:var(--fds-surface);
  color:var(--fds-text);font-family:inherit;font-size:1.6rem;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  transition:transform var(--fds-dur-fast) var(--fds-ease-standard),background-color var(--fds-dur-fast)}
.fds-stepper__big button:active{transform:scale(.92);background:var(--fds-raised)}
.fds-stepper__big button:disabled{opacity:.32;cursor:default;transform:none}
.fds-stepper__value{font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-variant-numeric:tabular-nums;
  font-size:var(--fds-fs-1500);font-weight:500;letter-spacing:-.03em;min-width:4.5rem;
  text-align:center;line-height:1;transition:transform var(--fds-dur-slow) var(--fds-ease-entrance)}
.fds-stepper__value.is-bumped{transform:scale(1.09)}

/* Native select. Kept native wherever the option list is short and static —
   the platform's own picker beats anything rebuilt, especially on a phone. */
.fds-select{appearance:none;padding-inline-end:2.25rem;cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),
                   linear-gradient(135deg,currentColor 50%,transparent 50%);
  background-position:calc(100% - 1.05rem) 55%,calc(100% - .68rem) 55%;
  background-size:.375rem .375rem,.375rem .375rem;background-repeat:no-repeat}

/* Combobox / autosuggest. Used only when the list is long enough that typing
   beats scrolling — a shop, a customer, a make. Filtering is `contains`, not
   `startsWith`: people type the distinctive part of a name, not its first
   letters. */
.fds-combobox{position:relative}
.fds-listbox{position:absolute;inset-inline:0;top:calc(100% + var(--fds-sp-2));
  z-index:var(--fds-z-dropdown);max-height:16rem;overflow-y:auto;
  background:var(--fds-floating);border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-card);box-shadow:var(--fds-e2),var(--fds-e-edge);
  padding:var(--fds-sp-2);margin:0;list-style:none;
  transform-origin:top;animation:fds-pop var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-listbox[hidden]{display:none}
.fds-option{display:flex;align-items:center;gap:var(--fds-sp-4);
  padding:var(--fds-sp-4) var(--fds-sp-5);border-radius:var(--fds-r-ctl-sm);
  font-size:var(--fds-ui-body-size);cursor:pointer;min-height:var(--fds-tap)}
.fds-option[aria-selected="true"],.fds-option.is-active{background:var(--fds-accent-tint);
  color:var(--fds-accent-mark)}
.fds-option__sub{margin-inline-start:auto;font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted)}
.fds-option mark{background:none;color:inherit;font-weight:600}
.fds-listbox__empty{padding:var(--fds-sp-5);font-size:var(--fds-ui-small-size);
  color:var(--fds-text-muted)}

/* Radio and checkbox. Both are drawn rather than native, because the native
   control cannot take a 3:1 border in both themes — but both keep a real input
   underneath so the form, the keyboard and the screen reader all still work.

   Radio: mutually exclusive, always visible, 2–5 options. Six or more is a
   select. Checkbox: independent choices. If unchecking one changes another,
   it was a radio. */
.fds-choice{display:flex;align-items:flex-start;gap:var(--fds-sp-5);
  padding:var(--fds-sp-4) 0;min-height:var(--fds-tap);cursor:pointer}
.fds-choice input{position:absolute;opacity:0;width:1px;height:1px}
.fds-choice__box{position:relative;flex-shrink:0;width:1.25rem;height:1.25rem;
  margin-top:.1em;border:var(--fds-border-thick) solid var(--fds-border-control);
  background:var(--fds-sunk);
  transition:border-color var(--fds-dur-fast),background-color var(--fds-dur-fast)}
.fds-choice--radio .fds-choice__box{border-radius:50%}
.fds-choice--check .fds-choice__box{border-radius:var(--fds-r-mark)}
.fds-choice input:checked + .fds-choice__box{background:var(--fds-accent);
  border-color:var(--fds-accent)}
.fds-choice--radio input:checked + .fds-choice__box::after{content:'';position:absolute;
  inset:.25rem;border-radius:50%;background:var(--fds-accent-ink)}
.fds-choice--check input:checked + .fds-choice__box::after{content:'';position:absolute;
  inset-inline-start:.34rem;top:.1rem;width:.34rem;height:.62rem;
  border:solid var(--fds-accent-ink);border-width:0 2px 2px 0;transform:rotate(42deg)}
.fds-choice input:indeterminate + .fds-choice__box{background:var(--fds-accent);border-color:var(--fds-accent)}
.fds-choice input:indeterminate + .fds-choice__box::after{content:'';position:absolute;
  inset-inline:.22rem;top:.47rem;height:2px;background:var(--fds-accent-ink)}
.fds-choice input:focus-visible + .fds-choice__box{outline:var(--fds-focus-w) solid var(--fds-focus);
  outline-offset:2px}
.fds-choice input:disabled ~ *{opacity:.45}
.fds-choice__text{min-width:0}
.fds-choice__label{font-size:var(--fds-ui-body-size);line-height:1.35}
.fds-choice__hint{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);margin-top:2px}

/* Switch. A switch takes effect immediately and needs no Save. If the change
   only lands when a form is submitted, it is a checkbox — the difference is
   not decorative and people rely on it. */
.fds-switch{display:flex;align-items:center;gap:var(--fds-sp-5);cursor:pointer;
  min-height:var(--fds-tap)}
.fds-switch input{position:absolute;opacity:0;width:1px;height:1px}
.fds-switch__track{position:relative;flex-shrink:0;width:2.75rem;height:1.5rem;
  border-radius:var(--fds-r-pill);background:var(--fds-surface);
  border:var(--fds-border-w) solid var(--fds-border-control);
  transition:background-color var(--fds-dur-base) var(--fds-ease-standard),
             border-color var(--fds-dur-base) var(--fds-ease-standard)}
.fds-switch__track::after{content:'';position:absolute;top:50%;inset-inline-start:.15rem;
  width:1.05rem;height:1.05rem;border-radius:50%;background:var(--fds-text-muted);
  transform:translateY(-50%);
  transition:transform var(--fds-dur-base) var(--fds-ease-entrance),background-color var(--fds-dur-base)}
.fds-switch input:checked + .fds-switch__track{background:var(--fds-accent);border-color:var(--fds-accent)}
.fds-switch input:checked + .fds-switch__track::after{transform:translateY(-50%) translateX(1.2rem);
  background:var(--fds-accent-ink)}
.fds-switch input:focus-visible + .fds-switch__track{outline:var(--fds-focus-w) solid var(--fds-focus);
  outline-offset:2px}

/* Toggle group / segmented control. The axis is named above it, never inside
   it. Two to four options; more than four is a select. */
.fds-seg{display:inline-flex;background:var(--fds-sunk);padding:3px;gap:2px;
  border:var(--fds-border-w) solid var(--fds-border);border-radius:var(--fds-r-pill)}
.fds-seg--block{display:flex;width:100%}
.fds-seg button{flex:1;border:0;background:transparent;color:var(--fds-text-muted);
  font-family:inherit;font-size:var(--fds-ui-caption-size);
  padding:var(--fds-sp-4) var(--fds-sp-5);min-height:calc(var(--fds-tap) - 6px);
  border-radius:var(--fds-r-pill);cursor:pointer;white-space:nowrap;
  transition:background-color var(--fds-dur-base) var(--fds-ease-standard),
             color var(--fds-dur-base) var(--fds-ease-standard)}
.fds-seg button[aria-pressed="true"],.fds-seg button[aria-selected="true"]{
  background:var(--fds-border);color:var(--fds-text);font-weight:500}

/* Date field and calendar. The text input is the primary path — someone who
   knows the date types it faster than any picker. The calendar is for choosing
   rather than entering: "the Tuesday after the long weekend". */
.fds-datefield{display:flex;align-items:center;gap:var(--fds-sp-4);cursor:text}
.fds-datefield input{flex:1;min-width:0;border:0;background:none;color:inherit;
  font:inherit;padding:0;outline:0}
.fds-datefield__btn{display:flex;align-items:center;justify-content:center;
  width:2rem;height:2rem;margin-inline-end:-.35rem;border:0;background:none;
  border-radius:var(--fds-r-ctl-sm);color:var(--fds-text-muted);cursor:pointer}
.fds-datefield__btn:hover{background:var(--fds-hover-wash);color:var(--fds-text)}
.fds-calendar{position:absolute;z-index:var(--fds-z-dropdown);width:19rem;
  background:var(--fds-floating);border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-card);box-shadow:var(--fds-e2),var(--fds-e-edge);
  padding:var(--fds-sp-5);margin-top:var(--fds-sp-2);
  animation:fds-pop var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-calendar[hidden]{display:none}
.fds-calendar__head{display:flex;align-items:center;justify-content:space-between;
  margin-bottom:var(--fds-sp-5)}
.fds-calendar__month{font-size:var(--fds-ui-body-size);font-weight:500}
.fds-calendar__grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}
.fds-calendar__dow{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);
  text-align:center;padding-bottom:var(--fds-sp-3)}
.fds-calendar__day{aspect-ratio:1;display:flex;align-items:center;justify-content:center;
  border:0;background:none;color:var(--fds-text);font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' 75;font-size:var(--fds-ui-small-size);
  border-radius:var(--fds-r-ctl-sm);cursor:pointer;min-height:2rem}
.fds-calendar__day:hover{background:var(--fds-hover-wash)}
.fds-calendar__day[data-outside]{color:var(--fds-text-disabled)}
.fds-calendar__day[data-today]{box-shadow:inset 0 0 0 1px var(--fds-border-control)}
.fds-calendar__day[aria-selected="true"]{background:var(--fds-accent);color:var(--fds-accent-ink);
  font-weight:600}
.fds-calendar__day:disabled{opacity:.3;cursor:not-allowed}

/* File upload. The drop zone is the affordance; the button inside it is the
   path for everyone not using a mouse. Both hit the same input. */
.fds-drop{position:relative;display:flex;flex-direction:column;align-items:center;
  gap:var(--fds-sp-5);text-align:center;padding:var(--fds-sp-9) var(--fds-sp-6);
  border:2px dashed var(--fds-border-control);border-radius:var(--fds-r-card);
  background:var(--fds-sunk);color:var(--fds-text-muted);
  transition:border-color var(--fds-dur-fast),background-color var(--fds-dur-fast)}
.fds-drop input[type=file]{position:absolute;inset:0;opacity:0;cursor:pointer}
.fds-drop.is-over{border-color:var(--fds-accent-mark);background:var(--fds-accent-tint);
  color:var(--fds-accent-mark)}
.fds-drop__title{font-size:var(--fds-ui-body-size);color:var(--fds-text);font-weight:500}
.fds-file{display:flex;align-items:center;gap:var(--fds-sp-5);padding:var(--fds-sp-5);
  border:var(--fds-border-w) solid var(--fds-border);border-radius:var(--fds-r-ctl-md);
  background:var(--fds-raised)}
.fds-file__name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size:var(--fds-ui-small-size)}
.fds-file__size{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);
  font-family:var(--fds-font-mono);font-variation-settings:'wdth' 75}


/* --- 13 · CARDS ----------------------------------------------------------
   A card is flat and carries a hairline. Not a shadow — a shadow implies the
   card floats, and nothing in a records product floats until a finger picks it
   up.

   A card holds one subject. Two subjects means two cards; a card that needs a
   divider through the middle was two cards that got stapled.

   Padding is --fds-card-pad and the internal gap is one step less, so the
   contents read as one group. Anything inside takes an inner radius of
   card-radius minus the padding, which at 12px/16px is square.
   ------------------------------------------------------------------------*/
.fds-card{background:var(--fds-raised);border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-card);padding:var(--fds-card-pad);
  transition:padding var(--fds-dur-slow) var(--fds-ease-standard),
             box-shadow var(--fds-dur-base) var(--fds-ease-standard),
             transform var(--fds-dur-base) var(--fds-ease-standard)}
.fds-card--quiet{background:var(--fds-surface)}
.fds-card--rows{padding-block:2px}
.fds-card--flush{padding:0;overflow:hidden}
.fds-card--interactive{cursor:pointer}
.fds-card--interactive:hover{box-shadow:var(--fds-e1),var(--fds-e-edge);
  border-color:var(--fds-border-control)}
.fds-card--interactive:active{transform:translateY(var(--fds-depress))}
.fds-card__head{display:flex;align-items:center;gap:var(--fds-sp-5);
  margin-bottom:var(--fds-sp-5)}
.fds-card__title{font-size:var(--fds-ui-heading);font-weight:500;flex:1;min-width:0}
.fds-card__foot{display:flex;align-items:center;justify-content:flex-end;
  gap:var(--fds-sp-4);margin-top:var(--fds-sp-6);padding-top:var(--fds-sp-5);
  border-top:var(--fds-border-w) solid var(--fds-hairline)}

.fds-panel{background:var(--fds-canvas);border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-panel);padding:var(--fds-sp-7)}
.fds-sheet{background:var(--fds-canvas);border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-sheet);padding:var(--fds-sp-6)}

.fds-row{display:flex;align-items:center;gap:var(--fds-sp-5);
  padding:var(--fds-row-pad) 0;min-height:var(--fds-tap);
  border-bottom:var(--fds-border-w) solid var(--fds-hairline);
  transition:padding var(--fds-dur-slow) var(--fds-ease-standard)}
.fds-row:last-child{border-bottom:0}
.fds-row__sp{flex:1;min-width:0}
.fds-row__name{font-size:var(--fds-ui-body-size);font-weight:500}
.fds-row__sub{font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted);margin-top:1px}
.fds-press{cursor:pointer;border-radius:var(--fds-r-ctl-md);
  transition:transform var(--fds-dur-fast) var(--fds-ease-standard),
             background-color var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-press:hover{background:var(--fds-hover-wash)}
.fds-press:active{transform:scale(.985);background:var(--fds-press-wash)}


/* --- 14 · CHIPS, BADGES, BANNERS -----------------------------------------
   A chip states a fact. A banner states a fact and what happens next. Neither
   is ever the only carrier of that fact: remove the colour and the sentence
   still reads.
   ------------------------------------------------------------------------*/
.fds-chip{display:inline-flex;align-items:center;gap:var(--fds-sp-3);
  font-size:var(--fds-ui-micro-size);font-weight:500;line-height:1.4;
  padding:3px 9px;border-radius:var(--fds-r-pill);white-space:nowrap;
  background:var(--fds-surface);color:var(--fds-text-muted)}
.fds-chip--accent{background:var(--fds-accent-tint);color:var(--fds-accent-mark)}
.fds-chip--ok{background:var(--fds-ok-tint);color:var(--fds-ok-mark)}
.fds-chip--wn{background:var(--fds-wn-tint);color:var(--fds-wn-mark)}
.fds-chip--dg{background:var(--fds-dg-tint);color:var(--fds-dg-mark)}
.fds-chip--info{background:var(--fds-in-tint);color:var(--fds-in-mark)}
.fds-chip--volt{background:var(--fds-volt);color:var(--fds-volt-ink)}
.fds-chip--solid{background:var(--fds-accent);color:var(--fds-accent-ink)}
.fds-badge{display:inline-flex;align-items:center;justify-content:center;
  min-width:1.25rem;height:1.25rem;padding:0 .35rem;border-radius:var(--fds-r-pill);
  background:var(--fds-dg);color:var(--fds-dg-ink);font-size:var(--fds-fs-100);
  font-weight:600;font-variant-numeric:tabular-nums}

.fds-banner{display:flex;gap:var(--fds-sp-5);align-items:flex-start;
  border-radius:var(--fds-r-card);padding:var(--fds-sp-5) var(--fds-sp-6);
  border:var(--fds-border-w) solid transparent;
  background:var(--fds-surface);color:var(--fds-text)}
.fds-banner > .fds-ic{margin-top:.1em}
.fds-banner__title{font-size:var(--fds-ui-small-size);font-weight:500;margin:0 0 2px}
.fds-banner__body{font-size:var(--fds-ui-caption-size);line-height:1.5;margin:0;opacity:.88}
.fds-banner--accent{background:var(--fds-accent-tint);color:var(--fds-accent-mark);
  border-color:color-mix(in srgb,var(--fds-accent-mark) 28%,transparent)}
.fds-banner--ok{background:var(--fds-ok-tint);color:var(--fds-ok-mark);
  border-color:color-mix(in srgb,var(--fds-ok-mark) 28%,transparent)}
.fds-banner--wn{background:var(--fds-wn-tint);color:var(--fds-wn-mark);
  border-color:color-mix(in srgb,var(--fds-wn-mark) 28%,transparent)}
.fds-banner--dg{background:var(--fds-dg-tint);color:var(--fds-dg-mark);
  border-color:color-mix(in srgb,var(--fds-dg-mark) 32%,transparent)}
.fds-banner--info{background:var(--fds-in-tint);color:var(--fds-in-mark);
  border-color:color-mix(in srgb,var(--fds-in-mark) 28%,transparent)}

/* --- 15 · NOTIFICATION & TOAST -------------------------------------------
   Four ways to tell someone something, and the rule for which one:

     inline error    the field they are in is wrong          .fds-error
     banner          a condition of this screen, persistent  .fds-banner
     toast           something finished, and they can leave  .fds-toast
     dialog          they cannot continue until they answer  (modal)

   A toast is for the outcome of an action the person took. It is never used
   for anything they must act on — a message that matters cannot be allowed to
   time out. If it needs a decision, it is a banner or a dialog.

   Toasts stack bottom-up, newest nearest the thumb. Maximum three; a fourth
   replaces the oldest, because a stack taller than three is a log and belongs
   somewhere a person can scroll it.

   Timing: 4s for a plain confirmation, 6s when it carries an Undo, and never
   auto-dismissing while hovered or focused. Anything with an Undo keeps the
   Undo alive for the full window — a toast that disappears mid-reach is worse
   than no toast.
   ------------------------------------------------------------------------*/
.fds-toaster{position:fixed;z-index:var(--fds-z-toast);inset-block-end:var(--fds-sp-6);
  inset-inline-end:var(--fds-sp-6);display:flex;flex-direction:column-reverse;
  gap:var(--fds-sp-4);width:min(24rem,calc(100vw - 2rem));pointer-events:none}
@media (max-width:32rem){
  .fds-toaster{inset-inline:var(--fds-sp-5);width:auto;
    inset-block-end:calc(var(--fds-sp-5) + env(safe-area-inset-bottom,0px))}
}
.fds-toast{pointer-events:auto;display:flex;align-items:flex-start;gap:var(--fds-sp-5);
  padding:var(--fds-sp-5) var(--fds-sp-6);border-radius:var(--fds-r-card);
  background:var(--fds-floating);color:var(--fds-text);
  border:var(--fds-border-w) solid var(--fds-border);
  box-shadow:var(--fds-e3),var(--fds-e-edge);
  animation:fds-toast-in var(--fds-dur-slow) var(--fds-ease-entrance)}
.fds-toast[data-leaving="true"]{animation:fds-toast-out var(--fds-dur-base) var(--fds-ease-exit) forwards}
.fds-toast > .fds-ic{margin-top:.1em}
.fds-toast--ok > .fds-ic{color:var(--fds-ok-mark)}
.fds-toast--wn > .fds-ic{color:var(--fds-wn-mark)}
.fds-toast--dg > .fds-ic{color:var(--fds-dg-mark)}
.fds-toast--info > .fds-ic{color:var(--fds-accent-mark)}
.fds-toast__body{flex:1;min-width:0}
.fds-toast__title{font-size:var(--fds-ui-small-size);font-weight:500}
.fds-toast__detail{font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted);margin-top:2px}
.fds-toast__action{border:0;background:none;padding:0;font-family:inherit;
  font-size:var(--fds-ui-small-size);font-weight:500;color:var(--fds-accent-mark);
  cursor:pointer;white-space:nowrap;align-self:center}
.fds-toast__close{border:0;background:none;padding:0;cursor:pointer;
  color:var(--fds-text-muted);display:flex;align-self:flex-start;margin-top:.1em}

@keyframes fds-toast-in{from{opacity:0;transform:translateY(.75rem) scale(.98)}
  to{opacity:1;transform:none}}
@keyframes fds-toast-out{to{opacity:0;transform:translateY(.4rem) scale(.98)}}
@keyframes fds-pop{from{opacity:0;transform:translateY(-.25rem) scaleY(.96)}
  to{opacity:1;transform:none}}


/* --- 16 · STATUS, PROGRESS, SPINNERS, SKELETON ---------------------------
   Three different questions, three different answers, and they are not
   interchangeable:

     "is this thing all right?"      a status dot or chip — a state, static
     "how far through is this?"      a completion bar — determinate, monotonic
     "is anything happening at all?" a spinner — indeterminate, and an apology

   A spinner is what you show when you cannot answer the second question. If
   the number of steps is known, show the bar. Under ~400ms show nothing at
   all: a spinner that flashes reads as a fault.

   Over ~1s a spinner needs a sentence beside it saying what is being waited
   for. Over ~4s it needs a way out.

   Completion bars use --fds-ease-data, which is monotonic. A bar that
   overshoots its own value and settles back is telling a lie about a number.
   ------------------------------------------------------------------------*/
.fds-status{display:inline-flex;align-items:center;gap:var(--fds-sp-4);
  font-size:var(--fds-ui-small-size)}
.fds-status__dot{width:.5rem;height:.5rem;border-radius:50%;flex-shrink:0;
  background:var(--fds-text-muted)}
.fds-status--ok .fds-status__dot{background:var(--fds-ok-mark)}
.fds-status--wn .fds-status__dot{background:var(--fds-wn-mark)}
.fds-status--dg .fds-status__dot{background:var(--fds-dg-mark)}
.fds-status--live .fds-status__dot{background:var(--fds-ok-mark);
  animation:fds-pulse 2s var(--fds-ease-standard) infinite}
@keyframes fds-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.85)}}

/* Completion bar. Determinate. */
.fds-bar{height:.375rem;border-radius:var(--fds-r-pill);overflow:hidden;
  background:var(--fds-surface)}
.fds-bar > i{display:block;height:100%;border-radius:inherit;background:var(--fds-accent);
  width:0;transition:width var(--fds-dur-slower) var(--fds-ease-data)}
.fds-bar--ok > i{background:var(--fds-ok)}
.fds-bar--wn > i{background:var(--fds-wn)}
.fds-bar--thin{height:.1875rem}
/* Elapsed, not achieved: how much of a period is gone. Never a task's progress
   — a task either happened or it didn't. */
.fds-track{height:.1875rem;border-radius:var(--fds-r-pill);overflow:hidden;
  margin-top:var(--fds-sp-4);background:color-mix(in srgb,currentColor 18%,transparent)}
.fds-track > i{display:block;height:100%;border-radius:inherit;background:currentColor;
  width:0;transition:width var(--fds-dur-slower) var(--fds-ease-data)}
/* A flow of known length. */
.fds-steps{display:flex;gap:5px}
.fds-steps > i{height:.1875rem;flex:1;border-radius:var(--fds-r-pill);
  background:var(--fds-hairline);overflow:hidden;position:relative}
.fds-steps > i::after{content:'';position:absolute;inset:0;background:var(--fds-accent);
  transform:scaleX(0);transform-origin:left;
  transition:transform var(--fds-dur-slower) var(--fds-ease-data)}
.fds-steps > i.is-done::after{transform:scaleX(1)}
/* Allocation across named parts, read left to right in the order named. */
.fds-split{display:flex;gap:4px}
.fds-split > i{height:.4375rem;border-radius:var(--fds-r-mark);display:block}

/* Spinner. Indeterminate. */
.fds-spinner{width:1.25rem;height:1.25rem;border-radius:50%;flex-shrink:0;
  border:2px solid color-mix(in srgb,currentColor 22%,transparent);
  border-top-color:currentColor;color:var(--fds-accent-mark);
  animation:fds-spin .7s linear infinite}
.fds-spinner--sm{width:1rem;height:1rem;border-width:2px}
.fds-spinner--lg{width:2rem;height:2rem;border-width:3px}
@keyframes fds-spin{to{transform:rotate(360deg)}}
.fds-waiting{display:flex;align-items:center;gap:var(--fds-sp-5);
  color:var(--fds-text-muted);font-size:var(--fds-ui-small-size)}

/* Skeleton. Only for content whose shape is known before it arrives. A
   skeleton that guesses wrong is worse than a blank space, because the layout
   moves twice. */
.fds-skeleton{background:var(--fds-skeleton);background-size:200% 100%;
  border-radius:var(--fds-r-mark);animation:fds-shimmer 1.4s linear infinite}
.fds-skeleton--text{height:.8em;margin:.35em 0;border-radius:var(--fds-r-mark)}
.fds-skeleton--line{height:1rem}
.fds-skeleton--block{height:4rem;border-radius:var(--fds-r-ctl-md)}
@keyframes fds-shimmer{from{background-position:200% 0}to{background-position:-200% 0}}

/* Success mark. Draws once, on arrival, and never loops. */
.fds-seal{width:4.75rem;height:4.75rem;border-radius:50%;background:var(--fds-ok-tint);
  display:flex;align-items:center;justify-content:center;color:var(--fds-ok-mark)}
.fds-seal svg{width:2.4rem;height:2.4rem;stroke:currentColor;stroke-width:2.2;fill:none;
  stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:34;stroke-dashoffset:34;
  animation:fds-draw var(--fds-dur-slower) var(--fds-ease-standard) .18s forwards}
@keyframes fds-draw{to{stroke-dashoffset:0}}

/* An undecided value. Not a zero, not a dash, not a guess — a hole the shape
   of the answer. It renders as a hole on purpose: an empty box gets filled, a
   plausible placeholder gets shipped. */
.fds-slot{display:inline-block;min-width:4.125rem;height:1.375rem;vertical-align:-.3em;
  border:1px dashed var(--fds-border-control);border-radius:var(--fds-r-mark)}
.fds-slot--lg{min-width:6rem;height:2.375rem;vertical-align:-.55em;
  border-radius:var(--fds-r-ctl-md)}


/* --- 17 · MENUS, POPOVERS, TOOLTIPS --------------------------------------*/
.fds-menu{min-width:12rem;padding:var(--fds-sp-2);margin:0;list-style:none;
  background:var(--fds-floating);border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-card);box-shadow:var(--fds-e2),var(--fds-e-edge);
  z-index:var(--fds-z-dropdown);animation:fds-pop var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-menu__item{display:flex;align-items:center;gap:var(--fds-sp-5);width:100%;
  padding:var(--fds-sp-4) var(--fds-sp-5);min-height:var(--fds-tap);
  border:0;background:none;font-family:inherit;font-size:var(--fds-ui-body-size);
  color:var(--fds-text);text-align:start;border-radius:var(--fds-r-ctl-sm);cursor:pointer}
.fds-menu__item:hover{background:var(--fds-hover-wash)}
.fds-menu__item--danger{color:var(--fds-dg-mark)}
.fds-menu__sep{height:var(--fds-border-w);background:var(--fds-hairline);
  margin:var(--fds-sp-2) var(--fds-sp-4)}
.fds-tooltip{position:absolute;z-index:var(--fds-z-tooltip);max-width:18rem;
  padding:var(--fds-sp-3) var(--fds-sp-5);border-radius:var(--fds-r-ctl-sm);
  background:var(--fds-neutral);color:var(--fds-neutral-ink);
  font-size:var(--fds-ui-caption-size);line-height:1.45;box-shadow:var(--fds-e2)}


/* --- 18 · DRAG AND DROP, MORPHING CARDS ----------------------------------
   Dragging answers three questions continuously, and all three have to stay
   answered or the person lets go somewhere they did not mean to:

     what am I holding    the source lifts to e-drag and tilts ~1.5°, and the
                          place it came from stays as a hollow, not a gap. A
                          gap makes the list reflow under the finger.
     where will it land   the live target takes a 2px accent edge and its
                          contents shift by one step. One target is lit at a
                          time, never two.
     can I let go here    a target that would refuse never lights up. There is
                          no "invalid drop" toast in this system, because the
                          question is answered before the finger lifts.

   Pointer events, not HTML5 drag-and-drop: HTML5 DnD has no touch support, and
   the shop floor is touch.

   MORPHING. A card has three form factors and the same identity in each:

     micro     one line, dragged. What a set looks like in transit.
     card      resting. Name, place, state.
     expanded  opened in place. History, actions, everything.

   The morph is one element changing size, not two elements cross-fading. The
   title is the anchor: it stays put and everything else grows around it, so
   the eye never loses which record it is looking at. Content that only exists
   in the expanded form fades in AFTER the size change lands, never during —
   text reflowing mid-resize is what makes a morph feel cheap.
   ------------------------------------------------------------------------*/
.fds-draggable{cursor:grab;touch-action:none;
  transition:box-shadow var(--fds-dur-base) var(--fds-ease-standard),
             transform var(--fds-dur-base) var(--fds-ease-standard)}
.fds-draggable[data-dragging="true"]{cursor:grabbing;position:relative;
  z-index:var(--fds-z-overlay);box-shadow:var(--fds-e-drag),var(--fds-e-edge);
  transform:rotate(1.5deg) scale(1.02);transition:none}
.fds-drag-hollow{border:1px dashed var(--fds-border-control);border-radius:var(--fds-r-card);
  background:transparent;opacity:.6}
.fds-droptarget{transition:border-color var(--fds-dur-fast) var(--fds-ease-standard),
  background-color var(--fds-dur-fast) var(--fds-ease-standard),
  padding var(--fds-dur-base) var(--fds-ease-standard)}
.fds-droptarget[data-over="true"]{border-color:var(--fds-accent-mark);
  border-width:var(--fds-border-thick);background:var(--fds-accent-tint)}

.fds-morph{overflow:hidden;
  transition:max-height var(--fds-dur-slow) var(--fds-ease-emphasized),
             padding var(--fds-dur-slow) var(--fds-ease-emphasized),
             border-radius var(--fds-dur-slow) var(--fds-ease-emphasized)}
.fds-morph[data-form="micro"]{max-height:3rem;padding:var(--fds-sp-4) var(--fds-sp-5);
  border-radius:var(--fds-r-ctl-md)}
.fds-morph[data-form="card"]{max-height:9rem;padding:var(--fds-card-pad);
  border-radius:var(--fds-r-card)}
.fds-morph[data-form="expanded"]{max-height:32rem;padding:var(--fds-card-pad);
  border-radius:var(--fds-r-card)}
.fds-morph__detail{opacity:0;transform:translateY(.35rem);
  transition:opacity var(--fds-dur-base) var(--fds-ease-standard) var(--fds-dur-base),
             transform var(--fds-dur-base) var(--fds-ease-standard) var(--fds-dur-base)}
.fds-morph[data-form="expanded"] .fds-morph__detail{opacity:1;transform:none}
.fds-morph__hide-micro{transition:opacity var(--fds-dur-fast) var(--fds-ease-exit)}
.fds-morph[data-form="micro"] .fds-morph__hide-micro{opacity:0;pointer-events:none}


/* --- 19 · TIMELINE & REFUSALS -------------------------------------------*/
.fds-timeline{list-style:none;position:relative;padding-inline-start:1.6rem;margin:0}
.fds-timeline::before{content:'';position:absolute;inset-inline-start:.35rem;top:.5rem;
  bottom:.5rem;width:1px;background:var(--fds-border)}
.fds-timeline > li{position:relative;padding-block-end:var(--fds-sp-7)}
.fds-timeline > li:last-child{padding-block-end:0}
.fds-timeline > li::before{content:'';position:absolute;inset-inline-start:-1.5rem;top:.4rem;
  width:.55rem;height:.55rem;border-radius:50%;background:var(--fds-accent)}
.fds-timeline__what{font-size:var(--fds-ui-body-size);font-weight:500;margin:0 0 2px}
.fds-timeline__detail{font-size:var(--fds-ui-small-size);color:var(--fds-text-muted);margin:0}

/* Danger as a mark, never as a fill, so a boundary reads as a boundary and not
   as an alarm. */
.fds-points{list-style:none;margin:0;padding:0}
.fds-points > li{display:flex;gap:var(--fds-sp-5);align-items:flex-start;
  padding:var(--fds-sp-5) 0;border-bottom:var(--fds-border-w) solid var(--fds-hairline)}
.fds-points > li:last-child{border-bottom:0}
.fds-points__mark{width:1.2rem;height:1.2rem;flex-shrink:0;margin-top:.12em;
  color:var(--fds-dg-mark)}
.fds-points__mark--ok{color:var(--fds-ok-mark)}


/* --- 20 · ACCOUNT STATE --------------------------------------------------
   The state a shop is in is a property of the document, not of a component.
   Set it once and every surface answers.

   Locked drains colour and interaction from anything carrying `.fds-lockable`,
   except what also carries `.fds-keeps` — anything that lets the shop take its
   records out. That exception is the product's whole position, in CSS.
   ------------------------------------------------------------------------*/
.fds-lockable{transition:filter var(--fds-dur-slower) var(--fds-ease-standard),
  opacity var(--fds-dur-slower) var(--fds-ease-standard)}
[data-account="locked"] .fds-lockable{filter:grayscale(1);opacity:.42;pointer-events:none}
[data-account="locked"] .fds-keeps{filter:none;opacity:1;pointer-events:auto}
.fds-when-locked{display:none}
[data-account="locked"] .fds-when-locked{display:revert}
[data-account="locked"] .fds-unless-locked{display:none}


/* --- 21 · ENTRANCE -------------------------------------------------------
   A screen rises once, in reading order, and then holds still. Stagger is one
   token so it cannot drift between screens.
   ------------------------------------------------------------------------*/
@media (prefers-reduced-motion:no-preference){
  .fds-rise > *{animation:fds-rise var(--fds-dur-slow) var(--fds-ease-entrance) both}
  .fds-rise > *:nth-child(1){animation-delay:calc(var(--fds-dur-stagger) * 0)}
  .fds-rise > *:nth-child(2){animation-delay:calc(var(--fds-dur-stagger) * 1)}
  .fds-rise > *:nth-child(3){animation-delay:calc(var(--fds-dur-stagger) * 2)}
  .fds-rise > *:nth-child(4){animation-delay:calc(var(--fds-dur-stagger) * 3)}
  .fds-rise > *:nth-child(5){animation-delay:calc(var(--fds-dur-stagger) * 4)}
  .fds-rise > *:nth-child(6){animation-delay:calc(var(--fds-dur-stagger) * 5)}
  .fds-rise > *:nth-child(7){animation-delay:calc(var(--fds-dur-stagger) * 6)}
  .fds-rise > *:nth-child(n+8){animation-delay:calc(var(--fds-dur-stagger) * 7)}
}
@keyframes fds-rise{from{opacity:0;transform:translateY(.55rem)}to{opacity:1;transform:none}}

/* --- 22 · DATA VISUALISATION ---------------------------------------------

   THE TAXONOMY. The component list is four layers, and knowing which layer a
   thing is in tells you what it may assume:

     MARKS         1–14  the encoding itself. Knows about data, not layout.
     DISPLAYS     15–19  rows and lists. Knows about records, not framing.
     FRAMES       20–24  what wraps a mark and gives it a title, a period, a
                         source and a state. Knows about framing, not data.
     COMPOSITIONS 25–28  arrangements of frames for an audience.

   A mark never draws its own title. A frame never knows what chart is inside
   it. That separation is what lets an operational board and an executive
   dashboard share every component and still read completely differently.

   ── THE SERIES RAMP, AND WHY IT IS FIVE ─────────────────────────────────
   The action hue is excluded from every chart ramp. A cyan bar looks pressable
   and people press it. Danger and success hue bands are excluded too, so a
   categorical series cannot accidentally say "good" or "bad".

   Within those exclusions, the five core series were chosen by search rather
   than by eye, optimising the minimum ΔE across normal vision and simulated
   deuteranopia, protanopia and tritanopia. Two hand-picked palettes were tried
   first and both failed: a lightness-stratified set collapsed indigo against
   teal at ΔE 10, and a higher-chroma night set fell to ΔE 3.9 under
   tritanopia. Measured beats designed here, every time.

   What the search then proved:

     5 series   min CVD ΔE  17.1 night · 23.4 day   usable
     8 series   min CVD ΔE   9.4 night · 10.8 day   two of the eight become
                                                    the same colour to someone
                                                    with CVD

   So FIVE IS THE CAP for colour-alone encoding. `--fds-dv-6..8` exist, and the
   moment you use them colour has stopped being the encoding: the chart needs
   direct labels, and it needs them for every series, not just the ambiguous
   pair.

   Night is structurally weaker than day (17.1 against 23.4) because every mark
   has to be light against a dark canvas, which compresses the lightness range
   the search can work in. Beyond three series in night, direct-label.

   ── RULES ───────────────────────────────────────────────────────────────
   · Direct-label over a legend whenever the geometry allows. A legend makes
     the eye travel and hold a colour in memory; a label at the end of a line
     does not.
   · Colour is never the only channel. Position, length and order come first.
   · Series index is assigned by the data's own order and never reshuffled
     between renders — a series that changes colour on reload is a bug the
     person reads as a data change.
   · Zero is on the axis for anything length-encoded. A truncated bar axis is
     a lie with a ratio in it.
   · Axis labels are mono, tabular, and at micro size. They are read, not
     scanned.
   · No animation on load for anything a person is about to read a number off.
     Bars grow only when the value actually changed.
   ------------------------------------------------------------------------*/
:root, [data-theme="day"]{
  /* Core five — searched, day. min CVD ΔE 23.4, min hue separation 45°. */
  --fds-dv-1:#742D1B; --fds-dv-2:#1B742D; --fds-dv-3:#386FB2;
  --fds-dv-4:#8B2DBE; --fds-dv-5:#913B6F;
  /* Extended — colour is no longer sufficient past here. */
  --fds-dv-6:#1B6F74; --fds-dv-7:#A57927; --fds-dv-8:#5844A7;

  /* Sequential, indigo family — deliberately not the action hue. Low end sits
     close to the surface on purpose; the cell border is what keeps the grid
     legible, not the fill. */
  --fds-dv-s1:#EDEFF9; --fds-dv-s2:#C6CCEB; --fds-dv-s3:#9AA5DB;
  --fds-dv-s4:#6B7AC4; --fds-dv-s5:#3E4B96;

  /* Diverging, gold ↔ indigo. Not red/green: this is the ramp that gets used
     for over/under target, which is exactly where a red/green scale strands
     the eight percent. */
  --fds-dv-d1:#9A6B0F; --fds-dv-d2:#C2A76F; --fds-dv-d3:#6E6E6E;
  --fds-dv-d4:#7A88C9; --fds-dv-d5:#3E4B96;

  --fds-dv-grid:var(--fds-hairline);
  --fds-dv-axis:var(--fds-border);
  --fds-dv-ref:var(--fds-text-muted);      /* a target or reference line     */
  --fds-dv-plot:transparent;
  --fds-dv-empty:var(--fds-surface);        /* no data, as distinct from zero */
}
[data-theme="night"]{
  /* Core five — searched, night. min CVD ΔE 17.1, min hue separation 37°. */
  --fds-dv-1:#D4815E; --fds-dv-2:#DEDAAF; --fds-dv-3:#6EC472;
  --fds-dv-4:#ABBCE3; --fds-dv-5:#C46EA6;
  --fds-dv-6:#B266CC; --fds-dv-7:#9A84CD; --fds-dv-8:#BDE7A6;

  --fds-dv-s1:#232840; --fds-dv-s2:#33406B; --fds-dv-s3:#4A5C99;
  --fds-dv-s4:#6B7CC4; --fds-dv-s5:#A3B0E5;

  --fds-dv-d1:#D9A05E; --fds-dv-d2:#B0A183; --fds-dv-d3:#A8A8A8;
  --fds-dv-d4:#93A0D1; --fds-dv-d5:#6B7CC4;

  --fds-dv-grid:var(--fds-hairline);
  --fds-dv-axis:var(--fds-border);
  --fds-dv-ref:var(--fds-text-muted);
  --fds-dv-plot:transparent;
  --fds-dv-empty:var(--fds-surface);
}
/* In high contrast the ramp collapses to three, because five values that each
   hold 7:1 against the surface cannot also hold enough separation from each
   other. Three series, and a pattern overlay past that. */
[data-contrast="high"]{
  --fds-dv-4:var(--fds-dv-1); --fds-dv-5:var(--fds-dv-2);
  --fds-dv-6:var(--fds-dv-3); --fds-dv-7:var(--fds-dv-1); --fds-dv-8:var(--fds-dv-2);
  --fds-dv-grid:var(--fds-border);
}

.fds-dv-1{--_dv:var(--fds-dv-1)} .fds-dv-2{--_dv:var(--fds-dv-2)}
.fds-dv-3{--_dv:var(--fds-dv-3)} .fds-dv-4{--_dv:var(--fds-dv-4)}
.fds-dv-5{--_dv:var(--fds-dv-5)} .fds-dv-6{--_dv:var(--fds-dv-6)}
.fds-dv-7{--_dv:var(--fds-dv-7)} .fds-dv-8{--_dv:var(--fds-dv-8)}


/* ═══ LAYER 1 · MARKS ═══════════════════════════════════════════════════ */

/* 1 · METRIC — one number and what it means. The number is the largest thing
   in the component; the label is above it and the comparison below. A metric
   without a comparison is a number without a verdict, and people will invent
   one. */
.fds-metric{display:flex;flex-direction:column;gap:var(--fds-sp-2);min-width:0}
.fds-metric__label{font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted)}
.fds-metric__value{font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-variant-numeric:tabular-nums;
  font-size:var(--fds-ui-display);font-weight:500;letter-spacing:-.03em;line-height:1.05}
.fds-metric__value small{font-size:.5em;font-weight:400;color:var(--fds-text-muted);
  margin-inline-start:.2em;letter-spacing:0}
.fds-metric__delta{display:inline-flex;align-items:center;gap:var(--fds-sp-3);
  font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted)}
/* Direction is not sentiment. A number going up is not automatically good —
   `--good` and `--bad` are set by the caller, who knows which. */
.fds-metric__delta--good{color:var(--fds-ok-mark)}
.fds-metric__delta--bad{color:var(--fds-dg-mark)}
.fds-metric--lg .fds-metric__value{font-size:clamp(2.25rem,1.6rem + 2.4vw,3.5rem)}

/* 2 · SPARKLINE — shape without a scale. It answers "which way", never "how
   much", which is why it carries no axis and must never be read off. Pair it
   with a metric that carries the number. */
.fds-spark{display:block;width:100%;height:2.25rem;overflow:visible}
.fds-spark path{fill:none;stroke:var(--_dv,var(--fds-dv-3));stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;vector-effect:non-scaling-stroke}
.fds-spark .fds-spark__area{fill:var(--_dv,var(--fds-dv-3));opacity:.12;stroke:none}
.fds-spark .fds-spark__last{fill:var(--_dv,var(--fds-dv-3));stroke:none}
.fds-spark--sm{height:1.25rem}

/* 3 · CHART — the general frame for anything with two axes. Grid lines sit
   behind, the axis rule is one hairline, and there is exactly one of it per
   direction. */
.fds-chart{position:relative;display:grid;
  grid-template-columns:auto minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto;
  gap:var(--fds-sp-4);min-height:9rem}
.fds-chart__y{grid-column:1;grid-row:1;display:flex;flex-direction:column;
  justify-content:space-between;align-items:flex-end;
  font-family:var(--fds-font-mono);font-variation-settings:'wdth' 75;
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);
  font-variant-numeric:tabular-nums;padding-inline-end:var(--fds-sp-3)}
.fds-chart__plot{grid-column:2;grid-row:1;position:relative;
  border-inline-start:1px solid var(--fds-dv-axis);
  border-block-end:1px solid var(--fds-dv-axis);background:var(--fds-dv-plot)}
.fds-chart__x{grid-column:2;grid-row:2;display:flex;justify-content:space-between;
  font-family:var(--fds-font-mono);font-variation-settings:'wdth' 75;
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)}
.fds-chart__grid{position:absolute;inset:0;pointer-events:none;
  background-image:repeating-linear-gradient(to top,var(--fds-dv-grid) 0 1px,transparent 1px var(--_g,25%))}
/* A reference line is a fact (a target, a capacity, last year). It is dashed so
   it cannot be mistaken for a series, and it carries its own label. */
.fds-chart__ref{position:absolute;inset-inline:0;border-block-start:1px dashed var(--fds-dv-ref);
  bottom:var(--_at,50%)}
.fds-chart__ref span{position:absolute;inset-inline-end:0;top:-1.1em;
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);background:var(--fds-canvas);
  padding-inline:var(--fds-sp-3)}

/* Bars. Length-encoded, so zero is always on the axis. */
.fds-bars{position:absolute;inset:0;display:flex;align-items:flex-end;
  gap:var(--fds-sp-3);padding-inline:var(--fds-sp-3)}
.fds-bars > i{flex:1;min-width:2px;display:block;background:var(--_dv,var(--fds-dv-3));
  border-radius:2px 2px 0 0;height:var(--_v,0%);
  transition:height var(--fds-dur-slower) var(--fds-ease-data)}
.fds-bars--stacked{align-items:stretch;flex-direction:column-reverse}

/* 4 · DISTRIBUTION — how values spread. A histogram answers "what is normal
   here", which an average cannot. */
.fds-dist{position:absolute;inset:0;display:flex;align-items:flex-end;gap:1px}
.fds-dist > i{flex:1;background:var(--_dv,var(--fds-dv-3));height:var(--_v,0%);opacity:.85;
  transition:height var(--fds-dur-slower) var(--fds-ease-data)}
.fds-dist__box{display:flex;align-items:center;gap:0;height:1.5rem;position:relative}
.fds-dist__whisk{flex:1;height:1px;background:var(--fds-dv-axis)}
.fds-dist__iqr{height:1.1rem;border-radius:2px;background:var(--_dv,var(--fds-dv-3));opacity:.35;
  border:1px solid var(--_dv,var(--fds-dv-3))}
.fds-dist__med{width:2px;height:1.4rem;background:var(--fds-text)}

/* 5 · RELATIONSHIP — two measures against each other. Points, not lines: a
   line between unordered points invents a sequence that is not in the data. */
.fds-scatter{position:absolute;inset:0}
.fds-scatter i{position:absolute;width:.5rem;height:.5rem;border-radius:50%;
  background:var(--_dv,var(--fds-dv-3));transform:translate(-50%,50%);
  inset-inline-start:var(--_x);bottom:var(--_y);opacity:.8}
.fds-scatter i[data-highlight]{outline:2px solid var(--fds-text);outline-offset:1px;opacity:1}

/* 6 · PART-TO-WHOLE — only when the parts genuinely sum to something a person
   cares about. Default to a bar, not a ring: a stacked bar compares parts and
   totals at once, and a ring compares neither well past two slices. */
.fds-part{display:flex;height:.75rem;border-radius:var(--fds-r-pill);overflow:hidden;
  background:var(--fds-dv-empty)}
.fds-part > i{display:block;background:var(--_dv);
  transition:flex-grow var(--fds-dur-slower) var(--fds-ease-data)}
.fds-part--thick{height:1.25rem;border-radius:var(--fds-r-mark)}
.fds-ring{--_pct:0;width:5rem;height:5rem;border-radius:50%;
  background:conic-gradient(var(--_dv,var(--fds-dv-3)) calc(var(--_pct) * 1%),
             var(--fds-dv-empty) 0);
  display:grid;place-items:center}
.fds-ring::after{content:'';width:64%;height:64%;border-radius:50%;background:var(--fds-raised)}
.fds-ring__label{grid-area:1/1;z-index:1;font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-size:var(--fds-ui-small-size);
  font-weight:500}

/* 7 · HEATMAP — density across two categories. The cell border is what keeps
   the grid legible, because the low end of a sequential ramp is meant to fade
   into the surface. Values are always available as text on hover and focus,
   and always present in the accessible table underneath. */
.fds-heat{display:grid;gap:2px;grid-template-columns:repeat(var(--_cols,7),minmax(0,1fr))}
.fds-heat i{aspect-ratio:1;border-radius:2px;background:var(--fds-dv-empty);
  border:1px solid var(--fds-hairline);display:block}
.fds-heat i[data-level="1"]{background:var(--fds-dv-s1)}
.fds-heat i[data-level="2"]{background:var(--fds-dv-s2)}
.fds-heat i[data-level="3"]{background:var(--fds-dv-s3)}
.fds-heat i[data-level="4"]{background:var(--fds-dv-s4)}
.fds-heat i[data-level="5"]{background:var(--fds-dv-s5)}
.fds-heat i:focus-visible{outline:2px solid var(--fds-focus);outline-offset:1px}

/* 8 · PROGRESS AND TARGET — a value against a commitment. The target is a
   notch, not a second bar: two bars invite comparing the bars instead of the
   value against the line. */
.fds-target{position:relative;height:1.25rem;border-radius:var(--fds-r-mark);
  background:var(--fds-surface);overflow:hidden}
.fds-target__fill{height:100%;background:var(--_dv,var(--fds-dv-3));width:var(--_v,0%);
  transition:width var(--fds-dur-slower) var(--fds-ease-data)}
.fds-target__mark{position:absolute;top:-2px;bottom:-2px;width:2px;
  background:var(--fds-text);inset-inline-start:var(--_t,100%)}
.fds-target--over .fds-target__fill{background:var(--fds-dv-d5)}
.fds-target--under .fds-target__fill{background:var(--fds-dv-d1)}

/* 9 · WATERFALL — how a total got from one number to another. Every bar is a
   change except the first and last, which are levels. That difference has to
   be visible or the chart is unreadable. */
.fds-waterfall{display:flex;align-items:stretch;gap:var(--fds-sp-3);height:9rem}
.fds-waterfall > div{flex:1;position:relative;display:flex;flex-direction:column;
  justify-content:flex-end}
.fds-waterfall i{display:block;border-radius:2px;background:var(--_dv,var(--fds-dv-3));
  height:var(--_h,10%);margin-block-end:var(--_b,0%)}
.fds-waterfall .is-total i{background:var(--fds-text-muted);border-radius:2px}
.fds-waterfall .is-up i{background:var(--fds-dv-d5)}
.fds-waterfall .is-down i{background:var(--fds-dv-d1)}
.fds-waterfall__label{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);
  text-align:center;margin-top:var(--fds-sp-3)}

/* 10 · TIMELINE — events on a real time axis, as opposed to the activity
   trail, which is an ordered list. If the gaps between events carry meaning,
   it is a timeline; if only the order does, it is a trail. */
.fds-tline{position:relative;padding-block:var(--fds-sp-6)}
.fds-tline__axis{position:relative;height:2px;background:var(--fds-dv-axis);
  border-radius:1px}
.fds-tline__ev{position:absolute;top:50%;inset-inline-start:var(--_at);
  width:.7rem;height:.7rem;border-radius:50%;background:var(--_dv,var(--fds-dv-3));
  transform:translate(-50%,-50%);border:2px solid var(--fds-canvas)}
.fds-tline__span{position:absolute;top:50%;height:.45rem;border-radius:var(--fds-r-pill);
  background:var(--_dv,var(--fds-dv-3));opacity:.5;transform:translateY(-50%);
  inset-inline-start:var(--_from);width:var(--_len)}
.fds-tline__ticks{display:flex;justify-content:space-between;margin-top:var(--fds-sp-4);
  font-family:var(--fds-font-mono);font-variation-settings:'wdth' 75;
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)}

/* 11 · FUNNEL — sequential stages with drop-off. Each stage is a proportion of
   the FIRST stage, and the step-to-step loss is labelled, because that is the
   number anyone actually wants. */
.fds-funnel{display:flex;flex-direction:column;gap:var(--fds-sp-4)}
.fds-funnel__row{display:flex;align-items:center;gap:var(--fds-sp-5)}
.fds-funnel__label{width:9rem;flex-shrink:0;font-size:var(--fds-ui-small-size)}
.fds-funnel__bar{flex:1;height:1.5rem;background:var(--fds-dv-empty);
  border-radius:var(--fds-r-mark);overflow:hidden}
.fds-funnel__bar i{display:block;height:100%;background:var(--_dv,var(--fds-dv-3));
  width:var(--_v,0%);transition:width var(--fds-dur-slower) var(--fds-ease-data)}
.fds-funnel__drop{width:5rem;flex-shrink:0;text-align:right;
  font-family:var(--fds-font-mono);font-variation-settings:'wdth' 75;
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)}

/* 12 · FLOW — quantity moving between states. Bands are proportional to the
   amount moved. Beyond about six flows this becomes decoration and should be a
   table instead. */
.fds-flow{display:flex;align-items:stretch;gap:var(--fds-sp-8);min-height:8rem}
.fds-flow__col{flex:1;display:flex;flex-direction:column;gap:var(--fds-sp-3);min-width:0}
.fds-flow__node{background:var(--fds-surface);border:1px solid var(--fds-border);
  border-radius:var(--fds-r-mark);padding:var(--fds-sp-3) var(--fds-sp-4);
  font-size:var(--fds-ui-caption-size);flex:var(--_w,1);display:flex;
  align-items:center;justify-content:space-between;gap:var(--fds-sp-4);min-height:1.75rem}
.fds-flow__node b{font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' 75;font-weight:500}
.fds-flow__link{position:relative;width:3rem;flex-shrink:0}
.fds-flow__link svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
.fds-flow__link path{fill:var(--_dv,var(--fds-dv-3));opacity:.28}

/* 13 · MAP — geography. FDS supplies the frame, the marker and the legend, and
   deliberately does not supply a projection or tiles: those are a vendor
   decision and change independently of the design system. A map is the right
   answer only when the question is genuinely spatial — "where are our
   customers" is; "how many customers" is not. */
.fds-map{position:relative;aspect-ratio:16/10;border-radius:var(--fds-r-card);
  overflow:hidden;background:var(--fds-surface);border:1px solid var(--fds-border)}
.fds-map__marker{position:absolute;inset-inline-start:var(--_x);top:var(--_y);
  transform:translate(-50%,-100%);color:var(--_dv,var(--fds-dv-3))}
.fds-map__legend{position:absolute;inset-block-end:var(--fds-sp-4);
  inset-inline-start:var(--fds-sp-4);background:var(--fds-floating);
  border:1px solid var(--fds-border);border-radius:var(--fds-r-ctl-sm);
  padding:var(--fds-sp-3) var(--fds-sp-4);font-size:var(--fds-ui-micro-size);
  box-shadow:var(--fds-e2)}

/* 14 · PHYSICAL LAYOUT — a real place, drawn to its own arrangement rather
   than to a data shape. A rack wall, a bay plan, a yard. The whole point is
   that its geometry matches what somebody sees when they look up, so it is
   never auto-arranged and never sorted. */
.fds-plan{display:grid;gap:var(--fds-sp-3);
  grid-template-columns:repeat(var(--_cols,8),minmax(0,1fr))}
.fds-plan__cell{aspect-ratio:1;border-radius:var(--fds-r-mark);
  background:var(--fds-surface);border:1px solid var(--fds-border);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:2px;font-family:var(--fds-font-mono);font-variation-settings:'wdth' 75;
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);cursor:pointer;
  transition:background-color var(--fds-dur-fast),border-color var(--fds-dur-fast)}
.fds-plan__cell[data-state="full"]{background:var(--fds-dv-s4);color:#FFF;border-color:transparent}
.fds-plan__cell[data-state="part"]{background:var(--fds-dv-s2);color:var(--fds-text);border-color:transparent}
.fds-plan__cell[data-state="empty"]{background:var(--fds-dv-empty)}
.fds-plan__cell[data-state="blocked"]{background:repeating-linear-gradient(45deg,
  var(--fds-surface) 0 4px,var(--fds-hairline) 4px 8px);color:var(--fds-text-disabled)}
.fds-plan__cell:hover{border-color:var(--fds-border-control)}
.fds-plan__cell[aria-current="true"]{outline:2px solid var(--fds-accent);outline-offset:1px}
.fds-plan__aisle{grid-column:1/-1;height:var(--fds-sp-5)}


/* ═══ LAYER 2 · DISPLAYS ════════════════════════════════════════════════ */

/* 17 · STATUS LIST — many things, each with one state. The state word is the
   content; the colour is the second channel. Sorted by severity by default,
   because a list of forty greens with one red must not hide the red. */
.fds-statuslist{display:flex;flex-direction:column}
.fds-statuslist__row{display:flex;align-items:center;gap:var(--fds-sp-5);
  padding:var(--fds-sp-4) 0;border-block-end:1px solid var(--fds-hairline);
  min-height:var(--fds-tap)}
.fds-statuslist__row:last-child{border-block-end:0}
.fds-statuslist__name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;font-size:var(--fds-ui-body-size)}
.fds-statuslist__meta{font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' 75;font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted)}

/* 18 · ACTIVITY TRAIL — what happened, in order, newest last. Order carries
   information; the interval does not. If the interval matters it is a
   timeline. */
.fds-trail{list-style:none;margin:0;padding:0;position:relative;
  padding-inline-start:1.6rem}
.fds-trail::before{content:'';position:absolute;inset-inline-start:.35rem;
  top:.6rem;bottom:.6rem;width:1px;background:var(--fds-border)}
.fds-trail > li{position:relative;padding-block:var(--fds-sp-4)}
.fds-trail > li::before{content:'';position:absolute;inset-inline-start:-1.5rem;
  top:1rem;width:.5rem;height:.5rem;border-radius:50%;background:var(--fds-border-control)}
.fds-trail > li[data-tone="ok"]::before{background:var(--fds-ok-mark)}
.fds-trail > li[data-tone="wn"]::before{background:var(--fds-wn-mark)}
.fds-trail > li[data-tone="dg"]::before{background:var(--fds-dg-mark)}
.fds-trail__what{font-size:var(--fds-ui-small-size)}
.fds-trail__who{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);margin-top:2px}

/* 19 · ALERT — something is wrong now and a person has to decide. It states
   the condition, when it started, and the one action available. An alert with
   no action is a status, and belongs in the status list. */
.fds-alert{display:flex;gap:var(--fds-sp-5);align-items:flex-start;
  padding:var(--fds-sp-5) var(--fds-sp-6);border-radius:var(--fds-r-card);
  border:1px solid transparent;background:var(--fds-wn-tint);color:var(--fds-wn-mark);
  border-color:color-mix(in srgb,var(--fds-wn-mark) 30%,transparent)}
.fds-alert--dg{background:var(--fds-dg-tint);color:var(--fds-dg-mark);
  border-color:color-mix(in srgb,var(--fds-dg-mark) 32%,transparent)}
.fds-alert__body{flex:1;min-width:0}
.fds-alert__what{font-size:var(--fds-ui-small-size);font-weight:500}
.fds-alert__since{font-size:var(--fds-ui-micro-size);opacity:.85;margin-top:2px}


/* ═══ LAYER 3 · FRAMES ══════════════════════════════════════════════════ */

/* 20 · SCORECARD — a small grid of metrics that are read together. Every cell
   uses the same period and the same units, or it is not a scorecard, it is
   four metrics sharing a border. */
.fds-scorecard{display:grid;gap:1px;background:var(--fds-border);
  border:1px solid var(--fds-border);border-radius:var(--fds-r-card);overflow:hidden;
  grid-template-columns:repeat(auto-fit,minmax(min(10rem,100%),1fr))}
.fds-scorecard > div{background:var(--fds-raised);padding:var(--fds-card-pad)}

/* 21 · CHART CARD — a mark with a title, a period, a state and a source. The
   frame owns every one of those; the mark owns none of them. */
.fds-chartcard{background:var(--fds-raised);border:1px solid var(--fds-border);
  border-radius:var(--fds-r-card);padding:var(--fds-card-pad);
  display:flex;flex-direction:column;gap:var(--fds-sp-5)}
.fds-chartcard__head{display:flex;align-items:flex-start;gap:var(--fds-sp-5)}
.fds-chartcard__title{flex:1;min-width:0}
.fds-chartcard__title h4{font-size:var(--fds-ui-body-size);font-weight:500;margin:0}
.fds-chartcard__period{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);
  margin-top:2px}
.fds-chartcard__foot{display:flex;align-items:center;gap:var(--fds-sp-5);
  flex-wrap:wrap;font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);
  padding-top:var(--fds-sp-4);border-top:1px solid var(--fds-hairline)}
/* Provenance is not optional on anything a decision gets made from. */
.fds-chartcard__source{margin-inline-start:auto}

/* Legend. Used only when direct labelling is geometrically impossible. */
.fds-legend{display:flex;flex-wrap:wrap;gap:var(--fds-sp-3) var(--fds-sp-6)}
.fds-legend > span{display:inline-flex;align-items:center;gap:var(--fds-sp-3);
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)}
.fds-legend i{width:.625rem;height:.625rem;border-radius:2px;background:var(--_dv);
  flex-shrink:0}

/* 22 · INSIGHT PANEL — a sentence a person can act on, with the evidence
   beside it. This is the only component in the system allowed to state a
   conclusion, and it must show the number the conclusion came from. */
.fds-insight{display:flex;gap:var(--fds-sp-6);align-items:flex-start;
  background:var(--fds-surface);border:1px solid var(--fds-border);
  border-inline-start:2px solid var(--fds-accent-mark);
  border-radius:var(--fds-r-card);padding:var(--fds-card-pad)}
.fds-insight__body{flex:1;min-width:0}
.fds-insight__claim{font-size:var(--fds-ui-body-size);line-height:1.5}
.fds-insight__evidence{display:flex;flex-wrap:wrap;gap:var(--fds-sp-6);
  margin-top:var(--fds-sp-5);padding-top:var(--fds-sp-4);
  border-top:1px solid var(--fds-hairline)}

/* 23 · DETAIL PANEL — everything known about one record. Label/value pairs,
   label left and value right, values in mono when they are identifiers. */
.fds-detail{display:grid;grid-template-columns:auto minmax(0,1fr);
  gap:var(--fds-sp-4) var(--fds-sp-6)}
.fds-detail dt{font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted);
  white-space:nowrap}
.fds-detail dd{margin:0;font-size:var(--fds-ui-small-size);min-width:0;
  overflow-wrap:anywhere}
.fds-detail dd.fds-mono{font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth)}

/* 24 · FILTER BAR — what is currently being excluded, stated out loud. The
   applied filters are always visible as removable chips: a filter a person
   forgot they set is how a wrong number gets believed. */
.fds-filterbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--fds-sp-4);
  padding:var(--fds-sp-4) var(--fds-sp-5);background:var(--fds-surface);
  border:1px solid var(--fds-border);border-radius:var(--fds-r-ctl-md)}
.fds-filterbar__count{margin-inline-start:auto;font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' 75;font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted)}
.fds-filterchip{display:inline-flex;align-items:center;gap:var(--fds-sp-3);
  padding:3px 4px 3px 10px;border-radius:var(--fds-r-pill);
  background:var(--fds-accent-tint);color:var(--fds-accent-mark);
  font-size:var(--fds-ui-micro-size);font-weight:500}
.fds-filterchip button{display:flex;width:1.1rem;height:1.1rem;border:0;padding:0;
  border-radius:50%;background:none;color:inherit;cursor:pointer;
  align-items:center;justify-content:center}
.fds-filterchip button:hover{background:color-mix(in srgb,currentColor 18%,transparent)}


/* ═══ LAYER 4 · COMPOSITIONS ════════════════════════════════════════════
   Same components, three audiences, three densities of meaning.

     OPERATIONAL  what do I do next, right now. Refreshes on its own, sorted by
                  urgency, one screen, no scrolling, and every item is
                  actionable. Shop density by default.
     ANALYTICAL   why did that happen. Filters, comparisons, scrolling is fine,
                  and every chart carries its source and period.
     EXECUTIVE    is it working. Few numbers, each with a comparison and a
                  verdict sentence. No chart a person has to interpret.

   The failure mode is building one dashboard and giving it to all three.
   ------------------------------------------------------------------------*/

/* 25 · DASHBOARD SECTION — a titled group of frames with one shared period. */
.fds-dsection{display:flex;flex-direction:column;gap:var(--fds-sp-6)}
.fds-dsection__head{display:flex;align-items:baseline;gap:var(--fds-sp-5);
  flex-wrap:wrap}
.fds-dsection__head h3{font-size:var(--fds-ui-heading);font-weight:500;margin:0}
.fds-dsection__period{font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted)}
.fds-dsection__act{margin-inline-start:auto}
.fds-dgrid{display:grid;gap:var(--fds-sp-6);
  grid-template-columns:repeat(auto-fit,minmax(min(var(--_min,17rem),100%),1fr))}
.fds-dgrid > .fds-span-2{grid-column:span 2}
@media (max-width:52rem){ .fds-dgrid > .fds-span-2{grid-column:span 1} }

/* 26 · OPERATIONAL BOARD */
.fds-board{display:flex;flex-direction:column;gap:var(--fds-sp-5);height:100%}
.fds-board__head{display:flex;align-items:center;gap:var(--fds-sp-5);
  padding-block-end:var(--fds-sp-5);border-block-end:1px solid var(--fds-border)}
.fds-board__clock{margin-inline-start:auto;font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-size:var(--fds-ui-small-size);
  color:var(--fds-text-muted)}
.fds-board__lanes{display:grid;gap:var(--fds-sp-5);flex:1;min-height:0;
  grid-template-columns:repeat(auto-fit,minmax(min(14rem,100%),1fr))}
.fds-board__lane{display:flex;flex-direction:column;gap:var(--fds-sp-4);min-height:0}
.fds-board__lanehead{display:flex;align-items:center;gap:var(--fds-sp-4);
  font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted)}
.fds-board__lanehead b{margin-inline-start:auto;font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' 75;color:var(--fds-text)}
.fds-board__stack{display:flex;flex-direction:column;gap:var(--fds-sp-4);
  overflow-y:auto;min-height:0}

/* 27/28 · ANALYTICAL and EXECUTIVE are the section grid with different
   defaults, not different components. */
.fds-dash--analytical{--_min:19rem}
.fds-dash--executive{--_min:14rem}
.fds-dash--executive .fds-metric__value{font-size:clamp(2rem,1.5rem + 1.8vw,3rem)}
.fds-verdict{font-family:var(--fds-font-serif);font-weight:300;
  font-size:calc(var(--fds-ui-body-size) + .125rem);line-height:1.5;
  color:var(--fds-text-muted);max-width:52ch;margin:0}
.fds-verdict b{color:var(--fds-text);font-weight:400}

/* --- 23 · TABLE (15) and MATRIX (16) -------------------------------------

   A table is for reading down a column. That single sentence decides every
   rule below: alignment, the sticky header, where the hairlines go, and what
   happens when the screen is narrower than the data.

   Rows-in-a-card (`.fds-card--rows`) is still the right answer for two to five
   fields that are read across. This is the other thing — many rows, many
   columns, and a person comparing values vertically.

   ── ALIGNMENT ───────────────────────────────────────────────────────────
   Text left. Numbers right, tabular, in mono. Dates left, because a date is
   read as a word, not compared as a magnitude — unless the column is a
   duration, which is a number.

   Headers take the alignment of their column. A right-aligned column with a
   left-aligned header is the commonest table defect and it breaks the ruler
   the column depends on.

   ── HAIRLINES, NOT ZEBRA ────────────────────────────────────────────────
   Zebra striping solves row-tracking by adding a second visual rhythm that
   then competes with everything else on the page, and it makes any real
   background state — selected, error, stale — unreadable on half the rows. One
   hairline per row, and a hover band. Row-tracking on very wide tables is
   solved by the frozen key column, not by stripes.

   ── SORTING ─────────────────────────────────────────────────────────────
   One sort at a time, stated in the header, reflected in `aria-sort`. The
   default sort is the one that puts the thing needing attention first, not
   alphabetical. Sorting never re-numbers a row: if the table shows a position,
   the position is data.

   ── WIDTHS ──────────────────────────────────────────────────────────────
   Columns size to content up to a cap, and the resize handle sets an explicit
   width that survives a re-render. A dragged width is a decision and the table
   must not overrule it on the next sort.
   ------------------------------------------------------------------------*/
.fds-tablewrap{position:relative;overflow:auto;max-height:var(--_maxh,none);
  border:1px solid var(--fds-border);border-radius:var(--fds-r-card);
  background:var(--fds-raised)}
.fds-table{width:100%;border-collapse:separate;border-spacing:0;
  font-size:var(--fds-ui-small-size);table-layout:auto}
.fds-table th,.fds-table td{
  padding:var(--fds-row-pad) var(--fds-sp-5);text-align:start;vertical-align:middle;
  border-block-end:1px solid var(--fds-hairline);white-space:nowrap}
.fds-table tbody tr:last-child > td{border-block-end:0}
.fds-table thead th{
  position:sticky;top:0;z-index:2;background:var(--fds-surface);
  font-size:var(--fds-ui-micro-size);font-weight:500;letter-spacing:.04em;
  text-transform:uppercase;color:var(--fds-text-muted);
  border-block-end:1px solid var(--fds-border);
  padding-block:var(--fds-sp-4);user-select:none}
/* The sticky header needs its own bottom rule painted, because a sticky cell's
   border scrolls away with the box in several engines. */
.fds-table thead th::after{content:'';position:absolute;inset-inline:0;bottom:-1px;
  height:1px;background:var(--fds-border)}
.fds-table tbody tr{transition:background-color var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-table tbody tr:hover{background:var(--fds-hover-wash)}
.fds-table tbody tr[aria-selected="true"]{background:var(--fds-accent-tint)}
.fds-table tbody tr[data-stale="true"] td{color:var(--fds-text-muted);font-style:italic}

.fds-table__num{text-align:end;font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-variant-numeric:tabular-nums}
.fds-table__id{font-family:var(--fds-font-mono);font-variation-settings:'wdth' 75}
/* Wraps rather than truncates — a name or a rack position is read back aloud. */
.fds-table__wrap{white-space:normal;min-width:12rem}

/* Sortable header. The indicator is present but dim on unsorted columns, so
   the affordance does not appear only on hover, which a touch screen has no
   way to discover. */
.fds-table th[aria-sort]{cursor:pointer}
.fds-table th[aria-sort] .fds-table__sort{display:inline-flex;align-items:center;
  gap:var(--fds-sp-3)}
.fds-table th[aria-sort] .fds-table__sort::after{content:'';width:0;height:0;
  border-inline:4px solid transparent;border-block-end:5px solid currentColor;
  opacity:.22;transition:opacity var(--fds-dur-fast),transform var(--fds-dur-fast)}
.fds-table th[aria-sort="ascending"] .fds-table__sort::after{opacity:1}
.fds-table th[aria-sort="descending"] .fds-table__sort::after{opacity:1;transform:rotate(180deg)}
.fds-table th[aria-sort]:hover .fds-table__sort::after{opacity:.6}
.fds-table th[aria-sort="ascending"]:hover .fds-table__sort::after,
.fds-table th[aria-sort="descending"]:hover .fds-table__sort::after{opacity:1}
.fds-table th.fds-table__num .fds-table__sort{justify-content:flex-end}

/* Resize handle. Widened hit area, hairline visual — the handle is 9px wide
   and looks like 1px, because a visible 9px divider between every column is a
   second grid nobody asked for. */
.fds-table__resize{position:absolute;top:0;bottom:0;inset-inline-end:-5px;width:9px;
  cursor:col-resize;z-index:3;touch-action:none}
.fds-table__resize::before{content:'';position:absolute;inset-block:25%;
  inset-inline-start:4px;width:1px;background:var(--fds-border);
  transition:background-color var(--fds-dur-fast)}
.fds-table__resize:hover::before,.fds-table__resize.is-dragging::before{
  background:var(--fds-accent-mark);inset-block:0}
.fds-table th{position:relative}
.fds-table.is-resizing{cursor:col-resize;user-select:none}

/* Frozen key column. The shadow only appears once the table is actually
   scrolled, so an unscrolled table has no false edge in it. */
.fds-table--freeze th:first-child,.fds-table--freeze td:first-child{
  position:sticky;inset-inline-start:0;z-index:1;background:var(--fds-raised)}
.fds-table--freeze thead th:first-child{z-index:3;background:var(--fds-surface)}
.fds-table--freeze tbody tr:hover td:first-child{background:var(--fds-surface)}
.fds-tablewrap[data-scrolled="true"] .fds-table--freeze th:first-child::before,
.fds-tablewrap[data-scrolled="true"] .fds-table--freeze td:first-child::before{
  content:'';position:absolute;top:0;bottom:0;inset-inline-end:-12px;width:12px;
  background:linear-gradient(to right,var(--fds-scrim),transparent);opacity:.28;
  pointer-events:none}

.fds-table__empty{padding:var(--fds-sp-11) var(--fds-sp-6);text-align:center;
  color:var(--fds-text-muted)}
.fds-table__foot{display:flex;align-items:center;gap:var(--fds-sp-5);
  padding:var(--fds-sp-4) var(--fds-sp-5);border-block-start:1px solid var(--fds-border);
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);
  background:var(--fds-surface);position:sticky;bottom:0}

/* --- WIDE TABLES ON SMALL SCREENS ----------------------------------------
   Three strategies. The choice is not about how wide the table is — it is
   about what the person is doing with it.

     Are they COMPARING values across many rows?      → scroll + freeze
     Are they FINDING one record and reading it?      → priority + expand
     Are they working through records one at a time?  → stack

   And the anti-patterns, which are anti-patterns because they destroy the
   thing the table was for:
     · shrinking the type — the table survives, the reader does not
     · truncating identifiers — a half-shown plate is worse than no plate
     · dropping columns with no way to get them back — silent data loss that
       the person cannot see happening
     · a horizontal scrollbar with no frozen column — scroll two columns right
       and every row becomes anonymous

   ── 1 · SCROLL + FREEZE ──────────────────────────────────────────────────
   Keeps table semantics and every value on screen. The key column stays put so
   rows never become anonymous. Best for numeric comparison. Needs a visible
   scroll affordance — the fade at the right edge — because a table that
   scrolls invisibly does not get scrolled.
   ------------------------------------------------------------------------*/
.fds-table--scroll{min-width:max-content}

/* ── 2 · PRIORITY + EXPAND ────────────────────────────────────────────────
   Columns carry a priority; the container sheds them from the lowest up as it
   narrows, and the row can be opened to show everything that was shed. Nothing
   is lost, and the person can see that nothing was lost.

   Priority 1 is the identifier and never sheds. Priority 2 is the one value
   the screen is about. Everything else is 3+.
   ----------------------------------------------------------------------*/
@container fds-table (max-width: 60rem){ .fds-table [data-pri="5"]{display:none} }
@container fds-table (max-width: 48rem){ .fds-table [data-pri="4"]{display:none} }
@container fds-table (max-width: 38rem){ .fds-table [data-pri="3"]{display:none} }
.fds-tablewrap{container-type:inline-size;container-name:fds-table}
/* Fallback for engines without container queries. */
@supports not (container-type:inline-size){
  @media (max-width:60rem){ .fds-table [data-pri="5"]{display:none} }
  @media (max-width:48rem){ .fds-table [data-pri="4"]{display:none} }
  @media (max-width:38rem){ .fds-table [data-pri="3"]{display:none} }
}
.fds-table__expand{display:none;border:0;background:none;padding:0;cursor:pointer;
  color:var(--fds-text-muted)}
.fds-table__expand svg{transition:transform var(--fds-dur-base) var(--fds-ease-standard)}
.fds-table tr[aria-expanded="true"] .fds-table__expand svg{transform:rotate(90deg)}
.fds-table__drawer{display:none}
.fds-table__drawer > td{padding:0;border-block-end:1px solid var(--fds-hairline)}
.fds-table__drawer .fds-detail{padding:var(--fds-sp-5) var(--fds-sp-6) var(--fds-sp-6);
  background:var(--fds-surface)}
@container fds-table (max-width: 60rem){
  .fds-table__expand{display:inline-flex}
  .fds-table tr[aria-expanded="true"] + .fds-table__drawer{display:table-row}
}
@supports not (container-type:inline-size){
  @media (max-width:60rem){
    .fds-table__expand{display:inline-flex}
    .fds-table tr[aria-expanded="true"] + .fds-table__drawer{display:table-row}
  }
}

/* ── 3 · STACK ────────────────────────────────────────────────────────────
   Each row becomes a record card: the identifier as a heading, the rest as
   label/value pairs. Table semantics are kept — the header cell text is echoed
   into the value's `::before` from a data attribute — so the same markup keeps
   working for a screen reader and nothing is duplicated in the DOM.

   Only for six fields or fewer. Past that a stacked row is taller than the
   screen and the person has lost the comparison entirely; use priority+expand.
   ----------------------------------------------------------------------*/
@container fds-table (max-width: 34rem){
  .fds-table--stack thead{position:absolute;width:1px;height:1px;overflow:hidden;
    clip:rect(0 0 0 0);white-space:nowrap}
  .fds-table--stack,.fds-table--stack tbody,.fds-table--stack tr,
  .fds-table--stack td{display:block;width:auto}
  .fds-table--stack tr{border:1px solid var(--fds-border);
    border-radius:var(--fds-r-ctl-md);padding:var(--fds-sp-5);
    margin-block-end:var(--fds-sp-5);background:var(--fds-raised)}
  .fds-table--stack td{border:0;padding:var(--fds-sp-3) 0;display:flex;
    align-items:baseline;gap:var(--fds-sp-5);white-space:normal}
  .fds-table--stack td::before{content:attr(data-label);flex-shrink:0;width:8rem;
    font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);
    text-transform:uppercase;letter-spacing:.04em}
  .fds-table--stack td[data-pri="1"]{padding-block-end:var(--fds-sp-4);
    margin-block-end:var(--fds-sp-3);border-block-end:1px solid var(--fds-hairline);
    font-size:var(--fds-ui-heading);font-weight:500}
  .fds-table--stack td[data-pri="1"]::before{display:none}
  .fds-table--stack .fds-table__num{text-align:start;justify-content:flex-start}
  .fds-table--stack [data-pri]{display:flex}
}

/* --- MATRIX (16) ---------------------------------------------------------
   A crosstab: two categorical axes, one measure. It is a table whose row and
   column headers both matter, so both are sticky, and the corner cell is
   sticky in both directions.

   A matrix is the right shape when a person asks "which combination", and the
   wrong one when they ask "how much" — that is a bar chart with a total.
   ------------------------------------------------------------------------*/
.fds-matrix{border-collapse:separate;border-spacing:0;font-size:var(--fds-ui-small-size)}
.fds-matrix th,.fds-matrix td{padding:var(--fds-sp-4) var(--fds-sp-5);
  border-block-end:1px solid var(--fds-hairline);
  border-inline-end:1px solid var(--fds-hairline);white-space:nowrap}
.fds-matrix thead th{position:sticky;top:0;z-index:2;background:var(--fds-surface);
  font-size:var(--fds-ui-micro-size);font-weight:500;text-transform:uppercase;
  letter-spacing:.04em;color:var(--fds-text-muted)}
.fds-matrix tbody th{position:sticky;inset-inline-start:0;z-index:1;
  background:var(--fds-raised);text-align:start;font-weight:400}
.fds-matrix thead th:first-child{z-index:3;inset-inline-start:0}
.fds-matrix td{text-align:end;font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-variant-numeric:tabular-nums}
.fds-matrix td[data-level]{color:var(--fds-text)}
.fds-matrix td[data-level="0"]{color:var(--fds-text-disabled)}
.fds-matrix td[data-level="1"]{background:var(--fds-dv-s1)}
.fds-matrix td[data-level="2"]{background:var(--fds-dv-s2)}
.fds-matrix td[data-level="3"]{background:var(--fds-dv-s3)}
.fds-matrix td[data-level="4"]{background:var(--fds-dv-s4)}
.fds-matrix td[data-level="5"]{background:var(--fds-dv-s5)}
.fds-matrix tfoot td,.fds-matrix tfoot th{font-weight:500;
  border-block-start:1px solid var(--fds-border);background:var(--fds-surface)}

/* --- 24 · OVERLAYS -------------------------------------------------------

   Four ways to put something on top, and one question that picks between them:
   HOW MUCH OF THE PAGE DOES THIS REPLACE, AND CAN THE PERSON IGNORE IT?

     POPOVER   anchored, non-modal, dismissible by looking away. A menu, a date
               picker, a definition. The page keeps working behind it.
     DRAWER    edge-anchored, modal, keeps the page visible as context. Filters,
               a record's detail beside the list it came from, a form whose
               result changes what is behind it.
     SHEET     the drawer's bottom-edge form. The same component on a phone,
               because a side drawer on a 380px screen is a modal wearing a
               costume.
     MODAL     centred, modal, replaces attention entirely. Only for a decision
               that cannot be deferred, or destructive confirmation.

   ── RULES ───────────────────────────────────────────────────────────────
   · ONE DEEP. A modal never opens a modal. If a flow needs a second step, it
     is a wizard inside the same modal — the frame stays, the contents change.
   · A drawer may open a modal (confirming a delete). A modal may not open a
     drawer.
   · Everything modal traps focus, restores it to the trigger on close, locks
     background scroll, and closes on Escape. The scrim is not a close button
     on destructive dialogs.
   · A modal always has a titled heading referenced by `aria-labelledby`. An
     untitled modal is a popup.
   · Destructive confirmation names the thing and the consequence in the button
     — "Delete 12 records", never "OK".
   · Nothing modal auto-opens. A person opened it, or it does not open.
   ------------------------------------------------------------------------*/
/* `hidden` must win over the component's own display.
   `.fds-modal` is (0,1,0) and the UA rule for `[hidden]` is (0,0,1), so a class
   that sets `display:flex` silently overrides the attribute and every declared
   overlay renders on load, stacked. Attribute + class is (0,2,0) and settles
   it. Any component here that sets `display` needs a matching line — this is
   the one specificity trap in the sheet. */
.fds-modal[hidden],.fds-drawer[hidden],.fds-popover[hidden],
.fds-scrim[hidden],.fds-menu[hidden],.fds-tooltip[hidden]{display:none}

.fds-scrim{position:fixed;inset:0;background:var(--fds-scrim);z-index:var(--fds-z-overlay);
  animation:fds-fade-in var(--fds-dur-base) var(--fds-ease-standard)}
.fds-scrim[data-leaving="true"]{animation:fds-fade-out var(--fds-dur-fast) var(--fds-ease-exit) forwards}
body.fds-locked{overflow:hidden}

/* MODAL */
.fds-modal{position:fixed;z-index:var(--fds-z-modal);
  inset-block-start:50%;inset-inline-start:50%;transform:translate(-50%,-50%);
  width:min(32rem,calc(100vw - 2rem));max-height:min(44rem,calc(100vh - 3rem));
  display:flex;flex-direction:column;
  background:var(--fds-floating);border:1px solid var(--fds-border);
  border-radius:var(--fds-r-panel);box-shadow:var(--fds-e4),var(--fds-e-edge);
  animation:fds-modal-in var(--fds-dur-slow) var(--fds-ease-entrance)}
.fds-modal[data-leaving="true"]{animation:fds-modal-out var(--fds-dur-fast) var(--fds-ease-exit) forwards}
.fds-modal--wide{width:min(48rem,calc(100vw - 2rem))}
.fds-modal__head{display:flex;align-items:flex-start;gap:var(--fds-sp-5);
  padding:var(--fds-sp-7) var(--fds-sp-7) var(--fds-sp-5)}
.fds-modal__title{flex:1;min-width:0;font-size:var(--fds-ui-title);font-weight:600;
  letter-spacing:-.02em;margin:0}
.fds-modal__body{padding:0 var(--fds-sp-7);overflow-y:auto;flex:1;min-height:0}
.fds-modal__foot{display:flex;align-items:center;justify-content:flex-end;
  gap:var(--fds-sp-4);padding:var(--fds-sp-6) var(--fds-sp-7) var(--fds-sp-7)}
.fds-modal__foot--split{justify-content:space-between}

/* DRAWER */
.fds-drawer{position:fixed;z-index:var(--fds-z-drawer);display:flex;flex-direction:column;
  background:var(--fds-floating);box-shadow:var(--fds-e3),var(--fds-e-edge)}
.fds-drawer[data-edge="end"]{inset-block:0;inset-inline-end:0;
  width:min(26rem,calc(100vw - 3rem));border-inline-start:1px solid var(--fds-border);
  animation:fds-drawer-end-in var(--fds-dur-slow) var(--fds-ease-emphasized)}
.fds-drawer[data-edge="start"]{inset-block:0;inset-inline-start:0;
  width:min(20rem,calc(100vw - 3rem));border-inline-end:1px solid var(--fds-border);
  animation:fds-drawer-start-in var(--fds-dur-slow) var(--fds-ease-emphasized)}
.fds-drawer[data-edge="end"][data-leaving="true"]{animation:fds-drawer-end-out var(--fds-dur-base) var(--fds-ease-exit) forwards}
.fds-drawer[data-edge="start"][data-leaving="true"]{animation:fds-drawer-start-out var(--fds-dur-base) var(--fds-ease-exit) forwards}
.fds-drawer__head{display:flex;align-items:center;gap:var(--fds-sp-5);
  padding:var(--fds-sp-6) var(--fds-sp-6) var(--fds-sp-5);
  border-block-end:1px solid var(--fds-border)}
.fds-drawer__title{flex:1;min-width:0;font-size:var(--fds-ui-heading);font-weight:500;margin:0}
.fds-drawer__body{flex:1;min-height:0;overflow-y:auto;padding:var(--fds-sp-6)}
.fds-drawer__foot{padding:var(--fds-sp-5) var(--fds-sp-6);
  border-block-start:1px solid var(--fds-border);display:flex;gap:var(--fds-sp-4);
  justify-content:flex-end}

/* SHEET — the drawer, bottom edge. Below 34rem a side drawer becomes one
   automatically, because that is the same component doing the same job in the
   only place it fits. */
.fds-drawer[data-edge="block-end"],
[data-sheet-below] .fds-drawer{--_sheet:1}
@media (max-width:34rem){
  .fds-drawer[data-edge="end"],.fds-drawer[data-edge="start"]{
    inset:auto 0 0 0;width:auto;max-height:88vh;
    border:1px solid var(--fds-border);border-block-end:0;
    border-start-start-radius:var(--fds-r-sheet);border-start-end-radius:var(--fds-r-sheet);
    padding-block-end:env(safe-area-inset-bottom,0px);
    animation:fds-sheet-in var(--fds-dur-slow) var(--fds-ease-emphasized)}
  .fds-drawer[data-leaving="true"]{animation:fds-sheet-out var(--fds-dur-base) var(--fds-ease-exit) forwards !important}
  .fds-drawer__head::before{content:'';position:absolute;top:var(--fds-sp-3);
    inset-inline-start:50%;transform:translateX(-50%);width:2.25rem;height:.25rem;
    border-radius:var(--fds-r-pill);background:var(--fds-border-control)}
  .fds-drawer__head{padding-top:var(--fds-sp-7);position:relative}
  .fds-modal{width:calc(100vw - 1.5rem)}
}

/* POPOVER — non-modal. No scrim, no focus trap, closes on outside click and on
   Escape, and never contains anything a person cannot get to another way. */
.fds-popover{position:absolute;z-index:var(--fds-z-dropdown);
  min-width:12rem;max-width:min(22rem,90vw);
  background:var(--fds-floating);border:1px solid var(--fds-border);
  border-radius:var(--fds-r-card);box-shadow:var(--fds-e2),var(--fds-e-edge);
  padding:var(--fds-sp-5);animation:fds-pop var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-popover[data-leaving="true"]{animation:fds-fade-out var(--fds-dur-instant) var(--fds-ease-exit) forwards}


/* --- 25 · MOTION CHOREOGRAPHY --------------------------------------------

   ONE LAW, and everything else follows from it:

     ENTER IS EXPLAINED. EXIT IS FAST.

   An entering thing has to tell you where it came from, so it is slower and
   uses a curve with a settle. A leaving thing has already been decided about,
   so it gets out of the way — shorter duration, accelerating curve, and never
   the entrance ease. An exit that takes as long as its entrance feels like the
   interface arguing with the person.

   Standard pairing, applied to every overlay above:

     surface      enter                        exit
     popover      fast   · standard            instant · exit
     toast        slow   · entrance            base    · exit
     drawer       slow   · emphasized          base    · exit
     modal        slow   · entrance            fast    · exit
     screen       base   · emphasized          fast    · exit

   ── DIRECTION MEANS SOMETHING ───────────────────────────────────────────
   Three transitions, and the one you pick states a relationship:

     SHARED AXIS   sideways. Peer steps in a sequence. Forward enters from the
                   end edge, back enters from the start edge. Getting this
                   backwards makes Back feel like Next, which people notice
                   without being able to say why.
     Z-AXIS        scale and fade. Going into a record and back out of it. The
                   child grows from the parent; the parent recedes but does not
                   move sideways, because it did not go anywhere.
     FADE-THROUGH  no direction at all. Two unrelated destinations, where any
                   direction would imply a relationship that does not exist.

   ── STATE CHOREOGRAPHY ──────────────────────────────────────────────────
   Anything that submits moves through the same four states, and every one of
   them is visible:

     idle → pending → settled(ok) → idle
                    ↘ settled(error) → idle

   `pending` never removes the label. `settled` holds for 900ms so the outcome
   is actually seen, then returns to idle. An error state does not
   auto-dismiss: it stays until the person changes something.
   ------------------------------------------------------------------------*/
:root{
  --fds-enter-pop:var(--fds-dur-fast) var(--fds-ease-standard);
  --fds-exit-pop:var(--fds-dur-instant) var(--fds-ease-exit);
  --fds-enter-overlay:var(--fds-dur-slow) var(--fds-ease-entrance);
  --fds-exit-overlay:var(--fds-dur-fast) var(--fds-ease-exit);
  --fds-enter-screen:var(--fds-dur-base) var(--fds-ease-emphasized);
  --fds-exit-screen:var(--fds-dur-fast) var(--fds-ease-exit);
}

@keyframes fds-fade-in{from{opacity:0}to{opacity:1}}
@keyframes fds-fade-out{to{opacity:0}}
@keyframes fds-modal-in{from{opacity:0;transform:translate(-50%,-46%) scale(.97)}
  to{opacity:1;transform:translate(-50%,-50%) scale(1)}}
@keyframes fds-modal-out{to{opacity:0;transform:translate(-50%,-48%) scale(.99)}}
@keyframes fds-drawer-end-in{from{transform:translateX(100%)}to{transform:none}}
@keyframes fds-drawer-end-out{to{transform:translateX(100%)}}
@keyframes fds-drawer-start-in{from{transform:translateX(-100%)}to{transform:none}}
@keyframes fds-drawer-start-out{to{transform:translateX(-100%)}}
@keyframes fds-sheet-in{from{transform:translateY(100%)}to{transform:none}}
@keyframes fds-sheet-out{to{transform:translateY(100%)}}

/* SCREEN TRANSITIONS. The container declares which relationship it is
   expressing; the leaving and entering panes are both present for one frame. */
.fds-screens{position:relative;overflow:hidden}
.fds-screen{grid-area:1/1}
.fds-screens[data-transition]{display:grid}

.fds-screens[data-transition="axis"][data-dir="forward"] .fds-screen[data-state="entering"]{
  animation:fds-axis-fwd-in var(--fds-enter-screen) both}
.fds-screens[data-transition="axis"][data-dir="forward"] .fds-screen[data-state="leaving"]{
  animation:fds-axis-fwd-out var(--fds-exit-screen) both}
.fds-screens[data-transition="axis"][data-dir="back"] .fds-screen[data-state="entering"]{
  animation:fds-axis-back-in var(--fds-enter-screen) both}
.fds-screens[data-transition="axis"][data-dir="back"] .fds-screen[data-state="leaving"]{
  animation:fds-axis-back-out var(--fds-exit-screen) both}
@keyframes fds-axis-fwd-in{from{opacity:0;transform:translateX(24px)}to{opacity:1;transform:none}}
@keyframes fds-axis-fwd-out{to{opacity:0;transform:translateX(-16px)}}
@keyframes fds-axis-back-in{from{opacity:0;transform:translateX(-24px)}to{opacity:1;transform:none}}
@keyframes fds-axis-back-out{to{opacity:0;transform:translateX(16px)}}

.fds-screens[data-transition="z"][data-dir="forward"] .fds-screen[data-state="entering"]{
  animation:fds-z-in var(--fds-enter-screen) both}
.fds-screens[data-transition="z"][data-dir="forward"] .fds-screen[data-state="leaving"]{
  animation:fds-z-recede var(--fds-exit-screen) both}
.fds-screens[data-transition="z"][data-dir="back"] .fds-screen[data-state="entering"]{
  animation:fds-z-return var(--fds-enter-screen) both}
.fds-screens[data-transition="z"][data-dir="back"] .fds-screen[data-state="leaving"]{
  animation:fds-z-out var(--fds-exit-screen) both}
@keyframes fds-z-in{from{opacity:0;transform:scale(.94)}to{opacity:1;transform:none}}
@keyframes fds-z-recede{to{opacity:0;transform:scale(1.04)}}
@keyframes fds-z-return{from{opacity:0;transform:scale(1.05)}to{opacity:1;transform:none}}
@keyframes fds-z-out{to{opacity:0;transform:scale(.96)}}

.fds-screens[data-transition="fade"] .fds-screen[data-state="entering"]{
  animation:fds-fade-in var(--fds-enter-screen) both}
.fds-screens[data-transition="fade"] .fds-screen[data-state="leaving"]{
  animation:fds-fade-out var(--fds-exit-screen) both}
.fds-screen[data-state="hidden"]{display:none}

/* WIZARD / SEQUENCE. The rail persists across every step — it is the one thing
   that must not animate, because it is what tells the person the steps are one
   thing. Steps are numbered and named; "Step 3 of 5" with no name is a
   progress bar pretending to be navigation. */
.fds-wizard{display:flex;flex-direction:column;gap:var(--fds-sp-7)}
.fds-wizard__rail{display:flex;align-items:center;gap:var(--fds-sp-4)}
.fds-wizard__step{display:flex;align-items:center;gap:var(--fds-sp-4);min-width:0}
.fds-wizard__dot{width:1.5rem;height:1.5rem;border-radius:50%;flex-shrink:0;
  display:flex;align-items:center;justify-content:center;
  border:1px solid var(--fds-border-control);background:var(--fds-surface);
  color:var(--fds-text-muted);font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' 75;font-size:var(--fds-ui-micro-size);
  transition:background-color var(--fds-dur-base) var(--fds-ease-standard),
             border-color var(--fds-dur-base) var(--fds-ease-standard),
             color var(--fds-dur-base) var(--fds-ease-standard)}
.fds-wizard__step[data-state="done"] .fds-wizard__dot{background:var(--fds-accent);
  border-color:var(--fds-accent);color:var(--fds-accent-ink)}
.fds-wizard__step[data-state="current"] .fds-wizard__dot{border-color:var(--fds-accent);
  color:var(--fds-accent-mark);border-width:2px}
.fds-wizard__name{font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fds-wizard__step[data-state="current"] .fds-wizard__name{color:var(--fds-text);font-weight:500}
.fds-wizard__link{flex:1;height:1px;background:var(--fds-border);min-width:var(--fds-sp-5)}
.fds-wizard__step[data-state="done"] + .fds-wizard__link{background:var(--fds-accent)}
@media (max-width:38rem){
  .fds-wizard__step:not([data-state="current"]) .fds-wizard__name{display:none}
}
.fds-wizard__foot{display:flex;align-items:center;gap:var(--fds-sp-4);
  padding-block-start:var(--fds-sp-6);border-block-start:1px solid var(--fds-hairline)}
.fds-wizard__foot .fds-wizard__next{margin-inline-start:auto}

/* SUBMIT STATE MACHINE. `data-state` on the control; the label never leaves. */
.fds-submit[data-state="pending"]{pointer-events:none}
.fds-submit[data-state="ok"]{background-color:var(--fds-ok);color:var(--fds-ok-ink);
  pointer-events:none}
.fds-submit[data-state="error"]{background-color:var(--fds-dg);color:var(--fds-dg-ink)}
.fds-submit__mark{display:none}
.fds-submit[data-state="ok"] .fds-submit__mark,
.fds-submit[data-state="error"] .fds-submit__mark{display:block;
  animation:fds-mark-in var(--fds-dur-base) var(--fds-ease-entrance) both}
.fds-submit[data-state="pending"] .fds-submit__idle,
.fds-submit[data-state="ok"] .fds-submit__idle,
.fds-submit[data-state="error"] .fds-submit__idle{display:none}
@keyframes fds-mark-in{from{opacity:0;transform:scale(.6)}to{opacity:1;transform:none}}

/* Content that arrives as a group. Used once per screen, never nested — two
   staggers running inside each other is a screen that never settles. */
.fds-sequence > *{animation:fds-rise var(--fds-dur-slow) var(--fds-ease-entrance) both}
.fds-sequence > *:nth-child(1){animation-delay:0ms}
.fds-sequence > *:nth-child(2){animation-delay:calc(var(--fds-dur-stagger) * 1)}
.fds-sequence > *:nth-child(3){animation-delay:calc(var(--fds-dur-stagger) * 2)}
.fds-sequence > *:nth-child(4){animation-delay:calc(var(--fds-dur-stagger) * 3)}
.fds-sequence > *:nth-child(5){animation-delay:calc(var(--fds-dur-stagger) * 4)}
.fds-sequence > *:nth-child(6){animation-delay:calc(var(--fds-dur-stagger) * 5)}
.fds-sequence > *:nth-child(n+7){animation-delay:calc(var(--fds-dur-stagger) * 6)}

/* Reduced motion: overlays still change state, they just stop travelling.
   Removing the transition entirely is correct; removing the state change is
   not, and is a common overcorrection. */
@media (prefers-reduced-motion:reduce){
  .fds-modal,.fds-drawer,.fds-scrim,.fds-popover,.fds-screen,.fds-sequence > *{
    animation:fds-fade-in var(--fds-dur-instant) linear both !important}
  .fds-modal[data-leaving="true"],.fds-drawer[data-leaving="true"],
  .fds-scrim[data-leaving="true"],.fds-popover[data-leaving="true"]{
    animation:fds-fade-out var(--fds-dur-instant) linear forwards !important}
}

/* --- 26 · PREFERENCES AND SYSTEM SIGNALS ---------------------------------

   Three of the four axes are a PERSON's preference, and a person who needs one
   of them has usually already told their operating system. Asking them to tell
   us again, in a settings screen they have to find first, is the accessibility
   equivalent of a form that does not remember your address.

   So every axis resolves in the same order:

     1. an explicit attribute on <html>          — the person chose, in-product
     2. the operating system's stated preference — they chose, once, globally
     3. the FDS default                          — nobody has said anything

   Which means: DO NOT set `data-theme`/`data-contrast` on the server unless the
   person has actually chosen. Writing `data-theme="day"` as a "safe default"
   overrides the OS signal and silently ignores somebody's stated preference.

   ── WHERE THE SWITCHES LIVE ─────────────────────────────────────────────
   These are per-person settings that follow the person between the counter
   tablet, the office desktop and their phone, so they belong on the PROFILE —
   not in localStorage, which strands the preference on one device and loses it
   when the shop reimages the tablet.

     Theme      global header, in the account menu, AND in settings.
                It is the one people flip several times a day.
     Contrast   settings → Display. Never behind an "Advanced" disclosure and
                never below the fold: somebody who needs high contrast is
                looking for it on a screen they currently cannot read well.
     Density    settings → Display, defaulted per device from `pointer:coarse`
                rather than remembered globally, because it is a property of
                the machine in front of them, not of them.

   Every one of the three carries a plain-language label and a description of
   what it changes. "High contrast" alone tells nobody whether it will help.
   ------------------------------------------------------------------------*/

/* OS-stated contrast preference. Mirrors the [data-contrast="high"] blocks so
   that a person who set it in Windows, macOS or iOS gets it without touching
   anything here. An explicit attribute still wins — it is more specific. */
@media (prefers-contrast: more){
  :root:not([data-contrast]):not([data-theme="night"]){
    --fds-canvas:#FFFFFF; --fds-surface:#FFFFFF; --fds-raised:#FFFFFF;
    --fds-floating:#FFFFFF; --fds-page:#F1F3F5; --fds-sunk:#FFFFFF;
    --fds-border:var(--fdsx-g-550); --fds-border-control:var(--fdsx-g-550);
    --fds-hairline:var(--fdsx-g-300);
    --fds-text:#000000; --fds-text-muted:var(--fdsx-g-750);
    --fds-text-disabled:var(--fdsx-g-500);
    --fds-accent:var(--fdsx-c-900); --fds-accent-mark:var(--fdsx-c-900);
    --fds-accent-ink:#FFFFFF; --fds-accent-tint:var(--fdsx-c-tint-hc-d);
    --fds-volt:var(--fdsx-v-700); --fds-volt-mark:var(--fdsx-v-700);
    --fds-volt-ink:#FFFFFF; --fds-volt-tint:var(--fdsx-v-tint-hc-d);
    --fds-ok:var(--fdsx-ok-700); --fds-ok-mark:var(--fdsx-ok-700);
    --fds-ok-ink:#FFFFFF; --fds-ok-tint:var(--fdsx-ok-tint-hc-d);
    --fds-wn:var(--fdsx-wn-700); --fds-wn-mark:var(--fdsx-wn-700);
    --fds-wn-ink:#FFFFFF; --fds-wn-tint:var(--fdsx-wn-tint-hc-d);
    --fds-dg:var(--fdsx-dg-700); --fds-dg-mark:var(--fdsx-dg-700);
    --fds-dg-ink:#FFFFFF; --fds-dg-tint:var(--fdsx-dg-tint-hc-d);
    --fds-in:var(--fdsx-in-700); --fds-in-mark:var(--fdsx-in-700);
    --fds-in-ink:#FFFFFF; --fds-in-tint:var(--fdsx-in-tint-hc-d);
    --fds-neutral:#000000; --fds-neutral-ink:#FFFFFF;
    --fds-focus:var(--fdsx-fc-700);
    --fds-fill-sheen:none; --fds-fill-sheen-pressed:none;
    --fds-dv-4:var(--fds-dv-1); --fds-dv-5:var(--fds-dv-2);
    --fds-dv-6:var(--fds-dv-3); --fds-dv-grid:var(--fds-border);
  }
  :root:not([data-contrast])[data-theme="night"]{
    --fds-page:#000000; --fds-canvas:var(--fdsx-n-999); --fds-surface:var(--fdsx-n-950);
    --fds-raised:var(--fdsx-n-925); --fds-floating:var(--fdsx-n-925); --fds-sunk:#000000;
    --fds-border:var(--fdsx-n-500); --fds-border-control:var(--fdsx-n-150);
    --fds-hairline:var(--fdsx-n-600);
    --fds-text:#FFFFFF; --fds-text-muted:var(--fdsx-n-150);
    --fds-text-disabled:var(--fdsx-n-400);
    --fds-accent:var(--fdsx-c-100); --fds-accent-mark:var(--fdsx-c-100);
    --fds-accent-ink:#0A0A0A; --fds-accent-tint:var(--fdsx-c-tint-hc-n);
    --fds-volt:var(--fdsx-v-300); --fds-volt-mark:var(--fdsx-v-300);
    --fds-volt-ink:#0A0A0A; --fds-volt-tint:var(--fdsx-v-tint-hc-n);
    --fds-ok:var(--fdsx-ok-100); --fds-ok-mark:var(--fdsx-ok-100);
    --fds-ok-ink:#0A0A0A; --fds-ok-tint:var(--fdsx-ok-tint-hc-n);
    --fds-wn:var(--fdsx-wn-100); --fds-wn-mark:var(--fdsx-wn-100);
    --fds-wn-ink:#0A0A0A; --fds-wn-tint:var(--fdsx-wn-tint-hc-n);
    --fds-dg:var(--fdsx-dg-100); --fds-dg-mark:var(--fdsx-dg-100);
    --fds-dg-ink:#0A0A0A; --fds-dg-tint:var(--fdsx-dg-tint-hc-n);
    --fds-in:var(--fdsx-in-100); --fds-in-mark:var(--fdsx-in-100);
    --fds-in-ink:#0A0A0A; --fds-in-tint:var(--fdsx-in-tint-hc-n);
    --fds-neutral:#FFFFFF; --fds-neutral-ink:#000000;
    --fds-focus:var(--fdsx-fc-200);
    --fds-fill-sheen:none; --fds-fill-sheen-pressed:none;
    --fds-dv-4:var(--fds-dv-1); --fds-dv-5:var(--fds-dv-2);
    --fds-dv-6:var(--fds-dv-3); --fds-dv-grid:var(--fds-border);
  }
}

/* --- FORCED COLOURS (Windows High Contrast) ------------------------------
   A different mechanism from `prefers-contrast`: the OS replaces the palette
   outright. FDS does not fight it — every token above is discarded and the
   system colours take over. What FDS has to do is make sure nothing was
   carrying meaning through a colour that just disappeared.

   Three repairs:
     · anything that showed state through a background gets a border, because
       backgrounds are flattened to Canvas
     · the focus ring is redeclared in Highlight, since the custom one is gone
     · CHART MARKS keep their own colours via `forced-color-adjust:none`. This
       is the documented exception: the ramp is data, not chrome, and letting
       five series collapse into one system colour destroys the chart. The
       legend and the axis chrome do adapt.
   ------------------------------------------------------------------------*/
@media (forced-colors: active){
  .fds :focus-visible{outline:2px solid Highlight;outline-offset:2px}
  .fds-btn{border:1px solid ButtonBorder;background:ButtonFace;color:ButtonText;
    background-image:none}
  .fds-btn--solid,.fds-btn:not(.fds-btn--outline):not(.fds-btn--text){
    background:ButtonText;color:ButtonFace;forced-color-adjust:none}
  .fds-btn:disabled,.fds-btn[aria-disabled="true"]{color:GrayText;border-color:GrayText;
    opacity:1}
  .fds-card,.fds-panel,.fds-sheet,.fds-modal,.fds-drawer,.fds-popover,.fds-menu,
  .fds-listbox,.fds-tablewrap,.fds-banner,.fds-alert,.fds-toast{
    border:1px solid CanvasText}
  /* State was a tint; a tint is now Canvas. Give it an edge and keep the word. */
  .fds-chip,.fds-filterchip{border:1px solid CanvasText;forced-color-adjust:none;
    background:Canvas;color:CanvasText}
  .fds-chip--solid,.fds-chip--volt{background:CanvasText;color:Canvas}
  .fds-table tbody tr[aria-selected="true"]{outline:2px solid Highlight;outline-offset:-2px}
  .fds-table thead th{border-block-end:1px solid CanvasText}
  .fds-input,.fds-textarea,.fds-select,.fds-datefield{border:1px solid CanvasText}
  .fds-choice__box,.fds-switch__track{border:1px solid CanvasText;forced-color-adjust:none}
  .fds-choice input:checked + .fds-choice__box,
  .fds-switch input:checked + .fds-switch__track{background:Highlight;border-color:Highlight}
  .fds-switch input:checked + .fds-switch__track::after{background:Canvas}
  .fds-scrim{background:Canvas;opacity:.85}
  /* Data keeps its palette. Chrome does not. */
  .fds-bars > i,.fds-dist > i,.fds-part > i,.fds-heat i,.fds-target__fill,
  .fds-waterfall i,.fds-scatter i,.fds-spark path,.fds-funnel__bar i,
  .fds-legend i,.fds-flow__link path,.fds-matrix td[data-level],
  .fds-plan__cell[data-state]{forced-color-adjust:none}
  .fds-heat i,.fds-plan__cell{border:1px solid CanvasText}
  .fds-bar > i,.fds-track > i,.fds-steps > i::after{background:Highlight;
    forced-color-adjust:none}
  .fds-spinner{border-color:CanvasText;border-top-color:transparent}
  .fds-skeleton{background:Canvas;border:1px dashed GrayText;animation:none}
  .fds-slot{border-color:CanvasText}
}

/* Density can take a sensible first guess from the device rather than making
   every shop tablet start at desk metrics. It is only a DEFAULT — an explicit
   attribute always wins, and the person can always override it. */
@media (pointer: coarse){
  :root:not([data-density]){
    --fds-h-sm:max(2.25rem,36px);   --fds-h-md:max(2.75rem,44px);
    --fds-h-lg:max(3.25rem,50px);   --fds-h-xl:max(3.875rem,58px);
    --fds-row-pad:var(--fds-sp-7);  --fds-card-pad:1.125rem;
    --fds-tap:max(2.75rem,44px);    --fds-mono-wdth:100;
    --fds-ui-display:var(--fds-fs-1500); --fds-ui-title:var(--fds-fs-1200);
    --fds-ui-heading:var(--fds-fs-900);  --fds-ui-body-size:var(--fds-fs-700);
    --fds-ui-small-size:var(--fds-fs-400);
    --fds-ui-caption-size:var(--fds-fs-300);
    --fds-ui-micro-size:var(--fds-fs-200);
  }
}

/* --- 27 · SPECIALISED CONTROLS -------------------------------------------

   Controls that know something about the trade. Everything here is built from
   the primitives above and adds domain behaviour, not domain styling.

   ── TWO OF THESE CROSS THE PRODUCT'S OWN LINE ───────────────────────────
   The invariant is: no specification values on any customer-facing surface. No
   torque, no pressures, no sizes, no "this fits". Those are engineering claims
   and they carry engineering liability.

   A "fitment selector" that answers *what should go on this car* is exactly
   that claim, and it cannot exist in this system. A control that records *what
   was actually fitted* is the opposite — it is a record of fact, and it is the
   product. The component below is therefore `.fds-fitment-record`, it has no
   suggestion behaviour, no compatibility check and no green tick, and it will
   accept a combination a catalogue would reject, because the shop fitted it and
   the record's job is to say so.

   The same split governs tread depth. Recording 4.2mm is a measurement.
   Rendering it green or captioning it "legal" is a judgement that varies by
   jurisdiction, changes without notice, and would be the product's opinion
   about roadworthiness. `.fds-tread` records the number and renders no verdict.
   ------------------------------------------------------------------------*/

/* 1–2 · DATE AND DATE RANGE ----------------------------------------------
   The typed field stays primary. Range adds a second field and a shared
   calendar; the two ends are one control and one label, never two fields that
   happen to sit together. */
.fds-daterange{display:flex;align-items:center;gap:var(--fds-sp-4);
  border:var(--fds-border-w) solid var(--fds-border-control);
  border-radius:var(--fds-r-field);background:var(--fds-sunk);
  padding-inline:var(--fds-sp-5);min-height:var(--fds-h-md)}
.fds-daterange:focus-within{border-color:var(--fds-accent-mark);
  box-shadow:0 0 0 3px var(--fds-focus-soft)}
.fds-daterange input{flex:1;min-width:0;width:100%;border:0;background:none;
  color:inherit;font:inherit;font-size:max(var(--fds-ui-body-size),16px);padding:0;outline:0}
.fds-daterange__sep{color:var(--fds-text-muted);flex-shrink:0}
.fds-daterange__presets{display:flex;flex-wrap:wrap;gap:var(--fds-sp-3);
  margin-top:var(--fds-sp-4)}
/* A range picked from a preset says which preset, so the person can see the
   filter they are looking at rather than reconstructing it from two dates. */
.fds-calendar__day[data-in-range]{background:var(--fds-accent-tint);border-radius:0}
.fds-calendar__day[data-range-start]{border-start-start-radius:var(--fds-r-ctl-sm);
  border-end-start-radius:var(--fds-r-ctl-sm)}
.fds-calendar__day[data-range-end]{border-start-end-radius:var(--fds-r-ctl-sm);
  border-end-end-radius:var(--fds-r-ctl-sm)}

/* 3 · TIME AND DURATION ---------------------------------------------------
   Duration is not a time of day and must not use a clock picker. It is a
   number with a unit, and on the floor it is usually one of five values, so it
   gets presets plus a free field rather than a spinner. */
.fds-duration{display:flex;flex-wrap:wrap;align-items:center;gap:var(--fds-sp-4)}
.fds-duration__chips{display:flex;gap:var(--fds-sp-3);flex-wrap:wrap}
.fds-duration__out{font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-variant-numeric:tabular-nums;
  font-size:var(--fds-ui-body-size);color:var(--fds-text-muted)}

/* 4–5 · UNIT INPUTS -------------------------------------------------------
   The unit lives in the control, not in the label, so it survives being read
   back on a printed record. Currency is a prefix; everything else is a suffix.
   The field is right-aligned and tabular because the value gets compared. */
.fds-unitfield{position:relative;display:flex;align-items:center;
  border:var(--fds-border-w) solid var(--fds-border-control);
  border-radius:var(--fds-r-field);background:var(--fds-sunk);
  min-height:var(--fds-h-md);overflow:hidden}
.fds-unitfield:focus-within{border-color:var(--fds-accent-mark);
  box-shadow:0 0 0 3px var(--fds-focus-soft)}
.fds-unitfield__affix{flex-shrink:0;padding-inline:var(--fds-sp-5);
  color:var(--fds-text-muted);font-size:var(--fds-ui-small-size);
  background:var(--fds-surface);align-self:stretch;display:flex;align-items:center}
.fds-unitfield input{flex:1;min-width:0;border:0;background:none;color:inherit;
  font-family:var(--fds-font-mono);font-variation-settings:'wdth' var(--fds-mono-wdth);
  font-variant-numeric:tabular-nums;text-align:end;
  font-size:max(var(--fds-ui-body-size),16px);
  padding-inline:var(--fds-sp-5);outline:0;-moz-appearance:textfield}
.fds-unitfield input::-webkit-outer-spin-button,
.fds-unitfield input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}

/* 8–9 · ENTITY AND VEHICLE PICKER -----------------------------------------
   An entity picker searches records and returns one. It shows enough of each
   result to tell two similar ones apart — which for a customer means the
   second line, not the name.

   The vehicle picker offers three ways in because a shop has three: the VIN
   from the door jamb, the plate from the front of the car, or year/make/model
   from the person at the counter. They are tabs on one control, not three
   separate fields, because only one is used per vehicle. */
.fds-picker{position:relative}
.fds-picker__result{display:flex;align-items:center;gap:var(--fds-sp-5);
  padding:var(--fds-sp-4) var(--fds-sp-5);border-radius:var(--fds-r-ctl-sm);
  min-height:var(--fds-tap);cursor:pointer}
.fds-picker__result:hover,.fds-picker__result.is-active{background:var(--fds-hover-wash)}
.fds-picker__main{flex:1;min-width:0}
.fds-picker__name{font-size:var(--fds-ui-body-size);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.fds-picker__sub{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fds-picker__chosen{display:flex;align-items:center;gap:var(--fds-sp-5);
  padding:var(--fds-sp-5);border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-ctl-md);background:var(--fds-raised)}

.fds-vehicle__tabs{margin-bottom:var(--fds-sp-5)}
.fds-vehicle__ymm{display:grid;gap:var(--fds-sp-4);
  grid-template-columns:repeat(auto-fit,minmax(7rem,1fr))}
/* A VIN is 17 characters with no I, O or Q. The field is mono and wide-tracked
   so a person can check it character by character against the jamb. */
.fds-vin{font-family:var(--fds-font-mono);font-variation-settings:'wdth' 112.5;
  letter-spacing:.12em;text-transform:uppercase}

/* 10 · TIRE SIZE ----------------------------------------------------------
   Three numbers and a letter, entered as three fields rather than one string,
   because "225/45R17" typed freehand is four ways to get it wrong. The control
   assembles the canonical string; the person never types a slash. */
.fds-sizefield{display:flex;align-items:center;gap:var(--fds-sp-3);flex-wrap:wrap}
.fds-sizefield input{width:4.5rem;text-align:center;font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-variant-numeric:tabular-nums}
.fds-sizefield__sep{color:var(--fds-text-muted);font-family:var(--fds-font-mono)}
.fds-sizefield__out{margin-inline-start:var(--fds-sp-4);font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-size:var(--fds-ui-body-size)}

/* 11 · FITMENT RECORD — records, never recommends. See the note at the top. */
.fds-fitment{display:grid;gap:var(--fds-sp-5)}
.fds-fitment__note{display:flex;gap:var(--fds-sp-4);align-items:flex-start;
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);line-height:1.5}

/* 25 · TREAD DEPTH --------------------------------------------------------
   Four positions, three points across each tread. Records millimetres and
   renders no verdict — see the note at the top of this section. The wear
   pattern is the diagnostic value: three equal numbers mean something
   different from a high outer and a low inner, and averaging destroys it. */
.fds-tread{display:grid;gap:var(--fds-sp-5);
  grid-template-columns:repeat(auto-fit,minmax(min(11rem,100%),1fr))}
.fds-tread__pos{border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-ctl-md);padding:var(--fds-sp-5);background:var(--fds-raised)}
.fds-tread__label{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);
  text-transform:uppercase;letter-spacing:.08em;margin-bottom:var(--fds-sp-4)}
.fds-tread__row{display:flex;gap:var(--fds-sp-3)}
.fds-tread__row input{width:100%;min-width:0;text-align:center;
  font-family:var(--fds-font-mono);font-variation-settings:'wdth' var(--fds-mono-wdth);
  font-variant-numeric:tabular-nums;
  background:var(--fds-sunk);color:var(--fds-text);
  border:var(--fds-border-w) solid var(--fds-border-control);
  border-radius:var(--fds-r-ctl-sm);min-height:var(--fds-h-md);
  font-size:max(var(--fds-ui-body-size),16px)}
.fds-tread__row input:focus{outline:0;border-color:var(--fds-accent-mark);
  box-shadow:0 0 0 3px var(--fds-focus-soft)}
.fds-tread__pts{display:flex;gap:var(--fds-sp-3);margin-top:var(--fds-sp-3)}
.fds-tread__pts span{flex:1;text-align:center;font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted)}
/* A profile of the three readings, drawn to scale, banded against THE SHOP'S
   OWN THRESHOLDS.

   This is the reconciliation of the liability rule with a gauge that everybody
   in the trade already reads in colour. The bands are legitimate on three
   conditions, and the component enforces all three:

     1. THE THRESHOLDS BELONG TO THE SHOP, not to the product. They come in on
        `data-monitor` and `data-replace` and have no defaults — a component
        with no thresholds set renders every bar neutral, exactly as before.
     2. THE WORDING IS OPERATIONAL, NEVER LEGAL. "Below this shop's replace
        threshold." Never "illegal", "unsafe", "failed" or "roadworthy" — those
        vary by jurisdiction, change without notice, and would be this
        product's opinion about a vehicle.
     3. COLOUR IS NEVER ALONE. The measurement is always shown as a number and
        the band is always named in text. Amber-and-red is the exact pairing
        the data-viz ramp excludes for colour-vision reasons, so here it is a
        second channel on top of a number, never the channel itself.

   What the record stores is unchanged: three millimetre readings and the
   threshold that was in force when they were taken. The colour is a rendering
   of the shop's own policy, and the policy is part of the record. */
.fds-tread__profile{display:flex;align-items:flex-end;gap:2px;height:2.25rem;
  margin-top:var(--fds-sp-4)}
.fds-tread__profile i{flex:1;background:var(--fds-text-muted);opacity:.35;
  border-radius:2px 2px 0 0;height:var(--_v,0%);
  transition:height var(--fds-dur-slow) var(--fds-ease-data),
             background-color var(--fds-dur-base) var(--fds-ease-standard)}
.fds-tread__profile i[data-band="monitor"]{background:var(--fds-wn);opacity:.85}
.fds-tread__profile i[data-band="replace"]{background:var(--fds-dg);opacity:.9}
.fds-tread__profile i[data-band="ok"]{background:var(--fds-ok);opacity:.7}
/* The band, stated. Colour never carries this on its own. */
.fds-tread__band{display:flex;align-items:center;gap:var(--fds-sp-3);
  margin-top:var(--fds-sp-4);font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted);min-height:1.1rem}
.fds-tread__band[data-band="monitor"]{color:var(--fds-wn-mark)}
.fds-tread__band[data-band="replace"]{color:var(--fds-dg-mark);font-weight:500}
.fds-tread__pos[data-band="replace"]{border-color:var(--fds-dg-mark)}
.fds-tread__pos[data-band="monitor"]{border-color:var(--fds-wn-mark)}
.fds-tread__thresholds{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);
  margin-top:var(--fds-sp-4)}

/* 14 · APPOINTMENT SLOTS --------------------------------------------------
   Slots are a grid, not a dropdown: the shape of the day is the information.
   A full morning and an empty afternoon is visible at a glance and invisible
   in a list. */
.fds-slots{display:grid;gap:var(--fds-sp-3);
  grid-template-columns:repeat(auto-fill,minmax(5.5rem,1fr))}
.fds-slot{min-height:var(--fds-tap);border-radius:var(--fds-r-ctl-md);
  border:var(--fds-border-w) solid var(--fds-border);background:var(--fds-surface);
  color:var(--fds-text);font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-variant-numeric:tabular-nums;
  font-size:var(--fds-ui-small-size);cursor:pointer;display:flex;
  flex-direction:column;align-items:center;justify-content:center;gap:1px;
  transition:background-color var(--fds-dur-fast),border-color var(--fds-dur-fast)}
.fds-slot small{font-family:var(--fds-font-sans);font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted)}
.fds-slot:hover{border-color:var(--fds-border-control)}
.fds-slot[aria-pressed="true"]{background:var(--fds-accent);color:var(--fds-accent-ink);
  border-color:var(--fds-accent)}
.fds-slot[aria-pressed="true"] small{color:inherit;opacity:.8}
.fds-slot:disabled{opacity:.4;cursor:not-allowed;
  background:repeating-linear-gradient(45deg,var(--fds-surface) 0 4px,
             var(--fds-hairline) 4px 8px)}

/* 15 · TIMER --------------------------------------------------------------
   A running job on the floor. Big, mono, tabular, and it does not tick a
   layout: the digits are tabular so nothing reflows once a second. Past the
   estimate it does not turn red — it states the overrun, because "12 minutes
   over" is actionable and a red number is only alarming. */
.fds-timer{display:flex;flex-direction:column;gap:var(--fds-sp-4)}
.fds-timer__face{display:flex;align-items:baseline;gap:var(--fds-sp-4)}
.fds-timer__digits{font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-variant-numeric:tabular-nums;
  font-size:clamp(2.25rem,1.7rem + 2vw,3.25rem);font-weight:500;letter-spacing:-.03em;
  line-height:1;font-feature-settings:'tnum'}
.fds-timer__unit{font-size:var(--fds-ui-small-size);color:var(--fds-text-muted)}
.fds-timer__over{color:var(--fds-wn-mark);font-size:var(--fds-ui-small-size)}
/* Three parts, because "how long has it run" and "how long was it meant to"
   are different facts and the gap between them is the one people act on. The
   estimate is a mark on the track, not a second bar. */
.fds-timer__ring{position:relative;width:100%;height:.5rem;
  border-radius:var(--fds-r-pill);background:var(--fds-surface);overflow:hidden}
.fds-timer__fill{position:absolute;inset-block:0;inset-inline-start:0;
  background:var(--fds-accent);width:var(--_v,0%);transition:width 1s linear}
.fds-timer__overrun{position:absolute;inset-block:0;inset-inline-start:var(--_est,100%);
  background:var(--fds-wn);width:var(--_over,0%);transition:width 1s linear}
.fds-timer__estmark{position:absolute;inset-block:-2px;
  inset-inline-start:var(--_est,100%);width:2px;background:var(--fds-text)}
.fds-timer[data-state="paused"] .fds-timer__digits{color:var(--fds-text-muted)}

/* 16 · QUEUE ITEM ---------------------------------------------------------
   The same record at three sizes, sharing the morph grammar. Micro is what it
   looks like in a stack or under a finger; card is resting; expanded is opened
   in place. */
.fds-queue{display:flex;flex-direction:column;gap:var(--fds-sp-4)}
.fds-queueitem{background:var(--fds-raised);border:var(--fds-border-w) solid var(--fds-border);
  overflow:hidden;
  transition:max-height var(--fds-dur-slow) var(--fds-ease-emphasized),
             padding var(--fds-dur-slow) var(--fds-ease-emphasized),
             border-radius var(--fds-dur-slow) var(--fds-ease-emphasized)}
.fds-queueitem[data-form="micro"]{max-height:3rem;padding:var(--fds-sp-4) var(--fds-sp-5);
  border-radius:var(--fds-r-ctl-md)}
.fds-queueitem[data-form="card"]{max-height:11rem;padding:var(--fds-card-pad);
  border-radius:var(--fds-r-card)}
.fds-queueitem[data-form="expanded"]{max-height:34rem;padding:var(--fds-card-pad);
  border-radius:var(--fds-r-card)}
.fds-queueitem__head{display:flex;align-items:center;gap:var(--fds-sp-4);min-width:0}
.fds-queueitem__title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;font-size:var(--fds-ui-body-size);font-weight:500}
.fds-queueitem__body{margin-top:var(--fds-sp-5)}
.fds-queueitem[data-form="micro"] .fds-queueitem__body{opacity:0}
.fds-queueitem__detail{opacity:0;transform:translateY(.35rem);
  transition:opacity var(--fds-dur-base) var(--fds-ease-standard) var(--fds-dur-base),
             transform var(--fds-dur-base) var(--fds-ease-standard) var(--fds-dur-base)}
.fds-queueitem[data-form="expanded"] .fds-queueitem__detail{opacity:1;transform:none}
/* A dwell time that has crossed its threshold is stated, not coloured in. */
.fds-queueitem__dwell{font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' 75;font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted)}
.fds-queueitem[data-dwell="over"] .fds-queueitem__dwell{color:var(--fds-wn-mark)}

/* 17 · BAY PICKER ---------------------------------------------------------
   A bay has a capability — hoist, balance, align — and a job has a need. The
   picker is a CAPABILITY MATCH, not a free/busy list: offering a free align
   bay to a job needing a hoist wastes a walk and a conversation.

   Three states, and the last two are different on purpose. `suggested` is the
   system's pick and is labelled as a suggestion, because the person on the
   floor knows things the board does not. `blocked` is shown rather than
   hidden, with its reason, so nobody hunts for a bay that is standing right
   there and apparently unused. */
.fds-bays{display:grid;gap:var(--fds-sp-4);
  grid-template-columns:repeat(auto-fit,minmax(min(11rem,100%),1fr))}
.fds-bay{display:flex;flex-direction:column;gap:var(--fds-sp-3);text-align:start;
  padding:var(--fds-sp-5);min-height:var(--fds-h-xl);cursor:pointer;
  border:var(--fds-border-w) solid var(--fds-border);border-radius:var(--fds-r-card);
  background:var(--fds-raised);color:var(--fds-text);font-family:inherit;
  transition:border-color var(--fds-dur-fast),background-color var(--fds-dur-fast)}
.fds-bay__name{font-size:var(--fds-ui-body-size);font-weight:500}
.fds-bay__kind{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)}
.fds-bay__free{font-family:var(--fds-font-mono);font-variation-settings:'wdth' 75;
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)}
.fds-bay__why{font-size:var(--fds-ui-micro-size);color:var(--fds-wn-mark)}
.fds-bay:hover{border-color:var(--fds-border-control)}
.fds-bay[data-state="suggested"]{border-color:var(--fds-accent-mark);
  border-width:var(--fds-border-thick);background:var(--fds-accent-tint)}
.fds-bay[data-state="blocked"]{opacity:.55;cursor:not-allowed;
  background:repeating-linear-gradient(45deg,var(--fds-surface) 0 5px,
             var(--fds-hairline) 5px 10px)}
.fds-bay[aria-pressed="true"]{border-color:var(--fds-accent);
  border-width:var(--fds-border-thick)}

/* 18 · SPOT PICKER — the wall ---------------------------------------------
   `.fds-plan` in selection mode. The plan is drawn to the real arrangement, so
   picking a spot on it is the same motion as walking to it.

   THERE IS NO SPOT DROPDOWN. The wall is addressable: type a code — `b7`,
   `d12` — and the cursor lands on that spot, because the codes are painted on
   the physical shelves and the fastest input device in the building is already
   in the technician's hand. A dropdown of ninety spots is a list nobody reads
   and a place a set gets filed wrongly.

   One set, one spot. There is no capacity field anywhere in this system: free
   spots are counted by looking, and that count is the headline number of the
   business. */
.fds-wall{display:flex;flex-direction:column;gap:var(--fds-sp-5)}
.fds-wall__head{display:flex;align-items:center;gap:var(--fds-sp-5);flex-wrap:wrap}
.fds-wall__free{font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-variant-numeric:tabular-nums;
  font-size:var(--fds-ui-heading);font-weight:500}
.fds-wall__addr{width:6.5rem;text-transform:uppercase;text-align:center;
  font-family:var(--fds-font-mono);font-variation-settings:'wdth' var(--fds-mono-wdth);
  letter-spacing:.1em}
/* The beam: a found set lights its spot on the wall. The search result IS the
   map, not a list somebody then has to translate into a location. */
.fds-plan__cell[data-lit="true"]{outline:var(--fds-border-thick) solid var(--fds-accent);
  outline-offset:2px;animation:fds-beam var(--fds-dur-slower) var(--fds-ease-standard)}
@keyframes fds-beam{0%{outline-color:transparent}40%{outline-color:var(--fds-accent)}}
.fds-plan--pick .fds-plan__cell{cursor:pointer}
.fds-plan--pick .fds-plan__cell[aria-pressed="true"]{background:var(--fds-accent);
  color:var(--fds-accent-ink);border-color:var(--fds-accent)}
.fds-plan--pick .fds-plan__cell:disabled{cursor:not-allowed;opacity:.45}
.fds-planlegend{display:flex;flex-wrap:wrap;gap:var(--fds-sp-5);
  margin-top:var(--fds-sp-5);font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted)}
.fds-planlegend span{display:inline-flex;align-items:center;gap:var(--fds-sp-3)}
.fds-planlegend i{width:.75rem;height:.75rem;border-radius:3px;
  border:1px solid var(--fds-border)}

/* 19 · CAMERA CAPTURE -----------------------------------------------------
   A photo taken at intake is evidence, so the control shows what was captured
   and lets it be retaken, never silently replaced. */
.fds-capture{display:grid;gap:var(--fds-sp-4);
  grid-template-columns:repeat(auto-fill,minmax(6rem,1fr))}
.fds-capture__slot{aspect-ratio:4/3;border-radius:var(--fds-r-ctl-md);
  border:2px dashed var(--fds-border-control);background:var(--fds-sunk);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:var(--fds-sp-3);color:var(--fds-text-muted);cursor:pointer;
  font-size:var(--fds-ui-micro-size);text-align:center;padding:var(--fds-sp-3)}
.fds-capture__slot[data-filled="true"]{border-style:solid;border-color:var(--fds-border);
  background:var(--fds-surface);color:var(--fds-text)}
.fds-capture__slot:hover{border-color:var(--fds-accent-mark);color:var(--fds-accent-mark)}

/* 6 · UPLOAD QUEUE --------------------------------------------------------
   Per-file state, because one failure in eight must not read as eight
   failures, and a retry must be per file. */
.fds-uploadq{display:flex;flex-direction:column;gap:var(--fds-sp-4)}
.fds-uploadq__row{display:flex;align-items:center;gap:var(--fds-sp-5);
  padding:var(--fds-sp-4) var(--fds-sp-5);border-radius:var(--fds-r-ctl-md);
  border:var(--fds-border-w) solid var(--fds-border);background:var(--fds-raised)}
.fds-uploadq__main{flex:1;min-width:0}
.fds-uploadq__name{font-size:var(--fds-ui-small-size);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.fds-uploadq__row[data-state="error"]{border-color:var(--fds-dg-mark)}
.fds-uploadq__row[data-state="done"] .fds-bar{display:none}

/* 21 · EDITABLE CELL ------------------------------------------------------
   A cell that can be changed looks changeable before it is touched, or nobody
   finds it. The affordance is a quiet underline, not a full field border,
   because forty bordered cells is a form, not a table. */
.fds-cell{position:relative;display:flex;align-items:center;gap:var(--fds-sp-3);
  width:100%;min-height:var(--fds-tap);padding:var(--fds-sp-2) var(--fds-sp-3);
  border:0;border-bottom:1px dashed var(--fds-border-control);
  background:none;color:inherit;font:inherit;text-align:inherit;cursor:text;
  border-radius:var(--fds-r-mark) var(--fds-r-mark) 0 0}
.fds-cell:hover{background:var(--fds-hover-wash)}
.fds-cell[data-editing="true"]{border-bottom-style:solid;
  border-bottom-color:var(--fds-accent-mark);background:var(--fds-sunk)}
.fds-cell[data-dirty="true"]::after{content:'';position:absolute;top:2px;
  inset-inline-end:2px;width:.375rem;height:.375rem;border-radius:50%;
  background:var(--fds-accent-mark)}
.fds-cell[aria-readonly="true"]{border-bottom-color:transparent;cursor:default}
.fds-cell[aria-readonly="true"]:hover{background:none}

/* 22 · SCAN TRIGGER -------------------------------------------------------
   The primary path on the floor. Large, obvious, and always paired with a
   manual-entry fallback — a scanner that will not read a scuffed barcode is
   the normal case, not the edge case. */
.fds-scan{display:flex;align-items:center;justify-content:center;gap:var(--fds-sp-5);
  width:100%;min-height:var(--fds-h-xl);border-radius:var(--fds-r-ctl-xl);
  border:var(--fds-border-thick) solid var(--fds-accent-mark);
  background:var(--fds-accent-tint);color:var(--fds-accent-mark);
  font-family:inherit;font-size:var(--fds-ui-body-size);font-weight:500;cursor:pointer}
.fds-scan:active{transform:scale(.99)}
.fds-scan[data-state="scanning"]{border-style:dashed}
.fds-scan__fallback{display:block;width:100%;text-align:center;
  margin-top:var(--fds-sp-4);font-size:var(--fds-ui-small-size)}

/* 23 · SAVE AND SYNC ------------------------------------------------------
   Three states with three different meanings, and they are never collapsed:
   saved locally, sent, and confirmed by the server. A shop on a bad connection
   needs to know which one it is holding. */
.fds-sync{display:inline-flex;align-items:center;gap:var(--fds-sp-4);
  font-size:var(--fds-ui-small-size);color:var(--fds-text-muted)}
.fds-sync__dot{width:.5rem;height:.5rem;border-radius:50%;flex-shrink:0;
  background:var(--fds-text-muted)}
.fds-sync[data-state="local"] .fds-sync__dot{background:var(--fds-wn-mark)}
.fds-sync[data-state="sending"] .fds-sync__dot{background:var(--fds-accent-mark);
  animation:fds-pulse 1.4s var(--fds-ease-standard) infinite}
.fds-sync[data-state="synced"] .fds-sync__dot{background:var(--fds-ok-mark)}
.fds-sync[data-state="failed"] .fds-sync__dot{background:var(--fds-dg-mark)}
.fds-sync[data-state="failed"]{color:var(--fds-dg-mark)}

/* 13 · REASON REQUIRED ----------------------------------------------------
   Attached to any action that overrides a rule or edits a closed record. The
   reason is part of the action, not a follow-up prompt: a dialog that asks
   afterwards gets dismissed and the audit trail gets a blank. */
.fds-reason{border:var(--fds-border-w) solid var(--fds-wn-mark);
  border-radius:var(--fds-r-card);padding:var(--fds-sp-5);
  background:var(--fds-wn-tint);display:flex;flex-direction:column;gap:var(--fds-sp-4)}
.fds-reason__why{font-size:var(--fds-ui-small-size);color:var(--fds-wn-mark)}
.fds-reason select,.fds-reason textarea{background:var(--fds-raised)}

/* 20 · REVIEW AND CONFIRM -------------------------------------------------
   The last screen before a record becomes permanent. Every field is shown with
   the way back to it, because a summary you cannot correct from is a receipt,
   not a review. */
.fds-review{border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-card);background:var(--fds-raised);overflow:hidden}
.fds-review__row{display:flex;align-items:baseline;gap:var(--fds-sp-5);
  padding:var(--fds-sp-5) var(--fds-sp-6);
  border-block-end:var(--fds-border-w) solid var(--fds-hairline)}
.fds-review__row:last-of-type{border-block-end:0}
.fds-review__k{width:9rem;flex-shrink:0;font-size:var(--fds-ui-caption-size);
  color:var(--fds-text-muted)}
.fds-review__v{flex:1;min-width:0;font-size:var(--fds-ui-body-size);overflow-wrap:anywhere}
.fds-review__edit{flex-shrink:0}
.fds-review__foot{padding:var(--fds-sp-5) var(--fds-sp-6);background:var(--fds-surface);
  border-block-start:var(--fds-border-w) solid var(--fds-border)}
.fds-review__row[data-missing="true"] .fds-review__v{color:var(--fds-dg-mark)}

/* 24 · PRINT AND LABEL — "the stub" ---------------------------------------
   4" x 6" thermal stock, drawn to its real proportion so the preview is a
   scale drawing rather than an impression. ONE LABEL PER TIRE.

   THE FLAG IS NOT DERIVED FROM THE TREAD. This was wrong in the first pass and
   the real labels disprove it: a set came through with 7 / 7 / 7 on the left
   rear — perfectly good tread — flagged REPLACE: IMPACT BULGE. No threshold
   computation on earth produces that. A flag is a technician's observation
   with a stated reason; low tread is one possible reason among several, and
   the commonest serious ones (sidewall bulge, impact damage, repair limit,
   age) leave the tread numbers looking healthy.

   So `data-flag` carries a REASON, is set by a person, and is independent of
   anything the tread bands compute. The bands may prompt; they never decide.

   THE LABEL CARRIES NO SPOT CODE and no barcode. Sets get shuffled between
   spots, so a label naming a spot the set has left is confidently wrong. The
   barcode was occupying a quarter of the label to encode a plate already
   printed on it in far larger type.
   ------------------------------------------------------------------------*/
.fds-stub{flex-shrink:0;width:12rem;aspect-ratio:4/6;
  background:#FFFFFF;color:#000000;border:1px solid #C8C8C8;border-radius:2px;
  padding:.6rem .65rem;display:flex;flex-direction:column;gap:.4rem;
  font-family:var(--fds-font-sans);font-size:.5rem;line-height:1.3;
  font-variant-numeric:tabular-nums;forced-color-adjust:none}

/* Corner and season, spelled out. "LF" is shop shorthand that a customer
   cannot read, and the stub is handed to customers. */
.fds-stub__head{display:flex;justify-content:space-between;align-items:baseline;
  gap:.4rem;font-size:.62rem;font-weight:500}

/* The plate. Boxed and by far the largest thing on the label, because it is
   the one field a person calls out across a rack aisle. */
.fds-stub__plate{border:1.5px solid #000;padding:.3rem .2rem;text-align:center;
  font-size:1.35rem;font-weight:700;letter-spacing:.03em;line-height:1}
.fds-stub__owner{font-size:.62rem}

/* Three readings, boxed equally. The number is the content; the position is
   its caption. Boxes rather than a row of figures, because on a stack of four
   labels the eye needs somewhere to land. */
.fds-stub__tread{display:flex;gap:.3rem;text-align:center}
.fds-stub__tread > div{flex:1;border:1px solid #000;padding:.18rem .1rem .12rem}
.fds-stub__tread b{display:block;font-size:.95rem;font-weight:700;line-height:1}
.fds-stub__tread i{font-style:normal;font-size:.38rem;letter-spacing:.06em}
/* The unit, stated ONCE. The sample labels print bare numbers, and a bare "3"
   is ambiguous in a way that matters: 3/32" and 3 mm are different tires. */
.fds-stub__unit{font-size:.38rem;letter-spacing:.06em;opacity:.75;text-align:center}

.fds-stub__spec{font-size:.5rem;line-height:1.5}
.fds-stub__spec div{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* The flag panel always prints, empty or not. A zone that only appears when
   flagged is a zone nobody learns to look at.

   Reversed ink, never red: thermal stock is one colour, so a red bar prints
   grey and disappears. That is also why this preview is black on white in both
   themes — it is a drawing of a physical object, not a UI surface. */
.fds-stub__flag{margin-top:auto;border:1.5px solid #000;min-height:1.45rem;
  padding:.2rem .3rem;display:flex;align-items:center;justify-content:center;
  text-align:center;font-size:.5rem;letter-spacing:.04em}
.fds-stub__flag[data-flag]{background:#000;color:#FFF;font-weight:700;
  font-size:.66rem;letter-spacing:.06em}
.fds-stub__foot{font-size:.38rem;opacity:.7;text-align:center}
.fds-stub--lg{width:16rem;font-size:.62rem}
.fds-stub--lg .fds-stub__plate{font-size:1.75rem}
.fds-stub--lg .fds-stub__tread b{font-size:1.2rem}

/* --- 28 · MOTION LIBRARY -------------------------------------------------

   The inventory is about 130 animations. This library is ten primitives and
   thirteen composed moments, because 128 of those 130 are the same handful of
   primitives recomposed, and a catalogue of 130 named animations is a
   catalogue nobody can hold in their head. People then invent a 131st.

   PRIMITIVES — everything is built from these:
     fade · scale · translate · rotate · stroke-draw · size-reveal ·
     colour · ring · icon-swap · reposition

   MOMENTS — the composed set that ships, named after what happened rather
   than what moved:
     mark (check / warn / error) · shake · nudge · pulse-once · collapse ·
     expand · disclose · swap · flash · countdown · lift · settle · reject

   ── THE RULES ───────────────────────────────────────────────────────────
   · Motion reports; it does not decorate. If nothing changed, nothing moves.
   · CONTINUOUS motion is reserved for things that are genuinely still
     happening: a spinner, a live status dot, a syncing pair. A warning that
     pulses forever is a warning people stop seeing, and it is the single most
     common way an interface trains its users to ignore it. Warnings get ONE
     pulse.
   · Animation confirms a destructive result; it never replaces the
     confirmation. A row that fades out beautifully after an unconfirmed delete
     is a beautifully deleted row.
   · Exit is faster than enter, always. See §25.
   · A number that a person is about to read does not animate.
   ------------------------------------------------------------------------*/
:root{
  /* Distances are px, not rem. Motion distance is perceptual and physical —
     the same reasoning that keeps shadows in px. An 8px entrance scaled to
     10px at 125% text would not read as "further", only as "slower". */
  --fds-move-micro:2px;    /* attention nudge, 1–2px moves          */
  --fds-move-enter:6px;    /* a component arriving, 4–8px           */
  --fds-move-panel:12px;   /* a toast or panel, 8–16px              */
  --fds-move-press:1px;    /* a control going down under a finger   */

  --fds-scale-enter:.96;   /* 0.92–0.98 → 1                         */
  --fds-scale-pop:.90;     /* a badge or mark landing               */
  --fds-scale-press:.985;
}

/* The press depth was 0.5px, which is below a device pixel on a 1x display
   and therefore rendered as nothing at all on exactly the shop tablets this
   system is for. 1px is the smallest move that actually exists. */
:root{ --fds-depress:var(--fds-move-press); }

/* --- 28.1 · PRIMITIVES --------------------------------------------------*/
@keyframes fds-m-in-up{from{opacity:0;transform:translateY(var(--_d,var(--fds-move-enter)))}
  to{opacity:1;transform:none}}
@keyframes fds-m-in-down{from{opacity:0;transform:translateY(calc(var(--_d,var(--fds-move-enter)) * -1))}
  to{opacity:1;transform:none}}
@keyframes fds-m-in-start{from{opacity:0;transform:translateX(calc(var(--_d,var(--fds-move-enter)) * -1))}
  to{opacity:1;transform:none}}
@keyframes fds-m-in-end{from{opacity:0;transform:translateX(var(--_d,var(--fds-move-enter)))}
  to{opacity:1;transform:none}}
@keyframes fds-m-scale-in{from{opacity:0;transform:scale(var(--_s,var(--fds-scale-enter)))}
  to{opacity:1;transform:none}}
@keyframes fds-m-pop{0%{transform:scale(var(--fds-scale-pop))}
  100%{transform:scale(1)}}
@keyframes fds-m-draw{to{stroke-dashoffset:0}}
@keyframes fds-m-shake{
  0%,100%{transform:translateX(0)}
  20%{transform:translateX(calc(var(--fds-move-enter) * -1))}
  40%{transform:translateX(var(--fds-move-enter))}
  60%{transform:translateX(calc(var(--fds-move-micro) * -1))}
  80%{transform:translateX(var(--fds-move-micro))}}
@keyframes fds-m-nudge{0%,100%{transform:translateX(0)}
  50%{transform:translateX(var(--fds-move-micro))}}
@keyframes fds-m-pulse-once{0%,100%{opacity:1;transform:scale(1)}
  45%{opacity:.5;transform:scale(.88)}}
@keyframes fds-m-flash{from{background-color:var(--fds-accent-tint)}
  to{background-color:transparent}}
@keyframes fds-m-collapse{to{opacity:0;transform:scale(.98);
  max-height:0;padding-block:0;margin-block:0;border-width:0}}
@keyframes fds-m-expand{from{opacity:0;max-height:0;padding-block:0;margin-block:0}}
@keyframes fds-m-countdown{from{transform:scaleX(1)}to{transform:scaleX(0)}}

/* Utility form. Re-trigger by removing and re-adding the attribute. */
.fds-m{animation-duration:var(--fds-dur-base);animation-fill-mode:both;
  animation-timing-function:var(--fds-ease-standard)}
.fds-m[data-m="in-up"]{animation-name:fds-m-in-up;animation-timing-function:var(--fds-ease-entrance)}
.fds-m[data-m="in-down"]{animation-name:fds-m-in-down;animation-timing-function:var(--fds-ease-entrance)}
.fds-m[data-m="in-start"]{animation-name:fds-m-in-start}
.fds-m[data-m="in-end"]{animation-name:fds-m-in-end}
.fds-m[data-m="scale-in"]{animation-name:fds-m-scale-in;animation-timing-function:var(--fds-ease-entrance)}
.fds-m[data-m="pop"]{animation-name:fds-m-pop;animation-duration:var(--fds-dur-fast);
  animation-timing-function:var(--fds-ease-entrance)}
.fds-m[data-m="shake"]{animation-name:fds-m-shake;animation-duration:var(--fds-dur-slow)}
.fds-m[data-m="nudge"]{animation-name:fds-m-nudge;animation-duration:var(--fds-dur-fast);
  animation-iteration-count:2}
.fds-m[data-m="pulse"]{animation-name:fds-m-pulse-once;animation-duration:var(--fds-dur-slow)}
.fds-m[data-m="flash"]{animation-name:fds-m-flash;animation-duration:var(--fds-dur-slower);
  animation-timing-function:var(--fds-ease-exit)}

/* --- 28.2 · THE MARK ----------------------------------------------------
   Confirmation, warning and failure as one component with three tones. The
   disc scales and fades in; the stroke draws after it lands, not during — a
   stroke drawing on a moving disc reads as a glitch rather than a gesture.

   This is the single most-used moment in the system, which is why it is one
   component and not three near-identical ones. */
.fds-mark{--_size:4.5rem;--_tone:var(--fds-ok-mark);--_tint:var(--fds-ok-tint);
  width:var(--_size);height:var(--_size);border-radius:50%;flex-shrink:0;
  background:var(--_tint);color:var(--_tone);
  display:flex;align-items:center;justify-content:center;
  animation:fds-m-scale-in var(--fds-dur-base) var(--fds-ease-entrance) both}
.fds-mark--wn{--_tone:var(--fds-wn-mark);--_tint:var(--fds-wn-tint)}
.fds-mark--dg{--_tone:var(--fds-dg-mark);--_tint:var(--fds-dg-tint)}
.fds-mark--accent{--_tone:var(--fds-accent-mark);--_tint:var(--fds-accent-tint)}
.fds-mark--sm{--_size:2.25rem}
.fds-mark--md{--_size:3.25rem}
.fds-mark--lg{--_size:6rem}
.fds-mark svg{width:52%;height:52%;stroke:currentColor;fill:none;
  stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
.fds-mark svg path,.fds-mark svg line{
  stroke-dasharray:var(--_len,40);stroke-dashoffset:var(--_len,40);
  animation:fds-m-draw var(--fds-dur-slow) var(--fds-ease-standard) forwards;
  /* after the disc has landed, never during */
  animation-delay:var(--fds-dur-base)}
.fds-mark svg path:nth-child(2),.fds-mark svg line:nth-child(2){
  animation-delay:calc(var(--fds-dur-base) + var(--fds-dur-fast))}

/* --- 28.3 · ICON SWAP ---------------------------------------------------
   Spinner → check, spinner → error, plus → check, pause → play, lock. One box,
   two children, the outgoing one leaves faster than the incoming one arrives.
   The box holds its size so nothing around it reflows mid-swap. */
.fds-swap{position:relative;display:inline-flex;align-items:center;
  justify-content:center;width:1.25em;height:1.25em;flex-shrink:0}
.fds-swap > *{position:absolute;inset:0;display:flex;align-items:center;
  justify-content:center;transition:opacity var(--fds-dur-fast) var(--fds-ease-standard),
  transform var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-swap > [data-when]{opacity:0;transform:scale(.8);pointer-events:none}
.fds-swap[data-state="idle"] > [data-when="idle"],
.fds-swap[data-state="busy"] > [data-when="busy"],
.fds-swap[data-state="ok"] > [data-when="ok"],
.fds-swap[data-state="error"] > [data-when="error"]{opacity:1;transform:none}
.fds-swap[data-state="ok"] > [data-when="ok"],
.fds-swap[data-state="error"] > [data-when="error"]{
  transition-duration:var(--fds-dur-base);
  transition-timing-function:var(--fds-ease-entrance)}

/* --- 28.4 · ROWS ARRIVING AND LEAVING -----------------------------------
   A removed row collapses its own height so the list closes rather than
   jumping. A row that only fades leaves a hole, and the hole is what makes a
   list feel broken. */
.fds-row-enter{animation:fds-m-expand var(--fds-dur-slow) var(--fds-ease-standard) both,
  fds-m-in-up var(--fds-dur-slow) var(--fds-ease-entrance) both;overflow:hidden}
.fds-row-leave{overflow:hidden;
  animation:fds-m-collapse var(--fds-dur-base) var(--fds-ease-exit) forwards}
/* Bulk removal fades everything together, THEN closes the layout once. Closing
   per row turns a tidy operation into a cascade. */
.fds-bulk-leave{transition:opacity var(--fds-dur-fast) var(--fds-ease-exit);opacity:0}

/* --- 28.5 · DISCLOSURE --------------------------------------------------*/
.fds-disclose{display:inline-flex;transition:transform var(--fds-dur-base) var(--fds-ease-standard)}
[aria-expanded="true"] > .fds-disclose,.fds-disclose[data-open="true"]{transform:rotate(90deg)}
.fds-disclose--180[data-open="true"]{transform:rotate(180deg)}
.fds-accordion{display:grid;grid-template-rows:0fr;
  transition:grid-template-rows var(--fds-dur-slow) var(--fds-ease-emphasized)}
.fds-accordion[data-open="true"]{grid-template-rows:1fr}
.fds-accordion > div{overflow:hidden;min-height:0}
.fds-accordion > div > *{opacity:0;
  transition:opacity var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-accordion[data-open="true"] > div > *{opacity:1;
  transition-delay:var(--fds-dur-fast)}

/* --- 28.6 · LIVE DATA ---------------------------------------------------
   A value that changed under someone's eyes gets one flash. It does not stay
   highlighted: a permanent highlight becomes a colour the person reads as
   meaning something about the value rather than about its age. */
.fds-flash{animation:fds-m-flash var(--fds-dur-slower) var(--fds-ease-exit)}
/* Numerals never bounce a layout. Tabular figures plus a fixed box. */
.fds-tick{font-variant-numeric:tabular-nums;font-feature-settings:'tnum';
  display:inline-block;min-width:var(--_w,auto);text-align:end}

/* --- 28.7 · UNDO COUNTDOWN ----------------------------------------------
   The line shrinks across the toast for exactly as long as the undo is alive.
   It is the only honest way to show a deadline the person did not set. */
.fds-countdown{height:2px;background:currentColor;opacity:.35;
  transform-origin:left;animation:fds-m-countdown linear forwards;
  animation-duration:var(--_for,6s)}
.fds-toast:hover .fds-countdown,.fds-toast:focus-within .fds-countdown{
  animation-play-state:paused}

/* --- 28.8 · RAISE -------------------------------------------------------
   Elevation as a moment. `data-level` steps a surface through the scale and
   the shadow is drawn to match — but the interesting part is WHICH CHANNEL
   MOVES, and it is not the same one in both themes.

   On day the surfaces are already near-white and cannot step up much further,
   so the shadow does the work. On night a shadow on a charcoal page is a
   smudge, so the surface lightens and the shadow only confirms it. Flip the
   theme under this component and the animation looks like two different
   animations, which is the point: elevation is a role, not a shadow.

   COST. Transitioning `box-shadow` repaints the element on every frame. That
   is fine for one card under a pointer and wrong for a list of forty. On a hot
   path, cross-fade two pre-rendered shadow layers instead — `.fds-raise--layered`
   below paints both shadows once and animates only opacity, which stays on the
   compositor. It costs a stacking context per card (the layers are
   pseudo-elements at z-index -1, so the card must isolate), which is why it is
   opt-in rather than the default. */
.fds-raise{
  transition:box-shadow var(--fds-dur-base) var(--fds-ease-standard),
             background-color var(--fds-dur-base) var(--fds-ease-standard),
             transform var(--fds-dur-base) var(--fds-ease-standard);
}
.fds-raise[data-level="0"]{box-shadow:none;background:var(--fds-raised)}
.fds-raise[data-level="1"]{box-shadow:var(--fds-e1),var(--fds-e-edge);
  background:var(--fds-raised)}
.fds-raise[data-level="2"]{box-shadow:var(--fds-e2),var(--fds-e-edge);
  background:var(--fds-floating)}
.fds-raise[data-level="3"]{box-shadow:var(--fds-e3),var(--fds-e-edge);
  background:var(--fds-floating)}
.fds-raise[data-level="4"]{box-shadow:var(--fds-e4),var(--fds-e-edge);
  background:var(--fds-floating)}
.fds-raise[data-level="drag"]{box-shadow:var(--fds-e-drag),var(--fds-e-edge);
  background:var(--fds-floating);transform:rotate(1.5deg) scale(1.02)}

/* Compositor-friendly variant for lists. Two stacked shadow layers, only
   opacity animates. */
.fds-raise--layered{position:relative;isolation:isolate}
.fds-raise--layered{box-shadow:none !important;transition:background-color var(--fds-dur-base)}
.fds-raise--layered::before,.fds-raise--layered::after{
  content:'';position:absolute;inset:0;border-radius:inherit;z-index:-1;
  pointer-events:none;transition:opacity var(--fds-dur-base) var(--fds-ease-standard)}
.fds-raise--layered::before{box-shadow:var(--fds-e1),var(--fds-e-edge);opacity:1}
.fds-raise--layered::after{box-shadow:var(--fds-e3),var(--fds-e-edge);opacity:0}
.fds-raise--layered[data-lifted="true"]::before{opacity:0}
.fds-raise--layered[data-lifted="true"]::after{opacity:1}

/* --- 28.9 · DRAG MOMENTS ------------------------------------------------
   Lift, valid, reject, settle. `reject` is a single shake and then a return to
   origin — never a red flash, because the drop was refused, not an error the
   person made. */
.fds-lift{box-shadow:var(--fds-e-drag),var(--fds-e-edge);
  transform:rotate(1.5deg) scale(1.02)}
.fds-reject{animation:fds-m-shake var(--fds-dur-slow) var(--fds-ease-standard)}
.fds-settle{transition:transform var(--fds-dur-slow) var(--fds-ease-entrance),
  box-shadow var(--fds-dur-base) var(--fds-ease-standard)}

/* --- 28.10 · REDUCED MOTION ----------------------------------------------
   Everything above collapses to a state change with no travel. The state still
   changes — removing the change as well as the movement is the overcorrection
   that leaves people unsure whether anything happened.

   The stroke draw is the one exception worth naming: it becomes an instant
   full stroke, so a checkmark is still a checkmark. */
@media (prefers-reduced-motion:reduce){
  .fds-m,.fds-mark,.fds-row-enter,.fds-row-leave,.fds-reject,.fds-flash{
    animation-duration:1ms !important;animation-delay:0ms !important;
    animation-iteration-count:1 !important}
  .fds-mark svg path,.fds-mark svg line{stroke-dashoffset:0 !important;
    animation:none !important}
  .fds-accordion{transition-duration:1ms}
  .fds-countdown{animation:none;transform:scaleX(0)}
  .fds-lift,.fds-raise[data-level="drag"]{transform:none}
  .fds-raise{transition-duration:1ms}
}
