@charset "utf-8";
/* ============================================================================
   SWISS TAX KIT — app.css
   Single stylesheet. Plain CSS + custom properties. No build step, no Tailwind.
   Mobile-first. Breakpoints: 480 / 768 / 960 (nav) / 1100 (table) / 1280 (max).
   Colour is defined ONCE per slot with light-dark(); the manual theme toggle
   flips `color-scheme` on <html data-theme>. Baseline-2024 feature, ~99% of
   traffic. Legacy escape hatch documented at the end of the token block.
   ----------------------------------------------------------------------------
   Brand direction: "Federal Instrument" — ink as the brand, one cold blue for
   interaction, green reserved strictly for recovered money, paper-cool
   neutrals, hairlines and tabular numerals. NO Swiss cross, NO Swiss red:
   see the note above the brand tokens and LEGAL.md.
   Type: IBM Plex Sans + Plex Mono, with Plex Sans Condensed as the German
   escape hatch. All contrast ratios are computed and stated inline.
   ========================================================================= */

@layer reset, tokens, base, layout, components, content, utilities;

/* ==========================================================================
   1. TOKENS
   ========================================================================== */
@layer tokens {

  :root {
    color-scheme: light dark;

    /* --- 1.1 Type families ------------------------------------------------
       ONE webfont family. Two network requests max (CSS + woff2 subset).
       --font-display defaults to the sans; if the brand adds a display face
       later, override ONLY this token — every heading already uses it. */
    --font-sans: "IBM Plex Sans", ui-sans-serif, system-ui, "Segoe UI", Roboto,
                 "Helvetica Neue", Arial, sans-serif;
    --font-display: var(--font-sans);
    --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Consolas,
                 "Liberation Mono", monospace;
    /* The German escape hatch. When Verrechnungssteuerrückerstattung has to
       fit a column header, switch FAMILY rather than dropping below 12px. */
    --font-condensed: "IBM Plex Sans Condensed", var(--font-sans);

    /* --- 1.2 Type scale ---------------------------------------------------
       Fluid only where it matters (display sizes). Body text is fixed so that
       long German compounds never reflow between breakpoints. */
    --fs-3xs: 0.6875rem;                            /* 11 — table micro-labels */
    --fs-2xs: 0.75rem;                              /* 12 — eyebrows, legal    */
    --fs-xs:  0.8125rem;                            /* 13 — table body, meta   */
    --fs-sm:  0.875rem;                             /* 14 — UI, captions       */
    --fs-base: 1rem;                                /* 16 — body               */
    --fs-md:  1.0625rem;                            /* 17 — prose body         */
    --fs-lg:  1.1875rem;                            /* 19 — lede, card titles  */
    --fs-xl:  1.375rem;                             /* 22 — h3                 */
    --fs-2xl: clamp(1.5rem, 1.2rem + 1.4vw, 1.875rem);   /* h2                 */
    --fs-3xl: clamp(1.875rem, 1.4rem + 2.2vw, 2.5rem);   /* h1 on inner pages  */
    --fs-4xl: clamp(2.125rem, 1.35rem + 3.6vw, 3.25rem); /* hero h1            */
    --fs-figure: clamp(2.5rem, 1.6rem + 4.6vw, 4rem);    /* the CHF number     */

    --lh-flat: 1;
    --lh-tight: 1.08;      /* display only */
    --lh-snug: 1.22;       /* h2/h3 */
    --lh-normal: 1.45;     /* UI */
    --lh-relaxed: 1.65;    /* prose — German needs the air */

    --tracking-display: -0.021em;
    --tracking-tight: -0.012em;
    --tracking-normal: 0;
    --tracking-caps: 0.07em;

    --fw-regular: 400;
    --fw-medium: 500;
    --fw-semibold: 600;
    --fw-bold: 700;

    --measure: 68ch;        /* prose max width */
    --measure-narrow: 46ch; /* lede, card copy */

    /* --- 1.3 Space scale (4px base) -------------------------------------- */
    --space-0: 0;
    --space-1: 0.25rem;   /*  4 */
    --space-2: 0.5rem;    /*  8 */
    --space-3: 0.75rem;   /* 12 */
    --space-4: 1rem;      /* 16 */
    --space-5: 1.25rem;   /* 20 */
    --space-6: 1.5rem;    /* 24 */
    --space-8: 2rem;      /* 32 */
    --space-10: 2.5rem;   /* 40 */
    --space-12: 3rem;     /* 48 */
    --space-16: 4rem;     /* 64 */
    --space-20: 5rem;     /* 80 */
    --space-24: 6rem;     /* 96 */
    --space-32: 8rem;     /* 128 */

    /* Vertical rhythm. Every <section> uses one of these three. No exceptions. */
    --section-y:       clamp(3rem, 1.5rem + 5vw, 5rem);    /* default       */
    --section-y-tight: clamp(2rem, 1.2rem + 3.2vw, 3.25rem);/* strips, notes */
    --section-y-loose: clamp(5rem, 3rem + 9vw, 8.5rem);     /* hero, pricing */

    /* --- 1.4 Layout ------------------------------------------------------ */
    --container: 1200px;         /* the widest any block may be: the nav's width */
    --container-narrow: 760px;   /* centred forms and result cards */
    --container-app: 1040px;     /* every /app/* screen: one width for card, table and panel */
    --container-wide: var(--container); /* retired: nothing may be wider than the nav */
    --gutter: clamp(1rem, 0.5rem + 2.5vw, 2.5rem);
    --nav-h: 56px;

    /* --- 1.5 Radii -------------------------------------------------------- */
    --r-xs: 4px;
    --r-sm: 6px;
    --r-md: 10px;
    --r-lg: 14px;
    --r-xl: 20px;
    --r-pill: 999px;
    --r-card: var(--r-lg);
    --r-field: var(--r-sm);

    /* --- 1.6 Borders ------------------------------------------------------ */
    --bw: 1px;
    --bw-strong: 1.5px;
    --border: var(--bw) solid var(--c-border);
    --border-strong: var(--bw) solid var(--c-border-strong);
    --border-dashed: var(--bw-strong) dashed var(--c-border-strong);

    /* --- 1.7 Elevation ----------------------------------------------------
       Hierarchy is carried by BORDER + SURFACE STEP first, shadow second.
       Dark mode keeps the same tokens but the shadows read as depth, not blur. */
    --shadow-0: none;
    --shadow-1: 0 1px 2px light-dark(rgba(16,18,22,.05), rgba(0,0,0,.5));
    --shadow-2: 0 1px 2px light-dark(rgba(16,18,22,.05), rgba(0,0,0,.5)),
                0 8px 20px -12px light-dark(rgba(16,18,22,.18), rgba(0,0,0,.7));
    --shadow-3: 0 2px 4px light-dark(rgba(16,18,22,.06), rgba(0,0,0,.55)),
                0 18px 42px -18px light-dark(rgba(16,18,22,.24), rgba(0,0,0,.8));
    --shadow-scroll: light-dark(rgba(16,18,22,.14), rgba(0,0,0,.6));

    /* --- 1.8 Motion ------------------------------------------------------- */
    --dur-1: 90ms;    /* hover/active colour */
    --dur-2: 150ms;   /* small transforms, dropzone state */
    --dur-3: 220ms;   /* accordion, drawer */
    --dur-4: 320ms;   /* page-level reveals (max) */
    --ease-out: cubic-bezier(.22, .8, .32, 1);
    --ease-in-out: cubic-bezier(.4, 0, .2, 1);
    --ease-emphasis: cubic-bezier(.34, 1.3, .64, 1); /* one bounce, used twice on the whole site */

    /* --- 1.9 Colour: neutrals (final) -------------------------------------
       light-dark(LIGHT, DARK). Warm-neutral greys; the dark end is a true
       near-black so the OLED phone half of the audience gets real contrast. */
    --c-canvas:          light-dark(#f7f7f5, #0d0f12);  /* page ground        */
    --c-surface:         light-dark(#ffffff, #15181c);  /* cards, nav, table  */
    --c-surface-raised:  light-dark(#ffffff, #1b1f24);  /* popovers, drawer   */
    --c-surface-sunken:  light-dark(#f1f1ee, #101317);  /* inputs, code, zebra*/
    --c-surface-inverse: light-dark(#14161a, #f2f4f6);

    --c-ink:    light-dark(#14161a, #f3f5f7);  /* headings, figures  ~16:1   */
    --c-ink-2:  light-dark(#3c4149, #c5ccd4);  /* body               ~10:1   */
    --c-ink-3:  light-dark(#5e6570, #949ca6);  /* meta, captions     ~5.1:1  */
    --c-ink-4:  light-dark(#8a9099, #6b737d);  /* decorative ONLY, never text*/
    --c-ink-inverse: light-dark(#ffffff, #14161a);

    --c-border:        light-dark(#e4e4e0, #272c32);
    --c-border-strong: light-dark(#cdcdc8, #3a4148);
    --c-overlay:       light-dark(rgba(20,22,26,.45), rgba(0,0,0,.66));

    /* --- 1.10 Colour: brand + accent -------------------------------------
       "Federal Instrument". Ink is the brand; one cold blue carries
       interaction; green is a SEMANTIC reserved for recovered money and is
       never used decoratively. Every rival spends its accent on everything,
       so the reclaimable CHF figure ends up being the only green thing on
       the page — which is exactly where the eye should go at the moment of
       the CHF 19 decision.

       NO SWISS CROSS AND NO SWISS RED, ANYWHERE. Both are legally permitted
       to a private company, and both are wrong here. The IPI's own
       examination guidelines use a private advisory service with a
       Swiss-prefixed name and a Swiss cross as the textbook example of
       misleading "official character" — tax filing is more government-
       adjacent than that example, not less. A red cross-or-plus glyph on
       white separately risks the Rotkreuzgesetz, which protects the inverse
       mark in any shade of red on any white ground. And red means loss in
       every financial UI on earth, which is semantically backwards for a
       product about getting money back. Red is reserved 100% for danger.
       See LEGAL.md.

       #0a47c7 sits 18° off Tailwind indigo (the sibling product), 10° off
       Bootstrap blue and 19° off moneyland's blue, and hits 7.67:1 against
       white in BOTH directions — so one value works as link text and as a
       button fill, at AAA. */
    --c-brand:        light-dark(#0a47c7, #93b0ff);   /* 7.67:1 / 8.10:1 AAA  */
    --c-brand-hover:  light-dark(#08379b, #b3c8ff);   /* 10.34:1              */
    --c-brand-ink:    light-dark(#ffffff, #0a121c);   /* on-brand text        */
    --c-brand-tint:   light-dark(#eaf0fe, #101f3a);   /* brand on tint 6.72:1 */
    --c-brand-border: light-dark(#c3d3f7, #24375c);
    --c-accent:       light-dark(#0a47c7, #93b0ff);   /* no second hue        */
    --c-accent-tint:  light-dark(#eaf0fe, #101f3a);

    /* --- 1.11 Colour: semantic status (final) ----------------------------- */
    /* Money recovered. Reserved — never a decorative green.
       WARNING, and this is not optional: under protanopia the money green and
       the danger red separate by only 1.14:1, and in dark mode by 1.03:1
       under deuteranopia — i.e. for red-green colourblind users they are
       INDISTINGUISHABLE. Therefore money-positive ALWAYS carries an explicit
       "+" and the CHF label, and danger ALWAYS carries an icon. Colour is
       never the sole carrier of meaning here (WCAG 1.4.1). */
    --c-positive:        light-dark(#0a5730, #4ed88e);  /* 8.68:1 / 9.44:1 AAA */
    --c-positive-ink:    light-dark(#0a5730, #7ae2b5);
    --c-positive-tint:   light-dark(#e9f6ee, #0f2b1e);
    --c-positive-border: light-dark(#0f6b3c, #1d5340);

    --c-warning:        light-dark(#8a5300, #e8a33d);
    --c-warning-tint:   light-dark(#fdf3e3, #2c2008);
    --c-warning-border: light-dark(#f0dcb4, #55411a);

    --c-danger:        light-dark(#b3261e, #ff6b61);
    --c-danger-ink:    light-dark(#8c1c16, #ff9d96);
    --c-danger-tint:   light-dark(#fdeceb, #2c1311);
    --c-danger-border: light-dark(#f2c6c2, #5c2925);

    --c-focus: light-dark(#14161a, #f3f5f7); /* focus ring: ink, not brand —
                                                guarantees 3:1 on every surface
                                                whatever the brand hue becomes */

    --c-selection: light-dark(#dbe6ff, #2a3d6b);  /* ◼ BRAND-adjacent */
  }

  /* Manual override. Server renders <html data-theme="dark"> from a cookie,
     so there is no flash and no blocking inline script. */
  :root[data-theme="light"] { color-scheme: only light; }
  :root[data-theme="dark"]  { color-scheme: only dark; }

  /* LEGACY ESCAPE HATCH (only if analytics ever shows pre-2024 browsers):
     wrap a light-only palette in
     @supports not (color: light-dark(#000,#fff)) { :root { --c-canvas:#f7f7f5; … } }
     and it degrades to light mode instead of breaking. */
}

/* ==========================================================================
   2. RESET + BASE
   ========================================================================== */
@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; }

  html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    /* Sticky nav must never obscure a focused element or an anchor target
       (WCAG 2.2 — 2.4.11 Focus Not Obscured). Global, not per-component. */
    scroll-padding-top: calc(var(--nav-h) + var(--space-4));
    scroll-behavior: smooth;
  }
  @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

  body {
    min-height: 100svh;
    background: var(--c-canvas);
    color: var(--c-ink-2);
    font-family: var(--font-sans);
    font-size: var(--fs-base);
    line-height: var(--lh-normal);
    font-synthesis-weight: none;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
  }

  img, svg, video, canvas { display: block; max-width: 100%; }
  img, video { height: auto; }
  input, button, textarea, select { font: inherit; color: inherit; }
  button { background: none; border: 0; }
  ::selection { background: var(--c-selection); }

  /* German compounds (Verrechnungssteuer-Rückforderungsformular) must never
     blow out a grid cell, so overflow-wrap is universal. Hyphenation is NOT:
     it is switched on only under a German, French or Italian <html lang>.
     The old form — an element rule setting hyphens:auto, then
     [lang="en"] { hyphens: manual } on the html element only — lost on every
     element to the element rule, and English shipped hyphenated site-wide
     ("sum-mary", "ac-count", "ex-ceeds"). tests/composition.spec.ts pins it. */
  h1, h2, h3, h4, p, li, td, th, dd, dt, summary, label, figcaption {
    overflow-wrap: break-word;
    hyphens: manual;
  }
  :lang(de) :is(h1, h2, h3, h4, p, li, td, th, dd, dt, summary, label, figcaption),
  :lang(fr) :is(h1, h2, h3, h4, p, li, td, th, dd, dt, summary, label, figcaption),
  :lang(it) :is(h1, h2, h3, h4, p, li, td, th, dd, dt, summary, label, figcaption) {
    hyphens: auto;
  }
}

@layer base {
  h1, h2, h3, h4 {
    font-family: var(--font-display);
    color: var(--c-ink);
    font-weight: var(--fw-semibold);
    text-wrap: balance;
  }
  h1 { font-size: var(--fs-3xl); line-height: var(--lh-tight); letter-spacing: var(--tracking-display); }
  h2 { font-size: var(--fs-2xl); line-height: var(--lh-snug); letter-spacing: var(--tracking-tight); }
  h3 { font-size: var(--fs-xl);  line-height: var(--lh-snug); letter-spacing: var(--tracking-tight); }
  h4 { font-size: var(--fs-lg);  line-height: var(--lh-snug); }
  p  { text-wrap: pretty; }

  a { color: var(--c-brand); text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
  a:hover { color: var(--c-brand-hover); }

  /* One ring, everywhere. Never remove without replacing. */
  :focus-visible {
    outline: 2px solid var(--c-focus);
    outline-offset: 2px;
    border-radius: var(--r-xs);
  }
  :focus:not(:focus-visible) { outline: none; }

  strong, b { font-weight: var(--fw-semibold); color: var(--c-ink); }
  small { font-size: var(--fs-sm); }
  code, kbd, samp { font-family: var(--font-mono); font-size: 0.92em; }

  /* All currency, percentages, dates, ISINs: one column width per glyph. */
  .num, td.num, th.num, .figure-chf, time, .tabular {
    font-variant-numeric: tabular-nums lining-nums slashed-zero;
    font-feature-settings: "tnum" 1, "lnum" 1, "zero" 1;
  }

  [hidden] { display: none !important; }
}

/* ==========================================================================
   3. LAYOUT PRIMITIVES
   ========================================================================== */
@layer layout {
  .container {
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }
  .container--narrow { max-width: var(--container-narrow); }
  .container--app    { max-width: var(--container-app); }
  .container--wide   { max-width: var(--container-wide); }

  .section { padding-block: var(--section-y); }
  .section--tight { padding-block: var(--section-y-tight); }
  .section--loose { padding-block: var(--section-y-loose); }
  /* A section that continues the one above it (the "I'm stuck" rail under a flow screen): no top padding, so the gap is one section's worth, not two. */
  .section--joined { padding-block-start: 0; }
  .section:has(+ .section--joined) { padding-block-end: var(--space-5); }
  .section--surface { background: var(--c-surface); border-block: var(--border); }
  .section--sunken  { background: var(--c-surface-sunken); }

  /* Below-the-fold sections skip layout/paint until near the viewport.
     Always pair with contain-intrinsic-size or you reintroduce CLS. */
  .section--deferred {
    content-visibility: auto;
    contain-intrinsic-size: auto 640px;
  }

  .stack > * + * { margin-block-start: var(--flow, var(--space-4)); }
  .stack--lg { --flow: var(--space-6); }
  .stack--sm { --flow: var(--space-2); }

  .cluster {
    display: flex; flex-wrap: wrap;
    gap: var(--gap, var(--space-3));
    align-items: center;
  }

  .section-head { max-width: 52ch; margin-block-end: var(--space-8); }
  .section-head .eyebrow { margin-block-end: var(--space-2); }
  .section-head p { margin-block-start: var(--space-3); font-size: var(--fs-lg); color: var(--c-ink-3); }

  .eyebrow {
    display: block;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--c-ink-3);
  }
}

/* ==========================================================================
   4. COMPONENTS
   ========================================================================== */
@layer components {

  /* --- 4.0 Skip link ---------------------------------------------------- */
  .skip-link {
    position: absolute; inset-inline-start: var(--space-4); top: var(--space-2);
    z-index: 100;
    transform: translateY(-160%);
    padding: var(--space-3) var(--space-4);
    background: var(--c-surface-raised); color: var(--c-ink);
    border: var(--border-strong); border-radius: var(--r-sm);
    box-shadow: var(--shadow-3);
    transition: transform var(--dur-2) var(--ease-out);
  }
  .skip-link:focus-visible { transform: translateY(0); }

  /* --- 4.1 Buttons ------------------------------------------------------
     min-height 44px everywhere (WCAG 2.2 SC 2.5.8 asks 24; we ship 44 because
     half the traffic is thumbs on a deadline). */
  .btn {
    display: inline-flex; align-items: center; justify-content: center;
    gap: var(--space-2);
    min-height: 44px;
    padding: var(--space-3) var(--space-5);
    border-radius: var(--r-md);
    font-size: var(--fs-base);
    font-weight: var(--fw-medium);
    line-height: var(--lh-normal);
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    white-space: normal;           /* German labels wrap, never clip */
    transition: background-color var(--dur-1) var(--ease-out),
                border-color var(--dur-1) var(--ease-out),
                color var(--dur-1) var(--ease-out),
                transform var(--dur-1) var(--ease-out);
  }
  .btn:active { transform: translateY(1px); }
  .btn[aria-disabled="true"], .btn:disabled { opacity: .7; pointer-events: none; }

  .btn--primary {
    background: var(--c-brand); color: var(--c-brand-ink);
    border: var(--bw) solid transparent;
    box-shadow: var(--shadow-1);
  }
  .btn--primary:hover { background: var(--c-brand-hover); color: var(--c-brand-ink); }

  .btn--secondary {
    background: var(--c-surface); color: var(--c-ink);
    border: var(--border-strong);
  }
  .btn--secondary:hover { background: var(--c-surface-sunken); color: var(--c-ink); }

  .btn--ghost { color: var(--c-ink-2); border: var(--bw) solid transparent; }
  .btn--ghost:hover { background: var(--c-surface-sunken); color: var(--c-ink); }

  .btn--lg { min-height: 52px; padding-inline: var(--space-6); font-size: var(--fs-lg); }
  .btn--block { width: 100%; }
  .btn--icon { min-width: 44px; padding: var(--space-2); }

  /* --- 4.2 Badges / chips ------------------------------------------------ */
  .badge {
    display: inline-flex; align-items: center; gap: var(--space-1);
    padding: 2px var(--space-2);
    border-radius: var(--r-pill);
    font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
    letter-spacing: .01em;
    border: var(--bw) solid transparent;
    /* A badge is a label, not a paragraph. Without this a two-word status in a
       narrow table column wraps to three lines and doubles the row height. The
       table already scrolls horizontally, so there is somewhere for it to go. */
    white-space: nowrap;
  }
  .badge--neutral  { background: var(--c-surface-sunken); color: var(--c-ink-2); border-color: var(--c-border); }
  .badge--positive { background: var(--c-positive-tint); color: var(--c-positive-ink); border-color: var(--c-positive-border); }
  .badge--warning  { background: var(--c-warning-tint);  color: var(--c-warning);     border-color: var(--c-warning-border); }
  .badge--danger   { background: var(--c-danger-tint);   color: var(--c-danger-ink);  border-color: var(--c-danger-border); }
  .badge--brand    { background: var(--c-brand-tint);    color: var(--c-brand);       border-color: var(--c-brand-border); }
  /* A badge inside a flex column (the price card) would be stretched to the
     column's width; it is a label and keeps its own. */
  .price-card > .badge { align-self: flex-start; margin-block: var(--space-4) var(--space-2); }
  /* The suggested tier's label, above the name so it is read before the price
     rather than as a footnote to it. Scoped to beat `.price-card > .badge`
     above, which is two classes deep and was still applying its space-4 top
     margin here: as the card's first child it needs no gap but the padding. */
  .price-card > .price-card__badge { margin-block: 0 var(--space-3); }
  .price-card__badge--none { visibility: hidden; }

  /* --- 4.3 Nav ----------------------------------------------------------- */
  .nav {
    position: sticky; top: 0; z-index: 50;
    height: var(--nav-h);
    display: flex; align-items: center;
    background: color-mix(in srgb, var(--c-canvas) 84%, transparent);
    backdrop-filter: saturate(1.6) blur(12px);
    -webkit-backdrop-filter: saturate(1.6) blur(12px);
    border-block-end: var(--bw) solid transparent;
    transition: border-color var(--dur-2) var(--ease-out),
                background-color var(--dur-2) var(--ease-out);
  }
  /* 4 lines of vanilla JS set data-scrolled; the hairline only appears once
     content is behind the bar. No shadow — it would shift nothing but it reads
     heavier than the rest of the page. */
  .nav[data-scrolled] { border-block-end-color: var(--c-border); }
  @supports not (backdrop-filter: blur(1px)) { .nav { background: var(--c-canvas); } }

  .nav__inner {
    width: 100%; max-width: var(--container);
    margin-inline: auto; padding-inline: var(--gutter);
    display: flex; align-items: center; gap: var(--space-4);
  }
  .nav__brand {
    display: inline-flex; align-items: center; gap: var(--space-2);
    min-height: 44px;              /* it is a link: a real target, not a caption */
    font-family: var(--font-display);
    font-weight: var(--fw-semibold);
    font-size: var(--fs-base);
    letter-spacing: var(--tracking-tight);
    color: var(--c-ink); text-decoration: none;
    margin-inline-end: auto;
  }
  .nav__brand svg, .nav__brand img { width: 22px; height: 22px; }

  .nav__links { display: none; }
  .nav__actions { display: flex; align-items: center; gap: var(--space-1); }
  /* The header CTA never wraps. At 375px the brand, the button and the menu
     share 341px: the theme toggle moves into the drawer below 480px, which is
     what lets "Create tax statement" fit beside the brand without wrapping. */
  .nav__actions > .btn { white-space: nowrap; }
  @media (max-width: 479px) {
    .nav__actions > .btn--primary { padding-inline: var(--space-3); font-size: var(--fs-sm); }
    .nav__actions .theme-toggle--header { display: none; }
  }
  /* Below 360px even that does not fit beside the brand and the burger, so
     the button drops "tax": the accessible name stays the full label. */
  .nav__cta-short { display: none; }
  @media (max-width: 359px) {
    .nav__cta-long { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
    .nav__cta-short { display: inline; }
    .nav__actions > .btn--primary { padding-inline: var(--space-2); }
  }
  @media (min-width: 480px) { .drawer .theme-toggle--drawer { display: none; } }
  /* The brand stays on one line from 360px up. Below that the four header
     items do not fit 320px on one line, and a wrapped brand beats a page
     that scrolls sideways (tests/a11y.spec.ts G8). */
  @media (min-width: 360px) { .nav__brand { white-space: nowrap; } }

  .nav__link {
    display: inline-flex; align-items: center;
    min-height: 44px; padding-inline: var(--space-3);
    border-radius: var(--r-sm);
    font-size: var(--fs-sm); font-weight: var(--fw-medium);
    color: var(--c-ink-2); text-decoration: none;
  }
  .nav__link:hover { background: var(--c-surface-sunken); color: var(--c-ink); }
  .nav__link[aria-current="page"] { color: var(--c-ink); font-weight: var(--fw-semibold); }

  .nav__burger { display: inline-flex; }

  @media (min-width: 960px) {
    :root { --nav-h: 64px; }
    .nav__links { display: flex; align-items: center; gap: var(--space-1); }
    .nav__burger { display: none; }
  }
  /* In app mode the six links live in the drawer at every width; the burger
     therefore stays. Only Contact (help) is in the bar, at >= 768px. */
  .nav--app .nav__burger { display: inline-flex; }

  /* Language switcher — native <details>, works with zero JS.
     Labels are full language names, never 2-letter codes only. */
  .lang { position: relative; }
  .lang > summary {
    display: inline-flex; align-items: center; gap: var(--space-2);
    min-height: 44px; padding-inline: var(--space-3);
    border-radius: var(--r-sm);
    font-size: var(--fs-sm); font-weight: var(--fw-medium);
    color: var(--c-ink-2); cursor: pointer; list-style: none;
  }
  .lang > summary::-webkit-details-marker { display: none; }
  .lang > summary:hover { background: var(--c-surface-sunken); color: var(--c-ink); }
  .lang[open] > summary { background: var(--c-surface-sunken); color: var(--c-ink); }
  .lang__menu {
    position: absolute; inset-inline-end: 0; top: calc(100% + var(--space-1));
    min-width: 12rem;
    padding: var(--space-1);
    background: var(--c-surface-raised);
    border: var(--border); border-radius: var(--r-md);
    box-shadow: var(--shadow-3);
    display: grid; gap: 2px;
  }
  .lang__menu a {
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
    min-height: 40px; padding: var(--space-2) var(--space-3);
    border-radius: var(--r-sm);
    font-size: var(--fs-sm); color: var(--c-ink-2); text-decoration: none;
  }
  .lang__menu a:hover { background: var(--c-surface-sunken); color: var(--c-ink); }
  .lang__menu a[aria-current="true"] { color: var(--c-ink); font-weight: var(--fw-semibold); }
  .lang__code { font-size: var(--fs-2xs); color: var(--c-ink-3); text-transform: uppercase; letter-spacing: var(--tracking-caps); }

  /* Theme toggle — 3-state (system / light / dark) is over-engineering for a
     once-a-year user. Two states; the server remembers via cookie. */
  .theme-toggle { min-width: 44px; min-height: 44px; display: inline-grid; place-items: center;
                  border-radius: var(--r-sm); color: var(--c-ink-2); cursor: pointer; }
  .theme-toggle:hover { background: var(--c-surface-sunken); color: var(--c-ink); }
  .theme-toggle .icon-sun  { display: none; }
  .theme-toggle .icon-moon { display: block; }
  :root[data-theme="dark"] .theme-toggle .icon-sun  { display: block; }
  :root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .theme-toggle .icon-sun  { display: block; }
    :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: none; }
  }

  /* Drawer below 960px — native <dialog>: focus trap, Esc, inert background
     and ::backdrop for free. Two lines of JS: showModal() / close(). */
  .drawer {
    width: min(92vw, 380px);
    max-width: none; height: 100dvh; max-height: 100dvh;
    margin: 0 0 0 auto; padding: 0;
    border: 0; border-inline-start: var(--border);
    background: var(--c-surface-raised); color: var(--c-ink-2);
    box-shadow: var(--shadow-3);
  }
  .drawer::backdrop { background: var(--c-overlay); }
  .drawer__inner { display: flex; flex-direction: column; height: 100%; padding: var(--space-4); gap: var(--space-2); }
  .drawer__head { display: flex; align-items: center; justify-content: space-between; min-height: var(--nav-h); }
  .drawer__links { display: grid; gap: 2px; margin-block: var(--space-4); }
  .drawer__links a {
    display: flex; align-items: center; min-height: 48px;
    padding-inline: var(--space-3); border-radius: var(--r-sm);
    font-size: var(--fs-lg); font-weight: var(--fw-medium);
    color: var(--c-ink); text-decoration: none;
  }
  .drawer__links a:hover { background: var(--c-surface-sunken); }
  .drawer__foot { margin-block-start: auto; padding-block-start: var(--space-4); border-block-start: var(--border); }
  /* NOT `@media (min-width:960px) { .drawer[open] { display:none } }`.
     display:none does not close a modal <dialog> or release the top layer: the
     dialog stays open and modal, the document stays inert, and the page is
     dead to both mouse and keyboard with nothing visible to explain it. A
     tablet rotated to landscape with the menu open is enough to trigger it.
     The drawer is closed in JS on the breakpoint change instead. */

  .drawer[open] { animation: drawer-in var(--dur-3) var(--ease-out); }
  .drawer[open]::backdrop { animation: fade-in var(--dur-3) var(--ease-out); }
  @keyframes drawer-in { from { transform: translateX(100%); } }
  @keyframes fade-in { from { opacity: 0; } }

  /* --- 4.4 Hero ----------------------------------------------------------
     Single column to 960. No stock illustration, no floating phone mockup.
     Every box that could load late has a reserved size → CLS 0. */
  .hero { padding-block: calc(var(--section-y-loose) * .8) var(--section-y); }
  .hero__brandline {
    display: inline-flex; align-items: center; gap: var(--space-2);
    font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
    letter-spacing: var(--tracking-caps); text-transform: uppercase;
    color: var(--c-ink-3);
    margin-block-end: var(--space-4);
  }
  .hero__brandline::before {
    content: ""; inline-size: 18px; block-size: 2px; border-radius: 2px;
    background: var(--c-brand);
  }
  .hero h1 {
    font-size: var(--fs-4xl);
    line-height: var(--lh-tight);
    letter-spacing: var(--tracking-display);
    font-weight: var(--fw-bold);
    max-width: 17ch;                 /* the promise, not a paragraph */
  }
  .hero__lede {
    margin-block-start: var(--space-5);
    font-size: var(--fs-lg);
    line-height: var(--lh-relaxed);
    color: var(--c-ink-3);
    max-width: var(--measure-narrow);
  }
  .hero__cta { margin-block-start: var(--space-8); --gap: var(--space-3); }
  .hero__meta {
    margin-block-start: var(--space-4);
    font-size: var(--fs-sm); color: var(--c-ink-3);
  }
  .hero__meta strong { color: var(--c-ink); }
  .hero__grid { display: grid; gap: var(--space-10); }
  @media (min-width: 960px) {
    /* Two columns only when there is something for the right one. A hero
       without a figure used to reserve the column anyway and left its text
       hugging the left edge of an empty half-page. */
    .hero__grid:has(.hero__figure) { grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
            gap: var(--space-16); align-items: center; }
    .hero__grid:has(.hero__figure) h1 { max-width: 15ch; }
    .hero__grid:not(:has(.hero__figure)) { text-align: center; }
    .hero__grid:not(:has(.hero__figure)) h1,
    .hero__grid:not(:has(.hero__figure)) .hero__lede,
    .hero__grid:not(:has(.hero__figure)) .hero__meta { margin-inline: auto; }
    .hero__grid:not(:has(.hero__figure)) .hero__cta { justify-content: center; }
    .hero__grid:not(:has(.hero__figure)) .hero__meta { max-width: var(--measure-narrow); }
  }
  .hero .launch-badge { margin-block-start: var(--space-4); }
  /* Slot for the product shot / live dropzone. Aspect ratio reserved up-front. */
  .hero__figure {
    aspect-ratio: 4 / 3;
    border: var(--border); border-radius: var(--r-xl);
    background: var(--c-surface);
    box-shadow: var(--shadow-2);
    overflow: hidden;
  }
  @media (max-width: 959px) { .hero__figure { margin-block-start: var(--space-10); aspect-ratio: 5/4; } }

  /* --- 4.5 Proof strip ---------------------------------------------------
     5 claims. On mobile it is a LIST, not a horizontal scroller: claims that
     have to be swiped for are claims nobody reads and Google can't weight. */
  .proof {
    border-block: var(--border);
    background: var(--c-surface);
    padding-block: var(--space-6);
  }
  /* The band names itself. Deliberately NOT a full .section-head: a display h2
     plus an eyebrow plus a lede directly under the hero competes with the H1
     and turns the strip into a section. One line at lede size is enough to
     stop five facts reading as orphan ticks, and it gives the list the
     accessible name it had no way to get before. */
  .proof__heading {
    font-size: var(--fs-lg);
    line-height: var(--lh-snug);
    letter-spacing: normal;
    font-weight: var(--fw-semibold);
    color: var(--c-ink);
    margin: 0 0 var(--space-5);
  }
  .proof__grid {
    display: grid; gap: var(--space-4);
    grid-template-columns: 1fr;
  }
  .proof__item {
    display: grid; grid-template-columns: 20px 1fr; gap: var(--space-3);
    align-items: start;
    font-size: var(--fs-sm); line-height: var(--lh-normal);
    color: var(--c-ink-2);
    min-height: 44px;                 /* reserves height before the icon font/SVG paints */
  }
  .proof__item:not(:has(> svg)) { display: block; }
  .proof__item svg { inline-size: 20px; block-size: 20px; color: var(--c-positive); margin-block-start: 1px; }
  .proof__item b { display: block; color: var(--c-ink); font-weight: var(--fw-semibold); }
  @media (min-width: 640px) { .proof__grid { grid-template-columns: repeat(2, minmax(0,1fr)); gap: var(--space-5) var(--space-8); } }
  @media (min-width: 1100px) {
    .proof__grid { grid-template-columns: repeat(5, minmax(0,1fr)); gap: var(--space-6); }
    .proof__item { border-inline-start: var(--border); padding-inline-start: var(--space-4);
                   grid-template-columns: 1fr; }
    .proof__item svg { margin-block-end: var(--space-2); }
  }

  /* --- 4.6 Stepper (3-step how it works) --------------------------------- */
  /* An ordered list is the correct semantics for steps, so the component has
     to survive being one: without the reset the browser markers render next to
     the numbered circles and every step reads "1. 1.". */
  .stepper {
    display: grid; gap: var(--space-8); counter-reset: step;
    list-style: none; padding: 0; margin: 0;
  }
  .step { position: relative; padding-inline-start: calc(var(--space-10) + var(--space-2)); counter-increment: step; }
  .step::before {
    content: counter(step);
    position: absolute; inset-inline-start: 0; inset-block-start: 0;
    inline-size: 40px; block-size: 40px;
    display: grid; place-items: center;
    border-radius: var(--r-pill);
    background: var(--c-surface); border: var(--border-strong);
    color: var(--c-ink); font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
  }
  /* Connector rail */
  .step::after {
    content: ""; position: absolute;
    inset-inline-start: 19.5px; inset-block: 48px -32px;
    inline-size: var(--bw); background: var(--c-border);
  }
  .step:last-child::after { display: none; }
  .step h3 { font-size: var(--fs-lg); margin-block-end: var(--space-2); }
  .step p { color: var(--c-ink-3); max-width: 44ch; }
  /* A page's own detail under a station's one-line job (explainer notes). */
  .step p + p { margin-block-start: var(--space-2); }
  @media (min-width: 860px) {
    .stepper { grid-template-columns: repeat(3, minmax(0,1fr)); gap: var(--space-8); }
    .step { padding-inline-start: 0; padding-block-start: calc(40px + var(--space-5)); }
    .step::after { inset: 20px auto auto 48px; inline-size: auto; block-size: var(--bw);
                   inset-inline: 48px calc(var(--space-8) * -1); }
  }

  /* --- 4.7 Upload dropzone ----------------------------------------------
     States driven by a single data-state attribute set by vanilla JS:
     idle | dragover | uploading | parsed | error
     Drag is an ENHANCEMENT only — the native input + visible "Choose file"
     button satisfy WCAG 2.2 SC 2.5.7 (Dragging Movements). */
  .dropzone {
    position: relative;
    display: grid; place-items: center; gap: var(--space-4);
    min-height: 280px;                /* fixed → no CLS when state changes */
    padding: var(--space-8) var(--space-5);
    text-align: center;
    background: var(--c-surface);
    border: var(--border-dashed);
    border-radius: var(--r-xl);
    transition: border-color var(--dur-2) var(--ease-out),
                background-color var(--dur-2) var(--ease-out),
                box-shadow var(--dur-2) var(--ease-out);
  }
  .dropzone__icon { inline-size: 40px; block-size: 40px; color: var(--c-ink-3); }

  /* The one line above a document list that says how many are needed. */
  .spec__lead { margin: 0 0 var(--space-3); font-size: var(--fs-sm); color: var(--c-ink-2); }
  .spec dt .badge { margin-inline-end: var(--space-1); vertical-align: middle; }

  /* One box per required file. The group of the checked broker tile shows;
     no script is needed to switch. Adding a live broker means adding its
     selector here — the upload page test checks that every live broker has one. */
  [data-upload] .slots { display: none; gap: var(--space-5); }
  [data-upload]:has(input[name="broker"][value="ibkr"]:checked) .slots[data-slots="ibkr"],
  [data-upload]:has(input[name="broker"][value="degiro"]:checked) .slots[data-slots="degiro"] { display: grid; }
  .slots__lead { margin: 0; font-size: var(--fs-sm); color: var(--c-ink-2); }
  .slot { display: grid; gap: var(--space-3); padding: var(--space-4); border: var(--border-strong); border-radius: var(--r-lg); background: var(--c-surface); }
  @media (min-width: 640px) { .slot { padding: var(--space-5); } }
  .slot__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); }
  .slot__name { font-weight: var(--fw-semibold); color: var(--c-ink); font-size: var(--fs-md); }
  .slot__spec { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); margin: 0; font-size: var(--fs-sm); }
  .slot__spec div { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-2); align-items: baseline; min-inline-size: 0; }
  .slot__spec dt { font-size: var(--fs-xs); font-weight: var(--fw-medium); letter-spacing: 0.04em; text-transform: uppercase; color: var(--c-ink-3); }
  .slot__spec dd { margin: 0; color: var(--c-ink); overflow-wrap: anywhere; }
  .slot__why { margin: 0; font-size: var(--fs-xs); color: var(--c-ink-3); }
  .dropzone--slot { min-height: 176px; padding: var(--space-5) var(--space-4); border-radius: var(--r-lg); background: var(--c-surface-sunken); }
  .dropzone--slot:hover { border-color: var(--c-brand); background: var(--c-brand-tint); }
  .dropzone--slot .dropzone__icon {
    inline-size: 44px; block-size: 44px; padding: 10px;
    border-radius: var(--r-pill); background: var(--c-surface); color: var(--c-brand);
    box-shadow: inset 0 0 0 1px var(--c-border);
  }
  .dropzone--slot .dropzone__state { gap: var(--space-3); }
  .dropzone--slot .dropzone__title { font-size: var(--fs-md); }
  .dropzone__title { font-size: var(--fs-lg); font-weight: var(--fw-semibold); color: var(--c-ink); }
  .dropzone__hint { font-size: var(--fs-sm); color: var(--c-ink-3); max-width: 38ch; }
  .dropzone__input { position: absolute; inline-size: 1px; block-size: 1px; opacity: 0; }
  /* The native <input type="file"> lives inside the zone and is visually
     hidden but focusable; its focus ring is projected onto the whole zone. */
  .dropzone:has(.dropzone__input:focus-visible) { outline: 2px solid var(--c-focus); outline-offset: 2px; }

  .dropzone[data-state="dragover"] {
    border-style: solid; border-color: var(--c-brand);
    background: var(--c-brand-tint);
    box-shadow: var(--shadow-2);
  }
  .dropzone[data-state="uploading"] { border-style: solid; border-color: var(--c-border-strong); }
  .dropzone[data-state="parsed"] {
    border-style: solid; border-color: var(--c-positive-border);
    background: var(--c-positive-tint);
  }
  .dropzone[data-state="error"] {
    border-style: solid; border-color: var(--c-danger-border);
    background: var(--c-danger-tint);
  }
  /* Only show the block matching the current state. */
  .dropzone__state { display: none; flex-direction: column; align-items: center; gap: var(--space-4); }
  .dropzone[data-state="idle"]      .dropzone__state[data-for="idle"],
  .dropzone[data-state="dragover"]  .dropzone__state[data-for="dragover"],
  .dropzone[data-state="uploading"] .dropzone__state[data-for="uploading"],
  .dropzone[data-state="parsed"]    .dropzone__state[data-for="parsed"],
  .dropzone[data-state="error"]     .dropzone__state[data-for="error"] { display: flex; }

  .progress {
    inline-size: min(320px, 100%); block-size: 6px;
    background: var(--c-surface-sunken);
    border-radius: var(--r-pill); overflow: hidden;
  }
  .progress__bar {
    block-size: 100%; inline-size: var(--value, 0%);
    background: var(--c-brand); border-radius: inherit;
    transition: inline-size var(--dur-3) var(--ease-out);
  }
  /* Indeterminate parse step */
  .progress--indeterminate .progress__bar {
    inline-size: 35%;
    animation: indeterminate 1.1s var(--ease-in-out) infinite;
  }
  @keyframes indeterminate { 0% { transform: translateX(-100%);} 100% { transform: translateX(320%);} }

  .filechip {
    display: inline-flex; align-items: center; gap: var(--space-2);
    max-width: 100%;
    padding: var(--space-2) var(--space-3);
    background: var(--c-surface-sunken); border: var(--border);
    border-radius: var(--r-pill);
    font-size: var(--fs-sm);
  }
  .filechip svg { inline-size: 16px; block-size: 16px; flex: none; color: var(--c-positive); }
  .filechip__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-inline-size: 22ch; }
  .filechip__size { color: var(--c-ink-3); font-variant-numeric: tabular-nums; flex: none; }

  /* Errors: icon + text + colour. Never colour alone (WCAG 1.4.1). */
  .field-error, .form-error {
    display: flex; align-items: start; gap: var(--space-2);
    font-size: var(--fs-sm); color: var(--c-danger-ink); text-align: start;
  }
  .form-error {
    padding: var(--space-4);
    background: var(--c-danger-tint);
    border: var(--bw) solid var(--c-danger-border);
    border-radius: var(--r-md);
  }
  .form-error h2 { font-size: var(--fs-base); color: var(--c-danger-ink); }
  .form-error a { color: var(--c-danger-ink); text-decoration: underline; }
  [aria-invalid="true"] { border-color: var(--c-danger) !important; }

  /* --- 4.8 Result / preview card ----------------------------------------- */
  .result {
    background: var(--c-surface);
    border: var(--border); border-radius: var(--r-xl);
    box-shadow: var(--shadow-2);
    overflow: hidden;
  }
  .result__head {
    padding: var(--space-6) var(--space-5) var(--space-5);
    background: var(--c-positive-tint);
    border-block-end: var(--bw) solid var(--c-positive-border);
    text-align: center;
  }
  /* The zero case. Green is reserved for recovered money and a CHF 0.00
     reclaim is not that, so the head drops to neutral rather than claiming a
     win the user did not get. The figure stays large: when the answer is
     nothing, a big honest zero says it better than a paragraph. */
  .result__head--muted {
    background: var(--c-surface-sunken);
    border-block-end-color: var(--c-border);
  }
  .result__head--muted .result__label,
  .result__head--muted .result__sub { color: var(--c-ink-3); }
  .result__head--muted .figure-chf { color: var(--c-ink-2); }

  .result__label {
    font-size: var(--fs-sm); font-weight: var(--fw-medium);
    color: var(--c-positive-ink);
  }
  .figure-chf {
    display: flex; align-items: baseline; justify-content: center; gap: .18em;
    margin-block-start: var(--space-2);
    font-family: var(--font-display);
    font-size: var(--fs-figure);
    font-weight: var(--fw-bold);
    line-height: var(--lh-flat);
    letter-spacing: -0.03em;
    color: var(--c-positive-ink);
  }
  .figure-chf .cur { font-size: .42em; font-weight: var(--fw-semibold); letter-spacing: 0; opacity: .85; }
  .figure-chf .dec { font-size: .48em; font-weight: var(--fw-semibold); }
  .result__sub { margin-block-start: var(--space-3); font-size: var(--fs-sm); color: var(--c-positive-ink); }
  /* The figure next to the price. A rule above it, because it is a different
     claim from the explanation it follows: not what the credit is, but what
     it is worth paying for. Never on the muted head — see above. */
  .result__gain {
    margin-block-start: var(--space-4);
    padding-block-start: var(--space-4);
    border-block-start: var(--bw) solid var(--c-positive-border);
    font-size: var(--fs-base);
    color: var(--c-positive-ink);
  }

  .result__rows { padding: var(--space-2) var(--space-5); }
  .result__row {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4);
    padding-block: var(--space-3);
    border-block-end: var(--bw) solid var(--c-border);
    font-size: var(--fs-sm);
  }
  .result__row:last-child { border-block-end: 0; }
  .result__row dt { color: var(--c-ink-2); }
  /* Per-row explanation, closed by default. The summary is a 1.1em "i" disc. */
  .hint { display: inline; }
  .hint > summary {
    display: inline-flex; align-items: center; justify-content: center;
    inline-size: 1.1em; block-size: 1.1em; margin-inline-start: var(--space-2);
    border: 1px solid var(--c-ink-3); border-radius: 50%;
    font-size: .7em; font-weight: var(--fw-semibold); font-style: italic; font-family: Georgia, serif;
    color: var(--c-ink-3); cursor: pointer; list-style: none; vertical-align: 1px;
  }
  .hint > summary::-webkit-details-marker { display: none; }
  .hint > summary:hover, .hint[open] > summary { color: var(--c-brand); border-color: var(--c-brand); }
  .hint > summary:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 2px; }
  .hint > p { margin: var(--space-1) 0 0; font-size: var(--fs-xs); line-height: 1.45; color: var(--c-ink-3); max-inline-size: 34rem; }
  .result__row dd { color: var(--c-ink); font-weight: var(--fw-medium);
                    font-variant-numeric: tabular-nums lining-nums; text-align: end; }
  .result__foot { padding: var(--space-5); border-block-start: var(--border); background: var(--c-surface-sunken); }
  /* The answer's own call to action. A link at the end of a sentence is read
     as prose; this is the next step and looks like one. */
  .result__foot--cta { display: grid; gap: var(--space-3); justify-items: start; }
  .result__foot--cta p { margin: 0; }

  /* Downloads — the delivery list. One link per file and the whole row is the
     target; the file the canton actually reads is the only row in brand
     colour. File type, purpose and size sit in the row, so nothing trails
     after the link and wraps on its own at 375px. */
  /* The DA-1 sheet on screen: a picture of the blank form with the figures
     laid over it as text at the form's own field positions (fractions of
     the sheet, from data/forms/da-1-2025-de.boxes.json). Sized by container
     query units so the type scales with the sheet; the image is grayscale
     and stays as it is in dark mode — a form is white paper. */
  .form-preview { margin: 0; max-width: 36rem; }
  .form-preview__sheet { position: relative; container-type: inline-size; overflow: hidden; border: var(--bw) solid var(--c-line); border-radius: var(--radius-sm); background: #fff; box-shadow: var(--shadow-sm); }
  .form-preview__form { display: block; width: 100%; height: auto; }
  .form-preview__field { position: absolute; display: flex; align-items: center; box-sizing: border-box; padding-inline: 0.4cqw; font-size: 1.3cqw; line-height: 1; color: #1b3a8a; white-space: nowrap; overflow: hidden; }
  .form-preview__field[style*="text-align:end"] { justify-content: flex-end; }
  .form-preview__field[style*="text-align:center"] { justify-content: center; }
  .form-preview__stamp { position: absolute; inset: 0; display: grid; place-items: center; font-size: 12cqw; font-weight: 700; letter-spacing: 0.12em; color: var(--c-warning); opacity: 0.22; transform: rotate(-28deg); pointer-events: none; }
  .form-preview__caption { margin-block-start: var(--space-2); font-size: var(--fs-sm); color: var(--c-ink-3); }
  @supports not (font-size: 1cqw) {
    .form-preview__field { font-size: 0.72rem; }
    .form-preview__stamp { font-size: 4rem; }
  }

  .downloads { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-3); }
  .download {
    display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--space-4);
    min-height: 64px; padding: var(--space-4) var(--space-5);
    border: var(--border); border-radius: var(--r-lg); background: var(--c-surface);
    color: var(--c-ink); text-decoration: none;
    transition: border-color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out);
  }
  .download:hover { border-color: var(--c-brand); background: var(--c-brand-tint); color: var(--c-ink); }
  .download--lead { border-color: var(--c-brand); box-shadow: inset 0 0 0 var(--bw) var(--c-brand); }
  .download__kind {
    min-inline-size: 3.25rem; padding: var(--space-1) var(--space-2); text-align: center;
    border-radius: var(--r-sm); background: var(--c-surface-sunken);
    font-size: var(--fs-2xs); font-weight: var(--fw-semibold); letter-spacing: var(--tracking-caps);
    text-transform: uppercase; color: var(--c-ink-2);
  }
  .download--lead .download__kind { background: var(--c-brand); color: var(--c-brand-ink); }
  .download__body { display: grid; gap: 2px; min-inline-size: 0; }
  .download__label { font-weight: var(--fw-semibold); line-height: var(--lh-snug); }
  .download__meta { font-size: var(--fs-xs); color: var(--c-ink-3); line-height: var(--lh-normal); }
  .download__arrow { inline-size: 20px; block-size: 20px; color: var(--c-brand); }
  @media (max-width: 479px) {
    .download { grid-template-columns: auto minmax(0, 1fr); }
    .download__arrow { display: none; }
  }

  /* Key-value metadata (document reference, generated, standard). A bare <dl>
     renders as an undifferentiated stack, which reads as broken markup. */
  .meta-dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--space-2) var(--space-6); margin: 0; font-size: var(--fs-sm); }
  .meta-dl dt { color: var(--c-ink-3); }
  .meta-dl dd { margin: 0; color: var(--c-ink); overflow-wrap: anywhere; font-variant-numeric: tabular-nums lining-nums; }
  @media (max-width: 479px) {
    .meta-dl { grid-template-columns: 1fr; gap: var(--space-1); }
    .meta-dl dd { margin-block-end: var(--space-2); }
  }

  /* "I'm stuck" follows the column it sits under. On a prose page that column
     is the measure, not the narrow container, and a wider panel under a
     narrower column reads as misaligned rather than as a design. The class
     sits on the panel's CONTAINER so it gets the same padding, centring and
     font size as the prose container above it — 68ch is only the same width
     when it is measured in the same font. */
  body:has(.container--narrow.prose) .stuck { max-inline-size: var(--measure); font-size: var(--fs-md); }

  /* Locked preview: show enough to trust, blur the rest. Never blur the money. */
  .locked { position: relative; }
  .locked__veil {
    position: absolute; inset: auto 0 0; block-size: 62%;
    background: linear-gradient(to bottom, transparent, var(--c-surface) 72%);
    display: grid; align-items: end; justify-items: center;
    padding-block-end: var(--space-5);
  }
  .locked__blur { filter: blur(4px); user-select: none; pointer-events: none; }

  /* --- 4.9 Pricing ------------------------------------------------------- */
  .pricing { display: grid; gap: var(--space-5); }
  @media (min-width: 860px) {
    .pricing { grid-template-columns: repeat(2, minmax(0, 420px)); justify-content: center; gap: var(--space-6); }
  }
  /* Three tiers in two columns left the third alone on its own row. */
  @media (min-width: 1100px) {
    .pricing { grid-template-columns: repeat(3, minmax(0, 360px)); }
  }
  .price-card {
    display: flex; flex-direction: column;
    padding: var(--space-6);
    background: var(--c-surface);
    border: var(--border); border-radius: var(--r-xl);
  }
  .price-card--featured {
    border-color: var(--c-brand-border);
    box-shadow: var(--shadow-2), inset 0 0 0 1px var(--c-brand-border);
  }
  .price-card__name { font-size: var(--fs-lg); font-weight: var(--fw-semibold); color: var(--c-ink); }
  .price-card__for  { margin-block-start: var(--space-1); font-size: var(--fs-sm); color: var(--c-ink-3); }
  .price-card__amount {
    display: flex; align-items: baseline; gap: var(--space-2);
    margin-block: var(--space-5);
    font-family: var(--font-display);
    font-size: clamp(2.25rem, 1.6rem + 2.4vw, 2.75rem);
    font-weight: var(--fw-bold); line-height: var(--lh-flat);
    letter-spacing: -0.03em; color: var(--c-ink);
    font-variant-numeric: tabular-nums lining-nums;
  }
  .price-card__amount .cur { font-size: .5em; font-weight: var(--fw-semibold); color: var(--c-ink-3); }
  .price-card__once { font-size: var(--fs-sm); font-weight: var(--fw-regular); color: var(--c-ink-3); letter-spacing: 0; }
  .price-card ul { list-style: none; padding: 0; margin-block: var(--space-2) var(--space-6); display: grid; gap: var(--space-3); }
  .price-card li { display: grid; grid-template-columns: 18px 1fr; gap: var(--space-3); font-size: var(--fs-sm); }
  /* See the note on .note: an icon-slot grid with no icon renders its text one
     character wide. Same guard, same reason. */
  .price-card li:not(:has(> svg)) { display: block; }
  .price-card li svg { inline-size: 18px; block-size: 18px; color: var(--c-positive); margin-block-start: 2px; }
  .price-card li[data-excluded] { color: var(--c-ink-3); }
  .price-card li[data-excluded] svg { color: var(--c-ink-4); }
  /* Last element in the card, so every tier's button sits on one line however
     long the feature list and the fine print above it are. */
  .price-card .btn { margin-block-start: auto; }
  .price-note { margin-block-start: var(--space-4); font-size: var(--fs-xs); color: var(--c-ink-3); }

  /* --- 4.10 Data table ---------------------------------------------------
     The fee table and the canton matrix are the two most linkable assets in
     the business. Rules, not zebra. Tabular figures. Real <table> at every
     width — never flattened to cards, because the comparison IS the value and
     a screenshot of stacked cards is worthless.

     Sticky-header caveat, decided deliberately:
     `overflow-x:auto` makes the wrapper a scroll container, which breaks a
     thead that is sticky to the viewport. So the wrapper only scrolls below
     1100px; above it the table fits and thead sticks under the nav. Below it,
     the sticky FIRST COLUMN carries row identity, which matters more on a
     phone than a sticky header row. */
  .table-wrap {
    overflow-x: auto;
    overflow-y: hidden;              /* the x scrollbar's own height otherwise triggers a y scrollbar */
    overscroll-behavior-x: contain;
    border: var(--border); border-radius: var(--r-lg);
    background:
      linear-gradient(to right, var(--c-surface) 30%, rgba(255,255,255,0)) left center / 44px 100% no-repeat local,
      linear-gradient(to left,  var(--c-surface) 30%, rgba(255,255,255,0)) right center / 44px 100% no-repeat local,
      radial-gradient(farthest-side at 0% 50%, var(--shadow-scroll), rgba(255,255,255,0)) left center / 16px 100% no-repeat scroll,
      radial-gradient(farthest-side at 100% 50%, var(--shadow-scroll), rgba(255,255,255,0)) right center / 16px 100% no-repeat scroll,
      var(--c-surface);
  }
  /* Keyboard users must be able to scroll it: give the wrapper
     role="region" aria-label="…" tabindex="0" in the HTML. */
  .table-wrap:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 2px; }
  /* A second horizontal scrollbar that stays at the bottom of the viewport
     while a tall table scrolls through it, so nobody has to reach the
     table's end to move sideways. The script adds it after a .table-wrap and
     shows it only while the table both overflows and is taller than most of
     the viewport; it mirrors the wrap's scroll position both ways. */
  .table-scroller { position: sticky; bottom: 0; z-index: 4; overflow-x: auto; overflow-y: hidden; block-size: 18px; margin-block-start: calc(-1 * var(--space-2)); background: var(--c-surface); border-block-start: var(--border); }
  .table-scroller > div { block-size: 1px; }

  .table {
    inline-size: 100%;
    min-inline-size: 42rem;          /* below this the columns crush */
    border-collapse: collapse;
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums lining-nums slashed-zero;
  }
  @media (min-width: 768px) { .table { font-size: var(--fs-sm); } }

  .table caption {
    caption-side: top;
    padding: var(--space-4) var(--space-4) var(--space-3);
    text-align: start;
    font-size: var(--fs-sm); color: var(--c-ink-3);
    border-block-end: var(--bw) solid var(--c-border);
  }
  .table caption b { display: block; margin-block-end: var(--space-1);
                     font-size: var(--fs-lg); color: var(--c-ink); font-weight: var(--fw-semibold); }
  .table caption .meta { font-size: var(--fs-xs); }

  .table thead th {
    position: sticky; top: 0; z-index: 3;
    padding: var(--space-3) var(--space-4);
    background: var(--c-surface-sunken);
    border-block-end: var(--bw-strong) solid var(--c-border-strong);
    font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
    letter-spacing: var(--tracking-caps); text-transform: uppercase;
    color: var(--c-ink-3);
    text-align: start;
    white-space: nowrap;
  }
  /* Once upon a time this switched .table-wrap to overflow: visible above
     1100px so the header could stick to the viewport. That assumes every
     table fits its column at 1100px, and a five-column securities table does
     not: it spilled past the card edge on the preview, invisible to axe and
     to every text assertion. The wrap now clips at every width. The header
     still sticks within the wrap, which is the behaviour a scrolling table
     actually needs; tests/layout.spec.ts checks the geometry at 1100, 1280
     and 1600. */
  @media (min-width: 1100px) {
    .table thead th { top: 0; }
  }

  /* Body AND foot. Scoping this to tbody left every tfoot cell on the UA's
     defaults — `padding: 1px` and, on a th, centred text. Nobody noticed while
     the figures were small: a six-figure total in the larger type of the total
     row filled its cell to within 2px of the next one, so "CHF 1'234'567.89"
     and the DA-1 credit beside it read as a single number. The foot carries
     the figures a reader copies onto a form, so it is the last place that can
     afford to be ambiguous. */
  .table tbody td, .table tbody th,
  .table tfoot td, .table tfoot th {
    padding: var(--space-3) var(--space-4);
    vertical-align: baseline;
    text-align: start;
  }
  .table tbody td, .table tbody th { border-block-end: var(--bw) solid var(--c-border); }
  .table tbody tr:last-child td, .table tbody tr:last-child th { border-block-end: 0; }
  /* The closing line of a security's movements: a rule above, the rest as a row. */
  .table tbody tr.table__subtotal td, .table tbody tr.table__subtotal th { border-block-start: var(--bw-strong) solid var(--c-border-strong); background: var(--c-surface-sunken); }
  /* The total of a table, set apart: the figure a reader carries to the form.
     Set apart by tint, rule and weight — deliberately NOT by size. It used to
     be two type steps above its own column (19px over a 13px body), which cost
     the one thing tabular figures exist to provide: a total whose digits sit
     directly under the digits it totals. It also sized itself out of a column
     whose width was computed from the smaller body text, so a six-figure
     total ran into the total beside it. Emphasis that changes the type size
     inside a column is emphasis the column cannot pay for. */
  .table tfoot tr.table__total th, .table tfoot tr.table__total td {
    background: var(--c-brand-tint);
    border-block-start: var(--bw-strong) solid var(--c-brand-border);
    font-weight: var(--fw-semibold);
  }
  .table tfoot tr.table__total strong { font-weight: var(--fw-bold); }
  /* A section of the statement page: a disclosure whose summary is the heading. */
  .disclosure--section > summary { grid-template-columns: minmax(0, 1fr) 12px; grid-template-areas: "title chev"; }
  .disclosure--section .disclosure__title { font-size: var(--fs-xl); }
  .prose .disclosure { margin-block: var(--space-6); }
  /* `.prose > * + *` is a direct-child rule, so prose inside a disclosure body
     got no rhythm at all and paragraphs ran together. The body is a prose
     container in its own right; give it the same rule. */
  .prose .disclosure__body > * + * { margin-block-start: var(--space-5); }
  .prose .disclosure__body > :first-child, .prose .disclosure .table-wrap { margin-block-start: 0; }
  .prose .disclosure .table-wrap { margin-block: 0; }
  /* A payment under its security: indented, lighter, so the security rows read as headings. */
  .table tbody th.table__sub { padding-inline-start: calc(var(--space-4) + 1.5rem + var(--space-2)); font-weight: var(--fw-regular); color: var(--c-ink-2); }
  .table tbody tr:hover td, .table tbody tr:hover th { background: var(--c-surface-sunken); }

  /* Row header = the institution name. Sticky on phones — and it WRAPS there.
     nowrap plus the permalink used to hand "PostFinance E-Trading #" three
     quarters of a 375px viewport and push the Cost column off screen. */
  .table tbody th[scope="row"] {
    position: sticky; inset-inline-start: 0; z-index: 2;
    background: var(--c-surface);
    font-weight: var(--fw-semibold); color: var(--c-ink);
    border-inline-end: var(--bw) solid var(--c-border);
    white-space: normal;
    min-inline-size: 7rem;
    max-inline-size: 38vw;
  }
  @media (min-width: 768px) {
    .table tbody th[scope="row"] { white-space: nowrap; max-inline-size: none; }
    /* Payment descriptions ("VT(US9220427424) CASH DIVIDEND USD 0.72 PER
       SHARE") are long; letting them wrap is what lets five columns fit
       the app width without a sideways scroll. */
    .disclosure .table tbody th[scope="row"] { white-space: normal; max-inline-size: 26rem; }
  }
  @media (min-width: 1100px) {
    .table tbody th[scope="row"] { position: static; border-inline-end: 0; }
  }
  /* The primary value column sits right after the row header, so it is on
     screen beside the sticky name at 375px. Tables with two or three short
     columns need no minimum width at all. */
  .table--narrow { min-inline-size: 0; }
  .table--narrow tbody th[scope="row"] { max-inline-size: 44vw; }
  /* A note that belongs to a row: a real second <tr> with one cell spanning
     the table, so the note never becomes a fourth column that wraps into a
     300px-tall row. The inner block is sticky to the left edge so the note
     stays in the viewport while the table scrolls sideways on a phone. */
  .table tbody tr.table__note-row td {
    padding-block: 0 var(--space-3);
    border-block-end: var(--bw) solid var(--c-border);
    font-size: var(--fs-xs); color: var(--c-ink-3);
    vertical-align: top;
  }
  .table tbody tr:has(+ .table__note-row) td,
  .table tbody tr:has(+ .table__note-row) th { border-block-end: 0; padding-block-end: var(--space-1); }
  .table__note {
    display: block; position: sticky; inset-inline-start: 0;
    max-inline-size: min(100%, calc(100vw - 2 * var(--gutter) - 2 * var(--space-4)));
    line-height: var(--lh-normal);
  }
  .table__note a { display: inline; min-block-size: 0; }
  .table tbody tr.table__note-row:hover td { background: transparent; }

  /* Numbers right-aligned, decimal-consistent, unit in the header not the cell. */
  /* Links inside a table row are targets, not prose: SC 2.5.8's inline-in-a-
     sentence exemption does not cover them, and at the default 13px body size
     they render ~17px tall. The row padding already supplies the width. */
  .table a {
    display: inline-flex; align-items: center;
    min-block-size: 24px;
  }

  .table .num { text-align: end; font-variant-numeric: tabular-nums lining-nums; white-space: nowrap; }
  .table th.num { text-align: end; }
  .table .num--zero { color: var(--c-positive); font-weight: var(--fw-semibold); }
  .table .num--high { color: var(--c-danger); }
  .table .cell-note { display: block; font-size: var(--fs-3xs); color: var(--c-ink-3); font-variant-numeric: normal; }
  /* The running number of a position, repeated on each of its payments in
     the "one by one" list so the two tables can be matched by eye. */
  /* The "N payments" toggle in a security row: a button that reads as the
     cell's text, with a chevron that turns when the payments are shown. */
  .fold { display: inline-flex; align-items: center; gap: var(--space-2); min-block-size: 24px; padding: 0 var(--space-1); margin-inline-start: calc(-1 * var(--space-1)); border: 0; border-radius: var(--r-sm); background: none; font: inherit; color: var(--c-brand); cursor: pointer; white-space: nowrap; }
  .fold:hover { text-decoration: underline; }
  .fold:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 2px; }
  .fold__chev { inline-size: 0.5em; block-size: 0.5em; border-inline-end: 2px solid currentColor; border-block-end: 2px solid currentColor; transform: rotate(-45deg) translateY(-1px); transition: transform var(--dur-2) var(--ease-out); }
  .fold[aria-expanded="true"] .fold__chev { transform: rotate(45deg) translateY(-2px); }
  /* --- The late DA-1 claim ------------------------------------------------
     A second door into the same engine. It borrows every component the
     statement flow uses; what it adds is the deadline line, the three
     confirmations and the offer of another year. */
  .claim-banner { margin-block-start: var(--space-3); padding: var(--space-2) var(--space-3); border-radius: var(--r-md); background: var(--c-brand-tint); font-size: var(--fs-sm); color: var(--c-ink-2); max-width: var(--measure-narrow); }
  .claim-banner a { margin-inline-start: var(--space-2); }
  .claim-checks { display: grid; gap: var(--space-3); margin: 0 0 var(--space-4); padding: 0; list-style: none; counter-reset: claim; }
  .claim-check { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: var(--space-2); }
  .claim-check .check { margin: 0; }
  /* The DA-1 waiting list: one checkbox per tax year still open. The legend
     reads as a field label, not a heading. */
  .year-picks { border: 0; padding: 0; margin: 0; display: grid; gap: var(--space-2); min-inline-size: 0; }
  .year-picks > legend { padding: 0; margin-block-end: var(--space-2); font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--c-ink); }
  .year-picks .check { min-block-size: 44px; align-items: center; }
  .waitlist-da1 { max-width: var(--measure-narrow); }
  /* The same list on the past-year page, which sits in a plain container
     rather than prose, so it brings its own spacing. */
  .late-wait { display: grid; gap: var(--space-3); max-width: var(--measure); margin-block-start: var(--space-10); padding-block-start: var(--space-8); border-block-start: var(--border); }
  .late-wait > * { margin: 0; }
  .waitlist-da1 .year-picks { margin-block-start: var(--space-2); }
  /* "I have a code" on a claim: a real disclosure, not the round "i" that
     .hint renders, because its summary is a sentence the reader must read. */
  .code-disclosure { margin-block-start: var(--space-3); border-block-start: var(--border); padding-block-start: var(--space-3); }
  .code-disclosure > summary { cursor: pointer; font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--c-ink-2); }
  .code-disclosure > summary:hover { color: var(--c-brand); }
  .code-disclosure > summary:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 2px; border-radius: var(--r-sm); }
  .code-disclosure > .stack { margin-block-start: var(--space-3); }
  .past-years { margin-block-start: var(--space-2); }
  .past-years a { white-space: nowrap; }
  /* A pay button that cannot yet do anything says so, rather than failing on click. */
  [data-da1-gate] button[disabled] { opacity: 0.55; cursor: not-allowed; }

  .rownum { display: inline-block; min-inline-size: 1.5rem; margin-inline-end: var(--space-2); padding: 0 var(--space-1); border: var(--border); border-radius: var(--r-sm); background: var(--c-surface-sunken); font-size: var(--fs-3xs); font-weight: var(--fw-semibold); line-height: 1.5rem; color: var(--c-ink-3); text-align: center; vertical-align: middle; font-variant-numeric: tabular-nums; }
  .table .row-notes { margin: var(--space-1) 0 0; padding-inline-start: 1em; font-size: var(--fs-3xs); font-weight: var(--fw-regular); color: var(--c-warning-ink, var(--c-ink-2)); font-variant-numeric: normal; }
  .table .row-notes li + li { margin-block-start: 2px; }
  .table .row-notes .hint > p { font-size: var(--fs-3xs); max-inline-size: 42rem; color: var(--c-ink-2); }

  /* Per-row permalink. Always in the DOM and focusable; revealed on hover/focus. */
  /* Faint rather than hidden. opacity:0-until-hover means the permalink does
     not exist on touch, which is ~half the traffic, on the two pages whose
     whole job is being linked to and quoted. */
  .row-link {
    display: inline-block; margin-inline-start: var(--space-2);
    min-inline-size: 24px; min-block-size: 24px;     /* SC 2.5.8 */
    color: var(--c-ink-3); text-decoration: none;
    opacity: .55; transition: opacity var(--dur-1) var(--ease-out);
  }
  /* On a phone the permalink drops to its own line under the name instead of
     widening the sticky column. */
  @media (max-width: 767px) { .row-link { display: block; margin-inline-start: 0; } }
  tr:hover .row-link, .row-link:focus-visible { opacity: 1; color: var(--c-brand); }
  @media (hover: none) { .row-link { opacity: 1; } }
  .table tbody tr:target th[scope="row"] { box-shadow: inset 3px 0 0 0 var(--c-brand); }
  .table tbody tr:target td, .table tbody tr:target th { background: var(--c-brand-tint); }

  /* `.table--stack` was here: a max-width:559px opt-in that set thead
     visually-hidden, tr to display:block and td to display:flex with
     ::before labels. Deleted rather than documented, because that pattern
     strips table semantics from the accessibility tree and no comment
     saying "only for 2-3 column tables" survives contact with a deadline.
     A narrow table fits at 320px; lower its min-inline-size instead. */
  /* Provenance line under every table — the thing that gets quoted with you. */
  .table-source {
    margin-block-start: var(--space-3);
    font-size: var(--fs-xs); color: var(--c-ink-3);
    display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  }

  /* Canton acceptance matrix: a compact variant with symbol cells. */
  .table--matrix td { text-align: center; }
  /* Colour reinforces, never carries. Under protanopia the positive green and
     the danger red separate by ~1.14:1, so every status glyph in these tables
     is paired with text the markup supplies (a .visually-hidden word at
     minimum). Do not reduce these to a bare coloured tick. */
  .table--matrix .yes { color: var(--c-positive); font-weight: var(--fw-semibold); }
  .table--matrix .no  { color: var(--c-ink-3); }
  .table .num--zero { font-weight: var(--fw-semibold); }   /* the cell already reads 0.00 */

  /* --- 4.11 FAQ accordion — native <details>, zero JS -------------------- */
  .faq { border-block-start: var(--border); }
  .faq__item { border-block-end: var(--border); }
  .faq__item > summary {
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
    min-height: 60px; padding-block: var(--space-4);
    font-size: var(--fs-md); font-weight: var(--fw-medium); color: var(--c-ink);
    cursor: pointer; list-style: none;
  }
  .faq__item > summary::-webkit-details-marker { display: none; }
  .faq__item > summary::after {
    content: ""; flex: none;
    inline-size: 11px; block-size: 11px;
    border-inline-end: 2px solid var(--c-ink-3);
    border-block-end: 2px solid var(--c-ink-3);
    transform: rotate(45deg) translateY(-2px);
    transition: transform var(--dur-2) var(--ease-out);
  }
  .faq__item[open] > summary::after { transform: rotate(225deg) translateY(-2px); }
  .faq__item > summary:hover { color: var(--c-brand); }
  .faq__body { padding-block-end: var(--space-5); color: var(--c-ink-2); max-width: var(--measure); }
  .faq__body > * + * { margin-block-start: var(--space-3); }

  /* Smooth open where supported; instant elsewhere. No JS, no height hacks. */
  @supports (interpolate-size: allow-keywords) {
    :root { interpolate-size: allow-keywords; }
    .faq__item::details-content {
      block-size: 0; overflow: clip; opacity: 0;
      transition: block-size var(--dur-3) var(--ease-out),
                  content-visibility var(--dur-3) allow-discrete,
                  opacity var(--dur-2) var(--ease-out);
    }
    .faq__item[open]::details-content { block-size: auto; opacity: 1; }
  }

  /* --- 4.11b Form controls -----------------------------------------------
     These did not exist. The token layer always anticipated them
     (--c-surface-sunken is commented "inputs, code, zebra") but no component
     was ever written, so every <input> and <select> in the generator rendered
     as raw browser furniture next to an otherwise finished design system.

     Sizing: 44px min block-size on every control, which is the 2.5.8 target
     and also what stops a CHF 19 form feeling like a 2009 web form. */
  .field { display: grid; gap: var(--space-2); }
  .field > label,
  .form-label {
    font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--c-ink-2);
  }
  .field__hint { font-size: var(--fs-sm); color: var(--c-ink-3); margin: 0; }

  .input, .select, textarea.input {
    inline-size: 100%;
    min-block-size: 44px;
    padding: var(--space-3) var(--space-4);
    background: var(--c-surface-sunken);
    color: var(--c-ink);
    border: var(--bw) solid var(--c-border-strong);
    border-radius: var(--r-md);
    transition: border-color var(--dur-2) var(--ease-out),
                background-color var(--dur-2) var(--ease-out);
  }
  .input::placeholder { color: var(--c-ink-3); }
  .input:hover, .select:hover { border-color: var(--c-ink-3); }
  .input:focus-visible, .select:focus-visible {
    outline: 2px solid var(--c-focus); outline-offset: 2px;
    background: var(--c-surface);
  }
  .input[aria-invalid="true"] { border-color: var(--c-danger-border); }

  /* The native arrow is inconsistent across platforms and cannot be themed,
     so it is replaced with one drawn in the ink token — which means it is
     legible in dark mode, unlike the OS default. */
  .select {
    appearance: none;
    padding-inline-end: var(--space-8);
    background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                      linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position: calc(100% - 20px) calc(50% + 2px), calc(100% - 14px) calc(50% + 2px);
    background-size: 6px 6px, 6px 6px;
    background-repeat: no-repeat;
  }
  /* The gradient arrow would otherwise sit on the sunken fill and lose it. */
  .select { background-color: var(--c-surface-sunken); }

  /* A control that should not stretch to the measure — a year picker has a
     natural width and looking like a text field is misleading. */
  .field--compact .select, .select--compact { inline-size: auto; min-inline-size: 12ch; }

  /* --- 4.11c Missing screenshot ------------------------------------------
     An honest gap rather than a stock image. The guided export needs shots of
     someone else's UI taken from a real account; inventing them would be both
     useless to the reader and a trademark problem. The caption says exactly
     what each one has to show, so whoever takes them knows the brief. */
  .figure-placeholder { margin-block: var(--space-5); }
  .figure-placeholder__box {
    display: grid; place-items: center;
    min-block-size: 120px;
    padding: var(--space-5);
    border: var(--border-dashed); border-radius: var(--r-lg);
    background: var(--c-surface-sunken);
    color: var(--c-ink-3);
    font-size: var(--fs-sm); font-weight: var(--fw-medium);
  }
  .figure-placeholder figcaption {
    margin-block-start: var(--space-2);
    font-size: var(--fs-sm); color: var(--c-ink-3);
  }

  /* --- 4.12 Callouts / notes ---------------------------------------------
     Two columns: a 20px icon, then the content.

     THE ICON COLUMN IS NOT OPTIONAL unless you say so. A `.note` with no
     <svg> used to drop its text into the 20px track and render one character
     per line — a whole paragraph as a vertical ribbon. It shipped in the
     footer on every page. The :has() guard below makes the no-icon case
     degrade to a plain block instead, so the same mistake is now merely
     plain rather than catastrophic. tests/a11y.spec.ts pins it. */
  .note {
    display: grid; grid-template-columns: 20px 1fr; gap: var(--space-3);
    padding: var(--space-4);
    border: var(--border); border-radius: var(--r-md);
    background: var(--c-surface);
    font-size: var(--fs-sm);
  }
  .note:not(:has(> svg)) { display: block; }
  .note svg { inline-size: 20px; block-size: 20px; color: var(--c-ink-3); margin-block-start: 1px; }
  /* A note is a small-text component. Headings inside one step down to match
     it rather than importing the prose scale, which otherwise produces a
     24px heading sitting on 14px body inside a 10px-radius box. */
  .note :is(h2, h3, h4) {
    font-size: var(--fs-base); line-height: var(--lh-snug);
    font-weight: var(--fw-semibold); color: var(--c-ink);
    margin: 0 0 var(--space-2);
  }
  .note > div > :last-child { margin-block-end: 0; }
  .note--info     { background: var(--c-brand-tint);    border-color: var(--c-brand-border); }
  .note--positive { background: var(--c-positive-tint); border-color: var(--c-positive-border); }
  .note--warning  { background: var(--c-warning-tint);  border-color: var(--c-warning-border); }
  .note > div > * + * { margin-block-start: var(--space-2); }
  .note ul { padding-inline-start: 1.2em; }
  .note li + li { margin-block-start: var(--space-1); }
  /* A fold inside a note: the headline and one line stay visible, the reasoning
     and the steps open on demand. Native <details>, the FAQ chevron, no JS. */
  .note__more > summary {
    display: inline-flex; align-items: center; gap: var(--space-2);
    font-weight: var(--fw-medium); color: var(--c-brand);
    cursor: pointer; list-style: none;
  }
  .note__more > summary::-webkit-details-marker { display: none; }
  .note__more > summary::after {
    content: ""; flex: none;
    inline-size: 8px; block-size: 8px;
    border-inline-end: 2px solid currentColor; border-block-end: 2px solid currentColor;
    transform: rotate(45deg) translateY(-2px);
    transition: transform var(--dur-2) var(--ease-out);
  }
  .note__more[open] > summary::after { transform: rotate(225deg) translateY(-2px); }
  .note__more > summary:hover { text-decoration: underline; }
  .note__more > summary:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 2px; border-radius: 2px; }
  .note__more > * + * { margin-block-start: var(--space-2); }

  /* A confirmation dialog, built by the script from the same words the
     no-script confirmation page carries. Native <dialog>: focus trap, Esc
     and an inert background come for free. */
  .confirm {
    max-inline-size: min(34rem, calc(100vw - 2rem));
    border: var(--border); border-radius: var(--r-md);
    background: var(--c-surface); color: var(--c-ink-2);
    padding: var(--space-5);
  }
  .confirm::backdrop { background: rgb(0 0 0 / 0.4); }
  .confirm h2 { font-size: var(--fs-lg); color: var(--c-ink); margin: 0 0 var(--space-3); }
  .confirm p { margin: 0 0 var(--space-4); }
  .confirm__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }

  /* --- Add an account ------------------------------------------------------
     An OFFER, not a section. It sits among bordered white disclosures, so it
     is tinted and carries a real button: as a plain card it read as a sixth
     review block and the eye skipped it. Icon, text, action — and the price
     under the button, where proximity puts it in front of the decision. */
  .addon {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-3) var(--space-4);
    padding: var(--space-4) var(--space-5);
    border: var(--bw) solid var(--c-brand-border);
    border-radius: var(--r-md);
    background: var(--c-brand-tint);
  }
  /* Beside the heading, not floating against the middle of three lines. */
  .addon__icon { inline-size: 28px; block-size: 28px; color: var(--c-brand); align-self: start; margin-block-start: 1px; }
  .addon h2 { font-size: var(--fs-md); line-height: var(--lh-snug); margin: 0; color: var(--c-ink); }
  .addon p { margin: 0; font-size: var(--fs-sm); color: var(--c-ink-2); }
  .addon__text > * + * { margin-block-start: var(--space-1); }
  .addon__fine { font-size: var(--fs-xs); color: var(--c-ink-3); }
  .addon__text > .addon__fine { margin-block-start: var(--space-2); }
  .addon__act { display: grid; justify-items: center; gap: var(--space-1); }
  .addon__price { font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--c-brand); text-align: center; }
  /* Narrow: the action drops to its own full-width row under the text, which
     keeps the button at a comfortable target size instead of squeezing it. */
  @media (max-width: 44rem) {
    .addon { grid-template-columns: auto minmax(0, 1fr); }
    .addon__act { grid-column: 1 / -1; justify-items: stretch; }
    .addon__act .btn { inline-size: 100%; }
  }

  /* --- Admin panel (ACCOUNTS-AND-ADMIN.md) ---------------------------------
     Operator-only screens on the same tokens. One bar, one row of tabs, and
     three data shapes: stat cards, horizontal bars, a sparkline. */
  /* Sits under the site header, which is itself sticky at the top. */
  .admin-bar { position: sticky; top: var(--nav-h); z-index: 40; background: var(--c-surface); border-block-end: var(--border); }
  .admin-bar__top { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); min-height: 40px; padding-block-start: var(--space-1); }
  .admin-bar__brand { font-family: var(--font-display); font-weight: var(--fw-semibold); color: var(--c-ink); text-decoration: none; white-space: nowrap; }
  .admin-bar__brand span { display: inline-block; padding: 2px var(--space-2); border-radius: var(--r-sm); background: var(--c-brand-tint); color: var(--c-brand); font-size: var(--fs-2xs); letter-spacing: var(--tracking-caps); text-transform: uppercase; }
  .admin-bar__user { display: flex; align-items: center; gap: var(--space-2); font-size: var(--fs-sm); color: var(--c-ink-3); min-inline-size: 0; }
  .admin-bar__user span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* The tabs own a full row and scroll sideways on a narrow screen. */
  .tabs { display: flex; gap: var(--space-1); overflow-x: auto; scrollbar-width: thin; padding-block-end: var(--space-1); }
  .tab {
    display: inline-flex; align-items: center; min-height: 44px; padding-inline: var(--space-3);
    border-radius: var(--r-sm); color: var(--c-ink-2); text-decoration: none; white-space: nowrap; font-size: var(--fs-sm);
  }
  .tab:hover { background: var(--c-surface-sunken); color: var(--c-ink); }
  .tab[aria-current="page"] { background: var(--c-brand-tint); color: var(--c-brand); font-weight: var(--fw-medium); }
  .admin-title { font-size: var(--fs-2xl); margin: 0 0 var(--space-4); }
  .admin-h2 { font-size: var(--fs-md); margin: var(--space-8) 0 var(--space-3); }
  .admin-days a { text-decoration: underline; }
  .admin-inline { margin-block-end: var(--space-3); }
  .admin-login { max-inline-size: 26rem; margin-inline: auto; }
  .admin-pre { padding: var(--space-4); border: var(--border); border-radius: var(--r-md); background: var(--c-surface-sunken);
               font-size: var(--fs-xs); line-height: var(--lh-normal); overflow-x: auto; white-space: pre-wrap; overflow-wrap: anywhere; }
  .stats { display: grid; gap: var(--space-3); grid-template-columns: repeat(2, minmax(0, 1fr)); margin-block-end: var(--space-6); }
  @media (min-width: 700px) { .stats { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
  /* Admin tables hold ids, codes and enums: never hyphenate them. */
  .admin .table td, .admin .table th { hyphens: none; }
  .stat { padding: var(--space-4); border: var(--border); border-radius: var(--r-md); background: var(--c-surface); }
  .stat__label { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: var(--tracking-caps); color: var(--c-ink-3); }
  .stat__value { margin-block-start: var(--space-1); font-size: var(--fs-xl); font-weight: var(--fw-semibold); color: var(--c-ink);
                 font-variant-numeric: tabular-nums lining-nums; }
  .stat__sub { font-size: var(--fs-xs); color: var(--c-ink-3); }
  /* The kinds an access code can have, each with the "i" disc that explains
     it. A definition list in all but name: the word, its disc, then one line
     of summary; the full paragraph is inside the disclosure. */
  .kind-help { list-style: none; margin: var(--space-3) 0 0; padding: 0; display: grid; gap: var(--space-2); }
  .kind-help > li { font-size: var(--fs-sm); }
  .kind-help > li > strong { font-family: var(--font-mono); color: var(--c-ink); }
  /* Inline, so the disc ends the line and its paragraph opens beneath the
     whole line rather than between the word and its summary. */
  .kind-help .meta { font-size: var(--fs-xs); color: var(--c-ink-3); }
  .kind-help .hint > p { max-inline-size: 42rem; }

  .admin-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); align-items: start; margin-block: var(--space-4) var(--space-6); }
  .panel { padding: var(--space-5); border: var(--border); border-radius: var(--r-lg); background: var(--c-surface); min-inline-size: 0; }
  .panel--wide { grid-column: 1 / -1; }
  .panel h2 { margin: 0 0 var(--space-3); font-size: var(--fs-md); }
  .bars { display: grid; gap: var(--space-2); }
  .bar { display: grid; grid-template-columns: minmax(5rem, 12rem) 1fr 3.5rem; align-items: center; gap: var(--space-3); font-size: var(--fs-sm); }
  .bar__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--c-ink-2); }
  .bar__track { display: block; block-size: 10px; background: var(--c-surface-sunken); border-radius: var(--r-pill); overflow: hidden; }
  .bar__track--inline { inline-size: 100%; min-inline-size: 6rem; }
  .bar__fill { display: block; block-size: 100%; inline-size: var(--w, 0%); background: var(--c-brand); border-radius: inherit; }
  .bar__value { text-align: end; font-variant-numeric: tabular-nums; color: var(--c-ink); }
  .tabular { font-variant-numeric: tabular-nums; }
  .spark { display: flex; align-items: flex-end; gap: 2px; block-size: 72px; padding-block-end: 2px; border-block-end: var(--border); }
  .spark__bar { flex: 1 1 0; min-inline-size: 3px; block-size: var(--h, 2%); background: var(--c-brand); border-radius: 2px 2px 0 0; }
  .health { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-2); font-size: var(--fs-sm); }
  .health li { display: flex; justify-content: space-between; align-items: center; gap: var(--space-3); }
  .admin-todo { margin: 0; padding-inline-start: 1.1em; font-size: var(--fs-sm); }
  .admin-todo li + li { margin-block-start: var(--space-2); }
  .admin-todo code { font-size: var(--fs-xs); white-space: nowrap; }
  .admin-inline { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: end; margin-block-end: var(--space-4); }
  .admin-inline .input { max-inline-size: 16rem; }
  .filters { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: end; margin-block-end: var(--space-4); }
  .filters .field { min-inline-size: 9rem; }
  .code-entry { margin-block-start: var(--space-4); border-block-start: var(--border); padding-block-start: var(--space-3); }
  .code-entry > summary { cursor: pointer; font-size: var(--fs-sm); color: var(--c-brand); min-height: 44px; display: flex; align-items: center; gap: var(--space-2); list-style: none; }
  .code-entry > summary::-webkit-details-marker { display: none; }
  .code-entry > summary::before {
    content: ""; flex: none; inline-size: 8px; block-size: 8px;
    border-inline-end: 2px solid currentColor; border-block-end: 2px solid currentColor;
    transform: rotate(-45deg); transition: transform var(--dur-2) var(--ease-out);
  }
  .code-entry[open] > summary::before { transform: rotate(45deg); }
  .code-entry > summary:hover { text-decoration: underline; }
  .code-entry[open] > summary { margin-block-end: var(--space-3); }
  .code-entry--top { margin-block: 0 var(--space-6); border-block-start: 0; padding-block-start: 0; }
  .check { display: flex; gap: var(--space-2); align-items: flex-start; font-size: var(--fs-sm); color: var(--c-ink-2); text-align: start; }
  .check input { flex: none; inline-size: 18px; block-size: 18px; margin-block-start: 2px; accent-color: var(--c-brand); }
  .purchase-code code { display: inline-block; font-size: var(--fs-lg); letter-spacing: .06em; padding: var(--space-2) var(--space-3);
                        background: var(--c-surface-sunken); border: var(--border); border-radius: var(--r-sm); user-select: all; }
  .purchases { list-style: none; padding: 0; display: grid; gap: var(--space-4); }
  .purchase { padding: var(--space-4); border: var(--border); border-radius: var(--r-md); background: var(--c-surface); }
  .purchase h2 { margin: 0 0 var(--space-2); font-size: var(--fs-md); }
  .purchase code { white-space: nowrap; }
  .admin-refund { display: flex; gap: var(--space-2); align-items: center; }
  .admin-refund .input { min-height: 36px; padding-block: var(--space-1); inline-size: auto; }
  .admin-refund .input[name="amount"] { inline-size: 6rem; }
  .admin-refund .input[name="reason"] { inline-size: 11rem; }

  /* --- Review item (disclosure) ------------------------------------------
     A collapsed section whose summary row is the whole signal: icon, title,
     a count badge coloured by severity, a one-line gist, and the chevron. */
  .disclosure { border: var(--border); border-radius: var(--r-lg); background: var(--c-surface); }
  .disclosure--warning { border-color: var(--c-warning-border); }
  .disclosure > summary {
    display: grid; grid-template-columns: 20px minmax(0, 1fr) auto 12px;
    grid-template-areas: "icon title badge chev" "icon hint hint chev";
    align-items: center; column-gap: var(--space-3); row-gap: 2px;
    min-height: 60px; padding: var(--space-4) var(--space-5);
    cursor: pointer; list-style: none;
  }
  .disclosure > summary::-webkit-details-marker { display: none; }
  .disclosure > summary > svg { grid-area: icon; inline-size: 20px; block-size: 20px; color: var(--c-ink-3); }
  .disclosure--warning > summary > svg { color: var(--c-warning); }
  .disclosure__title { grid-area: title; margin: 0; font-size: var(--fs-md); font-weight: var(--fw-semibold); line-height: var(--lh-snug); color: var(--c-ink); }
  .disclosure > summary > .badge { grid-area: badge; }
  .disclosure__hint { grid-area: hint; font-size: var(--fs-sm); color: var(--c-ink-3); }
  .disclosure > summary::after {
    content: ""; grid-area: chev; justify-self: end;
    inline-size: 10px; block-size: 10px;
    border-inline-end: 2px solid var(--c-ink-3); border-block-end: 2px solid var(--c-ink-3);
    transform: rotate(45deg) translateY(-2px); transition: transform var(--dur-2) var(--ease-out);
  }
  .disclosure[open] > summary::after { transform: rotate(225deg) translateY(-2px); }
  .disclosure > summary:hover .disclosure__title { color: var(--c-brand); }
  .disclosure__body { padding: var(--space-4) var(--space-5) var(--space-5); border-block-start: var(--border); }
  .disclosure__body > :first-child { margin-block-start: 0; }
  .disclosure__list { margin: 0; padding-inline-start: var(--space-5); display: grid; gap: var(--space-2); font-size: var(--fs-sm); color: var(--c-ink-2); }
  .disclosure .table-wrap { border-radius: var(--r-md); }
  @media (max-width: 479px) {
    .disclosure > summary { grid-template-columns: 20px minmax(0, 1fr) 12px; grid-template-areas: "icon title chev" "icon badge chev" "icon hint chev"; }
    .disclosure > summary > .badge { justify-self: start; }
  }

  /* --- Guided export ------------------------------------------------------
     A long how-to read mid-task in someone else's UI: one column, numbered
     rows with a connector, compact screenshot briefs, spec lists that fit
     the column, and a path chooser at the top. */
  .pathcards { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); margin-block: var(--space-6); }
  .pathcard {
    display: grid; gap: var(--space-1); align-content: start;
    padding: var(--space-4) var(--space-5);
    border: var(--border); border-radius: var(--r-lg); background: var(--c-surface);
    color: var(--c-ink); text-decoration: none;
  }
  .pathcard:hover { border-color: var(--c-brand); }
  .pathcard--lead { border-color: var(--c-brand); box-shadow: inset 0 0 0 var(--bw) var(--c-brand); }
  .pathcard .badge { justify-self: start; margin-block-end: var(--space-1); }
  .pathcard strong { font-size: var(--fs-md); }
  .pathcard span:not(.badge) { font-size: var(--fs-sm); color: var(--c-ink-3); }

  .guide-steps { list-style: none; padding: 0; margin: var(--space-6) 0; display: grid; gap: 0; counter-reset: guide; }
  .guide-step { position: relative; display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: var(--space-4); padding-block-end: var(--space-6); counter-increment: guide; }
  .guide-step__num {
    display: grid; place-items: center; inline-size: 40px; block-size: 40px;
    border-radius: var(--r-pill); background: var(--c-surface); border: var(--border-strong);
    color: var(--c-ink); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums;
  }
  .guide-step__num::before { content: counter(guide); }
  /* The connector between numbers; the last step has none. */
  .guide-step:not(:last-child)::before {
    content: ""; position: absolute; inset-inline-start: 19px; inset-block: 44px 0;
    inline-size: var(--bw); background: var(--c-border);
  }
  .guide-step__body { min-inline-size: 0; padding-block-start: var(--space-2); }
  .guide-step__body > h3 { margin: 0 0 var(--space-2); font-size: var(--fs-lg); line-height: var(--lh-snug); }
  .guide-step__body > p { margin: 0 0 var(--space-3); }
  .guide-step__body .note { margin-block: var(--space-3); }
  .figure-placeholder--inline { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-3); align-items: center; margin-block: var(--space-3) 0; }
  .figure-placeholder--inline .figure-placeholder__box { min-block-size: 0; padding: var(--space-1) var(--space-3); font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: var(--tracking-caps); border-radius: var(--r-sm); white-space: nowrap; }
  .figure-placeholder--inline figcaption { margin: 0; font-size: var(--fs-xs); }
  @media (max-width: 599px) { .figure-placeholder--inline { grid-template-columns: 1fr; gap: var(--space-1); } }

  .spec { margin: var(--space-4) 0 var(--space-6); border: var(--border); border-radius: var(--r-md); background: var(--c-surface); overflow: hidden; }
  .spec > div { display: grid; grid-template-columns: minmax(8rem, 13rem) minmax(0, 1fr); gap: var(--space-4); padding: var(--space-3) var(--space-4); border-block-start: var(--border); }
  .spec > div:first-child { border-block-start: 0; }
  .spec dt { font-weight: var(--fw-semibold); font-size: var(--fs-sm); color: var(--c-ink); }
  .spec dd { margin: 0; font-size: var(--fs-sm); color: var(--c-ink-2); }
  .spec__note { display: block; margin-block-start: 2px; font-size: var(--fs-xs); color: var(--c-ink-3); font-weight: var(--fw-regular); }
  @media (max-width: 599px) { .spec > div { grid-template-columns: 1fr; gap: var(--space-1); } }

  .chips { list-style: none; padding: 0; margin: var(--space-3) 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
  .chips li { padding: var(--space-1) var(--space-3); border: var(--border); border-radius: var(--r-pill); background: var(--c-surface); font-size: var(--fs-sm); color: var(--c-ink-2); }
  .prose .guide-steps, .prose .chips { padding-inline-start: 0; }
  .prose .guide-steps { gap: 0; }
  .guide > h2 { margin-block-start: var(--space-12); }
  .guide > h3 { margin-block-start: var(--space-8); }

  /* Footer legal lines. Small, muted, full width — the register of a
     disclaimer, not of a callout. See the comment in nav-html.ts for why
     this class exists rather than reusing `.note`. */
  .footer__legal {
    font-size: var(--fs-xs); line-height: var(--lh-normal);
    color: var(--c-ink-3); max-inline-size: var(--measure);
  }

  /* Consent-free analytics note. There is no cookie banner on this site, so
     this is the disclosure — inline in the footer, never a floating overlay
     (an overlay that appears after paint is pure CLS and pure distrust). */
  .privacy-note {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3);
    padding: var(--space-3) var(--space-4);
    border: var(--border); border-radius: var(--r-pill);
    background: var(--c-surface);
    font-size: var(--fs-xs); color: var(--c-ink-3);
  }
  .privacy-note::before {
    content: ""; inline-size: 8px; block-size: 8px; border-radius: 50%;
    background: var(--c-positive); flex: none;
  }
  .privacy-note a { color: var(--c-ink-2); }

  /* --- 4.13 Footer -------------------------------------------------------- */
  .footer {
    margin-block-start: var(--section-y);
    padding-block: var(--space-12) var(--space-8);
    background: var(--c-surface);
    border-block-start: var(--border);
    font-size: var(--fs-sm);
  }
  /* A band directly above the footer used to leave a strip of bare canvas
     between the two — the footer's own top margin. When the last section is
     a band, the band's padding is the rhythm and the margin is dropped. */
  main:has(> .section--sunken:last-child) + .footer,
  main:has(> .section--surface:last-child) + .footer,
  main:has(> .cta-band:last-child) + .footer { margin-block-start: 0; }
  .footer__grid { display: grid; gap: var(--space-8); }
  @media (min-width: 640px) { .footer__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  /* Column count follows the content, not a fixed 4: the Research column is
     dropped whole while fees, cantons, compare and blog are all hidden by
     VISIBILITY (src/urls.ts), and a fixed repeat(4) left a dead track on
     the right. grid-auto-flow handles 3 and 4 without a modifier class. */
  @media (min-width: 960px) { .footer__grid { grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: var(--space-10); } }
  .footer__operator { font-style: normal; line-height: var(--lh-relaxed); color: var(--c-ink-2); }
  .footer__operator strong { color: var(--c-ink); }
  .footer__operator a { color: var(--c-ink-2); display: inline-block; min-block-size: 24px; line-height: 24px; }
  .footer__grid .footer__legal { margin-block-start: var(--space-4); }
  .footer h2 {
    font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
    letter-spacing: var(--tracking-caps); text-transform: uppercase;
    color: var(--c-ink-3); margin-block-end: var(--space-4);
  }
  .footer ul { list-style: none; padding: 0; display: grid; gap: var(--space-1); }
  .footer li a {
    display: inline-flex; align-items: center; min-height: 32px;
    color: var(--c-ink-2); text-decoration: none;
  }
  .footer li a:hover { color: var(--c-ink); text-decoration: underline; }
  .footer__bottom {
    margin-block-start: var(--space-10); padding-block-start: var(--space-6);
    border-block-start: var(--border);
    display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: center; justify-content: space-between;
    font-size: var(--fs-xs); color: var(--c-ink-3);
  }
}

/* ==========================================================================
   5. CONTENT PAGES (SEO surface)
   ========================================================================== */
@layer content {
  .prose { max-width: var(--measure); font-size: var(--fs-md); line-height: var(--lh-relaxed); }
  .prose > * + * { margin-block-start: var(--space-5); }
  .prose h2 { margin-block-start: var(--space-12); scroll-margin-top: calc(var(--nav-h) + var(--space-6)); }
  /* A heading inside a note is the note's title, not a section break. Without
     this the prose h2 margin above wins over the component rule (content layer
     beats components) and the title lands a rhythm unit below the icon. */
  .prose .note :is(h2, h3, h4) { margin-block-start: 0; font-size: var(--fs-base); line-height: var(--lh-snug); }
  .prose .note p { margin: 0 0 var(--space-2); font-size: var(--fs-sm); }
  .prose .note > div > :last-child { margin-block-end: 0; }
  /* The delivery list and metadata list keep their own geometry inside prose. */
  .prose .downloads { padding: 0; gap: var(--space-3); }
  .prose .downloads li { margin: 0; padding: 0; }
  .prose .meta-dl { font-size: var(--fs-sm); }
  .prose h3 { margin-block-start: var(--space-8); scroll-margin-top: calc(var(--nav-h) + var(--space-6)); }
  .prose ul, .prose ol { padding-inline-start: var(--space-6); display: grid; gap: var(--space-2); }
  .prose img { border: var(--border); border-radius: var(--r-md); }
  .prose figure figcaption { margin-block-start: var(--space-2); font-size: var(--fs-sm); color: var(--c-ink-3); }
  /* Tables inside prose break the measure and go full container width. */
  .prose .table-wrap { max-inline-size: none; inline-size: min(100%, var(--container)); margin-block: var(--space-8); }
  /* `.prose ul { display: grid }` above beats the component's flex (content
     layer wins over components), which rendered six short chips as six
     full-width bars on the IBKR guide. The chips keep their own layout. */
  .prose .chips { display: flex; flex-wrap: wrap; gap: var(--space-2); padding-inline-start: 0; }
  /* An app screen whose container IS the prose (the failure pages, the
     purchases page, the generated statement) keeps the app width and caps
     each block to the measure, so the text shares the page head's left edge
     instead of floating centred in the middle of the column. Lives in this
     layer because `.prose { max-width }` above would otherwise win. */
  .page--app .container.prose { max-width: var(--container-app); }
  .page--app .container.prose > :not(.table-wrap, .downloads, .keyfacts, .purchases, .disclosure, .stack:has(> .disclosure)) { max-width: var(--measure); }
  /* A review disclosure holds a table, so it takes the app width like a
     table; the prose inside it keeps the measure. Before this the stack of
     disclosures was clamped to 68ch and a five-column table scrolled sideways
     on a 1280px screen. */
  .page--app .disclosure__body > p, .page--app .disclosure__body > dl:not(.spec), .page--app .disclosure__body > ul, .page--app .disclosure__list { max-width: var(--measure); }

  /* Answer-first block: 40–70 words directly under the H2 that restates the
     question. This is the single highest-leverage structure for snippets and
     for being quoted by an assistant. One per H2, maximum. */
  .answer {
    padding: var(--space-5);
    background: var(--c-surface);
    border: var(--border);
    border-inline-start: 3px solid var(--c-brand);
    border-radius: var(--r-md);
    font-size: var(--fs-md); line-height: var(--lh-relaxed); color: var(--c-ink);
  }
  .answer > p:first-child { font-weight: var(--fw-medium); }
  .answer .meta { margin-block-start: var(--space-3); font-size: var(--fs-xs); color: var(--c-ink-3); }

  /* Definition block — term + one-sentence definition, quotable standalone. */
  .deffn { padding: var(--space-5); background: var(--c-surface-sunken); border-radius: var(--r-md); }
  .deffn dt { font-weight: var(--fw-semibold); color: var(--c-ink); }
  .deffn dd { margin-block-start: var(--space-2); margin-inline-start: 0; }

  /* Reasons legend under the excluded-income table: one term per distinct
     reason, so a long explanation is printed once instead of in every row. */
  /* Each reason is a heading line with its explanation under it, both across
     the full width of the table above. A two-column grid with the reason on
     the left squeezed a long explanation into whatever the longest reason
     left over, which read as a caption gone wrong. */
  .reasons { display: grid; gap: var(--space-1); margin-block-start: var(--space-4); font-size: var(--fs-sm); }
  .reasons dt { font-weight: var(--fw-semibold); color: var(--c-ink); }
  .reasons dt:not(:first-child) { margin-block-start: var(--space-3); }
  .reasons dd { margin: 0; color: var(--c-ink-2); max-inline-size: none; }
  @media (max-width: 40rem) {
    .reasons { grid-template-columns: 1fr; gap: var(--space-1); }
    .reasons dd { margin-block-end: var(--space-3); }
  }

  /* Key-facts strip: each item self-contained (label, value, unit, year). */
  /* Two per row at phone width, never 2 + 1 + 1: the minimum is what two
     tiles need side by side at 375px, and the value drops a size there. */
  .keyfacts { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fit, minmax(min(100%, 7.5rem), 1fr)); }
  @media (min-width: 640px) { .keyfacts { gap: var(--space-4); } }
  .keyfact { padding: var(--space-4); border: var(--border); border-radius: var(--r-md); background: var(--c-surface); min-inline-size: 0; }
  .keyfact dt { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: var(--tracking-caps); color: var(--c-ink-3); }
  .keyfact dd { margin: var(--space-2) 0 0; font-size: var(--fs-base); font-weight: var(--fw-semibold);
                color: var(--c-ink); font-variant-numeric: tabular-nums lining-nums; overflow-wrap: anywhere; }
  @media (min-width: 480px) { .keyfact dd { font-size: var(--fs-lg); } }
  .keyfact__sub { display: block; margin-block-start: var(--space-1); font-size: var(--fs-xs); font-weight: var(--fw-regular); color: var(--c-ink-3); line-height: var(--lh-normal); }
  .keyfact__note { display: block; margin-block-start: var(--space-1); font-size: var(--fs-xs); font-weight: var(--fw-regular); color: var(--c-ink-3); }
  .keyfact--warning { background: var(--c-warning-tint); border-color: var(--c-warning-border); }
  .keyfact--warning dd { color: var(--c-warning); }
  .keyfact details { margin-block-start: var(--space-2); }
  .keyfact details > summary { list-style: none; cursor: pointer; font-size: var(--fs-xs); font-weight: var(--fw-medium); color: var(--c-brand); min-block-size: 24px; line-height: 24px; }
  .keyfact details > summary::-webkit-details-marker { display: none; }
  .keyfact details > summary:hover { text-decoration: underline; }
  .keyfact details form { margin-block-start: var(--space-2); display: grid; gap: var(--space-2); }
  /* A way back, inside the fact it corrects. Block-level so the target keeps its 24px. */
  .keyfact__action { display: block; margin-block-start: var(--space-1); min-block-size: 24px; line-height: 24px;
                     font-size: var(--fs-xs); font-weight: var(--fw-medium); color: var(--c-brand); }
  .section-head .preview-back { font-size: var(--fs-sm); color: var(--c-ink-3); margin-block-start: var(--space-3); }
  .preview-back a { display: inline-block; min-block-size: 24px; line-height: 24px; }

  .updated { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--fs-xs); color: var(--c-ink-3); }

  /* Table of contents for long guides */
  .toc { position: sticky; top: calc(var(--nav-h) + var(--space-4)); align-self: start; font-size: var(--fs-sm); }
  .toc ul { list-style: none; padding: 0; display: grid; gap: var(--space-1); border-inline-start: var(--border); }
  .toc a { display: block; padding: var(--space-2) var(--space-3); margin-inline-start: -1px;
           color: var(--c-ink-3); text-decoration: none; border-inline-start: 2px solid transparent; }
  .toc a:hover { color: var(--c-ink); }
  .toc a[aria-current="true"] { color: var(--c-ink); border-inline-start-color: var(--c-brand); font-weight: var(--fw-medium); }
  @media (max-width: 1023px) { .toc { display: none; } }

  .content-layout { display: grid; gap: var(--space-10); }
  @media (min-width: 1024px) {
    .content-layout { grid-template-columns: minmax(0,1fr) 15rem; gap: var(--space-16); align-items: start; }
  }

  .breadcrumb { font-size: var(--fs-xs); color: var(--c-ink-3); margin-block-end: var(--space-5); }
  .breadcrumb ol { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
  .breadcrumb li + li::before { content: "/"; margin-inline-end: var(--space-2); color: var(--c-ink-4); }
}

/* ==========================================================================
   5b. COMPOSITION SYSTEM (page head, flow, unlock, delivery, home)
   Added with the 2026-09 UX plan. Same tokens, same layers; new component
   types are documented in DESIGN.md before they are used.
   ========================================================================== */
@layer components {

  /* --- Page head: one of three variants on every page ------------------- */
  .page-head { padding-block: var(--section-y-tight) 0; }
  .page-head__inner { max-width: var(--measure); }
  .page-head h1 { margin-block-start: var(--space-2); }
  .page-head .eyebrow { margin-block-end: var(--space-2); }
  .page-head__lede { margin-block-start: var(--space-4); font-size: var(--fs-lg); line-height: var(--lh-relaxed); color: var(--c-ink-3); max-width: var(--measure-narrow); }
  .page-head .answer { margin-block-start: var(--space-5); }
  /* The one-line clarifier under a lede ("Not a tax return…"): quieter, and spaced as its own line. */
  .page-head .meta { margin-block-start: var(--space-3); max-width: var(--measure-narrow); }
  .page-head .updated { margin-block-start: var(--space-4); }
  .page-head + .section { padding-block-start: var(--section-y-tight); }
  .page-head .flow { margin-block: var(--space-2) var(--space-6); }
  /* Breadcrumb links are targets: 24px minimum. */
  .breadcrumb a { display: inline-block; min-block-size: 24px; line-height: 24px; color: var(--c-ink-3); text-decoration: none; }
  .breadcrumb a:hover { color: var(--c-ink); text-decoration: underline; }
  .breadcrumb a[aria-current="page"] { color: var(--c-ink-2); }
  /* Body text on an article page is left-aligned in the measure, same edge as the head. */
  .section > .container > .prose, .content-layout > .prose { margin-inline: 0; }
  /* An app screen whose container IS the prose (the failure pages, the
     purchases page, the generated statement) keeps the app width and caps
     each block to the measure, so the text shares the page head's left edge
     instead of floating centred in the middle of the column. */

  /* --- The five-station flow indicator ---------------------------------- */
  /* Never a scroll container (axe: a scrollable region needs keyboard
     access): the five items wrap, and on phones only the current station
     carries its word so they fit on one line anyway. */
  .flow {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) 0;
    list-style: none; padding: 0; margin: 0;
    font-size: var(--fs-xs); font-weight: var(--fw-medium); color: var(--c-ink-3);
  }
  .flow__item { display: flex; align-items: center; white-space: nowrap; }
  .flow__item > a, .flow__item > span { display: inline-flex; align-items: center; gap: var(--space-2); min-block-size: 32px; padding-inline: var(--space-2); border-radius: var(--r-sm); color: inherit; text-decoration: none; }
  .flow__item > a:hover { background: var(--c-surface-sunken); color: var(--c-ink); }
  .flow__item + .flow__item::before { content: ""; inline-size: 14px; block-size: var(--bw); background: var(--c-border-strong); flex: none; }
  .flow__num {
    display: inline-grid; place-items: center; inline-size: 22px; block-size: 22px;
    border-radius: var(--r-pill); border: var(--bw-strong) solid var(--c-border-strong);
    font-size: var(--fs-3xs); font-weight: var(--fw-semibold); color: var(--c-ink-3);
    font-variant-numeric: tabular-nums;
  }
  .flow__item--done .flow__num { background: var(--c-positive); border-color: var(--c-positive); color: var(--c-ink-inverse); }
  .flow__item--done { color: var(--c-ink-2); }
  .flow__item--current { color: var(--c-ink); font-weight: var(--fw-semibold); }
  .flow__item--current .flow__num { background: var(--c-brand); border-color: var(--c-brand); color: var(--c-brand-ink); }
  .flow__item--todo .flow__label { color: var(--c-ink-3); }
  /* In the app header: centred between the brand and the actions at >= 768px,
     as a strip under the bar below it. */
  .nav--app .nav__flow { display: none; flex: 1 1 auto; justify-content: center; min-inline-size: 0; }
  .nav--app .nav__flow-strip { display: block; border-block-start: var(--border); background: var(--c-surface); }
  .nav--app .nav__flow-strip .flow { padding: var(--space-1) var(--gutter); max-width: var(--container); margin-inline: auto; justify-content: space-between; }
  /* On a phone only the current station carries its word; the others are
     their numbers, so all five fit without scrolling. */
  @media (max-width: 479px) {
    .nav__flow-strip .flow__item:not(.flow__item--current) .flow__label { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
    .nav__flow-strip .flow__item + .flow__item::before { inline-size: 8px; }
  }
  @media (min-width: 768px) {
    .nav--app .nav__flow { display: flex; }
    .nav--app .nav__flow-strip { display: none; }
    .nav--app .nav__links--app { display: flex; }
  }
  /* The app header is taller by the strip on phones; the sticky offset follows. */
  .nav--app { height: auto; min-height: var(--nav-h); flex-wrap: wrap; }
  .nav--app .nav__inner { min-height: var(--nav-h); }
  .page--app { --nav-h: 56px; }
  @media (min-width: 960px) { .page--app { --nav-h: 64px; } }

  /* --- Explainer: five columns ------------------------------------------ */
  @media (min-width: 960px) {
    .stepper--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--space-6); }
    .stepper--5 .step::after { inset-inline: 48px calc(var(--space-6) * -1); }
  }
  @media (min-width: 640px) and (max-width: 959px) {
    .stepper--5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .stepper--5 .step::after { display: none; }
  }
  .stepper--5 .step h3 a { color: var(--c-ink); text-decoration: none; }
  .stepper--5 .step h3 a:hover { color: var(--c-brand); text-decoration: underline; }

  /* --- CTA band --------------------------------------------------------- */
  .cta-band__inner { max-width: 60ch; }
  .cta-band__inner h2 { margin-block-end: var(--space-2); }
  .cta-band__inner > p { color: var(--c-ink-3); font-size: var(--fs-lg); }
  .cta-band__inner > p:last-child { margin-block-start: var(--space-6); }

  /* --- Help rail -------------------------------------------------------- */
  .help-rail { margin-block-start: var(--space-8); border: var(--border); border-radius: var(--r-lg); background: var(--c-surface); }
  .help-rail__details > summary {
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
    min-height: 56px; padding: var(--space-3) var(--space-5);
    font-size: var(--fs-md); font-weight: var(--fw-medium); color: var(--c-ink);
    cursor: pointer; list-style: none;
  }
  .help-rail__details > summary::-webkit-details-marker { display: none; }
  .help-rail__details > summary::after {
    content: ""; flex: none; inline-size: 10px; block-size: 10px;
    border-inline-end: 2px solid var(--c-ink-3); border-block-end: 2px solid var(--c-ink-3);
    transform: rotate(45deg) translateY(-2px); transition: transform var(--dur-2) var(--ease-out);
  }
  .help-rail__details[open] > summary::after { transform: rotate(225deg) translateY(-2px); }
  .help-rail__body { padding: 0 var(--space-5) var(--space-5); font-size: var(--fs-sm); color: var(--c-ink-2); max-width: var(--measure); }
  .help-rail__body > * + * { margin-block-start: var(--space-3); }
  .help-rail__body ol { padding-inline-start: var(--space-5); display: grid; gap: var(--space-2); }

  /* --- Launch badge ----------------------------------------------------- */
  .launch-badge { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); font-size: var(--fs-sm); color: var(--c-ink-3); }

  /* --- Upload: the two paths, broker tiles, the device gate ------------- */
  /* One column: the new-statement card takes the app width, and the
     "I already paid" strip sits under it, collapsed. */
  .paths { display: grid; gap: var(--space-4); margin-block: var(--space-6); }
  .path { border: var(--border); border-radius: var(--r-xl); background: var(--c-surface); padding: var(--space-5); }
  @media (min-width: 768px) { .path { padding: var(--space-6) var(--space-8); } }
  .path--lead { border-color: var(--c-brand-border); box-shadow: inset 0 0 0 1px var(--c-brand-border); }
  .path > h2 { font-size: var(--fs-xl); margin-block-end: var(--space-2); }
  .path > .meta { margin-block-end: var(--space-4); }
  /* The card's header row: the heading and the one promise that matters at
     this station, with a rule under it so the form reads as its own object. */
  .path__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2) var(--space-4); padding-block-end: var(--space-4); margin-block-end: var(--space-6); border-block-end: var(--border); }
  .path__head h2 { font-size: var(--fs-xl); margin: 0; }

  /* The form's rhythm: three numbered steps — broker, year and canton, the
     export — each labelled the same way whether the label is a legend or a
     paragraph, so the eye reads one list and not a pile of fields. */
  .stack--form { counter-reset: form-step; }
  .form-step { display: grid; gap: var(--space-3); }
  .form-step__label {
    display: flex; align-items: center; gap: var(--space-3);
    margin: 0; padding: 0;
    font-size: var(--fs-md); font-weight: var(--fw-semibold); color: var(--c-ink);
  }
  .form-step__label::before {
    counter-increment: form-step; content: counter(form-step);
    display: inline-grid; place-items: center; flex: none;
    inline-size: 26px; block-size: 26px;
    border-radius: var(--r-pill);
    background: var(--c-brand); color: var(--c-brand-ink);
    font-size: var(--fs-xs); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums;
  }
  /* Two fields on one line where there is room; the year has a natural
     width and the canton takes the rest. Stacked below 640px. */
  .field-row { display: grid; gap: var(--space-4); }
  @media (min-width: 640px) { .field-row { grid-template-columns: minmax(9rem, 1fr) minmax(0, 2fr); align-items: start; } }
  .form-actions { display: grid; gap: var(--space-3); padding-block-start: var(--space-2); }
  .form-actions .btn { inline-size: 100%; }
  .form-actions__hint { margin: 0; font-size: var(--fs-sm); color: var(--c-ink-3); }
  @media (min-width: 640px) {
    .form-actions { grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: var(--space-5); }
    .form-actions .btn { inline-size: auto; }
  }
  .paid-form { display: grid; gap: var(--space-2); max-width: var(--measure); }
  .paid-form__row { display: grid; gap: var(--space-2); }
  @media (min-width: 480px) { .paid-form__row { grid-template-columns: minmax(0, 1fr) auto; } }

  .tiles { display: grid; gap: var(--space-3); grid-template-columns: minmax(0, 1fr); margin: 0; padding: 0; border: 0; }
  @media (min-width: 640px) { .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  .tiles > legend { float: left; inline-size: 100%; padding: 0; margin-block-end: var(--space-1); }
  /* Group labels span the grid: "Available now" above the radio tiles, "Planned" above the cards. */
  .tiles__group { grid-column: 1 / -1; margin: 0; font-size: var(--fs-xs); font-weight: var(--fw-medium); letter-spacing: 0.04em; text-transform: uppercase; color: var(--c-ink-3); }
  .tiles__group + .tiles__group, .tile + .tiles__group { margin-block-start: var(--space-2); }
  /* A tile is a monogram beside two lines of text. The chosen one carries
     the brand ring and a check in its corner, so the state is legible without
     colour; the planned one is dashed, quiet and not a control. */
  .tile {
    position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: var(--space-3);
    min-block-size: 64px; padding: var(--space-3) var(--space-4);
    border: var(--border-strong); border-radius: var(--r-lg); background: var(--c-surface); cursor: pointer;
    transition: border-color var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
  }
  .tile:hover { border-color: var(--c-ink-3); }
  .tile__mark {
    display: inline-grid; place-items: center; flex: none;
    inline-size: 40px; block-size: 40px;
    border-radius: var(--r-md);
    background: var(--c-surface-sunken); color: var(--c-ink-2);
    font-size: var(--fs-sm); font-weight: var(--fw-semibold); letter-spacing: 0.02em;
  }
  .tile__text { display: grid; gap: 2px; min-inline-size: 0; }
  .tile--planned { cursor: default; border-style: dashed; background: var(--c-surface-sunken); }
  .tile--planned .tile__name { color: var(--c-ink-3); }
  .tile--planned .tile__mark { background: var(--c-surface); color: var(--c-ink-3); }
  .tile input { position: absolute; inline-size: 1px; block-size: 1px; opacity: 0; }
  .tile:has(input:checked) { border-color: var(--c-brand); box-shadow: inset 0 0 0 1px var(--c-brand); background: var(--c-brand-tint); }
  .tile:has(input:checked) .tile__mark { background: var(--c-brand); color: var(--c-brand-ink); }
  /* The corner check: a brand disc (::after) and a tick drawn from two
     borders in the on-brand ink (::before), so no colour is baked into an
     image and both follow the theme. */
  .tile:has(input:checked)::after {
    content: ""; position: absolute; inset-block-start: 10px; inset-inline-end: 10px;
    inline-size: 18px; block-size: 18px; border-radius: var(--r-pill);
    background: var(--c-brand);
  }
  .tile:has(input:checked)::before {
    content: ""; position: absolute; z-index: 1;
    inset-block-start: 13px; inset-inline-end: 16px;
    inline-size: 5px; block-size: 9px;
    border-inline-end: 2px solid var(--c-brand-ink); border-block-end: 2px solid var(--c-brand-ink);
    transform: rotate(45deg);
  }
  .tile:has(input:focus-visible) { outline: 2px solid var(--c-focus); outline-offset: 2px; }
  .tile:has(input:disabled) { cursor: default; color: var(--c-ink-3); background: var(--c-surface-sunken); }
  .tile__name { font-weight: var(--fw-semibold); color: var(--c-ink); line-height: var(--lh-snug); }
  .tile:has(input:disabled) .tile__name { color: var(--c-ink-3); }
  .tile__meta { font-size: var(--fs-xs); color: var(--c-ink-3); line-height: var(--lh-normal); }
  .tile__meta a { position: relative; z-index: 1; }

  .gate { display: grid; gap: var(--space-4); padding: var(--space-5); margin-block-end: var(--space-6); border: var(--border-strong); border-radius: var(--r-xl); background: var(--c-surface); }
  .gate__qr { inline-size: 140px; block-size: 140px; border: var(--border); border-radius: var(--r-md); background: #fff; padding: var(--space-2); }
  @media (min-width: 480px) { .gate { grid-template-columns: auto minmax(0, 1fr); align-items: start; } }
  @media (min-width: 768px) { .gate { display: none; } }
  .gate h2 { font-size: var(--fs-md); margin-block-end: var(--space-1); }
  .copy-field { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-2); }
  .copy-field .input { font-family: var(--font-mono); font-size: var(--fs-xs); }
  .grant-line { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); align-items: center; padding: var(--space-3) var(--space-4); border: var(--border); border-radius: var(--r-md); background: var(--c-positive-tint); border-color: var(--c-positive-border); font-size: var(--fs-sm); color: var(--c-ink-2); }
  .grant-line strong { color: var(--c-positive-ink); }

  /* --- Unlock panel (station 4) ------------------------------------------ */
  .unlock { border: var(--border); border-radius: var(--r-xl); background: var(--c-surface); box-shadow: var(--shadow-2); border-color: var(--c-brand-border); overflow: hidden; }
  .unlock__head { padding: var(--space-6) var(--space-6) var(--space-4); }
  .unlock__head h2 { margin-block-end: var(--space-1); }
  .unlock__head p { color: var(--c-ink-3); font-size: var(--fs-sm); }
  .unlock__choices { display: grid; gap: 0; border-block-start: var(--border); }
  @media (min-width: 768px) { .unlock__choices { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  .unlock__choice { padding: var(--space-5) var(--space-6); display: flex; flex-direction: column; gap: var(--space-3); border-block-end: var(--border); }
  @media (min-width: 768px) { .unlock__choice { border-block-end: 0; } .unlock__choice + .unlock__choice { border-inline-start: var(--border); } }
  .unlock__choice h3 { font-size: var(--fs-md); }
  .unlock__choice .price-card__amount { margin-block: var(--space-1) var(--space-2); }
  .unlock__choice form { display: grid; gap: var(--space-3); }
  .unlock__choice .btn { margin-block-start: 0; }
  /* The claim panel has THREE columns, not two: what arrives, how to claim it,
     and the other years. Same rules as .unlock__choice, one more track. */
  .unlock__cols { display: grid; gap: 0; border-block-start: var(--border); }
  @media (min-width: 768px) { .unlock__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  @media (min-width: 1040px) { .unlock__cols { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
  .unlock__col { padding: var(--space-5) var(--space-6); display: flex; flex-direction: column; gap: var(--space-3); border-block-end: var(--border); }
  @media (min-width: 768px) { .unlock__col { border-block-end: 0; } .unlock__col + .unlock__col { border-inline-start: var(--border); } }
  @media (min-width: 768px) and (max-width: 1039px) { .unlock__col:nth-child(odd) { border-inline-start: 0; } .unlock__col:nth-child(-n+2) { border-block-end: var(--border); } }
  .unlock__col h3 { font-size: var(--fs-md); }
  .unlock__col .price-card__amount { margin-block: var(--space-1) var(--space-2); }
  .unlock__col form { display: grid; gap: var(--space-3); }
  .unlock__col .btn { margin-block-start: 0; }
  .unlock__before { display: grid; gap: var(--space-2); margin: 0; padding: 0; list-style: none; font-size: var(--fs-sm); }
  .unlock__before li { display: grid; grid-template-columns: 1.1em minmax(0, 1fr); gap: var(--space-2); align-items: start; }
  .unlock__before svg { inline-size: 1.1em; block-size: 1.1em; color: var(--c-positive); margin-block-start: .15em; }

  .unlock__before { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-2); font-size: var(--fs-sm); color: var(--c-ink-2); }
  .unlock__before li { display: grid; grid-template-columns: 18px 1fr; gap: var(--space-2); }
  .unlock__before li:not(:has(> svg)) { display: block; }
  .unlock__before svg { inline-size: 18px; block-size: 18px; color: var(--c-positive); margin-block-start: 2px; }
  .unlock__later { padding: var(--space-4) var(--space-6); font-size: var(--fs-sm); color: var(--c-ink-3); border-block-start: var(--border); background: var(--c-surface-sunken); }
  .unlock__mail { display: inline; }
  .unlock__mail > summary { display: inline; list-style: none; cursor: pointer; }
  .unlock__mail > summary::-webkit-details-marker { display: none; }
  .unlock__mail[open] > summary { display: block; margin-block-end: var(--space-3); }
  .unlock__mail > form { margin-block-start: var(--space-2); }
  .unlock__later .btn--link { display: inline; min-height: 0; padding: 0; color: var(--c-brand); text-decoration: underline; font-size: inherit; font-weight: inherit; }
  .paymethods { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
  .paymethods li { padding: 2px var(--space-2); border: var(--border); border-radius: var(--r-xs); font-size: var(--fs-2xs); font-weight: var(--fw-semibold); letter-spacing: .02em; color: var(--c-ink-2); background: var(--c-surface-sunken); }
  .values-line { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); font-size: var(--fs-sm); color: var(--c-ink-2); }
  .values-line .badge { flex: none; }

  /* --- Delivery: routes into the return --------------------------------- */
  .routes { border: 0; padding: 0; margin: 0; }
  .routes > legend { padding: 0; margin-block-end: var(--space-3); font-size: var(--fs-sm); color: var(--c-ink-3); }
  .routes__tabs { display: grid; gap: var(--space-2); margin-block-end: var(--space-4); }
  @media (min-width: 640px) { .routes__tabs { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
  .routes__tab { position: relative; display: flex; align-items: center; justify-content: center; text-align: center; min-block-size: 48px; padding: var(--space-2) var(--space-3); border: var(--border-strong); border-radius: var(--r-md); background: var(--c-surface); font-weight: var(--fw-medium); color: var(--c-ink-2); cursor: pointer; }
  .routes__tab input { position: absolute; inline-size: 1px; block-size: 1px; opacity: 0; }
  .routes__tab:has(input:checked) { border-color: var(--c-brand); box-shadow: inset 0 0 0 1px var(--c-brand); background: var(--c-brand-tint); color: var(--c-ink); }
  .routes__tab:has(input:focus-visible) { outline: 2px solid var(--c-focus); outline-offset: 2px; }
  .routes__panel { padding: var(--space-5); border: var(--border); border-radius: var(--r-lg); background: var(--c-surface); }
  .routes__panel + .routes__panel { margin-block-start: var(--space-3); }
  .routes__panel h3 { font-size: var(--fs-lg); margin-block-end: var(--space-3); }
  .routes__panel > * + * { margin-block-start: var(--space-3); }
  .routes__panel ol, .routes__panel ul { padding-inline-start: var(--space-5); display: grid; gap: var(--space-2); }
  /* Only where :has() exists does the segmented control hide the other two
     panels. Without it all three show stacked, which is the no-JS fallback
     the design already accepts. */
  @supports selector(:has(*)) {
    .routes:has(#route-import:checked) .routes__panel:not([data-route="import"]),
    .routes:has(#route-scan:checked) .routes__panel:not([data-route="scan"]),
    .routes:has(#route-accountant:checked) .routes__panel:not([data-route="accountant"]) { display: none; }
  }
  .paid-line { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); align-items: center; font-size: var(--fs-sm); color: var(--c-ink-2); }
  .keep-link { display: grid; gap: var(--space-2); padding: var(--space-4); border: var(--border); border-radius: var(--r-lg); background: var(--c-surface-sunken); }
  .keep-link h2 { font-size: var(--fs-md); }
  .keep-link .meta { margin: 0; }
  .keep-link form.stack { margin-block-start: var(--space-2); }
  .self-check { padding: var(--space-4) var(--space-5); border-inline-start: 3px solid var(--c-positive); background: var(--c-positive-tint); border-radius: var(--r-md); }
  .self-check h2 { font-size: var(--fs-md); margin-block-end: var(--space-1); }
  .downloads--cards .download--static { cursor: default; }
  .downloads--cards .download--static:hover { border-color: var(--c-border); background: var(--c-surface); }
  .download--view { border-style: dashed; }

  /* --- Home: hero figure, price strip, waitlist ------------------------- */
  .hero__figure { padding: var(--space-4); aspect-ratio: auto; display: grid; gap: var(--space-3); align-content: start; background: var(--c-surface-sunken); }
  .hero__figure .result { box-shadow: none; font-size: var(--fs-sm); }
  .hero__figure .figure-chf { font-size: clamp(2rem, 1.4rem + 2.6vw, 2.75rem); }
  .hero__figure .result__head { padding: var(--space-5) var(--space-4) var(--space-4); }
  .hero__figure .result__rows { padding-inline: var(--space-4); }
  .hero__figure .result__row { padding-block: var(--space-2); font-size: var(--fs-xs); }
  .hero__figure .result__foot { display: none; }
  .hero__figure .downloads { gap: var(--space-2); }
  .hero__figure .download { min-height: 48px; padding: var(--space-2) var(--space-3); }
  .hero__figure .download__label { font-size: var(--fs-sm); }
  .hero__figure__label { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: var(--tracking-caps); color: var(--c-ink-3); font-weight: var(--fw-semibold); display: flex; justify-content: space-between; gap: var(--space-3); }
  .hero__figure__label a { text-transform: none; letter-spacing: 0; font-weight: var(--fw-medium); }
  .hero .hero__meta strong { color: var(--c-ink); }
  .waitlist-form { display: grid; gap: var(--space-2); }
  .waitlist-form__row { display: grid; gap: var(--space-2); }
  @media (min-width: 480px) { .waitlist-form__row { grid-template-columns: minmax(0, 1fr) auto; } }
  .waitlist-form .btn { white-space: nowrap; }

  /* --- Guides: station 1 checklist --------------------------------------- */
  .checklist { list-style: none; padding: 0; margin: var(--space-4) 0; display: grid; gap: var(--space-2); }
  .checklist li { display: grid; grid-template-columns: 20px 1fr; gap: var(--space-3); align-items: start; }
  .checklist li:not(:has(> svg, > input, > label)) { display: block; }
  .checklist svg { inline-size: 20px; block-size: 20px; color: var(--c-brand); margin-block-start: 2px; }
  .checklist--interactive li { grid-template-columns: 1fr; }
  .checklist--interactive label { display: grid; grid-template-columns: 22px 1fr; gap: var(--space-3); align-items: start; min-block-size: 24px; cursor: pointer; }
  .checklist--interactive input { inline-size: 20px; block-size: 20px; margin-block-start: 2px; accent-color: var(--c-brand); }
  .checklist__progress { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); font-size: var(--fs-sm); color: var(--c-ink-3); }
  .checklist__progress .btn--link { display: inline; min-height: 24px; padding: 0; color: var(--c-brand); text-decoration: underline; font-size: inherit; }
  .price-strip { display: grid; gap: var(--space-4); }
  @media (min-width: 640px) { .price-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
}

/* ==========================================================================
   6. UTILITIES
   ========================================================================== */
@layer utilities {
  .visually-hidden:not(:focus):not(:active) {
    position: absolute !important; inline-size: 1px; block-size: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .text-center { text-align: center; }
  .text-muted { color: var(--c-ink-3); }
  .text-positive { color: var(--c-positive); }
  .measure { max-width: var(--measure); }
  .measure-narrow { max-width: var(--measure-narrow); }
  .mt-0 { margin-block-start: 0; }
  .full-bleed { width: 100vw; margin-inline: calc(50% - 50vw); }
  .no-wrap { white-space: nowrap; }
}

/* ==========================================================================
   7. MOTION + PRINT
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* People screenshot and print the fee table. Make that version good. */
@media print {
  :root { color-scheme: only light; }
  .nav, .drawer, .footer__grid, .skip-link, .row-link, .toc { display: none !important; }
  .table-wrap { overflow: visible; border: 0; background: none; }
  .table { min-inline-size: 0; font-size: 9pt; }
  .table thead th { position: static; background: none; border-block-end: 1pt solid #000; }
  .table tbody th[scope="row"] { position: static; }
  .table tbody tr { break-inside: avoid; }
  .table caption { border: 0; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 8pt; color: #555; }
  .section, .section--loose { padding-block: 0.5cm; }
}
