/* ============================================================================
   CHAMPION DIGITAL PTY LTD — cd-signature.css  ·  v1.0.0  ·  18 August 2026

   THE SIGNATURE. One file. Every page of Trade Handy, GeoBidder and
   GeoMundra — public, member, team, owner — links THIS and nothing else
   for foundation. Declared law by the owner, 18 August 2026.

   Full rationale, citations and the rules that bind this file:
        _DESIGN-LAW.md   (repo root, both repos)

   ── THE SEVEN RULES ──────────────────────────────────────────────────────
     1. Light is the default. Dark is a preference the user sets, never the
        brand. `[data-theme="dark"]` on <html>, remembered per user.
     2. Application code touches SEMANTIC tokens only. A raw hex in a page
        file is a build error — see _patch/_design_compliance.py.
     3. SUPERSEDED 7 Sep 2026 by LAW 108. It read "hierarchy is built in
        NEUTRALS; colour is reserved to carry meaning" — and obeyed literally
        it is why nine live screens measured TWO hues on a white page. The
        room is never white; a colour is chosen by the job of its area; and
        every value lives once, in §P below. See §P, §F, §G.
     4. Line-height in px ALWAYS lands on a multiple of 4.
     5. One product = one accent. The skin re-points the accent and NOTHING
        else. Same skeleton, different ramp (the Microsoft 365 model).
     6. Green is a STATUS colour only — paid, verified, passed. It is never
        a brand accent on any Champion Digital product.
     7. The peak moment is rationed. Five per platform, no more.
   =========================================================================

   ── NAMING — WHY --text AND NOT --ink ────────────────────────────────────
   th-core.css (linked by 122 of 130 pages) has defined `--ink` as the PAGE
   BED since long before this file existed. Here `--ink` would have meant the
   text colour — the exact opposite. Loading both would have inverted every
   page on the platform.

   The new file adapts, not the 122 old ones. Text tokens are --text,
   --text-body, --text-muted, --text-inverse. Do not reintroduce --ink.
   Checked 18 Aug 2026: the only other shared names (--line, --ok, --warn,
   --font-display) carry the SAME meaning in both files and are safe.
   ───────────────────────────────────────────────────────────────────────── */

/* ─────────────────────────────────────────────────────────────────────────
   1 · NEUTRAL LADDER — navy-tinted, saturation under 8%. Above ~10% a grey
   stops being neutral and starts fighting the accent.
   ───────────────────────────────────────────────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════════
   §P · THE FOURTEEN PURPOSES — LAW 108, 7 September 2026
   ───────────────────────────────────────────────────────────────────────────
   Sean: "any future change will be as easy as changing a colour code, and
   sections so we can evolve as the world and websites do into the future."

   THIS IS THAT PLACE. Change a value here and the whole platform follows —
   three pillars, 202 pages, both grounds. Nothing else holds a colour.

   A purpose is A JOB A COLOUR DOES TO A PERSON, chosen by the job of the area
   it sits in — never by which ramp it came from, never by which pillar owns
   the screen, and never by who the platform thinks is looking (rule 7).

   TO ADD ONE: add a section. Never widen an existing purpose to fit something
   new — that is the drift this file exists to stop.
   TO RETIRE ONE: supersede it against its replacement (Law 59) and leave the
   token declared. 202 pages may still name it.
   ═══════════════════════════════════════════════════════════════════════════ */
:root{
  /* 1 TRUST — the rail, the ink, weight that does not move.
       Measured: blue and green calming, red and black arousing (Jonauskaite 2020). */
  --p-trust-1:#142459;   --p-trust-2:#164A7B;
  /* 2 ACTION — the thing you press; pressing it commits the record.
       Standard: AS 1319 blue = MANDATORY. Right for what must be done, wrong for optional. */
  --p-action-1:#176BA0;  --p-action-2:#1C8CC0;
  /* 3 CLEAN — nothing outstanding. GROUND, not marks; the only large plain white allowed.
       Measured: lighter colours drew more positive emotions (Jonauskaite 2020). */
  --p-clean-1:#C7F9EE;   --p-clean-2:#6DFCD2;  --p-clean-3:#FFFFFF;
  /* 4 LIVE — happening now. A FILL, never lettering: #19AADE is 2.4:1 as type. */
  --p-live-1:#19AADE;    --p-live-2:#1AC9E6;
  /* 5 REWARD — DONE, and nothing else, ever. Rule 4, guarded above the other six.
       Standard: AS 1319 green = safe condition, first aid, egress. */
  --p-reward-1:#1DE4BD;  --p-reward-2:#42E7C9;
  /* 6 VALUE — money, standing, sealed, paid. Metal near chroma .095, never flat yellow,
       and never the lettering: the letters go dark (Law 107). */
  --p-value-1:#9C7E41;   --p-value-2:#EABD3B;  --p-value-3:#F4E3B4; --p-value-ink:#4A3A16;
  /* 7 GROWTH — effort climbing. ALWAYS a whole surface, never a small mark. */
  --p-growth-1:#820401;  --p-growth-2:#DE542C; --p-growth-3:#EF7E32;
  /* 8 WARNING — needs you soon. ALWAYS a small pill carrying a word and a number.
       Standard: AS 1319 yellow = significant hazard, and colour+SHAPE is the recognition.
       12 Sep 2026, GOLD-AS-WARN-12SEP. TWO DEFECTS IN ONE LINE, and neither is fixed with
       an invented colour — both are fixed with values this sheet already holds.
       (a) --p-warn-2 WAS #EABD3B, the identical hex Law 107 reserves as the metal pair
           six lines above at --p-value-2. "GOLD IS NOT A STATE … it means one thing only:
           THIS COST SOMETHING, OR IT WAS EARNED." A member who learns gold means
           paid-or-earned and then meets the same colour on a warning has learned nothing.
           It is now #F2C41B — the platform's OWN true yellow under Law 43, already
           declared at --warn-fill (line ~426 light, ~841 night). One warning yellow on
           this platform, not two, and the reserved metal gets its meaning back.
       (b) --p-warn-ink WAS #8A5A0B and FAILED ON BOTH STEPS OF ITS OWN BAND: 2.63:1 on
           #EE9A3A and 3.34:1 on the old #EABD3B, against a 4.5:1 floor. Poseidon clears
           both — 5.35:1 on #EE9A3A and 7.28:1 on #F2C41B, which is the exact pairing
           already measured and written into this file at the print-legibility note.
       Measured before changing, per the item's own instruction to read the consumers
       first: --p-warn-2 and --p-warn-ink had ZERO consumers anywhere in deploy/ outside
       this declaration, so nothing on screen moved — which is the only reason this was
       safe to do without a page-by-page walk. */
  --p-warn-1:#EE9A3A;    --p-warn-2:#F2C41B;   --p-warn-ink:#123955;
  /* 9 EMERGENCY — stop. The only purpose permitted to interrupt; reserved on all pillars. */
  --p-emerg-1:#C02323;   --p-emerg-2:#9E1C1C;
  /* 10 PLACE — ground, title, the unresolved. NEVER carries meaning alone:
        purple was the one colour with low cross-cultural agreement (Jonauskaite 2020). */
  --p-place-1:#29066B;   --p-place-2:#7D3AC1;  --p-place-3:#AF4BCE;
  /* 11 PEOPLE — crews, credit, care. Measured: pink among the most consistently
        positive colours across all 30 nations. */
  --p-people-1:#DB4CB2;  --p-people-2:#EB548C; --p-people-3:#EA7369;
  --p-people-4:#F0A58F;  --p-people-5:#FCEAE6; --p-people-ink:#7A1B4E;
  /* 12 EARTH — timber, soil, substrate, material. Not in the three ramps: added because
        a built-environment platform cannot describe its own materials without it. */
  --p-earth-1:#6B4E2E;   --p-earth-2:#A8783F;  --p-earth-3:#D2A96A; --p-earth-4:#EFDCC0;
  /* 13 STEEL — plant, hire, machinery, structure. A MEANING, not a neutral. */
  --p-steel-1:#3E4A5C;   --p-steel-2:#6B7A8F;  --p-steel-3:#9DAABB; --p-steel-4:#D6DEE8;
  /* 14 VEGETATION — trees, overlays, landscape. Deliberately far from REWARD:
        different hue, much darker, and never a small mark. */
  --p-veg-1:#14532D;     --p-veg-2:#2F7A3E;    --p-veg-3:#5CA85C;
  --p-veg-4:#9FD48A;     --p-veg-5:#DCF0C9;
}

/* ═══════════════════════════════════════════════════════════════════════════
   §F · THE FIELD — the room, and it is NEVER WHITE (Law 108, rule 1)
   ───────────────────────────────────────────────────────────────────────────
   Nine live screens measured TWO hues because --bed and --sheet were both
   #FFFFFF: the room and the cards were the same colour, so every card was a
   hairline drawn on nothing. This section is the fix, and it is one token.

   The field is a base plus BLOOMS of light. The blooms are not decoration —
   glass over flat canvas is a grey box (Law 80), so without them §G has
   nothing to refract and the whole treatment dies.

   Each pillar's field is made of its OWN purposes, so a pillar is recognised
   from the light in the room before any wordmark is read.
   ═══════════════════════════════════════════════════════════════════════════ */
/* ═══ RE-GROUNDED 12 SEPTEMBER 2026 — THE OWNER'S RULING ════════════════════
   Sean: "its harsh on the eyes to work all day on, and the psycology we
   researched i believe it has not hit the spot" ... "if it needs changing
   change it" ... "plaform wide gb gm etc" ... "its the champion digital
   branding".

   WHAT WAS WRONG, AND IT WAS NOT THE COLOUR. §3.1 of _DESIGN-LAW.md is exact:
   navy with ONE orange-family warm measured 34% more trustworthy, "blue does
   the work it is good at — it is our NEUTRAL ... and the warm accent carries
   the brand." What shipped was FOUR coloured lights — cyan, teal-green, gold
   and blue — behind every page at 10-26% and again behind every tile, with the
   warm buried at 15% competing with three others. Four accents at once is the
   opposite of one warm carrying the brand.

   AND THE ALL-DAY FATIGUE IS SATURATION, NOT BRIGHTNESS. Measured: the dark
   sheet a member reads a table on was #0E2C48 at chroma 22.7, on a #0F1B30
   ground at 12.9, under blooms up to 26%. A saturated colour behind text makes
   the eye re-focus against a ground with its own cast, and after an hour that
   is felt rather than seen. Every dark product built to be lived in sits under
   chroma 5; Stripe's navy is a hero band, never their dashboard.

   ONE HOUSE, THREE PILLARS. Sean's word — this is CHAMPION DIGITAL's branding,
   not Trade Handy's. So the GROUND is the same Poseidon paper and Poseidon
   night on all three, and the pillar is recognised by THE LIGHT IN THE ROOM
   rather than by the room being a different colour. The old rule said a pillar
   is known before any wordmark is read; that still holds — there is still one
   light and it is still that pillar's own hue. The company is the room.

   THE BLOOM IS NOT DELETED. Law 80 stands: glass over flat canvas is a grey
   box, so §G needs something to refract. One soft light at 7% (light) and 10%
   (night), in the top corner, away from the reading. */
:root, [data-skin="th"]{
  --field:#F7F9FB;
  --field-blooms:
    radial-gradient(70% 52% at 100% 0%, rgba(0,184,187,.07), transparent 66%);
}
[data-skin="gb"]{
  --field:#F7F9FB;
  --field-blooms:
    radial-gradient(70% 52% at 100% 0%, rgba(239,126,50,.07), transparent 66%);
}
[data-skin="gm"]{
  --field:#F7F9FB;
  --field-blooms:
    radial-gradient(70% 52% at 100% 0%, rgba(175,75,206,.07), transparent 66%);
}
/* THE NIGHT — one Champion Digital room, the pillar's own light in it.
   #101E29 is Poseidon's own hue (205 deg, taken from #123955 itself, not
   chosen) at chroma 9.8, down from 12.9 under four blooms. Body #C6D6E3 on it
   is 11.40:1. The previous per-pillar blacks — #23100D and #1A0A28 — were
   three different rooms for one company; the room is now the company and the
   light is the pillar. */
[data-theme="dark"], [data-ground="wash"]:not([data-theme="light"]),
[data-theme="dark"][data-skin="th"], [data-ground="wash"][data-skin="th"]:not([data-theme="light"]){
  --field:#0D141A;
  --field-blooms:
    radial-gradient(70% 52% at 100% 0%, rgba(0,184,187,.10), transparent 66%);
}
[data-theme="dark"][data-skin="gb"], [data-ground="wash"][data-skin="gb"]:not([data-theme="light"]){
  --field:#0D141A;
  --field-blooms:
    radial-gradient(70% 52% at 100% 0%, rgba(239,126,50,.10), transparent 66%);
}
[data-theme="dark"][data-skin="gm"], [data-ground="wash"][data-skin="gm"]:not([data-theme="light"]){
  --field:#0D141A;
  --field-blooms:
    radial-gradient(70% 52% at 100% 0%, rgba(175,75,206,.10), transparent 66%);
}
/* SUNLIGHT defeats the field, exactly as Law 80 defeats glass: a bloom on a
   screen in full sun is haze. Flat, maximum contrast, no gradient. */
[data-mode="sun"]{ --field:#FFFFFF; --field-blooms:none; }

/* ═══════════════════════════════════════════════════════════════════════════
   §M · THE MATERIALS — LAW 108, 8 September 2026
   ───────────────────────────────────────────────────────────────────────────
   Five materials sit ON the field, and each one MEANS something. A material is
   never chosen for how it looks; it is chosen because of what it is saying.

     GLASS   every tile — §G below.
     GLOW    a beacon. ONLY live things glow. If it pulses, it is happening now.
     CRYSTAL the prismatic edge. ONE surface per screen, on the thing the record
             is asking of you. The most expensive material here; spend it once.
     GOLD    value. Metal with a sheen across it, never a flat yellow, and the
             LETTERS GO DARK on it (Law 107 — #9C7E41 as text is 3.84:1).
     FLAME   growth. Warm, and ONLY where a number climbs or a streak runs.
             Nothing going wrong is allowed to be warm (Law 108 rule 5).

   Every value here is built from §P. Change a purpose and the materials follow.
   ═══════════════════════════════════════════════════════════════════════════ */
:root{
  /* CRYSTAL — the prism, and the facet of light across it */
  --crystal-edge:conic-gradient(from 210deg at 50% 50%,
      var(--p-live-2), var(--p-reward-1) 18%, var(--p-value-2) 38%,
      var(--p-growth-3) 52%, var(--p-place-3) 68%, var(--p-live-1) 86%, var(--p-live-2));
  --crystal-fill:rgba(255,255,255,.90);
  --crystal-sheen:linear-gradient(128deg,rgba(255,255,255,.50) 0%,rgba(255,255,255,0) 34%,
      rgba(255,255,255,0) 62%,rgba(255,255,255,.26) 100%);
  --crystal-halo:0 0 48px -14px rgba(26,201,230,.70);

  /* GLOW — three beacons, and nothing else in the platform may pulse */
  --beacon-live:0 0 0 3px rgba(29,228,189,.20), 0 0 16px 2px rgba(29,228,189,.85);
  --beacon-hot: 0 0 0 3px rgba(239,126,50,.20), 0 0 16px 2px rgba(239,126,50,.90);
  --beacon-red: 0 0 0 3px rgba(192,35,35,.18),  0 0 16px 2px rgba(192,35,35,.85);

  /* GOLD as a SURFACE — metal, a lit edge, a sheen, and dark letters */
  --gold-surface:linear-gradient(152deg,rgba(252,246,229,.88),
      rgba(244,227,180,.72) 44%,rgba(234,189,59,.42));
  --gold-edge:rgba(234,189,59,.45);
  --gold-halo:0 0 42px -16px rgba(234,189,59,.90);
  --gold-sheen:linear-gradient(105deg,transparent,rgba(255,255,255,.72),transparent);
  /* NOT --gold-ink: th-system already owns that name for the gold WORD on the level
     chip (used in dark only, where it is a pale metal). This is the opposite thing —
     the DARK LETTERS that sit ON a gold surface, because #9C7E41 as text measures
     3.84:1 and Law 73 forbids darkening the metal to fix it (Law 107). Two meanings,
     so two names; the collision was found by measuring the live page, where §M's
     value lost to th-system's html.th-on and the letters came back gold-on-gold. */
  --gold-letters:var(--p-value-ink);

  /* FLAME — growth, as a whole surface. Never a small mark. */
  --flame-surface:linear-gradient(158deg,#5A1408 0%,var(--p-growth-1) 26%,
      var(--p-growth-2) 62%,var(--p-growth-3) 100%);
  --flame-halo:0 0 56px -16px rgba(239,126,50,.95);
  --flame-ink:#FFF3E6;
  /* the flicker and the streak's glow, both taken from §P at alpha rather than
     hand-picked — the design gate refused the invented ones, correctly. */
  --flame-flicker:radial-gradient(46% 40% at 26% 96%,#EABD3B8C,transparent 66%),
                  radial-gradient(40% 36% at 72% 100%,#EF7E3280,transparent 68%);
  --streak-lit:linear-gradient(180deg,var(--p-value-3),var(--p-growth-3));
  --streak-glow:0 0 14px -3px #EF7E32F2;

  /* THE INSTRUMENTS — a meter carries the ramp at scale, never a grey bar */
  --meter-track:rgba(20,36,89,.09);
  --meter-inset:inset 0 1px 2px #14245920;
  --meter-cool:linear-gradient(90deg,var(--p-action-1),var(--p-live-1) 60%,var(--p-live-2));
  --meter-flow:linear-gradient(90deg,var(--p-live-1),var(--p-reward-1));
  --meter-fire:linear-gradient(90deg,var(--p-growth-1),var(--p-growth-3) 55%,var(--p-value-2));
  --meter-gold:linear-gradient(90deg,var(--p-value-ink),var(--p-value-1) 45%,var(--p-value-2));
  --meter-soc: linear-gradient(90deg,var(--p-people-1),var(--p-people-2) 55%,var(--p-people-4));
  --meter-vio: linear-gradient(90deg,var(--p-place-2),var(--p-place-3) 60%,var(--p-people-1));
  --meter-glow-cool:0 0 14px -2px rgba(25,170,222,.90);
  --meter-glow-flow:0 0 14px -2px rgba(29,228,189,.90);
  --meter-glow-fire:0 0 14px -2px rgba(239,126,50,.95);
  --meter-glow-gold:0 0 14px -2px rgba(234,189,59,.90);
}
[data-theme="dark"], [data-ground="wash"]:not([data-theme="light"]){
  --crystal-fill:rgba(24,38,64,.86);
  --crystal-sheen:linear-gradient(128deg,rgba(255,255,255,.16) 0%,rgba(255,255,255,0) 36%,
      rgba(255,255,255,0) 64%,rgba(255,255,255,.10) 100%);
  --gold-surface:linear-gradient(152deg,rgba(84,68,38,.70),rgba(156,126,65,.42) 44%,rgba(234,189,59,.22));
  --gold-letters:#F4E3B4;
  --meter-track:rgba(255,255,255,.14);
  --meter-inset:inset 0 1px 2px #00000059;
}
/* SUNLIGHT defeats every material — Law 80, obligation three. No glow, no sheen,
   no prism: flat fills at maximum contrast, because a glow on a screen in full
   sun is a smear and a sheen is glare. */
[data-mode="sun"]{
  --crystal-edge:var(--p-trust-1); --crystal-fill:#FFFFFF; --crystal-sheen:none; --crystal-halo:none;
  --beacon-live:none; --beacon-hot:none; --beacon-red:none;
  --gold-surface:#FFFFFF; --gold-edge:var(--p-value-1); --gold-halo:none; --gold-sheen:none;
  --flame-surface:var(--p-growth-1); --flame-halo:none;
  --meter-track:#FFFFFF; --meter-inset:none;
  --flame-flicker:none; --streak-glow:none;
  --meter-cool:var(--p-action-1); --meter-flow:var(--p-reward-1);
  --meter-fire:var(--p-growth-2); --meter-gold:var(--p-value-1);
  --meter-soc:var(--p-people-1);  --meter-vio:var(--p-place-2);
  --meter-glow-cool:none; --meter-glow-flow:none; --meter-glow-fire:none; --meter-glow-gold:none;
}

/* ═══════════════════════════════════════════════════════════════════════════
   §G · THE GLASS — what floats on the field
   ───────────────────────────────────────────────────────────────────────────
   White stops being the ground and becomes the thing ON it, which is where
   depth comes from for nothing. Translucent, blurred, with a lit top edge so
   the pane has a direction the light is coming from.

   Sunlight mode turns every one of these solid — Law 80, obligation three.
   ═══════════════════════════════════════════════════════════════════════════ */
:root{
  --glass:rgba(255,255,255,.78);
  --glass-2:rgba(255,255,255,.58);
  --glass-line:rgba(255,255,255,.85);
  --glass-blur:saturate(175%) blur(22px);
  --glass-lit:inset 0 1px 0 rgba(255,255,255,.92), inset 0 -1px 0 rgba(20,36,89,.05);
  --lift-1:0 1px 2px rgba(20,36,89,.05), 0 4px 14px -8px rgba(20,36,89,.16);
  --lift-2:0 2px 4px rgba(20,36,89,.05), 0 18px 40px -18px rgba(20,36,89,.30);
  --lift-3:0 4px 8px rgba(20,36,89,.07), 0 40px 80px -30px rgba(20,36,89,.40);
}
[data-theme="dark"], [data-ground="wash"]:not([data-theme="light"]){
  --glass:rgba(24,38,64,.55);
  --glass-2:rgba(24,38,64,.36);
  --glass-line:rgba(255,255,255,.14);
  --glass-lit:inset 0 1px 0 rgba(255,255,255,.13), inset 0 -1px 0 rgba(0,0,0,.30);
  --lift-1:0 1px 2px rgba(0,0,0,.45), 0 4px 14px -8px rgba(0,0,0,.6);
  --lift-2:0 2px 4px rgba(0,0,0,.5),  0 18px 40px -18px rgba(0,0,0,.75);
  --lift-3:0 4px 8px rgba(0,0,0,.55), 0 40px 80px -30px rgba(0,0,0,.85);
}
[data-mode="sun"]{
  --glass:#FFFFFF; --glass-2:#FFFFFF; --glass-line:var(--p-trust-1);
  --glass-blur:none; --glass-lit:none;
  --lift-1:none; --lift-2:none; --lift-3:none;
}

:root{
  /* ── 25 Aug 2026, LAW 74 — these are not greys. Every step is lifted from
     the owner's own Poseidon #123955 shade sheet, so the neutral ladder and
     the brand are the same colour at different lightness. Three brand colours
     on a cool grey read as an accent on a template; on their own darkest
     colour they read as one brand. Ratios measured against the real canvas
     #F4F9FD, not against white, by _patch/_colourproof.py. */
  --n-0:   #FFFFFF;
  /* THE WARM PAPER WING — the owner, 3 Sep 2026: "i did tell you im not a fan
     of this light blue" then "mix of a and c please" — white sheets (A) on a
     bronze-warmed paper ground (C), Poseidon-alpha hairlines. His word is the
     Law 74 amendment admitting these three warm neutrals, derived from Bronze
     Mist at near-white. The blue washes n-50/n-100 stay ONLY as values other
     roles read (dark-theme inks); no light GROUND reads them any more. */
  /* 3 Sep 2026, the owner on the cream: "THIS COLOUR IS OUTDATED." The warm
     tint is withdrawn. The bed is WHITE — Option A exactly — and a sunk well
     is Poseidon at 4%, never a tint of anything. */
  --w-25:  #FFFFFF;
  --w-50:  #FFFFFF;   /* THE LIGHT CANVAS — white                            */
  --w-100: rgba(18,57,85,.04);
  --n-25:  #FAFCFE;
  --n-50:  #F4F9FD;   /* the canvas                                          */
  --n-100: #EAF4FB;
  --n-200: #D0E5F5;
  --n-300: #ABD1ED;
  --n-400: #6CAFE0;
  --n-500: #2574AE;   /* 4.73:1 on canvas, 5.01:1 on surface — muted text    */   /* 5.5:1 on white, 5.2:1 on the page, 4.9:1 on the teal  */
                      /* tint. The old #6B7C88 carried a comment claiming 4.6:1 */
                      /* on white; it was 4.32:1, and 3.83:1 on the tint — so    */
                      /* every piece of muted text on the platform failed AA in  */
                      /* LIGHT mode, the default. Dark already passed. Measured   */
                      /* in a browser against real composited backgrounds.        */
  --n-600: #1B557E;   /* 7.60:1 on the warm canvas — body text             */
  --n-700: #164769;
  --n-800: #133C59;
  --n-900: #123955;   /* POSEIDON itself. 11.55:1 on the warm canvas       */

/* ─────────────────────────────────────────────────────────────────────────
   2 · SEMANTIC SURFACES — light is the default state of the platform.
   ───────────────────────────────────────────────────────────────────────── */
  /* RE-GROUNDED 3 Sep 2026 — his ruling "mix of a and c please": the ground
     is warm paper, the sheets are white, and the hairlines are Poseidon at
     low alpha instead of the pale blue steps. Ratios re-measured. */
  /* 2026 SET, light half. ELEVATION IS SHADOW HERE, NOT TINT — that is the
     difference between the two modes and it is the part most systems get wrong
     by inverting one into the other. Surfaces stay near-white; the base sits
     just off white so a full screen of it does not glare; depth comes from
     --lift-1 and --lift-2 below. */
  --canvas:      #F7F9FB;    /* the page — off white, chroma 1.6          */
  --surface:     #FFFFFF;    /* every sheet, at every elevation           */
  --surface-alt: #FFFFFF;
  --surface-sunk:#EFF3F6;    /* a sunk well, a table stripe               */
  --surface-3:   #FFFFFF;
  --surface-4:   #FFFFFF;
  --text:         #123955;   /* 11.42:1 on the page, 12.05:1 on a sheet   */
  /* 12 Sep 2026: was var(--n-600) = #1B557E at 7.93:1 on white. #264962 is
     9.00:1 on the new paper and 9.49:1 on a sheet — the same navy, closer to
     the anchor, and measurably less work over a long page. --n-600 itself is
     untouched because seven other roles read it. */
  --text-body:    #264962;
  --text-muted:   #5A7189;
  --text-inverse: var(--n-0);
  --line:        #E3E8EE;
  --line-strong: #CBD5DF;

/* ─────────────────────────────────────────────────────────────────────────
   3 · SIGNAL COLOURS — fixed across every skin, deliberately distinct from
   every brand accent so meaning never depends on which product you are in.

   🔴 DANGER IS MUTED ON PURPOSE. Fire-engine red measurably suppresses
   conversion and raises anxiety in B2B software. Brick reads as "stop and
   read" without reading as "you have broken something".
   ───────────────────────────────────────────────────────────────────────── */
  /* 25 Aug 2026, LAW 79. Signals gain an explicit FILL and an explicit ink.
     The ink is what may be type; the fill is what a lamp or badge is drawn in
     and it always carries Poseidon. Splitting them is what stopped the true
     yellow being used as text, which measures 1.53:1 on its own wash. */
  --ok:        #1F6B4A;   --ok-wash:     #E7F3ED;   --ok-line:   #A8E8CF;
  --ok-fill:   #13C692;   --ok-on:       #123955;
  /* ── 23 Aug 2026: THESE ARE NOW ALIASES, kept only so existing consumers
     keep working. The metal moved to --money-* in the :root block above,
     with a corrected value: #DEA221 sat at chroma 0.147 and read as yellow
     paint rather than metal, and the owner rejected it on sight.
     THREE FILES still read these names — deploy/index.html, th-desk.css and
     th-core.css. Aliasing rather than deleting means they pick up the
     corrected bronze automatically and nothing breaks in the meantime.
     Repoint them to --money-* and then delete this block. */
  --accent-gold:    var(--money);
  --accent-gold-on: var(--money-on);
  --accent-glint:   var(--money-light);
  --accent-shadow:  var(--money-shadow);
  --accent-metal:   var(--money-metal);
  /* --warn was #8A6D22 until 24 Aug 2026, which measured 4.42:1 on its own
     --warn-wash — under the 4.5:1 AA floor, and warn text sits on warn wash by
     definition. #856819 is the smallest darkening that clears every ground it
     can land on: wash 4.75, surface 5.26, canvas 4.94. Measured through the
     CSSOM with the real six stylesheets loaded, not computed from the file. */
  --warn:      #7A5D18;   --warn-wash:   #F6F0DF;   --warn-line: #EDD98C;
  /* the glow COLOUR, as distinct from --warn-glow the whole shadow. Added
     28 Aug 2026: 60 sites hard-coded rgba(245,185,69,…) — the retired amber
     #F5B945 struck out by law 79 — and two of them were glows, which cannot
     take a full box-shadow token because they sit inside `0 0 12px <colour>`. */
  --warn-glow-c:  rgba(242,196,27,.55);
  --warn-fill: #F2C41B;   --warn-on:     #123955;   /* TRUE YELLOW, law 43 */
  --danger:    #7A2E2E;   --danger-wash: #F6E8E8;   --danger-line:#E3BDB6;
  --danger-fill:#8C3A2E;  --danger-on:   #FFFFFF;   /* 7.61:1              */
  --info:      #2B5C8A;
  --fg-on-signal: #FFFFFF;
  /* Scrims stay dark in BOTH themes — a light scrim behind a modal reads as
     a rendering fault, not as depth. */
  --scrim:     rgba(6,19,32,.62);
  --scrim-solid: #061320;
  /* ...and therefore so does the text on one. Added 22 Aug 2026: --scrim-solid
     was theme-stable and had no matching foreground, so pages inverting a band
     reached for --text-inverse, which correctly FLIPS — and flipped to dark in
     dark mode, putting dark text on a permanently dark band. A token that never
     changes needs a partner that never changes. */
  --on-scrim:  #FFFFFF;
  /* A scrim needs a SECONDARY foreground too, or everything on it has to be
     pure white. Added 22 Aug 2026: 42 pages draw a permanently dark nav bar
     (rgba(12,36,56,.85)) and coloured its links with --mut, which flips with
     the theme — so in LIGHT mode the nav read mid-grey on dark navy at 2.3:1
     and was effectively unreadable. Theme-stable ground, theme-stable text.
     5.80:1 on the translucent bar, 9.09:1 on solid navy, 10.74:1 on #061320. */
  --on-scrim-muted: #B9C6D0;
}

/* ─────────────────────────────────────────────────────────────────────────
   4 · THE THREE SKINS — set `data-skin` on <html>. The skin re-points the
   accent and nothing else. Neutrals, type, space, radius and signals are
   identical across all three: that is what makes them one company.

   Navy + a warm accent is the highest-trust pairing measured in B2B — 34%
   more trustworthy than the alternatives tested. Plain blue is table stakes
   (≈70% of SaaS already uses it), so it is the NEUTRAL here, not the accent.

   Every accent below carries >=4.5:1 against --surface, and white text on
   the solid fill also carries >=4.5:1. Verified, not assumed.
   ───────────────────────────────────────────────────────────────────────── */
:root{
  /* ── MONEY · champagne bronze · added 23 Aug 2026 ───────────────────────
     Bronze is NOT a product colour. It is the money colour, and it belongs
     to every skin: pay, bid, win, deposit, upgrade, the tier badge. Fixing
     that meaning once is what lets a colour carry meaning at all.

     WHY THIS EXACT VALUE. The owner rejected two predecessors and was right
     both times. #DEA221 sat at chroma 0.147 and read as yellow paint;
     #9A6B10 sat at L56% and read as mud. The luxury research names the band
     as #C69B3C to #D4AF37, "warm and slightly desaturated", and warns that
     bright yellow-golds "read neon and cheap". The number that decides it is
     CHROMA, not lightness. Held near 0.095 inside the band, it reads as
     metal.

     GOLD IS NEVER TEXT. The deep gold that used to serve that role was
     rejected as brown, and the fix was to delete the role rather than soften
     it. Metal is a fill and a signal; type is navy or teal.

     Metal reads as metal through a RANGE, never one flat value. The dark
     stop is not decoration — without it the gradient reads as yellow. */
  /* ── 25 Aug 2026, LAWS 73 + 76. BRONZE MIST #9C7E41 is the metal, and the
     ramp STOPS at it — every step below the anchor is the brown the owner
     rejected, and where a darker warm tone seems wanted the answer is
     Poseidon. #CEA565 (the champagne that used to sit here) was never a
     colour anyone chose; #9C7E41 was in zero files before today. */
  --money:        #9C7E41;   /* the anchor, and the floor of the ramp       */
  --money-on:     #123955;   /* Poseidon is what goes ON the metal          */
  --money-light:  #D4C098;   /* highlight / gradient crown                  */
  --money-shadow: #B4914B;   /* gradient foot — NOT below the anchor        */
  --money-wash:   #F6F1E8;
  --money-metal:  linear-gradient(145deg,#D4C098 0%,#B4914B 52%,#9C7E41 100%);
  --money-glow:   0 5px 18px -5px rgba(156,126,65,.55);

  /* ── LAW 107, 7 Sep 2026 · THE GOLD, AND IT IS NOT A STATE ────────────────
     Sean, 7 Sep: "just add the gold to the specials and details". Bronze Mist
     stops being a brand anchor and becomes a MEANING: this cost something, or
     it was earned. Featured placement, a storefront, elite standing, a
     certified pack, a sealed certificate — and money, which it already carried.

     GOLD IS ALWAYS A HAIRLINE AND A SMALL-CAPS LABEL, NEVER A FILLED PILL.
     That is not taste. The three states below ARE filled pills with a dot, so a
     bordered, uppercase, dotless mark cannot be misread as one at a glance. A
     member learns one rule and it never breaks: filled pill means state, gold
     outline means paid or earned. Law 77 still governs it — the metal goes
     LIGHTER on the dark ground, never darker (see §5). */
  --gold:        var(--money);
  --gold-lit:    #EABD3B;
  --gold-wash:   var(--money-wash);
  --gold-line:   #DCC489;
  --gold-on:     var(--money-on);

  /* ── LAW 107 · STATE IS FIXED ON EVERY PILLAR AND BOTH GROUNDS ────────────
     The pillar colour changes room to room. These do not. A red bar means the
     same thing in a site diary, an auction and a title search. #C02323 is
     CRITICAL and is reserved — no pillar may spend it on identity, however well
     it would suit that pillar's ramp. And state is never carried by hue alone:
     every lamp carries a WORD and a SHAPE, which is the whole of superseded
     Law 79 and the reason this platform needs no colour-vision switch.
     -d is the DOT/stripe (the saturated step), -w is the wash it sits on. */
  --st-good: #0B6B58;  --st-good-w:#E2FAF3;  --st-good-d:#0F8F76;
  --st-warn: #8A5A0B;  --st-warn-w:#FDF0DC;  --st-warn-d:#EE9A3A;
  --st-crit: #9E1C1C;  --st-crit-w:#FBE9E7;  --st-crit-d:#C02323;
  --st-idle: #69799A;  --st-idle-w:#EEF2F8;

  /* ── LAW 106 · THE TWELVE ARCS READ DOWN ONE RAMP ─────────────────────────
     Where you stand in the life of an asset is visible on a strip nobody has to
     read. A ramp is an ordered thing and it is spent on the platform's ordered
     thing. Light is arc 1 (origination); deep is arc 12 (renewal and the loop). */
  --arc-1:#C7F9EE;  --arc-2:#B0F3E6;  --arc-3:#8FEDDA;  --arc-4:#6DFCD2;
  --arc-5:#42E7C9;  --arc-6:#1DE4BD;  --arc-7:#1AC9E6;  --arc-8:#19AADE;
  --arc-9:#1C8CC0;  --arc-10:#176BA0; --arc-11:#164A7B; --arc-12:#142459;
  --money-glow-c: rgba(156,126,65,.55);   /* the colour alone, for inline glows */

  /* ═══ TIER TWO · FUNCTION — RETIRED FROM THIS FILE 30 Aug 2026 ═══════════
     Six colours (--fn-1..6 and --fn-series) lived here and are gone, on Sean's
     GO. THE PLATFORM HAD TWO TIER TWO SETS AND THEY WERE MUTUALLY ILLEGAL.

     The survivor is th-tier2.css's --t2-1..8, and _lookcheck.py already names
     that file as the single Tier Two definition — so the six here were never
     registered as law in the first place.

     BOTH WERE MACHINE-SOLVED, so this was not a taste call. The six scored
     BETTER on their own stated numbers: min ΔE00 16.9, deuteranopia 14.0,
     protanopia 14.5, against the eight's worst pair of 13.9. They were retired
     anyway, for three reasons that outrank the headline figure:

       · THE SIX SOLVE ONE GROUND. Every contrast figure recorded for them was
         "on the dark ground" (worst 4.11:1). The eight are solved across the
         LIGHT set and the DARK set simultaneously, carry a third Sunlight set,
         and hold >=3.15:1 on every ground. This platform ships three modes and
         Law 80 requires the third to work outdoors.
       · THE TWO SETS FORBID EACH OTHER. th-tier2.css:56-64 records that hues
         64-142 hold no legal Tier Two colour at all — and --fn-3 #89A253 and
         --fn-4 #54815D are exactly that band. Each set's own method declares
         the other's colours out of bounds. Both could not stand.
       · NOTHING CONSUMED THE SIX. Counted before removing: --fn-1..6 and
         --fn-series, zero uses across every .html, .js and .css in deploy/.
         The eight are used 12 to 18 times each.

     The measurements above are preserved in this comment rather than lost with
     the values, because the method is the part worth keeping: hue, L* and C*
     solved TOGETHER by annealing, never spaced evenly round the wheel — an
     evenly-spaced ramp at constant lightness was tried first and scored 8.1
     under deuteranopia, because the hues collapse. Lightness varies on purpose
     so a dichromat separates by luminance where hue fails. th-tier2.css reaches
     the same conclusion independently and says so in its own header.

     EXTENDING TIER TWO: edit th-tier2.css, re-run its solver, and never add a
     colour by eye. Do not re-add a second set here. ─────────────────────────── */
  /* --money-stroke — THE GOLD THAT MAY BE A LINE. Added 24 Aug 2026 (D16).
     --money is a FILL and only a fill on light: it measures 2.14:1 on --canvas
     against a 3:1 floor for non-text graphics. A chart line, a rule, a small
     mark all sit under that floor.
     #A97C36 holds --money's hue (36.6deg) and saturation and measures 3.51:1 on
     --canvas, 3.73:1 on --surface — a margin, not a boundary.
     NOT --money-shadow #B08A48. That reads 3.19:1 on --surface, which is why it
     was used, but 2.9982:1 on --canvas, which is where charts actually sit. It
     fails by 0.0018. Measuring a token against the wrong ground is how a
     contrast fix ships short and reads as done.
     Gold only becomes legal TEXT at #926B2E, which is brown — so "gold is never
     type" is a measurement, not a style position. */
  /* 25 Aug 2026, LAW 76: #A97C36 was derived and sits in no step of the
     owner's ramp. The ANCHOR itself clears the non-text floor — 3.62:1 on
     --canvas, 3.84:1 on --surface — so the line comes from the ramp too and
     the palette carries no invented values at all. */
  --money-stroke: #9C7E41;

  /* THE SHELL BAR. th-shell.js:397 hardcoded rgba(255,255,255,.86), so #thTop
     stayed white in dark mode across all 81 shell pages while the sidebar, the
     canvas and every panel flipped correctly around it — the account menu has
     offered a Dark button since it was built. Same value as before; now a token
     with a dark counterpart. */
  --shell-bar: rgba(255,255,255,.86);
}

:root,
[data-skin="th"]{
  /* ══ LAW 106, 7 Sep 2026 — THE PILLAR WEARS ITS RAMP ══════════════════════
     TRADE HANDY IS THE COOL RAMP. The record: work, trust, custody — the
     colour of a thing being kept properly.
         ink #142459 · press #176BA0 · energy #19AADE
     PRESS is chosen by measurement, not taste: #176BA0 carries white at 5.7:1
     and reads as a link on white at the same figure.
     ENERGY IS A FILL AND NEVER LETTERING ON A LIGHT GROUND — #19AADE measures
     2.4:1 as type. That is the whole of superseded Law 75 carried forward; only
     the hue changed, the measurement did not.
     Supersedes the deep teal #0B7A7D (Law 97), which all three skins wore — so
     nothing on screen said which of the three products you had opened. */
  /* ── ONE INK LADDER, ALL THREE PILLARS — SEAN'S RULING, 14 SEPTEMBER 2026 ──
     "im not happy with the text colour both in light and dark on geobidder",
     then "not brown, i dont like the brown", then he chose to deepen Poseidon
     across the federation rather than tint each pillar's words.

     WHY THE WORDS STOP CARRYING THE PILLAR. Six marketplaces were measured on
     14 Sep: Ricardo #212121, Catawiki #000000, Reverb #212121, Vinted #15191A,
     Grays #333132 — and Machines4u, the only one using a navy ink (#203446),
     is also the most dated of the six. Every modern one puts the identity in
     the ACCENT and leaves the words alone. GeoBidder's ink was copper #472512,
     and copper as lettering is simply brown; its NIGHT ink was #FDF4F2, hue 11,
     a warm white that put a pink cast on every word after dark. Neither was a
     contrast failure, which is why no gate ever caught them.

     #101A21 IS NOT A NEW COLOUR. It is hue 205 — Poseidon's own hue, the same
     205 as #123955 — at lower saturation and half the lightness. A deeper step
     on the platform's own ramp, not a departure from it. 16.70:1 on the paper.

     AND THE LOGOS DO NOT MOVE. Sean, same day: "logos alway the same no colour
     change". Measured before touching anything: all fourteen marks carry their
     own #123955 and #00B8BB with ZERO currentColor, so no ink change can reach
     them. Poseidon is not weakened by this — it is concentrated into the mark
     and the accents, which is where a brand colour belongs. ── */
  --skin-ink:      #101A21;   /* hue 205 · 16.70:1 on the paper */
  --skin-ink-2:    #2B3B47;   /* hue 206 · 10.94:1 — links and seconds */
  --skin-mut:      #5A6B77;   /* hue 205 ·  5.23:1 — muted and captions */
  --accent:        #00B8BB;   /* the ANCHOR is the fill; the lettering is chosen */
  --accent-2:      #12547D;
  --accent-grad:   linear-gradient(135deg,#176BA0,#12547D);
  --accent-hover:  #12547D;
  --accent-press:  #0E4260;
  --accent-wash:   rgba(23,107,160,.09);
  --accent-line:   rgba(23,107,160,.32);
  --accent-on:     #123955;
  --accent-text:   #007173;   /* the LINK step — small text needs 4.5 where a fill does not */
  --energy:        #19AADE;
  --energy-on:     #04121F;
  --energy-wash:   rgba(25,170,222,.12);
}

  /* ── THE ALPHA LADDER ─────────────────────────────────────────────────────
   NOT INSIDE A BLOCK UNTIL 24 Aug 2026, AND THEREFORE DEAD. The ladder was
   written after the :root above had already closed, so all eight declarations
   sat at the top level of the stylesheet. CSS discards a declaration outside a
   rule, and the parser then loses its place: measured in the browser, neither
   --accent-a70 nor --accent-a90 existed in the CSSOM, and the whole
   [data-skin="gb"] block that follows was dropped with them — so GeoBidder's
   LIGHT skin had never applied either. Wrapped in :root, which is where it
   always meant to be.

   Plain :root, not :root,[data-skin="th"]: color-mix resolves var(--accent) at
   use time, so the ladder follows whichever skin and theme the element is under
   without being declared per-skin.

   The original note, kept because the reasoning still stands: 94 pages
   hand-wrote rgba(0,184,187,a) because no token existed — artwork ink used as
   interface paint, the single largest source of drift on the platform. The
   steps were chosen by measuring all 435 real uses, not by picking round
   numbers: .40 alone is 27% of them and .50 is 14.5%. */
:root{
  --accent-a08: color-mix(in srgb, var(--accent)  8%, transparent);
  --accent-a14: color-mix(in srgb, var(--accent) 14%, transparent);
  --accent-a22: color-mix(in srgb, var(--accent) 22%, transparent);
  --accent-a32: color-mix(in srgb, var(--accent) 32%, transparent);
  --accent-a40: color-mix(in srgb, var(--accent) 40%, transparent);
  --accent-a50: color-mix(in srgb, var(--accent) 50%, transparent);
  --accent-a70: color-mix(in srgb, var(--accent) 70%, transparent);
  --accent-a90: color-mix(in srgb, var(--accent) 90%, transparent);
}


[data-skin="gb"]{
  /* ══ LAW 106 — GEOBIDDER IS THE WARM RAMP ═════════════════════════════════
     The market. Maroon ink and orange energy: an auction house, not a warning
     label.   ink #820401 · press #820401 · energy #EF7E32
     IT PRESSES IN MAROON, NOT ORANGE, AND THE REASON IS BINDING. #EF7E32 fails
     contrast as a button, and #C02323 is CRITICAL on every pillar (Law 107). A
     market that shouts in error-red every time it wants a bid teaches people to
     ignore red, and the next time red means something they will not look.
     This block was bronze until 23 Aug and teal until today; the mark it has to
     agree with is the market stall, and maroon is what an auction house wears. */
  /* ── COPPER, NOT MAROON — SEAN'S RULING, 12 SEPTEMBER 2026 ────────────────
     "im not happy with geobidders colours at all", then he chose copper from
     five lawful candidates rendered on the real grounds.

     THE MAROON WAS NEVER WRONG BY THE NUMBERS — #820401 measures 10.12:1 on
     the paper, better than what replaces it. It was wrong by MEANING. A deep
     blood red on every heading and every rail made a marketplace read as an
     alarm, which is the same failure this law's own text warns about in the
     paragraph above: "a market that shouts in error-red every time it wants a
     bid teaches people to ignore red". That sentence was written about the
     ENERGY step and the ink was left shouting anyway.

     COPPER KEEPS EVERYTHING THE MAROON WAS FOR. It is warm, it is not blue,
     violet, green or gold — so it cannot be confused with Trade Handy,
     GeoMundra, a GOOD state or Law 107's "this cost something". It sits 21
     degrees from red, far enough that nobody reads a bid as a warning. And it
     reads as METAL AND TRADE, which is what a yard selling plant and tools
     should read as.

     EVERY STEP IS THE SAME 21-DEGREE HUE AT A DIFFERENT LIGHTNESS, solved
     against the real ground rather than picked: ink 12.91:1, secondary 9.34:1,
     press 6.27:1, all on #F7F9FB. */
  --skin-ink:      #101A21;   /* hue 205 · 16.70:1 on the paper */
  --skin-ink-2:    #2B3B47;   /* hue 206 · 10.94:1 — links and seconds */
  --skin-mut:      #5A6B77;   /* hue 205 ·  5.23:1 — muted and captions */
  --accent:        #B95A28;   /* the ANCHOR is the fill; the lettering is chosen */
  --accent-2:      #6C381B;
  --accent-grad:   linear-gradient(135deg,#8F4A24,#6C381B);
  --accent-hover:  #6C381B;
  --accent-press:  #582D16;
  --accent-wash:   rgba(143,74,36,.08);
  --accent-line:   rgba(143,74,36,.30);
  --accent-on:     #FFFFFF;
  --accent-text:   #A04E23;   /* the LINK step — small text needs 4.5 where a fill does not */
  --energy:        #D9743A;
  --energy-on:     #3A1200;
  --energy-wash:   rgba(217,116,58,.14);
}

[data-skin="gm"]{
  /* ══ LAW 106 — GEOMUNDRA IS THE VIOLET RAMP ═══════════════════════════════
     Place. Land, title, the map — the one pillar that is about ground rather
     than people.   ink #29066B · press #7D3AC1 · energy #AF4BCE
     --accent-text is one step deeper than --accent because a link sits on the
     room and a button sits on its own fill; they are not the same measurement. */
  --skin-ink:      #101A21;   /* hue 205 · 16.70:1 on the paper */
  --skin-ink-2:    #2B3B47;   /* hue 206 · 10.94:1 — links and seconds */
  --skin-mut:      #5A6B77;   /* hue 205 ·  5.23:1 — muted and captions */
  --accent:        #7D3AC1;   /* the ANCHOR is the fill; the lettering is chosen */
  --accent-2:      #692FA6;
  --accent-grad:   linear-gradient(135deg,#7D3AC1,#692FA6);
  --accent-hover:  #692FA6;
  --accent-press:  #55258A;
  --accent-wash:   rgba(125,58,193,.09);
  --accent-line:   rgba(125,58,193,.30);
  --accent-on:     #FFFFFF;
  --accent-text:   #7D3AC1;   /* the LINK step — small text needs 4.5 where a fill does not */
  --energy:        #AF4BCE;
  --energy-on:     #FFFFFF;
  --energy-wash:   rgba(175,75,206,.12);
}

/* ─────────────────────────────────────────────────────────────────────────
   5 · DARK — a PREFERENCE, never the brand. Set `data-theme="dark"` on
   <html> and persist it per user. Owner's ruling, 18 Aug 2026: "for the
   desk a user can set the theme to dark if they really want to."

   Dark is not light inverted. Three things change: never pure black
   (halation, and no room to elevate below it); accents LIFT because a
   4.5:1 accent on white is unreadable on near-black; shadows deepen and
   the hairline flips to white.
   ───────────────────────────────────────────────────────────────────────── */
/* 28 Aug 2026 — data-ground="wash" is the DARK GROUND FLAG, and it sat in the same
   declaration block as [data-theme="dark"], so it applied whatever the theme said.
   129 of 133 pages carry it on <html> and cd-theme.js never clears it, which made
   the light-mode switch INERT on all 129: measured on login.html, setting
   data-theme="light" left --canvas at #071520 and --surface at #0C273A.
   Sean's ruling is dark by default AND switchable, so the flag now yields to an
   explicit light choice. Dark is untouched — :not([data-theme="light"]) is true
   whenever the attribute is absent or says dark. */
[data-theme="dark"],
[data-ground="wash"]:not([data-theme="light"]){
  /* Money lifts in dark: a colour tuned against white goes muddy on
     near-black. Same hue, same chroma, higher lightness. */
  --money:        #D4C098;   /* 8.01:1 on --surface — law 77             */
  --money-light:  #EAE1CE;
  --money-shadow: #B4914B;   /* still never below the anchor              */
  --money-wash:   rgba(212,192,152,.13);
  --money-metal:  linear-gradient(145deg,#EAE1CE 0%,#D4C098 50%,#B4914B 100%);
  --money-glow-c: rgba(212,192,152,.42);   /* law 77 — lighter on the dark ground */
  /* In dark, --money reads 9.23:1 on the dark canvas (re-measured after
     the 2 Sep 2026 retone — it holds) and needs no
     substitute. */
  --money-stroke: var(--money);
  /* RETONED 2 Sep 2026 — the owner, on the near-black desk: "The background
     is black which i dont like, using to much black and gray … add more
     colour tone, a little more glow on the background only." Same darkness
     family, chroma pushed toward Poseidon — blue, never black. Every ratio
     beside a token below was re-measured against the new grounds. */
  /* DE-SATURATED 12 Sep 2026 on the owner's ruling. These four were the real
     source of the all-day fatigue: --surface, the sheet a member reads a TABLE
     on, was #0E2C48 at chroma 22.7 — more saturated than the ground behind it.
     The hue is unchanged (Poseidon's own 205 deg); only the amount of it moved.
     Every ratio below re-measured against the new grounds. */
  /* ══ THE OCEAN NIGHT — SEAN'S RULING, 12 SEPTEMBER 2026, SECOND PASS ═══════
     "BACK IS NOT A BACKGROUND FOR ME" — and he was right, with the record to
     prove it. On 2 Sep he had already said: "The background is black which i
     dont like, using to much black and gray ... add more colour tone, a little
     more glow on the background only." This morning I did the OPPOSITE: cut the
     ground's chroma from 12.9 to 9.8 chasing eye strain, and landed on #101E29
     at lightness 0.11 — barely above the #070E1E he rejected ten days ago.

     "DON MATCH THE EXISTING COLOUR, YOU SET A NEW WAY FIX IT." So this is not a
     revert. It is Poseidon at ITS OWN CHROMA, 26.3, held at 205 degrees and
     taken darker — colour, not the absence of it. Nothing here is desaturated
     toward grey and nothing reads black.

     AND THE STRAIN FIX SURVIVES, because the strain was never saturation on its
     own. It was FOUR coloured blooms up to 26% MOVING under a paragraph while
     pale text sat at 13:1 on a dark saturated sheet. The blooms are one light
     at 10% now; the ground is LIGHTER; and body text reads 9.49:1 instead of
     13:1. A lighter ground at 9.5:1 is the easier combination, not the harder
     one — the eye is not re-focusing against a surface that changes under it.

     EVERY INK AND EVERY PRESS WAS RE-SOLVED AGAINST THIS GROUND, not carried
     over: Trade Handy's accent measured 4.44:1 on it and would have shipped
     under the floor if it had been. All three now sit at 6.0:1, hover 7.6,
     press 9.2, each holding its own hue exactly. */
  /* ══ THE 2026 SET — SEAN'S RULING, 12 SEPTEMBER 2026 ═══════════════════════
     "this new data is you new theme and colour laws" — after asking the question
     that ended three failed attempts in one day: "IS THERE NO STANDARD YOU HAVE
     RESEACHED". There was, and it had not been read. Every colour today had been
     solved with a contrast calculator and none of it against published practice.

     WHAT THE RESEARCH SAYS, and each line here is sourced in Law 120:
       · DARK: a TRUE DARK base, never pure black, and elevation expressed by
         LIGHTNESS — each layer a step up from its parent. Material 3 does this
         as a tonal tint that grows 0/5/8/11/12/14 percent with elevation.
       · "More saturated colors tend to visually VIBRATE against darker
         backgrounds, making them harder to read." That sentence is why the
         chroma-26 ocean ground was rejected on sight.
       · LIGHT: elevation is SHADOW, not tint. Surfaces stay near-white and the
         base sits just off white so nothing glares.
       · ONE saturated accent, reserved for the primary action, so the CTA is
         unmissable BECAUSE everything else is calm.
       · GLOW is high-saturation colour at LOW OPACITY fading to transparent, on
         the interactive thing only — "controlled energy, not highlighter chaos".

     AND THE FINDING THAT DECIDED THE PALETTE: "Transformative Teal" is WGSN and
     Coloro's 2026 Colour of the Year, named as suited to B2B platforms needing
     credibility and innovation together. CYAN SKY #00B8BB, an anchor here since
     day one, IS that colour. The research also warns that "when every SaaS
     company, fintech startup and consulting firm uses the same blue-dominant
     palette, nobody stands out" — so Sean ruled, 12 Sep: CYAN CARRIES THE BRAND,
     POSEIDON HOLDS THE ROOM. Navy is the neutral ladder and nothing else.
     ═════════════════════════════════════════════════════════════════════════ */
  --canvas:       #0D141A;   /* level 0 · the page      chroma 5.1        */
  --surface:      #131C23;   /* level 1 · a card        chroma 6.3        */
  --surface-alt:  #19242B;   /* level 2 · raised        chroma 7.1        */
  --surface-sunk: #060E13;   /* a sunk well                              */
  --surface-3:    #212D35;   /* level 3 · menu, modal                    */
  --surface-4:    #2A363E;   /* level 4 · popover                        */
  --text:          #F2F7FA;  /* 17.19:1 on the page, 15.98:1 on a card   */
  --text-body:     #DCE6EE;  /* 14.66:1 on the page, 13.63:1 on a card   */
  --text-muted:    #8C9CA9;  /*  6.57:1 on the page,  6.11:1 on a card   */
  --text-inverse:  #071520;
  --line:         rgba(255,255,255,.10);
  --line-strong:  rgba(255,255,255,.17);

  --ok:     #13C692;  --ok-wash:     rgba(19,198,146,.13);  --ok-line:     rgba(19,198,146,.32);
  --ok-fill:#13C692;  --ok-on:       #071520;
  --warn:   #F2C41B;  --warn-wash:   rgba(242,196,27,.13);  --warn-line:   rgba(242,196,27,.32);
  --warn-glow-c:  rgba(242,196,27,.42);
  --warn-fill:#F2C41B; --warn-on:    #071520;

  /* --surface at the bar's own weight, so the chrome sits with the
     canvas instead of glowing white above it. */
  /* GLASS 3 Sep 2026 — his ruling: the chrome carries the background effect.
     Translucent over the aurora; #thTop already blurs behind it. */
  --shell-bar: rgba(14,44,72,.58);
  /* LAW 79 — the light brick #8C3A2E is 2.02:1 here and vanishes. This is
     the dark partner the old law never had. */
  --danger: #D2695B;  --danger-wash: rgba(210,105,91,.13); --danger-line: rgba(210,105,91,.32);
  --danger-fill:#D2695B; --danger-on: #071520;
  --fg-on-signal: #071520;
}

/* ── LAW 106 · THE NIGHT IS DESIGNED, NOT INVERTED ─────────────────────────
   Each pillar's night is ITS OWN deepest step taken darker — a near-black
   tinted toward that pillar's hue — so GeoBidder at night is warm-black and
   GeoMundra is violet-black. Three products with one near-black is one product
   with the lights off, which is the same defect the day side had.

   The press step LIFTS in the dark, because a colour that carries 4.5:1 on
   white carries nothing on near-black. What goes ON the lifted fill is the
   pillar's own darkest step, not white.

   The three of these used to be one shared teal, kept "as explicit selectors
   rather than deleted, so the next person can see the convergence was
   deliberate". It was deliberate, and it is now reversed for the same reason
   it was written down. */
[data-theme="dark"],
[data-ground="wash"]:not([data-theme="light"]),
[data-ground="wash"][data-skin="th"]:not([data-theme="light"]),
[data-theme="dark"][data-skin="th"]{
  /* 12 Sep 2026, ocean night: lifted to match the lighter ground, each pillar
     holding its OWN tint exactly — only lightness moved. Solved against #133A56,
     not carried over, because an ink that was right on a darker ground is not
     right on this one. 11.0 / 9.4 / 6.0 on the night. */
  --skin-ink: #F2F7FA;  --skin-ink-2: #D6E4EF;  --skin-mut: #A9BACA;   /* one night ink, hue 202-209 */
  /* 12 Sep 2026: the shell carried its OWN copy of the night, so calming
     --field alone would have left every rail and panel on the old saturated
     bed while the page behind them went quiet. Same values as --canvas and
     --surface above, deliberately, so there is one night and not two. */
  --skin-bed: #0D141A;  --skin-sheet: #131C23;
  /* ONE saturated accent, on the ACTION only. 7.0:1 on the page; the lettering
     is the page itself, which is what makes a fill read as a fill. */
  --accent: #00B8BB;  --accent-on: #0D141A;  --accent-text: #00B8BB;
  --accent-wash: rgba(0,184,187,.16);  --accent-line: rgba(0,184,187,.34);   /* the GLOW — low opacity, the accent's own colour */
  /* 12 Sep 2026: this pair sat BELOW the accent line and therefore WON — the file
     said one thing and the browser painted another, which is Law 119 exactly. Found
     by reading getComputedStyle for all six skin/mode pairs before pushing. */
  --accent-on: #0D141A;  --accent-text: #00B8BB;
  --energy: #1AC9E6;  --energy-on: #04121F;  --energy-wash: rgba(26,201,230,.16);
}
[data-theme="dark"][data-skin="gb"],
[data-ground="wash"][data-skin="gb"]:not([data-theme="light"]){
  /* 12 Sep 2026, ocean night: lifted to match the lighter ground, each pillar
     holding its OWN tint exactly — only lightness moved. Solved against #133A56,
     not carried over, because an ink that was right on a darker ground is not
     right on this one. 11.0 / 9.4 / 6.0 on the night. */
  --skin-ink: #F2F7FA;  --skin-ink-2: #D6E4EF;  --skin-mut: #A9BACA;   /* one night ink, hue 202-209 */
  /* 12 Sep 2026: unified with the Poseidon night. Only --field was changed this
     morning, so the SHELL kept painting this pillar's rail and panels on the old
     warm-black bed while the page behind them went Poseidon — two nights on one screen.
     Found by rendering the real stylesheet rather than reading it. */
  --skin-bed: #0D141A;  --skin-sheet: #131C23;
  /* 12 Sep 2026: the night step moves with the day step, so both are copper.
     #E89A63 is 7.45:1 on the new night — the value Sean actually looked at when
     he chose, which is why it is used rather than the one my solver preferred. */
  /* ONE saturated accent, on the ACTION only. 7.0:1 on the page; the lettering
     is the page itself, which is what makes a fill read as a fill. */
  --accent: #DD8B60;  --accent-on: #0D141A;  --accent-text: #D87A49;
  --accent-wash: rgba(221,139,96,.16);  --accent-line: rgba(221,139,96,.34);   /* the GLOW — low opacity, the accent's own colour */
  /* 12 Sep 2026: this pair sat BELOW the accent line and therefore WON — the file
     said one thing and the browser painted another, which is Law 119 exactly. Found
     by reading getComputedStyle for all six skin/mode pairs before pushing. */
  --accent-on: #0D141A;  --accent-text: #D87A49;
  /* #EABD3B WAS GOLD, AND GOLD IS SPOKEN FOR. Law 107 names that exact hex as the
     metal pair and reserves it for one meaning: THIS COST SOMETHING, OR IT WAS
     EARNED — featured placement, a storefront, elite standing, a sealed pack. As
     GeoBidder's night energy it meant every live bar on the marketplace after dark
     said "paid for". Copper instead, matching the day step, so the fill says LIVE
     and the gold keeps saying PAID. Found by rendering the real stylesheet; the
     comparison strip I built by hand used the day value and never showed it. */
  --energy: #E0854A;  --energy-on: #2A1600;  --energy-wash: rgba(224,133,74,.16);
}
[data-theme="dark"][data-skin="gm"],
[data-ground="wash"][data-skin="gm"]:not([data-theme="light"]){
  /* 12 Sep 2026, ocean night: lifted to match the lighter ground, each pillar
     holding its OWN tint exactly — only lightness moved. Solved against #133A56,
     not carried over, because an ink that was right on a darker ground is not
     right on this one. 11.0 / 9.4 / 6.0 on the night. */
  --skin-ink: #F2F7FA;  --skin-ink-2: #D6E4EF;  --skin-mut: #A9BACA;   /* one night ink, hue 202-209 */
  /* 12 Sep 2026: unified with the Poseidon night. Only --field was changed this
     morning, so the SHELL kept painting this pillar's rail and panels on the old
     violet-black bed while the page behind them went Poseidon — two nights on one screen.
     Found by rendering the real stylesheet rather than reading it. */
  --skin-bed: #0D141A;  --skin-sheet: #131C23;
  /* 12 Sep 2026: THE ONLY REAL CONTRAST DEFECT OF THE THREE. #AF4BCE measured
     3.81:1 on the new night ground — under the floor for a button and a link.
     Trade Handy and GeoBidder were reported as failing too and were NOT: that
     was a measurement error, reading each pillar's LIGHT-block value against
     the NIGHT ground when the dark block already overrides it. Lifted to
     7.46:1, hue held at 286 degrees exactly; only lightness moved. */
  /* ONE saturated accent, on the ACTION only. 7.0:1 on the page; the lettering
     is the page itself, which is what makes a fill read as a fill. */
  --accent: #B68FDD;  --accent-on: #0D141A;  --accent-text: #AC7FD9;
  --accent-wash: rgba(182,143,221,.16);  --accent-line: rgba(182,143,221,.34);   /* the GLOW — low opacity, the accent's own colour */
  /* 12 Sep 2026: this pair sat BELOW the accent line and therefore WON — the file
     said one thing and the browser painted another, which is Law 119 exactly. Found
     by reading getComputedStyle for all six skin/mode pairs before pushing. */
  --accent-on: #0D141A;  --accent-text: #AC7FD9;
  --energy: #DB4CB2;  --energy-on: #1B0416;  --energy-wash: rgba(219,76,178,.16);
}
/* LAW 107 · state and the metal in the dark. Law 77 is unchanged and still
   governs the gold: the metal goes LIGHTER on the dark ground, never darker. */
[data-theme="dark"],
[data-ground="wash"]:not([data-theme="light"]){
  --gold: #D8B457;  --gold-lit: #EABD3B;  --gold-wash: #2A2211;  --gold-line: #5E4C22;
  --st-good: #4FDDBB;  --st-good-w:#062B25;  --st-good-d:#1DE4BD;
  --st-warn: #F0B457;  --st-warn-w:#2E1F07;  --st-warn-d:#EE9A3A;
  --st-crit: #FF8080;  --st-crit-w:#320D0C;  --st-crit-d:#E24545;
  --st-idle: #8496B4;  --st-idle-w:#1A2437;
}

/* 24 Aug 2026: a SECOND [data-theme="dark"][data-skin="gm"] block sat here and
   set --accent back to #6BB4E8, the old blue. Identical selector, later in the
   file, so it WON — and GeoMundra's dark accent was blue on live, three lines
   under a comment explaining that the teal convergence was deliberate. It set
   the same six properties and nothing else, so removing it changes only the
   colour it was undoing. Found by the artifact sweep; the duplicate-selector
   shape is why it survived a visual check. */

/* ─────────────────────────────────────────────────────────────────────────
   6 · TYPE — two faces, and a rule about which sets what.

   THE FACES CHANGED 2 Sep 2026 — the owner's ruling ("yes thats it … make
   sure its consistent across the whole platform including the owner team
   side"): Instrument Sans replaces Inter and Space Grotesk goes, both
   self-hosted in th-fonts.css (Law 85, Law 59 retirement).

   THE DISPLAY FACE CHANGED AGAIN 7 Sep 2026 — Clash Display out, BRICOLAGE
   GROTESQUE in. Clash sat on Fontshare's licence rather than OFL, so a
   platform Champion Digital owns depended on one foundry's terms continuing;
   and it is a display-ONLY face while this codebase sets the display token at
   20px on card headings and 15px on register labels. Bricolage carries an
   OPTICAL SIZE axis — one variable file drawn correctly at 12px and at 60px —
   which is the only reason it beat the alternatives. Verified live on
   7 Sep: the weight axis and the optical-size axis both respond in the
   browser, so it is a real variable font and not a static cut.

   CLASH DISPLAY IS STILL SERVED and is not retired: it is one of the member's
   DOCUMENT brand faces in th-fontstudio.js, which Law 106 names as a separate
   feature — it paints the member's invoice, never the platform's chrome.

   Instrument Sans is a HUMANIST-leaning grotesk (OFL). It sets everything a
   customer reads before they decide to pay you — the same trust job Inter
   held, with more warmth at text sizes.

   Bricolage Grotesque (OFL) sets headings, numerals and the wordmark — and
   NEVER body copy. This is enforced. The rule outlives the face: a display
   grotesk is striking at display size and measurably harder to read in
   paragraphs, which is why the platform has two faces and not one.

   Values below are hand-tuned integers carried verbatim from th-tokens.css.
   Nobody world-class ships a modular ratio.
   ───────────────────────────────────────────────────────────────────────── */
:root{
  --font-sans: "Instrument Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --font-display: "Bricolage Grotesque",var(--font-sans);
  /* 23 Aug 2026 — the WORDMARK face, and nothing else. Owner's choice after
     seeing four set side by side. Never body, never headings: those stay
     Instrument Sans and Bricolage Grotesque. A third face earns its place by
     doing exactly one job. */
  --font-mark: "Manrope",var(--font-display);
  --font-mono: "JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;

  /* 9 Sep 2026 · --fs-11 NOW RESOLVES TO 12px, AND ITS NAME IS THE ONLY THING
     THAT STILL SAYS ELEVEN. _rendercheck.py measured the live shell and found
     seven labels computing to 11px — "All areas", "Trade Handy", "Staff only" —
     on EVERY internal page. No stylesheet declares anything under 12px, so no
     source gate could see it: the size came through this token, inherited.
     Sean, 9 Sep 2026: "cant see to small".
     12px is the floor _designcheck.py enforces and this is the one lever that
     moves all ~85 uses across 23 files at once (Law 100). The token is kept
     rather than deleted so nothing breaks — it is DEPRECATED: new work uses
     --fs-12, and the gate refuses a new use of --fs-11. */
  --fs-11: 12px;  --lh-11: 16px;  --ls-11:  .05em;
  --fs-12: 12px;  --lh-12: 16px;  --ls-12:  0;
  --fs-13: 13px;  --lh-13: 20px;  --ls-13: -.01em;
  --fs-14: 14px;  --lh-14: 20px;  --ls-14: -.011em;   /* DEFAULT body      */
  --fs-15: 15px;  --lh-15: 24px;  --ls-15: -.011em;
  --fs-16: 16px;  --lh-16: 24px;  --ls-16: -.011em;   /* MOBILE INPUT MIN  */
  /* 18 and 28 added 28 Aug 2026 on the owner's ruling. 28 pages were already
     written against --fs-18 and --fs-28 — a bold money figure and a card
     heading — and neither existed, so both declarations were invalid and the
     elements fell back to whatever else applied. The scale is hand-tuned
     integers, not a modular ratio, so two more hand-tuned integers belong in it.
     Line-heights interpolate the neighbours and land on a multiple of 4 as the
     law requires: 16→24 and 20→28 give 18→24 (tight, and these are display
     numerals); 24→32 and 32→40 give 28→36. Letter-spacing interpolates too. */
  --fs-18: 18px;  --lh-18: 24px;  --ls-18: -.012em;
  --fs-20: 20px;  --lh-20: 28px;  --ls-20: -.012em;
  --fs-24: 24px;  --lh-24: 32px;  --ls-24: -.014em;
  --fs-28: 28px;  --lh-28: 36px;  --ls-28: -.018em;
  --fs-32: 32px;  --lh-32: 40px;  --ls-32: -.022em;
  --fs-48: 48px;  --lh-48: 52px;  --ls-48: -.022em;
  --fs-64: 64px;  --lh-64: 64px;  --ls-64: -.022em;

  --fw-regular: 400;  --fw-medium: 510;  --fw-semibold: 590;
  --fs-input-mobile: var(--fs-16);   /* below 16px iOS Safari zooms on focus */

  /* SPACE — 4px base */
  --sp-0:0; --sp-025:2px; --sp-05:4px; --sp-075:6px; --sp-1:8px; --sp-15:12px;
  --sp-2:16px; --sp-25:20px; --sp-3:24px; --sp-4:32px; --sp-5:40px; --sp-6:48px;
  --sp-8:64px; --sp-10:80px; --sp-12:96px; --sp-16:128px;

  /* RADIUS by ELEMENT. 12px on a 32px button reads as a mistake. */
  /* ══ ONE GEOMETRY, NOT TWO — 20 September 2026 ══════════════════════════════
     These six were the older set and they still DEFINED the values _DESIGN-LAW
     §3.10 retired on 15 Sep ("I prefer the slightly rounded, squarish corners
     over these round tablet look"): 2, 4, 6 and 8px corners the current law does
     not ask for anywhere. Both sets were live at once, which is why the member
     desk measured EIGHT radii on one screen — 2, 4, 6, 8, 9, 10, 12 and 9999 —
     four of them within four pixels of each other. That is what "inconsistent"
     was, measured.
     They now RESOLVE to the th-* set rather than being deleted, so the 44 call
     sites that still name them land on the current law without 44 edits and
     without a chance to get one of them wrong. The px fallback is what
     curtain.html sees: it loads this sheet without the theme class. */
  --rad-xs:var(--th-rsm,2px); --rad-sm:var(--th-rsm,4px); --rad-md:var(--th-rsm,6px); --rad-lg:var(--th-r,8px);
  --rad-xl:var(--th-r,12px); --rad-2xl:var(--th-r-lg,16px); --rad-full:var(--th-pill,9999px);
  --r-focus-md:8px; --r-focus-lg:10px;

  /* CONTROLS. --tap-field exists because a gloved thumb needs more than 44. */
  --ctl-sm:32px; --ctl-md:var(--th-h,36px); --ctl-lg:44px;   /* --th-h carries the density ladder: 34 compact · 40 default · 46 roomy */
  --row-compact:32px; --row-default:40px; --row-comfortable:48px;
  --icon-sm:12px; --icon-md:16px; --icon-lg:20px; --icon-xl:24px;
  --tap-min:44px; --tap-field:56px;

  /* MOTION */
  --ease-out:cubic-bezier(.25,.46,.45,.94);
  --ease-in-out:cubic-bezier(.4,0,.2,1);
  --dur-fast:120ms; --dur:160ms; --dur-slow:240ms; --dur-peak:400ms;
}

/* ─────────────────────────────────────────────────────────────────────────
   7 · ELEVATION — the hairline lives INSIDE the shadow token. Multi-layer,
   hue-tinted, alpha 4-9%. `0 2px 4px rgba(0,0,0,.2)` is the most
   recognisable cheap shadow in existence. Never ship it.
   ───────────────────────────────────────────────────────────────────────── */
:root{
  --ring:  0 0 0 1px rgba(18,57,85,.08);
  /* A COLOUR-ONLY shadow tint. --sh-xs..xl are complete shadows; 61 shadows
     across 41 files needed their own geometry and so hardcoded the colour —
     32 of them as rgba(16,32,44,.10), which is the LIGHT tint. In dark that is
     a 10% navy on a near-black ground, i.e. invisible. This is the tint on its
     own so a bespoke geometry can still flip with the theme. Law: both themes
     are designed, not inverted. */
  --sh-tint: rgba(18,57,85,.10);
  --sh-xs: 0 1px 2px rgba(18,57,85,.05);
  --sh-sm: 0 1px 3px rgba(18,57,85,.06), 0 1px 2px rgba(18,57,85,.04);
  --sh-md: 0 4px 8px -2px rgba(18,57,85,.06), 0 2px 4px -2px rgba(18,57,85,.04);
  --sh-lg: 0 12px 16px -4px rgba(18,57,85,.06), 0 4px 6px -2px rgba(18,57,85,.03);
  --sh-xl: 0 24px 32px -8px rgba(18,57,85,.08), 0 8px 16px -4px rgba(18,57,85,.04);
  --sh-menu:  var(--ring), var(--sh-lg);
  --sh-modal: var(--ring), var(--sh-xl);
  --btn-edge:      inset 0 0 0 1px rgba(0,0,0,.10);
  --btn-highlight: inset 0 .5px 0 1.5px rgba(255,255,255,.55);
}
[data-theme="dark"],
[data-ground="wash"]:not([data-theme="light"]){
  --ring:  0 0 0 1px rgba(255,255,255,.10);
  --sh-tint: rgba(0,0,0,.44);
  --sh-xs: 0 1px 2px rgba(0,0,0,.30);
  --sh-sm: 0 1px 3px rgba(0,0,0,.36), 0 1px 2px rgba(0,0,0,.24);
  --sh-md: 0 4px 8px -2px rgba(0,0,0,.40), 0 2px 4px -2px rgba(0,0,0,.28);
  --sh-lg: 0 12px 16px -4px rgba(0,0,0,.44), 0 4px 6px -2px rgba(0,0,0,.28);
  --sh-xl: 0 24px 32px -8px rgba(0,0,0,.52), 0 8px 16px -4px rgba(0,0,0,.32);
  --btn-edge:      inset 0 0 0 1px rgba(255,255,255,.12);
  --btn-highlight: inset 0 .5px 0 1.5px rgba(255,255,255,.14);
}

/* ─────────────────────────────────────────────────────────────────────────
   THE PRIMITIVES th-components.css WAS WRITTEN AGAINST.  Added 24 Aug 2026.

   th-components.css is linked by 55 pages and reaches for --surface-1,
   --hairline, --brand-solid, --brand-fg and --fg-on-solid. Those are declared
   in th-tokens.css, which NO PAGE LINKS and NO STYLESHEET @imports. A var()
   that resolves to nothing and carries no fallback makes the browser drop the
   WHOLE DECLARATION, so 52 declarations were being discarded: 30 background,
   8 color, 6 border, 3 border-bottom, 2 box-shadow, and one each of
   border-color, stroke and border-top.

   Measured in a browser, not inferred. With th-core.css and th-components.css
   loaded, .btn-primary, .btn-secondary and .input all computed to
   background rgba(0, 0, 0, 0) — transparent — while a control element set to
   background:var(--accent) computed to rgb(14, 124, 126). The mechanism was
   fine; these particular tokens simply did not exist.

   WHY THE VALUES ARE TRANSPOSED, NOT COPIED.  th-tokens.css is DARK-FIRST: its
   :root carries the dark values and .theme-light,[data-theme="light"] carries
   the light ones. This file is LIGHT-FIRST. Linking th-tokens.css into the
   cascade would therefore let its dark :root override this file's light :root
   for the 88 tokens they share, and the light theme would break on every page
   that got it. So the primitives are declared here instead, the right way up.
   D22 records the remaining question: retire th-tokens.css, or make it real.
   ───────────────────────────────────────────────────────────────────────── */
:root{
  /* surfaces — light */
  --surface-0: var(--surface);
  --surface-1: #F7F9FA;
  --surface-2: #EFF3F5;
  --surface-3: #E5EAEE;
  --hairline:   rgba(16,32,44,.11);
  --hairline-2: rgba(16,32,44,.06);
  /* the brand ramp is one hue at fixed lightnesses — it does not flip */
  --brand-10:#001A1B; --brand-20:#002527; --brand-30:#00373A; --brand-40:#004A4D;
  --brand-50:#005E61; --brand-60:#007376; --brand-70:#00898C;
  --brand-80: var(--accent); --brand-90: var(--accent);
  --brand-100:#45D2D4; --brand-110:#6BDCDD; --brand-120:#90E5E6; --brand-130:#B0EDEE;
  --brand-140:#CBF3F4; --brand-150:#E0F8F9; --brand-160:#EFFCFC;
  /* semantic, and they follow the tokens above rather than repeating them */
  --brand-solid: var(--brand-80);
  /* th-tokens.css says var(--n-900), and that is right IN ITS OWN dark-first
     world: near-navy on the bright dark accent #3ED9CE is 9:1. Transposed to
     light it is navy on the darker #0E7C7E, which measures 3.17:1 — AA for
     large text only, and a button label is not large text. var(--surface)
     is correct in BOTH directions because the surface and the accent move
     opposite ways: white on #0E7C7E is 5.00:1, dark surface on #3ED9CE is
     10:1. It is also the pairing this file already documents beside --accent,
     "white on it 4.6:1". The one place the primitives are NOT copied verbatim. */
  --brand-fg:    var(--surface);
  --brand-tint:  var(--accent-a14);
  /* INK ON A SOLID SIGNAL FILL. Every use of this token in th-components.css
     — all four of them — sits on --sig-danger or --sig-safe. Those are AS 1319
     standard colours and they deliberately DO NOT flip between themes, so the
     ink must not flip either. var(--surface) followed the theme and therefore
     put near-black on red in dark: measured 3.29:1 on danger and 3.03:1 on
     safe, which is large-text only and a button label is not large text.
     White is 4.96:1 on danger and 5.39:1 on safe — AA in both themes at once.
     THE EXCEPTION, if this is ever reached for again: white on --sig-warn
     (#F5A623 amber) is 2.03:1 and fails. Amber has its own ink already,
     --sig-warn-ink #8A5A00, for exactly that reason. */
  --fg-on-solid: #FFFFFF;
  --r-xs: 2px;
}
[data-theme="dark"],
[data-ground="wash"]:not([data-theme="light"]){
  /* Retoned 2 Sep 2026 with the ground — same ladder, chroma toward blue. */
  --surface-0: #071B30;
  --surface-1: #0C2740;
  --surface-2: #123351;
  --surface-3: #173E60;
  --hairline:   rgba(255,255,255,.09);
  --hairline-2: rgba(255,255,255,.055);
}

/* THE AURORA — 2 Sep 2026, the owner's ruling on the platform ground:
   "a little more glow on the background only". Three whisper-alpha radials,
   every hue an anchor of THE LOOK — Cyan Sky, the ramp's azure, Bronze
   Mist — over a Poseidon sweep that ends on the canvas token. Background
   only: no ink, no surface, no chip changes colour because of this rule.
   [data-x] body outweighs th-core's plain `body{background:var(--ink)}`. */
[data-theme="dark"] body,
[data-ground="wash"]:not([data-theme="light"]) body{
  /* REBALANCED 3 Sep 2026 — the owner: "some places there is way too much
     Navy… i want it balanced, symmetric, colour evenly allocated and sharp
     and vibrant, aligned and not looking flat." Four corners, one glow per
     corner, alphas lifted so the colour reads through the glass chrome. */
  background:
    radial-gradient(1000px 640px at 88% -6%, rgba(0,184,187,.08), transparent 60%),
    radial-gradient(900px 620px at -6% 2%, rgba(108,175,224,.07), transparent 62%),
    radial-gradient(900px 620px at 106% 98%, rgba(212,192,152,.06), transparent 60%),
    radial-gradient(1000px 640px at -4% 102%, rgba(0,184,187,.05), transparent 62%),
    linear-gradient(178deg, #0B2A47 0%, var(--canvas) 48%, #0C2740 100%);
  background-attachment: fixed;
}

/* ─────────────────────────────────────────────────────────────────────────
   8 · BASE — every page inherits this. No page re-declares body type.
   ───────────────────────────────────────────────────────────────────────── */
/* ── THE LAST FIVE PIXELS — SEAN'S RULING, 14 SEPTEMBER 2026 ──────────────────
   "please balance pages with there boards and headers properly."
   After the two page measures and the gutter were matched, the Dashboard and
   Books landed pixel-identical — box 693, heading 749 — and the gate sat 5px
   right of both, at 698 and 754. MEASURED, not guessed, all three at viewport
   2248 in his own Chrome:

       Books      clientWidth 2237   scrollbar 11px   scrolls        box 693
       Dashboard  clientWidth 2237   scrollbar 11px   scrolls        box 693
       The gate   clientWidth 2248   scrollbar  0     DOES NOT       box 698

   The gate's content is short enough that the page never scrolls, so it keeps
   the 11px the other two give to the scrollbar, and a CENTRED 1040px box in an
   11px wider space moves half of that — five pixels. Nothing was wrong with the
   gate; every short page on the platform was drifting sideways by exactly how
   much happened to be on the screen that day.

   `stable` reserves the gutter whether the page scrolls or not, so the measure
   never moves again — and a modal that sets overflow:hidden no longer makes the
   page jump either. Browsers without support ignore it and get exactly today.
   This rule lives HERE and not in th-core.css because cd-signature.css is on
   258 of 260 pages and th-core.css on 252 — six pages, index.html and the
   invoice among them, would have been left behind. One lever or it did not
   happen. */
html{ color-scheme: light; -webkit-text-size-adjust:100%; scrollbar-gutter: stable; }
[data-theme="dark"],
[data-ground="wash"]:not([data-theme="light"]){ color-scheme: dark; }

body{
  margin:0;
  background:var(--canvas);
  color:var(--text-body);
  font-family:var(--font-sans);
  font-size:var(--fs-14);
  line-height:var(--lh-14);
  letter-spacing:var(--ls-14);
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
}

h1,h2,h3,h4,.t-display{
  font-family:var(--font-display);
  color:var(--text);
  font-weight:var(--fw-semibold);
  margin:0 0 var(--sp-2);
}
h1{font-size:var(--fs-32);line-height:var(--lh-32);letter-spacing:var(--ls-32)}
h2{font-size:var(--fs-24);line-height:var(--lh-24);letter-spacing:var(--ls-24)}
h3{font-size:var(--fs-20);line-height:var(--lh-20);letter-spacing:var(--ls-20)}
h4{font-size:var(--fs-16);line-height:var(--lh-16);letter-spacing:var(--ls-16)}

a{color:var(--accent-text);text-underline-offset:2px}
a:hover{color:var(--accent-hover)}
hr{border:0;border-top:1px solid var(--line);margin:var(--sp-4) 0}

/* ─────────────────────────────────────────────────────────────────────────
   9 · TYPE UTILITIES + the tabular-numerals rule.
   🔴 .tnum ON EVERY NUMBER. Money, quantities, timestamps, IDs, distances,
   counts, bid amounts. Numbers that change width as they update is an
   instant amateur signal — and these are invoicing and bidding products.
   ───────────────────────────────────────────────────────────────────────── */
.t-11{font-size:var(--fs-11);line-height:var(--lh-11);letter-spacing:var(--ls-11);text-transform:uppercase;font-weight:var(--fw-medium)}
.t-12{font-size:var(--fs-12);line-height:var(--lh-12)}
.t-13{font-size:var(--fs-13);line-height:var(--lh-13);letter-spacing:var(--ls-13)}
.t-14{font-size:var(--fs-14);line-height:var(--lh-14);letter-spacing:var(--ls-14)}
.t-15{font-size:var(--fs-15);line-height:var(--lh-15);letter-spacing:var(--ls-15)}
.t-16{font-size:var(--fs-16);line-height:var(--lh-16);letter-spacing:var(--ls-16)}
.t-muted{color:var(--text-muted)}
.t-with-icon{font-weight:var(--fw-medium)}
.measure{max-width:68ch}
.tnum{font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}

/* .tnum is opt-in, and measured 24 Aug 2026 it was used ZERO times — the
   mechanism was built and never applied, so the note above was aspiration
   rather than description. D10 asks for tabular numerals "everywhere money
   appears". There is no shared money class to hang that on: 279 money render
   sites wrap their output in page-local names — .r, .amt, .num, .v, .n, .fig —
   and most cannot be targeted safely. Measured which can:

     td / th   46 of the 279 money renders sit in a table cell, and a table is
               where alignment is the whole point. Digits only; text unaffected.
     .amt      15 uses, 15 of them money — 100%.
     .price    12 uses, 10 of them money — 83%.

   REJECTED, on measurement rather than taste: .n is 101 uses and only 3% money
   (it means "name"); .num is 38%, .r is 30%, .v is 25%. Applying this to them
   would have set tabular figures on names and labels across the estate.
   The remaining ~206 sites need a shared money class first — raised as D24. */
td, th, .amt, .price{font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}

/* EVERY BUTTON ANSWERS THE PRESS. Measured 24 Aug 2026: of 890 buttons in
   deploy/, only 348 carry a shared .btn class and therefore pick up
   .btn-primary:active{transform:scale(.97)} from th-components.css. The other
   542 - 171 with no class at all, the rest on page-local names like .on, .chip,
   .rm, .bell, .del - answered a press with nothing.

   Specificity 0-1-1, so this is a FLOOR: .btn-primary:active (0-2-0) and any
   page's own :active rule still win. Reduced motion is already handled - the
   global block sets transition-duration:.001ms !important, so this lands as an
   instant state change rather than an animation. */
button:active{transform:scale(.97)}

/* ─────────────────────────────────────────────────────────────────────────
   10 · SURFACES + CONTROLS — the shared primitives. A page that needs a
   card, a button or a badge uses these. It does not roll its own.
   ───────────────────────────────────────────────────────────────────────── */
.cd-card{
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--rad-xl);
  box-shadow:var(--sh-sm);
  padding:var(--sp-3);
}

.cd-btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-075);
  min-height:var(--ctl-md);padding:0 var(--sp-2);
  font-family:var(--font-sans);font-size:var(--fs-14);font-weight:var(--fw-medium);
  letter-spacing:var(--ls-14);
  border-radius:var(--rad-md);border:1px solid transparent;
  cursor:pointer;
  transition:background var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}
.cd-btn--primary{
  /* LAW 75: the accent carries POSEIDON, never white. */
  background:var(--accent);color:var(--accent-on);
  box-shadow:var(--btn-edge),var(--btn-highlight),var(--sh-xs);
}
.cd-btn--primary:hover{background:var(--accent-hover)}
.cd-btn--primary:active{background:var(--accent-press)}
.cd-btn--secondary{
  background:var(--surface);color:var(--text);border-color:var(--line-strong);
  box-shadow:var(--sh-xs);
}
.cd-btn--secondary:hover{background:var(--surface-sunk)}
.cd-btn--ghost{background:transparent;color:var(--accent-text)}
.cd-btn--ghost:hover{background:var(--accent-wash)}
.cd-btn--danger{background:var(--danger);color:var(--fg-on-signal)}
.cd-btn[disabled]{opacity:.5;cursor:not-allowed}

/* 🔴 On any page a tradie may use on site, primary actions take the FIELD
   target. Work gloves cut effective touch precision to 20-25mm; 44px is
   not enough. Add class="cd-field" to <body> or the form. */
.cd-field .cd-btn--primary,
.cd-field input,.cd-field select{min-height:var(--tap-field)}

/* Status badges — meaning carried by colour AND word, never colour alone.
   ~8% of men have a colour vision deficiency; a green dot is not a status. */
.cd-badge{
  display:inline-flex;align-items:center;gap:var(--sp-05);
  padding:2px var(--sp-1);border-radius:var(--rad-sm);
  font-size:var(--fs-12);line-height:var(--lh-12);font-weight:var(--fw-medium);
  border:1px solid transparent;
}
.cd-badge--ok{background:var(--ok-wash);color:var(--ok);border-color:var(--ok-line)}
.cd-badge--warn{background:var(--warn-wash);color:var(--warn);border-color:var(--warn-line)}
.cd-badge--danger{background:var(--danger-wash);color:var(--danger);border-color:var(--danger-line)}
.cd-badge--info{background:var(--accent-wash);color:var(--accent-text);border-color:var(--accent-line)}

/* ─────────────────────────────────────────────────────────────────────────
   11 · FOCUS — one treatment, platform-wide. `outline`, NOT box-shadow:
   box-shadow rings vanish in Windows forced-colors mode. WCAG 2.2 SC 2.4.11
   requires >=2px and >=3:1.
   ───────────────────────────────────────────────────────────────────────── */
:where(a,button,input,select,textarea,[tabindex]):focus-visible{
  outline:2px solid var(--accent);
  outline-offset:2px;
  border-radius:var(--r-focus-md);
}

/* ─────────────────────────────────────────────────────────────────────────
   12 · THE PEAK MOMENT — the unboxing.

   Kahneman's peak-end rule: people judge an experience by its most intense
   moment and its ending, not its average. The peak is built by delivering
   MORE than expected at the moment of highest emotional stake.

   🔴 RATIONED TO FIVE PER PLATFORM. The approved five:
        1. identity verified          2. first quote accepted
        3. first payment landed       4. onboarding complete
        5. first listing won (GB) / first route saved (GM)
   Anything else gets --dur, not --dur-peak. If it appears on every card it
   is decoration, and decoration is the opposite of premium.

   Add `.is-peak` when the event fires. Never on page load.
   ───────────────────────────────────────────────────────────────────────── */
.cd-peak{
  position:relative;overflow:hidden;
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--rad-xl);
  padding:var(--sp-4);
  box-shadow:var(--sh-sm);
  transition:box-shadow var(--dur-peak) var(--ease-out),
             border-color var(--dur-peak) var(--ease-out);
}
.cd-peak > *{
  opacity:0;transform:translateY(6px);
  transition:opacity var(--dur-peak) var(--ease-out),
             transform var(--dur-peak) var(--ease-out);
}
.cd-peak.is-peak{ border-color:var(--accent-line); box-shadow:var(--sh-lg); }
.cd-peak.is-peak > *{ opacity:1;transform:none; }
.cd-peak.is-peak > :nth-child(1){transition-delay:0ms}
.cd-peak.is-peak > :nth-child(2){transition-delay:90ms}
.cd-peak.is-peak > :nth-child(3){transition-delay:180ms}
.cd-peak.is-peak > :nth-child(4){transition-delay:270ms}

/* ─────────────────────────────────────────────────────────────────────────
   13 · THE FIVE LAMPS — the status vocabulary, fixed once for the platform.

   A tradesperson learns this once and it holds everywhere: on a tool tile, on
   a button, on a quote step, on a road closure in GeoMundra. That is the whole
   value. A lamp that means "fine" in one place and "on" in another is worse
   than no lamp, because it teaches people to stop reading them.

   🔴 GREY IS NOT RED, AND THIS IS THE ONE PEOPLE GET WRONG. A tool nobody has
   switched on is not broken. A feature not on your plan is not a fault. Show
   either of them in the failure colour and within a week every red lamp on the
   platform is background noise — and then a real failure goes unread. Grey is
   an invitation; red is a defect that was actually attempted.

   🔴 A LIT STATE IS A CLAIM. Nothing lights until there is a fact behind it.
   The owner's example, 23 Aug 2026: a quote panel must not show three lamps on
   before the client has chosen anything — it should be dark, and light as the
   work actually happens. The darkness is the information, and it is also where
   the sense of progress comes from.

   MEANING IS NEVER CARRIED BY COLOUR ALONE (law 45 / WCAG 1.4.1): every lamp
   ships beside a word. The colour makes it scannable; the word makes it
   readable to someone who cannot separate the two.
   ───────────────────────────────────────────────────────────────────────── */
.cd-lamp{
  width:9px;height:9px;border-radius:50%;flex:none;display:inline-block;
  background:var(--line-strong);vertical-align:middle;
  transition:background var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}
.cd-lamp.is-ok   { background:var(--ok);     box-shadow:0 0 0 3px var(--ok-wash),     0 0 10px var(--ok); }
.cd-lamp.is-warn { background:var(--warn);   box-shadow:0 0 0 3px var(--warn-wash),   0 0 10px var(--warn); }
.cd-lamp.is-bad  { background:var(--danger); box-shadow:0 0 0 3px var(--danger-wash), 0 0 10px var(--danger); }
.cd-lamp.is-money{ background:var(--money);  box-shadow:0 0 0 3px var(--money-wash),  0 0 10px var(--money); }
/* idle is the DEFAULT, and it is stated explicitly so a page cannot reach for
   .is-bad to mean "off". */
.cd-lamp.is-idle { background:var(--line-strong); box-shadow:none; }

/* A lamp may breathe ONLY where something is genuinely live and continuous —
   a running job, an open auction, a truck moving. Never on a settled state:
   a pulse on something finished reads as an unresolved problem. */
.cd-lamp.is-live{ animation:cd-breathe 2.4s var(--ease-in-out) infinite; }
@keyframes cd-breathe{ 0%,100%{opacity:1} 50%{opacity:.45} }

/* ─────────────────────────────────────────────────────────────────────────
   14 · GLASS — the floating layer, and ONLY the floating layer.

   Apple shipped Liquid Glass across iOS 26 and macOS 26 and refined it in June
   2026 specifically for readability. It is current, and it is what makes an
   interface feel like this year rather than 2019. It is NOT the 2020 blur: it
   refracts, it picks up highlights, it adapts to what passes beneath.

   🔴 WHERE IT BELONGS: the top bar, dialogs, sheets, popovers, search, a
   summary tile sitting over a photograph. Things that float ABOVE content.

   🔴 WHERE IT MUST NEVER GO: tables, forms, invoice lines, anything a
   tradesperson reads on site in sunlight. Law 45 already says field conditions
   outrank visual fashion, and Apple's own June revision was an admission that
   glass over content costs legibility. Both point the same way.

   ALWAYS PAINT A SOLID FALLBACK FIRST. Roughly one browser in twenty cannot do
   backdrop-filter, and a translucent panel with nothing behind it is unreadable
   rather than merely plainer. The @supports block is the enhancement, not the
   baseline.
   ───────────────────────────────────────────────────────────────────────── */
.cd-glass{
  background:var(--surface);                    /* the fallback, painted first */
  border:1px solid var(--line);
}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .cd-glass{
    background:color-mix(in srgb, var(--surface) 72%, transparent);
    backdrop-filter:blur(18px) saturate(1.5);
    -webkit-backdrop-filter:blur(18px) saturate(1.5);
    border-color:color-mix(in srgb, var(--text) 12%, transparent);
  }
}
/* On the navy bar the glass sits over a dark ground, so the tint flips. */
.cd-glass-dark{ background:rgba(18,57,85,.92); border:1px solid rgba(255,255,255,.14); }
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .cd-glass-dark{
    background:rgba(18,57,85,.72);
    backdrop-filter:blur(18px) saturate(1.5);
    -webkit-backdrop-filter:blur(18px) saturate(1.5);
  }
}

/* ─────────────────────────────────────────────────────────────────────────
   15 · INSTRUMENTS — meters, bars and figures that report their own state.

   The rule from every design system that does this well: ANIMATE TO REVEAL,
   NOT TO DECORATE. A bar draws so the ranking is legible before a single label
   is read. A figure counts so you notice it changed. Then both stop.

   🔴 NOTHING HERE LOOPS. A number that keeps counting is not information, it
   is a distraction on a screen somebody is trying to work from.

   🔴 EVERY FIGURE IS TABULAR. Money that shifts sideways as it updates reads
   as unreliable, and a column that does not line up on the decimal is the
   fastest way to look unserious about money.

   Fill widths and counter values are set by the page from real data; this file
   only supplies the movement, so a wrong number can never come from the CSS.
   ───────────────────────────────────────────────────────────────────────── */
.cd-meter{ height:7px;background:var(--surface-sunk);border-radius:var(--th-pill,9999px);overflow:hidden; }
.cd-meter > i{
  display:block;height:100%;width:0;border-radius:var(--th-pill,9999px);background:var(--accent);
  transition:width var(--dur-slow) var(--ease-out);
}
/* Usage approaching or at its limit is a status, so it speaks the lamp
   vocabulary rather than inventing a second one. */
.cd-meter > i.is-warn{ background:var(--warn); }
.cd-meter > i.is-full{ background:var(--danger); }

.cd-bar{ height:10px;background:var(--surface-sunk);border-radius:var(--th-rsm,10px);overflow:hidden; }
.cd-bar > i{
  display:block;height:100%;width:0;border-radius:var(--th-pill,9999px);background:var(--accent);
  transition:width var(--dur-slow) var(--ease-out);
}

/* Figures. font-variant-numeric is the whole point — see section 9. */
.cd-figure{
  font-family:var(--font-display);font-weight:590;
  font-variant-numeric:tabular-nums;color:var(--text);
}

/* Skeletons — a spinner says "wait", a skeleton says "it is nearly here, and
   this is its shape". It also stops the page jumping when the data lands,
   which is the part people feel without being able to name. */
.cd-skel{
  background:linear-gradient(90deg,
    var(--surface-sunk) 25%, var(--surface-alt) 37%, var(--surface-sunk) 63%);
  background-size:400% 100%;
  animation:cd-shimmer 1.4s var(--ease-in-out) infinite;
  border-radius:var(--th-rsm,10px);
}
@keyframes cd-shimmer{ 0%{background-position:100% 0} 100%{background-position:0 0} }

/* ─────────────────────────────────────────────────────────────────────────
   16 · THE LOCKUP — the icon is a FILE, the wordmark is TEXT.

   Before this, the shell loaded th-logo.svg: a baked image with navy fused into
   it. That is why login.html needed a second white copy of the same mark, and
   why the lockup could never follow a theme. Splitting them fixes all of it at
   once — and fixes it by TOUCHING NOTHING.

   THE ICON is one of the four *-symbol.svg files. Each is already a mechanical
   extract of the #logo-logo group from an official pack, produced by
   _extract_symbol.py with the text group removed. Not one path changes here.
   Law 44 is satisfied by leaving the artwork alone.

   THE WORDMARK is live text in --font-mark. Which means it takes its colour
   from a token, scales without blurring, and is read aloud by a screen reader —
   three things a PNG of the same word cannot do.

   🔴 THE CHIP, AND WHY IT IS ON EVERY MARK RATHER THAN THE TWO THAT NEED IT.
   Two of the four contain navy: Trade Handy rgb(11,31,58) and GeoMundra
   rgb(12,36,56). Both are within a few points of the navy ground behind them,
   so on the dark bar they render perfectly and are invisible. GeoBidder (teal
   gradient) and Champion Digital (cyan) do not have the problem.
   The chip gives every mark the light ground it was drawn for. Applying it to
   all four rather than only the two that need it is the owner's call, 23 Aug:
   three product headers that differ slightly read as careless, where four that
   match read as a decision.

   USE .cd-lock--dark WHEREVER THE GROUND IS DARK, not where the THEME is dark.
   The top bar is navy in both themes, so the chip belongs there always. Tying
   it to [data-theme] would break it in light mode, which is the mistake waiting
   to be made here.
   ───────────────────────────────────────────────────────────────────────── */
.cd-lock{ display:inline-flex; align-items:center; gap:12px; text-decoration:none; }
.cd-lock img{ height:34px; width:auto; display:block; flex:none; }
.cd-lock .cd-wm{
  font-family:var(--font-mark);
  font-weight:590;
  font-size:25px;
  line-height:1;
  letter-spacing:-.035em;   /* a heavy geometric face reads loose without this */
  color:var(--text);
  white-space:nowrap;
}
/* On a dark ground the word turns white. The ICON is unresolved: two of the four
   marks contain navy and vanish on the dark bar. A white chip behind the icon was
   tried and rejected by the owner on 23 Aug — he is supplying new artwork instead.
   Until it lands, do not paint anything behind a mark. */
.cd-lock--dark .cd-wm{ color:#FFFFFF; }
/* Two sizes, because a bar and a hero are not the same thing. */
.cd-lock--sm img{ height:24px; }
.cd-lock--sm .cd-wm{ font-size:17px; }
.cd-lock--lg img{ height:52px; }
.cd-lock--lg .cd-wm{ font-size:38px; }

/* Reduced motion is not optional. The peak still lands — instantly. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;scroll-behavior:auto !important;
  }
  .cd-peak > *{opacity:1;transform:none}
  /* 23 Aug 2026 — the instruments land at their final value instantly rather
     than not at all. A meter that never fills is a broken meter, not a calm
     one; the information still has to arrive. */
  .cd-lamp.is-live{animation:none}
  .cd-skel{animation:none;background:var(--surface-sunk)}
}


/* ─────────────────────────────────────────────────────────────────────────
   PRINT — 24 August 2026 (D10).

   WHY THIS EXISTS, measured rather than assumed. A quote, an invoice and a
   contract are printed and posted, and are read by the MEMBER'S OWN CUSTOMER.
   Before this block, 15 of 131 pages had any @media print at all, no shared
   sheet had a single print rule, and the two that matter disagreed: quote.html
   forces a light ground and dark ink, invoice.html does not, and contract.html
   had no print rules whatsoever.

   THE FAILURE IS NOT "IT PRINTS DARK". Browsers drop background colours when
   printing unless a page opts into print-color-adjust, and nothing here does.
   So the paper comes out white — but TEXT COLOUR ALWAYS PRINTS, and in dark
   theme --text is #E8EEF2. Measured on invoice.html with its own print rules
   applied and data-theme="dark": the sheet computed to rgb(180,194,204) text.
   On white paper that is near-invisible. A member on dark mode was sending
   their customer a blank-looking invoice.

   THE FIX IS TO RE-ASSERT THE LIGHT DEFINITIONS, NOT TO INVENT VALUES. The
   dark theme overrides only the SEMANTIC tokens and never touches the --n-*
   neutral ramp, so the light expressions below are exactly the ones :root
   already uses. Nothing new is introduced.
   ───────────────────────────────────────────────────────────────────────── */
@media print{
  :root,
  [data-theme="dark"],
  [data-theme="light"]{
    --canvas:      var(--n-0);
    --surface:     var(--n-0);
    --text:        var(--n-900);
    --text-muted:  var(--n-500);
    --line:        var(--n-200);
    --line-strong: var(--n-300);
    /* ink on paper does not need lifting off a dark ground */
    --ring: none;
    --sh-xs: none; --sh-sm: none; --sh-md: none; --sh-lg: none; --sh-xl: none;
    --sh-menu: none; --sh-modal: none; --sh-tint: transparent;
  }

  html, body{
    background: var(--n-0) !important;
    color: var(--n-900) !important;
  }

  /* The shell is not part of the document. These ids are injected by
     th-shell.js on every page that loads it, so hiding them here saves each
     page from remembering to. Page-specific chrome stays page-specific. */
  #thTop, #thSide, #thScrim, #thToasts, #thPal, #thUserMenu, #thBurger,
  #thSearch, #thdbBanner{ display: none !important; }

  /* A document should not be cut across a row, a card or a signature line. */
  tr, .cd-card, .card, .sheet{ break-inside: avoid; page-break-inside: avoid; }
  h1, h2, h3{ break-after: avoid; page-break-after: avoid; }

  /* Shadows are a screen affordance and cost toner. */
  *{ box-shadow: none !important; text-shadow: none !important; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE TOUCH FLOOR — 25 Aug 2026, on the owner's ruling. Design law §4.5.
   ---------------------------------------------------------------------------
   THE LAW: "Mobile inputs are never below 16px. Below that, iOS Safari zooms
   the viewport on focus and the user loses their place. This is not aesthetic."

   WHAT WAS ACTUALLY THERE: 117 rules across 69 files set an input, select or
   textarea below 16px, and THREE of them are in this shared layer —
   th-components.css (.input/.select/.textarea at --fs-14), th-core.css (14.5px)
   and th-fluent.css (--fs-14) — so the baseline broke the law and every page
   inherited it before adding its own. On index.html at 375px, five inputs on
   the front-page quote form rendered at 15px: the top of the funnel, zooming on
   the first tap.

   WHY NOT EDIT THE 117: 14px in a dense tool table on a desktop is a deliberate
   choice, and lifting all of them would fatten every form on the platform to fix
   something that only happens on a phone. This changes nothing on a mouse-driven
   screen and lifts the floor only where the zoom occurs.

   `!important` is deliberate and is the narrowest tool available: those 117 rules
   are page-level and would otherwise win on specificity. It is scoped to coarse
   pointers, to form controls, and to one property. Checkboxes, radios, ranges and
   colour swatches are excluded — font-size does not size them, and forcing 16px
   would only disturb their layout.

   Verified in a browser at 375px with a coarse pointer: inputs below 16px went
   from 5 to 0 on index.html, with no horizontal overflow introduced and the form
   visually unchanged.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (hover: none) and (pointer: coarse) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]),
  select,
  textarea { font-size: 16px !important; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE TAP FLOOR — 25 Aug 2026. Design law §: "Targets: 44px minimum."
   ---------------------------------------------------------------------------
   MEASURED, not assumed. A sweep of the 54 public pages at 390px found 165
   controls under 44px across 34 of them — and only SIXTEEN distinct classes,
   with the top ten covering 158 of the 165. It is not 34 separate jobs:

        button.btn / a.btn   108      a.back        6
        a.pick                17      button.chip   5
        bare <button>          9      button.tp-x   3
        <select>               6      the rest      1–2 each

   min-height rather than padding, because 106 of the 108 .btn offenders are
   ALREADY display:flex or inline-flex — the box grows and the label stays
   centred without touching the layout mode. All 17 .pick are display:block, so
   those get flex centring too, which keeps them full width exactly as block did.

   SCOPED TO COARSE POINTERS, the same precedent as the input touch floor above:
   36px is a considered size under a mouse, and the law's rationale — a thumb,
   often gloved — is a touch rationale. Desktop is untouched.

   DELIBERATELY NOT INCLUDED: .leaflet-control-zoom-in / -out. Those are Leaflet's
   own map chrome, sized and positioned by the library, and growing them breaks
   the control stack. Recorded rather than fixed.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (hover: none) and (pointer: coarse) {
  .btn, .pick, .chip, .back, .tab, .tp-x,
  /* 165 -> 19 with the classes above. The remaining tail, added after measuring
     rather than guessed at up front: nine BARE <button> elements with no class at
     all, the .brand logo link, and four one-offs (.btn-sell, .allcats, .seeall,
     .on). A bare button is still a tap target, and the law does not care whether
     somebody gave it a class. Leaflet's zoom controls are <a>, not <button>, so
     naming button here cannot reach them. */
  button:not(.leaflet-control-zoom-in):not(.leaflet-control-zoom-out),
  .brand, .btn-sell, .allcats, .seeall,
  select:not([multiple]):not([size]) { min-height: var(--tap-min, 44px); }
  .pick, .back { display: flex; align-items: center; }
  .chip, .tab, .tp-x { display: inline-flex; align-items: center; justify-content: center; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LAW 80 · GLASS IS A WORKING MATERIAL — 25 Aug 2026
   ---------------------------------------------------------------------------
   The law was set on 25 Aug and never built. Measured before writing this:
   --wash was declared 3 times and used as a background ZERO times, and .card
   — 267 uses across 71 pages — resolved to a flat var(--panel) box. Blur was
   scattered over 55 pages with no ground under any of it, which by the law's
   own first obligation makes every one of those panes a grey box. That is the
   whole of "everything is stale flat": not taste, a law that never shipped.

   THE THREE OBLIGATIONS, each built rather than restated:

   ONE · GLASS NEEDS A GROUND. A pane over flat canvas is a grey box, so the
   material is scoped to [data-ground="wash"] and CANNOT appear anywhere else.
   A page opts in on <html>; the ground and the material arrive together or
   not at all. Public document pages keep the light canvas and solid cards.

   TWO · THE BORDER AND THE INNER HIGHLIGHT DO THE LEGIBILITY WORK the blur
   takes away — hence the 1px cyan hairline, the inset top highlight, and the
   light-line across the pane's top edge.

   THREE · GLASS IS DEFEATED IN SUNLIGHT. [data-mode="sun"] takes blur to
   zero, panes to solid and hairlines to full-strength cyan. A blurred pane at
   62% opacity has lower effective contrast than a solid one and every trade
   on this platform works outdoors. prefers-reduced-transparency defeats it
   the same way, for the same reason.

   WHY IT IS SCOPED AND NOT WRITTEN INTO .card DIRECTLY: 70 of the 71 pages
   define .card in their own <style>, which loads after this file. A bare
   .card rule here would lose the cascade and change nothing on screen — the
   exact shape of a fix that ships and does not land. [data-ground="wash"]
   .card is (0,2,0) against a local (0,1,0) and wins on specificity from the
   shared sheet, so no page's style block is touched.

   The wash ground also re-points every dark token (see the six selectors
   extended above), so a page that opts in gets Poseidon ink for free.
   ═══════════════════════════════════════════════════════════════════════════ */

/* THE FIELD BED — 25 Aug 2026.
   th-core.css:224 hardcoded background:#fbfcfe on every text input while the
   ink stayed var(--tx). In dark that is #EAF4FB on #FBFCFE — 1.05:1, and the
   157 inputs across 125 pages have been unreadable in dark mode since dark
   mode existed. #fbfcfe is also off the Poseidon ramp by one channel, so it
   broke law 74 as well. One token fixes both: the light value moves onto the
   ramp at --n-25 and the dark value is the sunk surface. */
:root{ --field-bg: var(--n-25); }
[data-theme="dark"],
/* ── THE GUARD THAT WAS ON THE TOKENS AND NOT ON THE PAINT ──────────────────
   30 Aug 2026. Every block below paints the DARK ground. The blocks that flip
   the TOKENS to dark are all written `[data-ground="wash"]:not([data-theme="light"])`
   — lines 392, 432, 440, 555, 632, 646 — and these were not.

   So the moment anyone is on Light (a member choosing it, or the OS reporting
   light while the preference is `system`), on all 159 pages carrying
   data-ground="wash":

       · the tokens fall back to LIGHT   → --text becomes #123955
       · this paint STILL MATCHES        → the #123955 → #0C273A gradient is drawn
       · every heading resolves #123955 on #123955                    ≈ 1.0 : 1

   Sean found it by looking at the screen. No contrast checker can: the
   background COLOUR is light (#F4F9FD, 11.37:1) and the dark is a background
   IMAGE painted over it. Measured in his browser on index.html, 30 Aug.

   Adding the same :not() the token blocks already carry. Nothing else changes:
   on dark and on `system`-resolving-dark these selectors match exactly as
   before. ────────────────────────────────────────────────────────────────── */
[data-ground="wash"]:not([data-theme="light"]){ --field-bg: var(--surface-sunk); }

[data-ground="wash"]:not([data-theme="light"]){
  /* THE GROUND. Poseidon at two lightnesses — law 74, no generic grey. */
  --wash:       linear-gradient(135deg, #123955 0%, #0C273A 100%);
  /* The light source. A wash without one is a flat fill wearing a gradient. */
  --wash-lift:  radial-gradient(900px 520px at 12% -8%, rgba(84,208,216,.16), transparent 62%);
  --wash-lift2: radial-gradient(700px 460px at 92% 8%, rgba(212,192,152,.07), transparent 60%);

  /* THE PANE. Lifted verbatim from _lab/control-desk.html, which the owner
     approved on 25 Aug — "control desk is law ... love it". Not re-derived. */
  --glass-fill: linear-gradient(158deg, rgba(12,39,58,.86), rgba(7,21,32,.92));
  --glass-line: rgba(84,208,216,.18);
  --glass-hi:   rgba(140,232,238,.14);   /* the inner highlight — obligation 2 */
  --glass-edge: rgba(84,208,216,.72);    /* the top light-line                 */
  --glass-blur: blur(10px);
  --glass-sh:   0 0 0 1px rgba(7,21,32,.55), 0 20px 46px -22px rgba(0,0,0,.9);
  --glass-tick: rgba(84,208,216,.5);
}

/* ── OBLIGATION THREE · SUNLIGHT ────────────────────────────────────────────
   Owner ruling, 25 Aug 2026. The first build of this kept the Poseidon ground
   and merely brightened the hairline to cyan. That satisfied "glass is
   defeated" and broke the rest of the same sentence: law 80 says Sunlight
   takes "hairlines to full-strength POSEIDON", and #123955 on a dark ground
   is very nearly invisible — the law only coheres on a LIGHT ground. D36 had
   already proposed exactly that and was waiting on the owner's word.

   It is also the physically correct answer. In high ambient light a dark UI
   emits little and reflected glare dominates, so it washes out; a bright
   ground with dark ink keeps its contrast because the screen is competing
   with the sun rather than hiding from it.

   MEASURED, every value, on the white ground — not carried over from a dark
   palette and hoped for:
     ink      #071520  16.66:1     body   #123955  12.05:1
     muted    #1B557E   7.93:1     cyan   #007779   6.33:1  (and white ON it)
     hairline #123955  12.05:1     ok     #0E5B43   8.09:1
     danger   #8C3A2E   7.61:1     warn ink on #F2C41B 7.28:1
   Cyan drops from #00B8BB to #007779 because the brand cyan is a FILL — it
   measures 2.31:1 and can never be type or a hairline (law 75). Outdoors the
   accent has to survive as a line, so it steps down the same ramp.

   This block must restate the whole token set, not a few of them: the ground
   selector re-points every dark token, so Sunlight has to out-specify all of
   it. [data-ground][data-mode] is (0,2,0) against the ground's (0,1,0). */
[data-ground="wash"][data-mode="sun"]{
  /* the ground — flat white, no light source, no lift */
  --wash:       linear-gradient(#FFFFFF, #FFFFFF);
  --wash-lift:  none;
  --wash-lift2: none;

  /* surfaces */
  --canvas:       #FFFFFF;
  --surface:      #FFFFFF;
  --surface-sunk: #F4F9FD;
  --surface-alt:  #EAF4FB;
  --field-bg:     #FFFFFF;
  --shell-bar:    #FFFFFF;

  /* ink */
  --text:         #071520;
  --text-body:    #123955;
  --text-muted:   #1B557E;
  --text-inverse: #FFFFFF;

  /* hairlines to full strength — the words of the law */
  --line:         #123955;
  --line-strong:  #071520;

  /* the accent steps down so it can be a LINE outdoors */
  --accent:       #0B7A7D;
  --accent-hover: #123955;
  --accent-press: #123955;
  --accent-wash:  #ECFFFF;
  --accent-line:  #007779;
  --accent-on:    #FFFFFF;   /* 5.36:1 on #007779. Poseidon on it is 2.25:1 and fails. */
  --accent-text:  #007779;

  /* signals — the light-ground set, law 79 */
  --ok:     #0E5B43; --ok-wash:     #E8FAF3; --ok-line:     #0E5B43; --ok-on:     #FFFFFF;
  /* 28 Aug 2026 — THE FILL AND THE INK WERE SWAPPED HERE, and only here.
     --ok and --danger on the lines either side both hold their INK; this line
     held the FILL, so `color: var(--warn)` rendered true yellow on cream at
     1.53:1 — in the one mode built for reading a screen in direct sunlight.
     The fill is untouched: this block declares no --warn-fill, so it inherits
     #F2C41B from :root and true yellow still carries Poseidon at 7.28:1. */
  --warn:   #6E5400; --warn-wash:   #FDF6DC; --warn-line:   #6E5400; --warn-on:   #123955;
  --danger: #8C3A2E; --danger-wash: #FAE6E2; --danger-line: #8C3A2E; --danger-on: #FFFFFF;
  --fg-on-signal: #123955;

  /* money stays a fill; the stroke is the only gold that may be a line */
  --money: #9C7E41; --money-on: #123955; --money-stroke: #9C7E41;
  --money-wash: #F6F1E8;

  /* THE GLASS IS DEFEATED — panes solid, blur zero, edge a real Poseidon line */
  --glass-fill: linear-gradient(#FFFFFF, #FFFFFF);
  --glass-line: #123955;
  --glass-hi:   transparent;      /* no inner highlight: nothing to compensate */
  --glass-edge: #123955;
  --glass-blur: blur(0px);
  --glass-sh:   0 1px 2px rgba(7,21,32,.14);
  --glass-tick: #123955;

  /* type one step heavier — D36. Only the system's own weight tokens; pages
     that hardcode a weight keep it, and that is recorded rather than chased. */
  --fw-regular: 500; --fw-medium: 590; --fw-semibold: 650;

  color-scheme: light;
}

/* The mark goes back to Poseidon on the light ground — the wash rule below
   swaps it to the white lockup, and Sunlight must undo that or the wordmark
   disappears into white. Higher specificity, same mechanism. */
[data-ground="wash"][data-mode="sun"] img[src*="th-logo.svg"],   /* *= not $=: the src carries ?v= (19 Sep) */
[data-ground="wash"][data-mode="sun"] .logo-img,
[data-ground="wash"][data-mode="sun"] #thH .lg{
  content: url("th-logo.svg?v=264b9f69");
}
@media (prefers-reduced-transparency: reduce){
  [data-ground="wash"]{
    --glass-fill: linear-gradient(158deg, #0C273A, #0C273A);
    --glass-line: #54D0D8;
    --glass-blur: blur(0px);
  }
}

/* THE GROUND ITSELF. Three fixed layers on body rather than a positioned
   pseudo-element: a fixed ::before at z-index 0 sits over any unpositioned
   content, and this platform has 132 pages of it. */
[data-ground="wash"]:not([data-theme="light"]) body{
  /* An opaque floor UNDER the gradients. Without it background-color stays
     transparent and the ground is carried entirely by background-image — the
     viewer's own canvas shows through anywhere a layer does not paint. */
  background-color: var(--canvas);
  background-image: var(--wash-lift), var(--wash-lift2), var(--wash);
  background-attachment: fixed, fixed, fixed;
  background-repeat: no-repeat, no-repeat, no-repeat;
  color: var(--text);
}

/* THE MATERIAL. The top light-line is a 1px background layer, not ::before —
   pages already use ::before on cards and a collision is silent. */
[data-ground="wash"]:not([data-theme="light"]) .card,
[data-ground="wash"]:not([data-theme="light"]) .cd-card,
[data-ground="wash"]:not([data-theme="light"]) .panel,
[data-ground="wash"]:not([data-theme="light"]) .tile,
[data-ground="wash"]:not([data-theme="light"]) .pane,
[data-ground="wash"]:not([data-theme="light"]) .stat,
[data-ground="wash"]:not([data-theme="light"]) .kpi{
  background-color: transparent;
  background-image:
    linear-gradient(90deg, transparent, var(--glass-edge), transparent),
    var(--glass-fill);
  background-size: 100% 1px, auto;
  background-repeat: no-repeat, no-repeat;
  background-position: 0 0, 0 0;
  border: 1px solid var(--glass-line);
  box-shadow: var(--glass-sh), inset 0 1px 0 var(--glass-hi);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}

/* The floating layer — rails, bars and overlays. Law 80 names these. */
[data-ground="wash"]:not([data-theme="light"]) .topbar,
[data-ground="wash"]:not([data-theme="light"]) .sidebar,
[data-ground="wash"]:not([data-theme="light"]) .rail,
[data-ground="wash"]:not([data-theme="light"]) #thTop{
  background: linear-gradient(180deg, rgba(7,21,32,.52), rgba(7,21,32,.24));
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-color: var(--glass-line);
}

/* Instrument corner ticks — opt-in, never automatic. .tick + .tl/.tr/.bl/.br */
[data-ground="wash"]:not([data-theme="light"]) .tick{
  position:absolute; width:11px; height:11px; border-color:var(--glass-tick);
}
[data-ground="wash"]:not([data-theme="light"]) .tick.tl{ top:8px; left:8px;  border-top:1px solid; border-left:1px solid }
[data-ground="wash"]:not([data-theme="light"]) .tick.tr{ top:8px; right:8px; border-top:1px solid; border-right:1px solid }
[data-ground="wash"]:not([data-theme="light"]) .tick.bl{ bottom:8px; left:8px;  border-bottom:1px solid; border-left:1px solid }
[data-ground="wash"]:not([data-theme="light"]) .tick.br{ bottom:8px; right:8px; border-bottom:1px solid; border-right:1px solid }

/* THE MARK ON THE DARK GROUND. th-logo.svg is Poseidon #123955 lettering with
   the teal E — correct on canvas, invisible on the wash. th-logo-white-teal.svg
   is the same lockup with the 12 wordmark fills at #FFFFFF and the E still
   #00B8BB, and it already existed. content: url() on a replaced element swaps
   the raster without a second <img> in every header, and the URL resolves
   against THIS stylesheet, which sits beside the asset.
   NOT filter:invert() — that would take the teal E to #FF4744. */
/* 3 Oct 2026 · never in Sunlight: that mode paints a WHITE ground whatever the theme says, and the white cut went on it,
   invisible (Sean: "you change the logo to white on a white background"). */
[data-ground="wash"]:not([data-theme="light"]):not([data-mode="sun"]) img[src*="th-logo.svg"],   /* *= not $=: the src carries ?v= (19 Sep) */
[data-ground="wash"]:not([data-theme="light"]):not([data-mode="sun"]) .logo-img,
[data-ground="wash"]:not([data-theme="light"]):not([data-mode="sun"]) #thH .lg{
  content: url("th-logo-white-teal.svg?v=0580ad2d");
}

/* Decoration is damped in Sunlight. A soft accent glow behind a form is
   pleasant in a site office and is pure noise on a roof at midday — the mode
   exists for legibility, so anything that is not carrying information gets
   out of the way. Not removed: at .18 it still reads as the same page. */
[data-ground="wash"][data-mode="sun"] .blob,
[data-ground="wash"][data-mode="sun"] .glow,
[data-ground="wash"][data-mode="sun"] .orb{ opacity:.18; filter:saturate(.5); }

/* ═══════════════════════════════════════════════════════════════════════════
   THE TWELVE CAME OUT — 7 September 2026, LAW 106

   A block of twelve [data-palette] accents lived here from 28 August. It was
   real work, honestly measured (0 of 205 pages responded to the accent before
   FOUR-SWITCHES-4SEP, 199 of 201 after), and it is deleted on purpose.

   THE REASON, SO NOBODY REBUILDS IT: each PILLAR now owns a ramp — Trade Handy
   cool, GeoBidder warm, GeoMundra violet — so the colour of the room is what
   tells a person which product they have opened. A member repainting the app
   destroys exactly that. Twelve member accents and three fixed pillar
   identities cannot both be true, and the pillar wins because it carries
   information where a preference carries none.

   Anything that reintroduces a per-member hue — a setting, a theme, a "brand
   colour" that leaks out of a document and into the chrome — contradicts Law
   106. The member's DOCUMENT brand (invoice.html, th-fontstudio.js) is a
   different feature and is untouched: it paints the document, never the chrome.

   Sunlight mode stays. It is a field requirement for people working outdoors,
   not a preference, and it was never one of the switches that came out.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── THE FOCUS RING ──────────────────────────────────────────────────────────
   th-components.css sets, on .input/.select/.textarea:focus-visible:
       outline: 2px solid var(--focus);  border-color: transparent;
   Where --focus is undefined the OUTLINE is dropped as invalid but the
   transparent border still applies, so tabbing into a text field REMOVES its
   border and draws nothing — an indicator worse than none.

   Measured 28 Aug 2026: th-components.css is linked by 83 pages, and only 9 of
   them also linked th-desk-retint.css, the single file that defined --focus.
   Seventy-four pages of forms had no visible focus on any text input.

   It resolves to --accent-text and NOT --accent. A focus ring is non-text UI and
   must clear 3:1 against what it sits on; Cyan Sky #00B8BB measures 2.30:1 on the
   light canvas and 2.45:1 on light surface, so the retired shim's own
   --focus: var(--accent) was failing on the eighteen pages that did carry it.
   --accent-text is 5.04:1 / 5.36:1 in light and 10.02:1 in dark, and it follows
   the skin and the theme, so this is one line rather than a table of exceptions.
   No new colour: this is Tier One, already ruled.                             */
:root{ --focus: var(--accent-text); }


/* ══════════════════════════════════════════════════════════════════════════
   THE GRID — one tile rhythm for the whole platform
   Added 31 Aug 2026. Measured before it existed: 95 different
   grid-template-columns declarations across 412 uses, 27 different tile
   widths from 84px to 480px, and 29 different gap values. Nothing lined up
   between two pages because there was nothing to line up TO — this sheet is
   linked by 168 pages and had no grid system at all, so every page invented
   one.

   auto-FILL, never auto-fit. auto-fit collapses the empty tracks and
   stretches whatever is left to fill the row, so five cards in a four-wide
   grid render as four normal tiles and one enormous one underneath. That is
   the single reason the tiles looked out of shape, and it was on 92 of the
   106 responsive grids.
   ══════════════════════════════════════════════════════════════════════════ */
:root{
  --tile-sm:132px;   /* meters, chips, small counters      */
  --tile:232px;      /* the standard card                  */
  --tile-lg:300px;   /* wide cards that carry a chart      */
  --gap:16px;        /* the one gap. 4px scale: 4 8 12 16 20 24 32 40 */
}
.th-grid{display:grid;gap:var(--gap);
         grid-template-columns:repeat(auto-fill,minmax(var(--tile),1fr))}
.th-grid--sm{--tile:var(--tile-sm)}
.th-grid--lg{--tile:var(--tile-lg)}
.th-grid--tight{--gap:8px}
.th-grid--roomy{--gap:24px}
/* fixed counts, for when the number of things is known and meaningful */
.th-grid--2{grid-template-columns:repeat(2,minmax(0,1fr))}
.th-grid--3{grid-template-columns:repeat(3,minmax(0,1fr))}
.th-grid--4{grid-template-columns:repeat(4,minmax(0,1fr))}
/* minmax(0,1fr) not 1fr: a long unbroken word in one cell otherwise widens
   that column and every row on the page stops aligning. */
@media (max-width:900px){
  .th-grid--3,.th-grid--4{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:560px){
  .th-grid,.th-grid--2,.th-grid--3,.th-grid--4{grid-template-columns:1fr}
}

/* ══ THE ANCHOR PALETTE — 23 SEPTEMBER 2026 ═══════════════════════════════════
   The anchors BY NAME, so a setting can point at one.

   Every value here is already in this file — Poseidon appears 30 times, Bronze
   Mist 10 and the deep teal 5, all as raw literals. Nothing is invented and no
   fourth brand colour is added: THE LOOK §1 names Poseidon, Cyan Sky and Bronze
   Mist as the three anchors, and §1.3 names #007779 as cyan's text-safe form.
   This only gives them names.

   WHY IT WAS NEEDED. mig1490 let the look be chosen from a list — Poseidon,
   Bronze Mist, deep teal among them — and _thememake.py reads the chosen name
   out of this sheet rather than carrying any colour of its own, which is what
   stops a theme tool from defeating the colour law. Those three had no name to
   read, so SEVEN of the choices on offer would have made the build refuse. The
   menu promised what the kitchen could not cook. */
:root{
  --anchor-cyan:     #00B8BB;   /* Cyan Sky · THE LOOK §1 */
  --anchor-poseidon: #123955;   /* Poseidon · THE LOOK §1 */
  --anchor-bronze:   #9C7E41;   /* Bronze Mist · THE LOOK §1 */
  --anchor-deepteal: #007779;   /* cyan's text-safe form · THE LOOK §1.3 */
  --anchor-copper:   #B95A28;   /* GeoBidder's own */
  --anchor-violet:   #784CA9;   /* GeoMundra's own. 23 Sep 2026: was #7D3AC1 at chroma
                                   80.7 — 41% louder than the copper and 186% louder than
                                   the teal, which is why it read over its siblings rather
                                   than beside them. Same violet at the family's volume:
                                   hue 311 against 313, chroma matched to the copper's 57.3,
                                   white lettering 6.17:1, and it clears every kind of
                                   colour blindness at worst dE 34. */
}
