/* Site developed and built by sitethreesixty.com */

/* ============================================================
   containers.css — Container internal layout only
   No colours. No media queries. No max-width. Containers get
   their width from their position class and reflow with
   auto-fit/minmax, never with a breakpoint.
   ============================================================ */

/* --- Zone: degrees --- */

.degrees-container-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: var(--gap-xs);
  align-items: stretch;
}

.degrees-heading {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr));
  gap: var(--gap-xl);
  align-items: end;
}

/* The label and the h2 each span the full block. With three children in an
   auto-fit grid the label, h2 and paragraph each took a column, stranding
   the h2 over the middle card at half width — 6 rendered lines at 72px.
   Spanning both keeps the label attached to its heading and gives a
   statement-size h2 the width it needs; the supporting paragraph sits
   beneath, capped at its own 50ch measure. */
.degrees-heading > .section-label,
.degrees-heading > h2 {
  grid-column: 1 / -1;
}

.degrees-card {
  display: flex;
  min-height: 36rem;
  flex-direction: column;
}

.degrees-card-top,
.degrees-card-bottom {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  align-items: flex-end;
}

.degrees-card-bottom {
  margin-top: auto;
}

/* --- Zone: trades --- */

.trade-container-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
  gap: var(--gap-3xl);
  align-items: start;
}

.trade-list {
  display: grid;
}

.trade-list article {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  align-items: baseline;
  gap: var(--space-xs) var(--space-md);
}

.trade-list article p {
  grid-column: 2;
}

/* --- Zone: process --- */

.process-container-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 27rem), 1fr));
  gap: var(--gap-4xl);
  align-items: center;
}

.process-list {
  display: grid;
}

.process-list li {
  display: grid;
  grid-template-columns: 3rem 1fr;
  gap: var(--space-md-plus);
}

.process-facts-panel {
  display: grid;
}

.process-facts-head,
.process-facts-row,
.process-facts-output {
  display: grid;
  align-items: center;
}

.process-facts-head {
  grid-template-columns: 1fr auto;
}

.process-facts-row {
  grid-template-columns: 2.5rem 1fr auto;
  gap: var(--space-md);
}

.process-facts-output {
  grid-template-columns: 1fr auto;
  gap: var(--space-md);
}

/* --- Zone: scope --- */

.scope-container-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr));
  gap: var(--gap-lg);
  align-items: end;
}

.scope-pages,
.scope-note {
  grid-column: 1 / -1;
}

.scope-pages {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
}

/* --- Zone: examples --- */

.examples-container-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: var(--gap-xs);
  align-items: stretch;
}

.examples-heading {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 25rem), 1fr));
  gap: var(--gap-3xl);
  align-items: end;
}

.examples-card {
  display: grid;
  grid-template-rows: auto 1fr;
}

.examples-window {
  display: grid;
  grid-template-rows: auto 1fr;
}

.examples-window > div {
  display: grid;
  align-content: center;
}

/* --- Zone: about --- */

.about-container-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 23rem), 1fr));
  gap: var(--gap-5xl);
  align-items: start;
}

/* Sizes the watermark against its own column rather than the viewport, so the
   word shrinks to fit on one line instead of being chopped by word-break. The
   wrapper exists because an element cannot size its font from its own inline
   size — that would be circular. */
.about-kicker-frame {
  container-type: inline-size;
}

/* --- Zone: questions --- */

.faq-container-stack {
  display: grid;
}

/* --- Zone: closing CTA --- */

.final-cta-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
  gap: var(--gap-4xl);
  align-items: end;
}

/* --- Zone: footer --- */

/* The band is inset with every other Zone, so without this the first column's
   text sits hard against its left edge. --container-pad restores the same gap
   the full-bleed footer used to give it. */
.footer-grid {
  padding-inline: var(--container-pad);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  gap: var(--gap-lg);
  align-items: start;
}

.footer-colophon {
  grid-column: 1 / -1;
  display: grid;
  gap: var(--space-sm);
}

/* ============================================================
   Hosting page containers
   ============================================================ */

.fit-grid-wide {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr));
  gap: var(--gap-3xl);
  align-items: start;
}

/* Rewritten from a fixed .72fr/1.28fr pair to auto-fit on owner ruling.
   The pair could only collapse via a media query, and that media query lived
   in the critical file while this definition lived here — deferred CSS loads
   last, so the collapse never applied and the page ran 900px wide in a 390px
   viewport. auto-fit reflows with no breakpoint and no cascade race. */
/* ⛔ TWO children — so never more than TWO tracks. `auto-fit` does not know how many
   children a grid has: it fills the container with as many tracks as fit, and every track
   past the child count is EMPTY but still takes its 1fr share. At 1600px this grid ran
   three tracks for two children, so the content column lost a third of its width, the card
   grid inside it collapsed from 233px columns to 104px, and the h3 overran its card.

   Same root cause as the stranded-card bug (Build Lesson #20), a different symptom: there
   it was too few tracks for the items, here too many. Both are fixed the same way — state
   the track count instead of letting the container arithmetic guess it. */
.hosting-feature-container-grid,
.hosting-modern-container-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap-3xl);
  align-items: start;
}

/* The design's 0.72 / 1.28 split, restored. Ruling 5 accepted equal columns as the price
   of the `auto-fit` rewrite that fixed the 900px-in-390px mobile overflow — `auto-fit` can
   only produce equal tracks. These grids now state their tracks explicitly, so the price no
   longer has to be paid: the asymmetry comes back and the mobile collapse still holds.
   Measured against the design at 1600: 411px / 730px, which is exactly 0.72 / 1.28.

   It is not cosmetic. Equal columns gave the card side 627px instead of 730px, the 5-card
   grid inside dropped from 233px to 180px per card, and the h3 overran its card edge. */
@container frame (min-width: 700px) {
  .hosting-feature-container-grid,
  .hosting-modern-container-grid {
    grid-template-columns: 0.72fr 1.28fr;
  }
}

/* Same rewrite. The min track is 20rem, not 15rem, so this reflows to TWO
   columns at desktop rather than three: at 902px, three 15rem tracks left the
   heading only 243px, and "maintenance" at --font-h2 needs 403px — it overran
   its column and collided with the check list beside it. Two tracks give the
   heading 408px and the list a full-width row of its own. */
.hosting-zero-container-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: var(--gap-2xl);
  align-items: start;
}

.hosting-split-container-grid .hosting-closing-line,
.hosting-feature-container-grid .hosting-closing-line,
.hosting-zero-container-grid .hosting-closing-line,
.hosting-modern-container-grid .hosting-closing-line,
.hosting-managed-container-grid .hosting-emphasis,
.hosting-growth-container-grid .hosting-growth-track {
  grid-column: 1 / -1;
}

.hosting-feature-grid,
.hosting-modern-feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  gap: var(--space-md);
}

.hosting-growth-track {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--space-md-lg);
  align-items: center;
}

.hosting-inline-list,
.hosting-no-list,
.hosting-check-list,
.hosting-managed-lines {
  display: grid;
}

.hosting-resilience-visual {
  display: grid;
  place-items: center;
}

.hosting-resilience-visual span,
.hosting-resilience-visual strong {
  grid-area: 1 / 1;
}

.hosting-managed-lines li {
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  gap: var(--space-md);
  align-items: baseline;
}

.hosting-included-degrees {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: end;
}

.hosting-final-action {
  display: grid;
  align-content: end;
}

/* ============================================================
   Comparison page — container layout
   ============================================================ */

.route-cards-container-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 23rem), 1fr));
  gap: var(--space-md);
  align-items: stretch;
}

.comparison-section-heading {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap-3xl);
  align-items: end;
}

.route-card {
  display: flex;
  min-height: 38rem;
  flex-direction: column;
}

.head-row-split {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  align-items: center;
}

.route-card dl {
  margin-top: auto;
}

.route-card dl div {
  display: grid;
  grid-template-columns: 5rem minmax(0, 1fr);
  gap: var(--space-md);
}

.responsibility-ledger-container-grid {
  min-width: 0;
  display: grid;
  gap: var(--space-lg-xl);
}

.responsibility-ledger {
  width: 100%;
  min-width: 0;
  max-width: 100%;
  overflow-x: auto;
}

.ledger-row {
  min-width: 58rem;
  display: grid;
  grid-template-columns: minmax(12rem, 1.25fr) repeat(4, minmax(9rem, 1fr));
}

.split-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap-4xl);
  align-items: center;
}

/* Two children, so never a third track — see the note above the hosting feature grids. */
@container frame (min-width: 700px) {
  .value-equation-container-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.value-points {
  display: grid;
}

.value-points article {
  display: grid;
  grid-template-columns: 2.7rem minmax(0, 1fr);
  gap: var(--space-md-lg);
}

.value-equation-instrument {
  display: grid;
  grid-template-rows: auto 1fr auto;
}

.value-equation-head {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
}

.value-equation-core {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: var(--space-md-lg);
  align-items: center;
}

.value-equation-core div {
  display: grid;
  align-content: center;
  justify-items: center;
}

.single-col-grid {
  display: grid;
  gap: var(--space-md);
}

.degree-rail {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: var(--gap-lg);
  align-items: center;
}

.degree-rail-number, .degree-rail-price {
  display: grid;
  align-content: center;
}

.degree-rail-price {
  justify-items: end;
}

.split-grid-panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap-2xl);
  align-items: start;
}

/* Two children, two tracks — see the note above. */
@container frame (min-width: 700px) {
  .degree-extras-panel { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* This one keeps its higher threshold: at 24rem per track it was already single-column
   just above the collapse, and dropping to 700px would pair a heading with its copy far
   earlier than the design does. */
@container frame (min-width: 1000px) {
  .comparison-section-heading { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.degree-extras-list {
  display: grid;
  /* auto-fit, not a fixed pair + a breakpoint: reflows to one column on its own */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  gap: 0 var(--space-lg);
}

.single-col-panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

/* .degree-coverage-panel: dual-class with .single-col-panel (base) —
   zones.css .zone-dark/.zone-light .degree-coverage-panel and the
   @container frame (min-width: 700px) rule below still match the bare
   class. */

/* The worst of them: a 14rem min track put TWO children in FIVE columns at 1440 and SIX at
   1920, so three-quarters of the panel was empty track. */
@container frame (min-width: 700px) {
  .degree-coverage-panel { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.degree-coverage-column {
  display: flex;
  min-width: 0;
  flex-direction: column;
}

.degrees-shared-line {
  /* flex-wrap rather than a 3-track grid + a breakpoint: the middle rule takes
     the slack and the row wraps on its own when there isn't room. */
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
  align-items: center;
}

.degrees-shared-line > :nth-child(2) {
  flex: 1 1 8rem;
}

.degree-system-links {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
}

/* Two children, so never a third track — see the note above the hosting feature grids. */
@container frame (min-width: 700px) {
  .research-container-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.research-number {
  display: flex;
  align-items: flex-start;
  justify-content: center;
}

.research-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr));
}

.fit-container-grid {
  display: grid;
  gap: var(--space-xl);
}

.fit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 23rem), 1fr));
  gap: var(--space-md);
  align-items: stretch;
}

.fit-grid article {
  display: flex;
  min-height: 19rem;
  flex-direction: column;
}

.fit-grid h3 {
  margin-top: auto;
}

.degrees-comparison-container-grid > .comparison-section-heading {
  margin-bottom: var(--space-md);
}

.degrees-comparison-container-grid > .comparison-note {
  margin-top: var(--space-sm-md);
}

/* ============================================================
   360Dash & Ecosystem page — container layout
   ============================================================ */

/* --- Zone: four useful jobs (dark) --- */

.dash-jobs-container-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: var(--space-md);
  align-items: stretch;
}

.dash-jobs-heading {
  grid-column: 1 / -1;
}

/* No min-height: the spec states one for the ecosystem cards (Zone 5) but not
   these — align-items: stretch on the parent grid keeps the row even, and
   flex + margin-top: auto on the body settles title/copy toward the base. */
.stack-card {
  display: flex;
  flex-direction: column;
}

.job-card-top {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.job-card-body {
  margin-top: auto;
}

/* --- Zone: Content Map (light) --- */

/* flex-wrap, not grid: gives the spec's 58/42 weighted split (Section 5 —
   "the larger share goes to the copy side") AND collapses to one column with
   no media query, which a fixed-fr grid pair cannot do without one (Build
   Lesson #17/18's cascade-race trap). Same technique .degrees-shared-line
   already uses elsewhere on the site for an unequal reflow-without-a-
   breakpoint split; flex-grow ratio approximates 58/42 once wrapped content
   clears both basis widths. */
.wrap-row-lg {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-4xl);
  align-items: center;
}

.content-map-copy {
  flex: 1.4 1 26rem;
}

.content-map-panel {
  flex: 1 1 19rem;
}

.content-map-steps {
  display: grid;
  margin-top: var(--space-lg-xl);
}

.content-map-steps li {
  display: grid;
  grid-template-columns: 3rem 1fr;
  gap: var(--space-md-plus);
}

/* --- Zone: build progress (muted) --- */

.loose-grid {
  display: grid;
  gap: var(--gap-3xl);
}

.progress-line {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
}

.progress-line li {
  padding-right: var(--space-lg);
}

/* --- Zone: connected ecosystem (dark) --- */

.dash-bearings-container-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: var(--space-md);
  align-items: stretch;
}

.dash-bearings-heading {
  grid-column: 1 / -1;
}

.bearings-card {
  display: flex;
  min-height: 22.5rem;
  flex-direction: column;
}

.bearings-card-body {
  margin-top: auto;
}

/* --- Zone: mobile app (light) --- */

/* Same flex-wrap technique as Content Map, mirrored: copy is the larger share
   here too (60/40), it just sits first in DOM either way. Shared base is
   .wrap-row-lg. */

.mobile-app-copy {
  flex: 1.5 1 22rem;
}

.mobile-app-panel {
  flex: 1 1 16rem;
}

.mobile-app-list {
  display: grid;
  margin-top: var(--space-lg-xl);
}

/* ============================================================
   EVEN ROWS — why these grids do not use auto-fit
   ============================================================
   repeat(auto-fit, minmax(F, 1fr)) yields floor((C + gap) / (F + gap)) tracks,
   which walks DOWN through every integer as the container narrows. A 4-card
   grid therefore passes through 3 on the way to 2 and strands one card alone
   on a row. No value of F skips a count — the arithmetic has no gaps — so this
   is a technique problem, not a tuning problem.

   Fixed by stepping the track count explicitly through DIVISORS of the card
   count only: 4 -> 2 -> 1, and 5 -> 3 -> 1 (5 is prime, and 3+2 reads far
   better than a stranded 4+1).

   These are container queries, NOT viewport media queries. They add no
   breakpoint — gate C3 is untouched, the site still has exactly one viewport
   query — and they respond to the space the Container actually has, which is
   what "Containers reflow by available space" asks for. Thresholds are the
   Framework's inline size.

   ⚠️ Verified by scripts/rowfill.py, which counts items per rendered row at 16
   viewport widths. No other gate sees this: the CSS is valid, nothing
   overflows, no colour moves, and the page height looks plausible.
   ============================================================ */

/* 4 columns — 2+2, then 4 */
.footer-grid {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 560px) {
  .footer-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@container frame (min-width: 1120px) {
  .footer-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* 4 cards — 2+2, never 3+1 */
.route-cards-container-grid {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 700px) {
  .route-cards-container-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* 4 cards — 2+2 */
.fit-grid {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 700px) {
  .fit-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* 4 items — 2+2 */
.hosting-final-container-grid {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 760px) {
  .hosting-final-container-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* 5 cards — 3+2, never 4+1 */
.hosting-feature-grid {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 620px) {
  .hosting-feature-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* 5 cards — 3+2 */
.hosting-modern-feature-grid {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 620px) {
  .hosting-modern-feature-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* 5 items — 3+2, then 5 */
.scope-pages {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 560px) {
  .scope-pages {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@container frame (min-width: 1180px) {
  .scope-pages {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

/* 3 items — 3 or 1, never 2+1. 3 is prime, so 2+1 strands one */
.degrees-container-grid {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 1280px) {
  .degrees-container-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* 3 items — 3 or 1, never 2+1. 3 is prime, so 2+1 strands one */
.examples-container-grid {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 1280px) {
  .examples-container-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* 3 items — 3 or 1, never 2+1. 3 is prime, so 2+1 strands one */
.hosting-zero-container-grid {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 1700px) {
  .hosting-zero-container-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* 3 items — 3 or 1, never 2+1. 3 is prime, so 2+1 strands one */
.degree-rail {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 1000px) {
  .degree-rail {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* 3 items — 3 or 1, never 2+1. 3 is prime, so 2+1 strands one */
.degree-system-links {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 700px) {
  .degree-system-links {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* 3 items — 3 or 1, never 2+1. 3 is prime, so 2+1 strands one */
.research-stats {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 560px) {
  .research-stats {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* --- 360Dash & Ecosystem page ---
   Added in the same pass that fixed the other three pages; this page was
   missed because the verifying rowfill.py run named its pages explicitly and
   360dash.html was not among them. Run it with no page argument. */

/* 4 cards — 2+2, then 4. Spec Zone 02: "Use four across only where each card
   remains comfortably readable; otherwise two by two, then one per row" — so
   the 4-up step waits until each card clears ~239px (Framework 1200 = a 1003px
   Container). Below 560 it is one per row, which also satisfies the spec's
   "keep one icon card per row below approximately 480px". */
.dash-jobs-container-grid {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 560px) {
  .dash-jobs-container-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@container frame (min-width: 1200px) {
  .dash-jobs-container-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* 6 cards — 3, then 2, then 1, exactly as spec Zone 05 states. Never 6, 5 or
   4 across: three columns is the spec's ceiling, not just its starting point.
   The 3-up threshold is 1000 rather than a rounder number so that a 1000px
   viewport keeps three columns — at exactly 1000 the positions have collapsed
   and .frame has handed its gutter to the Zone, so the Framework is 1000px
   there but only 941px at 1001px. Both sides land on a spec-legal count. */
.dash-bearings-container-grid {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 560px) {
  .dash-bearings-container-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@container frame (min-width: 1000px) {
  .dash-bearings-container-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* 4 stages — 4, then 2+2, then a single vertical sequence below 1000px.
   ⚠️ This one needs the viewport breakpoint, and a container query cannot
   replace it. Spec Zone 04 states the collapse in VIEWPORT terms ("becomes a
   single vertical sequence below 1000px"), and below that breakpoint the
   Container is WIDER, not narrower — pos-content is 8/12 of the Framework
   above 1000px but full-width-minus-gutter below it, so this Container
   measures 627px at a 1001px viewport and 940px at 1000px. Querying the
   Framework would therefore expand the line at the exact width the spec says
   to collapse it. Measured, not assumed.

   No new breakpoint: max-width 1000px is the system's only one, and it sits
   after the container queries in this same file, so it wins on source order
   with no cascade race (Build Lesson #17). */
.progress-line {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 620px) {
  .progress-line {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@container frame (min-width: 1200px) {
  .progress-line {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (max-width: 1000px) {
  .progress-line {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ============================================================
   360° Booking page — container layout
   ============================================================ */

/* --- Zone 02: customer booking journey (dark) --- */

.stack-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
  align-items: stretch;
}

.booking-journey-heading {
  grid-column: 1 / -1;
}

/* 5 stages. ⚠️ SPEC-VS-FRAMEWORK, declared not silently resolved: the spec
   asks for "one row on a large screen, two per row at medium width and one
   per row on a handheld" — but 5 items two-per-row is 2+2+1, which strands
   one, the exact ORPHAN case Build Lesson #20 forbids and rowfill.py fails.
   5 is prime, so the lesson's rule for it is 5 -> 3+2 -> 1. Both 5-item grids
   already on this site (.hosting-feature-grid, .hosting-modern-feature-grid)
   resolve the same way. Reading order is unchanged at every width, which is
   what the spec's following sentence actually protects. */
@container frame (min-width: 620px) {
  .booking-journey-container-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@container frame (min-width: 1280px) {
  .booking-journey-container-grid {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

/* --- Zone 03: built to order (light) --- */

/* flex-wrap with an asymmetric grow ratio, not equal grid tracks: gives the
   spec's 60/40 copy-to-form split AND collapses to one column with no media
   query, which a fixed-fr grid pair cannot do (Build Lesson #17). Shared
   base is .wrap-row-lg. */

.flex-copy-xl {
  flex: 1.5 1 26rem;
}

/* .booking-custom-copy and .publishing-view-copy: dual-class with
   .flex-copy-xl (base) — elements.css .booking-custom-copy h2 and
   .publishing-view-copy h2 still match the bare class (found on a second
   pass; the zones.css/@container check alone missed this descendant rule
   in elements.css). .publishing-fit-ledger has no such rule and was
   genuinely full-replaced. */

.flex-copy {
  flex: 1 1 18rem;
}

.booking-capability-list {
  display: grid;
  margin: var(--space-lg-xl) 0 0;
}

.booking-capability-list > div {
  display: grid;
  grid-template-columns: 5rem minmax(0, 1fr);
  gap: var(--space-md-plus);
}

.booking-form-fields {
  display: grid;
}

/* --- Zone 04: included and outside fees (muted) --- */

.booking-fee-lines {
  display: grid;
  margin: 0 0 var(--space-md-lg);
}

/* --- Zone 05: integrated versus a separate tool (dark) --- */

/* Same technique again; the copy side is the smaller share here (40/60), so
   the ledger takes the larger grow factor. */
.wrap-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-3xl);
  align-items: flex-start;
}

.flex-copy-lg {
  flex: 1 1 20rem;
}

/* .booking-difference-copy: dual-class with .flex-copy-lg (base) — no
   zones.css/@container override on the bare class, kept for consistency
   with its group siblings which do have overrides. */

.booking-difference-ledger {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
  flex: 1.5 1 26rem;
}

/* 2 columns — 2 or 1. Never an orphan; a 2-item grid has no odd step. */
@container frame (min-width: 700px) {
  .booking-difference-ledger {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.booking-difference-column {
  display: flex;
  min-width: 0;
  flex-direction: column;
}

/* ============================================================
   360 Publishing Suite page — container layout
   ============================================================ */

/* --- Zone 02: useful inputs (dark) --- */

.publishing-inputs-heading,
.publishing-bridge {
  grid-column: 1 / -1;
}

/* 3 panels — 3 or 1, never 2+1. 3 is prime, so there is no middle step that
   does not strand one (Build Lesson #20). */
@container frame (min-width: 900px) {
  .publishing-inputs-container-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Horizontal on desktop; the 1000px block below turns it vertical and rotates
   the marks with it, which is the one thing a container query cannot express
   here because the spec states it in viewport terms ("on a handheld"). */
.publishing-bridge {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md-lg);
  align-items: center;
  justify-content: space-between;
}

/* --- Zone 03: the publishing view (light) --- */

/* 60/40 copy-to-capture via flex grow, so it collapses with no media query.
   Shared base is .wrap-row-lg. */

/* --- Zone 04: publishing outcomes (muted) --- */

.publishing-outcomes-heading {
  grid-column: 1 / -1;
}

/* 4 cards — 1, 2+2, then 4. Divisors only. */
@container frame (min-width: 620px) {
  .publishing-outcomes-container-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@container frame (min-width: 1200px) {
  .publishing-outcomes-container-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* --- Zone 05: fit and boundary (dark) --- */

/* .publishing-fit-copy: dual-class with .flex-copy-lg (base) — zones.css
   .zone-dark .publishing-fit-copy > p:last-child still matches the bare
   class. */

.flex-fill {
  flex: 1 1 100%;
}

/* .publishing-scope-note: dual-class with .flex-fill (base) — zones.css
   .zone-dark .publishing-scope-note still matches the bare class. */

/* --- Zone 06: one useful month (light) --- */

/* 4 cells — 1 then 2+2. Capped at two columns because the spec states two
   above the lock point, not four. */
.publishing-month-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
  flex: 1.5 1 24rem;
}

@container frame (min-width: 760px) {
  .publishing-month-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.publishing-month-note {
  grid-column: 1 / -1;
}

/* ============================================================
   Degrees pricing page — container layout
   ============================================================ */

/* --- Zone 03: the shared promise (light) ---
   One typographic column, deliberately not a grid of cards: the spec requires
   it to stay one section on a handheld rather than becoming six cards. */
.promise-container-stack {
  display: grid;
}

.promise-list > li {
  display: grid;
  grid-template-columns: 4rem minmax(0, 1fr);
  gap: var(--space-md-plus);
  align-items: baseline;
}

/* --- Zone 04: support path (muted) --- */

.support-path-heading {
  grid-column: 1 / -1;
}

/* 3 paths — 3 or 1, never 2+1. The order 90/180/360 must hold at every width,
   which a single-axis grid guarantees. */
@container frame (min-width: 900px) {
  .support-path-container-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.support-path-outcome {
  margin-top: auto;
}

/* --- Zone 05: what 360° adds (dark) --- */

/* 42/58 text-to-detail, text first in reading order. .pricing-extras-copy:
   dual-class with .flex-copy-lg (base) — zones.css .zone-dark
   .pricing-extras-copy > p:last-child still matches the bare class. */

.pricing-extras-detail {
  display: grid;
  gap: var(--space-md);
  flex: 1.4 1 26rem;
}

.pricing-booking-inset {
  display: grid;
}

.pricing-booking-inset dl {
  display: grid;
  margin: 0;
}

.pricing-booking-inset dl > div {
  display: grid;
  grid-template-columns: 7rem minmax(0, 1fr);
  gap: var(--space-md);
  align-items: baseline;
}

/* The panel is a 2-track grid, so its SMS note has to span or it lands as a
   stranded third item (rowfill ORPHAN). Scoped, so comparison.html is untouched. */
.pricing-extras-panel .degree-extras-note {
  grid-column: 1 / -1;
}

/* 8 items in two columns, per spec — 4+4, a clean divisor. Scoped to this
   panel so comparison.html's 7-item list keeps its own reflow. */
.pricing-extras-panel .degree-extras-list {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 700px) {
  .pricing-extras-panel .degree-extras-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* 2 links here, not the 3 the shared rule assumes — without this the third
   track renders empty. */
.pricing-system-links {
  grid-template-columns: minmax(0, 1fr);
}

@container frame (min-width: 700px) {
  .pricing-system-links {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* --- Zone 06: covered and outside costs (light) --- */

/* --- Zone 07: guarantees and charter terms (muted) --- */

/* 2 panels — 2 or 1. 90° stacks first below the lock point by DOM order. */
@container frame (min-width: 760px) {
  .assurance-container-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.assurance-note {
  grid-column: 1 / -1;
}

/* ============================================================
   Questions & Contact page — container layout
   ============================================================ */

/* --- Zone 02: the client's part (dark) --- */

.responsibility-container-stack {
  display: grid;
}

.responsibility-list > li {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  gap: var(--space-md-plus);
}

/* --- Zone 03: changes and support (light) --- */

/* Horizontal rails: the Degree sits beside its answer rather than above it,
   and collapses at the lock point. */
.answer-rail {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md-lg) var(--gap-2xl);
  align-items: flex-start;
}

.answer-rail > .display-sm-main {
  flex: 0 1 8rem;
}

.answer-rail-copy {
  flex: 1 1 22rem;
}

/* --- Zone 05: before and after launch (light) --- */

.launch-stages {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
  flex: 1.6 1 24rem;
}

/* 2 stages — 2 or 1. */
@container frame (min-width: 900px) {
  .launch-stages {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.launch-questions {
  flex: 1 1 100%;
  margin-top: var(--space-lg);
}

/* --- Zone 06: extra systems (dark) --- */

.systems-heading {
  grid-column: 1 / -1;
}

/* 3 fields — 3 or 1, never 2+1. */
@container frame (min-width: 900px) {
  .systems-container-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.system-field .text-link {
  margin-top: auto;
}

/* --- Zone 07: the two-destination signpost (muted) --- */

/* 2 destinations — 2 or 1. The single 1px divider between them is drawn by
   the second panel's leading edge, which flips from top to inline here. */
@container frame (min-width: 760px) {
  .signpost-container-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.signpost .btn-primary {
  margin-top: var(--space-lg);
  align-self: flex-start;
}

/* --- Zone 08: contact (dark) --- */

/* 38/62 details-to-form. */

.contact-form-panel {
  flex: 1.6 1 24rem;
}

/* .contact-after: dual-class with .flex-fill (base) — zones.css
   .zone-dark .contact-after still matches the bare class. */

.contact-rows {
  display: grid;
  margin: 0;
}

.contact-form,
.form-field {
  display: grid;
}

/* https://www.sitethreesixty.com */

/* ============================================================
   FAQ behaviour demo — container layout (internal page)
   ============================================================ */

/* Shared base is .loose-grid. */

.demo-notes {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0 var(--gap-2xl);
  margin: 0;
}

/* 6 rows — 2+2+2 or 1. Divisors only (Build Lesson #20). */
@container frame (min-width: 760px) {
  .demo-notes {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.demo-notes > div {
  display: grid;
  grid-template-columns: 9rem minmax(0, 1fr);
  gap: var(--space-md);
  align-items: baseline;
}

/* ============================================================
   Bearings marks — container layout (internal page)
   ============================================================ */

.marks-options-container-grid,
.marks-grid-container,
.marks-states-container,
.marks-sizes-container {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
}

.marks-options-heading,
.marks-note {
  grid-column: 1 / -1;
}

/* 2 options — 2 or 1. */
@container frame (min-width: 760px) {
  .marks-options-container-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.marks-verdict {
  margin-top: auto;
}

.marks-scores,
.marks-states,
.marks-sizes,
.marks-grid,
.marks-compare {
  display: grid;
  margin: 0;
}

.marks-scores {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.marks-shell {
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
}

/* 8 marks — 1, 2, 4. Divisors of 8 only (Build Lesson #20). .marks-grid:
   dual-class with .marks-shell (base) — the two @container rules below
   still target the bare class, and each of marks-grid/-states/-sizes needs
   its own column count at its own breakpoint (Batch A precedent). */
@container frame (min-width: 560px)  { .marks-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container frame (min-width: 1000px) { .marks-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* The side-by-side ground comparison keeps 2 columns of 8 tight. */
.marks-grid-tight { gap: var(--space-sm); }
@container frame (min-width: 560px)  { .marks-grid-tight { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container frame (min-width: 1000px) { .marks-grid-tight { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.marks-compare { gap: var(--gap-2xl); grid-template-columns: minmax(0, 1fr); }
@container frame (min-width: 900px) { .marks-compare { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.bearing-cell {
  display: grid;
  gap: var(--space-sm);
  justify-items: center;
  text-align: center;
}

/* 4 states — 1, 2, 4. .marks-states: dual-class with .marks-shell (base). */
@container frame (min-width: 560px)  { .marks-states { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container frame (min-width: 1000px) { .marks-states { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.marks-states article,
.marks-sizes article {
  display: grid;
  gap: var(--space-sm);
  justify-items: center;
  text-align: center;
}

/* 3 sizes — 3 or 1 (prime). .marks-sizes: dual-class with .marks-shell
   (base). */
@container frame (min-width: 700px) { .marks-sizes { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.marks-state-stage,
.marks-sizes article > div {
  display: grid;
  min-height: 6rem;
  place-items: center;
}

/* ============================================================
   About page — container layout
   ============================================================ */

/* --- Zone 02: why we exist (dark) --- */

.exists-container-stack {
  display: grid;
}

/* --- Zone 03: the restaurant analogy (light) --- */

.split-grid-lg {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap-2xl);
}

/* .analogy-container-grid: dual-class with .split-grid-lg (base) — the
   @container frame (min-width: 900px) rule below still targets the bare
   class, with its own column split distinct from the other two members. */

.analogy-heading,
.analogy-close {
  grid-column: 1 / -1;
}

/* 3 parts — 3 or 1, never 2+1. The reading order Builder → Agency →
   Sitethreesixty holds at every width (spec §7). */
@container frame (min-width: 900px) {
  .analogy-container-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* --- Zone 04: the model turned around (muted) --- */

/* Intro ~4 columns, stages ~8 — expressed as a grow ratio so it collapses
   with no media query. */

.model-stages {
  display: grid;
  flex: 2 1 26rem;
}

.model-stages > li {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  gap: var(--space-md-plus);
}

/* --- Zone 05: the ownership rail (dark) --- */

/* .ownership-readings: dual-class with .single-col-panel (base) —
   zones.css .zone-dark .ownership-readings and the
   @container frame (min-width: 900px) rule below still match the bare
   class. */

/* 3 readings — 3 or 1 (prime). */
@container frame (min-width: 900px) {
  .ownership-readings {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* --- Zone 06: company standards (light) --- */

.standards-container-stack {
  display: grid;
}

.standards-list > li {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr);
  gap: var(--space-md-plus);
}

/* --- Zone 07: the founder (muted) --- */

/* ~5 columns photo / ~7 copy. ⚠️ The photo is FIRST in the DOM because the
   spec permits it to lead when the crop reads as a strong portrait, and
   `order` is not used — so the stacked reading order is photo, then copy.
   If the real crop does not carry that, swap the two blocks in the markup
   rather than adding an order rule. */

.founder-photo {
  flex: 1 1 16rem;
}

.founder-copy {
  flex: 1.4 1 24rem;
}

/* --- Zone 08: proof before promise (dark) --- */

.proof-copy {
  flex: 2 1 24rem;
}

.proof-index {
  display: grid;
  margin: 0;
  flex: 1 1 16rem;
}

/* ============================================================
   Our Work page — container layout
   ============================================================ */

/* --- Zone 07: test your own website (dark, pos-content) ---
   Was Zone 02 in the previous build; moved near the end so the ONE blank
   address field on the page doesn't compete with the preloaded, one-click
   hero test. .speedtest-readings/-limit/-note dropped with it — the old
   zone showed its own 4-score result block; the new one reuses the shared
   pagespeed-modal for every test on this page, hero included, so a second
   inline result display is not needed here. */

.speedtest-instrument {
  flex: 1.8 1 26rem;
}

/* .speedtest-qualification: dual-class with .flex-fill (base) — zones.css
   .zone-dark .speedtest-qualification still matches the bare class. */

/* The field and its action share a line only while both stay usable; below
   that the button drops beneath, which flex-wrap does on its own. */
.speedtest-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md-lg);
  align-items: center;
}

/* --- Zone 02: the Curly Tail spotlight (muted) --- */

/* .spotlight-story: dual-class with .flex-copy-lg (base) — no override,
   kept for group consistency. */

/* .spotlight-evidence renamed .spotlight-result — the "evidence ledger"
   apparatus it held (dated before/after table, search-position table) is
   retired site-wide per owner ruling, 19 Aug 2026: the v2 spec's prove-it
   framing is rejected outright. Same flex-basis contract as before, new
   content: a plain confident reading instead of a ledger. */
.spotlight-result {
  display: grid;
  gap: var(--gap-lg);
  flex: 1.5 1 26rem;
}

.spotlight-result-list {
  display: grid;
  gap: var(--space-md-lg);
  margin: 0;
}

.spotlight-result-list > div {
  display: grid;
  grid-template-columns: 5rem minmax(0, 1fr);
  gap: var(--space-md-lg);
  align-items: baseline;
}

.spotlight-actions {
  display: grid;
  gap: var(--space-sm-md);
  justify-items: start;
}

/* --- Zone 03: the portfolio (light) --- */

/* Shared base is .loose-grid. */

.work-rail {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-2xl);
  align-items: flex-start;
}

/* The closing "design archive" rail — quieter without losing its actions,
   guideline's explicit instruction. Opacity is never used to fade text
   (Colour Absolutes / A8), so this reduces via the muted-text token same as
   any other de-emphasised copy, not a transparency trick. */
.work-rail-quiet .work-rail-copy > p:not(.work-status, .work-trade) {
  max-width: 60ch;
}

/* .work-rail-image: dual-class with .flex-copy-lg (base) — zones.css
   .zone-light .work-rail-image still matches the bare class. */

.work-rail-copy {
  flex: 1.2 1 22rem;
}

/* Image alternates side above the lock point only; stacked, every rail reads
   status → title → summary → score → actions in DOM order. A container
   query, so no breakpoint is added. */
@container frame (min-width: 1000px) {
  .work-rail:nth-child(even) {
    flex-direction: row-reverse;
  }
}

.work-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md-lg);
}

/* --- Zone 05: designed for phone and desktop (muted) ---
   NOTE: this CSS file's declaration order (viewports, then research-context)
   does not match the HTML page order (research-context/Zone 04 appears
   before viewports/Zone 05 in our-work.html) — CSS is order-independent
   across unrelated selectors, so this is a comment-accuracy note only, not a
   functional issue. --- */

.viewports-proof {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-2xl);
  flex: 1.8 1 24rem;
  align-items: flex-start;
}

.viewport-frame-phone {
  flex: 1 1 9rem;
}

.viewport-frame-desktop {
  flex: 2.2 1 18rem;
}

/* --- Zone 04: not everything, not nothing (dark) --- */

/* Two readings — 2 or 1. The owner-approved claim is "top 2%" plus the
   per-site scores; the banned 100-site study's third figure (94%) is never
   reinstated (owner ruling, 19 Aug 2026). */
.research-context-readings {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap-2xl);
  flex: 1.6 1 24rem;
}

@container frame (min-width: 900px) {
  .research-context-readings { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* --- Zone 08: compare everything, then choose (light) --- */

.proof-degree-copy {
  flex: 2 1 24rem;
}

.proof-degree-actions {
  display: grid;
  gap: var(--space-md);
  flex: 1 1 14rem;
  justify-items: stretch;
}

/* ============================================================
   Contact modal — container layout (portable)
   ============================================================ */

.contact-modal {
  display: none;
}

.contact-modal.is-open {
  display: grid;
}

/* ============================================================
   Marquee — the static, centred, wrapped trade-links strip.
   Reuses .section-label for the title and .text-link for each
   link (zone-scoped colour already exists in zones.css) — the
   only new classes are the wrapper and the "·" separator, neither
   of which had an existing match. One shared block, appears on
   11 pages, so kept to the minimum per the reuse rule.
   ============================================================ */

.marquee-container-grid > .section-label {
  display: block;
  text-align: center;
  margin: 0 0 var(--space-lg-xl);
}

.marquee-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: var(--space-sm) var(--space-md-lg);
}

/* ============================================================
   What You Get page — container layout
   Consolidation page: several Zones reuse an existing shape as-is
   (.content-map-steps, .dash-bearings-container-grid / .bearings-card,
   .promise-list, .final-cta-grid) with no new container rule needed for
   them. Only the genuinely new shapes get a new class below.
   ============================================================ */

/* --- Zone 2: from facts to website (dark) ---
   Same "one horizontal datum above 1000px, vertical stack below" shape as
   .progress-line, but for five items rather than four — .progress-line's
   own container-query steps (2-up, 4-up) don't reach 5-up, so this is a
   sibling class rather than a forced reuse of a four-item shape. Shared
   base is .loose-grid. .wyg-facts-line: dual-class with .single-col-panel
   (base) — zones.css .zone-dark .wyg-facts-line (+ related) and the
   @container frame rules below still match the bare class. */

/* Each step stacks its own parts. Without this the li's children — <b>, <span>,
   <strong>, <p> — are all inline, so they ran together as one paragraph:
   "01 TELL / US The business." with the label splitting across lines.
   The grid gives each part its own row.
   ⚠️ .wyg-move-line has the identical structure and was missed on the first
   pass — fixed together so the next list of this shape inherits it. */
.wyg-facts-line > li,
.wyg-move-line > li {
  display: grid;
  align-content: start;
  gap: var(--space-xs);
}

@container frame (min-width: 620px) {
  .wyg-facts-line {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@container frame (min-width: 1200px) {
  .wyg-facts-line {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

/* No new breakpoint: max-width 1000px is the system's only one, and it sits
   after the container queries in this same file, so it wins on source order
   with no cascade race (Build Lesson #17 pattern, matching .progress-line). */
@media (max-width: 1000px) {
  .wyg-facts-line {
    grid-template-columns: minmax(0, 1fr);
  }
}

.wyg-facts-line li {
  padding-right: var(--space-lg);
}

/* --- Zone 3: the website foundation (light) ---
   Left-position question beside a Right-position full-height ledger, per
   spec. Not a card grid — the spec explicitly forbids separating the rows
   into cards, so the ledger is one ruled list, matching .degree-coverage-list
   / .promise-list's "ruled row" grammar but with a three-part row (mono
   category + title + explanation) neither of those shapes carries.
   .wyg-foundation-container-grid: dual-class with .split-grid-lg (base) —
   the @container rule below still targets the bare class, with its own
   column split distinct from the other two members. */

@container frame (min-width: 820px) {
  .wyg-foundation-container-grid {
    grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
    align-items: start;
  }
}

@media (max-width: 1000px) {
  .wyg-foundation-container-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.wyg-ledger {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.wyg-ledger li {
  display: grid;
  grid-template-columns: minmax(9rem, .28fr) minmax(0, 1fr);
  gap: var(--space-md-plus);
  align-items: baseline;
  padding-block: var(--space-md-lg);
  border-top-width: var(--border-thin);
  border-top-style: solid;
}

.wyg-ledger li:last-child {
  border-bottom-width: var(--border-thin);
  border-bottom-style: solid;
}

@media (max-width: 1000px) {
  .wyg-ledger li {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- Zone 4: 360Dash and Content Map (muted) ---
   Same copy-then-image split shape as .content-map-container-grid /
   .dash-jobs-container-grid; reused via a page-scoped grid class because
   the split ratio (48/52) and the muted-zone image sizing differ slightly
   from the existing light/dark versions. .content-map-steps is reused
   unchanged for the four ruled jobs — only the wrapper differs. */
.wyg-dash-container-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap-3xl);
}

@container frame (min-width: 820px) {
  .wyg-dash-container-grid {
    grid-template-columns: minmax(0, .92fr) minmax(0, 1fr);
    align-items: center;
  }
}

@media (max-width: 1000px) {
  .wyg-dash-container-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.wyg-dash-jobs {
  margin-top: var(--space-lg-xl);
}

.wyg-dash-panel {
  min-height: 24rem;
}

/* --- Zone 6: Degree differences (light) ---
   Three horizontal rails: Degree at left, added service in the middle,
   route/boundary detail at right on wide views; one vertical block per
   rail on a handheld. Deliberately not .degree-rail — that shape carries
   a third price+button column this page must not show (pricing.html owns
   all price detail), so forcing the reuse would mean hiding an unwanted
   column rather than a genuine shared shape. */
.wyg-degrees-container-grid {
  display: grid;
  gap: var(--gap-2xl);
}

.wyg-degree-rail {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md-plus);
  padding-block: var(--space-lg-xl);
  border-top-width: var(--border-thin);
  border-top-style: solid;
}

@container frame (min-width: 820px) {
  .wyg-degree-rail {
    grid-template-columns: 6rem 10rem minmax(0, 1fr);
    align-items: baseline;
  }
}

@media (max-width: 1000px) {
  .wyg-degree-rail {
    grid-template-columns: minmax(0, 1fr);
  }
}

.wyg-degree-rail:last-of-type {
  border-bottom-width: var(--border-thin);
  border-bottom-style: solid;
}

.wyg-degrees-action {
  margin-top: var(--space-lg-xl);
}

/* --- Zone 7: after launch (muted) ---
   One large statement then a three-part sequence — reuses .promise-list's
   ruled-row grammar unchanged; only the wrapper name differs so this
   page's copy doesn't collide with pricing.html's zone-scoped .promise-list
   colour rules in zones.css (both are zone-scoped by their own zone class,
   so no collision in practice, but the container needs its own class since
   .promise-container-stack is pricing.html-shaped around a single column
   at a different max-width). */
.wyg-after-container-stack {
  display: grid;
}

.wyg-after-sequence {
  margin-top: var(--space-lg-xl);
}

.wyg-after-sequence li {
  display: grid;
  grid-template-columns: 10rem minmax(0, 1fr);
  gap: var(--space-md-plus);
  align-items: baseline;
}

@media (max-width: 1000px) {
  .wyg-after-sequence li {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- Zone 8: moving an existing site (dark) ---
   Left-position heading beside a Right-position four-stage line.
   .wyg-move-container-grid: dual-class with .split-grid-lg (base) — the
   @container rule below still targets the bare class, with its own column
   split distinct from the other two members. */

@container frame (min-width: 820px) {
  .wyg-move-container-grid {
    grid-template-columns: minmax(0, .7fr) minmax(0, 1.3fr);
    align-items: start;
  }
}

@media (max-width: 1000px) {
  .wyg-move-container-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.wyg-move-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
}

@container frame (min-width: 620px) {
  .wyg-move-line {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--gap-lg);
  }
}

@media (max-width: 1000px) {
  .wyg-move-line {
    grid-template-columns: minmax(0, 1fr);
  }
}

.wyg-move-line li {
  padding-block: var(--space-md-lg);
  padding-right: var(--space-lg);
}

.wyg-move-note {
  margin-top: var(--space-lg-xl);
}

/* --- Explore further ---
   Onward routes, placed INSIDE a page's closing section rather than in a zone
   of its own. Owner ruling 22 Aug: as a standalone zone below the hero it read
   as part of the hero. Sitting at the foot of the closing section it reads as
   that section's conclusion, and inherits its ground so it never introduces a
   new band of colour.
   Left-aligned to its parent, not centred — it follows the action row above it. */
.explore-links {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-sm) var(--space-md-lg);
  margin-top: var(--space-lg-xl);
}

/* The label sits on its own line above the links, full width of the flex row. */
.explore-links > .section-label {
  flex: 1 0 100%;
  margin: 0 0 var(--space-sm-md);
}

/* ============================================================
   Trade page — article Zone
   New page type (first trade page: electricians), so no existing container
   shape matches — searched .exists-container-stack, .standards-container-
   stack and the rest of the display:grid stack family (all single-page,
   page-prefixed, and F1 already flags that whole group as one duplicate set;
   reusing any one of them for unrelated content was judged worse than the
   +1 this adds — see build report). Named for what it does: a single-column
   stack for long-form article content, portable to any future long-form page.
   ============================================================ */

.content-stack {
  display: grid;
  gap: var(--space-lg);
}

/* Share row — appears twice per article (top and bottom). Icons are plain
   links; the row itself just distributes them with a label. */
.share-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm) var(--space-md);
}

.share-row-icons {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

/* Mid-article CTA — a short line plus a button pair. Reuses .action-row for
   the buttons themselves; this only supplies the panel around them. */
.article-cta {
  display: grid;
  gap: var(--space-md);
  padding: var(--gap-sm);
  border-width: var(--border-thin);
  border-style: solid;
  border-radius: var(--radius-lg);
  justify-items: start;
}

/* Booking showcase teaser — short pitch + one link to the existing demo
   page (booking.html). Reuses .inset-panel for the bordered shell and
   .single-col-grid for the internal stack (same shape as `display: grid;
   gap: var(--space-md)` already merged there — searched before adding a
   new selector and found the existing shared class instead). */
