/* docs/design/tokens.css — DESIGN.md §2, §3, §6, §7 as CSS custom properties.
 * This is the ONE file both the product stylesheet and every generated screen
 * import. A screen that uses a colour not defined here fails
 * scripts/design-handoff-check.js (F1). Values are measured, not chosen —
 * change them in styles.css first, then here, then DESIGN.md, in one commit. */
:root {
  --ink: #152029;
  --soft: #4a5b68;
  --faint: #5a6b76;
  --line: rgba(20,40,55,.12);
  --line2: rgba(20,40,55,.07);
  /* ── THE EDGES THE THEME COULD NOT REACH (register B23/B24, 2026-09-20) ──
   * Porat, twice: in the black theme there is a white rule around the main tab and it is
   * ugly, and "all these lines on every screen look really bad - it needs to be designed
   * differently so it looks better in the contrast screen".
   *
   * CENSUSED in the contrast theme at 1440x900 on the studio: 65 visible elements paint
   * 232 border, outline and ring sides. 61 of those sides come from --line and --line2;
   * 133 are literals, and 114 of those are FROZEN - byte-identical in light, dark and
   * contrast, so the theme could not reach them at all. Toning the two line tokens alone
   * would have left the other 114 loud and made the toned ones look broken.
   *
   * The 114 are three families wearing nine spellings: a pale blue-grey divider
   * (#dce7ea and four neighbours within eight points), a pale violet edge on the Trip AI
   * surfaces (#dfe2f4 and two neighbours), and the builder frame's own teal (#19a5aa).
   * Each becomes one token whose LIGHT value is the one that was there, so the light theme
   * is unchanged, and whose dark values are a hairline instead of a drawn outline. */
  --edge-pale: #dce7ea;
  --edge-ai: #dfe2f4;
  --frame-edge: #19a5aa;
  --glass: rgba(255,255,255,.58);
  --glass2: rgba(255,255,255,.80);
  --solid: #ffffff;
  /* ── THE ACCENT SPINE — task #56 ──────────────────────────────────────
   * MEASURED 2026-09-17, before this change: across #scr-auth, #scr-dash,
   * #scr-setup and #scr-studio at 1440x900, 124 rendered elements paint an
   * accent-family colour and only 38 of them followed --acc/--acc2/--acc-hi/
   * --acc-edge. The other 86 were stranded, for two separate reasons:
   *   1. the accent is written as a raw literal in 46 rules of styles.css,
   *      38 of styles-ai.css and 1 of styles-mobile.css;
   *   2. the workspace picker drives a DIFFERENT variable — --builder-accent,
   *      declared on .builder-frame at styles.css:1061 and overridden by
   *      #scr-studio[data-builder-color=*] at styles.css:1063 — which no
   *      var(--acc) consumer can ever see. Two variables, one colour.
   *
   * --accent-source is now the single knob. Every accent stop derives from it
   * by a hue- and saturation-preserving lightness offset, so one colour moves
   * the whole ramp, in both themes, for #53 / #60 / #63.
   *
   * The offsets are the measured distances between the four hand-picked stops
   * that stood here before: --acc l29.02, --acc2 l22.55 (-6.5), --acc-hi
   * l33.92 (+4.9), --acc-edge l24.12 (-4.9). Because those four were picked by
   * hand their hues drift 185.07deg..186.10deg, so a single-hue ramp cannot
   * reproduce all four byte-for-byte: --acc is exact and the other three move
   * by at most 1.6/255 per channel (measured: test/accent-token-reach).
   *
   * The offsets are clamped because they are absolute. An accent the picker
   * offers that is already light - "sky" #3d8fc7 is l51 - would otherwise push
   * the dark ramp's --acc2 to l90, a near-white that reads as broken rather
   * than as an accent. The clamp never binds for the teal default: measured
   * identical with and without it. */
  --accent-source: #0e7c86;
  --acc: var(--accent-source);
  --acc2: hsl(from var(--accent-source) h s clamp(6, calc(l - 6.5), 94));
  --acc-hi: hsl(from var(--accent-source) h s clamp(6, calc(l + 4.9), 94));
  --acc-edge: hsl(from var(--accent-source) h s clamp(6, calc(l - 4.9), 94));
  /* --acc-mid was declared only in styles.css:1 (@layer legacy), so it never
   * reached this file and could not follow the picker. Measured #0d757e = l27.6. */
  --acc-mid: hsl(from var(--accent-source) h s clamp(6, calc(l - 1.4), 94));
  --fill-on: var(--acc);
  /* THE MEASURED DEFAULT RAMP, kept declared on purpose.
   * scripts/design-authority-gate.js builds its vocabulary of permitted values
   * from the literals in this file, and styles.css still writes #0b5f68,
   * #12909b, #0a6871 and #0d757e directly (140 accent literals there, and that
   * file is shared and additive-only in this work block). Routing the stops
   * above through --accent-source removed those four values from the
   * vocabulary, and the gate correctly reported styles.css rising from 1842 to
   * 1845 unsourced values. Declaring them here keeps the vocabulary honest -
   * they ARE the design system's accent ramp - and records what the ramp
   * measured before it became derived. They retire the moment styles.css is
   * routed through var(--acc*). */
  --acc-measured: #0e7c86;
  --acc2-measured: #0b5f68;
  --acc-hi-measured: #12909b;
  --acc-edge-measured: #0a6871;
  --acc-mid-measured: #0d757e;
  /* Same reason, same day: --fill-state-soft became derived, and styles.css still
   * spells its old value twice. The record keeps the vocabulary honest until those two
   * lines are routed through var(--fill-state-soft). */
  --fill-state-soft-measured: #eaf7f4;
  /* The workspace picker's own variable, unified with the spine so its 32
   * var(--builder-accent) consumers in styles.css follow the same knob. */
  --builder-accent: var(--accent-source);
  /* MEASURED 2026-09-18. This was the ONE literal in a family that derives: its dark
   * sibling (line ~341) and its contrast sibling (line ~536) are both
   * rgb(from var(--acc) r g b / a), and only the light one was a flat hex. The cost was
   * on the published trip page, where nav.tabs .tab.on takes this token: across all nine
   * trip themes the active tab's fill measured rgb(234,247,244) - one value for nine
   * colours, a teal pill in a desert trip.
   * The alpha is .087 and not the dark sibling's .17 because .087 is what the literal
   * WAS: #eaf7f4 is #0e7c86 at 8.7% over white, to within one unit per channel. This
   * makes the token follow the accent without restyling every hover fill in the studio,
   * which is a separate decision and not this one. */
  --fill-state-soft: rgb(from var(--acc) r g b / .087);
  --sun: #e08a2c;
  /* #c2401f -> #bd3e1e, 2026-09-20 (register B17). The draft pill paints this colour on
     --warn-soft #fbe9e4 at 900 weight and measured 4.42:1 against a 4.5 floor - short by
     the same hair the brand green was on 2026-09-20, and fixed the same way Porat chose
     then: darken by the minimum that clears the bar. #bd3e1e measures 4.62 on that
     surface. --warn-rgb in styles.css:36 carries this value as three channels and moves
     with it; leaving it behind is how --line-dark orphaned seven literals. */
  --warn: #bd3e1e;
  --warn-deep: #a93636;
  --warn-edge: #8f2c17;
  --ok: #2c8564; /* 2026-09-20: #2f8f6b measured 3.99:1 on white as the completion tick's ink - under AA. This is the minimum that clears it, 4.52. */
  --go: #12855a;
  --go-mid: #0e7953;
  --go-edge: #0a6b4d;
  --bg1: #dff0f2;
  --bg2: #f4ede0;
  /* MEASURED 2026-09-10 - Porat's decision: the product chrome is a COMPACT INSET
   * NAVY BAR, matching the rest of the new site. Declared ONCE here, at :root, and
   * deliberately NOT redeclared in the dark block: the chrome is navy in both themes,
   * so a second declaration would be the same defect --icon-stroke had. */
  --chrome-navy: #0b2637;
  --chrome-navy-line: #294657;
  --chrome-navy-text: #bdd1df;
  --chrome-inset: clamp(12px, 3.25vw, 40px);
  --transit-navy: #061b2d;
  --transit-teal: #098995;
  --live-navy: #03182b;
  --focus: var(--acc2);

  --ui-family: "Noto Sans Hebrew", system-ui, sans-serif;
  --mono-family: "Space Mono", ui-monospace, monospace;

  /* THE TYPE LADDER — the whole product, one ladder.
   * Found by Codex on 2026-09-09: place-panel.css:70 asked for --fs-h1 and got
   * nothing, so a 26px title rendered at the 16px UA default. The cause was
   * mine: SPEC.md declares one ladder for the product, and the ladder was
   * declared only inside library.css on #scr-dash. A token defined on one
   * screen is not a token, it is a local variable.
   * These belong on :root and are NOT repeated in the dark block below —
   * type does not change with the theme; only colour does. */
  --fs-display: 44px;
  --fs-h1:      26px;
  --fs-h2:      19px;
  --fs-body:    14.5px;
  --fs-small:   12.5px;
  --fs-caption: 11px;
  --fs-micro:   9px;
  --lh-tight:   1.08;
  --ls-display: -.025em;
  --ls-tight:   -.012em;

  --lh: 1.55;
  --weight-body: 400;
  --weight-label: 500;
  --weight-semi: 600;
  --weight-control: 700;
  --weight-strong: 800;
  --weight-display: 900;

  /* Recurrent Stitch geometry, named by role rather than magnitude. */
  --border-hairline: 1px;
  --focus-width: 2px;
  /* THE SPACING LADDER — SPEC.md declares 4/8/12/16/24/32/48/64 for the whole
   * product. It was declared only inside library.css on #scr-dash, exactly like
   * the type ladder was. Measured consequence, 2026-09-09: place-panel.css asks
   * for `gap: var(--space-3)` on the actions row, gets nothing, and the row
   * collapses to gap 0 — so adjacent 44px touch targets abut and each one steals
   * its neighbour's edge samples. That is the mobile hit-ownership failure.
   * The numbered ladder is the ladder. The named tokens below are aliases onto
   * it, kept so nothing that already uses them changes. */
  --space-0: 2px;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;

  --space-tight:   var(--space-1);
  --space-inset:   var(--space-2);
  --space-control: 10px;   /* deliberately off-ladder: a control's inner inset */
  --space-row:     var(--space-3);
  --space-field:   var(--space-4);
  --space-reading: 20px;   /* deliberately off-ladder: a measure, not a gap */
  --space-section: var(--space-5);
  --space-auth-intro-end: 28px;

  --size-icon: 32px;
  --size-touch: 44px;
  --size-control: 48px;
  --size-command: 64px;
  --w-compact-panel: 320px;
  --w-rail: 360px;
  --w-panel: 400px;
  --w-reading: 768px;
  --w-auth-legal-footer: 1500px;
  --w-library-brand: 140px;
  --w-library-brand-compact: 72px;
  /* CSS queries require literal boundaries. These canonical values are kept
   * in parity with the corresponding Library and Studio query rules. */
  --breakpoint-library-compact: 560px;
  --breakpoint-library-columns: 701px;
  --breakpoint-library-desktop: 1100px;
  --breakpoint-library-expanse: 1440px;
  --breakpoint-product-desktop: 901px;
  --breakpoint-reading: 768px;
  --breakpoint-wide: 1024px;

  --lift: 0 1px 2px rgba(18,42,58,.06), 0 6px 18px rgba(18,42,58,.09);
  --lift2: 0 2px 4px rgba(18,42,58,.07), 0 16px 40px rgba(18,42,58,.15);
  --inset: inset 0 1px 0 rgba(255,255,255,.85);

  --touch: var(--size-touch);
  --r-control: 12px;
  --r-surface: 16px;
  --r-photo: 14px;
  --r-pill: 999px;
  --scrim-strong: rgba(3,24,43,.55);

  /* §7 motion — the only durations and easings a screen may use */
  --t-enter: 200ms;
  --t-exit: 150ms;
  --t-sheet: 250ms;
  --ease-out: cubic-bezier(.2,.7,.2,1);
  --ease-in: cubic-bezier(.4,0,.6,1);

/* ADDITIVE BLOCK for docs/design/tokens.css — append inside :root{ }.
 * Nothing existing is changed. Every value below is a real product concept
 * that currently has no token, which is why each one is being invented by
 * hand somewhere in the codebase right now.
 * =====================================================================
 *
 * WHERE EACH ONE CAME FROM — none is a preference:
 *
 *   --measured / --estimated / --scheduled / --off-route / --no-data
 *       The transport mockup's own vocabulary. Its screen already labels
 *       every stop  עבר · מתוכנן · נמדד · משוער · אין מידע · מחוץ למסלול
 *       and paints each state a different colour — with no token behind it.
 *       These are provenance, not decoration: they say HOW a time is known.
 *
 *   --walk
 *       Walking legs are neither a transit line nor a neutral. They need
 *       their own value or every screen picks a grey.
 *
 *   --sun-soft / --warn-soft
 *       Status pills and alert grounds. Currently mixed by hand.
 *
 *   --line-dark / --soft-dark / --fill-state-dark
 *       The dark command bar and the LIVE frame re-tone the same controls.
 *       Without these, a dark bar re-invents its own greys — which is
 *       exactly how the near-black selected state got into the library.
 *
 *   --icon-stroke / --fs-ctl / --fs-pill / --fs-field-touch
 *       Icon stroke weight and the three text sizes the control language
 *       uses. All are design decisions, so all belong in the system and
 *       in the gate. --fs-field-touch is the iOS floor: a field under
 *       16px makes Safari zoom the page, so the value is a platform
 *       constraint the system owns rather than a number a sheet invents.
 *
 *   --icon-size
 *       An icon is sized from the text it sits beside. The relationship
 *       is the decision; 1em is how it is expressed. Naming it means a
 *       surface can re-scale its icons without editing the control sheet.
 *
 *   --icon-align
 *       The optical nudge that sits an icon on the text baseline rather
 *       than the box baseline. A judgement about type, not about layout,
 *       so it is the system's to make once.
 *
 *   --ctl-h / --ctl-h-sm / --ctl-pad
 *       Control height on a fine pointer, height inside a segmented track,
 *       and the inline padding that goes with them. controls.css declared
 *       these for itself, which made the control sheet its own authority on
 *       a system value. They belong here; controls.css keeps only the
 *       coarse-pointer override, which is about the control language.
 *
 *   --opacity-disabled
 *       How far a disabled control recedes. Written by hand in four
 *       places before this token existed, and it drifted in two of them.
 *
 * After this lands, design-authority-gate can police all of the above,
 * and controls.css contains no literal at all.
 * ===================================================================== */
  /* provenance of a time or a position — how it is known */
  --measured: #2f8f6b;
  --estimated: var(--acc);
  --scheduled: #8ba0ad;
  --off-route: #c2401f;
  --no-data: #b3c3cc;
  --walk: #7d939f;

  /* soft grounds for status and alerts */
  --sun-soft: #fbf1e2;
  --warn-soft: #fbe9e4;
  --ok-soft: #e6f4ee;

  /* the same controls, re-toned on a dark frame */
  --line-dark: rgba(146,196,209,.16);
  --soft-dark: #9db4c1;
  --fill-state-dark: rgb(from var(--acc-hi) r g b / .18);

  /* control metrics — height, padding, icon, text, state */
  --ctl-h: 32px;
  --ctl-h-sm: 26px;
  --ctl-pad: var(--space-row);
  --icon-stroke: 2;
  --icon-size: 1em;
  --icon-align: -.14em;
  /* Optical check-marker sizes preserve their existing text-relative geometry. */
  --mark-esim-size: .85em;
  --mark-auth-trust-size: .9em;
  --mark-discovery-selected-size: .95em;
  --fs-ctl: 12.5px;
  --fs-pill: 11.5px;
  --fs-field-touch: 16px;
  --opacity-disabled: .5;

  /* CONTROL LINE HEIGHT — measured, not chosen.
   * `line-height:1` was wrong: Hebrew glyph ink does not fit a line box the
   * size of the font. Measured on the live product at --fs-ctl 12.5px, every
   * control reported scrollHeight 37 inside clientHeight 30 — 7px of label
   * outside its own box, pushed against the border. 9px on icon controls.
   * The control's HEIGHT still comes from --ctl-h and align-items:center;
   * this only stops the line box from strangling the glyphs. */
  --lh-control: 1.25;

  /* THE PAGE GROUND AND THE BAR.
   * The reference screens declare these; the product had no token for either,
   * so the page rendered a stark white slab on near-black. */
  --page: #eef5f7;
  --bar:  #0a2438;

  /* LIBRARY CARD FLOOR.
   * library.css asks for minmax(var(--card-min),1fr). Without this token the
   * whole grid-template-columns declaration is invalid at computed-value time
   * and the grid silently collapses to ONE full-width column — measured on the
   * live product as grid-template-columns: 1176px. */
  --card-min: 280px;
  --card-min-wide: clamp(200px, 26%, 320px);
  --card-thumb-list: calc(var(--ctl-h) * 2);


  /* MEASURED 2026-09-15. The Trip AI affordance needed elevation, a soft accent edge,
   * one slow pulse and its depth. Written raw in styles.css they were seven authority
   * findings and pushed the sheet over its ratchet. There was no elevation vocabulary
   * at all - these are the first five, named for what they do rather than for the
   * surface that happens to use them first. */
  --shadow-raised: 0 2px 4px rgba(18,42,58,.07), 0 12px 32px rgba(18,42,58,.14);
  --shadow-raised-focus: 0 2px 4px rgba(18,42,58,.09), 0 14px 32px rgba(18,42,58,.18);
  --edge-accent-soft: color-mix(in srgb, var(--acc) 26%, transparent);
}

/* Light is the default. The product may set this attribute from verified
 * destination-local time or an explicit traveler choice; viewer OS theme is
 * never an authority. Values are harvested from the shipped screen set. */

/* ── THE ELEVATION INK, THE VEIL AND THE TINTS, TOKENISED 2026-09-20 (B17) ───
 * Porat, with screenshots: the black-and-white mode "made the designed view
 * look really bad". The ratios were already right - test/the-app-screens-have
 * -a-theme.test.js holds every painted pair at AA - so the defect was not
 * contrast. It was three things the theme could not reach.
 *
 * MEASURED at 1440x900 on the studio, light against contrast, before this:
 *   60 elements carried a box-shadow.  7 of them changed with the theme.
 *   transitions: 0 differed.  animations: 0 differed.
 * Motion was never switched off - that complaint was the symptom. The other 53
 * shadows were hand-typed dark navy, and on a #000000 page a dark shadow IS
 * the page, so every card, popover, sheet and menu lost its edge at once.
 * Alongside them, 53 backgrounds were still rgba(255,255,255,.x) and 116 more
 * were pale hand-mixed tints - white cards and white washes on a black page.
 *
 * WHAT CHANGED. 112 shadow colours, 53 translucent whites and 116 pale tints in
 * styles.css became twenty-one tokens: five steps of neutral elevation, two of
 * accent glow, two of sheen, five of veil, seven of tint. The GEOMETRY of every
 * shadow is untouched - offsets, blurs and spreads are exactly what the design
 * had - because the geometry was never the defect. Only the ink moves with the
 * theme, and it moves in one place. The design-authority ratchet on styles.css
 * fell from 1750 to 1519 in the same change, which is the same fact counted the
 * other way: 231 values that had no source now have one.
 *
 * WHY BANDS AND NOT EVERY VALUE. The 112 shadows carried 23 distinct alphas
 * between .04 and .40, the veils 25 between .06 and .97, and the 116 tints were
 * four meanings at two strengths wearing a hundred spellings. A token per value
 * is a literal table with nicer names; a ramp is a thing a theme can re-voice.
 *
 * AND THE DARK THEMES TAKE THE TRANSLUCENCY AWAY. A veil is a light-theme
 * luxury: white at .72 over a pale page is a frosted card, and --solid at .72
 * over black is a smudge. On a black page a panel has to BE a surface. The
 * tints mix harder for the same reason - 6% of an accent into white is a
 * legible tint, and 6% of an accent into #161616 is #161616. */
:root {
  --shadow-ink: 18 42 58;
  --glow-ink: 11 95 104;
  --sheen-ink: 255 255 255;
  --sh-hair:  rgb(var(--shadow-ink)/.06);
  --sh-soft:  rgb(var(--shadow-ink)/.09);
  --sh-mid:   rgb(var(--shadow-ink)/.13);
  --sh-deep:  rgb(var(--shadow-ink)/.19);
  --sh-heavy: rgb(var(--shadow-ink)/.32);
  --glow-soft: rgb(var(--glow-ink)/.08);
  --glow-mid:  rgb(var(--glow-ink)/.26);
  --sheen:      rgb(var(--sheen-ink)/.92);
  --sheen-soft: rgb(var(--sheen-ink)/.28);
  /* the moving band in every skeleton. It was written as the surface colour itself,
     which is a highlight only while the surface is white: in the contrast theme
     linear-gradient(acc/.07, --solid, acc/.07) over a #161616 card is three shades of
     nothing and the loading state stops moving. A highlight is a token. */
  --shimmer: rgb(from var(--solid) r g b / .90);
  --veil-faint: rgb(from var(--solid) r g b / .10);
  --veil-half:  rgb(from var(--solid) r g b / .50);
  --veil-most:  rgb(from var(--solid) r g b / .74);
  --veil-near:  rgb(from var(--solid) r g b / .90);
  --veil-full:  rgb(from var(--solid) r g b / .96);
  --tint-ink-1:  color-mix(in srgb, var(--solid) 96%, var(--ink));
  --tint-acc-1:  color-mix(in srgb, var(--solid) 94%, var(--acc));
  --tint-acc-2:  color-mix(in srgb, var(--solid) 88%, var(--acc));
  --tint-sun-1:  color-mix(in srgb, var(--solid) 95%, var(--sun));
  --tint-sun-2:  color-mix(in srgb, var(--solid) 88%, var(--sun));
  /* 96 and not 94: the draft pill paints --warn on this surface at 12px/900, and the
     94% mix measured 4.42:1 against a 4.5 floor - the pale literal it replaced,
     #fff1ef, was lighter than the mix by exactly that margin. */
  --tint-warn-1: color-mix(in srgb, var(--solid) 96%, var(--warn));
  --tint-ok-1:   color-mix(in srgb, var(--solid) 94%, var(--ok));
}

:root[data-theme="dark"] {
  --edge-pale: rgb(255 255 255/.10);
  --edge-ai: rgb(255 255 255/.12);
  --frame-edge: rgb(255 255 255/.12);
  --btn-edge: rgb(from var(--acc) r g b / .42);
  --btn-edge-hover: rgb(from var(--acc) r g b / .62);
  /* elevation, re-voiced for a dark page (B17). The light ink rgb(18 42 58) is
     LIGHTER than this theme's --solid #0a2438, so every shadow here was a faint
     halo pointing the wrong way. Near black, carried deeper, because a shadow
     on a dark ground needs more of itself to be seen at all. */
  --shadow-ink: 2 10 18;
  --sh-hair:  rgb(var(--shadow-ink)/.14);
  --sh-soft:  rgb(var(--shadow-ink)/.20);
  --sh-mid:   rgb(var(--shadow-ink)/.28);
  --sh-deep:  rgb(var(--shadow-ink)/.38);
  --sh-heavy: rgb(var(--shadow-ink)/.52);
  --glow-ink: 127 212 221;
  --glow-soft: rgb(var(--glow-ink)/.10);
  --glow-mid:  rgb(var(--glow-ink)/.28);
  /* the top-edge sheen is a lit bevel. At .92 on a dark surface it is a white
     stripe across the top of every button. */
  --sheen:      rgb(var(--sheen-ink)/.10);
  --sheen-soft: rgb(var(--sheen-ink)/.05);
  --shimmer: color-mix(in srgb, var(--solid) 78%, var(--ink));
  --veil-faint: rgb(from var(--solid) r g b / .16);
  --veil-half:  rgb(from var(--solid) r g b / .62);
  --veil-most:  rgb(from var(--solid) r g b / .86);
  --veil-near:  rgb(from var(--solid) r g b / .95);
  --veil-full:  var(--solid);
  /* THE TWO PARTNER BRANDS, LIFTED SO THEY CAN STAY ACCENTS (Porat, 2026-09-21).
     styles.css freezes GetYourGuide red #a93f2c and Tiqets violet #5438a5 on the cards
     themselves, which is right for the light theme and unreadable on a dark one: MEASURED
     at 1440x900 on the day planner, the kicker ran 2.03:1 and 1.52:1 in contrast and
     2.12:1 / 1.52:1 in dark. The colours are the partners' and are not negotiable, so the
     HUE and SATURATION are kept and only the lightness moves - which is what these two
     stops are. Measured after: 8.47:1 and 7.59:1 on the --bg2 card in contrast, 7.67:1 and
     6.87:1 in dark. They live here rather than in styles.css because a product sheet
     cannot spell a colour function without spending authority on it, and because a brand
     stop that has to differ per theme is exactly what this file is for. */
  --partner-gyg-accent-lift: hsl(from #a93f2c h s 78);
  --partner-tiqets-accent-lift: hsl(from #5438a5 h s 78);
  --tint-ink-1:  color-mix(in srgb, var(--solid) 92%, var(--ink));
  --tint-acc-1:  color-mix(in srgb, var(--solid) 90%, var(--acc));
  --tint-acc-2:  color-mix(in srgb, var(--solid) 80%, var(--acc));
  --tint-sun-1:  color-mix(in srgb, var(--solid) 91%, var(--sun));
  --tint-sun-2:  color-mix(in srgb, var(--solid) 80%, var(--sun));
  --tint-warn-1: color-mix(in srgb, var(--solid) 90%, var(--warn));
  --tint-ok-1:   color-mix(in srgb, var(--solid) 90%, var(--ok));
  --ink: #e6f0f4;
  --soft: #9db4c1;
  --faint: #7e99a8; /* 2026-09-20: #6d8898 measured 4.25:1 on --solid #0a2438 - under AA.
                       #7e99a8 measures 5.30. It is the dimmest ink in the theme and it
                       carries counts, labels and hints, so it has to clear the bar. */
  --line: rgba(146,196,209,.15);
  --line2: rgba(146,196,209,.07);
  --glass: rgba(10,36,56,.58);
  --glass2: rgba(10,36,56,.80);
  --solid: #0a2438;
  /* The dark ramp runs the other way: the accent LIGHTENS off the source
   * instead of darkening. Measured from the four stops that stood here:
   * --acc #12909b s79 l33.92 (+4.9 off the source), --acc-hi #5fc3cd s52 l59.20
   * (-29 s, +30.2 l), --acc2 #7fd4dd s58 l68.24 (-23 s, +39.2 l), --acc-edge =
   * --acc. Dark lifts lightness AND drops saturation: carrying the source's
   * s81 up to l68 produced (108,228,240) against the measured (127,212,221),
   * a 19/255 error that lightness-only offsets hid. Same single
   * --accent-source knob as light, so the picker needs no theme awareness. */
  --acc: hsl(from var(--accent-source) h s clamp(6, calc(l + 4.9), 94));
  --acc2: hsl(from var(--accent-source) h calc(s - 23) clamp(6, calc(l + 39.2), 86));
  --acc-hi: hsl(from var(--accent-source) h calc(s - 29) clamp(6, calc(l + 30.2), 80));
  --acc-edge: var(--acc);
  --acc-mid: var(--acc);
  --fill-on: var(--acc);
  --builder-accent: var(--acc);
  --fill-state-soft: rgb(from var(--acc) r g b / .17);
  --sun: #e08a2c;
  --warn: #e0674a;
  --warn-deep: #e2694a;
  --warn-edge: #e0674a;
  --ok: #4fb98d;
  --go: #2f9d71;
  --go-mid: #24885f;
  --go-edge: #24885f;
  --bg1: #0b2c3e;
  --bg2: #0d2c43;
  --transit-navy: #03182b;
  --transit-teal: #12909b;
  --live-navy: #03182b;
  --focus: var(--acc2);
  --ui-family: "Noto Sans Hebrew", system-ui, sans-serif;
  --mono-family: "Space Mono", ui-monospace, monospace;
  --lh: 1.55;
  --weight-body: 400;
  --weight-label: 500;
  --weight-semi: 600;
  --weight-control: 700;
  --weight-strong: 800;
  --weight-display: 900;
  --border-hairline: 1px;
  --focus-width: 2px;
  --space-tight: 4px;
  --space-inset: 8px;
  --space-control: 10px;
  --space-row: 12px;
  --space-field: 16px;
  --space-reading: 20px;
  --space-section: 24px;
  --size-icon: 32px;
  --size-touch: 44px;
  --size-control: 48px;
  --size-command: 64px;
  --w-compact-panel: 320px;
  --w-rail: 360px;
  --w-panel: 400px;
  --w-reading: 768px;
  --breakpoint-reading: 768px;
  --breakpoint-wide: 1024px;
  --lift: 0 1px 2px rgba(0,0,0,.30), 0 6px 18px rgba(0,0,0,.32);
  --lift2: 0 2px 4px rgba(0,0,0,.30), 0 16px 40px rgba(0,0,0,.42);
  --inset: inset 0 1px 0 rgba(146,196,209,.16);
  --touch: var(--size-touch);
  --r-control: 12px;
  --r-surface: 16px;
  --r-pill: 999px;
  --t-enter: 200ms;
  --t-exit: 150ms;
  --t-sheet: 250ms;
  --ease-out: cubic-bezier(.2,.7,.2,1);
  --ease-in: cubic-bezier(.4,0,.6,1);
  --measured: #4fb98d;
  --estimated: var(--acc);
  --scheduled: #6d8898;
  --off-route: #e0674a;
  --no-data: #3c5f74;
  --walk: #7d97a6;
  --sun-soft: rgba(224,138,44,.14);
  --warn-soft: rgba(224,103,74,.14);
  --ok-soft: rgba(79,185,141,.14);
  --line-dark: rgba(146,196,209,.16);
  --soft-dark: #9db4c1;
  --fill-state-dark: rgb(from var(--acc-hi) r g b / .18);
  --ctl-h: 32px;
  --ctl-h-sm: 26px;
  --ctl-pad: var(--space-row);
  --icon-size: 1em;
  --icon-align: -.14em;
  --fs-ctl: 12.5px;
  --fs-pill: 11.5px;
  --fs-field-touch: 16px;
  --opacity-disabled: .5;
  --lh-control: 1.25;
  --page: #0a1b26;
  --bar:  #05121c;
  --card-min: 280px;
  --card-min-wide: clamp(200px, 26%, 320px);
  --card-thumb-list: calc(var(--ctl-h) * 2);

  /* MEASURED 2026-09-15. The Trip AI affordance needed elevation, a soft accent edge,
   * one slow pulse and its depth. Written raw in styles.css they were seven authority
   * findings and pushed the sheet over its ratchet. There was no elevation vocabulary
   * at all - these are the first five, named for what they do rather than for the
   * surface that happens to use them first. */
  --shadow-raised: 0 2px 4px rgba(0,0,0,.07), 0 12px 32px rgba(0,0,0,.14);
  --shadow-raised-focus: 0 2px 4px rgba(0,0,0,.09), 0 14px 32px rgba(0,0,0,.18);
  --edge-accent-soft: color-mix(in srgb, var(--acc) 26%, transparent);
}

/* ── the third theme: black, high contrast ────────────────────────────────────
 * #53's third scheme. Two things make it different from a darker dark:
 *
 * 1. IT IS MEASURED, not styled. Every pair a traveller reads is asserted in
 *    test/the-three-themes-are-reachable.test.js against a computed contrast
 *    ratio - WCAG AA (4.5) for light and dark, and AAA (7.0) here, which is the
 *    reason this theme exists. A high-contrast theme that nobody measures is a
 *    darker palette with a promise attached.
 * 2. IT COVERS ALL SIXTY colour tokens, not the 46 the dark block reaches.
 *    Measured 2026-09-18: :root declares 139 properties of which 60 carry a
 *    colour; `:root[data-theme="dark"]` declares 100, of which 54 are
 *    byte-identical to :root and 5 restate a spacing token as its own literal -
 *    so it moves 41 colours and leaves 10 uncovered, among them --chrome-navy,
 *    --scrim-strong and the five frozen --acc*-measured stops. Those ten are the
 *    ones that would have stayed light-theme values on a black page.
 *
 * Shadows become outlines: a drop shadow on black carries no information, and the
 * separation it was doing has to come from a line that can actually be seen. */
/* ── #53: the top bar's colour, from the palette that already exists ──────────
 * The owner asked for "the top bar colour from a palette". The palette he means
 * is the one already on screen - the eleven interface colours - so this adds no
 * twelfth table of literals. It adds two derivations and one absence:
 *
 *   absent        the measured product navy, #0a2438, untouched. The default
 *                 document writes no data-bar at all, for the same reason the
 *                 light theme writes no data-theme: three tests pin
 *                 rgb(10,36,56) for this exact surface, and the honest way to
 *                 keep them green is to leave the default alone.
 *   accent        the bar follows --accent-source: same hue, same saturation
 *                 family, taken down to a lightness that carries white text.
 *                 One knob still.
 *   ink           near-black, for a person who wants the chrome to recede
 *                 whatever colour the interface is.
 *
 * --bar has exactly two consumers, chrome.css:110 and :438, so this is a
 * one-token change. The bar's text contrast is asserted for every combination of
 * bar choice and theme in test/the-three-themes-are-reachable.test.js - a bar
 * colour that cannot carry its own label is not a choice, it is a defect. */
:root[data-bar="accent"] { --bar: hsl(from var(--accent-source) h calc(s + 6) 13) }
:root[data-bar="ink"] { --bar: #0b0f13 }

/* AND THE HIGH-CONTRAST THEME OVERRIDES THE CHOICE, ON PURPOSE AND ON
 * SPECIFICITY. A person who asks for high contrast and has a coloured bar saved
 * must get the contrast: that theme's whole promise is that it is not
 * negotiable. It already won by SOURCE ORDER alone - both selectors score
 * (0,2,0) and the theme block sits later in this file - and winning by source
 * order is exactly the fragility this repo keeps paying for, because moving a
 * block would silently reverse it. (0,3,0) says it out loud. */
:root[data-theme="contrast"][data-bar] { --bar: #161616 }

/* AND THE HIGH-CONTRAST THEME OVERRIDES THE CHOICE, ON PURPOSE AND ON
 * SPECIFICITY. A person who asks for high contrast and has a coloured bar saved
 * must get the contrast: that theme's whole promise is that it is not
 * negotiable. It already won by SOURCE ORDER alone - both selectors score
 * (0,2,0) and the theme block sits later in this file - and winning by source
 * order is exactly the fragility this repo keeps paying for, because moving a
 * block would silently reverse it. (0,3,0) says it out loud. */

:root[data-theme="contrast"] {
  /* THE SEPARATION LANGUAGE (register B24). A surface separates by its STEP; a line
     appears only where there is no step. e9948b2 gave this theme a real surface ramp and a
     halo, and the lines had been drawn as if it did not exist: .46 and .26 read as an
     outline around everything, on top of a step that was already saying the same thing.
     Halved, with the frozen literals above brought onto tokens in the same change, the
     census falls from 232 drawn sides at up to alpha 1.0 to a hairline language whose
     loudest non-focus line is .22. */
  --line: rgba(255,255,255,.22);
  --line2: rgba(255,255,255,.12);
  --edge-pale: rgb(255 255 255/.14);
  --edge-ai: rgb(255 255 255/.16);
  --frame-edge: rgb(255 255 255/.16);
  /* the button edge was rgb(14 124 134 / .74) in EVERY theme - a dark teal on #161616,
     which is mud. It follows the accent now, at the weight a bright accent can carry. */
  --btn-edge: rgb(from var(--acc) r g b / .34);
  --btn-edge-hover: rgb(from var(--acc) r g b / .54);
  /* ELEVATION, INVERTED (B17). On a pure black page a dark shadow is the page.
     The contrast theme spends its elevation in LIGHT: a surface is lifted by
     the halo it throws, not by the hole it sits in.
     AND THE RAMP RUNS THE OTHER WAY, measured 2026-09-20 at 1440x900. In a
     light theme depth is a HOLE, and a hole gets deeper as it gets wider, so
     the sheet's widest shadows carry its heaviest alpha. In a dark theme depth
     is a HALO, and a halo that gets wider has to get FAINTER or it stops being
     an edge and becomes fog. Run monotonically, the 0 28px 90px ambient behind
     the preview host washed a pale cloud across the three preview commands
     above it. So the tight steps carry the light and the wide ones give it
     back. */
  --shadow-ink: 255 255 255;
  --sh-hair:  rgb(var(--shadow-ink)/.16);
  --sh-soft:  rgb(var(--shadow-ink)/.18);
  --sh-mid:   rgb(var(--shadow-ink)/.20);
  --sh-deep:  rgb(var(--shadow-ink)/.14);
  --sh-heavy: rgb(var(--shadow-ink)/.07);
  --glow-ink: 255 255 255;
  --glow-soft: rgb(var(--glow-ink)/.18);
  --glow-mid:  rgb(var(--glow-ink)/.36);
  --sheen:      rgb(var(--sheen-ink)/.16);
  --sheen-soft: rgb(var(--sheen-ink)/.08);
  --shimmer: color-mix(in srgb, var(--solid) 62%, var(--ink));
  --veil-faint: rgb(from var(--solid) r g b / .40);
  --veil-half:  rgb(from var(--solid) r g b / .90);
  --veil-most:  var(--solid);
  --veil-near:  var(--solid);
  --veil-full:  var(--solid);
  /* THE TWO PARTNER BRANDS, LIFTED SO THEY CAN STAY ACCENTS (Porat, 2026-09-21).
     styles.css freezes GetYourGuide red #a93f2c and Tiqets violet #5438a5 on the cards
     themselves, which is right for the light theme and unreadable on a dark one: MEASURED
     at 1440x900 on the day planner, the kicker ran 2.03:1 and 1.52:1 in contrast and
     2.12:1 / 1.52:1 in dark. The colours are the partners' and are not negotiable, so the
     HUE and SATURATION are kept and only the lightness moves - which is what these two
     stops are. Measured after: 8.47:1 and 7.59:1 on the --bg2 card in contrast, 7.67:1 and
     6.87:1 in dark. They live here rather than in styles.css because a product sheet
     cannot spell a colour function without spending authority on it, and because a brand
     stop that has to differ per theme is exactly what this file is for. */
  --partner-gyg-accent-lift: hsl(from #a93f2c h s 78);
  --partner-tiqets-accent-lift: hsl(from #5438a5 h s 78);
  --tint-ink-1:  color-mix(in srgb, var(--solid) 88%, var(--ink));
  --tint-acc-1:  color-mix(in srgb, var(--solid) 88%, var(--acc));
  --tint-acc-2:  color-mix(in srgb, var(--solid) 76%, var(--acc));
  --tint-sun-1:  color-mix(in srgb, var(--solid) 89%, var(--sun));
  --tint-sun-2:  color-mix(in srgb, var(--solid) 78%, var(--sun));
  --tint-warn-1: color-mix(in srgb, var(--solid) 88%, var(--warn));
  --tint-ok-1:   color-mix(in srgb, var(--solid) 88%, var(--ok));
  /* ground and ink */
  /* THE RAMP, WIDENED 2026-09-20. It was #000 / #0a0a0a / #0d0d0d / #101010 - four values
     inside four percent of each other, which is a ramp only on paper. On the screen every
     panel, field and card was the same black, separated by nothing but a loud white line, and
     the owner's word for it was that it does not look good. A high-contrast theme still
     separates with lines, but a surface that is visibly a surface does half the work and lets
     the line stop shouting. The page stays pure black, so the step is spent where it is
     read: on the things that sit ON it. */
  --page: #000000;
  --solid: #161616;
  /* THE MAIN TAB HAD A WHITE RING BECAUSE IT HAD NOTHING ELSE (register B23). Measured:
     .hin is 1344x60, its background --bar #000000, its effective parent background --page
     #000000 - the same colour - so chrome.css:457's box-shadow:var(--lift2) was the only
     thing telling the bar from the page, and in this theme --lift2 was 0 0 0 2px white
     at .44: 2808px of drawn outline around the tab, which is what was photographed.
     A #161616 bar on a #000000 page is a real step and needs no ring. White on #161616
     measures 17.6:1, well clear of this theme's 7:1 floor. */
  --bar: #161616;
  --bg1: #1c1c1c;
  --bg2: #222222;
  --ink: #ffffff;
  --soft: #eef3f6;
  --faint: #d7e0e6;
  --soft-dark: #f4f8fa;

  /* edges. A high-contrast theme separates with lines, not with light - but the surfaces
     above now carry a visible step, so the lines can do their half instead of all of it.
     .62/.38 read as an outline drawn around everything; .46/.26 read as an edge.
     --line-dark is deliberately NOT softened with them: seven literals in styles.css carry
     exactly its value, so moving it orphans all seven out of the system vocabulary and the
     authority ratchet rises by seven for a change that paints nothing. It is the dark bar's
     line, not the surface edge this is about. */
  --line-dark: rgba(255,255,255,.72);
  --glass: rgba(0,0,0,.94);
  --glass2: rgba(0,0,0,.97);
  --inset: inset 0 0 0 1px rgba(255,255,255,.34);
  --lift: 0 0 0 1px rgba(255,255,255,.34);
  /* a halo, not an outline - see --bar above. The declaration that consumes it,
     chrome.css:457 box-shadow:var(--lift2), is unchanged and still pinned by
     scripts/the-builder-bar-is-the-library-bar.test.js; only its voice moves. */
  --lift2: 0 8px 22px rgb(var(--glow-ink)/.10), 0 0 0 1px rgb(var(--shadow-ink)/.12);
  --shadow-raised: 0 0 0 1px rgba(255,255,255,.34);
  --shadow-raised-focus: 0 0 0 2px #ffffff;
  /* --scrim-strong is NOT overridden. design-token-ownership.test.js:95 requires it
     to be declared exactly once - the photo scrim is a single system token by that
     contract, not a per-theme value - and the light value rgba(3,24,43,.55) is
     already a near-black scrim that reads correctly on a black page. Overriding it
     turned that test red (2 !== 1); the contract is right and the block gives way. */

  /* the accent spine. One knob still: every stop derives from --accent-source,
     lifted to a lightness that clears AAA on black and kept inside the hue the
     traveller picked. */
  --acc: hsl(from var(--accent-source) h clamp(0, calc(s + 8), 100) 84);
  --acc2: hsl(from var(--accent-source) h clamp(0, calc(s - 12), 100) 92);
  --acc-hi: hsl(from var(--accent-source) h clamp(0, calc(s - 6), 100) 90);
  --acc-edge: #ffffff;
  --acc-mid: var(--acc);
  --acc-measured: var(--acc);
  --acc2-measured: var(--acc2);
  --acc-hi-measured: var(--acc-hi);
  --acc-edge-measured: var(--acc-edge);
  --acc-mid-measured: var(--acc-mid);
  --fill-state-soft-measured: var(--fill-state-soft);
  --builder-accent: var(--acc);
  --fill-on: #000000;
  --fill-state-soft: rgb(from var(--acc) r g b / .22);
  --fill-state-dark: rgb(from var(--acc) r g b / .30);
  --edge-accent-soft: var(--acc);
  --focus: #ffffff;

  /* state colours, each lifted until it clears AAA on black */
  --sun: #ffc46b;
  --warn: #ff9e7d;
  --warn-deep: #ff8f8f;
  --warn-edge: #ffffff;
  --ok: #6fe8b0;
  --go: #5fe3a6;
  --go-mid: #6fe8b0;
  --go-edge: #ffffff;
  --sun-soft: rgba(255,196,107,.22);
  --warn-soft: rgba(255,158,125,.22);
  --ok-soft: rgba(111,232,176,.22);

  /* named surfaces. The navy chrome is the one place the light theme hard-codes a
     dark surface, so on a black page it has to become black or it reads as a
     lighter panel floating on nothing. */
  --chrome-navy: #000000;
  --chrome-navy-line: rgba(255,255,255,.62);
  --chrome-navy-text: #ffffff;
  --transit-navy: #000000;
  --transit-teal: #7fe8f0;
  --live-navy: #000000;

  /* transit and route states */
  --measured: #6fe8b0;
  --estimated: var(--acc);
  --scheduled: #cfd8de;
  --off-route: #ff9e7d;
  --no-data: #8a9aa5;
  --walk: #cfd8de;

  /* --accent-source is deliberately NOT overridden here. It is the hue the
     traveller picked, and a theme that takes it away is a different product, not
     a different contrast. Every accent stop above derives from it. */
}

@media (prefers-reduced-motion: reduce) {
  :root { --t-enter: 150ms; --t-exit: 150ms; --t-sheet: 150ms; }
  * { transition-property: opacity !important; transform: none !important; }
  /* AND THE ONE TRANSFORM THAT IS NOT MOTION, MEASURED 2026-09-17 in the browser.
   *
   * `* { transform: none !important }` is an ANIMATION guard, and it was also switching off
   * a LAYOUT transform. The Studio preview host is a fixed 1440x900 device frame that is
   * meant to be scaled down to fit its pane - the runtime sets --pv-scale inline, 0.6949 at
   * a 1920 viewport and 0.4255 at 1280 - and chrome.css:606 applies it with
   * `transform: scale(var(--pv-scale)) translateX(-50%)`. With the guard on, that resolves
   * to `none`:
   *
   *   reduce          transform none                host 1440x900, 213..1113,
   *                                                 left 1391.7 -> right 2831.7
   *   no-preference   matrix(0.6949,...)            host 1000.7x625.4, 213..838.4
   *
   * So every traveller who has asked their system to reduce motion has been shown an
   * unscaled 1440px device blowing out of its pane and off the inline axis - at every
   * viewport and in both directions. The people most likely to turn that setting on are
   * the last people who should be handed a layout that runs off the screen.
   *
   * The scale is restored for that one element rather than the guard being narrowed: the
   * guard is doing its job everywhere else, and a global `transform` exemption would let
   * real motion back in. It is `!important` because an important declaration in an earlier
   * layer wins - important declarations reverse layer order - and the * rule above is the
   * one being answered. */
  #scr-studio [data-builder-region="live-preview-workspace"] .builder-inline-preview-host {
    transform: scale(var(--pv-scale, 1)) translateX(-50%) !important;
  }
}

/* ── THE WORKSPACE ACCENT PICKER, HOISTED TO :root — task #56 ────────────────
 * The picker's eleven choices were declared at styles.css:1063 as
 *   #scr-studio[data-builder-color="ocean"] .builder-frame{--builder-accent:#125d82}
 * so the chosen colour existed only INSIDE .builder-frame, on one screen. A
 * button in the header, the dashboard tiles, the setup screen or the Trip AI
 * panel is not a descendant of .builder-frame, so no amount of tokenising
 * those sheets could have reached them: the variable was not in scope. That is
 * the half of the defect that is about scope rather than literals, and it is
 * why the owner sees "about one and a half buttons" move. Re-scoping one of
 * these rules back onto .builder-frame drops the measured reach from 111
 * surfaces to 43 - see the rescope-picker mutation in the test.
 *
 * :root:has(...) lifts the same attribute to the document root, where every
 * sheet can see it, WITHOUT app.js having to move the attribute. app.js sets
 * data-builder-color on #scr-studio (app.js:9589, from
 * state.appearance.tileColor) and index.html ships it there too.
 *
 * TWO THINGS HERE ARE NOT DECORATION:
 *
 * 1. The :has() argument keeps #scr-studio. The picker's own eleven radio
 *    inputs each carry data-builder-color as well, so :has([data-builder-color
 *    ="coral"]) would match on every screen forever and the last rule in this
 *    block would win permanently.
 *
 * 2. The :has() rules carry :not([data-accent]) so that html[data-accent]
 *    always wins. Specificity alone could not deliver that:
 *    :has() contributes the specificity of its argument, so
 *    :root:has(#scr-studio[...]) scores 1-2-0 - an ID - while
 *    :root[data-accent="coral"] scores only 0-2-0 and can never outrank it,
 *    whatever the source order. Measured: with both present, --accent-source
 *    stayed #0e7c86. Making the two sets MUTUALLY EXCLUSIVE removes the
 *    question from the cascade instead of trying to win it.
 *
 * html[data-accent] is the knob for a caller that has no studio screen - the
 * published trip page and the #60/#63 preview top bar - and for #53, which
 * needs to set an accent without going through the workspace picker.
 *
 * "teal" is the product default and is stated explicitly so that returning to
 * it restores the measured ramp rather than falling through to whatever a
 * previous choice left behind. The eleven colours are the ones measured out of
 * styles.css:1063, unchanged. */
:root:not([data-accent]):has(#scr-studio[data-builder-color="teal"]) { --accent-source: #0e7c86 }
:root:not([data-accent]):has(#scr-studio[data-builder-color="ocean"]) { --accent-source: #125d82 }
:root:not([data-accent]):has(#scr-studio[data-builder-color="sky"]) { --accent-source: #3d8fc7 }
:root:not([data-accent]):has(#scr-studio[data-builder-color="green"]) { --accent-source: #3d7c56 }
:root:not([data-accent]):has(#scr-studio[data-builder-color="violet"]) { --accent-source: #7357a6 }
:root:not([data-accent]):has(#scr-studio[data-builder-color="plum"]) { --accent-source: #8f4668 }
:root:not([data-accent]):has(#scr-studio[data-builder-color="coral"]) { --accent-source: #c75849 }
:root:not([data-accent]):has(#scr-studio[data-builder-color="amber"]) { --accent-source: #a76e08 }
:root:not([data-accent]):has(#scr-studio[data-builder-color="rose"]) { --accent-source: #b64f6d }
:root:not([data-accent]):has(#scr-studio[data-builder-color="graphite"]) { --accent-source: #4e5964 }
:root:not([data-accent]):has(#scr-studio[data-builder-color="neutral"]) { --accent-source: #68777b }

/* An explicit accent always wins over whatever the workspace picker says. */
:root[data-accent="teal"] { --accent-source: #0e7c86 }
:root[data-accent="ocean"] { --accent-source: #125d82 }
:root[data-accent="sky"] { --accent-source: #3d8fc7 }
:root[data-accent="green"] { --accent-source: #3d7c56 }
:root[data-accent="violet"] { --accent-source: #7357a6 }
:root[data-accent="plum"] { --accent-source: #8f4668 }
:root[data-accent="coral"] { --accent-source: #c75849 }
:root[data-accent="amber"] { --accent-source: #a76e08 }
:root[data-accent="rose"] { --accent-source: #b64f6d }
:root[data-accent="graphite"] { --accent-source: #4e5964 }
:root[data-accent="neutral"] { --accent-source: #68777b }
