/* sameastham.com — design tokens + system
   Dual-theme (OS-driven via prefers-color-scheme):
   light = intaglio inks on cotton paper;
   dark  = the deep-teal plate. Semantic tokens are the only switch point —
   components never reference raw hex. Three type roles: Literata (display),
   Schibsted Grotesk (text), Spline Sans Mono (utility). Self-hosted, subset. */

@font-face {
  font-family: 'Literata';
  src: url('../fonts/literata-v.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
}
@font-face {
  font-family: 'Schibsted Grotesk';
  src: url('../fonts/schibsted-v.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
}
/* Real italic, not a synthetic slant. Static 400 rather than the variable
   400-700 of the roman: the one place italic is used sets no other weight, and
   pinning the axis takes the face from 25.4KB to 12.1KB. Subset to exactly the
   roman's character set, so the italic can render anything the upright can and
   a copy edit cannot produce a missing glyph. */
@font-face {
  font-family: 'Schibsted Grotesk';
  src: url('../fonts/schibsted-italic.woff2') format('woff2');
  font-style: italic;
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Spline Sans Mono';
  src: url('../fonts/spline-mono-v.woff2') format('woff2');
  font-weight: 400 600;
  font-display: swap;
}

:root {
  color-scheme: light dark;

  /* ── Semantic tokens, defined per theme. Light = intaglio inks on cotton
     paper; dark = the deep-teal plate (user-tuned). Components reference ONLY
     these names; the theme blocks below are the single switch point. ── */

  /* light (default) — warm papers, petrol + carmine inks */
  --paper:      #F7F2E3;  /* page stock */
  --surface:    #FCF9F0;  /* card stock */
  --forest:     #EFE6CE;  /* inset / flagship-card stock */
  --ink:        #1E2A2B;  /* engraver's black */
  --muted:      #44514F;  /* secondary text — 7.4:1 on paper */
  --accent:     #14555C;  /* petrol — fills, buttons, the design point */
  --accent-text: #14555C; /* petrol is text-grade in light (7.5:1) */
  --oxide:      #8E1F2C;  /* carmine — exception strokes */
  --oxide-text: #8E1F2C;  /* carmine is text-grade in light (7.9:1) */
  --hair:       #CFC5A9;
  --rule:       #9A8F6E;
  --on-accent:  #F7F2E3;  /* text on accent fills */
  --accent-hover: #0C3B41;
  --link-hover: #8E1F2C;  /* petrol → carmine, the security-print move */
  --link-on-inset: #14555C;
  --focus-ring: #8E1F2C;
  --selection-bg: #F1E4C3;
  --shadow-1: 0 1px 2px rgba(30, 42, 43, 0.07), 0 2px 8px rgba(30, 42, 43, 0.05);
  /* The one element on the site that genuinely floats. Everything else stacks —
     see the press-shadow note further down — but the cover is a sheet lying on
     the page, and without a shadow under its bottom edge the lift reads as the
     page ending rather than as paper being drawn off it. Deeper than --shadow-1
     and still restrained: a tight contact shadow plus a wider soft one, both
     offset down only. */
  --shadow-lift: 0 10px 22px -6px rgba(30, 42, 43, 0.22), 0 2px 4px rgba(30, 42, 43, 0.10);
  /* schematic plate inks */
  --sg-structure: #1E2A2B;
  --sg-plate:     #FCF9F0;
  --sg-content-stroke: #44514F;
  --sg-faint-stroke: #CFC5A9;
  --sg-ghost-fill: #EFE6CE;
  --sg-anno-stroke: #8A7F5C;
  --sg-label-ink: #44514F;
  --sg-fignum-ink: #1E2A2B;
  --hatch-neutral: #9A8F6E;
  --hatch-accent-ink: #14555C;
  --hatch-exception-ink: #8E1F2C;

  /* evidence screenshots are captures of light-ground interfaces; the frame
     behind them stays white in both themes so no seam shows while they load.
     Dimming or inverting them would misrepresent the evidence. */
  --shot-ground: #FFFFFF;

  /* type roles */
  --font-display: 'Literata', 'Iowan Old Style', Georgia, serif;
  --font-text: 'Schibsted Grotesk', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-mono: 'Spline Sans Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* rhythm */
  --header-h: 4.15rem;  /* measured, not guessed: the sticky header renders at
                           70px at both 360px and 1280px (70/17 = 4.12rem).
                           Anchor targets offset by this so a jump to #services
                           doesn't land behind the bar. */
  --measure: 66ch;
  --col: 1120px;
  --section-gap: clamp(4.5rem, 10vw, 8rem);

  /* dark-on-dark strokes read fainter than light-on-light at equal contrast
     ratio — the dark value below compensates so the plate texture matches
     perceptually, not just by the numbers (the 1:1.9 ratio is the tuning).
     Ceiling is set by contrast, not by taste: the plate is position:fixed at
     z-index:-1, so it paints under any text whose own box is transparent
     (.findings-list, .eyebrow, figcaption, .fig-caption, .capsule .caption).
     At 0.16 a stroke under --muted dropped it to 5.46:1, below the 6.0 floor;
     0.10 puts the worst case back to 6.14:1. */
  --plate-opacity: 0.10;

  /* the plate texture's four stroke roles (tick bezel, two rosette
     passes, medium ring). Light keeps the real multi-ink registration
     look the asset ships with; dark collapses to the same two neutral
     tones the original concentric-ring texture used — no separate
     accent hues competing with the page on the dark plate. */
  --plate-1: var(--sg-structure);
  --plate-2: var(--accent);
  --plate-3: var(--oxide);
  --plate-4: #A9761F;
}

/* dark plate — the deep-teal scheme, switched by OS preference */
@media (prefers-color-scheme: dark) {
  :root {
    --paper:      #003F42;
    --surface:    #104C50;
    --forest:     #245F4C;
    --ink:        #F6F5F1;
    --muted:      #C6D8D5;
    --accent:     #73A942;
    /* Lightened from #9CC862 (Sam's call, 2026-08-25). On --surface it measured
       4.997:1 against a 4.95 floor -- passing on rounding luck rather than by
       design. The alternative was darkening the card stocks, which is the only
       other direction that works, but that puts the cards BELOW paper's
       luminance and inverts the raised-stock metaphor the whole dark theme
       rests on. Moving the text instead keeps the cards raised. 5.527:1 now. */
    --accent-text: #AAD078;
    --oxide:      #D07B5E;
    /* Same move, same reason: 4.952:1 on --surface, i.e. 0.002 above the floor.
       Now 5.526:1. */
    --oxide-text: #EBB9A3;
    --hair:       #2A6668;
    --rule:       #35777A;
    --on-accent:  #002B2E;
    --accent-hover: #82B855;
    --link-hover: #FFFFFF;
    --link-on-inset: #F6F5F1;
    --focus-ring: #9CC862;
    --selection-bg: #2A6668;
    --shadow-1: 0 1px 3px rgba(0, 20, 21, 0.4);
    /* A dark ground swallows a soft shadow, so the instinct is to deepen it — but the
       sheet's shadow paints OVER the page beneath, and on this theme that page has light
       text. At 0.58 the peak took body text from 10.76:1 to 3.22:1, under the 4.95 floor.
       Both layers composite, so what matters is their combined alpha: 0.26 and 0.16 give
       0.38, which holds about 5.5:1 at the darkest point of the falloff. */
    --shadow-lift: 0 12px 26px -6px rgba(0, 16, 17, 0.26), 0 2px 5px rgba(0, 16, 17, 0.16);
    --sg-structure: #D9E4E2;
    --sg-plate:     #0B4145;
    --sg-content-stroke: #B4C9C6;
    --sg-faint-stroke: #2A6668;
    --sg-ghost-fill: #1E5A5E;
    --sg-anno-stroke: #55898C;
    --sg-label-ink: #B8CDCA;
    --sg-fignum-ink: #F6F5F1;
    --hatch-neutral: #4E8184;
    --hatch-accent-ink: #73A942;
    --hatch-exception-ink: #D07B5E;
    --plate-opacity: 0.19;
    --plate-1: var(--rule);
    --plate-2: var(--hair);
    --plate-3: var(--rule);
    --plate-4: var(--hair);
  }
}

*, *::before, *::after { box-sizing: border-box; }

/* engraving-plate texture: rosette curves with the framing stripped out (no
   bounding circle, no bezel ticks, no compass-rose center) — those are what
   make the source ornament read as a discrete medallion; without them this
   reads as a fragment of a larger engraved plate, cut off at every edge on
   purpose. Fixed (not background-attachment)
   so it never scrolls with content and never trips iOS Safari's fixed-
   background bugs. Inline SVG so the strokes can bind var(--hair)/var(--rule)
   directly — re-themes for free, nothing to hand-update per theme. */
.paper-plate {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  opacity: var(--plate-opacity);
}
.paper-plate-art {
  width: 100%;
  height: 100%;
  display: block;
}

html {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-text);
  font-size: 106.25%; /* 17px base */
  line-height: 1.65;
  -webkit-text-size-adjust: 100%;
}

body { margin: 0; }

@media (prefers-reduced-motion: no-preference) {
  /* :focus-within so same-page nav clicks scroll smoothly while load-time
     fragment jumps (cross-page anchors) stay instant — Chromium skips the
     initial anchor scroll entirely when the root is always smooth */
  html:focus-within { scroll-behavior: smooth; }
  a, .btn { transition: color 120ms ease, background-color 120ms ease,
                        border-color 120ms ease; }
}

h1, h2, h3, h4, p, ul, ol, figure, blockquote { margin: 0; }

img, svg { max-width: 100%; height: auto; }

a { color: var(--accent-text); text-underline-offset: 0.15em; }
a:hover { color: var(--link-hover); }

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: 2px;
}

::selection { background: var(--selection-bg); }

/* ---------- structure ---------- */

.wrap {
  max-width: var(--col);
  margin-inline: auto;
  /* Floor lowered 1.25rem -> 1rem when the booking CTA joined the header
     (2026-08-25). 5vw only falls below 1rem under a 340px viewport, so this
     changes the gutter on the very smallest phones and nowhere else. It is on
     the SHARED .wrap rather than the header's copy of it, so the header and the
     page content shift together and stay aligned with each other. */
  padding-inline: clamp(1rem, 5vw, 3rem);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--accent);
  color: var(--on-accent);
  padding: 0.6rem 1rem;
  /* must outrank the sticky header, or a focused skip link is painted under it */
  z-index: 30;
}
.skip-link:focus-visible { left: 0; color: var(--on-accent); }

/* ---------- header ---------- */

/* the header follows the page down. On the teardown — fifteen findings, ten
   figures, two tables — the way back to the rest of the site was two or three
   screens behind you by the time you'd finished reading. position:sticky needs
   no JS, so the zero-JS invariant holds.

   The background has to be opaque: content scrolls underneath, and a
   transparent header would let it read straight through the wordmark. That does
   cover the plate texture in the header band, which is the one thing this
   costs. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  padding-block: 1.05rem;
  background: var(--paper);
  border-bottom: 1px solid var(--hair);
}

/* a landscape phone is ~380px tall; a sticky bar there eats the page it is
   meant to help you navigate, so below that it just scrolls away */
@media (max-height: 420px) {
  .site-header { position: static; }
}

/* The header is opaque and sticky, so it occludes the top of the scrollport.
   `scroll-padding-top` states that once, on the scrollport itself, and every
   scroll-into-view the browser performs then clears it: anchor links, but also
   keyboard focus, find-in-page, text-fragment links and `:target`.
   This used to be `scroll-margin-top` on `main[id], section[id], article[id]`,
   which only ever helped those three. Anything else the browser scrolled to --
   a link inside a paragraph, for instance -- landed underneath the header with
   nothing to stop it. Measured before the change: focusing the "Lemonade" link
   in #why put its top at +0.16px, entirely inside the 0–70.3px band the header
   paints over. Sections carry `--section-gap` of their own padding on top of
   this, so landing flush under the header costs no breathing room.
   With script off this is the whole story. With script on, the cover sheet
   displaces the page while it is up, and no fixed value can correct for that --
   the error depends on the reader's scroll position. The script adds a
   per-click `scroll-margin-top` that resolves to the right resting position and
   collapses to zero once nothing is displaced; see index.html. */
:root { scroll-padding-top: var(--header-h); }

.site-header .wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
}

.wordmark {
  font-family: var(--font-display);
  font-weight: 520;
  font-size: 1.2rem;
  color: var(--ink);
  text-decoration: none;
  letter-spacing: 0.01em;
  /* 44x44 minimum. This site publishes a teardown that measures other people's
     controls against 2.5.5, so its own header link has to clear it too. Padding
     grows the hit area; the equal negative margin keeps the header height and
     the baseline alignment with the drawer toggle exactly where they were. */
  display: inline-block;
  padding-block: 0.34rem;
  margin-block: -0.34rem;
}
.wordmark:hover { color: var(--accent-text); }

.site-nav { display: flex; flex-wrap: wrap; gap: 0.25rem 1.4rem; }

.site-nav a {
  font-size: 0.92rem;
  color: var(--muted);
  text-decoration: none;
}
.site-nav a:hover { color: var(--accent-text); text-decoration: underline; }

/* The persistent booking CTA.

   A DIRECT CHILD of the header row, not a child of .site-nav. Two reasons, both
   learned the hard way. Inside the nav, `.site-nav a` (0,1,1) out-specifies
   `.btn` (0,1,0), so the button silently lost its own colour and rendered as
   muted body text on a filled ground. And the nav is `display: none` below
   640px, which would have taken the CTA with it -- here it stays beside the
   drawer toggle at every width, which is the point of it.

   Height is the other trap: `.btn` at its full size is ~51px against a header
   row that is 33.66px, which grew the header to 104.65px between 641 and 810px
   and put every anchor landing that far behind the sticky bar. Same fix the
   wordmark and the drawer toggle already use -- padding buys the 44x44 target,
   an equal negative block margin gives the height back, so the row and
   `--header-h` are untouched.

   Scoped as `.site-header .header-cta` (0,2,0) rather than `.header-cta`
   (0,1,0): `.btn` is defined LATER in this file, so at equal specificity its
   `padding` shorthand won on source order and the button rendered at its full
   51px, growing the header to 117.9px. Specificity, not order, so it no longer
   matters where either rule sits. */
.site-header .header-cta {
  /* The row is `align-items: baseline`. A padded button aligned that way still
     contributes its below-baseline padding to the line box, so the negative
     margin alone left the header 3.33px tall than it should be (73.67 vs
     70.34). Centring this one item takes it out of the baseline calculation
     and lets the margin box be what counts. */
  align-self: center;
  font-size: 0.9rem;
  line-height: 1.2;
  font-weight: 600;
  padding-block: 0.75rem;
  padding-inline: 1rem;
  margin-block: -0.36rem;
  white-space: nowrap;
}
/* Narrow widths: the row is wordmark + CTA + drawer toggle, and the wordmark is
   already 136px of it. Trim the button's inline padding rather than its height,
   so the tap target survives while the row still fits. */
/* Once the drawer takes over, the row is wordmark | CTA toggle. `space-between`
   would strand the CTA in the middle of that row; an auto inline-start margin
   takes the free space instead, so the CTA sits immediately left of the toggle
   with the row's own 24px gap between them (Sam's spec, 2026-08-25). Above this
   width the nav is present and space-between still does the right thing. */
@media (max-width: 800px) {
  .site-header .header-cta { margin-inline-start: auto; }
  .site-header .wrap { column-gap: 24px; }
}

@media (max-width: 430px) {
  .site-header .header-cta { padding-inline: 0.7rem; font-size: 0.85rem; }
}
/* 320px is a supported width -- STATE.md verifies the h1 there -- and it is the
   width where wordmark + CTA + toggle genuinely runs out of room: 349.3 needed
   against 320. The wordmark is the brand and the toggle is at its 44px floor,
   so the CTA and the gap absorb it. Height stays above 44 because padding-block
   grows as the font shrinks. */
@media (max-width: 360px) {
  .site-header .header-cta {
    font-size: 0.78rem;
    padding-inline: 0.45rem;
    padding-block: 0.8rem;
  }
}
/* 24px cannot survive here: 136.2 wordmark + 88.5 CTA + 46.8 toggle + 24 gap +
   34 of gutter = 329.5 against a 320 viewport. Only the gap can give, and only
   below 340 -- every width from 360 up keeps the specified 24. */
@media (max-width: 340px) {
  .site-header .wrap { column-gap: 12px; }
}

/* The section the reader is in. Marked by a rule as well as a colour shift --
   colour alone would carry the whole message, which WCAG 1.4.1 does not allow,
   and the rule is also what reads as "you are here" at a glance. The bar is an
   absolutely positioned ::after so the header's height does not move when the
   mark changes. `aria-current` carries the same fact to assistive tech, and the
   attribute is what this rule selects on, so the two cannot drift apart. */
.site-nav a[aria-current] { color: var(--ink); position: relative; }
.site-nav a[aria-current]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.45em;
  height: 2px;
  background: var(--accent);
}

/* below ~630px the header's own flex-wrap drops the nav onto a second row
   under the wordmark -- switch to the drawer right there instead of letting
   that stacked state show. Zero JS: <details>/<summary> is a native
   disclosure, no toggle script needed.
   The threshold was 640px until the booking CTA joined the row (2026-08-25).
   Measured after: wordmark 136.2 + nav 404.3 + CTA 118.2 + two 25.5 gaps + 73
   of wrap padding = 782.7px needed for a single row, so anything under ~790
   wraps the header onto two lines and takes --header-h with it. 800 gives that
   a little margin for font-loading variance. The consequence is deliberate and
   worth knowing: tablet widths that used to see the full nav now see the
   drawer, which is the price of the CTA never leaving the header. */
.nav-drawer { display: none; }

@media (max-width: 800px) {
  .site-nav { display: none; }

  .nav-drawer { display: block; position: relative; align-self: center; }

  .nav-drawer-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    /* 2.5rem measured 42.5 at this root size -- 1.5px under the minimum this
       site holds other people's controls to. The margin compensation grows by
       half the size increase on each side, so the header height is unchanged. */
    width: 2.75rem;
    height: 2.75rem;
    margin: -0.625rem -0.625rem -0.625rem 0;
    border-radius: 3px;
    cursor: pointer;
    list-style: none;
  }
  .nav-drawer-toggle::-webkit-details-marker { display: none; }
  .nav-drawer-toggle:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

  .nav-drawer-icon,
  .nav-drawer-icon::before,
  .nav-drawer-icon::after {
    display: block;
    width: 1.35rem;
    height: 1.5px;
    background: var(--ink);
  }
  .nav-drawer-icon { position: relative; }
  .nav-drawer-icon::before, .nav-drawer-icon::after {
    content: "";
    position: absolute;
    left: 0;
  }
  .nav-drawer-icon::before { top: -6px; }
  .nav-drawer-icon::after { top: 6px; }

  .nav-drawer-panel {
    position: absolute;
    top: calc(100% + 0.6rem);
    right: 0;
    z-index: 5;
    display: flex;
    flex-direction: column;
    min-width: 11rem;
    padding: 0.5rem;
    background: var(--surface);
    border: 1px solid var(--hair);
    border-radius: 4px;
    box-shadow: var(--shadow-1);
  }
  .nav-drawer-panel a {
    padding: 0.6rem 0.7rem;
    font-size: 0.95rem;
    color: var(--ink);
    text-decoration: none;
    border-radius: 3px;
  }
  .nav-drawer-panel a:hover { background: var(--paper); color: var(--accent-text); }
  /* In the drawer the links are already full-strength ink, so the bar is the
     whole marker -- which also keeps it consistent with the desktop nav, where
     the bar is what says "you are here" and the colour shift is only possible
     because that nav's resting colour is muted. Tinting the text here instead
     would have put it at 5.00:1 against --surface in dark, 0.05 above the floor;
     leaving it at --ink keeps 8.86:1 and loses nothing, because the bar is not a
     colour cue. inset box-shadow rather than a border, so the row does not move
     when the mark changes. */
  .nav-drawer-panel a[aria-current] { box-shadow: inset 3px 0 0 var(--accent); }

}

/* ---------- type roles ----------
   Display carries voice at three weights: 600 (h1), 500 (h2), 460 (payoff).
   Text face carries prose. Mono carries the data register: labels, figures,
   prices, captions — insurance documents have two registers; so does this. */

h1, h2, .display {
  font-family: var(--font-display);
  line-height: 1.12;
  letter-spacing: -0.008em;
}

/* Floor is 2.05rem, not 2.15: at 320px the hero column is 278px and "The UX
   practice" needs 281px at 2.15rem — a 3px miss that wrapped the first beat of
   a two-beat headline. 2.05rem needs 268px and holds. The difference is under
   1px at any width where it is not the binding constraint. */
h1 { font-weight: 600; font-size: clamp(2.05rem, 5.2vw, 3.35rem); }

/* The h1 is two lines but one heading. Never two elements: that breaks heading
   order and hands a screen reader two page titles. Blocks make the break
   deliberate rather than a wrap, and the space between the spans in the source
   keeps the accessible name reading as one continuous sentence. */
h1 .h1-line { display: block; }

/* Line 2 must not wrap. Measured: at 390px the hero column is 348px wide and
   this string needs 18.14px of width per 1px of font-size, so it holds one line
   up to 19.16px — against line 1's 36.55px. Sizing it off the viewport rather
   than off line 1 puts the step-down only where the constraint actually is:
   4.8vw is 18.7px at 390 (a 2% margin under the limit) and grows until min()
   caps it. The cap is 0.97em rather than 1em: at 1280 the hero column is 1018px
   and this line needs 1030 at full size, missing by 12px — 1.2%. Three percent
   under is indistinguishable from equal weight and holds the single line at
   every width. So it reads as one statement in two beats at desktop, and as a
   title with its scope beneath on a phone, without a breakpoint. */
h1 .h1-scope { font-size: min(4.8vw, 0.97em); }
h2 { font-weight: 500; font-size: clamp(1.55rem, 3.2vw, 2.05rem); }

h3 {
  font-family: var(--font-text);
  font-weight: 640;
  font-size: 1.08rem;
  line-height: 1.35;
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 1.1rem;
}
.eyebrow::before {
  content: "";
  display: inline-block;
  width: 1.6rem;
  height: 1px;
  background: var(--accent);
  vertical-align: 0.22em;
  margin-right: 0.7rem;
}

.prose { max-width: var(--measure); }
.prose p + p { margin-top: 1.05em; }

.small { font-size: 0.875rem; color: var(--muted); max-width: var(--measure); }

.num, .price, .offer-terms {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* ---------- buttons ---------- */

.btn {
  display: inline-block;
  font-family: var(--font-text);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  padding: 0.85rem 1.5rem;
  border-radius: 3px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}
.btn:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--on-accent); }
.btn:active { transform: translateY(1px); } /* letterpress */

.cta-note { margin-top: 0.55rem; }

.text-cta {
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

/* ---------- hero + fig. 01 ---------- */

.hero { padding-block: clamp(2.5rem, 7vw, 5rem) 0; }

/* ---------- the cover sheet ----------
   The hero is a sheet of paper the height of the viewport, with the site header
   sitting underneath it. Scrolling slides the sheet off and reveals the nav —
   with no JavaScript and no scroll listener, because DOM order already does it:
   the sheet is simply the first thing on the page and the sticky header is the
   second. min-height rather than height, so a viewport shorter than the content
   lets the sheet grow and scroll instead of clipping it. */
.cover {
  position: relative;
  /* Above the page beneath it, which gets a stacking context of its own from
     `position: sticky`, so the sticky header's z-index: 20 stays contained
     inside it and cannot paint over the sheet. */
  z-index: 2;
  /* The negative margin is the whole trick -- see the --lift block below. */
  margin-bottom: calc(-1 * var(--lift));
  box-shadow: var(--shadow-lift);
  min-height: 100vh;
  min-height: 100dvh;   /* dvh accounts for mobile browser chrome; vh is the fallback */
  display: flex;
  flex-direction: column;
  background: var(--paper);
}

/* the engraved bands run the full bleed at the extreme top and bottom edges,
   outside the frame — the way a certificate's border sits outside its rule */
/* The sheet lifts and the page beneath it holds still, in CSS, with nothing
   moved per frame and no scroll listener.

   `--lift` is the distance the sheet travels. `.cover` pulls the page beneath it
   UP by that much with a negative bottom margin, so the page's LAID-OUT position
   is docY 0 rather than docY coverHeight — which is what every earlier sticky
   attempt got wrong, and why STATE.md 0.05 recorded sticky as structurally
   impossible here. From docY 0, `top: 0` is in range from the first pixel, so the
   page sticks immediately. `.cover-spacer` sits AFTER the page and gives the
   sticky constraint exactly `--lift` of travel before it releases; because the
   spacer replaces precisely the flow height the negative margin removed, the
   document is exactly as tall as cover + page and there is no dead space at the
   bottom. Release lands where the flow position already is, so there is no seam.

   Measured: the page holds at underTop 0.0 from scroll 0 to 998, releases at 999,
   and `underTop == cover.bottom` to the last decimal at every sample past it.

   --lift is 0 by default, so the no-script page is the plain page it always was
   and its anchors land natively. The script switches the lift on. */
:root { --lift: 0px; }
.js-lift { --lift: 100vh; --lift: 100dvh; }
.cover-spacer { height: var(--lift); }
#under { position: sticky; top: 0; z-index: 1; }



.cover-band {
  display: block;
  flex: none;
  width: 100%;
  height: clamp(18px, 2.8vw, 38px);
  opacity: 0.85;
}

/* double rule: the border is the outer line, ::before the inner one */
.cover-frame {
  flex: 1 1 auto;
  position: relative;
  margin: clamp(8px, 1.3vw, 18px);
  border: 1px solid var(--rule);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(2.2rem, 6vw, 4.5rem) clamp(1.25rem, 5vw, 3rem);
  overflow: hidden;
}
.cover-frame::before {
  content: "";
  position: absolute;
  inset: 6px;
  border: 1px solid var(--hair);
  pointer-events: none;
}

.cover-waves {
  position: absolute;
  left: 0;
  top: 6%;
  width: 100%;
  height: 48%;
  opacity: var(--plate-opacity);
  pointer-events: none;
}

.cover-body {
  position: relative;
  max-width: 58rem;
  text-align: center;
}

/* Centred and much larger than the inline hero it replaces. The .h1-scope
   step-down existed to keep the second beat on one line in a narrow left-aligned
   column; centred at this size the line is meant to wrap, so scope returns to
   full size and the two beats read as one statement. */
.cover h1 {
  font-size: clamp(2.3rem, 6.6vw, 5rem);
  line-height: 1.03;
}
.cover .h1-scope { font-size: 1em; }

.cover-sub {
  margin: clamp(1.4rem, 3vw, 2.2rem) auto 0;
  max-width: 30em;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.05rem, 1.9vw, 1.32rem);
  line-height: 1.5;
  color: var(--accent-text);
}

.cover-cred {
  margin-top: clamp(1.5rem, 3.4vw, 2.4rem);
  font-family: var(--font-mono);
  font-size: clamp(0.68rem, 1.05vw, 0.8rem);
  color: var(--muted);
  text-wrap: balance;
}

/* Responsive type below 600px (Sam's request, 2026-08-25).

   The site's clamps were already fluid, but their VW terms are dormant at phone
   widths -- 6.6vw on the h1 is 21px at 320, far under its own 2.3rem floor -- so
   every clamp bottomed out and the type was IDENTICAL at 320 and 390: h1 39.1px,
   subhead 17.85, credibility strip 11.56, section h2 26.35. Nothing scaled where
   it mattered most.
   This tier gives those roles a steeper VW term and a lower floor, so they
   actually shrink on small screens, capped at the size the main clamp would give
   at 600px so the two meet without a step.
   DISPLAY TYPE ONLY. Body prose stays at its 17px root: shrinking headings buys
   fit, shrinking running text just makes it harder to read, and the cover's
   length was the problem here rather than the paragraph size. */
@media (max-width: 600px) {
  .cover h1     { font-size: clamp(1.8rem, 8.6vw, 2.3rem); }
  /* The paragraph tracks the headline instead of getting relatively heavier as
     the screen narrows. Measured, the h1:paragraph ratio was 3.79 at 1440 and
     collapsed to 1.95 at 320 -- the paragraph carrying nearly twice the weight
     against the headline on a phone that it does on a desktop. 0.8rem/3.8vw
     holds it at ~2.26 across the whole mobile range, which also meets the 2.19
     that already applies from 430 to 600, so the curve has no bump in it. */
  .cover-sub    { font-size: clamp(0.8rem, 3.8vw, 1.05rem); }
  .cover-cred   { font-size: clamp(0.6rem, 3.2vw, 0.68rem); }
  h2            { font-size: clamp(1.32rem, 5.4vw, 1.55rem); }
}

.cover-ctas {
  margin-top: clamp(1.5rem, 3.4vw, 2.2rem);
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
  justify-content: center;
}
/* Once the two CTAs stack they should read as one control group, not two
   different-sized buttons -- they measured 144.3 and 174.9 side by side. In a
   column the cross axis is width, so `align-items: stretch` makes them equal by
   construction rather than by a hand-tuned width that would drift the moment
   either label changed. The container is capped so they do not span the whole
   measure, and min() keeps that cap inside the frame at 320. */
@media (max-width: 429px) {
  .cover-ctas {
    flex-direction: column;
    align-items: stretch;
    max-width: min(15rem, 100%);
    margin-inline: auto;
  }
  .cover-ctas .btn,
  .cover-ctas .ghost-btn { text-align: center; }
}

/* the outlined partner to .btn — same box, no fill */
.cover-ctas .ghost-btn {
  padding: 0.85rem 1.5rem;
  font-family: var(--font-text);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--ink);
  text-decoration: none;
  border-color: var(--ink);
}
.cover-ctas .ghost-btn:hover { border-color: var(--accent); color: var(--accent-text); }

/* a short viewport in landscape should not force a full screen of cover before
   any content — the sheet keeps its proportions but stops claiming the fold */
@media (max-height: 460px) {
  .cover { min-height: 0; }
  .cover-frame { padding-block: clamp(1.5rem, 5vh, 2.5rem); }
}

/* 46em used to live here and it capped the headline, not the subhead — the
   subhead carries its own 34em measure below. Line 2 needs 1030px at full size
   and 46em gave the whole hero 782, so equal weight was unreachable and the
   second beat wrapped. Capping the h1 itself does not help: a child cannot
   exceed its parent's width, so the cap has to come off here. */
.hero-copy { max-width: none; }

.hero-copy .subhead {
  max-width: 34em;
  margin-top: 1.3rem;
  font-size: 1.11rem;
  color: var(--ink);
}

.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.6rem;
  margin-top: 2rem;
}

/* the one deliberate break in rhythm: the annotated drawing at full width,
   technical-drawing style. It carried a four-note key while it sat in the hero
   and had to teach the drawing language cold; below the positioning section it
   illustrates a claim the reader has just read, so the caption carries it. */
.plate-fig {
  margin-top: clamp(2.5rem, 6vw, 4.5rem);
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: 4px;
  padding: clamp(1rem, 3vw, 2rem);
}
.plate-fig svg { display: block; width: 100%; }

/* FIG. 01's viewBox (680) is wider than the measured-controls drawing's (627),
   so the generic 34rem floor below renders its labels under the site's own
   10.71px mono-label floor. Needs its own, taller minimum. Scoped to
   .why-aside — the only place this figure appears — so the teardown's
   fig-scale rule below is untouched. 44rem × 17px root = 748px; 748/680 = 1.1,
   so labels hold at 10 × 1.1 = 11px regardless of viewport. Placed ahead of
   the rule below (rather than after) so it parses as its own rule and is not
   swallowed by the pre-existing unterminated declaration two rules down. */
.why-aside .plate-fig.fig-scale svg { min-width: 44rem; }
/* The findings table's own breakpoint (below) drops .table-scroll to
   overflow-x: visible under 900px, because that table switches to a card
   layout there and no longer needs to scroll. .table-scroll is a shared
   utility class, so that rule reaches this figure too and would let its
   forced-width svg spill past the page edge instead of scrolling — FIG. 01
   never switches layouts, so it always needs the scroll. Two classes beat
   the media query's one regardless of source order. */
.why-aside .table-scroll { overflow-x: auto; }

/* The measured-controls drawing is a scale figure: squeezing it into a 360px
   column puts its labels at about 4.5px and destroys the one thing it exists
   to show. It scrolls instead, on the same keyboard-reachable pattern the wide
   tables already use. Scoped to this figure — the home page's plate figure is
   a diagram that scales down fine and must keep doing so. */
.plate-fig.fig-scale .table-scroll { margin-top: 0; }
.plate-fig.fig-scale svg { min-width: 34rem; }

.fig-caption {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.6;
  color: var(--muted);
  border-top: 1px solid var(--hair);
  margin-top: clamp(0.8rem, 2vw, 1.2rem);
  padding-top: 0.8rem;
  max-width: none;
}
.fig-caption b { color: var(--ink); font-weight: 600; }

/* credibility strip */
.cred {
  border-top: 1px solid var(--rule);
  margin-top: clamp(2.5rem, 6vw, 4rem);
  padding-block: 1.1rem;
}
.cred p {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--muted);
  letter-spacing: 0.01em;
}

/* ---------- sections ---------- */

.section { padding-top: var(--section-gap); }
.section:last-of-type { padding-bottom: var(--section-gap); }

.section-head { margin-bottom: 1.4rem; max-width: var(--measure); }
/* Section headings are written as sentences and run to three lines at desktop,
   which is where an unbalanced wrap leaves a one-word last line. Balance
   distributes the lines instead; browsers without it fall back to normal
   wrapping, so nothing depends on it. */
.section-head h2 { margin-bottom: 0; text-wrap: balance; }

.outcome {
  font-family: var(--font-display);
  font-weight: 460;
  font-size: clamp(1.25rem, 2.4vw, 1.5rem);
  line-height: 1.4;
  color: var(--accent-text);
  max-width: 30em;
  margin-top: 1.6rem;
}

/* ---------- offer stack ---------- */

.offer-audit {
  background: var(--forest);
  border: 1px solid var(--hair);
  border-top: 3px solid var(--accent);
  border-radius: 4px;
  padding: clamp(1.5rem, 4vw, 2.5rem);
  margin-top: 2.2rem;
}

.offer-audit-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 2rem;
}

.offer-title {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.45rem;
}

.offer-terms {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
}

.offer-audit > p { max-width: var(--measure); margin-top: 0.9rem; }

.deliverables {
  list-style: none;
  padding: 0;
  margin-top: 1.4rem;
  max-width: var(--measure);
}
.deliverables li {
  padding: 0.65rem 0 0.65rem 1.6rem;
  position: relative;
}
.deliverables li + li { border-top: 1px solid var(--hair); }
.deliverables li::before {
  content: "";
  position: absolute;
  left: 0.15em;
  top: 1.22em;
  width: 0.55em;
  height: 0.55em;
  background: repeating-linear-gradient(45deg, var(--accent-text) 0 1px, transparent 1px 3px);
  border: 1px solid var(--accent-text);
}

.offer-close { color: var(--ink); margin-top: 1.2rem; max-width: var(--measure); }
/* links on forest cards need cream — the link green sits below AA there */
.offer-audit a:not(.btn), .transfer a:not(.btn) { color: var(--link-on-inset); }
.offer-audit a:not(.btn):hover, .transfer a:not(.btn):hover { color: var(--link-hover); }
/* same problem, the focus ring: --focus-ring on --forest is 3.85:1 in dark, an outlier
   next to every other ring on the site (5.00-8.37:1). Reuse the cream above — already
   the vetted color for this exact ground — instead of a new hex; 6.84:1. Light's ring
   is untouched; this only fires under prefers-color-scheme: dark. */
@media (prefers-color-scheme: dark) {
  .offer-audit :focus-visible, .transfer :focus-visible { outline-color: var(--link-on-inset); }
}

.offer-cta { margin-top: 1.6rem; }

.offer-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.4rem;
  margin-top: 1.4rem;
}

.offer-card {
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: 4px;
  padding: 1.5rem 1.6rem;
}
.offer-card h3 { display: flex; flex-wrap: wrap; gap: 0.3rem 1rem; justify-content: space-between; }
.offer-card .price { color: var(--accent-text); font-weight: 500; font-size: 0.9rem; }
.offer-card p { margin-top: 0.7rem; font-size: 0.97rem; }

/* The two cards sit side by side from 901px (.offer-row's grid stays 2-up above the
   900px stack point) up to the width where both titles clear their price. "Embedded
   engagement" is longer than "Advisory retainer", so its price wraps to its own line
   first -- measured wrapping alone between 901-939px, giving the row two different
   header heights while the cards are adjacent and the mismatch is visible. Force the
   same two-line shape on both across that band, independent of title length, instead of
   a threshold that would only patch today's copy. Naturally back to one line together
   once there's room for the longer pair (measured >=940px), and irrelevant once the row
   drops to one column at 900px, where each card has the full row's width regardless. */
@media (min-width: 901px) and (max-width: 944px) {
  .offer-card h3 > span:first-child { flex-basis: 100%; }
}

.capacity { margin-top: 1.5rem; }

/* ---------- audit card subheads ----------
   The audit block now carries three named parts under the offer — the
   deliverable, what I need, what happens after — and they have to be scannable
   without competing with the h3 that names the offer itself. Mono at eyebrow
   scale, ink rather than muted so they read as structure, with a rule above to
   separate the parts on a card that is otherwise one long column. */
.offer-sub {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink);
  margin-top: 2rem;
  padding-top: 1.15rem;
  border-top: 1px solid var(--hair);
}
.offer-sub + p,
.offer-sub + ul { margin-top: 0.9rem; }

/* ---------- the scheduling call to action ----------
   The copy for this is a sentence, not a button label, so it ships as a
   sentence and the link carries the weight instead: heavier, accent-colored,
   underlined thicker than a body link. The aria-label restates the whole
   sentence, because "here" alone is the textbook uninformative link name and
   this site publishes accessibility teardowns. It *contains* the visible word,
   so Label in Name still holds and voice control can still match "here". */
.cta-line {
  margin-top: 1.6rem;
  font-size: clamp(1.1rem, 2.4vw, 1.24rem);
  font-weight: 500;
  line-height: 1.45;
  max-width: var(--measure);
}
/* This has to out-rank the "read a teardown" link directly beneath it — at
   equal weight the secondary link wins on the eye, which is the wrong way
   round on the page's primary action. Size and weight do that work, since the
   copy is a sentence and cannot be a button label. */
.cta-inline {
  font-weight: 700;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.22em;
  padding: 0.2em 0.05em;
}
/* and the secondary link steps back to plain body weight beside it */
.cta-line + .cta-note .text-cta { font-weight: 500; }
.cta-line + .cta-note { margin-top: 0.7rem; }

/* Contracting is terms, not an offer. It used .offer-card, which made it read as
   a fourth engagement sitting under the three real ones.
   No max-width: it takes the wrap, which is exactly what the two cards span
   together at desktop and what the single stacked card spans on mobile, so it
   tracks the row above it at every width without a breakpoint of its own. */
.terms-note { margin-top: 2rem; }
.terms-note p { font-size: 0.875rem; color: var(--muted); max-width: var(--measure); }
.terms-note .capacity { margin-top: 1rem; }

/* Reads as a header without being one. A fourth h3 here would sit as a sibling
   of the three engagement headings under an h2 that counts three of them, and
   anyone navigating by heading would find a fourth thing to buy — which is the
   confusion the card styling was already causing. Mono at .offer-sub's scale so
   it carries the same weight as the audit card's subheads. */
.terms-head {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink);
  padding-top: 1.15rem;
  border-top: 1px solid var(--hair);
  margin-bottom: 0.9rem;
}

/* ---------- work capsules ---------- */

.capsules {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.4rem;
  margin-top: 2.2rem;
}

.capsule {
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: 4px;
  padding: 1.1rem 1.1rem 1.3rem;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.capsule-art {
  background: var(--surface);
  border-radius: 2px;
  overflow: hidden;
}

.capsule h3 { font-size: 1.02rem; }
.capsule h3 a { color: var(--ink); text-decoration: none; }
.capsule h3 a:hover { color: var(--accent-text); text-decoration: underline; }
.capsule h3 .more { color: var(--accent-text); }

.capsule .caption { font-size: 0.9rem; color: var(--muted); }

/* the sample-teardown capsule spans the grid as a full-width row */
@media (min-width: 901px) {
  .capsule-wide {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
    gap: 0.4rem 2.4rem;
    align-items: center;
  }
  .capsule-wide .capsule-art { grid-column: 1; grid-row: 1 / 3; }
  .capsule-wide h3 { grid-column: 2; align-self: end; }
  .capsule-wide .caption { grid-column: 2; align-self: start; }
}

/* ---------- schematic drawing classes ----------
   Vocabulary: petrol = the design point being made;
   oxide = flagged/exception states (always paired with a shape difference);
   hatch = redacted content; hatch-accent = redacted content that is the
   subject; ghost = inert chrome. Stroke weight carries meaning:
   1.5 structure · 1.0 content · 0.75 annotation. */

.sg         { stroke: var(--sg-structure); stroke-width: 1.5; fill: none; }
.sg-fill    { fill: var(--sg-plate); stroke: var(--sg-structure); stroke-width: 1.5; }
.sg-content { stroke: var(--sg-content-stroke); stroke-width: 1; fill: none; }
.sg-faint   { stroke: var(--sg-faint-stroke); stroke-width: 1; fill: none; }
.sg-ghost   { fill: var(--sg-ghost-fill); }
.sg-anno    { stroke: var(--sg-anno-stroke); stroke-width: 0.75; fill: none; }
.sg-accent  { stroke: var(--accent); stroke-width: 1.5; fill: none; }
.sg-accent-fill { fill: var(--accent); stroke: none; }
.sg-oxide   { stroke: var(--oxide); stroke-width: 1.5; fill: none; }
.sg-oxide-fill { fill: var(--oxide); stroke: none; }
.sg-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.05em;
  fill: var(--sg-label-ink);
}
.sg-label-accent { fill: var(--accent-text); font-weight: 600; }
.sg-label-oxide  { fill: var(--oxide-text); font-weight: 600; }
.sg-note {
  font-family: var(--font-mono);
  font-size: 11px;
  fill: var(--sg-label-ink);
}
.sg-fignum {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  fill: var(--sg-fignum-ink);
}

/* SVG hatch patterns take their ink from the theme (defs attrs are fallback) */
#hatch line        { stroke: var(--hatch-neutral); }
#hatch-accent line { stroke: var(--hatch-accent-ink); }
#hatch-oxide line  { stroke: var(--hatch-exception-ink); }

/* annotation layer hides on small screens; the caption carries the notes */
@media (max-width: 700px) {
  .anno { display: none; }
}

/* paper stacks, it doesn't float — soft press shadows on every card */
.offer-audit, .offer-card, .capsule, .decision, .transfer, .trust-panel,
/* The booking button leads the contact section, above the eyebrow (Sam's spec,
   2026-08-25). It also resolves what would otherwise read as a contradiction:
   the reader who is ready acts before reading anything, and the heading that
   follows -- "Not ready for a call, but still have questions?" -- then plainly
   addresses the reader who is not, with the email path under it. */
.contact-lead-cta { margin-bottom: 1.6rem; }

.contact-card, .plate-fig, .state, .mock { box-shadow: var(--shadow-1); }

/* ---------- figures / schematics ---------- */

figure.schematic { max-width: 100%; }
figure.schematic svg { display: block; width: 100%; }

figcaption {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.6;
  color: var(--muted);
  margin-top: 0.7rem;
  max-width: var(--measure);
}

/* ---------- ai trust panel ---------- */

.trust-panel {
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: 4px;
  padding: clamp(1.25rem, 3vw, 2rem);
  margin-top: 1.8rem;
  max-width: 760px;
}
.trust-panel p { max-width: var(--measure); font-size: 0.97rem; }
.trust-panel figure { margin-top: 1.1rem; }
.trust-panel .link-row { margin-top: 1.1rem; font-size: 0.95rem; }

/* ---------- evidence list (dormant until teardowns exist) ---------- */

.evidence {
  list-style: none;
  padding: 0;
  margin-top: 1.6rem;
  max-width: var(--measure);
}
.evidence li { padding: 0.8rem 0; }
.evidence li + li { border-top: 1px solid var(--hair); }

/* ---------- about ---------- */

.about-grid {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}
.about-grid .prose { grid-column: 1; grid-row: 1; }
.about-photo { grid-column: 2; grid-row: 1; max-width: 300px; }
.about-photo img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--hair);
  border-radius: 3px;
  background: var(--surface);
  padding: 0.45rem; /* mounted-print mat, matches figure framing */
}

/* ---------- contact / footer ---------- */

.contact-card {
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: 4px;
  padding: clamp(1.5rem, 4vw, 2.5rem);
  margin-top: 2.2rem;
}

.contact-card .email {
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 500;
  word-break: break-word;
}

.site-footer {
  border-top: 1px solid var(--rule);
  padding-block: 1.6rem 2.2rem;
}
.site-footer p { font-size: 0.85rem; color: var(--muted); }
.site-footer a { color: var(--muted); }
.site-footer a:hover { color: var(--accent-text); }

/* ---------- case study ---------- */

.crumb { font-size: 0.9rem; margin-bottom: 1.6rem; }

.cs-header { padding-top: clamp(2rem, 6vw, 4rem); }
.cs-header .subtitle {
  font-size: 1.11rem;
  color: var(--muted);
  max-width: var(--measure);
  margin-top: 1.1rem;
}

.cs-section { padding-top: clamp(3rem, 7vw, 5rem); }
.cs-section:last-of-type { padding-bottom: var(--section-gap); }
.cs-section h2 { margin-bottom: 1.1rem; }
/* sub-block heads inside a section — each one opens a new evidence group,
   so they need the air to read as a division rather than a caption */
.cs-section .wrap > h3 {
  font-size: 1.16rem;
  margin-top: clamp(2.4rem, 5vw, 3.4rem);
  margin-bottom: 1.2rem;
  max-width: var(--measure);
}
.cs-section .wrap > h3:first-of-type { margin-top: clamp(1.8rem, 4vw, 2.6rem); }

.decision {
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: 4px;
  padding: clamp(1.25rem, 3vw, 2rem);
  margin-top: 1.6rem;
}
.decision h3 { max-width: var(--measure); }
.decision p { max-width: var(--measure); margin-top: 0.7rem; font-size: 0.97rem; }
.decision figure { margin-top: 1.2rem; }

.transfer {
  background: var(--forest);
  border: 1px solid var(--hair);
  border-top: 3px solid var(--accent);
  border-radius: 4px;
  padding: clamp(1.5rem, 4vw, 2.5rem);
}

.transfer-map {
  list-style: none;
  padding: 0;
  margin-top: 1.6rem;
}
.transfer-map li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
  gap: 0.35rem 2rem;
  padding: 0.9rem 0;
}
.transfer-map li + li { border-top: 1px solid var(--hair); }
.transfer-map .from { font-weight: 640; font-size: 0.97rem; }
.transfer-map .to { font-size: 0.97rem; color: var(--ink); }

/* ---------- 404 ---------- */

.lost { padding-block: clamp(4rem, 14vw, 9rem); }
.lost p { margin-top: 1.2rem; max-width: var(--measure); }

/* ---------- responsive ---------- */

@media (max-width: 900px) {
  .capsules { grid-template-columns: 1fr; }
  .offer-row { grid-template-columns: 1fr; }
  .about-grid { grid-template-columns: 1fr; }
  .about-grid .prose, .about-photo { grid-column: auto; grid-row: auto; }
  .about-photo { max-width: 240px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition: none !important;
    animation: none !important;
    scroll-behavior: auto !important;
  }
}

/* ==================================================================
   UI MOCK VOCABULARY
   Interfaces depicted on this site are built as real markup, never as
   images of screens. Ink-on-white, same hairlines and radii as the rest
   of the system; the mono face carries every label and figure.
   ================================================================== */

.mock {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 4px;
  overflow: hidden;
  font-size: 0.9rem;
}
.mock-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.3rem 1rem;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--hair);
}
.mock-step {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.mock-body { padding: 1.1rem 1rem 1.3rem; display: grid; gap: 1rem; }
.mock h4 { font-family: var(--font-text); font-weight: 640; font-size: 1rem; margin: 0; }

.field { display: grid; gap: 0.3rem; }
.field > span {
  font-size: 0.78rem;
  color: var(--muted);
}
.field .control {
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 0.5rem 0.65rem;
  color: var(--muted);
  background: var(--surface);
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
}
.field .control.underlined { border: 0; border-bottom: 1px solid var(--rule); border-radius: 0; padding-inline: 0; }
.radio-row { display: flex; gap: 1.4rem; align-items: center; }
.radio-row .dot {
  display: inline-block;
  width: 0.72em; height: 0.72em;
  border: 1px solid var(--rule);
  border-radius: 50%;
  margin-right: 0.4em;
  vertical-align: -0.05em;
}
.ghost-btn {
  display: inline-block;
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 0.4rem 0.7rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.quote-panel {
  border: 1px solid var(--hair);
  border-radius: 3px;
  padding: 0.9rem 1rem;
  background: var(--paper);
}
.quote-panel .amount {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 1.3rem;
  color: var(--ink);
}
.quote-panel ul { list-style: none; padding: 0; margin-top: 0.7rem; display: grid; gap: 0.4rem; }
.quote-panel li { display: flex; align-items: center; gap: 0.5rem; }
.quote-panel .bar { flex: 1; height: 7px; background: repeating-linear-gradient(45deg, var(--hatch-neutral) 0 1.1px, transparent 1.1px 6px); }
.quote-panel .tick { color: var(--accent-text); font-size: 0.8rem; }

/* ---------- annotation layer over a mock ---------- */

.annotated { position: relative; }
.annotated .leaders {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}
.marker {
  position: absolute;
  width: 1.45rem; height: 1.45rem;
  border: 1px solid var(--oxide);
  border-radius: 50%;
  background: var(--surface);
  color: var(--oxide-text);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.findings-list {
  list-style: none;
  padding: 0;
  margin: 0;
  font-size: 0.9rem;
  display: grid;
  gap: 0.9rem;
}
.findings-list li { display: grid; grid-template-columns: 1.9rem 1fr; gap: 0.7rem; }
.findings-list .n {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--oxide-text);
  border: 1px solid var(--oxide);
  border-radius: 50%;
  width: 1.45rem; height: 1.45rem;
  display: inline-flex; align-items: center; justify-content: center;
}
.findings-list b { font-weight: 640; display: block; }
.findings-list p { color: var(--muted); font-size: 0.88rem; margin-top: 0.15rem; }

/* impact · priority · ship, in the same mono register the fix table uses */
.findings-list .fmeta {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 0.35rem;
}
.findings-list .fmeta .dia { color: var(--oxide-text); margin-right: 0.25em; }
.findings-list .fix { color: var(--ink); margin-top: 0.55rem; }
.findings-list .fix b {
  display: inline;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin-right: 0.45em;
}

/* literal strings quoted from a storefront — a SKU, a hostname */
code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  overflow-wrap: anywhere;
}

/* ---------- positioning: argument left, evidence right ----------
   The prose is capped at 66ch and the section wrap is 1120px, so 434px of the
   column sat empty to the right of every paragraph. The figure and the sentence
   that frames it move into it. Near-even split rather than the teardown's
   1.55fr/1fr: this figure is the constrained element — a scale drawing with
   text inside it — so it takes as much width as the prose can spare while the
   prose stays a comfortable measure. */
.why-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.6rem, 3.5vw, 3rem);
  align-items: start;
  margin-top: 1.6rem;
}
/* .plate-fig carries a large top margin for the full-width case; inside the
   grid it already sits at the top of its own column. */
/* The sentence framing the figure is set italic to step it back from the
   argument in the left column — it introduces the evidence rather than carrying
   the argument. Done in CSS rather than <em> or <i> on purpose: the intent is
   visual weight, not emphasis, and <em> would have a screen reader stress a
   paragraph that is not stressed. Schibsted Grotesk ships no italic face here
   (one @font-face, weights 400-700), so this is a synthetic oblique; loading a
   real italic would cost ~25KB on every page's render path against 1.4KB of
   remaining budget. */
.why-aside .prose p {
  font-style: italic;
  /* 0.75rem is the system's floor for running prose — the same size .fig-caption
     and figcaption use, and this sentence is caption-register text about the
     figure directly beneath it. Everything smaller in the scale (0.63–0.72rem)
     is mono label register: eyebrows, chips, table headers, data labels. Setting
     a four-line paragraph at those sizes would be using a label size for prose. */
  font-size: 0.75rem;
  line-height: 1.65;
  color: var(--muted);
}

.why-aside .plate-fig {
  margin-top: 1.1rem;
  /* every pixel of padding here comes straight off the drawing's scale, and the
     drawing is the width-starved element in this layout */
  padding: clamp(0.7rem, 1.6vw, 1.1rem);
}

/* Below the breakpoint the columns stack and the figure follows the argument,
   which is the order it had before this layout existed. */
@media (max-width: 940px) {
  .why-grid { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .why-aside { margin-top: 1.8rem; }
}

.teardown-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: clamp(1.4rem, 4vw, 3rem);
  align-items: start;
  margin-top: 1.8rem;
}

/* ---------- findings table ---------- */

.table-scroll { overflow-x: auto; margin-top: 1.6rem; }
.findings {
  border-collapse: collapse;
  width: 100%;
  min-width: 720px;
  font-size: 0.88rem;
  background: var(--surface);
}
.findings caption {
  text-align: left;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 0.7rem;
}
.findings th, .findings td {
  text-align: left;
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--hair);
  vertical-align: top;
}
.findings thead th {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  border-bottom: 1px solid var(--rule);
  white-space: nowrap;
}
.findings tbody tr:last-child td { border-bottom: 0; }
.findings .ids, .findings .ship { font-family: var(--font-mono); font-size: 0.82rem; white-space: nowrap; }
.findings .pri { white-space: nowrap; font-family: var(--font-mono); font-size: 0.82rem; }
.findings .pri .dia { color: var(--oxide-text); margin-right: 0.35em; }
.findings .pri.high { color: var(--oxide-text); font-weight: 600; }
/* ---- below 900px the table is wider than the column it sits in, so it stops
   being a table and becomes one card per row, every value on screen at once.
   The figure is measured, not chosen: the fix list lays out at 798px (its
   content is wider than the 720px min-width), and the column reaches that at
   ~886px viewport — verified scrolling at 880 and fitting at 890. Rounded to
   the 900px breakpoint the layout already uses.

   display:flex drops the implicit table roles, which is why the markup carries
   role="table"/"row"/"cell" explicitly — the semantics survive the reflow. The
   header row is display:none here rather than visually hidden, because each
   cell's data-label already supplies its column name; keeping both would make a
   screen reader read every label twice. ---- */
@media (max-width: 900px) {
  .table-scroll { overflow-x: visible; }
  .findings { min-width: 0; background: none; }
  .findings thead { display: none; }
  .findings tbody { display: block; }

  .findings tbody tr {
    display: flex;
    flex-wrap: wrap;
    gap: 0.55rem 1.5rem;
    background: var(--surface);
    border: 1px solid var(--hair);
    border-radius: 4px;
    padding: clamp(0.9rem, 3vw, 1.15rem);
    margin-bottom: 0.8rem;
  }
  .findings tbody tr:last-child { margin-bottom: 0; }

  .findings tbody th,
  .findings tbody td { border-bottom: 0; padding: 0; }

  /* the row header is the card's title */
  .findings tbody th {
    flex: 1 0 100%;
    font-size: 0.95rem;
    font-weight: 640;
    line-height: 1.35;
  }

  /* short values sit inline as a meta strip; long prose takes the full width */
  .findings tbody td { flex: 0 1 auto; font-size: 0.85rem; }
  .findings tbody td.rec { flex: 1 0 100%; }

  .findings tbody td::before {
    content: attr(data-label);
    display: block;
    font-family: var(--font-mono);
    font-size: 0.63rem;
    font-weight: 600;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--muted);
    margin-bottom: 0.1rem;
  }
  /* the recommendation reads as the card's answer, so it gets a rule above it */
  .findings tbody td.rec {
    margin-top: 0.15rem;
    padding-top: 0.7rem;
    border-top: 1px solid var(--hair);
    line-height: 1.5;
  }
  .findings caption { padding-bottom: 0.9rem; }
}

/* scope is carried by the words themselves, never by color */
.findings .scope {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--muted);
}

/* ---------- evidence shots ---------- */

/* a real screenshot, mounted in the same frame the drawn schematics use.
   pairs with .annotated (which supplies position:relative) when markers and
   leaders sit over it — the leaders' viewBox is the image's own pixel size,
   so svg units map 1:1 to image pixels and marker percentages land exactly. */
.shot {
  border: 1px solid var(--hair);
  border-radius: 4px;
  overflow: hidden;
  background: var(--shot-ground);
}
.shot img { display: block; width: 100%; height: auto; }
/* left/top on these markers address the marker's center, not its corner —
   that is what lets a marker be positioned straight from image coordinates */
.shot .marker { transform: translate(-50%, -50%); }

/* a leader drawn over a drawn schematic always crosses paper. A leader drawn
   over a screenshot crosses whatever the interface happens to be — white here,
   a dimmed gray modal backdrop there. The halo keeps the line readable on both
   without changing the annotation layer's color language. */
.shot .leaders .sg-anno {
  stroke-width: 1;
  filter: drop-shadow(0 0 1.5px var(--shot-ground)) drop-shadow(0 0 1.5px var(--shot-ground));
}

.shot-narrow { max-width: 340px; }

/* ---------- method box ---------- */

.method {
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: 4px;
  padding: clamp(1.1rem, 2.6vw, 1.7rem);
  margin-top: 1.6rem;
  max-width: 760px;
}
.method dl {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 0.55rem 1.3rem;
  margin: 0;
}
.method dt {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 0.18rem;
}
.method dd { margin: 0; font-size: 0.93rem; }
.method dd.stamp {
  font-family: var(--font-mono);
  font-size: 1.02rem;
  font-weight: 600;
  color: var(--accent-text);
}
.method .note { margin-top: 1.1rem; padding-top: 1.1rem; border-top: 1px solid var(--hair); font-size: 0.93rem; }

/* ---------- passes ---------- */

.passes {
  list-style: none;
  padding: 0;
  margin-top: 1.4rem;
  max-width: var(--measure);
  display: grid;
  gap: 1rem;
}
.passes li { display: grid; grid-template-columns: 1.5rem minmax(0, 1fr); gap: 0.7rem; font-size: 0.95rem; }
/* the glyph carries the meaning; color only reinforces it */
.passes .mark { font-family: var(--font-mono); font-weight: 700; color: var(--accent-text); }
.passes b { font-weight: 640; }

/* ---------- findings table: numeric columns ----------
   Priced/measured values read better ranged on the ones place than pinned
   left with everything else — the eye can scan a column of amounts without
   re-parsing each digit count. Tabular figures keep that ranging honest. */
.findings td.num, .findings th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* below 900px the row stops being a table and each td becomes a flex item
   sized to its own content — there is no shared right edge left for the
   value to range against, so a lingering text-align: right just shoves the
   number away from its data-label instead of aligning it to anything. The
   figure still reads fine in the tabular face; it just wants to sit under
   its own label like every other card value. */
@media (max-width: 900px) {
  .findings td.num { text-align: left; }
}

/* ---------- comparison block ----------
   Built for a wording diff that hasn't shipped in an article yet: prototype
   copy on the left, the copy that actually went out on the right. Grid
   rather than flex because the two sides are declared equal by definition —
   this component only exists to prove they aren't. Each side takes the
   surface/hairline/4px-radius panel treatment .method already uses for
   quoted material, so a reader's eye files it as "documented", not
   "designed"; the mono label above each column is what tells the two
   panels apart before either is read. */
.compare {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1.4rem, 4vw, 3rem);
  margin-top: 1.6rem;
}

.compare > div {
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: 4px;
  padding: clamp(1.1rem, 2.6vw, 1.7rem);
}

.compare-label {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.8rem;
}

/* names the Administrative Letter and carrier the pair was pulled from —
   same register and position as .fig-caption below a schematic, because
   that is exactly what this is: a caption citing a source, not prose */
.compare-cap {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.6;
  color: var(--muted);
  border-top: 1px solid var(--hair);
  margin-top: clamp(0.8rem, 2vw, 1.2rem);
  padding-top: 0.8rem;
  max-width: none;
}

/* ---------- finding 23: the automated-audit callout ----------
   The one finding that is about the method rather than the interface, so it
   sits outside the fix list. Built as markup rather than shipped as a picture
   of a results panel: the figures are data, they belong in the page's own type
   where they inherit the theme and stay selectable, and a screenshot of a
   dashboard is less checkable than the numbers themselves. The oxide edge is
   the same device the exception rows use elsewhere — this is the row that
   disagrees with the others. */
.audit-callout {
  margin-top: clamp(2.5rem, 6vw, 4rem);
  background: var(--surface);
  border: 1px solid var(--hair);
  border-left: 3px solid var(--oxide);
  border-radius: 4px;
  padding: clamp(1.2rem, 3.4vw, 2.2rem);
}
.audit-callout h2 { margin-top: 0.35rem; }
.audit-callout .prose { margin-top: 1.4rem; }
.audit-callout .compare-cap { margin-top: 1.4rem; }

/* One hairline grid, drawn with gap over a background rather than per-cell
   borders, so the cells never double up on the seams as they reflow. */
.audit-scores {
  list-style: none;
  padding: 0;
  margin: 1.5rem 0 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7.25rem, 1fr));
  gap: 1px;
  background: var(--hair);
  border: 1px solid var(--hair);
  border-radius: 3px;
  overflow: hidden;
}
.audit-scores li {
  background: var(--surface);
  padding: 0.85rem clamp(0.7rem, 2vw, 1.1rem);
}
.audit-scores .k {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--muted);
}
/* the count that is not zero: color reinforces, the number carries it */
.audit-scores .v {
  display: block;
  margin-top: 0.3rem;
  font-family: var(--font-mono);
  font-size: 1.45rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.audit-scores .v.flagged { color: var(--oxide-text); }

.compare-cap b { color: var(--ink); font-weight: 600; }

/* prototype is first in the DOM, so collapsing to one column needs nothing
   beyond the column count — source order already puts it above as-shipped */
@media (max-width: 700px) {
  .compare { grid-template-columns: 1fr; }
}

/* ---------- .dateline ----------
   No modifier added here: .method already carries a mono, accent-colored
   date value through dd.stamp (see the "Tested" row in the method box), so
   a second date-flavored variant of the same component would just be the
   same rule under a new name. Reuse dd.stamp for datelines until a case
   turns up that it can't actually cover. */

/* ---------- AI state matrix ---------- */

.states {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.2rem;
  margin-top: 1.8rem;
}
.state {
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: 4px;
  padding: 1.1rem 1.1rem 1.3rem;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}
.state-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.7rem;
}
.chip {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border: 1px solid currentColor;
  border-radius: 2px;
  padding: 0.18rem 0.45rem;
}
.chip-confident { color: var(--accent-text); }
.chip-uncertain { color: var(--oxide-text); }
.chip-unavailable { color: var(--muted); }
.state .range { font-family: var(--font-mono); font-size: 0.72rem; color: var(--muted); }
.state .conf {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 600;
}
.state .conf.hi { color: var(--accent-text); }
.state .conf.lo { color: var(--oxide-text); }
.state p { font-size: 0.88rem; }
.state .summary b { font-weight: 640; }

.why {
  border: 1px solid var(--oxide);
  border-radius: 3px;
  padding: 0.7rem 0.8rem;
}
.why > span {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--oxide-text);
  font-weight: 600;
}
.why ul { list-style: none; padding: 0; margin-top: 0.45rem; display: grid; gap: 0.25rem; }
.why li { font-size: 0.84rem; padding-left: 0.9rem; position: relative; }
.why li::before {
  content: "";
  position: absolute; left: 0; top: 0.62em;
  width: 0.34em; height: 0.34em;
  background: var(--oxide);
  transform: rotate(45deg);
}

.sources { margin-top: auto; }
.sources > span {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.sources ul {
  list-style: none; padding: 0; margin-top: 0.35rem;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--muted);
  display: grid; gap: 0.22rem;
}
.raw-record { display: grid; gap: 0.5rem; }
.raw-record .row { display: flex; align-items: center; gap: 0.6rem; }
.raw-record .redact {
  flex: 1; height: 8px;
  background: repeating-linear-gradient(45deg, var(--hatch-neutral) 0 1.1px, transparent 1.1px 6px);
}
.raw-record .ico { color: var(--muted); font-size: 0.8rem; width: 1.1em; }

.takeover {
  display: block;
  text-align: center;
  border: 1px solid var(--ink);
  border-radius: 3px;
  padding: 0.6rem 0.7rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink);
}

@media (max-width: 900px) {
  .states { grid-template-columns: 1fr; }
  .teardown-grid { grid-template-columns: 1fr; }
  /* once the grid stacks, a phone capture pinned to 340px looks stranded in a
     full-width column — let it take the measure it now has */
  .shot-narrow { max-width: 420px; }
}

/* below 700px the overlay markers are dropped; the findings list carries
   its own numbering inline, so nothing is lost */
@media (max-width: 700px) {
  .annotated .leaders, .annotated .marker { display: none; }
}
