/* Static-export fixes. Loaded AFTER gs-site-lab.css, which stays byte-identical
   to Lovable's file — keeping corrections here means the export can be replaced
   wholesale without losing them, and the diff stays readable.

   ---------------------------------------------------------------------------
   1. Body background does not follow the theme
   ---------------------------------------------------------------------------
   gs-site-lab.css:65 sets `body { background-color: var(--color-background) }`,
   and --background is the Tailwind/shadcn base token: oklch(97.2% .003 258),
   a near-white (line 1035). Its dark counterpart on line 1036 is gated on the
   shadcn `.dark` class, which React used to set and the static pages do not —
   they theme with [data-theme] instead. So in dark mode every page keeps a
   light body behind the .gsh wrapper, visible as a pale band around the header
   and down the page edges.

   .gsh itself is correct (#07080D dark / #F7F3EE light); it is only the body
   underneath that never switched. The ground values are written literally here
   because body sits outside .gsh and cannot read --gs-ground. */

html:not([data-theme="dark"]) body {
  background-color: #F7F3EE;
}
html[data-theme="dark"] body {
  background-color: #07080D;
}

/* Belt and braces for the first paint: the inline bootstrap sets
   data-theme before the stylesheet lands, but a viewer with no stored
   preference gets no attribute at all, so the light rule above has to be the
   one that applies by default — which it does, via :not(). */

/* ---------------------------------------------------------------------------
   2. About hero tab wordmark uses themed tokens instead of lockup colours
   ---------------------------------------------------------------------------
   The small "growthsanity" inside the About hero's browser-tab graphic takes
   --wm-g/--wm-i from var(--gs-green)/var(--gs-indigo). In dark mode those
   resolve through oklch() to #4BC680 and #8498EF — 22 and 17 levels off the
   real wordmark colours, so the mark in the tab graphic does not match the
   lockup in the header two inches above it.

   Measured in this export, dark mode:
     header .wm  #35BD6B   (correct)
     .ab-wm      #4BC680   (wrong)

   The wordmark is brand artwork, not themed UI: it takes the lockup's two
   colours and nothing else. Do not re-tokenise these.

   This was introduced in the About rewrite and corrected in the source CSS we
   hold, but that correction has not reached Lovable's project yet — so it is
   patched here as well until the next export carries it. */

.gsh .ab-tab {
  --wm-g: #173D24;
  --wm-i: #485289;
}
.gsh[data-theme="dark"] .ab-tab {
  --wm-g: #35BD6B;
  --wm-i: #7D8BDE;
}

/* ---------------------------------------------------------------------------
   3. Closing section: split the two bands, and make the status strip readable
   ---------------------------------------------------------------------------
   DESIGN CHANGE (Kyle, 29 Oct 2026). Supersedes the single-card treatment.

   The bench and the Acquret note were merged into one card with a hairline
   between them, to stop the page bottom reading as banner after banner. Seen
   rendered, the hairline does the opposite: two full-width bands sharing one
   container read as one thing awkwardly divided rather than two related
   things. They are now separate cards with real space between.

   The hierarchy that fixed the original problem stays: the bench keeps the
   full-size headline, the Acquret note stays demoted at 23px. Splitting the
   container does not make them two competing banners again, because they were
   never the same weight.

   The status strip set a full sentence in uppercase mono with wide tracking,
   which is slow to read and against the type system — JetBrains Mono is for
   kickers, IDs and timestamps, never for reading. Only the label stays mono
   now; the sentence and links move to the UI and heading faces in sentence
   case.
   ========================================================================= */

/* --- split the card into two --- */
.gsh[data-lab] .lab-close-card {
  border: 0;
  background: none;
  border-radius: 0;
  overflow: visible;
  display: grid;
  gap: 18px;
}

.gsh[data-lab] .lab-close-band {
  border: 1px solid var(--gs-rule);
  border-radius: 20px;
  background: var(--card-b);
  overflow: clip;
}
.gsh[data-lab].ab .lab-close-band {
  background: var(--card-a);
}
/* the hairline that used to divide them is gone with the shared container */
.gsh[data-lab] .lab-close-band + .lab-close-band {
  border-top: 1px solid var(--gs-rule);
}

/* --- status strip: its own quiet row, outside both cards --- */
.gsh[data-lab] .lab-close-strip {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px 22px;
  margin-top: 4px;
  padding: 14px 4px 0;
  border-top: 1px solid var(--gs-rule);
  background: none;
}
.gsh[data-lab].ab .lab-close-strip {
  background: none;
}

/* label stays mono — it is a label */
.gsh[data-lab] .lab-close-status {
  font-family: var(--gs-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* sentence and links become readable type */
.gsh[data-lab] .lab-close-ask {
  font: 400 13.5px/1.5 var(--gs-ui);
  letter-spacing: 0;
  text-transform: none;
  color: var(--gs-ink-2);
}
.gsh[data-lab] .lab-close-links {
  display: inline-flex;
  gap: 18px;
  margin-left: 0;
  white-space: nowrap;
}
.gsh[data-lab] .lab-close-links a {
  font: 700 13px/1.3 var(--gs-head);
  letter-spacing: 0;
  text-transform: none;
}

@media (max-width: 900px) {
  .gsh[data-lab] .lab-close-strip {
    grid-template-columns: minmax(0, 1fr);
    gap: 10px;
  }
  .gsh[data-lab] .lab-close-links {
    white-space: normal;
    flex-wrap: wrap;
  }
}

/* ---------------------------------------------------------------------------
   4. Bench pending note: same readability fix as the status strip
   ---------------------------------------------------------------------------
   DESIGN CHANGE (Kyle, 29 Oct 2026). Follows §3.

   "Email signup opens at launch — your pick isn't submitted yet." was still set
   in uppercase JetBrains Mono with wide tracking, like the strip sentence was
   before §3. Two sentences in the same section, one fixed and one not, reads as
   an oversight. Mono stays for kickers, IDs and timestamps; this is a sentence,
   so it takes the UI face in sentence case.

   The markup keeps its `mono` class (it comes from the exported component), so
   the override is by class here rather than a markup change — same reason as
   §3: no drift between the export and the Lovable source.
   ========================================================================= */

.gsh[data-lab] .lab-close-pending {
  font-family: var(--gs-ui);
  font-size: 12.5px;
  line-height: 1.5;
  letter-spacing: 0;
  text-transform: none;
  color: var(--gs-ink-3);
}

/* ---------------------------------------------------------------------------
   5. CTA typeface: one face for every pill CTA on the site
   ---------------------------------------------------------------------------
   DESIGN FIX (Kyle, 29 Oct 2026). The locked type system gives Helvetica Neue
   bold to headings AND all pill CTAs; Space Grotesk is UI, body and metrics.

   Pages whose body sits inside .gsh already follow it — gs-lab.css's
   `.gsh .btn` wins there. The legacy page bodies do not, because they predate
   the rule and site.css still carries the old system:

     site.css   `.btn`                        font-family: var(--gs-ui)   600
     design.css `.rd main .btn, .rd main .btn-dl`  font-family: var(--gs-ui)   600

   Ten CTAs were affected:
     /demand-gen-roi-calculator  "See my snapshot"
     /linkedin-post-auditor      "Audit my post"
     /tools                      Browse the tools, Visit acquret.com,
                                 Open the calculator, Audit a post,
                                 Book a consultation
     /404                        Browse the tools, Demand Gen ROI Calculator,
                                 LinkedIn Post Auditor

   Specificity is matched deliberately, not inflated: this file loads after both
   stylesheets, so an equal-specificity rule wins on order. No !important.
   `--gs-head` is declared at :root in site.css, so it resolves outside .gsh.

   Size is left alone. A 15px page CTA next to a 14px header CTA is hierarchy,
   not inconsistency — only the typeface was wrong.

   NOT changed, because they are not CTAs: the step pills (1 Inputs / 2 Snapshot
   …), the /shelf jump chips, and the Pipeline value / Closed revenue toggles.
   Those are labels and tabs, and JetBrains Mono / Space Grotesk is correct for
   them. Turning seven jump chips into Helvetica bold would read as seven
   competing CTAs.
   ========================================================================= */

.btn,
.btn-dl,
.nf-btn {
  font-family: var(--gs-head);
  font-weight: 700;
  letter-spacing: -0.01em;
}

/* matches design.css's own selector so it wins on load order */
.rd main .btn,
.rd main .btn-dl {
  font-family: var(--gs-head);
  font-weight: 700;
  letter-spacing: -0.01em;
}

/* 404 only: its own <style> block sits AFTER this file in the head, so an
   equal-specificity rule loses on order. `a.nf-btn` / `button.nf-btn` is one
   notch higher (0,1,1 vs 0,1,0) — the minimum needed to win, rather than
   !important. The inline rule is:
       .nf-btn { font-family: var(--gs-ui); font-size: 13.5px; font-weight: 600 }
   Its 13.5px size is kept; only the face and weight change. */
a.nf-btn,
button.nf-btn {
  font-family: var(--gs-head);
  font-weight: 700;
  letter-spacing: -0.01em;
}

/* ---------------------------------------------------------------------------
   6. Footer: one treatment site-wide
   ---------------------------------------------------------------------------
   DESIGN FIX (Kyle, 29 Oct 2026). The footer must look the same everywhere.

   `.lft` ships its own palette in gs-lab.css — a white panel in light, #0B0E1C
   in dark — so it lifts off the page ground. The fold-cadence block in
   gs-site-lab.css (line 4138) then overrides `--lft-bg` to `var(--fold-a)` on
   .gsh[data-lab] pages, which flattens the footer into the body:

     homepage, About, Shelf, Playlists, Films   #F7F3EE on #F7F3EE  (flat)
     tools, games, both tool pages              #FFFFFF on #F7F3EE  (lifted)

   The lifted version is the standard. This restores the native values on the
   pages the cadence had flattened; the pages that already looked right are
   unchanged, because these are the values they were already using.

   `.mq-shelf` is deliberately NOT included. The marquee sits above the footer
   and belongs to the page ground — the linen strip between content and footer
   is what gives the white panel its edge. */

.gsh[data-lab] .lft {
  --lft-bg: #FFFFFF;
  background: var(--lft-bg);
}
.gsh[data-lab][data-theme="dark"] .lft {
  --lft-bg: #0B0E1C;
  background: var(--lft-bg);
}

/* ---------------------------------------------------------------------------
   7. Quick Q (/quick-q)
   ---------------------------------------------------------------------------
   New page, 30 Oct. Built on the /shelf shell, so header, footer and tokens
   come free; only the page's own blocks are here.

   THE POINT OF THE CARD IS THE TYPE. Four faces, four jobs, in reading order:

     mechanical top    JetBrains Mono   dispatch no., time, persona pill
     the incoming ping Helvetica Neue   bold, 19px — hits at a glance
     the decode        Libre Caslon     italic lead-in, the human voice...
                       Space Grotesk    ...then the explanation
     the reply         Space Grotesk    calm, readable
     buttons           Helvetica Neue   bold, per §4
     watermark         JetBrains Mono   survives a screenshot

   That sequence is the retro-modern feel: cold machine header, loud incoming
   message, warm editorial turn, pragmatic answer.

   Caslon is used here as an editorial lead-in, not as a headline accent. It is
   the one place on the page the brand's voice speaks rather than reports.
   ========================================================================= */

.gsh[data-lab] .qq-sec { padding-block: 56px; background: var(--fold-a); }

/* --- page head ---
   Corrected 6 Nov (§44). This rule previously read:

     text-align: left; align-items: start; gap: 10px; margin: 0 0 26px;

   which was a half-done override and the cause of the ragged heads Lovable
   flagged. `.qq-head` / `.dt-head` are also `.sec-head`, and the locked sheet
   sets BOTH axes there (line 1242): `text-align: center; justify-items:
   center`. Flipping `text-align` to left while leaving `justify-items:
   center` standing meant every child shrink-wrapped, centred itself on the
   720px track, then ran its text left from wherever it landed — three
   different left edges at 1280 (kicker 343, headline 70, subhead 125) all
   sharing one centre at 430. `align-items` was the wrong property: it is the
   block axis. The inline axis is `justify-items`.

   The locked sheet already has the correct way to left-align a head — the
   `.sec-head.left` modifier at line 1469 sets `text-align: left` AND
   `justify-items: start` AND `margin-left: 0` together. Had left been wanted,
   that class in the HTML was the route, not this.

   Centred is also the house pattern: /shelf, /games and /tools all run their
   heads centred. These two were the only outliers, and only in this one
   property. So the fix is to stop declaring it and let line 1242 govern.
   The gap and bottom margin are kept — those were deliberate spacing — but
   the margin regains its `auto` inline sides so the 720px block itself
   centres in the column instead of sitting hard left. */
.gsh[data-lab] :is(.qq-head, .dt-head) { gap: 10px; margin: 0 auto 26px; }
.gsh[data-lab] :is(.qq-kick, .dt-kick) {
  color: var(--gs-green-active);
  font: 500 11px/1 var(--gs-mono); letter-spacing: .12em; text-transform: uppercase;
}
.gsh[data-lab][data-theme="dark"] :is(.qq-kick, .dt-kick) { color: var(--gs-green); }
.gsh[data-lab] :is(.qq-kick, .dt-kick) b { font-weight: 700; }
.gsh[data-lab] :is(.qq-h1, .dt-h1) {
  margin: 0; color: var(--gs-ink);
  font: 700 clamp(34px, 5vw, 56px)/1.05 var(--gs-head); letter-spacing: -.035em;
}
/* `balance` rather than `pretty`: the sub is two lines at desktop width and
   the complaint was a one-word orphan on line two, which is a line-LENGTH
   problem. (Not the same call as the /about headline in §42, where the ask
   was one sentence per line and balance actively fought it.) */
.gsh[data-lab] :is(.qq-sub, .dt-sub) { margin: 0; max-width: 56ch; font: 400 17px/1.6 var(--gs-ui); color: var(--gs-ink-2); text-wrap: balance; }

/* --- filter tabs --- */
.gsh[data-lab] .qq-tabs { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 22px; }
.gsh[data-lab] .qq-tab {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 8px 15px; border: 1px solid var(--gs-rule); border-radius: 999px;
  background: var(--card-a); color: var(--gs-ink-2);
  font: 700 13px/1 var(--gs-head); letter-spacing: -.01em; cursor: pointer;
  transition: border-color .2s ease, color .2s ease, background .2s ease;
}
.gsh[data-lab] .qq-tab i { font: 400 11px/1 var(--gs-ui); font-style: normal; color: var(--gs-ink-3); }
.gsh[data-lab] .qq-tab:hover { border-color: var(--gs-rule-2); color: var(--gs-ink); }
.gsh[data-lab] .qq-tab.is-on { background: var(--gs-ink); border-color: var(--gs-ink); color: var(--gs-ground); }
.gsh[data-lab] .qq-tab.is-on i { color: var(--gs-ground); opacity: .7; }
.gsh[data-lab] .qq-tab:focus-visible { outline: 2px solid var(--gs-green-active); outline-offset: 3px; }

/* --- card --- */
.gsh[data-lab] .qq-grid {
  /* Two columns, not auto-fit. With four cards, auto-fit computed three
     369px tracks at desktop width and stranded card #004 alone on row 2
     between two empty slots. A fixed 2x2 gives all four personas equal
     weight and widens each card to ~490px, which the telex layout needs —
     at 369px the cards ran 677px tall and read as mobile strips. */
  display: grid; gap: 16px;
  grid-template-columns: repeat(2, 1fr);
  max-width: 1040px; margin-inline: auto;
  align-items: start;
}
@media (width <= 768px) {
  .gsh[data-lab] .qq-grid { grid-template-columns: 1fr; }
}
.gsh[data-lab] .qq-card {
  display: grid; gap: 14px; padding: clamp(18px, 2.4vw, 22px);
  border: 1px solid var(--gs-rule); border-left: 3px solid var(--pc, var(--gs-rule));
  border-radius: 20px; background: var(--card-a);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.6);
}
.gsh[data-lab][data-theme="dark"] .qq-card { box-shadow: inset 0 1px 0 rgba(255,255,255,.05); }
.gsh[data-lab] .qq-card[hidden] { display: none; }

.gsh[data-lab] .p-felix { --pc: #1E432A; }
.gsh[data-lab] .p-chase { --pc: #1E3456; }
.gsh[data-lab] .p-sasha { --pc: #7F3025; }
.gsh[data-lab] .p-rohan { --pc: #6B4412; }
.gsh[data-lab][data-theme="dark"] .p-felix { --pc: #4ADE80; }
.gsh[data-lab][data-theme="dark"] .p-chase { --pc: #60A5FA; }
.gsh[data-lab][data-theme="dark"] .p-sasha { --pc: #FB7185; }
.gsh[data-lab][data-theme="dark"] .p-rohan { --pc: #FBBF24; }

/* mechanical top: dispatch number + persona pill */
.gsh[data-lab] .qq-top {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px;
  padding-bottom: 12px; border-bottom: 1px dashed var(--gs-rule);
  font: 500 11px/1 var(--gs-mono); letter-spacing: .08em; text-transform: uppercase;
}
.gsh[data-lab] .qq-dispatch { color: var(--gs-ink-3); }
.gsh[data-lab] .qq-dispatch i { font-style: normal; margin: 0 .5em; opacity: .55; }
.gsh[data-lab] .qq-pill {
  display: inline-flex; align-items: center; gap: 7px; margin-left: auto;
  padding: 4px 10px; border: 1px solid color-mix(in srgb, var(--pc) 35%, transparent);
  border-radius: 999px; color: var(--pc);
}
/* brackets as content, so the text stays clean for copy and screen readers */
.gsh[data-lab] .qq-pill::before { content: "[ "; opacity: .6; }
.gsh[data-lab] .qq-pill::after  { content: " ]"; opacity: .6; }
.gsh[data-lab] .qq-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--pc); flex: none; }

/* micro-labels — 11px / .08em per the type spec */
.gsh[data-lab] .qq-lbl {
  display: block; margin-bottom: 7px;
  font: 500 11px/1 var(--gs-mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--gs-ink-3);
}

/* the ping: the loudest thing on the card */
.gsh[data-lab] .qq-ping blockquote {
  margin: 0; padding: 14px 16px; border-radius: 14px 14px 14px 4px;
  background: color-mix(in srgb, var(--pc) 7%, var(--fold-a));
  border: 1px solid color-mix(in srgb, var(--pc) 18%, transparent);
}
.gsh[data-lab] .qq-ping p {
  margin: 0; color: var(--gs-ink);
  font: 700 19px/1.35 var(--gs-head); letter-spacing: -.02em; text-wrap: pretty;
}

/* the decode: Caslon turn, then Space Grotesk */
.gsh[data-lab] .qq-block p { margin: 0; font: 400 14px/1.6 var(--gs-ui); color: var(--gs-ink-2); }
.gsh[data-lab] .qq-lead {
  font: italic 400 15.5px/1.5 var(--gs-serif); letter-spacing: -.01em;
  color: var(--gs-ink); margin-right: .15em;
}

/* the reply */
.gsh[data-lab] .qq-reply {
  padding: 14px 15px; border-radius: 14px;
  background: var(--fold-a); border: 1px solid var(--gs-rule);
}
.gsh[data-lab] .qq-reply .qq-text { color: var(--gs-ink); }

.gsh[data-lab] .qq-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.gsh[data-lab] .qq-btn {
  position: relative; padding: 8px 16px;
  border: 1px solid var(--gs-ink); border-radius: 999px;
  background: var(--gs-ink); color: var(--gs-ground);
  font: 700 12.5px/1 var(--gs-head); letter-spacing: -.01em; cursor: pointer;
  transition: opacity .2s ease, border-color .2s ease, color .2s ease;
}
.gsh[data-lab] .qq-btn:hover { opacity: .88; }
.gsh[data-lab] .qq-btn-line { background: var(--card-a); color: var(--gs-ink); border-color: var(--gs-rule-2); }
.gsh[data-lab] .qq-btn-line:hover { opacity: 1; border-color: var(--gs-green-active); color: var(--gs-green-active); }
.gsh[data-lab][data-theme="dark"] .qq-btn-line:hover { border-color: var(--gs-green); color: var(--gs-green); }
.gsh[data-lab] .qq-btn:focus-visible { outline: 2px solid var(--gs-green-active); outline-offset: 3px; }
/* confirmation overlays the label, so the button never changes width */
.gsh[data-lab] .qq-done {
  position: absolute; inset: 0; display: grid; place-items: center;
  border-radius: inherit; background: inherit; color: inherit;
  opacity: 0; transition: opacity .18s ease;
}
.gsh[data-lab] .qq-btn.is-done .qq-done { opacity: 1; }

/* watermark — quiet on screen, legible in a screenshot */
.gsh[data-lab] .qq-mark {
  padding-top: 11px; border-top: 1px dashed var(--gs-rule);
  font: 400 10px/1 var(--gs-mono); letter-spacing: .06em;
  color: var(--gs-ink-3); opacity: .72;
}

.gsh[data-lab] .qq-note { margin-top: 24px; max-width: 70ch; font-family: var(--gs-ui); }

@media (prefers-reduced-motion: reduce) {
  .gsh[data-lab] :is(.qq-tab, .qq-btn, .qq-done) { transition: none; }
}
@media (max-width: 560px) {
  .gsh[data-lab] .qq-sec { padding-block: 36px; }
  .gsh[data-lab] .qq-tab { padding: 7px 12px; font-size: 12.5px; }
  .gsh[data-lab] .qq-tab i { display: none; }
  .gsh[data-lab] .qq-ping p { font-size: 17px; }
  .gsh[data-lab] .qq-pill { margin-left: 0; }
}

/* ============================================================
   §8  /downtime — the Playlists & films hub

   The shelf card "Playlists & films" used to point at /shelf#listen,
   which only scrolled back into the same page and landed on a podcast
   list. This page is its real destination.

   It reuses .pl-grid / .pl-card wholesale from /playlists and /films,
   so the only new rules are the two section headings that separate the
   collections. Nothing here restyles a card.
   ============================================================ */

/* The page head reuses Quick Q's rules verbatim via :is() above — same
   stylesheet chain, same skeleton, so the two shelf destinations cannot
   drift apart when either is polished. Only the group headings below are
   specific to this page. */
.gsh[data-lab] .dt-grp { margin-top: 44px; }
.gsh[data-lab] .dt-grp:first-of-type { margin-top: 32px; }

.gsh[data-lab] .dt-grp-h {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 0 12px;
  margin: 0 0 18px;
  font: 700 23px/1.25 var(--gs-head);
  letter-spacing: -.02em; color: var(--gs-ink);
}

/* The number is an index into the page, not a sequence to follow, so it
   stays quiet: mono, small, and never the same weight as the heading.

   The box it used to carry — 4px 7px padding, 1px border, 6px radius — came
   off on 5 Nov under invariant 2: a pill wrapper on a kicker. It was also
   `display: block` inside a wrapping flex row, so it took a whole line and
   left "01 //" stranded above the title on phones. */
.gsh[data-lab] .dt-grp-n {
  display: inline;
  font: 500 11px/1 var(--gs-mono);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--gs-ink-3);
  padding: 0; border: 0; border-radius: 0; background: none;
}

/* Full-width so the sentence sits under the heading rather than running
   on after it, which is what the baseline flex row would otherwise do. */
.gsh[data-lab] .dt-grp-h small {
  flex: 1 0 100%;
  margin-top: 6px;
  font: 400 14px/1.6 var(--gs-ui);
  letter-spacing: 0; color: var(--gs-ink-2);
}

/* Each collection carries its own "All …" link, so the two sit closer to
   their grid than the page-level "Back to the shelf" does. */
.gsh[data-lab] .dt-grp .shp-more { margin-top: 18px; }

@media (max-width: 560px) {
  .gsh[data-lab] .dt-grp { margin-top: 34px; }
  .gsh[data-lab] .dt-grp-h { font-size: 20px; }
}

/* ---------------------------------------------------------------------------
   9. Pillar kicker on pages whose body is not inside a .gsh wrapper
   ---------------------------------------------------------------------------
   §8 styles `.dt-kick` as `.gsh[data-lab] :is(.qq-kick, .dt-kick)`, which is
   right for the exported pages. /tools is not built that way: its two .gsh
   wrappers are the header chrome and the footer, and the hero sits directly on
   <body>. So the scoped rule never matched and the kicker rendered as plain
   15px Space Grotesk — body copy, where the system wants a mono eyebrow.

   This is the same treatment at a lower specificity, so the .gsh pages keep
   their own rule and nothing else changes. Values copied from
   gs-site-lab.css's `.gsh .mono` rather than re-invented. */
.dt-kick {
  display: inline-block;
  font-family: var(--gs-mono, "JetBrains Mono", ui-monospace, monospace);
  font-size: 11px; font-weight: 500; line-height: 1.4;
  letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--gs-ink-3, #6B7389);
}
.dt-kick b { font-weight: 500; color: var(--gs-green-active, var(--gs-green)); }


/* ---------------------------------------------------------------------------
   10. body still asked for Inter on the five exported pages
   ---------------------------------------------------------------------------
   gs-site-lab.css:10 sets `--font-body: "Inter", ...` and line 65 applies it to
   `body`. Lovable called this a dead fallback string; it is not — it is the
   body font on /, /about, /shelf, /playlists and /quick-q.

   The visible blast radius is small, because almost everything sits inside a
   .gsh wrapper that sets its own face. But Inter is out of the type system and
   is never downloaded, so anything that does inherit from body rendered Inter
   for viewers who happen to have it installed locally and Helvetica Neue — a
   heading face — for everyone else. Same divergence we fixed in site.css.

   Set here rather than in gs-site-lab.css so that file stays byte-identical to
   Lovable's export. --font-ui is their own token, defined in the same file. */
body { font-family: var(--font-ui, var(--gs-ui, "Space Grotesk")), "Helvetica Neue", Helvetica, Arial, sans-serif; }

/* ---------------------------------------------------------------------------
   11. Curation fold: per-card CTA, and unlinked items
   ---------------------------------------------------------------------------
   The rack's cards gained a uniform "Explore all" footer and, in two places,
   an item with no destination (Moneyball on the homepage, Kyle's rotating slot
   on /playlists). Everything else here is Lovable's existing chassis —
   .lab-sh-card, .lab-sh-i, .pl-card and the tones are untouched. */

/* Footer CTA. Pinned to the bottom so all four read as one row of cards even
   when one list runs a line longer than the others. */
.gsh .lab-sh-card { display: flex; flex-direction: column; }
.gsh .lab-sh-card ul { flex: 1 1 auto; }
.gsh .lab-sh-all {
  display: inline-flex; align-items: center; align-self: flex-start;
  margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--gs-rule);
  width: 100%;
  font-size: 11px; letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--gs-green-active); text-decoration: none;
}
.gsh[data-theme="dark"] .lab-sh-all { color: var(--gs-green); }
.gsh .lab-sh-all:hover { text-decoration: underline; }

/* An item with no link must not look clickable: no arrow, no hover, and the
   cursor stays default. */
.gsh .lab-sh-i.is-static { cursor: default; }
.gsh .lab-sh-i.is-static .gs-arr { display: none; }
.gsh .lab-sh-i.is-static:hover { background: none; }

/* The rotating slot on /playlists, same idea on the bigger chassis. */
.gsh .pl-card.is-static .pl-go { opacity: 0.6; }

/* .pl-why needs nothing here. gs-site-lab.css already styles it — Space
   Grotesk 13.5px with a hairline above — and that is right: DESIGN-LANGUAGE.md
   §2 reserves Caslon for a punchline inside a heading, never for a line of
   body copy. An earlier draft of this file set it in Caslon italic and was
   out-specified by Lovable's own rule, which is the correct outcome. */

/* ---------------------------------------------------------------------------
   12. /tools — Lovable's redesigned body, two rules it needs
   ---------------------------------------------------------------------------
   The new markup reuses `.card tool` from the legacy chain, and site.css sets
   `.tool h3 { font-family: var(--gs-accent); font-style: italic }` — Libre
   Caslon italic, left from the old tool cards. DESIGN-LANGUAGE.md §2 reserves
   Caslon for a punchline inside a heading, never a card title, and Lovable's
   own mock shows these in bold sans. Put back on the heading face.

   The heading is also no longer a direct child of .wrap — it sits inside
   `.sec-head tk-head` now — so the rule that sized it no longer reaches it.
   `.tk-h1` had no CSS at all, which left the page headline at 15px body copy. */
#tools .tool h3,
#tools .card h3 {
  font-family: var(--gs-head, "Helvetica Neue", Helvetica, Arial, sans-serif);
  font-style: normal; font-weight: 700;
  letter-spacing: -0.02em; line-height: 1.25;
  margin-top: 0;
}
.tk-hero .tk-h1 {
  font-family: var(--gs-head, "Helvetica Neue", Helvetica, Arial, sans-serif);
  font-weight: 700; letter-spacing: -0.03em; line-height: 1.06;
  font-size: clamp(34px, 5.4vw, 64px);
}

/* ---------------------------------------------------------------------------
   13. gs-flip has to reach the legacy pages too
   ---------------------------------------------------------------------------
   gs-site-lab.css scopes it as `html.gs-flip .gsh, html.gs-flip .gsh *`, which
   is right for the exported pages — everything there is inside a .gsh wrapper.
   On /tools, /privacy-and-terms, /404 and the games, only the header chrome
   and the footer are wrapped; the body sits directly on the page. So the
   suppression never reached the thing actually changing colour, and the
   grounds and cards eased at different rates — the flicker gs-flip exists to
   prevent.

   Unscoped, for one frame only, during a deliberate theme change. */
html.gs-flip,
html.gs-flip * { transition: none !important; }

/* ---------------------------------------------------------------------------
   14. (retired 4 Nov) /tools proof grid phone breakpoint
   ---------------------------------------------------------------------------
   `.tk-proof` was the four uppercase mono bullets. They are micro-cards now
   (§25), which carry their own breakpoints, so this rule matched nothing and
   was removed rather than left to mislead. Kept as a note because the reason
   still applies to the cards: 11px uppercase at 0.12em tracking does not fit
   a 147px column — "Backed by 20+ years" ran to four lines there. */

/* ---------------------------------------------------------------------------
   15. /tools hero: keep the typography, drop the decoration
   ---------------------------------------------------------------------------
   site.css scopes the whole tool-page hero on `.hero:has(.ctas)` — the 60px
   headline, the Caslon accent styling and the entrance animation all hang off
   it, which is why `.ctas` had to be restored. But the same selector also
   paints an indigo radial glow (::before) and a star field (::after), and
   those were written when tools-home.html WAS the homepage.

   The real homepage does not have them: measured on `/`, the hero's ::before
   content is `none` and `--gs-stars` is unset. So /tools was the only page on
   the site with a glow, which is what made dark mode look wrong there.

   Typography stays, decoration goes. */
.tk-hero::before,
.tk-hero::after { content: none !important; }

/* ---------------------------------------------------------------------------
   16. /tools headline leading
   ---------------------------------------------------------------------------
   Lovable specified `line-height: 1.06`. At the 60px desktop size that leaves
   3.6px of leading, and this headline is not plain text: the Caslon accents
   carry a drawn rule at `bottom: .04em`, so the underline beneath "funnel"
   lands in the cap height of the line below it. "stage" has a descender in the
   same place. The two lines read as one block.

   1.12 gives 7px of leading — still tight, still the same typographic
   intention, but the rule and the descenders clear the next line. A little
   more air under the headline for the same reason. */
.tk-hero .tk-h1 {
  line-height: 1.08 !important;
  margin-bottom: 1.25rem !important;
}
/* §16b: the inline clamp has to beat the `.hero:has(.ctas) h1` rule from
   site.css, which caps the headline at 64px. Lovable's layout runs it wider
   and larger — a statement across the measure rather than a two-line heading.
   Colour, weight and the accent treatment are untouched: those come from the
   homepage system via §17. */
.tk-hero .tk-h1 { font-size: clamp(32px, 4.5vw, 58px) !important; }

/* §16c (4 Nov): 74px was a statement, but it cost the page its job. Measured
   on a 1440x800 laptop: header 74 + 92px hero pad + a 166px headline + a 112px
   lede put the tool cards at y=600, with 200px of a 480px card showing. The
   cards are what the page is for, so the headline gives ground to them —
   58px max, tighter leading, less air under each block. Cards now start at
   y=471. Nothing else about the headline changed: the Caslon accents, the
   drawn rule and the colour all still come from the homepage system. */
.tk-hero .lede { margin-bottom: 1.75rem !important; }

/* ---------------------------------------------------------------------------
   17. /tools light mode was running a cool palette
   ---------------------------------------------------------------------------
   The two stylesheet chains define the same token NAMES with different VALUES,
   and only in light. Measured:

     token          homepage (gs-site-lab.css)   /tools (site.css)
     --gs-surface   #F2ECE4  warm beige          #FAFBFC  cool near-white
     --gs-raised    #FDFCF9  warm off-white      #FFFFFF  pure white
     --gs-rule      #E8E1D8  warm linen          #E4E7EC  cool grey

   In dark the two agree exactly (#0E1018 / #1A1D2B / #ffffff17), which is why
   only light looked wrong. The cards sat in cool grey on a warm linen ground
   while every other page sat in warm on warm.

   Kyle's call: the homepage values are correct, so this page takes them. The
   homepage's own picks cards measure #FFFFFF on a #E8E1D8 hairline — that is
   what these mirror. Scoped to this page so no other legacy page shifts
   underneath us. */
.gsh[data-lab]:not([data-theme="dark"]) .tk-hero,
.gsh[data-lab]:not([data-theme="dark"]) #tools,
html:not([data-theme="dark"]) .tk-hero,
html:not([data-theme="dark"]) #tools {
  --gs-surface: #F2ECE4;
  --gs-raised:  #FDFCF9;
  --gs-rule:    #E8E1D8;
}
/* The tool cards use the homepage's card recipe verbatim, measured off
   `.lab-sh-card` on `/` in each mode. Not approximated — background, hairline
   and both shadow layers are exactly what the picks rack uses, which is what
   makes a card on this site read as raised rather than merely tinted.

     light   #FFFFFF on #E8E1D8, inset white highlight + a 2px drop
     dark    #141825 on rgba(255,255,255,.09), inset white highlight only

   The ground under them is #F7F3EE / #07080D, the same folds the homepage
   rack sits on, so the contrast is identical too.

   Radius stays at Lovable's 16px rather than the homepage's 8px: the layout
   of this page is theirs, only the surface treatment is ours. */
html:not([data-theme="dark"]) #tools .card {
  background: #FFFFFF !important;
  border: 1px solid #E8E1D8;
  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, .7),
              0 1px 2px 0 rgba(26, 29, 43, .04);
}
html[data-theme="dark"] #tools .card {
  background: #141825 !important;
  border: 1px solid rgba(255, 255, 255, .09);
  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, .05);
}

/* ---------------------------------------------------------------------------
   18. /tools hero sat too close under the header
   ---------------------------------------------------------------------------
   site.css: `.hero:has(.ctas) { margin-top: -4.6rem; padding-top: 7rem }`.
   That -73.6px pull exists because tools-home.html used to BE the homepage,
   under a header of a different height — the comment in site.css says it
   "slid the headline under the sticky header" on the pages that inherited it
   unscoped.

   Restoring `.ctas` to get the headline typography back (§12) brought the pull
   with it. Measured against the real homepage:

     /          hero top  +6px,  padding-top 74px  ->  content at 80px
     /tools     hero top -14px,  padding-top 74px  ->  content at 60px

   Correction to an earlier note: the homepage hero has a negative pull too —
   `margin-top: -72px` with `padding-top: 103.5px`. The pull is not the
   difference; the net is. Measured at 1440px with the header bottom at 74px:

     /          first hero content at 166px  ->  92px clear of the header
     /tools     first hero content at 140px  ->  66px clear

   So this neutralises the -4.6rem and sets the padding to land on the same
   92px. One number, taken off the homepage, not guessed. */
/* Tried and reverted: `min-height: calc(100svh - 150px)` with the content
   centred, copied from the homepage's `.hero-split`. That rule is why the
   homepage headline sits lower on a tall window — its content is centred in a
   930px box. But the homepage hero holds the animated desk; this one holds
   four lines. Same rule, far less content, so the leftover space is bigger:
   measured 242px of clearance against the homepage's 158px, with the tool
   cards pushed off the first screen. Matching the RULE gives a worse result
   than matching the NUMBER. */
.tk-hero { margin-top: 0 !important; padding-top: 56px !important; }
@media (width <= 560px) {
  .tk-hero { padding-top: 36px !important; }
}

/* ---------------------------------------------------------------------------
   19. The theme toggle's knob, defined once for every page
   ---------------------------------------------------------------------------
   The knob lives in two different stylesheets with two different scopes:

     gs-site-lab.css   `.tico::before`          (5 exported pages + 3 ported)
     gs-lab.css        `.gsh .tico::before`     (/games, both tool pages)
     neither           —                        (/play and the 3 game boards)

   On /games the `.gsh .tico.is-dark::before` rule matches the element, has no
   !important competing with it and sits under no media query, yet the computed
   transform stayed at translateX(0) — the knob never moved. Rather than keep
   chasing that through the cascade, the control is defined here, in the one
   stylesheet every page loads last, unscoped so it does not depend on a .gsh
   wrapper. Values are gs-site-lab.css's, unchanged.

   This makes the toggle identical on all 15 pages in both themes, which is the
   point: it is the only control in the universal header. */
.tico { position: relative; }
.tico::before {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 26px; height: 26px; border-radius: 999px;
  background: var(--gs-ink);
}
.tico.is-dark::before { transform: translateX(28px) !important; }
.tico:not(.is-dark)::before { transform: translateX(0) !important; }

/* The knob only animates once the page has settled. `data-theme` is set
   pre-paint but `is-dark` is set by JS after the DOM is parsed, so a dark-mode
   load used to show the knob slide across the track a beat after everything
   else. `gs-knob-ready` is added by section 6 of gs-static-fix.js, one frame
   later; until then the transition is off everywhere, !important because the
   lab stylesheets declare their own on `.tico::before` and `.gsh .tico::before`. */
html:not(.gs-knob-ready) .tico::before { transition: none !important; }
html.gs-knob-ready .tico::before {
  transition: transform .25s var(--gs-ease, cubic-bezier(.2,.7,.3,1));
}

/* ---------------------------------------------------------------------------
   20. Canonical surface tokens on the legacy chain
   ---------------------------------------------------------------------------
   `site.css` defines these at :root with cool-grey light values; the lab chain
   defines them on `.gsh` with the warm ones. Same names, different colours,
   light mode only — which is why /tools read cool-grey for a week and why any
   `var(--gs-surface)` written against the homepage resolves differently on
   seven pages.

   Measured on `/` with transitions disabled, and approved by Lovable as the
   canonical set:

       --gs-surface   #F2ECE4   (site.css had #FAFBFC)
       --gs-raised    #FDFCF9   (site.css had #FFFFFF)
       --gs-rule      #E8E1D8   (site.css had #E4E7EC)

   Dark already agreed exactly — #0E1018 / #1A1D2B / rgba(255,255,255,.09) —
   so only light is redefined here. Set at :root so it reaches the legacy
   pages; the lab pages define these on `.gsh`, which is more specific and
   wins for everything inside it, so they are untouched. */
:root:not([data-theme="dark"]) {
  --gs-surface: #F2ECE4;
  --gs-raised:  #FDFCF9;
  --gs-rule:    #E8E1D8;
}
/* Dark is set here too, even though site.css already agrees, because /games
   loads neither site.css nor gs-site-lab.css — it had NO :root definition for
   these at all in dark, so `var(--gs-surface)` resolved to nothing there. */
:root[data-theme="dark"] {
  --gs-surface: #0E1018;
  --gs-raised:  #1A1D2B;
  --gs-rule:    rgba(255, 255, 255, .09);
}

/* ---------------------------------------------------------------------------
   21. /tools — token pins for content outside `.gsh`
   ---------------------------------------------------------------------------
   This page's two `gsh gsh-chrome` wrappers hold the header and the footer
   only; everything between them is unwrapped. Outside `.gsh` these tokens
   resolve from site.css's `:root` — the cool-grey set — and
   `--gs-green-active` is not defined there at all. A
   `color: var(--gs-green-active)` with no definition is invalid at
   computed-value time and silently inherits, which once shipped a green
   kicker in ink.

   §20 canonicalised surface/raised/rule at `:root`. These six are pinned here
   rather than widened to `:root`, because §20's three were measured and
   approved and these have not been checked against the ten ported pages that
   also read them outside `.gsh`. Values are the lab chain's, measured on
   `.gsh` on this page.

   The tool cards themselves no longer need these: they are the homepage
   `.tl-card` component now, inside their own `.gsh[data-lab]` wrapper (see
   §26). The micro-cards in §25 still sit outside it and do. */
#tools {
  --gs-green-active: #1B6B38;
  --gs-rule-2: #D9D0C4;
  --gs-red: #C8443A;
  --gs-ink-2: #5D5348;
  --gs-ink-3: #6E655B;
  --gs-indigo: #485289;
}
html[data-theme="dark"] #tools {
  --gs-green-active: oklch(78% .16 150);
  --gs-rule-2: rgba(255,255,255,.18);
  --gs-red: #E05D52;
  --gs-ink-2: #9AA2B8;
  --gs-ink-3: #8E97AB;
  --gs-indigo: oklch(70% .13 273);
}

/* ---------------------------------------------------------------------------
   22. /about — flip-card state, card-above-form on mobile, flush footer
   ---------------------------------------------------------------------------
   Lovable's 4 Nov spec. The flip itself is gs-runtime.js:221 — one of the
   three locked files — which toggles `.is-on` and `aria-pressed` and nothing
   else. The two labels that now change with that state are written by
   gs-static-fix.js §7; everything visual is here. */

/* Kicker reads `01 // The launch`, uppercase. */
.gsh[data-lab] .ab-card-top > span:first-child {
  text-transform: uppercase; letter-spacing: .1em;
}

/* Top-right tag is now a state indicator, so it needs a stable width or the
   card's top row shifts as the word changes length. */
.gsh[data-lab] .ab-card-tag {
  min-width: 68px; text-align: center; white-space: nowrap;
}

/* Footer action, as a tactile pill. */
.gsh[data-lab] .ab-card-foot {
  align-self: flex-start;
  display: inline-flex; align-items: center; gap: .35rem;
  margin-top: auto; padding: 5px 11px;
  border: 1px solid var(--gs-rule-2); border-radius: 999px;
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--gs-indigo);
  transition: border-color .18s ease, color .18s ease;
}
.gsh[data-lab] .ab-card.is-on .ab-card-foot {
  border-color: var(--gs-green-active); color: var(--gs-green-active);
}

/* Mobile: the live card sits above the fields it is built from, so a phone
   keyboard cannot cover the thing you are editing. `.idwrap` is a single
   column below 900px, which is what makes order meaningful here. */
/* `order` only sorts siblings, and `.idform` is a GRANDCHILD of `.idwrap` —
   the grid's two children are `.idcard` and an unnamed div that holds the
   form. The spec's `order: 5` on `.idform` was inert; it goes on the child
   that actually sits in the grid. Measured: the card was still rendering
   below the form with both orders applied. */
@media (width <= 900px) {
  .gsh[data-lab] #card .idwrap > .idcard { order: 4; }
  .gsh[data-lab] #card .idwrap > :not(.idcard) { order: 5; }
}

/* Footer brand shares the page's left edge on phones. `.lft-brand` otherwise
   carries calc(var(--hd-pad-l) + 1px), which is the desktop header inset. */
@media (width <= 720px) {
  .gsh .lft-brand { padding-left: 0; }
}

/* ---------------------------------------------------------------------------
   23. Card slot baselines, first-line dot anchoring, click focus
   ---------------------------------------------------------------------------
   Lovable's 4 Nov invariant spec. Measured before applying; three of their
   specifics did not match this build, so the numbers below are ours.

   WHAT WAS ACTUALLY MISALIGNED
   /tools: at 1280 and 1440 both descriptions run 2 lines and the tag rows
   already matched to the pixel (740/740, 750/750). The break is at 1024,
   where card 01 runs THREE lines and card 02 two — the opposite way round
   from the spec, which has card 01 at one line. The CTAs were never
   misaligned at any width: `.tk-go` carries `margin-top:auto`, so flex had
   already pinned them to the bottom (781/781, 790/790, 801/801).

   /about: real and exactly as described. Card 02 runs 3 lines against 2 for
   the rest at 1280 and 1440, pushing its body text a full line down
   (1492 vs 1465). At 1024 it inverts — 01/02/03 run 3 lines and 04 runs 2.
   A 3-line envelope covers every case.

   WHY MIN-HEIGHT AND NOT SUBGRID
   Sibling grid items size their rows independently, so only `subgrid` shares
   a track between cards — and it reaches one level, while these slots sit
   two levels down inside `.tk-in`. Flattening that wrapper to use it would
   cost more than it buys. Locked envelopes expressed in line units it is.

   The class is `.tk-tags`, not the spec's `.tk-chips`. */

/* --- /tools: lock the description envelope to its worst case, 3 lines ------
   14px text at 1.55 line-height. Both tag rows then start on the same Y at
   every width, at the cost of one line of air where the copy runs short. */
/* --- /about: lock the headline envelope on desktop ------------------------
   20px at 1.35 line-height, three lines. Applied from 1024 up, where the
   grid is four columns; below that the cards stack or pair and each sets its
   own height. */
@media (width >= 1024px) {
  .gsh[data-lab] .ab-card-body b { min-height: calc(1.35em * 3); }
}

/* --- Interactive cards: keyboard ring only --------------------------------
   Invariant 3. `.ab-card` already carried a `:focus-visible` rule and no
   plain `:focus` rule, so the double ring could not be reproduced here.
   Lovable confirmed on 4 Nov that what they saw came from focus transfer
   inside their live-preview iframe, not from this build — so this rule is a
   production safeguard rather than a fix for a fault that existed on the
   static site. Keep it: it costs nothing and seals the same class of problem
   in a real browser. It cannot regress keyboard focus — `:focus-visible`
   still draws. */
#tools .tl-card:focus:not(:focus-visible),
.gsh[data-lab] .ab-card:focus:not(:focus-visible) {
  outline: none;
}

/* ---------------------------------------------------------------------------
   24. Mobile shelf accordion
   ---------------------------------------------------------------------------
   Lovable's spec, with the directory lifted from the desktop `.shf-dd`
   dropdown rather than retyped, so the two never drift and the SVGs match.

   Three notes.

   The mobile panel already lives inside a `<details>` — the hamburger itself
   is the outer disclosure and `.lab-mnav-panel` is its content. This is a
   nested disclosure, which is legal and behaves correctly: a `<summary>`
   toggles only its own `<details>`.

   `.shf-m` was already in the stylesheet
   (`.gsh .shf-m .shf-cols { grid-template-columns: 1fr }`), so the body
   carries it and the two-column directory collapses to one for free.

   The existing `.gsh .shf-*` rules are NOT scoped to `.shf-dd`, so they reach
   into this panel. Most are wanted — same component, same look. One is not:
   `.shf-cols .shf-g + .shf-g` sets a `border-left`, which in a single column
   draws a stray vertical rule down the left edge. It is reset below. */
.mn-shelf-acc { display: block; width: 100%; }

.mn-shelf-acc summary.mn-shelf {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; padding: 13px 14px; border-radius: 12px;
}
.mn-shelf-acc summary.mn-shelf::-webkit-details-marker { display: none; }
.mn-shelf-acc summary.mn-shelf::marker { content: ""; }
.mn-shelf-acc summary.mn-shelf:hover { background: var(--gs-surface); }

.mn-shelf-chevron {
  flex: none; margin-left: 10px; color: var(--gs-ink-2);
  transition: transform .2s ease;
}
.mn-shelf-acc[open] .mn-shelf-chevron { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .mn-shelf-chevron { transition: none; }
}

.mn-shelf-body {
  margin: 6px 0; padding: 6px;
  border-radius: 14px; border: 1px solid var(--gs-rule);
  background: color-mix(in srgb, var(--gs-ink) 3%, transparent);
}
/* The single-column collapse comes from .shf-m; this kills the column rule
   that only makes sense side by side, and restores it as a row divider. */
.mn-shelf-body .shf-cols .shf-g + .shf-g { border-left: 0; }
.mn-shelf-body .shf-g + .shf-g,
.mn-shelf-body .shf-cols + .shf-cols { border-top: 1px solid var(--gs-rule); }
.mn-shelf-body .shf-g { padding: 10px 8px; }

/* The directory's own `.shf-i` / `.shf-h` styling carries over from the
   dropdown, which is the point. Only the full-page link needs re-seating,
   since it loses its `.shf-allw` wrapper here. */
/* --- Step 1, approved 5 Nov: drawer polish --------------------------------

   1. Items lay out as a row with the arrow flush right.

   Implemented as a two-column grid rather than the spec's literal
   `display: flex; justify-content: space-between`. The item has THREE
   children — `<b>`, `<small>` and the arrow `<svg>` — so a flex row puts the
   description beside the title instead of under it. The grid keeps the text
   stacked in column one and pins the arrow in column two, vertically centred,
   which is the stated intent: arrows flush right, never orphaned onto a third
   line. The inherited `.gsh .shf-i { display: grid }` from the desktop
   dropdown is what stacked them in the first place. */
.mn-shelf-body .shf-i {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  column-gap: .75rem;
  row-gap: 2px;
}
.mn-shelf-body .shf-i > b,
.mn-shelf-body .shf-i > small { grid-column: 1; }
.mn-shelf-body .shf-i > .gs-arr {
  grid-column: 2;
  grid-row: 1 / span 2;      /* NOT `1 / -1`: with no explicit rows declared,
                                -1 resolves to the end of the EXPLICIT grid,
                                which is row 1 — the arrow then pinned to the
                                title line and sat 18px above centre on
                                two-line items. Measured. */
  align-self: center;
  margin-left: 0;            /* the lifted SVG carries an inline .55em left
                                margin for inline flow; the grid column owns
                                the spacing here. */
}

/* 2. Group heads are pure mono — no number box.
   `.gsh .shf-h b` gave `03` a filled, rounded, padded chip: a pill wrapper in
   a navigation drawer, which invariant 2 bans outright. The number stays, as
   type. */
.mn-shelf-body .shf-h b,
.gsh[data-theme="dark"] .mn-shelf-body .shf-h b {
  background: none;
  border-radius: 0;
  padding: 0;
  margin-right: .5ch;
  color: var(--gs-ink-3);
}

/* 3. Group 03 keeps its title and status on one line.
   "03 The spaces // Coming soon" ran to two lines at 375px. The head is a
   flex row, so the fix is to let it shrink rather than wrap: the status tag
   stops being a flex item that forces a break, and the tracking eases off
   just on phones. Measured below 420px only; the desktop dropdown that shares
   these classes is untouched. */
@media (width <= 420px) {
  .mn-shelf-body .shf-h {
    flex-wrap: nowrap;
    letter-spacing: .04em;
    gap: 5px;
  }
  .mn-shelf-body .shf-h .shf-cs { flex: none; letter-spacing: .04em; }
}

/* 4. "Explore the full shelf page" — legible on its gradient.

   Lovable reported dark-on-dark here and our first verification pass said it
   did not reproduce. **They were right and we were wrong.** The check read
   `backgroundColor`, which is transparent: the button is painted by a
   `linear-gradient` living in `backgroundImage`. Measured properly, both
   themes were wrong, in opposite directions:

     light:  ink  #1A1D2B  on indigo -> dark green  (both ends dark)
     dark:   near-white    on oklch .70/.74         (both ends light)

   The desktop `.shf-dd .shf-all` has always had this right — white on the
   dark gradient, near-black on the light one. This mirrors it. The gradient
   itself is untouched.

   The same trap cost time on `.tl-go` earlier: a transparent
   `backgroundColor` does not mean a transparent background. */
/* The button paints its gradient but its colour came out INHERITED ink: the
   upstream rule is `color: var(--gs-on-accent)`, and that token is not
   defined on the five exported pages — an undefined var makes the whole
   declaration invalid at computed-value time, so `color` silently falls back
   to the inherited value. Defining the token here fixes the upstream rule
   rather than fighting it, and the explicit colour covers the arrow too. */
.mn-shelf-body { --gs-on-accent: #FFFFFF; }
.gsh[data-theme="dark"] .mn-shelf-body { --gs-on-accent: #07080D; }
.mn-shelf-body a.shf-all,
.mn-shelf-body a.shf-all .gs-arr { color: var(--gs-on-accent); }

.mn-shelf-body .shf-allw { margin-top: 6px; }
.mn-shelf-body .shf-all {
  display: flex; align-items: center; justify-content: space-between;
  padding: 11px 12px; border-radius: 0 0 8px 8px;
}

/* The drawer has to scroll itself now that it can hold the whole directory.
   Measured on a 360x640 Android with the accordion open: the panel ran 916px
   tall and put "Get in touch" at y=975. It is `position: absolute` inside a
   `position: sticky` header, so it travels with the viewport — scrolling the
   page moved the CTA not one pixel. The primary CTA was unreachable on every
   phone size tested (640, 812 and 844 tall).

   Capping the panel and letting it scroll internally is the standard drawer
   behaviour and keeps About and the CTA reachable at any height. `svh` so a
   mobile browser's collapsing toolbar cannot clip it; `vh` first for anything
   that does not know `svh`. `overscroll-behavior` stops the page behind the
   drawer from scrolling when the list hits its end. */
.lab-mnav-panel {
  max-height: calc(100vh - 84px);
  max-height: calc(100svh - 84px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ---------------------------------------------------------------------------
   25. /tools — the Operator Baseline spec strip
   ---------------------------------------------------------------------------
   Lovable's 4-bay structure and copy, answering our question about whether
   the proof points still earned their place under the richer tool cards:
   keep them, make them authoritative.

   Their spec arrived with the structure and copy only — it ends after the
   markup block with no styling section. Rather than invent a different look,
   this carries the established card language across to the new class names:
   raised ground, hairline border, 12px radius against the tool cards' 16px,
   a green pip, a mono kicker and sentence-case copy. Swap in their treatment
   when the rest of the spec lands.

   `.tk-proof` in their spec no longer exists — it became micro-cards in §51
   and is now this. `.pip`, `.tk-specs` and `.tk-spec-*` were all free.

   Slot envelopes are locked on desktop per the §23 invariant, so the four
   bays align across the strip; fluid below 1024 where they pair or stack. */
#tools .tk-specs {
  display: grid; gap: .85rem; max-width: 1040px; margin: 2.5rem auto 2rem;
  grid-template-columns: 1fr;                      /* < 720px: stacked */
}
@media (720px <= width <= 1023px) {
  #tools .tk-specs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (width >= 1024px) {
  #tools .tk-specs { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  #tools .tk-spec-kicker { min-height: 1em; }
  #tools .tk-spec-val { min-height: calc(1.45em * 3); }   /* worst case */
}

/* The Acquret callout follows immediately in the DOM and carries no top
   margin of its own, so the two blocks sat flush — 0px at every width. */

#tools .tk-spec-card {
  padding: .95rem 1rem 1.05rem;
  border-radius: 12px;
  border: 1px solid var(--gs-rule);
  background: var(--gs-raised);
  transition: transform .15s ease, border-color .15s ease;
}
#tools .tk-spec-card:hover {
  transform: translateY(-2px); border-color: var(--gs-rule-2);
}
@media (prefers-reduced-motion: reduce) {
  #tools .tk-spec-card { transition: none; }
  #tools .tk-spec-card:hover { transform: none; }
}

#tools .tk-spec-kicker {
  display: flex; align-items: center; gap: .5rem; margin-bottom: .6rem;
  font: 500 10.5px/1 var(--gs-mono); letter-spacing: .12em;
  text-transform: uppercase; color: var(--gs-ink-3);
}
#tools .tk-spec-kicker .pip {
  width: 5px; height: 5px; border-radius: 50%; flex: none;
  background: var(--gs-green-active);
}
/* Sentence case, never all-caps — this is the copy, not a label. */
#tools .tk-spec-val {
  margin: 0; color: var(--gs-ink-2);
  font: 400 13.5px/1.45 var(--gs-ui);
  text-wrap: balance;
}

/* ---------------------------------------------------------------------------
   26. The tool-card band: full-bleed carousel on phones
   ---------------------------------------------------------------------------
   `.tl-grid` already carries the homepage carousel — `overflow-x: auto`,
   `scroll-snap-type: x mandatory`, and a -20px/+20px margin-padding pair that
   lets the first card sit flush to the screen edge. It was not reading that
   way on /tools because of one extra gutter: this page's outer
   `<section id="tools">` carries an inline `padding: 0 1rem`, so the `.wrap`
   inside it measured 343px where the homepage's measures the full 375.
   Cards came out 261px against the homepage's 288, boxed in rather than
   running off the edge.

   Cancelling that 1rem for the card band only — the micro-cards and the
   Acquret callout below still want it. At desktop this changes nothing
   visible: `.wrap` stays centred at its 1040px measure. */
/* Only in the carousel band. `.tl-sec .tl-grid` becomes a snap scroller at
   <=759px and returns to two columns from 720px, so the bleed belongs below
   719. Above it, cancelling the gutter would widen `.wrap` to 1072px and
   break the approved 1040 measure the hero and micro-cards share — measured,
   after the first attempt did exactly that. */
@media (width <= 719px) {
  #tools .tk-toolwrap { margin-inline: -1rem; }
}

/* The band sets its own rhythm; the section padding above it can go. */
#tools .tk-toolwrap .tl-sec { padding-top: 16px !important; }

/* `.gsh` is a PAGE SHELL, not a band. Alongside its tokens it carries
   `min-height: 100vh`, `background: var(--gs-ground)`, a font-family and a
   colour transition — fine on the five exported pages, where it wraps the
   whole document, and wrong here, where it wraps two cards in the middle of
   a page. Measured: the wrapper stood 1000px tall against 501px of content,
   painting ~499px of empty white between the cards and the micro-cards, and
   it tracked the viewport (600px tall in a 600px window).

   Only the shell behaviour is cancelled. The tokens, the colour and the font
   are the whole reason for wrapping, so they stay. */
#tools .tk-toolwrap,
.tk-mqwrap {
  min-height: 0;
  background: transparent;
}

/* The marquee needs the same wrapper for a different reason: every rule for
   it is `.gsh .marquee…`, and on this page it was sitting directly on <body>.
   It rendered as one unstyled run-on line — no overflow clip, no flex track,
   **no animation**. The earlier check confirmed the strip was present and
   above the footer and never asked whether it was styled, which is how a dead
   component passed verification. Presence is not rendering. */

/* The card band shares the page's left edge from tablet up. `.wrap` carries
   20px of side padding, which on the homepage is what insets the cards — but
   there the headline is centred in a wider 1180px wrap, so nothing lines up
   against them. Here the hero, the micro-cards and the Acquret callout all
   sit on the 1040 measure, and the cards were landing 20px inside it. Measured
   at 1440: cards at x=220 against everything else at x=200.

   Dropped from 720px up only — below that the same padding is what holds the
   first card 20px off the screen edge in the carousel, exactly as on the
   homepage. */
@media (width >= 720px) {
  #tools .tk-toolwrap .wrap { padding-inline: 0; }
}

/* ---------------------------------------------------------------------------
   27. Phase 2 Step 2 — /games
   ---------------------------------------------------------------------------
   Persona order, kickers, headline token and CTA. The markup changes are in
   `launch-build/games/index.html`; these carry the type and the treatment.

   The seat used to live in `.ib-chip`, a pill beside the number. Invariant 2
   bans pill wrappers on kickers, so the seat moved into the kicker line
   itself — `01 // Marketing // Felix Funnel` — and the chip element is gone
   from the markup entirely rather than hidden. `.ib-ts` stays: a duration is
   a value, not a label. */
/* The floating dot pip. `gs-pages.css:64` draws a 6px persona-coloured circle
   before every kicker via `.ib-id::before`. That is precisely what invariant
   2 bans, and removing `.ib-chip` left it as the last one on the page — it
   survived our first pass because we were looking for pill *wrappers* and
   this is a pseudo-element. The persona is named in the kicker text now, so
   nothing is lost but the dot. */
.gh-grid .ib-id::before { content: none; }

#games-grid .ib-id, .gh-grid .ib-id {
  font: 500 10.5px/1 var(--gs-mono);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--gs-ink-3);
}

/* Phones: the four games become a horizontal snap carousel.

   Kyle's call, and it matches what the tool cards already do on `/tools` and
   the homepage — one pattern for card rows on a phone rather than two. A
   stack of four tall cards is a long scroll past three games to reach the
   fourth; a carousel puts them one swipe apart.

   `.gsh .gh-grid` is a one-column grid below 760px, so this switches it to
   `grid-auto-flow: column` with 86% columns and x-snap. The breakpoint is
   759px because that is where gs-pages.css already drops to one column —
   reusing it rather than inventing a second boundary.

   The grid lives inside the `.lw` window-frame mock, whose `.lw-body` carries
   16px of padding. The negative margin and matching padding let the first
   card sit flush to the frame's inner edge and the last one scroll fully into
   view, the same -pad/+pad pair the tool carousel uses against the viewport. */
@media (width <= 759px) {
  .gsh .gh-grid {
    grid-auto-flow: column;
    grid-template-columns: none;
    grid-auto-columns: 86%;
    gap: 12px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    margin-inline: -16px;
    padding: 0 16px 12px;
  }
  .gsh .gh-grid::-webkit-scrollbar { display: none; }
  .gsh .gh-grid > .ib { scroll-snap-align: start; }
}

/* Phones: the kicker gets the row to itself.

   `.ib-meta` is `flex-wrap: nowrap`, so at 375px the kicker was squeezed into
   204px of a 259px row by the `≈ 1 min` beside it, and wrapped mid-phrase —
   "01 // MARKETING // FELIX / FUNNEL", with the last word orphaned on its own
   line. Three of the four did it. Card 04 is 42 characters and cannot fit one
   line on a phone at any sensible size, so the answer is a deliberate break
   rather than a tighter squeeze: full width, eased tracking, and
   `text-wrap: balance` so the two lines come out even. The duration moves
   under it, right-aligned, instead of competing for the first line. */
@media (width <= 759px) {
  .gh-grid .ib-meta { flex-wrap: wrap; row-gap: 4px; align-items: baseline; }
  .gh-grid .ib-id {
    flex: 1 1 100%;
    letter-spacing: .08em;
    text-wrap: balance;
  }
  .gh-grid .ib-ts { margin-left: auto; }
}

/* In the carousel the cards sit side by side, so the peeking neighbour makes
   a mismatched kicker height visible in a way the old vertical stack never
   did. Cards 01 and 04 run two lines at phone widths while 02 and 03 run one.
   Floored to two lines below 560px, where the wrap actually happens — by
   759px the card is 585px wide and all four fit one line, so no floor there. */
@media (width <= 560px) {
  .gh-grid .ib-id { display: block; min-height: calc(1em * 2); }
  /* The persona line under the name wraps in some cards and not others —
     "10 dashboards before the 9 AM" against "12 form fills in the inbox" —
     which showed up at 390px as a one-line offset on the title below. Same
     floor, same reason. */
  .gh-grid .ib-who small { display: block; min-height: calc(1.35em * 2); }
}

/* Slot envelope for the kicker, per the §23 invariant.

   "04 // Leadership & Finance // Rohan Runway" is the longest of the four and
   wraps to two lines from about 900px down, with Felix's joining it at 768.
   The CTA is bottom-anchored so it stayed aligned, but everything between the
   kicker and it — avatar, name, title, description — sat 10px lower in the
   wrapped cards than in their row partners. Measured kicker heights at 768:
   [21, 11, 11, 21].

   Locked across the two-column band only. Above 1000px all four run one line
   and the floor costs nothing; below 720 the cards are a single column and
   there is nothing to align against. */
@media (720px <= width <= 1000px) {
  .gh-grid .ib-id { display: block; min-height: calc(1em * 2); }
}

/* The headline carried `em.g`, which is green. Invariant 3 reserves green for
   pips, confirmations and mono slashes — headlines are ink with Caslon
   italic. The class is now `.ink`; the italic and the face are untouched. */
.pg-h em.ink {
  color: inherit;   /* NOT var(--gs-ink): the dark headline is #E8EAF0, not
                       the ink token, so pinning the token made the accent 5
                       units brighter than its own headline. Inheriting is
                       what "same ink as the headline" actually means, and it
                       survives any future change to the heading colour. */
  font-family: var(--gs-serif, "Libre Caslon Text", Georgia, serif);
  font-style: italic;
}

/* "Play →" was a bare text link with a unicode arrow. Now a tactile operator
   pill with the site's thin-stroke SVG, which also settles invariant 7: no
   glyph arrow left in a CTA on this page. */
.gh-grid .ib-go {
  display: inline-flex; align-items: center; gap: .45rem;
  margin-top: .2rem; padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--gs-rule-2);
  background: var(--gs-raised);
  color: var(--gs-ink);
  font: 700 12.5px/1 var(--gs-head);
  letter-spacing: .01em;
  transition: background-color .15s ease, border-color .15s ease, transform .15s ease;
}
.gh-grid .ib:hover .ib-go {
  border-color: var(--gs-green-active);
  color: var(--gs-green-active);
  transform: translateY(-1px);
}
.gh-grid .ib-go .gs-arr { flex: none; transition: transform .15s ease; }
.gh-grid .ib:hover .ib-go .gs-arr { transform: translate(2px, -2px); }
@media (prefers-reduced-motion: reduce) {
  .gh-grid .ib-go, .gh-grid .ib-go .gs-arr { transition: none; }
  .gh-grid .ib:hover .ib-go,
  .gh-grid .ib:hover .ib-go .gs-arr { transform: none; }
}

/* ---------------------------------------------------------------------------
   28. Phase 2 Step 3 — /playlists (Downtime Hub)
   ---------------------------------------------------------------------------
   Lovable's spec names a `.dl-` chassis to port. **It does not exist in this
   build** — the tiers are `.dt-grp` and the cards `.pl-card` / `.pl-grid`,
   and their `.dl-why` editorial pull-quote is our `.pl-why`, already live on
   the Films tier. So this is not a port: it is their treatment applied to the
   classes that are actually here. Same outcome, our names. */

/* --- 1. Typographic kickers: `01 // SOUNDTRACKS` -------------------------
   The number sat alone in `.dt-grp-n` with the title as a bare text node, so
   a `//` added inline would wrap away from its number on a narrow screen —
   exactly the orphan the spec warns about. The slash is its own element
   inside the number span, which makes `01 //` a single unbreakable unit and
   lets the slashes take the green without touching the title. */
.gsh .dt-grp-n {
  /* The chip itself is removed at source, where it was added — see the
     `.gsh[data-lab] .dt-grp-n` block above. This only keeps `01 //` from
     ever breaking between the number and its slashes. */
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* The head is a wrapping flex row, so the title is a bare text node competing
   with the number for lines. Block display makes number and title one flow,
   and the title wraps under itself rather than the number standing alone. */
.gsh .dt-grp-h { display: block; }
.gsh .dt-slash {
  font-style: normal;
  color: var(--gs-green-active, #1B6B38);
  margin: 0 .5ch 0 .45ch;
  letter-spacing: .06em;
}
.gsh[data-theme="dark"] .dt-slash { color: var(--gs-green, #35BD6B); }
/* Below 720 the head stays a single flow — number, slashes and title together
   — so "01 //" can never sit on a line of its own above the title. */
@media (width <= 719px) {
  .gsh .dt-grp-h { text-wrap: balance; }
  .gsh .dt-grp-n { margin-right: .15ch; }
}

/* --- 2. A hairline accent per tier ---------------------------------------
   Tone tokens already exist per tier (`--tone-ink`): green on Soundtracks,
   blue on Podcasts, ochre on Films, coral on Teardowns. The hairline reads
   from the same token, so the accent cannot drift from the cards beneath it.

   NOTE: the spec asks for GREY on Films. Ours is ochre and has been since the
   page was built — left as is pending Lovable's call, since changing it
   silently would break the tier's relationship with its own cards. */
.gsh .dt-grp { --tier-accent: var(--gs-rule-2); }
.gsh .dt-grp:has(.pl-tone-green)  { --tier-accent: #1B6B38; }
.gsh .dt-grp:has(.pl-tone-blue)   { --tier-accent: #1E3456; }
.gsh .dt-grp:has(.pl-tone-ochre)  { --tier-accent: #6B4412; }
.gsh .dt-grp:has(.pl-tone-coral)  { --tier-accent: #7F3025; }
.gsh[data-theme="dark"] .dt-grp:has(.pl-tone-green) { --tier-accent: #4ADE80; }
.gsh[data-theme="dark"] .dt-grp:has(.pl-tone-blue)  { --tier-accent: #60A5FA; }
.gsh[data-theme="dark"] .dt-grp:has(.pl-tone-ochre) { --tier-accent: #FBBF24; }
.gsh[data-theme="dark"] .dt-grp:has(.pl-tone-coral) { --tier-accent: #FB7185; }
.gsh .dt-grp-h {
  border-top: 1px solid var(--tier-accent);
  padding-top: .85rem;
}

/* --- 3. Kyle's pick: dashed chassis, "Dropping next" ---------------------
   Still unlinked — a `<div>`, no href, no dummy URL, per the approved
   decision that the rotating slot carries no Open affordance. */
.gsh .pl-card.pl-next {
  border-style: dashed;
  border-color: var(--tier-accent, var(--gs-rule-2));
  background: transparent;
}
.gsh .pl-card.pl-next .pl-status {
  font-style: normal;
  margin-left: .6ch;
  padding-left: .6ch;
  border-left: 1px solid var(--gs-rule-2);
  color: var(--gs-ink-2);
}

/* --- 4. Per-tier carousel on phones --------------------------------------
   Same pattern locked on /tools and /games: snap scroller below 720, one
   swipe per card, page itself never scrolls sideways. `.pl-grid` sits inside
   `.wrap`, which carries the page gutter, so the bleed is the usual
   -gutter/+gutter pair. */
@media (width <= 719px) {
  .gsh .pl-grid {
    display: grid;
    grid-auto-flow: column;
    grid-template-columns: none;
    grid-auto-columns: 82%;
    gap: 12px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    margin-inline: -20px;
    padding: 0 20px 12px;
  }
  .gsh .pl-grid::-webkit-scrollbar { display: none; }
  .gsh .pl-grid > .pl-card { scroll-snap-align: start; }
}

/* --- 5. Anchor clearance for /films --------------------------------------
   `/films` now 301s to `/playlists#films`, so the Films tier needs the same
   sticky-header clearance the other in-page anchors use. */
.gsh #films, .gsh #watch { scroll-margin-top: calc(var(--hd-h, 72px) + 1.5rem); }

/* ---------------------------------------------------------------------------
   29. Phase 2 Step 4 — /shelf directory
   ---------------------------------------------------------------------------
   The page ran a 7-bucket A–G taxonomy (toolkit / watch / listen / read /
   join / spaces / teardowns) with a lettered key chip on every heading and
   jump link. It is now five groups matching the header drawer, numbered and
   slashed like every other kicker on the site.

   Five, not four. Lovable's four groups had no home for the Read bucket
   (Lenny's Newsletter, SaaStr) or the Join bucket (Exit Five, RevGenius) —
   four curated external picks that exist nowhere else on the site, and the
   targets of two live "Explore all" links on the homepage. Kyle's call: keep
   them as `05 // Reading & rooms`. Those two homepage links now point at
   `#reading`.

   Watch and Listen were retired without loss: their items (Acquret, Y
   Combinator, Lenny's Podcast) are already on /playlists, so the Downtime
   group links to that page instead of duplicating its contents.

   `lab-sh-key` — the lettered chip — is gone from the markup entirely. */
.gsh .shp-n {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: var(--gs-ink-3);
}
.gsh .shp-slash {
  font-style: normal;
  color: var(--gs-green-active, #1B6B38);
  margin: 0 .5ch 0 .45ch;
  letter-spacing: .06em;
}
.gsh[data-theme="dark"] .shp-slash { color: var(--gs-green, #35BD6B); }

/* Group headings: mono number and slashes, then the name in the heading face.
   No chip, no pip — the number is type, like every other kicker on the site. */
.gsh .shp-sec-head h2 .shp-n {
  font: 500 11px/1 var(--gs-mono);
  letter-spacing: .1em;
  text-transform: uppercase;
}

/* The jump bar was a row of lettered pills. Now typographic anchors: the
   number and slashes in green mono, the name in the UI face, nothing boxed. */
/* `gs-site-lab.css:3928` is `.gsh[data-lab] .shp-jump a` — a bordered, filled
   pill at specificity (0,3,1). That file is locked byte-identical, so this
   matches its specificity and wins on source order rather than being edited
   there. A plain `.gsh .shp-jump a` (0,2,1) lost and changed nothing. */
.gsh[data-lab] .shp-jump a {
  display: inline-flex;
  align-items: baseline;
  gap: .1rem;
  border: 0;
  background: none;
  border-radius: 0;
  padding: 6px 0;
  margin-right: 1.4rem;
}
.gsh[data-lab] .shp-jump a .shp-n {
  font: 500 10.5px/1 var(--gs-mono);
  letter-spacing: .1em;
  text-transform: uppercase;
}
.gsh[data-lab] .shp-jump a:hover .shp-slash,
.gsh[data-lab] .shp-jump a:focus-visible .shp-slash { opacity: .7; }

/* Anchor clearance under the sticky header, same as /playlists. */
.gsh #kit, .gsh #downtime, .gsh #spaces, .gsh #stealth, .gsh #reading {
  scroll-margin-top: calc(var(--hd-h, 72px) + 1.5rem);
}

/* ---------------------------------------------------------------------------
   30. Phase 2 Step 5 — /quick-q
   ---------------------------------------------------------------------------
   Two classes in the spec do not exist here. `.qq-who i` and
   `.qq-reply .qq-lab i` were named as the homes of the green pips; the actual
   pip was `.qq-pill > i.qq-dot`, a 6px persona-coloured circle inside a
   bracketed pill on the card's top bar. Both are now gone from the markup —
   the dot deleted, the pill unwrapped — and the name `.qq-who` adopted for
   the operator span so our markup matches their vocabulary from here.

   `.qq-sys` does not exist either; the system block is `.qq-block` with a
   `.qq-lead` lede, already Caslon italic. */

/* Top bar: DISPATCH #001 · 11:42 AM // FELIX FUNNEL · MARKETING */
.gsh[data-lab] .qq-top {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 .45rem;
}
.gsh[data-lab] .qq-who { color: var(--gs-ink-2); }
.gsh[data-lab] .qq-who i,
.gsh[data-lab] .qq-dispatch i {
  font-style: normal; margin: 0 .4ch; color: var(--gs-ink-3);
}
.gsh[data-lab] .qq-slash {
  font-style: normal;
  color: var(--gs-green-active, #1B6B38);
  letter-spacing: .06em;
}
.gsh[data-theme="dark"][data-lab] .qq-slash { color: var(--gs-green, #35BD6B); }
/* The bracket ornaments went with the pill. */
.gsh[data-lab] .qq-pill::before,
.gsh[data-lab] .qq-pill::after { content: none; }

/* Filter tabs: typographic, not bulky pills. The active state carries by
   weight and an underline rule rather than a filled block, so the row reads
   as navigation rather than four buttons. Specificity matches
   `.gsh[data-lab] .qq-tab` in the locked sheet — a lower one would lose
   silently, as the /shelf jump bar did. */
.gsh[data-lab] .qq-tab {
  border: 0; background: none; border-radius: 0;
  padding: 6px 0; margin-right: 1.3rem;
  display: inline-flex; align-items: baseline; gap: .45rem;
  font: 500 11px/1 var(--gs-mono);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--gs-ink-3);
  border-bottom: 1px solid transparent;
}
.gsh[data-lab] .qq-tab:hover { color: var(--gs-ink); border-bottom-color: var(--gs-rule-2); }
.gsh[data-lab] .qq-tab.is-on {
  background: none; color: var(--gs-ink);
  border-bottom-color: var(--gs-green-active, #1B6B38);
}
.gsh[data-lab] .qq-tab.is-on i { color: var(--gs-ink-2); opacity: 1; }
/* The seat sits in an <i> straight after the name. In a single mono uppercase
   run the two read as one string — "FELIX FUNNEL MARKETING" — so the seat
   takes the same `·` separator the card top bar uses, and stays a step
   quieter than the name. */
.gsh[data-lab] .qq-tab i {
  font: 500 11px/1 var(--gs-mono);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--gs-ink-3);
}
.gsh[data-lab] .qq-tab i::before {
  content: "\00b7"; margin-right: .45rem; opacity: .75;
}
.gsh[data-theme="dark"][data-lab] .qq-tab.is-on {
  border-bottom-color: var(--gs-green, #35BD6B);
}

/* Action CTAs: tactile pills, not hollow wireframes. */
.gsh[data-lab] .qq-btn {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: 9px 16px; border-radius: 999px;
  border: 1px solid var(--gs-rule-2);
  background: var(--gs-raised);
  color: var(--gs-ink);
  font: 700 12.5px/1 var(--gs-head);
  transition: border-color .15s ease, color .15s ease, transform .15s ease;
}
.gsh[data-lab] .qq-btn:hover {
  opacity: 1;
  border-color: var(--gs-green-active, #1B6B38);
  color: var(--gs-green-active, #1B6B38);
  transform: translateY(-1px);
}
.gsh[data-lab] .qq-btn.is-done {
  border-color: var(--gs-green-active, #1B6B38);
  color: var(--gs-green-active, #1B6B38);
}
@media (prefers-reduced-motion: reduce) {
  .gsh[data-lab] .qq-btn { transition: none; }
  .gsh[data-lab] .qq-btn:hover { transform: none; }
}

/* Cards size to their content — no fixed heights, so nothing clips or
   truncates when one dispatch runs longer than its row partner. */
.gsh[data-lab] .qq-card { height: auto; }
.gsh[data-lab] .qq-text { overflow: visible; text-overflow: clip; }

/* ---------------------------------------------------------------------------
   31. Thin-stroke icons replace unicode glyphs (5 Nov)
   ---------------------------------------------------------------------------
   Locked with Lovable: U+2714 (heavy check) and U+2197 (NE arrow) carry a
   default emoji presentation and render as colour blocks on iOS; the rest
   vary by platform. `.gs-ic` matches `.gs-arr`'s weight so a tick beside an
   arrow reads as one family.

   Not converted, deliberately: the `×` in "GrowthSanity × Acquret" and the
   `→` in "Visit → lead CVR" are typography, not icons — an SVG there would
   break the phrase. And the directional labels inside the game zones are read
   by the engines via textContent, so they stay text under the
   engines-untouched invariant. */
.gs-ic {
  display: inline-block;
  flex: none;
  vertical-align: -0.08em;
  margin-left: 0.4em;
}
.gs-ic-left { margin-left: 0; margin-right: 0.4em; }

/* ---------------------------------------------------------------------------
   32. The shared Acquret dock
   ---------------------------------------------------------------------------
   One component on /tools, /quick-q, /games and /playlists, replacing the
   inline-styled callout that lived only on /tools. The inline rules had to
   come out first — they beat any stylesheet, so a shared class could never
   have applied while they were there.

   Each page passes its own `?ref=growthsanity-<page>-callout`, which is the
   only thing that differs between instances. */
.gs-dock {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 1.5rem;
  max-width: 1040px; margin: 2rem auto 0;
  padding: 2rem 2.25rem;
  border-radius: 16px;
  border: 1px solid var(--gs-rule);
  background: var(--gs-surface);
}
.gs-dock-copy { min-width: 260px; max-width: 580px; }
.gs-dock-h {
  margin: 0 0 .4rem; color: var(--gs-ink);
  font: 700 19px/1.3 var(--gs-head); letter-spacing: -.02em;
}
.gs-dock-p {
  margin: 0; color: var(--gs-ink-2);
  font: 400 14px/1.55 var(--gs-ui);
}
/* `.gsh a { color: inherit }` in the locked gs-site-lab.css is (0,1,1) and
   beat a bare `.gs-dock-cta` at (0,1,0), so the label took `--gs-ink` on the
   same `--gs-ink` ground. Measured contrast 1.0 — identical colour and
   background — on /quick-q, /games and /playlists, in BOTH themes. /tools
   escaped only because its dock sits outside `.gsh`.

   `.gsh .gs-dock-cta` is (0,2,0), which beats (0,1,1) on its own. No
   `!important` needed: raising specificity is the fix, and an `!important`
   here would be a hammer that hides the next collision instead of losing to
   it visibly. */
.gsh .gs-dock-cta,
.gs-dock-cta {
  display: inline-flex; align-items: center; white-space: nowrap;
  padding: .75rem 1.4rem; border-radius: 999px;
  background: var(--gs-ink); color: var(--gs-ground);
  font: 700 13px/1 var(--gs-head); letter-spacing: .02em;
  text-decoration: none;
  transition: transform .15s ease, opacity .15s ease;
}
.gsh .gs-dock-cta:hover,
.gs-dock-cta:hover { transform: translateY(-1px); opacity: .92; }
@media (prefers-reduced-motion: reduce) {
  .gs-dock-cta { transition: none; }
  .gs-dock-cta:hover { transform: none; }
}
@media (width <= 560px) {
  .gs-dock { padding: 1.5rem 1.25rem; }
  .gs-dock-cta { width: 100%; justify-content: center; }
}

/* Film years sit quieter than the title they qualify. */
.gsh .pl-yr { color: var(--gs-ink-3); font-weight: 400; }
/* The nomination card is a prompt, not a destination — dashed like Kyle's
   pick, so "unlinked standby" and "send us something" read as the same
   species rather than two different kinds of dead card. */
.gsh .pl-card.pl-nom { border-style: dashed; background: transparent; }
.gsh .pl-card.pl-nom .pl-nom-go { color: var(--gs-ink); }

/* ---------------------------------------------------------------------------
   33. /quick-q — compact card anatomy
   ---------------------------------------------------------------------------
   Lovable asked for cards "~38% shorter". Measured first: at 375px a card is
   670px, and the body copy alone accounts for 314px of it — the subtext
   paragraph is 180px, the reply text 134px. Chrome (padding, gaps, labels,
   the watermark) is about 190px.

   So 38% — a 255px cut — cannot come from density. Even zeroing every pad
   and gap would not reach it. The remaining distance is copy length, which
   is Kyle's call, not a CSS one.

   What this does is take out the slack: tighter leading, a half-step smaller
   body, and every pad, gap and label margin trimmed. Measured result is
   reported honestly in the changelog rather than rounded up to the brief. */

/* Body copy: 1.6 leading to 1.45, 14px to 13.5. The two long paragraphs are
   where the height actually is. */
.gsh[data-lab] .qq-block p,
.gsh[data-lab] .qq-text {
  font-size: 13.5px;
  line-height: 1.45;
}
.gsh[data-lab] .qq-ping blockquote p { line-height: 1.3; }

/* Chrome: every gap and pad down a step. */
.gsh[data-lab] .qq-card { padding: 14px; gap: 10px; }
.gsh[data-lab] .qq-top { padding-bottom: 8px; }
.gsh[data-lab] .qq-lbl { margin-bottom: 5px; }
.gsh[data-lab] .qq-ping blockquote { padding: 11px 13px; }
.gsh[data-lab] .qq-reply { padding: 11px 13px; }
.gsh[data-lab] .qq-acts { margin-top: 9px; }
.gsh[data-lab] .qq-btn { padding: 7px 14px; }
.gsh[data-lab] .qq-mark { padding-top: 8px; }

/* ---------------------------------------------------------------------------
   34. Homepage hero — desk props hold Lovable's Felix @ 10 AM geometry
   ---------------------------------------------------------------------------
   Kyle: the cards should look messy but be messy in the SAME places, and
   Felix @ 10 AM on desktop is the reference to replicate. Lovable's design
   stands — only the alignment needed to become uniform.

   Measured all 4 personas x 4 time stops before changing anything. The drift
   has three causes, all emergent from the locked gs-site-lab.css meeting copy
   of different lengths:

   1. `.lab-flank` is a fixed-height flex column with
      `justify-content: space-between`. The first card pins to the top, the
      last pins to the BOTTOM, and the middle one floats in the remainder, so
      one extra wrapped line moves the two cards below it. Measured: `lab-dm`
      top ran 568 -> 608.1 while its bottom edge stayed at 682.5 in every
      state — bottom-pinned, confirmed. `lab-invite` 456.3 -> 479.9,
      `lab-ticket` 566.9 -> 581.8.
   2. Cards are shrink-to-fit, so width tracked copy length (158.4 -> 240.7).
      `lab-meet` sits in a `flex-end` column, so a narrower card pushes its
      LEFT edge right — 161.8 -> 216.0, a 54px jump, right edge constant.
   3. Height itself (74.3 -> 122.1) feeding back into (1).

   Fix: pin each prop to the geometry Lovable gives it at Felix @ 10 AM —
   its own width, its own horizontal anchor, its own top — so every persona
   and every time stop inherits that one arrangement. The props go
   `position: absolute` within the flank; `--x` / `--y` / `--r` are transforms
   and still paint on top untouched, so the deliberate mess is unchanged.

   Widths are per slot and NOT uniform, because Lovable's varied widths are
   the design: at >=1321px the reference measures meet 215, invite 220, dm
   236, quirk 192, sticky 190, ticket 236 against a 236px flank. They are
   written as percentages of the flank so they scale with it.

   SUPERSEDED 7 Nov, twice over, and left here because the reasoning below
   is still what the rules are arguing against. This originally ran two
   bands, mirroring the locked file's two compositions (236px flank from
   1321px, 210px flank at 1101-1320 with flank-r flipped to `flex-end`),
   and followed whichever edge the locked CSS anchored each card to. Both
   ideas are gone: the cards are uniform-width and left-anchored, and one
   236px band now covers 1101px up. Below 1101px the locked CSS still sets
   `.lab-flank { display: none }` — measured flank height 0 at 900px — so
   there are no floating cards to align and nothing is scoped there.

   Spacing note, stated honestly: holding Lovable's narrower widths means the
   wordiest states wrap one line further than they do at full width, so a few
   slots need slightly more room than Felix @ 10 AM happens to use. Tops are
   therefore Felix's spacing opened by 7-8px where a measured state would
   otherwise collide (invite 141 -> 148, dm 274 -> 282, ticket 263 -> 272).
   Without that, measured overlaps were 13.3px on meet/invite and 5.3px on
   sticky/ticket; a second measured pass opened dm and ticket in the lower
   band by a further 6px each to clear 3.5px and 0.9px residuals. Rotation is included in the margin: a rotated card's painted
   box is ~6-8px taller than its layout height.

   The flank's width is restated in both bands. It had been content-driven —
   set by the cards' own max-width — and taking them out of flow removes that
   basis, letting the `1fr` grid column collapse the flank to 95px.

   Specificity: the locked rules to beat are `.gsh .hero .lab-prop` (0,3,0),
   `.gsh .hero .lab-prop.lab-quirk` (0,4,0) and `.gsh .hero .lab-flank`
   (0,3,0). These are (0,4,0) and (0,6,0). Matching specificity is deliberate
   — loading later is not enough against the locked file. */

/* --- Desktop proper: Lovable's 236px flank composition (their line 3277).

   Boundary lowered 1321px -> 1200px on Lovable's ruling of 7 Nov, so the full
   236px composition reaches standard 1280 laptops. Their reason: the copy
   already fits at that width, and trimming 20 strings to suit one breakpoint
   solves it in the harder place.

   `margin-top: -24px` is carried into this block because the boundary moved.
   The locked file only applies their -24px correction from 1321px up; below
   it, it still sets `margin-top: -99px`, which yanks the props 80px over the
   H1. Extending the width band without the margin would have pulled that
   collision up into 1200-1320 along with it. At >=1321 this is a no-op — the
   locked file already sets the same value. --- */
@media (width >= 1101px) {
  .gsh[data-lab] .hero .lab-flank { position: relative; width: 236px; margin-top: -24px; }

  /* Widening the flank alone changed nothing: the locked file caps every prop
     at `max-width: 210px` from 1101-1320px (its own band), so cards sized as
     a percentage of a 236px flank were clipped straight back to 210 and the
     line counts did not move. The cap has to be lifted too.

     Only `max-width` is lifted. The same locked rule also sets
     `font-size: 12.5px`, but that is identical to the value already in force
     from 720px up — restating it would imply a difference that is not there.

     (0,4,0) against the locked (0,3,0). */
  .gsh[data-lab] .hero .lab-prop { max-width: 236px; }

  .gsh[data-lab] .hero .lab-flank .lab-prop.lab-meet,
  .gsh[data-lab] .hero .lab-flank .lab-prop.lab-invite,
  .gsh[data-lab] .hero .lab-flank .lab-prop.lab-dm,
  .gsh[data-lab] .hero .lab-flank .lab-prop.lab-quirk,
  .gsh[data-lab] .hero .lab-flank .lab-prop.lab-sticky,
  .gsh[data-lab] .hero .lab-flank .lab-prop.lab-ticket {
    position: absolute; bottom: auto; align-content: start;
  }

  /* UNIFIED GEOMETRY, 7 Nov, on Lovable's brief. Supersedes the per-slot
     widths and staggered tops above.

     Their diagnosis, confirmed against the file: the right flank stepped
     192 / 190 / 236px, so its outer edge ran as a staircase while the left
     edge ran ragged (meet pinned right at 91.10%, invite pinned left at
     93.22%, dm at 100%); and the two flanks sat on different baselines —
     left tier 2 at 148px against right at 134px, tier 3 at 282 against 272.

     All six cards now take the full flank width and share three baselines.
     Both outer edges go flush and the tiers line up across the hero.

     Full width is also what un-orphans "stopped." on the sticky card: the
     headline wrapped at 80.51% and does not at 100%.

     Note this REVERSES the reasoning in the header above, which held that
     Lovable's varied widths were the design and wrote them as percentages
     to preserve them. That was right about the intent and wrong about the
     result — the varied widths read as misalignment, not as deliberate
     mess. The mess that was wanted lives in `--r`, and `--r` is a transform
     applied on top of this box, so the organic tilt survives the grid being
     made uniform. Nothing here touches it.

     The side anchors are dropped rather than kept. At `width: 100%`,
     `left: 0` and `right: 0` describe the same box, so a mix of the two
     implied a distinction that no longer exists; `right: auto` clears the
     ones the superseded rules set.

     Tops: tier 2 moves to 142px, between the 148 and 134 it replaces, and
     tier 3 holds at 282. The header above opened these by 7-8px to clear
     measured collisions caused by narrow cards wrapping an extra line.
     Full width removes that wrapping, which is why the tighter baseline is
     affordable -- verified by measurement across all 16 states, not assumed.

     (0,5,0) against the locked `.gsh .hero .lab-prop.lab-quirk` (0,4,0). */
  .gsh[data-lab] .hero .lab-flank .lab-prop { left: 0; right: auto; width: 100%; }

  .gsh[data-lab] .hero .lab-flank .lab-prop.lab-meet,
  .gsh[data-lab] .hero .lab-flank .lab-prop.lab-quirk  { top: 0; }
  .gsh[data-lab] .hero .lab-flank .lab-prop.lab-invite,
  .gsh[data-lab] .hero .lab-flank .lab-prop.lab-sticky { top: 142px; }
  .gsh[data-lab] .hero .lab-flank .lab-prop.lab-dm,
  .gsh[data-lab] .hero .lab-flank .lab-prop.lab-ticket { top: 282px; }
}

/* --- The 210px band is gone, 7 Nov, on Lovable's ruling.
   It used to re-run the whole composition at 1101-1199px on a 210px flank,
   with tops at 0 / 148 / 294 because 142 / 282 overlapped by 1.4px there.
   The band above now starts at 1101px instead of 1200px, so one 236px
   composition and one set of baselines cover every width the flanks appear
   at, and the `max-width: 236px` lift in that block carries down with it.

   Lovable's reason for taking this over keeping two bands: at 210px the
   Calendar headline ("Six meetings back to back") wrapped, and no set of
   tops fixes a width problem. 236px is what puts it back on one line.

   It fits. Measured before proposing it: at 1101px a 236px flank gives a
   978px stage inside the viewport with 61px of side margin and no overflow,
   because `.hero-art` is a fixed 440px and only the side margins absorb the
   change. Checked again at 1101 and 1199 after the merge.

   Two things the deleted block carried, and where they went:

   - `margin-top: -24px`. The locked file leaves `.lab-flank` on
     `margin-top: -99px` below 1321px, which yanks the props 80px over the
     H1; Lovable corrected it to -24px but only from 1321px up. The deleted
     block carried their value down to 1101. The surviving block sets the
     same -24px and now reaches 1101 itself, so nothing is lost.

   - `padding-bottom: 1.5rem`. Dropped. The props are absolutely positioned,
     so flank padding does not hold them; it only padded a box whose height
     the grid row already sets. Verified after removal that the deepest card
     still sits inside the hero at 1101 and 1199. --- */

/* ---------------------------------------------------------------------------
   35. Mobile nav drawer — align the panel to the header bar
   ---------------------------------------------------------------------------
   Kyle, from a phone: the shelf drawer "is still looking messy". Checked
   Lovable's specs first — their mobile drawer work is Phase 2 Step 1, four
   items, and all four are built and live (number chips gone, arrows pinned
   right and centred, group 03 on one line, CTA contrast matched to the
   desktop dropdown). Nothing in Steps 2-5 or Phase 3 touched this panel. So
   this is unspecced structure from the original export, not a missed note.

   Measured at 390x844: the panel is `position: absolute; right: 0` inside
   `.lab-mnav` — the 36px hamburger — and sized by `min-width: min(280px,
   100vw - 32px)`. So it anchors to the button, not to anything visible. It
   ran 81 -> 361 while the header bar `.hd-in` runs 20 -> 370: a 61px dead
   gutter on the left, stopping 9px short on the right. It aligns to nothing,
   which is why it reads as floating over the hero.

   Three symptoms, one cause — 280px is too narrow for the content:
     - 4 of the 6 directory items wrapped to two lines (56/73/73/56/71/71)
     - content ran 855px against a 758px cap, overflowing by 97px
     - so it scrolled internally and sliced text mid-line at the top edge

   Fix: match the header bar. `.wrap` gives a constant 20px inset at 320, 360,
   390 and 430, so the bar is always `100vw - 40px` wide. The panel's
   containing block is the hamburger, whose right edge sits 9px inside the
   bar's — `.hd-in` has `padding-right: 8px` plus its 1px border — so
   `right: -9px` lands the panel flush with the bar. Both numbers are read off
   the locked CSS, not eyeballed.

   Measured after, at 390x844: panel 20 -> 370, exactly the bar. **All six
   items single-line**, two-line wraps 4 -> 0. Overflow 97px -> 14px.

   `max-width: 420px` caps the tablet end. The hamburger still shows up to
   720px, where a full-bar panel would be 680px wide and absurd; capped, it
   stays a right-anchored dropdown (420 wide, flush right, zero overflow at
   both 600 and 720).

   Floored at 360px deliberately. At 320px the hamburger itself sits OUTSIDE
   the header bar — bar ends at 300, the button runs to 313 — a pre-existing
   overflow in the locked header that has nothing to do with this panel.
   Anchoring to the button there would push the panel 2px past the viewport,
   so below 360px everything is left exactly as it is today. Every current
   phone is 360 or wider.

   Specificity: the locked rules are `.gsh .lab-mnav-panel` at (0,2,0) on
   lines 1922, 3220 and 3493. This matches at (0,2,0) and loads later. */
@media (360px <= width <= 720px) {
  .gsh .lab-mnav-panel {
    width: calc(100vw - 40px);
    max-width: 420px;
    right: -9px;
  }
}

/* ---------------------------------------------------------------------------
   36. Homepage "Explore the tools" lands clean, like "Restore sanity" does
   ---------------------------------------------------------------------------
   Kyle, from desktop and phone: after clicking the second-fold CTA you can
   still see the previous fold peeking at the top, while the first-fold
   "Restore sanity" jump "shows much cleaner to the next fold".

   He is right, and it is a straight asymmetry in the locked gs-site-lab.css.
   The homepage has exactly two in-page anchors and they are built
   differently:

                        #desk (Restore sanity)   #tools (Explore the tools)
     scroll-margin-top   0  (line 3811)          100px desktop / 94px phone
     own padding-top     96px (--hd-h + 24)      58.5px desktop / 40px phone

   `#desk` is special-cased on line 3811: it lands at viewport y=0 and clears
   the header with its own padding, so its background fills the top of the
   screen and nothing can show above it. `#tools` falls through to the generic
   `.gsh[data-lab] [id]` rule and lands 100px down. The header is a FLOATING
   pill with transparent gaps around it, not a full-width bar, so the previous
   section shows straight through that strip.

   Measured after a real click, desktop 1280x900: #tools top at y=100, the
   previous section's bottom at y=100, and the "Explore the tools" button the
   user just pressed still painted at y=40-80 — right around the pill. On
   phone, 5 separate elements of the previous fold were still on screen. The
   button staying visible is the real defect: it reads as "the click did
   nothing".

   Fix: give #tools the treatment Lovable already wrote for #desk. Nothing
   invented — the same two declarations, the same `--hd-h + 24` clearance.

   `!important` is required and is not a shortcut here: the locked file sets
   this padding with `!important` at
   `.gsh[data-lab] section.sec.tl-sec` (0,4,1), which is exactly why Lovable's
   own #desk line carries `!important` too. Our selector is (1,2,0) — an id
   outranks any number of classes — so the cascade is won on specificity and
   the `!important` only matches theirs rather than hiding a collision.

   Verified: #tools now lands identically to #desk, on both.

                        #desk        #tools after
     lands at           y=0          y=0
     previous fold      0 elements   0 elements
     heading clears pill 22px        22px

   Phone 390x844: leaked elements 5 -> 0, heading clearance 22px, matching
   #desk's own 22px there. The cost, stated plainly: the tools fold gains top
   padding (58.5 -> 96 desktop, 40 -> 90 phone), so on an ordinary scroll
   there is ~40px more air above the heading, and after a jump the heading
   sits 22px under the pill instead of 66px. Both are the numbers #desk
   already uses — the fold Kyle picked out as the clean one. */
.gsh[data-lab] #tools {
  scroll-margin-top: 0 !important;
  padding-top: calc(var(--hd-h, 72px) + 24px) !important;
}

/* ---------------------------------------------------------------------------
   37. The desk fold gives its CTA the same room the hero gives its own
   ---------------------------------------------------------------------------
   Kyle, from an iPhone 17 Pro Max after §36 shipped: "this now has a huge gap
   on phone and the CTA is so close."

   Both halves of that are right, and measuring the fold he does NOT complain
   about is what explains it. At 440x956:

                              CTA -> fold edge    CTA -> next heading
     hero -> desk  (fine)           53px                143px
     desk -> tools (wrong)          20px                110px

   At 1280x900 the same comparison is starker: 115px vs 20px.

   So the problem is not the size of the gap. The transition Kyle is happy
   with has the LARGER total gap — 143 against 110 on phone, 211 against 116
   on desktop. What differs is where the space sits. The hero gives its CTA
   room to breathe and then changes fold. The desk fold crams its CTA 20px
   from the boundary and puts all the air on the far side, where §36's
   header clearance made it a conspicuous empty band in the tools fold's
   cream. Hence both halves of his sentence: crammed button, huge gap.

   §36 is not the cause — that 20px has always been there — but §36 doubled
   the air on the other side and so made the imbalance impossible to miss.
   Stated plainly because the fix for it is not to undo §36: reverting would
   bring back the button that stays on screen after its own click, which is
   the worse defect.

   Fix: give the desk fold the same bottom room the hero gives its CTA, so
   the two fold transitions read the same way. `clamp(53px, 9vw, 115px)`
   lands on the hero's measured values at both ends — 9vw is 115.2 at 1280,
   and the 53px floor takes over below ~590px where 9vw would undershoot.

   `!important` matches the locked `.gsh section.sec.dk-sec` (0,3,1), which
   sets this padding with `!important`. Ours is (1,2,0) — an id outranks any
   number of classes — so specificity wins it and the `!important` only
   matches theirs rather than hiding a collision.

   Note this changes resting layout only. The jump targets are unaffected:
   #desk's landing depends on its padding-TOP, and #tools' on its own. Both
   still land at y=0 with zero leakage. */
.gsh[data-lab] #desk {
  padding-bottom: clamp(53px, 9vw, 115px) !important;
}

/* ---------------------------------------------------------------------------
   38. /quick-q — the filter row stays one line on a phone
   ---------------------------------------------------------------------------
   Lovable, from their live-site audit: at 375-390px the category filters wrap
   and leave "Leadership" orphaned on a second row.

   Reproduced at 375x812 before changing anything: the five tabs sit on two
   rows, four at y=315 and Leadership alone at y=347.

   The cause is ours, not theirs, and not the `gap`. §30 gave `.qq-tab` a
   `margin-right: 1.3rem` to space the typographic tabs, and that margin sits
   ON TOP of the container's `gap: 8px` — so the real spacing is 28.8px, not
   8px. Measured: tabs total 269.3px and need 269.3 + 4 x 28.8 = 384.5px
   against a 335px bar. It was always going to wrap; 375px is simply where it
   first shows.

   Fix is Lovable's: one horizontal scrolling row. This is the same carousel
   pattern the site already uses for `.tl-grid`, `.gh-grid` and `.lab-sh-grid`
   on phones, including the `-pad / +pad` bleed pair so the row runs edge to
   edge while its first and last tabs still line up with the page gutter.

   `flex: 0 0 auto` is required: without it the tabs shrink to fit instead of
   scrolling, which trades a wrap for truncated labels.

   Scoped to 460px, not Lovable's 430. They reported the wrap at 375-390 and
   capped there, but measured it still wraps at 431. The row needs 384px of
   tabs AND the fifth tab's own trailing `margin-right: 20.8px`, which flex
   counts in the line width — so 405px against a bar of `100vw - 40px`, and
   the wrap only clears at a 445px viewport. 460 leaves headroom for font
   rendering that differs by device. Between 445 and 460 the rule is inert:
   `nowrap` with content that already fits lays out identically to the
   static row and shows no scrollbar. */
@media (width <= 460px) {
  .gsh[data-lab] .qq-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    margin-inline: calc(var(--gs-pad, 20px) * -1);
    padding-inline: var(--gs-pad, 20px);
  }
  .gsh[data-lab] .qq-tabs::-webkit-scrollbar { width: 0; height: 0; display: none; }
  .gsh[data-lab] .qq-tab { flex: 0 0 auto; }
}

/* ---------------------------------------------------------------------------
   39. Shelf V2 — Lovable's visual elevation (Step 1: /games)
   ---------------------------------------------------------------------------
   Lovable's block, applied with the five decisions they approved on 6 Nov.

   DECISION 1 — `[data-lab]` dropped from every selector. Their block was
   scoped `.gsh[data-lab] .v2-*`. Measured on production: the `.gsh` wrapping
   the content of `/games` and `/tools` carries NO `data-lab` — only
   `.gsh-chrome` (header) and `.tk-mqwrap` (marquee) do. So every V2 rule
   would have matched nothing on those two pages while working on the two
   exported ones, from one stylesheet. Adding `data-lab` instead was not an
   option: it gates 270 selectors in the locked sheet and 165 of ours, and
   switching it on for two pages that never had it applies all 435 at once.
   `v2-` appears zero times in gs-site-lab.css, gs-static-fix.css and
   site.css, so the attribute bought nothing and cost two pages.

   DECISION 3 — the "the call" lines stay sentence case in the markup and are
   uppercased HERE, by CSS. Their HTML had them typed in capitals, which
   hard-codes the shouting into the content: unreversible from CSS and
   announced as capitals by screen readers.

   DECISION 4 — SUPERSEDED BY MEASUREMENT, and Lovable are told why. They
   asked us to add the `.gsh` prefix to the five rules they left unprefixed.
   We did, then built Step 2 and found the opposite problem to Step 1: on
   `/tools` the hero, the spec bays and the dock all sit OUTSIDE `.gsh`
   entirely — only the tool consoles are wrapped, in a `.gsh.tk-toolwrap`
   island. So `.gsh`-scoped V2 rules reach nothing there, and dropping
   `[data-lab]` alone was necessary but not sufficient.

   Every V2 selector is therefore unscoped. `v2-` collides with nothing in
   any of the three stylesheets, so the prefix was defensive rather than
   load-bearing, and the two page families place `.gsh` too differently for
   any single ancestor to serve both: `/games` wraps its whole main in `.gsh`,
   `/tools` wraps only an island inside it. The `--v2-*` tokens move to
   `:root` for the same reason, with `html[data-theme="dark"]` beside the
   `.gsh[data-theme="dark"]` variant so both families theme correctly. The
   `.gs-dock` restyle is unscoped on the same evidence — §32's base rule is
   unscoped too, so ours matches its specificity and wins on order.

   DECISION 2 — the dock is NOT duplicated. `.v2-dock` would have been a
   second component beside the `.gs-dock` already on all four pages — the one
   whose invisible CTA Lovable caught and §32 fixed. Instead the V2 treatment
   is applied to the existing `.gs-dock` container, heading and paragraph
   below. `.gs-dock-cta` is deliberately untouched: it already computes the
   right pill colours in both themes and that fix is not worth re-testing for
   a restyle that does not need it.

   DECISION 5 — `--gs-head` holds a Helvetica Neue stack site-wide and stays
   that way. Lovable confirmed Helvetica Neue Bold for all headings is a
   locked brand rule, so V2 inheriting it is correct, not a bug. Recorded here
   because the token name invites "fixing" it to Space Grotesk later.

   Live after Step 1: the arcade cabinets, the pill, the dock restyle and the
   mobile carousel. The plate, slip, tape, reel and tier rules below are
   Lovable's spec for Steps 2-4 and are inert until those pages are converted.
   --------------------------------------------------------------------------- */
:root, .gsh {
  --v2-ochre: #9A5B12;
  --v2-shell: #2A2620;
  --v2-shell-ink: #F2ECE4;
  --v2-screen: #14161E;
  --v2-screen-ink: #E9F5EC;
  --v2-glow: #5FD08A;
}
html[data-theme="dark"], .gsh[data-theme="dark"] {
  --v2-ochre: #E2A65A;
  --v2-shell: #F2ECE4;
  --v2-shell-ink: #1A1D2B;
  --v2-screen: #05060A;
  --v2-glow: #6BE39A;
}

.v2-kick { display: block; font-size: 12px; letter-spacing: .2em; text-transform: uppercase; color: var(--gs-ink-2); margin-bottom: 12px; }
.v2-kick b { color: var(--gs-green-active); font-weight: 600; }
.v2-h { font: 700 clamp(32px, 5vw, 56px)/1.04 var(--gs-head); letter-spacing: -.03em; margin: 0 0 14px; max-width: 18ch; }
.v2-h em { font-family: var(--gs-serif); font-style: italic; font-weight: 400; }
.v2-h em.g { color: var(--gs-green-active); }
.v2-h em.i { color: var(--gs-indigo); }
.v2-lede { font: 400 clamp(16px, 1.6vw, 19px)/1.5 var(--gs-ui); color: var(--gs-ink-2); max-width: 56ch; margin: 0; }
.v2-priv, .v2-anat { margin: 18px 0 0; font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--gs-green-active); }
.v2-anat span { color: var(--gs-ink-2); margin: 0 .5em; }

.v2-pill { display: inline-flex; align-items: center; gap: .5em; justify-content: center; padding: 12px 20px; border-radius: 999px; border: 0; cursor: pointer; font: 700 15px/1 var(--gs-head); color: var(--gs-ground); background: var(--gs-ink); text-decoration: none; min-height: 44px; }
.v2-ghost { display: inline-flex; align-items: center; gap: .5em; background: none; border: 0; cursor: pointer; font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--gs-ink-2); min-height: 44px; }

/* --- Steps 2-4 spec, inert until those pages convert --- */
.v2-plate { border-radius: 18px; background: var(--gs-surface); border: 1px solid var(--gs-rule); box-shadow: inset 0 1px 0 color-mix(in oklab, var(--gs-raised) 80%, transparent), 0 14px 30px -24px var(--gs-ink); overflow: hidden; margin-top: 24px; }
.v2-plate-top { display: flex; justify-content: space-between; padding: 12px 20px; font-size: 11px; letter-spacing: .18em; text-transform: uppercase; color: var(--gs-ink-2); border-bottom: 1px dashed var(--gs-rule); }
.v2-plate-row { display: grid; grid-template-columns: repeat(4, 1fr); }
.v2-bay { display: grid; grid-template-rows: auto 1fr; gap: 10px; padding: 20px; border-left: 1px solid var(--gs-rule); }
.v2-bay:first-child { border-left: 0; }
.v2-bay-k { font-size: 11px; letter-spacing: .16em; text-transform: uppercase; color: var(--gs-ink-2); }
.v2-bay-k b { color: var(--gs-green-active); }
.v2-bay strong { font: 600 17px/1.3 var(--gs-ui); color: var(--gs-ink); }
@media (max-width: 900px) { .v2-plate-row { grid-template-columns: 1fr 1fr; } .v2-bay:nth-child(3) { border-left: 0; } .v2-bay:nth-child(n+3) { border-top: 1px solid var(--gs-rule); } }
@media (max-width: 520px) { .v2-bay { padding: 16px; } .v2-bay strong { font-size: 15px; } }

.v2-tabs { display: flex; gap: 6px; flex-wrap: wrap; border-bottom: 1px solid var(--gs-rule); margin: 20px 0; }
.v2-tabs button { background: none; border: 0; border-bottom: 2px solid transparent; padding: 12px 10px; font-size: 12px; letter-spacing: .16em; text-transform: uppercase; color: var(--gs-ink-2); cursor: pointer; white-space: nowrap; min-height: 44px; }
.v2-tabs button[aria-pressed="true"] { color: var(--gs-ink); border-bottom-color: var(--gs-green-active); }
@media (max-width: 640px) { .v2-tabs-wrap { margin: 0 -20px; overflow-x: auto; scrollbar-width: none; } .v2-tabs { flex-wrap: nowrap; padding: 0 20px; width: max-content; min-width: 100%; } }

.v2-qq-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
.v2-slip { display: flex; flex-direction: column; gap: 12px; padding: 18px 20px; border-radius: 14px; background: var(--gs-raised); border: 1px solid var(--gs-rule); }
.v2-slip-bar { display: flex; justify-content: space-between; font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--gs-ink-2); }
.v2-slip-bar b { color: var(--gs-red-text, #B23A31); font-weight: 600; }
.v2-ping { margin: 0; font: 700 19px/1.3 var(--gs-head); letter-spacing: -.01em; color: var(--gs-ink); }
.v2-sub { margin: 0; padding-left: 12px; border-left: 2px solid var(--v2-ochre); font: 400 italic 15px/1.45 var(--gs-serif); color: var(--gs-ink-2); }
.v2-sub span, .v2-reply span { display: block; font: 500 10px/1 var(--gs-mono); letter-spacing: .18em; text-transform: uppercase; font-style: normal; margin-bottom: 6px; color: var(--v2-ochre); }
.v2-reply span { color: var(--gs-green-active); }
.v2-reply p { margin: 0; font: 400 15px/1.5 var(--gs-ui); color: var(--gs-ink); }
.v2-slip-act { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: auto; padding-top: 12px; border-top: 1px dashed var(--gs-rule); }
@media (max-width: 760px) { .v2-qq-grid { grid-template-columns: 1fr; gap: 12px; } .v2-slip { padding: 16px; } .v2-ping { font-size: 17px; } }

.v2-snap { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin: 20px 0 40px; }
.v2-tier-head { display: flex; align-items: baseline; justify-content: space-between; gap: 20px; flex-wrap: wrap; padding-top: 20px; border-top: 1px solid var(--gs-rule); }
.v2-tier-head .mono { font-size: 12px; letter-spacing: .2em; text-transform: uppercase; color: var(--gs-ink-2); }
.v2-tier-head b { color: var(--gs-green-active); }
.v2-tier-head blockquote { margin: 0; font: 400 italic clamp(17px, 2vw, 22px)/1.35 var(--gs-serif); color: var(--gs-ink); max-width: 40ch; }

.v2-tape, .v2-reel { display: flex; flex-direction: column; gap: 8px; padding: 16px; border-radius: 16px; background: var(--gs-raised); border: 1px solid var(--gs-rule); color: var(--gs-ink); text-decoration: none; }
.v2-tape h4, .v2-reel h4 { margin: 6px 0 0; font: 700 20px/1.2 var(--gs-head); letter-spacing: -.01em; }
.v2-tape p { margin: 0; color: var(--gs-ink-2); line-height: 1.45; }
.v2-link { margin-top: auto; padding-top: 8px; display: inline-flex; align-items: center; gap: .5em; font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--gs-green-active); }
.v2-shell { aspect-ratio: 16/9; border-radius: 10px; background: var(--v2-shell); padding: 10px; display: flex; flex-direction: column; gap: 8px; }
.v2-label { display: flex; justify-content: space-between; padding: 6px 10px; border-radius: 4px; background: var(--gs-surface); color: var(--gs-ink); font-size: 10px; letter-spacing: .16em; text-transform: uppercase; border-top: 3px solid var(--gs-green-active); }
.v2-window { flex: 1; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 0 14%; border-radius: 999px; background: color-mix(in oklab, var(--v2-shell-ink) 14%, var(--v2-shell)); }
.v2-window i { width: 30px; height: 30px; border-radius: 50%; border: 5px dotted var(--v2-shell-ink); animation: v2spin 6s linear infinite; }
.v2-window span { flex: 1; height: 2px; background: color-mix(in oklab, var(--v2-shell-ink) 40%, transparent); }
.v2-slot { flex: 1; display: grid; place-items: center; border: 1px dashed color-mix(in oklab, var(--v2-shell-ink) 45%, transparent); border-radius: 6px; color: var(--v2-shell-ink); font-size: 11px; letter-spacing: .16em; text-transform: uppercase; }
.v2-tape.is-empty { border-style: dashed; background: transparent; }
@keyframes v2spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .v2-window i { animation: none; } }

.v2-frame { aspect-ratio: 2.39/1; display: flex; justify-content: space-between; align-items: flex-end; padding: 10px 12px; border-radius: 8px; background: var(--v2-screen); color: var(--v2-ochre); font-size: 10px; letter-spacing: .18em; text-transform: uppercase; }
.v2-why { font: 400 italic 16px/1.4 var(--gs-serif); color: var(--gs-ink); }
.v2-meta { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--gs-ink-2); }

/* --- Step 1 live: the CRT arcade --- */
.v2-arc-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin: 20px 0 40px; }
.v2-arc { display: flex; flex-direction: column; gap: 10px; padding: 16px; border-radius: 18px; background: var(--gs-raised); border: 1px solid var(--gs-rule); border-bottom: 4px solid var(--gs-ink); color: var(--gs-ink); text-decoration: none; transition: transform .15s; }
.v2-arc:hover { transform: translateY(-2px); }
@media (prefers-reduced-motion: reduce) { .v2-arc { transition: none; } .v2-arc:hover { transform: none; } }
.v2-arc-bar { display: flex; justify-content: space-between; font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--gs-ink-2); }
.v2-arc-bar b { color: var(--gs-green-active); }
.v2-arc-who { display: flex; gap: 10px; align-items: center; }
.v2-arc-who img { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; border: 2px solid var(--gs-rule); background: var(--gs-surface); }
.v2-arc-who strong { display: block; font: 600 15px/1.2 var(--gs-ui); }
.v2-arc-who span { font-size: 11px; color: var(--gs-ink-2); display: block; }
.v2-arc h4 { margin: 4px 0 0; font: 700 21px/1.15 var(--gs-head); letter-spacing: -.01em; }
.v2-arc h4 em { font-family: var(--gs-serif); font-weight: 400; color: var(--gs-indigo); }
.v2-arc > p { margin: 0; color: var(--gs-ink-2); line-height: 1.45; font-size: 15px; }
.v2-call { margin-top: auto; padding: 12px; border-radius: 10px; background: var(--v2-screen); color: var(--v2-screen-ink); }
.v2-call span { display: block; font-size: 10px; letter-spacing: .18em; text-transform: uppercase; color: var(--v2-glow); margin-bottom: 6px; }
/* Decision 3: the caps live here, not in the markup. */
.v2-call p { margin: 0; font-size: 12px; line-height: 1.5; font-family: var(--gs-mono); text-transform: uppercase; }
.v2-play { width: 100%; }

/* Locked baseline envelopes — stops the cards staggering across columns.
   Decision 4: scoped, where Lovable left these five unprefixed. */
@media (min-width: 760px) {
  .v2-arc-who { min-height: 48px; }
  .v2-arc h4  { min-height: 52px; }
  .v2-arc > p { min-height: 66px; }
  .v2-call    { min-height: 108px; }
  .v2-play    { margin-top: auto; }
}
@media (max-width: 1000px) and (min-width: 760px) {
  .v2-arc-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Mobile carousel */
@media (max-width: 759px) {
  .v2-snap, .v2-arc-grid {
    display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
    margin: 0 -20px; padding: 4px 20px 12px; scroll-padding: 0 20px; scrollbar-width: none;
    overscroll-behavior-x: contain;
  }
  .v2-snap::-webkit-scrollbar, .v2-arc-grid::-webkit-scrollbar { width: 0; height: 0; display: none; }
  .v2-snap > *, .v2-arc-grid > * { flex: 0 0 82%; scroll-snap-align: start; }
  .v2-arc-who, .v2-arc h4, .v2-arc > p, .v2-call { min-height: auto; }
}

/* Decision 2: the V2 dock treatment lands on the EXISTING .gs-dock rather
   than adding .v2-dock beside it. `.gs-dock-cta` is untouched — it already
   computes correct pill colours in both themes (§32) and a restyle is no
   reason to re-open a contrast fix Lovable had to catch for us once. */
.gs-dock {
  padding: 28px; border-radius: 18px;
  background: var(--gs-raised);
  border: 1px solid var(--gs-rule);
  border-top: 3px solid var(--gs-green-active);
  margin-top: 40px; gap: 24px;
}
.gs-dock-h { margin: 0 0 8px; font: 700 clamp(22px, 2.6vw, 28px)/1.15 var(--gs-head); letter-spacing: -.02em; }
.gs-dock-h em { font-family: var(--gs-serif); font-weight: 400; font-style: italic; color: var(--gs-indigo); }
.gs-dock-p { margin: 0; color: var(--gs-ink-2); max-width: 58ch; line-height: 1.5; font-size: 15px; }
@media (max-width: 720px) {
  .gs-dock { flex-direction: column; align-items: stretch; padding: 22px; }
}

/* Step 2 additions. The dock kicker was first built as `.gs-dock-kick`;
   Lovable then specced `.v2-kick` and asked for it on all four docks, so the
   markup uses their class and §39's `.v2-kick` rule carries it. The bespoke
   rule is gone rather than left as a second way to style the same thing.
   The dock's own kicker needs no green fallback: `.v2-kick b` already has
   one, and all four docks sit where `--gs-green-active` resolves. */
.gs-ic-lock { vertical-align: -0.12em; margin-right: .45em; }

/* The /tools hero sits outside both `.gsh` AND `#tools`, so §21's pins do not
   reach it. The V2 privacy line asks for `--gs-green-active`, which is
   undefined out there — invalid at computed-value time, so it silently
   inherited `--gs-ink` and shipped as near-white instead of green. Measured:
   rgb(237,239,245) where the spec-plate kickers next to it resolved green.
   Same two values as §21, extended to the hero rather than widened to
   `:root`, for the same reason §21 gives. */
.tk-hero { --gs-green-active: #1B6B38; }
html[data-theme="dark"] .tk-hero { --gs-green-active: oklch(78% .16 150); }

/* Step 3 additions. The ping stays a <blockquote> rather than becoming a <p>:
   it is a quoted message, and Lovable's `.v2-ping` rule styles whatever
   carries the class, so the semantics cost nothing. These two rules zero the
   default margins their spec assumed away. */
.v2-ping p, .v2-sub p { margin: 0; }
.v2-slip-who { color: var(--gs-ink-2); }
@media (max-width: 420px) { .v2-slip-who { display: none; } }
/* The copied-state span is absolutely placed over the label in §30; inside a
   V2 button it needs the same relative parent. */
.v2-pill, .v2-ghost { position: relative; }

/* The site's `.gs-arr` is `display: block`, which is fine inside the flex
   buttons it was built for and wrong in running text: in the /quick-q anatomy
   line each arrow forced a line break, stacking "The Ping / The Subtext /
   The Reply" into three rows 88px tall in a 720px container. Inline only
   where the arrow sits in flow text. */
.v2-anat svg, .v2-priv svg { display: inline-block; vertical-align: -0.08em; }
.v2-anat span { white-space: nowrap; }

/* ---------------------------------------------------------------------------
   40. Shelf V2 Step 4 — /playlists hi-fi archive
   ---------------------------------------------------------------------------
   Tier accent hairlines, per Lovable's spec: green for the cassettes, ochre
   for the film reels, indigo for long-form audio, red for screen sessions.
   Applied to the tier heading rather than each card, so the accent reads as
   a shelf label rather than decoration repeated twelve times. */
#music  .dt-grp-h { border-top: 2px solid var(--gs-green-active, #1B6B38); }
#films  .dt-grp-h { border-top: 2px solid var(--v2-ochre); }
#audio  .dt-grp-h { border-top: 2px solid var(--gs-indigo, #485289); }
#watch  .dt-grp-h { border-top: 2px solid var(--gs-red, #C8443A); }
#music .dt-grp-h, #films .dt-grp-h, #audio .dt-grp-h, #watch .dt-grp-h { padding-top: 18px; }

/* The cassette shell and its parts are spans inside an <a>, so they need the
   box behaviour their spec assumed from divs. */
.v2-shell, .v2-label, .v2-window, .v2-slot, .v2-frame { display: flex; }
.v2-shell { flex-direction: column; }
.v2-label { justify-content: space-between; }
.v2-tape, .v2-reel { height: 100%; }
.v2-reel h4 .v2-meta { font: inherit; font-weight: 400; color: var(--gs-ink-2); }
/* Lovable's `.v2-why` is a shared italic; the first line of a reel is the
   one-liner and the second the editorial note, so the second sits quieter. */
.v2-reel .v2-why + .v2-why { color: var(--gs-ink-2); margin-top: 2px; }

/* The cassette label and slot carry `.mono` for the typeface, and `.mono`
   also sets a colour — it beat Lovable's `.v2-label` / `.v2-slot` at equal
   specificity and loaded later, so the standby text took `--gs-ink-3` instead
   of the chassis ink. Measured 2.63 against the dark chassis, under AA.
   Scoping to the shell restores the intended pair. */
.v2-shell .v2-label { color: var(--gs-ink); }
.v2-shell .v2-slot  { color: var(--v2-shell-ink); }

/* `.v2-frame` is a cinema screen: `--v2-screen` is dark in BOTH themes, but
   `--v2-ochre` flips — #9A5B12 in light, #E2A65A in dark. So in light mode
   the frame put dark ochre on a dark screen and measured 3.16, under AA,
   while dark mode was fine at 6.91. Same shape as the dock-CTA bug: a token
   that flips with the theme, used on a surface that does not.

   The screen always needs screen-bright ink, so the frame gets an ochre that
   does not flip. 7.49 in both themes. */
:root, .gsh { --v2-screen-ochre: #E2A65A; }
/* (0,2,0) to match `.gsh .mono` in the locked sheet, which was winning at
   (0,2,0) against a bare `.v2-frame` at (0,1,0) and painting the frame
   `--gs-ink-3`. The ochre film-frame treatment was never rendering in EITHER
   theme — dark merely happened to pass at 6.91 with the wrong colour, which
   is why only the light-mode number looked wrong. Loading later was not
   enough; it never is against the locked file. */
.v2-reel .v2-frame { color: var(--v2-screen-ochre); }

/* Tier 03 / 04 broadcast cards. They reuse Lovable's shared `.v2-reel` base
   (flex column, raised, 16px radius) with a mono kicker and a tactile arrow
   pill, and carry locked baselines so four cards hold one line. */
.v2-bcast { gap: 6px; }
.v2-bcast-k { font-size: 11px; letter-spacing: .16em; text-transform: uppercase; }
.v2-reel.v2-bcast .v2-bcast-k { color: var(--gs-ink-2); }
.v2-bcast p { margin: 0; color: var(--gs-ink-2); line-height: 1.45; font-size: 15px; }
.v2-bcast h4 { margin: 4px 0 0; }
.v2-bcast-acts { display: grid; gap: 8px; margin-top: auto; padding-top: 10px; }
.v2-bcast-acts .v2-link { margin-top: 0; padding-top: 0; }
@media (min-width: 760px) {
  .v2-bcast h4 { min-height: 50px; }
  .v2-bcast p  { min-height: 66px; }
}
@media (max-width: 759px) {
  .v2-bcast h4, .v2-bcast p { min-height: auto; }
}

/* ---------------------------------------------------------------------------
   41. The white band behind the floating header (6 Nov)
   ---------------------------------------------------------------------------
   Lovable's diagnosis is exactly right. `gs-site-lab.css:2392` sets
   `.gsh:not([data-theme="dark"]) { --gs-ground: #fff; background: #fff }`,
   so the canvas is pure white in light mode while the first fold below is
   warm. The header is a floating pill on a transparent bar, so the canvas
   shows through as a stark band above it.

   Measured, light, 1280: at y=10 the painted colour is `.gsh` rgb(255,255,255)
   and at y=90 it is rgb(247,243,238) — a 74px white strip.

   Affected pages are exactly the four they named. `/` and `/tools` are not:
   the homepage hero extends up under the header, and `/tools` is a ported
   page whose body carries the warm ground already. Verified all six.

   ONE CHANGE TO THEIR CSS, and it matters. They also set
   `--gs-ground: #F7F3EE`. That token is used as a FOREGROUND colour in 17
   places — `color: var(--gs-ground)` on `.gs-dock-cta`, `.v2-pill`,
   `.mn-cta` and others — so retokenising it would turn every pill label from
   white to warm cream and drop their contrast. Only the canvas needs to
   change, so only the canvas is changed.

   `!important` IS required here, and we were wrong to drop it first time.
   The judgement was that matching the locked rule's (0,2,0) and loading later
   would be enough. It is not: `.gsh.ab` carries an INLINE
   `style="...background:var(--gs-ground)..."`, and an inline declaration beats
   every stylesheet rule that is not `!important`. Measured after the first
   attempt — still rgb(255,255,255) at y=10. Lovable's original CSS had this
   right; the usual "match specificity instead of shouting" rule does not
   apply against an inline style.

   Their `.hd` / `.ab-head` background rules are still not applied. Those
   elements are transparent by design, and once the canvas is warm they reveal
   the right colour on their own. Their rules also lean on `--fold-a`, which
   is not defined in this file. */
.gsh:not([data-theme="dark"]) { background: #F7F3EE !important; }

/* ---------------------------------------------------------------------------
   42. /about closing fold — headline balance and marquee breathing room
   ---------------------------------------------------------------------------
   Lovable raised three things here. Measured all three first; two were real.

   1. HEADLINE — real. "GrowthSanity is the thinking. Acquret is where we put
      it to work." ran to THREE lines (132px over a 43.2px line-height),
      stranding "to work." alone. The band was near-even at 521/497px.
      Widening the left column balances it onto two.

   2. STATUS STRIP — NOT a defect, and nothing is changed. They asked us to
      anchor `.lab-close-strip` as "the official base of the card enclosure
      with a matching hairline". It already is: measured as the LAST CHILD of
      `.lab-close-card`, painted on that card's own white background, with
      `border-top: 1px solid rgb(232,225,216)` already present, and its bottom
      edge flush with the card's at 629px. There is nothing to connect — it is
      already the card's base.

   3. MARQUEE — real, but the opposite way round. They reported "almost zero
      vertical margin" above it. Measured, there is **56px above** it and
      **0px below**, with the footer flush against it. So the crowding is
      underneath, not on top.

      Their `padding-top: clamp(48px, 6vh, 64px)` would stack on the existing
      56px and open a 104-120px hole, so only the bottom padding is applied.
      The top is left alone and the numbers are reported back for them to
      judge. Their `background: transparent !important` is also skipped — the
      strip is already transparent.

      `!important` is required and we were wrong to drop it first time: the
      locked sheet carries
      `.gsh .mq-shelf.mq-end { padding-block-start: 0 !important;
      padding-block-end: 0 !important }`. Without it the rule matched, sat
      last in the cascade with the right value, and still computed 0px — even
      an inline `padding-bottom` on the element computed 0px, which is what
      finally pointed at an `!important` upstream. Lovable's CSS had this
      right too.

   4. HEADLINE, second pass. Widening the column from 521 to 566px was not
      enough on its own — still three lines. `text-wrap: balance` lets the
      browser even the lines rather than filling greedily. */
/* `text-wrap: balance` was the wrong tool and is not used: it evens line
   LENGTHS, while the ask is one SENTENCE per line. Those fight — balanced
   wrapping would happily give "GrowthSanity is the thinking. Acquret is" on
   line one. The split is explicit in the markup instead, each sentence a
   block span, with the column widened so the longer one fits on a single
   line: measured 598px against a 566px column, a 32px shortfall, so 1.5fr
   gives 611px. Below that each sentence wraps within itself, which is the
   right behaviour on a phone. */
/* CORRECTED 6 Nov. This rule shipped unscoped and that was a live bug.

   The locked sheet collapses the band to one column on phones:

     @media (width <= 760px) {
       .gsh[data-lab] .lab-close-band { grid-template-columns: minmax(0px, 1fr); gap: 22px }
     }                                                              -- line 3881

   That is (0,3,0). The rule below is ALSO (0,3,0) and loads later, and a
   media query adds nothing to specificity — so this beat the mobile rule
   INSIDE its own media query and the band stayed two columns at every width.
   Measured on /about at 390px before the fix: columns 167px and 111px, the
   headline shattered across 106px line boxes, the paragraph squeezed into a
   111px ribbon. The comment below cheerfully claimed "below that each
   sentence wraps within itself, which is the right behaviour on a phone" —
   it never got the chance, because this rule removed the collapse.

   Seventh specificity loss recorded in this file, and the first that broke a
   layout rather than quietly doing nothing. The lesson is now narrower than
   "count the selectors": an override that exists to fix ONE width has to be
   scoped to that width, or it silently follows the element everywhere.

   Scoped to the desktop side of the locked breakpoint so the phone rule gets
   its column back. `align-items: start` is added in the same query — see §48. */
@media (width >= 761px) {
  .gsh.ab .lab-close-band { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); }
}
.gsh.ab .ab-close-line { display: block; }
.gsh .mq-shelf.mq-end { padding-bottom: clamp(20px, 3vh, 32px) !important; }

/* ---------------------------------------------------------------------------
   43. Lovable's post-deploy batch — the `.mono` trap, one more time (6 Nov)
   ---------------------------------------------------------------------------
   1. `/games` "The call" body was unreadable in light. CONFIRMED at **3.16**
      (they reported 1.08; either way it is under AA). `.v2-call` carries
      `.mono`, `.gsh .mono` colours it `--gs-ink-3`, and `.v2-call p` never
      declared its own colour — so the paragraph INHERITED dark ink onto the
      #14161E screen.

      This is ours to own. It is the fourth instance of the same trap — the
      film frame, the cassette label, the standby slot, and now this — and
      after fixing the first three we did not sweep for the rest. A sweep of
      every `v2-` element against its painted background across all four rooms
      now shows this as the only remaining failure; `.v2-shell` also flagged
      at 1.11 but is a container with no direct text, all of it in children
      that measure 14.26 and 12.81.

      No `!important` needed here, unlike the last two: the colour arrives by
      INHERITANCE from the parent, and any direct rule on the child beats
      inheritance regardless of specificity. Verified rather than assumed.
      Their `.v2-call span` fix is already in §39 and measures 9.35.

   2. Dock spacing — the dock sat flush against the marquee, measured 0px.

   3. `/quick-q` desktop stagger — real, but NOT from the ping. All four pings
      measure 61px; their reported "#002 wraps to two lines" does not happen.
      The stagger is in the action row (790 vs 810) because card 1 is 429px
      tall against the row's 448px, and the cause is OUR OWN §30 rule
      `.gsh[data-lab] .qq-grid { align-items: start }`, still matching because
      the container kept its `qq-grid` class alongside `v2-qq-grid`.

      So the fix is one property, not three min-heights. Stretching the cards
      lets the existing `margin-top: auto` on `.v2-slip-act` do its job.
      Their envelopes would also work but bake 54/110/96px into the layout,
      which breaks the moment the copy changes — the same brittleness the hero
      props taught us. */
.v2-call p { color: var(--v2-screen-ink); }
.gs-dock { margin-bottom: clamp(48px, 6vh, 64px); }
/* (0,4,0) to beat §30's `.gsh[data-lab] .qq-grid { align-items: start }` at
   (0,3,0). A bare `.v2-qq-grid` at (0,1,0) lost silently — the cards stayed
   429px against a 448px row and the action rows kept their 20px stagger.
   Same trap as every other time: loading later is not enough. */
.gsh[data-lab] .qq-grid.v2-qq-grid { align-items: stretch; }

/* ---------------------------------------------------------------------------
   45. /playlists Tiers 03 + 04 — broadcast tuner and studio monitor
   ---------------------------------------------------------------------------
   Lovable, 6 Nov: Tiers 03 and 04 "still read as plain cards" next to the
   cassette decks in Tier 01 and the film reels in Tier 02, and should become
   a broadcast/hi-fi deck and a CRT/studio-monitor chassis respectively.

   Both reuse the `--v2-shell` / `--v2-screen` token pair the decks and reels
   already use, so this is the same instrument family rather than two new
   looks. Worth restating how that pair behaves, because it is unusual and
   easy to get wrong: `--v2-shell` INVERTS between themes (dark chassis on the
   light site, light chassis on the dark one) while `--v2-screen` stays dark in
   both — which is correct, since a powered screen is dark whatever the room
   is doing. Anything sitting on the chassis therefore has to take its colour
   from `--v2-shell-ink`, and anything on the screen from `--v2-screen-ink`.
   The two are not interchangeable and a literal hex is wrong in both places.

   That inversion is also why `--v2-dial` is declared per theme below rather
   than reused from `--v2-ochre`. The amber that reads on a dark chassis
   (#E2A65A, 7.5:1 on #2A2620) collapses to 1.8:1 on the light chassis the
   dark theme swaps in. One token, two values, measured for each ground.

   Note both tiers' cards carry `.mono` on `.v2-bcast-k` and `.v2-link`, which
   `.gsh .mono` at (0,2,0) colours `--gs-ink-3` — the trap that has now bitten
   four times. Every one of them is re-coloured explicitly below.
   --------------------------------------------------------------------------- */

:root, .gsh        { --v2-dial: #E2A65A; }
html[data-theme="dark"], .gsh[data-theme="dark"] { --v2-dial: #8A4F0C; }

/* --- the band stamp under each tier title --- */
/* `display: flex` + `width: fit-content` rather than `inline-flex`: the
   locked `.gsh .dt-grp-h` is `display: block`, so an inline badge would trail
   the subtitle text on whatever line it ended on. This gives it its own row
   while still shrinking to the label. */
.gsh .v2-band {
  display: flex; width: fit-content; align-items: center; gap: .5ch;
  margin-top: 12px; padding: 5px 11px;
  border: 1px solid color-mix(in oklab, var(--v2-dial) 55%, transparent);
  border-radius: 4px;
  font: 500 11px/1 var(--gs-mono); letter-spacing: .18em; text-transform: uppercase;
  color: var(--gs-ink);
  background: color-mix(in oklab, var(--v2-dial) 12%, var(--gs-ground));
}
.gsh .v2-band i { font-style: normal; opacity: .55; }

/* --- Tier 03: the tuner chassis ---
   Chassis body, with the dial scale drawn across the head as a repeating
   gradient and a single amber marker sitting on it. Ticks and marker are
   decoration, so they carry no contrast obligation; all text is shell ink. */
.gsh .v2-tier-audio .v2-bcast {
  position: relative;
  padding: 18px 16px 16px;
  background: var(--v2-shell);
  border: 1px solid color-mix(in oklab, var(--v2-shell-ink) 22%, transparent);
  border-radius: 12px;
  overflow: hidden;
}
.gsh .v2-tier-audio .v2-bcast::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 14px;
  background:
    repeating-linear-gradient(90deg,
      color-mix(in oklab, var(--v2-shell-ink) 38%, transparent) 0 1px,
      transparent 1px 9px),
    color-mix(in oklab, var(--v2-shell-ink) 7%, transparent);
  border-bottom: 1px solid color-mix(in oklab, var(--v2-shell-ink) 16%, transparent);
}
.gsh .v2-tier-audio .v2-bcast::after {
  content: ""; position: absolute; top: 0; left: 38%; width: 2px; height: 14px;
  background: var(--v2-dial);
}
.gsh .v2-tier-audio .v2-bcast > * { position: relative; }
.gsh .v2-tier-audio .v2-bcast .v2-bcast-k { margin-top: 10px; color: var(--v2-dial); }
.gsh .v2-tier-audio .v2-bcast h4 { color: var(--v2-shell-ink); }
.gsh .v2-tier-audio .v2-bcast p  { color: color-mix(in oklab, var(--v2-shell-ink) 74%, transparent); }
.gsh .v2-tier-audio .v2-bcast .v2-link { color: var(--v2-shell-ink); }
.gsh .v2-tier-audio .v2-bcast.is-static {
  background: color-mix(in oklab, var(--v2-shell) 92%, var(--gs-ground));
  border-style: dashed;
}

/* --- Tier 04: the studio monitor ---
   Inverted from Tier 03: here the CARD is the screen and the chassis is the
   bezel around it, which is what separates a monitor from a tuner at a
   glance. Scanlines overlay the whole face; `pointer-events: none` so they
   never intercept the card's own link. The screen is dark in both themes, so
   unlike Tier 03 nothing here needs a per-theme value. */
.gsh .v2-tier-watch .v2-bcast {
  position: relative;
  padding: 16px;
  background: var(--v2-screen);
  border: 6px solid var(--v2-shell);
  border-radius: 14px;
  overflow: hidden;
}
.gsh .v2-tier-watch .v2-bcast::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(180deg,
    color-mix(in oklab, var(--v2-screen-ink) 7%, transparent) 0 1px,
    transparent 1px 4px);
}
.gsh .v2-tier-watch .v2-bcast > * { position: relative; z-index: 1; }
.gsh .v2-tier-watch .v2-bcast .v2-bcast-k {
  display: inline-flex; align-items: center; gap: .6ch;
  color: var(--v2-glow);
}
/* The tally light. Decorative, hidden from the tree. */
.gsh .v2-tier-watch .v2-bcast .v2-bcast-k::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--v2-glow);
  box-shadow: 0 0 6px var(--v2-glow);
}
.gsh .v2-tier-watch .v2-bcast h4 { color: var(--v2-screen-ink); }
.gsh .v2-tier-watch .v2-bcast p  { color: color-mix(in oklab, var(--v2-screen-ink) 78%, transparent); }
.gsh .v2-tier-watch .v2-bcast .v2-link { color: var(--v2-screen-ink); }
.gsh .v2-tier-watch .v2-bcast.is-static .v2-bcast-k::before {
  background: color-mix(in oklab, var(--v2-screen-ink) 35%, transparent);
  box-shadow: none;
}

/* Tier 04 now carries four cards — Acquret, two reserved slots and the
   nomination card — so the locked `.gsh[data-lab] .pl-grid` auto-fit lays
   four even tracks on its own and needs NO override. The earlier two- and
   three-column pins were deleted rather than edited; when the real channels
   land they simply replace the slots and the grid is already right.

   The slots are placeholders and must read as such. They are not links, they
   carry no channel name, and the bezel goes dashed with the tally light dead
   — the screen-off state of the same chassis, so an empty bay still belongs
   to the rack instead of looking like a broken card. */
.gsh .v2-tier-watch .v2-bcast.is-slot {
  border-style: dashed;
  border-color: color-mix(in oklab, var(--v2-shell) 78%, var(--v2-screen));
}
.gsh .v2-tier-watch .v2-bcast.is-slot h4 {
  color: color-mix(in oklab, var(--v2-screen-ink) 62%, transparent);
  font-style: italic; font-weight: 400;
}
.gsh .v2-tier-watch .v2-bcast.is-slot .v2-bcast-k { color: color-mix(in oklab, var(--v2-screen-ink) 60%, transparent); }
.gsh .v2-tier-watch .v2-bcast.is-slot .v2-bcast-k::before {
  background: color-mix(in oklab, var(--v2-screen-ink) 30%, transparent);
  box-shadow: none;
}
.gsh .v2-tier-watch .v2-link-off {
  color: color-mix(in oklab, var(--v2-screen-ink) 52%, transparent);
  letter-spacing: .16em; text-transform: uppercase; font-size: 11px;
}

/* ---------------------------------------------------------------------------
   46. Mobile drawer runs the full width of the header bar
   ---------------------------------------------------------------------------
   Lovable, 6 Nov: make the mobile nav full-width below 720px, via
   `left: 16px; right: 16px`.

   That exact declaration cannot work here, and it is worth writing down why
   so it is not reached for again. `.lab-mnav-panel` is `position: absolute`
   and its offset parent is `details.lab-mnav` — the hamburger itself, which
   measures 36px wide (confirmed at 390px: offsetParent `.lab-mnav`, left 325,
   width 36). `left` and `right` resolve against THAT box, not the viewport,
   so the spec as written would render the menu as a 4px sliver from x=341 to
   x=345. The panel reaches full width by being sized, not by being inset.

   Measured before changing anything, the panel was already bar-flush on
   phones — 390px gave panel 20→370 against a header bar of 20→370, from §35.
   What was NOT full width was everything above ~460px, where the 420px cap
   from §35 starts binding: at 600px the panel ran 160→580 inside a 20→580
   bar, and at 720px 280→700 inside 20→700. So the cap is the whole of what
   Lovable is seeing, and dropping it is the entire fix.

   The 20px gutter is kept rather than moved to their 16px: `calc(100vw-40px)`
   is what aligns the panel's edges to the header bar, and §35 existed
   specifically to fix that alignment. 16px would push it 4px proud of the bar
   on both sides and re-introduce the thing §35 removed. */
@media (360px <= width <= 720px) {
  .gsh .lab-mnav-panel { max-width: none; }
}

/* With the disclosure gone the directory is always open, so the body no
   longer needs to read as a nested panel inside the drawer — it is the
   drawer. The label keeps `.mn-shelf`, so the locked flex/`b`/`small` rules
   at gs-site-lab.css:3494-3498 still dress it; only the chevron is dropped. */
.mn-shelf-open { display: block; width: 100%; }
.mn-shelf-lbl { cursor: default; }

/* Flattening the disclosure costs height: the directory that used to be
   behind a 36px summary row is now always open, and at 390x844 — one of the
   TALLER phones — that pushed "Get in touch" 43px past the drawer's scroll
   floor. Reachable by scrolling, but the primary CTA should not need one.

   The height comes back from the body's own chrome rather than from the
   content. As a nested panel it earned a border, a tint, a radius and its own
   padding to separate it from the drawer; as the drawer's main content it is
   just nesting a box inside an identical box. Dropping it reclaims ~26px and
   is what "open layout" should look like anyway. Group padding takes the
   remainder. */
.mn-shelf-body {
  margin: 2px 0 4px; padding: 0;
  border: 0; border-radius: 0; background: none;
}
/* These three all need to clear LOCKED rules, not just load later:
     .gsh .shf-m .shf-g            (0,3,0)  padding: 10px 6px   line 3225
     .gsh .lab-mnav-panel .mn-a    (0,3,0)  padding: 13px 14px  line 3227
   The first pass wrote them at (0,2,0) and (0,1,0) and they were dropped
   silently — content height did not move by a single pixel. Sixth time this
   file has lost that way; the computed value is checked below, not assumed. */
.gsh .shf-m.mn-shelf-body .shf-g { padding: 12px 8px 5px; }  /* top raised in §53 */
.gsh .shf-m.mn-shelf-body .shf-allw { margin-top: 2px; }
/* Retuned 6 Nov: the label lost its subtitle ("Curated tools, playlists &
   spaces"), which the open directory below already says. The 2px bottom was
   sized for a two-line stack; with one line it put the title 2px off the
   directory block. Removing the subtitle freed 19px of drawer height and the
   panel sits ~56px under its max-height, so the room is spent on the title
   rather than banked. */
.gsh .lab-mnav-panel .mn-shelf-lbl { padding-block: 12px 14px; }

/* Widening the drawer has a consequence Lovable's note did not cover: the
   directory is pinned to one column by `.gsh .shf-m .shf-cols` (0,3,0), so at
   720px each row became a label at far left and an arrow at far right with
   ~600px of nothing between them. Full width is only an improvement if the
   width gets used, so from 560px the directory returns to the two columns the
   desktop dropdown already uses. Below 560 it stays single-column.

   Flagged to Lovable as a judgement call rather than a spec item — trivially
   reverted by deleting this block if they want the single column at tablet.
   (0,4,0) against the locked (0,3,0); `.shf-soonrow` has its own 1fr rule at
   gs-site-lab.css:3614 and needs covering separately. */
@media (560px <= width <= 720px) {
  .gsh .shf-m.mn-shelf-body .shf-cols,
  .gsh .shf-m.mn-shelf-body .shf-soonrow { grid-template-columns: 1fr 1fr; }
  .gsh .shf-m.mn-shelf-body .shf-cols .shf-g + .shf-g,
  .gsh .shf-m.mn-shelf-body .shf-soonrow .shf-g + .shf-g {
    border-top: 0; border-left: 1px solid var(--gs-rule);
  }
}

/* ---------------------------------------------------------------------------
   47. Drawer: "Explore the full shelf page" steps down to secondary
   ---------------------------------------------------------------------------
   Kyle, 6 Nov, from a phone: the drawer shows two filled gradient pills —
   this one and "Get in touch" — and they clash. He is right, and it is a
   hierarchy problem rather than a colour one. A drawer should have exactly
   one primary action; two identical pills make the reader choose between
   them, and the one that should win (the CTA) is the lower of the two.

   The gradient comes from the LOCKED `.gsh .shf-all` (gs-site-lab.css:3610),
   which is shared with the desktop `.shf-dd` dropdown. Scoped here to
   `.mn-shelf-body` so the desktop dropdown is untouched — only the burger
   menu changes.

   Secondary, not invisible: it keeps the pill shape and padding so it still
   reads as a tappable destination next to the arrowed directory rows, and
   takes a hairline border plus the green arrow the rest of the shelf uses.
   A plain text link was the alternative and was rejected — it would have
   been the only unbordered interactive element in the drawer.

   This also makes §24's white-ink fix inert for this element. That rule
   existed because the gradient needed light text; with no gradient the text
   is ink, set explicitly here rather than left to fall back. The §24 comment
   is kept — the undefined-token trap it documents is still worth having.

   Specificity: locked base is (0,2,0) and its `:hover` / `:focus-visible`
   (0,3,0). Base here is (0,4,0) so it clears both; the hover below is (0,5,0)
   so it clears this base in turn. Computed values read back, not assumed. */
.gsh .shf-m.mn-shelf-body .shf-all {
  background: transparent;
  border: 1px solid color-mix(in oklab, var(--gs-ink) 22%, transparent);
  border-radius: 999px;
  color: var(--gs-ink);
  box-shadow: none;
  transform: none;
}
.gsh .shf-m.mn-shelf-body .shf-all .gs-arr { color: var(--gs-green-active, #1B6B38); }
.gsh[data-theme="dark"] .shf-m.mn-shelf-body .shf-all .gs-arr { color: var(--gs-green, #35BD6B); }
.gsh .shf-m.mn-shelf-body .shf-all:hover,
.gsh .shf-m.mn-shelf-body .shf-all:focus-visible {
  background: color-mix(in oklab, var(--gs-ink) 6%, var(--gs-ground));
  border-color: color-mix(in oklab, var(--gs-ink) 38%, transparent);
  box-shadow: none;
  transform: none;
}

/* ---------------------------------------------------------------------------
   48. /about closing fold — the two columns share a top edge
   ---------------------------------------------------------------------------
   Kyle, 6 Nov: the closing band reads messy. It does, and §42 did not touch
   the cause — it fixed the column RATIO and the sentence splits, which is why
   the fold still looked wrong after that work landed.

   The cause is `align-items: center` on the locked band (gs-site-lab.css:3842).
   The two columns hold different amounts — left is kicker + headline, right is
   paragraph + CTA — so centring them means NOTHING lines up at either edge.
   Measured at 1280 before this change:

     left  (kicker + headline)   top 2892   bottom 3007   height 115
     right (paragraph + CTA)     top 2857   bottom 3043   height 186

   The right block started 35px ABOVE the left kicker, floating against
   nothing. `align-items: start` puts the kicker and the paragraph's first
   line on one baseline — measured top delta 0 at 1024, 1280 and 1440.

   `end` was tested too and is worse (delta -71): it hangs the headline off
   the CTA's bottom edge, which is not a line anyone reads to.

   Scoped to the same >=761px query as §42, so the phone layout — now one
   column again — is untouched, where `align-items` has nothing to align.

   The 40px gutter is deliberately LEFT ALONE. Widening it reads better at
   1280 but narrows the left column, and the column width is already the
   binding constraint on the headline between ~1025 and ~1190 (see below).
   56px was the most it could take before the headline broke at 1280; 64px
   broke it outright. Not worth spending the headline's margin on air. */
@media (width >= 761px) {
  .gsh.ab .lab-close-band { align-items: start; }
}

/* ---------------------------------------------------------------------------
   49. /quick-q cards become a swipe carousel on phones
   ---------------------------------------------------------------------------
   Kyle: on a phone the Quick Q cards are a long vertical list while /tools,
   /games and /playlists all swipe. He is right that it is the odd one out —
   these cards are the tallest on the site (ping + subtext + reply + actions),
   so a stack of four is a very long scroll to reach the fourth.

   Reuses `.v2-snap`, the carousel helper the other V2 pages already use,
   declared on the grid in the markup so the intent is visible there rather
   than hidden in a selector. But `.v2-snap` alone could never have worked:
   it is (0,1,0) against `.gsh[data-lab] .qq-grid` (0,3,0) and the
   `.v2-qq-grid` single-column media rule, both of which set the display and
   the columns. Hence the (0,4,0) rule below — the class declares the intent,
   this gives it the weight to land. Same lesson as §43 and §46; the class
   being present in the HTML is not the same as the rule applying.

   Breakpoint is 768px, which is neither the 759px the other V2 carousels use
   nor the 760px of `.v2-qq-grid`. THREE rules drop this grid toward one
   column and they disagree:

     .v2-qq-grid            (0,1,0)  1fr at <= 760   line 2308
     .gsh[data-lab] .qq-grid (0,3,0)  1fr at <= 768   line 378
     other V2 carousels              -       <= 759   -

   The (0,3,0) rule wins, so the real single-column region is **<= 768**, and
   measuring 761px confirmed it: one 721px track, four rows — a stack of tall
   cards, exactly the thing being fixed, just at tablet width. Scoping the
   carousel to 759 or 760 would have left 761-768 as that stack and looked
   like the fix simply did not work on a tablet.

   So the breakpoint tracks the rule that actually governs, not the nearest
   convention. If line 378 ever moves, this moves with it.

   `grid-template-columns: none` is explicit because the element keeps its
   grid classes; switching `display` to flex leaves the column track
   definition inert but present, and leaving it set invites confusion later. */
@media (width <= 768px) {
  .gsh[data-lab] .qq-grid.v2-snap {
    display: flex;
    grid-template-columns: none;
    gap: 12px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    margin-inline: -20px;
    padding: 4px 20px 12px;
    scroll-padding: 0 20px;
  }
  .gsh[data-lab] .qq-grid.v2-snap::-webkit-scrollbar { width: 0; height: 0; display: none; }
  .gsh[data-lab] .qq-grid.v2-snap > .v2-slip {
    flex: 0 0 82%;
    scroll-snap-align: start;
  }
}

/* The persona filter hides cards with the `hidden` attribute, and `.v2-slip`
   carries an author `display: flex`. Author rules beat the UA sheet's
   `[hidden] { display: none }` on origin, not specificity, so whether the
   filter works at all rests on a UA detail — it does today in Chrome, which
   is why this was never visible. Stating it explicitly removes the question,
   and matters more now: in a flex row a card that failed to hide would not
   just appear, it would occupy a scroll slot. */
.gsh .v2-slip[hidden] { display: none; }

/* ---------------------------------------------------------------------------
   50. Mobile drawer clears the header bar
   ---------------------------------------------------------------------------
   Kyle, from an iPhone 17 Pro Max: the header and the drawer nearly touch.
   Measured at 430x932 — they do not nearly touch, the gap is exactly **0px**.

   The locked `top: calc(100% + 10px)` (gs-site-lab.css:1922) is measured from
   the panel's offset parent, which is the hamburger BUTTON, not the header
   bar. At 430px the hamburger ends at y=58 and the bar at y=68, so the bar's
   own 10px of padding below the button consumes the entire offset and the
   panel lands flush against the bar's edge.

   Measured options: 18px -> 8px gap, 20px -> 10px, 24px -> 14px. Taking 20px
   for a 10px visual gap, which is the separation the original 10px was
   plainly reaching for before the offset parent ate it.

   `max-height` drops by the same 10px so the drawer keeps the bottom room it
   had — pushing the top down without this would spend the gap twice, once at
   the top and again off the bottom of the screen. */
@media (width <= 720px) {
  .gsh .lab-mnav-panel {
    top: calc(100% + 20px);
    max-height: calc(100vh - 94px);
    max-height: calc(100svh - 94px);
  }
}

/* ---------------------------------------------------------------------------
   51. /about "Built by • marketers" — baseline and gradient
   ---------------------------------------------------------------------------
   Kyle: the pill's word and dot sit lower than "Built by", on every device.
   Measured by range rather than by eye: identical font size and identical
   67px text-rect heights, with "marketers" **6.5px lower** at 56px type. So
   it is purely a baseline offset, not a size or font difference.

   The cause is the locked `vertical-align: middle` on an inline-flex box that
   is taller than its text (padding plus line-height 1.1). `middle` aligns the
   BOX's centre to the parent's x-height midpoint, so any box taller than the
   text rides low — and the taller the display type, the worse it reads.

   `baseline` was tried first and is WORSE (12.6px): for an inline-flex the
   baseline comes from the first flex item, and the first item here is the dot
   `::before`, a textless box whose baseline is its bottom edge. Negative
   values were tried too and move it further down — the sign is inverted from
   what it looks like.

   0.224em zeroes it, and it is in `em` because the offset scales with the
   type. Verified at two sizes rather than assumed:

     1280px / 56.32px type:  6.46px -> -0.02px
      430px / 30px    type:  3.74px ->  0.28px

   --- the gradient -------------------------------------------------------

   The text takes the CTA's own gradient token. It must sit on an inner span:
   `background-clip: text` clips EVERY background on the element, so putting
   the gradient on `.ab-pill` itself would clip the pill's own raised fill to
   the glyphs and destroy the pill shape.

   The dot is `background: currentcolor`, and gradient text needs
   `color: transparent` — which would have made the dot vanish. It gets the
   gradient explicitly instead, so the mark and the word read as one object.

   Guarded by `@supports`: without `background-clip: text`, `color:
   transparent` is invisible text rather than a degraded colour. Outside the
   query the pill keeps its locked green, so the fallback is the design as it
   shipped. */
.gsh .ab-pill { vertical-align: 0.224em; }

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .gsh .ab-pill-t {
    background-image: var(--gs-cta-gradient);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
  .gsh .ab-pill::before {
    background: var(--gs-cta-gradient);
  }
}

/* ---------------------------------------------------------------------------
   52. Desktop shelf dropdown — clears the header, and the bar loses its tint
   ---------------------------------------------------------------------------
   Kyle: the desktop dropdown has the same problem as the mobile drawer, and
   the directory bar's background should be white like the rest of the panel.

   Both correct. The dropdown is the third instance of the same offset-parent
   trap: `top: calc(100% + 22px)` (gs-site-lab.css:3195) is measured from
   `details.shf-dd` — the summary — not from the header bar, so the bar's own
   padding below the summary absorbs the whole 22px and the panel lands flush
   at **0px**. (A first reading showed -6px, which was the `shfIn` animation
   caught mid-flight; measured against an explicit value it is 0.)

   32px gives a 10px gap — deliberately the same 10px §50 gave the mobile
   drawer, so the two menus sit the same distance off the same bar instead of
   each being tuned on its own.

   The bar goes `transparent` rather than being repainted white. The panel is
   `#FFFFFF` in light and `rgb(20,23,33)` in dark, so transparent means "the
   panel's colour, whatever it is" and the request holds in both themes
   without a second hardcoded value to keep in sync. The `border-bottom` stays,
   so the bar is still separated from the directory — it just stops being a
   different surface. The green glyph glow is untouched.

   (0,3,0) on both: the top rule is (0,3,0) and loads earlier; the bar's dark
   background rule `.gsh[data-theme="dark"] .shf-bar` is also (0,3,0), so
   `.gsh .shf .shf-bar` ties it and wins on order. A plain `.gsh .shf-bar`
   (0,2,0) would have fixed light and silently left dark tinted. */
.gsh .shf-dd .shf { top: calc(100% + 32px); }
.gsh .shf .shf-bar { background: transparent; }

/* --- §51 addendum: the dot centres on the word, not on the line box -------
   Kyle, after §51 shipped: the dot still is not centred against "marketers".

   He is right, and it is a different fault from the baseline one. Measured at
   1280 with real glyph metrics:

     baseline              325.38
     dot centre            304.36
     x-height centre       310.82   <- where a bullet before lowercase belongs
     cap-height centre     305.28

   The dot is **6.47px above the x-height centre** and within 1px of the CAP
   centre. That is exactly what `align-items: center` produces: it centres the
   dot on the flex line box, which spans the full em — ascender and descender
   space included — rather than on the letterforms beside it.

   For a word with capitals that reads correctly. "marketers" is all
   lowercase, so its visual mass is the x-height band and the dot floats high
   above it. The reference has to be the x-height, not the line box.

   0.115em brings it down onto that centre, in `em` so it tracks the type.
   Verified at two sizes rather than assumed:

     56.32px type:  6.47px off -> -0.01px
     30px    type:            -> -0.21px

   For the record the BASELINE is not the problem and was not touched again:
   "marketers" to "Built by" measures -0.02px at 1280 and 0.28px at 430, i.e.
   0.009em — below the threshold of anything visible. §51's vertical-align
   already settled that; this is the remaining, and genuinely visible, half. */
.gsh .ab-pill::before { position: relative; top: 0.115em; }

/* --- §51 addendum 2: the PILL BOX, which the first two passes never checked -
   Kyle, third time on this element: the pill itself is misaligned. He is
   right, and the reason I missed it twice is that I kept measuring the things
   I had already decided to look at — the text baseline, then the dot — and
   never the box around them.

   Measured against "Built by" at 45px type:

     air above the "B" cap       12.2px
     air below the "y" descender  2.6px
     pill centre vs cap band     -0.76   <- looked fine, which is why it passed
     pill centre vs FULL band    -4.83   <- the actual fault

   So the pill is correctly centred on the cap band and still rides high over
   the text as a whole, because "Built by" has a descender and the cap band
   ignores it. Nearly four times as much air above the capital as below the
   tail reads as a box floating up and to the right of its line.

   Centring on the full band — cap top to descender bottom — gives 7.4px on
   both sides.

   The box has to move WITHOUT the contents moving, since the baseline and the
   dot are both already correct. A transform does that: it shifts the box
   visually without touching layout or the baseline computation, and the two
   children take an equal counter-shift so they stay exactly where they are.
   One constant, applied three times, rather than retuning `vertical-align`
   and then compensating it twice with different numbers.

   `em` so it tracks the type. Verified the baseline does not move: -0.12px.

   The lesson, and it is the third variant of the same one on this element:
   "aligned" has to name its reference. Baseline, x-height centre and full-band
   centre are three different answers, and this element needed all three to be
   right at once. */
.gsh .ab-pill { transform: translateY(0.107em); }
.gsh .ab-pill .ab-pill-t { transform: translateY(-0.107em); }
.gsh .ab-pill::before { transform: translateY(-0.107em); }

/* ---------------------------------------------------------------------------
   53. Drawer directory — redistribute the space it already has
   ---------------------------------------------------------------------------
   Kyle, on a phone: the group headers sit too close to the rule above them and
   look untidy, the section has space that is not being used properly, and the
   item titles must not crowd their own descriptions.

   Measured inside one group before this change:

     divider rule -> "01 THE OPERATOR KIT"       5px   <- too tight
     header -> first item                        8px
     item padding-top                           10px
     "Tools" -> "Calculators & funnel audits"    2px   <- too tight
     one item -> the next                       26px   <- the wasted space

   So the complaint is exactly right, and it is a distribution problem rather
   than a shortage: 26px sits between two items while a title and its own
   description are 2px apart. The eye groups by proximity, so at 2px the
   description reads as part of the title, and at 26px the items read as
   further apart than they are.

   Both tight values are OURS. The 5px is §46, where the group padding was cut
   from 10px to reclaim drawer height so the CTA would fit; §85 later removed
   the label's subtitle and gave that height back, but the padding was never
   restored. The 2px is §24's `row-gap`.

   Three changes, netting +22px:

     group padding-top   5 -> 12px   (+7 x 4 groups  = +28)
     row-gap             2 -> 5px    (+3 x 6 items   = +18)
     item padding-block  9 -> 7px    (-4 x 6 items   = -24)

   The third is what makes the first two affordable without the drawer
   scrolling. It is not a squeeze: the item box keeps roughly its height while
   its contents breathe, and the space between one item and the next drops
   from 26px to 22px — still comfortably more than the 5px now separating a
   title from its description, so the grouping reads correctly.

   Budget checked at the tightest common phone, 390x844: cap 750, content was
   718, so 32px spare. +22 leaves the CTA reachable without a scroll, which is
   what §85 established and should not be given back.

   (0,4,0) on the item padding: the locked `.gsh .lab-mnav-panel .shf-i`
   is (0,3,0), so a two-class selector would have lost silently. */
.gsh .shf-m.mn-shelf-body .shf-i {
  padding-block: 7px;
  row-gap: 5px;
}

/* ---------------------------------------------------------------------------
   54. Drawer: About first, and the CTA pinned so it can never read as cut
   ---------------------------------------------------------------------------
   Kyle, two observations on a phone.

   1. "Get in touch" looks cut at the bottom, and the scroll that would reveal
      it is not something everyone notices.
   2. About gets lost under ~500px of shelf directory.

   **The CTA.** Nothing was actually clipped: measured, it has 9px between it
   and the panel's bottom edge at every size — inside a panel with a **20px
   corner radius**. A square-shouldered button sitting 9px inside a 20px curve
   reads as cut, because its corners are visually inside the radius. At
   375x667 the drawer also overflows by 169px, so the bottom genuinely is a
   scroll boundary, and a flush edge there looks terminal rather than
   continuing.

   Padding alone would only soften the symptom. `position: sticky` answers
   what was actually said: the button stays on screen while the directory
   scrolls behind it, so it is always visible, never reads as cut, and the
   user never has to discover the scroll to reach the primary action.

   A side effect worth stating because it relaxes an earlier constraint: §85
   and §92 both had to fit the whole drawer above its cap so the CTA would not
   need a scroll. **That constraint is now gone** — the CTA is reachable at any
   drawer height — which is why the panel can afford 14px of bottom padding
   here rather than the 8px it was rationed to.

   The upward shadow is not decoration: it is the cue that content passes
   behind the button. Without it a pinned bar on a matching ground reads as
   the end of the list.

   **About first.** Moved in the MARKUP on all 15 pages, not with CSS `order`.
   `order` moves only what is seen; keyboard focus and screen readers would
   still have run shelf -> About, which is exactly the kind of split that
   makes a menu unusable without sight. The drawer is mobile-only — the
   hamburger is hidden from 768 up and the desktop nav is separate markup — so
   desktop ordering is untouched. */
@media (width <= 720px) {
  .gsh .lab-mnav-panel { padding-bottom: 14px; }
  .gsh .lab-mnav-panel .mn-cta {
    position: sticky;
    bottom: 0;
    z-index: 2;
    box-shadow: 0 -10px 16px -10px color-mix(in oklab, var(--gs-ink) 26%, transparent);
  }
}

/* ---------------------------------------------------------------------------
   55. Clock offset chip — restore the font that was lost in transit
   ---------------------------------------------------------------------------
   Kyle: the BASE chip looks oversized and heavy next to Lovable's.

   Not a design difference. Lovable's source says:

     .lab-ck-off { flex:none; font:500 10px/1 var(--gs-mono); ... }

   What reached `gs-site-lab.css` (line 2477) is that rule with the `font:`
   shorthand serialised into EMPTY longhands — `font-weight: ;` `font-size: ;`
   `font-family: ;` and so on, names present, values gone. A blank declaration
   is dropped, so the chip inherits the page default and renders **600 / 16px
   / Helvetica Neue** instead of **500 / 10px / JetBrains Mono**.

   Proof it is a genuine fallback and not equivalent notation: `--gs-ui` is
   Space Grotesk, it is loaded, and a rule whose shorthand survived
   (`.shf-i small`) renders Space Grotesk correctly. Only the blanked rules
   come out Helvetica.

   Restored as LONGHANDS, not the shorthand, and deliberately without
   `font-weight`. Line 2515 sets `font-weight: 600` on purpose — Lovable sets
   600 there too — and a `font:` shorthand here would reset it to 500 and
   silently undo their own later rule. Only the three properties that are
   actually missing are written back.

   Mobile is untouched: `.gsh .lab-ck-off.is-base { font-size: 8.5px }` is
   (0,3,0) inside a media query and still beats this (0,2,0), so the phone
   keeps the smaller base chip Lovable specified.

   NOT changed here, on Kyle's instruction to stay inside what he raised:
   the same blanking affects `.tl-stats b`, `.dk-idx strong` and
   `.lab-sh-fmt`. Listed for a separate decision rather than widened into
   this fix. `.clock .tm` was checked and is NOT affected — Lovable's own
   later rules set its family, size and weight, so the blank base rule never
   mattered. `.shf-i b` is likewise already correct. */
.gsh .lab-ck-off {
  font-family: var(--gs-mono);
  font-size: 10px;
  line-height: 1;
}

/* ---------------------------------------------------------------------------
   56. Phones: the offset chip moves onto the time row
   ---------------------------------------------------------------------------
   Kyle's call, and a deliberate change to Lovable's layout rather than a
   restoration — flagged to them as a specific case, not a blanket override.

   Lovable hide every offset on phones (`.lab-ck-off { display: none }`,
   gs-site-lab.css:2520) and re-show only `.is-base`. The reason is plain once
   measured: beside the city name there is no room. At 390px the cards are
   170px wide and "San Francisco" plus a chip overruns — at 10px AND at 8.5px,
   so shrinking the chip does not rescue it.

   Kyle spotted the room is on the TIME row instead. It is: the time text is
   ~60px inside a 148px row, leaving **88-90px** against the 47px the widest
   chip needs. With the chip there, nothing clips on any card.

   --- the trap, found by trying it ---------------------------------------

   The chip CANNOT be a child of `.tm`. `gs-runtime.js` does

     qs(".tm", c).textContent = new Intl.DateTimeFormat(...)

   on every tick, and assigning `textContent` destroys all child nodes. Moving
   the chip inside `.tm` looked right and then silently vanished within a
   second. A screenshot taken fast enough would have passed it.

   So the chip stays exactly where Lovable put it in the markup, and is placed
   visually instead. `top: 100%` on an absolutely positioned chip inside the
   header means "just below the header", so it follows the header's real
   height rather than a number copied from a measurement — if the header ever
   wraps or changes height, the chip travels with it.

   Desktop is untouched by construction: everything here is inside the same
   `width <= 719px` query Lovable used to hide the chips, so above 720px not a
   single declaration applies.

   All twelve chips move together, `.is-base` included. Leaving BASE in the
   header while the others sat on the time row would have put the same
   component in two places on one screen. */
@media (width <= 719px) {
  .gsh .lab-ck-top { position: relative; }
  .gsh .lab-ck-off,
  .gsh .lab-ck-off.is-base {
    display: inline-flex;
    position: absolute;
    top: 100%;
    right: 0;
    font-size: 8.5px;
    /* 11px, not 6: measured at 6px the chip sat 5px above the time's vertical
       centre. This lands it level with the digits. */
    margin-top: 11px;
  }
}

/* ---------------------------------------------------------------------------
   57. The remaining blanked fonts, restored on Lovable's ruling
   ---------------------------------------------------------------------------
   Lovable, 7 Nov: restore the rest. Their words — "our design system reserves
   Space Grotesk for metrics and UI; Helvetica on stats was unintended
   minifier fallout." That confirms §55's diagnosis: a build step mangled the
   `font:` shorthands into empty longhands, it was never a design change.

   Two restored, one deliberately not:

     .tl-stats b      700 15px Helvetica   -> 700 clamp(18,2vw,24) Space Grotesk
     .dk-idx strong   700 18px Helvetica   -> family only; 700/18px already right
     .lab-sh-fmt      NO CHANGE — see below

   `.lab-sh-fmt` is left alone. Their ruling asks for "500 11px", and it
   already renders exactly 500 11px JetBrains Mono. Their own source file says
   `600 10px`, so the ruling and the source disagree — and the ruling matches
   what ships today. Changing it to 600/10px would contradict the instruction
   we were just given, so the live value stands and the discrepancy is in the
   reply back to them. Checked, not assumed: this is the second time a Lovable
   spec has described a value the build already had.

   Longhands again, not the `font:` shorthand, for the same reason as §55 —
   a shorthand resets every sub-property, including ones later rules set on
   purpose. Only what is actually wrong is written back. */
.gsh .tl-stats b {
  font-family: var(--gs-ui);
  font-size: clamp(18px, 2vw, 24px);
  font-weight: 700;
  line-height: 1;
}
.gsh .dk-idx strong { font-family: var(--gs-ui); }
