/* ==========================================================================
   Saphirion — compositions (web-consumable, deck-independent)
   Tokens + tone system + reusable compositions (cards / axes / tiles / steps /
   pipeline / quote / process-chain / promoted archetypes / type modifiers).
   Split out of motifs.css (qzw.1) so the website can reuse the component layer
   WITHOUT the 1920x1080 slide stage. Deck-only rules live in deck-shell.css.
   INVARIANT: no selector/value edits vs the original motifs.css.
   ========================================================================== */
@import url("./tokens.css?v=1779279148987");

/* ==========================================================================
   Tone system — the ONE place a palette tone becomes a colour.
   --------------------------------------------------------------------------
   Every toned composition (cards, bands, tiles, records, personas, the
   metric-band störer, tier ladders, column heads) reads ONE custom property,
   --sph-tone, for its accent — and --sph-tone-ink for a legible foreground
   when that tone is used as a SOLID FILL. These rules map each DS palette
   tone to its token (+ ink) once, so the SAME tone works on EVERY component:
   to make a new element tone-able, consume var(--sph-tone); no per-tone CSS,
   no gaps. This is the generalised form of the milestone-band --band trick.

   Two selectors per tone:
     • .sph-tone-<name>     — the explicit, forward-looking utility class.
     • [class*="--<name>"]  — back-compat: matches the per-component modifier
       classes the renderer already emits (.sph-metric-band--emerald,
       .sph-card-cream--slate, .sph-tier-ladder__band--brown, …). Audited for
       false positives across the whole stylesheet: the only non-tone class
       carrying a tone substring is .sph-mark--dark, which never reads
       --sph-tone, so the stray set is inert. ("--on-dark" does NOT contain
       the substring "--dark", so quote/tagline on-dark variants are safe.)

   Ink: white on the dark tones; raw Dark-Jungle-Green (never theme-flipped)
   on the light tones camel + sky, so a fill stays legible in both themes.
   steel→navy, wine→info, primary→brown are kept as palette aliases.         */
.sph-tone-slate,    [class*="--slate"]    { --sph-tone: var(--sph-accent-slate);    --sph-tone-ink: #fff; }
.sph-tone-navy,     [class*="--navy"]     { --sph-tone: var(--sph-accent-navy);     --sph-tone-ink: #fff; }
.sph-tone-emerald,  [class*="--emerald"]  { --sph-tone: var(--sph-accent-emerald);  --sph-tone-ink: #fff; }
.sph-tone-burgundy, [class*="--burgundy"] { --sph-tone: var(--sph-accent-burgundy); --sph-tone-ink: #fff; }
.sph-tone-earth,    [class*="--earth"]    { --sph-tone: var(--sph-accent-earth);    --sph-tone-ink: #fff; }
.sph-tone-camel,    [class*="--camel"]    { --sph-tone: var(--sph-accent-camel);    --sph-tone-ink: var(--sph-dark-jungle-green); }
.sph-tone-sky,      [class*="--sky"]      { --sph-tone: var(--sph-accent-sky);      --sph-tone-ink: var(--sph-dark-jungle-green); }
.sph-tone-info,     [class*="--info"]     { --sph-tone: var(--sph-info);            --sph-tone-ink: #fff; }
.sph-tone-dark,     [class*="--dark"]     { --sph-tone: var(--sph-dark);            --sph-tone-ink: #fff; }
.sph-tone-brown,    [class*="--brown"]    { --sph-tone: var(--sph-primary);         --sph-tone-ink: #fff; }
.sph-tone-primary,  [class*="--primary"]  { --sph-tone: var(--sph-primary);         --sph-tone-ink: #fff; }
.sph-tone-success,  [class*="--success"]  { --sph-tone: var(--sph-success);         --sph-tone-ink: #fff; }
.sph-tone-steel,    [class*="--steel"]    { --sph-tone: var(--sph-accent-navy);     --sph-tone-ink: #fff; }
.sph-tone-wine,     [class*="--wine"]     { --sph-tone: var(--sph-info);            --sph-tone-ink: #fff; }

/* Tone as TEXT colour — a text leaf with a `tone` type modifier (`text { tone brown { … } }`)
   carries `sph-tone-<name>` (sets --sph-tone above) plus this marker, which paints the glyphs in
   the tone. Foreground only; backgrounds/accents keep consuming --sph-tone their own way.        */
.sph-toned { color: var(--sph-tone); }

/* Cascade layer — see base.css / README.md §2.2. */
@layer base, components, motifs, projection, utilities;


@layer motifs {


/* ==========================================================================
   Slide chrome — eyebrow, top-right mark, bottom-left footer
   Sits on every content slide. The --on-dark modifier (or wrapping the slide
   in a .sph-on-dark host) flips colours for cover/divider/closing slides.
   ========================================================================== */
.sph-chrome-eyebrow {
  position: absolute;
  top: 56px;
  left: var(--sph-slide-pad-x);
  font: 600 16px/1 var(--sph-font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sph-primary);
}
.sph-chrome-mark {
  position: absolute;
  top: 48px;
  right: var(--sph-slide-pad-x);
  height: 40px;
  width: auto;
  opacity: 0.85;
  /* Dark theme: the light archetypes' mark is the BLACK ensemble <img>;
     CSS can't swap an img src, but the asset is monochrome, so the dark
     flip (tokens.css) sets --sph-mark-filter: invert(1) to turn it white.
     Unset everywhere else — bookends keep their white mark untouched.     */
  filter: var(--sph-mark-filter, none);
}
.sph-chrome-footer {
  position: absolute;
  bottom: 48px;
  left: var(--sph-slide-pad-x);
  font: 600 14px/1 var(--sph-font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sph-text-muted);
  display: inline-flex;
  align-items: center;
  gap: 12px;
}
/* Segment separator — the brand's geometric square, the same shape language
   as the list bullets and the .dvi-dot tagline separator. Never round. */
.sph-chrome-footer .dot {
  display: inline-block;
  width: 5px; height: 5px;
  background: var(--sph-primary);
}
/* Print-only timestamp — sits on the footer baseline at the right slide edge,
   opposite the bottom-left footer chrome. Empty and hidden on screen; the
   runtime fills the text and `@media print` reveals it (see deck-stage.js
   _printStamp / _onBeforePrint). Theme-aware colour so it reads on a dark
   print too. */
.sph-chrome-stamp {
  position: absolute;
  bottom: 48px;
  right: var(--sph-slide-pad-x);
  font: 600 12px/1 var(--sph-font-display);
  letter-spacing: 0.08em;
  font-variant-numeric: tabular-nums;
  color: var(--sph-text-muted);
  display: none;
}
@media print {
  .sph-chrome-stamp { display: block; }
}

/* ---- Plus marks ---------------------------------------------------------- */
/* Outline "+" on cream, used ONLY on dark backgrounds.
   Usage: <span class="sph-plus"></span> — position absolutely.              */
.sph-plus {
  position: absolute;
  width: 28px; height: 28px;
  display: inline-block;
  pointer-events: none;
}
.sph-plus::before,
.sph-plus::after {
  content: "";
  position: absolute;
  background: var(--sph-secondary);
  border-radius: 1px;
}
.sph-plus::before { left: 50%; top: 0; bottom: 0; width: 2.5px; transform: translateX(-50%); }
.sph-plus::after  { top: 50%; left: 0; right: 0; height: 2.5px; transform: translateY(-50%); }
.sph-plus--lg { width: 44px; height: 44px; }
.sph-plus--lg::before { width: 3px; }
.sph-plus--lg::after  { height: 3px; }
.sph-plus--sm { width: 18px; height: 18px; }
.sph-plus--sm::before { width: 1.5px; }
.sph-plus--sm::after  { height: 1.5px; }

/* ---- Wave (thin cream curve, dark slides only) -------------------------- */
.sph-wave {
  position: absolute;
  inset: auto 0 0 0;
  height: 240px;
  width: 100%;
  pointer-events: none;
}
.sph-wave::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1920 240' preserveAspectRatio='none'><path d='M0 180 C 360 60, 720 240, 1080 140 S 1680 80, 1920 200' stroke='%23E8E4CB' stroke-width='1.5' fill='none' opacity='0.55'/></svg>");
  background-size: 100% 100%;
  background-repeat: no-repeat;
}

/* ---- Card system — three archetypes ------------------------------------ */

/* Shared shell + text slots for the two stacked-card archetypes — the accent
   card (a) and the cream card (b). Defined ONCE here so the two never drift
   apart; each archetype below overrides only what genuinely differs (surface,
   border, body colour). This is the DS "define-once" rule — shared
   declarations live in one grouped selector, never hand-copied per variant.
   See README.md §2.1.

   Deck-floor sizing (1920×1080): title 22/700, body 18/500/1.5,
   meta 14/600 uppercase. Card is a flex column so it stretches cleanly
   inside `align-items: stretch` row grids. See README §4 "Card text
   floors (decks)".                                                          */
.sph-card-accent,
.sph-card-cream {
  display: flex;
  flex-direction: column;
  padding: 32px 36px;
}
.sph-card-accent__eyebrow,
.sph-card-cream__eyebrow {
  font: 600 14px/1 var(--sph-font-display);
  color: var(--sph-text-soft);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  /* Tight eyebrow·title·content rhythm. In a card-row subgrid the grid row-gap
     owns the spacing (this margin is zeroed there); standalone cards use it. */
  margin: 0 0 8px;
}
.sph-card-accent__title,
.sph-card-cream__title {
  font: 700 22px/1.25 var(--sph-font-display);
  /* Title takes the tone (tone-system layer) for both card families; the
     default stays Info. Cream's marker / title-tone rules still override
     this later in the file where those modifiers are present. */
  color: var(--sph-tone, var(--sph-info));
  letter-spacing: -0.005em;
  margin: 0 0 8px;
}
.sph-card-accent__body,
.sph-card-cream__body {
  font: 500 18px/1.5 var(--sph-font-display);
  margin: 0;
}
/* Lateral aside note (28e.3): a secondary caveat placed BESIDE the card's primary
   content (bullets / text), on the cream + banded families. The `__split` grid is
   emitted only when an `aside` is authored — a plain card is unchanged — so it
   collapses gracefully to nothing otherwise. `__main` holds the normal slots (they
   keep their margin-based rhythm); the note reads as a muted italic side column. */
.sph-card-cream__split,
.sph-card-banded__split {
  display: grid;
  grid-template-columns: 1fr minmax(0, var(--sph-card-aside-col, 34%));
  column-gap: 20px;
  align-items: start;
}
.sph-card-cream__main,
.sph-card-banded__main { min-width: 0; }
.sph-card-cream__aside,
.sph-card-banded__aside {
  min-width: 0;
  padding-left: 20px;
  border-left: 2px solid var(--sph-border);
  font: italic 500 var(--sph-fs-sm)/var(--sph-lh-normal) var(--sph-font-body);
  color: var(--sph-text-muted);
}
/* Content layout type — `align baseline` drops the whole content block to the card floor (it
   grows UPWARD as the copy lengthens), the look the d2v-process row uses. The default
   (`topline`) keeps content at the top. The card is a flex column, so this is one knob on the
   content block, independent of the marker style. */
.sph-card-cream--baseline,
.sph-card-accent--baseline { justify-content: flex-end; }

/* (a) Accent-bordered card: thin solid left border on a white body, coloured
   title. Default border + title colour = Lonestar (Info); modifier classes
   map to the official Saphirion accents.                                    */
.sph-card-accent {
  position: relative;
  background: var(--sph-white);
  border-left: 4px solid var(--sph-tone, var(--sph-info));
  box-shadow: 0 1px 3px rgba(29,40,55,.06);
}
.sph-card-accent__body { color: var(--sph-text-soft); }
/* Left border + title take the tone (tone-system layer): every palette tone,
   not just the seven that used to have hand-written rules. The default (no
   tone) stays Info, matching the border above. */

/* (b) Cream block — Loafer-filled rect with an optional accent-coloured border.
   Shares the shell + text slots defined above with the accent card.

   The accent colour is carried by the `--sph-cream-accent` custom property,
   so the same tonal modifier (`--info`, `--brown`, …) drives whichever edge
   the structural modifier picks:
     • default            — 4 px accent border on the LEFT
     • `--topband`        — 4 px accent border on the TOP   (see README §6)
     • `--invert`         — dark surface, light text        (emphasis column)
   Combine freely, e.g. `sph-card-cream sph-card-cream--topband
   sph-card-cream--invert sph-card-cream--info`.                              */
.sph-card-cream {
  /* The cream accent IS the tone (tone-system layer); every palette tone
     drives the border, the title, the bullets, the audience pill, and —
     through --sph-card-tone below — the marker/title-tone chrome. */
  --sph-cream-accent: var(--sph-tone, transparent);
  /* background set via --sph-card-surface (card axes block below) */
  border-left: 4px solid var(--sph-cream-accent);
  color: var(--sph-text);
}
.sph-card-cream__body { color: var(--sph-text); }
/* Tonal modifiers now come from the tone-system layer (top of file): a
   .sph-card-cream--<tone> sets --sph-tone, which the base rule above turns
   into --sph-cream-accent, so all palette tones work (camel, sky, navy, …). */

/* Structural modifier — accent on the TOP edge instead of the left.          */
.sph-card-cream--topband {
  border-left: 0;
  border-top: 4px solid var(--sph-cream-accent);
}

/* Structural modifier — inverted (dark) surface for an emphasis column,
   e.g. the "tomorrow" stage of a `.sph-row-flow`. Placed after the
   tonal rules so the light title wins; the accent border still tracks the
   tonal modifier.                                                            */
.sph-card-cream--invert {
  background: var(--sph-dark);
  color: var(--sph-text-on-dark);
}
.sph-card-cream--invert .sph-card-cream__eyebrow,
.sph-card-cream--surface-inverse .sph-card-cream__eyebrow  { color: var(--sph-loafer); }
.sph-card-cream--invert .sph-card-cream__body,
.sph-card-cream--surface-inverse .sph-card-cream__body  { color: var(--sph-loafer); }
/* inverse title colour lives in the card-axes block below, so it can win
   over the §2.2 marker ink-reset that comes after these tonal rules.       */

/* Structural modifier — centre the card's content block vertically. Useful
   for content-light cards (a label + a short quote) that would otherwise
   sit at the top of a tall, stretched grid cell.                             */
.sph-card-cream--center { justify-content: center; }

/* Legacy aliases (wine→info, steel→navy) are handled by the tone-system layer. */

/* Card stat — a big-number line that pins to the BOTTOM of a flex card
   (margin-top:auto), divided from the content above by a hairline rule.
   Drop it in as the last child of a `.sph-card-cream` / `.sph-card-accent`.
   Markup: <div class="sph-card-stat sph-card-stat--info"><b>€5–30k</b>
           <span>per-seat ceiling</span></div>

   Wrap contract — the row is a `<b>` headline number + a `<span>` caption:
     • <b>   is ATOMIC — `white-space:nowrap` + `flex-shrink:0`. The number
              is the stat; it must never break (e.g. `€250k–€1m` splitting
              at the en-dash) and must never be squeezed by a long caption.
     • <span> is the FLEXIBLE member — it absorbs the leftover width and
              wraps to a second line when the caption is long. It always
              reserves a 2-line slot (`min-height`), so a 1-line caption
              and a 2-line caption produce an IDENTICAL stat-block height.
   Three consequences, all deliberate:
     1) the number never breaks and never shrinks (see <b> above);
     2) because every stat block is the same height, the bottom-pinned
        numbers stay baseline-aligned ACROSS a compare pair even when one
        caption wraps and the other does not — the layout stays balanced;
     3) the row uses `align-items: center` (not baseline) so the big number
        VERTICALLY centres on the caption block — a 1-line caption sits
        at the number's vertical midpoint, a 2-line caption sandwiches the
        number top-to-bottom. The visual reads as one balanced row in both
        cases. Baseline alignment would make a wrapped caption's second
        line hang below the number, looking unbalanced (the Slide-4 bug).
   Keep stat captions to AT MOST 2 lines. See README §6 "Card stat".        */
.sph-card-stat {
  margin-top: auto;
  display: flex;
  align-items: center;   /* number vertically centres on the caption block */
  gap: var(--sph-space-3);
  padding-top: var(--sph-space-5);
  border-top: 1px solid var(--sph-border);
}
.sph-card-stat b {
  font: 800 var(--sph-fs-3xl)/1 var(--sph-font-display);
  letter-spacing: -0.02em;
  color: var(--sph-tone, var(--sph-dark));
  white-space: nowrap;   /* the headline number is atomic — never break it */
  flex-shrink: 0;        /* …and never let a long caption squeeze it       */
}
.sph-card-stat span {
  font: 600 14px/1.3 var(--sph-font-display);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--sph-text-soft);
  min-width: 0;          /* the caption is the flexible member — may wrap   */
  min-height: 2.6em;     /* reserve a 2-line slot (2 × 1.3 line-height): a  */
                         /* 1-line and a 2-line caption then yield the same */
                         /* stat height, so paired numbers stay aligned     */
  display: flex;
  flex-direction: column;
  justify-content: center; /* caption block centres inside its 2-line slot */
}
/* The headline number takes the tone (tone-system layer) — all tones. */
.sph-card-cream--invert .sph-card-stat { border-top-color: rgba(255,255,255,.18); }
.sph-card-cream--invert .sph-card-stat span { color: var(--sph-loafer); }

/* (c) Banded card — full-width coloured band at top, white card below.
   Default band = San Juan slate. Modifier classes pick another official
   accent. Use sparingly — the band is loud.                                  */
.sph-card-banded {
  background: var(--sph-white);
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(29,40,55,.06);
}
.sph-card-banded__head {
  background: var(--sph-tone, var(--sph-accent-slate));
  color: var(--sph-tone-ink, #fff);
  padding: 14px 22px;
  font: 700 16px/1.2 var(--sph-font-display);
  letter-spacing: -0.005em;
}
.sph-card-banded__body {
  margin: 0;
  padding: 20px 22px;
  font: 500 15px/1.4 var(--sph-font-display);
  color: var(--sph-text-soft);
}
/* Band fill is the tone — every palette tone via the tone-system layer. */

/* Banded-card list — rows with alternating cream/white stripes               */
.sph-card-banded__row {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 16px;
  padding: 14px 22px;
  align-items: baseline;
  border-top: 1px solid var(--sph-border);
}
.sph-card-banded__row:nth-child(even) { background: var(--sph-bg-callout); }
.sph-card-banded__row b {
  font: 700 15px/1.3 var(--sph-font-display);
  color: var(--sph-text);
}
.sph-card-banded__row p {
  margin: 0;
  font: 500 14px/1.4 var(--sph-font-display);
  color: var(--sph-text-soft);
}

/* ---- Card axes — surface × marker × tone --------------------------------
   Contract: notes/card-axes-design-v1.md. Tone modifiers above set
   --sph-cream-accent; the carriers read it. Surfaces set ONLY
   --sph-card-surface; each marker sets ONLY its own geometry; a card
   carries exactly ONE marker. Markers are REAL child elements (compiler
   emits them; hand-authored decks copy the one-liner) — PPTX-safe.        */
.sph-card-cream {
  --sph-card-tone: var(--sph-tone, var(--sph-cream-accent));
  --sph-card-surface: var(--sph-bg-muted);
  --sph-card-tone-tint: color-mix(in srgb, var(--sph-card-tone) 8%, var(--sph-white));
  --sph-card-tone-line: color-mix(in srgb, var(--sph-card-tone) 25%, var(--sph-white));
  background: var(--sph-card-surface);
}
.sph-card-cream--invert { --sph-card-surface: var(--sph-dark); }

/* Surfaces — carrier only. (cream is the base default; the explicit class
   is provided so an authored `surface cream` / stamped deck default has a
   real rule rather than a dead class.)                                     */
.sph-card-cream--surface-white   { --sph-card-surface: var(--sph-surface); }
.sph-card-cream--surface-cream   { --sph-card-surface: var(--sph-bg-muted); }
.sph-card-cream--surface-inverse { --sph-card-surface: var(--sph-dark); }
.sph-card-cream--surface-tint {
  --sph-card-surface: var(--sph-card-tone-tint);
  border: 1px solid var(--sph-card-tone-line);
}
/* Title colour policy (spec §2.2 — quiet by default). The tonal modifier
   rules above colour the title with the tone (kept for the bar/bar-top
   back-compat default). These rules layer the axes behaviour on top, each
   later rule winning where it should:
     1. new markers reset the title to ink (quiet);
     2. tint surfaces + `title-tone on` re-tone it;
     3. `title-tone off` forces ink even on tint / bar;
     4. inverse surfaces always carry a white title.                        */
.sph-card-cream--marker-seal  .sph-card-cream__title,
.sph-card-cream--marker-rule  .sph-card-cream__title,
.sph-card-cream--marker-frame .sph-card-cream__title,
.sph-card-cream--marker-tab   .sph-card-cream__title,
.sph-card-cream--marker-rail  .sph-card-cream__title,
.sph-card-cream--marker-folio .sph-card-cream__title,
.sph-card-cream--marker-chain .sph-card-cream__title,
.sph-card-cream--marker-none  .sph-card-cream__title { color: var(--sph-ink); }
.sph-card-cream--surface-tint .sph-card-cream__title,
.sph-card-cream--title-tone   .sph-card-cream__title { color: var(--sph-card-tone); }
.sph-card-cream--title-tone-off .sph-card-cream__title { color: var(--sph-ink); }
.sph-card-cream--invert          .sph-card-cream__title,
.sph-card-cream--surface-inverse .sph-card-cream__title { color: #fff; }

/* Markers — every non-bar marker retires the legacy left bar. */
.sph-card-cream--marker-none,  .sph-card-cream--marker-seal,
.sph-card-cream--marker-rule,  .sph-card-cream--marker-frame,
.sph-card-cream--marker-tab,   .sph-card-cream--marker-rail,
.sph-card-cream--marker-folio, .sph-card-cream--marker-chain { border-left: 0; }
.sph-card-cream--marker-bar     { border-left: 4px solid var(--sph-card-tone); }
.sph-card-cream--marker-bar-top { border-left: 0; border-top: 4px solid var(--sph-card-tone); }

/* seal — the brand square centred on the top hairline. */
.sph-card-cream--marker-seal { position: relative; border: 1px solid var(--sph-border); }
.sph-card-cream__seal {
  position: absolute;
  top: calc((var(--sph-card-seal-size) + 1px) / -2);
  left: var(--sph-card-seal-inset);
  width: var(--sph-card-seal-size); height: var(--sph-card-seal-size);
  background: var(--sph-card-tone);
}

/* rule — the ledger baseline. Pairs with .sph-card-stat (hairline above
   the stat + this 2px tone rule = the accountant's double line). */
.sph-card-cream--marker-rule { border-bottom: 2px solid var(--sph-card-tone); }

/* frame — inner hairline (passe-partout); meta sits ON the top line. */
.sph-card-cream--marker-frame {
  position: relative;
  padding: calc(var(--sph-card-frame-inset) + 22px) calc(var(--sph-card-frame-inset) + 26px);
}
.sph-card-cream__frame {
  position: absolute; inset: var(--sph-card-frame-inset);
  border: 1px solid color-mix(in srgb, var(--sph-ink) 26%, var(--sph-card-surface));
  pointer-events: none;
}
.sph-card-cream--marker-frame .sph-card-cream__eyebrow {
  position: absolute;
  top: calc(var(--sph-card-frame-inset) - 7px);   /* meta is 14px/1 → half */
  left: calc(var(--sph-card-frame-inset) + 16px);
  background: var(--sph-card-surface);
  padding: 0 8px; margin: 0;
}

/* tab — the meta as a dossier tab on the card's top edge. Structural:
   compiler wraps card + tab in .sph-card-tabwrap (grid cell = wrapper). */
.sph-card-tabwrap { display: flex; flex-direction: column; }
.sph-card-cream__tab {
  align-self: flex-start;
  margin-left: var(--sph-card-seal-inset);
  /* The tab is a SIBLING of the card, so it can't inherit the card's
     --sph-card-tone. The compiler emits the tone class on the chip itself;
     re-resolving the carrier HERE means the accent is computed on this
     element, so the dark-theme authored-island pin (tokens.css) keeps the
     white-on-tone contrast instead of the brightness-lifted accent.        */
  --sph-card-tone: var(--sph-tone, var(--sph-cream-accent));
  background: var(--sph-card-tone); color: var(--sph-white);
  font: 600 14px/1 var(--sph-font-display);
  letter-spacing: 0.14em; text-transform: uppercase;
  padding: 10px 16px;
}
.sph-card-cream--marker-tab { border: 1px solid var(--sph-border); flex: 1; }

/* rail — structural tone column with an outlined number square. */
.sph-card-cream--marker-rail { flex-direction: row; padding: 0; border: 1px solid var(--sph-border); }
.sph-card-cream__rail {
  width: var(--sph-card-rail-w); flex-shrink: 0;
  /* Tone class is emitted on the column too, so the accent resolves HERE —
     same authored-island contrast guarantee as the tab chip (and as
     .sph-card-banded__head, spec §8.7).                                     */
  --sph-card-tone: var(--sph-tone, var(--sph-cream-accent));
  background: var(--sph-card-tone);
  display: flex; justify-content: center; padding-top: 16px;
}
.sph-card-cream__rail-num {
  width: 24px; height: 24px; text-align: center;
  border: 1px solid rgba(255,255,255,.55);
  color: var(--sph-white);
  font: 700 12px/22px var(--sph-font-display);
}
.sph-card-cream__rail-body {
  padding: 32px 36px;
  display: flex; flex-direction: column; flex: 1; min-width: 0;
}

/* folio — ultra-light numeral, top-right. It is a WATERMARK: the card isolates its own
   stacking context and the numeral sits on a negative layer — above the card surface,
   below the in-flow content — so wrapped eyebrow/title/body text paints over the numeral,
   never under it (a positioned auto-z child otherwise paints on top of in-flow text).
   This also matches PPTX export, where the first-child numeral is the lowest shape.
   Content alignment is a SEPARATE concern: add `align baseline` for the
   drop-to-the-floor look; it is no longer implied by this marker. */
.sph-card-cream--marker-folio { position: relative; isolation: isolate; }
.sph-card-cream__folio {
  position: absolute; top: 8px; right: 20px;
  z-index: -1;
  font: 200 var(--sph-card-folio-fs)/1 var(--sph-font-display);
  letter-spacing: -0.02em;
  color: color-mix(in srgb, var(--sph-ink) 11%, var(--sph-card-surface));
}

/* chain — the cover watermark echoed at card scale (inline SVG child,
   strokes via currentColor, circle fill = the card surface). Watermark
   layer: same isolate + negative-z treatment as the folio numeral, so
   baseline-aligned or long content paints over it, never under it. */
.sph-card-cream--marker-chain { position: relative; isolation: isolate; }
.sph-card-cream__chain {
  position: absolute; right: 12px; bottom: 10px;
  z-index: -1;
  width: 128px; height: 44px; pointer-events: none;
  color: color-mix(in srgb, var(--sph-ink) 22%, var(--sph-card-surface));
}

/* ---- Numbered step list (steps · step) --------------------------------- */
/* An ordered numbered list of steps with a numbered marker per row, an optional
   vertical connector rail (--connected), and per-step tone via --step-accent
   (any palette tone; 'success' is the green completion step). */
.sph-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
  position: relative;
}
.sph-step {
  display: grid;
  grid-template-columns: 36px 1fr;
  gap: 16px;
  align-items: start;
  padding: 10px 0;
  position: relative;
  z-index: 1;
}
.sph-step__num {
  width: 36px; height: 36px;
  background: var(--step-accent, var(--sph-accent-slate));
  color: #fff;
  font: 600 16px/1 var(--sph-font-display);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  position: relative;
  z-index: 1;
}
.sph-step__body {
  font: 500 16px/1.4 var(--sph-font-display);
  color: var(--sph-text);
  padding-top: 4px;
}
/* Connector rail: one continuous line behind the markers, from the first marker
   centre to the last; the opaque markers sit above it (z-index) so only the
   segments between them show. */
.sph-steps--connected::before {
  content: "";
  position: absolute;
  left: 17px;
  top: 28px;
  bottom: 28px;
  width: 2px;
  background: var(--sph-border);
  z-index: 0;
}

/* ---- Stat tile (cream, big number) ------------------------------------ */
.sph-tile {
  background: var(--sph-bg-muted);
  padding: 28px 22px;
  text-align: center;
  color: var(--sph-text);
}
.sph-tile__value {
  font: 800 64px/1 var(--sph-font-display);
  letter-spacing: -0.02em;
  margin: 0;
  color: var(--sph-dark);
}
.sph-tile__label {
  font: 600 14px/1.3 var(--sph-font-display);
  color: var(--sph-text);
  margin-top: 12px;
}

/* ---- Pipeline — 7-stage status -------------------------------------- */
.sph-pipeline {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 8px;
  align-items: stretch;
}
.sph-pipeline__stage {
  background: var(--sph-light);
  color: #fff;
  padding: 14px 12px;
  font: 700 13px/1 var(--sph-font-display);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: center;
  position: relative;
}
.sph-pipeline__stage::after {
  content: "›";
  position: absolute;
  right: -12px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 18px;
  color: var(--sph-text-muted);
  background: var(--sph-bg);
  padding: 0 3px;
  font-weight: 400;
}
.sph-pipeline__stage:last-child::after { content: none; }
/* Pipeline — 7 stages mapped to the official palette progression:
   Smoke → San Juan → Navy → Donkey Brown → Amazon → Kelly Green → Burgundy.
   The journey moves from neutral to brand-led to positive to terminal.       */
.sph-pipeline__stage--smoke    { background: var(--sph-smoke); }
.sph-pipeline__stage--slate    { background: var(--sph-accent-slate); }
.sph-pipeline__stage--navy     { background: var(--sph-accent-navy); }
.sph-pipeline__stage--brown    { background: var(--sph-primary); }
.sph-pipeline__stage--emerald  { background: var(--sph-accent-emerald); }
.sph-pipeline__stage--success  { background: var(--sph-success); }
.sph-pipeline__stage--burgundy { background: var(--sph-accent-burgundy); }
.sph-pipeline__stage--neutral  { background: #9C9C9C; }

/* Legacy aliases */
.sph-pipeline__stage--gray    { background: var(--sph-smoke); }
.sph-pipeline__stage--steel   { background: var(--sph-accent-navy); }
.sph-pipeline__stage--forest  { background: var(--sph-accent-emerald); }
.sph-pipeline__stage--mid     { background: var(--sph-success); }
.sph-pipeline__stage--bright  { background: var(--sph-success); }

/* ---- Num-tile — eyebrow + title + body in a vertical stack -------------
   Used in card grids on content slides (3, 5, 6, 11 of the D2V deck).
   Title and body share size (22 px); weight does the hierarchy work.
   See README §4 "Same-size + weight hierarchy".                            */
.num-tile {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 30px 24px;   /* tightened to the accent bar for a harmonic gap */
  background: var(--sph-white);
  border-left: 4px solid var(--sph-tone, var(--sph-primary));
  color: var(--sph-text);
}
.num-tile__num {
  font: 700 16px/1 var(--sph-font-display);
  color: var(--sph-tone, var(--sph-primary));
  text-transform: uppercase;
  letter-spacing: 0.18em;
  margin: 0;
}
.num-tile__title {
  font: 700 22px/1.25 var(--sph-font-display);
  color: var(--sph-dark);
  letter-spacing: -0.01em;
  margin: 0;
}
.num-tile__body {
  font: 500 22px/1.5 var(--sph-font-display);
  color: var(--sph-text-soft);
  margin: 0;
}
/* Tonal variants */
.num-tile--cream     { background: var(--sph-bg-muted); }
/* Accent bar + number take the tone (tone-system layer) — all tones. */

/* ---- Big quote — oversized statement copy, used as slide proposition ----
   Default sits at display size with tight leading. The `--longform`
   modifier loosens leading for multi-clause sentences where each phrase
   wants its own line via explicit <br>. See README §8 "Vertical typography
   rhythm on dense slides".                                                  */
.big-quote {
  font: 800 clamp(36px, 4.4vw, 56px)/1.15 var(--sph-font-display);
  letter-spacing: -0.01em;
  color: var(--sph-dark);
  margin: 0;
}
.big-quote em,
.big-quote .em {
  color: var(--sph-info);
  font-style: normal;
}
.big-quote--longform {
  font-size: clamp(28px, 3.2vw, 40px);
  line-height: 1.32;
}
.big-quote--on-dark { color: #fff; }

/* ==========================================================================
   DATA2VALUE process chain — the 5-bubble signature
   The geometry lives in `assets/graphics/data2value-process.svg`. The SVG
   is structured as labeled groups (id attributes you can target from
   CSS or JS):

     #raw-data-circle, #partfinder-circle, #nlpp-circle,
     #opportunity-tracker-circle, #pl-circle       — the five circles
     #raw-data-icon, #partfinder-icon, #nlpp-icon,
     #opportunity-tracker-icon, #pl-icon           — the icon glyphs
     #raw-data-arrow, #partfinder-arrow, #nlpp-arrow,
     #opportunity-tracker-arrow                     — the connector arrows
     #data2value-bubble-line                        — the bottom flow line

   USAGE — highlight a single step
   ---------------------------------------------------------------------------
   Inline the SVG (or load with <object> / <svg><use>) and add a `data-active`
   attribute on the host. The CSS below recolours the matching `*-circle`
   and `*-arrow` groups and the icon to the chosen accent.

     <div class="sph-process-chain" data-active="nlpp">
       <!-- inlined contents of data2value-process.svg -->
     </div>

   Supported values — use the step name without `-circle`:
     raw-data · partfinder · nlpp · opportunity-tracker · pl

   USAGE — highlight multiple steps in different colours
   ---------------------------------------------------------------------------
   Skip `data-active` and use the per-step `data-tone-*` attributes instead.
   Each tone token maps to a brand colour (see list further down):

     <div class="sph-process-chain"
          data-tone-raw-data="primary"
          data-tone-partfinder="slate"
          data-tone-nlpp="info"
          data-tone-opportunity-tracker="emerald"
          data-tone-pl="burgundy">
       ...inlined SVG...
     </div>

   Tones: primary · info · slate · emerald · burgundy · navy · success · dark.
   Defaults: all circles + arrows fill --sph-dark-jungle-green with --sph-loafer icons.
   ========================================================================== */

.sph-process-chain {
  display: inline-block;
  position: relative;
  line-height: 0;        /* avoid baseline gap below inline SVG             */
}
.sph-process-chain svg { display: block; width: 100%; height: auto; }

/* Default colouring. The SVG's circle, arrow and icon shapes are filled
   with `currentColor`, so setting the `color` property on their groups
   recolours them. Every rule below pairs a step's `*-arrow` with its
   `*-circle`, so an arrow always tracks its own circle's colour. The
   bubble-line keeps its own static cream fill. Defaults: every circle +
   arrow = Dark Jungle Green; every icon = Loafer-cream.                  */
.sph-process-chain [id$="-circle"],
.sph-process-chain [id$="-arrow"] { color: var(--sph-dark);   transition: color var(--sph-dur-base) var(--sph-ease-standard); }
.sph-process-chain [id$="-icon"] { color: var(--sph-loafer); transition: color var(--sph-dur-base) var(--sph-ease-standard); }

/* ---- Single-step highlight via [data-active] ---------------------------- */
.sph-process-chain[data-active="raw-data"]            :is(#raw-data-circle, #raw-data-arrow),
.sph-process-chain[data-active="partfinder"]          :is(#partfinder-circle, #partfinder-arrow),
.sph-process-chain[data-active="nlpp"]                :is(#nlpp-circle, #nlpp-arrow),
.sph-process-chain[data-active="opportunity-tracker"] :is(#opportunity-tracker-circle, #opportunity-tracker-arrow),
.sph-process-chain[data-active="pl"]                  #pl-circle { color: var(--sph-primary); }
.sph-process-chain[data-active="raw-data"]            #raw-data-icon,
.sph-process-chain[data-active="partfinder"]          #partfinder-icon,
.sph-process-chain[data-active="nlpp"]                #nlpp-icon,
.sph-process-chain[data-active="opportunity-tracker"] #opportunity-tracker-icon,
.sph-process-chain[data-active="pl"]                  #pl-icon { color: #fff; }

/* ---- Per-step tone tokens (different colour per bubble) ----------------- */
/* For each (step, tone) pair, recolour the step group + its icon. When ANY
   tone is set on a step, its icon flips to white for contrast; the `dark`
   tone is the exception and keeps the Loafer-cream icon.                   */
.sph-process-chain[data-tone-raw-data="primary"]            :is(#raw-data-circle, #raw-data-arrow),
.sph-process-chain[data-tone-partfinder="primary"]          :is(#partfinder-circle, #partfinder-arrow),
.sph-process-chain[data-tone-nlpp="primary"]                :is(#nlpp-circle, #nlpp-arrow),
.sph-process-chain[data-tone-opportunity-tracker="primary"] :is(#opportunity-tracker-circle, #opportunity-tracker-arrow),
.sph-process-chain[data-tone-pl="primary"]                  #pl-circle { color: var(--sph-primary); }

.sph-process-chain[data-tone-raw-data="info"]            :is(#raw-data-circle, #raw-data-arrow),
.sph-process-chain[data-tone-partfinder="info"]          :is(#partfinder-circle, #partfinder-arrow),
.sph-process-chain[data-tone-nlpp="info"]                :is(#nlpp-circle, #nlpp-arrow),
.sph-process-chain[data-tone-opportunity-tracker="info"] :is(#opportunity-tracker-circle, #opportunity-tracker-arrow),
.sph-process-chain[data-tone-pl="info"]                  #pl-circle { color: var(--sph-info); }

.sph-process-chain[data-tone-raw-data="slate"]            :is(#raw-data-circle, #raw-data-arrow),
.sph-process-chain[data-tone-partfinder="slate"]          :is(#partfinder-circle, #partfinder-arrow),
.sph-process-chain[data-tone-nlpp="slate"]                :is(#nlpp-circle, #nlpp-arrow),
.sph-process-chain[data-tone-opportunity-tracker="slate"] :is(#opportunity-tracker-circle, #opportunity-tracker-arrow),
.sph-process-chain[data-tone-pl="slate"]                  #pl-circle { color: var(--sph-accent-slate); }

.sph-process-chain[data-tone-raw-data="emerald"]            :is(#raw-data-circle, #raw-data-arrow),
.sph-process-chain[data-tone-partfinder="emerald"]          :is(#partfinder-circle, #partfinder-arrow),
.sph-process-chain[data-tone-nlpp="emerald"]                :is(#nlpp-circle, #nlpp-arrow),
.sph-process-chain[data-tone-opportunity-tracker="emerald"] :is(#opportunity-tracker-circle, #opportunity-tracker-arrow),
.sph-process-chain[data-tone-pl="emerald"]                  #pl-circle { color: var(--sph-accent-emerald); }

.sph-process-chain[data-tone-raw-data="burgundy"]            :is(#raw-data-circle, #raw-data-arrow),
.sph-process-chain[data-tone-partfinder="burgundy"]          :is(#partfinder-circle, #partfinder-arrow),
.sph-process-chain[data-tone-nlpp="burgundy"]                :is(#nlpp-circle, #nlpp-arrow),
.sph-process-chain[data-tone-opportunity-tracker="burgundy"] :is(#opportunity-tracker-circle, #opportunity-tracker-arrow),
.sph-process-chain[data-tone-pl="burgundy"]                  #pl-circle { color: var(--sph-accent-burgundy); }

.sph-process-chain[data-tone-raw-data="navy"]            :is(#raw-data-circle, #raw-data-arrow),
.sph-process-chain[data-tone-partfinder="navy"]          :is(#partfinder-circle, #partfinder-arrow),
.sph-process-chain[data-tone-nlpp="navy"]                :is(#nlpp-circle, #nlpp-arrow),
.sph-process-chain[data-tone-opportunity-tracker="navy"] :is(#opportunity-tracker-circle, #opportunity-tracker-arrow),
.sph-process-chain[data-tone-pl="navy"]                  #pl-circle { color: var(--sph-accent-navy); }

.sph-process-chain[data-tone-raw-data="success"]            :is(#raw-data-circle, #raw-data-arrow),
.sph-process-chain[data-tone-partfinder="success"]          :is(#partfinder-circle, #partfinder-arrow),
.sph-process-chain[data-tone-nlpp="success"]                :is(#nlpp-circle, #nlpp-arrow),
.sph-process-chain[data-tone-opportunity-tracker="success"] :is(#opportunity-tracker-circle, #opportunity-tracker-arrow),
.sph-process-chain[data-tone-pl="success"]                  #pl-circle { color: var(--sph-success); }

.sph-process-chain[data-tone-raw-data="dark"]            :is(#raw-data-circle, #raw-data-arrow),
.sph-process-chain[data-tone-partfinder="dark"]          :is(#partfinder-circle, #partfinder-arrow),
.sph-process-chain[data-tone-nlpp="dark"]                :is(#nlpp-circle, #nlpp-arrow),
.sph-process-chain[data-tone-opportunity-tracker="dark"] :is(#opportunity-tracker-circle, #opportunity-tracker-arrow),
.sph-process-chain[data-tone-pl="dark"]                  #pl-circle { color: var(--sph-dark); }

/* Icons: white on every accent tone except `dark` (Loafer). */
.sph-process-chain[data-tone-raw-data]            #raw-data-icon,
.sph-process-chain[data-tone-partfinder]          #partfinder-icon,
.sph-process-chain[data-tone-nlpp]                #nlpp-icon,
.sph-process-chain[data-tone-opportunity-tracker] #opportunity-tracker-icon,
.sph-process-chain[data-tone-pl]                  #pl-icon { color: #fff; }
.sph-process-chain[data-tone-raw-data="dark"]            #raw-data-icon,
.sph-process-chain[data-tone-partfinder="dark"]          #partfinder-icon,
.sph-process-chain[data-tone-nlpp="dark"]                #nlpp-icon,
.sph-process-chain[data-tone-opportunity-tracker="dark"] #opportunity-tracker-icon,
.sph-process-chain[data-tone-pl="dark"]                  #pl-icon { color: var(--sph-loafer); }

/* Size modifiers ---------------------------------------------------------- */
.sph-process-chain--xl { width: 1280px; max-width: 100%; }
.sph-process-chain--lg { width: 960px;  max-width: 100%; }
.sph-process-chain--md { width: 720px;  max-width: 100%; }
.sph-process-chain--sm { width: 480px;  max-width: 100%; }
/* Breadcrumb — the quiet, mostly-grey form used at the bottom of cover     */
/* slides; tone every step "dark" via the tone tokens for that effect.       */
.sph-process-chain.is-breadcrumb { opacity: 0.7; }

/* ---- Flow card — the small content cards that sit under the chain ------
   Used on Solution Overview hero (one per bubble). Top-border accent +
   subtle cream background. Pair the `--heart` variant with the NLPP step. */
.sph-process-card {
  border-top: 3px solid var(--sph-primary);
  background: var(--sph-bg-subtle);
  padding: 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.sph-process-card__label {
  font: 700 14px/1 var(--sph-font-display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sph-primary);
}
.sph-process-card__body {
  font: 500 18px/1.5 var(--sph-font-display);
  color: var(--sph-text);
  margin: 0;
}
.sph-process-card--heart {
  border-top-color: var(--sph-info);
  background: var(--sph-bg-callout);
}
.sph-process-card--heart .sph-process-card__label { color: var(--sph-info); }

/* ---- Flow-card row — cards keyed to the chain's five bubbles ------------
   `.sph-chain-cards` stacks the DATA2VALUE chain over a five-column card
   track, so each `.sph-process-card[data-bubble]` lands directly under its
   bubble. The bubble x-centres in data2value-process.svg sit at
   9.73 / 29.97 / 50.00 / 70.16 / 90.27 % of the artwork width — i.e. the
   column centres of a `repeat(5, 1fr)` grid (10/30/50/70/90 %) to within
   ±0.3 %. So equal columns align under the bubbles with no per-pixel
   tuning. Cards may be sparse — placement is by `data-bubble`, not order. */
.sph-chain-cards {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--sph-space-7);
  /* Fill an available host while keeping the chain and cards in normal flow.
     The fixed deck stage adds its bottom-pinned card-row behavior in
     deck-shell.css; web consumers load this reusable stack unchanged. */
  flex: 1;
  min-height: 0;
  position: relative;
}
.sph-chain-cards .sph-process-chain { width: 100%; }
.sph-process-cards {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--sph-space-5);
  width: 100%;
  align-items: stretch;   /* every card matches the tallest in the row */
}
.sph-process-card[data-bubble="raw-data"]            { grid-column: 1; }
.sph-process-card[data-bubble="partfinder"]          { grid-column: 2; }
.sph-process-card[data-bubble="nlpp"]                { grid-column: 3; }
.sph-process-card[data-bubble="opportunity-tracker"] { grid-column: 4; }
.sph-process-card[data-bubble="pl"]                  { grid-column: 5; }

/* ---- Cream pill — used on cover slides --------------------------------- */
.sph-pill {
  display: inline-flex;
  align-items: center;
  background: var(--sph-secondary);
  color: var(--sph-text);
  padding: 14px 28px;
  border-radius: 999px;
  font: 600 16px/1 var(--sph-font-display);
}

/* ---- Extract pill — corner status marker ------------------------------
   A small outlined-primary uppercase pill pinned to the slide's bottom-right
   (e.g. "Customer-extractable"). Promoted from the pricing-framework deck;
   generalised to pin to any `.sph-slide` and to use a token tint (was a
   deck-local rgba). */
.sph-extract-pill {
  position: absolute;
  right: var(--sph-slide-pad-x);
  bottom: var(--sph-slide-note-bottom);
  font: 700 11px/1 var(--sph-font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sph-primary);
  border: 1px solid var(--sph-primary);
  padding: 8px 14px;
  border-radius: 999px;
  background: var(--sph-bg-callout);
}

/* ---- Saphirion top-right wordmark (small, on every content slide) ------- */
.sph-mark {
  position: absolute;
  top: 5%;
  right: 5%;
  height: 36px;
  width: auto;
  opacity: 0.85;
}
.sph-mark--dark { opacity: 0.35; }

/* ==========================================================================
   PROMOTED ARCHETYPES — patterns elevated from the Pitch Deck (May 2026)
   The six blocks below are recurring slide compositions that earn their own
   archetype because they have specific semantic structure (not just layout).
   Each one ships with deck-floor type sizes and the same chrome treatment
   as the rest of the motif system, so they drop into a 1920×1080
   `.sph-slide` body without re-tuning.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1) Chart panel — `.sph-chart-panel` (+ paired row, + fan + hatch helpers)
   --------------------------------------------------------------------------
   A repeatable wrapper for any chart that needs the Saphirion grammar:
   - one chart-text size (14 px), three weights (Regular tick / Medium axis /
     Bold callout) — see README §8 "Charts — harmony and rhythm";
   - a cream surface that lets bars/lines carry the only colour;
   - an eyebrow + headline + caption row sized to match `num-tile`.

   Pair two of them in a `.sph-chart-panels` grid for the two-up
   financial comparison (e.g. revenue projection vs gross margin
   trajectory).

   Helpers:
     .sph-chart-hatch   — diagonal-stripe overlay for *provisional /
                            forecast / not-yet-realized* bars (HTML).
                            For SVG bars, use `fill="url(#sph-hatch)"`
                            with the matching <pattern> snippet below.
     .sph-chart-fan     — a horizontal "forward fan" shading helper:
                            solid at the left edge, fading to transparent
                            at the right edge. Use to communicate widening
                            uncertainty / scenario cones. CSS version for
                            HTML elements; for SVG use a <linearGradient>
                            and reference the same role colour.

   For the SVG hatch pattern (drop inside the chart's <defs>):

     <pattern id="sph-hatch" patternUnits="userSpaceOnUse"
              width="8" height="8" patternTransform="rotate(45)">
       <line x1="0" y1="0" x2="0" y2="8"
             stroke="currentColor" stroke-width="2" opacity=".35"/>
     </pattern>
   -------------------------------------------------------------------------- */
/* Generator-chart panel — `chart { panel on }` puts the inline-SVG chart on the
   same tinted surface as the hand-authored .sph-chart-panel, with rounded corners. */
.sph-chart--panel {
  background: var(--sph-bg-muted);
  border-radius: var(--sph-radius-lg);
  padding: 28px 32px;
}
.sph-chart-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 16px;
  background: var(--sph-bg-muted);
  padding: 32px 36px;
  color: var(--sph-text);
  min-width: 0;     /* lets flex children shrink so SVGs honour 100%        */
}
.sph-chart-panel--cream  { background: var(--sph-bg-muted); }
.sph-chart-panel--paper  { background: var(--sph-white); box-shadow: var(--sph-shadow-xs); }
.sph-chart-panel--inverse{
  background: var(--sph-bg-inverse);
  color: var(--sph-text-on-dark);
  --sph-text-soft: var(--sph-loafer);
  --sph-text-muted: rgba(221,220,197,.7);
}

.sph-chart-panel__head {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.sph-chart-panel__eyebrow {
  font: 600 14px/1 var(--sph-font-display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sph-primary);
  margin: 0;
}
.sph-chart-panel__title {
  font: 700 22px/1.25 var(--sph-font-display);
  letter-spacing: -0.005em;
  color: var(--sph-dark);
  margin: 0;
}
.sph-chart-panel--inverse .sph-chart-panel__title { color: #fff; }

/* Chart structural colors — dark-theme indirection. chart-svg.js hardcodes
   exactly three structural colors as SVG fill/stroke ATTRIBUTES (ink
   #1D2326 for labels/values/baseline/legend/title, smoke #788176 for axis
   values and x-labels, loafer #DDDCC5 for gridlines and range bands), so
   token inheritance can't reach them. CSS properties override presentation
   attributes (same technique as `.sph-chart-panel--inverse text.axis`
   below), and the attribute selectors pick out exactly the structural
   colors — series colors keep their brand values in both themes. With the
   --sph-chart-* properties unset (light theme, print, export, static
   pages) every fallback equals the authored value, so these rules are
   no-ops. The dark flip in tokens.css binds them. Generators untouched —
   deckc renders the same chart-svg.js under goja, so SVG output (and the
   DeckLang goldens) stay byte-identical.                                  */
.sph-chart__svg text[fill="#1D2326"] { fill: var(--sph-chart-ink, #1D2326); }
.sph-chart__svg text[fill="#788176"] { fill: var(--sph-chart-muted, #788176); }
.sph-chart__svg [stroke="#1D2326"]   { stroke: var(--sph-chart-ink, #1D2326); }
.sph-chart__svg [stroke="#DDDCC5"]   { stroke: var(--sph-chart-grid, #DDDCC5); }
.sph-chart__svg rect[fill="#DDDCC5"] { fill: var(--sph-chart-band, #DDDCC5); }

/* Interactive-generator SVGs (roi-matrix &co.) — the same dark-theme
   indirection for GROUND ink. Generators bake class hooks onto text that
   sits directly on the slide surface (stats, axis headers, row labels,
   legend — "ink"/"muted"), while cell text on tinted fills stays literal:
   the tints keep their light washes in both themes, so flipping that text
   would break contrast the other way. Hairlines route by stroke attribute
   like the chart gridlines. Fallbacks equal the authored values, so light
   theme, print, and export are no-ops. */
.sph-interactive svg text.ink   { fill: var(--sph-chart-ink, #1D2326); }
.sph-interactive svg text.muted { fill: var(--sph-chart-muted, #788176); }
.sph-interactive svg line[stroke="#DBD9C9"] { stroke: var(--sph-chart-grid, #DBD9C9); }
.sph-chart-panel__caption {
  font: 500 20px/1.45 var(--sph-font-display);
  color: var(--sph-text-soft);
  margin: 0;
}
.sph-chart-panel__plot {
  flex: 1;
  min-height: 0;
  position: relative;
}
.sph-chart-panel__plot > svg { width: 100%; height: 100%; display: block; }

/* Chart text — 14 px floor, three weights, no other size. NB: the JS-generated chart
   (chart-svg.js) sizes its SVG labels per-element (value 10.5, axis 14) via font-size
   attributes; a blanket lift here would override that hierarchy and break the computed
   layout. Lifting chart-label legibility for projection belongs in the generator, not here. */
.sph-chart-panel text { font-family: var(--sph-font-display); font-size: 14px; }
.sph-chart-panel text.tick      { font-weight: 400; fill: var(--sph-text-muted); }
.sph-chart-panel text.axis      { font-weight: 500; fill: var(--sph-dark);
                                  text-transform: uppercase; letter-spacing: .14em; }
.sph-chart-panel--inverse text.axis { fill: #fff; }
.sph-chart-panel text.callout   { font-weight: 700; }

/* Legend row — uses a 12 px swatch + 14 px Medium label.                     */
.sph-chart-panel__legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 24px;
  margin: 0;
  padding: 0;
  list-style: none;
  font: 500 14px/1.2 var(--sph-font-display);
  color: var(--sph-text);
}
.sph-chart-panel--inverse .sph-chart-panel__legend { color: var(--sph-loafer); }
.sph-chart-panel__legend li {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.sph-chart-panel__legend .sw {
  width: 14px; height: 14px;
  background: var(--sw, var(--sph-primary));
  display: inline-block;
  flex-shrink: 0;
}
.sph-chart-panel__legend .sw--hatch {
  background-image:
    repeating-linear-gradient(45deg,
      var(--sw, var(--sph-primary)) 0 3px,
      transparent 3px 6px);
  background-color: transparent;
  outline: 1px solid var(--sw, var(--sph-primary));
}

/* Paired-panel container — two charts side-by-side at the same proportions  */
.sph-chart-panels {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  align-items: stretch;
}

/* Provisional hatch — applied to HTML bars or callout strips. For SVG bars
   use the <pattern id="sph-hatch"> snippet documented above.                */
.sph-chart-hatch {
  background-image: repeating-linear-gradient(
    45deg,
    var(--sph-primary)     0 3px,
    transparent            3px 7px
  );
  background-color: transparent;
}
.sph-chart-hatch--burgundy { background-image: repeating-linear-gradient(45deg, var(--sph-accent-burgundy) 0 3px, transparent 3px 7px); }
.sph-chart-hatch--emerald  { background-image: repeating-linear-gradient(45deg, var(--sph-accent-emerald)  0 3px, transparent 3px 7px); }
.sph-chart-hatch--slate    { background-image: repeating-linear-gradient(45deg, var(--sph-accent-slate)    0 3px, transparent 3px 7px); }

/* Forward fan — left-solid → right-transparent gradient. The fan colour is
   driven by the `--sph-fan-color` custom property so a single class
   handles every variant.                                                    */
.sph-chart-fan {
  --sph-fan-color: var(--sph-primary);
  background: linear-gradient(
    to right,
    color-mix(in srgb, var(--sph-fan-color) 38%, transparent) 0%,
    color-mix(in srgb, var(--sph-fan-color) 8%,  transparent) 100%
  );
}
.sph-chart-fan--burgundy { --sph-fan-color: var(--sph-accent-burgundy); }
.sph-chart-fan--emerald  { --sph-fan-color: var(--sph-accent-emerald); }
.sph-chart-fan--slate    { --sph-fan-color: var(--sph-accent-slate); }
.sph-chart-fan--success  { --sph-fan-color: var(--sph-success); }

/* --------------------------------------------------------------------------
   2) Record card — `.sph-record-card`   ·   acquirer preset: `.sph-exit-band`
   --------------------------------------------------------------------------
   A card with a tinted header (eyebrow + title) over a `dl` of label/value
   rows. Everything is author-controlled: the header colour
   (`--sph-record-head`, or a preset modifier), the row labels (`dt` text),
   and the label-column width (`--sph-record-label-col`).

   Place several cards in a `.sph-record-card-group` and the header and every
   row align across the group via subgrid — the row separators line up no
   matter how much text each row holds. A lone card just renders normally.

   `.sph-exit-band` is the acquirer / exit preset — the SAME card reached by
   its legacy semantic name, so existing decks render unchanged. Its parts
   `__cat` / `__name` are aliases of the generic `__eyebrow` / `__title`.
   Every rule below is define-once: the generic and the alias are grouped
   selectors, never hand-copied.

   Markup:
     <div class="sph-record-card-group" style="--sph-record-rows: 4">
       <article class="sph-record-card sph-record-card--slate">
         <header class="sph-record-card__head">
           <span class="sph-record-card__eyebrow">Strategic acquirer · Tier 1</span>
           <h4 class="sph-record-card__title">Enterprise software majors</h4>
         </header>
         <dl class="sph-record-card__body">
           <div class="sph-record-card__row"><dt>Who</dt><dd>…</dd></div>
           <div class="sph-record-card__row"><dt>Why</dt><dd>…</dd></div>
           <div class="sph-record-card__row"><dt>Anchor</dt><dd>…</dd></div>
           <div class="sph-record-card__row"><dt>Multiple</dt><dd>8–12× ARR</dd></div>
         </dl>
       </article>
       …more cards…
     </div>

   Header colour — set `--sph-record-head` to any colour, or use a preset:
   --slate · --navy · --emerald · --burgundy · --earth · --brown · --dark
   · --info. Group row count — `--sph-record-rows` on the group (default 4).
   -------------------------------------------------------------------------- */

/* Card shell */
.sph-record-card,
.sph-exit-band {
  --sph-record-head: var(--sph-accent-slate);
  /* Label column is auto-fit by default (see `__row` grid). `label-col <n>`
     sets this var inline to pin a fixed px width when an author wants one. */
  background: var(--sph-white);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: var(--sph-shadow-xs);
  min-width: 0;
}

/* Tinted header — eyebrow + title */
.sph-record-card__head,
.sph-exit-band__head {
  background: var(--sph-record-head);
  color: #fff;
  padding: 18px 22px 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.sph-record-card__eyebrow,
.sph-exit-band__cat {
  font: 600 12px/1 var(--sph-font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0.78;
}
.sph-record-card__title,
.sph-exit-band__name {
  font: 700 22px/1.2 var(--sph-font-display);
  letter-spacing: -0.005em;
  margin: 0;
}

/* Labeled-record body */
/* One shared grid for the whole body so the label column is sized to the
   WIDEST label across all rows — every label shows in full and all values
   line up. `min(max-content, …)` lets a long label grow but caps it so the
   value column always keeps room; `label-col <n>` still pins a fixed px width.
   Rows are `display: contents`, so the row separator + padding move onto the
   cells. */
.sph-record-card__body,
.sph-exit-band__body {
  margin: 0;
  padding: 8px 0 12px;
  display: grid;
  grid-template-columns: var(--sph-record-label-col, min(max-content, 340px)) 1fr;
  column-gap: 16px;
  align-items: start;
}
.sph-record-card__row,
.sph-exit-band__row { display: contents; }
.sph-record-card__row dt,
.sph-exit-band__row dt,
.sph-record-card__row dd,
.sph-exit-band__row dd {
  padding-block: 12px;
  border-top: 1px solid var(--sph-border);
}
.sph-record-card__row:first-child dt,
.sph-exit-band__row:first-child dt,
.sph-record-card__row:first-child dd,
.sph-exit-band__row:first-child dd { border-top: 0; }
.sph-record-card__row dt,
.sph-exit-band__row dt {
  grid-column: 1;
  padding-left: 22px;
  /* Label reads at body-text size (--sph-fs-md) so it matches a `text`
     paragraph; keeps the uppercase tracked treatment that marks it a label. */
  font: 700 var(--sph-fs-md)/1.25 var(--sph-font-display);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sph-text-muted);
  margin: 0;
}
/* `lead-labels` — render each row label as a large, tone-colored stat (a year or
   count that headlines the row) instead of the default small uppercase label. */
.sph-record-card--lead-labels .sph-record-card__row dt {
  font: var(--sph-weight-extrabold) var(--sph-fs-2xl)/1 var(--sph-font-display);
  letter-spacing: var(--sph-tracking-tight);
  text-transform: none;
  color: var(--sph-record-head);
  align-self: center;
}
.sph-record-card__row dd,
.sph-exit-band__row dd {
  grid-column: 2;
  padding-right: 22px;
  /* Value reads at body-text size (--sph-fs-md), matching `text`. */
  font: 500 var(--sph-fs-md)/var(--sph-lh-normal) var(--sph-font-display);
  color: var(--sph-text);
  margin: 0;
}
.sph-record-card__row dd b,
.sph-exit-band__row dd b {
  font-weight: 700;
  color: var(--sph-dark);
}

/* `style stacked` — a definition list where each row is a prominent term over a
   BLOCK description paragraph (term-over-paragraph), instead of the default
   inline label|value grid. Orthogonal to tone / lead-labels. */
.sph-record-card--stacked .sph-record-card__body {
  display: block;
}
.sph-record-card--stacked .sph-record-card__row {
  display: block;
  padding: 12px 22px;
  border-top: 1px solid var(--sph-border);
}
.sph-record-card--stacked .sph-record-card__row:first-child {
  border-top: 0;
}
.sph-record-card--stacked .sph-record-card__row dt {
  grid-column: auto;
  padding: 0;
  border-top: 0;
  margin: 0 0 4px;
  font: 700 var(--sph-fs-md)/1.3 var(--sph-font-display);
  letter-spacing: 0;
  text-transform: none;
  color: var(--sph-record-head);
}
.sph-record-card--stacked .sph-record-card__row dd {
  grid-column: auto;
  padding: 0;
  border-top: 0;
  margin: 0;
  font: 500 var(--sph-fs-md)/var(--sph-lh-normal) var(--sph-font-display);
  color: var(--sph-text);
}

/* Header colour = the tone. The tone-system layer (top of file) sets --sph-tone
   from any .sph-record-card--<tone> / .sph-exit-band--<tone>, so `--slate` and
   the rest are interchangeable across both, and EVERY palette tone is available
   (camel, sky, navy, …). For an off-palette colour, set --sph-record-head inline. */
.sph-record-card, .sph-exit-band { --sph-record-head: var(--sph-tone); }

/* Group — cards align row-for-row via subgrid so the separators line up.
   The group is a column grid; each card spans (1 header + N rows) tracks and
   subgrids onto them, and `__body` is display:contents so its rows become
   direct grid items of the card. `--sph-record-rows` is the body-row count
   (default 4 — the canonical WHO / WHY / ANCHOR / MULTIPLE). */
.sph-record-card-group {
  --sph-record-rows: 4;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 0 24px;
  /* Keep rows content-sized even if the group is stretched tall — record
     rows read best tight, not ballooned to fill a slide. */
  align-content: start;
}
.sph-record-card-group > .sph-record-card,
.sph-record-card-group > .sph-exit-band {
  display: grid;
  /* Own 2-column track (label | value) so the tinted head can span BOTH columns
     and the body rows line up; the row tracks come from the group via subgrid. */
  grid-template-columns: var(--sph-record-label-col, min(max-content, 340px)) 1fr;
  grid-template-rows: subgrid;
  grid-row: span calc(var(--sph-record-rows) + 1);
}
/* The header spreads across the whole card, not just the label column. */
.sph-record-card-group > .sph-record-card > .sph-record-card__head,
.sph-record-card-group > .sph-exit-band   > .sph-exit-band__head {
  grid-column: 1 / -1;
}
.sph-record-card-group > .sph-record-card > .sph-record-card__body,
.sph-record-card-group > .sph-exit-band   > .sph-exit-band__body {
  display: contents;
}
/* Grouped records pack more rows per card (e.g. Slide-13 optionality: 4 bands ×
   4 rows of long copy). Tighten the vertical rhythm so a dense group clears the
   slide footnote instead of overflowing into it; standalone records keep the
   roomier base spacing. */
.sph-record-card-group .sph-record-card__head,
.sph-record-card-group .sph-exit-band__head { padding-block: 8px; }
.sph-record-card-group .sph-record-card__row dt,
.sph-record-card-group .sph-record-card__row dd,
.sph-record-card-group .sph-exit-band__row dt,
.sph-record-card-group .sph-exit-band__row dd { padding-block: 6px; }
.sph-record-card-group .sph-record-card__body,
.sph-record-card-group .sph-exit-band__body { padding-block: 4px 8px; }

/* --------------------------------------------------------------------------
   3) Chart + table — `.sph-chart-table`
   --------------------------------------------------------------------------
   A two-pane archetype: a chart on the left, a data table on the right. The
   cap table (donut + Pre/Round/Post table) is the canonical use.

   The chart cell takes any figure; `__donut` is the donut/ring chart provided
   here. The table is the shared deck table — `.sph-table.sph-table--deck` —
   with a thin `__table` customisation; there is no separate table style.

   Markup:
     <div class="sph-chart-table">
       <figure class="sph-chart-table__donut">
         <svg viewBox="0 0 240 240">…stroke-based donut…</svg>
         <figcaption class="sph-chart-table__center">
           <b>100%</b><span>Post-round</span>
         </figcaption>
       </figure>
       <table class="sph-table sph-table--deck sph-chart-table__table">
         <thead><tr><th>Holder</th><th>Pre</th><th>Round</th><th>Post</th></tr></thead>
         <tbody>
           <tr>
             <th><span class="sph-chart-table__dot" style="--dot:var(--sph-primary)"></span>Founder</th>
             <td>100%</td><td class="round-cell">—</td><td>80%</td>
           </tr>
         </tbody>
         <tfoot><tr><th>Total</th><td>100%</td><td>+20%</td><td>100%</td></tr></tfoot>
       </table>
     </div>

   Tune the split with `--sph-ct-chart-col` (chart column, default
   minmax(260px,380px)) and `--sph-ct-gap` (default 48px).
   -------------------------------------------------------------------------- */
.sph-chart-table {
  --sph-ct-chart-col: minmax(260px, 380px);
  --sph-ct-gap: 48px;
  display: grid;
  grid-template-columns: var(--sph-ct-chart-col) 1fr;
  gap: var(--sph-ct-gap);
  align-items: center;
}

/* Donut / ring chart with a centred overlay label */
.sph-chart-table__donut {
  margin: 0;
  position: relative;
  aspect-ratio: 1 / 1;
}
.sph-chart-table__donut svg { width: 100%; height: 100%; display: block; }
.sph-chart-table__center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  pointer-events: none;
}
.sph-chart-table__center b {
  font: 800 64px/1 var(--sph-font-display);
  letter-spacing: -0.02em;
  color: var(--sph-dark);
}
.sph-chart-table__center span {
  font: 600 14px/1.2 var(--sph-font-display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sph-text-muted);
  margin-top: 8px;
}

/* Square legend swatch for the table's first column */
.sph-chart-table__dot {
  display: inline-block;
  width: 12px; height: 12px;
  background: var(--dot, var(--sph-primary));
  margin-right: 12px;
  vertical-align: middle;
  position: relative;
  top: -1px;
}

/* Table customisation — layered on `.sph-table--deck` (the generic deck
   table, which carries the header rule, row borders and `tfoot` total).
   This adds only what the chart-table needs: every column after the first
   is a right-aligned numeric column (no per-cell `.num`), and `.round-cell`
   flags the funding-round delta column. */
.sph-chart-table__table th:not(:first-child),
.sph-chart-table__table td:not(:first-child) {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.sph-chart-table__table td.round-cell {
  color: var(--sph-info);
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   4) Use-of-funds — `.sph-fund-pie` + `.sph-milestones`
   --------------------------------------------------------------------------
   The "ask" pattern: a pie/donut + 6-row legend on the left, a
   month-band milestones row underneath.  Pie and legend share one
   colour mapping; milestones reuse the same `--band` accent.

   Markup:
     <section class="sph-fund-pie">
       <figure class="sph-fund-pie__chart">
         <svg>…donut…</svg>
         <figcaption><b>€3.5M</b><span>Seed round</span></figcaption>
       </figure>
       <ol class="sph-fund-pie__legend">
         <li style="--sw: var(--sph-primary)">
           <span class="sw"></span>
           <b>Engineering</b>
           <span class="pct">40%</span>
           <span class="amt">€1.40M</span>
         </li>
         …
       </ol>
     </section>

     <ol class="sph-milestones">
       <li class="sph-milestones__band" style="--band: var(--sph-primary)">
         <span class="sph-milestones__month">M0 – M6</span>
         <span class="sph-milestones__label">Hire 4 eng · ship v1</span>
       </li>
       …
     </ol>
   -------------------------------------------------------------------------- */
.sph-fund-pie {
  display: grid;
  grid-template-columns: minmax(260px, 360px) 1fr;
  gap: 56px;
  align-items: center;
}
.sph-fund-pie__chart {
  margin: 0;
  position: relative;
  aspect-ratio: 1 / 1;
}
.sph-fund-pie__chart svg {
  width: 100%; height: 100%; display: block;
}
.sph-fund-pie__chart figcaption {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.sph-fund-pie__chart figcaption b {
  font: 800 56px/1 var(--sph-font-display);
  letter-spacing: -0.02em;
  color: var(--sph-dark);
}
.sph-fund-pie__chart figcaption span {
  font: 600 14px/1.2 var(--sph-font-display);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sph-text-muted);
  margin-top: 8px;
}

.sph-fund-pie__legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.sph-fund-pie__legend li {
  display: grid;
  grid-template-columns: 24px 1fr auto auto;
  align-items: baseline;
  gap: 0 18px;
  padding: 14px 0;
  border-top: 1px solid var(--sph-border);
}
.sph-fund-pie__legend li:first-child { border-top: 0; }
.sph-fund-pie__legend .sw {
  width: 16px; height: 16px;
  background: var(--sw, var(--sph-primary));
  display: inline-block;
  align-self: center;
  position: relative;
  top: 1px;
}
.sph-fund-pie__legend b {
  font: 600 18px/1.2 var(--sph-font-display);
  color: var(--sph-dark);
}
.sph-fund-pie__legend .pct {
  font: 700 18px/1 var(--sph-font-display);
  color: var(--sph-text);
  font-variant-numeric: tabular-nums;
  min-width: 4ch;
  text-align: right;
}
.sph-fund-pie__legend .amt {
  font: 500 16px/1 var(--sph-font-display);
  color: var(--sph-text-muted);
  font-variant-numeric: tabular-nums;
  min-width: 7ch;
  text-align: right;
}
/* Optional one-line description under a legend row — drop a `.note` as the
   row's last child; it spans from the label column to the row's end.         */
.sph-fund-pie__legend li:has(.note) { row-gap: 4px; }
.sph-fund-pie__legend .note {
  grid-column: 2 / -1;
  font: 500 var(--sph-fs-xs)/1.4 var(--sph-font-display);
  color: var(--sph-text-soft);
}

/* Milestones — month-band row.  Each band is a coloured strip with the
   month range as the eyebrow and a single-line label below.  Bands sit
   in a flex row with equal weights so 4–6 bands span the slide.            */
.sph-milestones {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 8px;
}
.sph-milestones__band {
  --band: var(--sph-primary);
  background: var(--band);
  color: #fff;
  padding: 14px 18px 16px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 92px;
  position: relative;
}
.sph-milestones__month {
  font: 700 12px/1 var(--sph-font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0.85;
}
.sph-milestones__label {
  font: 600 16px/1.3 var(--sph-font-display);
  letter-spacing: -0.005em;
}
.sph-milestones__band--soft {
  background: color-mix(in srgb, var(--band) 18%, var(--sph-white));
  color: var(--sph-dark);
  border-left: 4px solid var(--band);
}
.sph-milestones__band--soft .sph-milestones__month { color: var(--band); opacity: 1; }

/* --------------------------------------------------------------------------
   5) Persona — `.sph-persona` + `.sph-persona--lead`
   --------------------------------------------------------------------------
   Persona cards with a built-in prominence hierarchy. Drop a mix of
   leads and rest into `.sph-persona-grid` and the layout self-organises:
   leads claim two columns and a taller photo; rest sit at one column
   with a smaller photo and tighter type.

   Markup:
     <div class="sph-persona-grid">
       <article class="sph-persona sph-persona--lead">
         <div class="sph-persona__portrait">…image or initials…</div>
         <p class="sph-persona__role">Chief Procurement Officer</p>
         <h4 class="sph-persona__name">Inga · €2B+ direct spend</h4>
         <p class="sph-persona__quote">"Our spend categories drift…"</p>
         <ul class="sph-persona__beats">
           <li>Owns the supplier panel</li>
           <li>Quarterly board readouts</li>
         </ul>
       </article>
       …
     </div>
   -------------------------------------------------------------------------- */
.sph-lead-grid,
.sph-persona-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 20px;
  align-items: stretch;
}
/* The 20px column gap stays; the eyebrow · title · content ROW rhythm is owned by the shared
   card-row subgrid rule near the top of this file (row-gap: 8px), not here. */
.sph-lead-grid > *,
.sph-persona-grid > .sph-persona { grid-column: span 1; }
.sph-lead-grid > .is-lead,
.sph-persona-grid > .sph-persona--lead { grid-column: span 2; }

.sph-persona {
  display: flex;
  flex-direction: column;
  background: var(--sph-white);
  padding: 20px 22px 22px;
  border-top: 3px solid var(--sph-tone, var(--sph-primary));
  color: var(--sph-text);
  box-shadow: var(--sph-shadow-xs);
  min-width: 0;
}
.sph-persona__portrait {
  width: 64px; height: 64px;
  background: var(--sph-bg-muted);
  border-radius: var(--sph-radius-pill);
  margin-bottom: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex-shrink: 0;
  font: 700 20px/1 var(--sph-font-display);
  color: var(--sph-primary);
  letter-spacing: 0.04em;
}
.sph-persona__portrait img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.sph-persona__role {
  font: 600 12px/1.1 var(--sph-font-display);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sph-tone, var(--sph-primary));
  margin: 0 0 6px;
}
.sph-persona__name {
  font: 700 18px/1.25 var(--sph-font-display);
  letter-spacing: -0.005em;
  color: var(--sph-dark);
  margin: 0 0 10px;
}
.sph-persona__quote {
  font: 500 14px/1.45 var(--sph-font-display);
  color: var(--sph-text-soft);
  margin: 0;
  flex: 1;
}
.sph-persona__beats {
  list-style: square;
  margin: 12px 0 0;
  padding: 0 0 0 18px;
  font: 500 13px/1.5 var(--sph-font-display);
  color: var(--sph-text-soft);
}
.sph-persona__beats li::marker { color: var(--sph-tone, var(--sph-primary)); }

/* --- Lead modifier: prominent variant ----------------------------------- */
.sph-persona--lead {
  padding: 28px 32px 32px;
  border-top-width: 4px;
}
.sph-persona--lead .sph-persona__portrait {
  width: 96px; height: 96px;
  margin-bottom: 20px;
  font-size: 28px;
}
.sph-persona--lead .sph-persona__role {
  font-size: 14px;
}
.sph-persona--lead .sph-persona__name {
  font-size: 24px;
  margin-bottom: 14px;
}
.sph-persona--lead .sph-persona__quote {
  font-size: 18px;
  line-height: 1.5;
  color: var(--sph-text);
}
.sph-persona--lead .sph-persona__beats {
  font-size: 15px;
  margin-top: 16px;
}

/* Tonal variants — apply to either prominence. Border-top + role + bullet
   marker all take the tone via the tone-system layer, so all tones work. */

/* --------------------------------------------------------------------------
   6) Category claim — `.sph-category-claim`
   --------------------------------------------------------------------------
   The "Brand → Concept" stripe used to define a category by analogy:
   each row pairs a famous brand with the abstraction it owns, and the
   final row makes the Saphirion claim. Render as a <dl> so the markup
   keeps the semantic link.

   Markup:
     <dl class="sph-category-claim">
       <div class="sph-category-claim__row">
         <dt class="sph-category-claim__brand">Stripe</dt>
         <dd class="sph-category-claim__concept">Payments infrastructure</dd>
       </div>
       <div class="sph-category-claim__row">
         <dt class="sph-category-claim__brand">Snowflake</dt>
         <dd class="sph-category-claim__concept">Data warehouse, as-a-service</dd>
       </div>
       <div class="sph-category-claim__row sph-category-claim__row--us">
         <dt class="sph-category-claim__brand">Saphirion</dt>
         <dd class="sph-category-claim__concept">Value intelligence</dd>
       </div>
     </dl>
   -------------------------------------------------------------------------- */
.sph-category-claim {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  gap: 0;
}
.sph-category-claim__row {
  display: grid;
  grid-template-columns: minmax(220px, 1fr) 56px minmax(0, 2fr);
  gap: 0;
  align-items: baseline;
  padding: 20px 0;
  border-top: 1px solid var(--sph-border);
  position: relative;
}
.sph-category-claim__row:first-child { border-top: 0; }
.sph-category-claim__row::after {
  content: "→";
  grid-column: 2;
  grid-row: 1;
  justify-self: center;
  align-self: center;
  font: 400 22px/1 var(--sph-font-display);
  color: var(--sph-text-muted);
}
.sph-category-claim__brand {
  font: 700 24px/1.15 var(--sph-font-display);
  letter-spacing: -0.005em;
  color: var(--sph-text-muted);
  margin: 0;
  grid-column: 1;
}
.sph-category-claim__concept {
  font: 500 22px/1.25 var(--sph-font-display);
  color: var(--sph-text);
  margin: 0;
  grid-column: 3;
}

/* The Saphirion-claim row — Donkey brand color, heavier weight,
   bigger type. Visually anchors the stripe.                                */
.sph-category-claim__row--us {
  padding-top: 28px;
  margin-top: 4px;
  border-top: 2px solid var(--sph-primary);
}
.sph-category-claim__row--us .sph-category-claim__brand {
  color: var(--sph-primary);
  font-size: 28px;
}
.sph-category-claim__row--us .sph-category-claim__concept {
  color: var(--sph-dark);
  font-weight: 700;
  font-size: 26px;
}
.sph-category-claim__row--us::after {
  color: var(--sph-primary);
  font-weight: 700;
}

/* Inline variant — pack three rows into a single horizontal stripe, used
   as a hero band ("Brand → Concept · Brand → Concept · Saphirion →
   Value intelligence"). Loses the per-row borders and right-aligns the
   final claim.                                                              */
.sph-category-claim--inline {
  flex-direction: row;
  align-items: center;
  gap: 32px;
  flex-wrap: wrap;
}
.sph-category-claim--inline .sph-category-claim__row {
  display: inline-flex;
  align-items: baseline;
  padding: 0;
  border: 0;
  gap: 16px;
}
.sph-category-claim--inline .sph-category-claim__row::after {
  position: static;
  grid-column: auto;
  font-size: 18px;
}
.sph-category-claim--inline .sph-category-claim__brand    { font-size: 18px; }
.sph-category-claim--inline .sph-category-claim__concept  { font-size: 18px; }
.sph-category-claim--inline .sph-category-claim__row--us  { margin-left: auto; }
.sph-category-claim--inline .sph-category-claim__row--us .sph-category-claim__brand   { font-size: 22px; }
.sph-category-claim--inline .sph-category-claim__row--us .sph-category-claim__concept { font-size: 22px; }


/* ==========================================================================
   PROMOTED ARCHETYPES — second wave (May 2026)
   Patterns elevated from the Pitch Deck's deck-local layer after the first
   refactor (see Design-System-Refactor-Plan §5). Each ships with deck-floor
   type sizes and drops straight into a 1920×1080 `.sph-slide` body.
   ========================================================================== */

/* --------------------------------------------------------------------------
   7) Logo wall — `.sph-logo-wall`
   --------------------------------------------------------------------------
   A cream band carrying a single row of customer wordmarks / logos. Children
   auto-distribute into equal columns, so 5–8 marks span the slide width.
   Drop an <img> or a text wordmark into each `__mark`.

   Markup:
     <div class="sph-logo-wall">
       <span class="sph-logo-wall__mark">DMG Mori</span>
       <span class="sph-logo-wall__mark"><img src="…" alt="Bosch"></span>
       …
     </div>
   -------------------------------------------------------------------------- */
.sph-logo-wall {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  align-items: center;
  gap: 32px;
  background: var(--sph-bg-muted);
  padding: 36px 48px;
}
.sph-logo-wall__mark {
  font: 600 22px/1.15 var(--sph-font-display);
  color: var(--sph-text-soft);
  text-align: center;
}
.sph-logo-wall__mark img {
  display: block;
  max-width: 100%;
  max-height: 44px;
  margin: 0 auto;
  object-fit: contain;
}

/* --------------------------------------------------------------------------
   7b) Image — `.sph-image`
   --------------------------------------------------------------------------
   A single bitmap or SVG with a centre-clip shape (original / square / circle),
   an optional matte inset, rounded corners, shadow, bitmap zoom, a tone tint,
   and a watermark overlay. The compiler embeds the asset as a data: URI so the
   HTML stays self-contained, and emits per-instance values as inline custom
   properties:
     --sph-image-size    box edge for square / circle  (px; default ¼ slide width)
     --sph-image-radius  corner radius for original / square (px)
     --sph-image-inset   matte padding                 (px)
     --sph-image-matte   matte fill behind the inset    (tone; default Loafer)
     --sph-image-zoom    bitmap scale, 1 = fit
     --sph-image-tint    tone colour for the tint

   SVG tint reuses the process-chain mechanism: an inlined `__svg` inherits
   `color`, and its `fill="currentColor"` paths recolour exactly. Raster tint is
   an approximation — a tone layer blended over the pixels.

   Markup:
     <figure class="sph-image sph-image--circle sph-image--shadow-md"
             style="--sph-image-size:480px">
       <img class="sph-image__media" src="data:image/jpeg;base64,…" alt="…">
       <figcaption class="sph-image__watermark">Saphirion · Draft</figcaption>
     </figure>
   -------------------------------------------------------------------------- */
.sph-image {
  position: relative;
  display: block;
  width: fit-content;
  max-width: 100%;
  align-self: start;          /* never stretch to a grid / flex row's height */
  overflow: hidden;
  background: var(--sph-white);
  --sph-image-zoom: 1;
}
.sph-image__media {
  display: block;
  width: 100%;
  height: auto;               /* natural aspect (original) */
  object-fit: cover;
  object-position: center;
  transform: scale(var(--sph-image-zoom));
}
.sph-image__svg [fill="currentColor"] { fill: currentColor; }

/* Placeholder — an image with alt but no file yet. Unlike a real image (which is
   fit-content + align-self:start) the placeholder stretches to fill its slot so the
   reserved space is visible: a dashed frame, a muted fill, the photo glyph, and the
   alt text as the label. */
.sph-image--placeholder { width: 100%; align-self: stretch; background: transparent; overflow: visible; }
.sph-image__placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  width: 100%;
  height: 100%;
  min-height: 200px;
  padding: 24px;
  box-sizing: border-box;
  text-align: center;
  background: var(--sph-bg-muted);
  border: 2px dashed var(--sph-border-strong);
  color: var(--sph-text-muted);
}
.sph-image__placeholder-icon { width: 56px; height: 56px; opacity: 0.55; }
.sph-image__placeholder-label { font: 500 15px/1.4 var(--sph-font-display); max-width: 36ch; }

/* shape — centre clip */
.sph-image--original .sph-image__media { object-fit: contain; }
.sph-image--square,
.sph-image--circle {
  --sph-image-size: calc(var(--sph-slide-w) / 4);   /* default ¼ slide width = 480px */
  width:  var(--sph-image-size);
  height: var(--sph-image-size);
}
/* square / circle fill their fixed box and crop from the centre */
.sph-image--square .sph-image__media,
.sph-image--circle .sph-image__media { height: 100%; }
.sph-image--circle { border-radius: var(--sph-radius-pill); }

/* rounded corners — original | square */
.sph-image--rounded { border-radius: var(--sph-image-radius, var(--sph-radius-lg)); }

/* matte inset */
.sph-image--inset {
  padding: var(--sph-image-inset, var(--sph-space-3));
  background: var(--sph-image-matte, var(--sph-loafer));
}
/* When a rounded image ALSO has an inset matte, round the PICTURE's own edge too, at HALF the
   image's authored corner radius (--sph-image-radius), so the inset reads as clearly rounded
   instead of a square picture inside a rounded frame. Half the radius — rather than the concentric
   outer−inset — keeps a visible curve even when the matte is nearly as thick as the radius (e.g.
   `rounded 12 inset 10` → 6px, not 2px). Without an inset the picture is flush with the frame and
   the figure's own border-radius + overflow:hidden already rounds it, so this is only the inset case. */
.sph-image--rounded.sph-image--inset .sph-image__media {
  border-radius: calc(var(--sph-image-radius, var(--sph-radius-lg)) * 0.5);
}

/* shadow */
.sph-image--shadow-sm { box-shadow: var(--sph-shadow-sm); }
.sph-image--shadow-md { box-shadow: var(--sph-shadow-md); }
.sph-image--shadow-lg { box-shadow: var(--sph-shadow-lg); }
.sph-image--shadow-xl { box-shadow: var(--sph-shadow-xl); }

/* tint — SVG recolours via currentColor; raster blends a tone layer over pixels */
.sph-image--tint { color: var(--sph-image-tint, var(--sph-primary)); }
.sph-image--tint-raster::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--sph-image-tint, var(--sph-primary));
  mix-blend-mode: multiply;
  pointer-events: none;
}

/* placement within the body flow */
.sph-image--center { margin-inline: auto; }
.sph-image--right  { margin-inline-start: auto; margin-inline-end: 0; }

/* A default-shape image FITS its area and never overflows it: the figure fills its cell and
   the media scales to CONTAIN (preserve aspect — no clipped bottom). `zoom` then enlarges the
   media within that box and the overrun is cropped (the figure's overflow:hidden clips). This
   applies in any layout container — body, row, column, grid, split — so a too-tall image
   scales down instead of running off the slide. The figure goes transparent so a contained
   image letterboxes against the slide, not a white box. Fixed-size shapes (square / circle)
   keep their own box and are unaffected. */
.sph-image--original {
  width: 100%;
  min-height: 0;
  align-self: stretch;                /* fill a grid cell's height (row / grid / split) */
}
.sph-image--original:not(.sph-image--inset) { background: transparent; }
.sph-image--original > .sph-image__media {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.sph-image--original:is(.sph-image--inset, .sph-image--rounded, .sph-image--shadow-sm, .sph-image--shadow-md, .sph-image--shadow-lg, .sph-image--shadow-xl) > .sph-image__media {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
}
/* An SVG has no intrinsic px size to shrink-wrap by, so it would collapse to the tiny CSS default.
   Fill the cell width instead and let its viewBox drive the aspect (height follows, capped above). */
.sph-image--original.sph-image--svg:is(.sph-image--inset, .sph-image--rounded, .sph-image--shadow-sm, .sph-image--shadow-md, .sph-image--shadow-lg, .sph-image--shadow-xl),
.sph-image--original.sph-image--svg:is(.sph-image--inset, .sph-image--rounded, .sph-image--shadow-sm, .sph-image--shadow-md, .sph-image--shadow-lg, .sph-image--shadow-xl) > .sph-image__media {
  width: 100%;
}
.sph-image--original:not(.sph-image--svg):is(.sph-image--inset, .sph-image--rounded, .sph-image--shadow-sm, .sph-image--shadow-md, .sph-image--shadow-lg, .sph-image--shadow-xl) > .sph-image__media {
  width: 100%;
  height: 100%;
}

/* watermark overlay */
.sph-image__watermark {
  position: absolute;
  right: var(--sph-space-3);
  bottom: var(--sph-space-3);
  max-width: calc(100% - var(--sph-space-7));
  padding: var(--sph-space-1) var(--sph-space-2);
  border-radius: var(--sph-radius-sm);
  background: rgba(29, 35, 38, .72);
  color: var(--sph-white);
  font: 600 var(--sph-fs-xs)/1.3 var(--sph-font-display);
  letter-spacing: var(--sph-tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sph-image__watermark:empty { display: none; }

/* --------------------------------------------------------------------------
   7c) QR code — `.sph-qr`
   --------------------------------------------------------------------------
   A figure wrapping a self-contained, editable inline QR <svg> (drawn by the
   Go `generator.qrSvg` adapter — no JS engine) with the centered nlpp logo
   composited in. Like `.sph-image` it shrink-wraps and never stretches in a
   row/grid. The code's two colours are custom properties so the whole code
   themes from one place: `--sph-qr-bg` (quiet zone + background) and
   `--sph-qr-module` (the dark modules; `module-tone` overrides it on the
   figure). The SVG elements also carry an inline `fill:var(--…, <hex>)` so a
   sidecar opened on its own — with no stylesheet — still renders scannably. */
.sph-qr {
  --sph-qr-size: 320px;
  --sph-qr-bg: var(--sph-white);
  --sph-qr-module: var(--sph-dark);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sph-space-2);
  width: fit-content;
  max-width: 100%;
  align-self: start;            /* never stretch to a grid / flex row's height */
}
.sph-qr__code {
  display: block;
  width: var(--sph-qr-size);
  max-width: 100%;
  height: auto;                 /* square via the viewBox */
}
.sph-qr__caption {
  max-width: var(--sph-qr-size);
  color: var(--sph-text-muted);
  font: 500 var(--sph-fs-sm)/1.4 var(--sph-font-display);
  text-align: center;
}
/* panel — the same tinted, rounded surface chart panels use, so a QR sits on a
   card-like backing with clear quiet-zone clearance around the white code. */
.sph-qr--panel {
  padding: var(--sph-space-4);
  background: var(--sph-bg-muted);
  border-radius: var(--sph-radius-lg);
}

/* --------------------------------------------------------------------------
   8) Milestone timeline — `.sph-timeline`
   --------------------------------------------------------------------------
   A horizontal rail with N evenly-spaced ticks; each tick = dot + date
   eyebrow + title + body. The rail carries an Info end-cap on the left and
   a Primary end-cap on the right, so it reads as a journey from past to
   present. First and last ticks align their text to the slide edges.
   An optional `__caption` row sets a few one-word descriptors below.

   Markup:
     <div class="sph-timeline">
       <div class="sph-timeline__rail"></div>
       <div class="sph-timeline__ticks">
         <div class="sph-timeline__tick">
           <div class="sph-timeline__dot"></div>
           <div class="sph-timeline__date">June 2023</div>
           <h3 class="sph-timeline__title">The founder's question</h3>
           <p class="sph-timeline__body">…</p>
         </div>
         …
       </div>
       <div class="sph-timeline__caption">
         <span>Calm</span><span>Deliberate</span><span>Methodical</span>
       </div>
     </div>
   -------------------------------------------------------------------------- */
.sph-timeline {
  display: flex;
  flex-direction: column;
  flex: 1;
  justify-content: center;
  margin: var(--sph-space-6) 0 0;
}
.sph-timeline__rail {
  position: relative;
  height: 2px;
  background: var(--sph-border);
  margin: 0 var(--sph-space-10);
}
.sph-timeline__ticks {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  margin: 0 var(--sph-space-10);
}
.sph-timeline__tick { position: relative; text-align: center; }
.sph-timeline__tick:first-child { text-align: left; }
.sph-timeline__tick:last-child  { text-align: right; }
/* Tick markers — the brand's geometric square, the same Swiss-precise mark
   as the list bullets, the footer dot and .dvi-dot; never round. The
   first/last ticks are enlarged and colour-accented and sit flush at the
   rail ends, so they cap the rail themselves — no separate rail end-cap. */
.sph-timeline__dot {
  position: absolute;
  top: -11px;
  left: 50%;
  transform: translateX(-50%);
  width: 14px; height: 14px;
  background: var(--sph-dark);
}
.sph-timeline__tick:first-child .sph-timeline__dot {
  left: 0; transform: none;
  background: var(--sph-info);
  width: 20px; height: 20px; top: -14px;
}
.sph-timeline__tick:last-child .sph-timeline__dot {
  left: auto; right: 0; transform: none;
  background: var(--sph-primary);
  width: 20px; height: 20px; top: -14px;
}
.sph-timeline__date {
  font: 700 var(--sph-fs-base)/1 var(--sph-font-display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sph-primary);
  margin: var(--sph-space-7) 0 var(--sph-space-3);
}
.sph-timeline__tick:first-child .sph-timeline__date { color: var(--sph-info); }
.sph-timeline__title {
  font: 700 var(--sph-fs-lg)/1.2 var(--sph-font-display);
  color: var(--sph-dark);
  margin: 0 0 var(--sph-space-2);
}
.sph-timeline__body {
  font: 500 var(--sph-fs-base)/1.45 var(--sph-font-display);
  color: var(--sph-text-soft);
  margin: 0 auto;
  max-width: 26ch;
}
.sph-timeline__tick:first-child .sph-timeline__body { margin-left: 0; }
.sph-timeline__tick:last-child  .sph-timeline__body { margin-right: 0; }
.sph-timeline__caption {
  margin: var(--sph-space-9) var(--sph-space-10) 0;
  display: flex;
  gap: var(--sph-space-7);
  font: 700 var(--sph-fs-lg)/1.1 var(--sph-font-display);
  color: var(--sph-primary);
}
.sph-timeline__caption span::after {
  content: "\00B7";
  margin-left: var(--sph-space-7);
  color: var(--sph-border-strong);
}
.sph-timeline__caption span:last-child::after { content: none; }

/* --------------------------------------------------------------------------
   9) Layout primitive extras — column head, row flow, grid matrix
   --------------------------------------------------------------------------
   `.sph-stack__head` is a column's tone-able header label (replaces the old
   comparison head). `.sph-row-flow` is a `row flow on`: children
   sit side by side with a centre `__arrow` for a "stage A → stage B"
   transition. A flow row is the slide's own argument, so card titles run at
   hero size. `.sph-grid-matrix` is `grid`: an explicit N-column matrix
   (`--sph-grid-cols`) that wraps into as many rows as needed.

   Markup — row flow (stage A → stage B):
     <div class="sph-row-flow">
       <div class="sph-stack">
         <div class="sph-stack__head sph-stack__head--info">Manual</div>
         <article class="sph-card-cream …">…</article>
       </div>
       <div class="sph-row-flow__arrow">&rarr;</div>
       <div class="sph-stack">…</div>
     </div>
   -------------------------------------------------------------------------- */
.sph-stack__head {
  font: 700 14px/1 var(--sph-font-display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sph-tone, var(--sph-text-muted));
  margin: 0;
}
/* head-tone <v> takes the tone via the tone-system layer — all tones. */
.sph-row-flow {
  display: flex;
  align-items: stretch;
  gap: var(--sph-space-9);
  flex: 1;
}
.sph-row-flow > * { flex: 1; min-width: 0; }
/* The stages of a flow row read as one band: every stack's final block grows
   to the row height, so the boxes bottom-align even when their copy differs.
   (Direct card children already equalise via the row's align-items: stretch;
   this covers the `column { head … card }` form, where the stack stretches
   but its card otherwise would not.)                                         */
.sph-row-flow > .sph-stack > :last-child { flex: 1 1 auto; }
.sph-row-flow__arrow {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--sph-primary);
}
/* The connector is an inline SVG chevron (stroke, round caps — the DS
   chevron language, not a font glyph): the text arrow rendered spindly and
   undersized against full-height stages. currentColor keeps it on the
   primary role token, so theme flips carry it automatically.                 */
.sph-row-flow__arrow svg {
  width: 40px;
  height: 40px;
  display: block;
}
/* A flow row is itself the slide's argument, so its card titles sit above the
   standard 22 px card-title floor (ported from the retired compare rule).     */
.sph-row-flow .sph-card-cream__title {
  font: 800 var(--sph-fs-2xl)/1.15 var(--sph-font-display);
  letter-spacing: -0.01em;
}
.sph-grid-matrix {
  display: grid;
  gap: 24px;
  align-items: stretch;
  grid-template-columns: repeat(var(--sph-grid-cols, 3), 1fr);
}

/* Box alignment — the author writes the absolute `valign` (vertical) + `halign` (horizontal)
   on any layout container (row, column, grid); the renderer maps each to the container's CSS
   axis using these utilities (align-items = cross axis, justify-content = main axis). Both
   apply to flex and grid, so the one simple way to center an element is a column with
   `valign center  halign center`. */
.sph-align-start  { align-items: start; }
.sph-align-center { align-items: center; }
.sph-align-end    { align-items: end; }
.sph-justify-start  { justify-content: start; }
.sph-justify-center { justify-content: center; }
.sph-justify-end    { justify-content: end; }

/* --------------------------------------------------------------------------
   10) Team layout — `.sph-team`
   --------------------------------------------------------------------------
   One prominent member (large portrait + full bio) beside a stacked list of
   N compact members (small portrait + name + role). The standing team slide
   in every deck.

   Markup:
     <div class="sph-team">
       <div class="sph-team__founder">
         <div class="sph-team__portrait"><img src="…" alt="">  <!-- or initials --></div>
         <div class="sph-team__bio">
           <span class="sph-team__eyebrow">Founder · CEO</span>
           <h3 class="sph-team__name">Robert M. Münch</h3>
           <p class="sph-team__role">Inventor of NLPP · Founder &amp; CEO since 2006</p>
           <p class="sph-team__lede">…</p>
           <p class="sph-team__photo-note">Photo placeholder</p>
         </div>
       </div>
       <div class="sph-team__rest">
         <div class="sph-team__member">
           <div class="sph-team__member-portrait">AB</div>
           <div>
             <h4 class="sph-team__member-name">Andreas Bolka</h4>
             <p class="sph-team__member-role">CTO · Head of Development …</p>
           </div>
         </div>
         …
       </div>
     </div>
   -------------------------------------------------------------------------- */
.sph-team {
  display: grid;
  grid-template-columns: 1fr 1.05fr;
  gap: var(--sph-space-8);
  flex: 1;
  align-items: stretch;
}
/* A team with founders but no members spans the full width (no empty bench column). */
.sph-team--no-members { grid-template-columns: 1fr; }
/* The founders column — stacks one or more founders (a team may have several). */
.sph-team__founders {
  display: grid;
  gap: var(--sph-space-7);
  align-content: start;
  min-width: 0;
}
.sph-team__founder {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: var(--sph-space-7);
  align-items: start;
}
.sph-team__bio { min-width: 0; }
.sph-team__portrait {
  width: 280px; height: 340px;
  background: var(--sph-accent-slate);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  color: var(--sph-loafer);
  font: 800 var(--sph-fs-hero)/1 var(--sph-font-display);
  letter-spacing: -0.02em;
}
.sph-team__portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
.sph-team__eyebrow {
  display: block;
  font: 700 var(--sph-fs-sm)/1 var(--sph-font-display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sph-primary);
  margin-bottom: var(--sph-space-3);
}
.sph-team__name {
  font: 800 var(--sph-fs-2xl)/1.08 var(--sph-font-display);
  letter-spacing: -0.01em;
  color: var(--sph-dark);
  margin: 0 0 var(--sph-space-2);
}
.sph-team__role {
  font: 600 var(--sph-fs-md)/1.3 var(--sph-font-display);
  color: var(--sph-text-soft);
  margin: 0 0 var(--sph-space-3);
}
.sph-team__lede {
  font: 500 var(--sph-fs-base)/1.5 var(--sph-font-display);
  color: var(--sph-text);
  margin: 0;
  max-width: 50ch;
}
.sph-team__photo-note {
  font: 600 var(--sph-fs-micro)/1 var(--sph-font-display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sph-text-muted);
  margin-top: var(--sph-space-2);
}
.sph-team__rest {
  display: flex;
  flex-direction: column;
  gap: var(--sph-space-6);
}
.sph-team__member {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: var(--sph-space-5);
  align-items: center;
  padding: var(--sph-space-4);
  background: var(--sph-bg-subtle);
  border-left: 3px solid var(--sph-primary);
}
.sph-team__member-portrait {
  width: 100px; height: 100px;
  background: var(--sph-accent-slate);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  color: var(--sph-loafer);
  font: 800 var(--sph-fs-2xl)/1 var(--sph-font-display);
}
.sph-team__member-portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
.sph-team__member-name {
  font: 700 var(--sph-fs-lg)/1.15 var(--sph-font-display);
  color: var(--sph-dark);
  margin: 0 0 var(--sph-space-2);
}
.sph-team__member-role {
  font: 500 var(--sph-fs-sm)/1.45 var(--sph-font-display);
  color: var(--sph-text-soft);
  margin: 0;
}
.sph-team__member-bio {
  font: 500 var(--sph-fs-sm)/1.45 var(--sph-font-display);
  color: var(--sph-text);
  margin: var(--sph-space-2) 0 0;
}

/* --------------------------------------------------------------------------
   11) Statement — `.sph-statement`
   --------------------------------------------------------------------------
   The centred hero sentence that fills a `.sph-slide--statement`. One
   single-clause line at cover-H1 size, with inline <em> (or .em) keywords
   in Loafer-cream. An optional `__attr` pins an attribution bottom-left.

   Markup:
     <section class="sph-slide sph-slide--statement">
       <div class="sph-statement">
         <p class="sph-statement__text">Why doesn't Saphirion make
           <em>10×</em> more revenue?</p>
       </div>
       <div class="sph-statement__attr">
         <b>The founder's question</b>
         Robert M. Münch · CEO · June 2023
       </div>
     </section>
   -------------------------------------------------------------------------- */
.sph-statement {
  width: 100%;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.sph-statement__text {
  font: 800 var(--sph-slide-h1-cover-size)/1.08 var(--sph-font-display);
  letter-spacing: -0.02em;
  color: #fff;
  text-align: center;
  max-width: 28ch;
  margin: 0;
}
.sph-statement__text em,
.sph-statement__text .em { color: var(--sph-loafer); font-style: normal; }
.sph-statement__attr {
  position: absolute;
  bottom: var(--sph-space-11);
  left: var(--sph-slide-pad-dark);
  font: 500 var(--sph-fs-md)/1.4 var(--sph-font-display);
  color: var(--sph-loafer);
}
.sph-statement__attr b {
  display: block;
  font: 700 var(--sph-fs-xs)/1 var(--sph-font-display);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  margin-bottom: var(--sph-space-2);
}

/* --------------------------------------------------------------------------
   11) Tier ladder — `.sph-tier-ladder`
   --------------------------------------------------------------------------
   A stack of horizontal bands that visualises a weighted hierarchy. Each
   band's WIDTH carries its weight relative to the slide (100% = full
   width); each band's left-accent color carries its tier identity. The
   ladder accepts 1..N bands — author picks the count and the weights.

   Width contract: each band sets its own `--width` (default 100%). Wider
   bands read as "more weight"; narrower bands recede. A single 50%-wide
   band reads as half-weight against an implicit 100% baseline. Tiers
   share a single cream fill (DS rule: hierarchy via width, not via
   varying fill) — that keeps the ladder readable at deck distance and
   keeps Saphirion's restrained surface palette intact. The previous
   deck-local recipe used a white fill on the smallest tier; on a white
   slide that band vanished. The DS rule replaces that with the cream
   fill so every band stays visible regardless of the slide background.

   Slot shape:
     .sph-tier-ladder
       .sph-tier-ladder__band [tonal modifier] style="--width: 100%"
         .sph-tier-ladder__share         — optional left stat (e.g. "100%")
                                            or .--label (uppercase "LEVEL 01")
         .sph-tier-ladder__content       — right body
           .sph-tier-ladder__name        — uppercase tier name eyebrow
           .sph-tier-ladder__inclusions  — descriptive line
       .sph-tier-ladder__band style="--width: 65%" ...
       .sph-tier-ladder__band style="--width: 35%" ...
       .sph-tier-ladder__callout         — optional bottom-right callout pill

   Tonal modifiers (set the left accent border color):
     --brown · --slate · --burgundy · --emerald · --earth · --info · --navy
   Or set inline: `style="--sph-tier-accent: var(--sph-primary)"`.
   -------------------------------------------------------------------------- */
.sph-tier-ladder {
  display: flex;
  flex-direction: column;
  gap: 18px;
  width: 100%;
  position: relative;
}
.sph-tier-ladder__band {
  --sph-tier-accent: var(--sph-tone, var(--sph-primary));
  width: var(--width, 100%);              /* author-set per band */
  display: grid;
  grid-template-columns: 160px 1fr;
  align-items: center;
  gap: 32px;
  padding: 28px 36px;
  background: var(--sph-bg-muted);        /* one cream fill across all tiers */
  border-left: 6px solid var(--sph-tier-accent);
}
/* The share — large stat by default (e.g. "100%"). Mirrors the tier accent
   so the number and the band's identity read together. */
.sph-tier-ladder__share {
  font: 800 48px/1 var(--sph-font-display);
  letter-spacing: -0.02em;
  color: var(--sph-tier-accent);
  font-variant-numeric: tabular-nums;
}
/* Variant — uppercase label (e.g. "LEVEL 01") for the customer-facing
   ladder where percentage shares aren't shown. */
.sph-tier-ladder__share--label {
  font: 700 var(--sph-fs-lg)/1 var(--sph-font-display);
  letter-spacing: 0.22em;
  text-transform: uppercase;
}
/* A label band has no numeric share to align in the fixed 160 px column, so pack the
   label and the name together — the text then sits inside the bar instead of across a
   wide gap. */
.sph-tier-ladder__band:has(.sph-tier-ladder__share--label) {
  grid-template-columns: auto 1fr;
  gap: 20px;
}
.sph-tier-ladder__content {
  display: block;
}
.sph-tier-ladder__name {
  display: block;
  font: 700 16px/1 var(--sph-font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sph-text-muted);
  margin-bottom: 8px;
}
.sph-tier-ladder__inclusions {
  font: 500 18px/1.5 var(--sph-font-display);
  color: var(--sph-text);
  margin: 0;
}
.sph-tier-ladder__inclusions b {
  color: var(--sph-dark);
  font-weight: 700;
}

/* Tonal modifiers — drive the accent border + the share-stat color. */
/* Per-band accent = the tone (tone-system layer): all palette tones. */

/* Optional bottom-right callout pill — the matching analytical note for
   the ladder (e.g. cherry-pick math for a bundle hierarchy). Pins to the
   right edge of the ladder under the last band; `align-self: flex-end`
   inside the flex column keeps it from claiming a full-width row. */
.sph-tier-ladder__callout {
  /* Pinned into the bottom-right, beside the short last band, so it pairs with the ladder
     instead of floating in an isolated row below it. The ladder is `position: relative`. */
  position: absolute;
  right: 0;
  bottom: 0;
  padding: 18px 24px;
  /* `--sph-dark` inverts with the theme (dark on light slides, light on dark slides); the
     text rides `--sph-bg` (the opposite), so the pill stays high-contrast in BOTH themes —
     the white-on-white dark-mode bug was a hard-coded `color:#fff`. */
  background: var(--sph-dark);
  color: var(--sph-bg);
  font: 600 18px/1.3 var(--sph-font-display);
  max-width: 460px;
}
.sph-tier-ladder__callout b {
  display: block;
  font: 700 14px/1 var(--sph-font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sph-bg);
  opacity: 0.72;
  margin-bottom: 8px;
}
.sph-tier-ladder__callout .formula {
  font-family: ui-monospace, "JetBrains Mono", monospace;
  font-size: 16px;
  font-weight: 700;
  color: var(--sph-primary);
  display: block;
  margin-top: 4px;
  letter-spacing: -0.01em;
}

/* --------------------------------------------------------------------------
   12) Cover — `.sph-cover`
   --------------------------------------------------------------------------
   The opening-slide composition that fills a `.sph-slide--cover`. Four
   parts: a brand lockup pinned top-left (the ensemble logo with the
   DATA·VALUE·IMPACT tagline locked directly beneath it, tracked to the
   logo's exact width); an eyebrow + headline group offset into the upper
   third; a meta row pinned to the bottom; and a low-opacity DATA2VALUE
   process-chain breadcrumb drifting behind the content.

   The cover and the closing are the deck's bookend — see README §8. They
   share the rhythm (logo top-left, headline upper, meta row bottom). The
   tagline is the one slot they place differently: the cover locks it into
   the brand mark beneath the logo, the closing keeps it bottom-right.

   Markup:
     <section class="sph-slide sph-slide--cover sph-cover">
       <div class="sph-cover__brand">
         <img class="sph-cover__logo" src="…/saphirion-ensemble-white.svg" alt="Saphirion">
         <span class="sph-cover__tagline">Data<span class="dvi-dot"></span>Value<span class="dvi-dot"></span>Impact</span>
       </div>
       <div class="sph-cover__head">
         <span class="sph-cover__eyebrow">Investor Pitch · May 2026</span>
         <h1 class="sph-cover__title">The <span class="em">Value</span> Intelligence Company</h1>
       </div>
       <div class="sph-cover__meta">
         <div><b>Author</b>Robert M. Münch · CEO &amp; Founder</div>
         <div><b>Company</b>Saphirion AG · Zug · Switzerland</div>
         <div><b>Founded</b>2006</div>
       </div>
       <div class="sph-process-chain is-breadcrumb"
            data-process-chain="…/data2value-process.svg" aria-hidden="true"></div>
     </section>

   Surface, padding, dark-text colour and the top↔bottom `space-between`
   rhythm all come from `.sph-slide--cover` — this block adds only the
   cover-specific composition.
   -------------------------------------------------------------------------- */
.sph-cover {
  /* Brand-lockup width = the rendered width of the ensemble logo. The logo
     artwork is 1869×340 (≈5.497∶1); at 308px wide it stands 56px tall. The
     tagline beneath is tracked to this same width. */
  --sph-cover-lockup-w: 308px;
}

/* Brand lockup — the ensemble logo with the tagline locked beneath it. */
.sph-cover__brand {
  position: absolute;
  top: var(--sph-space-11);            /* 96px */
  left: var(--sph-slide-pad-dark);     /* 110px — the cover's padding */
  z-index: 2;
  width: var(--sph-cover-lockup-w);
  display: flex;
  flex-direction: column;
  gap: var(--sph-space-4);             /* 16px between logo and tagline */
}
.sph-cover__logo {
  display: block;
  width: 100%;                         /* fills the lockup width exactly */
  height: auto;
}
/* DATA·VALUE·IMPACT tagline — tracked so the line spans the logo's width.
   At 16px Work Sans SemiBold, 0.477em is the tracking measured to render
   the line 308px wide — the exact width of the logo above it. The negative
   margin-right cancels the trailing track so the box stays honest. The
   square `.dvi-dot` separators inherit the cream text colour via
   currentColor — the same lockup the closing carries. */
.sph-cover__tagline {
  align-self: start;
  font: var(--sph-weight-semibold) var(--sph-fs-base)/1 var(--sph-font-display);
  letter-spacing: 0.477em;
  text-transform: uppercase;
  color: var(--sph-loafer);
  white-space: nowrap;
  margin-right: -0.477em;
}

/* Headline group — eyebrow + H1, offset into the upper third. It does NOT
   sit flush under the brand lockup (README §8 "Opening & closing bookend"). */
.sph-cover__head {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: var(--sph-space-5);             /* 20px */
  margin-top: 200px;
}
.sph-cover__eyebrow {
  font: var(--sph-weight-semibold) var(--sph-fs-lg)/1 var(--sph-font-display);
  letter-spacing: var(--sph-tracking-wider);
  text-transform: uppercase;
  color: var(--sph-loafer);
}
/* Cover headline — one line at cover-H1 size (README §4 "Titles do not
   wrap"). A headline too long to fit the content width takes a deliberate
   deck-local size-down below the --sph-slide-h1-cover-size token. */
.sph-cover__title {
  font: var(--sph-weight-extrabold) var(--sph-slide-h1-cover-size)/1.05 var(--sph-font-display);
  letter-spacing: var(--sph-tracking-tight);
  color: var(--sph-white);
  margin: 0;
  white-space: nowrap;
}
.sph-cover__title em,
.sph-cover__title .em { color: var(--sph-loafer); font-style: normal; }

/* Meta row — speaker / company / since, pinned to the bottom.
   No-wrap rule: the first two cells (Author, Company) MUST never wrap. The
   rightmost cell may wrap within its track. The row is locked to one line's
   height so a wrapped rightmost cell grows DOWNWARD (into the bottom padding)
   instead of pushing the first line up — the single-line baseline stays put.
   Locked height = label (fs-sm) + its margin (space-2) + one text line
   (fs-xl × line-height 1.4). */
.sph-cover__meta {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: flex-start;
  height: calc(var(--sph-fs-sm) + var(--sph-space-2) + var(--sph-fs-xl) * 1.4);
  gap: var(--sph-space-9);             /* 56px */
  margin-bottom: var(--sph-space-9);
  font: var(--sph-weight-medium) var(--sph-fs-xl)/1.4 var(--sph-font-display);
  color: var(--sph-text-on-dark);
}
/* Author + Company never wrap; the rightmost cell wraps within its width. */
.sph-cover__meta > *:nth-child(1),
.sph-cover__meta > *:nth-child(2) { white-space: nowrap; }
.sph-cover__meta > *:last-child   { min-width: 0; }
.sph-cover__meta b {
  display: block;
  font: var(--sph-weight-bold) var(--sph-fs-sm)/1 var(--sph-font-display);
  letter-spacing: var(--sph-tracking-wider);
  text-transform: uppercase;
  color: var(--sph-loafer);
  margin-bottom: var(--sph-space-2);
}

/* Process-chain breadcrumb — a quiet, low-opacity DATA2VALUE motif drifting
   behind the content in the open middle band. Quieter than the standard
   `.is-breadcrumb` (0.7): on the cover it reads as a watermark, not a step. */
.sph-cover .sph-process-chain {
  position: absolute;
  left: 50%;
  top: 52%;
  transform: translate(-50%, -50%);
  width: 980px;
  opacity: 0.22;
  z-index: 1;
}

/* --------------------------------------------------------------------------
   Vertical stack utilities — `.sph-stack--*`
   --------------------------------------------------------------------------
   Two DS-wide primitives for the recurring "column of stacked rows that
   must span a known vertical band" problem — used on Capacity (Slide-15,
   chart on one side, two commentary cards on the other) and Ask (Slide-16,
   dark headline + five `ask-row` rows beside a use-of-funds pie). Both
   were per-deck regressions: deck-local containers stacked their children
   at natural height, leaving an empty band at the bottom of the column
   that didn't line up with the neighbouring column. The primitives below
   handle the two valid distribution intents — stretch every child equally
   (`--fill`), or pin first-and-last and even-space the rest (`--space`).
   Apply on a flex column (a `<div class="sph-stack sph-stack--fill">`),
   typically as a column inside `.sph-grid-chart` or `.sph-grid-2`.

   `.sph-stack` is the shared shell — flex column with the DS gap. The two
   modifier classes choose the distribution. Pick exactly one.
   -------------------------------------------------------------------------- */
.sph-stack {
  display: flex;
  flex-direction: column;
  gap: var(--sph-space-5);
  min-height: 0;
}

/* --fill — every direct child gets `flex: 1 1 0`, so children grow to the
   same height and together fill the column. Use for stacks of cards that
   carry comparable weight (e.g. the two Capacity commentary cards). */
.sph-stack--fill > * { flex: 1 1 0; min-height: 0; }

/* --space — `justify-content: space-between`. First child top-pinned, last
   child bottom-pinned, every child in between distributes equally through
   the leftover space. Children keep their natural height — this is the
   right pick when a "headline + N rows" column needs its LAST row to
   align with the bottom of a neighbouring column (the Ask col). */
.sph-stack--space { justify-content: space-between; }

/* --------------------------------------------------------------------------
   Closing slide — `.sph-closing`
   --------------------------------------------------------------------------
   The deck's sign-off slide. The cover's twin — same dark San-Juan surface,
   same brand-mark in the top-left, same headline-upper / contact-row-bottom
   rhythm. The one difference is the tagline placement: the cover locks the
   tagline beneath the logo (`.sph-cover__tagline`); the closing keeps it
   bottom-right (`.sph-closing__tagline`). README §8 "Opening & closing
   bookend" describes the matched rhythm; this archetype enforces it.

   Markup:
     <section class="sph-slide sph-slide--closing sph-closing">
       <img class="sph-closing__logo" src="…/saphirion-ensemble-white.svg" alt="Saphirion">
       <div class="sph-closing__head">
         <h1 class="sph-closing__title">Let's spend <em>60 minutes</em> together</h1>
         <p class="sph-closing__promise">I'll bring the math · you bring the questions</p>
       </div>
       <div class="sph-closing__contact">
         <div><b>Write</b>name@saphirion.com</div>
         <div><b>Call</b>+41 41 55 20 21 1</div>
         <div><b>LinkedIn</b>linkedin.com / robertmuench</div>
         <div><b>Visit</b>saphirion.com</div>
       </div>
       <span class="sph-closing__tagline">Data<span class="dvi-dot"></span>Value<span class="dvi-dot"></span>Impact</span>
     </section>

   `.sph-slide--closing` already supplies the surface, padding, dark text
   colour and `space-between` rhythm — this archetype adds only the parts
   the cover/closing pair places. The `__title` size comes from the
   `--sph-slide-h1-closing-size` token so the closing matches the cover
   when both tokens are tuned together. Deck-local CSS should not need to
   restyle these slots.
   -------------------------------------------------------------------------- */
.sph-closing {
  /* Same lockup width as the cover — keeps the brand mark visually identical
     on opening and closing. */
  --sph-closing-lockup-w: 308px;
}
/* Brand lockup — the ensemble logo with the DATA · VALUE · IMPACT tagline
   locked directly beneath it, tracked to the logo's width. The closing now
   mirrors the cover exactly here — same wrapper, same tagline placement.
   (Earlier the closing kept the tagline bottom-right; that's been retired
   so the two slides really do bookend the deck with one identical brand
   lockup top-left.) */
.sph-closing__brand {
  position: absolute;
  top: var(--sph-space-11);
  left: var(--sph-slide-pad-dark);
  z-index: 2;
  width: var(--sph-closing-lockup-w);
  display: flex;
  flex-direction: column;
  gap: var(--sph-space-4);
}
.sph-closing__logo {
  display: block;
  width: 100%;
  height: auto;
}
/* Tagline — tracked so the line spans the logo's width. At 16 px Work Sans
   SemiBold, 0.477em is the tracking measured to render the line 308 px
   wide — the exact width of the logo above. The negative margin-right
   cancels the trailing track. The square `.dvi-dot` separators inherit
   the cream text colour via `currentColor` — same lockup as the cover. */
.sph-closing__tagline {
  align-self: start;
  font: var(--sph-weight-semibold) var(--sph-fs-base)/1 var(--sph-font-display);
  letter-spacing: 0.477em;
  text-transform: uppercase;
  color: var(--sph-loafer);
  white-space: nowrap;
  margin-right: -0.477em;
}

.sph-closing__head {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: var(--sph-space-5);
  margin-top: 200px;          /* same offset as `.sph-cover__head` */
}
.sph-closing__title {
  font: var(--sph-weight-extrabold) var(--sph-slide-h1-closing-size)/1.05 var(--sph-font-display);
  letter-spacing: var(--sph-tracking-tight);
  color: var(--sph-white);
  margin: 0;
  white-space: nowrap;
}
.sph-closing__title em,
.sph-closing__title .em { color: var(--sph-loafer); font-style: normal; }
.sph-closing__promise {
  font: var(--sph-weight-medium) var(--sph-fs-xl)/1.45 var(--sph-font-display);
  color: var(--sph-loafer);
  margin: 0;
  max-width: 60ch;
}
/* Contact row — mirrors `.sph-cover__meta`: same row layout, same b-label
   eyebrow treatment. Stays at the bottom of the slide via the parent's
   `justify-content: space-between`. */
.sph-closing__contact {
  position: relative;
  z-index: 2;
  display: flex;
  gap: var(--sph-space-9);
  margin-bottom: var(--sph-space-9);
  font: var(--sph-weight-medium) var(--sph-fs-xl)/1.4 var(--sph-font-display);
  color: var(--sph-text-on-dark);
}
.sph-closing__contact b {
  display: block;
  font: var(--sph-weight-bold) var(--sph-fs-sm)/1 var(--sph-font-display);
  letter-spacing: var(--sph-tracking-wider);
  text-transform: uppercase;
  color: var(--sph-loafer);
  margin-bottom: var(--sph-space-2);
}
/* Optional QR codes between the promise and contact (the "scan to connect" accent). Several
   are allowed and lay out in a left-aligned row. They are secondary marks on the sign-off
   lockup, so they sit a touch smaller than the standalone default; the white codes keep high
   contrast on the dark surface, and captions lighten to stay legible there. `size` on a block
   still overrides the size (inline wins). */
.sph-closing__codes {
  position: relative;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sph-space-7);
  align-items: flex-start;
}
.sph-closing .sph-qr { --sph-qr-size: 240px; }
.sph-closing .sph-qr__caption { color: var(--sph-loafer); }

/* --------------------------------------------------------------------------
   Small shared utilities
   -------------------------------------------------------------------------- */

/* Square inline separator — the brand's geometric square in place of a round
   mid-dot. Use between the words of a short lockup, e.g. the DATA · VALUE ·
   IMPACT tagline. Inherits the text colour via `currentColor`.               */
.dvi-dot {
  display: inline-block;
  width: 0.26em;
  height: 0.26em;
  background: currentColor;
  vertical-align: 0.16em;
  margin: 0 0.62em;
}

/* Figure formula — the equation caption that sits under a hand-drawn figure
   (a hinge curve, a model sketch). Bold display type, dark ink.              */
.fig-formula {
  font: 700 var(--sph-fs-lg)/1.3 var(--sph-font-display);
  color: var(--sph-dark);
  margin: 0;
}

/* Inline formula — a `formula` used in a composition's text slot (metric-band / card /
   num-tile) instead of `text`. It reuses the standalone `.fig-formula` bold-display look so a
   formula reads the same wherever it appears, but inherits the slot's colour and sits at the
   supporting-text size so it fits the band or card. */
.fig-formula--inline {
  color: inherit;
}

/* ── Metric band ────────────────────────────────────────────────────────
   A full-width, brand-coloured emphasis band that leads with ONE big number
   ("Störer"). Use it below a grid of cards to make a single figure land —
   turnaround time, headline KPI, savings number. The trailing-sibling guard
   above treats a `.sph-metric-band` like a footnote, so the grid above it
   still stretches to fill the body.                                          */
.sph-metric-band {
  display: flex;
  align-items: center;
  gap: var(--sph-space-7);
  background: var(--sph-tone, var(--sph-primary));
  color: var(--sph-tone-ink, #fff);
  border-radius: var(--sph-radius-lg);
  padding: var(--sph-space-6) var(--sph-space-8);
  margin-top: var(--sph-space-2);
}
/* The value and the text are different sizes, so centring their line-BOXES
   (plain `align-items: center`) leaves their glyphs misaligned — the line box
   carries the font's ascent/descent leading, which scales with size. We trim
   each item's box to its real glyph extent (cap-height → alphabetic baseline)
   with `text-box-trim`; once both boxes hug their glyphs, `align-items:center`
   lands their visual centres on the same line, at any size pair. Structural,
   not a per-value nudge. */
.sph-metric-band__value,
.sph-metric-band__text {
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
}
.sph-metric-band__value {
  font: var(--sph-weight-extrabold) var(--sph-fs-3xl)/1 var(--sph-font-display);
  letter-spacing: var(--sph-tracking-tight);
  white-space: nowrap;
}
.sph-metric-band__text {
  font: var(--sph-weight-medium) var(--sph-fs-md)/1.4 var(--sph-font-display);
  /* Slightly softer than the value, tracking whichever ink the tone picked
     (white on dark tones, dark ink on camel/sky) so it reads in both. */
  color: color-mix(in srgb, var(--sph-tone-ink, #fff) 90%, transparent);
  /* Take the band's remaining width past the value and centre the copy in it,
     rather than leaving it left-packed with whitespace trailing right. */
  flex: 1;
  text-align: center;
}
/* Per-tone fills now come from the tone-system layer (top of file): any
   .sph-metric-band--<tone> sets --sph-tone, consumed by the base rule above,
   so every palette tone works here — emerald, navy, camel, … — not just three. */

/* ── Chain callout ──────────────────────────────────────────────────────
   "Where X fits in DATA2VALUE" as a one-class slide body: the DATA2VALUE
   process chain (highlight a step with `data-active` on the chain) plus a
   note box keyed under the active bubble. The note sits left by default
   (under the raw-data entry); the `--note-center` / `--note-right`
   modifiers move it under a middle / right-hand bubble.                      */
.sph-chain-callout {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--sph-space-7);
}
.sph-chain-callout .sph-process-chain { width: 100%; }
.sph-chain-callout__note {
  max-width: 600px;
  background: var(--sph-loafer-soft);
  border-left: 4px solid var(--sph-primary);
  border-radius: var(--sph-radius-md);
  padding: var(--sph-space-5) var(--sph-space-6);
  font: var(--sph-weight-medium) var(--sph-fs-md)/1.5 var(--sph-font-display);
  color: var(--sph-text-soft);
}
.sph-chain-callout__note b {
  display: block;
  font: var(--sph-weight-bold) var(--sph-fs-sm)/1 var(--sph-font-display);
  letter-spacing: var(--sph-tracking-wider);
  text-transform: uppercase;
  color: var(--sph-primary);
  margin-bottom: var(--sph-space-2);
}
.sph-chain-callout--note-center .sph-chain-callout__note { margin-left: auto; margin-right: auto; }
.sph-chain-callout--note-right  .sph-chain-callout__note { margin-left: auto; }

/* ── Table of contents ──────────────────────────────────────────────────
   A compiler-generated agenda: the deck's frame titles, grouped by the
   `divider` section names. Two columns for a long deck; section subheads in
   primary, items numbered. The author writes `toc { }` and the compiler fills
   it — there is no hand-listing (see decklang/charts is N/A; compositions §28).*/
.sph-toc {
  columns: 2;
  column-gap: var(--sph-space-9);
  margin: 0;
  padding: 0;
  list-style: none;
  font: var(--sph-weight-medium) var(--sph-fs-md)/1.7 var(--sph-font-display);
  color: var(--sph-text-soft);
  counter-reset: sph-toc;
}
.sph-toc__item {
  break-inside: avoid;
  counter-increment: sph-toc;
  /* Number gutter + text column: a wrapped entry's second line starts under
     the TEXT, not under the number (hanging indent). max-content sizes the
     gutter to the two tabular digits, identical on every entry.             */
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: var(--sph-space-4);
}
.sph-toc__item::before {
  content: counter(sph-toc, decimal-leading-zero);
  font-weight: var(--sph-weight-bold);
  color: var(--sph-primary);
  font-variant-numeric: tabular-nums;
}
.sph-toc__section {
  break-inside: avoid;
  margin-top: var(--sph-space-5);
  font: var(--sph-weight-bold) var(--sph-fs-sm)/1.6 var(--sph-font-display);
  letter-spacing: var(--sph-tracking-wider);
  text-transform: uppercase;
  color: var(--sph-primary);
}
.sph-toc__section:first-child { margin-top: 0; }
/* Entries are live links into the deck (deck-stage jumps on click via the
   [data-deck-toc-page] delegation) but must not read as browser links: no
   underline, inherited color. The affordance is a quiet jump chevron that
   fades in on hover / keyboard focus — invisible on paper and in exports.    */
.sph-toc__link {
  color: inherit;
  text-decoration: none;
}
.sph-toc__link::after {
  content: "\203A"; /* › */
  display: inline-block;
  margin-left: var(--sph-space-2);
  font-weight: var(--sph-weight-bold);
  color: var(--sph-primary);
  opacity: 0;
  transform: translateX(-3px);
  transition: opacity 120ms ease, transform 120ms ease;
}
.sph-toc__link:hover { color: var(--sph-dark); }
.sph-toc__link:hover::after,
.sph-toc__link:focus-visible::after { opacity: 1; transform: none; }
.sph-toc__link:focus-visible {
  outline: 2px solid var(--sph-focus);
  outline-offset: 2px;
}
/* A toc entry whose target slide is hidden (deck-stage sets data-deck-hide via _syncTocHidden)
   drops out entirely — on screen and in print. display:none also skips the `counter-increment`,
   so the remaining items renumber automatically. */
.sph-toc__item[data-deck-hide], .sph-toc__section[data-deck-hide] { display: none; }

/* ── Split ──────────────────────────────────────────────────────────────
   A constrained two-column body container for heterogeneous blocks — one
   block (or vertical stack) per side, on an even DS grid track. Unlike
   `row` (cards → grid) it places ANY blocks side-by-side; unlike a generic
   columns container it is exactly two sides and does not nest (see
   compositions §29 / ds-rules SPH-W5S2).                                     */
.sph-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sph-space-9);
  width: 100%;
  align-items: stretch;
}
.sph-split > * { min-width: 0; }
/* A table in a split column stays content-height and top-aligned — it must not
   stretch to match the other column (an HTML table fills extra height by
   distributing it into the rows, which balloons the row gaps). */
.sph-split > .sph-table { align-self: start; }

}

/* ==========================================================================
   Type modifiers — author-set WEIGHT, SCALE and ALIGNMENT on any styled leaf.
   --------------------------------------------------------------------------
   Authored as `text { scale 2xl  weight bold  align center { … prose … } }`:
   the modifiers are ordinary DeckLang settings and the prose sits in its own
   inner braces (its clear border). Universal — the same trio works on a body
   `text`, a slide `title`, a `statement`, a card `title`, a `formula`, …

   Each maps onto a DS token (tokens.css): the 12-step type SCALE (--sph-fs-*)
   and the 9-step WEIGHT ladder (--sph-weight-*) — never a raw px size, so the
   design system stays the single source of type. They live in the `utilities`
   layer (declared last) so a deliberate modifier wins over a component's base
   type without !important. */
@layer utilities {

  .sph-scale-micro { font-size: var(--sph-fs-micro); }
  .sph-scale-xs    { font-size: var(--sph-fs-xs); }
  .sph-scale-sm    { font-size: var(--sph-fs-sm); }
  .sph-scale-base  { font-size: var(--sph-fs-base); }
  .sph-scale-md    { font-size: var(--sph-fs-md); }
  .sph-scale-lg    { font-size: var(--sph-fs-lg); }
  .sph-scale-xl    { font-size: var(--sph-fs-xl); }
  .sph-scale-2xl   { font-size: var(--sph-fs-2xl); }
  .sph-scale-3xl   { font-size: var(--sph-fs-3xl); }
  .sph-scale-4xl   { font-size: var(--sph-fs-4xl); }
  .sph-scale-5xl   { font-size: var(--sph-fs-5xl); }
  .sph-scale-hero  { font-size: var(--sph-fs-hero); }

  .sph-weight-thin       { font-weight: var(--sph-weight-thin); }
  .sph-weight-extralight { font-weight: var(--sph-weight-extralight); }
  .sph-weight-light      { font-weight: var(--sph-weight-light); }
  .sph-weight-regular    { font-weight: var(--sph-weight-regular); }
  .sph-weight-medium     { font-weight: var(--sph-weight-medium); }
  .sph-weight-semibold   { font-weight: var(--sph-weight-semibold); }
  .sph-weight-bold       { font-weight: var(--sph-weight-bold); }
  .sph-weight-extrabold  { font-weight: var(--sph-weight-extrabold); }
  .sph-weight-black      { font-weight: var(--sph-weight-black); }

  /* A display scale wants tighter leading so a bold summary line doesn't gap;
     this only applies once an author opts into one of the big sizes. */
  .sph-scale-2xl, .sph-scale-3xl, .sph-scale-4xl,
  .sph-scale-5xl, .sph-scale-hero { line-height: var(--sph-lh-tight); }

  .sph-text-left   { text-align: left; }
  .sph-text-center { text-align: center; }
  .sph-text-right  { text-align: right; }
}
