/* AI Disclosure Checker
   ---------------------
   Layer order is the cascade contract: later layers win, so a component never
   needs !important to beat a base rule. Colour comes from tokens only; the one
   saturated family in the system is the verdict ladder. */

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

/* Inter, self-hosted. Not the Google Fonts CDN: embedding it transmits visitor
   IPs to a third party, which a German court found breached GDPR in 2022, and
   an EU compliance tool leaking its visitors' IPs would be a poor look.
   Self-hosting also removes a network dependency from a page that must feel
   instant.

   One variable file spans 400–700, so weight costs no extra request.
   `font-display: optional` means the browser uses Inter when it is already
   cached and the system stack otherwise, no flash, no layout shift, no blocked
   paint. The latin-ext file is only fetched if a character in its range appears,
   so English visitors never pay for it.

   Inter is licensed under SIL OFL 1.1, see assets/fonts/Inter-LICENSE.txt,
   which must stay alongside the font files when this is deployed. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: optional;
  src: url('../fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC,
                 U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: optional;
  src: url('../fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* STIX Two Text for headings. Self-hosted for the same reason as Inter, and
   licensed under SIL OFL 1.1, see assets/fonts/STIXTwoText-LICENSE.txt, which
   must ship alongside the font file.

   `font-display: swap` rather than `optional` here: headings are a small part
   of the page and the swap is worth seeing, where body text mid-read is not.
   One variable file covers 400–700. */
@font-face {
  font-family: 'STIX Two Text';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/stix-two-text-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------------------------------------------------------------- reset -- */
@layer reset {
  *, *::before, *::after { box-sizing: border-box; }

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

  body, h1, h2, h3, p, ol, ul, figure, fieldset, legend {
    margin: 0;
    padding: 0;
  }

  fieldset { border: 0; min-inline-size: 0; }
  ol, ul { list-style: none; }
  button, input { font: inherit; color: inherit; }
  svg { display: block; }

  :where(a) { color: inherit; }
}

/* --------------------------------------------------------------- tokens -- */
@layer tokens {
  :root {
    /* Inter with the system stack behind it. Under `font-display: optional` the
       fallback is a real reading condition, not a theoretical one, so the stack
       is ordered to match Inter's metrics rather than left to chance. */
    --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                 "Helvetica Neue", Arial, sans-serif;
    /* Headings only. A serif against the sans body gives the page a voice
       without touching legibility where it matters, the answers stay in
       Inter. Georgia leads the fallback because its proportions are closest. */
    --font-display: 'STIX Two Text', Georgia, 'Times New Roman', serif;
    --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
                 "Liberation Mono", monospace;

    /* Four sizes carry almost the whole interface: 12 for anything subordinate,
       16 for body, 22 for the headings inside a step, 28 for the step heading
       itself. The scale used to carry 13 and 15 as well, which meant several
       sizes doing one job and no rule for choosing between them. */
    --text-xs: 0.75rem;      /* 12 */
    --text-base: 1rem;       /* 16 */
    --text-lg: 1.125rem;     /* 18, the hero subheading only */
    --text-xl: 1.375rem;     /* 22 */
    --text-2xl: 1.75rem;     /* 28 */
    --text-3xl: 2.25rem;
    --text-4xl: 3rem;
    --text-5xl: 4.5rem;

    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.5rem;
    --space-6: 2rem;
    --space-7: 3rem;
    --space-8: 4rem;
    /* One step past where the scale used to stop, keeping the 1.5x rhythm the
       top of it already uses (32, 48, 64). Lived in know.css until law.css
       wanted the same band inset, which is the condition that file named for
       moving it here. */
    --space-9: 6rem;   /* 96 */

    /* Same 4-based scale as spacing. The previous 6/10/14 was invented rather
       than derived, and two components were already hardcoding 4px because the
       scale had no step small enough. One set of numbers is easier to hold. */
    --radius-xs: 4px;    /* pills, toggle options, chips  */
    --radius-sm: 8px;    /* buttons, toggle track          */
    --radius: 12px;      /* cards, option rows, inputs     */
    --radius-lg: 16px;   /* the verdict panel              */
    --radius-xl: 24px;   /* the know screens, the law cards */
    --radius-full: 999px;/* the stepper circles            */

    /* Reading measure is capped independently of the container. The page can
       be 1440 wide; a line of prose still shouldn't be, or the eye loses its
       place returning to the next line. */
    --measure: 68ch;
    --shell: 90rem;   /* 1440px */

    /* Exponential ease-out. One curve for the whole surface. */
    --ease: cubic-bezier(0.16, 1, 0.3, 1);

    color-scheme: light dark;
  }

  /* Light, the default reading condition. */
  :root {
    /* Warm ground, white cards. Keeping surfaces pure white against a warm
       page is what makes a panel read as lifted rather than just tinted, the
       neutrals below are warmed to match, so nothing sits on it looking grey
       by accident. */
    --bg: #f9f4f1;
    --bg-sunken: #f2eae5;
    --surface: #ffffff;
    --surface-raised: #ffffff;
    --border: #e8ded8;
    --border-strong: #d6c8c0;
    /* Navy rather than near-black. The secondary tones are mixed from it into
       the warm ground rather than picked as greys, a neutral grey next to
       navy type reads as a different family and makes the page look assembled
       from parts. */
    --text: #00346b;
    --text-muted: color-mix(in oklab, var(--text) 80%, var(--bg));
    /* Was 58%. The lighter navy took the stepper labels and the search
       placeholder under 4.5:1. A tone that worked against the darker ink does
       not automatically survive the swap. */
    --text-subtle: color-mix(in oklab, var(--text) 72%, var(--bg));
    /* Blue is deliberately off the verdict ladder. Green, amber, and red mean
       "this is your answer"; blue means "this is a control". They must never
       be confusable. */
    --accent: #0040ff;
    --accent-hover: #0f33b1;
    --accent-text: #ffffff;
    --accent-soft: color-mix(in oklab, var(--accent) 7%, var(--bg));
    --focus: #0040ff;

    /* Chosen answers, off the blue scale. Solid blue stays on buttons, those
       are actions. A selection is a state, and reads better as a soft field
       than as another thing shouting to be pressed. */
    /* The primary of each verdict family, used where a graphic needs the lit
       version of a colour rather than the ink one. Named by hue, because they
       are wanted on tags and primary elements too, not only on the ladder. */
    --accent-green: #008238;
    --accent-yellow: #fdc237;
    --accent-purple: #9451e4;
    --accent-orange: #fd6f00;
    --accent-red: #fd0000;
    --accent-blue: #57b9ff;
    --accent-lilac: #e8aaff;

    /* The footer band. Blue70, the same value the law panel sits on, but its
       own name: that one is a section, this one closes the page, and a shared
       token would tie two unrelated decisions together. */
    --footer-band: #004285;

    --select-bg: #cce9ff;     /* blue10 */
    --select-fg: #004285;     /* blue70, 7.87:1 on blue10 */
    --select-border: #57b9ff; /* blue30 */
    --select-mark: #0059b2;   /* blue60, the dot, dark enough to hold its shape */
    --shadow-raised: 0 1px 2px rgb(24 24 27 / 0.05), 0 4px 12px rgb(24 24 27 / 0.06);

    /* The verdict ladder, taken straight off the supplied base scales.
       Backgrounds are the -10 step, as chosen. Text is the -70 step of the
       same family and borders are -30, so nothing here is invented, every
       value is a real rung on the ladder you already have.

       Success/warning/danger map onto green/yellow/red, matching how the
       status tokens already use them. */
    /* Green and yellow sit on -5 so a good answer and a suggestion stay quiet.
       Red keeps -10 and is the only panel that raises its voice, which is the
       whole point of a traffic light. */
    --green-bg: #d3fde5;   /* green5   */
    --green-fg: #024f23;   /* green70, 8.83:1 */
    --green-border: #0cca5e; /* green30 */

    --amber-bg: #fff3d2;   /* yellow5  */
    --amber-fg: #554106;   /* yellow70, 8.86:1 */
    --amber-border: #dea702; /* yellow30 */

    --red-bg: #fbdada;     /* red10    */
    --red-fg: #7f2929;     /* red70  , 7.17:1 */
    --red-border: #f79190; /* red30    */
  }

  /* Dark, creators publish late, and this is the condition they do it in. */
  @media (prefers-color-scheme: dark) {
    :root {
      --bg: #09090b;
      /* Must stay clear of --bg: this token carries checked options, ghost
         hover, and the toggle track. Matching --bg makes all three invisible. */
      --bg-sunken: #17171b;
      --surface: #101012;
      --surface-raised: #1d1d21;
      --border: #26262a;
      --border-strong: #3a3a40;
      --text: #f4f4f5;
      --text-muted: #a1a1aa;
      --text-subtle: #8a8a93;
      /* Same blue, lifted for a near-black ground. #0040ff reads at only
         2.9:1 here, and a darker hover on a dark page moves the wrong way, 
         so dark lightens on hover rather than deepening. */
      --accent: #4d74ff;
      --accent-hover: #7a97ff;
      --accent-text: #06091a;
      --accent-soft: color-mix(in oklab, var(--accent) 12%, var(--bg));
      --focus: #6d8cff;

      /* Same idea read downward: the deep end of the blue scale for the field,
         the pale end for the type on it. */
      /* Blue80, a step back up the same scale, for the reason the law band
         steps: a full-strength blue70 against a near-black page is a slab. */
      --footer-band: #00346b;

      --select-bg: #00346b;     /* blue80 */
      --select-fg: #85ccff;     /* blue20 */
      --select-border: #004285; /* blue70 */
      --select-mark: #57b9ff;   /* blue30 */
      --shadow-raised: 0 1px 2px rgb(0 0 0 / 0.4), 0 4px 14px rgb(0 0 0 / 0.35);

      /* Dark grounds carry a bigger tint step before contrast suffers, so the
         confidence line can sit further from the headline here than in light. */
      --verdict-meta-mix: 80%;

      /* The same ladder read from the other end. Dark grounds take the -90
         step, text the -20, borders the -70, which is what the deep end of
         these scales is for. */
      --green-bg: #001f0d;   /* green90  */
      --green-fg: #09e167;   /* green20, 9.97:1 on green90 */
      --green-border: #024f23; /* green70 */

      --amber-bg: #201904;   /* yellow90 */
      --amber-fg: #f7ba02;   /* yellow20, 9.96:1 on yellow90 */
      --amber-border: #554106; /* yellow70 */

      /* Red sits one step shallower than the others here too, on a dark ground
         a lighter panel is the one that carries, so this is the same escalation
         read downward. */
      --red-bg: #4e1313;     /* red80    */
      --red-fg: #ffadad;     /* red20  , 8.30:1 */
      --red-border: #7f2929; /* red70    */
    }
  }
}

/* ----------------------------------------------------------------- base -- */
@layer base {
  body {
    background: var(--bg);
    color: var(--text);
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    font-optical-sizing: auto;
    /* The variable file carries real 400–700, so never let the browser fake a
       weight, synthesised bold is what makes a webfont look counterfeit. */
    font-synthesis: none;
  }

  /* A backstop, not the mechanism. Nothing should overflow sideways, the hero
     flag runs off the screen from inside its own canvas rather than by hanging
     past it, but a stray wide element must never make the whole page scrollable
     horizontally. `clip` rather than `hidden`, because hidden would make this a
     scroll container and quietly break position: sticky and scroll anchoring for
     everything inside it. */
  html { overflow-x: clip; }

  /* Hold the scrollbar's space open whether or not there is a scrollbar.
     Without this, opening the tile grid makes the page taller than the window,
     the scrollbar appears, the viewport loses about 15px, and everything laid
     out against that width shifts: the hero flag is a percentage of it, so it
     resizes and re-renders, and the headline re-wraps. Typing one letter should
     not move the top of the page. */
  html { scrollbar-gutter: stable; }

  h1, h2, h3 {
    font-family: var(--font-display);
    line-height: 1.2;
    /* A serif needs far less negative tracking than a grotesque; -0.015em on
       STIX closes the counters and makes it look cramped. */
    letter-spacing: -0.005em;
    text-wrap: balance;
    font-weight: 600;
  }

  p { text-wrap: pretty; }

  a {
    color: var(--text);
    text-decoration-color: var(--border-strong);
    text-underline-offset: 0.2em;
  }

  a:hover { text-decoration-color: currentColor; }

  /* No border-radius here. The outline already follows each element's own
     corner shape, and forcing one made the ring disagree with round controls. */
  :focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
  }

  ::selection {
    background: var(--text);
    color: var(--bg);
  }

  .sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

  .icon {
    width: 1.125rem;
    height: 1.125rem;
    flex: none;
  }
}

/* --------------------------------------------------------------- layout -- */
@layer layout {
  /* The gutter is published as a variable, not just applied. Anything that has
     to break out of the shell, the hero flag, the pill strip, needs to cancel
     exactly this much, and hard-coding the desktop value in those rules left
     them overhanging the viewport at every other width. */
  .shell {
    --shell-pad: var(--space-6);
    width: 100%;
    max-width: var(--shell);
    margin-inline: auto;
    padding-inline: var(--shell-pad);
  }

  /* Gutters grow with the page. At 1440 a 32px margin reads as content falling
     off the edge rather than as a deliberate full-width layout. */
  @media (min-width: 64rem) {
    .shell { --shell-pad: var(--space-8); }
  }

  @media (max-width: 34rem) {
    .shell { --shell-pad: var(--space-5); }
  }

  /* White bar across the full width, warm page beneath it. The bar is what
     makes the ground read as deliberate rather than as an off-white page. */
  .topbar {
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    padding-block: var(--space-4);
  }

  /* 48/48, per the Figma. It was 64 top and 48 bottom. */
  .intro {
    /* Less underneath than above. The hero opens the page, but what follows it
       is the thing you came to use, and it was sitting further from the search
       box than the search box sits from the headline. */
    padding-block: var(--space-7) var(--space-6);
  }

  @media (max-width: 34rem) {
    .intro { padding-block: var(--space-6) var(--space-5); }
  }

  /* The flag only earns its place once there is width to spare beside the
     heading. Narrower than this it would sit above the copy and push the tool
     itself below the fold, which is a poor trade for decoration, so it is not
     shrunk, it is dropped, and the script for it is never fetched.

     1280 rather than 1024: between those two the flag technically fits, but it
     squeezes the headline column hard enough to break the second line in half,
     and a cramped headline costs more than a decoration is worth. */
  /* The flag sits beside the heading at every width. It never drops below the
     copy: the hero is one object, copy on the left and cloth running off the
     right, and stacking them turns it into two.

     What changes with width is how much of the row it takes and how large it
     is drawn. The cloth is sized by the canvas height rather than its width,
     because the camera frames it vertically and any extra width is just empty
     space to its left, so the canvas overhangs its column to reach the edge
     without the hero growing to match. */
  /* Below the desktop layout the flag floats to the top right of the hero and
     the copy wraps around it.

     A float rather than a column or an absolute box, because those two both get
     the same thing wrong: they reserve the width down the whole hero, so the
     search field loses it as well, and the search field is the tool. It runs
     the full width of the screen. A float gives up its width the moment the
     text clears the bottom of the cloth, which is exactly the behaviour wanted
     and the only mechanism that reads the flag's real height rather than
     guessing at it. */
  .intro__flag {
    display: block;
    float: right;
    position: relative;
    width: 46%;
    height: 10rem;
    margin-right: calc(0px - var(--shell-pad));
    margin-left: var(--space-4);
    /* Nothing here is interactive, and the canvas overhangs its box on to the
       words below, where it would otherwise swallow taps meant for them. */
    pointer-events: none;
  }

  .intro__flag canvas {
    display: block;
    position: absolute;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    width: 100%;
    height: 14rem;
  }

  /* The float is cleared before the pills below, or a short hero would let them
     ride up beside the cloth. */
  .intro::after {
    content: '';
    display: block;
    clear: both;
  }

  @media (min-width: 48rem) {
    .intro__flag {
      width: 42%;
      height: 14rem;
    }

    .intro__flag canvas { height: 20rem; }
  }

  @media (min-width: 80rem) {
    .intro {
      display: grid;
      /* 892px of copy and no gap, per the Figma, the flag begins exactly where
         the text block ends and overlaps into it. A gap plus a fixed flag width
         was what squeezed the headline. */
      grid-template-columns: minmax(0, 55.75rem) minmax(0, 1fr);
      gap: 0;
      align-items: center;
    }

    /* Wide enough to be a real column, so the float goes and the grid places
       both sides explicitly: the source order puts the flag first. */
    .intro__copy { grid-column: 1; grid-row: 1; }
    .intro::after { display: none; }

    .intro__flag {
      grid-column: 2;
      grid-row: 1;
      float: none;
      margin-left: 0;
      width: auto;
      pointer-events: auto;
      /* The canvas is transparent, so the warm ground shows through wherever
         the cloth is not. No panel, no border. It reads as an object on the
         page rather than an image in a box.

         The negative margin reaches back across whatever gutter the centred,
         capped shell leaves, so the canvas ends exactly at the right-hand edge
         of the screen, min() holds it at zero once the window is narrower than
         the cap. It stops there rather than overhanging: the cloth is pushed
         off the edge inside the canvas instead (see FLY_OUT in flag.js), which
         gets the same bleed without the page overflowing. Relying on
         overflow-x: clip to hide a real overhang did not survive contact with
         the browser. The page still scrolled sideways by the overhang.

         Kept in flow rather than positioned, so it still gives the hero its
         height. */
      /* Two things to cancel so the cloth runs clean off the side of the screen:
         the shell's own right padding, which applies at every width, and the
         gutter the centred shell leaves once the window is wider than the cap.
         min() holds that second term at zero below 1440, where the cap and the
         screen edge are already the same line.

         The flag is the one element that escapes the 1440 measure. Everything
         that carries meaning stays inside it. This is decoration running off
         the edge, which is the point of it. */
      margin-right: calc(min(0px, (var(--shell) - 100vw) / 2) - var(--shell-pad));
      touch-action: pan-y;

      /* Height here is what the flag contributes to the hero, not how big it
         is drawn. The canvas below overhangs it, so the cloth reads large
         without the heading being pushed away from the tool underneath. */
      position: relative;
      height: 23rem;
    }

    .intro__flag canvas {
      display: block;
      position: absolute;
      top: 50%;
      right: 0;
      transform: translateY(-50%);
      width: 100%;
      height: 31rem;
    }
  }

  main { padding-bottom: var(--space-8); }

  /* Blue band, and the heaviest thing on the page on purpose. It used to be
     white like the top bar, which closed the page politely and left the
     disclaimer reading as an afterthought stuck to the bottom. Weight here
     ends the page instead of letting it run out.

     Deep enough that white sits at 8.6:1 on it, and it steps down a place in
     dark for the same reason the law band does. Declared in app.css rather
     than a section file because the policy pages load only this stylesheet
     and a footer that changes colour between pages is not a footer. */
  .footer {
    background: var(--footer-band);
    padding-block: var(--space-7) var(--space-8);
    color: #ffffff;
    font-size: var(--text-base);
  }

  /* -- the columns ---------------------------------------------------- *
     The disclaimer keeps the width it had; the three link columns sit beside
     it. Four equal tracks rather than auto, so the labels line up down the
     row instead of being pushed about by how long the links happen to be.

     Only index.html carries these. The policy pages keep the short footer,
     and every rule here is on a class they do not use, so they are untouched
     by it apart from the colour. */
  .footer__columns {
    display: grid;
    gap: var(--space-6) var(--space-5);
    margin-bottom: var(--space-7);
  }

  @media (min-width: 48rem) {
    .footer__columns { grid-template-columns: repeat(2, 1fr); }
  }

  @media (min-width: 64rem) {
    .footer__columns { grid-template-columns: repeat(4, 1fr); }
  }

  .footer__col {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
  }

  /* The same small-caps label the law cards use, on the same tracking. */
  .footer__label {
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: #ffffff;
    opacity: 0.72;
  }

  /* Underlined only on approach. Twelve permanently underlined links in a row
     is a fence, and these are for the one reader in fifty who wants a source,
     not for everyone else to read past. */
  .footer__col a,
  .footer__col button {
    padding: 0;
    border: 0;
    background: none;
    color: #ffffff;
    font: inherit;
    /* The frame sets these at 14. The scale jumps 12 to 16 with nothing
       between, and one footer is not a reason to put a step there, so they
       take the 16. It reads a shade larger than the comp and gives a hand on
       a phone a target worth aiming at. */
    font-size: var(--text-base);
    line-height: 1.6;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
  }

  .footer__col a:hover,
  .footer__col button:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  .footer__col a:focus-visible,
  .footer__col button:focus-visible {
    outline: 2px solid #ffffff;
    outline-offset: 3px;
  }

  /* On a phone these stack into a single column and a 26px line of text is a
     poor thing to aim a thumb at. Same 2.75rem the buttons elsewhere use, and
     the column gap comes down so the list does not spread to make room for
     targets that are mostly air. Untouched on a pointer, where the compact
     stack the frame draws is right. */
  @media (max-width: 48rem) {
    .footer__col { gap: 0; }

    .footer__col a,
    .footer__col button {
      display: inline-flex;
      align-items: center;
      min-height: 2.75rem;
    }
  }

  /* A white tint, not --border. That token is a warm grey for the cream page
     and measures nothing against navy, which is the same trap know.css
     documents for its panel edge. */
  .footer__rule {
    height: 1px;
    margin-bottom: var(--space-5);
    background: rgb(255 255 255 / 0.28);
  }

  .footer p + p { margin-top: var(--space-3); }

  /* The disclaimer runs to seventy per cent of the shell rather than the reading
     measure. The measure held it to about half the row with the rest empty
     beside it, which read as a stray column rather than the closing statement
     of the page.

     No character ceiling alongside it: the shell is already capped at 1440, so
     seventy per cent of it cannot run away, and a ceiling that never binds is
     just a number to maintain. Released on narrow screens, where seventy per
     cent of a phone is a column too thin to read.

     Scoped to the shell's own children so it stops at the short footer the
     policy pages use. Inside a column the paragraph already has a column to
     be as wide as, and seventy per cent of that is a ribbon. */
  .footer > .shell > p { max-width: 70%; }

  @media (max-width: 48rem) {
    .footer > .shell > p { max-width: none; }
  }

  /* Stamp and policy links share a row on the same baseline. Stacked below the
     breakpoint rather than squeezed, because two things pushed apart on a phone
     read as a mistake. */
  .footer__foot {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3) var(--space-5);
    margin-top: var(--space-3);
  }

  .footer__foot .footer__stamp { margin-top: 0; }

  .footer__links {
    display: flex;
    gap: var(--space-5);
    margin-left: auto;
  }

  .footer__links a {
    color: #ffffff;
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  .footer__links a:hover { opacity: 0.78; }

  /* -- policy pages -------------------------------------------------- */
  /* Prose, and nothing else. These are read start to finish rather than
     scanned, so they get the reading measure and no furniture. */
  .prose {
    max-width: var(--measure);
    padding-block: var(--space-7) var(--space-8);
  }

  .prose h1 {
    font-family: var(--font-display);
    font-size: var(--text-3xl);
    font-weight: 400;
    line-height: 1.15;
    letter-spacing: -0.012em;
  }

  .prose h2 {
    font-size: var(--text-lg);
    font-weight: 600;
    margin-top: var(--space-6);
    margin-bottom: var(--space-2);
  }

  .prose p, .prose li { color: var(--text-muted); }
  .prose p + p, .prose ul { margin-top: var(--space-3); }
  .prose ul { padding-left: var(--space-5); }
  .prose li { list-style: disc; margin-top: var(--space-2); }
  .prose a { color: var(--accent); text-underline-offset: 3px; }

  .prose__lede {
    margin-top: var(--space-4);
    font-size: var(--text-lg);
    color: var(--text);
  }

  .prose__updated {
    margin-top: var(--space-6);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border);
    font-size: var(--text-xs);
    color: var(--text-subtle);
  }

  /* Marks the details only the operator can supply. Deliberately loud: an
     unfilled placeholder in a published policy is worse than no policy. */
  .todo {
    background: var(--amber-bg);
    color: var(--amber-fg);
    padding: 0 var(--space-1);
    border-radius: var(--radius-xs);
    font-weight: 600;
  }
}

/* ----------------------------------------------------------- components -- */
@layer components {

  /* -- masthead ------------------------------------------------------ */
  /* The logo, in the body face rather than the display serif. It sits against
     the serif hero directly below it, and two serifs at different sizes read
     as one thing failing to line up. The sans makes it clearly a mark. */
  .wordmark {
    display: inline-block;
    font-family: var(--font-sans);
    font-size: var(--text-base);
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--text);
    text-decoration: none;
  }

  .wordmark:hover { color: var(--accent); }

  /* The hero line. Larger than any step heading because it is the only thing
     on the page making the case for using the tool at all, after step one it
     has done its job and gets out of the way. */
  .standfirst {
    font-family: var(--font-display);
    font-size: var(--text-5xl);
    font-weight: 400;
    line-height: 1;
    letter-spacing: -0.012em;
    color: var(--text);
    text-wrap: balance;
  }

  /* Stays at the full 72px beside the flag. My earlier reduction was solving a
     problem I had created: the copy column was too narrow because the flag had
     been given a fixed 27rem and a 48px gap. The Figma gives the text 892px and
     lets the flag start right where it ends, at that width 72px holds two
     lines, so the headline never needed shrinking. */

  /* The qualifying detail, in the body face and a size down. It answers
     "which rules?" for anyone who wants to know, without competing with the
     line that has to land first. */
  .substandfirst {
    margin-top: var(--space-4);
    font-family: var(--font-sans);
    font-size: var(--text-lg);
    line-height: 1.5;
    color: var(--text-muted);
    max-width: 46ch;
    text-wrap: pretty;
  }

  /* One line beside the flag, as in the Figma. The 46ch measure is right when
     this sits under a full-width headline; in the two-column hero it broke a
     single short sentence across two lines for no reason. */
  @media (min-width: 80rem) {
    .substandfirst { max-width: none; }
  }

  @media (max-width: 48rem) {
    .standfirst { font-size: var(--text-3xl); }
    .substandfirst { font-size: var(--text-base); }
  }

  @media (max-width: 34rem) {
    .standfirst { font-size: var(--text-2xl); }
  }

  /* Over half a phone viewport was going on preamble before the first option.
     The visitor arrived to answer a question, not to read a header.

     The hero's own size is set at 48rem and 34rem above. It used to be clamped
     here too, back when `.standfirst` was a small intro line rather than the
     hero, left in place it silently overrode both, because it sits later in
     the file at the same specificity. */
  @media (max-width: 30rem) {
    .stepper { margin-bottom: var(--space-5); }
    .step__note { margin-bottom: var(--space-4); }
  }

  .skip {
    position: absolute;
    left: var(--space-4);
    top: var(--space-4);
    padding: var(--space-2) var(--space-4);
    background: var(--surface-raised);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    transform: translateY(-250%);
    z-index: 10;
  }

  .skip:focus-visible { transform: none; }

  /* -- stepper. The sequence is information the visitor needs -------- */
  /* The top margin is what gives steps 2, 3 and the answer their air. On the
     first screen the hero supplies it, and this is hidden, so it cannot be
     put on `main` without doubling the gap on the landing page. 48px matches
     the hero's own top padding, so every screen starts on the same line. */
  .progress {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-5);
    margin-top: var(--space-7);
    margin-bottom: var(--space-6);
  }

  .stepper {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-xs);
    color: var(--text-subtle);
  }

  /* Step one's answer, carried forward. The chip repeats the tile it was picked
     in, corner and all, so it reads as the same object rather than a new one. */
  .carry {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    font-size: var(--text-base);
    color: var(--text-muted);
  }

  /* The trail under the stepper is the same object as the one on the result,
     so it wears the same rules. Only the ground differs: here it sits on the
     cream page rather than inside a coloured band, so it takes an edge. */
  .answers__set--carry {
    border: 1px solid var(--border);
  }

  @media (max-width: 48rem) {
    .carry { font-size: var(--text-xs); }
    .answers__set--carry { font-size: var(--text-xs); }
  }

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

  /* The button carries the padding so the hit area covers the number and the
     word together, clicking either has to work, and a 24px circle on its own
     is a mean target. */
  .stepper__go {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-2);
    margin: calc(var(--space-1) * -1) calc(var(--space-2) * -1);
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
  }

  /* Not yet reached. Left looking exactly as it did before it was a button, 
     the affordance should appear when it becomes true, not sit there greyed. */
  .stepper__go:disabled {
    cursor: default;
    color: inherit;
    opacity: 1;
  }

  .stepper__go:not(:disabled):hover { background: var(--select-bg); }

  .stepper__go:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 1px;
  }

  .stepper__step + .stepper__step::before {
    content: "";
    width: var(--space-4);
    height: 1px;
    background: var(--border-strong);
    margin-right: var(--space-2);
  }

  .stepper__num {
    display: grid;
    place-items: center;
    width: 1.5rem;
    height: 1.5rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-full);
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
  }

  .stepper__step[aria-current="step"] {
    color: var(--text);
    font-weight: 500;
  }

  .stepper__step[aria-current="step"] .stepper__num {
    background: var(--accent);
    color: var(--accent-text);
    border-color: var(--accent);
  }

  /* Done steps take the selection blue, the same pair used for a chosen option
     everywhere else. It reads as "settled, and you can go back to it", which is
     exactly what it is, and it separates cleanly from the solid accent marking
     where you actually are. */
  .stepper__step[data-done] { color: var(--select-fg); }

  .stepper__step[data-done] .stepper__num {
    background: var(--select-bg);
    border-color: var(--select-border);
    color: var(--select-fg);
  }

  @media (max-width: 30rem) {
    .stepper__label { display: none; }
    .stepper__step + .stepper__step::before { width: var(--space-5); }
  }

  /* -- steps --------------------------------------------------------- */
  .step:focus { outline: none; }

  .titlerow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-5);
    margin-bottom: var(--space-3);
  }

  .step__title {
    font-size: var(--text-2xl);
    margin-bottom: var(--space-3);
  }

  /* The title carries the row's bottom margin, so inside the row it drops it. */
  .titlerow .step__title { margin-bottom: 0; }

  /* A promoted question is a full sentence, so it needs the row's width rather
     than hugging beside the switch. */
  .titlerow .step__title { flex: 1 1 20ch; }

  /* Says which question of the three you are on. Quiet on purpose: the bar
     above is the progress that matters, this only explains the split. */
  .substep {
    font-size: var(--text-xs);
    color: var(--text-subtle);
    margin-bottom: var(--space-2);
  }

  /* Two views of the same answers. Not a link: it changes this page rather than
     going anywhere, and showing both options means the current one never has to
     be inferred from the label of the other. */
  .switch {
    display: inline-flex;
    gap: 2px;
    padding: 3px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-sunken);
  }

  .switch__opt {
    border: 1px solid transparent;
    border-radius: var(--radius-xs);
    background: none;
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-base);
    font-weight: 500;
    color: var(--text-muted);
    cursor: pointer;
    transition: background 0.15s var(--ease), color 0.15s var(--ease);
  }

  .switch__opt:hover { color: var(--text); }

  .switch__opt[data-on] {
    background: var(--surface);
    border-color: var(--border);
    color: var(--text);
  }

  @media (max-width: 40rem) {
    .switch__opt { font-size: var(--text-xs); }
  }

  .step__note {
    color: var(--text-muted);
    max-width: var(--measure);
    margin-bottom: var(--space-5);
  }

  .fieldset + .error { margin-top: var(--space-4); }

  /* Pushed to the far right of the actions row, away from Back and Continue.
     Plain text rather than a button: it undoes work, and giving it the weight
     of the control that moves you forward would invite the press it least
     wants. Underlined, because it does something rather than goes somewhere,
     but quiet enough that it is found only when looked for. */
  .reset {
    margin-left: auto;
    border: 0;
    padding: 0;
    background: none;
    font: inherit;
    font-size: var(--text-base);
    color: var(--text-subtle);
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: color-mix(in oklab, var(--text-subtle) 40%, transparent);
    cursor: pointer;
  }

  .reset:hover { color: var(--text); text-decoration-color: currentColor; }

  .reset:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
    border-radius: var(--radius-xs);
  }

  /* Marks a question that is still waiting, after Continue has been pressed and
     refused.

     A tint rather than a ring. The questions butt straight against each other,
     no gap and a hairline rule between them, so an outline had nowhere to sit:
     any offset at all put it through the question above and through the message
     below. A background can only ever fill the row's own padding, so it cannot
     collide with anything.

     It reaches into the gutters by the same amount it is padded, which leaves
     every word exactly where it was and lets the tint read as a panel rather
     than a stripe cut across the column.

     The tint stays after the pulse ends. The pulse says look here, the tint
     says still here, and someone who scrolled away during the animation would
     otherwise come back to no sign of anything. */
  /* The glow goes round the buttons, not round the question.
     Round the whole row it had to be paid for in padding, and padding on a row
     whose width is pinned moved the buttons sideways, so the thing being
     pointed at jumped as it was pointed at. A shadow on the buttons themselves
     costs no layout at all, and it lands on the part that needs pressing rather
     than on the paragraph explaining it. */
  .question--missing .toggle {
    box-shadow: 0 0 0 4px color-mix(in oklab, var(--select-bg) 70%, transparent);
    border-color: var(--select-border);
  }

  @media (prefers-reduced-motion: no-preference) {
    .question--missing .toggle { animation: toggle-waiting 1.2s var(--ease) 3; }
  }

  /* Settles on the resting glow rather than fading to nothing: the pulse says
     look here, what it leaves behind says still here. */
  @keyframes toggle-waiting {
    0%, 100% { box-shadow: 0 0 0 4px color-mix(in oklab, var(--select-bg) 70%, transparent); }
    50% { box-shadow: 0 0 0 10px color-mix(in oklab, var(--select-bg) 0%, transparent); }
  }

  /* -- search ---------------------------------------------------------- */

  /* Deliberately not a dropdown. On a phone, focusing an input raises the
     keyboard over exactly where a dropdown list would appear, so the options
     stay on the page and search filters them in place. */
  /* Capped. A search field spanning 1440px reads as a page-wide banner rather
     than something you type a word into. */
  /* Figma: 24px down from the sub-line, 10px between field and button, and the
     two the same height, hence stretch rather than centre, which left the
     button 7px short of the field. */
  .search {
    position: relative;
    display: flex;
    align-items: stretch;
    gap: var(--space-2);
    margin-top: var(--space-5);
    max-width: 41rem;
  }

  /* The field takes the room; the button takes what it needs. */
  .search__field {
    position: relative;
    display: flex;
    align-items: center;
    flex: 1 1 auto;
    min-width: 0;
  }

  .search__go { flex: none; }

  /* Side by side, the button squeezes the field down to a few characters on a
     phone. Stacked, both get the full width. */
  @media (max-width: 34rem) {
    .search {
      flex-direction: column;
      align-items: stretch;
    }
  }

  .search__icon {
    position: absolute;
    left: var(--space-4);
    color: var(--text-subtle);
    pointer-events: none;
  }

  .search__input {
    width: 100%;
    /* Figma: 16px in to the icon, an 18px icon, then a 12px gap before the
       text. Derived rather than written as 46px so it stays right if any of the
       three change, and it was 64px, which pushed the placeholder well clear
       of its own icon. */
    padding: var(--space-4) var(--space-6) var(--space-4)
             calc(var(--space-4) + 1.125rem + var(--space-3));
    font-size: var(--text-base);
    /* The reset gives inputs `font: inherit`, which drags the body's 1.6 in
       with it and made the field 60 tall against a design that wants 51. Text
       in a single-line input has no business using a reading line-height. */
    line-height: 1.2;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    transition: border-color 0.12s var(--ease), box-shadow 0.12s var(--ease);
  }

  .search__input::placeholder { color: var(--text-subtle); }

  .search__input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 18%, transparent);
  }

  /* The browser's own clear button duplicates ours and is unstyleable. */
  .search__input::-webkit-search-cancel-button { display: none; }

  .search__clear {
    position: absolute;
    right: var(--space-3);
    display: grid;
    place-items: center;
    width: 2rem;
    height: 2rem;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--text-muted);
    cursor: pointer;
  }

  .search__clear:hover { background: var(--bg-sunken); color: var(--text); }

  .search__status {
    margin-top: var(--space-3);
    font-size: var(--text-xs);
    color: var(--text-subtle);
  }

  /* The dead end, given real weight. Someone who searched and found nothing
     needs a next move, not an empty page. */
  .search__status--empty {
    margin-top: var(--space-4);
    padding: var(--space-4) var(--space-5);
    border: 1px solid var(--amber-border);
    border-radius: var(--radius);
    background: var(--amber-bg);
    color: var(--amber-fg);
    font-size: var(--text-base);
    max-width: var(--measure);
  }

  .show-all {
    margin-top: var(--space-4);
    font-size: var(--text-xs);
  }

  /* Gone to look at and gone to reach, but still holding its place. `hidden`
     would take it out of the flow, and it is the last thing in the hero, so
     the block the flag floats in would get shorter and the whole top of the
     page would move the moment somebody typed a letter. */
  .show-all.is-gone {
    visibility: hidden;
    pointer-events: none;
  }

  /* -- hero furniture ------------------------------------------------ */
  /* Figma: Inter Semi Bold 16/25.6, tracking −0.01em, 8px to the headline.
     It was 13px with positive tracking, a small-caps-ish eyebrow rather than
     the standfirst-sized label the design actually uses. */
  .eyebrow {
    font-size: var(--text-base);
    font-weight: 600;
    line-height: 1.6;
    letter-spacing: -0.01em;
    color: var(--accent);
    margin-bottom: var(--space-2);
  }

  /* The break in the headline is a desktop nicety. On a narrow screen it would
     force a line where the text does not want one. */
  @media (max-width: 60rem) {
    .standfirst br { display: none; }
  }

  /* Reads as a link, behaves as a button. It changes what is on screen rather
     than navigating, so it must not be an anchor. */
  .linkish {
    display: inline;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-size: var(--text-base);
    color: var(--text-muted);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
  }

  .linkish:hover { color: var(--text); }

  /* Figma: 8px under the search, indented 16px, Inter Medium 15/24. The indent
     lines up with nothing else on the screen, it may well be incidental in the
     file rather than intended, so it is the one value here worth a second look. */
  .show-all {
    margin-top: var(--space-2);
    padding-left: var(--space-4);
    font-size: var(--text-base);
    font-weight: 500;
    line-height: 1.6;
  }

  .load-more { margin-top: var(--space-4); }

  /* -- the strip ----------------------------------------------------- */
  /* The resting state of the first screen. It says what this covers without
     asking anything, and every pill in it is a way in.

     Runs edge to edge of the 1440 measure by cancelling the shell's padding,
     and clips its own overflow, so the track can be far wider than the page
     without the document ever gaining a horizontal scrollbar. */
  /* One slot, two states, and the space above belongs to the slot rather than
     to whichever state is showing. Carried on the strip alone, swapping to the
     grid took 32px off the top of the page and everything below it jumped on
     the first keystroke. The margin collapses out through `main` either way,
     so the only thing that matters is that it does not change. */
  .step[data-panel="1"] { margin-top: var(--space-4); }

  .marquee {
    margin-inline: calc(var(--shell-pad) * -1);
    overflow: hidden;
    /* Dissolves at both ends instead of stopping dead. A mask rather than a
       painted gradient, so it works on any background it is put on. */
    mask-image: linear-gradient(to right,
      transparent, #000 5rem, #000 calc(100% - 5rem), transparent);
  }

  /* Driven from JavaScript rather than by a CSS animation. The strip has to do
     three things at once, drift on its own, be draggable, and answer a
     trackpad, and a keyframe animation can only do the first. One offset that
     everything writes to keeps them from fighting, and removes the state where
     it could be left paused with nothing to resume it. */
  .marquee__track {
    display: flex;
    width: max-content;
    will-change: transform;
  }

  .marquee__row {
    display: flex;
    gap: var(--space-2);
    padding-right: var(--space-2);
  }

  /* Draggable, and it should look it. */
  .marquee { cursor: grab; }
  .marquee[data-dragging] { cursor: grabbing; }

  /* Mid-drag the pills are something you are pushing, not something you are
     about to press. A text selection dragged out from under the cursor is the
     classic tell of a carousel that was never finished.

     `pointer-events: none` was here too, and it silently broke picking. The
     attribute is set on pointerdown, which happens before the browser hit-tests
     mousedown, so mousedown landed on the strip rather than the pill, mouseup
     landed on the pill, and the click fired at their common ancestor. Nothing
     matched `[data-pick]` and no pill could be chosen. Stopping selection is all
     this needs to do. */
  .marquee[data-dragging] * { user-select: none; }

  /* Named for its block, not `.pill`. That class was already taken by the
     level badges on the result page, which sit later in this file and were
     quietly winning. The look itself comes from the `.option` rule below, which
     this shares; only the things a scrolling row needs are set here. */
  .marquee__pill {
    flex: none;
    font: inherit;
    font-size: var(--text-base);
    color: var(--text);
    white-space: nowrap;
  }

  .marquee__pill:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
  }

  /* -- selectable option rows ---------------------------------------- */
  /* Gains columns as the page widens rather than stretching each row. Two
     very long rows on a 1440 screen is a worse target than four short ones. */
  .options {
    display: grid;
    gap: var(--space-2);
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  }

  /* Tiles arrive on a diagonal from the top left, ranked in JS because the
     column count depends on the width. Everything on one diagonal lands
     together, so the grid reads as a single surface tilting into place rather
     than as a queue of tiles.
     Ten ranks is the cap. Beyond that the wave is long enough that somebody
     reaching for a tile at the bottom would see it move under the cursor, and
     the whole point of the cap is that the grid is settled before a hand can
     get there. Ten ranks lands everything inside 780ms. */
  @media (prefers-reduced-motion: no-preference) {
    .option--enter {
      animation: option-in 320ms var(--ease) backwards;
      animation-delay: calc(var(--i, 0) * 46ms);
    }

    /* The same move played backwards: back down, back out of focus. Quicker
       than the entrance and on a tighter wave, because a list clearing itself
       while you are still typing has to be gone by the next keystroke. */
    .option--leave {
      animation: option-out 200ms var(--ease) forwards;
      animation-delay: calc(var(--i, 0) * 26ms);
      pointer-events: none;
    }
  }

  /* Up, forward, and into focus. The blur is what stops it reading as a plain
     slide: the tile resolves rather than simply moves, which is the difference
     between something arriving and something being pushed. Opacity finishes
     early so the last of the movement happens on a tile you can already read. */
  @keyframes option-out {
    to {
      opacity: 0;
      transform: translate3d(0, 14px, 0) scale(0.965);
      filter: blur(3px);
    }
  }

  @keyframes option-in {
    from {
      opacity: 0;
      transform: translate3d(0, 14px, 0) scale(0.965);
      filter: blur(3px);
    }
    55% {
      opacity: 1;
      filter: blur(0);
    }
    to {
      opacity: 1;
      transform: none;
      filter: blur(0);
    }
  }

  /* The strip shares this rule rather than restating it. They are the same
     object in two places, a tile you can pick, and the moment their looks are
     written out twice they start to drift. */
  .option,
  .marquee__pill {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    cursor: pointer;
    transition: border-color 0.12s var(--ease), background-color 0.12s var(--ease);
  }

  /* Hover turns it blue. It used to shift the border one step of warm grey,
     which on a warm ground is close to no answer at all: you could not tell
     whether the thing under the pointer was going to do anything.
     A lighter blue than a chosen row, so hovering never impersonates having
     picked. Chosen stays the stronger fill. */
  .option:hover,
  .marquee__pill:hover {
    border-color: var(--select-border);
    /* Mixed from the chosen fill rather than from the page accent. The tile is
       white, and the page's soft accent is mixed with the cream ground, so on
       white it landed 1.2:1 from where it started and the fill said nothing.
       This is the same blue a chosen row wears, at just under half strength:
       clearly blue, clearly not chosen. */
    background: color-mix(in oklab, var(--select-bg) 45%, var(--surface));
  }

  .option input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }

  /* No empty box on every row. Twelve unticked circles is furniture, and the
     row already says what it is. A tick appears on the right once a row is
     chosen, a shape, not just a colour, so the choice is still legible to
     someone who can't rely on the blue. */
  .option__mark {
    width: 1.125rem;
    height: 1.125rem;
    flex: none;
    margin-left: auto;
    display: grid;
    place-items: center;
  }

  .option__mark::after {
    content: "";
    width: 0.875rem;
    height: 0.875rem;
    background: var(--select-mark);
    clip-path: polygon(14% 47%, 0 61%, 39% 100%, 100% 22%, 85% 8%, 38% 70%);
    opacity: 0;
    transform: scale(0.8);
    transition: opacity 0.12s var(--ease), transform 0.12s var(--ease);
  }

  .option:has(input:checked) {
    border-color: var(--select-border);
    background: var(--select-bg);
    color: var(--select-fg);
  }

  .option:has(input:checked) .option__mark::after {
    opacity: 1;
    transform: none;
  }

  /* Superseded, the icons carry brand colour now, and the selected-state rule
     lives with the rest of the icon colouring below. Left as a note because the
     reasoning here (the mark taking the row's foreground) was correct while the
     marks were monochrome, and would be correct again if they ever went back. */

  .option:has(input:focus-visible) {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
  }

  /* Each mark in its own brand colour, matching the Figma. Anything without a
     real brand, a podcast, a blog article, has no `--brand` set and falls
     back to the muted ink, so the invented icons never pretend to be logos.

     Worth knowing what this costs: colour elsewhere in this system carries
     meaning (the verdict ladder is the only saturated thing in it), and sixteen
     brand hues on the first screen spend some of that. The marks are what people
     scan for, so it is a fair trade, but it is a trade. */
  .option__icon {
    width: 1.125rem;
    height: 1.125rem;
    flex: none;
    color: var(--brand, var(--text-subtle));
    transition: color 0.12s var(--ease);
  }

  /* Only the unbranded marks shift on hover and selection. A logo that changes
     colour when you point at it stops being that company's logo. */
  .option:hover .option__icon { color: var(--brand, var(--text-muted)); }
  .option:has(input:checked) .option__icon { color: var(--brand, var(--text)); }

  .option__label { line-height: 1.4; }

  /* -- grouped checkboxes -------------------------------------------- */
  .group + .group { margin-top: var(--space-6); }

  .group__title {
    font-size: var(--text-xl);
    font-weight: 600;
    margin-bottom: var(--space-1);
  }

  .group__hint {
    font-size: var(--text-base);
    color: var(--text-muted);
    margin-bottom: var(--space-3);
  }

  /* -- the way out ---------------------------------------------------- */
  /* Carries whatever was typed, so it is never a place we claim to cover. The
     dashed border and the recessed fill say "not one of the real ones" without
     needing a word for it, and it takes you straight on, like every other
     tile, because there is nothing left to fill in. */
  .fallback {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    max-width: 34rem;
    margin-top: var(--space-5);
    padding: var(--space-3) var(--space-4);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius);
    background: var(--bg-sunken);
    font: inherit;
    font-size: var(--text-base);
    color: var(--text);
    text-align: left;
    cursor: pointer;
    transition: border-color 0.12s var(--ease), background-color 0.12s var(--ease);
  }

  .fallback:hover {
    border-color: var(--accent);
    background: var(--surface);
  }

  .fallback:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
  }

  .fallback__icon {
    width: 1.125rem;
    height: 1.125rem;
    flex: none;
    color: var(--text-subtle);
  }

  /* Shown at the foot of the full list rather than as a card: nothing has gone
     wrong, so this is a pointer back to the search, not an option to take. */
  .fallback__hint {
    margin-top: var(--space-5);
    padding-top: var(--space-5);
    border-top: 1px solid var(--border);
    font-size: var(--text-base);
    color: var(--text-muted);
  }

  /* -- conditional questions ----------------------------------------- */
  .question {
    padding-block: var(--space-5);
    border-top: 1px solid var(--border);
    display: grid;
    gap: var(--space-3);
  }

  .question:last-of-type { border-bottom: 1px solid var(--border); }

  @media (min-width: 40rem) {
    .question {
      grid-template-columns: 1fr auto;
      align-items: start;
      gap: var(--space-5);
    }
  }

  .question__text {
    font-weight: 500;
    margin-bottom: var(--space-1);
  }

  .question__help {
    font-size: var(--text-base);
    color: var(--text-muted);
    /* Was 46ch, which wrapped every help to two lines while several hundred
       pixels sat empty to its right. Wide enough to use the column it is in,
       still short of a line you lose your place returning from. */
    max-width: 62ch;
  }

  /* Two columns for the all-at-once view. The rows are short and independent,
     so nothing about them needs the full width, and pairing them takes the
     step from eight rows to four. */
  @media (min-width: 60rem) {
    #conditionals {
      display: flex;
      flex-wrap: wrap;
      /* A full 64, not the 24 this started at. 24 is the same distance as the
         space inside a row between a question and its toggle, so nothing on the
         page said where one column ended: it read as one wide row with an odd
         break in it. The gutter has to be wider than any gap inside a cell for
         the eye to see two stacks. */
      column-gap: var(--space-8);
    }

    #conditionals > .question {
      width: calc(50% - var(--space-8) / 2);
    }

    /* Anything with more than two answers keeps the row to itself: three
       options and a paragraph will not share a half column without the copy
       collapsing into a ribbon.

       But a full-width row must not push its control to the far edge. Left to
       `1fr auto` the toggle landed at x891 while every other toggle on the page
       sat at x463, so the one question that matters most had its answers
       stranded half a screen from the words. The copy column is pinned to the
       width the two-column rows give it, which puts this toggle in the same
       vertical line as the rest. */
    #conditionals > .question:has(.toggle__opt:nth-child(3)) {
      width: 100%;
      grid-template-columns: minmax(0, 23.5rem) auto;
      justify-content: start;
    }
  }

  /* The guided view: one question, promoted into the heading above, so the row
     itself is just the control. */
  .question[data-solo] {
    display: block;
    border: 0;
    padding-block: 0;
    width: 100%;
  }

  /* In the grid the toggle is a grid item and sizes to its contents. Solo, the
     row is a plain block, so the track would stretch the full width and read
     as one long bar rather than two buttons. */
  .question[data-solo] .toggle {
    align-self: start;
    width: max-content;
  }

  /* At body size rather than the 12px the dense grid needs. Here it is the only
     thing on the page to touch, and a small target for no reason is just a
     small target. */
  .question[data-solo] .toggle__opt {
    font-size: var(--text-base);
    padding: var(--space-3) var(--space-7);
    border-radius: 7px;
  }

  .question[data-solo] .toggle {
    padding: 4px;
    border-radius: var(--radius-sm);
  }

  .hint {
    font-size: var(--text-base);
    color: var(--text-subtle);
    margin-top: var(--space-4);
  }

  .toggle {
    display: flex;
    gap: var(--space-1);
    padding: 3px;
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    align-self: start;
  }

  .toggle__opt {
    position: relative;
    padding: var(--space-2) var(--space-5);
    border-radius: var(--radius-xs);
    cursor: pointer;
    font-size: var(--text-xs);
    font-weight: 500;
    color: var(--text-muted);
    transition: background-color 0.12s var(--ease), color 0.12s var(--ease);
  }

  .toggle__opt input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }

  .toggle__opt:hover { color: var(--text); }

  /* Filled, not a pale raised thumb. A white chip on a grey track relied
     entirely on a soft shadow to say "chosen", which at a glance said nothing, 
     and this is the answer the whole verdict turns on. */
  .toggle__opt:has(input:checked) {
    background: var(--select-bg);
    color: var(--select-fg);
    box-shadow: inset 0 0 0 1px var(--select-border);
  }

  .toggle__opt:has(input:focus-visible) {
    outline: 2px solid var(--focus);
    outline-offset: 1px;
  }

  /* -- buttons ------------------------------------------------------- */
  .actions {
    display: flex;
    gap: var(--space-3);
    margin-top: var(--space-6);
    flex-wrap: wrap;
  }

  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-5);
    min-height: 2.75rem;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    font-size: var(--text-base);
    font-weight: 500;
    cursor: pointer;
    background: none;
    transition: background-color 0.12s var(--ease), border-color 0.12s var(--ease),
                color 0.12s var(--ease);
  }

  .btn--primary {
    background: var(--accent);
    color: var(--accent-text);
  }

  .btn--primary:hover { background: var(--accent-hover); }
  .btn--primary:active { background: var(--accent); filter: brightness(0.9); }

  .btn--ghost {
    border-color: var(--border-strong);
    color: var(--text);
  }

  .btn--ghost:hover { background: var(--bg-sunken); }
  .btn--ghost:active { background: var(--border); }

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

  .btn--wide { flex: 1; }

  /* On a phone, twelve content types push Continue several hundred pixels
     below the fold. Pinning the step's actions keeps the primary action
     reachable at every step without shortening the list. Scoped to the steps, 
     the restart button in the result footer has no reason to follow you. */
  @media (max-width: 40rem) {
    .step .actions {
      position: sticky;
      bottom: 0;
      z-index: 5;
      margin-top: var(--space-5);
      padding-block: var(--space-4);
      background: var(--bg);
      border-top: 1px solid var(--border);
    }

    /* Only the primary grows. Letting both flex handed the retreat action half
       the bar, which is the opposite of what pinning it was for. */
    .step .actions .btn--primary { flex: 1; }
    .step .actions .btn--ghost { flex: 0 0 auto; }
  }

  /* -- error and loading states -------------------------------------- */
  .error {
    color: var(--red-fg);
    font-size: var(--text-base);
    font-weight: 500;
  }

  .state {
    padding: var(--space-7) 0;
    color: var(--text-muted);
  }

  .state--error { max-width: var(--measure); }

  .state__title {
    font-size: var(--text-xl);
    color: var(--text);
    margin-bottom: var(--space-3);
  }

  .state__text { margin-bottom: var(--space-4); }

  .state__detail {
    font-family: var(--font-mono);
    font-size: var(--text-base);
    color: var(--text-subtle);
    margin-bottom: var(--space-4);
    word-break: break-word;
  }

  /* -- result -------------------------------------------------------- */
  /* The answer is not a panel inside the page, it is the page. Verdict,
     warnings, actions and reasoning each take a full-width band, so the colour
     of the verdict reaches the edges of the screen the way a masthead does
     rather than sitting in a box the way a notification does. */
  .result { max-width: none; }

  .result:focus { outline: none; }

  /* Break out of the shell this is nested in. The shell centres and pads; the
     bands need the whole viewport, then re-pad and re-cap their own contents so
     the reading column still lines up with the rest of the site. */
  .band,
  .hero {
    margin-inline: calc(50% - 50vw);
    padding-inline: max(var(--shell-pad), calc(50vw - var(--shell) / 2 + var(--shell-pad)));
  }

  .band__inner,
  .hero__inner {
    max-width: calc(var(--shell) - var(--shell-pad) * 2);
    margin-inline: auto;
  }

  .band--notices { padding-block: var(--space-5); }

  /* -- hero ---------------------------------------------------------- */
  .hero {
    background: var(--verdict-bg);
    color: var(--verdict-fg);
    padding-block: var(--space-7) var(--space-6);
  }

  .hero__line {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-5);
    margin-bottom: var(--space-4);
  }

  .hero__eyebrow {
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: color-mix(in oklab, var(--verdict-fg) var(--verdict-meta-mix, 90%), var(--verdict-bg));
  }

  .hero__headline {
    font-size: var(--text-4xl);
    line-height: 1.1;
    max-width: 20ch;
    color: var(--verdict-fg);
  }

  .hero__note {
    margin-top: var(--space-3);
    font-size: var(--text-lg);
    max-width: var(--measure);
    color: var(--verdict-fg);
  }

  @media (max-width: 48rem) {
    .hero__headline { font-size: var(--text-2xl); }
    .hero__note { font-size: var(--text-base); }
  }

  /* -- what was answered --------------------------------------------- */
  /* Only the AI uses can run long: the place is one choice and the context is
     one of three words, but eleven uses can be ticked. So only they shorten,
     and to a count rather than an ellipsis. "+2" says there were three; an
     ellipsis says the string was cut, and on a page whose verdict depends on
     exactly what was ticked that difference matters. */
  .answers {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    font-size: var(--text-base);
  }

  .answers__set {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 6px var(--space-3);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text-muted);
  }

  /* Each part of the pill is the way back to the question it came from, so each
     part answers to the pointer the way everything else pickable does: quiet at
     rest, blue underneath when you point at it. */
  .answers__seg {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 2px var(--space-2);
    margin-inline: -2px;
    border: 0;
    border-radius: var(--radius-xs);
    background: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
    transition: background-color 0.12s var(--ease);
  }

  .answers__seg:hover {
    background: color-mix(in oklab, var(--select-bg) 45%, var(--surface));
  }

  .answers__seg:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 1px;
  }

  .answers__place { color: var(--text); font-weight: 500; }
  .answers__sep { color: var(--border-strong); }
  .answers__more { color: var(--accent); font-weight: 500; }

  /* The count says how many were left out; hovering says which. Focusable as
     well as hoverable, because a keyboard user has the same question and a
     phone has no hover at all. */
  .answers__rest { position: relative; }

  /* The list form: one row per question, its answer set hard right so the
     answers line up and the whole thing can be read down the right edge. */
  .answers__tip--list {
    display: grid;
    gap: var(--space-1);
    text-align: left;
  }

  .answers__row {
    display: flex;
    justify-content: space-between;
    gap: var(--space-4);
  }

  .answers__answer { font-weight: 600; }

  .answers__tip {
    position: absolute;
    top: calc(100% + var(--space-2));
    left: 50%;
    translate: -50% 0;
    z-index: 2;
    min-width: max-content;
    max-width: 22rem;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    background: var(--text);
    color: var(--bg);
    font-size: var(--text-xs);
    line-height: 1.5;
    box-shadow: var(--shadow-raised);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.12s var(--ease), visibility 0.12s;
  }

  /* :focus as well as :focus-visible. focus-visible only matches keyboard
     navigation, so tapping or clicking the count would have opened nothing,
     and a phone has no hover to fall back on. */
  .answers__rest:hover .answers__tip,
  .answers__rest:focus .answers__tip,
  .answers__rest:focus-within .answers__tip {
    opacity: 1;
    visibility: visible;
  }

  .answers__rest:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
    border-radius: var(--radius-xs);
  }

  /* Marks a tab holding a point we have not checked. Without it, closing a tab
     could hide the one caveat on the page. */
  /* An unread mark, riding the corner of the count it belongs to. The ring is
     the page behind it rather than a border, so the dot reads as sitting on
     top of the circle instead of being welded to its edge.
     It wears the Unverified tag's own colour, because that is exactly what it
     is counting. The two move together: change one and change the other. */
  .tab__flag {
    position: absolute;
    top: -1px;
    right: -1px;
    width: 8px;
    height: 8px;
    border-radius: var(--radius-full);
    background: var(--accent-yellow);
    /* Two rings, because the two tab states need different things from it. On
       the selected circle the yellow fill carries the dot at 4.05:1. On a
       closed one it lands on near-white at 1.41:1 and disappears, so the deep
       amber edge holds the shape there instead. Outside both, the page colour,
       which is what makes it sit on the circle rather than in it. */
    box-shadow: 0 0 0 1px var(--amber-fg), 0 0 0 3px var(--bg);
  }

  .answers__icon {
    width: 18px;
    height: 18px;
    flex: none;
    color: var(--brand, currentColor);
  }

  /* -- the ladder ----------------------------------------------------- */
  /* Six rungs because the engine has six levels. Showing all of them is what
     turns a colour into a measurement: you can see this verdict is the top of
     the scale, not merely that it is red. The rung you are on is the verdict's
     own colour at full strength, the rest are that colour at 35%, so "not this
     one" is said by weight rather than by a second hue. */
  .ladder {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: var(--space-2);
    margin-top: var(--space-6);
  }

  .ladder__bar {
    display: block;
    height: 4px;
    border-radius: 2px;
    background: var(--verdict-accent, var(--verdict-fg));
    opacity: 0.35;
  }

  .ladder__label {
    display: block;
    margin-top: var(--space-2);
    font-size: var(--text-xs);
    color: var(--verdict-fg);
    opacity: 0.6;
  }

  .ladder__rung[data-at] .ladder__bar { opacity: 1; }
  .ladder__rung[data-at] .ladder__label { opacity: 1; font-weight: 600; }

  @media (max-width: 48rem) {
    .ladder { grid-template-columns: repeat(3, minmax(0, 1fr)); row-gap: var(--space-4); }
  }

  /* Reads back what they told us, nothing more. The advice underneath comes
     from the rules, so repeating any of it here would just be noise. */
  .result__context {
    margin-top: var(--space-4);
    font-size: var(--text-base);
    color: var(--text-muted);
  }

  /* The verdict is the largest thing on its own screen and carries the one
     lifted surface in the system. It is what the visitor came for. */
  .verdict {
    display: flex;
    gap: var(--space-4);
    padding: var(--space-6) var(--space-5);
    border: 1px solid var(--verdict-border);
    background: var(--verdict-bg);
    border-radius: var(--radius-lg);
    color: var(--verdict-fg);
    box-shadow: var(--shadow-raised);
  }

  .result[data-light="green"] { --verdict-fg: var(--green-fg); --verdict-bg: var(--green-bg); --verdict-border: var(--green-border); --verdict-accent: var(--accent-green); }
  .result[data-light="amber"] { --verdict-fg: var(--amber-fg); --verdict-bg: var(--amber-bg); --verdict-border: var(--amber-border); --verdict-accent: var(--accent-yellow); }
  .result[data-light="red"]   { --verdict-fg: var(--red-fg);   --verdict-bg: var(--red-bg);   --verdict-border: var(--red-border); --verdict-accent: var(--accent-red); }

  /* Nothing matched. Neutral on purpose, a gap in the ruleset must not wear
     the colour of an answer. */
  .result[data-light="unknown"] {
    --verdict-fg: var(--text);
    --verdict-bg: var(--bg-sunken);
    --verdict-border: var(--border-strong);
  }

  .verdict__icon {
    width: 2rem;
    height: 2rem;
    flex: none;
    margin-top: 2px;
  }

  .verdict__headline {
    font-size: var(--text-3xl);
    /* Barely tightened. This is the largest type on the page, where a serif
       shows its drawing, pulling it in the way a sans wants would flatten it. */
    letter-spacing: -0.012em;
    color: var(--verdict-fg);
    margin-bottom: var(--space-2);
  }

  @media (max-width: 30rem) {
    .verdict { padding: var(--space-5) var(--space-4); }
    .verdict__headline { font-size: var(--text-2xl); }
  }

  /* A real step off the ladder hue, not a grey and not an opacity knock-down.
     Opacity took this below 4.5:1 on the green ground; matching the headline
     exactly left it with no rank of its own. Mixing toward the verdict's own
     background keeps the hue and buys the hierarchy. */
  /* Nuance that sits with the verdict without contradicting it. Full-weight
     foreground, because it carries meaning; the confidence line beneath it
     steps back. */
  .verdict__note {
    font-size: var(--text-base);
    color: var(--verdict-fg);
    margin-bottom: var(--space-2);
    max-width: 54ch;
  }


  /* -- the payload: wording to paste --------------------------------- */
  .wording { margin-top: var(--space-6); }

  .wording__title {
    font-size: var(--text-base);
    margin-bottom: var(--space-3);
  }

  .wording__copy {
    flex: none;
    padding-inline: var(--space-4);
  }

  /* -- what to do ----------------------------------------------------- */
  /* Two columns, because both are things you act on: the words to publish and
     where to go when the checker cannot settle it. Together above the
     reasoning rather than buried under it. */
  .band--act {
    background: var(--surface);
    border-block: 1px solid var(--border);
  }

  .act {
    display: grid;
    gap: var(--space-6);
    padding-block: var(--space-6);
  }

  @media (min-width: 60rem) {
    .act {
      grid-template-columns: 1fr 1fr;
      gap: var(--space-8);
    }

    .act__col--next {
      padding-left: var(--space-8);
      border-left: 1px solid var(--border);
    }
  }

  /* Headings, not eyebrows. These two name the halves of the page you act on,
     which is the same job the step headings do, so they are set the same way:
     the display face at 22, in ink, sentence case. Small uppercase sans said
     "label above a field" and these are not labels. */
  .act__title {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: 600;
    color: var(--text);
    margin-bottom: var(--space-3);
  }

  /* The sentence is the button: a value to be moved verbatim into another
     system, not prose, so the whole of it is the target rather than a control
     parked underneath it. Every wording uses this row, the first one and the
     alternatives behind the disclosure alike. */
  .copyable {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    width: 100%;
    text-align: left;
    /* Roomier on the left than on the other three sides. The wording is the
       thing being read, and an even inset put it hard against the rule. */
    padding: var(--space-3) var(--space-3) var(--space-3) var(--space-5);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    font-size: var(--text-base);
  }

  .copyable {
    cursor: pointer;
    transition: background 0.12s var(--ease);
  }

  .copyable__text {
    font-size: var(--text-base);
    line-height: 1.5;
    color: var(--text);
    /* Selecting part of the line still has to be possible: the click stands
       down when there is a selection inside the button. */
    user-select: text;
  }

  /* Hovering anywhere in the row moves the button, because the whole row is
     what you are pressing. */
  .copyable:hover { background: color-mix(in oklab, var(--accent) 6%, var(--surface)); }
  .copyable:hover .wording__copy { background: var(--accent-hover); }

  .copyable:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
    background: color-mix(in oklab, var(--accent) 6%, var(--surface));
  }

  /* Confirmation lands on the thing that was acted on, not elsewhere. The
     button carries it too, so the alternatives, where the button is the only
     target, say the same thing as the row above them.

     One rule for every confirmation on this page: green border, green text,
     and a fill that separates the control from whatever it is sitting on.
     The button inside the row takes the surface, because the row underneath
     it has already gone green and a green button on a green field disappears.

     It used to be a solid --green-fg, which is nearly black. Against the
     outlined confirmation on "Copy a link" it read as a different kind of
     event rather than the same one. */
  .copyable[data-copied] { background: var(--green-bg); border-color: var(--green-border); }

  .copyable[data-copied] .wording__copy {
    background: var(--surface);
    border-color: var(--green-border);
    color: var(--green-fg);
  }

  /* The link button confirms the same way. It had the attribute already and
     nothing to show for it, so only its label changed and the two halves of
     the page disagreed about what "done" looks like. */
  .btn--ghost[data-copied] {
    background: var(--green-bg);
    border-color: var(--green-border);
    color: var(--green-fg);
  }

  @media (max-width: 48rem) {
    .copyable {
      flex-direction: column;
      align-items: stretch;
      gap: var(--space-3);
      padding: var(--space-4);
    }

    .copyable .wording__copy { width: 100%; }
  }

  .act__lede {
    color: var(--text-muted);
    max-width: var(--measure);
  }

  .act__buttons {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-top: var(--space-4);
  }

  .act__note {
    margin-top: var(--space-3);
    font-size: var(--text-base);
    color: var(--text-subtle);
  }

  /* One wording is offered, the rest are behind a disclosure. They are
     alternatives rather than additions: each platform has its own house
     phrasing for the same fact, so three cards of near-identical text was a
     lot of furniture for one decision. */
  .others summary { list-style: none; cursor: pointer; }
  .others summary::-webkit-details-marker { display: none; }

  .others__list {
    margin-top: var(--space-3);
    display: grid;
    gap: var(--space-2);
  }

  /* The alternatives are .copyable rows too, so there is nothing left to style
     here: the gap between them comes from .others__list. */

  /* -- the ruling ----------------------------------------------------- */
  /* Half what it was underneath. The ruling band closes the answer with its
     own padding and `main` adds the same again below it, so two paddings were
     doing one job and left a gap you could park in. Both halve; every other
     page keeps the deeper close, because on those the shell's padding is the
     only thing between the last line and the footer. */
  .band--ruling { padding-block: var(--space-7) var(--space-6); }

  main:has(.result:not([hidden])) { padding-bottom: var(--space-6); }

  .ruling__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
  }

  .ruling__title { font-size: var(--text-2xl); }
  .ruling__count { font-size: var(--text-xs); color: var(--text-subtle); }

  /* Tabbed by where the duty comes from, because whether a thing is the Act or
     a platform's terms changes who can enforce it. The selected tab is marked
     in ink, never in a colour: every colour here already means a severity. */
  /* Wraps rather than scrolls. It used to be an `overflow-x: auto` strip, and
     that had two problems. Setting one axis to auto sets the other to auto as
     well, and the tabs hang 1px below the container so their underline can sit
     on its rule, which left exactly 1px of scrollable height: enough that the
     wheel was captured by the strip instead of scrolling the page.
     The horizontal scroll was not worth keeping either. A tab hidden off the
     right edge of a strip is a tab nobody finds. */
  .tabs {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-6);
    border-bottom: 1px solid var(--border);
    margin-bottom: var(--space-5);
  }

  /* Pushed to the right of the same row: it narrows the table the way the tabs
     do, so it belongs on their line rather than floating above the header. */
  .tabs__only {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-left: auto;
    padding-bottom: var(--space-3);
    font-size: var(--text-base);
    color: var(--text-subtle);
    white-space: nowrap;
    cursor: pointer;
  }

  .tabs__only:hover { color: var(--text); }
  .tabs__only input { accent-color: var(--accent); cursor: pointer; }
  .tabs__only label { cursor: pointer; }

  @media (max-width: 48rem) {
    .tabs__only { margin-left: 0; width: 100%; }
  }

  /* Closes the wording column, set off from the wordings above it by a rule.
     It is a different kind of copying: those go into a post, this goes into a
     note to yourself, and without the line the button reads as one more
     wording. */
  .keep {
    margin-top: var(--space-5);
    padding-top: var(--space-5);
    border-top: 1px solid var(--border);
  }

  .keep__note {
    margin-top: var(--space-3);
    max-width: var(--measure);
    font-size: var(--text-xs);
    color: var(--text-subtle);
  }

  .findings__empty {
    padding: var(--space-5);
    color: var(--text-subtle);
  }

  .tab {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    border: 0;
    background: none;
    padding: var(--space-1) 0 var(--space-3);
    margin-bottom: -1px;
    border-bottom: 2px solid transparent;
    font-family: var(--font-display);
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--text-subtle);
    white-space: nowrap;
    cursor: pointer;
  }

  .tab:hover { color: var(--text); }
  .tab[aria-selected="true"] { color: var(--text); border-bottom-color: var(--text); }

  /* A count is a badge, not a control, so it is deliberately not blue. Blue
     means "press this" everywhere else on the surface, and putting it on a
     number that cannot be pressed on its own would say the wrong thing. Neutral
     while the tab is closed, the page's own ink once it is open, so the circle
     tracks the tab rather than competing with it. */
  .tab__count {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Sized so a two-digit count still sits inside a true circle. Three digits
       would stretch it into a lozenge, which no section will ever reach. */
    min-width: 1.375rem;
    height: 1.375rem;
    padding-inline: 0.25rem;
    border-radius: var(--radius-full);
    background: var(--accent-soft);
    font-family: var(--font-sans);
    font-size: var(--text-xs);
    font-weight: 500;
    line-height: 1;
    /* Full ink rather than the subtle grey the closed label uses. The open and
       closed states are told apart by the fill, not by the text. */
    color: var(--text);
    transition: background 0.12s var(--ease), color 0.12s var(--ease);
  }

  .tab:hover .tab__count { background: color-mix(in oklab, var(--accent) 14%, var(--bg)); }

  .tab[aria-selected="true"] .tab__count {
    background: var(--accent);
    color: var(--accent-text);
  }

  /* Findings as rows: weight, finding, source. That order because it is the
     order the questions arrive in, how much does this matter, what is it, who
     says so. Closed, the whole ruling is a list you can scan. */
  .findings {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    overflow: hidden;
  }

  /* The last column is a fixed width, not `auto`. The header has no chevron, so
     with `auto` its fourth column collapsed to zero, the flexible column
     absorbed the difference, and the SOURCE label sat 18px right of every
     source beneath it. A fixed track makes the two rows agree whether or not
     the cell is filled. */
  /* The weight track is named because three things have to agree on it: the
     header, the rows, and the indent under an open row. Written out three
     times it drifts the first time one of them changes. */
  .findings { --col-weight: 13rem; }

  .findings__head,
  .finding__row {
    display: grid;
    grid-template-columns: var(--col-weight) minmax(0, 1fr) 18rem 18px;
    align-items: center;
    gap: var(--space-5);
    padding: var(--space-3) var(--space-5);
  }

  .findings__head {
    background: var(--bg-sunken);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-subtle);
  }

  .finding + .finding { border-top: 1px solid var(--border); }

  .finding__row {
    list-style: none;
    cursor: pointer;
    padding-block: var(--space-4);
  }

  .finding__row::-webkit-details-marker { display: none; }
  .finding__row:hover { background: var(--accent-soft); }

  .finding__weight { display: inline-flex; gap: var(--space-2); flex-wrap: wrap; }

  .finding__title {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--space-3);
    font-family: var(--font-display);
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--text);
  }

  /* A pill that filters. It has to look identical to a plain one, because it
     is still first and foremost the label saying what this finding is: the
     pointer and the pressed state are what tell you it does anything, and
     neither of them costs the label any clarity. */
  .pill--filter {
    border: 0;
    font: inherit;
    font-size: var(--text-xs);
    font-weight: 500;
    cursor: pointer;
  }

  .pill--filter:hover { box-shadow: 0 0 0 2px var(--bg), 0 0 0 3px var(--tag-bg); }
  .pill--filter[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--text); }
  .pill--filter:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

  /* Unverified now rides with the title, so it must not compete with it. */
  .pill--note { font-weight: 500; }

  .finding__source { font-size: var(--text-xs); color: var(--text-subtle); }

  .finding__chevron {
    width: 18px;
    height: 18px;
    color: var(--text-subtle);
    transition: transform 0.15s var(--ease);
  }

  .finding[open] .finding__chevron { transform: rotate(180deg); }

  /* Indented to the finding column, so the reasoning sits under the thing it
     explains rather than under the weight that labels it. The inset is the
     row's own padding, plus the weight track, plus the gap between them. */
  .finding__body {
    /* The row's own 16 sits above this, so the text lands 28 clear of the
       title rather than 20. At 20 the two read as one block, which is wrong:
       the title is the claim and this is the working behind it. */
    padding: var(--space-3) var(--space-5) var(--space-5)
             calc(var(--space-5) + var(--col-weight) + var(--space-5));
    display: grid;
    gap: var(--space-4);
    justify-items: start;
  }

  .finding__why { color: var(--text-muted); max-width: var(--measure); }

  @media (max-width: 60rem) {
    .findings__head { display: none; }

    .finding__row {
      grid-template-columns: minmax(0, 1fr) auto;
      row-gap: var(--space-2);
    }

    .finding__weight { grid-column: 1 / -1; order: -1; }
    .finding__source { grid-column: 1 / -1; }

    /* No weight column to line up with once the row stacks, so the reasoning
       goes back to the row's own edge. */
    .finding__body { padding-left: var(--space-5); }
  }

  /* -- source link ---------------------------------------------------- */
  /* What sat here was the previous result design: findings as articles, in
     sections, with a head and a foot. The rebuild replaced all of it, and the
     rules stayed behind and kept applying. `.finding { padding-block: 24px }`
     was the white band above and below every hover, because the tint sits on
     the row while the padding sat on the details wrapper around it. The old
     `.finding__title` and `.finding__why` were also still fighting the new
     ones. Only the source link survives the change, so only it is left. */
  .finding__link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    color: var(--text-muted);
    white-space: nowrap;
  }

  .finding__link .icon { width: 0.875rem; height: 0.875rem; }

  /* Level pill. Shape and text carry the meaning; colour reinforces it. */
  /* Filled, one colour per level, from the Accent family. Outlined pills all
     read as the same object in different inks; a filled one reads as a label
     stuck on, which is what a weight is.

     Three of the seven sit below 4.5:1 with white text and are kept that way
     deliberately. Recorded here rather than argued: it is a decision, not an
     oversight. Orange 2.82, red 4.06, blue 2.15. */
  .pill {
    flex: none;
    font-size: var(--text-xs);
    font-weight: 500;
    letter-spacing: 0.01em;
    padding: 3px var(--space-2);
    border-radius: var(--radius-xs);
    border: 0;
    white-space: nowrap;
    background: var(--tag-bg, var(--bg-sunken));
    color: var(--tag-fg, var(--text));
  }

  .pill[data-level="none"]        { --tag-bg: var(--accent-green);  --tag-fg: #ffffff; }
  .pill[data-level="recommended"] { --tag-bg: var(--accent-lilac);  --tag-fg: #000000; }
  .pill[data-level="platform"]    { --tag-bg: var(--accent-purple); --tag-fg: #ffffff; }
  .pill[data-level="act"]         { --tag-bg: var(--accent-orange); --tag-fg: #ffffff; }
  .pill[data-level="legal"]       { --tag-bg: var(--accent-red);    --tag-fg: #ffffff; }
  .pill[data-level="info"]        { --tag-bg: var(--accent-blue);   --tag-fg: #ffffff; }
  .pill[data-level="unverified"]  { --tag-bg: var(--accent-yellow); --tag-fg: #000000; }

  /* This block used to repaint the unverified notice neutral grey, to separate
     it from the draft-ruleset warning. It never did: `.notice` is declared
     below it at the same specificity, so the base rule won every time and both
     have always been amber. Removed rather than moved, because amber is the
     treatment that has actually been shipping and the one that was reviewed.
     If the two ever do need telling apart, it should be by wording, not by a
     colour that says a different thing from the icon beside it. */

  /* -- warnings ------------------------------------------------------ */
  /* Marked with an edge rather than boxed. A tinted fill inside a matching
     border, at a generous radius, is the anatomy of every framework's alert
     component, and a page carrying three of them reads as assembled rather
     than designed. The 4px rule does the same job with one line: it says
     "attend to this" without enclosing it.

     The field keeps its full strength. Quieting it was tempting, but this
     component is how the page admits part of an answer is unchecked, and that
     is not something to whisper. Square against the rule, softened outside,
     so the edge reads as a mark in the margin rather than one wall of a box. */
  .notice {
    display: flex;
    gap: var(--space-3);
    margin-top: var(--space-6);
    padding: var(--space-4) var(--space-5) var(--space-4) var(--space-4);
    border: 0;
    border-left: 4px solid var(--amber-border);
    border-radius: 0 6px 6px 0;
    background: var(--amber-bg);
    color: var(--amber-fg);
    /* Body size, not the small step. This is the page telling you part of your
       answer is unchecked, and 12px asked people to squint at the one paragraph
       that qualifies everything above it. The list of which points underneath
       keeps the small step, so the two read as statement and detail. */
    font-size: var(--text-base);
  }

  .notice .icon { margin-top: 2px; }
  .notice p + p { margin-top: var(--space-2); }

  /* The way out when the tool can't finish the job. Given a real border and
     the accent, because for a red result this is the most useful thing on the
     page, not a footnote. */
  .next {
    margin-top: var(--space-7);
    padding: var(--space-5);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    background: var(--bg-sunken);
  }

  .next__title {
    font-size: var(--text-lg);
    margin-bottom: var(--space-2);
  }

  .next__lede {
    color: var(--text-muted);
    max-width: var(--measure);
    margin-bottom: var(--space-4);
  }

  .next__list {
    display: grid;
    gap: var(--space-4);
  }

  .next__list a {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-weight: 500;
    color: var(--accent);
    text-decoration-color: color-mix(in oklab, var(--accent) 40%, transparent);
  }

  .next__list a .icon { width: 0.875rem; height: 0.875rem; }
  .next__list a:hover { text-decoration-color: currentColor; }

  .next__list span {
    display: block;
    margin-top: var(--space-1);
    font-size: var(--text-base);
    color: var(--text-muted);
    max-width: var(--measure);
  }

  /* The limits list has no link of its own: the sentence is the whole item, so
     it needs neither the indent under a link nor the tighter rhythm that pair
     asks for. */
  .next__list--plain { gap: var(--space-3); }
  .next__list--plain span { margin-top: 0; }

  .next__note {
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border);
    font-size: var(--text-base);
    color: var(--text-subtle);
    max-width: var(--measure);
  }

  .stamp {
    margin-top: var(--space-6);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border);
    font-size: var(--text-xs);
    color: var(--text-subtle);
  }


  /* The serif, at body size. It is the one line in the footer that is a
     statement rather than a signpost, and the display face is what says so
     without making it any bigger. */
  .footer__title {
    font-family: var(--font-display);
    font-size: var(--text-base);
    font-weight: 600;
    color: #ffffff;
    margin-bottom: var(--space-2);
  }

  /* Fine print, and dimmed rather than greyed: a grey text token mixed for the
     cream page has nothing to say on navy. */
  .footer__stamp,
  .footer__col p {
    font-size: var(--text-xs);
    line-height: 1.75;
    color: #ffffff;
    opacity: 0.78;
  }

  .footer__stamp { margin-top: 0; }
}

/* ------------------------------------------------------------ utilities -- */
@layer utilities {
  .visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

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

/* The one authored moment: the verdict arriving. Everything else is a
   12ms state change. */
@media (prefers-reduced-motion: no-preference) {
  .result[data-arrived] > * {
    animation: settle 0.5s var(--ease) backwards;
  }

  .result[data-arrived] > :nth-child(2) { animation-delay: 0.05s; }
  .result[data-arrived] > :nth-child(3) { animation-delay: 0.1s; }
  .result[data-arrived] > :nth-child(n + 4) { animation-delay: 0.14s; }

  @keyframes settle {
    from {
      opacity: 0;
      transform: translateY(8px);
      filter: blur(3px);
    }
  }
}
