/* =========================================================================
   GRAVITAS+, SITE COMPONENTS
   Built on top of gravitas.css (the design system). Everything here is a
   component the new architecture needs and the old single-page site didn't.
   ========================================================================= */

/* ---- the signature texture, available to every page ---------------------
   .g-well lives in the design system, but the image it draws was declared only
   in hero.css. Any page that didn't load the home page's stylesheet, About,
   Community, Account, rendered the well as an empty box, which is why their
   CTA panels came out flat while the identical panel on the home page carried
   the curved grid. The file is already in assets/; point at it here, where
   every page can see it. */
:root { --g-well-image: url("spacetime-well.svg"); }

/* ---------- shared chrome ------------------------------------------------ */
/* Moved out of hero.css: the header, mobile menu and button centring belong to
   every page. Left in the hero stylesheet, the dropdown simply did not exist
   anywhere except the home page. */
/* ---- Header additions ---- */
/* One height for every control in the header actions row, pill or circle. All
   of them had been sized independently and drifted apart: the CTA and the
   burger at 2.75rem, the theme toggle at 2.5rem, and Sign in and Join Us at
   whatever `.g-btn--sm`'s padding happened to add up to, which is 35px. On a
   phone that put a small circle between two larger ones; on a desktop it put
   two short pills beside a taller circle.

   The token is the row's height, not just the circles' diameter: the buttons
   take it as a min-height and centre their label in it, so a change to the
   type scale or the button padding cannot pull the row out of line again.
   2.75rem below 880px is the larger of the two circle sizes already in use and
   clears the 44px touch target. */
:root { --gh-control: 2.5rem; --gh-control-icon: 1.125rem; }
@media (max-width: 880px) { :root { --gh-control: 2.75rem; } }

/* The pills: same height as the circles, their own width. */
.gh-actions .g-btn { min-height: var(--gh-control); }

/* ---- The logo is never the thing that gives way -------------------------
   The header row is logo + nav + actions, and the logo was the only flex
   item allowed to shrink, so every time the row ran out of room it was the
   mark that got crushed: 149px down to 15px at 721px wide, and to 14px on a
   360px phone. It is the one element on the page that cannot be allowed to
   degrade, so it is locked at its intrinsic width and the row sheds controls
   instead, in this order:

     under 880px   the nav collapses into the menu, and "Sign in" goes with it
     under 400px   "Join Us" follows, and the header keeps logo + theme + menu

   Both live in the dropdown at every width below the breakpoint, so nothing
   is ever merely hidden. The 880 figure is measured, not guessed: logo 149 +
   nav 351 + actions 248 + two 24px gaps + gutters needs 864px, so the full
   row is only offered where it actually fits. */
.g-logo { flex: none; }

.lp-header__cta { display: inline-flex; }
/* The CTA's mark is sized in rem, not in the button's em. At --sm the button
   sets 0.8125rem, and a 13px box cannot render a 24-unit grid without the 1.7
   stroke landing on a fraction of a pixel — the one icon in the header that
   would have been drawn at a different weight from all the others. 1rem in a
   2.5rem pill leaves whole pixels either side, and the optical lead-shift below
   is recomputed from that width instead of from 1em. */
.lp-header__cta .g-btn__icon { width: 1rem; height: 1rem; }
.lp-header__cta.g-btn--lead { --_shift: calc((1rem + 0.5rem) / 4); }
/* Same for Sign in beside it, so both marks in the header row render at one
   weight, which was the point. The dropdown's Join Us is deliberately not in
   this rule: it is not a header control, it is the last row of a menu, and it
   is sized to that column instead, below. */
.gh-signin .g-btn__icon { width: 1rem; height: 1rem; }
.gh-signin.g-btn--lead { --_shift: calc((1rem + 0.5rem) / 4); }
.lp-menu-btn { display: none; }
.g-header__inner { position: relative; }
.lp-header__actions { display: flex; align-items: center; gap: var(--g-space-2xs); flex: none; flex-wrap: nowrap; }

/* In the dropdown only. On desktop these two sit in the header itself. */
.gh-nav-signin, .gh-nav-join, .g-nav__sep { display: none; }
/* Likewise the nav marks: shipped in the markup on every page, revealed by the
   dropdown's own media query below. */
.g-nav__ic { display: none; }

@media (max-width: 880px) {
  /* Collapse the CTA to a circular icon button that sits beside the hamburger.
     (The old rule targeted `.lp-header__cta .g-btn span`, but the class is on the
     button itself, so it never matched, the full label stayed, and the burger
     wrapped onto a second line.) */
  .lp-header__cta span { display: none; }
  .lp-header__cta {
    width: var(--gh-control); height: var(--gh-control); padding: 0;
    border-radius: var(--g-radius-pill); flex: none;
  }
  .lp-header__cta .g-btn__icon { width: var(--gh-control-icon); height: var(--gh-control-icon); }
  .lp-menu-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: var(--gh-control); height: var(--gh-control); flex: none;
    border-radius: var(--g-radius-pill);
    border: 1px solid var(--g-border); background: transparent; color: var(--g-text);
    cursor: pointer;
  }
  /* Beats the width/height attributes on the inline SVG, so the three glyphs
     carry the same optical weight and not just the same circle. */
  .lp-menu-btn svg { width: var(--gh-control-icon); height: var(--gh-control-icon); }
  .lp-menu-btn[aria-expanded="true"] { background: rgb(var(--g-tint) / 0.12); border-color: var(--g-border-strong); }

  /* The dropdown panel lives entirely in CSS so nothing leaks back to desktop.
     Only the .is-open class is toggled, no inline styles. */
  .g-nav {
    display: none;
    position: absolute; top: calc(100% + 0.5rem); right: 0;
    flex-direction: column; align-items: stretch; gap: 0;
    min-width: 12rem;
    background: var(--g-surface-raised);
    border: 1px solid var(--g-hairline);
    border-radius: var(--g-radius);
    box-shadow: var(--g-shadow);
    padding: var(--g-space-2xs);
    z-index: var(--g-z-overlay);
  }
  .g-nav.is-open { display: flex; }
  /* Row whitespace is set from the *ink*, not from the boxes. A mark's box is
     20px but its drawing stops about 1.8px short on each side, so a 1rem inset
     and a 0.5rem gap measured box-to-box actually read as 17.8px of air on the
     left and 9.8px between the mark and its label — the mark ends up nearly
     twice as far from the panel edge as from the word it belongs to, which is
     why it looked stuck to the text. At 0.75rem each, both whitespaces come to
     13.8px of ink-to-ink air, and the icon column separates cleanly. The label
     lands at 44px, unchanged, so nothing else in the panel moves. */
  .g-nav .g-nav__link {
    padding: var(--g-space-2xs) var(--g-space-xs);
    border-bottom: 0; border-radius: var(--g-radius-sm);
    display: flex; align-items: center; gap: var(--g-space-xs);
  }
  /* The marks appear here and nowhere else in the header. On the desktop row
     the nav is five words in a line, where an icon in front of each one turns
     navigation into a toolbar; in a vertical panel the same marks are what
     make the rows scannable at arm's length. Same markup, one display switch. */
  /* Dimmed via --g-ic-dim, not --g-text-subtle: see that token's note. An icon
     is several stroked paths, and a per-path alpha lights up every crossing. */
  .g-nav .g-nav__ic { display: block; color: var(--g-ic-dim); opacity: var(--g-ic-dim-a); }
  /* Sign in is the one mark whose drawing is not symmetric about the box: a
     solid dot on one side, an open bracket on the other, so the centroid rule
     that positions every mark leaves its *bounding box* 2.4 units left of
     centre. On a plate that is right and invisible. In a fixed-width column it
     is visible, because the eye aligns the drawings, not the boxes — the mark
     sat 2px left of the other five and 2px further from its own label.

     So in a row it is centred on its box instead. Whole pixels, deliberately:
     a fractional margin puts a 1.7 stroke on a half-pixel grid and softens the
     one mark it was meant to fix. This is a row-context correction only; on the
     plate the mark keeps its centroid position. */
  .g-nav .g-ic--signin { margin-left: 2px; }
  .g-nav .g-nav__link:hover .g-nav__ic,
  .g-nav .g-nav__link[aria-current="page"] .g-nav__ic { color: var(--g-accent); opacity: 1; }
  /* Sign in used to be indented by hand to reach the text column the five
     marked rows share. It has its own mark now, so the indent is gone and the
     alignment comes from the same box the others use. */
  .g-nav .g-nav__link:hover,
  .g-nav .g-nav__link[aria-current="page"] {
    background: rgb(var(--g-tint) / 0.1); color: var(--g-text);
  }

  /* Sign in leaves the header here and becomes a menu item. The hairline
     above it separates account actions from navigation, which is the only
     grouping the panel needs. */
  .gh-actions .gh-signin { display: none; }
  .gh-nav-signin { display: block; }
  .g-nav__sep {
    display: block; height: 1px; margin: var(--g-space-2xs) var(--g-space-3xs);
    background: var(--g-hairline);
  }

  /* A phone header has room for the mark and two controls, no more. The gap
     tightens with it so the logo keeps its full width down to 320px. */
  .g-header__inner { gap: var(--g-space-sm); }
}

@media (max-width: 400px) {
  .gh-actions .lp-header__cta { display: none; }
  /* Join Us drops out of the header and becomes the last row of the menu. It
     stays a filled pill, because it is still the one loud control, but it stops
     behaving like a standalone button: a full-width button centres its
     contents, so its mark floated in the middle of the panel while the six
     above it sat in a column at the left edge. That is one icon out of seven
     out of line, and it is the first thing the eye finds.

     So it is set to the menu's column rather than to its own centre. The rows
     put their label at 0.75rem of padding + a 1.25rem mark + a 0.75rem gap =
     2.75rem from the panel's content edge, and the button reproduces that.

     Its padding and gap are restated rather than inherited: at --sm the button
     is built on 1rem and 0.5rem, and it needs the rows' 0.75rem and 0.75rem.
     Setting padding-inline outright also retires the optical lead-shift, which
     exists to balance a *centred* label and is simply wrong for a flush one. */
  .gh-nav-join { display: inline-flex; width: 100%; margin-top: var(--g-space-3xs);
                 justify-content: flex-start; }
  .g-nav .gh-nav-join { gap: var(--g-space-xs); }
  .g-nav .gh-nav-join.g-btn--lead { padding-inline: var(--g-space-xs); }
  .gh-nav-join .g-btn__icon { width: 1.25rem; height: 1.25rem; }
}

/* ---- Optical centring for buttons with a leading icon --------------------
   A leading icon pushes the label right of the button's centre. Correcting for
   it *fully* centres the label but leaves the ink block visibly left of centre
   with a lopsided 1:2 whitespace split, which reads worse than the original
   problem. So compensate partially: shift by ~45% of the icon+gap width. The
   label lands a few px right of centre, the ink a few px left, and the two
   errors cancel to the eye.

   Driven by an explicit class rather than :has(), so every browser resolves it
   the same way, with :has() the correction silently vanished wherever it
   wasn't supported, giving two different buttons. */
/* Without correction the label sits (icon + gap)/2 right of centre. Shifting by
   exactly half of that splits the error evenly between label and ink, the
   minimax compromise. Expressed in em so it tracks each button size on its own:
   6px on --lg, 5.5px default, 5.25px on --sm. */
.g-btn--lead { --_shift: calc((1em + 0.5rem) / 4); }
.g-btn--lead    { padding-left: calc(1.5rem - var(--_shift)); padding-right: calc(1.5rem + var(--_shift)); }
.g-btn--lead.g-btn--lg { padding-left: calc(2rem   - var(--_shift)); padding-right: calc(2rem   + var(--_shift)); }
.g-btn--lead.g-btn--sm { padding-left: calc(1rem   - var(--_shift)); padding-right: calc(1rem   + var(--_shift)); }
/* the header CTA collapses to an icon-only circle on mobile, no label to centre */
@media (max-width: 880px) { .lp-header__cta.g-btn--lead { padding: 0; } }

/* ---- Topic filter row (PBS season selector) ---- */
.lp-filters { display: flex; flex-wrap: wrap; gap: var(--g-space-2xs); margin-bottom: var(--g-space-lg); }
.lp-filter {
  font: inherit; font-size: var(--g-fs-small); font-weight: var(--g-w-medium);
  color: var(--g-text-muted); background: transparent;
  border: 1px solid var(--g-border); border-radius: var(--g-radius-pill);
  padding: 0.4rem 0.9rem; cursor: pointer;
  transition: color var(--g-dur) var(--g-ease-orbit), border-color var(--g-dur) var(--g-ease-orbit), background-color var(--g-dur) var(--g-ease-orbit);
}

.g-header__inner { position: relative; }
.gh-actions { display: flex; align-items: center; gap: var(--g-space-2xs); flex: none; flex-wrap: nowrap; }

/* depth switch, the answer to serving two audiences without splitting the
   site in two. It re-renders the page in place instead of sending people
   somewhere else, so nobody has to choose an identity at the door. */
.depth {
  display: inline-flex; align-items: center; padding: 2px;
  border: 1px solid var(--g-border); border-radius: var(--g-radius-pill);
  background: rgb(var(--g-tint) / 0.05);
}
.depth button {
  font: inherit; font-size: var(--g-fs-caption); font-weight: var(--g-w-medium);
  letter-spacing: .02em; color: var(--g-text-muted); background: transparent;
  border: 0; border-radius: var(--g-radius-pill); padding: .3rem .7rem;
  cursor: pointer; white-space: nowrap;
  transition: color var(--g-dur) var(--g-ease-orbit), background-color var(--g-dur) var(--g-ease-orbit);
}
.depth button[aria-pressed="true"] { background: var(--g-solid-bg); color: var(--g-solid-fg); }
.depth button:hover[aria-pressed="false"] { color: var(--g-text); }
/* The switch is hidden below 900px because it used to live in the header,
   where there is no room for it on a phone. It does not live there any more -
   it sits in the .depthbar above the text it governs, so hiding it left the
   bar announcing "this essay reads two ways" with no way to pick either.
   Hide it only where it is still chrome. */
@media (max-width: 900px) { .g-header .depth { display: none; } }

/* content that only belongs to one reading level */
/* Depth visibility.
   This used to force `display: block` when revealing, which stamped on whatever
   the element's own layout was, a flex list row became a block, its badge lost
   `flex: none` and stretched, and the text next to it shifted. Hiding is the
   only thing this rule should decide; the element keeps its own display value
   via `revert`, so a row stays a row. */
/* Only ever hide. `revert` on the revealed element brought back the UA default
   (an li became list-item) rather than the author's own rule, so revealed rows
   laid out differently from the ones already showing. Hiding the other side
   leaves every visible element with exactly the display its own CSS gives it -
   and with no JS at all, everything simply shows. */
body.is-overview [data-level="deep"],
body.is-deep     [data-level="overview"] { display: none; }

/* The two readings share one slot in the flow, so they must start at the same
   height. `display: none` removes an element from layout but not from sibling
   matching, so `.g-prose > * + *` still saw the Overview block sitting in front
   of the In-depth block and gave the second one a --g-space-sm top margin the
   first never had. Switching depth then moved the essay down by that much,
   about half a line, with nothing above it to explain the shift.
   Zero the margin on the alternate, then put it back on the first line below
   for the case where the pair does not open the prose - there the block it
   replaces would have carried the gap, so the alternate has to carry it too. */
.g-prose > [data-level="overview"] + [data-level="deep"],
.g-prose > [data-level="deep"] + [data-level="overview"] { margin-top: 0; }
.g-prose > * + [data-level="overview"] + [data-level="deep"],
.g-prose > * + [data-level="deep"] + [data-level="overview"] { margin-top: var(--g-space-sm); }

.depth-note {
  font-family: var(--g-font-mono); font-size: var(--g-fs-caption);
  color: var(--g-text-subtle); border-left: 2px solid var(--g-border);
  padding-left: var(--g-space-sm); margin-bottom: var(--g-space-lg);
}

/* ---------- page head ---------------------------------------------------- */
.page-head { padding-block: clamp(2.5rem, 5vw, 4.5rem) clamp(1.5rem, 3vw, 2.5rem); position: relative; }
/* A section page opens with its own mark, above the crumb rather than beside
   the heading: alongside an h1 that runs to 3.4rem the plate either has to
   grow into a badge or sit visibly undersized, and both fight the headline.
   Stacked, it reads as the section's stamp and the type keeps its full width.
   Detail pages get the same mark at 1rem inside the crumb instead, which says
   where you are without repeating the section's opening gesture. */
/* Block-level, not the plate's inline-flex: as an inline box it would sit on a
   line box and carry the line's leading under it, so the gap to the crumb would
   be the margin plus whatever the descender space happened to be. */
.page-head__ic { display: flex; margin-bottom: var(--g-space-sm); }
/* Sized and aligned against the crumb's own type rather than left at --sm, and
   in em rather than rem so the mark tracks the crumb if that type ever moves.
   The glyph only inks the middle ~80% of its 24-unit box, so a box matched to
   the type looked undersized once drawn; 1.25em puts the visible mark a shade
   above the cap height, which is where a line of uppercase mono wants it.
   The crumb is uppercase, so the mark centres on the cap height, not the
   x-height: DM Mono caps run to 0.7em, so the box centre belongs at 0.35em
   above the baseline, and vertical-align sets the box's bottom edge, hence
   0.35em - 1.25em/2. Aligning to the x-height instead is what dropped it. */
.crumb__ic {
  display: inline-block; width: 1.25em; height: 1.25em;
  vertical-align: -0.275em; margin-right: 0.35em; color: var(--g-accent);
}
.page-head h1 {
  font-size: clamp(2rem, 1.3rem + 2.6vw, 3.4rem); font-weight: var(--g-w-bold);
  letter-spacing: -.03em; line-height: 1.05; margin: 0 0 var(--g-space-sm);
}
.page-head .g-lead { max-width: 60ch; }
.crumb {
  font-family: var(--g-font-mono); font-size: var(--g-fs-caption);
  letter-spacing: .1em; text-transform: uppercase; color: var(--g-text-subtle);
  margin-bottom: var(--g-space-sm);
}
.crumb a { color: var(--g-text-muted); text-decoration: none; }
.crumb a:hover { color: var(--g-text); }

/* ---------- the six spaces (home) ----------------------------------------
   Six equal cards, three to a row. They used to be a 6-column grid with the
   first two spanning half a row each and four narrow ones beneath, which meant
   the section called "Six Ways In" showed two important ways and four
   afterthoughts, and it needed four media queries and two `!important`s to
   hold that shape together. Now that each card carries its own mark, the set
   reads as six of one kind, and the grid is three columns that halve twice.
   No spans, no overrides.

   The old rules are gone rather than commented out: `.space--wide` no longer
   exists, and the two queries at 900px and 560px were already dead, beaten by
   the `!important` in the 861px one. */
.spaces { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--g-space-sm); }
@media (max-width: 860px) { .spaces { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .spaces { grid-template-columns: minmax(0, 1fr); } }
.space {
  position: relative; overflow: hidden;
  border: 1px solid var(--g-hairline); border-radius: var(--g-radius);
  background: linear-gradient(180deg, var(--g-surface-raised), var(--g-surface));
  padding: var(--g-space-md); text-decoration: none; display: flex; flex-direction: column;
  min-height: 200px;
  transition: transform var(--g-dur) var(--g-ease-orbit),
              border-color var(--g-dur) var(--g-ease-orbit),
              background-color var(--g-dur) var(--g-ease-orbit);
}
.space:hover { transform: translateY(-3px); border-color: var(--g-border); }
/* Mark left, number right, on one line: the number keeps its old job of
   ordering the set, but stops being the card's opening image. Baseline
   alignment would hang the digits off the plate's cap height, so the row is
   centred on the plate instead and the number is nudged by its own optical
   centre, not the box's. */
.space__top {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: var(--g-space-sm);
}
.space__n {
  font-family: var(--g-font-mono); font-size: var(--g-fs-eyebrow); letter-spacing: .16em;
  color: var(--g-text-subtle);
}
.space:hover .space__ic {
  border-color: var(--g-border); background: rgb(var(--g-tint) / 0.1);
}
.space h3 { font-size: var(--g-fs-h4); margin: 0 0 var(--g-space-3xs); }
.space p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0; }
.space__go {
  margin-top: auto; padding-top: var(--g-space-sm);
  font-family: var(--g-font-mono); font-size: var(--g-fs-caption);
  color: var(--g-accent); display: inline-flex; align-items: center; gap: .35rem;
}
.space:hover .space__go { color: var(--g-text); }

/* ---------- filter bar --------------------------------------------------- */
.filters { display: flex; flex-wrap: wrap; gap: var(--g-space-2xs); align-items: center; margin-bottom: var(--g-space-lg); }
.filters__label {
  font-family: var(--g-font-mono); font-size: var(--g-fs-caption); letter-spacing: .1em;
  text-transform: uppercase; color: var(--g-text-subtle); margin-right: var(--g-space-2xs);
}
.chip {
  font: inherit; font-size: var(--g-fs-small); font-weight: var(--g-w-medium);
  color: var(--g-text-muted); background: transparent;
  border: 1px solid var(--g-border); border-radius: var(--g-radius-pill);
  padding: .34rem .8rem; cursor: pointer;
  transition: color var(--g-dur) var(--g-ease-orbit), border-color var(--g-dur) var(--g-ease-orbit),
              background-color var(--g-dur) var(--g-ease-orbit);
}
.chip:hover { color: var(--g-text); border-color: var(--g-border-strong); }
.chip[aria-pressed="true"] { background: var(--g-solid-bg); color: var(--g-solid-fg); border-color: var(--g-solid-bg); }
.search {
  flex: 1 1 12rem; min-width: 9rem; background: var(--g-surface-sunken);
  border: 1px solid var(--g-border); border-radius: var(--g-radius-pill);
  color: var(--g-text); font: inherit; font-size: var(--g-fs-small); padding: .42rem .9rem;
}
.search::placeholder { color: var(--g-text-subtle); }
.search:focus-visible { outline: none; border-color: var(--g-accent); box-shadow: 0 0 0 3px rgb(var(--g-tint) / .2); }
.result-count { font-family: var(--g-font-mono); font-size: var(--g-fs-caption); color: var(--g-text-subtle); }
.is-hidden { display: none !important; }
.empty {
  border: 1px dashed var(--g-border); border-radius: var(--g-radius);
  padding: var(--g-space-xl); text-align: center; color: var(--g-text-muted);
}

/* ---------- entry cards -------------------------------------------------- */
.entry {
  display: grid; grid-template-columns: 8.5rem minmax(0, 1fr); gap: var(--g-space-md);
  padding-block: var(--g-space-md); border-bottom: 1px solid var(--g-hairline);
  text-decoration: none; align-items: start;
  transition: background-color var(--g-dur) var(--g-ease-orbit);
}
.entry:hover { background: rgb(var(--g-tint) / .04); }
.entry__type {
  font-family: var(--g-font-mono); font-size: var(--g-fs-caption); letter-spacing: .1em;
  text-transform: uppercase; color: var(--g-accent); padding-top: .2rem;
}
.entry h3 { font-size: var(--g-fs-h4); margin: 0 0 var(--g-space-3xs); line-height: 1.3; }
.entry:hover h3 { color: var(--g-accent); }
.entry p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0 0 var(--g-space-2xs); }
.entry__meta { font-family: var(--g-font-mono); font-size: var(--g-fs-caption); color: var(--g-text-subtle); display: flex; align-items: center; gap: var(--g-space-2xs); flex-wrap: wrap; }
/* The separator belongs between the spans, not inside the next one. Unscoped,
   the ::before landed inside the reading-level pill and drew the dot within
   its border, which is not a separator, it is a bullet in a tag. */
.entry__meta span + span:not(.entry__lvl)::before { content: "·"; margin-right: var(--g-space-2xs); }
.entry__meta .entry__lvl { margin-left: var(--g-space-3xs); }
@media (max-width: 620px) { .entry { grid-template-columns: minmax(0, 1fr); gap: var(--g-space-2xs); } }

/* ---------- topic ------------------------------------------------------ */
.topic-nav {
  position: sticky; top: 4.5rem; z-index: 40; background: rgb(var(--g-chrome) / var(--g-chrome-a));
  backdrop-filter: blur(14px); border-block: 1px solid var(--g-hairline);
}
.topic-nav__row { display: flex; gap: clamp(.9rem, 2.4vw, 2rem); overflow-x: auto; scrollbar-width: none; }
.topic-nav__row::-webkit-scrollbar { display: none; }
.topic-nav a {
  flex: none; padding: var(--g-space-sm) 0; font-size: var(--g-fs-small);
  font-weight: var(--g-w-medium); color: var(--g-text); opacity: .68; text-decoration: none;
  border-bottom: 2px solid transparent; white-space: nowrap;
  transition: opacity var(--g-dur) var(--g-ease-orbit), border-color var(--g-dur) var(--g-ease-orbit);
}
.topic-nav a:hover, .topic-nav a.is-active { opacity: 1; border-bottom-color: var(--g-accent); }

.layer { padding-block: clamp(2.5rem, 5vw, 4rem); border-top: 1px solid var(--g-hairline); }
.layer:first-of-type { border-top: 0; }
.layer__head { display: flex; align-items: baseline; gap: var(--g-space-sm); margin-bottom: var(--g-space-md); flex-wrap: wrap; }
.layer__n {
  font-family: var(--g-font-mono); font-size: var(--g-fs-caption); color: var(--g-accent);
  letter-spacing: .1em;
}
.layer__head h2 { font-size: clamp(1.4rem, 1.1rem + 1.2vw, 2rem); margin: 0; letter-spacing: -.02em; }

/* video frame */
.video {
  position: relative; aspect-ratio: 16/9; border-radius: var(--g-radius);
  overflow: hidden; background: var(--g-grad-deep-space); border: 1px solid var(--g-hairline);
  display: grid; place-items: center; text-decoration: none;
}
.video__play {
  width: 4.5rem; height: 4.5rem; border-radius: 50%; display: grid; place-items: center;
  border: 1.5px solid rgb(241 239 236 / .7); background: rgb(3 3 3 / .35);
  backdrop-filter: blur(4px); color: var(--g-white-tint);
  transition: transform var(--g-dur) var(--g-ease-orbit), background-color var(--g-dur) var(--g-ease-orbit);
}
.video:hover .video__play { transform: scale(1.08); background: rgb(0 48 73 / .6); }
/* The poster frame, when assets/thumbnails/ holds one for the page this card
   points at. See the thumbnail module in site.js for how it is found. The
   image is absolute rather than a grid item so it fills the frame without
   displacing the play button, which stays centred by the grid. */
.video__img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0;
  transition: opacity var(--g-dur-slow) var(--g-ease-orbit),
              transform var(--g-dur-slow) var(--g-ease-orbit);
}
.video.has-thumb .video__img { opacity: 1; }
/* Light and even. The ring carries its own dark disc and blur, so this is not
   holding up the contrast on its own — it only keeps a pale patch of a
   thumbnail from swallowing the border, and stays faint enough that the
   artwork is still the thing you see. */
.video.has-thumb::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: rgb(3 3 3 / .14);
}
.video__play { position: relative; z-index: 1; }
.video:hover .video__img { transform: scale(1.03); }
.video.is-flat:hover .video__img { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .video__img { transition: opacity var(--g-dur-fast) linear; }
  .video:hover .video__img { transform: none; }
}

/* sources at three levels */
.levels { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--g-space-md); }
@media (max-width: 860px) { .levels { grid-template-columns: minmax(0, 1fr); } }
.level { border: 1px solid var(--g-hairline); border-radius: var(--g-radius); padding: var(--g-space-md); }
.level__tag {
  font-family: var(--g-font-mono); font-size: var(--g-fs-caption); letter-spacing: .1em;
  text-transform: uppercase; color: var(--g-accent); margin: 0 0 var(--g-space-2xs);
}
.level h4 { font-size: var(--g-fs-body); margin: 0 0 var(--g-space-2xs); }
.level ul { margin: 0; padding-left: 1.05rem; }
.level li { font-size: var(--g-fs-small); color: var(--g-text-muted); margin-bottom: var(--g-space-2xs); }
.level li::marker { color: var(--g-accent); }
.level a { color: var(--g-text-muted); text-decoration-color: var(--g-border); }
.level a:hover { color: var(--g-text); }

/* plates: the photographic run above the timeline ------------------------
   A timeline of six sentences is a list of claims you are asked to take on
   trust. Four of them have a picture — a man, a room, a trajectory, a
   machine made of rules — and the pictures are what make the century feel
   like it happened. They sit above the dates rather than inside them
   because only four of the six entries have one, and threading images into
   half a list leaves the other half looking unfinished.

   Fixed tile height, cover crop: five sources of different proportions
   (a passport photo, a press photo, a square render, a 250px GIF) have to
   agree on a line, and letterboxing each into its own ratio would break it. */
.plates {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--g-space-md); margin-bottom: var(--g-space-lg);
}
@media (max-width: 860px) { .plates { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 460px) { .plates { grid-template-columns: minmax(0, 1fr); } }
.plate { margin: 0; min-width: 0; }
.plate img {
  display: block; width: 100%; height: 11rem; object-fit: cover;
  border-radius: var(--g-radius); border: 1px solid var(--g-hairline);
  background: var(--g-surface-sunken);
}
.plate--face img { object-position: center 20%; }
.plate--wide img { object-position: 62% 50%; }
/* Cellular automata upscale badly through a smoothing filter: keep the
   cells square, and give the GIF the white ground it was drawn on. */
.plate--pixel img { image-rendering: pixelated; background: #fff; object-position: 28% 22%; }
.plate figcaption { margin-top: var(--g-space-2xs); font-size: var(--g-fs-small); color: var(--g-text-muted); }
.plate__yr { display: block; font-family: var(--g-font-mono); font-size: var(--g-fs-caption); color: var(--g-accent); }
.plate__what { display: block; color: var(--g-text); font-weight: var(--g-w-semibold); }
.plates__credit { font-size: var(--g-fs-caption); color: var(--g-text-subtle); margin: 0 0 var(--g-space-lg); }
.plates__credit a { color: inherit; }

/* timeline */
.tl { position: relative; padding-left: 1.6rem; }
.tl::before { content: ""; position: absolute; left: .32rem; top: .4rem; bottom: .4rem; width: 1px; background: var(--g-border); }
.tl__item { position: relative; padding-bottom: var(--g-space-md); }
.tl__item::before {
  content: ""; position: absolute; left: -1.53rem; top: .45rem; width: .52rem; height: .52rem;
  border-radius: 50%; background: var(--g-accent); box-shadow: 0 0 0 3px var(--g-bg);
}
.tl__yr { font-family: var(--g-font-mono); font-size: var(--g-fs-caption); color: var(--g-accent); }
.tl__item h4 { font-size: var(--g-fs-body); margin: .1rem 0 .2rem; }
.tl__item p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0; }

/* viewpoints */
.views { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--g-space-md); }
@media (max-width: 760px) { .views { grid-template-columns: minmax(0, 1fr); } }
.view { border: 1px solid var(--g-hairline); border-radius: var(--g-radius); padding: var(--g-space-md); }
.view--for { border-left: 2px solid var(--g-signal-positive); }
.view--against { border-left: 2px solid var(--g-signal-caution); }
.view__tag {
  font-family: var(--g-font-mono); font-size: var(--g-fs-caption); letter-spacing: .1em;
  text-transform: uppercase; margin: 0 0 var(--g-space-2xs);
}
.view--for .view__tag { color: var(--g-signal-positive); }
.view--against .view__tag { color: var(--g-signal-caution); }
.view p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0 0 var(--g-space-2xs); }
.view cite { font-family: var(--g-font-mono); font-size: var(--g-fs-caption); font-style: normal; color: var(--g-text-subtle); }

/* comments */
.comment { display: grid; grid-template-columns: 2.4rem minmax(0,1fr); gap: var(--g-space-sm); padding-block: var(--g-space-sm); border-bottom: 1px solid var(--g-hairline); }
.avatar {
  width: 2.4rem; height: 2.4rem; border-radius: 50%; display: grid; place-items: center;
  background: var(--g-surface-hover); color: var(--g-text); font-size: var(--g-fs-caption);
  font-weight: var(--g-w-semibold); border: 1px solid var(--g-hairline);
}
.comment__who { font-size: var(--g-fs-small); font-weight: var(--g-w-semibold); }
.comment__role {
  font-family: var(--g-font-mono); font-size: var(--g-fs-caption); color: var(--g-accent);
  border: 1px solid var(--g-border); border-radius: var(--g-radius-pill); padding: 0 .4rem; margin-left: .4rem;
}
.comment p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: .25rem 0 0; }

/* ---------- lab / games -------------------------------------------------- */
.games { display: grid; grid-template-columns: repeat(auto-fit, minmax(255px, 1fr)); gap: var(--g-space-md); }
.game-card {
  border: 1px solid var(--g-hairline); border-radius: var(--g-radius); overflow: hidden;
  background: var(--g-surface-raised); text-decoration: none; display: flex; flex-direction: column;
  transition: transform var(--g-dur) var(--g-ease-orbit), border-color var(--g-dur) var(--g-ease-orbit);
}
.game-card:hover { transform: translateY(-3px); border-color: var(--g-border); }
.game-card__art { aspect-ratio: 16/10; position: relative; overflow: hidden; background: var(--g-grad-deep-space); }
.game-card__art canvas { width: 100%; height: 100%; display: block; }
.game-card__b { padding: var(--g-space-md); display: flex; flex-direction: column; flex: 1; }
.game-card h3 { font-size: var(--g-fs-h4); margin: 0 0 var(--g-space-3xs); }
.game-card p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0 0 var(--g-space-sm); }
/* Typed as `.game-card p` elsewhere, which outranks a single class, so the
   `margin-top: auto` never applied and the meta line stopped short of the
   card's foot with a 1rem margin under it. That left a band of dead space at
   the bottom of every card, which is where the PLANNED chip had been parked. */
.game-card p.game-card__meta {
  margin-top: auto; margin-bottom: 0;
  font-family: var(--g-font-mono); font-size: var(--g-fs-caption); color: var(--g-accent);
}
.badge-soon {
  position: absolute; top: var(--g-space-2xs); right: var(--g-space-2xs);
  font-family: var(--g-font-mono); font-size: var(--g-fs-caption);
  background: rgb(3 3 3 / .7); color: var(--g-text-muted); border-radius: var(--g-radius-sm);
}

/* game shell */
.play {
  border: 1px solid var(--g-hairline); border-radius: var(--g-radius-lg);
  background: var(--g-surface); overflow: hidden;
}
.play__bar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--g-space-sm);
  padding: var(--g-space-sm) var(--g-space-md); border-bottom: 1px solid var(--g-hairline);
  font-family: var(--g-font-mono); font-size: var(--g-fs-caption); color: var(--g-text-muted);
  flex-wrap: wrap;
}
.play__body { padding: var(--g-space-md) var(--g-space-md) var(--g-space-lg); }
/* a canvas panel centres its figure instead of pinning it to the top edge */
.play__body--fig { padding: 0; display: grid; place-items: center; min-height: 380px; }
.play__body--fig canvas { display: block; }
.triple { display: flex; gap: var(--g-space-2xs); flex-wrap: wrap; align-items: center; }
/* The default number spinners are tiny grey browser chrome and they were the
   ugliest thing on the page. Suppressed, and the field made a proper display
   for a value you are about to test. */
.triple input {
  width: 5.6rem; background: var(--g-surface-sunken); border: 1px solid var(--g-border);
  border-radius: var(--g-radius-sm); color: var(--g-text); font-family: var(--g-font-mono);
  font-size: 1.25rem; font-weight: var(--g-w-medium); letter-spacing: .02em;
  padding: .7rem .6rem; text-align: center;
  transition: border-color var(--g-dur) var(--g-ease-orbit),
              box-shadow var(--g-dur) var(--g-ease-orbit),
              background-color var(--g-dur) var(--g-ease-orbit);
  -moz-appearance: textfield; appearance: textfield;
}
.triple input::-webkit-outer-spin-button,
.triple input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.triple input:hover { border-color: var(--g-border-strong); background: var(--g-surface-hover); }
.triple { gap: .45rem; }
.triple__sep { font-family: var(--g-font-mono); color: var(--g-text-subtle);
  font-size: 1.1rem; padding: 0 .1rem; }
.triple input:focus-visible { outline: none; border-color: var(--g-accent); box-shadow: 0 0 0 3px rgb(var(--g-tint) / .2); }
.trials { margin-top: var(--g-space-md); border-top: 1px solid var(--g-hairline); }
.trial {
  display: flex; align-items: center; gap: var(--g-space-sm); padding: .5rem 0;
  border-bottom: 1px solid var(--g-hairline); font-family: var(--g-font-mono); font-size: var(--g-fs-small);
}
.verdict { font-weight: var(--g-w-bold); }
.verdict--yes { color: var(--g-signal-positive); }
.verdict--no  { color: var(--g-signal-critical); }
.trial__note { margin-left: auto; color: var(--g-text-subtle); font-size: var(--g-fs-caption); }

/* ---------- learning paths ---------------------------------------------- */
.path-card {
  border: 1px solid var(--g-hairline); border-radius: var(--g-radius); padding: var(--g-space-md);
  text-decoration: none; display: flex; flex-direction: column; background: var(--g-surface-raised);
  transition: transform var(--g-dur) var(--g-ease-orbit), border-color var(--g-dur) var(--g-ease-orbit);
}
.path-card:hover { transform: translateY(-3px); border-color: var(--g-border); }
.path-card h3 { font-size: var(--g-fs-h4); margin: var(--g-space-2xs) 0 var(--g-space-3xs); }
.path-card p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0 0 var(--g-space-sm); }
.progress { height: 4px; border-radius: 99px; background: rgb(var(--g-tint) / .18); overflow: hidden; margin-top: auto; }
.progress i { display: block; height: 100%; background: var(--g-accent); border-radius: 99px; }

.step { display: grid; grid-template-columns: 2.6rem minmax(0,1fr); gap: var(--g-space-md); padding-block: var(--g-space-md); border-bottom: 1px solid var(--g-hairline); }
.step__n {
  width: 2.2rem; height: 2.2rem; border-radius: 50%; border: 1px solid var(--g-border);
  display: grid; place-items: center; font-family: var(--g-font-mono); font-size: var(--g-fs-caption);
  color: var(--g-accent);
}
.step h4 { font-size: var(--g-fs-body); margin: .15rem 0 var(--g-space-3xs); }
.step p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0 0 var(--g-space-2xs); }
.step__links { display: flex; gap: var(--g-space-2xs); flex-wrap: wrap; }

/* ---------- community ---------------------------------------------------- */
/* six roles, three to a row */
.roles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--g-space-sm); }
@media (max-width: 860px) { .roles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .roles { grid-template-columns: minmax(0, 1fr); } }
.role { border: 1px solid var(--g-hairline); border-radius: var(--g-radius); padding: var(--g-space-md); }
.role h4 { font-size: var(--g-fs-body); margin: 0 0 var(--g-space-3xs); }
.role p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0; }
.role__icon { width: 1.4rem; height: 1.4rem; color: var(--g-accent); margin-bottom: var(--g-space-2xs); }

.event { display: grid; grid-template-columns: 5rem minmax(0,1fr) auto; gap: var(--g-space-md); align-items: center;
  padding-block: var(--g-space-sm); border-bottom: 1px solid var(--g-hairline); }
.event__date { font-family: var(--g-font-mono); font-size: var(--g-fs-caption); color: var(--g-accent); text-align: center; }
.event__date b { display: block; font-size: var(--g-fs-h4); color: var(--g-text); font-weight: var(--g-w-bold); }
.event h4 { font-size: var(--g-fs-body); margin: 0 0 var(--g-space-3xs); }
.event p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0; }
@media (max-width: 620px) { .event { grid-template-columns: 4rem minmax(0,1fr); } .event > .g-btn { grid-column: 2; justify-self: start; } }

/* poll */
.poll__opt { display: block; width: 100%; text-align: left; background: transparent; border: 1px solid var(--g-border);
  border-radius: var(--g-radius-sm); padding: .7rem .9rem; margin-bottom: var(--g-space-2xs);
  color: var(--g-text); font: inherit; font-size: var(--g-fs-small); cursor: pointer; position: relative; overflow: hidden;
  transition: border-color var(--g-dur) var(--g-ease-orbit); }
.poll__opt:hover { border-color: var(--g-border-strong); }
.poll__opt[aria-pressed="true"] { border-color: var(--g-accent); }
.poll__bar { position: absolute; inset: 0 auto 0 0; background: rgb(var(--g-tint) / .14); width: 0; transition: width .6s var(--g-ease-orbit); }
.poll__txt { position: relative; display: flex; justify-content: space-between; gap: var(--g-space-sm); }
.poll__pct { font-family: var(--g-font-mono); color: var(--g-accent); opacity: 0; transition: opacity .3s; }
.poll.is-voted .poll__pct { opacity: 1; }

/* ---------- programmes strip -------------------------------------------- */
/* five items: three then two, both rows full, rather than four then a widow */
.programs { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--g-space-sm); }
.program { grid-column: span 2; }
.program:nth-child(4), .program:nth-child(5) { grid-column: span 3; }
/* two columns: the fifth is the odd one, so let it take the full width */
@media (max-width: 900px) { .programs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .program { grid-column: span 1 !important; }
  .program:nth-child(5) { grid-column: span 2 !important; } }
@media (max-width: 560px) { .programs { grid-template-columns: minmax(0, 1fr); }
  .program, .program:nth-child(5) { grid-column: span 1 !important; } }
.program { border: 1px solid var(--g-hairline); border-radius: var(--g-radius); padding: var(--g-space-md); background: var(--g-surface-raised); }
.program__n { font-family: var(--g-font-mono); font-size: var(--g-fs-caption); color: var(--g-accent); letter-spacing: .1em; }
.program h3 { font-size: var(--g-fs-body); margin: var(--g-space-3xs) 0; }
.program p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0 0 var(--g-space-2xs); }
.program__cad { font-family: var(--g-font-mono); font-size: var(--g-fs-caption); color: var(--g-text-subtle); }

/* ---------- misc --------------------------------------------------------- */
.kv { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .35rem var(--g-space-md); font-size: var(--g-fs-small); }
.kv dt { font-family: var(--g-font-mono); font-size: var(--g-fs-caption); color: var(--g-text-subtle); letter-spacing: .06em; text-transform: uppercase; }
.kv dd { margin: 0; color: var(--g-text-muted); }
.pill-row { display: flex; gap: var(--g-space-2xs); flex-wrap: wrap; }
.split { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); gap: clamp(1.5rem, 4vw, 3.5rem); align-items: start; }
@media (max-width: 900px) { .split { grid-template-columns: minmax(0, 1fr); } }
/* A grid item's default `min-width: auto` means "never narrower than my own
   min-content". A <pre> does not wrap, so its min-content is the width of its
   longest line: on a phone the essay column refused to go below ~560px, the
   grid pushed past the viewport, and the whole page - header included - could
   be scrolled sideways. The In-depth essay is the only place with code, which
   is why it only happened there. The column may now shrink; the code box keeps
   its own scrollbar. */
.split > * { min-width: 0; }
.aside { position: sticky; top: 6rem; }
@media (max-width: 900px) { .aside { position: static; } }
.callout {
  border: 1px solid var(--g-hairline); border-left: 2px solid var(--g-accent);
  border-radius: 0 var(--g-radius) var(--g-radius) 0; padding: var(--g-space-md);
  background: var(--g-surface-sunken);
}
.callout h4 { font-size: var(--g-fs-body); margin: 0 0 var(--g-space-3xs); }
.callout p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0; }

.rv { opacity: 0; transform: translateY(14px); transition: opacity .6s var(--g-ease-orbit), transform .6s var(--g-ease-orbit); }
.rv.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .rv { opacity: 1; transform: none; } }

/* ---- Footer social icons, uniform circular buttons, one visual weight ---- */
/* The Elsewhere column needs a little more room than the others so all five
   sit on a single row rather than wrapping 4+1. */
.g-footer__grid { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.35fr); }
/* These must sit beside the rule above: it is unlayered, so it outranks the
   design system's own breakpoints and would otherwise hold four columns all
   the way down to 360px. */
@media (max-width: 800px) { .g-footer__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .g-footer__grid { grid-template-columns: minmax(0, 1fr); gap: var(--g-space-md); } }
.lp-social { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.375rem; }
.lp-social__link {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.25rem; height: 2.25rem; border-radius: var(--g-radius-pill);
  border: 1px solid var(--g-hairline); color: var(--g-text-muted);
  text-decoration: none;
  transition: color var(--g-dur) var(--g-ease-orbit),
              border-color var(--g-dur) var(--g-ease-orbit),
              background-color var(--g-dur) var(--g-ease-orbit),
              transform var(--g-dur) var(--g-ease-orbit);
}
.lp-social__link:hover {
  color: var(--g-text); border-color: var(--g-border-strong);
  background: rgb(var(--g-tint) / 0.08); transform: translateY(-2px);
}
/* 1.25rem in a 2.25rem button leaves exactly 8px of air on each side at
   100% zoom (36 - 20, halved) instead of 8.4px. The browser has to round
   the leftover space to the device grid either way, but a whole number is
   the one case where it rounds to nothing at all. */
.lp-social__link svg { width: 1.25rem; height: 1.25rem; display: block; }


/* ---- reading level on magazine entries --------------------------------
   In Overview the technical pieces stay in the list but step back, and say
   why. Hard-hiding them makes the page look broken when the count drops. */
.entry__lvl {
  font-family: var(--g-font-mono); font-size: .62rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--g-text-subtle);
  border: 1px solid var(--g-hairline);
}
body.is-overview .entry[data-reading="deep"] { opacity: .55; }
body.is-overview .entry[data-reading="deep"]:hover { opacity: 1; }
body.is-deep .entry[data-reading="overview"] { opacity: .62; }
body.is-deep .entry[data-reading="overview"]:hover { opacity: 1; }
body.is-deep .entry__lvl { color: var(--g-accent); border-color: var(--g-border); }
.entry[data-reading] { transition: opacity var(--g-dur) var(--g-ease-orbit); }

/* ---- article ---------------------------------------------------------- */
.crumb { font-family: var(--g-font-mono); font-size: var(--g-fs-caption);
  letter-spacing: .1em; text-transform: uppercase; color: var(--g-text-subtle);
  margin: 0 0 var(--g-space-md); }
.crumb a { color: inherit; }
.art__title { font-size: clamp(1.9rem,1.2rem+2.4vw,3rem); line-height:1.08;
  letter-spacing:-.025em; margin:0 0 var(--g-space-sm); text-wrap:balance; }
.art__standfirst { font-size: var(--g-fs-lead); color: var(--g-text-muted);
  line-height:1.5; margin:0 0 var(--g-space-md); }
.art__meta { display:flex; flex-wrap:wrap; gap:1rem; font-family:var(--g-font-mono);
  font-size:var(--g-fs-caption); color:var(--g-text-subtle);
  border-top:1px solid var(--g-hairline); padding-top:var(--g-space-sm); }
.art { font-size:var(--g-fs-lead); line-height:1.68; padding-block: var(--g-space-lg); }
.art > * + * { margin-top: var(--g-space-md); }
.art h2 { font-size: clamp(1.25rem,1.05rem+.8vw,1.6rem); margin-top: var(--g-space-xl); }
.art__lede { font-size: 1.18em; color: var(--g-text); }
/* ---- two-line drop cap --------------------------------------------------
   The letter has to sit on two lines exactly: its cap top level with the cap
   top of line one, its baseline sitting on the baseline of line two. The old
   values did neither, sizing the float against the *line box* rather than the
   cap height left the A floating high, meeting neither line.

   Worked from DM Sans's real metrics (1000 upem, cap height 700, ascender 992,
   descender -310) against the lede's line-height of 1.68:

     line 1 cap top  = 0.481em      baseline 2 = 2.861em   (from the line-1 top)
     cap height wanted = 2.861 - 0.481   = 2.38em
     font-size = 2.38 / 0.7 (cap ratio)  = 3.4em
     margin-top so the baselines meet    = 0.15em of that

   Browsers that support initial-letter do this arithmetic themselves, from
   whichever font actually loaded, so let them, the float is the fallback. */
.art__lede::first-letter {
  float: left;
  font-size: 3.4em;
  line-height: .7;
  margin: .15em .1em 0 0;
  font-weight: var(--g-w-bold);
  /* Not the accent. On dark, Sisal against White Tint body copy is a hairline
     of difference and reads as emphasis; on light the same token is bronze,
     and two lines tall it stops being an initial and becomes a brown blob at
     the top of the article. Weight and scale already do all the work an
     initial needs. */
  color: var(--g-text);
}
@supports (initial-letter: 2) or (-webkit-initial-letter: 2) {
  .art__lede::first-letter {
    -webkit-initial-letter: 2;
    initial-letter: 2;
    float: none;
    font-size: inherit;
    line-height: inherit;
    margin: 0 .1em 0 0;
  }
}
.art__aside { border-left:2px solid var(--g-border-strong); background:var(--g-surface-sunken);
  border-radius:0 var(--g-radius) var(--g-radius) 0; padding:var(--g-space-md);
  font-size:.94rem; color:var(--g-text-muted); }
.art__aside-h { font-family:var(--g-font-mono); font-size:.68rem; letter-spacing:.14em;
  text-transform:uppercase; color:var(--g-accent); margin:0 0 .5rem; }
.art__sources { border-top:1px solid var(--g-hairline); padding-top:var(--g-space-lg);
  margin-top: var(--g-space-lg); }
.src { list-style:none; margin:0 0 var(--g-space-sm); padding:0; }
.src li { display:flex; gap:.8rem; align-items:baseline; padding:.45rem 0;
  border-bottom:1px solid var(--g-hairline); }
.src__lvl { flex:none; font-family:var(--g-font-mono); font-size:.6rem; letter-spacing:.12em;
  text-transform:uppercase; color:var(--g-text-subtle); border:1px solid var(--g-hairline); }
.art__next { padding-block: var(--g-space-xl) var(--g-space-2xl); }
.art__next h3 { font-size:1.1rem; margin-bottom:var(--g-space-md); }
.nextcard { display:flex; flex-direction:column; gap:.4rem; text-decoration:none;
  border:1px solid var(--g-hairline); border-radius:var(--g-radius);
  padding:var(--g-space-md); background:var(--g-surface-raised);
  transition:.3s var(--g-ease-orbit); }
.nextcard:hover { transform:translateY(-3px); border-color:var(--g-border-strong);
  background:var(--g-surface-hover); }
.nextcard__k { font-family:var(--g-font-mono); font-size:.62rem; letter-spacing:.14em;
  text-transform:uppercase; color:var(--g-accent); }
.nextcard strong { font-size:1.02rem; }
.nextcard span:last-child { font-size:.86rem; color:var(--g-text-muted); }


/* ---- planned, not yet built -------------------------------------------
   These keep their place so the shape of the plan is visible, but they are
   not links and must not behave like them. */
.is-soon { cursor: default; position: relative; }
.is-soon > *:not(.soon-chip) { opacity: .62; }
.is-soon:hover { transform: none !important; border-color: var(--g-hairline) !important;
                 background: inherit; box-shadow: none !important; }
.soon-chip {
  position: absolute; top: .7rem; right: .7rem; z-index: 2;
  font-family: var(--g-font-mono); font-size: .6rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--g-text-subtle);
  border: 1px solid var(--g-hairline);
  background: rgb(var(--g-chrome) / 0.85);
}

/* ---- where the chip sits on its card ------------------------------------
   A floating chip reads as debris unless it lines up with something. It was
   pinned .7rem from the card's corner, which is inside the card's own padding
   and so lines up with nothing: on a magazine entry it floated above the
   ANALYSIS eyebrow, on a path card above the step count, and on a game card it
   sat in the dead space under the meta line.

   Each card anchors it to a real line of text instead, centring the chip on
   that line: the card's padding, plus half the difference between the line box
   and the chip. The chip is ~21px and the entry eyebrow's line is 21.45px, so
   there the correction rounds away; the path card's eyebrow line is 16.8px,
   which is where the negative term comes from. */
.entry.is-soon .soon-chip { top: calc(var(--g-space-md) + .2rem); right: 0; }  /* the eyebrow's own padding-top */
.path-card.is-soon .soon-chip,
.game-card.is-soon .soon-chip {
  /* Right edge on the card's content column, not floating inside its padding. */
  top: auto; right: var(--g-space-md); bottom: calc(var(--g-space-md) + .06rem);
}

/* ---- and it must not land on the words ----------------------------------
   An absolutely positioned chip takes no space, so the line it sits on ran
   straight under it: the step count on a path card and the meta line on a
   game card were both being overprinted at narrow widths, and an entry's
   headline reached it as soon as the column got tight.

   The line it shares keeps a gutter the width of the chip. PLANNED is a fixed
   string at a fixed size, so its width is a known 4.25rem; the rest is the
   space between the two. */
.is-soon { --chip-w: 4.25rem; --chip-gutter: calc(var(--chip-w) + var(--g-space-sm)); }
.entry.is-soon h3 { padding-right: var(--chip-gutter); }
.path-card.is-soon > p:last-child,
.game-card.is-soon .game-card__meta { padding-right: var(--chip-gutter); }

/* The trailing margin under the last line left a band of dead space at the
   foot of the card, the same one the game card had. */
.path-card.is-soon > p:last-child { margin-bottom: 0; }


/* ---- tag pills ----------------------------------------------------------
   PLANNED, TECHNICAL, the source levels: same object, so one rule owns their
   geometry and each component only sets colour and size.

   They were centred by luck rather than by construction. The text sat in a
   1.65 line box inherited from the body, so the box was drawn around the
   font's full ascent and descent while the label is uppercase and uses
   neither: the caps ended up riding high with the descender space showing
   underneath, worst on the reading-level pill, which was also being stretched
   by its flex row. Set the line box to the type size and let the padding do
   the centring, which is a figure that can be reasoned about. The .02em
   asymmetry is the caps' own overshoot above the baseline. */
.soon-chip, .entry__lvl, .src__lvl, .badge-soon {
  display: inline-flex; align-items: center; justify-content: center;
  align-self: center;
  line-height: 1;
  padding: .5em .75em .48em;
}
.soon-chip, .entry__lvl, .src__lvl { border-radius: 99px; }
/* Tracking adds a space after the last letter that the box counts but the eye
   does not, so the label reads left of centre. Half of it back. */
.soon-chip { text-indent: .07em; }
.entry__lvl, .src__lvl { text-indent: .06em; }

/* a reference or control whose destination does not exist yet: still readable,
   no longer pretending to be a link */
.is-flat { cursor: default; text-decoration: none; color: var(--g-text-muted); }
.is-flat:hover { color: var(--g-text-muted); }

/* verdict line under a simulation, states the reading, so the control has a
   consequence in words as well as pixels */
.play__note { margin: 0; padding: .7rem var(--g-space-md);
  border-top: 1px solid var(--g-hairline); font-size: var(--g-fs-small); }
.play__note.is-good { color: var(--g-signal-positive); }
.play__note.is-meh  { color: var(--g-text-muted); }
.play__note.is-bad  { color: var(--g-signal-caution); }

/* ---- join ---------------------------------------------------------------
   The old version was a form dropped on a flat band. This one borrows the
   hero's treatment, the well and the starfield behind it, real vertical
   room, and the roles shown as things you become rather than a sentence
   describing them. */
.join { position: relative; overflow: hidden; isolation: isolate;
  background: var(--g-grad-deep-space);
  border-radius: var(--g-radius-lg);
  padding: clamp(2.6rem, 5vw, 5rem) clamp(1.25rem, 4vw, 3.5rem);
  text-align: center; }
.join__in { position: relative; z-index: 2; max-width: 46rem; margin-inline: auto; }
/* One line that dominates, one that answers it quietly. The old version had
   three competing blocks of text at similar weight and no clear entry point. */
.join__h { font-size: clamp(2.2rem, 1.4rem + 3.2vw, 4rem); line-height: 1.0;
  letter-spacing: -.035em; margin: 0 0 var(--g-space-2xs); text-wrap: balance;
  font-weight: var(--g-w-bold); }
.join__lede { color: var(--g-accent); font-size: clamp(1.1rem, 1rem + .7vw, 1.6rem);
  font-weight: var(--g-w-light); letter-spacing: -.02em;
  margin: 0 auto var(--g-space-xl); }
.join__roles { display: flex; flex-wrap: wrap; gap: .45rem; justify-content: center;
  margin-bottom: var(--g-space-lg); }
.join__role { font-family: var(--g-font-mono); font-size: var(--g-fs-caption);
  letter-spacing: .1em; text-transform: uppercase; color: var(--g-text-muted);
  border: 1px solid var(--g-border); border-radius: var(--g-radius-pill);
  padding: .34rem .85rem; transition: color var(--g-dur) var(--g-ease-orbit),
  border-color var(--g-dur) var(--g-ease-orbit), background-color var(--g-dur) var(--g-ease-orbit); }
.join__role:hover { color: var(--g-text); border-color: var(--g-border-strong);
  background: rgb(var(--g-tint) / .08); }
.join__act { display: flex; gap: .7rem; justify-content: center; flex-wrap: wrap; }
.join__fine { font-family: var(--g-font-mono); font-size: var(--g-fs-caption);
  color: var(--g-text-subtle); margin: var(--g-space-md) 0 0; }

/* ---- account ------------------------------------------------------------ */
.auth { position: relative; overflow: hidden; isolation: isolate;
  background: var(--g-grad-deep-space); padding-block: clamp(2.5rem,5vw,4.5rem); }
.auth__in { position: relative; z-index: 2; width: 100%; max-width: var(--g-max-width);
  margin-inline: auto; padding-inline: var(--g-gutter);
  display: grid; grid-template-columns: minmax(0,1.25fr) minmax(0,.85fr);
  gap: clamp(1.5rem,4vw,3.5rem); align-items: start; }
@media (max-width: 900px) { .auth__in { grid-template-columns: minmax(0, 1fr); } }
.auth__panel { background: var(--g-surface); border: 1px solid var(--g-hairline);
  border-radius: var(--g-radius-lg); padding: clamp(1.25rem,3vw,2.2rem); }
.auth__tabs { display: flex; gap: .3rem; background: var(--g-surface-sunken);
  border: 1px solid var(--g-hairline); border-radius: var(--g-radius-pill);
  padding: .25rem; margin-bottom: var(--g-space-lg); }
.auth__tabs button { flex: 1; font: inherit; font-size: var(--g-fs-small);
  font-weight: var(--g-w-medium); color: var(--g-text-muted); background: none;
  border: 0; border-radius: var(--g-radius-pill); padding: .5rem .8rem; cursor: pointer;
  transition: .22s var(--g-ease-orbit); }
.auth__tabs button[aria-selected="true"] { background: var(--g-solid-bg);
  color: var(--g-solid-fg); font-weight: var(--g-w-semibold); }
.auth__h { font-size: clamp(1.5rem,1.2rem+1.4vw,2.1rem); letter-spacing: -.025em;
  line-height: 1.1; margin: 0 0 var(--g-space-2xs); }
.auth__lede { color: var(--g-text-muted); font-size: var(--g-fs-small);
  margin: 0 0 var(--g-space-lg); max-width: 46ch; }
.auth__form > .g-field { margin-bottom: var(--g-space-lg); }
.auth__form > .g-btn { margin-top: var(--g-space-2xs); }
.auth__roles { border: 0; padding: 0; margin: 0 0 var(--g-space-md); }
.auth__rolegrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .5rem; }
@media (max-width: 720px) { .auth__rolegrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 460px) { .auth__rolegrid { grid-template-columns: minmax(0, 1fr); } }
.rolepick { position: relative; display: block; cursor: pointer; }
.rolepick input { position: absolute; opacity: 0; inset: 0; }
.rolepick span { display: block; border: 1px solid var(--g-hairline);
  border-radius: var(--g-radius); padding: .6rem .7rem; font-size: var(--g-fs-caption);
  color: var(--g-text-subtle); line-height: 1.45; transition: .22s var(--g-ease-orbit); }
.rolepick b { display: block; font-size: var(--g-fs-small); color: var(--g-text);
  font-weight: var(--g-w-semibold); margin-bottom: .1rem; }
.rolepick:hover span { border-color: var(--g-border); }
.rolepick input:checked + span { border-color: var(--g-accent);
  background: rgb(var(--g-tint) / .09); color: var(--g-text-muted); }
.rolepick input:focus-visible + span { outline: 2px solid var(--g-accent); outline-offset: 2px; }
.auth__check { display: flex; gap: .55rem; align-items: flex-start;
  font-size: var(--g-fs-small); color: var(--g-text-muted); margin-bottom: var(--g-space-md); }
.auth__row { display: flex; justify-content: space-between; align-items: center;
  gap: 1rem; flex-wrap: wrap; }
.auth__alt { font-size: var(--g-fs-small); color: var(--g-text-muted);
  margin: var(--g-space-md) 0 0; text-align: center; }
.linkish { background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  color: var(--g-text); text-decoration: underline;
  text-decoration-color: var(--g-border-strong); text-underline-offset: .2em; }
.linkish:hover { text-decoration-color: var(--g-text); }
.auth__note { font-family: var(--g-font-mono); font-size: var(--g-fs-caption);
  color: var(--g-text-subtle); margin: var(--g-space-lg) 0 0;
  border-top: 1px solid var(--g-hairline); padding-top: var(--g-space-sm); }
.auth__side { padding-top: var(--g-space-sm); }
.auth__side h2 { font-size: var(--g-fs-h4); margin: 0 0 var(--g-space-sm); }
.auth__list { list-style: none; margin: 0 0 var(--g-space-md); padding: 0; }
.auth__list li { font-size: var(--g-fs-small); color: var(--g-text-muted);
  padding: .6rem 0; border-bottom: 1px solid var(--g-hairline); }
.auth__list b { color: var(--g-text); font-weight: var(--g-w-medium); }
.auth__side-note { font-size: var(--g-fs-caption); color: var(--g-text-subtle); margin: 0; }

/* ---- topic viewpoints + poll, on the design system -------------------- */
.view { border: 1px solid var(--g-hairline); border-radius: var(--g-radius);
  background: var(--g-surface-raised); padding: var(--g-space-md); }
.view__k { font-family: var(--g-font-mono); font-size: var(--g-fs-caption);
  letter-spacing: .14em; text-transform: uppercase; color: var(--g-accent); margin: 0 0 .4rem; }
.view h3 { font-size: var(--g-fs-h4); margin: 0 0 var(--g-space-2xs); }
.view p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0; }
.poll { margin-top: var(--g-space-lg); border: 1px solid var(--g-hairline);
  border-radius: var(--g-radius); background: var(--g-surface-sunken); padding: var(--g-space-md); }
.poll__q { font-size: var(--g-fs-small); font-weight: var(--g-w-medium); margin: 0 0 var(--g-space-sm); }
.poll__opts { display: flex; flex-wrap: wrap; gap: .5rem; }
.poll__note { font-family: var(--g-font-mono); font-size: var(--g-fs-caption);
  color: var(--g-text-subtle); margin: var(--g-space-sm) 0 0; }

/* ---- stance list --------------------------------------------------------
   What replaced the poll. The three positions are the editorial content and
   are worth showing; the vote was not, because nothing counts it. So this
   keeps the framed, one-per-row reading of the poll and drops every affordance
   that implied a tally: no button, no bar, no percentage, no pressed state. */
.stance { margin-top: var(--g-space-lg); border: 1px solid var(--g-hairline);
  border-radius: var(--g-radius); background: var(--g-surface-sunken); padding: var(--g-space-md); }
.stance__list { list-style: none; margin: 0; padding: 0; }
.stance__opt { display: block; border: 1px solid var(--g-border); border-radius: var(--g-radius-sm);
  padding: var(--g-space-sm) var(--g-space-md); font-size: var(--g-fs-small); }
.stance__opt + .stance__opt { margin-top: .5rem; }
ul.stance { list-style: none; }

/* ---- reading-depth bar --------------------------------------------------
   Moved out of the header, where it was permanent chrome competing with
   navigation on every page. Sat at the top of the section it governs, it reads
   as a property of that content. */
.depthbar { display: flex; align-items: center; justify-content: space-between;
  gap: var(--g-space-md); flex-wrap: wrap;
  border: 1px solid var(--g-hairline); border-radius: var(--g-radius);
  background: var(--g-surface-sunken);
  padding: .6rem .6rem .6rem var(--g-space-md);
  margin-bottom: var(--g-space-lg); }
.depthbar__note { margin: 0; font-size: var(--g-fs-small); color: var(--g-text-muted); }
@media (max-width: 620px) {
  .depthbar { flex-direction: column; align-items: stretch; gap: var(--g-space-2xs);
              padding: var(--g-space-sm); }
  .depthbar .depth { width: 100%; }
  .depthbar .depth button { flex: 1; padding-block: .5rem; }
}

/* ---- sign-in row: the two controls sit on one baseline ------------------
   "Keep me signed in" is a label wrapping a checkbox and "Forgotten password"
   is a bare link; without a shared baseline they sat at different heights. */
.auth__row { align-items: center; min-height: 2rem;
  /* it was sitting hard against the submit button */
  margin-bottom: var(--g-space-lg); padding-bottom: var(--g-space-sm);
  border-bottom: 1px solid var(--g-hairline); }
.auth__row .auth__check { margin-bottom: 0; align-items: center; }
.auth__row > a, .auth__row > .is-flat { font-size: var(--g-fs-small); line-height: 1.4; }

/* ---- role picker: every card the same height ---------------------------- */
.auth__rolegrid { align-items: stretch; }
.rolepick { display: flex; }
.rolepick span { display: flex; flex-direction: column; width: 100%; height: 100%; }

/* ---- team ---------------------------------------------------------------
   A list, not a one-off block, because more people are coming. The open slot
   is a real card so the section does not look thin at one person. */
/* One card per row, and generous, these are the people, not a sidebar. */
.team { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: minmax(0, 1fr); gap: var(--g-space-md); max-width: 54rem; }
.person { display: flex; gap: var(--g-space-lg); align-items: flex-start;
  border: 1px solid var(--g-hairline); border-radius: var(--g-radius);
  background: var(--g-surface-raised); padding: var(--g-space-md); }
.person__pic { width: 168px; height: 168px; flex: none; border-radius: var(--g-radius);
  object-fit: cover; object-position: 50% 22%;
  background: var(--g-grad-deep-space); }
.person__pic--open { display: grid; place-items: center; color: var(--g-text-subtle);
  border: 1px dashed var(--g-border); background: transparent; }
.person__pic--open svg { width: 1.6rem; height: 1.6rem; }
.person__body h3 { font-size: var(--g-fs-h3); margin: 0 0 .2rem; letter-spacing: -.015em; }
.person__role { font-family: var(--g-font-mono); font-size: var(--g-fs-caption);
  letter-spacing: .1em; text-transform: uppercase; color: var(--g-accent); margin: 0 0 .5rem; }
.person__body > p:not(.person__role) { font-size: var(--g-fs-body);
  color: var(--g-text-muted); margin: 0; max-width: 52ch; }
.person__links { display: flex; gap: .5rem; margin-top: var(--g-space-md); flex-wrap: wrap; }
.person__links a { display: inline-flex; align-items: center; gap: .4rem;
  font-size: var(--g-fs-caption); font-family: var(--g-font-mono); letter-spacing: .06em;
  text-transform: uppercase; color: var(--g-text-muted); text-decoration: none;
  border: 1px solid var(--g-border); border-radius: var(--g-radius-pill);
  padding: .34rem .8rem; transition: .22s var(--g-ease-orbit); }
.person__links a:hover { color: var(--g-text); border-color: var(--g-accent);
  background: rgb(var(--g-tint) / .08); }
.person__links svg { width: .95rem; height: .95rem; }
.person.is-open { border-style: dashed; background: transparent; }
@media (max-width: 560px) { .person { flex-direction: column; gap: var(--g-space-md); }
  .person__pic { width: 100%; height: 200px; object-position: 50% 18%; } }

/* ---- history list -------------------------------------------------------
   A dated list rather than a decorated timeline: the year is the anchor, a
   single rule carries the eye down, and nothing competes with the entries. */
.hist { list-style: none; margin: 0; padding: 0; max-width: 52rem; }
.hist li { display: grid; grid-template-columns: 6.5rem minmax(0, 1fr);
  gap: var(--g-space-md); padding: var(--g-space-md) 0;
  border-top: 1px solid var(--g-hairline); }
.hist li:last-child { border-bottom: 1px solid var(--g-hairline); }
.hist__y { font-family: var(--g-font-mono); font-size: var(--g-fs-small);
  color: var(--g-accent); letter-spacing: .06em; padding-top: .12rem; }
.hist h3 { font-size: var(--g-fs-body); margin: 0 0 .25rem; }
.hist p { font-size: var(--g-fs-small); color: var(--g-text-muted); margin: 0; max-width: 60ch; }
@media (max-width: 560px) { .hist li { grid-template-columns: minmax(0, 1fr); gap: .3rem; } }

/* ---- an aside that starts level with the body of the column beside it ----
   The previous version pushed the aside down with `padding-top: 13rem`, a
   guess at the height of the heading and lead above the poll. Two things were
   wrong with it. The guess only held at one viewport and one font, so the box
   was never quite level; and because sticky positioning measures the border
   box, those 13rem of padding sat *above* the callout, so the box began
   sticking 13rem before the navbar ever reached it.

   Both problems come from faking the alignment. So don't fake it: put the
   intro on its own grid row and let the aside share the second row with the
   thing it is supposed to line up with. Now it is level by construction, at
   any width, and sticky measures the callout itself. */
.split--rows { grid-template-rows: auto auto; }
.split--rows > .split__intro { grid-column: 1; grid-row: 1; }
.split--rows > .split__body  { grid-column: 1; grid-row: 2; }
.split--rows > .aside        { grid-column: 2; grid-row: 2; }
/* The poll carries its own top margin for the case where it follows text.
   Here the grid row gap is already the separation, so drop it, otherwise the
   aside sits a margin higher than the box it is levelling with. */
.split--rows > .split__body > .poll:first-child { margin-top: 0; }
@media (max-width: 900px) {
  .split--rows > .split__intro,
  .split--rows > .split__body { grid-column: 1; }
  .split--rows > .aside { grid-column: 1; grid-row: 3; }
}

/* ---- sticky chrome, measured rather than guessed -------------------------
   Every sticky offset on the site was a hand-picked round number, which is why
   the topic sub-nav sat on top of the callouts instead of above them. These
   describe the actual chrome, so anything that has to clear it can say so. */
:root {
  --g-header-h: calc(4.5rem + 1px);        /* min-height + bottom hairline */
  --g-topic-nav-h: 3.5rem;               /* pinned below, so this stays true */
  --g-chrome-h: calc(var(--g-header-h) + var(--g-topic-nav-h) + 2px);
  --g-sticky-gap: 1.5rem;
}

.topic-nav { top: var(--g-header-h); }
/* Pin the row's height so --g-topic-nav-h is a fact and not a hope. */
.topic-nav__row { height: var(--g-topic-nav-h); align-items: center; }
.topic-nav a { padding-block: 0; display: inline-flex; align-items: center; height: 100%; }

.aside { top: calc(var(--g-header-h) + var(--g-sticky-gap)); }
/* On a topic there are two bars overhead, not one. Without this the callout
   parked underneath the section nav and was covered by it. */
.layer .aside { top: calc(var(--g-chrome-h) + var(--g-sticky-gap)); }

/* Anchors have to clear the same chrome, or a jump from the section nav lands
   with the heading hidden behind the bar that sent you there. */
.layer, .page-head { scroll-margin-top: calc(var(--g-chrome-h) + .75rem); }
:where(main) :target { scroll-margin-top: calc(var(--g-chrome-h) + .75rem); }

/* ---- a section that continues the page head -----------------------------
   The first block on About is not a new subject, it is the rest of the
   sentence the page head started. Given a full section's top padding on top of
   the head's own bottom padding, ~10rem of nothing opened up between two
   paragraphs that look identical, which reads as a fault rather than a break. */
.g-section--continues { padding-top: clamp(1.25rem, 2.5vw, 2rem); }

/* ---- the invisible radio was eating the cursor --------------------------
   .rolepick sets cursor:pointer, but its input is positioned over the whole
   card at opacity 0, so the pointer it reported was the input's, not the
   label's. */
.rolepick input { cursor: inherit; }

/* ---- forgotten password is a link, so it looks and behaves like one ------ */
.auth__forgot {
  font-size: var(--g-fs-small); color: var(--g-text-muted);
  text-decoration: underline; text-decoration-color: var(--g-border-strong);
  text-underline-offset: .2em; cursor: pointer;
  transition: color var(--g-dur) var(--g-ease-orbit);
}
.auth__forgot:hover { color: var(--g-text); text-decoration-color: var(--g-text); }

/* ---- back out of a sub-flow --------------------------------------------- */
.auth__back {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  font-size: var(--g-fs-small); color: var(--g-text-muted);
  display: inline-flex; align-items: center; gap: .4rem; margin-bottom: var(--g-space-md);
}
.auth__back:hover { color: var(--g-text); }
.auth__back svg { width: .9rem; height: .9rem; }


/* =========================================================================
   LIGHT THEME, the parts a token swap can't reach
   -------------------------------------------------------------------------
   Most of the site flips automatically now that surfaces, ink and overlays
   all come from tokens. What is left here are the places where the dark
   design isn't a colour but a *device*: a night sky, a scrim over a dark
   still, an inline SVG with white strokes baked in. Those don't translate -
   they have to be switched off or redrawn.
   ========================================================================= */

/* The night sky is a dark-mode idea. Rendered on paper it is grey dust on a
   cream field: noise with no meaning. Off. */
:root[data-theme="light"] .g-starfield,
:root[data-theme="light"] .lp-stars,
:root[data-theme="light"] .lp-meteors { display: none; }
/* The comet stays. It is the cursor, the hero sets `cursor: none` and lets the
   canvas draw the pointer instead, so hiding it in light mode did not remove a
   decoration, it removed the mouse pointer. It takes its colour from the tokens
   and composites normally, so on paper it is simply a dark comet. */

/* The well is a single SVG with White Tint strokes, used as a background
   image in three places. Inverting the render is cheaper and more reliable
   than shipping a second file, alpha is preserved, so only the strokes
   flip. */
:root[data-theme="light"] .g-well { filter: invert(1); opacity: 0.16; }

/* Scrims and play buttons assume something dark underneath. On light
   thumbnails they invert. */
:root[data-theme="light"] .video__play {
  border-color: rgb(0 48 73 / 0.35);
  background: rgb(255 255 255 / 0.7);
  color: var(--g-cosmos-600);
}
:root[data-theme="light"] .video:hover .video__play {
  background: var(--g-cosmos-600); color: var(--g-white-tint);
}
/* Except over a thumbnail. The inversion above is right for the empty frame,
   which is drawn from theme tokens and really does go light — but a thumbnail
   is a fixed picture and ours are deep space by definition. It does not
   lighten because the reader flipped the toggle, so the ring laid over it
   follows the image rather than the page. */
:root[data-theme="light"] .video.has-thumb .video__play {
  border-color: rgb(241 239 236 / .7); background: rgb(3 3 3 / .35);
  color: var(--g-white-tint);
}
:root[data-theme="light"] .video.has-thumb:hover .video__play {
  background: rgb(0 48 73 / .6); color: var(--g-white-tint);
}
:root[data-theme="light"] .g-video-card__duration {
  background: rgb(0 34 54 / 0.8);
}
:root[data-theme="light"] .lp-play span {
  border-color: rgb(0 48 73 / 0.35); background: rgb(255 255 255 / 0.72);
  color: var(--g-cosmos-600);
}

/* The orbit sits in a soft well that exists to stop the blue field tinting
   the bodies. On light the same job needs the opposite value. */
:root[data-theme="light"] .lp-orbit::before {
  background: radial-gradient(closest-side,
    rgb(255 255 255 / .9), rgb(255 255 255 / .55) 55%, rgb(255 255 255 / 0) 100%);
}

/* Cards on a white page need an edge, not a glow, or they dissolve into the
   background. The dark theme gets that edge from a lighter surface; here it
   has to come from the border and a shadow on hover. */
:root[data-theme="light"] .space,
:root[data-theme="light"] .program,
:root[data-theme="light"] .game-card,
:root[data-theme="light"] .path-card,
:root[data-theme="light"] .role,
:root[data-theme="light"] .level,
:root[data-theme="light"] .view,
:root[data-theme="light"] .person,
:root[data-theme="light"] .nextcard { border-color: var(--g-border); }
:root[data-theme="light"] .space:hover,
:root[data-theme="light"] .game-card:hover,
:root[data-theme="light"] .path-card:hover,
:root[data-theme="light"] .nextcard:hover { box-shadow: var(--g-glow-md); }

/* ---- Theme toggle -------------------------------------------------------
   Sits in the header actions at --gh-control, the same footprint as the menu
   button and the collapsed CTA, so the row keeps its rhythm at every width.
   Both icons are in the DOM; CSS decides which shows,
   which means no repaint flicker and no icon swap in JS. */
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--gh-control); height: var(--gh-control); flex: none;
  border-radius: var(--g-radius-pill);
  border: 1px solid var(--g-border); background: transparent;
  color: var(--g-text-muted); cursor: pointer;
  transition: color var(--g-dur) var(--g-ease-orbit),
              border-color var(--g-dur) var(--g-ease-orbit),
              background-color var(--g-dur) var(--g-ease-orbit);
}
.theme-toggle:hover {
  color: var(--g-text); border-color: var(--g-border-strong);
  background: rgb(var(--g-tint) / 0.08);
}
/* 18px of glyph inside the control, the same figure the CTA and the burger
   now use. Also brings the toggle into the same optical weight as the footer
   marks. */
.theme-toggle svg { width: var(--gh-control-icon); height: var(--gh-control-icon); display: block; }
.theme-toggle .ic-sun { display: none; }
:root[data-theme="light"] .theme-toggle .ic-sun { display: block; }
:root[data-theme="light"] .theme-toggle .ic-moon { display: none; }



/* The join band appears on the home page too now, where it sits inside a
   normal section rather than at the foot of the community page. */
.join .g-starfield { opacity: .35; }


/* =========================================================================
   LONG-FORM READING COMFORT
   -------------------------------------------------------------------------
   Topics and Magazine pieces are supposed to be dense, that is the product,
   and cutting them would be cutting the thing people came for. So the fix on
   these pages is not fewer words but a page that is easier to hold: a shorter
   measure, more air between ideas than inside them, headings that announce
   themselves by position rather than by size alone, and a visible sense of
   how much is left.
   ========================================================================= */

/* A 68ch line at lead size is roughly 100 characters on a wide screen, which
   is past the point where the eye starts losing its place on the return
   sweep. 62ch is the comfortable end of the classic range. */
div.art, .g-prose { max-width: 62ch; min-width: 0; }

/* Last line of defence. Every known overflow is fixed at its source above; this
   only means that if a future <pre>, table or wide embed escapes its box, it
   cannot drag the whole page sideways with it. `clip` rather than `hidden`
   because `hidden` would make the page a scroll container and break every
   sticky header on the site. */
html { overflow-x: clip; }
div.art { line-height: 1.72; }

/* Space between paragraphs should exceed space between lines, or the block
   reads as one undifferentiated mass. It did not. */
div.art > * + * { margin-top: 1.15em; }

/* Section heads inside an article get a short rule in the accent rather than
   a bigger type size. It marks the break at a glance while scanning, and it
   costs no vertical space that the text could have had. */
.art h2 {
  margin-top: 2.4em; position: relative; padding-top: var(--g-space-sm);
  line-height: 1.25;
}
.art h2::before {
  content: ""; position: absolute; top: 0; left: 0;
  width: 2.5rem; height: 2px; background: var(--g-accent);
}
.art h3 { margin-top: 1.9em; color: var(--g-text); }

/* Lists inside long-form were running at the same rhythm as prose, so a
   four-item list looked like a paragraph with bullets. */
.art ul, .art ol { padding-left: 1.2rem; }
.art li + li { margin-top: .45em; }
.art li::marker { color: var(--g-accent); }

/* The aside is the one place a reader is allowed to stop, so it should look
   like a stop: a different ground, not just a rule down one side. */
.art__aside { border-left-color: var(--g-accent); }

/* Topic layers are the same problem one level up. A named number and a rule
   let someone re-enter the page halfway down and know where they are. */
.layer { padding-block: clamp(3rem, 6vw, 5rem); }
.layer__head { margin-bottom: var(--g-space-lg); }
.layer__head h2 { line-height: 1.2; }

/* ---- Reading progress ---------------------------------------------------
   Two thousand words with no end in sight is a different experience from two
   thousand words that visibly end. One hairline, no numbers, no percentage -
   the information is "how much is left", and a bar answers that without
   asking to be read. */
.readbar {
  position: fixed; left: 0; top: 0; height: 2px; width: 100%;
  z-index: calc(var(--g-z-sticky) + 1);
  background: transparent; pointer-events: none;
}
.readbar i {
  display: block; height: 100%; width: 0;
  background: var(--g-accent);
  transition: width 90ms linear;
}
@media (prefers-reduced-motion: reduce) { .readbar i { transition: none; } }
