/* ============================================================================
   FDS — Fitmentry Design System · v0.20 "Signal"

   No wordmark is set here. §32 names four products in its ramp tokens and
   nowhere else: the lockup renders text the consumer hands it, in the
   consumer's locale, and the system holds no display name for any product.

   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}

/* >>> GENERATED — Noto fallback layer. tools/build-fonts.ts owns this block. */
/* Archivo is the Latin sans and stays the Latin sans. Every face below is a
   fallback for a script Archivo cannot draw, declared with the `unicode-range`
   it covers so a browser downloads a file only when a codepoint in that range
   is actually rendered. A Latin-only screen fetches none of them.

   Order matters and is not alphabetical: these come after the Archivo faces in
   §0, so where two faces could serve a codepoint the earlier declaration wins.
   Noto Sans is pulled for Cyrillic and Greek only, with its own Latin ranges
   dropped, because a fallback that can draw an "a" will draw it.

   JAPANESE AND KOREAN ARE NOT HERE, and that is measured rather than assumed.
   Those two families need 240 ranges between them, which is 185 KB of
   declarations and **31 KB brotli on every page load** — more than the whole of
   the rest of this stylesheet after its comments come off. `unicode-range` stops
   a browser downloading a font it does not need; it does not stop it downloading
   the declaration. So CJK ships as `fds-cjk.css`, generated by the same script,
   and a deployment that serves Japanese or Korean links it after this file. The
   six ranges below cost 632 bytes brotli and are carried by everyone. */

/* Noto Sans — Cyrillic and Greek. Archivo has four Greek glyphs and no Cyrillic at all.
   4 ranges. */
@font-face{font-family:'Noto Sans';
  src:url('fonts/noto/noto-sans-cyrillic.woff2') format('woff2');
  font-weight:100 900;font-style:normal;font-display:swap;
  unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116}
@font-face{font-family:'Noto Sans';
  src:url('fonts/noto/noto-sans-cyrillic-ext.woff2') format('woff2');
  font-weight:100 900;font-style:normal;font-display:swap;
  unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F}
@font-face{font-family:'Noto Sans';
  src:url('fonts/noto/noto-sans-greek.woff2') format('woff2');
  font-weight:100 900;font-style:normal;font-display:swap;
  unicode-range:U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF}
@font-face{font-family:'Noto Sans';
  src:url('fonts/noto/noto-sans-greek-ext.woff2') format('woff2');
  font-weight:100 900;font-style:normal;font-display:swap;
  unicode-range:U+1F00-1FFF}

/* Noto Sans Devanagari — Hindi, Marathi, Nepali.
   1 range. */
@font-face{font-family:'Noto Sans Devanagari';
  src:url('fonts/noto/noto-sans-devanagari-devanagari.woff2') format('woff2');
  font-weight:100 900;font-style:normal;font-display:swap;
  unicode-range:U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09}

/* Noto Sans Arabic — Arabic and Persian, in Naskh. Urdu renders but conventionally wants Nastaliq, which is a separate face and an open decision.
   1 range. */
@font-face{font-family:'Noto Sans Arabic';
  src:url('fonts/noto/noto-sans-arabic-arabic.woff2') format('woff2');
  font-weight:100 900;font-style:normal;font-display:swap;
  unicode-range:U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1}
/* <<< GENERATED — end of the Noto fallback layer. */


/* --- 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 --- */
  /* Archivo first, always: it draws Latin and it is the interface's voice. The
     Noto faces behind it are not preferences, they are coverage — each one is
     declared for the `unicode-range` of a script Archivo has no glyphs for, so
     the browser reaches them only for a codepoint Archivo cannot draw and a
     Latin screen downloads none of them. Order is the whole mechanism: move
     Noto Sans in front of Archivo and it wins Cyrillic *and* nothing else
     changes visibly, which is how that mistake survives review.

     The serif is deliberately Latin-only. Newsreader is the marketing tier, it
     never reaches a shop tablet, and giving it global coverage means choosing a
     second serif skeleton for every script — a real decision with no consumer
     asking for it yet. Non-Latin serif text falls back to the platform. */
  --fds-font-sans: 'Archivo','Noto Sans','Noto Sans Devanagari','Noto Sans Arabic',
                   'Noto Sans JP','Noto Sans KR','Helvetica Neue',Arial,system-ui,sans-serif;
  --fds-font-serif:'Newsreader',Georgia,'Times New Roman',serif;
  --fds-font-mono: 'Martian Mono','Noto Sans',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-depress reads the motion distance token declared in §28 so the press
     depth has one definition. It was a literal 0.5px, which is below a device
     pixel at 1x and rendered as nothing on the shop tablets this is for. */
  --fds-border-w:1px; --fds-border-thick:2px; --fds-focus-w:2px;
  --fds-depress:var(--fds-move-press);

  /* --- 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);
  /* Neutral is the one status family with no dedicated -tint-hc-n primitive, so
     high contrast reused the standard tint and the pair measured 6.93:1 against
     a 7:1 floor. One step darker on the same ramp: 8.24:1. Found by the checker
     the first time it was pointed at this palette. */
  --fds-neutral:#FFFFFF; --fds-neutral-ink:#000000; --fds-neutral-mark:var(--fdsx-n-150); --fds-neutral-tint:var(--fdsx-n-800);
  --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;

  /* A pad key is a thumb pad plus the ring you aim at, which is a larger
     number than a control sized to sit in a bar. At desk that number is
     already `--fds-h-xl`, so this is an alias rather than a second literal —
     the pad needs a size of its own only where a glove is involved. */
  --fds-pad-key:var(--fds-h-xl);

  --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;

  /* 58px reads small against a gloved thumb: two shipping references at 393pt
     both land near 70. This is the one density where a pad key is not a
     control height. */
  --fds-pad-key:max(4.5rem,72px);

  --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}

/* A trap of the same family, recorded here because it will bite again outside
   the component that found it: `grid-template-rows:1fr` is shorthand for
   `minmax(auto,1fr)`, and that `auto` is a floor, not a default. A track told
   to collapse to `0fr` still reserves its own min-content height, so a row
   clipped by `overflow:hidden` is hidden without being zero and the layout goes
   on paying for it. Write `minmax(0,1fr)` wherever a track may ever collapse.

   Both cases are one shorthand quietly supplying a value nobody asked for, and
   neither is visible in the declaration that causes it. There is no rule to add
   here — this note is the fix. */
@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)}
/* Mirroring, for right-to-left.

   Not every arrow flips, which is why this is a marked class and not a blanket
   rule on `.fds-ic`. An icon mirrors when it points along the direction of
   reading — back, forward, next month, hand-off from one party to the next.
   It does not mirror when its direction means something else: up and down are
   the block axis and unchanged, a download arrow points at the device, a
   refresh arrow is rotational, and an external-link arrow is a symbol for
   "leaves this page" rather than a direction to read in — mirroring it is the
   commonest RTL icon mistake and the platforms that got it right leave it
   alone.

   `FDS.icons` owns the list; `FDS.icons.svg()` applies this class, so a
   consumer that asks for an icon by name gets the right behaviour without
   knowing the rule. Written through `:is()` so the `[dir]` fallback survives
   in an engine that does not know `:dir()`, which a bare selector list would
   not — one unknown pseudo-class invalidates the whole list.

   The independent `scale` property, not `transform`. `transform` is one
   property and this system already spends it on icons elsewhere — `.fds-swap`
   scales its children, `.fds-disclose` rotates — so a `transform:scaleX(-1)`
   here would be silently overwritten by the more specific of the two and the
   mirroring would vanish in exactly the components that need it. `scale`
   composes with `transform` instead of replacing it. */
:is(:dir(rtl),[dir="rtl"]) .fds-ic--flip{scale:-1 1}

/* 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:end;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 aligns to the reading edge and is
   tabular. `end`, not `right`: in an RTL column the reading edge is the left. */
.fds-input--num{text-align:end;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}
/* The arrow has to be mirrored by hand, because the two properties that place
   it do not agree about direction. `padding-inline-end` is logical and moves
   to the left in RTL; `background-position` percentages are PHYSICAL and 100%
   is the right edge in every direction. So RTL reserved the space on the left
   and went on painting the arrow on the right, on top of the first characters
   of the option — reported from fitmentry.com in Arabic, where the arrow sat
   over the text.

   0.17 introduced direction support and missed this: `manifest.json#direction`
   describes mirrored ICONS, and an affordance painted with a background
   gradient is not an icon and was not on the list.

   The two boxes keep their left-to-right order rather than swapping, because
   the shape they form is symmetric about its vertical centre — mirroring the
   pair while reversing which gradient sits where would turn the chevron back
   the way it came. .68rem is the inner box and 1.05rem the outer, the same
   two numbers as above, measured from the other edge. */
:is(:dir(rtl),[dir="rtl"]) .fds-select{
  background-position:.68rem 55%,1.05rem 55%}

/* 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)}
/* Held geometry — the shadow and the 1.5° — is the Lift moment and is declared
   once, in §28.9. What is drag's own and stays here is the cursor, the
   stacking and the suppressed transition while a finger is on it. */
.fds-draggable[data-dragging="true"]{cursor:grabbing;position:relative;
  z-index:var(--fds-z-overlay);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:end;
  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="ok"] .fds-submit__idle,
.fds-submit[data-state="error"] .fds-submit__idle{display:none}
.fds-submit[data-state="pending"] .fds-submit__idle{display:inline}
@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-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;
    --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);
  }
  :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-neutral-mark:var(--fdsx-n-150); --fds-neutral-tint:var(--fdsx-n-800);
    --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-7:var(--fds-dv-1); --fds-dv-8:var(--fds-dv-2);
    --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.
   Calling it "safe" or "legal" is a jurisdiction-specific roadworthiness claim.
   `.fds-treadcapture` records only. The saved `.fds-tread` may render operational
   monitor/replace bands when explicit shop-owned thresholds are supplied; FDS
   provides no defaults and never turns those bands into a legal 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 · LEGACY TREAD RECORD -----------------------------------------------
   Compatibility surface from the pre-0.11 line. New work uses Recorded Tread.
   This legacy form may render caller-supplied data-assessment state, but FDS no
   longer compares its measurements with thresholds or originates a band.
   ------------------------------------------------------------------------*/
.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)}
/* Profile geometry reflects measured values. Any data-band colour is a
   direct rendering of caller-supplied assessment, never threshold inference. */
.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)}

/* 26 · TREAD FAMILY — 0.11 "Signal" --------------------------------------
   Three patterns share one measurement vocabulary but have different jobs.

     TREAD INPUT      technician capture. Neutral entry, cyan current/action.
     RECORDED TREAD   read-only internal evidence and caller-owned assessment.
     TREAD SUMMARY    customer, invoice, PDF and print presentation.

   The capture surface is intentionally ordinary document flow. It never opens
   a native numeric keyboard, sheet, overlay, floating focus card or progress
   rail. Four-tire capture is always a two-by-two physical map, including the
   smallest supported screen. DOM order is caller walk order, so the default
   LF -> RF -> RR -> LR auto-advance can coexist with the physical LF/RF over
   LR/RR map.

   `data-unit` is presentation/configuration (`32` or `mm`). FDS does not
   convert units. `data-scheme` is measurement meaning (`single` or
   `three-point`), and single is not a partly-empty three-point record.

   Null/skip is an explicit recorded state. Missing is pending. They must never
   render as the same glyph or payload state.

   `.fds-treadcapture*` remains as a 0.9 compatibility selector family; new
   markup and registry entries use `.fds-treadinput*`.
   ------------------------------------------------------------------------*/

/* Tread Input ------------------------------------------------------------ */
.fds-treadinput,.fds-treadcapture{display:grid;gap:var(--fds-sp-4);width:100%}

.fds-treadinput__map,.fds-treadcapture__map{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:var(--fds-sp-3);align-items:start
}
.fds-treadinput[data-count="1"] .fds-treadinput__map,
.fds-treadcapture[data-count="1"] .fds-treadcapture__map{grid-template-columns:1fr}

/* Physical map without changing caller/DOM walk order. */
.fds-treadinput[data-layout="vehicle"][data-count="4"] .fds-treadinput__map,
.fds-treadcapture[data-layout="vehicle"][data-count="4"] .fds-treadcapture__map{
  grid-template-areas:"lf rf" "lr rr"
}
.fds-treadinput[data-layout="vehicle"][data-count="4"] .fds-treadinput__tire[data-pos="LF"],
.fds-treadcapture[data-layout="vehicle"][data-count="4"] .fds-treadcapture__wheel[data-pos="LF"]{grid-area:lf}
.fds-treadinput[data-layout="vehicle"][data-count="4"] .fds-treadinput__tire[data-pos="RF"],
.fds-treadcapture[data-layout="vehicle"][data-count="4"] .fds-treadcapture__wheel[data-pos="RF"]{grid-area:rf}
.fds-treadinput[data-layout="vehicle"][data-count="4"] .fds-treadinput__tire[data-pos="LR"],
.fds-treadcapture[data-layout="vehicle"][data-count="4"] .fds-treadcapture__wheel[data-pos="LR"]{grid-area:lr}
.fds-treadinput[data-layout="vehicle"][data-count="4"] .fds-treadinput__tire[data-pos="RR"],
.fds-treadcapture[data-layout="vehicle"][data-count="4"] .fds-treadcapture__wheel[data-pos="RR"]{grid-area:rr}

.fds-treadinput__tire,.fds-treadcapture__wheel{
  min-width:0;display:grid;gap:var(--fds-sp-3);padding:var(--fds-sp-3);
  border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-ctl-md);background:var(--fds-surface);
  transition:border-color var(--fds-dur-fast) var(--fds-ease-standard),
             background-color var(--fds-dur-fast) var(--fds-ease-standard)
}
.fds-treadinput__tire[data-current="true"],.fds-treadcapture__wheel[data-current="true"]{
  border-color:var(--fds-accent-mark);background:var(--fds-raised)
}
.fds-treadinput__head,.fds-treadcapture__head{
  display:flex;align-items:baseline;gap:var(--fds-sp-3);min-width:0
}
.fds-treadinput__name,.fds-treadcapture__name{
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size:var(--fds-ui-caption-size);font-weight:600;color:var(--fds-text)
}
.fds-treadinput__code,.fds-treadcapture__code{
  margin-inline-start:auto;flex-shrink:0;font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)
}
.fds-treadinput__points,.fds-treadcapture__points{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--fds-sp-2)
}
.fds-treadinput__points[data-scheme="single"],
.fds-treadcapture__points[data-scheme="single"]{grid-template-columns:1fr}

.fds-treadinput__cell,.fds-treadcapture__cell{
  appearance:none;min-width:0;min-height:var(--fds-h-xl);padding:var(--fds-sp-2);
  border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-ctl-sm);background:var(--fds-raised);color:var(--fds-text);
  font:inherit;cursor:pointer;display:grid;place-items:center;align-content:center;
  gap:var(--fds-sp-1);position:relative;z-index:0;touch-action:manipulation;
  -webkit-tap-highlight-color:transparent;
  transition:border-color var(--fds-dur-fast) var(--fds-ease-standard),
             background-color var(--fds-dur-fast) var(--fds-ease-standard),
             box-shadow var(--fds-dur-fast) var(--fds-ease-standard)
}
.fds-treadinput__cell:hover,.fds-treadcapture__cell:hover{border-color:var(--fds-border-control)}
.fds-treadinput__measure,.fds-treadcapture__measure{
  display:flex;align-items:baseline;justify-content:center;gap:var(--fds-sp-1);min-width:0
}
.fds-treadinput__value,.fds-treadcapture__value{
  font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth),'wght' 600;
  font-variant-numeric:tabular-nums;font-size:var(--fds-ui-heading);line-height:1
}
.fds-treadinput__unit,.fds-treadcapture__unit{
  display:none;font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth);font-size:var(--fds-ui-micro-size);
  color:var(--fds-accent-mark);white-space:nowrap
}
.fds-treadinput__point,.fds-treadcapture__point{
  font-size:var(--fds-ui-micro-size);text-transform:uppercase;
  letter-spacing:.08em;color:var(--fds-text-muted)
}
.fds-treadinput__cell[aria-current="true"],.fds-treadcapture__cell[aria-current="true"]{
  z-index:1;border-color:var(--fds-accent-mark);background:var(--fds-raised);
  box-shadow:0 0 0 var(--fds-border-w) var(--fds-accent-mark)
}
.fds-treadinput__cell[aria-current="true"] .fds-treadinput__unit,
.fds-treadcapture__cell[aria-current="true"] .fds-treadcapture__unit{display:inline}
.fds-treadinput__cell[aria-current="true"] .fds-treadinput__point,
.fds-treadcapture__cell[aria-current="true"] .fds-treadcapture__point{
  color:var(--fds-accent-mark);font-weight:600
}
/* Skip is evidence; pending is absence. Both remain neutral in capture. */
.fds-treadinput__cell[data-readstate="skipped"] .fds-treadinput__value,
.fds-treadcapture__cell[data-readstate="skipped"] .fds-treadcapture__value{
  font-family:var(--fds-font-sans);font-size:var(--fds-ui-micro-size);
  font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--fds-text-muted)
}
.fds-treadinput__cell[data-readstate="pending"] .fds-treadinput__value,
.fds-treadcapture__cell[data-readstate="pending"] .fds-treadcapture__value{
  color:var(--fds-text-muted)
}

/* The keypad is the compact numeric Pad, in flow, on the same capture surface
   as the map. There is no tread keypad geometry here and there must not be one
   again: `.fds-pad--compact` owns the keys, the grid and the press, and this
   pattern owns only the seam between the map and the pad. No native numeric
   input exists anywhere in this family. */
.fds-treadinput > .fds-pad--compact,.fds-treadcapture > .fds-pad--compact{
  padding-top:var(--fds-sp-4);border-top:var(--fds-border-w) solid var(--fds-border)
}
.fds-treadinput__complete,.fds-treadcapture__complete{
  display:grid;gap:var(--fds-sp-3);padding-top:var(--fds-sp-4);
  border-top:var(--fds-border-w) solid var(--fds-border)
}
.fds-treadinput__complete[hidden],
.fds-treadcapture__complete[hidden]{display:none}
.fds-treadinput__save .fds-submit__mark,.fds-treadcapture__save .fds-submit__mark{
  align-items:center;justify-content:center;gap:var(--fds-sp-3)
}
.fds-treadinput__save[data-state="pending"] .fds-submit__mark,
.fds-treadinput__save[data-state="ok"] .fds-submit__mark,
.fds-treadinput__save[data-state="error"] .fds-submit__mark,
.fds-treadcapture__save[data-state="pending"] .fds-submit__mark,
.fds-treadcapture__save[data-state="ok"] .fds-submit__mark,
.fds-treadcapture__save[data-state="error"] .fds-submit__mark{display:inline-flex}
.fds-treadinput__hint,.fds-treadcapture__hint{text-align:center}

/* Utility actions stay secondary and in normal flow. The open More content is
   part of the document, not an overlay or bottom sheet. */
.fds-treadinput__footer{
  display:flex;align-items:flex-start;gap:var(--fds-sp-4);justify-content:space-between;
  padding-top:var(--fds-sp-3);border-top:var(--fds-border-w) solid var(--fds-hairline)
}
.fds-treadinput__more{min-width:0}
.fds-treadinput__more > summary{list-style:none;cursor:pointer}
.fds-treadinput__more > summary::-webkit-details-marker{display:none}
.fds-treadinput__menu{display:none;flex-wrap:wrap;justify-content:flex-end;
  gap:var(--fds-sp-2);padding-top:var(--fds-sp-3)}
.fds-treadinput__more[open] .fds-treadinput__menu{display:flex}
.fds-treadinput__more[open]{flex:1}
.fds-treadinput__more[open] > summary{margin-inline-start:auto}

/* Recorded Tread --------------------------------------------------------- */
.fds-recordedtread{display:grid;gap:var(--fds-sp-5)}
.fds-recordedtread__meta{display:flex;flex-wrap:wrap;gap:var(--fds-sp-3) var(--fds-sp-6);
  font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted)}
.fds-recordedtread__grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(15rem,100%),1fr));
  gap:var(--fds-sp-5)
}
.fds-recordedtread__tire{
  min-width:0;display:grid;gap:var(--fds-sp-4);padding:var(--fds-card-pad);
  border:var(--fds-border-w) solid var(--fds-border);border-radius:var(--fds-r-card);
  background:var(--fds-raised)
}
.fds-recordedtread__tire[data-assessment="good"]{border-color:var(--fds-ok-mark)}
.fds-recordedtread__tire[data-assessment="watch"]{border-color:var(--fds-wn-mark)}
.fds-recordedtread__tire[data-assessment="action"]{border-color:var(--fds-dg-mark)}
.fds-recordedtread__head{display:flex;align-items:baseline;gap:var(--fds-sp-4)}
.fds-recordedtread__head strong{font-size:var(--fds-ui-small-size)}
.fds-recordedtread__head span{margin-inline-start:auto;font-family:var(--fds-font-mono);
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)}
.fds-recordedtread__readings{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--fds-sp-2)
}
.fds-recordedtread__readings[data-scheme="single"]{grid-template-columns:1fr}
.fds-recordedtread__reading{
  min-width:0;padding:var(--fds-sp-4) var(--fds-sp-3);text-align:center;
  border:var(--fds-border-w) solid var(--fds-border);border-radius:var(--fds-r-ctl-sm);
  background:var(--fds-sunk)
}
.fds-recordedtread__reading strong{display:block;font-family:var(--fds-font-mono);
  font-size:var(--fds-ui-body-size);font-variant-numeric:tabular-nums}
.fds-recordedtread__reading strong span{font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted);margin-inline-start:var(--fds-sp-1)}
.fds-recordedtread__reading small{display:block;margin-top:var(--fds-sp-2);
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);
  text-transform:uppercase;letter-spacing:.07em}
.fds-recordedtread__reading[data-state="skipped"],
.fds-recordedtread__reading[data-state="unavailable"],
.fds-recordedtread__reading[data-state="pending"]{color:var(--fds-text-muted)}
.fds-recordedtread__assessment{
  display:grid;gap:var(--fds-sp-1);padding-inline-start:var(--fds-sp-4);
  border-inline-start:var(--fds-border-thick) solid var(--fds-border-control);
  font-size:var(--fds-ui-caption-size)
}
.fds-recordedtread__assessment span{color:var(--fds-text-muted)}
.fds-recordedtread__assessment[data-assessment="good"]{border-color:var(--fds-ok-mark)}
.fds-recordedtread__assessment[data-assessment="watch"]{border-color:var(--fds-wn-mark)}
.fds-recordedtread__assessment[data-assessment="action"]{border-color:var(--fds-dg-mark)}
.fds-recordedtread__metrics{display:flex;flex-wrap:wrap;gap:var(--fds-sp-3)}
.fds-recordedtread__metrics span{
  padding:var(--fds-sp-2) var(--fds-sp-4);border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-pill);font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)
}
.fds-recordedtread__history{margin:0;font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)}

/* Tread Summary ---------------------------------------------------------- */
.fds-treadsummary{
  display:grid;gap:var(--fds-sp-6);padding:var(--fds-card-pad);
  border:var(--fds-border-w) solid var(--fds-border);border-radius:var(--fds-r-card);
  background:var(--fds-raised);color:var(--fds-text)
}
.fds-treadsummary__header{display:flex;align-items:flex-start;gap:var(--fds-sp-6);
  padding-bottom:var(--fds-sp-5);border-bottom:var(--fds-border-w) solid var(--fds-hairline)}
.fds-treadsummary__meta{margin-inline-start:auto;display:flex;flex-direction:column;
  align-items:flex-end;gap:var(--fds-sp-1);font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted)}
.fds-treadsummary__grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(11rem,100%),1fr));
  gap:var(--fds-sp-4)
}
.fds-treadsummary__tire{
  min-width:0;display:grid;gap:var(--fds-sp-4);padding:var(--fds-sp-5);
  border:var(--fds-border-w) solid var(--fds-border);border-radius:var(--fds-r-ctl-md)
}
.fds-treadsummary__head{display:flex;align-items:baseline;gap:var(--fds-sp-3)}
.fds-treadsummary__head strong{font-size:var(--fds-ui-caption-size)}
.fds-treadsummary__head span{margin-inline-start:auto;font-family:var(--fds-font-mono);
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)}
.fds-treadsummary__value{
  font-family:var(--fds-font-mono);font-size:var(--fds-ui-title);
  font-weight:700;font-variant-numeric:tabular-nums;line-height:1
}
.fds-treadsummary__value span{font-size:var(--fds-ui-caption-size);
  color:var(--fds-text-muted);margin-inline-start:var(--fds-sp-1)}
.fds-treadsummary__status{
  display:grid;gap:var(--fds-sp-1);padding:var(--fds-sp-3) var(--fds-sp-4);
  border-radius:var(--fds-r-ctl-sm);border:var(--fds-border-w) solid var(--fds-border);
  font-size:var(--fds-ui-micro-size)
}
.fds-treadsummary__status span{color:inherit;opacity:.82}
.fds-treadsummary__status[data-assessment="good"]{
  background:var(--fds-ok-tint);border-color:var(--fds-ok-mark);color:var(--fds-ok-mark)
}
.fds-treadsummary__status[data-assessment="watch"]{
  background:var(--fds-wn-tint);border-color:var(--fds-wn-mark);color:var(--fds-wn-mark)
}
.fds-treadsummary__status[data-assessment="action"]{
  background:var(--fds-dg-tint);border-color:var(--fds-dg-mark);color:var(--fds-dg-mark)
}
.fds-treadsummary__details{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--fds-sp-2)
}
.fds-treadsummary__details[data-scheme="single"]{grid-template-columns:1fr}
.fds-treadsummary__details > div{text-align:center;padding-top:var(--fds-sp-3);
  border-top:var(--fds-border-w) solid var(--fds-hairline)}
.fds-treadsummary__details strong{display:block;font-family:var(--fds-font-mono);
  font-size:var(--fds-ui-small-size)}
.fds-treadsummary__details strong span{font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted);margin-inline-start:var(--fds-sp-1)}
.fds-treadsummary__details small{display:block;margin-top:var(--fds-sp-1);
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);text-transform:uppercase}
.fds-treadsummary__legend{display:flex;flex-wrap:wrap;gap:var(--fds-sp-3) var(--fds-sp-6);
  padding-top:var(--fds-sp-5);border-top:var(--fds-border-w) solid var(--fds-hairline);
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)}

@media (max-width:22rem){
  /* Deliberately do NOT collapse a four-tire map to one column. */
  .fds-treadinput__tire,.fds-treadcapture__wheel{padding:var(--fds-sp-2)}
  .fds-treadinput__map,.fds-treadcapture__map{gap:var(--fds-sp-2)}
  .fds-treadsummary__header{display:block}
  .fds-treadsummary__meta{align-items:flex-start;margin-top:var(--fds-sp-3)}
}
@media (prefers-reduced-motion:reduce){
  .fds-treadinput__tire,.fds-treadinput__cell,
  .fds-treadcapture__wheel,.fds-treadcapture__cell{transition:none}
}
@media (forced-colors:active){
  .fds-recordedtread__tire,.fds-recordedtread__reading,
  .fds-treadsummary,.fds-treadsummary__tire,.fds-treadsummary__status{
    border-color:CanvasText;background:Canvas;color:CanvasText
  }
  .fds-treadinput__cell[aria-current="true"],.fds-treadcapture__cell[aria-current="true"]{
    outline:2px solid Highlight;outline-offset:0
  }
}

/* Print presentation is intentionally independent of the dark/light app
   surface. Assessment remains legible in grayscale because every status is
   written and every tire remains boxed. */
@media print{
  .fds-treadsummary{
    background:#fff !important;color:#000 !important;border-color:#9a9a9a !important;
    box-shadow:none !important;break-inside:avoid
  }
  .fds-treadsummary__header,.fds-treadsummary__legend,
  .fds-treadsummary__details > div{border-color:#c8c8c8 !important}
  .fds-treadsummary__tire,.fds-treadsummary__status{
    background:#fff !important;color:#000 !important;border-color:#9a9a9a !important;
    break-inside:avoid
  }
  .fds-treadsummary__meta,.fds-treadsummary__head span,
  .fds-treadsummary__value span,.fds-treadsummary__details small,
  .fds-treadsummary__details strong span,.fds-treadsummary__legend{
    color:#444 !important
  }
  .fds-treadsummary__status[data-assessment="good"]{border-style:solid !important}
  .fds-treadsummary__status[data-assessment="watch"]{border-style:dashed !important}
  .fds-treadsummary__status[data-assessment="action"]{border-width:2px !important}
}

/* 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 — 0.14 "Signal", Beta ---------------------------

   The inventory is about 130 animations. What ships is FOURTEEN COMPOSED
   MOMENTS, because a catalogue of 130 named animations is a catalogue nobody
   can hold in their head, and people then invent a 131st.

   MOMENTS are the whole public vocabulary, named after what happened rather
   than what moved — "mark", not "checkmark draw plus circle scale":

     mark (ok / warn / error) · shake · nudge · pulse once · row leaves ·
     row arrives · disclose · icon swap · live update · undo countdown ·
     raise · lift · settle · reject

   There is deliberately no second vocabulary underneath them. The keyframes
   in §28.1 are shared implementation, reused so that fourteen moments are not
   fourteen unrelated animations; they are not a published API, they carry no
   compatibility promise, and nothing outside this file should reach for one
   by name. A consumer composes a MOMENT or it composes nothing.

   ── 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;
}

/* `--fds-depress` in §BASE reads --fds-move-press and is the only definition
   of the press depth. It was a literal 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.

   0.14 corrected it in §BASE rather than re-declaring `--fds-depress` here.
   Two :root declarations of one token is two sources of truth that happen to
   agree, and the one that loses is invisible in a devtools panel. */

/* --- 28.1 · SHARED KEYFRAMES — implementation, not vocabulary -----------
   These exist so the moments below compose rather than duplicate. They are
   not a public animation taxonomy and are not documented as one: a consumer
   that reaches past a moment for `fds-m-in-up` has left the library. */
@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: g-050 to g-000 is barely a step, and everything
   above e1 is shadow. On night a shadow on a charcoal page is a smudge, so the
   surface carries the first move — n-800 to n-700 is a real lightening — and
   the shadow confirms it. Same declarations, same semantic roles, two different
   animations, which is the point: elevation is a role, not a shadow.

   Stated honestly, because the documentation used to overstate it: the surface
   channel moves ONCE, at e0 to e1, and the palette has no rung above
   `--fds-floating` for it to keep climbing. Above e1 the shadow carries the
   scale in both themes — which on night is a heavier shadow to begin with
   (black at .40 to .66, against day's .07 to .20). A night scale that lightened
   all the way to e4 would need a surface role that does not exist; see
   FDS-GAP-0005 rather than mixing one in here.

   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);
}
/* Level 0 rested on `--fds-raised`, which is the same colour as `--fds-floating`
   in every theme — so the whole scale changed only its shadow and the surface
   channel never moved at all, in day or in night, while the comment above and
   the documentation both said it did. 0.14 maps the levels onto the surface
   roles §6 already assigns to e0..e4, which is the one real step the semantic
   layer offers: `--fds-surface` -> `--fds-raised` -> `--fds-floating`.

   That step is deliberately worth much more on night than on day. Day runs
   g-050 -> g-000 and stops, because there is nothing above white. Night runs
   n-800 -> n-700, a visible lightening, and then also stops — the palette has
   no rung above `--fds-floating`, so above e1 the shadow carries the rest in
   both themes. Do not mint one locally to close that; see FDS-GAP-0005. */
.fds-raise[data-level="0"]{box-shadow:none;background:var(--fds-surface)}
.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, settle, reject — the three outcomes of a drag, and the only three.

   There is one drag system in FDS, `[data-fds-dragzone]` in fds.js, and these
   are its vocabulary rather than a second one beside it: the held item wears
   Lift for as long as a finger is on it, an accepted drop wears Settle, and a
   refused drop wears Reject.

   Lift is declared here and worn by the drag component's own held state, so
   the tilt has one definition rather than a copy per consumer.

   `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. Origin is where
   the item already is: nothing in fds.js moved it into the target, so removing
   the held styles returns it. The shake reports the refusal; the return is the
   absence of a move, not an animation undoing one.

   A drag that ends over nothing is a cancel, not a refusal. Nothing changed,
   so nothing moves — see the rules at the top of §28. */
.fds-lift,.fds-draggable[data-dragging="true"]{
  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)}

/* --- PAD — a docked surface of fixed targets ------------------------------
   Deliberately unnumbered. This file carries two concatenated numbering runs
   already, so fifteen numbers mean two different things; a sixteenth would be
   a worse index than none. Find it by name.

   One component, configured by `data-pad`. What varies across the
   configurations is one thing: what a key means.

     action    key fires and forgets          returns nothing, does something
     choice    key latches, one of a set      returns a value
     step      two keys walk a value          returns a value
     number    fixed keypad                   returns a value
     rail      detented track, snap only      returns a value
     size      slots plus a shared keypad     returns a composite
     cascade   stages that narrow in sequence returns a composite

   THE PAD NEVER VALIDATES. It returns what was entered and the screen decides
   whether that is acceptable. A pad that knows which sizes are real is a pad
   carrying specification, which `../SOURCE-OF-TRUTH.md` forbids outright.

   Sizing is `--fds-pad-key`, not a control height: a key is a thumb pad plus
   the ring you aim at. See §DENSITY.

   PRESS IS `[data-press]`, NOT `:active`. `:active` on touch clears at the
   platform's timing rather than ours, and a same-frame tap would scale down
   and back inside one paint — a blink, not a press. `fds.js` holds the
   attribute a minimum `--fds-dur-instant`. This is knowingly a second press
   language: the rest of the system presses on `:active` with the contact echo,
   and reconciling them is a breaking change deferred to the next MAJOR. */
.fds-pad{
  --_press:.94;                 /* component-private: the one magic number here */
  display:flex;flex-direction:column;gap:var(--fds-sp-7);
  width:100%;
  background:var(--fds-surface);
  border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-sheet);
  box-shadow:var(--fds-e3),var(--fds-e-edge);
  padding:var(--fds-card-pad);
  padding-bottom:calc(var(--fds-card-pad) + env(safe-area-inset-bottom));
}

/* ---- readout ------------------------------------------------------------
   An action pad owns no text at all. An input pad owns exactly one line: the
   thing it is editing. That is not a context strip — a context strip describes
   the screen the pad sits on, a readout IS the pad's subject. If a pad needs a
   second line, it is two pads. */
.fds-pad__value{display:flex;align-items:baseline;justify-content:center;
  gap:var(--fds-sp-4);min-height:var(--fds-h-lg)}
.fds-pad__read{font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth),'wght' 550;
  font-variant-numeric:tabular-nums;font-size:var(--fds-ui-title);
  line-height:1;color:var(--fds-text)}
.fds-pad__read[data-empty]{color:var(--fds-text-muted)}
.fds-pad__unit{font-size:var(--fds-ui-small-size);color:var(--fds-text-muted)}

/* ---- grids --------------------------------------------------------------
   Three geometries. --flow derives its columns from how many keys it holds,
   --fixed is declared for keypads whose shape is not negotiable, and --plan
   places four keys as an arrangement rather than a sequence.

   Tracks are minmax(0,·) and never 1fr. `1fr` means `minmax(auto,1fr)`, and
   that auto floor keeps a collapsed track alive at its min-content height —
   which is the dead space that appears under the icons when the pad is
   collapsed. See the note in §BASE. */
.fds-pad__grid{display:grid;justify-items:center;
  column-gap:var(--fds-sp-5);row-gap:var(--fds-sp-7);overflow:hidden;
  transition:grid-template-rows var(--fds-dur-base) var(--fds-ease-entrance),
             row-gap var(--fds-dur-base) var(--fds-ease-entrance)}

/* THE COUNT LAW. Columns are a function of the number of keys, capped at three
   per row: a key plus a two-line label needs a third of a 393pt screen. The
   host renders one to six keys and the pad decides the shape; nothing is
   declared by the caller. Seven or more is not this component.

     1 -> 1      4 -> 2x2        (never 3+1: a lone key on a bottom row reads
     2 -> 2      5 -> 3+2 centred as one that failed to load, and it puts the
     3 -> 3      6 -> 3x2         fourth action where "primary" lives at six) */
.fds-pad__grid--flow{grid-template-columns:repeat(3,minmax(0,1fr))}
.fds-pad__grid--flow:has(> .fds-pad__key:nth-child(1):last-child){
  grid-template-columns:minmax(0,1fr)}
.fds-pad__grid--flow:has(> .fds-pad__key:nth-child(2):last-child),
.fds-pad__grid--flow:has(> .fds-pad__key:nth-child(4):last-child){
  grid-template-columns:repeat(2,minmax(0,1fr))}
/* Five is the only count that cannot balance, so it is the only special case:
   six tracks with every key spanning two, and the fourth starting at track two
   so the short row sits centred instead of hanging left. */
.fds-pad__grid--flow:has(> .fds-pad__key:nth-child(5):last-child){
  grid-template-columns:repeat(6,minmax(0,1fr))}
.fds-pad__grid--flow:has(> .fds-pad__key:nth-child(5):last-child) > .fds-pad__key{
  grid-column:span 2}
.fds-pad__grid--flow:has(> .fds-pad__key:nth-child(5):last-child) > .fds-pad__key:nth-child(4){
  grid-column:2 / span 2}

/* A second row track exists only when a second row of keys does. Declaring it
   on a one-row pad hangs a row-gap under the icons with nothing beneath it to
   justify the space. Collapse is therefore a no-op below four keys, and the
   control that triggers it should be absent rather than disabled. */
.fds-pad__grid--flow:has(> .fds-pad__key:nth-child(4)){
  grid-template-rows:minmax(0,1fr) minmax(0,1fr)}
.fds-pad[data-state="peek"] .fds-pad__grid--flow:has(> .fds-pad__key:nth-child(4)){
  grid-template-rows:minmax(0,1fr) minmax(0,0fr);row-gap:0}

.fds-pad__grid--fixed{grid-template-columns:repeat(var(--cols,3),minmax(0,1fr));
  row-gap:var(--fds-sp-5)}

/* Plan view — the same idea as `.fds-plan`, which draws a real place to its own
   arrangement, applied to a vehicle instead of a room. The four-key case is
   already a rectangle, so the rectangle may as well be the car. Placement is
   explicit so DOM order can stay the TPMS walk (LF, RF, RR, LR) while visual
   order is the plan: reading order and physical order are different things and
   this is the one component where both matter. */
.fds-pad__grid--plan{grid-template-columns:repeat(2,minmax(0,1fr));
  row-gap:var(--fds-sp-7)}
.fds-pad__grid--plan > .fds-pad__key:nth-child(1){grid-area:1 / 1}
.fds-pad__grid--plan > .fds-pad__key:nth-child(2){grid-area:1 / 2}
.fds-pad__grid--plan > .fds-pad__key:nth-child(3){grid-area:2 / 2}
.fds-pad__grid--plan > .fds-pad__key:nth-child(4){grid-area:2 / 1}

/* ---- key ---------------------------------------------------------------
   The platform paints a highlight rect over the whole hit area on touch and
   clears it on lift; that clear is the flicker, not the transition. */
.fds-pad__key{
  --_fill:var(--fds-raised); --_ink:var(--fds-text);
  appearance:none;background:none;border:0;padding:0;margin:0;font:inherit;
  color:var(--fds-text);cursor:pointer;
  display:flex;flex-direction:column;align-items:center;gap:var(--fds-sp-5);
  -webkit-tap-highlight-color:transparent;touch-action:manipulation;
  user-select:none;-webkit-user-select:none;-webkit-touch-callout:none}
.fds-pad__disc{
  width:var(--fds-pad-key);height:var(--fds-pad-key);
  border-radius:var(--fds-r-pill);
  background-color:var(--_fill);color:var(--_ink);
  display:grid;place-items:center;position:relative;
  transform:translateZ(0);backface-visibility:hidden;
  transition:transform var(--fds-dur-fast) var(--fds-ease-standard),
             background-color var(--fds-dur-fast) var(--fds-ease-standard),
             color var(--fds-dur-fast) var(--fds-ease-standard)}
/* Settles on exit, returns on standard. No overshoot: entrance easing belongs
   to things that are arriving, and a key you just tapped is not arriving. */
.fds-pad__key[data-press] .fds-pad__disc{
  transform:translateZ(0) scale(var(--_press));
  transition-timing-function:var(--fds-ease-exit)}
.fds-pad__label{font-size:var(--fds-ui-small-size);color:var(--fds-text);
  text-align:center;text-wrap:balance;max-width:7.5rem}
.fds-pad__num{font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth),'wght' 550;
  font-variant-numeric:tabular-nums;font-size:var(--fds-ui-heading)}

/* Latched. Live state is the neutral fill, so a held key reads as held in every
   theme without a colour role that means "on". */
.fds-pad__key[aria-pressed="true"],
.fds-pad__key[aria-checked="true"]{
  --_fill:var(--fds-neutral); --_ink:var(--fds-neutral-ink)}
.fds-pad__key--go{--_fill:var(--fds-ok); --_ink:var(--fds-ok-ink)}
.fds-pad__key--stop{--_fill:var(--fds-dg); --_ink:var(--fds-dg-ink)}
.fds-pad__key[aria-pressed="true"] .fds-pad__disc,
.fds-pad__key[aria-checked="true"] .fds-pad__disc,
.fds-pad__key--go .fds-pad__disc,
.fds-pad__key--stop .fds-pad__disc{background-image:var(--fds-fill-sheen)}
/* A ring, not a border — same box filled or outlined, per the contract. */
.fds-pad__key--quiet .fds-pad__disc{background-color:transparent;
  background-image:none;color:var(--fds-text-muted);
  box-shadow:inset 0 0 0 var(--fds-border-w) var(--fds-border-control)}
.fds-pad__key:disabled,.fds-pad__key[aria-disabled="true"]{
  opacity:.42;cursor:not-allowed;pointer-events:none}

/* ---- compact ------------------------------------------------------------
   A pad that is not the surface it stands on. It drops the sheet — background,
   border, shadow, padding — because a host that already owns a surface would
   otherwise show two, and its keys are rectangles rather than discs.

   The discs are not shrunk, they are gone: a twelve-key pad under a four-tire
   map on a 393pt screen cannot spend 72px of diameter per key and still show
   both. What replaces the disc is a full-width key at `--fds-h-xl`, so the
   target stays a control height rather than becoming a smaller circle — the
   count law's ceiling of three per row is what pays for it, and a numeric pad
   is the fixed grid, not the flow one.

   The press moves the key here, because there is no disc left to move. */
.fds-pad--compact{
  gap:var(--fds-sp-4);
  background:none;border:0;box-shadow:none;padding:0}
.fds-pad--compact .fds-pad__grid{
  column-gap:var(--fds-sp-2);row-gap:var(--fds-sp-2)}
.fds-pad--compact .fds-pad__key{
  width:100%;min-height:var(--fds-h-xl);gap:0;justify-content:center;
  border-radius:var(--fds-r-ctl-sm);
  background-color:var(--_fill);color:var(--_ink);
  box-shadow:inset 0 0 0 var(--fds-border-w) var(--fds-border-control);
  transform:translateZ(0);backface-visibility:hidden;
  transition:transform var(--fds-dur-fast) var(--fds-ease-standard),
             background-color var(--fds-dur-fast) var(--fds-ease-standard),
             color var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-pad--compact .fds-pad__key[data-press]{
  transform:translateZ(0) scale(var(--_press));
  transition-timing-function:var(--fds-ease-exit)}

/* The badge sits off the pad's trailing top corner and its nudge has to follow
   the same edge, so the translate reads a private token the RTL rule flips. */
:is(:dir(rtl),[dir="rtl"]) .fds-pad__count{--_countnudge:-25%}
.fds-pad__count{position:absolute;inset-block-start:0;inset-inline-end:0;
  transform:translate(var(--_countnudge,25%),-25%);
  min-width:var(--fds-sp-8);height:var(--fds-sp-8);
  border-radius:var(--fds-r-pill);
  background:var(--fds-accent);color:var(--fds-accent-ink);
  font-size:var(--fds-ui-micro-size);font-weight:600;
  font-variant-numeric:tabular-nums;
  display:grid;place-items:center;padding-inline:var(--fds-sp-2);
  box-shadow:0 0 0 var(--fds-border-w) var(--fds-surface)}

/* ---- stepper row --------------------------------------------------------
   Not `.fds-stepper`. That component is an inline bordered box sized to sit in
   a form row, and giving it pad-sized targets would change what it does
   unconfigured — a breaking change under the version rule. Two components, one
   idea, until a MAJOR can merge them. */
.fds-pad__step{display:grid;
  grid-template-columns:var(--fds-pad-key) minmax(0,1fr) var(--fds-pad-key);
  align-items:center;gap:var(--fds-sp-5)}
.fds-pad__stepread{display:flex;flex-direction:column;align-items:center;
  gap:var(--fds-sp-2);min-width:0}

/* ---- rail ---------------------------------------------------------------
   A radiogroup drawn on a track, not a slider. Every other pad keeps one
   promise — the target does not move, so a thumb learns it — and a slider's
   target moves by definition. Three to seven stops, snap only.

   NOT FOR MEASURED VALUES. Tread depth is a record of what a gauge said;
   dragging to a number invents it. The rail is for judgements, where the
   technician's opinion is the data. */
.fds-pad__rail{position:relative;display:flex;align-items:flex-start;
  justify-content:space-between;padding-inline:calc(var(--fds-tap) / 2);
  touch-action:none}
.fds-pad__railtrack{position:absolute;
  inset-inline:calc(var(--fds-tap) / 2);
  inset-block-start:calc(var(--fds-tap) / 2 - var(--fds-sp-1));
  height:var(--fds-sp-2);border-radius:var(--fds-r-pill);
  background:var(--fds-raised)}
/* The fill grows from the rail's start edge, which is the right-hand one in a
   right-to-left interface. `left` pinned it to the wrong end of the track. */
.fds-pad__railfill{position:absolute;inset-block:0;inset-inline-start:0;width:0;
  border-radius:var(--fds-r-pill);background:var(--fds-accent);
  transition:width var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-pad__detent{appearance:none;background:none;border:0;padding:0;margin:0;
  font:inherit;color:var(--fds-text-muted);cursor:pointer;position:relative;
  display:flex;flex-direction:column;align-items:center;gap:var(--fds-sp-4);
  flex:0 0 auto;width:var(--fds-tap);
  -webkit-tap-highlight-color:transparent;touch-action:manipulation;
  user-select:none;-webkit-user-select:none}
.fds-pad__detent::before{content:"";
  width:var(--fds-tap);height:var(--fds-tap);border-radius:var(--fds-r-pill);
  background:var(--fds-surface);
  box-shadow:inset 0 0 0 2px var(--fds-border-control);
  transform:translateZ(0) scale(.5);
  transition:transform var(--fds-dur-fast) var(--fds-ease-entrance),
             box-shadow var(--fds-dur-fast) var(--fds-ease-standard),
             background-color var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-pad__detent[aria-checked="true"]{color:var(--fds-text)}
.fds-pad__detent[aria-checked="true"]::before{transform:translateZ(0) scale(1);
  background:var(--fds-neutral);box-shadow:inset 0 0 0 2px var(--fds-neutral)}
.fds-pad__detentlabel{font-size:var(--fds-ui-micro-size);text-align:center;
  max-width:4.5rem}

/* ---- slots — composite entry -------------------------------------------
   `__slot` carries the same meaning as `.fds-slot` at its first definition: an
   undecided value, rendered as a hole the shape of the answer rather than as a
   plausible placeholder that gets shipped. This is the first component where
   that hole is interactive.

   Note for whoever fixes it: `.fds-slot` is defined twice in this file with two
   unrelated meanings, and the second wins, so the placeholder is currently
   dead. That is a pre-existing defect and is not touched here. */
.fds-pad__slots{display:flex;align-items:center;justify-content:center;
  gap:var(--fds-sp-3);flex-wrap:wrap}
.fds-pad__slot{appearance:none;font:inherit;cursor:pointer;
  min-width:calc(var(--fds-pad-key) * .8);height:var(--fds-h-lg);
  padding-inline:var(--fds-sp-5);
  background:var(--fds-raised);border:0;border-radius:var(--fds-r-ctl-md);
  color:var(--fds-text);
  font-family:var(--fds-font-mono);
  font-variation-settings:'wdth' var(--fds-mono-wdth),'wght' 550;
  font-variant-numeric:tabular-nums;font-size:var(--fds-ui-heading);
  box-shadow:inset 0 0 0 var(--fds-border-w) transparent;
  -webkit-tap-highlight-color:transparent;touch-action:manipulation;
  transition:box-shadow var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-pad__slot[data-empty]{color:var(--fds-text-muted)}
.fds-pad__slot[aria-current="true"]{
  box-shadow:inset 0 0 0 2px var(--fds-accent)}
.fds-pad__sep{font-size:var(--fds-ui-heading);color:var(--fds-text-muted)}

/* ---- cascade ------------------------------------------------------------
   Stages that narrow in sequence. This is also the wizard: a wizard is a
   cascade whose stages happen to be screens. `.fds-wizard` draws a progress
   rail of dots and names and does not narrow anything, so it is a different
   drawing of a neighbouring idea rather than a component to extend.

   A resolved stage collapses to a crumb and stays tappable, which is the back
   path. If a flow needs a progress bar to be understandable, the stages are
   wrong before the component is. */
.fds-pad__crumbs{display:flex;flex-wrap:wrap;align-items:center;
  justify-content:center;gap:var(--fds-sp-3)}
.fds-pad__crumb{appearance:none;font:inherit;cursor:pointer;
  height:var(--fds-h-sm);padding-inline:var(--fds-sp-5);
  border:0;border-radius:var(--fds-r-pill);
  background:var(--fds-raised);color:var(--fds-text);
  font-size:var(--fds-ui-small-size);
  -webkit-tap-highlight-color:transparent;touch-action:manipulation}
.fds-pad__crumb[data-pending]{background:transparent;color:var(--fds-text-muted);
  box-shadow:inset 0 0 0 var(--fds-border-w) var(--fds-border)}
.fds-pad__crumb[aria-current="true"]{background:var(--fds-accent);
  color:var(--fds-accent-ink);background-image:var(--fds-fill-sheen)}
.fds-pad__crumb:disabled{cursor:not-allowed;pointer-events:none}
.fds-pad__list{display:grid;gap:var(--fds-sp-4);
  grid-template-columns:repeat(auto-fit,minmax(5.5rem,1fr))}
.fds-pad__opt{appearance:none;font:inherit;cursor:pointer;
  min-height:var(--fds-h-lg);padding-inline:var(--fds-sp-4);
  border:0;border-radius:var(--fds-r-ctl-md);
  background:var(--fds-raised);color:var(--fds-text);
  font-size:var(--fds-ui-body-size);font-variant-numeric:tabular-nums;
  -webkit-tap-highlight-color:transparent;touch-action:manipulation;
  transition:background-color var(--fds-dur-fast) var(--fds-ease-standard),
             color var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-pad__opt[data-press]{background:var(--fds-neutral);
  color:var(--fds-neutral-ink)}

@media (prefers-reduced-motion:reduce){
  .fds-pad__grid,.fds-pad__disc,.fds-pad__detent::before,.fds-pad__railfill{
    transition-duration:1ms}
  /* The press still reports, it just stops travelling. */
  .fds-pad__key[data-press] .fds-pad__disc{transform:translateZ(0);opacity:.7}
  .fds-pad--compact .fds-pad__key[data-press]{transform:translateZ(0);opacity:.7}
}

/* --- 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 rather than an empty disc.

   Read the list below as fourteen entries, not as a blanket suppression. Each
   moment is named here because each one has a state to keep: the row is still
   gone, the chevron is still turned, the icon still says settled, the value is
   still the new value. Reduced motion removes the travel and nothing else. */
@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,.fds-disclose,.fds-settle,.fds-bulk-leave{
    transition-duration:1ms}
  /* Icon swap keeps the state and loses the scale, so the box still reports
     idle → busy → settled without anything growing into place. */
  .fds-swap > *{transition-duration:1ms}
  .fds-swap > [data-when]{transform:none}
  .fds-countdown{animation:none;transform:scaleX(0)}
  .fds-lift,.fds-draggable[data-dragging="true"],
  .fds-raise[data-level="drag"]{transform:none}
  .fds-raise{transition-duration:1ms}
}


/* ==========================================================================
   29 · DATA-STATE & ANALYTICAL PATTERNS — 0.10 "Signal"

   These patterns render caller-owned meaning. They do not decide whether data
   is sufficient, stale, ambiguous, good, bad, safe, recommended or compliant.
   Geometry may be computed by FDS from caller data (Position Scale) or supplied
   as data/SVG geometry (Range History). Business judgement stays outside FDS.
   ==========================================================================*/

/* Position Scale --------------------------------------------------------- */
.fds-position{padding-top:var(--fds-sp-4)}
.fds-position__track{position:relative;height:var(--fds-sp-9);margin-inline:var(--fds-sp-4)}
.fds-position__rule{position:absolute;inset-inline:0;top:50%;height:var(--fds-border-w);background:var(--fds-dv-axis)}
.fds-position__cap{position:absolute;top:50%;height:var(--fds-sp-6);width:var(--fds-border-w);background:var(--fds-dv-axis);transform:translateY(-50%)}
.fds-position__cap--low{inset-inline-start:var(--_lo,0%)}
.fds-position__cap--high{inset-inline-start:var(--_hi,100%)}
.fds-position__band{position:absolute;top:50%;inset-inline-start:var(--_q1,25%);width:calc(var(--_q3,75%) - var(--_q1,25%));height:var(--fds-sp-6);transform:translateY(-50%);background:var(--fds-dv-s1);border:var(--fds-border-w) solid var(--fds-dv-s5);border-radius:var(--fds-r-pill)}
.fds-position__median{position:absolute;inset-inline-start:var(--_med,50%);top:50%;height:var(--fds-sp-7);width:var(--fds-border-w);transform:translateY(-50%);background:var(--fds-dv-s5)}
.fds-position__subject{position:absolute;inset-inline-start:var(--_subject,50%);top:50%;transform:translate(-50%,-50%);z-index:1;display:inline-flex;align-items:center;gap:var(--fds-sp-2);white-space:nowrap;background:var(--fds-text);color:var(--fds-canvas);border-radius:var(--fds-r-pill);padding:var(--fds-sp-2) var(--fds-sp-4);font-family:var(--fds-font-mono);font-size:var(--fds-ui-micro-size);font-weight:700;font-variant-numeric:tabular-nums}
.fds-position[data-relation="above"] .fds-position__subject{background:var(--fds-wn-mark);color:var(--fds-wn-ink)}
.fds-position[data-relation="below"] .fds-position__subject{background:var(--fds-in-mark);color:var(--fds-in-ink)}
.fds-position__observations{position:absolute;inset:0}
.fds-position__obs{position:absolute;inset-inline-start:var(--_at);top:50%;width:var(--fds-sp-3);height:var(--fds-sp-3);transform:translate(-50%,-50%);border-radius:50%;background:var(--fds-dv-s5);box-shadow:0 0 0 var(--fds-sp-2) var(--fds-canvas)}
.fds-position__labels{position:relative;min-height:var(--fds-sp-10);margin-top:var(--fds-sp-2);font-size:var(--fds-ui-micro-size);font-family:var(--fds-font-mono);color:var(--fds-text-muted)}
.fds-position__labels span{position:absolute;inset-inline-start:var(--_at);transform:translateX(-50%);white-space:nowrap;text-align:center}
.fds-position__labels span[data-edge="start"]{transform:none}
.fds-position__labels span[data-edge="end"]{transform:translateX(-100%)}
.fds-position__labels em{display:block;font-family:var(--fds-font-sans);font-style:normal;text-transform:uppercase;letter-spacing:var(--fds-tr-eyebrow);font-size:var(--fds-ui-micro-size)}
.fds-position__meta{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--fds-sp-5);margin-top:var(--fds-sp-4);font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted)}
.fds-position__meta strong{color:var(--fds-text);font-weight:500}
.fds-position[data-freshness="stale"]{opacity:.82}
.fds-position[data-sufficiency="insufficient"] .fds-position__band,
.fds-position[data-sufficiency="insufficient"] .fds-position__median{display:none}

/* Range History ---------------------------------------------------------- */
.fds-rangehistory{position:relative;min-height:var(--fds-sp-14);margin-top:var(--fds-sp-5);border-inline-start:var(--fds-border-w) solid var(--fds-dv-axis);border-block-end:var(--fds-border-w) solid var(--fds-dv-axis);background:repeating-linear-gradient(to top,var(--fds-dv-grid) 0 var(--fds-border-w),transparent var(--fds-border-w) 25%)}
.fds-rangehistory svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
.fds-rangehistory__range{fill:var(--fds-dv-s2);opacity:.5}
.fds-rangehistory__iqr{fill:var(--fds-dv-s4);opacity:.55}
.fds-rangehistory__median{fill:none;stroke:var(--fds-text-muted);stroke-width:1;stroke-dasharray:4 4;vector-effect:non-scaling-stroke}
.fds-rangehistory__subject{fill:none;stroke:var(--fds-accent);stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;vector-effect:non-scaling-stroke}
.fds-rangehistory__subject--muted{stroke:var(--fds-text-muted)}
.fds-rangehistory__gap{stroke:var(--fds-wn-mark);stroke-width:1.5;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}
.fds-rangehistory__key{display:flex;flex-wrap:wrap;gap:var(--fds-sp-4) var(--fds-sp-6);margin-top:var(--fds-sp-4);font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)}
.fds-rangehistory__legend{display:inline-flex;align-items:center;gap:var(--fds-sp-3)}
.fds-rangehistory__legend i{display:block;width:var(--fds-sp-6);height:var(--fds-sp-3);border-radius:var(--fds-r-sharp);background:var(--fds-dv-s2)}
.fds-rangehistory__legend--iqr i{background:var(--fds-dv-s4)}
.fds-rangehistory__legend--subject i{height:var(--fds-border-w);background:var(--fds-accent)}

/* Comparison Result ------------------------------------------------------ */
.fds-comparison{border-block-end:var(--fds-border-w) solid var(--fds-hairline)}
.fds-comparison > summary{list-style:none}
.fds-comparison > summary::-webkit-details-marker{display:none}
.fds-comparison__trigger{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:var(--fds-sp-5);width:100%;min-height:var(--fds-tap);padding:var(--fds-sp-4) 0;color:inherit;text-align:start;cursor:pointer}
.fds-comparison__id{min-width:0}
.fds-comparison__title{display:block;font-weight:600}
.fds-comparison__sub{display:block;color:var(--fds-text-muted);font-size:var(--fds-ui-caption-size);margin-top:var(--fds-sp-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fds-comparison__signal{text-align:end;font-family:var(--fds-font-mono);font-size:var(--fds-ui-small-size);font-weight:700}
.fds-comparison__signal small{display:block;font-family:var(--fds-font-sans);font-size:var(--fds-ui-micro-size);font-weight:400;color:var(--fds-text-muted);margin-top:var(--fds-sp-1)}
.fds-comparison__signal[data-tone="warning"]{color:var(--fds-wn-mark)}
.fds-comparison__signal[data-tone="info"]{color:var(--fds-in-mark)}
.fds-comparison__chev{color:var(--fds-text-muted);transition:transform var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-comparison[open] .fds-comparison__chev{transform:rotate(90deg)}
.fds-comparison__body{padding:0 0 var(--fds-sp-7)}
.fds-comparison__lede{margin:0 0 var(--fds-sp-5);font-size:var(--fds-ui-small-size);line-height:var(--fds-lh-body)}
.fds-comparison__facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,10rem),1fr));gap:var(--fds-sp-5);margin-top:var(--fds-sp-6)}
.fds-comparison__fact{padding:var(--fds-sp-4);background:var(--fds-surface);border-radius:var(--fds-r-card)}
.fds-comparison__fact span{display:block;color:var(--fds-text-muted);font-size:var(--fds-ui-micro-size)}
.fds-comparison__fact strong{display:block;margin-top:var(--fds-sp-1);font-family:var(--fds-font-mono);font-size:var(--fds-ui-body-size)}

/* Facet Filter ----------------------------------------------------------- */
.fds-facets{display:flex;flex-direction:column}
.fds-facet{border-block-end:var(--fds-border-w) solid var(--fds-hairline)}
.fds-facet__head{display:flex;align-items:center;gap:var(--fds-sp-4);padding-block:var(--fds-sp-4);font-size:var(--fds-ui-small-size);font-weight:500}
.fds-facet__head span:first-child{flex:1}
.fds-facet__count{font-family:var(--fds-font-mono);font-size:var(--fds-ui-micro-size);color:var(--fds-accent-mark)}
.fds-facet__option{display:flex;align-items:center;gap:var(--fds-sp-4);min-height:var(--fds-tap);font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted)}
.fds-facet__option input{accent-color:var(--fds-accent)}
.fds-facet__option span{flex:1}
.fds-facet__option b{font-family:var(--fds-font-mono);font-size:var(--fds-ui-micro-size);font-weight:400}
.fds-facet__option[data-zero="true"]{color:var(--fds-text-disabled)}
.fds-facetfilter{display:grid;grid-template-columns:minmax(12rem,18rem) minmax(0,1fr);gap:var(--fds-sp-6);align-items:start}
.fds-facetfilter__pane{padding-inline-end:var(--fds-sp-6);border-inline-end:var(--fds-border-w) solid var(--fds-hairline)}

/* Metric Summary --------------------------------------------------------- */
.fds-metricsummary{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,11rem),1fr));gap:var(--fds-border-w);border:var(--fds-border-w) solid var(--fds-border);border-radius:var(--fds-r-card);overflow:hidden;background:var(--fds-border)}
.fds-metricsummary > *{background:var(--fds-raised);padding:var(--fds-card-pad)}

/* Run Status ------------------------------------------------------------- */
.fds-runstatus{display:flex;align-items:flex-start;gap:var(--fds-sp-5);padding:var(--fds-sp-5);border:var(--fds-border-w) solid var(--fds-border);border-radius:var(--fds-r-card);background:var(--fds-raised)}
.fds-runstatus__body{flex:1;min-width:0}
.fds-runstatus__title{font-size:var(--fds-ui-small-size);font-weight:600}
.fds-runstatus__meta{margin-top:var(--fds-sp-1);font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted);line-height:var(--fds-lh-body)}
.fds-runstatus__progress{margin-top:var(--fds-sp-4)}
.fds-runstatus__mark{display:flex;align-items:center;justify-content:center;min-width:var(--fds-h-md)}

/* Evidence List ---------------------------------------------------------- */
.fds-evidence{display:flex;flex-direction:column}
.fds-evidence__row{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:var(--fds-sp-4);align-items:center;min-height:var(--fds-tap);padding-block:var(--fds-sp-4);border-block-end:var(--fds-border-w) solid var(--fds-hairline)}
.fds-evidence__row:last-child{border-block-end:0}
.fds-evidence__name{font-size:var(--fds-ui-small-size);font-weight:500}
.fds-evidence__meta{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);margin-top:var(--fds-sp-1)}
.fds-evidence__value{font-family:var(--fds-font-mono);font-weight:600}
.fds-evidence__age{font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted);white-space:nowrap}
.fds-evidence__row[data-freshness="stale"] .fds-evidence__age{color:var(--fds-wn-mark)}
.fds-evidence__row[data-process="error"] .fds-evidence__name{color:var(--fds-dg-mark)}
.fds-evidence__row[data-availability="none"]{opacity:.62}

/* Empty State ------------------------------------------------------------ */
.fds-empty{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;min-height:var(--fds-sp-14);padding:var(--fds-sp-7);background:var(--fds-surface);border:var(--fds-border-w) dashed var(--fds-border);border-radius:var(--fds-r-card)}
.fds-empty__glyph{font-size:var(--fds-ui-title);line-height:1;color:var(--fds-text-muted);margin-bottom:var(--fds-sp-4)}
.fds-empty__title{font-size:var(--fds-ui-heading);font-weight:600}
.fds-empty__body{max-width:44rem;margin-top:var(--fds-sp-2);font-size:var(--fds-ui-caption-size);line-height:var(--fds-lh-body);color:var(--fds-text-muted)}
.fds-empty__action{margin-top:var(--fds-sp-5)}

@media (max-width:52rem){
  .fds-facetfilter{grid-template-columns:1fr}
  .fds-facetfilter__pane{display:none}
  .fds-comparison__trigger{grid-template-columns:minmax(0,1fr) auto}
  .fds-comparison__signal{grid-column:1;grid-row:2;text-align:start}
  .fds-comparison__chev{grid-column:2;grid-row:1 / span 2}
}

@media (forced-colors:active){
  .fds-position__rule,.fds-position__cap,.fds-position__median,.fds-position__obs{background:CanvasText}
  .fds-position__band{background:Canvas;border-color:CanvasText}
  .fds-position__subject{background:Highlight;color:HighlightText}
  .fds-rangehistory__range{fill:Canvas;stroke:CanvasText;stroke-width:1}
  .fds-rangehistory__iqr{fill:CanvasText;opacity:.2;stroke:CanvasText;stroke-width:1}
  .fds-rangehistory__median{stroke:CanvasText}
  .fds-rangehistory__subject{stroke:Highlight}
  .fds-rangehistory__gap{stroke:CanvasText}
}

@media (prefers-reduced-motion:reduce){
  .fds-comparison__chev{transition-duration:1ms}
}


/* ==========================================================================
   30 · NAVIGATION — 0.12 "Signal"

   ONE NAVIGATION MODEL, ONE DOM. The same destinations in the same source
   order become a drawer, a rail, a sidebar or a bottom bar. Nothing here
   duplicates markup per breakpoint, because two copies of a navigation is
   two navigations that disagree by Thursday — and a screen reader reads
   both of them.

   SM / MD / LG NAME NAVIGATION REAL ESTATE, NOT DEVICES. There is no phone
   breakpoint in this section and no tablet. `.fds-navshell` owns the
   thresholds because the shell is the thing that knows how much room it
   has; a navigation that asks the viewport instead is wrong the moment it
   is embedded in a panel.

     SM   < 48rem      no room to spare    drawer, opened from the App Bar
     MD   48 – 75rem   room for icons      persistent rail
     LG   >= 75rem     room for words      persistent labelled sidebar

   CONFIGURED BY `data-nav`, the way the pad is configured by `data-pad`.
   `auto` lets the shell choose; naming a presentation pins it.

     auto      the shell decides from its own width
     drawer    off-canvas, inline-edge mounted, scrimmed
     rail      persistent, icon over short label
     sidebar   persistent, icon beside full label
     bottom    horizontal bar on the block-end edge

   BOTTOM IS NOT THE SM FORM OF THE SIDEBAR. No media query here ever
   produces it. A consumer names `data-nav="bottom"` after proving that three
   to five peer destinations carry the traffic; anything else belongs in the
   drawer, where it can be a list instead of a compromise. Past five it is a
   worse drawer with smaller targets.

   THE NAVIGATION DRAWER IS NOT THE DRAWER. `.fds-drawer` is contextual
   overlay and becomes a bottom sheet under 34rem. Navigation stays mounted
   to the inline edge at every width, because a person reaches for
   navigation in the same place every time and a sheet is where transient
   things live. Two components, two jobs, two class names — deliberately,
   and `.fds-nav` is not `.fds-sidebar` either: that name was already taken
   by the two-column layout primitive in §composition and still means it.

   ACTIVE IS `aria-current`, NOT A CLASS. The marker is drawn from the
   attribute that already has to be correct for a screen reader, so a
   navigation that looks right and reads wrong cannot be built.

   ACTION BAR IS NOT NAVIGATION. It carries the actions of the workflow in
   front of you and it is in this section only because it competes for the
   same block-end edge. Putting a destination in it is the mistake this
   paragraph exists to prevent.
   ==========================================================================*/
:root{
  /* Derived, so density carries through without a second literal. A rail is
     one target wide plus the breathing room either side of it. */
  --fds-nav-rail-w:calc(var(--fds-h-xl) + var(--fds-sp-7));
  --fds-nav-side-w:16rem;
  --fds-nav-item-h:var(--fds-h-lg);
  --fds-appbar-h:var(--fds-h-xl);
}

/* ---- the shell ----------------------------------------------------------
   Three rows and two columns. The bar spans the top, navigation takes the
   inline column, the block-end row is for a bottom presentation or an
   Action Bar. A drawer is `position:fixed` and therefore leaves the grid,
   which collapses its column to nothing without a second rule. */
.fds-navshell{display:grid;min-height:100vh;min-height:100dvh;
  grid-template-columns:auto minmax(0,1fr);
  grid-template-rows:auto minmax(0,1fr) auto;
  grid-template-areas:"bar bar" "nav main" "foot foot"}
.fds-navshell > .fds-appbar{grid-area:bar}
.fds-navshell > .fds-nav{grid-area:nav}
.fds-navshell > .fds-navmain{grid-area:main;min-width:0;overflow:auto}
.fds-navshell > .fds-actionbar,
.fds-navshell > .fds-nav[data-nav="bottom"]{grid-area:foot}

/* ---- the navigation itself ----------------------------------------------
   `hidden` must win over the component's own display; see the specificity
   note in §OVERLAYS. */
.fds-nav{display:flex;flex-direction:column;gap:var(--fds-sp-2);
  padding:var(--fds-sp-5) var(--fds-sp-4);overflow-y:auto;
  background:var(--fds-surface);
  border-inline-end:var(--fds-border-w) solid var(--fds-border)}
.fds-nav[hidden]{display:none}

/* Persistent column — rail and sidebar are the same thing at two widths. */
.fds-nav[data-nav="rail"],.fds-nav[data-nav="sidebar"]{position:relative}
.fds-nav[data-nav="sidebar"]{width:var(--fds-nav-side-w)}
.fds-nav[data-nav="rail"]{width:var(--fds-nav-rail-w);padding-inline:var(--fds-sp-3)}

/* Rail: the label is still there, still read, just small and under the icon.
   A rail with no words is an icon quiz. */
.fds-nav[data-nav="rail"] .fds-navitem{flex-direction:column;gap:var(--fds-sp-1);
  justify-content:center;text-align:center;padding-inline:var(--fds-sp-2)}
.fds-nav[data-nav="rail"] .fds-navitem__label{font-size:var(--fds-ui-micro-size);
  line-height:1.1;white-space:normal}
.fds-nav[data-nav="rail"] .fds-navitem__count{margin:0;position:absolute;
  inset-block-start:var(--fds-sp-2);inset-inline-end:var(--fds-sp-2)}
.fds-nav[data-nav="rail"] .fds-navgroup__title{
  position:absolute;width:1px;height:1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap}

/* Drawer: the inline edge, at every width. Reuses the existing drawer
   keyframes rather than minting a second travel language. */
.fds-nav[data-nav="drawer"]{position:fixed;inset-block:0;inset-inline-start:0;
  z-index:var(--fds-z-drawer);width:min(var(--fds-nav-side-w),calc(100vw - 3rem));
  background:var(--fds-floating);box-shadow:var(--fds-e3),var(--fds-e-edge);
  animation:fds-drawer-start-in var(--fds-dur-slow) var(--fds-ease-emphasized)}
.fds-nav[data-nav="drawer"][data-leaving="true"]{
  animation:fds-drawer-start-out var(--fds-dur-base) var(--fds-ease-exit) forwards}

/* Bottom: opt-in only, and it owns the safe area so a destination never sits
   under a home indicator. */
.fds-nav[data-nav="bottom"]{flex-direction:row;width:auto;overflow:visible;
  justify-content:space-around;gap:0;
  padding:var(--fds-sp-2) var(--fds-sp-3);
  padding-block-end:calc(var(--fds-sp-2) + env(safe-area-inset-bottom,0px));
  border-inline-end:0;
  border-block-start:var(--fds-border-w) solid var(--fds-border)}
.fds-nav[data-nav="bottom"] .fds-navitem{flex:1;min-width:0;flex-direction:column;
  gap:var(--fds-sp-1);justify-content:center;text-align:center;
  padding-inline:var(--fds-sp-2)}
.fds-nav[data-nav="bottom"] .fds-navitem__label{font-size:var(--fds-ui-micro-size);
  line-height:1.1}
.fds-nav[data-nav="bottom"] .fds-navgroup{flex-direction:row;flex:1;gap:0}
.fds-nav[data-nav="bottom"] .fds-navgroup + .fds-navgroup{margin:0;padding:0;border:0}
.fds-nav[data-nav="bottom"] .fds-navgroup__title{
  position:absolute;width:1px;height:1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap}

/* ---- item and group ------------------------------------------------------
   The item is a row that happens to be a destination. It borrows the row's
   rhythm rather than inventing a second one. */
.fds-navitem{position:relative;display:flex;align-items:center;
  gap:var(--fds-sp-5);min-height:var(--fds-nav-item-h);
  padding-inline:var(--fds-sp-5);border-radius:var(--fds-r-control);
  color:var(--fds-text-muted);text-decoration:none;
  transition:background-color var(--fds-hover-in) var(--fds-ease-standard),
             color var(--fds-hover-in) var(--fds-ease-standard),
             transform var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-navitem__icon{flex:none;display:grid;place-items:center;
  width:var(--fds-sp-8);height:var(--fds-sp-8)}
.fds-navitem__icon svg{width:100%;height:100%;display:block}
.fds-navitem__label{min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.fds-navitem__count{margin-inline-start:auto;flex:none;
  font-family:var(--fds-font-mono);font-size:var(--fds-ui-micro-size);
  font-variant-numeric:tabular-nums;color:var(--fds-accent-mark)}
.fds-navitem[aria-current]{color:var(--fds-text);font-weight:500;
  background:var(--fds-accent-tint)}
.fds-navitem[aria-disabled="true"]{color:var(--fds-text-disabled);
  pointer-events:none}

/* The marker. Scale from the centre so it reveals rather than slides in from
   an edge it does not belong to. */
.fds-navitem::before{content:'';position:absolute;
  inset-block:var(--fds-sp-3);inset-inline-start:0;width:var(--fds-border-thick);
  border-radius:var(--fds-r-pill);background:var(--fds-accent);
  transform:scaleY(0);transform-origin:center;
  transition:transform var(--fds-dur-base) var(--fds-ease-emphasized)}
.fds-navitem[aria-current]::before{transform:scaleY(1)}
.fds-nav[data-nav="bottom"] .fds-navitem::before{
  inset-block:auto;inset-block-start:0;inset-inline:var(--fds-sp-6);
  width:auto;height:var(--fds-border-thick);
  transform:scaleX(0);transition-property:transform}
.fds-nav[data-nav="bottom"] .fds-navitem[aria-current]::before{transform:scaleX(1)}

.fds-navgroup{display:flex;flex-direction:column;gap:var(--fds-sp-1)}
.fds-navgroup + .fds-navgroup{margin-block-start:var(--fds-sp-5);
  padding-block-start:var(--fds-sp-5);
  border-block-start:var(--fds-border-w) solid var(--fds-hairline)}
.fds-navgroup__title{padding-inline:var(--fds-sp-5);
  padding-block:var(--fds-sp-2);
  font-size:var(--fds-ui-micro-size);text-transform:uppercase;
  letter-spacing:var(--fds-tr-eyebrow);color:var(--fds-text-muted)}

/* ---- app bar -------------------------------------------------------------
   Brand, the drawer trigger at SM, the title of where you are, and actions.
   It is sticky, not fixed: it scrolls with a long page's own scroller and
   stops at the top, which keeps it out of the way of a virtual keyboard. */
.fds-appbar{display:flex;align-items:center;gap:var(--fds-sp-5);
  min-height:var(--fds-appbar-h);padding-inline:var(--fds-sp-5);
  position:sticky;inset-block-start:0;z-index:var(--fds-z-sticky);
  background:var(--fds-surface);
  border-block-end:var(--fds-border-w) solid var(--fds-border)}
.fds-appbar[hidden]{display:none}
.fds-appbar__menu{flex:none}
.fds-appbar__brand{flex:none;font-weight:600;letter-spacing:-.01em;
  color:var(--fds-text);text-decoration:none}
.fds-appbar__title{min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;font-size:var(--fds-ui-heading);font-weight:500;margin:0}
.fds-appbar__actions{margin-inline-start:auto;display:flex;align-items:center;
  gap:var(--fds-sp-4)}

/* ---- action bar ----------------------------------------------------------
   Workflow actions for the thing in front of you. Not a destination, ever. */
.fds-actionbar{display:flex;align-items:center;gap:var(--fds-sp-4);
  padding:var(--fds-sp-4) var(--fds-sp-5);
  padding-block-end:calc(var(--fds-sp-4) + env(safe-area-inset-bottom,0px));
  background:var(--fds-raised);
  border-block-start:var(--fds-border-w) solid var(--fds-border)}
.fds-actionbar[hidden]{display:none}
.fds-actionbar__count{font-family:var(--fds-font-mono);
  font-size:var(--fds-ui-caption-size);color:var(--fds-text-muted)}
.fds-actionbar__actions{margin-inline-start:auto;display:flex;align-items:center;
  gap:var(--fds-sp-4)}
.fds-actionbar[data-dock="block-end"]{position:sticky;inset-block-end:0;
  z-index:var(--fds-z-sticky)}

/* ---- auto, resolved by the shell's own width -----------------------------
   Only `auto` is answered here. A named presentation is a decision the
   consumer already made and a media query does not get to overrule it.
   These blocks carry the geometry that differs, not a second copy of the
   component. */
@media (max-width:47.999rem){
  .fds-navshell{grid-template-columns:minmax(0,1fr);
    grid-template-areas:"bar" "main" "foot"}
  .fds-nav[data-nav="auto"]{position:fixed;inset-block:0;inset-inline-start:0;
    z-index:var(--fds-z-drawer);width:min(var(--fds-nav-side-w),calc(100vw - 3rem));
    background:var(--fds-floating);box-shadow:var(--fds-e3),var(--fds-e-edge);
    animation:fds-drawer-start-in var(--fds-dur-slow) var(--fds-ease-emphasized)}
  .fds-nav[data-nav="auto"][data-leaving="true"]{
    animation:fds-drawer-start-out var(--fds-dur-base) var(--fds-ease-exit) forwards}
}
@media (min-width:48rem) and (max-width:74.999rem){
  .fds-nav[data-nav="auto"]{position:relative;width:var(--fds-nav-rail-w);
    padding-inline:var(--fds-sp-3)}
  .fds-nav[data-nav="auto"] .fds-navitem{flex-direction:column;gap:var(--fds-sp-1);
    justify-content:center;text-align:center;padding-inline:var(--fds-sp-2)}
  .fds-nav[data-nav="auto"] .fds-navitem__label{font-size:var(--fds-ui-micro-size);
    line-height:1.1;white-space:normal}
  .fds-nav[data-nav="auto"] .fds-navitem__count{margin:0;position:absolute;
    inset-block-start:var(--fds-sp-2);inset-inline-end:var(--fds-sp-2)}
  .fds-nav[data-nav="auto"] .fds-navgroup__title{
    position:absolute;width:1px;height:1px;overflow:hidden;
    clip:rect(0 0 0 0);white-space:nowrap}
}
@media (min-width:75rem){
  .fds-nav[data-nav="auto"]{position:relative;width:var(--fds-nav-side-w)}
}

/* ---- motion --------------------------------------------------------------
   A fine pointer gets a small lateral move on hover; a coarse one does not,
   because a thumb is already on top of the thing it is moving. */
@media (hover:hover) and (pointer:fine){
  .fds-navitem:hover{background:var(--fds-hover-wash);color:var(--fds-text)}
  .fds-nav[data-nav="sidebar"] .fds-navitem:hover,
  .fds-nav[data-nav="drawer"] .fds-navitem:hover{transform:translateX(2px)}
  .fds-nav[data-nav="bottom"] .fds-navitem:hover .fds-navitem__icon{
    transform:translateY(-2px)}
}
.fds-nav[data-nav="bottom"] .fds-navitem__icon{
  transition:transform var(--fds-dur-fast) var(--fds-ease-standard)}
.fds-nav[data-nav="bottom"] .fds-navitem[aria-current] .fds-navitem__icon{
  transform:translateY(-2px)}

@media (forced-colors:active){
  .fds-navitem[aria-current]{background:Highlight;color:HighlightText}
  .fds-navitem::before{background:CanvasText}
  .fds-nav,.fds-appbar,.fds-actionbar{border-color:CanvasText}
}

/* Travel goes; state does not. The marker still appears, the active item is
   still filled — they simply arrive without the journey. */
@media (prefers-reduced-motion:reduce){
  .fds-nav[data-nav="drawer"],.fds-nav[data-nav="auto"]{animation-duration:1ms !important}
  .fds-navitem{transition-duration:1ms}
  .fds-navitem::before{transition-duration:1ms}
  .fds-navitem:hover{transform:none !important}
  .fds-nav[data-nav="bottom"] .fds-navitem__icon{transition-duration:1ms}
}


/* ==========================================================================
   31 · VALUE DISPLAY — 0.15 "Signal"

   THE KEYPAD IS AN ENTRY SURFACE, NOT THE OWNER OF THE DISPLAY. Until 0.15 the
   only readout in the system was `.fds-pad__value`, which belongs to the pad
   and dies with it. So a value entered on a pad could be shown in exactly one
   place: on that pad. Every screen needing the same fact somewhere else — in a
   form, beside a photo, repeated four times across a vehicle map — built its
   own. This is that readout as a component, mountable anywhere.

   ONE PRIMITIVE, THREE SCALES, and the scale is a decision about reading
   distance rather than about space left over:

     pad      ~22.5rem   the value being entered right now
     compact  ~13.5rem   a fact on a form, verified while it is read
     micro    ~8rem      a glance in a grid of readings

   MICRO IS FOR GLANCE, NOT FOR VERIFICATION. Four pressures across a vehicle
   map are a scan for the odd one out; a VIN somebody is checking character by
   character is not, and does not belong at micro.

   FDS DOES NOT CONVERT. The caller supplies the value and the unit already in
   the form it wants shown. This component picks no unit, applies no scale
   factor and holds no preference: tread-contract.json has said "/32 and mm are
   presentation/configuration; FDS does not convert units" since 0.11, and a
   display that quietly divided by ten would be the same class of mistake as a
   pad that knew which tire sizes are real. Units are chosen once, by the app,
   in its own settings — and never by a switch inside a keypad.

   COMPACT DOES NOT MEAN TRUNCATE THE FACT. An identifier that does not fit
   scrolls; it is never ellipsed and never abbreviated, because a VIN missing
   its last four characters still looks like a VIN. Only the LABEL truncates,
   because the label is what the reader already knows.
   ==========================================================================*/
.fds-valuedisplay{
  --_vd-pad-i:var(--fds-sp-7);   /* component-private: the scale is the API */
  --_vd-pad-b:var(--fds-sp-6);
  --_vd-value:2rem;
  --_vd-label:var(--fds-ui-micro-size);
  min-width:0;
  display:grid;gap:var(--fds-sp-2);align-content:center;
  padding:var(--_vd-pad-b) var(--_vd-pad-i);
  background:var(--fds-raised);
  border:var(--fds-border-w) solid var(--fds-border);
  border-radius:var(--fds-r-card);
  box-shadow:var(--fds-e-edge);
  transition:background-color var(--fds-dur-base) var(--fds-ease-standard),
             border-color var(--fds-dur-base) var(--fds-ease-standard),
             box-shadow var(--fds-dur-base) var(--fds-ease-standard);
}
/* The value being entered right now. The same accent edge the pad's current
   key uses, so a detached display still reads as attached to what is being
   typed. */
.fds-valuedisplay[data-current="true"]{
  border-color:var(--fds-accent-mark);
  box-shadow:0 0 0 var(--fds-border-w) var(--fds-accent-mark),var(--fds-e-edge)}

.fds-valuedisplay__top{display:flex;align-items:center;gap:var(--fds-sp-3);min-width:0}
/* The label is the one part that may truncate: the reader already knows what
   they asked for, and the fact is what they came to read. */
.fds-valuedisplay__label{min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;font-size:var(--_vd-label);font-weight:700;
  letter-spacing:.09em;text-transform:uppercase;color:var(--fds-text-muted)}
.fds-valuedisplay__meta{margin-inline-start:auto;flex-shrink:0;
  font-family:var(--fds-font-mono);font-variant-numeric:tabular-nums;
  font-size:var(--fds-ui-micro-size);color:var(--fds-text-muted)}
.fds-valuedisplay__read{min-width:0;display:flex;align-items:baseline;
  gap:var(--fds-sp-2);overflow:hidden}
.fds-valuedisplay__value{min-width:0;font-family:var(--fds-font-mono);
  font-variation-settings:"wdth" var(--fds-mono-wdth),"wght" 620;
  font-size:var(--_vd-value);line-height:1;letter-spacing:-.035em;
  font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--fds-text)}
.fds-valuedisplay__value[data-empty]{color:var(--fds-text-muted)}
.fds-valuedisplay__unit{font-family:var(--fds-font-mono);
  font-size:var(--fds-ui-caption-size);color:var(--fds-accent-mark);
  white-space:nowrap}
.fds-valuedisplay__sub{font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted);line-height:1.25;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* ---- scales ------------------------------------------------------------ */
.fds-valuedisplay--pad{width:min(100%,22.5rem);min-height:5.25rem}
.fds-valuedisplay--compact{--_vd-pad-i:var(--fds-sp-5);--_vd-pad-b:var(--fds-sp-4);
  --_vd-value:1.35rem;
  width:min(100%,13.5rem);min-height:4rem;border-radius:var(--fds-r-ctl-md)}
.fds-valuedisplay--micro{--_vd-pad-i:var(--fds-sp-4);--_vd-pad-b:var(--fds-sp-3);
  --_vd-value:1rem;
  width:min(100%,8rem);min-height:3.25rem;border-radius:var(--fds-r-mark);
  gap:var(--fds-sp-1)}
/* Micro is a glance. Provenance and caption are the first things to go,
   because at this size they are noise around the number. */
.fds-valuedisplay--micro .fds-valuedisplay__meta,
.fds-valuedisplay--micro .fds-valuedisplay__sub{display:none}

/* ---- format grammars ---------------------------------------------------
   The shell is one component; what varies is how the characters are grouped.
   A structured value is still one fact, so it is still one display. */
.fds-valuedisplay--identifier .fds-valuedisplay__value{
  font-size:min(var(--_vd-value),1.18rem);letter-spacing:.055em;
  /* Scrolls rather than truncates. A VIN missing four characters still looks
     like a VIN, which is what makes silent truncation dangerous here. */
  overflow-x:auto;scrollbar-width:none}
.fds-valuedisplay--identifier .fds-valuedisplay__value::-webkit-scrollbar{display:none}
.fds-valuedisplay--compact.fds-valuedisplay--identifier .fds-valuedisplay__value{font-size:.9rem}
.fds-valuedisplay--micro.fds-valuedisplay--identifier .fds-valuedisplay__value{
  font-size:.72rem;letter-spacing:.035em}
.fds-valuedisplay--plate .fds-valuedisplay__value{letter-spacing:.12em}

/* Segmented: 225/45R17 is one canonical value, not three fields. The
   separators are dimmer than the numbers because nobody reads a slash. */
.fds-valuedisplay__parts{display:flex;align-items:baseline;gap:.2em;min-width:0;
  font-family:var(--fds-font-mono);font-variant-numeric:tabular-nums;
  white-space:nowrap;overflow:hidden}
.fds-valuedisplay__part{font-size:var(--_vd-value);line-height:1;
  letter-spacing:-.035em;
  font-variation-settings:"wdth" var(--fds-mono-wdth),"wght" 620;
  color:var(--fds-text)}
.fds-valuedisplay__sep{font-size:calc(var(--_vd-value) * .65);color:var(--fds-text-muted)}
.fds-valuedisplay__tag{font-size:calc(var(--_vd-value) * .6);
  color:var(--fds-accent-mark);font-weight:700}

/* Split: a DOT code is a week and a year, and reading it as one four-digit
   number is the mistake the split exists to prevent. */
.fds-valuedisplay__split{display:flex;gap:var(--fds-sp-4);align-items:flex-end}
.fds-valuedisplay__splitpart{display:grid;gap:1px}
.fds-valuedisplay__splitvalue{font-family:var(--fds-font-mono);
  font-size:var(--_vd-value);line-height:1;font-variant-numeric:tabular-nums;
  font-variation-settings:"wdth" var(--fds-mono-wdth),"wght" 620;color:var(--fds-text)}
.fds-valuedisplay__splitlabel{font-size:var(--fds-ui-micro-size);
  color:var(--fds-text-muted);text-transform:uppercase;letter-spacing:.08em}
.fds-valuedisplay--micro .fds-valuedisplay__split{gap:var(--fds-sp-3)}

/* ---- mounting -----------------------------------------------------------
   Attached, the display and the pad are one surface and the seam between them
   is a hairline rather than two borders and a shadow. Separate, the display is
   an ordinary card and may sit anywhere on the screen. Nothing about the pad
   changes either way, which is the whole point of detaching it. */
.fds-valuedisplay--attached{border-end-start-radius:0;border-end-end-radius:0;
  border-block-end-color:var(--fds-hairline);width:100%;max-width:none}
.fds-valuedisplay--attached + .fds-pad{border-start-start-radius:0;
  border-start-end-radius:0;border-block-start:0}

@media (prefers-reduced-motion:reduce){
  .fds-valuedisplay{transition-duration:1ms}
}

/* =========================================================================
   32 · BRAND & LOADER — 0.16 "Signal"

   The brand layer was blocked on the wordmark from 0.7.1 to 0.15. It ships
   now because the four application names settled: Floor, Shop, Storage,
   Counter. Those four appear here as ramp keys and nowhere else — no
   display name for any of them is stored in this stylesheet, because the
   product word is transcreated per locale and belongs to the consumer.

   One component, one DOM, five presentations — the same shape as §30. A
   lockup is live text, never artwork: vector output is the frozen rendering
   for print and video, not the source. Reflow to fit; never condense,
   never scale horizontally, never squeeze the tracking.

   Fitmentry does not translate. The product word does. The parent is the
   certification layer and it is the same word in every market.
   ========================================================================= */

:root{
  --fds-brand-font-latin:var(--fds-font-sans);
  --fds-brand-font-jp:'Noto Sans JP','Hiragino Sans','Yu Gothic',sans-serif;
  --fds-brand-font-kr:'Noto Sans KR','Apple SD Gothic Neo','Malgun Gothic',sans-serif;
  --fds-brand-parent-track:.34em;
  --fds-brand-master-track:.18em;
  --fds-brand-parent-weight:500;
  --fds-brand-child-weight:600;
  --fds-brand-child-track:-.03em;
  --fds-loader-gap:2rem;
  --fds-loader-hold:3s;
  --fds-loader-in:.45s;
  --fds-loader-out:.45s;
}

/* --- 32.1 · product identity ------------------------------------------
   m1/m2/m3 are the loader ramp. Identity is carried by the motion field,
   not by the hue — the field is what tells a technician which application
   is starting, and colour only confirms what the field already said.

   One construction rule per ramp: hue arc under 90 degrees, chroma held
   flat, lightness doing the climbing. Day is the same hue angles with
   lightness running the other way. Both themes are authored; neither is a
   transform of the other and neither may be generated from the other.

   Storage is a warm neutral because the blue band is not available. Cyan
   is --fds-accent, blue is --fds-focus, violet is --fds-in, and every
   angle from 195 to 255 degrees measures 11-29 ΔE from one of them at the
   required contrast, in both themes. Taupe is what is left once Floor
   holds green, Counter holds magenta and Shop holds cool steel.

   Three colour identities fit this system, not four. Green, magenta and
   cool neutral clear comfortably. The fourth is a warm neutral separated
   from Shop by temperature and by its motion field — Breath against Hive,
   which are nothing alike. Say that plainly rather than pretending the
   wheel has four free families on it.

   Clearance from the reserved hues is 20 ΔE here, not 40. .fds-loader is
   a full-surface state with no interactive elements on it: no focus ring,
   no control, no status mark. 40 belongs to a product mark placed beside
   live controls, and this is not one. Do not carry this 20 over to any
   surface that is.

   Known weak points, recorded rather than hidden:
     · shop/storage 14.1 night, 13.8 day — the closest pair in the set,
       two neutrals separated by temperature. It survives CVD; it does
       not shout.
     · counter day m3 19.4 ΔE from --fds-dg — under the 20 it is measured
       against, by six tenths.
     · floor night bloom 6.5 ΔE from --fds-ok — accepted only because a
       bloom appears at wave crests and never as a solid area. It is the
       one number here that would not survive being placed on a fill.

   --fds-loader-tick-scale is the measured day correction: a stroke of the
   same rendered weight reads lighter against a light page, so day carries
   1.28x to hold the same presence. It is a token rather than a constant so
   the correction is visible where the palettes are.                       */
:root,[data-theme="day"]{
  --fds-product-floor-m1:#7AA700;   --fds-product-floor-m2:#379400;
  --fds-product-floor-m3:#007F2C;   --fds-product-floor-bloom:#009D68;
  --fds-product-shop-m1:#809EA3;    --fds-product-shop-m2:#6A868D;
  --fds-product-shop-m3:#57717B;    --fds-product-shop-bloom:#FF335D;
  --fds-product-storage-m1:#AF9385; --fds-product-storage-m2:#997B71;
  --fds-product-storage-m3:#856660; --fds-product-storage-bloom:#009A8C;
  --fds-product-counter-m1:#FA4BEF; --fds-product-counter-m2:#F500A3;
  --fds-product-counter-m3:#D80063; --fds-product-counter-bloom:#FF3559;
  --fds-product-field-floor:#E9ECEF;
  --fds-loader-tick-scale:1.28;
}
[data-theme="night"]{
  --fds-product-floor-m1:#6F9800;   --fds-product-floor-m2:#4BC000;
  --fds-product-floor-m3:#00E558;   --fds-product-floor-bloom:#00D690;
  --fds-product-shop-m1:#739196;    --fds-product-shop-m2:#90ADB5;
  --fds-product-shop-m3:#AFCBD6;    --fds-product-shop-bloom:#FFA1A5;
  --fds-product-storage-m1:#A18678; --fds-product-storage-m2:#C1A297;
  --fds-product-storage-m3:#E2BFB8; --fds-product-storage-bloom:#00D2C0;
  --fds-product-counter-m1:#EB39E0; --fds-product-counter-m2:#FF79C1;
  --fds-product-counter-m3:#FFB0C1; --fds-product-counter-bloom:#FFA2A3;
  --fds-product-field-floor:#141824;
  --fds-loader-tick-scale:1;
}

/* The same values again, for the consumer that has not set the axis at all.

   FDS asks consumers to leave `data-theme` unset unless a person has stated a
   preference -- omitted means "nobody has said", and the OS answers. The
   semantic palette honours that in §2 through its `prefers-color-scheme:dark`
   mirror on `:root:not([data-theme])`. The brand layer did not, and shipped in
   0.16 declaring these tokens under
   `:root,[data-theme="day"]` and `[data-theme="night"]` only.

   So a phone on OS dark with the axis unset resolved a NIGHT page and a DAY
   field: `--fds-product-field-floor` came out #E9ECEF, above the page instead
   of below it, and the dark form the field is built around inverted and
   vanished. The loader still ran, still drew its ticks and still held its
   floor, so nothing failed loudly -- it just stopped being the thing it is.
   Reported from Floor on three devices, which is the whole consumer list.

   The day values stay on `:root` and need no branch here; only the dark path
   was missing. `:root:not([data-theme])` is (0,2,0) against `:root`'s (0,1,0),
   so these win where they apply and never fight an axis somebody has set. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme]){
    --fds-product-floor-m1:#6F9800; --fds-product-floor-m2:#4BC000;
    --fds-product-floor-m3:#00E558; --fds-product-floor-bloom:#00D690;
    --fds-product-shop-m1:#739196; --fds-product-shop-m2:#90ADB5;
    --fds-product-shop-m3:#AFCBD6; --fds-product-shop-bloom:#FFA1A5;
    --fds-product-storage-m1:#A18678; --fds-product-storage-m2:#C1A297;
    --fds-product-storage-m3:#E2BFB8; --fds-product-storage-bloom:#00D2C0;
    --fds-product-counter-m1:#EB39E0; --fds-product-counter-m2:#FF79C1;
    --fds-product-counter-m3:#FFB0C1; --fds-product-counter-bloom:#FFA2A3;
    --fds-product-field-floor:#141824;
    --fds-loader-tick-scale:1;
  }
}

/* --- 32.2 · lockup ----------------------------------------------------
   The child carries lang, so the script picks its own face. The CJK
   families are named here because the FDS sans has no CJK coverage and a
   lockup that falls back to a system face has stopped being the lockup. */
.fds-lockup{display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;gap:var(--fds-sp-5);overflow:hidden}
.fds-lockup__parent{font-family:var(--fds-brand-font-latin);
  font-weight:var(--fds-brand-parent-weight);text-transform:uppercase;
  letter-spacing:var(--fds-brand-parent-track);text-indent:var(--fds-brand-parent-track);
  white-space:nowrap;color:var(--fds-text-muted);font-size:.7rem;margin:0}
.fds-lockup__child{font-family:var(--fds-brand-font-latin);
  font-weight:var(--fds-brand-child-weight);letter-spacing:var(--fds-brand-child-track);
  line-height:.92;white-space:nowrap;color:var(--fds-text);
  font-size:clamp(2.6rem,8vw,7rem);margin:0}
[lang^="ja"] .fds-lockup__child,.fds-lockup__child[lang^="ja"]{
  font-family:var(--fds-brand-font-jp);letter-spacing:.01em;line-height:1}
[lang^="ko"] .fds-lockup__child,.fds-lockup__child[lang^="ko"]{
  font-family:var(--fds-brand-font-kr);letter-spacing:0;line-height:1}

.fds-lockup[data-lockup="brand-hero"] .fds-lockup__parent{
  font-size:clamp(2rem,5vw,4.6rem);color:var(--fds-text);
  letter-spacing:var(--fds-brand-master-track);text-indent:var(--fds-brand-master-track)}
.fds-lockup[data-lockup="brand-hero"] .fds-lockup__child{
  font-size:clamp(1.3rem,2.5vw,2rem);color:var(--fds-text-muted);
  letter-spacing:var(--fds-tr-heading);font-weight:500}
.fds-lockup[data-lockup="balanced"]{flex-direction:row;align-items:baseline;
  gap:var(--fds-sp-6);justify-content:flex-start;text-align:start}
.fds-lockup[data-lockup="balanced"] .fds-lockup__parent{font-size:.68rem}
.fds-lockup[data-lockup="balanced"] .fds-lockup__child{font-size:clamp(1.8rem,4vw,3.2rem)}
.fds-lockup[data-lockup="brand-only"] .fds-lockup__child{display:none}
.fds-lockup[data-lockup="brand-only"] .fds-lockup__parent{
  font-size:clamp(2.8rem,8vw,7rem);color:var(--fds-text);
  letter-spacing:var(--fds-brand-master-track);text-indent:var(--fds-brand-master-track)}
.fds-lockup[data-lockup="product-only"] .fds-lockup__parent{display:none}

/* --- 32.3 · loader ----------------------------------------------------
   A full-surface indeterminate state for a surface with nothing to show
   yet. The bar is drawn at tick weight in the field's own ramp so it
   belongs to the field rather than sitting on it, and it is throttled to
   a minimum hold: a bar that races to full and then waits is worse than
   one that was honest about its floor.                                   */
.fds-loader{position:fixed;inset:0;z-index:var(--fds-z-overlay,900);
  background:var(--fds-page);display:grid;place-items:center;overflow:hidden}
/* Bounded rather than full-surface: a panel that is still loading, and the
   field catalogue in the documentation. It is the same component and the
   same behaviour — only the box changes, which is why this is a modifier
   and not a second loader. A bounded loader still owes its floor. */
.fds-loader--inline{position:relative;inset:auto;z-index:auto;
  width:100%;height:100%;border-radius:var(--fds-r-ctl-md)}
.fds-loader__field{position:absolute;inset:0;display:block;width:100%;height:100%}
.fds-loader__mark{position:relative;z-index:1;
  transition:opacity var(--fds-loader-in) var(--fds-ease-standard,ease)}
.fds-loader[data-state="out"] .fds-loader__mark{opacity:0}
.fds-loader__label{position:absolute;inset-inline:0;bottom:var(--fds-sp-8);
  text-align:center;font-family:var(--fds-font-mono);font-size:var(--fds-fs-100);
  letter-spacing:var(--fds-tr-micro);text-transform:uppercase;color:var(--fds-text-muted)}

/* The field freezes rather than disappearing. A loader that respects
   reduced motion by removing itself has removed the loading state, so the
   identity, the label and the bar all stay and only the travel stops. */
@media (prefers-reduced-motion:reduce){
  .fds-loader__field{opacity:.55}
  .fds-loader__mark{transition:none}
}
