/* Built by build.mjs. Do not edit — edit src/styles/*.css.
   Colour authority: 02-design-system/stactray-tokens.css (v4.0, D13). */

/* ---------- stactray-tokens.css (READ, not copied) ---------- */
/* ============================================================================
   Stactray Private Limited — Stratum web tokens
   v4.0 · LUMINOUS · 12 August 2026 · Session 5c · D13 RATIFIED

   Supersedes v2.0 (12 Aug 2026, light) and v1.0 (14 Jul 2026, print).
   v1.0 remains correct for print and Office. This file is the authority for
   stactray.tech and any Stactray web, portal, dashboard or HTML surface.

   Governing documents
     Vision/stactray-brand/Stactray_Visual_System.md   the ratified system
     00-brief/DECISIONS-BLOCKED.md  D10, D11           the direction
     02-design-system/DESIGN.md     v2.0               what each token is for
     06-qa/CONTRAST-MATRIX.md                          every pairing, computed
     06-qa/contrast.py                                 the script that computes it
     skill: stactray-luminous-stratum                  the standard this implements

   WHAT CHANGED IN v4.0 — the accent decision, and it was Sunil's
   ---------------------------------------------------------------------------
   v3.0 made a six-step ember ramp the light. That was my derivation, never
   ratified, and it was wrong in a way the maths hid: I held the HUE at 352-356
   and told myself that made it still oxblood. Hue was never the problem. At
   the luminance a light source needs, hue 356 is PINK — and Stactray's brand
   is ink plus a deep, dark oxblood at 31% luminance.

   D13 resolves it into FOUR ROLES:

     THE LIGHT      ivory  #F5F1EA   ratified brand colour, hue 38, warm white.
                                     Tenebrism is light emerging from darkness,
                                     and the light in a Tenebrist painting is
                                     warm white, because that is what a flame is.
     THE MASS       oxblood #7A2530  ratified. Never light, never text on dark.
     THE STRUCTURE  moss  hue 92     NEW. Replaces the blue-grey steel faces.
     THE ACCENT     ember 500-300    KEPT but demoted: three steps, not six, and
                                     ONE moment per page. Pink against ivory is
                                     a sharp contrast — but only when pink is
                                     not also doing the lighting.

   WHY MOSS, AND WHY NOT VERDIGRIS
   ---------------------------------------------------------------------------
   The obvious structural colour was verdigris — patinated copper, institutional,
   complementary to oxblood, rare in tech. It lands at hue 164, NINE DEGREES from
   ParcelIQ's #14B8A6. Rule 4 forbids a product accent on a Stactray surface and
   nine degrees is not a distinction anyone sees. Rejected.

   Moss sits at hue 92: 76 degrees from the nearest venture hue, 100 from
   oxblood. It is NOT the green tech already uses — that is bright terminal
   green, Supabase #3ECF8E at hue 153 saturation 60. Moss is hue 92 at
   saturation 22: sixty degrees away and less than half as saturated. It reads
   as ledger green, a banker's lamp, felt on a board table, oxidised bronze.
   Institutional rather than technical, which is the register a quiet holding
   brand wants.

   THE RULE THAT GOVERNS THIS FILE
   ---------------------------------------------------------------------------
   The palette was computed and ratified on 12 August and ships in the
   reference implementation. **Values here are COPIED, never re-derived.**
   Nothing in this file is a judgement about how a colour looks. If a pairing
   is not in the matrix, it has not been cleared, and it does not ship.

   WHAT CHANGED FROM v2.0 — and three of them are defects, not preferences
   ---------------------------------------------------------------------------
   0. The ground inverts. D10: the site is dark, and the Stratum mark's
      isometric geometry becomes the spatial system of the whole surface.
      This is NOT a rebrand. The mark, the brand rules and the voice are
      untouched. The semantic-alias layer in §4 is what makes it a token
      operation rather than a rewrite — it was built for the ink footer.

   1. DEFECT — `--st-rim-interactive` was #5E6A85 and was published as
      clearing SC 1.4.11 on "every ground". Void, canvas, raised and panel
      were measured. **`float` was not, and on float it is 2.71:1.** A control
      edge is how a control is identified, so that is a genuine failure.
      Lifted to #64718E, holding hue 221.5deg and saturation exactly — hue
      drift 0.11deg. Clears 3.00 on float and improves everywhere else.

   2. DEFECT — `--st-text-faint` and `--st-ember-400` do not survive a
      scrimmed band. Over the 0.86 scrim with a worst-case field particle
      beneath, text-faint falls 5.08 -> 3.48 and ember-400 5.47 -> 3.74. Both
      are now **cleared on solid grounds only** and are forbidden as body text
      on any band that lets the field through.

   3. DEFECT — the hero had no scrim at all. Its ground is `transparent`, the
      field composites additively, and the spray envelope reaches x = -0.24 of
      viewport width — right across the text column. Against a full-value
      field pixel NO text token clears anything; light on light is not
      winnable by choosing a colour. Added `--st-scrim-text`, a left-anchored
      wash at 0.92 that restores a computable ground under every hero word.

   Items 1-3 were found by 06-qa/contrast.py, not by eye. The v2.0 derivations
   `--st-text-muted` and `--st-caution-text` remain outstanding at Visual
   System §8, now joined by the ember ramp and mark motion — four in total.
   Raised, not silently applied.
   ========================================================================= */

:root {

  /* ==========================================================================
     1 · CORE PALETTE — LIGHT · ratified, do not alter
     Vision/stactray-brand/Stactray_Visual_System.md §2
     Retained in full. Still the authority for print, Office and any light
     surface. The web tier no longer defaults to it; it is reachable through
     [data-ground="light"] below.
     ====================================================================== */
  --st-ink:            #1C1B1A;
  --st-ink-deep:       #0E0D0C;
  --st-ink-soft:       #383634;
  --st-oxblood:        #7A2530;  /* THE ratified accent                       */
  --st-oxblood-deep:   #521821;
  --st-oxblood-light:  #93343F;
  --st-ivory:          #F5F1EA;
  --st-paper:          #FFFFFF;
  --st-stone:          #8B8680;  /* NON-TEXT only on light                    */
  --st-hairline-warm:  #DDD8D0;

  --st-positive:       #2E5E4E;
  --st-caution:        #9C6F1E;  /* NON-TEXT only                             */
  --st-negative:       #7A2530;

  /* Light-tier derivations, v2.0. Each exists because a ratified colour could
     not carry a job at AA. Pending at Visual System §8. */
  --st-text-muted:     #6F6A64;  /* paper 5.36 · ivory 4.76                   */
  --st-caution-text:   #846017;  /* paper 5.73 · ivory 5.09                   */
  --st-paper-dim:      #FAF8F4;

  /* ==========================================================================
     2 · CORE PALETTE — LUMINOUS · v3.0, D10-ratified
     Every figure below is the ratio against the WORST ground the token can
     land on, which is `float`. A token cleared against the average ground and
     used on float is how a violation ships.
     ====================================================================== */

  /* --- Grounds. Never pure black. -----------------------------------------
     #000000 makes emission read as a screen defect, smears on OLED, and
     crushes the mark's own shadow faces. Luminance ladder, measured:
     void 0.0024 · canvas 0.0044 · raised 0.0082 · panel 0.0132 · float 0.0215 */
  --st-void:    #07080A;   /* hero and terminal states ONLY                   */
  --st-canvas:  #0C0E12;   /* the page ground                                 */
  --st-raised:  #131620;
  --st-panel:   #1A1E2A;
  --st-float:   #232837;   /* overlays, menus — the worst ground in the system */

  /* --- Text. Off-white body; pure white reserved. --------------------------
     Pure #FFFFFF at body size halates on near-black — it measures better and
     reads worse. It is kept for the H1 and the focus ring. */
  --st-white:           #FFFFFF;  /* float 14.68 — headings, focus ring       */
  --st-off-white:       #F2F4F8;  /* float 13.33 — body                       */
  --st-grey-200:        #C8CCD6;  /* float  9.13 — secondary                  */
  --st-grey-400:        #9AA0AE;  /* float  5.60 — subtle. AA body everywhere */
  --st-grey-500:        #7C8394;  /* float  3.86 — SOLID GROUNDS ONLY. See §0.2
                                     Large text or non-text on solid ground;
                                     never body text over a scrimmed band.    */

  /* --- 1 · THE LIGHT. Ivory. -----------------------------------------------
     A ratified brand colour doing the job it was always suited to. Every
     figure below is the ratio against `float`, the worst ground in the system. */
  --st-ivory:      #F5F1EA;  /* 13.04 — THE LIGHT. Clears AA as text too.   */
  --st-ivory-dim:  #C9C2B8;  /*  8.13 — a lit face falling away             */
  --st-ivory-deep: #8E8878;  /*  4.15 — the away face, still lit            */

  /* --- 2 · THE MASS. Oxblood, unchanged and unlifted. ----------------------
     1.49 on the worst ground. It is SUPPOSED to fail as light — that is the
     whole point of the role. The colour of the asset before it is lit. */
  --st-oxblood-deep-mass: #521821;  /* 1.05 — deepest mass                  */
  /* The dark-ground oxblood triad — NOT derived. Read straight off
     Brand Assets/stactray/stactray-logo-reversed.svg, the ratified reversed
     lockup, which has carried these three faces since v1.0.              */
  --st-oxblood-light:  #93343F;   /* 1.96 — mass, mid face                  */
  --st-oxblood-lift: #B04A56;   /* 2.76 — mass, light face                */

  /* --- 3 · THE STRUCTURE. Moss, hue 92. NEW in v4.0. -----------------------
     Replaces steel-1/2/3. Same job: the three isometric faces under one light
     source. Adjacent faces separate at 1.35 and 1.51 — perceivable. */
  --st-moss-1:    #262E1F;  /* 1.04 — the away face, darkest               */
  --st-moss-2:    #37442C;  /* 1.41 — the top face                         */
  --st-moss-3:    #4E603E;  /* 2.14 — the face turned toward the light     */
  --st-moss-edge: #6B8055;  /* 3.38 — THE EDGE. Clears SC 1.4.11 on every
                               ground, which is what makes a dark object
                               perceivable at all.                          */

  /* --- 4 · THE ACCENT. Ember, demoted to three steps. ----------------------
     ONE moment per page. Not the light, not the structure — the contrasting
     note against ivory. ember-700 is not listed because it IS --st-oxblood;
     two names for one hex is how a palette starts to drift. */
  --st-ember-500: #C24A54;  /* 3.07 — glow source, large text, non-text UI  */
  --st-ember-400: #D9636B;  /* 4.15 — glow, large text                     */
  --st-ember-300: #E8838A;  /* 5.62 — ACCENT TEXT, every ground            */

  /* --- Warm neutrals. The field's cool half. -------------------------------
     Stratum's neutrals are WARM. Blue-shifted greys were tried and were
     nobody's colours. There is NO TEAL on a Stactray surface — teal belongs
     to ParcelIQ, Dry Powder and Pramaana (CLAUDE.md rule 4). */
  --st-warm-ivory:    #F5F1EA;   /* float 13.04                               */
  --st-warm-hairline: #DDD8D0;   /* float 10.35                               */
  --st-warm-stone:    #8B8680;   /* float  4.06 — non-text on dark            */

  /* --- Structure. Two rims, and they are NOT interchangeable. --------------
     A decorative rim-light and a control boundary are different jobs with
     different floors. They are named apart precisely so they cannot be
     swapped by accident. */
  --st-rim:             #37442C;  /* moss-2. DECORATIVE rim-light only.      */
  --st-rim-interactive: #6B8055;  /* moss-edge. 3.38 on float · 4.62 void ·
                                     4.45 canvas · 4.16 raised · 3.83 panel.
                                     CONTROL boundaries. Clears SC 1.4.11 on
                                     every ground.                            */
  --st-hairline:        rgba(242,244,248,.10);

  /* v3.0's blue-grey steel is WITHDRAWN — replaced by the moss ramp above.
     Retained here as aliases only so nothing breaks mid-migration; they will
     be deleted once every surface has moved. */
  --st-steel-1: var(--st-moss-1);
  --st-steel-2: var(--st-moss-2);
  --st-steel-3: var(--st-moss-3);

  /* --- Functional, on dark ------------------------------------------------- */
  --st-positive-on-dark: #6FB79C;  /* float 6.24 — AA body                    */
  --st-caution-on-dark:  #C79A3E;  /* float 5.67 — AA body                    */
  --st-negative-on-dark: #E8838A;  /* float 5.62 — shares the ember family     */

  /* ==========================================================================
     3 · SCRIMS — the tokens that make the field safe
     The dispersion field is ONE fixed canvas behind the whole document. Bands
     dim it by their own opacity. The contrast matrix is computed against
     SOLID grounds, so a scrim must stay opaque enough that the effective
     ground is unchanged. Lightening one is a RECOMPUTE, not a taste decision.
     ====================================================================== */
  --st-scrim-band: rgba(12,14,18,.86);  /* the floor. Below this, the matrix
                                           is invalid. Clears text,
                                           text-secondary, text-subtle and
                                           ember-300; NOT text-faint or
                                           ember-400.                         */
  --st-scrim-text: rgba(12,14,18,.92);  /* behind hero/void text columns.
                                           Effective ground #1F2125 — text
                                           14.64, secondary 10.02, subtle
                                           6.15. Without it the hero ground is
                                           whatever the field painted and
                                           nothing is computable.             */

  /* ==========================================================================
     4 · SEMANTIC ALIASES — what components reference
     Components NEVER reference a core token directly. They reference these.
     A surface is changed by re-pointing an alias, not by editing a component.
     THIS LAYER IS WHY THE D10 INVERSION COST A DAY AND NOT A REWRITE.
     Default context is now DARK — canvas.
     ====================================================================== */
  --st-surface:              var(--st-canvas);
  --st-surface-alt:          var(--st-raised);
  --st-surface-sunk:         var(--st-void);
  --st-surface-panel:        var(--st-panel);
  --st-surface-float:        var(--st-float);
  --st-surface-inverse:      var(--st-paper);      /* print / light context   */
  --st-surface-inverse-deep: var(--st-ivory);

  --st-text:                 var(--st-off-white);
  --st-text-strong:          var(--st-white);
  --st-text-secondary:       var(--st-grey-200);
  --st-text-subtle:          var(--st-grey-400);
  --st-text-faint:           var(--st-grey-500);   /* solid grounds only      */
  --st-text-inverse:         var(--st-ink);
  --st-text-inverse-subtle:  var(--st-text-muted);

  /* THE LIGHT — what emits, and what a scene's subject is made of */
  --st-light:                var(--st-ivory);
  --st-light-dim:            var(--st-ivory-dim);
  --st-light-deep:           var(--st-ivory-deep);

  /* THE ACCENT — one moment per page, and it is never the light */
  --st-accent:               var(--st-ember-300);  /* accent TEXT             */
  --st-accent-strong:        var(--st-ember-300);
  --st-accent-soft:          var(--st-ember-400);  /* large text              */
  --st-accent-glow:          var(--st-ember-500);  /* the accent's own glow   */
  --st-accent-mass:          var(--st-oxblood);    /* UNLIT structure         */

  --st-border:               var(--st-hairline);         /* decorative       */
  --st-border-structural:    var(--st-rim-interactive);  /* real boundaries  */
  --st-border-rim:           var(--st-rim);              /* rim-light only   */

  --st-focus:                var(--st-white);      /* 14.68 on the worst ground */
  --st-focus-inverse:        var(--st-ink);

  /* ==========================================================================
     5 · ELEVATION — by EDGE, not by fill
     Withdrawn from v1.0: "there is no elevation scale."
     Computed reason: canvas -> raised is 1.07:1, raised -> panel 1.08:1,
     panel -> float 1.13:1. All far below perceivable. On light grounds a card
     separates because the SHADOW does the work; on dark there is no shadow —
     a dark shadow on a dark ground is nothing.

     So a panel separates by a 1px rim-light on its top edge, implying one
     source above and outside the frame, plus an occlusion wash beneath. The
     fill step is retained as a SUPPORTING signal, never the only one.

     ONE implied light source across the whole system. Two panels lit from
     different directions on one screen is the single most common tell that a
     dark UI was assembled rather than designed.
     ====================================================================== */
  --st-edge-rim:        inset 0 1px 0 var(--st-rim);
  --st-edge-rim-strong: inset 0 1px 0 var(--st-rim-interactive);
  --st-occlusion:       0 18px 32px -24px rgba(0,0,0,.85);
  --st-elevation-panel: var(--st-edge-rim), var(--st-occlusion);
  --st-elevation-float: var(--st-edge-rim-strong), 0 24px 48px -28px rgba(0,0,0,.92);

  /* ==========================================================================
     6 · GLOW — budgeted, and never load-bearing
     Glow is the one thing a dark system has that a light system cannot do,
     which is exactly why it gets overspent. Only conceptually emissive things
     may glow. ONE primary light source per view. Glow is a falloff, not an
     outline. Anything a glow says must ALSO be said in text — it fails in
     forced-colors, in print, at low brightness and under reduced transparency.
     NEVER a glow as a focus indicator.
     ====================================================================== */
  /* v4/D13: the glow is IVORY. Firelight spilling into a warm mid-tone, then
     falling off through ivory-deep so the spill DARKENS into the palette
     rather than washing out of it. Ember never appears in a light source. */
  --st-glow-source:  radial-gradient(closest-side,
                       rgba(245,241,234,.55), rgba(201,192,174,.24), transparent);
  --st-glow-budget-section: 1;   /* enforced at the QA gate, like the accent  */
  --st-glow-budget-page:    2;

  /* ==========================================================================
     7 · TYPOGRAPHY
     Archivo 600 display · Source Serif 4 long-form · IBM Plex Mono figures.
     Self-hosted woff2, subset latin. No CDN — a third-party font request is a
     third-party dependency on a page whose whole argument is institutional
     self-sufficiency, and it costs render-blocking time on a 4G connection.

     D10-d — the serif is no longer the site-wide body face. Register follows
     the reader's job:
       serif  Thesis · Company · Governance      (institutional work)
       sans   Home · Portfolio · How we build · Contact · Legal
     A high-contrast serif at body size on dark loses its thins, which is the
     technical half of the reason; the register split is the other half.
     ====================================================================== */
  --st-font-display: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --st-font-body:    "Source Serif 4", Georgia, "Times New Roman", serif;
  --st-font-sans:    "Archivo", "Helvetica Neue", Arial, sans-serif;
  --st-font-mono:    "IBM Plex Mono", Consolas, monospace;

  --st-track-wordmark: 0.32em;
  --st-track-eyebrow:  0.22em;
  --st-track-display: -0.022em;  /* deepened from v2.0: Archivo 600 at 84px
                                    needs more pull than it did at 56px      */

  /* Scale. Fluid between 360px and 1200px. The hero step RISES in v3.0 —
     v2.0 capped at 56px, which reads modest in a void. Every other step is
     unchanged from v2.0. */
  --st-fs-hero:    clamp(36px, 2.4rem + 4.2vw, 84px);
  --st-fs-display: clamp(28px, 1.9rem + 1.6vw, 44px);
  --st-fs-h1:      clamp(24px, 1.5rem + 1.1vw, 32px);
  --st-fs-h2:      clamp(20px, 1.25rem + 0.6vw, 24px);
  --st-fs-h3:      clamp(17px, 1.05rem + 0.3vw, 19px);
  --st-fs-body:    17px;
  --st-fs-small:   15px;
  --st-fs-caption: 13px;   /* NOT large text. WCAG large starts at 18.66px —
                              the v1.0 error that produced --st-text-muted.  */

  --st-lh-hero:    1.02;   /* tightened from 1.08: 84px needs less leading   */
  --st-lh-display: 1.14;
  --st-lh-heading: 1.25;
  --st-lh-body:    1.65;
  --st-lh-tight:   1.4;

  --st-fw-display: 600;
  --st-fw-body:    400;
  --st-fw-body-strong: 600;

  --st-measure:       68ch;
  --st-measure-tight: 46ch;

  /* ==========================================================================
     8 · SPACING — 8px base
     ====================================================================== */
  --st-space-0: 4px;
  --st-space-1: 8px;
  --st-space-2: 16px;
  --st-space-3: 24px;
  --st-space-4: 40px;
  --st-space-5: 64px;
  --st-space-6: 96px;
  --st-space-7: 128px;

  --st-section-y: clamp(56px, 8vw, 128px);
  --st-gutter:    clamp(20px, 5vw, 48px);

  /* ==========================================================================
     9 · LAYOUT
     ====================================================================== */
  --st-container:      1240px;
  --st-container-text:  760px;
  --st-container-wide: 1320px;

  --st-bp-sm:  480px;
  --st-bp-md:  768px;
  --st-bp-lg: 1024px;
  --st-bp-xl: 1280px;

  /* ==========================================================================
     10 · SHAPE AND RULES
     Withdrawn from v1.0: the 2px ink top rule on cards. It existed only
     because ivory-on-paper was 1.13:1 — a constraint that does not exist on
     dark, where the rim-light does that work instead.
     ====================================================================== */
  --st-radius:      2px;
  --st-radius-none: 0;
  --st-rule:        1px solid var(--st-border);
  --st-rule-strong: 1px solid var(--st-border-structural);
  --st-rule-accent: 2px solid var(--st-accent-glow);

  --st-focus-width:  2px;
  --st-focus-offset: 3px;

  /* ==========================================================================
     11 · MOTION
     Character: institutional. Things arrive, they do not bounce. No easing
     with overshoot anywhere in this system.

     THE WAVE. Three layers, one 3s sinusoid, each layer one second further
     along than the one above, so the crest travels bottom -> middle -> top —
     the mark's own sanctioned build order. Three layers x 1s closes exactly
     one period, so the loop is mathematically seamless.

     Two things about it that are counter-intuitive and were both learned the
     expensive way:
       - The sine is SAMPLED at eighths and driven LINEAR. `ease-in-out`
         decelerates into both extremes, so every layer visibly HOLDS at the
         top and bottom of its travel and the wave stutters instead of rolling.
       - Delays are NEGATIVE. A positive delay leaves the upper layers still
         until their turn arrives, so the first cycle is not a wave at all.
     ====================================================================== */
  --st-dur-instant: 80ms;
  --st-dur-fast:   140ms;
  --st-dur-base:   220ms;
  --st-dur-slow:   360ms;
  --st-dur-scene:  900ms;
  --st-dur-wave:     3s;
  --st-wave-step:    1s;
  --st-ease:      cubic-bezier(0.2, 0, 0.2, 1);
  --st-ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
  --st-stagger:   120ms;

  /* ==========================================================================
     12 · STRATUM MOTIF
     ONE projection angle across every scene in the system. Two angles is the
     defect a viewer feels without being able to name it.
     ====================================================================== */
  --st-iso-angle:   26.57deg;   /* atan(0.5) — 2:1 isometric, the mark's own  */
  --st-iso-light:   top-left;   /* ONE light direction, every scene, always   */
  --st-band-height: 6px;        /* strata band: 3:2:3 of this                 */
  --st-band-outer:  var(--st-moss-3);
  --st-band-centre: var(--st-oxblood);    /* THE MASS — its permitted use     */
  --st-band-inner:  var(--st-moss-1);

  --st-z-field:  0;
  --st-z-content: 1;
  --st-z-header: 100;
  --st-z-skip:   200;
}

/* ============================================================================
   GROUND CONTEXTS
   Applied to any section that is not the default canvas. Re-points the
   semantic aliases so a component written once renders correctly on every
   ground without a variant. This is why components reference aliases and
   never core tokens.
   ========================================================================= */

[data-ground="void"] {
  --st-surface:      var(--st-void);
  --st-surface-alt:  var(--st-canvas);
  --st-surface-sunk: var(--st-void);
}

[data-ground="raised"] {
  --st-surface:      var(--st-raised);
  --st-surface-alt:  var(--st-panel);
  --st-surface-sunk: var(--st-canvas);
}

[data-ground="panel"] {
  --st-surface:      var(--st-panel);
  --st-surface-alt:  var(--st-float);
  --st-surface-sunk: var(--st-raised);
}

/* The worst ground in the system. Every token used here is cleared against it
   in the matrix — that is the whole reason `float` is measured separately. */
[data-ground="float"] {
  --st-surface:      var(--st-float);
  --st-surface-alt:  var(--st-float);
  --st-surface-sunk: var(--st-panel);
}

/* ----------------------------------------------------------------------------
   LIGHT CONTEXT — print, Office, and any light-tier surface.
   The v1.0/v2.0 system, reachable rather than deleted. Oxblood on ink is
   1.75:1 and stays forbidden; this context is for LIGHT grounds only.
   -------------------------------------------------------------------------- */
[data-ground="light"] {
  --st-surface:           var(--st-paper);
  --st-surface-alt:       var(--st-ivory);
  --st-surface-sunk:      var(--st-paper-dim);
  /* These two were missed on the first cut and the verifier caught it. Without
     them a panel inside a light context kept its DARK fill while the text went
     to ink — 1.03:1, invisible. A ground context has to re-point EVERY surface
     alias it owns or it is not a context, it is a partial one. */
  --st-surface-panel:     var(--st-ivory);
  --st-surface-float:     var(--st-paper);
  --st-text:              var(--st-ink);
  --st-text-strong:       var(--st-ink-deep);
  --st-text-secondary:    var(--st-ink-soft);
  --st-text-subtle:       var(--st-text-muted);
  --st-text-faint:        var(--st-text-muted);
  --st-accent:            var(--st-oxblood);
  --st-accent-strong:     var(--st-oxblood-deep);
  --st-accent-soft:       var(--st-oxblood-light);
  --st-accent-mass:       var(--st-oxblood);
  --st-border:            var(--st-hairline-warm);
  --st-border-structural: var(--st-ink);
  --st-focus:             var(--st-ink);
}

/* ============================================================================
   REDUCED MOTION — non-negotiable, and a token-level concern
   Motion is ADDED, never removed: the static surface is the default state in
   the markup. A build that works by removing a "no-motion" class ships broken
   for the people who need it most.
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  :root {
    --st-dur-instant: 0ms;
    --st-dur-fast:    0ms;
    --st-dur-base:    0ms;
    --st-dur-slow:    0ms;
    --st-dur-scene:   0ms;
    --st-stagger:     0ms;
  }
}

/* ============================================================================
   FORCED COLORS — Windows high-contrast
   A dark, rule-light, glow-dependent design degrades WORSE here than any
   other kind, because everything carrying structure is decorative. Declare
   intent: boundaries become CanvasText, decoration is dropped, and nothing
   that carried meaning through glow alone is left mute.
   ========================================================================= */
@media (forced-colors: active) {
  :root {
    --st-border:            CanvasText;
    --st-border-structural: CanvasText;
    --st-border-rim:        CanvasText;
    --st-rule:              1px solid CanvasText;
    --st-rule-strong:       1px solid CanvasText;
    --st-elevation-panel:   none;
    --st-elevation-float:   none;
    --st-glow-source:       none;
  }
}

/* ============================================================================
   PRINT — the ground inverts back
   A dark page printed is a page of toner. Every dark surface returns to light
   and the emissive layer is dropped entirely.
   ========================================================================= */
@media print {
  :root {
    --st-surface:           var(--st-paper);
    --st-surface-alt:       var(--st-paper);
    --st-surface-sunk:      var(--st-paper);
    --st-text:              var(--st-ink);
    --st-text-strong:       var(--st-ink-deep);
    --st-text-secondary:    var(--st-ink-soft);
    --st-text-subtle:       var(--st-text-muted);
    --st-text-faint:        var(--st-text-muted);
    --st-accent:            var(--st-oxblood);
    --st-accent-mass:       var(--st-oxblood);
    --st-border:            var(--st-hairline-warm);
    --st-border-structural: var(--st-ink);
    --st-elevation-panel:   none;
    --st-elevation-float:   none;
    --st-glow-source:       none;
    --st-scrim-band:        transparent;
    --st-scrim-text:        transparent;
  }
}


/* ---------- tokens-web.css ---------- */
/* ============================================================================
   TOKENS — web surface alias layer
   Session 6 · 12 August 2026

   THIS FILE DEFINES NO COLOUR. It only gives the ratified --st-* tokens the
   short names the page CSS already uses. Every hex in the built stylesheet
   comes from ONE file:

       02-design-system/stactray-tokens.css   (v4.0 · D13 RATIFIED)

   which build.mjs reads and prepends. It is never copied — a second copy is a
   second source, and S5d spent a session migrating four of those.

   build.mjs HARD-FAILS if a hex literal appears in this file, in base.css, or
   in any page stylesheet. If a colour is needed and no --st-* token carries it,
   the answer is to ratify a token, not to type a hex here.

   Palette roles (D13). Do not lift the accent to make something glow.
     THE LIGHT      ivory     the light itself, warm white, hue 38
     THE MASS       oxblood   unlifted, 1.49, and it is SUPPOSED to fail as light
     THE STRUCTURE  moss      hue 92, replaces the v3 blue-greys
     THE ACCENT     ember     three steps, one moment per surface, never the light

   NOT PRESENT AND NOT TO BE ADDED — the three functional on-dark colours
   (--st-positive-on-dark, --st-caution-on-dark, --st-negative-on-dark) are
   live in tokens.css but were never on the D14 ratification sheet. They are
   open for v1.2. Note that --st-negative-on-dark shares a value with
   --st-ember-300; alias the ember token, never the functional one.
   ========================================================================= */

:root{
  /* Grounds — never pure black (dark-mode-design-system §1) */
  --void:var(--st-void); --canvas:var(--st-canvas); --raised:var(--st-raised);
  --panel:var(--st-panel); --float:var(--st-float);

  /* Text — off-white body, pure white reserved for the H1 (halation, §4) */
  --text:var(--st-off-white); --text-strong:var(--st-white);
  --text-secondary:var(--st-grey-200); --text-subtle:var(--st-grey-400);
  --text-faint:var(--st-grey-500);

  /* 1 · THE LIGHT — hue 38. Clears AA as text on every ground. */
  --ivory:var(--st-ivory);           /* 13.04 worst ground — the light itself */
  --ivory-dim:var(--st-ivory-dim);   /*  8.13 — falloff, lit-face shadow side */
  --ivory-deep:var(--st-ivory-deep); /*  4.15 — deepest spill, still AA large */

  /* 2 · THE MASS — ratified oxblood, unlifted. 1.49. Fails as light BY DESIGN. */
  --oxblood:var(--st-oxblood);
  --oxblood-deep:var(--st-oxblood-deep);   /* 1.05 — mass in shadow */
  /* The dark-ground triad, read off the ratified reversed lockup — not derived */
  --oxblood-light:var(--st-oxblood-light); /* 1.96 */
  --oxblood-lift:var(--st-oxblood-lift);   /* 2.76 — peaks below G5b's 4.50 */

  /* 3 · THE STRUCTURE — moss, hue 92 */
  --moss-1:var(--st-moss-1);       /* away face   1.04 */
  --moss-2:var(--st-moss-2);       /* top face    1.41 */
  --moss-3:var(--st-moss-3);       /* light face  2.14 */
  --moss-edge:var(--st-moss-edge); /* 3.38 — clears SC 1.4.11 on every ground */

  /* 4 · THE ACCENT — three steps. One moment per surface. Never the light. */
  --ember-500:var(--st-ember-500); /* 3.08 worst ground — large text, marks */
  --ember-400:var(--st-ember-400); /* 4.16 */
  --ember-300:var(--st-ember-300); /* 5.62 — accent TEXT, all grounds */

  /* Structure edges */
  --rim:var(--st-rim);                         /* decorative only — NOT a control edge */
  --rim-interactive:var(--st-rim-interactive); /* control boundaries — 3.38 worst ground */
  /* The one composited value. Derived from --st-off-white at 10% rather than
     restated as rgba(), so it still has exactly one source. */
  --hairline:color-mix(in srgb, var(--st-off-white) 10%, transparent);

  /* THE RIM-LIGHT SHADOW — restored, Session 6.
     HOME.html and PORTFOLIO.html both used var(--edge-rim) in four box-shadow
     declarations and neither of them ever defined it. An undefined custom
     property makes the WHOLE declaration invalid at computed-value time, so
     those cards were shipping with no rim AND no drop shadow — the shadow was
     silently discarded along with the rim. The value below is the one from
     CARD-COMPARISON.html, where the token was defined and the cards were
     ratified. Found by the build's unresolved-token guard, not by eye. */
  --edge-rim:inset 0 1px 0 var(--rim);

  /* Migration aliases — steel is gone; anything still asking for it gets moss */
  --steel-1:var(--moss-1); --steel-2:var(--moss-2); --steel-3:var(--moss-3);

  /* Type. Families live in fonts.css; only the stacks are tokens. */
  --font-display:"Archivo","Helvetica Neue",Arial,sans-serif;
  --font-body:"Source Serif 4",Georgia,serif;
  --font-mono:"IBM Plex Mono",Consolas,monospace;
  --track-eyebrow:.22em; --track-wordmark:.32em;

  --fs-hero:clamp(36px,2.4rem + 4.2vw,84px);
  --fs-h2:clamp(20px,1.25rem + .6vw,24px);
  --fs-h3:clamp(17px,1.05rem + .3vw,19px);
  --fs-body:17px; --fs-small:15px; --fs-caption:13px;

  --sp-1:8px; --sp-2:16px; --sp-3:24px; --sp-4:40px; --sp-5:64px; --sp-6:96px;
  --gutter:clamp(20px,5vw,48px); --container:1240px;
  --radius:2px;

  --dur-fast:140ms; --dur-base:220ms; --dur-slow:360ms; --dur-scene:900ms;
  /* THE WAVE. 3s cycle, 1s per layer, three layers — so the stagger closes
     exactly one full period and the wave is perfectly seamless: the instant
     the top layer starts its rise, the bottom layer is starting again. */
  --dur-wave:3s; --wave-step:1s;
  --ease:cubic-bezier(.2,0,.2,1); --ease-out:cubic-bezier(.16,1,.3,1);
  --stagger:120ms;

  /* ── THE MOTION SPINE — added 14 Aug 2026 (D15) ────────────────────────
     Three durations for the three gestures the site actually performs. They
     exist because an audit found the same gesture running at five different
     speeds: `settle`, `hsettle`, `tsettle`, `vcSettle` and `rise` were five
     names for one keyframe — opacity 0→1 with a small translateY — at 560ms,
     600ms, 620ms, 700ms and 900ms on four different pages. `draw` and `tdraw`
     were one gesture at 1400ms and 1300ms; `ride` and `tride` at 9s and 8s.

     That is what "the pages don't flow into each other" was: not a missing
     effect, but the same effect timed differently on every surface, so the
     site read as four sites sharing a stylesheet. One duration per gesture,
     referenced everywhere, is the whole fix. */
  --dur-reveal:620ms;   /* an element arrives */
  --dur-draw:1400ms;    /* a stroke draws itself */
  --dur-ride:9s;        /* a marker travels a path, looping */
}

@media (prefers-reduced-motion:reduce){
  /* Zeroing the durations here is what makes the contract safe: a component
     that reads its timing from a token cannot forget to honour the query. */
  :root{--dur-fast:0ms;--dur-base:0ms;--dur-slow:0ms;--dur-scene:0ms;
        --stagger:0ms;--dur-reveal:0ms;--dur-draw:0ms}
}
@media (forced-colors:active){ :root{--hairline:CanvasText;--rim:CanvasText} }


/* ---------- fonts.css ---------- */
/* ============================================================================
   FONTS — self-hosted and self-subset. CLAUDE.md rule 6 CLOSED, 14 Aug 2026.
   Session 6 · 12 August 2026 · closed and re-cut Session 15 · 14 August 2026

   Four faces, cut from the upstream originals rather than taken from Google's
   web subsets. Total 133 KB; the two preloaded in head.html are 72 KB of it.

   ── WHY UPSTREAM AND NOT THE CDN SUBSETS ────────────────────────────────
   CLAUDE.md rule 14 requires currency in ₹ Lakh/Crore, and **U+20B9 is in no
   subset Google publishes for these three families** — not latin, not
   latin-ext. The CDN could not render a rupee sign either, so nothing had
   regressed; the gap was simply invisible because no page carries a currency
   figure yet. The first one would have fallen back to a system font mid-word.

   Cutting from upstream closes it. **Every face below contains U+20B9.**

   ── THE unicode-range DESCRIPTORS ARE GONE, DELIBERATELY ────────────────
   The first self-hosting pass copied Google's `latin` unicode-range onto each
   face. That range does NOT include U+20B9 — so the browser would have
   refused to use these faces for the one glyph they were re-cut to provide,
   and the fallback would have happened anyway. One file per style now covers
   the whole supported set, and the descriptor is removed rather than widened.

   ── WHAT IS IN THE SUBSET, AND WHY IT IS WIDER THAN THE SITE NEEDS ──────
   Basic Latin, a working set of typographic punctuation, four arrows, € and
   ₹, ™ − ∕ ≤ ≥, and about twenty accented letters for names. The site today
   uses exactly three characters above U+02BB. Subsetting to those three would
   make the next copy edit a silent tofu bug, so the set is bounded but has
   headroom. Re-cut with `fontTools` if a genuinely new script is ever needed.

   ── AXES, PINNED ────────────────────────────────────────────────────────
   Archivo stays VARIABLE across wght 400–600 — one file serves the three
   weights the design uses — with wdth pinned to 100. Source Serif roman and
   italic are pinned to wght 400 and opsz 12 and are therefore static: leaving
   opsz variable cost more than the optical sizing was worth at 12–19px, and
   made the "optimised" subset larger than the CDN version it replaced.

   ── AND THE POINT OF ALL THIS ───────────────────────────────────────────
   Self-hosting is not only a rule. It removes a third-party request from every
   page load and stops a font CDN observing every visitor to a site whose whole
   proposition is that it can be checked.
   ========================================================================= */

/* Archivo — display. ONE variable file covers 400, 500 and 600. */
@font-face{
  font-family:"Archivo";
  font-style:normal;
  font-weight:400 600;
  font-display:swap;
  src:url("/fonts/archivo-var.woff2") format("woff2");
}

/* Source Serif 4 — body. The annual-report register, and deliberate. */
@font-face{
  font-family:"Source Serif 4";
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url("/fonts/source-serif-4-var.woff2") format("woff2");
}

/* The true italic. Without this face the browser slants the roman, which is
   what shipped from Session 6 to Session 15. Used by the Thesis figures. */
@font-face{
  font-family:"Source Serif 4";
  font-style:italic;
  font-weight:400;
  font-display:swap;
  src:url("/fonts/source-serif-4-var-italic.woff2") format("woff2");
}

/* IBM Plex Mono — the register voice: CIN, coordinates, axis numerals. */
@font-face{
  font-family:"IBM Plex Mono";
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url("/fonts/ibm-plex-mono-400.woff2") format("woff2");
}


/* ---------- base.css ---------- */

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{margin:0;background:var(--void);color:var(--text);
  font-family:var(--font-display);font-size:var(--fs-body);line-height:1.6;
  -webkit-font-smoothing:antialiased}

/* Focus is a solid ring, never a glow (dark-mode-design-system §6) */
:where(a,button,summary,[tabindex]):focus-visible{
  outline:2px solid var(--text-strong);outline-offset:3px;border-radius:var(--radius)}
:where(a,button):focus:not(:focus-visible){outline:none}

.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
.skip{position:absolute;left:var(--sp-2);top:-4rem;z-index:200;background:var(--text-strong);
  color:var(--void);padding:var(--sp-1) var(--sp-2);font-size:var(--fs-small);
  font-weight:600;text-decoration:none;transition:top var(--dur-fast) var(--ease)}
.skip:focus-visible{top:var(--sp-2)}
.wrap{max-width:var(--container);margin:0 auto;padding:0 var(--gutter)}
.eyebrow{font-size:var(--fs-caption);font-weight:600;text-transform:uppercase;
  letter-spacing:var(--track-eyebrow);color:var(--text-faint);margin:0}

/* ---------- HEADER ---------- */
.hdr{position:sticky;top:0;z-index:100;background:rgba(7,8,10,.72);
  backdrop-filter:blur(12px);border-bottom:1px solid var(--hairline)}
.hdr__in{display:flex;align-items:center;justify-content:space-between;
  gap:var(--sp-4);padding:14px 0}
.brand{display:flex;align-items:center;gap:10px;text-decoration:none;color:var(--text)}
.brand svg{display:block;flex:none}
.brand span{font-size:var(--fs-caption);font-weight:600;text-transform:uppercase;
  letter-spacing:var(--track-wordmark)}
.nav ul{display:flex;gap:var(--sp-4);list-style:none;margin:0;padding:0}
.nav a{font-size:var(--fs-small);color:var(--text-secondary);text-decoration:none;
  padding:10px 0;display:inline-block;border-bottom:2px solid transparent;
  transition:color var(--dur-fast) var(--ease)}
.nav a:hover{color:var(--text-strong)}
.nav a[aria-current="page"]{color:var(--text-strong);border-bottom-color:var(--ember-500)}
/* Mobile nav — the ratified <details> disclosure (DESIGN.md header component).
   Native element: keyboard-accessible and announced with zero JavaScript,
   which also keeps B5 (JS off) true on phones. Hidden on desktop; below
   860px it replaces .nav rather than leaving a void — the S14b finding was
   that .nav{display:none} shipped with no replacement at all. */
.nav-m{display:none;position:relative}
.nav-m summary{font-size:var(--fs-small);color:var(--text-secondary);cursor:pointer;
  padding:10px 0;min-height:24px;min-width:24px}
.nav-m summary:hover,.nav-m[open] summary{color:var(--text-strong)}
.nav-m nav{position:absolute;right:0;top:calc(100% + 8px);min-width:220px;z-index:110;
  background:var(--raised);border:1px solid var(--hairline);border-radius:4px;
  box-shadow:var(--edge-rim),0 20px 40px -28px rgba(0,0,0,.9)}
.nav-m nav ul{list-style:none;margin:0;padding:6px 0;display:block}
.nav-m nav a{display:block;font-size:var(--fs-small);color:var(--text-secondary);
  text-decoration:none;padding:12px 18px;min-height:24px;
  border-left:2px solid transparent}
.nav-m nav a:hover{color:var(--text-strong)}
.nav-m nav a[aria-current="page"]{color:var(--text-strong);
  border-left-color:var(--ember-500)}
@media (max-width:860px){ .nav{display:none} .nav-m{display:block} }

/* ---------- VOID HERO ---------- */
.hero{position:relative;z-index:1;background:transparent;overflow:hidden;
  padding:clamp(56px,9vw,120px) 0 clamp(64px,10vw,140px)}

/* ---------- THE DISPERSION FIELD — site-wide ----------
   Fixed to the viewport, behind every section, on every page. Sections do not
   each get their own field; there is ONE, and each band decides how much of it
   shows through by how opaque its own ground is. That is what makes it read as
   the background of a site rather than an effect on a hero.
   Decorative, additive, and entirely optional: if the script never runs the
   pages are unchanged. */
.field{position:fixed;inset:0;width:100%;height:100%;display:block;
  pointer-events:none;z-index:0;opacity:0;transition:opacity 1.4s var(--ease)}
.field.lit{opacity:1}
@media (prefers-reduced-motion:reduce){ .field{transition:none} }
/* grain — stops the void reading as a flat fill */
.hero::after{content:"";position:absolute;inset:0;pointer-events:none;opacity:.05;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E")}
.hero__grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.85fr);
  gap:clamp(32px,5vw,80px);align-items:center;position:relative;z-index:1}
@media (max-width:920px){ .hero__grid{grid-template-columns:1fr;gap:var(--sp-5)} }

h1{font-size:var(--fs-hero);line-height:1.02;letter-spacing:-.022em;font-weight:600;
  color:var(--text-strong);margin:var(--sp-3) 0 0;max-width:14ch;text-wrap:balance}
.standfirst{font-family:var(--font-body);font-size:clamp(17px,1rem + .35vw,20px);
  line-height:1.6;color:var(--text-secondary);max-width:52ch;margin:var(--sp-3) 0 0}
.routes{display:flex;flex-wrap:wrap;gap:var(--sp-3);margin:var(--sp-4) 0 0}
.route{display:inline-flex;align-items:center;gap:10px;min-height:44px;padding:11px 18px;
  font-size:var(--fs-small);font-weight:500;text-decoration:none;border-radius:var(--radius);
  transition:background var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}
.route--pri{color:var(--void);background:var(--text);border:1px solid var(--text)}
.route--pri:hover{background:var(--text-strong);border-color:var(--text-strong)}
.route--sec{color:var(--text);background:transparent;border:1px solid var(--rim-interactive)}
.route--sec:hover{background:var(--panel);border-color:var(--text-subtle)}
.route .arw{transition:transform var(--dur-fast) var(--ease)}
.route:hover .arw{transform:translateX(3px)}
.hero__note{margin:var(--sp-4) 0 0;font-size:var(--fs-caption);color:var(--text-faint);
  font-family:var(--font-mono);letter-spacing:.02em}

/* ---------- THE SCENE ---------- */
.scene{container-type:inline-size;position:relative}
.defs{position:absolute;width:0;height:0;overflow:hidden}
.scene svg{display:block;width:100%;height:auto;overflow:visible}
.scene .v{display:none}
@container (max-width:560px){ .scene .h{display:none} .scene .v{display:block} }

/* Assembly, then transit.
   Static IS the default. Motion is ADDED, never removed.

   Two layers of transform, nested, so the one-shot entry and the continuous
   loop never fight over the same property:
     .slab--m  outer  -> the entry rise (runs once, forwards)
     .drift    inner  -> the continuous transit (runs forever)

   The loop is SEMANTIC, not decorative. The mark's middle slab is the asset
   in motion between holders; the holders above and below it stay perfectly
   still, and that contrast is the composition. See isometric-scene-system,
   the looping exception. */
@media (prefers-reduced-motion:no-preference){
  .slab{opacity:0;transform:translateY(26px)}
  .lamp{opacity:0}
  .scene.go .slab{animation:settle var(--dur-scene) var(--ease-out) forwards}
  .scene.go .slab--b{animation-delay:0ms}
  .scene.go .slab--m{animation-delay:var(--stagger)}
  .scene.go .slab--t{animation-delay:calc(var(--stagger)*2)}
  .scene.go .lamp{animation:lamp 1100ms var(--ease-out) calc(var(--stagger)*3) forwards}
  @keyframes lamp{to{opacity:1}}

  /* ---- THE WAVE -------------------------------------------------------
     Every layer runs the same 3s sinusoid; each is one second further along
     than the one above it, so the crest travels UP the stack — bottom, middle,
     top — which is the mark's own sanctioned build order.

     NEGATIVE delays, not positive. A positive delay leaves the upper layers
     sitting still until their turn arrives, so the first three seconds are not
     a wave at all. A negative delay starts each layer mid-cycle, so every
     layer is already in motion on the first frame and the wave is running
     before anyone sees it.

     It never stops. No viewport gate, no visibility handler, no script —
     browsers already throttle background tabs, and a hand-rolled pause risks
     the stack being caught standing still, which is the one thing it must
     never do. */
  .scene.go .wave{animation:wave var(--dur-wave) linear infinite}
  .scene.go .wave--b{animation-delay:calc(var(--wave-step) * -2)}
  .scene.go .wave--m{animation-delay:calc(var(--wave-step) * -1)}
  .scene.go .wave--t{animation-delay:0s}
  /* The light sits with the middle layer, in phase, so it is brightest as the
     asset crests. One idea, not two animations sharing a page. */
  .scene.go .breathe{
    animation:breathe var(--dur-wave) linear calc(var(--wave-step) * -1) infinite}

  /* The mark carries the same wave, same period, same one-second step, same
     phase order — the header and the hero crest together. Amplitude is smaller
     in absolute terms but the LAYERS SHEAR against each other by up to 2px,
     and it is that relative flex, not the travel, that reads at 26px. */
  .mark-wave{animation:mark-wave var(--dur-wave) linear infinite;
    transform-box:fill-box;transform-origin:center}
  .mark-wave--b{animation-delay:calc(var(--wave-step) * -2)}
  .mark-wave--m{animation-delay:calc(var(--wave-step) * -1)}
  .mark-wave--t{animation-delay:0s}

  /* A TRUE SINUSOID, sampled at eighths, driven LINEAR.
     ease-in-out was wrong for a wave: it decelerates into both extremes, so
     each layer visibly holds at the top and bottom of its travel and the wave
     stutters instead of rolling. Sampling the sine and letting the timing
     function stay linear gives constant angular velocity — the crest moves at
     a steady rate and the loop is seamless because sin(0) = sin(2pi), in both
     value and slope.
     Amplitude 8 units, so 16 of travel on the 95-unit gap between layers: 17%,
     inside the 10-20% band. Measured, not judged by eye. */
  @keyframes wave{
    0%    {transform:translateY(0)}
    12.5% {transform:translateY(-5.66px)}
    25%   {transform:translateY(-8px)}
    37.5% {transform:translateY(-5.66px)}
    50%   {transform:translateY(0)}
    62.5% {transform:translateY(5.66px)}
    75%   {transform:translateY(8px)}
    87.5% {transform:translateY(5.66px)}
    100%  {transform:translateY(0)}}

  @keyframes mark-wave{
    0%    {transform:translateY(0)}
    12.5% {transform:translateY(-4.95px)}
    25%   {transform:translateY(-7px)}
    37.5% {transform:translateY(-4.95px)}
    50%   {transform:translateY(0)}
    62.5% {transform:translateY(4.95px)}
    75%   {transform:translateY(7px)}
    87.5% {transform:translateY(4.95px)}
    100%  {transform:translateY(0)}}

  @keyframes breathe{
    0%    {opacity:.72}
    25%   {opacity:1}
    50%   {opacity:.72}
    75%   {opacity:.55}
    100%  {opacity:.72}}
}

/* ---------- NEXT BAND (ground transition) ---------- */
.strata{position:relative;z-index:1;height:6px;background:linear-gradient(to bottom,
  var(--moss-3) 0 37.5%,var(--oxblood) 37.5% 62.5%,var(--moss-1) 62.5% 100%)}
/* Content bands sit ON the field and dim it. 0.82 leaves the field just
   visible as texture while every contrast figure in the matrix still holds —
   they were computed against solid grounds, so the scrim must stay opaque
   enough that the effective ground is unchanged. */
.band{position:relative;z-index:1;background:rgba(12,14,18,.86);
  padding:clamp(56px,8vw,104px) 0}
.band h2{font-family:var(--font-display);font-weight:600;font-size:var(--fs-h2);
  color:var(--text-strong);margin:var(--sp-2) 0 0;max-width:26ch;line-height:1.3}
.band p{font-family:var(--font-body);color:var(--text-secondary);max-width:62ch;
  margin:var(--sp-2) 0 0;line-height:1.65}
.band strong{color:var(--text);font-weight:400;
  border-bottom:1px solid var(--oxblood);padding-bottom:1px}

/* (The preview's notes-panel CSS was removed with the notes panel itself.
   It carried six non-token colours and no longer had any markup to style.) */

/* ═══════════════════════════════════════════════════════════════════════
   HOME — the sections below the hero.
   Four mechanics from the Huly reference, applied without a screenshot:
     1  light escapes from BEHIND an object, never a wash over the card
     2  the object leads, the words caption it
     3  section headlines at display scale, not heading scale
     4  sections are near-full-screen, not padded bands
   Nothing below invents a colour. Every value is a v3.0 token.
   ═══════════════════════════════════════════════════════════════════════ */

/* MECHANIC 4 — a section is a screen, not a band. Roughly double the old
   rhythm. The old --st-section-y (56-128px) produced a document; this
   produces a descent. */
.sec{position:relative;z-index:1;padding:clamp(88px,13vw,180px) 0;
  background:rgba(12,14,18,.86)}
.sec--void{background:rgba(7,8,10,.90)}
.sec--tall{min-height:88vh;display:flex;align-items:center}
.sec__in{width:100%}

/* MECHANIC 3 — section headlines at DISPLAY scale. The old h2 capped at 24px,
   which is why every section whispered. */
.sec h2{font-family:var(--font-display);font-weight:600;
  font-size:clamp(30px,2.1rem + 1.9vw,52px);line-height:1.08;
  letter-spacing:-.022em;color:var(--text-strong);
  margin:var(--sp-2) 0 0;max-width:19ch;text-wrap:balance}
.sec .lede{font-family:var(--font-body);font-size:clamp(17px,1rem + .3vw,19px);
  line-height:1.65;color:var(--text-secondary);max-width:60ch;margin:var(--sp-3) 0 0}
.sec .lede strong{color:var(--text);font-weight:400;
  border-bottom:1px solid var(--oxblood);padding-bottom:1px}

/* ── 2 · WHAT WE DO — the functional layer. No treatment at all. ──────────
   Was `.negative`, when this section said what the company was not. D15
   inverted the section, so the class was renamed with it: a selector called
   `.negative` sitting on a positioning block is exactly the drift a later
   session reads as a mistake and "fixes" in the wrong direction. */
/* The measure goes on the CHILDREN, not on the container. This element also
   carries `.wrap`, which centres itself inside 1240px — so a max-width here
   fought that centring and pushed the whole block to x=407 while every other
   section sat on the left rail at x=148. `.negative` had the same defect from
   the beginning; it was invisible because only one section used it. Found
   14 Aug 2026 when D15 put `.stance` on four more. */
.stance > *{max-width:74ch}
.stance h2{max-width:24ch}

/* The standing line. Set apart from the body it follows — it is an assertion
   about what the company is, not a continuation of the paragraph above it. */
.stance__line{font-family:var(--font-display);font-weight:600;
  font-size:clamp(17px,1rem + .4vw,21px);color:var(--text-strong);
  letter-spacing:-.012em;line-height:1.4;
  margin:var(--sp-4) 0 0;padding-top:var(--sp-3);
  border-top:1px solid var(--hairline)}

/* ── 6 · BUILT AGAINST — the procurement answer ──────────────────────────
   The plainest component in the system, and that is the design. A reader
   checking whether a supplier meets their standards is scanning for names,
   not reading prose. Two-line records on a hairline grid, no accent, no
   elevation, no motion. It looks like a list because it is one. */
/* Flex, not grid, and the reason is the item count. A grid with gap over a
   painted container background draws its gridlines by letting the background
   through — which means an incomplete last row renders as a filled empty
   cell. Seven standards never divide evenly into an auto-fit track count, so
   that hole is permanent. Flex items grow to fill whatever row they land in,
   so there is no hole to fill at any width or any item count. */
.stds{list-style:none;margin:var(--sp-4) 0 0;padding:0;
  display:flex;flex-wrap:wrap;
  border-top:1px solid var(--hairline);border-bottom:1px solid var(--hairline)}
.stds li{flex:1 1 250px;padding:var(--sp-3);
  border-left:1px solid var(--hairline);
  display:flex;flex-direction:column;gap:5px}
.stds li:first-child{border-left:0}
.stds b{font-family:var(--font-display);font-weight:600;
  font-size:var(--fs-small);color:var(--text-strong);letter-spacing:-.005em}
.stds span{font-family:var(--font-body);font-size:var(--fs-caption);
  color:var(--text-secondary);line-height:1.5}

/* -- 3 . THE CHAIN — a rail, and five stages riding it -------------------
   Redrawn from the serpentine value-chain reference. The move that reference
   makes, and the ring could not: a CONTINUOUS BAND threading every stage, so
   it reads as one chain rather than five items in a row.
   Ours says something the reference does not have to — the band IS ReferenceDa.
   Every stage rides the same verification rail, which is precisely the claim
   the section makes in words. The rail is therefore the lit element, and it
   earns the page's accent moment by carrying the argument rather than
   decorating it.
   ---------------------------------------------------------------------- */
.chain{position:relative;margin:var(--sp-5) 0 0}
.chain__stage{position:relative;container-type:inline-size}
.chain__glow{position:absolute;left:50%;top:46%;transform:translate(-50%,-50%);
  width:82%;height:34%;z-index:0;pointer-events:none;
  background:radial-gradient(closest-side,
    rgba(217,99,107,.42) 0%,rgba(194,74,84,.18) 48%,rgba(122,37,48,0) 100%);
  filter:blur(30px)}
.chain__stage svg{position:relative;z-index:1;display:block;width:100%;height:auto;overflow:visible}

.rail-bed{fill:none;stroke:var(--moss-1);stroke-width:26;stroke-linecap:round}
.rail{fill:none;stroke:url(#railGrad);stroke-width:18;stroke-linecap:round}
.rail-core{fill:none;stroke:var(--ivory);stroke-width:1.6;opacity:.55}
.rail-lbl{font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:1.9px;fill:var(--ivory-dim)}

.n-t{fill:var(--moss-2)} .n-r{fill:var(--moss-1)} .n-l{fill:var(--moss-3)}
.n-d{fill:url(#nodeDots)}
.n-e{fill:none;stroke:var(--rim-interactive);stroke-width:1.4}
.n-num{font-family:var(--font-mono);font-size:15px;fill:var(--text-subtle);letter-spacing:.5px}
.n-name{font-family:var(--font-display);font-size:12.5px;font-weight:600;
  letter-spacing:1.5px;fill:var(--text)}
.n-role{font-family:var(--font-display);font-size:10.5px;font-weight:400;
  letter-spacing:.4px;fill:var(--text-faint)}
.nd--off .n-t{fill:var(--moss-1)}

/* the pulse — the asset in motion along the rail. Same doctrine that sanctions
   the mark's loop: the thing that moves is the thing the business is about, so
   the motion states a fact rather than decorating one. */
.pulse{fill:var(--ivory)}

@media (prefers-reduced-motion:no-preference){
  .chain .rail-bed,.chain .rail,.chain .rail-core{
    stroke-dasharray:1800;stroke-dashoffset:1800;
    animation:draw var(--dur-draw) var(--ease-out) forwards}
  .chain .rail{animation-delay:120ms}
  .chain .rail-core{animation-delay:240ms}
  .chain .nd{opacity:0;transform:translateY(14px);
    animation:settle var(--dur-reveal) var(--ease-out) forwards;
    animation-delay:calc(560ms + var(--i) * var(--stagger))}
  .chain .pulse{offset-path:path("M-40.0,168.0 C-15.0,168.0 50.8,156.3 110.0,168.0 C169.2,179.7 246.7,238.0 315.0,238.0 C383.3,238.0 451.7,168.0 520.0,168.0 C588.3,168.0 660.8,238.0 725.0,238.0 C789.2,238.0 863.3,179.7 905.0,168.0 C946.7,156.3 963.3,168.0 975.0,168.0");offset-rotate:0deg;
    animation:ride var(--dur-ride) linear 1600ms infinite}
}

/* ══ THE MOTION SPINE — the three shared gestures ═════════════════════════
   Declared once, at the top level, so every page consumes the same keyframes
   rather than redeclaring its own. Before 14 Aug 2026 this file had `rise`
   and `settle`, portfolio.css had `vcSettle`, thesis.css had `tsettle` and
   how-we-build.css had `hsettle` — five names for ONE keyframe, running at
   five different durations. Timing lives in tokens (--dur-reveal, --dur-draw,
   --dur-ride); this block only says what each gesture IS.

   Outside any media query on purpose: the reduced-motion contract is that the
   DURATION tokens go to 0ms, which ends the animation at its final frame.
   Gating the keyframes themselves would leave elements stuck at opacity:0. */
@keyframes settle{to{opacity:1;transform:translateY(0)}}
@keyframes draw{to{stroke-dashoffset:0}}
@keyframes ride{from{offset-distance:0%}to{offset-distance:100%}}
@media (prefers-reduced-motion:reduce){.chain .pulse{display:none}}

/* ── 4 · VERIFIABLE FACTS — objects on a lit surface, not a row of text ── */
.plinth{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;
  margin:var(--sp-5) 0 0;background:var(--hairline);
  border-radius:4px;overflow:hidden;box-shadow:0 24px 48px -30px rgba(0,0,0,.9)}
@media (max-width:860px){.plinth{grid-template-columns:repeat(2,1fr)}}
@media (max-width:460px){.plinth{grid-template-columns:1fr}}
.fact{background:linear-gradient(180deg,var(--raised),var(--panel));
  padding:var(--sp-3) var(--sp-3) var(--sp-4);position:relative}
.fact::before{content:"";position:absolute;inset:0 0 auto 0;height:1px;background:var(--rim)}
.fact dt{font-size:var(--fs-caption);font-weight:600;text-transform:uppercase;
  letter-spacing:var(--track-eyebrow);color:var(--text-faint);margin:0}
.fact dd{margin:var(--sp-2) 0 0;font-family:var(--font-mono);
  font-size:clamp(15px,.9rem + .35vw,19px);color:var(--text);
  font-variant-numeric:tabular-nums;line-height:1.35;word-break:break-word}
.fact .src{display:block;margin-top:10px;font-family:var(--font-display);
  font-size:var(--fs-caption);color:var(--text-subtle);letter-spacing:0}

/* ── 4a · SOLVE-FOR CAROUSEL — the plinth set adrift (13 Aug, S13).
      Compositor-only: transform, nothing else. Hover or focus pauses.
      Under prefers-reduced-motion it is the static grid again and the
      aria-hidden clones vanish — nothing moves, nothing is missing. ── */
.marq{margin-top:var(--sp-5);overflow:hidden;border-radius:4px;
  box-shadow:0 24px 48px -30px rgba(0,0,0,.9)}
.plinth--marq{display:flex;width:max-content;margin-top:0;border-radius:0;
  overflow:visible;box-shadow:none;animation:marq-drift 48s linear infinite}
.plinth--marq .fact{flex:0 0 clamp(260px,26vw,340px)}
/* Pause. :hover is guarded to fine pointers — on touch :hover LATCHES on tap and
   never releases, which paused the drift permanently and could not be undone
   (found on a handset, 28 Aug 2026). :focus-within was dead code: the cards
   carry no focusable descendants, so focus never entered. The button is the
   real SC 2.2.2 mechanism and works for touch, mouse and keyboard alike. */
@media (hover:hover) and (pointer:fine){
  .marq:hover .plinth--marq{animation-play-state:paused}
}
.marq.is-paused .plinth--marq{animation-play-state:paused}
.marq__ctl{display:inline-flex;align-items:center;min-height:32px;
  margin-top:var(--sp-2);padding:6px 14px;background:transparent;
  border:1px solid var(--rim-interactive);border-radius:var(--radius);
  color:var(--text-secondary);font-family:var(--font-display);
  font-size:var(--fs-caption);letter-spacing:var(--track-eyebrow);
  text-transform:uppercase;cursor:pointer;
  transition:color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}
.marq__ctl:hover,.marq__ctl:focus-visible{color:var(--text);border-color:var(--text)}
@keyframes marq-drift{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:reduce){
  .plinth--marq{display:grid;width:auto;animation:none}
  .plinth--marq .fact{flex:none}
  .plinth--marq .fact[aria-hidden="true"]{display:none}
  .marq__ctl{display:none}
}

/* ── 5 · HOW WE BUILD — a mosaic, not four equal boxes ── */
.disc{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--sp-2);
  margin:var(--sp-5) 0 0}
@media (max-width:760px){.disc{grid-template-columns:1fr}}
.d{background:linear-gradient(180deg,var(--raised),var(--panel));
  box-shadow:var(--edge-rim),0 20px 40px -28px rgba(0,0,0,.9);
  border-radius:4px;padding:var(--sp-3);position:relative;overflow:hidden}
/* MECHANIC 2, cheaply — each discipline carries a slab of its own, lit from
   behind at low strength. One glow per card, well inside the page budget
   because the section spends its accent here rather than in the copy. */
.d::after{content:"";position:absolute;right:-40px;bottom:-56px;width:180px;height:120px;
  background:radial-gradient(closest-side,rgba(194,74,84,.20),rgba(122,37,48,0));
  filter:blur(18px);pointer-events:none}
.d h3{font-family:var(--font-display);font-size:var(--fs-h3);font-weight:600;
  color:var(--text-strong);margin:var(--sp-2) 0 0}
.d p{font-size:var(--fs-small);color:var(--text-secondary);margin:10px 0 0;line-height:1.62}
.d .n{font-family:var(--font-mono);font-size:var(--fs-caption);color:var(--ivory-deep)}

/* ── 6 · ROUTES ── */
.close{display:grid;grid-template-columns:1.1fr .9fr;gap:clamp(32px,5vw,72px);align-items:end}
@media (max-width:860px){.close{grid-template-columns:1fr;gap:var(--sp-4)}}

/* ── FOOTER — terminal close, the void returning ── */
.ftr{position:relative;z-index:1;background:rgba(7,8,10,.94);
  padding:clamp(56px,7vw,88px) 0 var(--sp-4);border-top:1px solid var(--hairline)}
.ftr__grid{display:flex;justify-content:space-between;gap:var(--sp-4);flex-wrap:wrap;
  align-items:flex-start}
.ftr__ent{font-size:var(--fs-caption);color:var(--text-subtle);line-height:1.7;
  margin:var(--sp-3) 0 0;max-width:38ch}
.ftr nav ul{list-style:none;display:flex;flex-wrap:wrap;gap:var(--sp-3);margin:0;padding:0}
.ftr nav a{color:var(--text-secondary);font-size:var(--fs-small);text-decoration:none;
  display:inline-block;min-height:24px;line-height:24px;
  border-bottom:1px solid transparent;transition:color var(--dur-fast) var(--ease)}
.ftr nav a:hover{color:var(--text-strong);border-bottom-color:var(--ember-500)}
.ftr__rule{height:1px;background:var(--hairline);margin:var(--sp-4) 0}
.ftr__fine{font-size:var(--fs-caption);color:var(--text-faint);margin:0}

/* ── Session 6 additions ──────────────────────────────────────────────────
   Assembly only. No redesign — these exist because a multi-page site needs
   things a standalone file did not. */

/* A planned-but-unwritten nav item. Rendered as text, never as a link to
   nowhere: a focusable control that does nothing is an accessibility defect,
   and href="#" in a shipped nav is exactly that. Disappears per item as each
   page is built, with no edit to the nav. */
.nav__soon{color:var(--text-faint);cursor:default}
.ftr nav .nav__soon{color:var(--text-faint)}

/* ── Session 7 additions ──────────────────────────────────────────────────
   Two defects found by assembling the remaining five pages, not by eye. Both
   were live in Session 6 output and both now have a build guard. */

/* 1 · THE PAGE LEAD.
   Portfolio shipped with NO h1 — it opened on an h2 because it was styled
   before it was a page in a set, and .sec h2 already carried display scale so
   nothing looked wrong. A document with no h1 has no top-level heading for a
   screen reader to land on, and .sec h2 is the wrong element for the job.
   This class is the h1 that pages opening on a statement rather than a hero
   use: correct element, section display scale, deliberately identical to
   .sec h2 so the visual voice does not change. Home keeps the hero h1. */
.lead-h1{font-family:var(--font-display);font-weight:600;
  font-size:clamp(30px,2.1rem + 1.9vw,52px);line-height:1.08;
  letter-spacing:-.022em;color:var(--text-strong);
  margin:var(--sp-2) 0 0;max-width:19ch;text-wrap:balance}

/* 2 · AN UNCONFIRMED OUTBOUND DESTINATION.
   Portfolio shipped two venture cards whose outbound link was href="#" — a
   focusable control that does nothing, which is the same accessibility defect
   .nav__soon was written to avoid, arriving on a different page. Rendered as
   text until a URL is confirmed, exactly as the nav does. */
.vc__soon{margin:auto 0 0;padding-top:var(--sp-3);font-family:var(--font-display);
  font-size:var(--fs-small);font-weight:500;color:var(--text-faint);
  display:inline-flex;align-items:center;gap:8px;min-height:24px;cursor:default}

/* ══ THE CONCIERGE — scripted answers, D15 · 14 August 2026 ═══════════════
   Restrained on purpose. The reference class for this site does not run chat
   bubbles, and a widget that behaves like one would undercut the register the
   rest of the page is holding. It is a small pill that opens a written index.

   Nested native <details>: keyboard-operable and announced with no script, so
   the JS-off guarantee survives. No glow, no gradient, no accent — it appears
   on all eight pages, and two of those (Governance, Legal) fail a build gate
   on any treatment at all. */
.cg{position:fixed;right:var(--gutter);bottom:var(--sp-3);z-index:120;
  width:min(380px,calc(100vw - var(--gutter) * 2))}
.cg > summary{list-style:none;cursor:pointer;
  display:inline-flex;align-items:center;gap:8px;
  margin-left:auto;width:max-content;min-height:44px;padding:11px 18px;
  font-family:var(--font-display);font-size:var(--fs-small);font-weight:500;
  color:var(--text);background:var(--raised);
  border:1px solid var(--rim-interactive);border-radius:var(--radius);
  box-shadow:var(--edge-rim),0 18px 40px -30px rgba(0,0,0,.95);
  transition:background var(--dur-fast) var(--ease),
             border-color var(--dur-fast) var(--ease)}
.cg > summary::-webkit-details-marker{display:none}
.cg > summary::after{content:"+";font-family:var(--font-mono);
  font-size:var(--fs-caption);color:var(--text-faint)}
.cg[open] > summary::after{content:"\2212"}
.cg > summary:hover{background:var(--panel);border-color:var(--text-subtle)}

.cg__panel{margin-top:10px;max-height:min(70vh,560px);overflow-y:auto;
  background:var(--raised);border:1px solid var(--hairline);
  border-radius:var(--radius);padding:var(--sp-3);
  box-shadow:var(--edge-rim),0 26px 60px -34px rgba(0,0,0,.95)}
.cg__hd{margin:0;font-family:var(--font-display);font-weight:600;
  font-size:var(--fs-small);color:var(--text-strong);letter-spacing:-.005em}
.cg__sub{margin:6px 0 var(--sp-3);font-family:var(--font-body);
  font-size:var(--fs-caption);color:var(--text-faint);line-height:1.55}

.cg__list{list-style:none;margin:0;padding:0}
.cg__list > li{border-top:1px solid var(--hairline)}
.cg__list summary{list-style:none;cursor:pointer;padding:12px 22px 12px 0;
  position:relative;min-height:24px;
  font-family:var(--font-display);font-size:var(--fs-small);
  color:var(--text);line-height:1.45}
.cg__list summary::-webkit-details-marker{display:none}
.cg__list summary::after{content:"+";position:absolute;right:2px;top:12px;
  font-family:var(--font-mono);font-size:var(--fs-caption);color:var(--text-faint)}
.cg__list details[open] summary::after{content:"\2212"}
.cg__list summary:hover{color:var(--text-strong)}
.cg__list details p{margin:0 0 12px;font-family:var(--font-body);
  font-size:var(--fs-caption);color:var(--text-secondary);line-height:1.6}
.cg__list details p b{color:var(--text-strong);font-weight:600}
.cg__go a{font-family:var(--font-display);font-size:var(--fs-caption);
  color:var(--text);text-decoration:underline;
  text-decoration-color:var(--rim-interactive);text-underline-offset:3px}
.cg__go a:hover{text-decoration-color:var(--ember-300)}

.cg__foot{margin:var(--sp-3) 0 0;padding-top:var(--sp-2);
  border-top:1px solid var(--hairline);
  font-family:var(--font-body);font-size:var(--fs-caption);
  color:var(--text-faint);line-height:1.55}
.cg__foot a{color:var(--text);text-decoration:underline;
  text-decoration-color:var(--rim-interactive);text-underline-offset:3px}

/* Below the mobile-nav breakpoint the pill would sit on top of the footer's
   own links, so it becomes a full-width sheet anchored to the bottom edge. */
@media (max-width:560px){
  .cg{right:0;left:0;bottom:0;width:100%;padding:0 var(--gutter) var(--sp-2)}
  .cg__panel{max-height:62vh}
}
@media print{ .cg{display:none} }

/* ══ 404 — the way back ══════════════════════════════════════════════════
   Every page, listed. A 404 that offers only "go home" makes the reader do
   the search again; a 404 that lists the whole site is faster than the nav
   they were already failing to use. Eight rows is the entire site, so there
   is no editorial judgement about which to show. */
.nf{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));
  margin:var(--sp-5) 0 var(--sp-4);
  border-top:1px solid var(--hairline)}
/* `.doc a` in governance.css underlines every link inside the reading well, at
   the same (0,1,1) specificity as `.nf a` and later in the concatenation — so a
   plain reset here lost, and both the label and its description shipped
   underlined. Scoped one level deeper to win on specificity rather than on
   file order, which would break again the next time a stylesheet is added. */
.doc .nf a,.nf a{display:flex;flex-direction:column;gap:4px;
  padding:var(--sp-3) var(--sp-3) var(--sp-3) 0;
  text-decoration:none;border-bottom:1px solid var(--hairline)}
.nf b{font-family:var(--font-display);font-weight:600;font-size:var(--fs-small);
  color:var(--text-strong);letter-spacing:-.005em;
  border-bottom:1px solid var(--rim-interactive);padding-bottom:1px;
  align-self:flex-start;transition:border-color var(--dur-fast) var(--ease)}
.nf span{font-family:var(--font-body);font-size:var(--fs-caption);
  color:var(--text-secondary);line-height:1.5}
.nf a:hover b,.nf a:focus-visible b{border-bottom-color:var(--ember-300)}
.nf a:focus-visible{outline:2px solid var(--rim-interactive);outline-offset:3px}


/* ---------- portfolio.css ---------- */

/* ═══════════════════════════════════════════════════════════════════════
   PORTFOLIO — seven ventures as a bento mosaic.
   Each card is led by the venture's OWN isometric form, derived from what
   that venture actually does. That is the differentiation, and it works
   where colour could not: ParcelIQ and ReferenceDa are the same hex.
   ═══════════════════════════════════════════════════════════════════════ */

/* the house, introduced before the five and not as a sixth sibling */
.house{background:linear-gradient(180deg,var(--raised),var(--panel));
  box-shadow:var(--edge-rim),0 24px 48px -30px rgba(0,0,0,.9);
  border-radius:6px;padding:var(--sp-4);margin:var(--sp-5) 0 0;
  display:grid;grid-template-columns:1.2fr .8fr;gap:var(--sp-4);align-items:center}
@media (max-width:820px){.house{grid-template-columns:1fr}}
.house h2{font-family:var(--font-display);font-size:clamp(22px,1.4rem + .8vw,30px);
  font-weight:600;color:var(--text-strong);margin:6px 0 0;letter-spacing:-.014em}
.house p{font-size:var(--fs-small);color:var(--text-secondary);margin:12px 0 0;
  line-height:1.65;max-width:56ch}

/* THE MOSAIC. Deliberately not a uniform 3-column grid — seven equal boxes
   read as a list, and the argument is that these are not a list. The rail
   spans, because the rail spans. */
.mosaic{display:grid;grid-template-columns:repeat(6,1fr);gap:var(--sp-2);
  margin:var(--sp-4) 0 0}
.vc{grid-column:span 2}
.vc--wide{grid-column:span 3}
.vc--rail{grid-column:span 6}
@media (max-width:1000px){.mosaic{grid-template-columns:repeat(4,1fr)}
  .vc,.vc--wide{grid-column:span 2}.vc--rail{grid-column:span 4}}
@media (max-width:620px){.mosaic{grid-template-columns:1fr}
  .vc,.vc--wide,.vc--rail{grid-column:span 1}}

.vc{position:relative;border-radius:6px;overflow:hidden;
  background:linear-gradient(180deg,var(--raised) 0%,var(--panel) 100%);
  box-shadow:var(--edge-rim),0 26px 52px -28px rgba(0,0,0,.9);
  transition:transform var(--dur-base) var(--ease),box-shadow var(--dur-base) var(--ease);
  display:flex;flex-direction:column}
.vc:hover{transform:translateY(-3px);
  box-shadow:inset 0 1px 0 var(--rim-interactive),0 32px 62px -28px rgba(0,0,0,.95)}

/* the stage — the venture's form leads, the words caption it */
.vc__stage{position:relative;aspect-ratio:400/230;overflow:hidden;
  background:radial-gradient(120% 92% at 50% 116%,var(--panel) 0%,var(--void) 60%)}
.vc--rail .vc__stage{aspect-ratio:1000/200}
.vc__stage svg{position:relative;z-index:2;display:block;width:100%;height:100%}
/* light from BEHIND the form. One per card is inside budget because the
   Portfolio page spends its accent on the forms rather than on the copy. */
/* THE GLOW BUDGET, AND WHAT A MOSAIC DOES TO IT.
   DESIGN.md sets 1 glow per section, 2 per page. A seven-card mosaic is one
   section, so seven equal glows is seven times over — and worse than the
   arithmetic, isometric-scene-system says a scene has EXACTLY ONE lit element,
   because a picture with seven subjects has none.
   Resolved by strength rather than by deletion, which is what the reference
   grids actually do on inspection: the five stage cards get an AMBIENT BED —
   enough to lift the form off the ground, not enough to read as a source.
   Two things on this page emit: the ReferenceDa rail, which is the one lit
   element, and Pramaana, which is lit from within.
   This still needs your ratification as a DESIGN.md amendment — the rule as
   written does not cover card grids, and I am not quietly rewriting it. */
.vc__glow{position:absolute;left:50%;top:62%;transform:translate(-50%,-50%);
  width:70%;height:44%;z-index:1;pointer-events:none;
  background:radial-gradient(closest-side,rgba(194,74,84,.16) 0%,
    rgba(122,37,48,.06) 52%,rgba(122,37,48,0) 100%);filter:blur(22px)}
/* the one lit element */
.vc--rail .vc__glow{width:86%;height:56%;
  background:radial-gradient(closest-side,rgba(217,99,107,.50) 0%,
    rgba(194,74,84,.22) 46%,rgba(122,37,48,0) 100%);filter:blur(26px)}
/* lit from WITHIN — the structural exception that keeps "fire only on
   verified" alive without importing Pramaana's fire onto a Stactray surface */
.vc--self .vc__glow{width:52%;height:40%;top:52%;
  background:radial-gradient(closest-side,rgba(242,168,173,.42) 0%,
    rgba(217,99,107,.20) 50%,rgba(122,37,48,0) 100%);filter:blur(14px)}

.vc__body{padding:var(--sp-3);display:flex;flex-direction:column;flex:1}
/* The 48px mark slot — DESIGN.md 7.6. UNUSED SINCE D8-b, kept deliberately.
   No venture mark appears on any Stactray surface in v1, so no card currently
   emits this element. The rule stays because 7.6 specifies ONE component with
   TWO renderings, and deleting half a ratified component to save 90 bytes is
   how the second rendering stops existing.
   A tile, not a scale-to-fit: the mark renders AT 48, never shrunk below it,
   because Pramaana's chevron stops resolving under that size. The slot has no
   reserved height, which is why removing the element costs no reflow — the
   eyebrow simply rises to the rim, exactly as the "no mark" state specifies.
   alt="" would be deliberate: the venture name is in the adjacent h3, so alt
   text would make a screen reader announce it twice. */
.vc__mark{width:48px;height:48px;display:block;object-fit:contain;
  margin:0 0 var(--sp-2);flex:none}
.vc__role{font-size:var(--fs-caption);font-weight:600;text-transform:uppercase;
  letter-spacing:var(--track-eyebrow);color:var(--text-faint);margin:0}
.vc h3{font-family:var(--font-display);font-weight:600;color:var(--text-strong);
  font-size:24px;letter-spacing:-.012em;margin:6px 0 0;line-height:1.14}
.vc__body p{font-size:var(--fs-small);color:var(--text-secondary);
  margin:10px 0 0;line-height:1.6}
.vc__target{margin:var(--sp-2) 0 0;padding:10px 0 0;border-top:1px solid var(--hairline);
  font-size:var(--fs-caption);color:var(--text-subtle);line-height:1.55}
.vc__target b{color:var(--ember-300);font-weight:600;font-family:var(--font-mono)}
.vc__target .src{display:block;color:var(--text-faint);margin-top:3px}
.vc__link{margin:auto 0 0;padding-top:var(--sp-3);font-family:var(--font-display);
  font-size:var(--fs-small);font-weight:500;color:var(--text);text-decoration:underline;
  text-underline-offset:.2em;text-decoration-color:var(--rim-interactive);
  display:inline-flex;align-items:center;gap:8px;min-height:24px}
.vc__link:hover{color:var(--text-strong);text-decoration-color:var(--ember-300)}
.vc__link .arw{transition:transform var(--dur-fast) var(--ease)}
.vc__link:hover .arw{transform:translateX(3px)}

/* form parts, shared */
.p-t{fill:var(--moss-2)} .p-r{fill:var(--moss-1)} .p-l{fill:var(--moss-3)}
.p-e{fill:none;stroke:var(--rim-interactive);stroke-width:1.5}
.p-plane{fill:none;stroke:var(--moss-3);stroke-width:1.5;stroke-dasharray:6 8;opacity:.8}
.p-grid{stroke:var(--rim-interactive);stroke-width:1;opacity:.75}
.p-dots{fill:url(#pDots)}
.p-lit{fill:url(#pLit)} .p-litl{fill:var(--ember-300)} .p-litr{fill:var(--oxblood)}
.p-hole{fill:var(--void)}
.p-facet-a{fill:var(--ember-500)} .p-facet-b{fill:var(--oxblood)}
.p-facet-c{fill:var(--ember-400)} .p-facet-d{fill:var(--oxblood)}

/* per-venture assembly beats. Motion is ADDED — the static form is default. */
@media (prefers-reduced-motion:no-preference){
  .vc__stage .beat{opacity:0;transform:translateY(10px);
    animation:settle var(--dur-reveal) var(--ease-out) forwards;
    animation-delay:calc(var(--b,0) * 110ms)}
  /* Uses the shared `settle` gesture and --dur-reveal from the motion spine
     in base.css. This file previously declared its own identical keyframe
     at its own duration, which is what made each page feel separately
     timed rather than part of one site (D15, 14 Aug 2026). */
  /* ParcelIQ: the boundary resolves */
  .vc__stage .resolve{stroke-dasharray:210;animation:vcTrace var(--dur-scene) var(--ease-out) both}
  @keyframes vcTrace{from{stroke-dashoffset:210;opacity:0}to{stroke-dashoffset:0;opacity:.8}}
  /* Dry Powder: the vessel fills */
  .vc__stage .fill{transform-origin:50% 100%;animation:vcFill 1100ms var(--ease-out) 400ms both}
  @keyframes vcFill{from{transform:scaleY(.1);opacity:.4}to{transform:scaleY(1);opacity:1}}
}

/* The ratified construction for a venture figure is three parts, and all three
   are load-bearing (DECISIONS-BLOCKED.md, the venture-commitments amendment):
   the figure, its source label, and the words "Stated target" that make it a
   target rather than a result. The page shipped with parts one and two only —
   an ember figure reading "45% timeline improvement", which is closer to the
   construction the amendment forbids ("Within reduces timelines by 45%") than
   to the one it requires. Restored 14 Aug 2026 (D15 sweep).

   Set quieter than the figure on purpose: it qualifies the number, so it has
   to be read WITH it rather than instead of it. */
.vc__target i{font-style:normal;font-family:var(--font-display);
  font-size:var(--fs-caption);color:var(--text-faint);
  letter-spacing:.04em;text-transform:uppercase;margin-right:6px}


/* ---------- governance.css ---------- */
/* ============================================================================
   GOVERNANCE — archetype A4, the reading well
   Session 6 · 12 August 2026

   DESIGN.md §6: "A4 takes none of the treatment. No glow, no gradient, no
   rim-light theatre, no scene. On Governance and Legal it is the whole page,
   and that is correct."

   So this file adds structure and measure, and nothing else. If a rule here
   ever grows a radial-gradient, build.mjs stops the build.

   Register D10-d: serif. This is a page a reader takes seriously or does not
   read at all, and 25% of the benchmark weight sits on it.

   ── Session 7 · shared with .doc ─────────────────────────────────────────
   Thesis, Company and Legal are the same archetype: A4, the reading well.
   Rather than write three more stylesheets that say this in slightly
   different words, every structural rule below serves BOTH `.gov` and
   `.doc`. One source for the reading well, four pages consuming it.

   The alternative was a legal.css duplicating these rules, which is a second
   source for a ratified component — the exact failure S5d spent a session
   undoing. Page-specific components (the axis records, the cadence list, the
   founder slots, the routes) live in their own files and nowhere else.

   Register note: .doc carries no font-family of its own. Thesis and Company
   are serif and inherit it here; How we build and Contact are sans and do not
   use .doc at all (D10-d).
   ========================================================================= */

.gov,.doc{max-width:68ch}
.gov h1,.doc h1{margin:0 0 var(--sp-3)}
.gov .lede,.doc .lede{margin-bottom:var(--sp-5)}

.gov section,.doc section{padding-block:var(--sp-5) 0}
.gov section + section,.doc section + section{
  border-top:1px solid var(--hairline);margin-top:var(--sp-5)}

.gov h2,.doc h2{font-family:var(--font-display);font-size:var(--fs-h2);font-weight:600;
  color:var(--text-strong);letter-spacing:-.01em;margin:0 0 var(--sp-2)}
.gov h3,.doc h3{font-family:var(--font-display);font-size:var(--fs-h3);font-weight:600;
  color:var(--text);margin:var(--sp-3) 0 6px}
.gov p,.doc p{font-family:var(--font-body);font-size:var(--fs-body);line-height:1.68;
  color:var(--text-secondary);margin:0 0 var(--sp-2);max-width:66ch}

/* The entity block. Set as a record, not as a paragraph — this is the first
   thing anyone running diligence looks for, so it is findable at a glance. */
.gov__entity{font-family:var(--font-mono);font-size:var(--fs-small);
  line-height:1.9;color:var(--text);background:var(--raised);
  border:1px solid var(--hairline);border-left:2px solid var(--oxblood);
  border-radius:var(--radius);padding:var(--sp-3);margin:0 0 var(--sp-3)}
.gov__entity b{font-weight:400;color:var(--text-strong)}
.gov__entity span{color:var(--text-subtle)}

/* Officers */
.gov__people{list-style:none;margin:0;padding:0;display:grid;gap:var(--sp-2);
  grid-template-columns:repeat(auto-fit,minmax(230px,1fr))}
.gov__people li{border-top:1px solid var(--hairline);padding-top:10px}
.gov__people b{display:block;font-family:var(--font-display);font-weight:600;
  font-size:var(--fs-h3);color:var(--text-strong)}
.gov__people span{font-size:var(--fs-small);color:var(--text-subtle)}

/* The regulatory register. Two columns: what it is, which regime governs it.
   The whole page turns on the second column naming a regime and stopping. */
.gov__reg{width:100%;border-collapse:collapse;margin:var(--sp-2) 0 0;
  font-family:var(--font-body);font-size:var(--fs-small)}
.gov__reg caption{text-align:left;font-family:var(--font-display);
  font-size:var(--fs-caption);font-weight:600;text-transform:uppercase;
  letter-spacing:var(--track-eyebrow);color:var(--text-faint);
  padding-bottom:var(--sp-2)}
.gov__reg th,.gov__reg td{text-align:left;vertical-align:top;
  padding:12px 14px 12px 0;border-bottom:1px solid var(--hairline)}
.gov__reg th{font-family:var(--font-display);font-weight:600;
  color:var(--text-strong);width:34%;white-space:nowrap}
.gov__reg td{color:var(--text-secondary);line-height:1.6}
@media (max-width:620px){
  .gov__reg,.gov__reg tbody,.gov__reg tr,.gov__reg th,.gov__reg td{display:block;width:auto}
  .gov__reg th{border-bottom:0;padding-bottom:2px}
  .gov__reg td{padding-top:0}
}

/* The line the page is built on, stated once and set apart. */
.gov__line,.doc__line{font-family:var(--font-body);font-size:var(--fs-body);line-height:1.68;
  color:var(--text);border-left:2px solid var(--moss-edge);
  padding:2px 0 2px var(--sp-2);margin:var(--sp-3) 0;max-width:64ch}
.gov__line strong,.doc__line strong{color:var(--text-strong);font-weight:400;
  border-bottom:1px solid var(--oxblood);padding-bottom:1px}

.gov__note,.doc__note{font-size:var(--fs-caption);color:var(--text-faint);
  font-family:var(--font-display);line-height:1.7;margin-top:var(--sp-3)}
.gov a,.doc a{color:var(--text-strong);text-decoration:underline;
  text-decoration-color:var(--rim-interactive);text-underline-offset:3px}
.gov a:hover,.doc a:hover{text-decoration-color:var(--ember-300)}
.doc .lede strong{color:var(--text);font-weight:400;
  border-bottom:1px solid var(--oxblood);padding-bottom:1px}

/* ══ D15 · THE CONTROL PLANE AND DELIVERY ASSURANCE — 14 August 2026 ═══════
   Archetype A4 takes no glow, no gradient and no scene, and build.mjs fails
   this page on any of them. So both components below are built from structure
   and hairlines alone. That constraint is the right one here: a page whose
   entire claim is "nothing here has to be taken on trust" cannot afford to
   look persuaded of itself.
   ========================================================================= */

/* ── THE CONTROL PLANE ────────────────────────────────────────────────────
   Six positions and their current state. A governance page is read by someone
   checking one specific thing; this is the index that gets them there. */
.gov__plane{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  margin:0 0 var(--sp-5);
  border-top:1px solid var(--hairline);border-bottom:1px solid var(--hairline)}
.gov__plane a{display:flex;flex-direction:column;gap:5px;
  padding:var(--sp-3) var(--sp-3) var(--sp-3) 0;
  text-decoration:none;border-bottom:1px solid var(--hairline)}
.gov__plane a:last-child,.gov__plane a:nth-last-child(2):nth-child(odd){border-bottom:0}
.gov__plane b{font-family:var(--font-display);font-weight:600;
  font-size:var(--fs-small);color:var(--text-strong);letter-spacing:-.005em}
.gov__plane span{font-family:var(--font-body);font-size:var(--fs-caption);
  color:var(--text-secondary);line-height:1.5}
/* The affordance is on the label, not on a box: this is a document index, and
   a document index that lights up like a button stops reading as a document.
   Ember, not oxblood — every other link on this page uses --rim-interactive
   at rest and --ember-300 on hover, and oxblood is reserved here for the
   typographic accent (the lede underline and the entity record's edge).
   An interactive state in the accent colour is accent creep, and this page's
   whole credibility rests on not doing that kind of thing. */
.gov__plane a b{border-bottom:1px solid var(--rim-interactive);padding-bottom:1px}
.gov__plane a:hover b,.gov__plane a:focus-visible b{
  border-bottom-color:var(--ember-300)}
.gov__plane a:focus-visible{outline:2px solid var(--rim-interactive);outline-offset:3px}

/* ── CLAIM → MECHANISM → ARTEFACT ─────────────────────────────────────────
   The shape is the argument. A governance claim with no mechanism is a value
   statement, and a mechanism with no artefact is an intention — so the record
   is not allowed to render unless all three are present. */
.gov__cme{list-style:none;counter-reset:cme;margin:var(--sp-4) 0 0;padding:0;
  display:grid;gap:var(--sp-4)}
.gov__cme li{counter-increment:cme;position:relative;
  padding-left:var(--sp-4);border-left:1px solid var(--hairline)}
.gov__cme li::before{content:counter(cme,decimal-leading-zero);
  position:absolute;left:0;top:2px;transform:translateX(-50%);
  background:var(--canvas);padding:2px 0;
  font-family:var(--font-mono);font-size:var(--fs-caption);
  color:var(--ivory-deep);letter-spacing:.06em}

.gov .gov__cme .cme__c{font-family:var(--font-display);font-weight:600;
  font-size:clamp(16px,.95rem + .3vw,19px);color:var(--text-strong);
  letter-spacing:-.012em;line-height:1.42;margin:0}
.gov .gov__cme .cme__m,
.gov .gov__cme .cme__a{font-size:var(--fs-small);color:var(--text-secondary);
  line-height:1.62;margin:10px 0 0}
.gov .gov__cme b{font-family:var(--font-display);font-weight:600;
  color:var(--text);letter-spacing:.01em}
/* The artefact line is the one a sceptical reader is looking for, so it is
   the one that is set apart rather than run on from the mechanism. */
.gov .gov__cme .cme__a{padding-top:10px;border-top:1px solid var(--hairline)}


/* ---------- thesis.css ---------- */
/* ============================================================================
   THESIS — three figures and a reading well
   Session 8 · 14 August 2026

   Rewritten from A4 (the reading well) to a figure-led page. The argument did
   not change; the register did. The page now states the method in three moves
   that are drawn rather than described — the screen, the lifetime, the
   instrument — with the prose demoted to caption and to the sections that
   carry legal weight.

   The two-layer rule still binds, and binds harder here. The three figures are
   soul layer. Everything below them — the conventions, the cadence, the
   boundary — is functional layer: sober, high-contrast, undecorated. A page
   arguing that method beats instinct cannot arrive decorated.

   The one accent is the lifeline in figure two. Two of three figures are moss
   and steel throughout so that the lit one is unambiguous. CLAUDE.md rule 5 —
   counted by build.mjs.
   ========================================================================= */

/* ── FIGURE SHELL ─────────────────────────────────────────────────────────
   Shared by all three. The stage is a container so figure internals can react
   to the figure's own width rather than the viewport's. */
.fig{position:relative;margin:var(--sp-5) 0 0;container-type:inline-size}
.fig__svg{position:relative;z-index:1;display:block;width:100%;height:auto;
  overflow:visible}

.fig__cap{font-family:var(--font-body);font-size:var(--fs-small);
  color:var(--text-secondary);line-height:1.62;max-width:62ch;
  margin:var(--sp-4) 0 0}
.fig__cap b{font-weight:600;color:var(--text-strong)}
.fig__cap--note{font-size:var(--fs-caption);color:var(--text-faint);
  border-top:1px solid var(--hairline);padding-top:var(--sp-2)}

/* The DEFAULT face for figure text, and it has to lose to anything more
   specific — hence :where(), which contributes no specificity at all.

   Written first as `.fig text` at (0,1,1), which quietly beat `.scr__q`,
   `.life__gt`, `.life__wr`, `.scr__role` and `.rd__q` — all (0,1,0) — and
   overrode their --font-body. Every italic in all three figures was
   therefore rendering as Archivo mechanically slanted, on a page whose type
   brief is an annual-report serif. Found 14 Aug 2026 while checking whether
   the newly self-hosted Source Serif italic was being used: it was not, and
   neither was the roman. */
:where(.fig) text{font-family:var(--font-display)}

/* ── ONE · THE SCREEN ─────────────────────────────────────────────────────
   Six sectors, six assertions, one membrane. Moss and steel only — this
   figure states the intake, not the claim, so it takes no accent. */
.scr__hd{font-size:10px;font-weight:600;letter-spacing:2.2px;
  fill:var(--text-faint)}
.scr__hd--c{text-anchor:middle}

.scr__sector{font-size:11.5px;font-weight:600;letter-spacing:1.3px;
  fill:var(--text)}
.scr__q{font-family:var(--font-body);font-style:italic;font-size:13px;
  fill:var(--text-secondary)}

.s-t{fill:var(--moss-2)} .s-r{fill:var(--moss-1)} .s-l{fill:var(--moss-3)}
.s-d{fill:url(#tDots)}
.s-e{fill:none;stroke:var(--moss-edge);stroke-width:1.2}

/* the run from each sector to the membrane */
.scr__run{fill:none;stroke:var(--steel-1);stroke-width:1.1;opacity:.55}

/* the membrane. Lit, but lit ivory — light without colour, so it reads as a
   test being applied rather than as the page's accent. */
.scr__mem{fill:url(#tLit)}
.scr__post{stroke:var(--steel-2);stroke-width:1.1}
.scr__mesh{stroke:var(--void);stroke-width:1.1;opacity:.55}
.scr__out{fill:none;stroke:var(--steel-2);stroke-width:1.4}

.p-t{fill:url(#tFace)} .p-l{fill:var(--steel-1)} .p-r{fill:var(--steel-3)}
.p-e{fill:none;stroke:var(--ivory-dim);stroke-width:1.2}
.scr__name{font-size:12.5px;font-weight:600;letter-spacing:1.5px;
  fill:var(--text-strong);text-anchor:middle}
.scr__role{font-family:var(--font-body);font-style:italic;font-size:12.5px;
  fill:var(--text-faint);text-anchor:middle}

/* ── TWO · THE LIFETIME ───────────────────────────────────────────────────
   The accent moment. The lifeline is the only lit-in-colour element on the
   page, and it is lit because it is the claim: the record accumulates, and
   what accumulates survives. */
.life{margin-top:var(--sp-5)}
/* Sits on the lifeline, not on the centre of the figure block — the block
   includes a caption, so 50% of it lands well below the line it is lighting. */
.life__glow{position:absolute;left:50%;top:40%;transform:translate(-50%,-50%);
  width:78%;height:20%;z-index:0;pointer-events:none;
  background:radial-gradient(closest-side,
    rgba(217,99,107,.38) 0%,rgba(194,74,84,.16) 50%,rgba(122,37,48,0) 100%);
  filter:blur(30px)}

.life__hd{font-size:10px;font-weight:600;letter-spacing:2.2px;
  fill:var(--text-faint)}

/* ABOVE THE LINE — what is asserted. Dashed, unfilled, unlit: it is a claim
   with nothing under it, and it is drawn as one. */
.life__ghost{fill:none;stroke:var(--steel-1);stroke-width:1.1;
  stroke-dasharray:4 4;opacity:.75}
.life__gt{font-family:var(--font-body);font-style:italic;font-size:13px;
  fill:var(--text-faint);text-anchor:middle}
.life__drop{fill:none;stroke:var(--steel-1);stroke-width:1;
  stroke-dasharray:3 5;opacity:.5}

/* THE LIFELINE */
.life__bed{fill:none;stroke:var(--moss-1);stroke-width:16;stroke-linecap:round}
.life__line{fill:none;stroke:url(#tLife);stroke-width:11;stroke-linecap:round}
.life__core{fill:none;stroke:var(--ivory);stroke-width:1.4;opacity:.5}
.life__pulse{fill:var(--ivory)}

/* BELOW THE LINE — what the standard writes. Solid, lit, on the record. */
.l-t{fill:url(#tFace)}
.l-e{fill:none;stroke:var(--ivory-dim);stroke-width:1.1}
.life__name{font-size:12px;font-weight:600;letter-spacing:1.6px;
  fill:var(--text-strong);text-anchor:middle}
.life__wr{font-family:var(--font-body);font-style:italic;font-size:12.5px;
  fill:var(--text-secondary);text-anchor:middle}

@media (prefers-reduced-motion:no-preference){
  .life__bed,.life__line,.life__core{
    stroke-dasharray:1000;stroke-dashoffset:1000;
    animation:draw var(--dur-draw) var(--ease-out) forwards}
  .life__line{animation-delay:110ms}
  .life__core{animation-delay:220ms}
  /* Uses the shared `settle` gesture and --dur-reveal from the motion spine
     in base.css. This file previously declared its own identical keyframe
     at its own duration, which is what made each page feel separately
     timed rather than part of one site (D15, 14 Aug 2026). */
  .life__st{opacity:0;transform:translateY(12px);
    animation:settle var(--dur-reveal) var(--ease-out) forwards;
    animation-delay:calc(520ms + var(--i) * var(--stagger))}
  .scr__row{opacity:0;transform:translateY(10px);
    animation:settle var(--dur-reveal) var(--ease-out) forwards;
    animation-delay:calc(var(--i) * var(--stagger))}
  .life__pulse{offset-path:path("M20,250 H980");offset-rotate:0deg;
    animation:ride var(--dur-ride) linear 1500ms infinite}
}
@media (prefers-reduced-motion:reduce){.life__pulse{display:none}}

/* ── THREE · THE INSTRUMENT ───────────────────────────────────────────────
   A five-axis frame, drawn and left unplotted. Rule 12 — the framework is
   Level A and the scores are not the reader's business — is therefore stated
   by the drawing itself rather than only in a footnote. */
.radar{max-width:740px;margin-inline:auto}

.rd__rings polygon{fill:none;stroke:var(--hairline);stroke-width:1}
.rd__edge{fill:none;stroke:var(--moss-edge);stroke-width:1.4}
.rd__spokes line{stroke:var(--steel-1);stroke-width:1;opacity:.6}
.rd__hub{fill:var(--ivory-deep)}
.rd__scale text{font-family:var(--font-mono);font-size:10px;
  fill:var(--text-faint)}

.rd__ax{font-size:11.5px;font-weight:600;letter-spacing:1.3px;
  fill:var(--text-strong)}
.rd__q{font-family:var(--font-body);font-style:italic;font-size:12px;
  fill:var(--text-secondary)}
.rd__ax--c{text-anchor:middle}
.rd__ax--s{text-anchor:start}
.rd__ax--e{text-anchor:end}

/* ── NARROW VIEWPORTS ─────────────────────────────────────────────────────
   Below 700px the wide figures cannot hold their label sets at a legible
   size. They are withdrawn rather than shrunk, and the sr-only lists that
   back them are promoted to visible text — the same content, set as records.
   A graphic that has to be pinched to be read is not a graphic. */
@media (max-width:700px){
  .fig .fig__svg:not(.radar){display:none}
  .life__glow{display:none}
  .fig .sr-only{position:static;width:auto;height:auto;overflow:visible;
    clip:auto;clip-path:none;white-space:normal;
    list-style:none;margin:var(--sp-3) 0 0;padding:0}
  .fig .sr-only li{font-family:var(--font-body);font-size:var(--fs-small);
    color:var(--text-secondary);line-height:1.6;
    padding:var(--sp-2) 0;border-top:1px solid var(--hairline)}
  .fig .sr-only li:last-child{border-bottom:1px solid var(--hairline)}
}

/* ── CADENCE AND DISCIPLINE ───────────────────────────────────────────────
   Functional layer. A term list in substance, set as a two-column record so
   the term is scannable and the explanation is readable, collapsing to
   stacked on narrow viewports rather than squeezing the second column. */
.doc--tight{margin-top:var(--sp-5)}

.cadence{list-style:none;margin:var(--sp-3) 0 var(--sp-4);padding:0}
.cadence li{display:grid;grid-template-columns:10.5rem 1fr;gap:var(--sp-3);
  padding:var(--sp-2) 0;border-top:1px solid var(--hairline)}
.cadence li:last-child{border-bottom:1px solid var(--hairline)}
.cadence b{font-family:var(--font-display);font-weight:600;font-size:var(--fs-small);
  color:var(--text-strong);letter-spacing:-.005em}
.cadence span{font-family:var(--font-body);font-size:var(--fs-small);
  color:var(--text-secondary);line-height:1.62}
@media (max-width:620px){
  .cadence li{grid-template-columns:1fr;gap:4px}
}


/* ---------- how-we-build.css ---------- */
/* ============================================================================
   HOW WE BUILD — sans register (D10-d)
   Session 7 · 12 August 2026

   The differentiator page. It reuses the discipline mosaic already ratified on
   Home rather than inventing a second one — the same four disciplines told at
   greater length should look like the same four disciplines, and a reader
   arriving here from the Home précis should recognise the component.

   What this file adds: the second paragraph inside each card (the mechanism),
   the cost grid, and one page-lead rule. Nothing else.
   ========================================================================= */

/* The page lead is .lead-h1, in base.css — three pages open on a statement
   rather than a hero (this one, Portfolio and Contact), so it is a shared
   component and does not live in a page stylesheet. */

/* THE MECHANISM. The sentence that turns a value statement into a discipline,
   and the reason this page exists rather than being three lines on Home.
   Set apart from the description above it by a rule, not by a colour —
   the mosaic already spends what treatment this page can afford. */
.hwb__disc .d{display:flex;flex-direction:column}
.d__mech{margin:var(--sp-3) 0 0;padding-top:var(--sp-2);
  border-top:1px solid var(--hairline)}
.d__mech b{font-family:var(--font-display);font-weight:600;color:var(--text)}

/* WHAT IT COSTS. Four honest paragraphs, set as plain records with no card
   treatment at all — this is the section a reader is most likely to suspect of
   being decorative, so it is the one section that is given nothing. */
.hwb__costs{display:grid;grid-template-columns:repeat(2,1fr);
  gap:var(--sp-4) var(--sp-5);margin:var(--sp-5) 0 0}
@media (max-width:760px){.hwb__costs{grid-template-columns:1fr;gap:var(--sp-4)}}
.hwb__costs article{border-top:1px solid var(--hairline);padding-top:var(--sp-2)}
.hwb__costs h3{font-family:var(--font-display);font-size:var(--fs-h3);font-weight:600;
  color:var(--text-strong);margin:0}
.hwb__costs p{font-family:var(--font-body);font-size:var(--fs-small);
  color:var(--text-secondary);line-height:1.65;margin:10px 0 0;max-width:52ch}

/* ══ D15 · THE CAPABILITY SURFACE — added 14 August 2026 ═══════════════════
   Six new sections turn this page from "the discipline" into the capability
   surface D15 requires. The two-layer rule binds throughout: the isolation
   figure is soul layer, everything else here is functional — sober, plain,
   high-contrast. A procurement reader is scanning, not being persuaded.

   No accent anywhere in this file. The page spends its one oxblood moment on
   the discipline mosaic above, and these sections are moss and steel so that
   stays unambiguous. CLAUDE.md rule 5.
   ========================================================================= */

/* ── ISOLATION FIGURE ─────────────────────────────────────────────────────
   Four customers, one platform, and the boundaries between them. The walls
   are the subject: the claim is not that customers exist, it is that a
   failure inside one of them stops at its edge. */
.iso__row{font-family:var(--font-display);font-size:10px;font-weight:600;
  letter-spacing:1.8px;fill:var(--text-faint)}
.iso__name{font-family:var(--font-display);font-size:11.5px;font-weight:600;
  letter-spacing:1.4px;fill:var(--text);text-anchor:middle}

.i-t{fill:var(--moss-2)}
.i-d{fill:url(#hDots)}
.i-e{fill:none;stroke:var(--moss-edge);stroke-width:1.2}

/* The failed cell. Drawn differently, not coloured differently — there is no
   accent available on this page, and a caution colour would be a product
   accent on a Stactray surface (rule 4). */
.iso__cell--out .i-t{fill:var(--steel-1);opacity:.45}
.i-x{fill:none;stroke:var(--ivory-dim);stroke-width:1.3;stroke-dasharray:5 4}
.iso__flag{font-family:var(--font-display);font-size:10px;font-weight:600;
  letter-spacing:1.8px;fill:var(--text-secondary);text-anchor:middle}

.iso__wall{stroke:var(--steel-2);stroke-width:1.1;stroke-dasharray:4 5;opacity:.7}
.iso__wall--held{stroke:var(--ivory-dim);stroke-width:1.4;stroke-dasharray:none;opacity:.85}

.iso__base{fill:var(--moss-1)}
.iso__base-d{fill:url(#hDots)}
.iso__base-e{fill:none;stroke:var(--moss-edge);stroke-width:1.3}
.iso__baselbl{font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:2.2px;fill:var(--text-secondary);text-anchor:middle}

@media (prefers-reduced-motion:no-preference){
  /* Uses the shared `settle` gesture and --dur-reveal from the motion spine
     in base.css. This file previously declared its own identical keyframe
     at its own duration, which is what made each page feel separately
     timed rather than part of one site (D15, 14 Aug 2026). */
  .iso__cell{opacity:0;transform:translateY(12px);
    animation:settle var(--dur-reveal) var(--ease-out) forwards;
    animation-delay:calc(var(--i) * var(--stagger))}
}

/* ── THE TRANSITION STEPS ─────────────────────────────────────────────────
   An ordered list, and the order is the argument — decommissioning last is
   the whole claim. Numbered by counter so the numeral is styled without a
   second element to carry it. */
.hwb__steps{list-style:none;counter-reset:step;margin:var(--sp-4) 0 0;padding:0;
  display:grid;gap:1px;background:var(--hairline);
  border-top:1px solid var(--hairline);border-bottom:1px solid var(--hairline)}
.hwb__steps li{counter-increment:step;background:var(--canvas);
  display:grid;grid-template-columns:2.6rem 9.5rem 1fr;gap:var(--sp-3);
  align-items:baseline;padding:var(--sp-3) 0}
.hwb__steps li::before{content:counter(step,decimal-leading-zero);
  font-family:var(--font-mono);font-size:var(--fs-caption);
  color:var(--ivory-deep);letter-spacing:.06em}
.hwb__steps b{font-family:var(--font-display);font-weight:600;
  font-size:var(--fs-small);color:var(--text-strong);letter-spacing:-.005em}
.hwb__steps span{font-family:var(--font-body);font-size:var(--fs-small);
  color:var(--text-secondary);line-height:1.62}
@media (max-width:700px){
  .hwb__steps li{grid-template-columns:2.4rem 1fr;row-gap:4px}
  .hwb__steps span{grid-column:2}
}


/* ---------- company.css ---------- */
/* ============================================================================
   COMPANY — archetype A4, the reading well (serif, D10-d)
   Session 7 · 12 August 2026

   Structure only. The shared reading well is in governance.css; the cadence
   record is in thesis.css and is reused here rather than restated.

   What this file owns: the founder record and its RESERVED PORTRAIT SLOT.
   ========================================================================= */

/* ── THE FOUNDER RECORD ───────────────────────────────────────────────────
   Two people, each with a slot, a name, a role and one line of accountability.
   Never a biography — CLAUDE.md rule 15 has no exception for biographies, and
   a founder card is exactly where an advisory mandate wants to appear. */
.ppl{list-style:none;margin:var(--sp-4) 0 0;padding:0;
  display:grid;grid-template-columns:repeat(2,1fr);gap:var(--sp-4)}
@media (max-width:720px){.ppl{grid-template-columns:1fr;gap:var(--sp-4)}}

.ppl__x{display:grid;grid-template-columns:96px 1fr;gap:var(--sp-3);
  align-items:start}
@media (max-width:420px){.ppl__x{grid-template-columns:72px 1fr;gap:var(--sp-2)}}

/* THE RESERVED SLOT — a documented empty state, not a missing image.
   A grey box with a broken-image icon says the page failed. A mark-bearing
   plate at the exact final dimensions says the photograph is not here yet and
   that this was decided rather than forgotten. When two portraits clear the
   Visual System §6 bar, an <img> goes inside this element and nothing else
   changes: same box, same aspect, same rhythm, no reflow.
   Deliberately flat — no glow, no gradient bloom. A4 takes none of that. */
.ppl__slot{aspect-ratio:1/1.12;border-radius:var(--radius);
  background:linear-gradient(180deg,var(--raised),var(--panel));
  box-shadow:var(--edge-rim);
  border:1px solid var(--hairline);
  display:flex;align-items:center;justify-content:center;
  overflow:hidden}
.ppl__slot svg{opacity:.28}
.ppl__slot img{width:100%;height:100%;object-fit:cover;display:block;
  filter:saturate(.82)}

.doc .ppl__t b{display:block;font-family:var(--font-display);font-weight:600;
  font-size:var(--fs-h3);color:var(--text-strong);letter-spacing:-.008em}
.doc .ppl__r{display:block;font-family:var(--font-display);font-size:var(--fs-caption);
  font-weight:600;text-transform:uppercase;letter-spacing:var(--track-eyebrow);
  color:var(--text-faint);margin-top:6px}
.doc .ppl__t p{font-size:var(--fs-small);color:var(--text-secondary);
  line-height:1.62;margin:12px 0 0;max-width:34ch}


/* ---------- contact.css ---------- */
/* ============================================================================
   CONTACT — sans register (D10-d)
   Session 7 · 12 August 2026

   D6: one address, two routes, differentiated by a pre-filled subject.
   No form and no form backend — a form is the moment a site starts collecting
   rather than stating, and this one has nothing it needs to collect. Keeping
   the risk profile flat is a design decision here, not a v1 limitation.

   Reworded 15 Aug 2026, when the Legal page dropped its review-status notice.
   This comment used to justify the no-form decision by that status, and the
   justification outlived it. Note the trap: CSS comments are NOT stripped by
   the build — unlike HTML comments — so site.css is a shipped surface, and the
   first attempt at this note failed the phrase sweep by quoting the very words
   it was removing.

   The route buttons and the fact plinth are both already ratified and are
   reused verbatim from base.css. This file adds the two-column route record
   and nothing that needs a new token.
   ========================================================================= */

.rt{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--sp-2);
  margin:var(--sp-5) 0 0}
@media (max-width:820px){.rt{grid-template-columns:1fr}}

.rt__c{background:linear-gradient(180deg,var(--raised),var(--panel));
  box-shadow:var(--edge-rim),0 20px 40px -28px rgba(0,0,0,.9);
  border-radius:4px;padding:var(--sp-4) var(--sp-3) var(--sp-3);
  display:flex;flex-direction:column}

.rt__k{font-family:var(--font-display);font-size:var(--fs-caption);font-weight:600;
  text-transform:uppercase;letter-spacing:var(--track-eyebrow);
  color:var(--text-faint);margin:0}

.rt__c p:not(.rt__k):not(.rt__m){font-family:var(--font-body);
  font-size:var(--fs-small);color:var(--text-secondary);line-height:1.65;
  margin:var(--sp-2) 0 0;max-width:44ch}

/* the button sits at the foot of the card, so two cards of unequal copy length
   still present their controls on one line */
.rt__c .route{margin:auto 0 0;align-self:flex-start}

/* the address in full, under the button — a reader who wants to copy it rather
   than open a mail client should not have to hover a link to find it */
.rt__m{font-family:var(--font-mono);font-size:var(--fs-caption);
  color:var(--text-subtle);margin:var(--sp-2) 0 0;
  word-break:break-word}

.rt__note{font-family:var(--font-body);font-size:var(--fs-small);
  color:var(--text-subtle);line-height:1.65;max-width:62ch;
  margin:var(--sp-4) 0 0}
.rt__note a,.rt__lnk{color:var(--text);text-decoration:underline;
  text-decoration-color:var(--rim-interactive);text-underline-offset:3px}
.rt__note a:hover,.rt__lnk:hover{color:var(--text-strong);
  text-decoration-color:var(--ember-300)}
