/* ==========================================================================
   osler-theme — design tokens  ·  VISUAL RESTYLE (Direction A, "Ward Paper")
   --------------------------------------------------------------------------
   Drop-in replacement for osler/static/theme/tokens.css.

   Layering, variant hooks, accessibility remaps and token NAMES are
   unchanged, so components.css needs no edit to keep working. What changed
   is what the names resolve to.

   ---- what this file may and may not touch ------------------------------

   Per README "Geometry vs appearance", every value below is appearance:
   palette, semantic colour, typeface/weight/tracking, radius, shadow.

   NOT touched, and deliberately copied through byte-for-byte:
     · the spacing grid --s-*, radius is shape but --bw-* border weights are
       NOT (a 1px -> 2px border moves the content inside it)
     · --slide-w/h, the artefact/media/radiology size ladders, --sz-*,
       --touch-*, --safe-*, --z-*
     · the physical-job colours (--rad-ground/--rad-ink, --qr-ink/--qr-quiet,
       --blackout) — see PINNED below
     · the facsimile palettes --strip-* and --meter-* and the ECG paper
       --ecg-* — the analyser printout, the ketone meter and the ECG paper are
       the teaching material, not the chrome
     · --fs-* type sizes and --lh-* ratios, held constant on purpose: the
       restyle is a typeFACE change, not a type-SCALE change, so that the 91
       screenshot baselines shift in appearance only and no box is re-sized

   ---- PINNED: physical-job colours -------------------------------------

   These previously resolved through the neutral ramp (--c-n950 / --c-n50 /
   --c-n25). The ramp is now warm, so resolving through it would have moved
   them. They are restated as literal steps holding their EXACT previous
   values, which is also stronger than before: a future palette change
   provably cannot reach them.

     --rad-ground  #0d141b   (was var(--c-n950))
     --rad-ink     #f4f6f8   (was var(--c-n50))
     --qr-quiet    #fbfcfd   (was var(--c-n25))
     --qr-ink      #0d141b   (was var(--c-n950))
     --blackout    #0d141b   (was var(--c-n950))
   ========================================================================== */

/* ==========================================================================
   1. PALETTE — raw ramps. Components must not reference these directly.
   ========================================================================== */

:root {
  /* ---- neutrals (warm paper) -------------------------------------------
     Was a cool slate ramp. Slate-50 grounds under white cards is the single
     most template-looking thing a UI can wear, and it also sat coldly beside
     every artefact the app generates — ECG paper is salmon, thermal paper is
     warm off-white. The ramp is now pulled toward the paper the product
     itself prints, so the artefacts read as objects lying ON the interface
     rather than as panels of it. */
  --c-n0:   #ffffff;   /* reserved: not a surface in this palette */
  --c-n25:  #faf9f5;
  --c-n50:  #f1efe9;
  --c-n100: #e7e4db;
  --c-n200: #d9d5ca;
  --c-n300: #bdb8aa;
  /* Control boundaries — see --line-control. Darkened one step from the first
     cut (#7d7869): that measured 3.46:1 against --surface-sunken, which passes
     WCAG 1.4.11 but with only 15% headroom, and an input track sitting on the
     sunken ground is the exact case the rule exists for. Now 3.83:1. */
  --c-n350: #767162;
  --c-n400: #928e83;
  --c-n500: #6a675e;
  --c-n600: #514e46;
  --c-n700: #3a3830;
  --c-n800: #26241f;
  --c-n900: #1a1815;
  --c-n950: #0f0e0c;

  /* ---- monitor green — the state hue -----------------------------------
     Replaces the teal brand ramp. Not a brand colour: it is the green of a
     ward monitor's trace, and it is the ONLY hue in the system that may mean
     "this is happening" — live, published, selected, correct. It is never
     decoration, and it never appears in the wordmark.
     Teal went because it is a framework default's second suggestion, and
     because a brand hue implies a brochure. This platform is an instrument. */
  --c-m50:  #e4f0e9;
  --c-m100: #c9e5d5;
  --c-m200: #93cdae;
  --c-m300: #55b184;   /* TEXT on a dark ground (projector) */
  --c-m400: #24955f;
  /* An odd half-step, and it earns its place: this is the LIGHTEST green in the
     ramp that clears 3:1 against every ground the theme actually renders — the
     app's page, card and sunken surfaces AND the projector's — with margin, in
     one value. It exists because --accent-line has to be a real boundary
     rather than a tint (see the note on that token). Measured: 4.13 card,
     3.78 page, 3.42 sunken, 4.06 projector card, 4.42 projector page. */
  --c-m450: #1c8a56;
  --c-m500: #147a4c;   /* FILL */
  --c-m600: #0d6b45;   /* FILL, and TEXT on a light ground */
  --c-m700: #0a5537;
  --c-m800: #08402a;

  /* ---- status hues ------------------------------------------------------
     Same rule of thumb as before: 500 is a FILL, 600/700 are TEXT on light,
     200/300 are TEXT on dark.

     Re-derived from the objects the app already generates rather than from a
     generic ramp: danger is the brick red the analyser prints an abnormal
     value in and the ECG marks a warning with; caution is thermal-paper
     ochre. They now belong to the same world as the artefacts.

     --c-i* (info) is the exception that has to stay a real, distinguishable
     hue rather than collapsing into ink: components.css spends it on
     TEACHING semantics, not on notices — ectopic beats in .cs-part /
     .o-ladder and the left-axis zone in .ax-diagram. It is re-hued to the
     steel blue-grey of a lightbox surround so it stops reading as a
     framework's info-blue. */
  --c-d100: #f6ddd6;  --c-d300: #e08c76;  --c-d500: #a8391f;  --c-d700: #8c2f1c;
  --c-w100: #f6e8c9;  --c-w300: #e0b45e;  --c-w500: #8a6413;  --c-w700: #6f5010;
  --c-i100: #dde6ec;  --c-i300: #8fb3c7;  --c-i500: #3d5a6c;  --c-i700: #2c4655;
  /* ok/correct is the state hue itself — one green, not two. */

  /* ---- pinned physical-job values (see header) -------------------------- */
  --c-fix-black: #0d141b;
  --c-fix-white: #f4f6f8;
  --c-fix-quiet: #fbfcfd;

  /* ---- fixed alphas ----------------------------------------------------- */
  /* Warmed to match the ground: a cool scrim over warm paper reads blue. */
  --c-scrim:      rgba(20, 16, 11, 0.72);
  --c-scrim-deep: rgba(10, 8, 5, 0.94);
  --c-wash-light: rgba(255, 255, 255, 0.08);
  --c-wash-dark:  rgba(0, 0, 0, 0.16);
  --c-transparent: rgba(0, 0, 0, 0);

  /* ---- duotone stops for the login portrait ----------------------------
     The portrait is mapped into the palette's own ink and paper rather than
     sepia. Sepia is the heritage shorthand and looks bought; duotoning into
     the product's own two colours looks art-directed. Two stops only, and
     both come from the ramp. */
  --c-duo-ink:   #241f1a;
  --c-duo-paper: #e6e2d8;
}

/* ==========================================================================
   2. SEMANTIC — the only colour tokens components may read.
   ========================================================================== */

:root,
[data-variant="app"] {
  color-scheme: light;

  /* ---- surfaces --------------------------------------------------------- */
  /* --surface-card is now only one step from --surface-page. That is
     deliberate: the restyle replaces most boxes with rules and space, so a
     card should read as a slight lift, not as a floating object. Where a
     thing genuinely IS an object (a modal, an artefact) the border and the
     ground do the separating. */
  --surface-page:    var(--c-n50);
  --surface-card:    var(--c-n25);
  --surface-sunken:  var(--c-n100);
  --surface-inverse: var(--c-n900);
  --surface-overlay: var(--c-n25);
  --surface-scrim:   var(--c-scrim);
  --surface-hover:   var(--c-n100);
  --surface-active:  var(--c-n200);

  /* ---- ink -------------------------------------------------------------- */
  --ink:          var(--c-n900);
  --ink-strong:   var(--c-n950);
  --ink-muted:    var(--c-n500);
  --ink-faint:    var(--c-n400);
  --ink-inverse:  var(--c-n50);
  --ink-on-accent: var(--c-n50);   /* NOT #fff — see the projector rule */

  /* ---- lines ------------------------------------------------------------ */
  /* Lines carry far more of the structure than they used to, because the
     shadows are gone and most cards are gone with them. */
  --line:        var(--c-n200);
  --line-strong: var(--c-n300);
  --line-faint:  var(--c-n100);
  --line-control: var(--c-n350);   /* WCAG 1.4.11, 3:1 — unchanged job */

  /* ---- accent = state, and nothing else --------------------------------- */
  --accent:        var(--c-m600);
  --accent-hover:  var(--c-m700);
  --accent-active: var(--c-m800);
  --accent-ink:    var(--c-m700);
  --accent-soft:   var(--c-m50);
  /* NOT a pale tint. The first cut used the 200 step (#93cdae) and I reported
     it as 3.0:1 — it is 1.72:1, and nothing in the pale end of the ramp gets
     near 3:1 (the 400 step still only reaches 2.98 on --surface-sunken). Since
     this is the border of .o-card--accent, which is how "needs your review"
     and "session not started" announce themselves, it is a boundary carrying
     state and not a decorative divider — so it meets the non-text threshold
     properly, at the same value in both variants. */
  --accent-line:   var(--c-m450);

  /* ---- status ----------------------------------------------------------- */
  --ok:        var(--c-m500);  --ok-ink:   var(--c-m700);  --ok-soft:   var(--c-m50);
  --bad:       var(--c-d500);  --bad-ink:  var(--c-d700);  --bad-soft:  var(--c-d100);
  --warn:      var(--c-w500);  --warn-ink: var(--c-w700);  --warn-soft: var(--c-w100);
  --info:      var(--c-i500);  --info-ink: var(--c-i700);  --info-soft: var(--c-i100);

  /* ---- the primary verb (restyle) ---------------------------------------
     Accent means state; the main button is not a state. Ink-filled here,
     accent-filled on the projector — the pair keeps components.css
     unconditional. */
  --btn-primary-fill:        var(--ink);
  --btn-primary-ink:         var(--ink-inverse);
  --btn-primary-fill-hover:  var(--ink-strong);
  --btn-primary-fill-active: var(--c-n800);

  /* ---- focus ------------------------------------------------------------ */
  --focus-ring:   var(--c-m500);
  --focus-width:  var(--bw-3);
  --focus-offset: var(--bw-2);

  /* ---- elevation -------------------------------------------------------- */
  /* The projector variant already collapsed these to none because a lens
     renders a soft shadow as a smudge. The same discipline now applies to the
     app variant, for a different reason: an ambient 0 1px 3px drop shadow on
     every card is the signature of a generated UI, and this product's own
     artefacts are the only things on screen that should look physical. The
     one exception in the system is --strip-shadow, below: the analyser
     printout is an object lying on the slide, so it still casts one. */
  --shadow-1: none;
  --shadow-2: none;
  --shadow-3: none;

  /* ---- type scale ------------------------------------------------------- */
  /* UNCHANGED — see the header. */
  --fs-xs:  0.75rem;
  --fs-sm:  0.875rem;
  --fs-md:  1rem;
  --fs-lg:  1.125rem;
  --fs-xl:  1.375rem;
  --fs-2xl: 1.75rem;
  --fs-3xl: 2.25rem;
  --fs-4xl: 3rem;

  /* ---- component-specific sizing that shifts per variant ---------------- */
  --poll-bar-h:   var(--sz-poll-bar);
  --qr-size:      var(--sz-qr-app);
  --border-card:  var(--bw-1);

  /* ---- FACSIMILE: generated thermal strip (S4) — UNCHANGED -------------- */
  --strip-paper:      #f4f1e8;
  --strip-ink:        #16150f;
  --strip-ink-muted:  #5d5a4e;
  --strip-ink-faint:  #8f8b7c;
  --strip-rule:       #b9b4a2;
  --strip-warn:       #7a2f1d;
  --strip-shadow:     drop-shadow(0 2px 6px rgba(13, 20, 27, 0.28));
  --strip-fs-body:    12px;
  --strip-fs-head:    14px;

  /* ---- FACSIMILE: ketone meter — UNCHANGED ------------------------------ */
  --meter-case:       #2b2f36;
  --meter-bezel:      #1b1e23;
  --meter-screen:     #b9c9a6;
  --meter-screen-ink: #1d2417;
  --meter-brand:      #9aa3b0;
  --meter-sim:        #d8b4a0;
  --meter-w:          260px;
  --meter-fs-value:   44px;
  --meter-fs-units:   13px;
  --meter-fs-label:   12px;
  --meter-fs-small:   10px;
  --meter-tracking:       0.12em;
  --meter-tracking-brand: 0.14em;
  --meter-tracking-sim:   0.18em;

  /* ---- FACSIMILE: ECG paper (S5) — UNCHANGED ---------------------------- */
  --ecg-paper:        #fdf6f2;
  --ecg-grid-minor:   #f0c4bb;
  --ecg-grid-major:   #e09a8c;
  --ecg-trace:        #1a1512;
  --ecg-ink:          #3a2f2a;
  --ecg-warn:         #7a2f1d;
  --ecg-fs-meta:      3px;
  --ecg-fs-lead:      3.4px;
  --ecg-fs-head:      4px;
  --ecg-stroke-trace: 0.3;
  --ecg-stroke-minor: 0.08;
  --ecg-stroke-major: 0.2;

  /* ---- login portrait (new) --------------------------------------------- */
  /* An opaque band, not a translucent gradient: the credit sits over a
     photograph whose backing varies pixel to pixel, so the only way the
     contrast pair means anything is for the backing to be a known colour.
     The quotation is set clear of the image entirely. */
  --portrait-ink:    var(--c-duo-ink);
  --portrait-paper:  var(--c-duo-paper);
  --portrait-band:   var(--c-n950);
  --portrait-credit: var(--c-n300);
}

[data-variant="display"] {
  /* Read "display" as "the projector view" — the attribute keeps its
     original spelling deliberately. */
  color-scheme: dark;

  /* ---- FACSIMILE: dimmed for the room — UNCHANGED ----------------------- */
  --strip-paper:      #d8d4c8;
  --strip-ink:        #14130e;
  --strip-ink-muted:  #4a473d;
  --strip-ink-faint:  #6f6c60;
  --strip-rule:       #9c9787;
  --ecg-paper:        #e8ddd8;
  --ecg-grid-minor:   #d4a89f;
  --ecg-grid-major:   #c08476;
  --ecg-trace:        #120e0b;

  /* ---- surfaces --------------------------------------------------------- */
  /* Warm near-black, not the old cool near-black, so the ECG paper and the
     thermal strip sit on a ground of the same temperature as themselves. */
  --surface-page:    var(--c-n950);
  --surface-card:    var(--c-n900);
  --surface-sunken:  var(--c-n800);
  --surface-inverse: var(--c-n50);
  --surface-overlay: var(--c-n900);
  --surface-scrim:   var(--c-scrim-deep);
  --surface-hover:   var(--c-n800);
  --surface-active:  var(--c-n700);

  /* ---- ink -------------------------------------------------------------- */
  /* --ink is n100. Still never #fff, and the ramp being warm means the
     highest ink in the room is a warm off-white, which is easier again on a
     dark-adapted eye. */
  --ink:          var(--c-n100);
  --ink-strong:   var(--c-n50);
  --ink-muted:    var(--c-n400);
  --ink-faint:    var(--c-n500);
  --ink-inverse:  var(--c-n900);
  --ink-on-accent: var(--c-n950);

  /* ---- lines ------------------------------------------------------------ */
  --line:        var(--c-n700);
  --line-strong: var(--c-n600);
  --line-faint:  var(--c-n800);
  --line-control: var(--c-n400);

  /* ---- accent ----------------------------------------------------------- */
  --accent:        var(--c-m300);
  --accent-hover:  var(--c-m200);
  --accent-active: var(--c-m100);
  --accent-ink:    var(--c-m300);
  --accent-soft:   var(--c-m800);
  /* Same value as the app variant — the only token in the file where that is
     true. A boundary that must clear 3:1 against near-white AND near-black has
     exactly one band available to it; splitting it per variant would push one
     side back under the threshold. */
  --accent-line:   var(--c-m450);

  /* ---- status ----------------------------------------------------------- */
  --ok:        var(--c-m300);  --ok-ink:   var(--c-m300);  --ok-soft:   #0a2b1c;
  --bad:       var(--c-d300);  --bad-ink:  var(--c-d300);  --bad-soft:  #38160f;
  --warn:      var(--c-w300);  --warn-ink: var(--c-w300);  --warn-soft: #33260a;
  --info:      var(--c-i300);  --info-ink: var(--c-i300);  --info-soft: #16242c;

  /* ---- the primary verb: accent-filled here — ink on near-black would
     vanish, and the room's main action must be findable in the dark. */
  --btn-primary-fill:        var(--accent);
  --btn-primary-ink:         var(--ink-on-accent);
  --btn-primary-fill-hover:  var(--accent-hover);
  --btn-primary-fill-active: var(--accent-active);

  /* ---- focus ------------------------------------------------------------ */
  --focus-ring: var(--c-m200);

  /* ---- elevation -------------------------------------------------------- */
  --shadow-1: none;
  --shadow-2: none;
  --shadow-3: none;

  /* ---- type scale — UNCHANGED (~1.45x) ---------------------------------- */
  --fs-xs:  1rem;
  --fs-sm:  1.125rem;
  --fs-md:  1.375rem;
  --fs-lg:  1.625rem;
  --fs-xl:  2rem;
  --fs-2xl: 2.75rem;
  --fs-3xl: 3.5rem;
  --fs-4xl: 4.75rem;

  /* ---- component sizing ------------------------------------------------- */
  --poll-bar-h:  var(--sz-poll-bar-lg);
  --qr-size:     var(--sz-qr-display);
  --border-card: var(--bw-2);

  /* ---- login portrait --------------------------------------------------- */
  /* Defined for both variants, per the README's warning that a token defined
     for one lets the projector find the gap. The duotone stops do not invert:
     a portrait rendered as a photographic negative is not a treatment. */
  --portrait-ink:    var(--c-duo-ink);
  --portrait-paper:  var(--c-duo-paper);
  --portrait-band:   var(--c-n950);
  --portrait-credit: var(--c-n400);
}

/* ==========================================================================
   3. STRUCTURE — space, size, motion, type.
   ========================================================================== */

:root {
  /* ---- border widths — UNCHANGED, AND OUT OF SCOPE ---------------------
     Border WEIGHT is geometry, whatever the README's table says: on a
     border-box element 1px -> 2px moves the content inside it, which is the
     one thing this restyle may not do. The restyle changes border COLOUR and
     the decision of whether a border exists at all — never its weight. */
  --bw-1: 1px;
  --bw-2: 2px;
  --bw-3: 3px;
  --bw-4: 4px;
  --bw-6: 6px;

  /* ---- spacing — UNCHANGED (geometry) ----------------------------------- */
  --s-1:  4px;   --s-2:  8px;   --s-3:  12px;  --s-4:  16px;
  --s-5:  20px;  --s-6:  24px;  --s-7:  32px;  --s-8:  40px;
  --s-9:  48px;  --s-10: 64px;  --s-11: 96px;

  /* SLIDE MARGINS — geometry, restored at implementation: these three were
     absent from the delivered restyle tokens but are consumed by the slide
     surfaces (gallery second-pass ruling: bottom inset never smaller than
     the others; media padding never zero). Values unchanged from
     pre-restyle. */
  --slide-pad:         var(--s-8);
  --slide-pad-bottom:  var(--s-8);
  --slide-pad-media:   var(--s-5);

  /* ---- radius -----------------------------------------------------------
     Was 4 / 8 / 14 / 22 / pill — one ladder, applied fairly evenly, which is
     the uniform-corner tell. Now: chrome is square, controls get a 2px
     softening you read as a machined edge rather than as a rounded card, and
     the pill radius survives ONLY where a shape is genuinely a track or a
     dot (the switch, the connection dot, the spinner).

     The old names are kept and remapped so nothing dangles; the three shape
     tokens below are what components should migrate to. */
  --r-sm:   0;
  --r-md:   2px;
  --r-lg:   2px;
  --r-xl:   2px;
  --r-pill: 999px;    /* switch track, dot, spinner — NOT badges */
  --r-circle: 50%;

  --r-surface: 0;     /* cards, panels, modals, slide furniture */
  --r-control: 2px;   /* buttons, inputs, selects, segments, thumbs */
  --r-badge:   0;     /* a badge is a stamped label, not a pill */
  --r-track:   999px; /* switch track, dot, spinner */

  /* ---- variant-invariant functional colours — PINNED -------------------- */
  /* Literals rather than ramp references: the ramp is now warm and these
     must not move. Each was a real failure, not a preference. */
  --qr-quiet:   var(--c-fix-quiet);
  --qr-ink:     var(--c-fix-black);
  --blackout:   var(--c-fix-black);
  --transparent: var(--c-transparent);
  --rad-ground: var(--c-fix-black);
  --rad-ink:    var(--c-fix-white);

  /* ---- the slide canvas — UNCHANGED (geometry) --------------------------- */
  --slide-w: 1280px;
  --slide-h: 720px;

  /* ---- media and artefact ladders — UNCHANGED (geometry) ---------------- */
  --artefact-w-sm:   560px;  --artefact-w-md:   820px;
  --artefact-w-lg:  1060px;  --artefact-w-full: 100%;
  --artefact-h-sm:   260px;  --artefact-h-md:   380px;
  --artefact-h-lg:   500px;  --artefact-h-full: 100%;
  --media-h-sm:   200px;  --media-h-md:   320px;
  --media-h-lg:   440px;  --media-h-full: 520px;
  --rad-h-sm:   320px;  --rad-h-md:   420px;
  --rad-h-lg:   500px;  --rad-h-full: 100%;

  /* ---- fixed component sizes — UNCHANGED (geometry) --------------------- */
  --sz-radwheel:       72px;
  --sz-radwheel-ridge:  6px;
  --sz-radwheel-groove: 2px;
  --sz-poll-bar:    36px;
  --sz-poll-bar-lg: 56px;
  --sz-qr-app:      240px;
  --sz-qr-display:  min(52vh, 520px);
  --sz-thumb-w:     136px;
  --sz-switch-w:    52px;
  --sz-switch-h:    30px;
  --sz-dot:         10px;
  --sz-icon:        20px;
  --sz-spinner:     18px;

  /* ---- touch — UNCHANGED (accessibility floor) -------------------------- */
  --touch-min: 44px;
  --touch-lg:  64px;

  /* ---- type -------------------------------------------------------------
     Three faces, three jobs, and the pairing is the brand — there is no mark.

       --font-display  Libre Caslon Text. The Osler half: a period text face
                       of the era of bedside teaching, doing display and
                       editorial work. Replaces the Charter stack, which was
                       a sensible zero-KB default rather than a decision.
       --font-body     IBM Plex Sans. The modern half. Drawn for an
                       engineering house, not for a marketing site, and
                       chosen partly on metrics: its x-height (0.516 em) is
                       within ~1% of the system stack it replaces, so with
                       --fs-* and --lh-* held constant, line boxes keep their
                       heights. Advance widths differ by ~1.5%, so long
                       strings may re-wrap a word earlier — that, and only
                       that, is the drift this restyle spends.
       --font-mono     IBM Plex Mono, for UI machine output: join codes,
                       timers, tabular numbers.
       --font-machine  NEW, and the important one. The facsimile artefacts
                       (.o-strip, .o-ecg, .o-ketone) were reading --font-mono,
                       so restyling it would have re-lettered the analyser
                       printout and the ketone meter — exactly the failure
                       the brief calls out. Their type is part of the object,
                       so it gets its own token, pinned to the previous
                       stack, and it never follows the UI.

     All three webfonts are OFL/open. Ship as woff2 under theme/fonts/ with
     font-display: swap and the fallbacks below; nothing else needs to know. */
  --font-display: "Libre Caslon Text", "Charter", "Bitstream Charter",
                  "Iowan Old Style", Georgia, serif;
  --font-body: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
               system-ui, "Helvetica Neue", Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", "Cascadia Mono",
               Menlo, Consolas, monospace;
  --font-machine: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Mono",
                  "Roboto Mono", Menlo, Consolas, monospace;

  /* Plex Mono's zero is not slashed by default, and attendees type the join
     code by hand off a projected screen. The feature string is a token so
     components.css can apply it without typing a literal. */
  --mono-features: "zero" 1, "tnum" 1;

  /* ---- leading, tracking, weight ---------------------------------------- */
  /* Ratios unchanged, so no line box changes height. */
  --lh-tight: 1.15;
  --lh-snug:  1.35;
  --lh-body:  1.6;

  --tracking-tight: -0.011em;
  --tracking-normal: 0;
  --tracking-wide:  0.04em;
  --tracking-caps:  0.08em;
  /* The wordmark: OSLER set in the display serif at wide tracking, in ink.
     That is the whole lockup. No mark, no accent colour — accent means state,
     and a logo is not a state. */
  --tracking-wordmark: 0.16em;

  /* Machine print — UNCHANGED: these belong to the artefacts. */
  --tracking-print:   0.06em;
  --tracking-machine: 0.15em;
  --tracking-banner:  0.22em;

  /* 550 was a synthetic weight — Plex Sans ships 400/500/600/700, and asking
     for 550 gets a fake interpolation or a silent snap. 500 is the real one. */
  --weight-normal: 400;
  --weight-medium: 500;
  --weight-bold:   600;

  --measure: 66ch;
  --text-align-prose: left;

  /* ---- motion — UNCHANGED ----------------------------------------------- */
  --dur-fast: 120ms;
  --dur-base: 200ms;
  --dur-slow: 320ms;
  --ease:     cubic-bezier(0.2, 0, 0.2, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);

  /* ---- layering — UNCHANGED (behaviour) --------------------------------- */
  --z-bar: 10;  --z-pill: 20;  --z-poll: 30;
  --z-toast: 40;  --z-modal: 50;  --z-blackout: 60;

  /* ---- safe areas — UNCHANGED ------------------------------------------- */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
}

/* ==========================================================================
   ACCESSIBILITY SWITCHES — still token remaps, still unknown to components.
   ========================================================================== */

/* ---- dyslexia-friendly text mode ----------------------------------------
   One change of substance: the mode used to remap --font-display to a sans
   as well as --font-body, which under the old theme cost nothing and under
   this one would silently delete the entire serif thread for the readers who
   need the most help. The evidence is for spacing, leading, measure and
   ragged-right — not for replacing a well-set text serif — so the display
   face stays and the mode does the work that is actually supported.

   The sans still prefers Atkinson Hyperlegible / OpenDyslexic if the reader
   already has them installed. We ship neither. */
[data-text="dyslexia"] {
  --font-body: "Atkinson Hyperlegible", "OpenDyslexic", "Lexend",
               "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
               system-ui, "Helvetica Neue", Arial, sans-serif;

  --tracking-normal: 0.055em;
  --tracking-tight:  0.03em;
  --tracking-wide:   0.08em;

  --lh-body:  1.85;
  --lh-snug:  1.55;
  --lh-tight: 1.3;

  --measure: 52ch;
  --text-align-prose: left;

  --weight-normal: 450;
}

/* The warm ground, on the app variant only. It has to stay PERCEPTIBLY
   warmer and quieter than the base page, and the base page is now warm
   paper — so the old #faf7f0 would have been invisible against it. Stepped
   further, and the ink is lifted off full strength to cut glare, which is the
   other half of what the mode is for. Compound selector so it wins inside a
   variant-scoped subtree; regression test covers this. */
[data-text="dyslexia"][data-variant="app"],
[data-text="dyslexia"]:not([data-variant="display"]) {
  --surface-page:   #f6f1e4;
  --surface-card:   #fdfaf1;
  --surface-sunken: #ebe4d2;
  --ink:            #221f19;
  --line:           #d8d0ba;
}

/* ---- reduced motion — UNCHANGED ------------------------------------------ */
[data-motion="reduced"] {
  --dur-fast: 0.01ms;
  --dur-base: 0.01ms;
  --dur-slow: 0.01ms;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-fast: 0.01ms;
    --dur-base: 0.01ms;
    --dur-slow: 0.01ms;
  }
}

/* ---- forced colours (Windows high contrast) — UNCHANGED ------------------ */
@media (forced-colors: active) {
  :root {
    --shadow-1: none;
    --shadow-2: none;
    --shadow-3: none;
  }
}
