/*
 * FridayFika — design tokens
 *
 * Direction: **fika.** Enamel, ceramic and birch, in the light of a Nordic
 * office at three in the afternoon when the lamps come on.
 *
 * The product is named after the Swedish coffee break and for a long time took
 * nothing from it but a coffee ring on a card back. This is that world properly
 * mined: the deep petrol of old enamelware, the warm off-white of its glaze,
 * cardamom, fired brick, birch.
 *
 * Four rules run through everything:
 *
 *   1. **The ground is a material, not a page.** Saturated enamel, faintly
 *      speckled. This is load-bearing: the design it replaced put objects on a
 *      neutral table, so every gap read as *missing*. On a saturated ground a
 *      gap reads as *surface*. The cure for the empty screens is in the
 *      material, which is what stops it coming back one layout at a time.
 *   2. **Every object has a rim.** A bright fill inside a heavy dark edge with
 *      a thin line of glaze just inside it, the way a chipped enamel mug is.
 *      This is the signature, and it is also the mechanism: separation comes
 *      from the rim rather than from luminance, which is what lets an object
 *      keep its identity when the room's light changes.
 *   3. **Colour says what phase it is, never who you are.** A private card is
 *      monochrome. Two people holding different cards must have pixel-identical
 *      screens, or the game leaks across a shared desk.
 *   4. **Strongest is furthest from the ground.** In the evening that is warm
 *      and bright; by daylight it is dark and deep. The primary flips; the
 *      hierarchy does not.
 */

/* ------------------------------------------------------------------ fonts */

/*
 * Self-hosted. This product's pitch is surviving hostile corporate networks;
 * the reasoning that ruled out WebRTC in §12 rules out a font CDN.
 *
 * Familjen Grotesk is drawn by Letters from Sweden for Swedish public identity
 * — chosen because of what this is, rather than reached for. Hanken Grotesk is
 * the humanist body face beside it. Both ship one variable file per subset.
 * Plex Mono stays: it is genuinely good at data, already subset, and the change
 * of voice is carried by the ground, the rim and the display face.
 *
 * Latin loads for everyone; latin-ext is behind its unicode-range and arrives
 * only for a player whose name needs it — which for å, ä and ö is most of them.
 */
@font-face {
  font-family: "Familjen Grotesk";
  src: url("../assets/fonts/familjen-var-latin.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
    U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Familjen Grotesk";
  src: url("../assets/fonts/familjen-var-latin-ext.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

@font-face {
  font-family: "Hanken Grotesk";
  src: url("../assets/fonts/hanken-var-latin.woff2") format("woff2");
  font-weight: 300 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
    U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Hanken Grotesk";
  src: url("../assets/fonts/hanken-var-latin-ext.woff2") format("woff2");
  font-weight: 300 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("../assets/fonts/plex-mono-500-latin.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
    U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("../assets/fonts/plex-mono-500-latin-ext.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("../assets/fonts/plex-mono-600-latin.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
    U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("../assets/fonts/plex-mono-600-latin-ext.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

/*
 * ------------------------------------------------------- the style faces
 *
 * The three faces above are the room's own voice and dress every surface that
 * belongs to the table. A *boxed game* does not follow the tablecloth (see
 * "a boxed game does not follow the tablecloth"), and neither does its
 * lettering: a game whose cover promises a woodcut, a marquee or a chalkboard
 * carries a face to match, scoped on its own stage and never leaking to the
 * room. These load only when a game that wants one mounts — the room never
 * pays for them. All OFL / open-licence, self-hosted beside the room's faces,
 * subset to latin + latin-ext like the rest.
 *
 * Rye — a distressed wood-type slab. One Night's woodcut headline face.
 */
@font-face {
  font-family: "Rye";
  src: url("../assets/fonts/rye-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
    U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Rye";
  src: url("../assets/fonts/rye-latin-ext.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

/*
 * ------------------------------------------------------------ the palettes
 *
 * **Two orthogonal axes.** `data-theme` is the light in the room — daylight or
 * evening — and `data-palette` is what the room is made of. They compose:
 * fourteen palettes × two lights, and the switch for each is its own control in
 * the rail.
 *
 * They are grouped by **character rather than by hue**, because the first five
 * varied only in hue and therefore all read the same: every one was a deep
 * saturated ground under a warm cream, so picking between them was picking a
 * tint. The three groups differ in what kind of room they are.
 *
 *   **Classic** — muted, low chroma, nothing that would have looked strange in
 *   1965. The ground carries little colour by day.
 *
 *   **Bright** — the daylight ground *is* a colour. These are the only palettes
 *   where `--light-ground` has real chroma, and it changes the whole feel of a
 *   screen far more than swapping an accent does.
 *
 *   **Dark** — deep grounds and high contrast. Their daylight themes are quiet
 *   on purpose; these are built for the evening.
 *
 * Every palette fills the same twenty slots below, and the semantic roles
 * further down are written *once* in terms of those slots. So a new palette is
 * a colour decision and never a layout one, and nothing downstream can drift
 * per palette — there is only one mapping to keep honest.
 *
 * Each block is selected two ways. `:root[data-palette="x"]` themes the app;
 * `.pal-x` themes any single element, which is how the picker draws a live
 * swatch of a palette it is not currently wearing without a second copy of the
 * hexes to keep in step.
 *
 * The slots, and what each is for:
 *
 *   --deep …-2 …-lift …-pale   the saturated family. The ground at night; the
 *                              ink by day. `-2` recedes, `-lift` raises.
 *   --edge                     the rim on every object, and ink printed on pale
 *   --pale …-dim …-bright      the light material: cards, sheets, slips
 *   --light-ground …-deep      the ground by daylight
 *   --mid                      a tone between them: controls, a turned tile
 *   --accent …-lift …-deep     the primary at night; never a state
 *   --alert …-bright …-deep    one person being singled out
 *   --good …-bright …-deep     the table having agreed
 *
 * `--accent`, `--alert` and `--good` must stay tellable apart *by hue* in every
 * palette. They mean different things and two of them appear together.
 */

/* ============================ CLASSIC ==================================== */

/*
 * --- Björk. Birch paper and spruce, with a rowan orange. ---
 *
 * THE DEFAULT, AND THE ONLY PALETTE IN HERE THAT WAS MEASURED RATHER THAN
 * CHOSEN. Every value below is sampled from the design Erik supplied on
 * 2026-08-14, pixel by pixel, rather than matched by eye:
 *
 *   table          #fdf6eb   the frame's own ground
 *   card fill      #fdf6eb   the same — the boxes are not a lighter material
 *   strip fill     #f5eada   the panel the page closes on
 *   ink            #08302f   headline, titles, body: near-black, spruce cast
 *   primary        #074325   the filled buttons
 *   accent         #f4640a   the full stop, and nothing else on that page
 *
 * It breaks rule 1 at the top of this file — the ground here is paper, not
 * saturated enamel — and that is the point of it being a palette rather than a
 * rewrite. The twelve enamel rooms are untouched and one press away; this is
 * the room the product opens in.
 *
 * The three functional inks are hue-separated the way the contract above
 * demands, and it is tighter here than anywhere else because the accent is
 * already warm: orange for the accent, a wine crimson for a person being
 * singled out, and blue — not green — for the table having agreed, since a
 * green agreement on a spruce ground says nothing.
 */
:root,
:root[data-palette="bjork"],
.pal-bjork {
  --deep: #0d382e;
  --deep-2: #07261f;
  --deep-lift: #1a5142;
  --deep-pale: #2f6d5c;

  --edge: #041914;
  --pale: #fdf6eb;
  --pale-dim: #efe4d2;
  /*
   * The code field in the supplied design, measured — and NOT a near-white.
   *
   * The first cut of this palette put `--pale-bright` at #fffdf6, on the
   * reasoning that `--pale` was already near-white so its bright partner should
   * be brighter still. `belt-legible.spec.ts` failed on it: `--stock` is
   * `--pale-bright` by daylight, the Belt's gear is a 1.8px stroke of `--edge`
   * on that stock, and the antialiased skirt of a stroke that thin landed at
   * **2.83:1** across 11% of the glyph — under the 3:1 an object owes its
   * ground. Proven: green on the tree before this palette, red with it, green
   * again at the value below.
   */
  --pale-bright: #fdf7ed;

  --light-ground: #fdf6eb;
  --light-ground-deep: #f5eada;
  --mid: #e6d9c2;

  --accent: #f4640a;
  --accent-lift: #ff7c2b;
  /* Carries the headline's full stop on the near-white ground: 5.0:1, where
     the measured #f4640a is 2.7:1 and would read as a smudge. */
  --accent-deep: #b8460a;

  --alert: #9d2040;
  --alert-bright: #bd3557;
  --alert-deep: #7d132e;

  --good: #2a6288;
  --good-bright: #3a80ad;
  --good-deep: #1c4763;
}

/* --- Fika. Enamelware and the three o'clock lamp. Petrol, cardamom, brick. --- */
:root[data-palette="fika"],
.pal-fika {
  --deep: #17394a;
  --deep-2: #102934;
  --deep-lift: #1e4a5f;
  --deep-pale: #2b5568;

  --edge: #08161d;
  --pale: #f2ede1;
  --pale-dim: #dcd3c1;
  --pale-bright: #f9f6ee;

  --light-ground: #e8dcc4;
  --light-ground-deep: #dccfb3;
  --mid: #d3c4a4;

  --accent: #c98a2e;
  --accent-lift: #dd9c3c;
  --accent-deep: #9a6a22;

  --alert: #a6402e;
  --alert-bright: #c4553f;
  --alert-deep: #8e2e1e;

  --good: #2e6b5e;
  --good-bright: #3e8c7a;
  --good-deep: #1f5a4c;
}

/* --- Marin. Navy, cream and brass: the most conventional room on the shelf. --- */
:root[data-palette="marin"],
.pal-marin {
  --deep: #1b2a4a;
  --deep-2: #131e36;
  --deep-lift: #26385c;
  --deep-pale: #3b5080;

  --edge: #080f1f;
  --pale: #f2efe6;
  --pale-dim: #dcd7c9;
  --pale-bright: #faf8f2;

  --light-ground: #e4e3da;
  --light-ground-deep: #d6d5ca;
  --mid: #c6c7bc;

  --accent: #c9963f;
  --accent-lift: #dca950;
  --accent-deep: #8e6725;

  --alert: #9e3348;
  --alert-bright: #be4a60;
  --alert-deep: #7d2235;

  --good: #2e6656;
  --good-bright: #3f8770;
  --good-deep: #1d5040;
}

/* --- Bibliotek. Tobacco leather, parchment and faded gilt. --- */
:root[data-palette="bibliotek"],
.pal-bibliotek {
  --deep: #3a2e20;
  --deep-2: #2a2016;
  --deep-lift: #4e3f2d;
  --deep-pale: #6b5942;

  --edge: #150f08;
  --pale: #f2ead9;
  --pale-dim: #dcd0b9;
  --pale-bright: #f9f4e7;

  --light-ground: #e7ddc6;
  --light-ground-deep: #dacfb3;
  --mid: #c9b998;

  --accent: #b08333;
  --accent-lift: #c69746;
  --accent-deep: #815e21;

  --alert: #8e3a32;
  --alert-bright: #a94e44;
  --alert-deep: #6f2419;

  --good: #40705a;
  --good-bright: #55906f;
  --good-deep: #2a5140;
}

/* --- Skiffer. Slate, chalk and ochre. The coolest, quietest room here. --- */
:root[data-palette="skiffer"],
.pal-skiffer {
  --deep: #2e3944;
  --deep-2: #212a33;
  --deep-lift: #3e4c59;
  --deep-pale: #566878;

  --edge: #11171d;
  --pale: #f0f0ec;
  --pale-dim: #d8d8d2;
  --pale-bright: #f8f8f5;

  --light-ground: #e2e3e0;
  --light-ground-deep: #d4d6d2;
  --mid: #c2c5c1;

  --accent: #c08a3e;
  --accent-lift: #d49d4f;
  --accent-deep: #8a6127;

  --alert: #a03f32;
  --alert-bright: #bc5545;
  --alert-deep: #7d2c20;

  --good: #367063;
  --good-bright: #48907e;
  --good-deep: #235045;
}


/* ============================= BRIGHT ==================================== */

/*
 * These four put chroma in `--light-ground`, which none of the classic ones do.
 * That single slot is what makes a room feel bright — far more than the accent,
 * because it is most of what you are looking at.
 */

/* --- Citron. Lemon by day, deep olive at night. Teal, so it is not all warm. --- */
:root[data-palette="citron"],
.pal-citron {
  --deep: #3b3f1c;
  --deep-2: #2b2e13;
  --deep-lift: #4f5428;
  --deep-pale: #6b7139;

  --edge: #15170a;
  --pale: #fbfaef;
  --pale-dim: #e7e5d0;
  --pale-bright: #fefef7;

  --light-ground: #ecea92;
  --light-ground-deep: #dfdc79;
  --mid: #d0cc63;

  /* Light enough to take near-black text: the primary at night is a fill with
     `--edge` on it, and a deep teal under a near-black label measured 3.07:1. */
  --accent: #3ea3ae;
  --accent-lift: #55b8c2;
  --accent-deep: #226067;

  --alert: #b0301f;
  --alert-bright: #c9432f;
  --alert-deep: #8b2214;

  --good: #3a6b33;
  --good-bright: #4e8a44;
  --good-deep: #2a5024;
}

/* --- Mynta. Mint by day, deep pine at night. Gold, and blue for agreement. --- */
:root[data-palette="mynta"],
.pal-mynta {
  --deep: #1b4038;
  --deep-2: #12302a;
  --deep-lift: #275449;
  --deep-pale: #3c7365;

  --edge: #061a15;
  --pale: #f2f5ef;
  --pale-dim: #d9ded4;
  --pale-bright: #fafcf8;

  --light-ground: #cbe6da;
  --light-ground-deep: #b8dbcc;
  --mid: #a2cbb9;

  --accent: #c8992f;
  --accent-lift: #dbab41;
  --accent-deep: #8f6d1e;

  --alert: #b0392c;
  --alert-bright: #c74e3f;
  --alert-deep: #8c2a1e;

  /* Blue, not green: on a green ground a green "the table agreed" says nothing. */
  --good: #2a5f86;
  --good-bright: #3a7ca8;
  --good-deep: #1b4664;
}

/* --- Persika. Apricot by day, deep plum-brown at night. Teal against warm. --- */
:root[data-palette="persika"],
.pal-persika {
  --deep: #4a2a33;
  --deep-2: #371f26;
  --deep-lift: #613a45;
  --deep-pale: #80525e;

  --edge: #190b10;
  --pale: #fbf2ec;
  --pale-dim: #e8d9cf;
  --pale-bright: #fef8f4;

  --light-ground: #f4d3ba;
  --light-ground-deep: #e9c2a5;
  --mid: #ddaf8e;

  /* Same reason as Citron's: it has to carry near-black text as the primary. */
  --accent: #45aeb2;
  --accent-lift: #5cc4c8;
  --accent-deep: #23686b;

  --alert: #b23a2e;
  --alert-bright: #cb4e40;
  --alert-deep: #8f2a20;

  --good: #4a7a3a;
  --good-bright: #5f9a4c;
  --good-deep: #34592a;
}

/* --- Tegel. Fired brick and glazed tile: warm ground, brass, teal. --- */
:root[data-palette="tegel"],
.pal-tegel {
  --deep: #5a2418;
  --deep-2: #421a11;
  --deep-lift: #723526;
  --deep-pale: #8f4a38;

  --edge: #1f0a05;
  --pale: #f6ede3;
  --pale-dim: #e0d2c3;
  --pale-bright: #fcf6ef;

  --light-ground: #efdfce;
  --light-ground-deep: #e3d0bb;
  --mid: #d6bfa6;

  --accent: #d69a2b;
  --accent-lift: #e8ae3e;
  --accent-deep: #a87418;

  --alert: #c9482b;
  --alert-bright: #e2664a;
  --alert-deep: #99331a;

  --good: #2f7d74;
  --good-bright: #3e9a8f;
  --good-deep: #205a53;
}

/* ============================== DARK ===================================== */

/*
 * Deep grounds, built for the evening. Their daylight themes are deliberately
 * the quietest on the shelf — these are the rooms with the lamps off.
 */

/* --- Natt. Blue-black and ice: the darkest room, and the highest contrast. --- */
:root[data-palette="natt"],
.pal-natt {
  --deep: #12151c;
  --deep-2: #0a0c11;
  --deep-lift: #1e2330;
  --deep-pale: #333b4d;

  --edge: #04050a;
  --pale: #e8ecf2;
  --pale-dim: #cbd2dc;
  --pale-bright: #f5f8fc;

  --light-ground: #dee3ea;
  --light-ground-deep: #ccd3de;
  --mid: #b6c0ce;

  /*
   * A cool accent, which is the one thing near-black grounds are a cliché for.
   * It earns it here by not being alone: there are three functional colours,
   * and the alert and the agree are both warm-shifted against it.
   */
  --accent: #6fa8d4;
  --accent-lift: #86bce4;
  --accent-deep: #46759b;

  --alert: #c2453a;
  --alert-bright: #d75b4e;
  --alert-deep: #973026;

  --good: #379078;
  --good-bright: #48ac91;
  --good-deep: #206353;
}

/* --- Lingon. Spruce woods and berries: green ground, honey, lingonberry. --- */
:root[data-palette="lingon"],
.pal-lingon {
  --deep: #1e3a2e;
  --deep-2: #142a20;
  --deep-lift: #2a4e3e;
  --deep-pale: #3c6a55;

  --edge: #07150f;
  --pale: #f1eee2;
  --pale-dim: #d9d4c2;
  --pale-bright: #f8f6ee;

  --light-ground: #e5e2ce;
  --light-ground-deep: #d8d4bc;
  --mid: #cac4a8;

  --accent: #d9a441;
  --accent-lift: #e8b855;
  --accent-deep: #a97c22;

  --alert: #b3372b;
  --alert-bright: #ce5442;
  --alert-deep: #8c2519;

  --good: #3f8a5e;
  --good-bright: #55a776;
  --good-deep: #2a6444;
}

/* --- Plommon. Late plum: aubergine ground, brass, a warm red against cool. --- */
:root[data-palette="plommon"],
.pal-plommon {
  --deep: #3a2440;
  --deep-2: #2a1930;
  --deep-lift: #4e3356;
  --deep-pale: #6b4a73;

  --edge: #140b17;
  --pale: #f1ebe6;
  --pale-dim: #dbd2cc;
  --pale-bright: #f8f4f1;

  --light-ground: #e9e0dc;
  --light-ground-deep: #dcd1cc;
  --mid: #cdbfc4;

  --accent: #d0a24a;
  --accent-lift: #e3b75e;
  --accent-deep: #a47c2d;

  --alert: #c04a2e;
  --alert-bright: #d9654a;
  --alert-deep: #97341c;

  --good: #37806e;
  --good-bright: #4a9e89;
  --good-deep: #245c4e;
}

/* --- Bläck. The quiet one: warm charcoal and bone, amber, no colour cast. --- */
:root[data-palette="ink"],
.pal-ink {
  --deep: #22201e;
  --deep-2: #171614;
  --deep-lift: #33302d;
  --deep-pale: #4a4643;

  --edge: #0b0a09;
  --pale: #f2efe8;
  --pale-dim: #dad6cc;
  --pale-bright: #faf8f3;

  --light-ground: #e9e5dc;
  --light-ground-deep: #dcd7cc;
  --mid: #cbc5b8;

  --accent: #dda02b;
  --accent-lift: #efb43f;
  --accent-deep: #a9761a;

  --alert: #b3342a;
  --alert-bright: #cc4b3e;
  --alert-deep: #8c2119;

  --good: #2f7355;
  --good-bright: #40906d;
  --good-deep: #1f5741;
}

/*
 * --- Lampan. One lamp, a warm dark room, and everything else in its shadow. ---
 *
 * THE FOURTEENTH, AND THE ONE THE TONE WAS MEASURED IN. Added rather than
 * substituted: `docs/design-verdict.md` is a measurement of what the product
 * ships and every number in it was taken in Björk, so replacing a palette would
 * have destroyed the baseline the argument rests on. A palette nobody has
 * selected changes nothing and can be compared against the thirteen beside it,
 * which is the only way anybody finds out whether this is better.
 *
 * It is in the Dark group and it is the darkest warm room on the shelf. Natt is
 * blue-black and ice — a night with no lamp in it; Bläck is charcoal with the
 * colour taken out. This is the room those two do not cover: one light source,
 * low and warm, and a ground that is a material rather than an absence.
 *
 * Every value is measured from the working mockup of the system
 * (`redesign/room-system.html`, the Word Tiles room) and then pulled into this
 * file's nineteen slots, because a palette here is not a wallpaper — it fills
 * the same contract as the other thirteen and inherits every semantic role
 * written below without one exception.
 *
 * The three functional inks are hue-separated as the contract demands, and it
 * matters more here than in most: the accent is the LIGHT in this room, so it
 * is already spending the warm end. Amber for the accent at 35°, lingonberry
 * for a person being singled out at 348°, and a spruce green for the table
 * having agreed at 160° — three hues, no two of which are neighbours.
 */
:root[data-palette="lampan"],
.pal-lampan {
  /* The table top by lamplight, and the corners the lamp does not reach. */
  --deep: #2a1710;
  --deep-2: #170c07;
  --deep-lift: #3b2318;
  --deep-pale: #5c3a2b;

  --edge: #0c0503;
  --pale: #fbefe3;
  --pale-dim: #e6d3c1;
  --pale-bright: #fff9f1;

  /* Quiet by day, on purpose — this is a room built for the evening, and its
     daylight theme is the lamp switched off rather than a second design. */
  --light-ground: #f0e7dd;
  --light-ground-deep: #e4d9cd;
  --mid: #d6c7b6;

  /*
   * The lamp itself, and every number below is painted rather than reasoned.
   *
   * `--primary` in the evening, where it carries `--edge` at **8.21:1**.
   * `--accent-deep` is the daylight cut and carries the front door's full stop
   * on the pale ground at **5.11:1** — the lamp's own value there is 2.4:1 and
   * would read as a smudge on the end of the sentence, which is the argument
   * Björk's `--accent-deep` records and the reason this slot is not the accent.
   */
  --accent: #e8912f;
  --accent-lift: #f7a648;
  --accent-deep: #8e5210;

  /* A person singled out: 7.21:1 by day, 12.01:1 in the evening. */
  --alert: #bf3a52;
  --alert-bright: #d9556b;
  --alert-deep: #94243a;

  /* The table having agreed: 7.11:1 by day, 11.67:1 in the evening. */
  --good: #2f8266;
  --good-bright: #46a684;
  --good-deep: #1f5a46;

  /*
   * Measured across every role this palette fills, both lights, painted in
   * Chromium off the running product rather than computed from the hexes:
   *
   *   primary label on its fill    15.11 / 8.21
   *   control label on its fill    11.63 / 12.89
   *   body on the ground           10.20 / 10.01
   *   dim on the ground             7.95 /  6.84
   *   dim on a raised panel         8.30 /  7.80
   *   ink on stock                 19.33 / 17.87
   *   the dead primary's label      7.16 /  8.41   (floor is 4.5)
   *
   * The lowest number this palette produces anywhere is `--dimmer` at 3.54,
   * which is the tone the whole shelf keeps below the floor on purpose — it is
   * a hairline and a disabled hint, never running text — and it is better here
   * than in eight of the thirteen palettes beside it.
   */
}

/*
 * ---------------------------------------------------------------- themes
 *
 * The same enamel, in two lights. Daylight is the default; dark is the same
 * room in the evening once the lamps are on.
 *
 * This is a genuine inversion, which the design it replaced could not do: there,
 * separation came from luminance — paper had to be the brightest thing or
 * nothing read as lying on anything — so the light theme was stuck with a
 * mid-tone table it could never brighten. Here separation comes from **the
 * rim**, so an object survives having a ground brighter than itself, and by
 * daylight petrol becomes the ink it is the ground for at night.
 */
/*
 * Everything below is written in **slots**, never in hexes.
 *
 * That is the whole discipline of this section. Any literal here would be a
 * colour that survives a palette change — and the first draft had eighteen of
 * them, so four of the five palettes would have shipped with petrol-blue rules,
 * petrol-blue dim text and a petrol-blue turned tile. Where a tone between two
 * slots is wanted, it is mixed from them rather than picked by eye.
 */
:root {
  color-scheme: light;

  /* ---------------------------------------------------------- the ground */

  --table: var(--light-ground);
  --table-deep: var(--light-ground-deep);
  --panel: color-mix(in srgb, var(--pale) 55%, var(--light-ground));
  --panel-soft: color-mix(in srgb, var(--pale) 32%, var(--light-ground));
  --rule: color-mix(in srgb, var(--deep) 22%, var(--light-ground));
  --rule-strong: color-mix(in srgb, var(--deep) 38%, var(--light-ground));
  --facedown: var(--mid); /* a tile turned over: recedes, either way up */

  /* ----------------------------------------------------------- the glaze */

  --stock: var(--pale-bright); /* enamelled inserts — everything printed */
  --stock-dim: color-mix(in srgb, var(--pale) 70%, var(--mid));
  --stock-white: #ffffff; /* hover on an insert, and nothing else */

  /*
   * Text *on the ground*, which is a different job from text on glaze. Keeping
   * these apart is what makes two themes possible: `--stock` is a material and
   * `--on-table` is a contrast, and only one of them flips.
   */
  --on-table-strong: var(--deep-2);
  --on-table: color-mix(in srgb, var(--deep) 88%, var(--light-ground));
  /* 74% cleared on the muted grounds and failed on the bright ones, where the
     ground is both lighter and chromatic and the mix lands paler than it looks. */
  --dim: color-mix(in srgb, var(--deep) 80%, var(--light-ground));
  /*
   * `--dimmer` IS NOT A TEXT COLOUR, AND CANNOT BE MADE INTO ONE.
   *
   * It is the tone for a mark that is not a word: a rule, a dashed outline, a
   * placeholder in an empty field. Its whole job is to sit below the ink, so
   * there is no value it can take that both does that job and clears the 4.5:1
   * a word owes its ground — raise it far enough to read and it is `--dim`,
   * and the third step of the hierarchy is gone.
   *
   * Measured rather than asserted, because it had drifted onto four sets of
   * words in the lobby. Across all thirteen palettes and both lights, `--dimmer`
   * against the ground it is printed on measures **2.75:1 to 3.65:1** on
   * `--table` and **2.24:1 to 3.83:1** on a raised `--panel`. Not one of the
   * fifty-two cells clears the floor, and no palette is the reason: the token
   * is.
   *
   * So a word takes `--dim` — the quiet ink, which clears the floor in every
   * palette (worst 5.16:1 on the ground, 4.97:1 on a panel with `--dim-lift`)
   * — and this stays for the things that are not words: `--rule`,
   * `--rule-strong`, a dashed outline, a placeholder in an empty field.
   *
   * `.conn`, `.picker-head`, `.picker-group-note` and `.seat--empty` in
   * components.css are where it had gone wrong, and each of them carries the
   * number it was measured at.
   *
   * SWEPT, 2026-08-25. The four games this comment used to name as unaudited
   * were the tip of **62 `color: var(--dimmer)` declarations across 22
   * stylesheets**. Fifty-six moved to `--dim`; six stayed, and they are the
   * shape of the licence rather than a list to be maintained:
   *
   *   - three `::placeholder`s — the use this comment names, and it was wrong
   *     to say the tree had none;
   *   - three marks the panels hide from the accessibility tree entirely —
   *     `.portal-modes-sep` ("·"), `.ww-arrow` ("→"), both `aria-hidden`, and
   *     `.nt-key > summary::after` ("▸"), which is generated content.
   *
   * So the rule is checkable and is checked, on every run, by
   * `tests/guards/ink-is-not-ground.test.ts`: `--dimmer` may print a mark a
   * screen reader is never given, or a placeholder, and nothing else. The
   * `aria-hidden` half is read off the shipped panels rather than listed, so a
   * mark that stops being decorative goes red on its own.
   */
  --dimmer: color-mix(in srgb, var(--deep) 54%, var(--light-ground));

  /*
   * `--dim`, cut against a RAISED PANEL instead of against the ground.
   *
   * A third ground, and it needs its own cut for the same reason `--stock` and
   * `--on-table` are kept apart above: `--dim` is a contrast against `--table`,
   * and `--panel` is not `--table`. By daylight the panel is *lighter* than the
   * ground, so dark type on it only improves — this is the same value. In the
   * evening the panel is the lighter of the two and the same ink loses a third
   * of its contrast: measured on the shelf's own boxes at 4.10:1, under the
   * floor, in the palette the product ships with.
   *
   * Written from the primitives rather than from `--dim`, deliberately: the
   * raised surfaces in components.css set `--dim: var(--dim-lift)` for their
   * own subtree, so anything printed inside a box is correct without every
   * future component having to remember which ground it is standing on — and a
   * definition in terms of `--dim` would be a cycle.
   */
  /*
   * EIGHTY-SIX AND NOT EIGHTY, BECAUSE THE MEASUREMENT IS OF PAINT.
   *
   * At 80% the *computed* colour clears 4.5:1 in all fourteen palettes — the
   * thinnest is citron at 5.16:1, and reading the two hex values against each
   * other says this token is comfortably fine. It is not, and the gap between
   * those two sentences is the whole reason this line moved.
   *
   * `legible.ts` does not compare computed colours. It samples the pixels
   * actually painted and buckets them, so a thin glyph is judged on the ink
   * that reached the screen rather than the ink that was asked for. The join
   * link is `--font-mono` at `--t-data-sm` with 0.06em of tracking: strokes
   * about a pixel wide, most of whose marks are antialiased blends toward
   * whatever is behind them. Citron's ground is the brightest in the set — a
   * saturated yellow — so its blends travel furthest, and the measurement came
   * back 4.27:1 across 20% of the glyphs' marks where the computed pair said
   * 5.16.
   *
   * Six points of mix buys citron roughly 5.9:1 computed and clears the floor
   * in paint. It costs the quiet register very little: this ink is still a
   * long way off `--on-table`, which is the thing it must not be mistaken for.
   */
  --dim-lift: color-mix(in srgb, var(--deep) 86%, var(--light-ground));

  /* ------------------------------------------------------------ controls */

  /*
   * Strongest is furthest from the ground — rule 4. By daylight the ground is
   * pale, so the primary is the darkest, most saturated object on it and
   * ordinary controls sit one small step in the same direction.
   */
  --control: var(--mid);
  --control-hover: color-mix(in srgb, var(--mid) 72%, var(--pale));
  --control-press: color-mix(in srgb, var(--mid) 80%, var(--deep));
  --on-control: var(--deep-2);
  --primary: var(--deep);
  --on-primary: var(--pale);

  /* ------------------------------------------------------------ the inks */

  /*
   * `--stamp` marks the spy, an accusation, and whose turn it is: the three
   * things that are about *a person being singled out*. `--agree` marks the
   * table having agreed, and appears at the reveal only.
   *
   * One exception each in the connection dot, which is platform chrome rather
   * than part of the game's language. Neither ever appears inside a card — a
   * card is ink on glaze, see rule 3.
   *
   * Both take the deepest cut by daylight so they still clear 4.5:1 on a pale
   * ground, in every palette.
   */
  --stamp: var(--alert-deep);
  --stamp-bright: var(--alert);
  --on-stamp: var(--pale);
  --agree: var(--good-deep);
  --on-agree: var(--pale);

  /*
   * The one ornamental ink on the site: the front door's full stop.
   *
   * Cut from the accent rather than added as a hex, so the flourish is a
   * different colour in each of the thirteen rooms and there is nothing to keep
   * in step. `--accent-deep` by daylight and not `--accent` itself, because
   * this is punctuation inside a headline — brass on a pale ground measures
   * near 2:1 and would read as a smudge on the end of the sentence rather than
   * as the mark that finishes it.
   */
  --flourish: var(--accent-deep);

  --focus: var(--deep);

  /* Depth by daylight: a soft cast in the palette's own dark, not soot. */
  --lift: 0 24px 44px -20px color-mix(in srgb, var(--edge) 34%, transparent);
  --lift-sm: 0 18px 34px -18px color-mix(in srgb, var(--edge) 28%, transparent);
  --lift-xs: 0 10px 22px -12px color-mix(in srgb, var(--edge) 30%, transparent);
  --drop: drop-shadow(0 16px 16px color-mix(in srgb, var(--edge) 36%, transparent));

  /* Enamel is speckled. Two offset stipples rather than one clean dot grid. */
  --grain: radial-gradient(color-mix(in srgb, var(--deep) 6%, transparent) 1px, transparent 1px),
    radial-gradient(color-mix(in srgb, var(--deep) 4%, transparent) 1px, transparent 1px);
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --table: var(--deep);
  --table-deep: var(--deep-2);
  --panel: var(--deep-lift);
  --panel-soft: color-mix(in srgb, var(--deep-lift) 55%, var(--deep-2));
  --rule: var(--deep-pale);
  --rule-strong: color-mix(in srgb, var(--deep-pale) 72%, var(--pale));
  --facedown: color-mix(in srgb, var(--deep-2) 78%, var(--deep));

  --stock: var(--pale);
  --stock-dim: var(--pale-dim);
  --stock-white: var(--pale-bright);

  --on-table-strong: var(--pale);
  --on-table: color-mix(in srgb, var(--pale) 80%, var(--deep));
  /* 56% measured 4.4:1 on the petrol ground — under the floor by a hair, in
     three palettes out of five. Labels are small text and have to clear it. */
  --dim: color-mix(in srgb, var(--pale) 64%, var(--deep));
  --dimmer: color-mix(in srgb, var(--pale) 40%, var(--deep));
  /* 64% measures 4.10:1 against `--deep-lift`. 72% clears the floor by
     arithmetic in every palette and by ~0.02 once the rasteriser has had its
     share, which is not a margin; 76% is worst-case 4.90:1 painted. See the
     note beside the daylight value. */
  --dim-lift: color-mix(in srgb, var(--pale) 76%, var(--deep));

  /* Evening: the ground is deep, so the strongest thing is warm and bright. */
  --control: var(--deep-lift);
  --control-hover: color-mix(in srgb, var(--deep-lift) 80%, var(--pale));
  --control-press: color-mix(in srgb, var(--deep-lift) 78%, var(--edge));
  --on-control: var(--pale);
  --primary: var(--accent);
  --on-primary: var(--edge);

  /*
   * Both state inks are *tints* at night, not the saturated hue.
   *
   * The old identity's ground was near-black, so a mid-tone red cleared 4.5:1
   * on it comfortably. This ground is a mid-dark saturated colour, and the same
   * red against it measures 2.7:1 — the seat tag reading "asking" was failing
   * contrast in every palette, this one included. Tinting towards the pale
   * material costs some of the punch and buys back legibility, and the
   * saturated hue is still available as a fill underneath dark text.
   */
  /* Measured against `--panel`, not the ground: a raised field is the lighter
     of the two backgrounds a seat tag sits on, and it is the one that fails. */
  --stamp: color-mix(in srgb, var(--alert-bright) 34%, var(--pale));
  --stamp-bright: color-mix(in srgb, var(--alert-bright) 20%, var(--pale));
  --on-stamp: var(--edge);
  --agree: color-mix(in srgb, var(--good-bright) 46%, var(--pale));
  --on-agree: var(--edge);

  /* The other way up the accent is already the brightest thing in the room, so
     the flourish takes it at full strength rather than cut towards the ground. */
  --flourish: var(--accent-lift);

  --focus: var(--accent);

  --lift: 0 26px 50px -22px rgb(0 0 0 / 0.72);
  --lift-sm: 0 20px 40px -20px rgb(0 0 0 / 0.62);
  --lift-xs: 0 12px 26px -14px rgb(0 0 0 / 0.66);
  --drop: drop-shadow(0 20px 22px rgb(0 0 0 / 0.7));

  --grain: radial-gradient(color-mix(in srgb, var(--pale) 6%, transparent) 1px, transparent 1px),
    radial-gradient(color-mix(in srgb, var(--pale) 4%, transparent) 1px, transparent 1px);
}

/*
 * ======================================================== THE ROOM'S LIGHT
 *
 * ## What this section is, and why it is not a fifteenth palette
 *
 * `docs/design-verdict.md` §7 records the open problem in the whole redesign:
 * every direction scored 4–5 out of 10 on "does this survive thirty-seven
 * games", and the note under it is the diagnosis — *"Hearth's identity is
 * currently a fire. A fire is not a system."*
 *
 * The answer the owner picked is not one room with the pieces swapped, which
 * would be thirty-seven identical lobbies and would throw away the one thing
 * this shelf has that the competitor's does not. It is:
 *
 *     THE TONE IS SHARED. THE ROOM IS THE GAME'S OWN.
 *
 * A game supplies a GROUND and a LIGHT. It inherits the rules. The rules are
 * the seven written at the head of the working mockup, and four of them are
 * colour and therefore live here:
 *
 *   1. ONE LIGHT SOURCE, and every shadow and highlight obeys it. A game says
 *      where the light is; nothing on the screen may disagree.
 *   3. THE CODE IS THE LIT OBJECT — the first thing the eye lands on, in
 *      whatever this room's warm tone is.
 *   4. THE PEOPLE ARE SECOND. Glossy counters, big, in their own colours.
 *   5. ONE PRIMARY, in the game's own light.
 *
 * ## Why these are derived and not a palette of their own
 *
 * Every token below is written from the slots above — never from a hex — for
 * the same reason the semantic roles are. There are fourteen palettes and two
 * lights, so an absolute value here would be a colour that survives a palette
 * change, and the tone would be right in the one room it was drawn in and
 * wrong in the other twenty-seven. Derived, the tone arrives in ALL of them:
 * Björk gets a rowan-orange lamp on birch, Natt an ice-blue one on blue-black,
 * and neither had to be designed.
 *
 * A GAME OVERRIDES `--lit` AND THE TWO COORDINATES, and nothing else. That is
 * the whole of what a lobby is allowed to change, and it is published by
 * `shell/room.js` from `games/registry.js` — the one file allowed to know
 * which game it is talking about.
 *
 * ## Named for the job
 *
 * `--lit`, not `--fire`; `--person-1`, not `--lingon`. A token called `--fire`
 * has an opinion about the product — it says this room has a hearth in it —
 * and the moment Pool wants a lamp over a table and Minesweeper wants the glow
 * of a screen, a hearth-shaped name is a lie in twelve files. The name says
 * what the value is FOR, so the same rule paints three rooms that look nothing
 * alike.
 */
:root {
  /*
   * THE COLOUR OF THE LIGHT IN THIS ROOM.
   *
   * `--accent` is the palette's own warm — the brass on Fika's enamel, the
   * rowan on Björk's birch — and it is already the colour this house reaches
   * for when something is lit. A game replaces this with its own.
   *
   * MIXED TOWARDS THE PALE MATERIAL, AND THAT IS NOT A TASTE. The accent at
   * full strength is a PIGMENT: Björk's is #f4640a, which is the colour of a
   * printed full stop and not the colour of anything that emits. Photographed
   * on the join card before this mix, the pool under the room code was a
   * pink-orange smudge on cream — a paint spill rather than lamplight. Every
   * light a game supplies is already a lifted, low-chroma version of its ink
   * (`games/registry.js` derives them at OKLCH lightness 0.80), so the fallback
   * has to be one too or the screens outside a room disagree with the screens
   * inside it. 62% is that same lift, expressed in the one thing available
   * here: the palette's own pale material.
   */
  --lit: color-mix(in srgb, var(--accent) 62%, var(--pale));
  /* Type ON a lit surface. The rim's near-black, because a lit surface is by
     construction the brightest thing in the room. Worst measured pairing across
     all fourteen palettes and both lights: 5.56:1, in daylight Bibliotek. */
  --on-lit: var(--edge);

  /*
   * WHERE THE LIGHT HANGS, as two percentages of the room.
   *
   * Default: low and to the left, which is a lamp on the table rather than a
   * ceiling fitting. A game moves it — `registry.js` argues why a warm room is
   * lit from below and a cold one from above — and every gradient, glow and
   * highlight in `components.css` reads these two numbers, so moving the light
   * moves all of them together. That is rule 1, and it is enforced by there
   * being one pair of coordinates rather than a value per surface.
   */
  --light-x: 8%;
  --light-y: 86%;

  /*
   * HOW MUCH OF THE LIGHT REACHES THE GROUND.
   *
   * A percentage rather than a colour, so the same declaration is safe on all
   * twenty-eight grounds: the wash is always a short step from whatever
   * `--table` currently is. This is the mechanism the invitation band's own
   * six-percent wash already uses, and the argument beside it in
   * `components.css` is the argument here — a tint mixed against the ground
   * cannot be wrong about a ground it has never seen.
   *
   * `--lit-wash` is the light falling on the whole room and carries body text.
   * `--lit-pool` is the pool directly under the code and carries only
   * `--on-table-strong`, so it can be denser.
   *
   * BOTH NUMBERS WERE CHOSEN BY MEASUREMENT AND BOTH CAME DOWN. The evening's
   * wash started at 20%, which is what the mockup's one room used, and a sweep
   * across fourteen palettes × two lights put `--dim` on the washed ground at
   * **3.78:1 in Lingon** — under the floor, in a palette nobody would have
   * tested by hand. 13% moved the failure to Tegel at 4.27, 10% to 4.49, and 9%
   * clears everywhere at **4.57:1 worst**. The pool went 26% → 24% for the same
   * reason and measures 6.96:1 at its worst.
   *
   * The measurement is deliberately of a FLAT fill, where the paint is a radial
   * gradient with its peak in the corner the light hangs in. So the number is
   * the worst case for a sentence that never actually sits there, which is the
   * right way round for a floor.
   */
  --lit-wash: 12%;
  --lit-pool: 20%;

  /*
   * THE HALO A LIT OBJECT THROWS.
   *
   * By daylight there is no bloom to have — a bright thing on a bright ground
   * does not glow, it just sits there — so this is a soft warm contact shadow
   * in the palette's own dark instead. The evening's value below is the bloom.
   * Same token, same job, two honest answers.
   */
  --glow: 0 6px 18px -8px color-mix(in srgb, var(--edge) 34%, transparent);
  --glow-lg: 0 14px 40px -18px color-mix(in srgb, var(--edge) 40%, transparent);

  /*
   * THE PEOPLE, AS SIX COUNTERS.
   *
   * §6 of the verdict is the reason these exist and it is worth quoting,
   * because it is the item the panel called the most important on the page:
   * *"The roster is dead. The lobby's entire job is 'look who's coming', and
   * the people are rendered in the same cool grey-green as the card they sit
   * next to. A room with two colleagues in it feels emptier than Table Party's
   * empty seat."*
   *
   * CONSTANT ACROSS THE FOURTEEN PALETTES, exactly like `--cardback` and for
   * the same reason written beside it: a card is the same object in any light
   * because it is a thing on the surface rather than part of it, and so is a
   * person. Erik is not a different colour in Plommon than he is in Skiffer.
   *
   * ...AND THIS DOES NOT BREAK RULE 3 AT THE TOP OF THIS FILE. *"Colour says
   * what phase it is, never who you are"* is about a SECRET: two people holding
   * different cards must have pixel-identical screens or the game leaks across
   * a shared desk. A roster is the opposite of a secret — it is the one list
   * every screen in the room draws identically — and `shell/chrome.js` picks
   * from these by `seatIndex`, which is public, and never by anything only one
   * browser knows. Nobody learns anything from your disc that they cannot learn
   * by reading the name printed under it.
   *
   * TWELVE, and it was six until 2026-08-26. The paragraph that used to stand
   * here said *"six because eight is the largest table on the shelf and the
   * seventh and eighth wrap round — two people sharing a hue two seats apart is
   * a smaller cost than two hues nobody can tell apart"*, and every word of that
   * was true of a room that seated eight.
   *
   * `FREE_PLAN.maxPlayers` went to **twelve** the same day, to free the fifteen
   * cards that print a maximum of ten or twelve. Nobody looked at this file, and
   * `% 6` on a full table does not put two people two seats apart — it puts
   * **six pairs** in matching discs, which is not a shared hue at the edge of a
   * table, it is half the room. The guard that keeps the two numbers together
   * lives in `tests/guards/`; the modulus lives in `shell/chrome.js` and reads
   * `PERSON_INKS`.
   *
   * ## How the six new ones were chosen, since taste could not do it
   *
   * The first six are mid-light gouache counters, and three hand-picked sets
   * that *looked* right beside them were each closer to a colour already here
   * than the closest pair already here is to itself — #f0a35e sits 0.036 from
   * #eba93f in OKLab, and the tightest shipped pair is 0.099. So the bar became
   * that number: **no new counter may be closer to another counter than the two
   * closest counters already shipped**, searched in OKLCh over the register the
   * first six occupy, with anything that clipped out of sRGB thrown away.
   *
   * What survives is not what an eye would have picked — a moss and a lime sit
   * next to each other in the 91° hole between amber and mint, because that
   * hole is where the room is. It is also what a twelve-counter boxed game
   * looks like, which is the object these are pretending to be.
   *
   * Each carries the near-black below at 4.5:1 or better, measured; the worst
   * of the twelve is 5.89.
   */
  --person-1: #e8657a; /* rose      · initials at 5.89:1 */
  --person-2: #4fc79b; /* mint      · initials at 8.94:1 */
  --person-3: #8a97e8; /* periwinkle · initials at 6.88:1 */
  --person-4: #5cb8e0; /* sky       · initials at 8.41:1 */
  --person-5: #eba93f; /* amber     · initials at 9.21:1 */
  --person-6: #c37fd0; /* orchid    · initials at 6.51:1 */
  --person-7: #f1a2b1; /* blush     · initials at 9.45:1 */
  --person-8: #d9822b; /* burnt     · initials at 6.43:1 */
  --person-9: #9aa63a; /* moss      · initials at 7.07:1 */
  --person-10: #b3d356; /* lime     · initials at 11.09:1 */
  --person-11: #25a49f; /* teal     · initials at 6.17:1 */
  --person-12: #ff8beb; /* magenta  · initials at 9.09:1 */
  /*
   * The initials printed on a counter. Constant, like the counters, and near-
   * black rather than `--edge` for exactly that reason: `--edge` is a palette
   * slot and would be a different ink in each of the fourteen rooms, so the six
   * ratios above would be fourteen sets of six and none of them checked. One
   * value, six numbers, measured in Chromium; the worst is 5.89:1 and the floor
   * is 4.5.
   */
  --on-person: #1a0f08;

  /*
   * THE FACE OF A COUNTER, as one rule any disc can take.
   *
   * Rule 2 is *materials, not fills*: a counter is a glossy plastic disc, so
   * it has a highlight where the light hits it and it is darker on the side
   * away. Both are placed from `--light-x`/`--light-y`, so a room lit from the
   * lower left and a room lit from the upper right hand out different discs
   * from one declaration, and rule 1 holds without anybody remembering it.
   *
   * ## The coordinates were flipped, and the flip was backwards
   *
   * This rule shipped as `calc(100% - var(--light-x)) calc(100% - …-y)`, with
   * a comment claiming the flip was *"because a highlight sits on the side
   * FACING the lamp"*. It does — which is why the flip was wrong: at Word
   * Tiles' 6%/88% it put the shine at 94%/12%, the far top corner, which is
   * the one place in this system that is defined as dark. `.table-body`'s
   * third gradient paints the unlit corner at exactly `calc(100% - --light-x)
   * calc(100% - --light-y)`, so the counters were carrying their highlight in
   * the shadow the room draws behind them.
   *
   * Found by photographing the lobby and handing the photograph to
   * `tools/design/imgedit.py` under two briefs — richer materials, and warmer
   * — and both came back with counters that read as lit objects rather than as
   * filled circles. Neither can be quoted on the DIRECTION: both lit the discs
   * from the upper left, which is a drawing convention rather than a reading
   * of this room. What they were right about is that the discs were not lit at
   * all, and following that into this token is where the inversion turned up.
   *
   * Every OTHER rule here reads the coordinates straight — `.code::before`
   * leans the pool *towards* the lamp and says so at length, and the room's
   * own lamp is painted AT them. One rule out of five inverted them.
   *
   * So the highlight now sits at the coordinates themselves, which is what the
   * sentence above always said it should be, and the shade at 78% falls on the
   * side away by construction.
   */
  --gloss: radial-gradient(
    circle at var(--light-x) var(--light-y),
    rgb(255 255 255 / 0.45) 0%,
    rgb(255 255 255 / 0.12) 34%,
    rgb(0 0 0 / 0.14) 78%
  );

  /*
   * THE SHADOW A COUNTER THROWS, and it is not `--lift-xs`.
   *
   * `--lift-xs` is the smallest of the three PANEL shadows — `0 10px 22px
   * -12px` by day, `0 12px 26px -14px` in the evening — and it is drawn for an
   * object the size of a card. Handed to a 72px disc, the negative spread eats
   * it: a −14px spread shrinks the shadow to a 44px disc under a 72px one, so
   * what is left sits entirely behind the counter and 26px of blur spreads it
   * too thin to see past the edge. The counters were painted with a depth token
   * and came out flat, which two renderings of this screen — the materials
   * brief and the warmth brief, which agree about very little else — both
   * corrected the same way: a short, tight contact shadow directly beneath, the
   * way a draught piece sits on a board.
   *
   * Straight down, and NOT placed from the light. A cast shadow's direction is
   * a fair thing to derive, but a counter is a disc lying flat ON the table
   * rather than standing up on it, so what is under it is contact — the
   * ambient occlusion where two surfaces meet — and that is beneath the object
   * in any light. The directional half of the lighting is `--gloss` above,
   * which does read the coordinates.
   */
  --cast: 0 3px 6px -1px color-mix(in srgb, var(--edge) 26%, transparent);
}

:root[data-theme="dark"] {
  /* A lamp in a dark room is most of what you can see. */
  --lit-wash: 9%;
  --lit-pool: 34%;

  /* ...and here the halo really is a halo: light spilling off the object onto
     the ground behind it, in the colour of the light that is falling on it. */
  --glow: 0 0 28px -2px color-mix(in srgb, var(--lit) 42%, transparent);
  --glow-lg: 0 0 64px -6px color-mix(in srgb, var(--lit) 46%, transparent);

  /* Contact is harder in a dark room, for the same reason `--lift-*` doubles
     its alpha here: there is no bounced light to fill the gap under the disc. */
  --cast: 0 4px 8px -1px rgb(0 0 0 / 0.55);
}

:root {
  /* ------------------------------------------------- the card's own colours */

  /*
   * A card is the same object in any light — that is the point of it being a
   * thing on the surface rather than part of it. So these do not flip.
   */
  --cardback: var(--deep-2);
  --cardback-edge: var(--edge);
  --cardback-ink: var(--pale);
  --cardback-dim: color-mix(in srgb, var(--pale) 54%, var(--deep-2));

  /* Ink printed on glaze. Constant, for the same reason `--stock` is. */
  --ink: var(--edge);

  /*
   * AND THE QUIET ONE, WHICH THIS SYSTEM DID NOT HAVE — 2026-08-27.
   *
   * There has been exactly one quiet ink here, `--dim`, and the note beside it
   * calls it "the quiet ink" without qualification. It is nothing of the kind:
   * it is cut against the ROOM and it flips with the light. A card does not.
   *
   * So every game that wanted a quiet word ON PAPER reached for the only quiet
   * ink named in the system, and got one that inverts out from under the card
   * after dark. In one evening that was Word Tiles' tile values at 1.53:1, Six
   * Nimmt's bull counts at 1.95, Jaipur's camel stepper at 1.53 and Decrypto's
   * empty-cell dash at 1.78 — four games, four independent bugs, one missing
   * token. Two of them had already been "fixed" once by somebody who correctly
   * removed an opacity and then reached for `--dim` again.
   *
   * The pair is the point, and it is the same pair as `--ink` and `--on-table`:
   *
   *   `--dim`        quiet on the ROOM.  Flips.
   *   `--ink-quiet`  quiet on PAPER.     Does not.
   *
   * 74% was measured rather than chosen, and the measuring had a false floor in
   * it worth recording. 62% clears bare stock at 4.83:1 and 70% at 6.29, so 70
   * looked like the quiet end of safe — on STOCK. Cards are not all bare stock:
   * Jaipur's goods each wear a tinted wash, and on gold's, in citron by day, 70%
   * reads **4.47:1**. The token was set to 70, one game regressed by three
   * hundredths, and the census said so on the next run.
   *
   * So the number is the one the game that had already been measured on washes
   * was using. It clears every surface in the product: 7.23:1 on stock, 4.9 on
   * the most tinted wash, and it is still unmistakably the quiet register.
   *
   * The lesson is smaller than it looks and worth keeping: a quiet ink verified
   * against the lightest ground it will ever sit on has not been verified.
   */
  --ink-quiet: color-mix(in srgb, var(--ink) 74%, var(--stock));

  /*
   * The stamp **as printed on glaze**, and its own text colour.
   *
   * Glaze does not flip with the light — a slip and a place tile are pale in
   * both themes — so a mark on one cannot use the themed `--stamp`, which is
   * dark by day and a pale tint at night. At night that tint would be a pale
   * mark on pale stock: the chosen slip's CONFIRM would have disappeared.
   *
   * Constant, like the card back, and for the same reason: it belongs to the
   * object rather than to the room.
   */
  --stamp-print: var(--alert-deep);
  --on-stamp-print: var(--pale);

  /*
   * AND THE OTHER ACCENT, FOR THE SAME REASON, FOUND ONE GAME LATER.
   *
   * The paragraph above is about `--stamp`. `--agree` has the identical shape —
   * `--good-deep` by day, `color-mix(--good-bright 46%, --pale)` after dark, a
   * pastel — and had no printed counterpart, so anything marking agreement ON
   * GLAZE was a pale tint on pale stock the moment the room went dark.
   *
   * PHOTOGRAPHED on 2026-08-26: The Needle's scoring band is three weights of
   * `--agree` painted on the dial's `--stock` face, and it is the psychic's own
   * private information — the only thing they have to write a clue from. In the
   * evening it was very nearly not there. The game's own stylesheet had the
   * reason written down fifty lines below the bands, beside its needle: *"glaze
   * does not flip with the light, and the themed `--stamp` is a pale tint in the
   * evening, which on pale stock would be a needle that disappeared."* The same
   * sentence, about the same file's other accent, and neither noticed the other.
   *
   * Constant, like `--stamp-print` and the card back, because it belongs to the
   * object rather than to the room.
   *
   * THIS IS NOT A REPLACEMENT FOR `--agree`. An agree mark on a ground that
   * flips — a panel, a chip, the room's own table — still wants the themed one,
   * and swapping it there would trade this fault for its mirror image. The
   * question is what the mark is printed on, which is the same question
   * `docs/design.md` asks of every ink.
   */
  --agree-print: var(--good-deep);

  /* --------------------------------------------------------------- the rim */

  /*
   * The signature, as three properties any object can take: a heavy dark edge,
   * a line of glaze catching the light just inside it, and a radius, because
   * enamel is pressed steel and nothing pressed comes out with a sharp corner.
   *
   * `.enamelled` in components.css is the one place these are assembled.
   */
  --rim: var(--edge);
  --rim-w: 2px;
  --rim-hair: 1.5px; /* small objects: a chip, a badge, a seat */
  --glaze-line: inset 0 1px 0 0 rgb(255 255 255 / 0.2), inset 0 0 0 1px rgb(255 255 255 / 0.07);
  --glaze-line-dark: inset 0 1px 0 0 rgb(255 255 255 / 0.1), inset 0 0 0 1px rgb(255 255 255 / 0.04);
  /*
   * THE SAME LINE ON THE DARKEST FILLS IN THE PRODUCT — a game's baize.
   *
   * 20% of white is a highlight on the mid-tone and bright objects this
   * interface is made of. On a `--deep*` ground it is not a line: MEASURED at
   * 1280×800 by `edgeAcross()` in `legible.ts`, the top edge of a `.baize`
   * after dark reads
   *
   *     glaze   Secret Codes (--deep)   Mosaic (--deep-2)
   *     0.20            3.10                  2.57
   *     0.26            3.63                  3.09
   *     0.32            4.32                  3.77
   *     0.40            5.33                  4.69
   *
   * against a floor of 3 — which is what `.table`, the room's own surface,
   * measures by the same instrument. 0.20 puts the darker of the two grounds
   * under the floor and 0.26 puts it a hundredth over, which is not a margin.
   * 0.32 clears both with room and is where this stops: 0.40 is a bright line
   * around a dark cloth rather than light caught on its edge.
   *
   * What is held constant between this and `--glaze-line` is the JOB — a line
   * of light just inside the rim, at a contrast a person can see — and not the
   * alpha. An alpha held constant across a fill that ranges from `--pale-bright`
   * to `--deep-2` is a line that means something different on every object.
   *
   * DAYLIGHT IS UNTOUCHED BY THIS and the measurement says so: the same two
   * edges read 16.88 and 16.94 at every value in the table, because by day the
   * edge is a dark baize against a cream room and the glaze is not what you are
   * seeing.
   */
  --glaze-hair-deep: rgb(255 255 255 / 0.32);
  --glaze-ring-deep: rgb(255 255 255 / 0.11);
  --glaze-line-deep: inset 0 1px 0 0 var(--glaze-hair-deep), inset 0 0 0 1px var(--glaze-ring-deep);
  /*
   * THE SAME LINE AGAIN, MOVED INBOARD PAST A RIM THAT IS NOT A BORDER.
   *
   * `.baize-flush` in `base.css` draws its rim as an inset ring on a
   * pseudo-element rather than as a border, because two of the surfaces that
   * want an edge cannot spend the four pixels a border costs. An inset shadow
   * is measured from the BORDER box, so the line above would land underneath
   * that ring and be painted out. These two are offset by the rim's own width
   * and then covered back down to a hairline by it — same 1px of glaze, in the
   * same place, on a stage whose geometry did not move.
   *
   * The alphas are shared with the line above rather than restated, so the
   * measurement in the table beside it governs both. That is the whole reason
   * `--glaze-hair-deep` and `--glaze-ring-deep` exist: a second copy of `0.32`
   * is a second thing to forget.
   */
  --glaze-line-deep-flush: inset 0 calc(var(--rim-w) + 1px) 0 0 var(--glaze-hair-deep),
    inset 0 0 0 calc(var(--rim-w) + 1px) var(--glaze-ring-deep);

  /* ----------------------------------------------------------- typography */

  --font-display: "Familjen Grotesk", "Segoe UI", Helvetica, Arial, sans-serif;
  --font-body: "Hanken Grotesk", "Segoe UI", Helvetica, Arial, sans-serif;
  /* Data — every machine fact. Room code, clock, counts, field labels. */
  --font-mono: "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
  /*
   * THE LIT OBJECT'S OWN FACE, AND THE ONLY SERIF IN THE PRODUCT.
   *
   * Rule 3 of the tone asks for the room code in a serif: large, glowing, found
   * first. One face, used on one element, and the whole point of it is that it
   * agrees with nothing else on the screen — two grotesks and a mono are the
   * voice of the interface, and the code is not part of the interface. It is
   * the object on the table.
   *
   * A SYSTEM STACK AND NOT A WEBFONT, which is not a compromise. The note at
   * the top of this file rules out a font CDN — *"this product's pitch is
   * surviving hostile corporate networks"* — so a fourth family means a fourth
   * self-hosted subset, and this is one element on one screen. Georgia and
   * Iowan are on every Windows, macOS and iOS this product is played on;
   * Android has neither and resolves `serif` to Noto Serif, which is a
   * perfectly good transitional face. Three shapes for six characters, and any
   * of the three is unmistakably not the sans beside it.
   *
   * If the code ever earns a drawn face, this token is the one line to change.
   */
  --font-lit: Georgia, "Iowan Old Style", "Palatino Linotype", "Times New Roman", serif;

  --t-hero: clamp(2.75rem, 5.4vw, 4.125rem); /* the pitch, the ballot question */
  /* The location, once, at the end. Capped lower than the old 9.375rem so the
     announcement and the sheet under it both fit a 900px stage. */
  --t-reveal: clamp(2.5rem, 6.5vw, 5.75rem);
  --t-card: clamp(2rem, 3.4vw, 2.875rem); /* the card face, and nothing else */
  --t-display: clamp(1.5rem, 2.6vw, 2.125rem);
  --t-turn: clamp(1.5rem, 2.6vw, 2.125rem); /* "Priya asks Tomas" */
  /*
   * THE INSTRUCTION. What to do, right now, in this phase — the shell's
   * `.prompt`, and a game's own imperative where it repeats one.
   *
   * It is a new step and it is here because the scale had a hole exactly where
   * the interface needed one. Between `--t-body` at 17px and `--t-display` at
   * 24px there was only `--t-lead`, and `--t-lead` was 20px — the same number
   * as `--t-name`. So on a game screen the sentence telling you what to do was
   * set at the same size as a colleague's name in the roster beside it, and
   * both were within 1.2x of the body copy under them. Three roles, one size
   * band, no hierarchy: whatever else was wrong with a screen, nothing on it
   * announced itself as the thing to read first.
   *
   * 22px on a phone rising to 28px on a laptop is roughly 1.6x body, which is
   * the smallest step that reads as a different rank rather than as emphasis.
   */
  --t-say: clamp(1.375rem, 2.1vw, 1.75rem);
  --t-name: 1.25rem;
  --t-lead: 1.25rem;
  --t-body: 1.0625rem; /* 17px — arm's length */
  /*
   * A BLOCK CONTROL'S LABEL, which is a step above an ordinary one's.
   *
   * ## The argument this replaces, and why its premise expired
   *
   * What was here: *"`.btn` is mono caps at `--t-data` — 13px — because a
   * button label is an instruction to the machine and mono is this interface's
   * voice for those"*, and 15px was chosen over 17px because *"mono caps gain
   * about 12% width over the same size in the body face"*.
   *
   * Both halves rested on the primary being mono caps, and it is not any more.
   * `docs/design-verdict.md` §1 measured what that voice was actually doing —
   * eleven mono-caps labels on one lobby, and *"uppercase removes word-shape,
   * which is what fast readers actually read"* — and the house rule that came
   * out of it is that mono is a machine fact and never an instruction. A block
   * control is now the body face in sentence case, so the 12% is not being
   * paid and the ordinary control's label sits at `--t-small` (15px).
   *
   * 18px, therefore, and the number is doing a job rather than being a nicer
   * size: `primary-visible.spec.ts` asserts that the primary's label is set
   * ABOVE an ordinary control's, and at 15px against 15px it would not be. It
   * is one step over `--t-body` too, so the button is larger than the sentence
   * explaining it — which is the rank the verdict says the competitor has and
   * we did not.
   *
   * Still measured against the longest label on the shelf at 390px: *"End this
   * round and pick another game"* wraps to two lines in a 334px stage, exactly
   * as it did at 15px mono, because sentence case at 18px is very nearly the
   * same set width as caps at 15px.
   */
  --t-action: 1.125rem;
  --t-small: 0.9375rem;
  --t-data: 0.8125rem; /* mono, uppercase */
  --t-data-sm: 0.6875rem;
  --t-clock: clamp(1.5rem, 2.4vw, 1.875rem);

  --track-data: 0.18em;
  /*
   * Tracking for mono caps set larger than `--t-data`: a fraction of the size,
   * so holding 0.18em at 15px would widen the label by more than the step up
   * bought in legibility.
   *
   * It was the primary's, and the primary is neither mono nor caps any more —
   * see `.btn--primary` in `components.css` and §1 of the design verdict. Its
   * one remaining reader is `.hp-key` in `lib/howto.css`, which is not a
   * control but a PICTURE of one: an explainer drawing a keycap with a word on
   * it. A depicted key is a machine object and keeps the machine's voice, which
   * is exactly the line the house rule draws.
   */
  --track-action: 0.14em;
  --track-label: 0.2em;
  /* Familjen Grotesk is already tight; Archivo's -0.04em closed it up. */
  --track-display: -0.018em;

  /* --------------------------------------------------------------- space */

  /*
   * The screen's own margins — the notch, the rounded corners, the home
   * indicator.
   *
   * `index.html` declares `viewport-fit=cover`, which is what a game asking for
   * the screen needs and what an installed home-screen launch requires: without
   * it iOS letterboxes the page inside the safe area and the field stops short
   * of the glass. With it the page gets the whole display and these four
   * numbers become ours to honour. Named as tokens rather than written as
   * `env()` at each site because they appear in compound `calc()` paddings on
   * four surfaces, and the fallback belongs in one place.
   *
   * Zero everywhere that has no notch — including every headless browser, which
   * is why no test can see this and a real phone can.
   */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);

  /*
   * THE THUMB FLOOR, AS A NAME RATHER THAN AS A NUMBER IN SIX PLACES.
   *
   * A finger's contact patch is about 9mm, which is 44 CSS pixels at the
   * density every published floor is written against — Apple's HIG, the WCAG
   * 2.2 target-size minimum and Material's own 48 all land within four pixels
   * of each other, and 44 is the one the phone this product is played on
   * measures itself in.
   *
   * `.btn` has held exactly this value since block controls got a floor
   * (`components.css`), written as `2.75rem` with the 9mm argument in a comment
   * beside it. Naming it changes nothing computed — it is the same 44px in the
   * same place — and it buys the two things a literal cannot: a control that is
   * NOT a `.btn` can take the floor by asking for it rather than by somebody
   * remembering the number, and the next person to widen the floor edits one
   * line instead of grepping for `2.75rem` and hoping.
   *
   * IT IS DELIBERATELY NOT APPLIED WHOLESALE. A control's height is not always
   * styling: in an immersive composition it is the camera's arithmetic, and
   * `47615c6` is the commit where handing every button a floor took four
   * assertions red in one game. The token is a floor available to whoever wants
   * it, not a rule imposed on everything with a rim.
   */
  --tap-min: 2.75rem;

  --s1: 0.25rem;
  --s2: 0.5rem;
  --s3: 0.75rem;
  --s4: 1rem;
  --s5: 1.5rem;
  --s6: 2rem;
  --s7: 3rem;
  --s8: 4rem;

  --measure: 32rem;
  --shell-max: 92rem;
  --gutter: clamp(1rem, 3vw, 2rem);

  /* ------------------------------------------------------------- surface */

  /*
   * Radius is a rule, not a taste: **enamel is pressed steel.** Nothing here is
   * guillotined paper any more, so nothing is cut square — the size of the
   * radius says the size of the object, and that is all it says.
   */
  --r-field: 18px; /* a full region of enamel */
  --r-card: 14px;
  --r-panel: 12px;
  --r-control: 10px;
  --r-chip: 8px;
  --r-pill: 999px;

  --hair: 1px solid var(--rule);
  --hair-strong: 1px solid var(--rule-strong);
  --hair-print: 1px solid rgb(8 22 29 / 0.22); /* a rule printed on glaze */

  /*
   * A SHEET OF PAPER LYING ON THE TABLE, AND WHY IT IS NOT `--hair-strong`.
   *
   * The three above are RULES — lines drawn *inside* an object to divide it.
   * This one is an EDGE: the boundary between one object and the thing behind
   * it, at a hairline's weight because the object it goes round is paper rather
   * than a stage.
   *
   * The difference is not stylistic and it is measured. The three drawing games
   * each paint a `--stock` sheet, and `--stock` is cream; in DAYLIGHT the room's
   * table is cream too, so the only thing between the two materials is the line
   * round the sheet. `--rule-strong` is `color-mix(--deep 38%, --light-ground)`,
   * a tone mixed to divide cream from cream, and it measures — photographed by
   * `edgeAcross()` at 1280×800, the median step crossing the top of the canvas:
   *
   *     napkin     2.18:1        telephone  2.18:1
   *     oneline    2.18:1        the floor  3:1, which is what `.table` owes
   *
   * All three, identically, because all three copied the same five properties.
   * After dark they read 12.33 and always did, which is why three sessions
   * looked at this genre and saw nothing: the fault is in the DEFAULT light.
   *
   * `--rim` is `--edge` — the near-black every enamelled object in this product
   * is bounded by. A sheet was the one printed thing not taking it, and taking
   * it costs no layout at all: same width, same 1px, a different colour.
   *
   * NOT `.baize`, which is the same idea one size up and carries `--rim-w` (2px)
   * plus `--glaze-line-deep`. A baize is a stage and its rim is furniture; this
   * is the cut edge of a piece of paper, and 2px round a 416px sheet reads as a
   * frame somebody put the drawing in.
   */
  --hair-rim: 1px solid var(--rim);

  /* The speckle that makes the ground a material rather than a fill. Themed
     above: dark specks by daylight, light ones in the evening. */
  --grain-size: 7px 7px, 11px 11px;
  --grain-pos: 0 0, 4px 6px;
  /* Coarser, and always light — it sits on the card's dark enamel back. */
  --grain-card: radial-gradient(rgb(242 237 225 / 0.08) 1px, transparent 1px);
  --grain-card-size: 7px 7px;
  /* Glaze is not perfectly flat; a very faint horizontal draw from dipping. */
  --ruling: repeating-linear-gradient(0deg, rgb(8 22 29 / 0.028) 0 1px, transparent 1px 6px);

  /* ---------------------------------------------------------------- motion */

  --ease: cubic-bezier(0.2, 0.8, 0.2, 1);
  /* A press has to land under the finger, not catch up with it. */
  --press: 70ms;
  --fast: 140ms;
  /*
   * `--slow`, and it was `--mid` until 2026-08-05.
   *
   * `--mid` is also the name of a PALETTE colour — the tone between the pale
   * and the deep, declared in all thirteen palettes above — and the two
   * collided. The palettes declare theirs on a selector list whose first entry
   * is the bare `:root`, so a room that has never had `data-palette` written on
   * it (which is every first visit: index.html only sets the attribute when
   * localStorage already holds a choice) matched at specificity (0,1,0) — and
   * so does this block, which comes later and therefore won.
   *
   * `--mid` was then `240ms` by daylight, and the damage was not to an
   * animation. `--control: var(--mid)` a few hundred lines up became a
   * duration, `--control-hover` became `color-mix(in srgb, 240ms 72%, …)`
   * which is not a colour, the declaration was dropped as invalid, and **every
   * plain `.btn` on the site fell back to transparent** for anybody who had not
   * opened the palette picker. Live, in the default room, on every screen.
   *
   * Photographed before and after in `docs/screens/mid-token-fix.png`, twelve
   * cells: "Take a seat" on the front door measured `rgba(0, 0, 0, 0)` with the
   * collision and `rgb(211, 196, 164)` without it, and the five rooms that HAD
   * chosen a palette are pixel-identical either way — they outrank the duration
   * at (0,2,0) and never saw it. Both themes are in the sheet; the evening
   * never saw it either, because `--control` is `--deep-lift` there.
   * `.btn--primary` takes `--ink`/`--stock` and was never involved, which is
   * why the specimen is the plain button and not the loud one.
   *
   * Renaming the duration rather than the colour, because the colour is named
   * in the palette contract at the top of this file, appears thirteen times, and
   * is what a reader of `--pale`/`--mid`/`--deep` expects; and `press · fast ·
   * slow` is a scale a reader of durations expects.
   */
  --slow: 240ms;
}

/*
 * Everything here is arrival, not ambience — a card turning over, a bar coming
 * off. Collapsing the duration keeps the end state, which is the readable one.
 *
 * The delays go too, and that is not pedantry: the reveal staggers its results
 * 2.1s behind the bar, so leaving delays intact would make somebody who asked
 * for less motion wait longer for the answer.
 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}
