/* ==========================================================================
   Hermes Web Apps — design tokens
   One source of truth for color, type, spacing, radius and elevation.

   Loaded by every app via <link rel="stylesheet" href="/tokens.css">.
   This is the one exception to the "all CSS inline" convention in CLAUDE.md:
   tokens are shared by definition, and eight near-identical greys accumulated
   precisely because there was nowhere for a shared value to live.

   Rules:
   - Never hard-code a hex, size or radius in an app. Reference a token.
   - If a value you need isn't here, add it here first, then use it.
   - Tokens are semantic (what it MEANS), not descriptive (what it looks like).
     --text-secondary, never --grey-3. The name has to survive a repaint.
   - Light mode only. No dark-mode block — that's a deliberate product call.

   REGISTER: Editorial. Graphic. Confident. Ground truth is
   /root/wiki/design-manifesto.md — a printed guidebook or a design-biennale
   poster, NOT a corporate dashboard and NOT Apple's soft minimalism.
   Borders over shadows. Black over tints. Type leads.
   ========================================================================== */


/* ==========================================================================
   IBM Plex Sans Thai — SELF-HOSTED (2026-08-22)
   Thai previously fell through to whatever the device happened to install:
   Thonburi on iOS, Noto on Android, Loma on the Linux kiosk. Three different
   typefaces with three different vertical metrics on a piece whose primary
   audience reads Thai. Self-hosted so the exhibition does not depend on a CDN
   or on the venue's network, and so every device renders the same face.
   ========================================================================== */
@font-face{font-family:'IBM Plex Sans Thai';font-style:normal;font-weight:400;
  font-display:swap;src:url('/vendor/plex-thai-400-0.woff2') format('woff2');
  unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F}
@font-face{font-family:'IBM Plex Sans Thai';font-style:normal;font-weight:400;
  font-display:swap;src:url('/vendor/plex-thai-400-1.woff2') format('woff2');
  unicode-range:U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC}
@font-face{font-family:'IBM Plex Sans Thai';font-style:normal;font-weight:400;
  font-display:swap;src:url('/vendor/plex-thai-400-2.woff2') format('woff2');
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'IBM Plex Sans Thai';font-style:normal;font-weight:400;
  font-display:swap;src:url('/vendor/plex-thai-400-3.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'IBM Plex Sans Thai';font-style:normal;font-weight:600;
  font-display:swap;src:url('/vendor/plex-thai-600-4.woff2') format('woff2');
  unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F}
@font-face{font-family:'IBM Plex Sans Thai';font-style:normal;font-weight:600;
  font-display:swap;src:url('/vendor/plex-thai-600-5.woff2') format('woff2');
  unicode-range:U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC}
@font-face{font-family:'IBM Plex Sans Thai';font-style:normal;font-weight:600;
  font-display:swap;src:url('/vendor/plex-thai-600-6.woff2') format('woff2');
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'IBM Plex Sans Thai';font-style:normal;font-weight:600;
  font-display:swap;src:url('/vendor/plex-thai-600-7.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'IBM Plex Sans Thai';font-style:normal;font-weight:700;
  font-display:swap;src:url('/vendor/plex-thai-700-8.woff2') format('woff2');
  unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F}
@font-face{font-family:'IBM Plex Sans Thai';font-style:normal;font-weight:700;
  font-display:swap;src:url('/vendor/plex-thai-700-9.woff2') format('woff2');
  unicode-range:U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC}
@font-face{font-family:'IBM Plex Sans Thai';font-style:normal;font-weight:700;
  font-display:swap;src:url('/vendor/plex-thai-700-10.woff2') format('woff2');
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'IBM Plex Sans Thai';font-style:normal;font-weight:700;
  font-display:swap;src:url('/vendor/plex-thai-700-11.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}

/* ==========================================================================
   Space Grotesk — SELF-HOSTED (2026-08-24), same reasoning as IBM Plex Sans
   Thai above: no CDN dependency, every device renders the same face.
   It's a variable font — Google serves the identical 3 files (split by
   unicode-range, not by weight) under four separate font-weight
   declarations, and the browser instantiates the correct weight from the
   file's own weight axis. That's not a mistake below, it's how the format
   works: don't "fix" it into four distinct files per weight.
   ========================================================================== */
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:400;
  font-display:swap;src:url('/vendor/space-grotesk-vietnamese.woff2') format('woff2');
  unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:400;
  font-display:swap;src:url('/vendor/space-grotesk-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:400;
  font-display:swap;src:url('/vendor/space-grotesk-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:500;
  font-display:swap;src:url('/vendor/space-grotesk-vietnamese.woff2') format('woff2');
  unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:500;
  font-display:swap;src:url('/vendor/space-grotesk-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:500;
  font-display:swap;src:url('/vendor/space-grotesk-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:600;
  font-display:swap;src:url('/vendor/space-grotesk-vietnamese.woff2') format('woff2');
  unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:600;
  font-display:swap;src:url('/vendor/space-grotesk-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:600;
  font-display:swap;src:url('/vendor/space-grotesk-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:700;
  font-display:swap;src:url('/vendor/space-grotesk-vietnamese.woff2') format('woff2');
  unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:700;
  font-display:swap;src:url('/vendor/space-grotesk-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:700;
  font-display:swap;src:url('/vendor/space-grotesk-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}

:root {

  /* --- Typeface ---------------------------------------------------------
     One stack, everywhere. Previously four, including a stray Tahoma.       */
  /* ONE stack carries both scripts. The Thai face sits inside the Latin stack
     so the browser resolves Thai glyphs per-glyph via normal font fallback.
     Nothing switches font-family by language — a language-dependent stack made
     Latin text re-render in the Thai face and reflowed the whole document on
     every toggle. Space Grotesk became the primary face 2026-08-24 (Alex) —
     it doesn't cover Thai glyphs, so IBM Plex Sans Thai stays in the chain
     for exactly the same per-glyph-fallback reason as before.               */
  --font-sans: 'Space Grotesk', -apple-system, 'SF Pro Text', 'Helvetica Neue', 'IBM Plex Sans Thai', sans-serif;
  --font-mono: ui-monospace, 'SF Mono', monospace;

  /* --- Type scale -------------------------------------------------------
     Nine steps, collapsed from eighteen ad-hoc sizes. Steps are far enough
     apart to read as distinct levels; .7 vs .72 never was.                  */
  --fs-3xs:  .65rem;   /* micro labels under a stat number                   */
  --fs-2xs:  .7rem;    /* captions, metadata, timestamps                     */
  --fs-xs:   .75rem;   /* secondary detail lines in cards                    */
  --fs-sm:   .8rem;    /* small body                                         */
  --fs-md:   .85rem;   /* default body                                       */
  --fs-lg:   .9rem;    /* emphasised body, stat values                       */
  --fs-base: 1rem;     /* alias of --fs-xl; body-default where named that way */
  --fs-xl:   1rem;     /* section and page headings                          */
  --fs-2xl:  1.2rem;   /* large headings                                     */
  --fs-3xl:  1.5rem;   /* hero numbers                                       */
  --fs-4xl:  1.75rem;  /* page title                                         */
  --fs-5xl:  2rem;     /* page title, larger app                              */
  --fs-6xl:  2.5rem;   /* dashboard hero                                      */


  /* --- Surfaces --------------------------------------------------------- */
  --bg:             #faf7f2;  /* page ground — warmer step, was #f7f6f3 until 2026-08-24 */
  --surface:        #ffffff;  /* cards, sheets                               */
  --surface-sunken: #f5f2ed;  /* wells, inset rows, dividing fills — was #f0f0f2 until 2026-08-24 */

  /* --- Text ------------------------------------------------------------- */
  --text:           #1a1a1a;  /* primary — was #1d1d1f until 2026-08-24      */
  --text-muted:     #40403d;  /* dense secondary text that must stay readable */
  --text-secondary: #666361;  /* supporting detail — 5.96:1 white, 5.58:1 on --bg */
  --text-disabled:  #b2ada6;  /* DISABLED CONTROLS ONLY — 2.23:1. Never use it for
                                 de-emphasised content: an empty state or a
                                 chevron is not disabled, it is the message. */
  --text-on-accent: #ffffff;

  /* --- Lines ------------------------------------------------------------ */
  --border-card:   #1a1a1a;   /* CARDS, chips, pills. Manifesto §3. Was #1d1d1f until 2026-08-24 */
  --bw-card:       2px;       /* Border WEIGHT for cards/chips/pills. 1px read
                                 too thin against the editorial register.      */
  --divider:       #ebe5de;   /* hairline between rows inside a card — was #e2e0db until 2026-08-24 */
  --divider-minor: #ebe5db;   /* lightest hairline, dense row lists — was #edebe6 until 2026-08-24 */
  --border:        #e0d9cc;   /* default card and control border — was #e7e6e6 until 2026-08-24 */
  --border-strong: #1a1a1a;   /* pressed, focused, or deliberately heavier — was #d3d2d2 until 2026-08-24,
                                 now equals --text/--border-card rather than a mid-grey */

  /* The same two neutrals used as fills rather than lines — empty progress
     tracks, inactive heatmap cells. Aliased on purpose so a reader never has
     to wonder why a background references a border token.                   */
  --fill-subtle: #f5f2ed;  /* was #e7e6e6 until 2026-08-24 */
  --fill-muted:  #e0d9cc;  /* was #d3d2d2 until 2026-08-24 */

  /* --- Semantic ---------------------------------------------------------
     One family: Apple system colors. The suite previously mixed Apple,
     Material and Tailwind palettes across different apps.                   */
  --accent:   #3b9c70;  /* GREEN. Manifesto §3: if a UI element demands emphasis
                           it gets accent, not an arbitrary tint. Active states,
                           selected chips, bold lines. Was black (#1d1d1f) until
                           2026-08-24, #007aff until 2026-08-22.                */
  --positive: #4a9d5c;  /* success, deficit, favourable — deliberately distinct
                           from --accent (warmer/less-blue green) so a plain
                           emphasis element never reads as a genuine health
                           signal. Was #30d158 until 2026-08-24.               */
  --warning:  #e8a838;  /* attention, surplus, needs-review — was #ff9f0a until 2026-08-24 */
  --negative: #e87359;  /* error, failure, destructive — was #ff3b30 until 2026-08-24 */

  /* Each semantic has THREE roles. Use the right one — this is the rule that
     keeps the palette both consistent and readable:
       --x      fill:    bars, dots, filled chips. Bright, never used as text.
       --x-bg   tint:    the light background behind a badge or banner.
       --x-fg   dark:    the dark end of the ramp. The ONLY role allowed on
                         text — and also the correct fill when the label on
                         top is white. Clears AA on white, on its own tint,
                         and under white text.
     Never put --x and --x-fg in the same visual unit: adjacent they read as
     two colors, separated across views they read as one.
     The Apple system colors are fills; at ~2:1 on white they are unreadable as
     text. The -fg ramp is those hues darkened, not a second palette. */
  --accent-bg:   #e5f2e8;  /* green-tinted, was warm-neutral #ebe9e4 until 2026-08-24 */
  --positive-bg: #e8f3ea;  /* distinct from --accent-bg — was #e8f8ee until 2026-08-24 */
  --warning-bg:  #faf0d9;  /* was #fff4e5 until 2026-08-24 */
  --negative-bg: #fae5e0;  /* was #ffecea until 2026-08-24 */

  --accent-fg:   #26734d;  /* 5.77:1 on white, 5.00:1 on --accent-bg. Was black until 2026-08-24 */
  --positive-fg: #2b6b3d;  /* 6.42:1 on white, 5.63:1 on --positive-bg. Was #1d7f3c until 2026-08-24 */
  --warning-fg:  #91600c;  /* 5.41:1 on white, 4.77:1 on --warning-bg. Figma reported #99660d
                              (4.93/4.35) which fails AA on --warning-bg — darkened 5% to clear
                              both; Figma's Semantic/warning-fg needs the matching correction to
                              stay in sync with this file, per the source-of-truth note in DESIGN.md */
  --negative-fg: #b2402e;  /* 5.73:1 on white, 4.73:1 on --negative-bg. Was #cf1f14 until 2026-08-24 */

  /* --- Brand ------------------------------------------------------------
     CosmoLocal / Nomad Futures. Used in app headers, not for status.        */
  --brand:      #250632;  /* aubergine */
  --brand-gold: #edb92a;
  --brand-cream:#fefef4;
  --brand-bg:   #f0ebf5;  /* ~8% aubergine wash                                */
  /* --brand is ~17:1 on white, so it serves as both fill and text. It is the
     one semantic that does NOT need a -fg role. Don't invent one. */

  /* --- Spacing ----------------------------------------------------------
     4px base. Defined for adoption as apps are touched; existing padding is
     not force-migrated, since spacing changes are visual, not mechanical.   */
  --sp-1: .25rem;
  --sp-2: .5rem;
  --sp-3: .75rem;
  --sp-4: 1rem;
  --sp-5: 1.25rem;
  --sp-6: 1.5rem;
  --sp-8: 2rem;

  /* --- Radius -----------------------------------------------------------
     ALL ZERO (2026-08-22, Alex). Sharp 90-degree corners everywhere — the
     graphic/editorial register has no rounded corners at all: not on cards,
     chips, pills, buttons, modals or inputs. The names are kept rather than
     deleted so every app squares off without a single per-app edit, and so a
     future decision has one place to change.                                */
  --r-xs:     0;
  --r-sm:     0;
  --r-md:     0;
  --r-lg:     0;
  --r-pill:   0;
  --r-circle: 0;

  /* --- Elevation --------------------------------------------------------
     Restrained on purpose: this is a flat, paper-like aesthetic.           */
  /* Manifesto §6 refuses drop shadows as primary structure — a card is defined
     by its border, not by floating. These two are now none, deliberately kept
     as names so every app that references them flattens without an edit.      */
  --shadow-sm:     none;
  --shadow-raised: none;
  /* GO FLAT (2026-08-22, Alex): no shadow anywhere, overlays included. An
     overlay now reads as detached through a 2px black border, not elevation.
     Kept as a name so any stray reference resolves to nothing.               */
  --shadow-float:  none;

  /* Hard offset "stamp" shadow — confirmed against Figma 65:188/68:336
     (2026-08-24): a flat, 0-blur, 100%-opaque near-black duplicate offset
     3px right/down from card-level components (daily total, weekly total,
     etc). This does NOT reverse the shadow-sm/-raised/-float refusal above —
     those refuse a *floating, blurred* shadow standing in for elevation.
     A 0-blur hard offset has no blur falloff to read as elevation; it reads
     as another flat graphic plane, same register as a border. Not yet
     applied suite-wide — currently adopted by tracker.html's card-level
     components; add here first if another app needs it, per the token rule. */
  --shadow-card: 3px 3px 0 0 var(--border-card);

  /* --- Motion -----------------------------------------------------------
     One duration and one curve, so expand/collapse feels identical
     everywhere.                                                            */
  --ease:     cubic-bezier(.4, 0, .2, 1);
  --dur-fast: .15s;
  --dur:      .25s;

  /* --- Map: the emphasis ramp -------------------------------------------
     For the Wat Ket field map. Three steps, and only three. Every lens is a
     table of which cartographic primitive sits at which step — that IS the
     lens mechanic. A fourth step would make the hierarchy unreadable.
     Several are aliases of existing values on purpose: aliasing keeps the
     map inside the palette rather than beside it.                          */
  --map-paper:      #f7f6f3;  /* alias --bg. The drawing sits ON the page.   */
  --map-quiet:      #f0efec;  /* present, not read. One warm step off paper  */
  --map-rest:       #e7e6e6;  /* alias --fill-subtle                         */
  --map-promoted:   #d3d2d2;  /* alias --fill-muted                          */

  /* Line work. Deliberately NOT the --text-* ramp: a recessed map line is
     not de-emphasised text, and it is certainly not a disabled control.     */
  --map-ink:        #48484a;  /* alias --text-muted — promoted line work     */
  --map-ink-rest:   #9a9998;  /* default line work                           */
  --map-ink-quiet:  #cfcecb;  /* recessed line work                          */

  --map-water:      #dde8f2;  /* keyed to --accent-bg, desaturated. The one
                                 new hue: water must read as water without
                                 becoming a second accent.                   */
  --map-water-line: #b9cfe3;
  --map-boundary:   #8e8d8b;  /* dashed administrative line                  */

  --marker:         #3b9c70;  /* alias --accent — was black (#1d1d1f) until
                                 2026-08-24; Alex confirmed the marker should
                                 follow the new green accent rather than stay
                                 black for contrast reasons. */
  --marker-held:    #3b9c70;
  --marker-ring:    #ffffff;  /* the white ring that separates pin from tiles  */

  /* Stroke weights, in SVG user units at the base viewBox.                  */
  --map-w-quiet:    .5;
  --map-w-rest:     1;
  --map-w-promoted: 2;

  /* --- Thai -------------------------------------------------------------
     The sans stack names no Thai face, so Thai renders through OS fallback:
     Thonburi/SF Thai on iOS, Noto Sans Thai on Android, unpredictable on a
     Linux kiosk. Naming the face is tracked in t_ce10081b; the line-height
     below is the part that cannot wait.                                    */
  --font-thai: 'IBM Plex Sans Thai', 'Noto Sans Thai', 'Thonburi', 'Sarabun', sans-serif;
  --lh-latin:  1.35;
  --lh-thai:   1.6;   /* MEASURED FLOOR. At 1.35 and 1.2rem, Thai ink
                         overflows its own line box by ~2px — tone marks clip.
                         Latin is fine at 1.35. Confirm on a real device
                         before content lock; this was measured against Loma. */

  /* --- Map motion ------------------------------------------------------- */
  --dur-lens:  .45s;  /* a district re-drawing itself. --dur (.25s) is right
                         for a card and too abrupt for this.                 */
  --dur-trace: 1.2s;  /* the GPS trace drawing in, west to east.             */
}

/* Respect the OS setting for users who ask for less movement. */
@media (prefers-reduced-motion: reduce) {
  :root { --dur-fast: .01ms; --dur: .01ms; }
}
