/* tw-metro-tokens.css - THE STUDIO'S DESIGN LANGUAGE.
 *
 * Ruled by Andrew, 2026-08-20: Metro is the single Studio design language,
 * mobile and desktop, in BOTH themes. This supersedes the warm "Editorial
 * Instrument" token set for every Studio surface. Tonight, Host and Venue keep
 * their own profiles and must NOT load this file.
 *
 * Governing page: /studio/research/visual-excellence-system
 *
 * ── THE THREE-STATE THEME CONTRACT ──────────────────────────────────────────
 * The bare :root carries the COMPLETE light palette, because the default
 * "system" setting stamps nothing on the root element and only
 * prefers-color-scheme separates light from dark. Dark is redefined twice: in
 * the media query (guarded so an explicit light choice beats a dark OS) and
 * under [data-theme="dark"] (so a toggle wins in both directions).
 *
 * NEVER give a colour its only definition inside a media or [data-theme] block.
 * That colour then never applies in the un-stamped state, and the surface
 * renders one theme's text on the other theme's ground.
 *
 * ── AN ACCENT HAS THREE VALUES, NOT ONE ─────────────────────────────────────
 * Learned building the light theme:
 *   --x        FILL   saturated; identical in both themes; a block works on
 *                     white or black
 *   --x-ink    ON-FILL the ink that sits on that fill; dark in both themes
 *   --x-text   TEXT   the accent used as type. It MUST darken on a light
 *                     ground or it fails contrast. Gold #FFC220 is unreadable
 *                     on white; its light value is #8A6500 - the old brass.
 * Using a FILL value as text is the mistake this structure exists to prevent.
 *
 * ── COLOUR IS ASSIGNED BY FAMILY, NOT BY MECHANIC ───────────────────────────
 * Measured 2026-08-20: 78 mechanics registered, 46 with entries. Nobody learns
 * 46 colours and there are not 46 distinguishable hues. Six families are
 * learnable AND encode something true about the content. A bank sets its family
 * once (see the .fam-* classes) and the whole surface repaints.
 *
 * ── IDENTITY AND STATE SEPARATE BY FORM, NOT HUE ────────────────────────────
 * Sound gold sits near warning amber and no palette juggling fixes that safely.
 * So they never take the same shape:
 *     FAMILY COLOUR FILLS TILES.  STATE IS ONLY EVER A CHIP, DOT OR STRIPE.
 * A filled block is always identity. A small mark is always state.
 */

:root {
  /* ══ SURFACES - light ══ */
  --m-ground:      #FFFFFF;
  --m-tile:        #F1F1F1;
  --m-tile-2:      #E6E6E6;
  --m-edge:        #D6D6D6;
  /* ── THE CONTROL BOUNDARY, and why it is a second token ──────────────
     --m-edge is 1.45:1 on this ground. That is fine for a divider: WCAG
     1.4.11 exempts decoration, and a hairline between two areas you can
     already tell apart carries no information. It is NOT fine for the only
     line that says "this is a field" - that boundary IS the component, so
     1.4.11 asks for 3:1. Measured 2026-09-06; #909090 is 3.19:1 here.
     Use --m-edge to separate. Use --m-edge-strong to BOUND a control. */
  --m-edge-strong: #909090;

  /* ══ ACTION - what happens if I touch this ═══════════════════════════
     Ruled 2026-09-06. Colour in the CHROME answers one question, and it is
     not "what kind of question is this" - the mechanic's name is already on
     screen. It is "what will this DO", which nothing else on screen says.

     Exactly TWO hues, and the count is measured, not chosen. A third
     (an amber for "the room can see this") simulates to dE 3.6 against
     danger-red under deuteranopia - the same colour to roughly one man in
     twelve. Commit-blue against danger-red is dE >= 91 under every
     simulation, normal and all three dichromacies. So LIVE is a treatment
     and a word, never a third hue.

     Everything else stays NEUTRAL. Measured 2026-09-06: the estate's five
     commonest button labels are cancel, save, close, edit, next - four of
     the five reversible. Colour that lands on everything reports nothing. */
  /* ══ THE PRIMARY ACTION IS A NAMED PAIR, AND IT IS INK ════════════════
     Andrew, ruling 1 of 2026-09-08 (the visual study's A11): "the primary
     action is INK, as drawn. .m-btn--key repaints from blue to --m-ink
     estate-wide. Blue --m-act is no longer a button colour."
     It is a PAIR because the fill and the label are one decision:
     _test-v7-the-chrome-wears-the-kit.js holds the invariant that a button's
     label comes from its fill's own ink token, "never white-on-yellow", and
     that invariant is worth more than the two names it costs. 19.80:1.
     ⚠ The pair is where the primary colour lives now. A surface that wants
       to repaint the primary action changes THESE, not forty call sites. */
  --m-key:         var(--m-ink);
  --m-key-ink:     var(--m-ground);
  /* ⚠ --m-act IS NO LONGER A BUTTON FILL. It remains because --m-focus is
     this value and a focus RING is a state drawn OUTSIDE the control - which
     is the form/hue split working, not an exception to it. */
  --m-act:         #1F5FD0;   /* focus ring only since 2026-09-08  5.82:1 */
  --m-act-2:       #174CAB;   /* its hover - darker, not lighter */
  --m-act-ink:     #FFFFFF;   /* on the commit fill            5.82:1 */
  --m-act-danger:  #B02A16;   /* irreversible: delete, remove  6.59:1 */
  --m-act-danger-2:#8C2011;
  --m-act-danger-ink: #FFFFFF;

  /* ── THE FOCUS RING IS FIXED, and that is the point ──────────────────
     It was var(--m-accent), which .fam-* re-points - so the ring that says
     "you are here" changed hue depending on which mechanic was open. A
     keyboard user cannot learn a landmark that moves. */
  --m-focus:       #1F5FD0;

  /* ELEVATION - the OPENED entry, ruled 2026-08-21. A tile step is already
     directional: tile -> tile-2 darkens on light and LIGHTENS on dark. These
     continue that same step rather than inventing a new idea.
     ⚠ DO NOT COLLAPSE THIS BACK INTO --m-tile-2 TO SAVE A TOKEN.
     It sits one rung PAST tile-2 for exactly one reason: .row:hover is already
     tile-2, so reusing it paints a hovered sibling and the opened card
     identically at the moment the user is pointing at something. Collapsing
     them reintroduces that bug. Values chosen 2026-08-21; the step sizes are
     near-symmetric in the two themes on purpose, so the ladder reads the same
     in both rather than being tuned for light and inherited badly by dark.
     NEUTRAL BY RULE - never tinted toward the family accent. Family colour is
     identity; this is which entry is open. */
  --m-tile-open:       #DEDEDE;
  --m-tile-open-inset: #D0D0D0;

  /* ══ INK - light ══ */
  --m-ink:         #0A0A0A;
  --m-ink-2:       #3B3B3B;
  --m-ink-3:       #6E6E6E;

  /* ══ FAMILY FILLS - identical in both themes ══ */
  --m-sound:       #FFC220;  --m-sound-2:      #FFD75E;
  --m-picture:     #00B7C3;  --m-picture-2:    #5AD9E1;
  --m-word:        #A855F7;  --m-word-2:       #C89BFF;
  --m-wordplay:    #EC4899;  --m-wordplay-2:   #F786BA;
  --m-knowledge:   #3B82F6;  --m-knowledge-2:  #7FB0FF;
  --m-constraint:  #00C9A7;  --m-constraint-2: #5FE3CC;

  /* ══ INK ON A FILL - dark in both themes ══ */
  --m-sound-ink:      #1A1200;
  --m-picture-ink:    #001416;
  --m-word-ink:       #180A2A;
  --m-wordplay-ink:   #2A0715;
  --m-knowledge-ink:  #04122B;
  --m-constraint-ink: #00201A;

  /* ══ ACCENT AS TEXT - darkened for the light ground ══ */
  --m-sound-text:      #8A6500;
  --m-picture-text:    #00707A;
  --m-word-text:       #6B21A8;
  --m-wordplay-text:   #A81E5C;
  --m-knowledge-text:  #1D4FBF;
  --m-constraint-text: #00705E;

  /* ══ THE CURRENT BANK'S FAMILY (re-pointed by .fam-*) ══ */
  --m-accent:      var(--m-sound);
  --m-accent-2:    var(--m-sound-2);
  --m-accent-ink:  var(--m-sound-ink);
  --m-accent-text: var(--m-sound-text);

  /* ══ THE AUDIO WORLD - reserved long before this language existed ══
     The audio-trim tokens sat unused in tw-tokens.css. Audio keeps its own
     place, distinct from words, in both themes. */
  --m-audio:      #EDE9FB;
  --m-audio-2:    #C9BCE8;
  --m-audio-ink:  #2E2360;
  --m-audio-wave: #D6CCF0;

  /* ══ THE VENUE STAGE - identical in both themes, NEVER redefined below ══
   * The venue television is always the dark stage, whatever theme the host
   * device runs. These are the live Scoreboard overlay's venue values, lifted
   * here so the Fact or Fail overlay (tw-overlay-fact-or-fail.js) draws from
   * tokens instead of literals. Gold is --m-sound. */
  --m-stage-ground: #171722;
  --m-stage-tile:   #20202e;
  --m-stage-tile-2: #2a2a3a;
  --m-stage-mute:   #8d8da3;
  --m-stage-ink:    #FFFFFF;

  /* ══ MEDIA DEPENDENCY - a SECOND axis, and not the mechanic family ══
   * Andrew, 2026-09-07. On the book's left cell and the editor rail, colour
   * answers ONE question: what has to be working for this question to be
   * askable. It is deliberately not the family - a Music 3-Part delivered as
   * video is BLUE here and stays in the sound family on the inspector header
   * (the artboards carry exactly that case as Q8).
   *
   * ★ THEY LIVED IN ONE SURFACE UNDER A PRIVATE NAME (measured 2026-09-07).
   * The four values existed only as --v7b-needs-* inside book_v7.html, plus a
   * third hand-typed copy of #E4E4E4 in server/tw-bbt-booklet-pdf.js. Three
   * homes for one legend is precisely what "design elements must be as
   * consistent as possible" forbids: the screen and the printed page could
   * drift apart with every gate green. One home, and the book aliases it.
   *
   * These are the PAPER values, and they are what both surfaces start from.
   * book_v7.html re-cuts them as dim washes of the same hue at the same rank
   * for the dark scheme, for a reason written down in that file: on the Book
   * they fill a cell on a rendered PAGE. The editor rail is chrome rather
   * than paper and takes them as they are. That is an EARNED difference and
   * this comment is where it is recorded. */
  --m-needs-screen: #F7DCE4;   /* needs the screen - anything pictured */
  --m-needs-audio:  #DDE9D8;   /* audio only, no video */
  --m-needs-video:  #D9E4F5;   /* has video */
  --m-needs-none:   #E4E4E4;   /* answerable read aloud */
  --m-needs-ink:    #0A0A0A;

  /* ══ THE INTERLUDE BANDS (added 2026-09-08) ══════════════════════════
   * The printed question book sets an interlude - PROMO SLIDE, PRIZE GAME -
   * as a coloured band in its chronological place, and these are the two
   * tones it uses: a label tile and a body band. They were literals inside
   * server/tw-bbt-booklet-pdf.js (PRIZE_LABEL / PRIZE_BODY) and nowhere else,
   * so the Book view - which omitted the bands entirely until Andrew put the
   * two books side by side on 2026-09-08 - had nothing to read.
   * They live HERE for the same reason --m-needs-* does: the PDF renderer
   * already parses this file, and one home is the only way two renderers of
   * one page agree. They are NOT a fifth media-need value and never fill a
   * question row: an interlude is not a question, which is the whole point of
   * the band.
   * Andrew 2026-09-01: one interlude colour, two interlude kinds - the promo
   * band was yellow and shouted louder than anything a host reads. */
  --m-interlude-label: #F4B083;   /* the PRIZE GAME tile */
  --m-interlude-body:  #FBE4D5;   /* the band itself, both kinds */

  /* ══ STATE - never a tile fill; chips, dots and stripes only ══ */
  --m-ok:   #1E7A34;  --m-ok-bg:   #E3F3E7;
  --m-warn: #8A5100;  --m-warn-bg: #FBEEDC;
  --m-err:  #B02A16;  --m-err-bg:  #FBE4E0;
  /* RETIRED is not an alarm. It is a record standing aside, so it is the
     one state colour that is deliberately GREY - reserving red for a
     genuinely blocked or destructive state, per the Metro colour rule. */
  --m-rest: #5A5A5A;  --m-rest-bg: #ECECEC;

  /* ══ TYPE - Gotham's eight weights carry the hierarchy alone.
     The serif was dropped 2026-08-20. Mono is JetBrains Mono, self-hosted. ══ */
  --m-sans: 'Gotham','Gotham Narrow','Montserrat','Segoe UI Variable','Segoe UI',
            -apple-system,system-ui,sans-serif;
  --m-mono: 'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;

  --m-display:   100 76px/78px var(--m-sans);
  --m-title:     200 46px/50px var(--m-sans);
  --m-heading:   300 27px/32px var(--m-sans);
  --m-subhead:   300 22px/27px var(--m-sans);
  --m-body:      400 15px/1.55 var(--m-sans);
  --m-body-lite: 300 15px/1.6  var(--m-sans);
  --m-micro:     500 11px/1    var(--m-sans);   /* pair with --m-track */
  --m-data:      400 13px/1.4  var(--m-mono);

  /* ── VENUE DISTANCE ─────────────────────────────────────────────────────
   * The ladder above is a DESK-DISTANCE instrument: fixed pixel steps, read
   * at arm's length on a Studio monitor. The venue television is read across
   * a dim room, and a fixed 76px headline is either shouting on a laptop
   * preview or lost on a projector. These steps are the SAME language at
   * another distance - same family, same sub-500 weights - but fluid, so the
   * size follows the surface it is thrown onto.
   *
   * ★ THIS IS NOT AN EXEMPTION FROM THE LADDER, IT IS A SECOND RUNG OF IT.
   *   slides_display.html carried clamp(28px,4vw,64px) hand-typed, and the
   *   first instinct on adopting the page was to "fix" it onto --m-display.
   *   That instinct was wrong: the clamp was the considered choice and the
   *   fixed step would have been the regression. Use these ONLY on surfaces
   *   an audience reads from across a room; a Studio panel that reaches for
   *   them is saying it is a stage, and it is not.
   */
  --m-venue-display: 200 clamp(28px,4vw,64px)/1.08   var(--m-sans);
  --m-venue-subhead: 300 clamp(22px,2vw,36px)/1.2    var(--m-sans);
  --m-venue-body:    300 clamp(16px,1.8vw,28px)/1.35 var(--m-sans);
  --m-venue-status:  400 clamp(15px,1.4vw,22px)/1.2  var(--m-sans);
  --m-venue-micro:   500 clamp(12px,1vw,16px)/1      var(--m-sans);

  /* ★ THE COCKPIT STEP - RULED 2026-08-31 (bible-design-language).
   * "ALL recommendations from UX report adopted." (Andrew, 2026-08-31)
   *
   * The ladder above tops at 500, which is a DESK ladder: display 100 is
   * Gotham Thin and it is correct at arm's length and a hairline at four
   * metres. Three surface classes asked for the same heavier step
   * independently - the car surface (2026-08-23), a lectern, and the nine
   * host/venue surfaces measured 2026-08-31 running 700-800 across their
   * chrome. Three requests is the ladder failing to express something real,
   * not three careless authors, so the step is a RUNG rather than nine
   * silent deviations.
   *
   * ⚠ IT IS EARNED BY READING DISTANCE, NOT BY IMPORTANCE. A dense desk
   *   list is not a cockpit because its author wants it noticed.
   * ⚠ IT IS A WEIGHT, NOT A LICENCE. Size, family and tracking still come
   *   from the house scale - overriding only the weight is the whole
   *   deviation, exactly as the Capture TAP control did it.
   * ⚠ IT DOES NOT REACH THE SHOW'S TYPOGRAPHY. <text>/<tspan> inside the
   *   canonical slide are the show's own type and the bible exempts them. */
  --m-cockpit-weight: 700;
  --m-cockpit-strong: 800;
  --m-track:     .18em;                          /* ALL CAPS letter-spacing */
  /* The wordmark's two weights, named. They are a RELATIONSHIP - the second
     word is the heavier one (Andrew: Works is bold) - and check G in
     _test-ux-components-speak-one-language.js asserts the order, not the
     numbers, so a later change of scale cannot silently invert the mark. */
  --m-mark-w1:    300;
  --m-mark-w2:    700;
  /* ★ THE WORDMARK'S OWN TRACK, and the ONLY all-caps run that is not
     --m-track (2026-09-08). TopBar.dc.html:60 draws the mark at .2em - a
     LOGO is set once and read as a shape, not as a label in a column of
     labels, so it does not follow the label track. It gets a NAME rather
     than a hand-typed .2em, because a rule stated in a raw number is a rule
     nobody can apply, and because the gate that counts un-named trackings
     should count this as answered rather than as the 153rd offender. */
  --m-track-mark: .2em;

  /* ══ THE TARGET AND THE MARK - two numbers, not one ═══════════════════
     Ruled 2026-09-07 from the Components artboard. Andrew, on the Bank and
     the Inspector side by side: "the tick boxes have no specified size".
     They had none because there was no token to have - every tick in the
     estate was an accent-coloured UA checkbox at whatever size that
     browser draws, which is 13px on one platform and 16px on another.

     TWO numbers, because a target and a mark are different things. The
     TARGET is what a thumb hits and is the estate's 44px floor. The MARK is
     what the eye reads and is 20px, sitting centred inside it with 12px of
     silent target all round. A 44px box would be a huge tick; a 20px target
     would be a control half the operators cannot press. Square, like
     everything: radius is 0. */
  --m-target:     44px;
  --m-target-box: 20px;

  /* ══ RHYTHM - Metro gutters are TIGHT; the grid does the separating ══
   * ★ RULED 2026-09-06. Spacing was the one chapter of the language with
   *   no scale at all: three gutter tokens, and 339 hand-typed pixel
   *   values across the shared layer spread over 34 distinct numbers from
   *   1px to 90px. Nothing in that is a system - a value that appears
   *   once is a value nobody can match, and 14/10/18 outnumbered every
   *   token.
   *
   *   The scale is a 4px base grid, which is the published choice of
   *   every major system (Material, Carbon, Tailwind) and not a
   *   preference of mine. It is doubled at each end for the two cases a
   *   linear grid serves badly: hairline separation below 4px, and
   *   section breaks above 32px.
   *
   *   THE SCALE IS THE ONLY SPACING VOCABULARY. A gap the scale cannot
   *   express is a layout problem, not a missing token: reach for the
   *   grid before you reach for a new number. */
  --m-s-hair:  1px;   /* rule weight, not a gap                  */
  --m-s-tight: 2px;
  --m-s-1:     4px;
  --m-s-2:     8px;
  --m-s-3:    12px;
  --m-s-4:    16px;
  --m-s-5:    20px;
  --m-s-6:    24px;
  --m-s-8:    32px;
  --m-s-10:   40px;
  --m-s-12:   48px;   /* section break; above this, use the grid  */

  /* The three historic gutters, kept as ROLE names and re-pointed at the
   * scale. --m-pad-lg was 30px, which sat off the grid it now defines;
   * 32px is the same gutter to the eye and on the scale in the file. */
  /* ★ --m-gap is 6px and sits OFF the 4px grid, on purpose and as the one
   *   admitted exception. It is the Metro tile gutter: the gap that makes
   *   a row of tiles read as tiles and not as one block. 4px closes it to
   *   a seam and 8px opens it to a margin; it has been 6px on every
   *   Metro surface since the language started, and re-pitching every
   *   tile grid in the estate to move it one pixel would be a visible
   *   change to surfaces Andrew uses daily, made to satisfy a scale
   *   written this week. The exception is named here rather than left as
   *   drift, and 6px is admitted by the gate for this token ALONE. */
  --m-gap:    6px;
  --m-pad:    var(--m-s-5);
  --m-pad-lg: var(--m-s-8);
  /* The touch target and the box inside it (bible-components C1, C5). 44px is
     the commonest number in the estate's component CSS and had no name. */
  --m-target: 44px;
  --m-target-box: 20px;
  --m-radius: 0;    /* not a variable to change. Metro has no rounded corners. */

  /* ══ MOTION ═══════════════════════════════════════════════════════════
   * ★ RULED 2026-09-06. Motion was the one chapter of the language with a
   *   rule and no tokens: "fast and directional, never decorative", stated
   *   once in 2026-08-20 and never given a value to be fast BY. The layer
   *   held six durations and four easings, arrived at one declaration at a
   *   time. Three durations and two easings is not a compromise between
   *   them; it is the shape every mature system converges on, because the
   *   eye distinguishes about that many and no more.
   *
   * ⚠ THE VALUES ARE THE ONES ALREADY IN USE, not new ones. .12s was seven
   *   of the nine transitions in the layer and .34s was the entrance; the
   *   only value retired is a lone .14s that had no reason to differ. A
   *   token pass that also changes the timing cannot be reviewed, because
   *   nobody can tell which difference they are looking at.
   *
   * ★ WHICH ONE TO REACH FOR. Directional, not decorative, decides it:
   *     -state   a tint, a border, a background - the thing did not MOVE.
   *     -move    something changed position or rotated in place.
   *     -enter   something arrived that was not on screen before.
   *   If you cannot say which of the three a change is, it is decoration
   *   and the answer is no transition at all. */
  --m-dur-state: .12s;
  --m-dur-move:  .18s;
  --m-dur-enter: .34s;

  /* ⚠ LINEAR IS DELIBERATE FOR STATE, and it is the one people "correct".
   *   Easing describes a body with mass accelerating and settling. A tint
   *   changing has no mass and no distance to cross, so an eased colour
   *   fade reads as a slow start rather than as physics. Metro spends its
   *   brightness on blocks; it does not pretend they are objects. */
  --m-ease-state: linear;
  --m-ease-enter: cubic-bezier(.1, .9, .2, 1);

  /* The entrance stagger, as a unit rather than a ladder of literals. The
   * nth child waits n of these; see .m-enter in tw-metro-kit.css. */
  --m-stagger: .04s;

  /* ★ THE ONE SLIDE-CONTENT COLOUR THE CHROME NEEDS (2026-08-23).
   * A scrim is painted on the SLIDE, not the interface, so it is dark in both
   * themes - it clears space on a photograph and a light scrim clears nothing.
   * The Bank previews one while the author drags it, and the alternative was a
   * hex literal in the modal, which the modal contract refuses.
   * ⚠ DEFINED IN THE SCHEME, NOT HERE. one-colour-system counts every hex
   *   outside tw-metro-scheme.css as sprawl and ratchets it down; putting the
   *   value in this file raised that count by one and failed the gate, which
   *   was right - the scheme is the declared single source for colour. */
  --m-scrim: var(--tw-scrim);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* ★ THE DARK GROUND IS THE SCHEME'S NAVY, NOT GREY (2026-08-22).
     * Before this, the kit's dark world was achromatic (#0A0A0A / #1A1A1A)
     * while tw-metro-scheme.css defined a navy one - so a .m-tile rendered on
     * a scheme page produced grey boxes on navy, with the kit's #FFC220 gold
     * arguing with the scheme's #E3AD49 brass. Two palettes, one estate,
     * declared as "one coherent design system". Re-pointing here is what
     * makes that sentence true, and it is one edit rather than a migration
     * of every consumer.
     * Requires tw-metro-scheme.css to be loaded FIRST.
     *
     * ★ NO HEX FALLBACKS HERE, DELIBERATELY (2026-08-27). These were written
     *   as var(--tw-navy-N, hand-copied hex). Measured across the whole file
     *   that day: 14 tokens carried a fallback and SIX had already drifted
     *   from the scheme they copied - navy-2, navy-3, navy-4, navy-5, navy-6
     *   and navy-10, every one darker than the derived LCh ramp, and all six
     *   still passing AA so no contrast gate would ever have said so. (The
     *   drifted values are not quoted here: a hex in a comment is one more
     *   home for it. Compare against tw-metro-scheme.css, the single source.)
     *   That is the ONE THING, TWO HOMES fault: the value had two names, so
     *   it drifted, and nothing in the estate could see it drift.
     *   A fallback here cannot help in any case - scheme-before-tokens proves
     *   the scheme loads first on every Metro surface - so all it did was hold
     *   a second, staler copy and inflate one-colour-system's sprawl count.
     *   If the scheme ever goes missing the surface must break LOUDLY rather
     *   than quietly repaint itself in six wrong greys that still pass AA.
     *   ⚠ DO NOT REINTRODUCE A FALLBACK. Follow --m-scrim above: bare var(). */
    --m-ground: var(--tw-navy-1);
    --m-tile:   var(--tw-navy-2);
    --m-tile-2: var(--tw-navy-3);
    --m-tile-open:       var(--tw-navy-4);
    --m-tile-open-inset: var(--tw-navy-5);
    --m-edge:   var(--tw-navy-6);
    --m-edge-strong: var(--tw-navy-7);   /* 3.36:1 on navy-1 */
    --m-act:         #5B9BFF;   /* 6.30:1 on navy-1 */
    --m-act-2:       #8FBBFF;
    --m-act-ink:     #06132B;
    --m-act-danger:  #FFABA5;
    --m-act-danger-2:#FFD1CD;
    --m-act-danger-ink: #2B0906;
    --m-focus:       #5B9BFF;

    --m-ink:    var(--tw-navy-12);
    --m-ink-2:  var(--tw-navy-11);
    --m-ink-3:  var(--tw-navy-10);
    /* fills and on-fill inks are unchanged - only TEXT values brighten */
    /* the accent in dark is the scheme's BRASS, not the kit's brighter gold */
    --m-sound-text:      var(--tw-brass-11);
    --m-picture-text:    #4FD2DC;
    --m-word-text:       #C89BFF;
    --m-wordplay-text:   #F786BA;
    --m-knowledge-text:  #7FB0FF;
    --m-constraint-text: #5FE3CC;
    --m-accent-text:     var(--m-sound-text);
    --m-audio:      #332880;
    --m-audio-2:    #4B3CA8;
    --m-audio-ink:  #D9CBFF;
    --m-audio-wave: #5B4CC4;
    --m-ok:   var(--tw-green-11);  --m-ok-bg:   var(--tw-green-3);
    --m-warn: #FFA63D;  --m-warn-bg: #2A1D0C;
    --m-err:  var(--tw-red-11);   --m-err-bg:  var(--tw-red-3);
    --m-rest: #A8A8A8;  --m-rest-bg: #232323;
  }
}

:root[data-theme="dark"] {
  /* ★ MUST MATCH THE MEDIA-QUERY BLOCK ABOVE, VALUE FOR VALUE. The Capture
   * PWA sets data-theme="dark" on <html> explicitly, so this block - not the
   * media query - is what that surface actually gets. Leaving it grey while
   * the media query went navy would have produced two different products
   * depending on how the reader arrived at dark. Change one, change both. */
  --m-ground: var(--tw-navy-1);
  --m-tile:   var(--tw-navy-2);
  --m-tile-2: var(--tw-navy-3);
  --m-tile-open:       var(--tw-navy-4);
  --m-tile-open-inset: var(--tw-navy-5);
  --m-edge:   var(--tw-navy-6);
  --m-edge-strong: var(--tw-navy-7);   /* 3.36:1 on navy-1 */
  --m-act:     #5B9BFF;   /* 6.30:1 on navy-1 */
  --m-act-2:     #8FBBFF;
  --m-act-ink:   #06132B;
  --m-act-danger:  #FFABA5;
  --m-act-danger-2:#FFD1CD;
  --m-act-danger-ink: #2B0906;
  --m-focus:     #5B9BFF;

  --m-ink:    var(--tw-navy-12);
  --m-ink-2:  var(--tw-navy-11);
  --m-ink-3:  var(--tw-navy-10);
  --m-sound-text:      var(--tw-brass-11);
  --m-picture-text:    #4FD2DC;
  --m-word-text:       #C89BFF;
  --m-wordplay-text:   #F786BA;
  --m-knowledge-text:  #7FB0FF;
  --m-constraint-text: #5FE3CC;
  --m-accent-text:     var(--m-sound-text);
  --m-audio:      #332880;
  --m-audio-2:    #4B3CA8;
  --m-audio-ink:  #D9CBFF;
  --m-audio-wave: #5B4CC4;
  --m-ok:   var(--tw-green-11);  --m-ok-bg:   var(--tw-green-3);
  --m-warn: #FFA63D;  --m-warn-bg: #2A1D0C;
  --m-err:  var(--tw-red-11);   --m-err-bg:  var(--tw-red-3);
  --m-rest: #A8A8A8;  --m-rest-bg: #232323;
}

/* ══ FAMILY SELECTION - one class repaints a whole bank ══ */
.fam-sound      { --m-accent:var(--m-sound);      --m-accent-2:var(--m-sound-2);
                  --m-accent-ink:var(--m-sound-ink);      --m-accent-text:var(--m-sound-text); }
.fam-picture    { --m-accent:var(--m-picture);    --m-accent-2:var(--m-picture-2);
                  --m-accent-ink:var(--m-picture-ink);    --m-accent-text:var(--m-picture-text); }
.fam-word       { --m-accent:var(--m-word);       --m-accent-2:var(--m-word-2);
                  --m-accent-ink:var(--m-word-ink);       --m-accent-text:var(--m-word-text); }
.fam-wordplay   { --m-accent:var(--m-wordplay);   --m-accent-2:var(--m-wordplay-2);
                  --m-accent-ink:var(--m-wordplay-ink);   --m-accent-text:var(--m-wordplay-text); }
.fam-knowledge  { --m-accent:var(--m-knowledge);  --m-accent-2:var(--m-knowledge-2);
                  --m-accent-ink:var(--m-knowledge-ink);  --m-accent-text:var(--m-knowledge-text); }
.fam-constraint { --m-accent:var(--m-constraint); --m-accent-2:var(--m-constraint-2);
                  --m-accent-ink:var(--m-constraint-ink); --m-accent-text:var(--m-constraint-text); }


/* ══ REDUCED MOTION ═══════════════════════════════════════════════════════
 * ★ WCAG 2.3.3, honoured at the TOKEN and not at the call site. Before this,
 *   two of the ten stylesheets carried a reduce block and eight did not, so
 *   whether a surface respected the setting depended on which file happened
 *   to style it. Zeroing the durations here reaches every consumer in the
 *   layer, including the ones written next year.
 *
 * ⚠ THE DURATIONS GO TO ZERO; THE EASINGS AND THE FILL DO NOT CHANGE. An
 *   animation declared `both` still applies its end state at 0s, which is
 *   the behaviour we want: the element arrives, it simply does not travel.
 *   Removing the animation instead is what once left rows stuck at the
 *   opacity a filled animation had settled on - see the note in
 *   tw-metro-shell.css. Reduced motion must not become no state at all. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --m-dur-state: 0s;
    --m-dur-move:  0s;
    --m-dur-enter: 0s;
    --m-stagger:   0s;
  }
}
