/* ==================================================================
   THE SPACE PROGRAM — shared styles for the Train + Programs pages.
   Design system lifted from the staging homepage (Heavy Meta / hm-):
   Helvetica Neue 700 sentence-case headlines with an amador accent word,
   SF Mono eyebrows/labels, chalk #e0d9cc + gold #ddcc5c on
   near-black #0a0a0a, grain overlay, hairline-bordered tiles.
   Scoped under .sp-page so it never touches the rest of the site.
   Per DESIGN-SYSTEM.md: headings are Helvetica Neue 700 with
   text-transform:none — never forced uppercase. The accent word (.sp-black)
   is amador 400 in gold; amador ships ONE weight, so never set 700 on it.
   amador is an Adobe Typekit face — each page's markup must link
   https://use.typekit.net/oxm2mud.css (it replaces the old Pirata One link).
   ================================================================== */

.sp-page{
  --bg:#0a0a0a;
  --surface:#0f0f0e;
  --chalk:#e0d9cc;
  --gold:#ddcc5c;
  /* Muted copy is NEUTRAL gray, not warm tan — DESIGN-SYSTEM.md § Tokens
     (--text-muted "no warm color cast"). --mut/--dim keep their names so
     existing rules resolve, but both now carry the neutral value.
     Alphas are contrast-floored against --bg #0a0a0a: --mut = 5.46:1 and
     --dim = 5.01:1, both clearing WCAG AA 4.5:1.

     --dim CANNOT go darker. It lands on 11px mono labels (specs, .sp-fine,
     .sp-door .d), which get no large-text exemption, so .50 (4.68:1) is the
     floor; a deeper "dim" like .38 measures 3.14:1 and fails AA.

     That leaves --dim and --mut only 3% apart — near-identical as colors. The
     quiet/loud hierarchy they used to carry is therefore held by SIZE, WEIGHT
     and TRACKING instead (11px tracked mono label vs 15px/1.65 sans copy),
     which is the more robust signal anyway. Do not try to restore the
     contrast gap by darkening --dim. */
  --dim:rgba(236,236,236,.52);
  --mut:rgba(236,236,236,.55);
  --hair:rgba(224,217,204,.18);
  --hair-soft:rgba(224,217,204,.12);
  --mono:ui-monospace,'SF Mono',Menlo,Consolas,monospace;

  background:var(--bg);
  color:var(--chalk);
  font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;
  font-weight:300;
  -webkit-font-smoothing:antialiased;
  position:relative;
  overflow-x:hidden;
}
.sp-page *{box-sizing:border-box;margin:0;padding:0;}
.sp-page a{color:var(--chalk);text-decoration:none;transition:color .2s,border-color .2s,background .2s;}
.sp-page a:hover{color:var(--gold);}

/* Squarespace reveal-system neutralizer (same fix as the program pages) */
.sp-page .preFade,.sp-page .fadeIn,.sp-page .preScale,.sp-page .scaleIn{
  opacity:1 !important;transform:none !important;transition:none !important;animation:none !important;
}
.sp-page h1,.sp-page h2,.sp-page h3{
  color:var(--chalk);
  font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;
  font-weight:700;text-transform:none;
}

/* Film-grain overlay (from the homepage) */
.sp-grain{
  position:absolute;inset:0;pointer-events:none;z-index:40;opacity:.06;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 .8 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
}

/* ---- Type ---- */
.sp-mono{font-family:var(--mono);}
/* ---- Kicker / callout — the section label (DESIGN-SYSTEM.md § Kicker).
   Mono, gold, tracked caps, with the short accent rule before it. It is
   inline-flex so it shrink-wraps to its text rather than filling the column. */
.sp-kicker,.sp-eyebrow{
  font-family:var(--mono);font-size:11px;letter-spacing:.32em;
  text-transform:uppercase;color:var(--gold);font-weight:500;
  display:inline-flex;align-items:center;gap:14px;
}
.sp-kicker::before,.sp-eyebrow::before{
  content:"";width:24px;height:1px;background:currentColor;
  display:inline-block;flex:none;
}
@media (min-width:761px){.sp-kicker{font-size:13px;}}

/* .sp-eyebrow is the SMALL kicker — the same component one step down, for
   labels that repeat above every row/section and would otherwise compete with
   the heading they introduce. It stays at 11px rather than stepping up to
   13px, and its rule shortens to match the smaller type. */
.sp-eyebrow{font-size:11px;letter-spacing:.28em;gap:10px;}
.sp-eyebrow::before{width:16px;}
/* Hero title — Helvetica 700, sentence case, line-height .95. Same shape as
   the space-program hero (.ev-hero--space h1), but capped at 80px on desktop
   rather than that page's 64px: this is a section landing page, so the title
   is the largest thing on it and carries the whole header.
   The 44px floor and the 8vw fluid middle are unchanged — only the cap moves,
   so nothing below ~1000px is affected. */
.sp-h1{
  margin-top:4px;
  font-size:clamp(44px,8vw,80px);
  line-height:.95;letter-spacing:normal;
  text-transform:none;font-weight:700;
  text-wrap:balance;
}
.sp-h2{
  margin-top:14px;
  font-size:clamp(40px,5vw,68px);
  line-height:1.02;letter-spacing:-.015em;
  text-transform:none;font-weight:700;
}
/* Gothic accent word — the “heavy meta” logotype face. amador has a single
   weight, so it must reset the 700 it would otherwise inherit from the
   heading, and its case follows the copy rather than being forced. */
/* amador runs small next to Helvetica at the same px, so it is bumped 1.12em
   to keep the cap heights matched — same treatment as the hero's <em>. */
.sp-black{
  font-family:'amador','Helvetica Neue',Helvetica,sans-serif;
  font-style:normal;font-weight:400;text-transform:none;color:var(--gold);
  font-size:1.12em;line-height:1;
}
/* Hero lede — matches .ev-hero--v2 .ev-lede: clamp(18px,2.2vw,24px)/1.35 at
   weight 300, capped 600px. */
.sp-lede{
  margin-top:26px;max-width:600px;
  font-size:clamp(18px,2.2vw,24px);line-height:1.35;
  font-weight:300;color:var(--mut);text-wrap:pretty;
}
.sp-lede strong{color:var(--chalk);font-weight:500;}
.sp-fine{font-family:var(--mono);font-size:11px;letter-spacing:1px;color:var(--dim);}

/* Jump targets for the hero's split-lede buttons. scroll-margin-top holds
   the heading off the viewport's top edge when it is scrolled to, so it
   lands with air above it rather than flush against the browser chrome.
   Set on the TARGETS, not as a global scroll-padding, so it cannot affect
   any other anchor on the page.

   smooth is opt-out under prefers-reduced-motion: a full-page jump is
   exactly the kind of motion that setting exists for. */
.sp-page #sp-start-sequence,
.sp-page #sp-missions{scroll-margin-top:80px;}
@media (prefers-reduced-motion:no-preference){
  html:has(.sp-hero--split){scroll-behavior:smooth;}
}

/* ---- HERO VARIANT · split lede (.sp-hero--split) -------------------
   The default hero runs one 600px paragraph under the title. This variant
   splits it into the two things the page actually contains — the Start
   Sequence and the Missions — as a labelled two-column row beneath the h1.

   Why a variant class rather than a replacement: the single-paragraph lede
   is the right shape wherever the header introduces ONE idea. This page's
   lede is two definitions that happen to be joined by a full stop, and a
   reader scanning it has to parse the sentence to find the split. Columns
   make the parallel structure the layout instead of the prose.

   The row is NOT capped at the lede's 600px: two columns of that measure
   would run to a 300px each, too narrow for the copy. It takes the section
   width and caps each column by its own track instead. */
.sp-hero--split .sp-lede-split{
  margin-top:38px;
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  /* The column gap is wide because there is no rule between the tracks —
     the space is what separates them, so it has to be unambiguous. */
  gap:40px 64px;
  max-width:980px;
  /* Both columns are the same track (1fr each) AND the same height: the
     buttons sit at the bottom of each column, so without a common height
     they would land at different points depending on how many lines the
     copy above them ran to. */
  align-items:stretch;
}
/* The column is a flex column so the button can be pushed to its bottom
   edge with margin-top:auto, which is what lines the two buttons up when
   the copy above them is uneven. */
.sp-hero--split .sp-lede-split > div{
  display:flex;flex-direction:column;align-items:flex-start;
}
/* Each column is led by a Helvetica heading, not the mono label component:
   these name the two halves of the page, and a gold mono label here would
   read as the same tier as the section eyebrows directly below it. Chalk
   and sentence case keeps the hero's own voice; gold stays on the accent
   word in the h1 and on the buttons' hover. */
.sp-hero--split .sp-lede-split h2{
  font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;
  font-size:clamp(19px,1.8vw,23px);line-height:1.2;
  font-weight:700;text-transform:none;letter-spacing:normal;
  color:var(--chalk);
  margin:0 0 12px;
}
/* The qualifier line sits ABOVE the name it belongs to — "Completed in order"
   over "Start Sequence". It is a <span> inside the h2 rather than its own
   element so the two stay one heading to a screen reader, read as one phrase,
   and cannot be separated by a future layout change.

   display:block is what puts it on its own line; the name follows on the next.
   Set in the mono label treatment at the small variant's proportions, so it
   reads as a caption to the name rather than as a second heading. It carries
   no rule of its own — the .sp-eyebrow dash would collide with the h2 beneath
   it, and at this size the position already says what the line is. */
.sp-hero--split .sp-lede-split h2 .q{
  display:block;
  font-family:var(--mono);
  font-size:11px;letter-spacing:.28em;text-transform:uppercase;
  font-weight:500;line-height:1.4;
  color:var(--gold);
  margin-bottom:8px;
}
/* Body sits a step DOWN from the single-paragraph lede's 18-24px: at half
   the width that size wraps every four or five words. This is the same
   role, set to the measure it actually has. */
.sp-hero--split .sp-lede-split p{
  font-size:clamp(16px,1.5vw,19px);line-height:1.5;
  font-weight:300;color:var(--mut);
  text-wrap:pretty;margin:0;
}
.sp-hero--split .sp-lede-split strong{color:var(--chalk);font-weight:500;}
/* Jump link to the section each column describes. TERTIARY tier: no border,
   no fill, flush left — the hero already has the h1 carrying it, so these
   are wayfinding, not calls to action.

   The tertiary rule elsewhere is scoped to .sp-prog-body, so it is restated
   here rather than widened: the two live at different sizes and this one
   needs the top margin the row version does not.

   margin-top:auto pushes it to the bottom of its flex column, so the two
   buttons share a baseline even when one column's copy runs a line longer. */
.sp-hero--split .sp-lede-split .sp-ghost{
  margin-top:auto;
  padding-top:18px;padding-left:0;padding-right:0;padding-bottom:0;
  background:transparent;border-color:transparent;
  color:var(--chalk);
}
.sp-hero--split .sp-lede-split .sp-ghost:hover{
  background:transparent;border-color:transparent;color:var(--gold);
}
/* The arrow points DOWN, not right: this scrolls to a section further down
   the same page rather than navigating away, and the direction is the only
   thing distinguishing the two at a glance. Rotating the shared icon keeps
   one arrow asset on the page instead of adding a second SVG path. */
.sp-hero--split .sp-lede-split .sp-ghost .hm-btn__arw{
  transform:rotate(90deg);
  transition:transform .2s;
}
.sp-hero--split .sp-lede-split .sp-ghost:hover .hm-btn__arw{
  transform:rotate(90deg) translateX(3px);
}
@media (prefers-reduced-motion:reduce){
  .sp-hero--split .sp-lede-split .sp-ghost:hover .hm-btn__arw{transform:rotate(90deg);}
}
/* One column below 760px — at phone width two tracks put the copy at ~20
   characters. Stacked, the labels do the separating on their own, so the
   row gap tightens; 32px is enough to keep the two blocks distinct without
   pushing the second one off the fold. */
@media (max-width:760px){
  .sp-hero--split .sp-lede-split{
    grid-template-columns:1fr;
    gap:32px;
    margin-top:30px;
  }
  /* Stacked there is no second column to align against, so the button sits
     on its own spacing rather than being pushed to a shared bottom edge. */
  .sp-hero--split .sp-lede-split .sp-ghost{margin-top:0;padding-top:14px;}
}

/* ---- Layout ---- */
.sp-sect{padding:100px 56px 84px;max-width:1200px;margin:0 auto;position:relative;}
.sp-sect + .sp-sect{padding-top:0;}
@media (max-width:760px){.sp-sect{padding-left:20px;padding-right:20px;}}

/* ---- Buttons ----
   Shared component with the homepage (home.html) and the single-program
   pages (program/styles.css): one .hm-btn base, three tiers. Scoped with
   .sp-page so the color declarations out-rank the global
   `.sp-page a{color:chalk}` link rule (chalk-on-chalk = invisible text).

   .sp-cta / .sp-ghost are kept as aliases so existing markup on this page
   keeps working — both now resolve to the shared tiers rather than to
   their own one-off treatments. */
.sp-page .hm-btn,
.sp-page .sp-cta,
.sp-page .sp-ghost{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  font-family:var(--mono);font-size:12px;letter-spacing:2px;
  text-transform:uppercase;font-weight:600;line-height:1;white-space:nowrap;
  padding:15px 22px;border:1.5px solid transparent;
  cursor:pointer;transition:background .2s,color .2s,border-color .2s;
}
.sp-page .hm-btn--block{display:flex;width:100%;}

.sp-page .hm-btn--primary,
.sp-page .sp-cta{background:var(--gold);border-color:var(--gold);color:#0a0a0a;}
/* .sp-cta carried its own top margin; kept so the pages that rely on it
   (preview-train.html, the last-call CTA) keep their spacing. */
.sp-page .sp-cta{margin-top:34px;}
.sp-page .hm-btn--primary:hover,
.sp-page .sp-cta:hover{background:var(--chalk);border-color:var(--chalk);color:#0a0a0a;}

.sp-page .hm-btn--secondary,
.sp-page .sp-ghost{background:transparent;border-color:rgba(224,217,204,.45);color:var(--chalk);}
.sp-page .hm-btn--secondary:hover,
.sp-page .sp-ghost:hover{background:var(--chalk);border-color:var(--chalk);color:#0a0a0a;}

/* Tertiary — the quietest tier (DESIGN-SYSTEM.md § Tiers): no border, no fill,
   no side padding so it optically aligns with the copy above it. It is the one
   tier that does not invert on hover — it shifts to gold text, because a chalk
   fill would reintroduce exactly the weight this tier exists to avoid.
   Vertical padding stays so it holds the same baseline row as a bordered
   button beside it. */
.sp-page .hm-btn--tertiary{
  background:transparent;border-color:transparent;color:var(--chalk);
  padding-left:0;padding-right:0;
}
.sp-page .hm-btn--tertiary:hover{
  background:transparent;border-color:transparent;color:var(--gold);
}
/* The drawn underline (DESIGN-SYSTEM.html § Buttons — Tertiary). It is a
   pseudo-element wiped in with scaleX, NOT a border-bottom: a border would
   reserve layout space and shift the row the moment it appeared, and it could
   not animate directionally. transform-origin:left means it draws outward from
   the label's start edge.

   ::BEFORE, not ::after. The programs page already spends .sp-ghost::after on
   the full-row click overlay (see .sp-prog-body .sp-ghost::after below), so the
   underline takes the free pseudo-element. Using ::after here would silently
   destroy row clickability.

   currentColor, so it inherits the gold the label shifts to on hover rather
   than needing its own hover rule. */
.sp-page .hm-btn--tertiary{position:relative;}
.sp-page .hm-btn--tertiary::before{
  content:"";position:absolute;left:0;right:0;bottom:-8px;
  height:1.5px;background:currentColor;
  transform:scaleX(0);transform-origin:left center;
  transition:transform .35s cubic-bezier(.2,.7,.3,1);
}
.sp-page .hm-btn--tertiary:hover::before{transform:scaleX(1);}
/* No underline on the disabled tier — it reads as an affordance. */
.sp-page .hm-btn--tertiary.hm-btn--disabled::before{display:none;}

.sp-page .hm-btn--disabled,
.sp-page .hm-btn:disabled{
  background:transparent;border-color:rgba(224,217,204,.2);color:var(--dim);
  cursor:not-allowed;pointer-events:none;
}
.sp-page .hm-btn:focus-visible,
.sp-page .sp-cta:focus-visible,
.sp-page .sp-ghost:focus-visible{outline:2px solid var(--gold);outline-offset:3px;}

.sp-page .hm-btn__arw{flex:none;transition:transform .2s;}
.sp-page .hm-btn:hover .hm-btn__arw,
.sp-page .sp-cta:hover .hm-btn__arw,
.sp-page .sp-ghost:hover .hm-btn__arw{transform:translateX(3px);}
@media (prefers-reduced-motion:reduce){
  .sp-page .hm-btn__arw{transition:none;}
  .sp-page .hm-btn:hover .hm-btn__arw{transform:none;}
}

/* ---- Stats strip (Open Space) ---- */
.sp-stats{
  display:flex;flex-wrap:wrap;gap:12px 48px;justify-content:space-between;
  border-top:1px solid var(--hair-soft);border-bottom:1px solid var(--hair-soft);
  padding:22px 0;margin-top:44px;font-family:var(--mono);
}
.sp-stats b{color:var(--gold);font-size:20px;font-weight:500;display:block;}
.sp-stats span{font-size:11px;letter-spacing:2px;text-transform:uppercase;color:var(--dim);}

/* ---- Utility doors (Log / Movement Library / Shorties) ---- */
.sp-doors{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:44px;}
.sp-door{border:1px solid var(--hair);padding:26px 22px;display:flex;flex-direction:column;gap:10px;}
.sp-door .k{font-family:var(--mono);font-size:11px;letter-spacing:2px;text-transform:uppercase;color:var(--dim);}
.sp-door .t{font-size:20px;font-weight:700;text-transform:none;color:var(--chalk);}
.sp-door .d{font-size:13px;line-height:1.6;color:var(--dim);flex:1;}
.sp-door .sp-ghost{align-self:flex-start;margin-top:6px;}
.sp-door:hover{border-color:rgba(224,217,204,.35);}
@media (max-width:900px){.sp-doors{grid-template-columns:1fr;}}

/* ---- Mission mini-tiles (Train page) ---- */
.sp-tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-top:40px;}
.sp-tile{
  position:relative;display:flex;flex-direction:column;justify-content:flex-end;
  min-height:210px;border:1px solid var(--hair);padding:18px 16px;overflow:hidden;
}
.sp-tile img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  filter:grayscale(.2) brightness(.5) contrast(1.05);transition:filter .5s ease;
}
.sp-tile:hover img{filter:grayscale(0) brightness(.62) contrast(1.05);}
.sp-tile .shade{position:absolute;inset:0;background:linear-gradient(180deg,rgba(10,10,10,.1) 30%,rgba(10,10,10,.85) 100%);}
.sp-tile .o{position:relative;font-size:12px;line-height:1.4;color:var(--mut);font-style:italic;}
.sp-tile .n{position:relative;font-size:15px;font-weight:600;margin-top:8px;color:var(--chalk);}
@media (max-width:1000px){.sp-tiles{grid-template-columns:repeat(2,1fr);}}

/* ---- Start Sequence steps (Train page, compact) ---- */
.sp-steps{margin-top:40px;border-top:1px solid var(--hair-soft);}
.sp-step{
  display:flex;align-items:baseline;gap:22px;
  padding:20px 4px;border-bottom:1px solid var(--hair-soft);
}
.sp-step .num{font-family:var(--mono);font-size:12px;color:var(--gold);flex:0 0 34px;}
.sp-step .name{font-size:19px;font-weight:700;text-transform:none;flex:0 0 auto;}
.sp-step .what{font-size:14px;color:var(--dim);}

/* ==================================================================
   PROGRAMS PAGE — the rail. One continuous line down the left edge
   visually joins the Start Sequence (numbered nodes, gated order) and
   the Missions below it (diamond nodes). Rows are image-left cards.
   ================================================================== */
.sp-rail{position:relative;padding-left:64px;margin-top:56px;
  /* --rail-end: distance from rail top to the LAST node's center; JS sets it so
     the line stops AT the final node instead of trailing to the section bottom.
     --rail-fill: scroll-driven gold progress height (0 → --rail-end). Both have
     fallbacks so the line still renders if JS never runs. */
  --rail-end:calc(100% - 60px);
  --rail-fill:0px;
}
/* Base (unfilled) rail line — ends at the last node, not the rail bottom.
   z-index:0 keeps both line layers BEHIND the node circles (z-index:1) so the
   scroll fill passes behind each 01/02/03 instead of drawing over it. */
.sp-rail::before{
  content:"";position:absolute;z-index:0;left:19px;top:8px;width:1px;height:calc(var(--rail-end) - 8px);
  background:linear-gradient(180deg,var(--gold) 0,var(--hair) 140px,var(--hair) 100%);
}
/* Scroll progress fill — a subtle gold gradient (not a solid bar) that grows
   down the line to the scroll position, capped at the last node. Fades in from
   the top and softens toward the leading edge so it reads as a glow, not a
   hard rule. */
.sp-rail::after{
  content:"";position:absolute;z-index:0;left:19px;top:8px;width:1px;
  height:min(var(--rail-fill),calc(var(--rail-end) - 8px));
  background:linear-gradient(180deg,
    rgba(198,168,90,0) 0,
    rgba(198,168,90,.28) 22%,
    rgba(198,168,90,.45) 78%,
    rgba(198,168,90,.6) 100%);
  transition:height .5s ease-out;
}
@media (max-width:760px){.sp-rail{padding-left:44px;}.sp-rail::before,.sp-rail::after{left:11px;}}
/* No rail on mobile: the connecting line + node gutter costs ~44px of a
   ~320px column, and the sequence is already carried by the numbered order of
   the rows themselves. Drops the line, the scroll fill, and the indent.
   space.js still sets --rail-end/--rail-fill; with the pseudo-elements gone
   they simply have nothing to draw. */
@media (max-width:640px){
  .sp-rail{padding-left:0;}
  .sp-rail::before,.sp-rail::after{display:none;}
  .sp-rail-head::before{display:none;}
  /* The node holds the step number (01/02/03) — the only thing carrying the
     Start Sequence's ORDER, so it is re-placed rather than hidden (display:none
     would drop it for screen readers too). It is a direct child of the .sp-prog
     GRID, so going position:static makes it a grid item: it is given its own
     full-width row above the image+copy pair via grid-column:1/-1, otherwise
     it would claim the image cell and shove both columns out of place. */
  .sp-node{
    position:static;grid-column:1;
    width:auto;height:auto;
    border:0;border-radius:0;background:none;transform:none;
    justify-content:flex-start;
    margin-bottom:2px;font-size:11px;letter-spacing:2px;
  }
  .sp-node.diamond{transform:none;width:auto;height:auto;}
  /* No explicit grid-row on the image/body: auto-placement puts them on row 1
     for Mission rows (no node) and row 2 for Start Sequence rows (node first).
     Pinning them to row 2 would leave an empty track — and its row gap — above
     every Mission row. */
}

/* group headers sit ON the rail */
.sp-rail-head{position:relative;padding:26px 0 6px;}
/* The kicker is inline-flex by design (it shrink-wraps, and the ::before rule
   is centered by the flex alignment). An older display:block here overrode
   that, stretching the label full-width and dropping the rule out of line. */
.sp-rail-head .sp-kicker,
.sp-rail-head .sp-eyebrow{display:inline-flex;}
.sp-rail-head h2{font-size:clamp(30px,3.6vw,46px);line-height:1.05;letter-spacing:-.015em;
  text-transform:none;font-weight:700;margin-top:10px;}
/* :not() excludes the kicker — it is a <p> too, and this rule's 15px/--mut
   was overriding the component's own 11px mono gold and pushing it off the
   heading with a 14px top margin. */
.sp-rail-head p:not(.sp-kicker):not(.sp-eyebrow){
  margin-top:14px;max-width:560px;font-size:15px;line-height:1.65;color:var(--mut);
}
.sp-rail-head::before{
  content:"";position:absolute;left:-64px;top:34px;width:39px;height:1px;background:var(--hair);
}
@media (max-width:760px){.sp-rail-head::before{left:-44px;width:26px;}}

/* node on the rail per program row */
.sp-prog{
  position:relative;
  /* 230px column = a ~307px-tall 3:4 tile. The old 300px was sized for a
     landscape crop; at 3:4 it would stand 400px tall and tower over the copy
     beside it. */
  display:grid;grid-template-columns:230px 1fr;gap:40px;align-items:center;
  padding:46px 0;border-bottom:1px solid var(--hair-soft);
}
.sp-prog:last-child{border-bottom:0;}
/* Centered on the row to match its now vertically-centered content. It was
   pinned to a fixed top offset back when rows were top-aligned; that offset
   would now drift on any row whose copy runs long. space.js measures the node
   rects live for the rail line, so it follows this automatically. */
.sp-node{
  position:absolute;z-index:1;left:-64px;top:50%;transform:translateY(-50%);
  width:39px;height:39px;
  display:flex;align-items:center;justify-content:center;
  font-family:var(--mono);font-size:12px;color:var(--gold);
  border:1px solid var(--hair);border-radius:50%;background:var(--bg);
}
.sp-node.diamond{border-radius:0;transform:translateY(-50%) rotate(45deg);
  width:28px;height:28px;left:-58px;}
.sp-node.diamond i{transform:rotate(-45deg);font-style:normal;font-size:11px;}
@media (max-width:760px){
  .sp-node{left:-44px;width:24px;height:24px;font-size:10px;}
  .sp-node.diamond{width:18px;height:18px;left:-41px;}
}

/* 3:4 PORTRAIT thumbnail — matches the homepage program cards (.hm-prog).
   aspect-ratio (not a fixed height) so the tile scales with the grid column.
   The row's align-items:center keeps the grid from stretching this to the full
   row height — a stretched item ignores the ratio, so that centering is
   load-bearing, not just cosmetic. min-height is the no-support floor only. */
.sp-prog-img{
  position:relative;overflow:hidden;border:1px solid var(--hair);
  aspect-ratio:3/4;min-height:0;
}
.sp-prog-img img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  filter:grayscale(.2) brightness(.72) contrast(1.03);transition:filter .5s ease,transform .5s ease;
}
.sp-prog:hover .sp-prog-img img{filter:grayscale(0) brightness(.8);transform:scale(1.03);}

.sp-prog-body{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;}
/* align-items:baseline so the diamond sits on the label's baseline rather than
   centering against the taller line box. */
.sp-prog-meta{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:8px 22px;
  font-family:var(--mono);font-size:11px;letter-spacing:2px;text-transform:uppercase;
}
.sp-prog-meta .dur{color:var(--gold);}
/* The objective is "Label · Detail" split into two spans, because CSS cannot
   break a text node at a character or hide part of one.

   DESKTOP: both halves sit on ONE line, joined by a "·" separator that is
   generated here rather than living in the markup — the two spans are
   adjacent in the HTML with no character between them, so the separator has
   to be a pseudo-element that mobile can switch off.

   display:inline (not flex): an inline box lets the line sit on the meta
   row's baseline beside the ◆, so the diamond shares the label's line
   instead of being pushed above a flex block. */
.sp-prog-meta .obj{
  display:inline;
  font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;
  /* The mono meta row sets uppercase + wide tracking for its 11px labels;
     the objective opts out of both — it is Helvetica running text, not a
     label, and tracked small caps on a sans reads as a different component.
     Bold carries the line instead: it sits above the title and has to hold
     its own against it without borrowing the mono labels' case or tracking. */
  text-transform:none;
  font-weight:700;
  letter-spacing:normal;
  font-size:13px;
}
.sp-prog-meta .obj-k{display:inline;}
.sp-prog-meta .obj-v{display:inline;margin-top:0;}
/* the separator between the two halves, desktop only. A colon binds tight to
   the word before it and takes its space after — unlike the middot it
   replaced, which floated between the two halves on equal margins. */
.sp-prog-meta .obj-k::after{content:':';margin:0 .4em 0 0;}

/* Gold stays the only accent — these are not tints, they are the one neutral
   chalk at two alphas, so the line recedes without introducing a hue.
   Measured against --bg #0a0a0a:
     obj-v (detail)  .72  7.52:1
     obj-k (label)   .55  4.76:1
   Both clear AA 4.5:1 at 13px, but the label has only 0.26 of headroom — .52
   is the floor, do not dim it further. The LABEL is the quieter half: "Objective" /
   "Stage 1" is a category name that repeats down the column, so it drops back
   and lets the detail beside it lead. */
.sp-prog-meta .obj{color:rgba(224,217,204,.72);}
.sp-prog-meta .obj-k{color:rgba(224,217,204,.55);}
.sp-prog-meta .obj-v{color:inherit;}
.sp-prog-body h3{
  margin-top:12px;font-size:clamp(22px,2.5vw,32px);
  line-height:1.1;letter-spacing:-.01em;
  text-transform:none;font-weight:700;
  color:var(--chalk);
}
.sp-prog-body .desc{margin-top:14px;max-width:520px;font-size:15px;line-height:1.65;
  color:var(--chalk);}
/* duration + equipment specs — stacked mono rows with mini icons */
.sp-prog-specs{
  margin-top:16px;display:flex;flex-direction:column;gap:8px;
  font-family:var(--mono);font-size:11px;letter-spacing:1.5px;
  text-transform:uppercase;color:var(--dim);
}
.sp-prog-specs .spec{display:flex;align-items:center;gap:9px;}
/* Spec icons are LineAwesome glyphs (clock) and the filters-page dumbbell —
   solid fills on a 32x32 viewBox, so they inherit color via fill:currentColor
   rather than stroke. Nudged down slightly to sit on the mono baseline. */
.sp-prog-specs .spec svg{flex:none;opacity:.85;fill:currentColor;vertical-align:-.12em;}
.sp-prog-specs .spec.dur{color:var(--gold);}
/* "Learn more" is the row's quietest control and sits under body copy, so it
   uses the TERTIARY treatment rather than the bordered secondary: no border,
   no fill, flush left with the text above it, gold on hover.
   It keeps the .hm-btn__arw — dropping the border is what makes the arrow the
   link's only affordance, so the tertiary tier needs it MORE than the bordered
   tiers do. The shared .sp-ghost:hover .hm-btn__arw rule above still matches,
   so the 3px hover slide carries over unchanged. */
.sp-prog-body .sp-ghost{
  margin-top:16px;
  background:transparent;color:var(--chalk);
  border-top-color:transparent;border-left-color:transparent;border-right-color:transparent;
  padding-left:0;padding-right:0;
}
/* border-color is deliberately NOT reset here: the underline below is a
   border-bottom, and a blanket `border-color:transparent` on :hover would
   erase it exactly when the cursor is on the link. Only the three sides that
   never carry a visible edge are held transparent. */
.sp-prog-body .sp-ghost:hover{
  background:transparent;color:var(--gold);
  border-top-color:transparent;border-left-color:transparent;border-right-color:transparent;
}
/* The row "Learn more" is the tertiary tier (DESIGN-SYSTEM.md § Where buttons
   appear), so it gets the same underline the standalone tertiary uses on the
   space-program page (.hm-btn--alone): a real border-bottom that is TRANSPARENT
   at rest rather than an absolutely-positioned pseudo-element.

   Why the change: the ::before version was pinned at bottom:-8px, which put the
   line 8px below the label's own box — far enough that it read as a rule under
   the row rather than an underline on the link. A border sits on the element's
   bottom edge by definition, so it lands where an underline belongs and needs
   no offset to maintain.

   Transparent rather than absent, because a border appearing from nothing would
   shift the label up by its own width on hover. Reserving the 1.5px and
   animating only its colour keeps the text still — the same reasoning as the
   space-program rule this is ported from.

   The ghost MUST stay position:static. The border-bottom needs no containing
   block of its own, and leaving the relative positioning here (a leftover from
   the ::before version, which did need it) silently breaks the row: the
   ::after row-click overlay below resolves inset:0 against the nearest
   positioned ancestor, so a relative ghost shrinks the overlay from the whole
   row down to the 94x44 label — the row stops being clickable anywhere except
   the image. */
.sp-prog-body .sp-ghost{
  border-bottom:1.5px solid transparent;
  transition:background .2s,color .2s,border-color .2s;
}
/* Hovering anywhere on the row draws it, because the whole row is the control. */
.sp-prog:hover .sp-prog-body .sp-ghost{border-bottom-color:currentColor;}
/* Make the WHOLE program row clickable: the "Learn more" link stretches an
   invisible overlay across the entire .sp-prog. The ::after is absolutely
   positioned, so its containing block is the nearest positioned ancestor —
   .sp-prog (position:relative) — NOT the ghost link, so it must stay static.
   The node sits above the overlay so it never blocks it visually. */
.sp-prog{cursor:pointer;}
.sp-prog-body .sp-ghost::after{
  content:"";position:absolute;inset:0;z-index:2;
}
.sp-prog .sp-node{z-index:3;}
/* DEBUG (localhost only): the overlay above covers the whole row, so DevTools
   element-picking always lands on it instead of the thing under the cursor.
   The preview pages set data-sp-debug on <html> when the host is local, which
   drops the overlay so inner elements can be inspected and links clicked
   individually. Never set on the live site — the flag is only written by the
   localhost-guarded snippet in the preview files, and the paste-in blocks do
   not carry it. */
html[data-sp-debug] .sp-prog-body .sp-ghost::after{content:none;}
html[data-sp-debug] .sp-prog{cursor:default;}

/* future missions — dashed placeholder (architected, not built) */
.sp-prog.soon{opacity:.85;}
.sp-prog.soon .sp-prog-img{border-style:dashed;border-color:rgba(224,217,204,.25);
  display:flex;align-items:center;justify-content:center;aspect-ratio:3/4;}
.sp-prog.soon .sp-prog-img span{font-family:var(--mono);font-size:11px;letter-spacing:2px;text-transform:uppercase;color:var(--dim);}
.sp-prog.soon h3{color:var(--dim);}
.sp-node.soon{border-style:dashed;color:var(--dim);}

/* TABLET (861px unchanged above; 641–860px here) — stay HORIZONTAL.
   The row previously collapsed to one column at 860px, which stacked the
   image above the copy on every tablet. It keeps the image+text side-by-side
   and just narrows the image column instead. */
@media (max-width:860px){
  .sp-prog{grid-template-columns:160px 1fr;gap:22px;}
  .sp-prog-img{height:auto;}
}

/* MOBILE (≤640px) — TWO COLUMNS. A narrow portrait thumbnail beside the copy,
   which keeps the 3:4 image legible without it eating a whole phone screen as
   a stacked full-width block would. */
@media (max-width:640px){
  /* Rows are TOP-aligned here, not centered: with a small thumbnail beside
     tall copy, centering floats the image against the middle of the text and
     leaves it unanchored. align-self:start on the image holds the 3:4 ratio
     that the row's centering was previously protecting. */
  /* Image sits in the RIGHT column: copy leads, thumbnail trails. Done by
     column placement, NOT by reordering the markup — DOM order stays
     image-then-copy so reading order and tab order are unchanged. */
  .sp-prog{grid-template-columns:1fr 120px;gap:0 12px;padding:22px 0;align-items:start;}
  .sp-prog-img{height:auto;align-self:start;}

  /* the copy column is narrow now, so the long-form specs and description
     would wrap to shreds — tighten them rather than letting them stack. */
  .sp-prog-body .desc{font-size:14px;line-height:1.55;}
  .sp-prog-specs{gap:5px;font-size:10px;letter-spacing:1px;margin-top:10px;}
  .sp-prog-body h3{font-size:19px;}
  .sp-prog-body .sp-ghost{margin-top:10px;}

  /* MOBILE LAYOUT
       block 1: image (col 1) beside subtitle + title (col 2)
       block 2: description, specs and link, full width beneath
     .sp-prog-body is display:contents so its children become grid items of
     .sp-prog directly — that lets the meta and title sit beside the image
     while their siblings drop below, without restructuring the markup. */
  .sp-prog-body{display:contents;}

  /* Subtitle and title stack at the top of column 1. The image now spans the
     whole row rather than just these two, so there is no surplus height to
     distribute between them — they simply sit at the start of their rows and
     the margins set the rhythm (the row-gap is 0; see .sp-prog above). */
  .sp-prog-meta{grid-column:1;align-self:start;margin-bottom:8px;}
  .sp-prog-body h3{grid-column:1;align-self:start;margin-top:0;line-height:1.15;}

  /* Image pinned to column 2 and spanning both rows of the top block, so it
     stands beside the subtitle+title pair rather than only the subtitle.
     The row is left to auto-placement: on a Start Sequence row the full-width
     .sp-node takes row 1, so the image correctly starts at row 2. */
  /* Spans EVERY row of the copy column, so the row is a single 1fr text
     column with the thumbnail alongside the whole of it — not just the top
     block. NOT grid-row:1/-1: -1 counts to the end of the EXPLICIT grid, and
     these rows are implicit, so it would collapse to row 1. A span larger
     than any row's row-count covers them all. It stays align-self:start with
     its 3:4 aspect-ratio intact — stretching it would let the row's height
     win over the ratio and distort the crop. */
  .sp-prog-img{grid-column:2;grid-row:1 / span 99;align-self:start;}

  /* All copy stays in column 1 — the image no longer interrupts it. */
  .sp-prog-body .desc,
  .sp-prog-body .sp-prog-specs,
  .sp-prog-body .sp-ghost{grid-column:1;}
  .sp-prog-body .desc{margin-top:8px;}
  /* display:contents drops the body's flex context, which was what kept the
     ghost link shrink-wrapped (align-items:flex-start). As a grid item it
     would stretch the full column, so it re-shrinks itself. */
  .sp-prog-body .sp-ghost{justify-self:start;}

  /* Stays a ROW, not a column: flex-direction:column here would stack the ◆
     above the objective, but the diamond must share the label's line. The
     ◆ + label sit on one baseline and obj-v breaks beneath them (base rule). */
  .sp-prog-meta{
    flex-direction:row;align-items:baseline;gap:0;
    font-size:10px;letter-spacing:1.2px;
  }
  /* MOBILE: the label half ("Objective" / "Stage 1") is dropped — it is a
     category name repeating down every row, and the detail beside it already
     says what the row is. Hiding the SPAN rather than the text via a
     pseudo-element keeps it out of the accessibility tree too, which is right:
     it is redundant here, not decorative. Its separator goes with it, and the
     detail no longer needs the break that divided the two. */
  .sp-prog-meta .obj-k{display:none;}
  .sp-prog-meta .obj-v{display:inline;margin-top:0;}
  /* The parent meta row drops to 10px/1.2px tracking for its mono labels, but
     the objective is Helvetica bold and inherits neither — it keeps its own
     size and normal tracking, just a step down from desktop.
     8px under it: with the label gone this is a single bold line butted
     against the title beneath it, and the two need separating. */
  .sp-prog-meta .obj{font-size:12px;letter-spacing:normal;}
}

/* ==================================================================
   PROGRAMS PAGE · VARIANT B (programs-b.html) — full-width hero with
   the full-size h1, then each group as a split section: subheading in
   the left third (sticky), program cards in the right two-thirds.
   Only the Start Sequence keeps the rail (it IS sequential); Missions
   are unconnected rows. The future-missions row carries a newsletter
   signup.
   ================================================================== */
.sp-v2 .sp-split{
  display:grid;grid-template-columns:1fr 2fr;gap:72px;align-items:start;
}
.sp-v2 .sp-side{position:sticky;top:48px;}
.sp-v2 .sp-side h2{
  font-size:clamp(30px,3vw,44px);line-height:1.05;letter-spacing:-.015em;
  text-transform:none;font-weight:700;margin-top:12px;
}
.sp-v2 .sp-side p{margin-top:14px;font-size:15px;line-height:1.65;color:var(--mut);}
.sp-v2 .sp-cards{min-width:0;}
.sp-v2 .sp-cards .sp-rail{margin-top:0;}
.sp-v2 .sp-cards .sp-prog:first-child{padding-top:8px;}

.sp-v2 .sp-cards .sp-rail::before{top:0;
  background:linear-gradient(180deg,var(--gold) 0,var(--hair) 90px,var(--hair) calc(100% - 60px),transparent 100%);}

/* plain (rail-less) mission list */
.sp-plain{margin-top:8px;}
.sp-plain .sp-rail-head::before{display:none;}
.sp-v2 .sp-plain .sp-prog{grid-template-columns:210px 1fr;}
/* The ◆ marker is hidden: the objective carries its own tier by colour now, so
   the glyph in front of it was redundant decoration. Left in the markup rather
   than stripped from 7 files, so it can be switched back on in one line. */
.sp-prog-meta .mark{display:none;}

/* group subheading pulled OUT of the rail — full width, no rail tick */
.sp-rail-head.sp-head--out{padding:0;}
.sp-rail-head.sp-head--out::before{display:none;}
.sp-rail-head.sp-head--out + .sp-rail{margin-top:40px;}
.sp-plain + .sp-rail-head.sp-head--out,
.sp-rail + .sp-rail-head.sp-head--out{margin-top:88px;}
.sp-rail-head.sp-head--out + .sp-plain{margin-top:16px;}

/* newsletter signup (future-missions row) */
.sp-news{display:flex;margin-top:20px;max-width:430px;}
.sp-news input{
  flex:1;min-width:0;background:transparent;
  border:1.5px solid rgba(224,217,204,.35);border-right:0;
  color:var(--chalk);font-family:var(--mono);font-size:12px;padding:13px 14px;outline:none;
}
.sp-news input::placeholder{color:var(--dim);}
.sp-news button{
  background:transparent;border:1.5px solid var(--chalk);color:var(--chalk);
  font-family:var(--mono);font-size:11px;letter-spacing:2px;text-transform:uppercase;
  font-weight:600;padding:13px 18px;cursor:pointer;transition:all .2s;white-space:nowrap;
}
.sp-news button:hover{background:var(--chalk);color:#0a0a0a;}

@media (max-width:1000px) and (min-width:641px){
  .sp-v2 .sp-split{grid-template-columns:1fr;gap:36px;}
  .sp-v2 .sp-side{position:static;}
  /* NOT single-column: rows stay horizontal (image + text) here, matching
     variant A. This previously collapsed to 1fr, which stacked the image above
     the copy on every tablet and phone. The image column just narrows.
     Scoped to >640px: below that the shared mobile rule owns the 1fr 2fr
     split, and this selector (0,2,0) would otherwise outrank it. */
  .sp-plain .sp-prog{grid-template-columns:160px 1fr;}
}

/* Variant B's own tablet/mobile ladder, mirroring variant A's. .sp-v2 rules
   are declared at top level, so they out-order the shared breakpoints and have
   to be restated here rather than inherited. */
@media (max-width:860px) and (min-width:641px){
  .sp-v2 .sp-plain .sp-prog,
  .sp-v2 .sp-cards .sp-prog{grid-template-columns:160px 1fr;gap:22px;}
}
@media (max-width:640px){
  /* the split's 1000px collapse is bounded at 641px now, so mobile restates it */
  .sp-v2 .sp-split{grid-template-columns:1fr;gap:32px;}
  .sp-v2 .sp-side{position:static;}
  .sp-v2 .sp-plain .sp-prog,
  .sp-v2 .sp-cards .sp-prog{grid-template-columns:1fr 120px;gap:0 12px;align-items:start;}
}

/* ── LAST CALL — closing section with movement-archive GIF field ──────────
   Markup: <section class="sp-last"><div class="sp-giffield"></div>
           <div class="sp-last-inner"> eyebrow / h2 / .row CTAs </div></section>
   The field is populated + parallaxed by space.js. */
.sp-last{position:relative;overflow:hidden;min-height:86vh;display:flex;align-items:center;justify-content:center;text-align:center;padding:160px 0;margin-top:40px;}
.sp-giffield{position:absolute;inset:-6%;z-index:0;pointer-events:none;}
/* The field carries ONE dimming overlay instead of fading each gif: the gifs
   themselves mount at full opacity (only the 0→1 entrance transition uses it),
   so depth no longer washes the images out. The scrim sits above every tile
   and under the vignette/copy, which keeps the headline just as readable. */
.sp-giffield::after{
  content:'';position:absolute;inset:0;z-index:2;pointer-events:none;
  background:rgba(10,10,10,.55);
}
.sp-gif{position:absolute;overflow:hidden;border-radius:3px;opacity:0;transition:opacity 1.2s ease;will-change:opacity;background:#141311;}
.sp-gif.in{opacity:1;}
.sp-gif img{width:100%;height:100%;object-fit:cover;display:block;filter:saturate(.7) brightness(.85) contrast(1.02);}
/* vignette so the headline stays readable over the field */
.sp-last::after{content:'';position:absolute;inset:0;z-index:1;pointer-events:none;
  background:radial-gradient(ellipse 62% 55% at 50% 50%, rgba(10,10,10,.9) 0%, rgba(10,10,10,.55) 55%, rgba(10,10,10,.25) 100%);}
.sp-last-inner{position:relative;z-index:2;max-width:900px;padding:0 24px;}
.sp-last .sp-h2{font-size:clamp(42px,6.5vw,88px);line-height:.98;max-width:16ch;margin:18px auto 0;}
.sp-last .row{display:flex;gap:14px;justify-content:center;align-items:center;flex-wrap:wrap;margin-top:44px;}
.sp-last .row .sp-cta,.sp-last .row .sp-ghost{margin:0;}
@media (max-width:700px){
  .sp-last{min-height:70vh;padding:110px 0;}
}
@media (prefers-reduced-motion:reduce){
  .sp-gif{transition:none;}
}

/* ── GLOBAL FOOTER links section (archive/space-footer.html) ──────────────
   Sits below the .sp-last CTA. 3 link columns (Train / Company / Connect with
   social icons), then a legal row. Scoped under .sp-page like everything else. */
.sp-foot{max-width:1200px;margin:0 auto;padding:72px 56px 64px;position:relative;}
.sp-foot-cols{
  display:grid;grid-template-columns:repeat(3,1fr);gap:40px;
}
.sp-foot-col{display:flex;flex-direction:column;align-items:flex-start;gap:12px;}
.sp-foot-head{
  font-family:var(--mono);font-size:11px;letter-spacing:2px;text-transform:uppercase;
  color:var(--gold);margin-bottom:6px;
}
.sp-page .sp-foot-col a{font-size:15px;line-height:1.4;color:var(--mut);}
.sp-page .sp-foot-col a:hover{color:var(--gold);}
/* social sprite row sits under the Connect links */
.sp-foot-social{margin-top:14px;}
.sp-foot-social .sqs-svg-icon--list{display:flex;align-items:center;flex-wrap:wrap;}

.sp-foot-rule{height:1px;background:var(--hair-soft);margin:48px 0 26px;}

.sp-foot-legal{
  display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap;
  font-family:var(--mono);font-size:12px;letter-spacing:1px;color:var(--dim);
}
.sp-foot-wordmark{font-size:22px;line-height:1;}
.sp-foot-copy{color:var(--dim);}
.sp-foot-legal-links{display:flex;align-items:center;gap:12px;}
.sp-page .sp-foot-legal-links a{color:var(--dim);}
.sp-page .sp-foot-legal-links a:hover{color:var(--gold);}
@media (max-width:760px){
  .sp-foot{padding:56px 26px 48px;}
  .sp-foot-cols{grid-template-columns:1fr 1fr;gap:32px 24px;}
  .sp-foot-legal{justify-content:flex-start;}
}
@media (max-width:460px){
  .sp-foot-cols{grid-template-columns:1fr;}
}

/* ==================================================================
   MOBILE VARIANT · CARDS — opt-in with .sp-cardgrid on the .sp-page.
   Instead of one row per program, mobile lays the programs out as a
   2-up grid of stacked cards: image on top, copy beneath. The
   description is hidden — at half a phone width there is no room for
   prose, so the card carries image, subtitle, title and duration only.
   Desktop and tablet are untouched; this is a ≤640px override.
   ================================================================== */
@media (max-width:640px){
  .sp-cardgrid .sp-rail,
  .sp-cardgrid .sp-plain{
    display:grid;grid-template-columns:1fr 1fr;gap:20px 14px;
  }

  /* each program becomes a stacked card, not a two-column row. display:flex
     discards the row layout's grid-template-columns entirely, so the 120px
     image column does not leak in here. */
  .sp-cardgrid .sp-prog{
    display:flex;flex-direction:column;align-items:stretch;
    gap:0;padding:0;border-bottom:0;
  }
  /* display:contents is what the row layout used to promote the body's
     children into the row grid; a card wants them back in their own flow. */
  .sp-cardgrid .sp-prog-body{
    display:flex;flex-direction:column;align-items:flex-start;
  }

  /* image on top, full card width, ratio intact */
  .sp-cardgrid .sp-prog-img{
    grid-column:auto;grid-row:auto;
    width:100%;aspect-ratio:3/4;margin-bottom:10px;
  }

  /* copy beneath */
  .sp-cardgrid .sp-prog-meta{
    grid-column:auto;align-self:auto;margin:0 0 8px;
    flex-direction:row;align-items:baseline;gap:0;
  }
  .sp-cardgrid .sp-prog-body h3{
    grid-column:auto;align-self:auto;
    margin:0;font-size:16px;line-height:1.2;
  }

  /* no prose on a half-width card */
  .sp-cardgrid .sp-prog-body .desc{display:none;}

  /* duration only — the equipment line is the first thing to go */
  .sp-cardgrid .sp-prog-specs{
    grid-column:auto;margin-top:8px;gap:4px;
  }
  .sp-cardgrid .sp-prog-specs .spec:not(.dur){display:none;}

  .sp-cardgrid .sp-prog-body .sp-ghost{
    grid-column:auto;justify-self:auto;margin-top:10px;
  }

  /* the step number sits above the card's image */
  .sp-cardgrid .sp-node{
    position:static;grid-column:auto;
    width:auto;height:auto;border:0;border-radius:0;background:none;
    transform:none;justify-content:flex-start;
    margin-bottom:6px;font-size:10px;letter-spacing:2px;
  }

  /* the dashed "in development" placeholder spans both columns */
  .sp-cardgrid .sp-prog.soon{grid-column:1 / -1;}

  /* the rail cannot draw down a 2-up grid; the base mobile block already
     drops the line, this makes sure the indent goes with it. */
  .sp-cardgrid .sp-rail{padding-left:0;}
  .sp-cardgrid .sp-rail::before,
  .sp-cardgrid .sp-rail::after{display:none;}
}
