/* ═══════════════════════════════════════════════════════════════════════════
   SageX - primitives
   Button · Pill · Card · Section
   ═══════════════════════════════════════════════════════════════════════════

   Requires tokens.css. Load it first:
     <link rel="stylesheet" href="/assets/css/tokens.css">
     <link rel="stylesheet" href="/assets/css/primitives.css">

   ───────────────────────────────────────────────────────────────────────────
   PROVENANCE - every rule is one of two things, and says which
   ───────────────────────────────────────────────────────────────────────────
   [LIFTED]    Traits copied from sagex-component-showcase.html, the locked
               SGX-* look-and-behaviour contract. Gradient angles, hover,
               active scale, shadows and animation are unchanged. Only the
               token NAMES were translated (mapping below).
   [COMPOSED]  No showcase rule existed to lift. Built from tokens.css values
               plus the "Ready-made parts" spec in sagex-design-system.md.
               Flagged so it is obvious what was not inherited.

   ───────────────────────────────────────────────────────────────────────────
   TOKEN TRANSLATION - showcase name -> tokens.css name
   ───────────────────────────────────────────────────────────────────────────
     --sans          ->  --font-display      --bg-2          ->  --surface-section
     --fg-0          ->  --ink-heading       --bg-3          ->  --surface-card
     --fg-1          ->  --ink-high          --indigo        ->  --brand-fill
     --fg-3          ->  --ink-subtle        --indigo-text   ->  --brand-text
     --accent-color  ->  --accent            --line-2 / -3   ->  --hairline
     --mono          ->  --font-mono         --ease-out      ->  defined here, GAP 1
     --bitter        ->  omitted entirely - the italic line needs a 4th font
                         the token set does not carry. See GAP 2.

   ───────────────────────────────────────────────────────────────────────────
   ONE DELIBERATE DEPARTURE FROM THE SHOWCASE
   ───────────────────────────────────────────────────────────────────────────
   The showcase defines SGX-BTN-ENH as linear-gradient(135deg, indigo, #06b6d4).
   That gradient was RETIRED in the capital-markets production build for a
   measured accessibility failure - white-on-cyan reads 2.43:1, and 2.88:1 at
   the worst point under the label. No single ink clears 4.5:1 across
   #4C4CFF -> #06B6D4, so a stop had to move rather than the ink.
   This file uses the corrected --cta-gradient-from -> --cta-gradient-to ramp.
   The showcase is the contract for TRAITS; a documented contrast fix
   outranks it on VALUES.

   ───────────────────────────────────────────────────────────────────────────
   GAPS - real, not hidden
   ───────────────────────────────────────────────────────────────────────────
   1. CLOSED 2026-08-07. --ease-out moved to tokens.css, value unchanged
      (cubic-bezier(0.2, 0, 0, 1)). --ease-in-out added there alongside it.
      There is no local copy in this file any more.
   2. The showcase's italic "frustration" line uses a Bitter serif. tokens.css
      carries three fonts (--font-display, --font-body, --font-mono), not four.
      That rule is omitted rather than substituted with a wrong face.
   3. No nav, footer or trust bar here. Those are assembly, not styling -
      they belong in partials.

   4. CLOSED 2026-08-07 (Ram approved the resolution). The capital-markets base
      had adopted the sgx-* contract but defined NINE names differently from
      this file. Resolved by giving each variant its own true lineage rather
      than overwriting either side:

        .sgx-card       -> stays the .advantage-card lift, as it was.
                           The CM cards became .sgx-sqc* (SGX-SQC Status-Quo
                           Cost Card) - a different showcase component, now
                           promoted below under its own name.
        .sgx-pill       -> stays the animated conic-sweep pill.
                           The CM chip became .sgx-tag*, promoted below.
        .sgx-btn--reg   -> the CM hero's "reg" was transparent + border, i.e.
                           THIS file's --ghost. Renamed there to
                           .sgx-btn--ghost; the two rules were already
                           byte-identical, so nothing moved.
        .sgx-card__icon -> stays the 52px card part. The CM 38px one is
                           .sgx-sqc__icon.
        .sgx-section    -> base is shared again. The CM page's translucency
                           (it mounts a fixed ambient backdrop) moved onto a
                           `.has-ambient` body flag, so it no longer redefines
                           what this file declares. Pages with no backdrop are
                           unaffected.
        --ease-out      -> reconciled to THIS value; see GAP 1, now closed.

      The .sgx-btn base was aligned too (padding 14px 22px, gap 8px), which
      took the CM buttons 44px -> 48px tall. Deliberate: adopting the shared
      contract is the point.

      ONE DELIBERATE DIVERGENCE REMAINS, and it is not drift. This file's
      .sgx-btn--enh:hover uses filter:brightness(1.07) with the note "glow,
      never transform - so :active scale still reads". The capital-markets lane
      is under a standing instruction from Ram that motion is TRANSFORM AND
      OPACITY ONLY, so that page keeps translateY(-1px) on enh hover instead of
      animating a filter. Both are correct in their own lane; do not "fix"
      either into the other without checking which lane you are in.

      The single-dash `.sgx-btn.btn-enh` / `.btn-reg` aliases further down are
      now unused by the capital-markets page (it moved to the -- forms). They
      are kept only for any other surface still on the old markup.
   ═══════════════════════════════════════════════════════════════════════════ */


/* GAP 1 CLOSED 2026-08-07 - --ease-out moved to tokens.css (same value,
   cubic-bezier(0.2, 0, 0, 1), the [LIFTED] showcase curve). No local copy here
   any more, so there is exactly one definition. --ease-in-out lives there too. */


/* ═══════════════════════════════════════════════════════════════
   SECTION  [COMPOSED]
   sagex-design-system.md: "Max width 1320px, generous vertical
   padding, light/dark variants". All four values are tokens.
   ═══════════════════════════════════════════════════════════════ */
.sgx-section{
  padding:var(--pad-y) var(--pad-x);
  background:var(--surface-page);
  position:relative;
}
.sgx-section--base{ background:var(--surface-deep); }
.sgx-section--alt { background:var(--surface-section); }

/* ═══════════════════════════════════════════════════════════════════════════
   BREAKOUT GRID  [COMPOSED]  -  D-2026-0807-09, opt-in via .sgx-section--grid.

   One grid declaration replaces per-component max-widths plus their media
   queries. A block picks a band with ONE line (`grid-column: cards`) instead of
   a width plus two breakpoint overrides.

   THE PART THAT FIXES TABLET. Every inner track is `minmax(0, step)` - it may
   grow to the step, and it collapses to NOTHING when there is no room. So the
   ladder dissolves on its own as the screen narrows and the grid degrades to
   gutter-content-gutter with no media query anywhere. The hand-set-inset version
   of this could not do that: measured, it still took 48px per side at 640px and
   produced 199px cards. A minmax(0,…) track cannot, by arithmetic.

   The gutter track is `minmax(var(--gutter), 1fr)`: --gutter is the FLOOR, and
   1fr means that above 1320px the gutters absorb everything, so the bands stop
   growing rather than sprawling on a large monitor.

   THE NARROW TRACK subtracts the gutters (`100% - var(--gutter)*2`) so the
   innermost band can never push past its own breathing room on a phone.

   WHY NOT subgrid FOR NESTED BLOCKS. `.sgx-flow` sits inside `.how-block`, not
   directly on the wrap, so it cannot take a grid-column from this grid. It gets
   `max-width:var(--band-comp); margin-inline:auto` instead, which lands on the
   identical pixel because its parent is centred too. Same TOKEN, so there is
   still one source of truth for the number - only the delivery differs.
   ═══════════════════════════════════════════════════════════════════════════ */
.sgx-section--grid{ padding-inline:0; }      /* the gutter track replaces --pad-x */

.sgx-section--grid > .sgx-wrap{
  display:grid;
  max-width:none;
  margin-inline:0;
  grid-template-columns:
    [full-start]   minmax(var(--gutter), 1fr)
    [page-start]   minmax(0, var(--step-page))
    [cards-start]  minmax(0, var(--step-cards))
    [comp-start]   minmax(0, var(--step-comp))
    [narrow-start] min(var(--band-narrow), 100% - var(--gutter) * 2) [narrow-end]
                   minmax(0, var(--step-comp))  [comp-end]
                   minmax(0, var(--step-cards)) [cards-end]
                   minmax(0, var(--step-page))  [page-end]
                   minmax(var(--gutter), 1fr)   [full-end];
}
/* Default band. Everything sits on `page` unless it says otherwise, so adding a
   block to a section can never accidentally land it full-bleed. */
.sgx-section--grid > .sgx-wrap > *{ grid-column:page; }
.sgx-section--grid > .sgx-wrap > .band-cards { grid-column:cards; }
.sgx-section--grid > .sgx-wrap > .band-comp  { grid-column:comp; }
.sgx-section--grid > .sgx-wrap > .band-narrow{ grid-column:narrow; }
.sgx-section--grid > .sgx-wrap > .band-full  { grid-column:full; }

/* NESTED COMPONENTS RIDE THE REAL GRID, VIA subgrid.
   The first cut gave these `max-width:var(--band-comp); margin-inline:auto` and it
   was WRONG below about 1050px, caught by rubric #2. Reason: the ladder's step
   tracks are minmax(0,…), so they compress proportionally as space runs out - but
   a max-width does not compress, it just stops binding. Below 1050 the flow filled
   its parent and rendered WIDER than the card row above it. Measured at 1024:
   cards 814px, flow 891px. The ladder was upside down and no screenshot showed it.

   `grid-template-columns:subgrid` makes a block adopt its parent's tracks AND their
   line names, so a child two levels down can sit on `comp` and compress in step
   with everything else. Subgrid: Chrome 117+, Safari 16+, Firefox 71+.

   The block itself stays on `page`, so its heading still aligns with the section's
   own intro copy. Only the diagram inside it pulls in. That alignment is the reason
   this is not simply `.how-block{grid-column:comp}`. */
.sgx-section--grid > .sgx-wrap > .how-block{
  display:grid;
  grid-template-columns:subgrid;
  align-content:start;
}
/* :where() so this default carries ZERO specificity and any band class beats it.
   Written the obvious way first - `.sgx-section--grid > .sgx-wrap > .how-block > *`
   at (0,3,0) - it out-specified `.in-comp` at (0,2,0) and silently pinned every
   nested component back to the page band. The rubric caught it: comp read 1320px
   against cards 1152px at every width. A default must never out-rank an opt-in. */
:where(.sgx-section--grid > .sgx-wrap > .how-block) > *{ grid-column:page; }
.sgx-section--grid .in-comp{ grid-column:comp; }

/* COMPONENT ROWS RESPOND TO THEIR BAND, NOT THE VIEWPORT.
   `auto-fit` + a minimum is the container-aware mechanism for a grid: the row
   fits as many columns as it can at >= the floor and drops one when it cannot.
   It is why the rubric's 260px starvation floor is enforced by construction here
   rather than checked after the fact. This REPLACES the two viewport media
   queries these rows used to carry - those could not see that the row now sits
   in a narrower band, which is precisely how the 199px card happened.
   `min(260px,100%)` keeps it from overflowing a phone narrower than the floor. */
.sgx-section--grid .row-fit{
  grid-template-columns:repeat(auto-fit, minmax(min(260px, 100%), 1fr));
}

/* UNDER-NAV  [COMPOSED]
   The header is sticky, so it occupies flow and the first section starts at the
   header's bottom edge. A section that clips its own paint - via its own
   overflow:hidden, which .sgx-section does NOT set; add it per page if you
   need it - has anything it paints
   section paints - its background, and any .bloom inside it - is CLIPPED on that
   line. The band the bar sits on is therefore bare body colour with no glow,
   and the hero's glow does not fade in, it STARTS, at a hard horizontal edge.
   Measured 2026-08-07 on capital-markets: flat rgb(20,16,9) above the line,
   ramping to rgb(73,52,27) within 22px below it.

   This pulls the section's box up by exactly the header's height so the clip box
   starts at y=0 and the section paints behind the bar, then gives the same amount
   back as padding so NOTHING MOVES - content stays on the same pixel and the page
   height is unchanged. Only what is painted behind the bar changes.

   Opt-in, not baked into .sgx-section: a page whose first section is a plain band
   with no glow does not need it, and a page with an opaque header must not have it.
   Put it on the FIRST section only - on a later one it would slide the page up.

   --sgx-hdr-h is published by chrome.css, which owns the header. */
.sgx-section--under-nav{
  margin-top:calc(-1 * var(--sgx-hdr-h));
  padding-top:calc(var(--pad-y) + var(--sgx-hdr-h));
}

.sgx-wrap{
  max-width:var(--max-width);
  margin:0 auto;
  position:relative;
  z-index:2;
}


/* ═══════════════════════════════════════════════════════════════
   BOX MODEL - primitives declare their own. Added 2026-08-28.
   ═══════════════════════════════════════════════════════════════
   WHY THIS EXISTS. Every rule below that pairs `padding` with a
   `min-height`/`height`/`width` is written assuming border-box. Neither
   tokens.css nor this file declared it, so under the browser default
   content-box the padding adds ON TOP of the height and the component
   inflates.

   THIS WAS ALREADY KNOWN AND ALREADY MEASURED. chrome.css:154-161 records
   it - "The 44px CTA measured 68px without this line" - and fixed it by
   scoping `box-sizing:border-box` to `.sgx-hdr *` and `.sgx-ft *`. That
   repaired the two chrome regions and left the defect here, in the shared
   file, for page BODY content to walk into.

   AND EVERY PAGE HAD BEEN PAPERING OVER IT INDEPENDENTLY. Audited
   2026-08-28: index.html ships `* { box-sizing: border-box; }`,
   capital-markets ships `*{box-sizing:border-box}`, why-sagex ships
   `*,*::before,*::after{box-sizing:border-box}` - three authors writing
   the same boilerplate because the primitive would not. The blog page did
   not write it and its CTA rendered 74px against an intended 44px. That is
   the whole failure: a primitive that only renders correctly inside
   someone else's reset is not a primitive.

   SCOPED, NOT GLOBAL. This file styles `.sgx-*`; it declares the box model
   for those elements and their children and imposes nothing on the page
   around them. A page may still ship its own global reset - the three
   above do, and this rule is idempotent with them (verified: VISUAL
   pixel-identical on all three after this landed).
   ═══════════════════════════════════════════════════════════════ */
[class*="sgx-"], [class*="sgx-"] *{ box-sizing:border-box; }


/* ═══════════════════════════════════════════════════════════════
   BUTTON  -  SGX-BTN-REG / ENH / SPC / NUDGE
   Base + :active [LIFTED] from showcase .btn
   ═══════════════════════════════════════════════════════════════ */
.sgx-btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:8px;
  font-family:var(--font-display);
  font-weight:600;
  font-size:var(--text-button);
  line-height:1.2;
  letter-spacing:-0.01em;
  padding:14px 22px;
  min-height:44px;              /* touch target - do not shrink */
  border-radius:var(--radius-pill);
  border:1px solid transparent;
  cursor:pointer;
  text-decoration:none;
  position:relative;
  transition:transform 160ms var(--ease-out),
             background 200ms var(--ease-out),
             box-shadow 200ms var(--ease-out),
             border-color 200ms var(--ease-out),
             filter 200ms var(--ease-out);
}
.sgx-btn:active{ transform:scale(0.985); }

/* ── REG - general use  [LIFTED] ── */
.sgx-btn--reg{
  background:var(--brand-fill);
  /* --cta-text, NOT #fff. FIXED 2026-08-28 at the S4 close.
     --brand-fill flipped lightness when the palette moved: indigo #4C4CFF is a
     DARK fill (white read at 5.55:1), ember #FFAD52 is a LIGHT one - the same
     white measured 1.85:1, a hard AA fail. --cta-text is per-palette (#FFFFFF
     on indigo, #2B1304 on ember = 9.48:1), so the token is correct on BOTH and
     sweep5's 64-run both-palette gate still passes. Same defect class as the
     six home-port rules recorded in decisions/0024. */
  color:var(--cta-text);
}
.sgx-btn--reg:hover{ background:color-mix(in srgb, var(--brand-fill) 86%, #000); }

/* ── REG-GHOST - outline  [LIFTED] ── */
.sgx-btn--ghost{
  background:transparent;
  color:var(--ink-high);
  border-color:var(--hairline);
}
.sgx-btn--ghost:hover{
  color:var(--ink-heading);
  border-color:var(--brand-text);
}

/* ── ENH - primary CTA  [LIFTED traits, CORRECTED values - see header] ── */
.sgx-btn--enh{
  background:linear-gradient(135deg, var(--cta-gradient-from), var(--cta-gradient-to));
  color:var(--cta-text);
  box-shadow:0 10px 26px -12px color-mix(in srgb, var(--cta-gradient-from) 75%, transparent);
}
.sgx-btn--enh:hover{
  filter:brightness(1.07);      /* glow, never transform - so :active scale still reads */
  box-shadow:0 9px 26px -8px color-mix(in srgb, var(--cta-gradient-from) 70%, transparent);
}

/* ── SPC / GLOW - hero tier, pulsing halo  [LIFTED] ── */
.sgx-btn--glow{
  background:color-mix(in srgb, var(--brand-fill) 4%, transparent);
  color:var(--ink-heading);
  border:1px solid color-mix(in srgb, var(--brand-text) 35%, transparent);
  padding:18px 30px;
  font-size:16px;
  overflow:visible;
  isolation:isolate;
}
.sgx-btn--glow::before{
  content:"";
  position:absolute; inset:-2px;
  border-radius:inherit;
  background:radial-gradient(60% 120% at 50% 50%,
             color-mix(in srgb, var(--brand-fill) 45%, transparent), transparent 70%);
  filter:blur(14px);
  z-index:-1;
  animation:sgx-glow-pulse 2.6s ease-in-out infinite;
}
.sgx-btn--glow::after{
  content:"";
  position:absolute; inset:0;
  border-radius:inherit;
  background:linear-gradient(180deg,
             color-mix(in srgb, var(--brand-text) 14%, transparent),
             color-mix(in srgb, var(--brand-text) 3%, transparent));
  z-index:-1;
}
.sgx-btn--glow:hover{
  border-color:color-mix(in srgb, var(--brand-text) 70%, transparent);
  background:color-mix(in srgb, var(--brand-fill) 10%, transparent);
}
/* Stop the pulse once the user has engaged - it has done its job */
.sgx-btn--glow.is-interacted::before{ animation:none; opacity:0; }
.sgx-btn--glow .sgx-arrow{ display:inline-flex; transition:transform 200ms var(--ease-out); }
.sgx-btn--glow:hover .sgx-arrow{ transform:translateX(3px); }

/* ── NUDGE - try-it invitation, breathing ring  [LIFTED] ── */
.sgx-btn--nudge{
  background:var(--surface-section);
  color:var(--ink-heading);
  border:1px solid var(--hairline);
  animation:sgx-nudge-ring 2.2s ease-in-out infinite;
}
.sgx-btn--nudge:hover{
  background:var(--surface-card);
  border-color:color-mix(in srgb, var(--brand-fill) 55%, var(--hairline));
}

/* Aliases - the capital-markets hero uses single-dash names.
   Kept so existing markup does not break. Prefer the -- forms. */
.sgx-btn.btn-enh{
  background:linear-gradient(135deg, var(--cta-gradient-from), var(--cta-gradient-to));
  color:var(--cta-text);
  box-shadow:0 10px 26px -12px color-mix(in srgb, var(--cta-gradient-from) 75%, transparent);
}
.sgx-btn.btn-reg{
  background:transparent;
  color:var(--ink-high);
  border-color:var(--hairline);
}


/* ═══════════════════════════════════════════════════════════════
   PILL  -  SGX-PILL-REG / ENH / SPC
   [LIFTED] from .advantage-pill. SPC reuses this verbatim per the
   showcase's own note. Set --accent per instance to recolour.
   ═══════════════════════════════════════════════════════════════ */
.sgx-pill{
  display:inline-flex;
  align-items:center;
  gap:10px;
  padding:10px 16px;
  border-radius:var(--radius-pill);
  position:relative;
  z-index:1;
  isolation:isolate;
  background:linear-gradient(135deg,
             color-mix(in srgb, var(--accent) 20%, transparent),
             color-mix(in srgb, var(--accent) 6%, transparent));
  box-shadow:0 2px 12px -2px color-mix(in srgb, var(--accent) 32%, transparent);
}
/* Conic sweep around the edge - masked to a 1.5px ring */
.sgx-pill::before{
  content:"";
  position:absolute; inset:-1px;
  border-radius:inherit;
  padding:1.5px;
  --sgx-pill-angle:0deg;
  background:conic-gradient(from var(--sgx-pill-angle),
             color-mix(in srgb, var(--accent) 28%, transparent) 0%,
             color-mix(in srgb, var(--accent) 28%, transparent) 60%,
             color-mix(in srgb, var(--accent) 90%, white) 76%,
             var(--accent) 84%,
             color-mix(in srgb, var(--accent) 28%, transparent) 100%);
  -webkit-mask:linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite:xor;
          mask-composite:exclude;
  animation:sgx-pill-sweep 3.2s linear infinite;
  pointer-events:none;
  z-index:-1;
}
.sgx-pill__icon{ display:inline-flex; }
.sgx-pill__icon svg{ width:16px; height:16px; display:block; color:var(--accent); }
.sgx-pill__text{ font-family:var(--font-display); font-size:14.22px; color:var(--ink-high); }
.sgx-pill__text strong{ color:var(--ink-heading); }


/* ═══════════════════════════════════════════════════════════════
   CARD
   Base [COMPOSED] - the showcase has no .advantage-card base rule
   to lift, so it is built from tokens. Using --panel-* means the
   card follows data-material automatically: solid or glass, no
   second class needed. That is the whole point of the axis.
   Accent line, glow, hover [LIFTED] verbatim.
   ═══════════════════════════════════════════════════════════════ */
.sgx-card{
  position:relative;
  isolation:isolate;
  overflow:hidden;
  padding:var(--space-lg);
  border-radius:var(--radius-card);
  background:var(--panel-bg);
  backdrop-filter:var(--panel-blur);
  -webkit-backdrop-filter:var(--panel-blur);
  border:1px solid color-mix(in srgb, var(--hairline) calc(var(--panel-edge) * 100%), transparent);
  box-shadow:var(--shadow);
  transition:transform 200ms var(--ease-out),
             border-color 200ms var(--ease-out),
             box-shadow 200ms var(--ease-out);
}
/* Top accent hairline  [LIFTED] */
.sgx-card::before{
  content:"";
  position:absolute; top:0; left:12%; right:12%;
  height:1px;
  background:linear-gradient(90deg, transparent,
             color-mix(in srgb, var(--brand-text) 55%, transparent), transparent);
}
/* Upward glow behind the card  [LIFTED] */
.sgx-card::after{
  content:"";
  position:absolute; top:-40%; left:-20%;
  width:140%; height:80%;
  background:radial-gradient(50% 60% at 50% 100%,
             color-mix(in srgb, var(--brand-fill) 18%, transparent), transparent 70%);
  opacity:.55;
  pointer-events:none;
  z-index:-1;
}
/* Hover lift  [LIFTED] */
.sgx-card:hover{
  transform:translateY(-4px);
  border-color:color-mix(in srgb, var(--brand-fill) 40%, transparent);
  box-shadow:0 28px 56px -22px color-mix(in srgb, var(--brand-fill) 40%, transparent);
}

/* Card parts  [LIFTED] */
.sgx-card__icon{
  width:52px; height:52px;
  border-radius:14px;
  display:flex; align-items:center; justify-content:center;
  margin:0 0 28px;
  position:relative; z-index:1;
  color:var(--accent);
  background:linear-gradient(135deg,
             color-mix(in srgb, var(--accent) 28%, transparent),
             color-mix(in srgb, var(--accent) 8%, transparent));
  border:1px solid color-mix(in srgb, var(--accent) 38%, transparent);
}
.sgx-card__icon svg{ width:30px; height:30px; display:block; }

.sgx-card__title{
  font-family:var(--font-display);
  font-weight:600;
  font-size:clamp(20px, 2.2vw, 26px);
  line-height:1.25;
  letter-spacing:-0.018em;
  color:var(--ink-heading);
  margin:0 0 24px;
  position:relative; z-index:1;
}
.sgx-card__divider{
  height:1px;
  margin:0 0 20px;
  position:relative; z-index:1;
  background:linear-gradient(90deg, transparent,
             color-mix(in srgb, var(--brand-text) 45%, transparent), transparent);
}
.sgx-card__body{
  font-family:var(--font-body);
  font-size:var(--text-body);
  line-height:1.65;
  color:var(--ink-body);
  position:relative; z-index:1;
}


/* ═══════════════════════════════════════════════════════════════
   PANEL  [COMPOSED]  - added 2026-08-07
   A raised, interactive SURFACE. Distinct from .sgx-card: a card is a
   self-contained content unit with its own accent line and glow; a panel
   is a container that HOLDS interaction (a run that replays, a chip that
   flips, a button that fires).

   Rule this encodes: ELEVATION ENCODES INTERACTIVITY, NOT IMPORTANCE.
   The capital-markets page had this inverted - blocks that invited a click
   were painted the page's own colour at 85% with no shadow, while static
   hero cards got the lift. A panel therefore sits on --surface-card, takes
   the shadow, and carries a brighter-than-hairline border.
   Note it does NOT set cursor:pointer - a panel is not itself a click
   target; the control inside it is.
   ═══════════════════════════════════════════════════════════════ */
.sgx-panel{
  position:relative;
  padding:var(--space-md);
  border-radius:var(--radius-card);
  background:var(--surface-card);
  border:1px solid color-mix(in srgb, var(--ink-heading) 12%, var(--hairline));
  box-shadow:var(--shadow);
}


/* ═══════════════════════════════════════════════════════════════
   ICON  [COMPOSED]  - added 2026-08-07
   A standalone icon chip. NOT .sgx-card__icon - that one is a card PART
   (52px, accent-filled, 28px bottom margin) and belongs to a card. This
   one is used inside panels, rows and list beats, where a __card parent
   would be a false BEM ancestor.
   Two sizes; stroke colour is the tone axis.
   ═══════════════════════════════════════════════════════════════ */
.sgx-icon{
  flex:0 0 30px;
  width:30px; height:30px;
  border-radius:9px;
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--surface-card);
  border:1px solid var(--hairline);
}
.sgx-icon svg{
  width:17px; height:17px;
  fill:none;
  stroke:var(--ink-body);
  stroke-width:1.6;
  stroke-linecap:round; stroke-linejoin:round;
}
.sgx-icon--sm{ flex:0 0 22px; width:22px; height:22px; border-radius:7px; }
.sgx-icon--sm svg{ width:13px; height:13px; }
.sgx-icon--warn svg{ stroke:var(--cta-gradient-from); }
.sgx-icon--who { color:var(--brand-text); }
.sgx-icon--who svg{ stroke:var(--brand-text); }


/* ═══════════════════════════════════════════════════════════════
   LABEL  [COMPOSED]  - added 2026-08-07
   Bare mono uppercase micro-copy: lane tags, band labels, unit lines,
   roster captions, document heads. NOT a pill - it has no border, no
   radius and no padding, so folding it into .sgx-pill would have been a
   naming lie. This is the .14em cluster that a tracking audit of the
   capital-markets page found repeated across seven separate classes.
   ═══════════════════════════════════════════════════════════════ */
.sgx-label{
  font-family:var(--font-mono);
  font-size:var(--text-mono);
  letter-spacing:var(--tracking-caps);
  text-transform:uppercase;
  color:var(--ink-body);
}
.sgx-label--brand  { color:var(--brand-text); }
.sgx-label--subtle { color:var(--ink-subtle); }
.sgx-label--sm     { letter-spacing:var(--tracking-caps-sm); }
.sgx-label--centred{ text-align:center; }

/* ── SECTION EYEBROW - added 2026-08-09 ──────────────────────────────────
   The uppercase kicker that opens a section. NOT the same thing as
   .sgx-label, which is a MONO data label in --ink-body - they were conflated
   once during the drift audit and the distinction is worth keeping: this is
   display-face, semibold, brand-coloured, and carries the bond margin to the
   heading it introduces.

   Promoted from the capital-markets page, whose values these are verbatim.
   The home page had drifted its own copy - grey instead of brand, .16em
   instead of --tracking-caps, raw 14px/600 instead of the scales - which is
   the "same name, two meanings" collision the audit flagged.

   CM still carries its own .eyebrow rule and is deliberately NOT edited here:
   it is live, and adding this class to a shared file while the page also
   defines it would be a COLLISION under check-drift.py. Different name, so no
   collision. CM converges to this the next time that page is legitimately
   open. New pages use .sgx-eyebrow. */
/* ═══ ADOPTION STATUS - n=2 by ADOPTION since 2026-08-27. ═══
   The 2026-08-10 trigger fired: why-sagex adopted this class (8 markup uses,
   measured on why-sagex/_gate.html at that page's close). With index.html's
   7 uses that is two independent pages - a proven two-consumer primitive.
   CM still renders its own local .eyebrow and converges the next time that
   page is legitimately open. Record: 2026-08-10-sagex-s2-close.md. */
.sgx-eyebrow{
  font-family:var(--font-display);
  font-weight:var(--weight-semibold);
  font-size:var(--text-eyebrow);
  line-height:var(--leading-heading);
  letter-spacing:var(--tracking-caps);
  text-transform:uppercase;
  color:var(--brand-text);
  margin:0 0 var(--space-bond);
}
.sgx-eyebrow--subtle { color:var(--ink-body); }


/* ═══════════════════════════════════════════════════════════════
   SQC - STATUS-QUO COST CARD   [LIFTED from SGX-SQC]  - added 2026-08-07
   A DIFFERENT showcase component from .sgx-card above (which is the
   .advantage-card lift). Both are cards; neither is the other. SQC states a
   cost the reader is paying today: 3px top gradient strip, hover lift with a
   coloured glow, one gradient-clipped emphasis line, and a door-foot that
   resolves it.

   The strip colour is the SEMANTIC axis, per the success register:
   amber = cost / brand attention, green = gained outcome.
   ═══════════════════════════════════════════════════════════════ */
.sgx-sqc{
  position:relative;
  overflow:hidden;
  contain:paint;
  /* Ram, twice: "even more padding needed here - inside the cards". Sides and
     bottom move --space-md -> --space-lg (20->32 desktop). The top keeps its +6
     lift over the side value so the icon still sits off the edge. Deliberately
     NOT a new token - --space-lg is already ratified and already the paragraph
     step, so the card's inner rhythm now matches the page's. */
  padding:calc(var(--space-lg) + 6px) var(--space-lg) var(--space-lg);
  border-radius:14px;
  background:var(--panel-bg);
  backdrop-filter:var(--panel-blur);
  -webkit-backdrop-filter:var(--panel-blur);
  border:1px solid var(--hairline);
  box-shadow:var(--shadow);
  will-change:transform;
  /* OPACITY IS LISTED HERE ON PURPOSE  -  added 2026-08-08.
     `.rv` (the page's scroll-reveal utility) and `.sgx-sqc` are BOTH (0,1,0), and
     primitives loads last, so this rule won the tie and `.rv`'s opacity transition
     never reached these six cards. Measured result: the card's opacity FLIPPED
     instantly while everything else on the page faded over --dur-slow.
     Fixing it here rather than by out-specifying `.rv` on the page, because the
     collision is the component's fault, not the utility's.
     Opacity gets --dur-slow (it only ever changes on reveal); transform keeps
     --dur-micro so the HOVER lift stays snappy. Verified: no hover, :active or
     .is-demo rule touches opacity, so this cannot affect any interaction state. */
  transition:opacity var(--dur-slow) var(--ease-in-out),
             transform var(--dur-micro) var(--ease-out),
             box-shadow var(--dur-micro) var(--ease-out),
             border-color var(--dur-micro) var(--ease-out);
}
.sgx-sqc::before{
  content:"";
  position:absolute; inset:0 0 auto 0;
  height:3px;
  opacity:.9;
}
.sgx-sqc:hover{
  transform:translateY(-4px);
  border-color:color-mix(in srgb, var(--ink-heading) 14%, transparent);
  box-shadow:0 20px 48px -20px var(--shadow-color);
}
.sgx-sqc--problem::before{ background:linear-gradient(90deg, var(--cta-gradient-from), var(--cta-gradient-to)); }
.sgx-sqc--problem:hover, .sgx-sqc--problem.is-demo { box-shadow:0 20px 48px -20px var(--shadow-color), 0 0 80px -30px var(--cta-gradient-from); }
.sgx-sqc--outcome::before{ background:linear-gradient(90deg, var(--signal-ok-bright), var(--signal-ok)); }
.sgx-sqc--outcome:hover, .sgx-sqc--outcome.is-demo { box-shadow:0 20px 48px -20px var(--shadow-color), 0 0 80px -30px var(--signal-ok); }

/* FTUX - the card shows the visitor what it does, once, without being touched.
   Ram: play the hover state about 2s after load, "All three cards in sequence."
   Same declarations as :hover so there is exactly one definition of the state -
   a second copy would drift the moment the hover is retuned. Transform and
   box-shadow only, per the motion rule; nothing that triggers layout. */
.sgx-sqc.is-demo{
  transform:translateY(-4px);
  border-color:color-mix(in srgb, var(--ink-heading) 14%, transparent);
  box-shadow:0 20px 48px -20px var(--shadow-color);
}

.sgx-sqc__icon{
  width:38px; height:38px;
  border-radius:10px;
  display:grid; place-items:center;
  background:var(--well-bg);
  border:1px solid var(--hairline);
}
/* A DEFAULT stroke is required, not optional. A page may override it with an
   SVG paint-server (the capital-markets page strokes these with a brand
   gradient via stroke:url(#dg)), but without a default here an icon on a page
   that defines no gradient renders as an empty box - fill:none + stroke:none
   is invisible. */
.sgx-sqc__icon svg{ width:20px; height:20px; fill:none; stroke:var(--brand-text);
                    stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round; }
/* The emphasis line. Its gradient END STOP is --cta-gradient-to LIGHTENED 35%
   toward --cta-gradient-from: the pure stop measured 3.94:1 against card glass
   over the bloom peak, and 18px bold needs 4.5. The mixed stop measures ~4.8:1.
   Do not revert it to a plain var(--cta-gradient-to). */
.sgx-sqc__emph{
  font-family:var(--font-display);
  font-weight:var(--weight-bold);
  font-size:18px;
  line-height:1.3;
  letter-spacing:var(--tracking-tight);
  color:var(--brand-text);
  margin:2px 0 0;
}
@supports (background-clip:text) or (-webkit-background-clip:text){
  .sgx-sqc__emph{
    background:linear-gradient(135deg, var(--cta-gradient-from),
               color-mix(in srgb, var(--cta-gradient-to) 65%, var(--cta-gradient-from)));
    -webkit-background-clip:text; background-clip:text;
    -webkit-text-fill-color:transparent; color:transparent;
  }
}
/* Door foot. The anchor stretches over the whole card via ::after, so the card
   is one click target with one accessible name - which is also why the card is
   interactive and therefore correctly elevated. */
.sgx-sqc__foot{ margin-top:auto; padding-top:14px; border-top:1px solid var(--hairline); }
.sgx-sqc__foot a{
  display:inline-flex; align-items:center; gap:8px;
  font-family:var(--font-display);
  font-weight:var(--weight-semibold);
  font-size:var(--text-small);
  color:var(--ink-heading);
  text-decoration:none;
}
.sgx-sqc__foot a::after{ content:""; position:absolute; inset:0; }
.sgx-sqc__foot a:focus-visible{ outline:none; }
.sgx-sqc:has(.sgx-sqc__foot a:focus-visible){ outline:2px solid var(--accent-bright); outline-offset:3px; }
.sgx-sqc:active{ transform:translateY(-2px) scale(.99); }

.sgx-sqc__figure{
  font-family:var(--font-display);
  font-weight:var(--weight-bold);
  font-size:46.19px;
  line-height:1;
  letter-spacing:var(--tracking-display);
  color:var(--signal-ok);
  font-variant-numeric:tabular-nums;
}
@supports (background-clip:text) or (-webkit-background-clip:text){
  .sgx-sqc__figure{
    background:linear-gradient(135deg, var(--signal-ok-bright), var(--signal-ok));
    -webkit-background-clip:text; background-clip:text;
    -webkit-text-fill-color:transparent; color:transparent;
  }
}
/* the unit line under a figure - compose with .sgx-label */
.sgx-sqc__unit{ margin:12px 0 0; }


/* ═══════════════════════════════════════════════════════════════
   TAG  [COMPOSED]  - added 2026-08-07
   A bare bordered chip. NOT .sgx-pill above - that one is the animated
   conic-sweep advantage pill with __icon/__text children and an --accent
   gradient fill. This is data chrome: a border, a pill radius, small type.
   Two different components; folding them would have been a naming lie.
   ═══════════════════════════════════════════════════════════════ */
.sgx-tag{
  display:inline-flex;
  align-items:center;
  border:1px solid var(--hairline);
  border-radius:var(--radius-pill);
}
.sgx-tag--chip { font-family:var(--font-mono); font-size:var(--text-small);
                 color:var(--ink-heading); background:var(--surface-card); padding:7px 14px; }
.sgx-tag--ask  { color:var(--ink-high); border-style:dashed; }
.sgx-tag--data { font-family:var(--font-mono); font-size:14.22px;
                 letter-spacing:var(--tracking-micro); padding:2px 9px;
                 font-weight:var(--weight-regular); color:var(--ink-body); }
.sgx-tag--data-sm{ font-family:var(--font-mono); font-size:14.22px;
                 letter-spacing:var(--tracking-micro); padding:2px 8px;
                 font-weight:var(--weight-regular); color:var(--ink-body); }
.sgx-tag--ruled{ font-family:var(--font-mono); font-size:14.22px;
                 letter-spacing:var(--tracking-micro); padding:2px 9px;
                 color:var(--signal-ok);
                 border-color:color-mix(in srgb, var(--signal-ok) 45%, transparent); }
.sgx-tag--ghost{ font-size:14.22px; color:var(--ink-body); border-style:dashed; padding:2px 10px; }
.sgx-tag--dest { gap:7px; font-size:14.22px; font-weight:var(--weight-medium);
                 letter-spacing:var(--tracking-micro); padding:3px 12px;
                 color:var(--cta-gradient-from);
                 border-color:color-mix(in srgb, var(--cta-gradient-from) 40%, transparent); }
.sgx-tag--dest svg{ width:13px; height:13px; fill:none; stroke:currentColor;
                    stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round; }
/* WRAPPING GEOMETRY  -  added 2026-08-08 with the larger type scale.
   At 12.64px mono these statement tags run ~37 characters and cannot fit one
   line on a 334px card, so they wrap - and a pill radius on a two-line box
   renders as a stretched stadium. A 10px radius reads as a deliberate block at
   two lines and still reads fine at one. Mobile only: measured, they do not
   wrap above 640px, so the pill stays a pill everywhere it still works. */
@media (max-width:639px){
  .sgx-tag--data,.sgx-tag--data-sm,.sgx-tag--ruled{
    align-items:flex-start; border-radius:10px; padding:6px 10px; line-height:1.5;
  }
}

/* success recolour, composable with any tag variant */
.sgx-tag.is-ok{ color:var(--signal-ok);
                border-color:color-mix(in srgb, var(--signal-ok) 40%, transparent); }


/* ═══════════════════════════════════════════════════════════════
   KEYFRAMES  [LIFTED] - values verbatim from the showcase
   ═══════════════════════════════════════════════════════════════ */
@keyframes sgx-glow-pulse{
  0%,100%{ opacity:.55; transform:scale(1); }
  50%    { opacity:1;   transform:scale(1.04); }
}
@keyframes sgx-nudge-ring{
  0%,100%{ box-shadow:0 0 0 0 color-mix(in srgb, var(--brand-fill) 0%, transparent); }
  50%    { box-shadow:0 0 0 4px color-mix(in srgb, var(--brand-fill) 30%, transparent); }
}
@property --sgx-pill-angle{ syntax:'<angle>'; initial-value:0deg; inherits:false; }
@keyframes sgx-pill-sweep{
  from{ --sgx-pill-angle:0deg; }
  to  { --sgx-pill-angle:360deg; }
}


/* ═══════════════════════════════════════════════════════════════
   LOGO STRIP  -  PROMOTED 2026-08-10, n=2 BY ADOPTION

   The one component both live pages independently ship. Home built
   .logo-carousel / .logo-track / .logo-item / .logo-img--*; capital-markets
   built .roster .rticker / .rtrack / .rlogos. Measured the same on every axis
   that matters: the same 7 mono assets, the same filter:grayscale(100%), the
   same pause-on-hover, and marquee keyframes that are behaviourally identical
   (translateX(0) -> translateX(-50%) in both).

   IMPORTANT for the promotion record: home did NOT copy this from CM. It was
   invented independently in the April interim LP; only its TREATMENT was
   converged onto CM's on 2026-08-09. Two independent inventions of one
   component is what n=2 means - this is not us promoting our own duplication.

   THE PAGE OWNS THE LIST AND ITS ORDER. This styles the strip only. Home runs
   bloomberg-before-BofA, CM runs BofA-before-bloomberg; both are correct,
   because order is page data. Never hardcode a sequence, a count, or an asset
   here. Per-logo heights are page data too - see --sgx-logos-h below.

   The track must contain its content TWICE for the -50% loop to be seamless.
   That duplication is the page's job (both pages already render 7 x 2 = 14).
   ═══════════════════════════════════════════════════════════════ */
.sgx-logos{
  width:100%;
  overflow:hidden;
  /* fade both edges so logos enter and leave instead of being chopped */
  -webkit-mask-image:linear-gradient(90deg, transparent 0, #000 var(--sgx-logos-fade,10%),
                                     #000 calc(100% - var(--sgx-logos-fade,10%)), transparent 100%);
          mask-image:linear-gradient(90deg, transparent 0, #000 var(--sgx-logos-fade,10%),
                                     #000 calc(100% - var(--sgx-logos-fade,10%)), transparent 100%);
}
.sgx-logos__track{
  display:inline-flex;
  align-items:center;
  gap:var(--sgx-logos-gap, 56px);
  width:max-content;
  animation:sgx-logos-scroll var(--sgx-logos-dur, 38s) linear infinite;
  will-change:transform;
}
.sgx-logos:hover .sgx-logos__track{ animation-play-state:paused; }
.sgx-logos__item{
  flex-shrink:0;
  display:flex;
  align-items:center;
  justify-content:center;
  opacity:var(--sgx-logos-rest, .78);
  /* transform is in the list so a page can add a hover lift without having to
     re-declare `transition` - that would be a COLLISION under check-drift.py */
  transition:opacity 240ms var(--ease-out), transform 240ms var(--ease-out);
}
.sgx-logos__item:hover{ opacity:1; }
.sgx-logos__img{
  display:block;
  width:auto;
  object-fit:contain;
  filter:grayscale(100%);
  /* Height is PAGE DATA - every logo has a different aspect ratio, so a shared
     number would be wrong for six of seven. Defaults to `none` so setting the
     token is opt-in and a page that sizes its own images is never clamped. */
  max-height:var(--sgx-logos-h, none);
}
@keyframes sgx-logos-scroll{
  from{ transform:translateX(0); }
  to  { transform:translateX(-50%); }
}


/* ═══════════════════════════════════════════════════════════════
   MOTION SAFETY  [COMPOSED]
   The showcase kills these animations in its own print/static
   context. Same intent, driven by the user's stated preference.
   ═══════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion:reduce){
  .sgx-btn,
  .sgx-btn--glow::before,
  .sgx-btn--nudge,
  .sgx-pill::before,
  .sgx-logos__track,
  .sgx-card{
    animation:none !important;
    transition:none !important;
  }
  .sgx-card:hover{ transform:none; }

  /* ADDED 2026-08-10 (S2). Its own rule, deliberately NOT folded into the block
     above - that block also covers .sgx-btn, .sgx-btn--nudge, .sgx-pill::before
     and .sgx-card, none of which should be dimmed.

     sgx-glow-pulse cycles opacity .55 -> 1 -> .55. Killing the animation alone
     drops the pseudo-element back to its INITIAL opacity of 1 - brighter than the
     animation's own average, for a user who explicitly asked for less motion.
     Pin it to the cycle's low end so reduced-motion is calmer, not louder.

     Surfaced by the home page, which had been compensating for this page-locally
     with a duplicate opacity declaration - a COLLISION that failed check-drift.py.
     Fixing it here means every page using .sgx-btn--glow inherits the fix instead
     of re-inventing the workaround. Cannot move capital-markets' pixel baseline:
     that is captured without prefers-reduced-motion. */
  .sgx-btn--glow::before{ opacity:.55; }
}

/* MOBILE STEP-DOWN FOR THE SMALLEST CHROME  -  ratified 2026-08-08.
   Ram set two floors: 14px at >=900px, 12px below. These are the roles that take
   the lower one - data chips, doc names, flow captions, micro-labels. The step is
   12.64px, one scale stop under 14.22, so nothing lands ON 12.
   Restated here because the collapse rewrote declarations in place and there was
   no mobile rule to rewrite: without this block they inherit 14.22 everywhere,
   which is larger than what was reviewed and leaves mobile chrome split across
   two sizes for one role. */
@media (max-width:639px){
  .sgx-tag--data,
  .sgx-tag--data-sm,
  .sgx-tag--ruled,
  .sgx-tag--ghost{font-size:12.64px}
}


/* ═══════════════════════════════════════════════════════════════════════════
   S3 - PROMOTED AT THE WHY-SAGEX PAGE CLOSE, 2026-08-27.
   Ruled by Ram ("go with reco") off the firewall table; full record + rejected
   list: handoff-tasks/2026-08-27-sagex-s3-close.md. Every recipe below existed
   page-local on BOTH live consumers (capital-markets authored them; why-sagex
   copied byte-for-byte) - promotion is what makes page four consume instead of
   copying a third time. Values are VERBATIM from the pages; a change here would
   be a fork of two shipped pages at once.

   CONVERGENCE STATE (staged, same as .sgx-logos and .sgx-eyebrow before it):
   · .sgx-ambient family - why-sagex assembly converged 2026-08-27 (its chassis
     now mounts these classes and carries no copy). CM keeps its page-local
     .ambient/.amb-* until that page is next legitimately open.
   · .sgx-bloom / .sgx-grain / .sgx-wash / .sgx-callout - why-sagex CONVERGED
     at the uniform name-pass (2026-08-27, s3converge.py): its sections mount
     the sgx names, keep byte-matched standalone copies in ASSEMBLY: DELETE
     blocks, and tune via page-owned selectors (#the-shift .w1-qcard::after)
     or the --sgx-bloom-top variable. CM still runs its local copies
     (.bloom/.wash/.ucase-card) until next legitimately open.
   · .sgx-section clip fix (overflow:hidden;isolation:isolate) - RATIFIED for
     S3 but NOT landed here: why-sagex's copy is now assembly-stripped, but CM
     and HP still carry identical page-local copies, and landing it while they
     do flips THEM to check-drift collisions. It lands when CM + HP converge.
     Do not add it here before then.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── SGX-AMBIENT - the fixed living backdrop  [n=2 by adoption: CM + why-sagex;
      n=3 by pattern counting HP's own hero glows]. Tokens (--ambient-*) have
      shipped per-palette in tokens.css since 2026-08-09 - this completes that
      promotion with the recipe. Mount once per page, first child of <body>,
      aria-hidden; flag the body `has-ambient` so sections let it through.
      Pages WITHOUT a backdrop are untouched - that is the .has-ambient scoping,
      CM's own design. */
.sgx-ambient{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:clip;
  isolation:isolate;contain:layout paint style;opacity:.7}
.sgx-ambient div{position:absolute;border-radius:50%}
.sgx-ambient__fa{width:120vmax;height:120vmax;left:-35vmax;top:-30vmax;
  background:radial-gradient(closest-side,var(--ambient-primary) 0%,var(--ambient-tertiary) 42%,rgba(0,0,0,0) 72%);
  animation:amb-flow-a 20s ease-in-out infinite alternate}
.sgx-ambient__fb{width:100vmax;height:100vmax;right:-30vmax;bottom:-28vmax;
  background:radial-gradient(closest-side,var(--ambient-secondary) 0%,rgba(0,0,0,0) 70%);
  animation:amb-flow-b 26s ease-in-out infinite alternate}
@keyframes amb-flow-a{from{transform:translate(-13vw,-11.1vh) scale(1)}to{transform:translate(13vw,11.1vh) scale(1.18)}}
@keyframes amb-flow-b{from{transform:translate(13vw,11.1vh) scale(1.18)}to{transform:translate(-13vw,-11.1vh) scale(1)}}
.sgx-ambient__core{width:48vmax;height:48vmax;left:50%;top:44%;margin-left:-24vmax;margin-top:-24vmax;
  background:radial-gradient(closest-side,var(--ambient-deep) 0%,rgba(0,0,0,0) 72%)}
.sgx-ambient__feed{width:58vmax;height:58vmax;left:50%;top:44%;margin-left:-29vmax;margin-top:-29vmax}
.sgx-ambient__f1{background:radial-gradient(closest-side,var(--ambient-primary) 0%,rgba(0,0,0,0) 70%);
  animation:amb-conv-tl 12s ease-in 0s infinite}
.sgx-ambient__f2{background:radial-gradient(closest-side,var(--ambient-secondary) 0%,rgba(0,0,0,0) 70%);
  animation:amb-conv-tr 14s ease-in -4s infinite}
.sgx-ambient__f3{background:radial-gradient(closest-side,var(--ambient-tertiary) 0%,rgba(0,0,0,0) 70%);
  animation:amb-conv-bl 13s ease-in -8s infinite}
.sgx-ambient__f4{background:radial-gradient(closest-side,var(--ambient-primary) 0%,rgba(0,0,0,0) 70%);
  animation:amb-conv-br 15s ease-in -2s infinite}
@keyframes amb-conv-tl{0%{transform:translate(-44vw,-31.7vh) scale(1);opacity:0}28%{opacity:1}100%{transform:translate(0,0) scale(.42);opacity:0}}
@keyframes amb-conv-tr{0%{transform:translate(44vw,-31.7vh) scale(1);opacity:0}28%{opacity:1}100%{transform:translate(0,0) scale(.42);opacity:0}}
@keyframes amb-conv-bl{0%{transform:translate(-44vw,31.7vh) scale(1);opacity:0}28%{opacity:1}100%{transform:translate(0,0) scale(.42);opacity:0}}
@keyframes amb-conv-br{0%{transform:translate(44vw,31.7vh) scale(1);opacity:0}28%{opacity:1}100%{transform:translate(0,0) scale(.42);opacity:0}}
/* the sections let it through - opt-in via the body flag, so the base
   .sgx-section stays untouched for pages with no backdrop */
.has-ambient .sgx-section{background:color-mix(in srgb,var(--surface-page) 88%,transparent)}
.has-ambient .sgx-section--alt{background:color-mix(in srgb,var(--surface-section) 88%,transparent)}
/* ⚑ PHONES REST THE AMBIENT (2026-09-11). Six 100-120vmax gradient discs in a
   position:fixed layer, animating transform and scale for ever, under every
   section of the page. On a Pixel 10 Pro XL (Ram: "a lot of flicker on cm page
   why sagex page blog listing page ... if the scroll is just slightly faster")
   the pages that flicker are exactly the pages that mount this backdrop, and
   the home page, which does not, was not named. The CM orb's own comment
   already records the class: a large fixed composited layer moving under
   momentum scroll. So on a coarse pointer the ambient takes the same rest
   composition it takes under reduced motion - the look at rest is the look
   that ships; only the drift stops. Not device-measured (no Android here);
   reversible by removing the second query. */
@media (prefers-reduced-motion:reduce),(hover:none) and (pointer:coarse){
  /* the ambient falls back to the composition it would have made anyway: the two
     flow fields rest where they are, the four feeders park partway in and hold. */
  .sgx-ambient__fa,.sgx-ambient__fb{animation:none}
  .sgx-ambient__f1,.sgx-ambient__f2,.sgx-ambient__f3,.sgx-ambient__f4{animation:none;opacity:.85}
  .sgx-ambient__f1{transform:translate(-18.5vw,-13.3vh) scale(.72)}
  .sgx-ambient__f2{transform:translate(18.5vw,-13.3vh) scale(.72)}
  .sgx-ambient__f3{transform:translate(-18.5vw,13.3vh) scale(.72)}
  .sgx-ambient__f4{transform:translate(18.5vw,13.3vh) scale(.72)}
}

/* ── SGX-GRAIN - the film overlay that keeps large dark surfaces from banding.
      [rides with the ambient family; per-section mounts on why-sagex, one
      page-level mount on CM]. Absolute film over its positioned host. */
.sgx-grain{position:absolute;inset:0;z-index:1;pointer-events:none;opacity:.16;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E")}

/* ── SGX-BLOOM - the drifting radial pair behind a section's head  [n=2 by
      adoption, byte-identical on both pages]. Driven by the per-palette
      --bloom-* tokens (in tokens.css since the CM build). Host section must
      clip its own paint (the pages pair it with overflow:hidden;isolation).
      why-sagex converged 2026-08-27 (uniform name-pass); CM still runs
      local `.bloom` until that page is next open.
      `top` is the tunable: the raw -30% is a PERCENTAGE of the host section,
      written for a ~1060px hero - any materially taller section shows a
      wandering glow. why-sagex's own enrichment note flagged it; the variable
      resolves it. Pages pin via `--sgx-bloom-top` (why-sagex: -320px on
      .sgx-section) instead of re-setting `top` on this shared class, which
      would be a drift collision. */
.sgx-bloom{position:absolute;top:var(--sgx-bloom-top,-30%);right:-14%;width:min(120%,1100px);height:660px;z-index:0;
  pointer-events:none;
  opacity:calc(var(--bloom-opacity) * 1.25);
  background:radial-gradient(46% 50% at 50% 40%,var(--bloom-from),transparent 68%),
             radial-gradient(36% 42% at 62% 64%,var(--bloom-to),transparent 70%);
  animation:bloom-drift 26s ease-in-out infinite alternate}
@keyframes bloom-drift{from{transform:translate3d(0,0,0) scale(1)}to{transform:translate3d(-3%,2%,0) scale(1.06)}}
/* phones rest the bloom for the same reason as the ambient above (2026-09-11) */
@media (prefers-reduced-motion:reduce),(hover:none) and (pointer:coarse){.sgx-bloom{animation:none}}

/* ── SGX-WASH - the living panel: a drifting dual-radial layer behind a
      panel's content  [n=2 by RECIPE: CM's .wash (the origin, Ram-requested in
      his own words) + why-sagex's s1 quote card]. The DEVICE is the primitive -
      structure, motion, reduced-motion gating, z-layering; consumers may tune
      the color stops and opacity to their host (CM's .is-sx and why-sagex's
      quote card both do). Host must be a positioned stacking context that
      clips or contains paint (.sgx-panel's contain:paint qualifies).
      why-sagex converged 2026-08-27 - s1's quote card consumes this and tunes
      it via an id-prefixed page selector. CM converges when next open. */
@media (prefers-reduced-motion:no-preference){
  .sgx-wash::after{
    content:"";position:absolute;inset:-55%;z-index:-1;pointer-events:none;
    background:
      radial-gradient(38% 42% at 30% 38%,color-mix(in srgb,var(--brand-fill) 20%,transparent),transparent 70%),
      radial-gradient(34% 38% at 74% 66%,color-mix(in srgb,var(--accent) 14%,transparent),transparent 72%);
    opacity:.5;
    animation:panel-drift 26s ease-in-out infinite alternate;
  }
}
@keyframes panel-drift{
  from{transform:translate3d(-4%,-3%,0) scale(1)}
  to  {transform:translate3d(5%,4%,0) scale(1.14)}
}
/* phones keep the wash layer but rest its drift (2026-09-11, same reason as the ambient) */
@media (hover:none) and (pointer:coarse){.sgx-wash::after{animation:none}}

/* ── SGX-CALLOUT - a raised panel with a 3px gradient rail down its left edge
      and a lit top edge  [n=2 by recipe: CM's .ucase-card authored it;
      why-sagex's s3 record cards read it back ON the token scale - this is
      that on-scale form, which is what a promotion ships. CM's raw 12px
      radius / off-scale padding are its own baselined debt and shrink when it
      converges]. Margins and typography stay the page's.
      why-sagex converged 2026-08-27 - s3's record card consumes this. */
.sgx-callout{position:relative;overflow:hidden;
  padding:var(--space-md) var(--space-md) var(--space-md) calc(var(--space-md) + 11px);
  border-radius:var(--radius-card);
  background:var(--surface-raised);
  border:1px solid var(--hairline);
  box-shadow:var(--shadow)}
.sgx-callout::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;
  border-radius:var(--radius-pill);
  background:linear-gradient(180deg,var(--cta-gradient-from),var(--cta-gradient-to))}
/* the glint, gated on --panel-edge so it paints only on the glass material */
.sgx-callout::after{content:"";position:absolute;left:0;right:0;top:0;height:1px;
  background:var(--glint);opacity:var(--panel-edge)}


/* ═══════════════════════════════════════════════════════════════
   SGX-QA - the <details> Q&A accordion            [S4 CANDIDATE, promoted
   2026-08-27 at the blog page close]
   ═══════════════════════════════════════════════════════════════
   PROMOTED AT n=2 BY ADOPTION, SAME STRUCTURE ON BOTH CONSUMERS:
     · capital-markets  .qa   x5  - authored it, and this is the promoted form
     · blog             x11 posts - built .faq__q for the identical job before
       checking sgx-component-lookup.md. That was the miss the lookup exists to
       prevent; resolved by promoting CM's form rather than keeping two.

   ⚑ THE PROMOTED FORM IS CM'S, sliced VERBATIM and renamed .qa* -> .sgx-qa*.
   It is the on-token-scale one (--hairline, --radius-card, --glass-display,
   --space-*, --ease-out, --text-small, --ink-*, --brand-text). The blog's
   .faq__q was a thinner hand-build and is deleted, not merged.

   NOT PROMOTED - CM's .qa-tag / -beats / -beat / -ico and .qa-body .interlink
   are that PAGE'S content furniture, not the accordion. They stay page-local.

   MARKUP
     <details class="sgx-qa">
       <summary class="sgx-qa__q">
         <span class="sgx-qa__chev" aria-hidden="true"><svg .../></span>Question
       </summary>
       <div class="sgx-qa__body"><p class="sgx-qa__a">Answer</p></div>
     </details>

   `.is-closing` is a JS-driven exit state. Without that JS the accordion still
   works natively - the class is simply inert. CM drives it; the blog does not.

   CONVERGENCE: CM still runs its local `.qa` names - DORMANT by name, exactly
   as .sgx-bloom/.sgx-wash/.sgx-callout were left at the S3 mint. CM renames in
   the same pass that converges those. No drift collision meanwhile: the names
   differ, so nothing is declared twice.
   ═══════════════════════════════════════════════════════════════ */
.sgx-qa{border:1px solid var(--hairline);border-radius:var(--radius-card); background:var(--glass-display); -webkit-backdrop-filter:var(--panel-blur);backdrop-filter:var(--panel-blur); /* ELEVATION RECIPE 2 of 2 on this page. Recipe 1 is --shadow (0 40px 90px -50px) - a 90px blur with -50px spread is invisible under a 66px row, so a shallow recipe is needed, not a tweak of the deep one. R4 allows two authored recipes; it forbids a ramp. If a third is ever wanted, replace one. */ box-shadow:0 10px 30px -22px var(--shadow-color); transition:border-color var(--dur-micro) var(--ease-out), background-color var(--dur-micro) var(--ease-out);}
.sgx-qa:hover{border-color:color-mix(in srgb,var(--ink-heading) 16%,transparent)}
.sgx-qa[open]{background:color-mix(in srgb,var(--surface-card) 72%,transparent); border-color:color-mix(in srgb,var(--brand-text) 26%,var(--hairline))}
.sgx-qa__body{opacity:1;transform:none; transition:opacity 170ms var(--ease-out),transform 170ms var(--ease-out)}
.sgx-qa.is-closing .sgx-qa__body{opacity:0;transform:translateY(-6px)}
.sgx-qa__q{list-style:none;cursor:pointer;display:flex;gap:var(--space-sm);align-items:flex-start; padding:15px var(--space-md);font-family:var(--font-display);font-weight:var(--weight-semibold);font-size:16px; /* Ram: the question and the answer were both white, so nothing said which was which. --brand-text is the page's existing link/emphasis tone - no new token. */ line-height:1.4;color:var(--brand-text)}
.sgx-qa__q::-webkit-details-marker{display:none}
.sgx-qa__q:hover{color:var(--ink-heading)}
.sgx-qa__chev{flex:0 0 16px;width:16px;height:16px;margin-top:2px;color:var(--brand-text); transition:transform var(--dur-micro) var(--ease-out)}
.sgx-qa__chev svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2; stroke-linecap:round;stroke-linejoin:round;display:block}
.sgx-qa[open] .sgx-qa__chev{transform:rotate(90deg)}
.sgx-qa__body{padding:0 var(--space-md) var(--space-md) calc(var(--space-md) + 16px + var(--space-sm))}
.sgx-qa__a{font-size:var(--text-small);line-height:1.6;color:var(--ink-high);margin:0;max-width:40em}
@media (max-width:639px) {
  .sgx-qa__q{font-size:16px;padding:14px var(--space-md)}
}
@media (prefers-reduced-motion:reduce) {
  .sgx-qa__body{transition:none}
  .sgx-qa.is-closing .sgx-qa__body{opacity:1;transform:none}
  .sgx-qa__chev{transition:none}
}

/* ═══════════════════════════════════════════════════════════════
   SGX-PROSE - running rich-text presets          [S5 MINT, promoted
   2026-09-01 at the invention-queue drain, D1]
   ═══════════════════════════════════════════════════════════════
   PROMOTED BECAUSE THE SYSTEM SHIPPED NO PROSE TYPOGRAPHY AT ALL. Verified at
   the drain: zero prose defaults anywhere in primitives. Every page so far is
   hand-composed sections, so each one that needed running text invented its own.
     · blog      .post__body  x12 - authored it, and THIS IS THE PROMOTED FORM
     · about     .abt-pull + .abt-body strong - the 2nd hand-composed-prose page
   ⚑ RULED FORWARD, NOT AT n=2 ALONE. privacy, terms and trust are all running
   prose and all three are still in build.py's UNBUILT_DIRS. Landing this BEFORE
   they are built is the whole point: they consume a primitive instead of forking
   blog's page-local CSS a third, fourth and fifth time. Ram, 2026-09-01:
   "do D1 now, before privacy/terms/trust".

   ⚑ THE PROMOTED FORM IS BLOG'S, SLICED VERBATIM and renamed .post__body ->
   .sgx-prose - the same procedure .sgx-qa used at the S4 close. Verbatim is
   deliberate: it means blog converges PIXEL-IDENTICALLY when it renames, and it
   keeps a type-scale decision (below) out of a promotion where it does not belong.

   ONE ADDITION, and it is ruled in: `__pull`, from about's `.abt-pull`. Blog's
   set has no emphasis rung between body and a heading, and About needed one for
   the /about-2 argument beat. About's `strong` rung needed no addition - blog's
   set already carries it, so the promotion ABSORBS it rather than merging it.

   NOT PROMOTED - blog's `.post__toc`, `.post__progress`, `.post__rt`,
   `.post__disc`, `.post__table` and the `h2{scroll-margin-top}` that serves the
   TOC. Those are long-form PAGE FURNITURE, not running text. They are PENDING D1
   in the queue and convert when a second long-form page actually needs them.

   ⚑ NOT DECIDED HERE, ON THE RECORD: THE TYPE SCALE HAS A HOLE between
   `--text-h3`/`--text-lead` (20.25px) and `--text-h2` (39px), and THREE separate
   page-local inventions are patching it - blog's h2 `clamp(22px,2.4vw,27px)`,
   blog's h3 `clamp(18px,1.9vw,20.25px)` and about's `--abt-conv-title`
   `calc(var(--text-lead) * 1.185)` = 24px. Those clamps come across VERBATIM
   below rather than being resolved onto tokens, because minting type rungs is a
   scale decision for Ram, not a side effect of a promotion. Raised at the drain.

   MARKUP
     <div class="sgx-prose"> ...h2/h3/p/ul/ol/li/a/strong/blockquote/code... </div>
     <p class="sgx-prose__pull">the one argument beat that must not read as body</p>

   CONVERGENCE: blog still runs `.post__body` and about still runs `.abt-pull` -
   DORMANT BY NAME, exactly as .sgx-bloom/.sgx-wash/.sgx-callout were left at the
   S3 mint and .qa at S4. Both rename in the CM + HP convergence pass. No drift
   collision meanwhile: the names differ, so nothing is declared twice, and NO
   PAGE'S PIXELS MOVE ON THIS COMMIT.
   ═══════════════════════════════════════════════════════════════ */
.sgx-prose{font-family:var(--font-body);font-size:var(--text-body);
  line-height:var(--leading-body);color:var(--ink-body)}
.sgx-prose h2{font-family:var(--font-display);font-weight:var(--weight-bold);
  font-size:clamp(22px,2.4vw,27px);line-height:1.15;
  letter-spacing:var(--tracking-tight);color:var(--ink-heading);
  margin:var(--space-xl) 0 var(--space-md)}
.sgx-prose h3{font-family:var(--font-display);font-weight:var(--weight-semibold);
  font-size:clamp(18px,1.9vw,20.25px);line-height:1.3;
  letter-spacing:var(--tracking-tight);color:var(--ink-high);
  margin:var(--space-comp) 0 var(--space-sm)}
.sgx-prose p{margin:0 0 var(--space-md)}
.sgx-prose ul,.sgx-prose ol{margin:0 0 var(--space-md);padding-left:22px}
.sgx-prose li{margin:0 0 8px}
/* links take --brand-text, never --brand-fill: amber fill fails as link ink */
.sgx-prose a{color:var(--brand-text);text-decoration:underline;
  text-underline-offset:2px}
.sgx-prose a:hover{color:var(--ink-heading)}
.sgx-prose strong{color:var(--ink-high);font-weight:var(--weight-semibold)}
.sgx-prose blockquote{margin:var(--space-md) 0;padding:var(--space-md);
  border-left:3px solid var(--accent);border-radius:0 var(--radius-card) var(--radius-card) 0;
  background:var(--surface-card);color:var(--ink-body)}
.sgx-prose blockquote p:last-child{margin:0}
.sgx-prose code{font-family:var(--font-mono);font-size:var(--text-mono);
  background:var(--surface-card);padding:2px 8px;border-radius:var(--radius-pill)}
/* THE PULL RUNG - about's `.abt-pull`, the one addition to blog's set. A step
   above body in BOTH size and colour, inset both sides. Deliberately NO left
   rail: `.sgx-callout` is the rail device, and a second rail reads as a repeat
   of it rather than as emphasis. Scoped under .sgx-prose at (0,2,0) so it
   out-ranks `.sgx-prose p` (0,1,1) on margin - the same specificity reason the
   About original carried. */
.sgx-prose .sgx-prose__pull{font-size:var(--text-lead);line-height:1.5;
  color:var(--ink-high);padding-inline:var(--space-md);margin:var(--space-xl) 0}
