/* ROBOTO FLEX — the brand face since 2026-10-01, replacing EuropeExt, which had
   been the reference's face for every heading and button label in the panel.
   Picked off "S585 - Side Panel - Roboto Flex (font study).html".

   It is set the way aquaspirit.boats sets its hero: stretch 114%, GRAD -105,
   XOPQ 100, XTRA 603, YOPQ 84, YTAS 744, YTDE -203, YTFI 738, YTLC 514,
   YTUC 674, opsz 17, and slnt -10 for the italic. THOSE AXES ARE PINNED INTO
   THE FILES with the fontTools instancer, because @font-face cannot carry
   font-variation-settings and a per-element setting would override
   font-weight. Only wght stays live, so 500 / 700 / 800 come out as asked.
   GRAD -105 is why 700 reads lighter than EuropeExt Bold did. That matches the
   site and is not a bug.

   The paths are relative to THIS file, and every document that links it sits
   beside fonts/. font-display:swap draws the first frame in Barlow Semi
   Condensed rather than drawing nothing. The EuropeExt woff2 files are still
   in fonts/ and nothing loads them. */
@font-face{font-family:'AQS Roboto Flex';src:url('fonts/aqs-robotoflex-upright.woff2') format('woff2');
  font-weight:100 1000;font-style:normal;font-display:swap}
@font-face{font-family:'AQS Roboto Flex';src:url('fonts/aqs-robotoflex-italic.woff2') format('woff2');
  font-weight:100 1000;font-style:italic;font-display:swap}

/* ══════════════════════════════════════════════════════════════════════════
   AQUASPIRIT UI — the design system.

   Every control the configurator draws is defined here and nowhere else.
   `design-system.html` and `… - Side Panel (3D).html` both <link> this file,
   which is the whole point: a button changed here is changed in the product
   and on the specimen page in the same edit.

   ─────────────────────────────────────────────────────────────────────────
   THE ONE RULE THAT MAKES IT A SYSTEM

   The token block below is split in two, and the split is load-bearing:

     §1 GEOMETRY   sizes, radii, spacing, type scale, motion.
                   Declared on :root ONLY. No theme may touch these.
     §2 COLOUR     hues, surfaces, ink, lines, shadow weight.
                   Declared on :root and re-declared by [data-theme="light"].

   So a theme CANNOT change the size of anything. If .btn is 40px tall it is
   40px tall in both themes by construction, not by anyone remembering to
   update the second copy — there is no second copy. Raise --btn-h once and
   the white version raises with it.

   THE INVARIANT TO PROTECT: no rule below §2 may know which theme it is in.
   Every component reads tokens; the theme swaps the tokens. If you ever find
   yourself writing `[data-theme="light"] .some-button { … }`, the thing that
   needs fixing is the component — it is reaching for a colour that should
   have been a token. The three brand-art rules at the foot of §3 are the one
   sanctioned exception, and they exist only because a baked-in SVG fill
   cannot read a variable.

   THE ALPHA STAYS AT THE USE SITE, on purpose. .03 and .06 and .10 of the
   same ink are three intensities of one decision, not three colours; a token
   per alpha would be sixty tokens describing one idea. What moves with the
   theme is the hue.
   ══════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════
   §1  GEOMETRY — theme-independent by construction.
       Nothing in this block may be re-declared under [data-theme].
   ═══════════════════════════════════════════════════════════════════════ */
:root{

  /* ── motion ───────────────────────────────────────────────────────── */
  --ease:cubic-bezier(.25,.46,.45,.94);

  /* ── radii ────────────────────────────────────────────────────────── */
  --r-panel:12px;    /* cards, the dock, the tour card */
  /* NOT A PILL ANY MORE (2026-09-07, by request: "make all controls more
     geometric"). It was 100px, and the two button families that used to be a
     deliberate contrast — the top bar's r8 pair against the panel's capsule
     Back/Next — are now one shape. 8 is not a new number: it is what
     --r-ctl already carries and what the top bar was already overriding to,
     so the whole interface lands on one control radius.
     Kept as its own token rather than folded into --r-ctl because the two
     still answer different questions: this one is "what shape is a button",
     that one is "what shape is a box you act on". They agree today.
     Reaches every .btn and .icon-btn, and NOTHING ELSE ANY MORE. It used to
     carry the toggle track and the count badge too, which was a fair grouping
     while all three were the same capsule and a bad one the moment it became a
     number: see --tgl-r for the track and .tab .n for the badge, both of which
     now hold their own shape. */
  --r-btn:8px;
  --r-ctl:8px;       /* the square-ish controls: option row, field, swatch.
                        8 and not 10 since 2026-09-02: the reference's option
                        row is r8, and this token is what draws it. Fields and
                        the grid chip follow it by construction - that is the
                        point of it being one token - so if a later state draws
                        either at 10, they need their own token, not a theme. */

  /* ── THE BUTTON ───────────────────────────────────────────────────────
     Four weights share one shape. Change the shape here and all four move
     together, which is the only reason they read as one family. That shape
     is an r8 rectangle since 2026-09-07 — see --r-btn.

     THE LABEL IS 13px SENTENCE CASE (2026-08-12, by request). It used to be
     8.5px UPPERCASE on .11em, measured back off the vector assets — "START
     GUIDED TOUR" is drawn 111px wide on a 6px cap height, and that is what
     8.5/.11em reproduces. It reproduced the drawing and lost the button:
     8.5px is a third under the 12px floor this file now holds every label
     to, and caps tracked out that far are read letter by letter rather than
     as a word. 13px is --fs-option, which is the point: a button and an
     option row are both things you press, so they take the same size.

     DROPPING THE CAPS ALSO PAYS THE PANEL BACK. Uppercase was supposed to
     belong to .grp-title alone — that is what separates a 13px label from a
     13px option at the same size — and six button variants wearing it was
     the rule's biggest exception. Now nothing but the group title is
     capitalised, so the tracking goes with it: caps need .11em to breathe,
     sentence case does not, and at 13px it would only look loose. */
  --btn-h:40px;         /* the standard button */
  --btn-h-sm:32px;      /* .btn-dark — stage chrome, lighter than the top bar */
  --btn-pad-x:17px;
  --btn-gap:10px;       /* icon to label */
  --btn-ico:16px;
  --btn-ico-rise:-.015em; /* the mark's optical lift — see .btn svg */
  --btn-fs:13px;
  --btn-fs-sm:12px;
  --btn-ls:0;
  --btn-ls-sm:.01em;    /* a hair, because .btn-dark sits a size below */
  --btn-press:1px;      /* the :active nudge — one value for the whole set */

  /* ── THE TARGET FLOOR ─────────────────────────────────────────────────
     Two numbers, and everything below has to clear the first of them.

       --tap-min    24px  WCAG 2.2 SC 2.5.8 Target Size (Minimum), AA.
                          A hard floor. Nothing you can press may be under
                          it in either dimension, ever, in either layout.
       --tap-touch  44px  SC 2.5.5 (AAA), and the same figure Apple's HIG
                          and Material both land on for a fingertip.

     The desktop controls sit BETWEEN the two deliberately — 32 to 40, which
     is where every desktop system draws a pointer target and what the Figma
     draws here. Below 900px they go to --tap-touch, because there the same
     control is a thumb's job. The floor is the part that is not a judgement
     call: if a rule here produces a box under 24, the rule is wrong.

     WHERE THE BOX AND THE DRAWING DISAGREE, EXTEND THE BOX. .feat and
     .opt-info both draw smaller than 24 by design — a 22px switch track and
     a 14px glyph — and both reach the floor by growing the hit area around
     the drawing rather than by redrawing it bigger. */
  --tap-min:24px;
  --tap-touch:44px;

  /* ── ICON BUTTONS ─────────────────────────────────────────────────────
     Four sizes, and each one earns its size from where it sits. The disc
     floats over the model, the square is the quietest control in the set,
     the utility button lives in panel chrome, the nav button steps a
     selection. They are not four attempts at the same button.

     All four clear --tap-min; --sq is the one that only just does. */
  --disc:36px;          /* .icon-round  — stage controls */
  /* 16, AS FIGMA 579:6810 DRAWS IT (2026-09-21, by request: "too bold …
     use exactly this design"). Each glyph sits in a 16px frame at 10,10 in the
     36 button, and Figma draws inside that frame on a 1.333 margin, so what
     lands on screen is a 13.33 mark with 11.3 clear either side. That margin
     IS the design, not slack to be taken up: 2026-09-18 scaled the box to
     19.2 to make the mark a true 16, and next to a full-strength edge it read
     heavy and crowded the box. Do not "correct" it again. */
  --disc-ico:16px;
  /* THE DISC IS NOT A DISC ANY MORE (2026-09-07, same request). The five stage
     controls were border-radius:50%; they are rounded squares on the shared
     control radius now, so they read as the same family as the buttons above
     them rather than as five separate objects floating on the model.
     THE CLASS NAME OUTLIVED THE SHAPE and is left alone on purpose: it is in
     the build, in design-system.html, in the static captures and in the panel
     harness, and renaming it in five places buys nothing a comment does not.
     Its own token rather than --r-ctl directly, so a later pass can soften or
     square these without touching the option row. */
  --disc-r:8px;
  --sq:24px;            /* .icon-square — the dock's collapse control */
  --sq-ico:12px;
  --sq-r:6px;
  --util:34px;          /* .icon-btn    — drawer head, utilities */
  --util-ico:16px;
  --nav:32px;           /* .tabnav      — tab strip, tour card Prev/Next */
  --nav-r:4px;
  /* navchev() draws a 4.25 x 8.5 mark inside a 32px box: the BOX is the
     size, the mark just sits in it. Hence a 32px svg in a 32px button. */
  --nav-ico:32px;

  /* ── SWITCHES ─────────────────────────────────────────────────────────
     .feat .tgl and .opt .toggle are the same switch at two volumes — same
     38x22 track, same 18px knob, same 16px of travel. What differs is the
     hairline and the inset, and that difference is deliberate: the bar's
     switch is one of two controls on its row, the option's is one of many
     rows. Sharing the geometry is what keeps them one control. */
  --tgl-w:38px;
  --tgl-h:22px;
  --tgl-knob:18px;
  --tgl-travel:16px;
  --tgl-inset:2px;      /* .opt .toggle, which has no border */
  --tgl-inset-b:1px;    /* .feat .tgl, whose 1px border eats the difference */
  /* THE SWITCH SQUARED OFF (2026-09-07, same request), AND IT OWNS ITS OWN
     RADIUS NOW. The track used to take --r-btn, which was fine while both were
     capsules and stops being fine the moment that token means a number: 8 on a
     40px button is a fifth of its height and reads as a rectangle, 8 on a 22px
     track is over a third of its height and reads as a lozenge. Same value,
     two different shapes — so the switch is not a place --r-btn should reach.
     Neither figure is new: 6 is --sq-r and 4 is --nav-r and --sw-row-r, which
     is what the rest of the small furniture in here already rounds to.
     CONCENTRIC, AND THAT IS WHAT FIXES THE KNOB TO THE TRACK: the knob sits
     2px inside on both switches (--tgl-inset, or --tgl-inset-b plus its 1px
     border), so the knob's radius is the track's less 2. Move one, move both.
     A round knob in a rounded rectangle reads as a bug rather than a choice,
     which is the whole reason the knob moved at all. */
  --tgl-r:6px;
  --tgl-knob-r:4px;

  /* 18px, from the Figma — 20 left the ring reading as heavy beside a 13px
     name. WHOLE PIXELS ON THE DOT, or it is not centred: the ring is 18 on a
     2px border, so the dot's box is 14, and `inset:2.5px` centres a 9px dot
     arithmetically while rasterising it off-centre — at 1x the two 2.5s round
     opposite ways and the dot lands a pixel up and left. 3px insets on an 8px
     dot are the same circle, half a pixel smaller, on the grid from every
     side. */
  --radio:18px;
  --radio-ring:2px;
  --radio-dot:3px;      /* inset, not diameter */

  /* ── ROWS AND FIELDS ──────────────────────────────────────────────────
     The option row and the text input are the same object: a full-width box
     in the same column that you act on. They share every number here, which
     is why an input six rows under the last option does not read as a
     different kind of control. */
  --row-h:50px;
  /* THE ROW IS 50 TOTAL AND CARRIES A 1px BORDER, so these have 48 to divide,
     not 50: 10 + 28 + 10 + 2 is the 50 the reference draws. Measuring the chip
     at 11 off the row's outer edge and writing 11 here is the mistake to avoid -
     one of those 11s is the border. */
  --row-pad-y:10px;
  --row-pad-x:12px;
  /* AND THE TWO SIDES ARE NOT EQUAL. The reference sets the chip 13 off the
     left edge and the info icon 9 off the right - 12 + border and 8 + border.
     It is not a slip: the icon is a 20px box around an 18x12 glyph, so equal
     padding would leave it looking further in than the chip is. */
  --row-pad-r:8px;
  --row-gap:10px;       /* chip to label: 41 to 51 on the reference's row */
  --row-gutter:12px;    /* between rows in the grid - the reference's gap:
                           its rows sit at y125 and y187 on a 50px row */
  --area-h:104px;       /* the textarea */

  /* ── SECTIONS ─────────────────────────────────────────────────────────
     A TITLE BELONGS TO WHAT IS UNDER IT, AND THE SPACE HAS TO SAY SO.
     (2026-09-14, by request — "it is not obvious where titles belong, to the
     top part or to the bottom, it should be obvious as a rule".) The gap
     above a section title is the gap between two sections; the gap below it
     is the gap between a title and its own content; the first is always the
     larger by well over double, so the title can only be read as the head of
     what follows.

     THESE ARE EDGE-TO-EDGE NUMBERS. The panel's groups had them already —
     32 and 13, off the Figma, from a bordered row's box. The drawer did not:
     its rows carried 11px of padding top AND bottom, and that padding landed
     on both sides of every title, which is what made "Selected options" sit
     halfway between Finish and its own rows. Whatever consumes these must
     start its first line and end its last one flush, or the rule is spent on
     padding before it reaches the eye. */
  --sec-gap:32px;       /* one section's last line to the next title */
  --sec-head-gap:13px;  /* a title to its own first line */

  /* ── SWATCHES ─────────────────────────────────────────────────────────
     The selection ring sits OUTSIDE the chip: -3px on a 13px radius keeps
     it concentric with a 56px/10px chip. If --sw-r moves, --sw-ring-r wants
     to move with it by the same 3. */
  --sw:56px;
  --sw-r:10px;
  /* THE SAME COLOUR AS A ROW rather than as a grid cell - the reference's option
     list puts a 28px chip where the radio sits. Its own pair of tokens and not a
     scale of --sw, because the two are drawn at unrelated sizes and tying them
     would move the grid every time the row moved. */
  --sw-row:28px;
  --sw-row-r:4px;
  --sw-ring:-3px;
  --sw-ring-r:13px;
  --sw-item:78px;       /* the column, wide enough for a two-line caption */
  --sw-gap:16px;

  --badge:16px;         /* the count pill on a tab — 15 until the numeral
                           inside it went from 8px to --fs-badge */
  --tab-drop:9px;       /* how far the tab strip sits below the head's centre,
                           to bring the label down onto its ink line — see .tab */

  /* ══════════════════════════════════ THE PANEL TYPE SCALE ══
     TWO FAMILIES, AND THE SPLIT IS BY ROLE. EuropeExt is the brand face and
     wears every heading and button label; BARLOW SEMI CONDENSED IS THE TEXT
     FACE and wears everything you read rather than press (2026-09-15,
     replacing Roboto Condensed, which had replaced Montserrat on 2026-09-07).
     --font-brand keeps the text face behind EuropeExt in the stack so the
     swap frame is drawn in a sibling rather than in Arial.

     THE SIZES BELOW WERE NOT TOUCHED BY THE SECOND SWAP, and that is measured
     rather than hoped. Picked off the side-by-side in "S585 - Side Panel -
     Barlow (font study).html", which showed it at exactly these numbers. On
     the panel's own copy at 500, Barlow Semi Condensed sets at .98 of Roboto
     Condensed's width, so the September point-bump below still buys what it
     bought. Cap .700 vs .711 is the same; x-height .509 vs .528 is a shade
     lower, which is most of why it reads a touch lighter.

     The families live here and not at each use site for the reason the sizes
     do: a face is geometry, so §2 may not touch it, and a swap is one edit.
     Two literals stay outside CSS on purpose — the canvas that draws the
     screenshot watermark, and the document.fonts.load() gate that holds the
     first screen until the face is live. Both are in the build's JS.

     Four roles, and the only four the options panel is allowed to set type
     in. Measured off the 1920 Figma — update/Group 42002701.svg, whose panel
     is 453 wide — and confirmed against update/layout.png:

       tab       16px  600 selected / 400 not   no tracking   "Hull"
       title     13px  600  UPPERCASE           .04em         "HULL COLOUR"
       option    13px  600  sentence case       no tracking   "Hypalon tube"
       caption   11px  500  sentence case       .02em         "Arctic White"

     THE CAPTION IS 12, AND WAS 11 AND NOT THE FIGMA'S 10 (2026-08-04, by
     request; 12 since the face changed on 2026-09-07). The
     one place the mock was overruled on size, for the reason the mobile top
     bar was overruled on 2026-08-03: 10px of sentence-case Montserrat at 500
     is drawn to be looked at rather than read, and this is the line that
     tells you which colour you are looking at. A point buys real legibility
     and costs only the wrap — "Signature Red" breaks to two lines, which is
     what the reference render shows it doing anyway.

     UPPERCASE BELONGS TO THE TITLE AND NOWHERE ELSE in the panel. It is what
     makes a 13px group label read as a label rather than as another option
     at the same size, and it only works while it is the one thing wearing
     it. (The quote form's field labels take the CAPTION role for this
     reason, which is where the panel parts company with its reference.)

     EVERY SIZE HERE WENT UP A POINT ON 2026-09-07, and the reason is the
     new face rather than a change of mind about the scale. Measured at
     200px, the two faces are the SAME HEIGHT — x-height .535 vs .530 of the
     em, cap .700 vs .710 — and Roboto Condensed sets at .766 of Montserrat's
     WIDTH. Nothing got shorter; a quarter of the ink went missing sideways,
     which is what "it looks smaller" actually is. Matching the old width
     would need 17px where 13 was, and that draws letters half again too
     tall. TWO POINTS is where this landed, by eye, on the real panel: it
     puts the width back to ~89% of Montserrat's and costs ~18% of extra cap
     height. One point (~82% / ~9%) was tried first and still read light in
     the column. Sizes are stated per role, so this was eight numbers and no
     rules.

     THE BRAND ROLES DID NOT MOVE. EuropeExt is the same face it was, so it
     has nothing to compensate for — --btn-fs and the tour card's title and
     numeral are untouched, and the panel head still measures what it did.

     CONTRAST FOLLOWS ROLE, NOT SIZE — see the ink tokens in §2. */
  --font-text:'Barlow Semi Condensed',system-ui,sans-serif;
  /* Roboto Flex since 2026-10-01. The EuropeExt notes above are history. */
  --font-brand:'AQS Roboto Flex','Barlow Semi Condensed',system-ui,sans-serif;
  --fs-tab:18px;
  --fs-tab-on:19px;     /* the active tab, a step up so it wins the row on
                           its own weight rather than on colour alone */
  --fs-title:15px;
  --fs-option:15px;
  --fs-caption:13px;
  --fs-opt-sub:13px;
  /* THE ONE OPTICAL CORRECTION IN THE FILE, AND SINCE 2026-09-09 IT IS ONLY
     A FALLBACK — it applies where `text-box-trim` is missing. The centring
     itself is decided on .opt-name in §9; read that rule first.

     WHAT THE 2026-09-07 VERSION GOT WRONG. It assumed that centring the LINE
     BOX puts the cap band dead centre, and so corrected .045em UP from there
     on the theory that a sentence-case word reads low. Roboto Condensed's
     line box is not symmetric about its letters. Measured on the real row
     (15px on 1.3, in the row's 28px content box): the baseline sits 14.00
     below the line box top and 5.50 above its bottom, and the cap is 10.69
     — so 3.31 of slack over the caps against 5.50 under the baseline.
     Centring the box already put the caps 1.10 HIGH; .045em up made it 1.77.
     That is what came back on 2026-09-09, photographed with the label
     selected: 3.58 of gap above the line box and 4.93 below.

     .073em is the measured distance the other way — cap band centred, which
     is where text-box-trim puts it, so the two paths draw the letters in the
     same place and differ only in the box they leave behind. It is
     (baseline→lineBoxBottom − lineBoxTop→capTop) halved, in em: recompute it
     if the face, --fs-option or that 1.3 changes. Nothing in it is taste.

     RECOMPUTED FOR BARLOW SEMI CONDENSED ON 2026-09-15, AND IT CAME OUT 0.
     Its line box is exactly 1.2em (ascent 1.000, descent .200) with the cap
     at .700, so at 15px on 1.3 the unsnapped cap band sits .75px LOW of the
     box's centre. Chrome floors the baseline to the device pixel, and at 1x
     that throws away exactly the .75: measured on the real row with
     _optalign.html, the cap band is 4.50 from the top and 4.50 from the
     bottom. At 1.25x the floor throws away less and it reads .20 low; at 2x,
     .49 low. That spread is the whole of the fallback's error now, and it
     was already there under Roboto. Same method as .073em, same 1x screen. */
  --lc-optical:0em;
  --ls-caps:.04em;      /* EVERY uppercase label, 4% and no wider (2026-09-14,
                           by request): past that it stops reading as a label
                           and starts reading as a logotype. The phone's .14em
                           group title and the page's .13–.22em kickers all
                           take this one number now. */
  --ls-caption:.02em;

  /* ── THE FLOOR UNDER ALL OF IT ────────────────────────────────────────
     WCAG sets no minimum type size — 1.4.4 asks that text scale to 200%,
     which this does, and stops there. So the floor is a house rule, and it
     is the one every published guideline converges on: 11px is the smallest
     Apple's HIG will draw a label at, 11sp is Material's smallest role,
     and nothing in either is smaller. This file therefore holds
     ANY LABEL A PERSON HAS TO READ AT 12px. It used to allow 11 for
     exactly two things read at a glance rather than actually read —
     --fs-caption, a two-word swatch name sitting under the colour it names,
     and --fs-badge, one digit. THE SWAP TO ROBOTO CONDENSED SPENT BOTH
     EXEMPTIONS (2026-09-07): the point every role gained took those two to
     12 as well, so nothing in the panel is under the floor any more and the
     rule has no exceptions left to defend. Neither was moved FOR that
     reason — it is what a uniform bump happened to buy.

     That rule cost four numbers on 2026-08-12: the button label (8.5),
     .btn-dark (8), the bar switch's label (9) and the count badge (8).
     All four were measured honestly off a reference drawn to be looked at
     rather than read — which is the same trap --fs-caption was pulled out
     of on 2026-08-04, hit three more times.

     --fs-ctl is the fifth role and the youngest: the label on a piece of
     chrome that is not in the options column at all — the bar switch, the
     drawer's own tab strip, the stage hint. It is a size below an option
     because it labels the furniture rather than the goods. */
  --fs-ctl:14px;
  --fs-badge:13px;      /* the numeral in a .tab count pill */
}


/* ═══════════════════════════════════════════════════════════════════════
   §2  COLOUR — the only thing a theme changes.

   Six hues the whole interface is mixed from. Everything below is one of
   these, a wash of one, or a surface from the ramp — so a theme is this
   block and nothing else.

   :root carries the dark set so a page with no data-theme attribute is dark,
   and [data-theme="dark"] restates it so a subtree can be forced back the
   other way. That second selector is what lets design-system.html put both
   themes on screen at once.
   ═══════════════════════════════════════════════════════════════════════ */
:root,
[data-theme="dark"]{
  --ink-rgb:254,242,220;     /* brand warm white — every wash, line and ring */
  --shade-rgb:0,0,0;         /* shadow and the stage vignette */
  --accent-rgb:142,19,41;    /* #8E1329 */
  --accent-2-rgb:168,23,47;  /* #A8172F */
  --glass-rgb:27,27,27;      /* what the frosted panels are frosted FROM */
  --silent-rgb:28,28,28;     /* .btn-silent's wash, and the light chip's edge */
  --pre-ink-rgb:240,240,240; /* the preloader's own neutral, colder than --t1 */
  --scrim-rgb:10,10,10;
  --card-glass-a:30,30,30;   /* tour card, head */
  --card-glass-b:20,20,20;   /* tour card, foot */
  --glass-hover:38,38,38;

  /* HOW HARD THE SHADOWS FALL. The set runs .45–.9 of black, which is right
     over a #222 field and far too heavy over white — so the light theme
     scales all of them from here instead of restating every box-shadow. */
  --shadow-k:1;
  /* The same idea for the big accent radials — the two backdrop glows and
     the preloader's tide. On paper a 55% red bloom is a stain, not an
     atmosphere. */
  --bloom-k:1;

  /* surfaces, darkest first */
  --surf-sunk:#161616;       /* the toast, which sits UNDER the eye level */
  /* THE ⓘ'S TOOLTIP, AND IT NEEDS TWO because the row under it has two states.
     The bubble's whole job is to separate from the row it covers, so it goes
     the way the ramp goes — lighter than the panel here, darker than it in the
     light theme — and the second value is the step it takes again when the row
     beneath is the selected one and already carries an accent wash. */
  --tip:#3A3A3A;
  --tip-on:#474747;
  --surf-deep:#181818;       /* preloader + model select field, drawer foot */
  --bg-deep:#1B1B1B;         /* body */
  --panel:#1C1C1C;           /* dock, technical-data pill */
  --surf-raised:#202020;     /* drawer head */
  --bg:#222222;              /* stage field, before the vignette */
  --surf-hover:#232323;      /* every solid hover in the set */
  --panel-2:#262626;
  --panel-3:#323232;
  --panel-glass:rgba(var(--glass-rgb),.82);   /* the round stage controls */
  /* THE OPTION ROW AT REST, AND IT IS THE ONE SURFACE THE TWO THEMES DISAGREE
     ABOUT (2026-09-09). `update/div.grp.svg` draws it #FEF2DC at 3% — a cream
     LIFT off the dark glass, which is what this is. See the light block for
     why turning that over does not give the light theme's answer. */
  --row-fill:rgba(var(--ink-rgb),.03);

  /* the model card's gradient, top to bottom */
  --card-top:#3A3A3A;
  --card-mid:#2C2C2C;
  --card-foot:var(--surf-hover);

  /* text */
  --t1:#FEF2DC;              /* brand warm white — selection ink, headings */
  --t2:#D0D0D0;              /* button + control label */
  --t3:#B5B5B5;
  --t4:#8B8B8B;
  --ink-strong:#EBEBEB;      /* the tab-strip arrows, on their own wash */
  --on-accent:#fff;          /* ink on an accent fill, and the toggle knob */

  /* lines */
  --line:rgba(var(--ink-rgb),.10);   /* dock border, dock rule */
  --line-2:rgba(var(--ink-rgb),.20);
  --hair:rgba(var(--ink-rgb),.06);   /* the 24px square button */
  /* The phone sheet's scroll thumb (.dock-thumb). Dark keeps the colour every
     native scrollbar here already draws in (§4); the light block takes the
     Figma's own #DBDAD7 off 552:3251. */
  --scroll-thumb:rgba(var(--ink-rgb),.16);
  /* THE STAGE CONTROLS' EDGE AND FIELD (.icon-round), at rest and on hover.
     Dark is what it was before 2026-09-18 — a 12% hairline on the dock's own
     glass. The light block takes Figma 579:6815 instead, and says why. */
  --ring:rgba(var(--ink-rgb),.12);
  --ring-h:var(--line-2);
  --disc-fill:var(--panel-glass);
  --disc-fill-h:rgba(var(--glass-hover),.9);
  --stroke-dark:#373633;             /* technical-data pill */
  --stroke-hover:#4A4945;

  /* controls */
  --track:#3E3E3E;           /* toggle track */
  --track-off:#2C2A28;       /* the warmer one, off */
  --watermark:#373737;       /* the S585 mark set into the stage */

  /* brand. THE RED DOES NOT MOVE BETWEEN THEMES — it is the yard's, not the
     interface's, and a white version of the configurator is still
     Aquaspirit. Five near-identical reds were in the sheet before this
     (#8E1329, #A50E2D, #A90F30, #B21030, #C7172F); they are two now, which
     is what the system always claimed. A sixth, --accent-mob, joined and left
     again — the selected tab below 900px used it for both label and rule;
     now the label is ink and only the rule is red, --accent-2, same as
     ≥900px, so the token had no caller left. */
  --accent:#8E1329;
  --accent-2:#A8172F;
  --glow:0 8px 24px -8px rgba(var(--accent-rgb),calc(.45*var(--shadow-k)));
  --glow-h:0 14px 32px -10px rgba(var(--accent-rgb),calc(.58*var(--shadow-k)));

  /* The switches' "on" fill, and it is ONE VALUE AGAIN — no light-block
     counterpart, by the same rule as --accent three lines up: the red does not
     move between themes. The ochre #C5B297 pass of 2026-08-14 covered both
     themes, then dark only was walked back the same day, and on 2026-08-15 the
     light half went too ("before, the toggle was proper contrast for the white
     theme… make them red"). Which is the reading the ramp supports: #C5B297 on
     the light panel is a sand chip on a cream field, and a switch has to say
     ON at a glance rather than blend into the swatch it sits above.

     Still a literal and not var(--accent), so a future retune of the brand red
     does not silently retune the switches with it. */
  /* THE SWITCH'S ON STATE, read off the reference's own Switch (417:5428) on
     2026-09-02: track #A8172F, its edge #E6E2D9, knob #F0F0EF. The track was
     #8E1329 — the darker of the two brand reds — which is the fill the guided-
     tour BUTTON uses; the switch uses the lighter one, and the two sit side by
     side on the same screen, so they were never meant to be the same value.
     Still literal and not var(--accent-2) even though they agree today, for the
     reason the old comment gave: retuning the brand red should not silently
     retune the switches. One value in both themes, as before. */
  --tgl-on:#A8172F;
  --tgl-on-line:#E6E2D9;
  --tgl-on-knob:#F0F0EF;

  /* The panel footer's Back / Next. A token rather than --t2 because this is
     the one button in the set that sits on the PANEL rather than on the
     stage, and the panel is the lightest field in the light theme. */
  --btn-outline-ink:var(--t2);

  /* the light button — highest-contrast neutral in the set */
  --chip:#D4D2D0;
  --chip-h:#E6E4E2;
  --chip-ink:#373633;
  --chip-line:rgba(var(--silent-rgb),.10);

  /* THE INK ROLES. Contrast follows ROLE, not size — and getting that
     backwards is the bug this replaced. The old panel ran 11px tabs against
     a 10px title tracked out to .26em, and the title won every screen it was
     on: the smallest, widest-set thing in the column was also the loudest,
     so the eye landed on "HULL COLOUR" instead of on "Hull".

     Now the brightest ink goes to the two things you act on — the tab you
     are in and an option you can pick — and everything that only labels them
     steps back. A caption under a swatch repeats what the swatch already
     showed you in colour, so it sits back furthest; SELECTING the swatch is
     what promotes it, because at that point it has stopped being a caption
     and started being the answer. */
  --ink-tab:var(--t1);         /* #FEF2DC — where you are */
  /* THE UNSELECTED TAB IS NOT A CAPTION, and putting it on --t4 alongside one
     was the mistake (fixed 2026-08-15, by request — "almost white in the dark
     theme, almost black in white"). A tab you have not opened yet is still one
     of six places you can GO; a swatch caption only names something already on
     screen in colour. The rule at the top of this block says the brightest ink
     goes to what you act on, and every tab in the strip is that.

     THE SAME INK AS THE SELECTED TAB, at rest, and that is the point rather
     than an oversight. It went --t4 → --ink-strong → here across 2026-08-15:
     --ink-strong (#EBEBEB / #26231E) was still read as too quiet, and the ask
     was "non-selected tabs by default should be the colour of hover" — hover
     being --ink-tab. So the strip is now ONE INK, and the selected tab is
     marked by everything except colour: 17px against 16, weight 600 against
     400, and the red .tab-ink rule under it. That was already the argument for
     giving it its own size on 2026-08-14; this carries it to its conclusion.

     WHICH MEANS COLOUR HAS NOTHING LEFT TO SAY ABOUT A TAB — see .tab:hover,
     which is gone for exactly that reason. If a hover mark is wanted back it
     has to be something other than ink. */
  --ink-tab-off:var(--ink-tab);
  --ink-title:#CDCDCD;         /* a label for a group, one step back */
  --ink-option:var(--t1);      /* #FEF2DC — a thing you can pick */
  --ink-caption:var(--t4);     /* #8B8B8B — a name for a colour you can see */
  --ink-caption-h:var(--t3);   /* under the cursor */
  --ink-caption-on:var(--t1);  /* until it is the one chosen */

  /* .065, up 30% from the .05 measured off the reference (2026-09-08, by
     request — the grid was reading as almost nothing on screen). Opacity
     scales the mark's distance from the field linearly, so x1.3 on this
     number IS +30% contrast; the reference's ten levels become thirteen. */
  --plus:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='58' height='58'%3E%3Cpath d='M29 23.5v11M23.5 29h11' stroke='%23FEF2DC' stroke-opacity='.065' stroke-width='1' stroke-linecap='round'/%3E%3C/svg%3E");

  --brand-ink:#F0F0F0;         /* see §3 — the logo art's own two greys */
  --brand-ink-2:#E5E5E5;

  /* THE TWO FIRST-RUN SCREENS, model select and loading (2026-09-15). Redrawn
     in Figma (AQS-3d configurator, 556:3913 and 556:4024) in the light theme
     only, and by request the dark theme keeps every colour it had — its
     gradients and its red — and takes only the new geometry and icons. So each
     value here is what that screen already painted, and the light block states
     the frame's. The ones holding var() are restated there, as literals. */
  --sel-field:transparent;     /* the shared .backdrop and its glows show through */
  --sel-glow-o:1;              /* the select screen's two red glows */
  --sel-mark:#F0F0F0;
  --sel-lead:var(--t2);
  --sel-intro:var(--t2);
  --mcard-fill:linear-gradient(155deg,var(--card-top) 0%,var(--card-mid) 46%,var(--surf-hover) 100%);
  --mcard-line:rgba(var(--ink-rgb),.10);
  --mcard-line-h:rgba(var(--ink-rgb),.24);
  --mcard-name:var(--t1);
  --mcard-arrow:var(--t2);
  --mcard-arrow-h:var(--t1);
  --mcard-spec:var(--t4);
  --pre-field:#181818;
  --pre-tide-k:1;              /* the two red glows, on top of --bloom-k */
  --pre-mark:#F0F0F0;
  --pre-mark-2:#E5E5E5;        /* the wordmark, a step back, as the art drew it */
  --pre-status:rgba(240,240,240,.88);
  --fr-tile-o:0;               /* the frames' plus tile — a light-theme field */
}


/* ═══════════════════════════════════════════════════ THE WHITE VERSION ══
   Set data-theme="light" on <html> — ?theme=light does it, see the boot code
   in the configurator. Everything here is a token the dark set already
   defines; there is not one new selector, and no rule in this file knows
   which theme it is in. If that stops being true, the thing to fix is the
   rule.

   IT IS WARM, NOT WHITE. The dark build's ink is #FEF2DC — a cream, not a
   grey — and the surfaces under it are warm-black. Inverting to a pure #FFF
   on #111 would drop the warmth and read as a different product wearing the
   same layout, so the ink becomes a warm near-black and the fields a warm
   paper. Sampling the dark set's own hue and swapping lightness is what
   keeps the two recognisably one design.

   WHAT DOES NOT INVERT:
   - The brand red. It is the yard's, and a white configurator is still
     Aquaspirit. Both accents and the mobile tab red are the same in both.
   - The boat. The swatch palette (MODEL), the marker colours and the inline
     logo art are product and brand colour, not theme colour.
   - The scene. Verge3D renders on a transparent canvas over this backdrop,
     so the light field shows through behind the model exactly as the dark
     one did.

   THE ROLES SURVIVE THE FLIP, which is the part worth checking on any
   change. --chip is "the highest-contrast neutral in the set": on dark that
   is a near white, so on light it has to be a near black or the commercial
   action goes quiet. --silent-rgb is a wash of near-black over the STAGE, so
   on light it becomes a wash of near-white for the same reason. Contrast
   follows role.

   NOTE WHAT IS ABSENT: not one size, radius or type value. §1 is untouched
   from here, and that is the guarantee — the white version cannot drift from
   the dark one on geometry because it has no way to say anything about it. */
[data-theme="light"]{
  --ink-rgb:28,26,22;        /* the cream, turned over */
  --shade-rgb:74,68,56;      /* warm, so shadows are not grey holes in paper */
  --glass-rgb:255,255,255;
  --silent-rgb:254,250,244;
  --pre-ink-rgb:38,35,30;
  --scrim-rgb:60,55,46;
  --card-glass-a:255,255,255;
  --card-glass-b:246,244,239;
  --glass-hover:238,235,229;

  /* .22 and not 1: the same geometry, a fifth of the weight. At full
     strength the panel's `0 30px 70px -26px` reads as a bruise on paper. */
  --shadow-k:.22;
  --bloom-k:.30;

  /* surfaces, lightest first — the ramp runs the other way now, and each one
     keeps the JOB its dark counterpart had rather than its lightness. */
  --surf-sunk:#FFFFFF;       /* the toast: a card ON the paper, not a hole in it */
  /* The tooltip, turned over: a grey slab on a white row, and WHITE on the
     selected row — which is the reference's own pairing, and the reason the
     pair exists. A grey bubble on the accent wash reads as the row being
     dimmed; white on it reads as something laid on top. Warm-neutral rather
     than the comp's flat #E4E4E4, because everything else on this panel is. */
  --tip:#E2DFD9;
  --tip-on:#FFFFFF;
  --surf-deep:#F1EEE8;       /* preloader + model select field, drawer foot */
  --bg-deep:#FAF9F8;         /* body — the reference's Background fill. It was
                                #F6F4EF, which is the warmer paper the brand art
                                uses; Actual+++ paints the stage a shade cooler
                                and lighter, and the panel sits on it as white. */
  --panel:#FFFFFF;           /* dock */
  --surf-raised:#FFFFFF;     /* drawer head */
  /* THE LAYOUT'S FIELD, and the only thing --bg paints: one fixed .backdrop
     under everything. #E8E7E5 by measurement on 2026-09-02, replacing the
     #EBE8E1 that was picked to sit "a shade under the panels on it" — the
     right relationship, the wrong hue. This one is neutral where that was
     warm, which is the whole difference: the panels on it are #FFF, and a
     beige field under white panels tints them by contrast. */
  --bg:#E8E7E5;
  --surf-hover:#E4E0D8;
  --panel-2:#EFECE5;
  --panel-3:#E4E0D8;
  --panel-glass:rgba(var(--glass-rgb),.82);
  /* WHITE, AND NOT THE DARK BLOCK'S WASH TURNED OVER (2026-09-09, by request:
     "in design these blocks are white inside"). The 3% is a LIFT — cream over
     dark glass, the row standing slightly proud of the panel. Inverting the ink
     inverts the gesture with it: 3% of #1C1A16 on white paper is a grey wash,
     so the row sank into the panel instead of rising off it, and the column
     read as a stack of grey slabs. The light theme's way of saying the same
     thing is the panel's own white, with the hairline left to draw the edge.
     It is opaque on purpose — the dock is .82 glass, and `transparent` here
     would put the moving boat inside every row. */
  --row-fill:var(--panel);
  --scroll-thumb:#DBDAD7;    /* 552:3251's bar - a literal, so not an alias below */

  --card-top:#FFFFFF;
  --card-mid:#F4F1EB;
  --card-foot:#E9E5DD;

  /* text. Same four steps, same intervals, walked down instead of up. */
  --t1:#1C1A16;
  --t2:#3B3730;
  --t3:#5C564C;
  --t4:#8A8377;
  --ink-strong:#26231E;
  --ink-title:#55504A;
  --on-accent:#fff;          /* unchanged: the ink on a red fill is still white */

  /* ── WHERE A LIGHTNESS FLIP WAS NOT ENOUGH (2026-08-11, by request) ──
     Two inks that the ramp inverted correctly and that still came out too
     quiet, for the same reason in both cases: they sit on the PANEL, which
     is #FFF here and #1C1C1C there, and a mid grey is not symmetrical
     between those two fields. --t4 gives 5.1:1 on the dark panel and only
     3.8:1 on white — the same step down the ramp, most of the contrast gone.

     So these two step further down the ramp on paper than their dark
     counterparts do. Same four values, a different rung — not new colours.

     THE CAPTION STILL HAS THREE STATES and that is the constraint that sets
     the rung. Selecting a swatch is signalled by its caption alone, so
     resting ink cannot arrive at --ink-caption-on or choosing a colour would
     look like nothing happening. t3 → t2 → t1 keeps all three legible with
     even gaps. */
  --ink-caption:var(--t3);     /* #5C564C — 7.3:1, was #8A8377 at 3.8:1 */
  --ink-caption-h:var(--t2);
  --btn-outline-ink:var(--t1); /* the footer pair, at full ink */

  --stroke-dark:#D7D2C8;
  --stroke-hover:#B6AFA1;

  --track:#D7D2C8;
  /* THE GROUND UNDER THE STAGE BAR, AS ONE HEX (2026-09-21, by request: the
     switches and the three buttons were "warmer" than the background, and
     are to be "the same #hex for both mobile and web"). It was #E6E2D9 — red
     13 over blue where the field is 4, and 9 levels lighter than what it sat
     on. #DDDCD9 is --bg (#E8E7E5) under .backdrop's vignette where the bar
     is: measured at 1600x900 it runs #DBD9D7-#DEDCDA from the switches to
     the buttons, and at 390x844 #DDDBD9-#DFDEDB, so this one hex is within
     two levels of the ground on both layouts. Not var(--bg): the vignette
     puts .05-.07 of warm shade over the field down there, and --bg would
     have come out a light chip. Move the bar or the vignette and re-measure. */
  --track-off:#DDDCD9;
  /* THE STAGE CONTROLS, AS FIGMA 579:6815 DRAWS THEM (2026-09-21, by request).
     A 1px #3B3730 edge around a #3B3730 mark, which is --t2 here to the
     character, on a field the colour of the stage under it. Opaque, not the
     82% white glass these had been: that made three light chips stand off
     the row the switches sit IN. The field is the switch's own off track —
     the ground's hex, above — and not Figma's #EFEEEC, because that is
     Actual+++'s stage colour and ours is darker under the vignette.
     Hover moves the ink rather than the field, one step to --t1. Figma draws
     no hover; this is the smallest one that still shows. */
  --ring:var(--t2);
  --ring-h:var(--t1);
  --disc-fill:var(--track-off);
  --disc-fill-h:var(--track-off);
  /* THE MARK IS AN OUTLINE HERE, so this is a LINE colour and not a fill, and
     it steps up rather than down: #DEDAD1 was a fill two shades off the field
     and it read as a smudge. A 1px line on #E8E7E5 is a drawing — visible, and
     still nothing you would mistake for a heading. See .stage-title in the
     build for the stroke itself.

     #B8B8B8 AND NOT #C5C5C5 since later the same day, by eye and then by
     measurement: the ask was "15% more contrast", and of the three things that
     can mean this is the one that means the word. It is the contrast RATIO
     against the field that goes up 15%, 1.41:1 to 1.62:1 — not the 5-level
     gap (which would be #C0C0C0, and invisible as a change) and not the
     colour's own value (#A7A7A7, which is a different mark).

     #A1A1A1 SINCE 2026-09-09, +30% by the same reading: 1.61:1 → 2.09:1.

     THE FIELD DROPS OUT OF THAT SUM, which is why the number is trustworthy
     here. Asking for x1.3 on (F+.05)/(M+.05) is the same as asking (M+.05) to
     shrink by 1/1.3, and F never appears — so the grey that is +30% on the
     token #E8E7E5 is the same grey that is +30% on the corner the mark
     actually occupies. That distinction matters at this end of the ramp:
     .backdrop::after lays up to .20 of --shade-rgb over the field and the
     watermark sits ABOVE that wash, so where the mark actually is — centred
     on the .panelbar row at 1920, measured off the render — the pairing is
     #A1A1A1 on #DCDBD8 and the ratio is 1.87:1, not the 2.09 the two tokens
     alone would suggest. Both went up 30%; only one of them was ever the
     whole picture.

     The dark half is untouched: #373737 on #222222 is 1.34:1, quieter still,
     but it was not what was in front of us. */
  --watermark:#A1A1A1;

  /* --tgl-on IS DELIBERATELY ABSENT. It carried the ochre #C5B297 here until
     2026-08-15; the switches are the yard's red in both themes now, so the one
     declaration in the block above is the whole story. See it for the history —
     this note exists so that a re-declaration is not added back by reflex on
     the grounds that "every colour token has a light half". */

  /* The light button inverts, because its job is "the loudest neutral". */
  --chip:#2C2A28;
  --chip-h:#3B3730;
  --chip-ink:#F6F4EF;

  /* ── THE DERIVED TOKENS HAVE TO BE RESTATED, and this is the subtle one ──
     A custom property whose value contains var() is substituted ON THE ELEMENT
     WHERE IT IS DECLARED, not where it is used. `--line:rgba(var(--ink-rgb),.10)`
     is declared up in the dark block, so it resolves against THAT element's
     --ink-rgb and freezes as a cream wash. Re-declaring --ink-rgb further down
     the tree changes --ink-rgb and nothing else: --line keeps the value it was
     already computed to.

     While the theme sits on <html> — which is what ?theme=light does — the
     dark block and the light block are the SAME element, the substitution
     happens once with the light values, and none of this is visible. The
     moment a theme is scoped to a subtree it breaks in half: the primitives
     flip and everything derived from them does not. That is exactly what
     design-system.html does to put both themes on screen at once, and it is
     how this was found — the option labels came out cream on white paper.

     So every alias below is a token the dark block builds out of another
     token that this block changes. They are not new decisions and not new
     colours: each one is character-for-character what the dark block says,
     re-stated here so it resolves against the light values.

     IF YOU ADD A TOKEN WHOSE VALUE CONTAINS var(), IT BELONGS IN THIS LIST —
     unless everything it references is a literal in both themes. */
  --line:rgba(var(--ink-rgb),.10);
  --line-2:rgba(var(--ink-rgb),.20);
  --hair:rgba(var(--ink-rgb),.06);
  --chip-line:rgba(var(--silent-rgb),.10);
  --glow:0 8px 24px -8px rgba(var(--accent-rgb),calc(.45*var(--shadow-k)));
  --glow-h:0 14px 32px -10px rgba(var(--accent-rgb),calc(.58*var(--shadow-k)));
  --ink-tab:var(--t1);
  --ink-tab-off:var(--ink-tab);      /* #1C1A16 here — almost black, same call */
  --ink-option:var(--t1);
  --ink-caption-on:var(--t1);

  /* The backdrop's plus marks, which are drawn in the ink and therefore have
     to be redrawn: the data URI cannot read a token. Same 58px grid, same
     idea one notch stronger — the ink is the only thing that changed.
     .078 was the dark theme's +30% applied to this theme's own .06, and for
     a day the two fields moved together as one decision. THEY DO NOT ANY
     MORE (2026-09-09, by request: "add 15% more contrast ... but only in
     white mode"). .078 x 1.15 = .0897, written .09; dark stays at .065.
     The split is the request's, and it is a fair one to make on this token
     rather than on both: a near-black mark on paper is the one of the two
     that competes with a bright field, so it is the one that goes on being
     hard to see at a level the dark theme already reads at. */
  --plus:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='58' height='58'%3E%3Cpath d='M29 23.5v11M23.5 29h11' stroke='%231C1A16' stroke-opacity='.09' stroke-width='1' stroke-linecap='round'/%3E%3C/svg%3E");

  --brand-ink:#1C1A16;
  --brand-ink-2:#3B3730;

  /* The first-run screens as the Figma frames draw them — see the dark block
     for why only this theme moved. A flat card with no edge, black ink, and no
     glow on either screen.

     ══ BUT NOT THE FRAMES' FIELD, SINCE 2026-09-16 (by request: "get back
     background to uploading screen and here ... our with crosses pattern") ══
     The flat white under Figma's 5% tile is gone from both screens. What they
     stand on now is the stage's own field — --bg, the 58px --plus grid and
     .backdrop's vignette — so the select screen, the loading screen and the
     configurator behind the boat are one surface, and choosing a model no
     longer steps from white paper onto grey.

     THE SELECT SCREEN GETS IT BY BEING TRANSPARENT, the way the dark theme's
     always has: .backdrop is already under it. The glows that would then show
     through are held at 0 — the frame draws none and the stage has none.

     THE LOADING SCREEN HAS TO PAINT ITS OWN COPY. It is a fixed layer over
     #app, and the select screen is still .on underneath it while the scene
     finishes, so a transparent loader would show the cards through it. These
     three layers are .backdrop's, in .backdrop's order, from the viewport's
     own origin — which is what keeps the crosses on the same pixels when the
     loader fades off the stage. Change .backdrop and change this. */
  --sel-field:transparent;
  --sel-glow-o:0;
  --sel-mark:#000000;
  --sel-lead:#212121;
  --sel-intro:#0D0D0D;
  /* WHITE, NOT THE FRAME'S #D7D7D7 (2026-09-16, by request: "make cards
     white", desktop and phone alike). The frame's grey was drawn against its
     white field; on the stage's #E8E7E5 it came out a shade darker than the
     ground it sits on and the three cards read as holes rather than as things
     you pick. One token, so both layouts take it — neither overrides the fill. */
  --mcard-fill:#FFFFFF;
  --mcard-line:transparent;
  --mcard-line-h:transparent;
  --mcard-name:#000000;
  --mcard-arrow:#000000;
  --mcard-arrow-h:#000000;
  --mcard-spec:rgba(27,27,27,.88);
  --pre-field:radial-gradient(120% 90% at 50% 46%,transparent 0%,
      rgba(var(--shade-rgb),.10) 72%,rgba(var(--shade-rgb),.20) 100%),
    var(--plus) 0 0/58px 58px repeat,var(--bg);
  --pre-tide-k:0;
  --pre-mark:#000000;
  --pre-mark-2:#000000;
  --pre-status:#000000;
  --fr-tile-o:0;               /* the frames' tile would double the crosses */
}


/* ═══════════════════════════════════════════════════════════════════════
   §3  THE ONE PLACE A RULE IS ALLOWED TO KNOW ITS THEME

   THE BRAND ART CANNOT READ A TOKEN. The Aquaspirit lockup on the loading
   and model-select screens, and the model names on the cards, are verbatim
   SVG out of UI-logos/ with their fills baked in — two-tone, #F0F0F0 for the
   mark and #E5E5E5 for its shading. On paper that is an invisible mark on an
   invisible field, which is the one thing a white version cannot ship with.

   Overridden BY THE VALUE THEY ALREADY CARRY rather than by editing 29 paths
   to currentColor: the delivered art stays byte-for-byte what the designer
   handed over, the dark theme is untouched by construction, and re-exporting
   the logo does not silently opt out of the light theme — it keeps working
   as long as the export still uses these two greys. If a future export
   changes them, this is the block that needs the new values, and the symptom
   will be a logo that disappears at ?theme=light.

   The lighter grey maps to the darker ink so the two-tone contrast survives
   the flip in the same direction it was drawn.
   ═══════════════════════════════════════════════════════════════════════ */
[data-theme="light"] [fill="#F0F0F0"]{fill:var(--brand-ink)}
[data-theme="light"] [stroke="#F0F0F0"]{stroke:var(--brand-ink)}
[data-theme="light"] [fill="#E5E5E5"]{fill:var(--brand-ink-2)}


/* ═══════════════════════════════════════════════════════════════════════
   §4  BASE

   Deliberately NOT including `body{}` — the configurator's body is
   overflow:hidden and 100vh, the specimen page scrolls. Each page owns its
   own body; the system owns everything you can press.
   ═══════════════════════════════════════════════════════════════════════ */
*{margin:0;padding:0;box-sizing:border-box}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
img{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
:focus-visible{outline:2px solid var(--accent-2);outline-offset:3px;border-radius:2px}
::selection{background:var(--accent);color:var(--on-accent)}
::-webkit-scrollbar{width:8px;height:8px}
::-webkit-scrollbar-thumb{background:rgba(var(--ink-rgb),.16);border-radius:8px}
::-webkit-scrollbar-track{background:transparent}

/* The group title — "HULL COLOUR", "TUBE MATERIAL". The one uppercase style
   in the panel; see the type scale for why it is the only one. The
   padding-left cancels the trailing letter-space so the T of TUBE lines up
   with the swatch under it rather than sitting a hair right of it. */
.grp-title{display:block;font-size:var(--fs-title);font-weight:600;
  letter-spacing:var(--ls-caps);text-transform:uppercase;
  color:var(--ink-title);line-height:1.2;padding-left:var(--ls-caps)}

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


/* ═══════════════════════════════════════════════════════════════════════
   §5  THE BUTTON

   Five weights, in descending order of how much they ask for:

     .btn-accent   #8E1329 fill, white ink   — the one thing to press
     .btn-light    #D4D2D0 fill, dark ink    — the commercial action
     .btn-silent   10% ink over the backdrop — present, not competing
     .btn-dark     panel fill, hairline edge — chrome that sits on the stage
     .btn-outline  panel-2 fill, no edge     — secondary, inside a panel

   plus one edge-only variant that borrows the shape without a fill:

     .btn-exit     accent hairline           — ending something, not starting it

   All six share the same box from .btn, and that is the entire family
   relationship: --btn-h and friends are declared once in §1, so a size
   change reaches every variant and both themes at the same time.

   THE BOX IS NO LONGER A CAPSULE (2026-09-07, by request). It is r8, the same
   radius the top bar's pair was already overriding to and the same one
   --r-ctl gives the option row — so "two families on one screen" is one
   family now, and the override in the build is redundant rather than
   contradicted. --r-btn carries the note.
   ═══════════════════════════════════════════════════════════════════════ */
/* No text-transform. Every label in the markup is already written the way it
   should read — "Back to site", "Explore features" — so sentence case is
   what you get by deleting a line rather than by adding one, and the labels
   stay legible in the one place CSS cannot reach them: aria-label, the tab
   title, a screen reader. */
/* EUROPEEXT BOLD IS THE FAMILY'S FACE, not the top bar's (2026-09-02). It was
   declared on .tb-c>.btn / .tb-r>.btn alone, so "Explore features" and
   "Get quote" were the brand face at 13/700 while the panel's own Back and
   Next — the same component, one column below — came out Montserrat 600. The
   reference draws every button label in EuropeExt Bold 13 (417:6073, 417:6076
   and 407:2571 all say so), so it belongs to .btn and the top bar's override
   now carries only what is genuinely its own: r8, 20px of padding, a 10px gap. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--btn-gap);
  height:var(--btn-h);padding:0 var(--btn-pad-x);border-radius:var(--r-btn);
  font-family:var(--font-brand);
  font-size:var(--btn-fs);font-weight:700;letter-spacing:var(--btn-ls);
  /* LEADING IS WHAT WAS ACTUALLY MOVING THE LABEL (2026-09-11). Every .btn is
     one nowrap line inside a fixed --btn-h, so the inherited 1.6 buys nothing
     — and it costs. Chrome puts the baseline at FLOOR(half-leading + ascent)
     on the device-pixel grid, so 1.6 at 13px (half-leading 2.9) throws away
     .9px and paints the whole label that much high; 1.6 at 12px, or the same
     page at 1.25x or 2x, throws away a different amount. That is where the
     "the icon sits low" reading came from, and it is also why it looked
     different on different screens. `normal` makes half-leading exactly 0 at
     every size and every ratio, so the label's own box is centred the way
     align-items:center says it is. Measured over 13/12px x 1/1.25/1.5/2/3:
     the mark lands within .13px of the cap band everywhere, against .98px of
     drift before. */
  line-height:normal;
  white-space:nowrap;border:1px solid transparent;
  transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s,transform .2s}
/* THE MARK RIDES ON THE CAP BAND, WHICH IS A HAIR ABOVE THE CENTRE.
   `align-items:center` centres two BOXES, and the two boxes are not the two
   drawings. The icon is symmetric, so its box centre is also its ink centre.
   The label's box is not: it is the font's ascent + descent, and a mixed-case
   word only occupies cap-top to baseline. The room the descender is owed sits
   below the ink whether the word has one or not, so the text rides high in its
   own box by (ascent - descent)/2 - capHeight/2 — which for EuropeExt is
   .3365em - .3516em, or a fifth of a pixel at 13. That is all this line is.

   THE ANCHOR IS THE CAP BAND, and it is measured off the reference rather than
   chosen: in the supplied render of "Start guided tour" (4.125x) the play
   ring's ink spans y 82..136 and the caps span 91..127 — both centred on 109.0
   to the pixel. Not the ink bounding box, which the 'g' drags to 115, and not
   the x-height band, which sits at 114.5. The cap band is also the right anchor
   for a family: it is a font metric, so a label that happens to carry a
   descender does not move its own icon.

   IN EM, because every term in it is the label's. --btn-fs-sm carries .btn-dark
   a size down and the em follows with no second rule. translateY and not a
   margin, because this is a drawing correction and must not change what the row
   measures. The label cannot be the thing that moves — it is a bare text node
   in every button in the project, with nothing to put a rule on — so the mark
   comes to the label.

   TWO WRONG ANSWERS CAME BEFORE THIS ONE, both from measuring the right thing
   and asking the wrong question. +.5px, off an ink CENTROID: a centroid is
   mass-weighted, so the five lowercase letters sitting in the x-height band
   outvoted the caps and pushed the mark the wrong way entirely. Then -.07em,
   off the browser's own numbers at 1x — true there and nowhere else, because
   most of that .98px was the leading artefact the `line-height` above now
   removes rather than anything about the type.

   #btnBack is untouched: it is .brandmark, not .btn, and has no label to be
   aligned to. Every .btn in the project carries a label, so there is no
   icon-only case for this to knock off centre. */
.btn svg{width:var(--btn-ico);height:var(--btn-ico);flex:0 0 auto;
  transform:translateY(var(--btn-ico-rise))}
/* One press gesture for the whole family, stated once. */
.btn:active{transform:translateY(var(--btn-press))}

.btn-accent{background:var(--accent);color:var(--on-accent);box-shadow:var(--glow)}
.btn-accent:hover{background:var(--accent-2);box-shadow:var(--glow-h)}

.btn-light{background:var(--chip);color:var(--chip-ink);border-color:var(--chip-line);
  box-shadow:0 6px 18px -12px rgba(var(--shade-rgb),calc(.9*var(--shadow-k)))}
.btn-light:hover{background:var(--chip-h)}

/* The silent one. A 10% wash of near-black over the backdrop plus a blur —
   it reads as a button when you look for it and as nothing when you do not. */
.btn-silent{background:rgba(var(--silent-rgb),.10);color:var(--t2);
  -webkit-backdrop-filter:blur(9.45px);backdrop-filter:blur(9.45px)}
.btn-silent:hover{background:rgba(var(--silent-rgb),.34);color:var(--t1)}

/* Stage chrome: solid, so it stays legible over whatever the model puts
   behind it. --btn-h-sm rather than --btn-h — it floats over the model and
   should not weigh as much as the top bar does. */
.btn-dark{height:var(--btn-h-sm);padding:0 var(--btn-pad-x);background:var(--panel);
  border-color:var(--stroke-dark);color:var(--t2);
  font-size:var(--btn-fs-sm);letter-spacing:var(--btn-ls-sm)}
.btn-dark:hover{color:var(--t1);border-color:var(--stroke-hover);background:var(--surf-hover)}

/* The tour's exit. It stands in the slot the accent pill was in, so it
   borrows the accent — as a 1px edge on nothing, not as a fill. Filled it
   would go on asking to be pressed as loudly as "Explore features" did,
   when the thing it now wants is to be findable and otherwise stay out of a
   presentation; empty with a red edge it reads as the same control turned
   off. It replaced a .btn-silent grey wash, which said nothing about what it
   was ending. Straight from update/iPhone 13 & 14 - 61.png: 167 x 32 on
   mobile, #8E1329 hairline, no fill, warm-white label, ring-cross before it.
   (The reference sets that label in caps; the whole family stopped doing
   that on 2026-08-12 — see --btn-fs.) */
.btn-exit{background:transparent;border-color:var(--accent);color:var(--t1)}
.btn-exit:hover{background:rgba(var(--accent-rgb),.16);border-color:var(--accent-2)}

/* A soft fill, not a hairline (2026-08-14, by request — the border read as
   too much contrast against the panel). --panel-2 is one step off the panel
   itself in both themes, so the button reads as sitting ON the panel rather
   than floating over it with an edge. --panel-3 is the next step, for hover.

   ITS INK IS A TOKEN AND NOT --t2, because this is the one button in the set
   that sits on the PANEL rather than on the stage, and the panel is the
   lightest field in the light theme. Everything else wearing --t2
   (.btn-dark, .btn-silent, .icon-round) floats over the stage, where --t2
   still lands. See --btn-outline-ink. */
.btn-outline{background:var(--panel-2);color:var(--btn-outline-ink)}
.btn-outline:hover{color:var(--t1);background:var(--panel-3)}

/* Two ways to say "not now", and they are not the same word. :disabled is
   the browser's, .is-dead is ours for the elements that are not form
   controls; both land on the same 35% so the two never read as two states. */
.btn:disabled,
.btn.is-dead,.icon-btn.is-dead,.icon-round.is-dead{opacity:.35;pointer-events:none}


/* ═══════════════════════════════════════════════════════════════════════
   §6  ICON BUTTONS
   ═══════════════════════════════════════════════════════════════════════ */

/* Stage control — the screenshot / default view / hide-controls set. Round
   until 2026-09-07; a rounded square on --disc-r now, which is why the class
   name and the rule no longer agree. The token carries that note.

   THE COLOURS ARE FOUR TOKENS (--ring, --ring-h, --disc-fill, --disc-fill-h)
   because the two themes draw this control differently rather than as one
   ramp turned over: light is Figma 579:6815, dark is what it was before.
   2026-09-18 put both on a full-strength edge and a --t1 mark to match the
   switches beside them; it was rejected on 2026-09-21 as too bold. */
.icon-round{width:var(--disc);height:var(--disc);border-radius:var(--disc-r);flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;color:var(--t2);
  background:var(--disc-fill);border:1px solid var(--ring);
  -webkit-backdrop-filter:blur(3.4px);backdrop-filter:blur(3.4px);
  transition:color .2s,border-color .2s,background .2s}
.icon-round:hover{color:var(--t1);border-color:var(--ring-h);background:var(--disc-fill-h)}
.icon-round svg{width:var(--disc-ico);height:var(--disc-ico)}

/* Square micro-button — the dock's own collapse control, and the only place
   a 24px control appears. Deliberately quieter than everything on the stage. */
.icon-square{width:var(--sq);height:var(--sq);border-radius:var(--sq-r);flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;color:var(--t2);
  background:var(--bg-deep);border:1px solid var(--hair);
  transition:color .2s,border-color .2s,background .2s}
.icon-square:hover{color:var(--t1);border-color:var(--line);background:var(--surf-hover)}
.icon-square svg{width:var(--sq-ico);height:var(--sq-ico)}

/* Utility icon button — drawer head, tour navigation. Not on the stage. */
.icon-btn{width:var(--util);height:var(--util);border-radius:var(--r-btn);
  display:flex;align-items:center;justify-content:center;color:var(--t3);
  border:1px solid transparent;background:none;
  transition:color .2s,border-color .2s,background .2s}
.icon-btn:hover{color:var(--t1);border-color:var(--line);background:rgba(var(--ink-rgb),.05)}
.icon-btn svg{width:var(--util-ico);height:var(--util-ico)}

/* ── THE NAV BUTTON ───────────────────────────────────────────────────────
   The tab strip's two arrows and the tour card's Prev / Next. It is a FILLED
   button and that is the point: for a long time the strip's arrows were a
   bare 13px chevron in #8B8B8B on translucent glass, with no edge and no
   field — at a glance the head read as a row of labels with two smudges
   beside it, and the one control that reaches the tabs you cannot see was
   the least visible thing in the panel.

   Every number is measured, not chosen: 32x32 on a 4px radius, filled with
   the panel's own warm white at 12% over a 4px blur, which lands on #373633
   — the same value the reference draws and the same one --stroke-dark
   already carries. The chevron is the reference's own path, 4.25 x 8.5 on a
   1.2 stroke in #EBEBEB, small inside a field that is not.

   display:none is the BASE state: the strip only scrolls on the wide layout,
   and the configurator turns these on from its own ≥900px block. */
.tabnav{position:relative;display:none;flex:0 0 auto;
  width:var(--nav);height:var(--nav);border-radius:var(--nav-r);
  align-items:center;justify-content:center;border:0;color:var(--ink-strong);
  background:rgba(var(--ink-rgb),.12);
  -webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
  transition:background .2s,color .2s,opacity .2s}
.tabnav svg{width:var(--nav-ico);height:var(--nav-ico);display:block;transition:opacity .2s}
.tabnav:hover:not(:disabled){background:rgba(var(--ink-rgb),.2)}
.tabnav:active:not(:disabled){background:rgba(var(--ink-rgb),.26)}
/* Kept in the layout when it cannot act, so the strip does not shuffle
   sideways on the first and last tab. The field dims with the glyph — a
   filled button that stays at full strength while its arrow greys reads as a
   live control with a broken icon. The dot survives at full opacity because
   it matters most exactly when the arrow is dead — see .has-sel. */
.tabnav:disabled{pointer-events:none;background:rgba(var(--ink-rgb),.05)}
.tabnav:disabled svg{opacity:.3}
/* THE SELECTION IS THAT WAY. Set when scrolling has carried the active tab
   off this end of the strip, so the head never shows a row of labels with
   nothing marked. Same ink as the underline it stands in for. */
.tabnav.has-sel::after{content:'';position:absolute;top:4px;right:4px;
  width:5px;height:5px;border-radius:50%;background:var(--t1);opacity:1}


/* ═══════════════════════════════════════════════════════════════════════
   §7  TABS

   16px — the top of the panel's scale, because the tab is the one thing in
   the head that says where you are. Weight carries the selection and colour
   carries it again; the ink line under it is the third statement, and at
   this size all three are legible at a glance where 11px needed the line to
   do the work.
   ═══════════════════════════════════════════════════════════════════════ */
/* THE LABEL SITS BELOW THE HEAD'S CENTRE, and --tab-drop is how far. Centred,
   a 16px label in a 68px head leaves 22.8px between its baseline box and the
   ink line under it — as much air as there is above it against the panel's own
   top edge — and an underline that far off stops reading as attached to the
   word it marks. The drop closes it by a fifth (2026-08-12, by request)
   without touching the head, so the panel's outer geometry and the 68px tab
   target are both exactly where they were.

   HALF, BECAUSE align-items:center SPLITS IT. The padding takes 9px off the
   top of a stretched box and the line box re-centres in what is left, so the
   text moves 4.5 and the gap goes 22.8 → 18.3. The strip's two chevrons flank
   the labels and read against them, so they have to fall the same 4.5 — that
   is a margin on .tabwrap's arrows and it lives in the build, with the rest of
   where-things-sit. It cannot live on .tabnav: the tour card's Prev/Next are
   the same button in a row that has no tab strip in it.

   --tab-drop is declared in §1. Padding and not margin, so the tab's PRESSABLE
   box is still the full 68. */
.tab{position:relative;flex:0 0 auto;display:inline-flex;align-items:center;align-self:stretch;
  font-size:var(--fs-tab);font-weight:400;color:var(--ink-tab-off);white-space:nowrap;
  padding:var(--tab-drop) 6px 0;
  transition:color .22s}
/* NO :hover RULE HERE, and its absence is a decision (2026-08-15). It was
   `color:var(--t2)` — a step up from a quiet --t4 — until --ink-tab-off was
   raised to the selected tab's own ink, at which point every candidate hover
   colour was a step DOWN in one theme or both, i.e. the label would have dimmed
   under the cursor. There is nothing brighter than --ink-tab to move to, so the
   rule went rather than being left pointing at its own resting value.
   button{cursor:pointer} still answers the pointer. A hover mark, if one is
   wanted, now has to be drawn — a rule, a wash — not tinted. */
.tab.on{color:var(--ink-tab);font-weight:600;font-size:var(--fs-tab-on)}

/* The count badge. Shared by .tab and .dr-tab — same object in two strips. */
/* 100px AND NOT --r-btn, WHICH IT USED TO TAKE. It renders identically today
   — the badge is 16 tall, so --r-btn's 8 is exactly its capsule — and that is
   the trap: the shape would survive this pass and then quietly break the next
   time the button radius moved. A count chip is a capsule because it is a
   capsule, not because the buttons happen to be. Pinned 2026-09-07 with the
   rest of the geometry pass, and it is the one thing in that pass that did
   not change. */
.tab .n,.dr-tab .n{display:inline-block;margin-left:6px;
  min-width:var(--badge);height:var(--badge);padding:0 5px;border-radius:100px;
  background:var(--accent);color:var(--on-accent);font-size:var(--fs-badge);font-weight:700;
  line-height:var(--badge);text-align:center;vertical-align:1px}
.tab .n:empty,.tab .n.zero,
.dr-tab .n:empty,.dr-tab .n.zero{display:none}

/* Selection ink line under the active tab. The configurator positions it;
   the system says what it looks like. --accent-2, not --t1 — the same red
   .dr-tab.on::after marks its own selection with below, so the two strips
   agree. The label itself stays on the ink role (bright, not red): red text
   at 16px reads muddier than the ink it replaces, so the colour carries the
   rule and the ink carries the word. One rule for both breakpoints — mobile
   used to run its own red text + red rule here; see the note where it used
   to sit in the build's mobile block. */
.tab-ink{position:absolute;height:2px;background:var(--accent-2);bottom:0;left:0;width:0;
  transition:transform .35s var(--ease),width .35s var(--ease),opacity .2s}

/* The drawer's own strip — smaller, and it marks the selection with a fixed
   rule rather than a sliding one, because these three never scroll. */
.dr-tabs{display:flex;gap:24px;border-bottom:1px solid var(--line)}
.dr-tab{position:relative;font-size:var(--fs-ctl);font-weight:500;color:var(--t2);
  padding-bottom:13px;transition:color .2s}
.dr-tab:hover{color:var(--t1)}
.dr-tab.on{color:var(--t1);font-weight:600}
.dr-tab.on::after{content:'';position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--accent-2)}


/* ═══════════════════════════════════════════════════════════════════════
   §8  SWITCHES

   .feat is the bar switch: 38 x 22 on a full-strength warm-white hairline
   with an 18px cream knob — brighter furniture than the .opt toggles inside
   the panel, which is right. Those are one of many rows; this is one of two
   controls on its bar.

   BOTH ARE RECTANGLES NOW (2026-09-07, by request). The track is --tgl-r and
   the knob is --tgl-knob-r, which is that radius less the inset so the two
   corners stay concentric. Nothing about the size, the travel or the colour
   moved, and the switch no longer takes its shape from --r-btn — §1 says why.
   ═══════════════════════════════════════════════════════════════════════ */
/* min-height, not height: the track is 22 and the label 14, so the box this
   control offers a pointer was 22 tall — two under the floor, and the only
   AA target-size failure in the set. The switch is still drawn at 22; the
   box around it is 24, which costs a pixel of clearance above and below and
   nothing else, because the bar centres its row anyway. */
.feat{display:inline-flex;align-items:center;gap:10px;flex:0 0 auto;color:var(--t1);
  min-height:var(--tap-min)}
.feat .tgl{position:relative;box-sizing:border-box;
  width:var(--tgl-w);height:var(--tgl-h);border-radius:var(--tgl-r);flex:0 0 auto;
  background:var(--track-off);border:1px solid var(--t1);transition:background .22s}
.feat .tgl::after{content:'';position:absolute;top:var(--tgl-inset-b);left:var(--tgl-inset-b);
  width:var(--tgl-knob);height:var(--tgl-knob);border-radius:var(--tgl-knob-r);background:var(--t1);
  transition:transform .22s var(--ease)}
.feat[aria-checked="true"] .tgl{background:var(--tgl-on);border-color:var(--tgl-on-line)}
.feat[aria-checked="true"] .tgl::after{transform:translateX(var(--tgl-travel));
  background:var(--tgl-on-knob)}
/* --t1 and not the --t2 the rest of the chrome labels use: this one stands
   next to a full-strength #FEF2DC knob, and a grey word beside a cream
   switch reads as a disabled control rather than a quiet one. The Figma sets
   it the same.

   THE SIZE TOOK THREE GOES. The Figma draws it at 8; it went to 9 on
   2026-08-03 on the same reasoning as the mobile top bar, and to --fs-ctl
   (12) on 2026-08-12, which is the first of the three that clears the floor
   in the type-scale block above. It is a switch whose whole state is the
   word beside it — "Hide features" is what tells you which way it is
   pointing — so it is the last label in the interface that should have been
   set three sizes under an option row. */
.feat-lbl{font-size:var(--fs-ctl);font-weight:600;letter-spacing:.02em;
  white-space:nowrap;color:var(--t1)}

/* The option row's switch. Same geometry, no hairline, one step quieter. */
.toggle{position:relative;width:var(--tgl-w);height:var(--tgl-h);border-radius:var(--tgl-r);
  background:var(--track);flex:0 0 auto;transition:background .22s}
.toggle::after{content:'';position:absolute;top:var(--tgl-inset);left:var(--tgl-inset);
  width:var(--tgl-knob);height:var(--tgl-knob);border-radius:var(--tgl-knob-r);
  background:var(--on-accent);transition:transform .22s var(--ease)}
.opt.on .toggle{background:var(--tgl-on);border-color:var(--tgl-on-line)}
.opt.on .toggle::after{background:var(--tgl-on-knob)}
.opt.on .toggle::after{transform:translateX(var(--tgl-travel))}

.radio{position:relative;width:var(--radio);height:var(--radio);border-radius:50%;
  border:var(--radio-ring) solid var(--line-2);flex:0 0 auto;transition:border-color .22s}
.opt.on .radio{border-color:var(--accent-2)}
.opt.on .radio::after{content:'';position:absolute;inset:var(--radio-dot);
  border-radius:50%;background:var(--accent-2)}


/* ═══════════════════════════════════════════════════════════════════════
   §9  THE OPTION ROW

   ONE LINE AND AN INFO GLYPH, which is what the reference draws: 407 x 50 on
   a 10px radius, 18px in to the control, 16px on to the label, and the ⓘ
   hard right at the same 18px inset.

   THE DESCRIPTION MOVED BEHIND THE GLYPH. It used to sit under the name as a
   two-line clamp, and on a 453px column that made a single option row taller
   than the whole swatch group above it — the panel read as a wall of text
   with some controls in it. The full sentence is one press away on the ⓘ
   (and still in the drawer's build summary, unclamped), so nothing has been
   deleted; it has been given a door.
   ═══════════════════════════════════════════════════════════════════════ */
.opts{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--row-gutter)}
.opt{display:flex;align-items:center;gap:var(--row-gap);min-height:var(--row-h);
  padding:var(--row-pad-y) var(--row-pad-r) var(--row-pad-y) var(--row-pad-x);
  text-align:left;width:100%;
  background:var(--row-fill);border:1px solid var(--line);border-radius:var(--r-ctl);
  transition:border-color .2s,background .2s,opacity .2s}
.opt:hover{border-color:var(--line-2);background:rgba(var(--ink-rgb),.06)}
.opt.on{border-color:rgba(var(--accent-2-rgb),.65);background:rgba(var(--accent-rgb),.10)}
.opt-txt{flex:1 1 auto;min-width:0}
/* 500 SINCE 2026-09-08, DOWN FROM 600, by request ("more thinner"). It is the
   one weight in the panel that could afford to come down, and the reason is the
   September face change: Roboto Condensed at 600 sets noticeably heavier than
   Montserrat did at the same number, and every option row in the column wears
   it — so the thing the type scale calls the quiet half of "an option you can
   pick" was reading as the loudest block on the panel.

   WHAT KEEPS THE ROLE INTACT AT 500 is that the scale never rested on weight
   alone: the option row is 15px against the group title's 15 UPPERCASE and the
   caption's 13, and it holds --ink-option (full-strength cream) against the
   title's #CDCDCD. Uppercase still separates the label from the option, ink
   still marks what you act on. Only the mass moved.

   IT IS STILL A STEP ABOVE THE CAPTION, which is also 500 but two sizes down
   and on --t4 — so the two do not collide. 400 was the other candidate and is
   a step too far: at 15px on a translucent panel over a moving render it
   starts to thin out where the boat behind it is light. */
/* "CENTRED" HERE MEANS THE CAP BAND, NOT THE LINE BOX (2026-09-09).
   `text-box:trim-both cap alphabetic` trims the label's box to exactly
   cap-top → baseline, so the row's align-items:center has nothing left to
   centre except the letters. Both of the things reported as wrong fall out
   of it: the drawing is centred (the caps had been sitting 1.77px high), and
   the BOX is centred with it — 8.66 of gap above and 8.67 below, where the
   old rule left 3.58 and 4.93. The second one is what you see if you select
   the label to check it, which is how this was found.

   CAP AND NOT `ex`: the catalogue mixes sentence case with all-caps
   ("Anchor roller" beside "ANCHOR ROLLER XX"), and x-centring drops the
   all-caps rows visibly low — the cap band is the one band every label has.
   It is one word to swap if that judgement changes.

   The @supports arm puts the letters in the same place with a transform, for
   browsers without text-box-trim; its box stays asymmetric, which is the
   part that cannot be had without the trim. See --lc-optical. */
.opt-name{display:block;font-size:var(--fs-option);font-weight:500;color:var(--ink-option);line-height:1.3;
  text-box:trim-both cap alphabetic}
@supports not (text-box-trim:trim-both){
  .opt-name{transform:translateY(var(--lc-optical))}
}
/* NOTHING EMITS THIS ANY MORE — the description moved behind the ⓘ. Kept,
   with --fs-opt-sub, because putting it back is then one line of markup
   rather than a style decision taken twice; delete both if the row is meant
   to stay one line for good. */
.opt-sub{display:block;font-size:var(--fs-opt-sub);color:var(--t4);margin-top:3px;line-height:1.45}
/* Quiet enough to be furniture at rest, bright enough to be worth pressing
   once you have looked for it. It is not a <button> because the row already
   is one and buttons do not nest; the click handler stops there so the ⓘ
   explains the option rather than choosing it. */
/* THE BOX IS THE TARGET NOW, and this used to be a 20px box with an
   ::after{inset:-2px} grown around it to reach --tap-min. Same 24px of target
   either way — but ::after is the tooltip's tail since 2026-09-09, and an
   element has only two pseudos. Nothing moved on screen: --tap-min is 24, the
   glyph is 14 centred in it, and the negative right margin is now -4 instead
   of -2 so the drawing still lands 8px inside the row's padding edge (the box
   grew 4, half of that on the right, and the margin swallows it). Change one
   of those three numbers and you have to change the other two.
   It overlaps the row it sits in, which is allowed and is the point: the row
   is the parent target, and the click handler stops the event here so the
   overlap can never choose the option by accident. */
.opt-info{position:relative;flex:0 0 auto;display:flex;align-items:center;justify-content:center;
  width:var(--tap-min);height:var(--tap-min);margin:-4px -4px -4px 0;
  color:var(--t4);cursor:help;transition:color .2s;
  --tip-face:var(--tip)}
.opt-info svg{width:14px;height:14px}
.opt-info:hover{color:var(--t1)}
/* THE SELECTED ROW'S ⓘ IS RED AT REST — the reference draws it that way, and
   it is the one row where the muted --t4 disappears into an accent wash. Note
   the specificity does the work on hover too: (0,3,0) here beats the (0,2,0)
   of .opt-info:hover above, so the disc a selected row fills with is the
   accent and not the ink, with no fourth rule to say so. */
.opt.on .opt-info{color:var(--accent-2);--tip-face:var(--tip-on)}
/* HOVER TURNS THE RING INTO A DISC. The ⓘ is drawn as a stroked circle with a
   stroked "i" inside it, all of it currentColor — so filling the ring alone
   would swallow the mark. .i-disc takes the fill, .i-stem and .i-tittle are
   repainted in the panel's own colour, and the mark reads as a hole rather
   than as ink. That is why ICONS.info is the one glyph whose parts are named.
   --panel and not white: in the dark theme the disc is cream and the hole has
   to be the dark it was cut from. */
.opt-info:hover .i-disc{fill:currentColor}
.opt-info:hover .i-stem{stroke:var(--panel)}
.opt-info:hover .i-tittle{fill:var(--panel)}

/* ── THE ⓘ'S OWN TOOLTIP (2026-09-09) ─────────────────────────────────────
   It replaces the native title=, which the markup no longer emits. Two
   reasons, and the second is the one that mattered: the UA tooltip takes
   about a second to appear, draws in the OS's font on the OS's chrome, and
   ignores every token in this sheet — so the one place the panel explains
   itself looked like it belonged to a different product. And the ⓘ has been
   cursor:help since it was drawn, which promises a tooltip; this is the panel
   keeping that promise in its own material. The note is still on data-note
   and the click still raises the toast, so nothing was traded away — a touch
   screen has no hover and gets the same words the other way. aria-label on
   the span carries the text into the row's accessible name.

   IT OPENS TO THE LEFT, CENTRED ON THE ROW, AND THAT IS NOT A STYLE CHOICE.
   .dock-body is overflow-y:auto, which computes overflow-x to auto with it,
   so anything that leaves the column horizontally is clipped or grows a
   scrollbar — and the ⓘ sits hard against the column's right padding, with
   nowhere to the right to go. Above would clip on the first row of a step,
   which for Hull is the only row anyone hovers. Left of the icon it opens
   over the empty middle of its own row, where there is never anything but
   the gap between the name and the icon.

   IT IS A SPEECH BUBBLE AND NOT A CARD — flat fill, no border, no shadow, a
   6x10 tail pointing at the icon it came out of. That is the reference, and it
   is also the honest drawing: a shadowed bordered card is a surface that has
   arrived and will stay, and this one is a remark that lasts as long as the
   pointer does. The two halves are ::before (the body) and ::after (the tail),
   which is the whole reason the hit target moved onto the box above.

   THE GEOMETRY IS ONE CHAIN, measured off the comp and anchored to the DISC
   rather than to the box around it. The box is 24 wide and the disc is 11.5
   (r9 plus a 1.7 stroke, scaled 24→14), so `right:100%` already stands 6.25px
   clear of the disc's left edge. From there: the tail's tip at 100% - 2px
   leaves the comp's ~4px of air, its 6px of width puts its base at 100% + 4px,
   and the body's right edge at 100% + 3px laps 1px under it. That 1px is why
   --tip is an opaque colour and not an rgba() wash — two translucent halves
   would double their alpha where they meet and draw a seam down the join. */
.opt-info::before{content:attr(data-note);position:absolute;z-index:5;
  right:calc(100% + 3px);top:50%;
  width:max-content;max-width:210px;padding:5px 10px;border-radius:var(--r-ctl);
  background:var(--tip-face);color:var(--t1);
  font-size:12px;font-weight:500;line-height:1.4;text-align:left;white-space:normal;
  opacity:0;pointer-events:none;
  transform:translateY(-50%) translateX(4px);
  transition:opacity .18s,transform .18s var(--ease)}
/* The tail. 10px tall against the body's 27 and its 8px corners, which leaves
   it exactly the straight part of that edge to grow out of — a taller one
   would break the silhouette at the corners. */
.opt-info::after{content:'';position:absolute;z-index:5;
  right:calc(100% - 2px);top:50%;
  width:0;height:0;border:5px solid transparent;border-right:0;
  border-left:6px solid var(--tip-face);
  opacity:0;pointer-events:none;
  transform:translateY(-50%) translateX(4px);
  transition:opacity .18s,transform .18s var(--ease)}
/* :focus-visible too — the ⓘ is reachable by keyboard through the row, and a
   tooltip only a mouse can open is half a control. */
/* [data-note] AND NOT A BARE .opt-info: an ⓘ with nothing to say must not open
   an empty pill. content:attr() on a missing attribute is the empty string, not
   nothing — it still lays out the padding and paints the fill — and there is at
   least one ⓘ in the set that carries no note (design-system.html draws the row
   as a specimen). The gate belongs on the attribute rather than on the caller. */
.opt-info[data-note]:hover::before,.opt-info[data-note]:hover::after,
.opt:focus-visible .opt-info[data-note]::before,.opt:focus-visible .opt-info[data-note]::after{
  opacity:1;transform:translateY(-50%) translateX(0)}
/* Hover on a touch screen is a sticky lie — the bubble opens on tap and stays
   until you press something else, on top of the toast that just answered the
   same question. The toast is the phone's tooltip. */
   The disc fill goes with it: a filled ⓘ is what "you are pointing at this"
   looks like, and a finger is not pointing at anything. */
@media (hover:none){
  .opt-info::before,.opt-info::after{content:none}
  .opt-info:hover .i-disc{fill:none}
  .opt-info:hover .i-stem{stroke:currentColor}
  .opt-info:hover .i-tittle{fill:currentColor}
}

/* ══ AND ON A PHONE THE SAME NOTE ARRIVES BY HOLDING THE ROW (2026-09-16) ══
   By request, with the two-column colour grid: "info icon drops but it will
   show info tooltip by hold tap".

   THE ⓘ DOES NOT LEAVE THE DOCUMENT DOWN THERE, it leaves the LAYOUT — the
   narrow block takes it out of flow and clips it to a pixel rather than setting
   display:none, because its aria-label is the only thing that says the note out
   loud and a control that exists for screen readers is not one to delete when
   the drawing of it stops fitting. What it gives back is 32px of row, which at
   two colour cells to a line is the difference between "Anthracite grey" and
   "Anthracite gr…".

   ONE ELEMENT AT BODY LEVEL, NOT A PSEUDO ON THE ROW, for the two reasons
   .step-menu is out there: .dock is overflow:hidden, so a pill that stands
   above the row is cut off by the sheet's own edge; and .dock/.docks both take
   transforms, which would turn a position:fixed pill into one fixed to the
   panel and drag it with the sheet. JS writes left/top and --tip-x; everything
   that makes it look like the ⓘ's own tooltip is here.

   Same face, same 12px/1.4, same radius and the same 10px tail as
   .opt-info::before — it is the same object answering the same question, and a
   second look for it would read as a different kind of message. The tail points
   DOWN rather than right because the pill stands over the row rather than
   beside it, and --tip-x is where the row's centre ended up after the pill was
   clamped to the panel's edges: a pill that had to slide sideways to fit still
   points at the thing it is about.

   --tip AND NEVER --tip-on, WHICH IS THE ONE PLACE THIS PARTS COMPANY WITH THE
   ⓘ'S PILL. Up there the accent face is right because the ⓘ sits INSIDE a
   selected row, on its wash, where the light theme's #FFFFFF reads as a raised
   chip. Down here the pill stands clear of the row and over the panel, and a
   white pill on a white panel is a pill nobody can see — which is exactly how
   it came out the first time it was drawn. The panel's own tip face is the one
   that works against the panel.

   AND IT CASTS A SHADOW, for the reason .step-menu does and with the same
   numbers scaled down: both are body-level boxes that float over the panel's
   own rows rather than sitting in them, and without an edge the pill reads as
   part of whatever row it happens to be covering. */
.opt-tip{position:fixed;z-index:40;
  width:max-content;max-width:210px;padding:5px 10px;border-radius:var(--r-ctl);
  background:var(--tip);color:var(--t1);
  box-shadow:0 8px 20px -8px rgba(var(--shade-rgb),calc(.9*var(--shadow-k)));
  font-size:12px;font-weight:500;line-height:1.4;text-align:left;white-space:normal;
  opacity:0;pointer-events:none;
  transform:translateY(4px);
  transition:opacity .18s,transform .18s var(--ease)}
.opt-tip::after{content:'';position:absolute;z-index:40;
  top:calc(100% - 2px);left:var(--tip-x,50%);
  width:0;height:0;border:5px solid transparent;border-bottom:0;
  border-top:6px solid var(--tip);
  transform:translateX(-50%)}
.opt-tip.show{opacity:1;transform:translateY(0)}

.opt.locked{opacity:.4;pointer-events:none}

/* The standard-fit note that closes some groups. Dashed, because it is the
   one box in the panel you cannot act on. */
.std-note{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;border-radius:var(--r-ctl);
  border:1px dashed var(--line);color:var(--t4);font-size:12px;line-height:1.6}
.std-note svg{width:15px;height:15px;flex:0 0 auto;margin-top:2px;color:var(--t3)}
.std-note button{color:var(--t1);text-decoration:underline;text-underline-offset:3px;font-size:12px}


/* ═══════════════════════════════════════════════════════════════════════
   §10  SWATCHES
   ═══════════════════════════════════════════════════════════════════════ */
.swatches{display:flex;gap:var(--sw-gap);flex-wrap:wrap}
.sw-item{position:relative;display:flex;flex-direction:column;align-items:center;
  gap:9px;width:var(--sw-item)}
.sw{width:var(--sw);height:var(--sw);border-radius:var(--sw-r);position:relative;
  border:1px solid rgba(var(--ink-rgb),.22);transition:transform .22s var(--ease)}
/* The selection ring: 62 x 62 on a 13px radius around a 56px swatch, i.e. 3px
   of air and a 2px line, per the Figma. It used to sit 5px out on a 14px
   radius, which left the ring visibly rounder than the chip inside it. It sits
   OUTSIDE the chip rather than on it, so choosing a colour never changes the
   colour's own edge. */
.sw::after{content:'';position:absolute;inset:var(--sw-ring);border-radius:var(--sw-ring-r);
  border:2px solid transparent;transition:border-color .22s}
.sw-item:hover .sw{transform:scale(1.05)}
.sw-item.on .sw::after{border-color:var(--t1)}
/* The bottom of the scale, and deliberately the quietest thing in the group:
   the swatch above it has already said what the colour is. */
.sw-name{font-size:var(--fs-caption);font-weight:500;letter-spacing:var(--ls-caption);
  color:var(--ink-caption);text-align:center;line-height:1.35;transition:color .22s}
.sw-item:hover .sw-name{color:var(--ink-caption-h)}
.sw-item.on .sw-name{color:var(--ink-caption-on)}

/* A COLOUR AS A ROW. The reference stopped drawing colour groups as a grid of
   chips with the name underneath and draws them as option rows with the chip
   where the radio would be - the same box, border, ink and info icon as every
   other choice in the panel, so a list of colours and a list of options read as
   one list rather than as two kinds of furniture.
   .swatches above is what it replaced, and is still what the phone layout uses:
   the narrow block was pinned to its own numbers and was not part of this pass.
   The ring is off because the ROW carries the selection now - .opt.on draws it -
   and two rings on one choice read as a mistake. */
.opt-sw .sw{width:var(--sw-row);height:var(--sw-row);border-radius:var(--sw-row-r);flex:0 0 auto}
/* THE ABSENCE OF A COLOUR, drawn rather than left blank: an empty chip with a
   hairline corner-to-corner. The gradient is the stroke — one line at 45deg,
   sized so it meets both corners whatever --sw-row becomes. Without it "None"
   is a white square, which in a list of colours reads as white. */
.opt-sw .sw-none{background:var(--panel);
  background-image:linear-gradient(45deg,transparent calc(50% - .5px),
    rgba(var(--ink-rgb),.42) calc(50% - .5px),rgba(var(--ink-rgb),.42) calc(50% + .5px),
    transparent calc(50% + .5px))}
.opt-sw .sw::after{content:none}
.opt-sw:hover .sw{transform:none}


/* ═══════════════════════════════════════════════════════════════════════
   §11  FIELDS

   The inputs borrow .opt's field exactly — same 3% wash, same --line
   hairline, same --r-ctl radius, same --row-h minimum — because they are the
   same object in the same column: a full-width box you act on. An input
   drawn any other way would read as a different kind of control six rows
   under the last option row.

   THE COLUMN IS THE DRAWER'S NOW, not the panel's. The quotation form was the
   last tab until 2026-08-12 and is a drawer view since; at 460px against 453
   nothing here had to change, which is the argument for the borrowing rather
   than a coincidence — both are the one-thing-wide column this system draws.

   THE LABELS ARE NOT UPPERCASE, which is where this parts company with the
   reference. Uppercase in this panel means "group title", and the type scale
   only works while nothing else wears it — a column of uppercase field
   labels under an uppercase kicker makes the kicker stop reading as one.

   They took --fs-caption for that until 2026-08-12 and now take --fs-ctl.
   The case was the point, not the size, and 11px was the wrong size to
   borrow along with it: a swatch caption is glanced at under a colour that
   has already said most of it, whereas "Email" and "we could not read that
   address" are the two things in the panel a person has to read carefully
   while typing. Same reasoning for .field-bad, which is an error message.
   ═══════════════════════════════════════════════════════════════════════ */
/* --fs-ctl and not --fs-opt-sub: this is the only paragraph of running prose
   in the panel, and it carries the one link that says the build can still be
   checked before the form is sent. 11px was the size of a swatch caption. */
/* --t1 ON THE LEAD AND THE LABELS (2026-09-14, by request — "small texts should
   be more contrast", with the build summary's value ink as the colour to use).
   They were --t4, the placeholder's ink, which put "Name" and "e.g. Jane Smith"
   on the same grey: a label read as a hint and a hint as a label. The
   placeholder keeps --t4 — it is the one piece of text here that is meant to
   look like it is not there. The link keeps its underline, which is what marks
   it now that its colour matches the sentence around it. */
.form-lead{font-size:var(--fs-ctl);line-height:1.6;color:var(--t1);margin:-4px 0 20px}
.form-lead button{color:var(--t1);text-decoration:underline;text-underline-offset:2px}
.field{margin-bottom:14px}
.field-label{display:block;font-size:var(--fs-ctl);font-weight:500;
  letter-spacing:var(--ls-caption);color:var(--t1);margin-bottom:7px}
.field-label i{font-style:normal;color:var(--accent-2)}
/* --row-fill AND NOT ITS OWN WASH (2026-09-09): a field is the option row's
   shape by construction — same --row-h, same --row-pad, same hairline, same
   radius — so it has to be the same surface too, or the light theme's white
   rows sit above a form of grey slabs. It followed the row here by copy; now
   it follows it by token. */
.field-input{display:block;width:100%;min-height:var(--row-h);
  padding:var(--row-pad-y) var(--row-pad-x);
  font-family:inherit;font-size:var(--fs-option);font-weight:500;color:var(--t1);
  background:var(--row-fill);border:1px solid var(--line);border-radius:var(--r-ctl);
  transition:border-color .2s,background .2s}
.field-input::placeholder{color:var(--t4);font-weight:400}
.field-input:hover{border-color:var(--line-2)}
/* The accent, at the strength the chosen option row wears it. A focus ring
   is the one place this panel says "you are here" about something you type
   into. */
.field-input:focus{outline:0;border-color:rgba(var(--accent-2-rgb),.65);background:rgba(var(--ink-rgb),.06)}
.field-area{min-height:var(--area-h);resize:vertical;line-height:1.55}
.field-input.bad{border-color:var(--accent-2)}
.field-bad{display:block;font-size:var(--fs-ctl);color:var(--accent-2);
  margin-top:6px;line-height:1.4}
.field-bad:empty{display:none}
/* Full width, and 10px clear of the last field rather than the 14 between
   fields — it is the end of the form, not another row of it. It stayed at the
   end of the fields when the form moved into the drawer, rather than being
   pinned to the drawer's footer: a submit belongs under what it submits, and
   the footer it would have joined is hidden on that view anyway. */
.form-send{width:100%;margin-top:10px}


/* ═══════════════════════════════════════════════════════════════════════
   §12  PAGINATION DOTS
   ONE INDICATOR, TWO PLACES: the panel head's step dots and the tour card's.
   They were different objects until 2026-09-02 — the head drew 9px rings and
   filled the current one, the card drew 5px filled dots and STRETCHED the
   current one into a 16px pill — and they answer the same question two
   centimetres apart, so a viewer reads them as two different systems rather
   than as the same one twice.

   The head's version won because it is the reference's: 9px on a 12px pitch,
   which is its eight dots across 92. A ring for the steps not taken, a filled
   disc for the one you are on. The stretch is gone with the pill; a dot that
   changes SHAPE as well as colour says more than "you are here" and the row
   stops being a row of like things.

   .step-dots keeps its display rule in the build, because whether the head
   shows at all is a breakpoint question. What one looks like is this file's.
   ═══════════════════════════════════════════════════════════════════════ */
.tc-dots,.step-dots{display:flex;align-items:center;gap:3px;height:9px}
.tc-dots i,.step-dots i{width:9px;height:9px;border-radius:50%;box-sizing:border-box;
  border:1px solid var(--line-2);transition:background .2s,border-color .2s}
.tc-dots i.on,.step-dots i.on{background:var(--accent-2);border-color:var(--accent-2)}

/* ═══════════════════════════════════════════════════════ STEP PICKER ══
   (2026-09-14, by request — "a dropdown arrow near section title … a list of
   steps to quickly go to".) The step header's title becomes the button, with a
   chevron after the word, and the list it opens is every step in TABS order.

   THE WHOLE TITLE IS THE TARGET, NOT THE ARROW ALONE. A 16px chevron is under
   --tap-min on its own, and a title you can read but not press next to an arrow
   you can is two targets for one control. The arrow is what says it opens.

   IT INHERITS ITS TYPE FROM THE HEADING. font:inherit, text-transform and
   letter-spacing all come from .step-title, so the button draws the same word
   the h2 drew before it was a button, at both breakpoints, and the build's two
   .step-title rules remain the only place the title's size is set.

   THE ELLIPSIS MOVED ONTO .step-name. It used to sit on the h2, and a
   text-overflow on a block does not reach into an inline-flex child — the
   label would have run past its box instead of truncating. */
.step-pick{display:inline-flex;align-items:center;gap:8px;min-width:0;max-width:100%;
  margin:0;padding:0;border:0;background:none;color:inherit;cursor:pointer;
  font:inherit;text-transform:inherit;letter-spacing:inherit;text-align:left}
.step-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* A 24px disc around a 16px mark. The disc is empty at rest and takes the same
   ink wash .opt:hover does when the title is pointed at or the list is open —
   a hover mark here is DRAWN rather than tinted, for the reason .tab lost its
   :hover (the title already sits on the strongest ink there is). */
.step-chev{flex:0 0 auto;display:flex;align-items:center;justify-content:center;
  width:24px;height:24px;border-radius:50%;
  transition:background .2s}
.step-chev svg{width:16px;height:16px;transition:transform .22s var(--ease)}
.step-pick:hover .step-chev,.step-pick[aria-expanded="true"] .step-chev{
  background:rgba(var(--ink-rgb),.08)}
.step-pick[aria-expanded="true"] .step-chev svg{transform:rotate(180deg)}

/* THE LIST. Solid --panel, not the dock's glass: it opens over the options
   themselves, and frosted glass over a column of text is text over text. It is
   position:fixed and placed by placeStepMenu(), because .dock clips its
   overflow — on the phone the list opens ABOVE the sheet, outside that box.

   ONE ROW, ONE STEP. The number is the counter's face at the chrome size; the
   name is an option row's type, because a step is a thing you pick. The step
   you are on wears .opt.on's own wash and edge — the panel already has a
   "selected" look, and a second one would be a second vocabulary.

   TWO BOXES SINCE 2026-09-21: .step-menu is the card and .step-list inside it
   is what scrolls. The phone draws the sheet's own scroll thumb here
   (.dock-thumb, in the build), and a thumb has to sit OUTSIDE the box that
   scrolls, or it scrolls away with the rows. The card clips to its radius so
   rows scrolled under a corner do not show past it. */
.step-menu{position:fixed;z-index:40;display:flex;flex-direction:column;overflow:hidden;
  background:var(--panel);border:1px solid var(--line-2);border-radius:var(--r-panel);
  box-shadow:0 18px 40px -16px rgba(var(--shade-rgb),calc(.9*var(--shadow-k)));
  opacity:0;transform:translateY(-4px);
  transition:opacity .16s,transform .16s var(--ease)}
.step-menu[data-up]{transform:translateY(4px)}
.step-menu.on{opacity:1;transform:none}
/* display:flex above outranks the UA's [hidden] rule, so it is restated. */
.step-menu[hidden]{display:none}
.step-list{display:flex;flex-direction:column;gap:2px;flex:0 1 auto;min-height:0;
  padding:6px;overflow-y:auto;overscroll-behavior:contain}
.step-item{display:flex;align-items:center;gap:14px;flex:0 0 auto;width:100%;min-height:40px;
  margin:0;padding:0 12px;text-align:left;cursor:pointer;
  background:none;border:1px solid transparent;border-radius:var(--r-ctl);
  color:var(--ink-option);font:inherit;
  transition:background .15s,border-color .15s}
.step-item:hover{background:rgba(var(--ink-rgb),.06)}
.step-item[aria-checked="true"]{border-color:rgba(var(--accent-2-rgb),.65);
  background:rgba(var(--accent-rgb),.10)}
.step-item:focus-visible{outline-offset:-2px}
.step-item-n{flex:0 0 auto;min-width:2.2em;font-family:var(--font-brand);
  font-size:var(--fs-ctl);font-weight:700;color:var(--t4);font-variant-numeric:tabular-nums}
.step-item[aria-checked="true"] .step-item-n{color:var(--accent-2)}
.step-item-l{flex:1 1 auto;min-width:0;font-size:var(--fs-option);font-weight:500;line-height:1.3;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
