/* ============================================================================
   THE GRID — shared by about, design-system and coffer.

   Extracted from those three pages, which each carried an identical copy. One
   source now, so a change lands everywhere and the pages cannot drift.

   THE COLUMN COUNT IS RESPONSIVE, and that is the whole point. A grid that
   only works at one width is decoration. This one RE-FORMS at each breakpoint
   and the content re-spans with it, so every rule keeps marking something real
   instead of becoming a line that happens to be there.

     ≥1025px   14 columns — 2 for the margin numeral, 12 for the body.
               Twelve divides by 2, 3, 4 and 6, so every repeating grid on a
               page lands on a rule. Boundaries: 0 · 2 · 5 · 8 · 11 · 14
     641–1024  8 columns. The numeral moves ABOVE the content and the body
               takes the full width. Eight divides by 2 and 4, which is what
               the two-across tablet layouts need. Boundaries: 0 · 2 · 4 · 6 · 8
     ≤640px    4 columns, single-column content, one interior reference rule
               the content is allowed to cross. Boundaries: 0 · 2 · 4

   Two weights, and the distinction carries meaning:
     SOLID   a structural edge — content starts or stops exactly here.
     DASHED  a reference column content may cross, and the dashes say so.

   Which is which is EARNED per breakpoint, not fixed. The two margins are
   always structural. Boundary 2 is structural on desktop only, because that is
   the only width where the body actually begins there.

   The boundary set is fixed, not per-section: all 27 sections that existed
   when this was extracted used the same one, so the override was dead weight.
   ========================================================================== */

:root{
  --margin: 8vw;
  --gutter: clamp(10px, 1.3vw, 22px);
  --cols: 14;
  --col-w: calc((100vw - var(--margin) * 2 - var(--gutter) * (var(--cols) - 1)) / var(--cols));
  --ease-swoop: cubic-bezier(.6,0,0,1);
}
@media (max-width:1024px){ :root{ --cols: 8; --margin: 7vw; } }
@media (max-width:640px) { :root{ --cols: 4; --margin: 6vw; --gutter: 10px; } }


/* ==========================================================================
   VERTICAL RULES

   Every boundary any breakpoint needs is emitted once. Position is computed
   from --cols, so the same elements slide into their new places when the
   column count changes — nothing is regenerated on resize, and no resize
   listener is needed.
   ========================================================================== */
.grid-lines{ position:absolute; inset:0; pointer-events:none; z-index:0; }
.grid-lines i{
  position:absolute; top:0; bottom:0; width:1px;
  left: calc(var(--margin) + var(--n) * (var(--col-w) + var(--gutter)));
  display:none;
  /* One property carries the whole weight distinction, so a breakpoint only
     has to NAME its structural boundaries rather than restate how a solid rule
     is painted. Dashed is the default because most boundaries are reference
     columns. */
  background: var(--rule-paint, repeating-linear-gradient(to bottom, currentColor 0 2px, transparent 2px 9px));
  opacity: var(--rule-op, .16);
  transform: scaleY(0); transform-origin: top center;
  transition: transform 1.15s var(--ease-swoop) var(--vd, 0ms);
}
.grid-lines.drawn i{ transform: scaleY(1); }

/* Solid is EARNED, not fixed: a rule goes solid only where content actually
   starts or stops on it. A rule content crosses stays dashed and says so —
   which is the distinction the first version of this grid missed.

   The final boundary of each set is the right margin, not another column
   start, so it pins to the edge; the running formula would overshoot it by
   exactly one gutter. It draws early, alongside the left margin, because the
   frame should arrive before the measurements inside it.

   So each breakpoint below names three things: which boundaries exist, which
   of them are structural, and which one is the right margin. */
@media (min-width:1025px){
  .grid-lines i:is([data-n="0"],[data-n="2"],[data-n="5"],[data-n="8"],[data-n="11"],[data-n="14"]){ display:block; }
  /* 2 is where the body begins, so it is a real edge here. */
  .grid-lines i:is([data-n="0"],[data-n="2"],[data-n="14"]){ --rule-paint: currentColor; --rule-op: .17; }
  .grid-lines i[data-n="14"]{ left:auto; right: var(--margin); --vd: 120ms; }
}
@media (max-width:1024px){
  /* Below desktop the numeral moves above the content and the body spans the
     full width, so 2 stops marking an edge and demotes to a reference column
     the content crosses. Only the two margins stay structural. */
  .grid-lines i:is([data-n="0"],[data-n="2"]){ display:block; }
  .grid-lines i[data-n="0"]{ --rule-paint: currentColor; --rule-op: .17; }
}
@media (min-width:641px) and (max-width:1024px){
  .grid-lines i:is([data-n="4"],[data-n="6"],[data-n="8"]){ display:block; }
  .grid-lines i[data-n="8"]{ --rule-paint: currentColor; --rule-op: .17; left:auto; right: var(--margin); --vd: 120ms; }
}
@media (max-width:640px){
  .grid-lines i[data-n="4"]{
    display:block; --rule-paint: currentColor; --rule-op: .17;
    left:auto; right: var(--margin); --vd: 120ms;
  }
}

@media (prefers-reduced-motion: reduce){
  .grid-lines i{ transform:none !important; transition:none !important; }
}


/* ==========================================================================
   SPREADS
   ========================================================================== */
.spread{
  display:grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  column-gap: var(--gutter);
  /* The band above and below the cells is where the grid breathes — the cells
     themselves are tight, so this is what separates one section from the next. */
  padding: 9vh var(--margin);
  position:relative;
  overflow:hidden;
}
/* overflow:hidden on an ancestor makes IT the scroll container, so a sticky
   child sticks to the spread instead of the viewport and never engages. Any
   spread holding a sticky has to opt out of the clip. */
.spread.no-clip{ overflow:visible; }
/* Everything in a spread stacks above the rules — except the layers that are
   deliberately behind them: the rules themselves, the drifting colour blocks,
   and the oversized ghost numeral. */
.spread > *:not(.grid-lines):not(.blocks):not(.ghost-num){ position:relative; z-index:1; }

.idx{
  grid-column: 1 / 3;
  position:relative;
  font-family: var(--font-body); font-weight:600;
  font-size: clamp(12px, 1.05vw, 16px); letter-spacing:.06em;
  padding-top:.55em;
}
/* Runs the numeral down into the next section, so the margin column reads as
   one continuous spine rather than a stack of unrelated numbers. */
.idx::after{
  content:''; position:absolute; left:.1em; top:3.2em; bottom:-9vh;
  width:1px; background: currentColor; opacity:.35;
}
.spread:last-of-type .idx::after{ display:none; }

.body{
  grid-column: 3 / calc(var(--cols) + 1);
  display:grid; grid-template-columns: repeat(12, 1fr); column-gap: var(--gutter);
}


/* ==========================================================================
   CELLS — the horizontal half of the grid.

   Columns on their own are stripes. Every content group sits in a CELL: a
   full-width row with a rule along its top edge, so each piece has a box to
   occupy rather than floating between two verticals.
   ========================================================================== */
.cell{
  grid-column: 1 / 13;
  display:grid; grid-template-columns: repeat(12, 1fr); column-gap: var(--gutter);
  padding: clamp(26px, 3.4vh, 54px) 0;
  position:relative;
}
.cell::before,
.cell:last-child::after{
  content:''; position:absolute; height:1px;
  /* Reaches back across the numeral column to the left margin, so the numeral
     gets a box too. */
  left: calc(-2 * (var(--col-w) + var(--gutter))); right:0;
  background: currentColor; opacity:.17;
  transform: scaleX(0); transform-origin: left center;
}
.cell::before{ top:0; transition: transform 1s var(--ease-swoop) var(--rd, 0ms); }
/* Closes the last box, so a section reads as bounded rather than trailing off. */
.cell:last-child::after{ bottom:0; transition: transform 1s var(--ease-swoop) calc(var(--rd, 0ms) + 160ms); }
.cell.is-in::before,
.cell:last-child.is-in::after{ transform: scaleX(1); }

.cell > *{ grid-column: 1 / 13; }
/* Content stops on drawn rules: body column 9 for headlines (page rule 11),
   body column 6 for reading measures (page rule 8). */
.cell > h2.big{ grid-column: 1 / 10; }
.cell > .lede, .cell > .note{ grid-column: 1 / 7; }
.cell > h3.sub{ grid-column: 1 / 10; }



/* ==========================================================================
   TABLET — 8 columns, numeral above the content
   ========================================================================== */
@media (max-width:1024px){
  .idx{ grid-column: 1 / -1; margin-bottom:1.1em; }
  .idx::after{ display:none; }          /* no margin column left to run down */
  .body{ grid-column: 1 / -1; grid-template-columns: repeat(8, 1fr); }
  .cell{ grid-column: 1 / 9; grid-template-columns: repeat(8, 1fr); }
  .cell > *{ grid-column: 1 / 9; }
  /* Stops on this grid's own rules — 6 and 4 — not the desktop ones. */
  .cell > h2.big{ grid-column: 1 / 7; }
  .cell > .lede, .cell > .note{ grid-column: 1 / 5; }
  .cell > h3.sub{ grid-column: 1 / 7; }
  /* Nothing to reach back over once the numeral sits above the content. */
  .cell::before, .cell:last-child::after{ left:0; }
}


/* ==========================================================================
   PHONE — 4 columns, single-column content
   ========================================================================== */
@media (max-width:640px){
  .spread{ padding: 7vh var(--margin); }
  .body{ grid-template-columns: repeat(4, 1fr); }
  .cell{ grid-column: 1 / 5; grid-template-columns: repeat(4, 1fr); padding: 26px 0; }
  .cell > *,
  .cell > h2.big,
  .cell > .lede,
  .cell > .note,
  .cell > h3.sub{ grid-column: 1 / 5; }
}

@media (prefers-reduced-motion: reduce){
  .cell::before, .cell:last-child::after{ transform:none !important; transition:none !important; }
}


/* ==========================================================================
   UTILITIES — after the breakpoint sections on purpose. These override the
   generic `.cell > *` span, and at equal specificity the later rule wins.
   ========================================================================== */
/* THREE-UP — the one arity that cannot divide the body evenly, so it gets a
   shared utility instead of being re-derived (and re-broken) per page.

   Three across the FULL twelve columns divides at 4 and 8, and neither is
   drawn — that is why three-item rows were the only ones drifting off the
   rules. Narrowing the row is what fixes it:

     desktop  9 of 12 body columns → divides at body 3 and 6 = page rules 5, 8
     tablet   6 of 8               → divides at page rules 2 and 4
     phone    stacks; three across 4 columns cannot land anywhere

   Geometry only. Each page keeps its own gap and card styling. */
.three-up{ grid-column: 1 / 10; display:grid; grid-template-columns: repeat(3, 1fr); }
@media (max-width:1024px){ .three-up{ grid-column: 1 / 7; } }
@media (max-width:640px) { .three-up{ grid-column: 1 / 5; grid-template-columns: 1fr; } }

/* FOUR-UP — the arity that needs no help, which is worth stating next to the
   one that does. Four across the FULL twelve body columns divides at body 4, 7
   and 10, and those are page rules 5, 8 and 11 — all three drawn. So unlike
   .three-up this one is not narrowed; the full width IS the aligned width.

     desktop  12 of 12 body columns → divides on page rules 5, 8, 11
     tablet    8 of 8               → divides on page rules 2, 4, 6
     phone     two-up; four across four columns leaves nothing to read

   Phone goes to 2×2 rather than stacking, because four stat figures in a
   single column is a very long ladder for something read at a glance. */
.four-up{ grid-column: 1 / 13; display:grid; grid-template-columns: repeat(4, 1fr); }
@media (max-width:1024px){ .four-up{ grid-column: 1 / 9; } }
@media (max-width:640px) { .four-up{ grid-column: 1 / 5; grid-template-columns: repeat(2, 1fr); } }
