/* ==========================================================================
   Insanity Wolf — every minute, on the minute.
   Layout, type scale and colour system follow the Roadie Brew reference
   build.
   ========================================================================== */

/* Both faces are single-weight (usWeightClass 400). Never ask for 500+ —
   the browser answers with a synthetic bold that smears the letterforms.
   The reference sets literally everything at 400 for the same reason. */
@font-face {
  font-family: "Washington Heights JNL";
  src: url(font/WashingtonHeightsJNL.woff2) format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "School Project JNL";
  src: url(font/SchoolProjectJNL.woff2) format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "WS EXISTENCE";
  src: url(font/WSEXISTENCEItalic.woff2) format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url(font/IBMPlexMono-Medium.woff2) format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "WS WARBLER";
  src: url(font/WSWARBLERRegular.woff2) format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}

:root {
  /* sand */
  --sand-50: #fbfaf9;
  --sand-100: #f7f6f3;
  --sand-200: #efede7;
  --sand-300: #e7e4dc;
  --sand-400: #dfdbd0;
  --sand-500: #d7d2c4;
  --sand-600: #b5b1a3;
  --sand-700: #948f82;
  --sand-800: #726e62;
  --sand-900: #514c41;
  --sand-950: #1a1812;
  /* red */
  --red: #d71e12;
  --red-500: #d71e12;
  --red-600: #b3241e;
  --red-700: #861b17;
  /* the site accent — one red, no yellow anywhere in the chrome */
  --gold: #d71e12;
  /* AccentChoice.red in the shipping app, and Signal Red in the manual. */
  --app-accent: #cf251d;
  --ink: #111111;
  --cream: #f8f6e1;

  /* manual 03 — Road Map is the ground, not sand-300 */
  --ground: #efede7;
  --header-h: 56px;
  --wrap: 1440px;
  --pad: 20px;
  --ease: cubic-bezier(0.76, 0, 0.24, 1);

  /* The reference's own two faces. Display carries headings, labels, numerals
     and buttons; Warbler is wider and carries anything you have to READ.
     Body runs at tracking normal — tracking is for short tracked caps only. */
  --font-display: "Washington Heights JNL", "Oswald", Impact, sans-serif;  /* display, headlines, nav */
  --font-body: "School Project JNL", Georgia, serif;                        /* body, editorial, details */
  --font-util: "WS WARBLER", "Space Grotesk", system-ui, sans-serif;        /* utility, labels, wayfinding */
  --font-script: "WS EXISTENCE", Georgia, serif;                            /* accent, notes */
  /* The rendered phone is not "an iOS app" any more — it is THIS app, which
     was rebranded per the field manual. Tokens below are lifted from
     ~/Projects/InsanityWolf/Shared/Theme.swift; change them together. */
  --app-bg: #111111;        /* Theme.bg — coal, deliberately not black */
  --app-ink: #fbfaf9;       /* Theme.ink */
  --app-ink-label: #a8a296;
  --app-ink-muted: #6e6a5f;   /* Theme.inkMuted */
  --app-border: #2a271f;      /* Theme.border */
  --font-data: "IBM Plex Mono", ui-monospace, Menlo, monospace;
}

@media (min-width: 1024px) {
  :root { --pad: 80px; }
}

/* --------------------------------------------------------------------- */

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

html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
/* Smooth scroll is motion, and this page is ~13,000px tall — the logo now
   jumps the whole way. Ungated, one click would sweep the entire page past
   someone browsing with Reduce Motion on, which is the exact thing that
   setting is for. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--sand-950);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 16px;
  line-height: 24px;
  letter-spacing: normal;
  text-transform: uppercase;
  -webkit-font-smoothing: antialiased;
}

/* Paper. The reference tiles its grain WITH the content on the cream
   surfaces — not as a fixed full-screen veil, and never over the
   photography. A fixed overlay reads as a screen filter; this reads as
   stock. */
body {
  background-image: url(img/paper.png);
  background-size: 600px 600px;
  background-repeat: repeat;
}
.pin, .get, .pack, .letters, .footer, .header, .rounds, .about, .modal__card {
  background-image: url(img/paper.png);
  background-size: 600px 600px;
  background-repeat: repeat;
}

img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; letter-spacing: normal; text-transform: inherit; }

.wrap { max-width: var(--wrap); margin: 0 auto; padding-inline: var(--pad); }
/* anchored sections must clear the sticky bar, or a jump lands with the
   heading hidden underneath it */
#app, #rounds, #about, #ledger { scroll-margin-top: var(--header-h); }

.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

/* ---- type scale ------------------------------------------------------ */

.t1 { font-size: 88px; line-height: 88px; font-weight: 400; letter-spacing: normal; margin: 0; }
.t2 { font-size: 32px; line-height: 36px; font-weight: 400; letter-spacing: normal; margin: 0; }
.t3 { font-size: 28px; line-height: 32px; font-weight: 400; letter-spacing: normal; margin: 0; }
.eyebrow { font-size: 14px; line-height: 20px; font-weight: 400; letter-spacing: normal; margin: 0; }

/* reading copy — the wider face, no tracking */
.body {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;
}
.body p { margin: 0 0 18px; max-width: 62ch; }
.body p:last-child { margin-bottom: 0; }
.body strong { font-weight: 400; }

@media (min-width: 1024px) {
  .t1 { font-size: 104px; line-height: 120px; }
  .t2 { font-size: 48px; line-height: 54px; }
  .t3 { font-size: 32px; line-height: 40px; }
  .body { font-size: 18px; line-height: 26px; }
}

/* small print rides the body face too — it is prose, not a label */
/* utility + wayfinding — manual 04 */
.price-row__note,
.rounds__date,
.letters__fine,
.get__meta,
.pack__count,
.pack__row span,
.pack__fine,
.eyebrow { font-family: var(--font-util); letter-spacing: normal; }
/* accent / note — manual 04 */
.band__cap {
  font-family: var(--font-script);
  font-style: italic;
  text-transform: none;
  font-size: 22px;
  letter-spacing: 0.01em;
}

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 40px;
  padding: 0 20px;
  border: 0;
  border-radius: 4px;
  background: var(--red-500);
  color: var(--sand-50);
  font-size: 16px;
  line-height: 18px;
  font-weight: 400;
  cursor: pointer;
  transition: background 0.2s var(--ease);
}
.btn:hover { background: var(--red-600); }
.btn--lg { height: 48px; padding: 0 28px; font-size: 16px; }
.btn--ghost { background: transparent; color: var(--sand-950); box-shadow: inset 0 0 0 2px var(--sand-950); }
.btn--ghost:hover { background: var(--sand-950); color: var(--sand-50); }

/* ==== announcement marquee ============================================ */

.announce {
  position: relative;
  z-index: 60;
  background: var(--sand-950);
  color: var(--sand-100);
  overflow: hidden;
  padding: 5px 0;
  font-size: 14px;
  line-height: 20px;
  font-weight: 400;
}
.announce__track { display: flex; width: max-content; animation: marquee 38s linear infinite; }
.announce__track span { padding-inline: 22px; white-space: nowrap; }
@keyframes marquee { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .announce__track { animation: none; } }

/* ==== header ========================================================== */

.header {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--header-h);
  background: var(--sand-200);
  border-bottom: 1px solid var(--sand-400);
}
.header__in {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--header-h);
  max-width: var(--wrap);
  margin: 0 auto;
  padding-inline: 16px;
}
@media (min-width: 1024px) { .header__in { padding-inline: 24px; } }

.nav { display: none; gap: 0; align-items: center; }
@media (min-width: 900px) { .nav { display: flex; } }
.nav a {
  position: relative;
  padding: 0 14px;
  font-size: 18px;
  line-height: 20px;
  font-weight: 400;
}
.nav a + a::before {
  content: "";
  position: absolute;
  left: 0; top: 50%;
  width: 1px; height: 14px;
  margin-top: -7px;
  background: var(--sand-950);
  opacity: 0.5;
}
.nav a:hover { color: var(--red-600); }

/* the supplied lockup is already a printed red plate, so it hangs below the
   bar as-is rather than sitting inside one we draw */
/* The plate is a physical thing hanging off the bar, so it casts. Two layers:
   a tight contact shadow that keeps the bottom edge attached, and a wider,
   softer one for the drop. Not a glow — manual 08 rules those out. */
.brand {
  position: absolute;
  left: 50%;
  top: 0;
  transform: translateX(-50%);
  border-radius: 0 0 3px 3px;
  overflow: hidden;
  line-height: 0;
  box-shadow:
    0 1px 1px rgba(26, 24, 18, 0.30),
    0 7px 16px -4px rgba(26, 24, 18, 0.34);
}
.brand img { display: block; width: 96px; height: auto; }
@media (max-width: 899px) { .brand img { width: 74px; } }

.header__end { display: flex; align-items: center; gap: 14px; }

/* Share. Quiet next to the red button — the CTA stays the loud thing. */
.share {
  display: grid; place-items: center;
  width: 34px; height: 34px;
  padding: 0; margin: 0;
  background: none; border: 0; cursor: pointer;
  color: var(--ink);
  border-radius: 3px;
  transition: color 0.16s ease-out, background-color 0.16s ease-out;
}
.share svg { width: 19px; height: 19px; display: block; }
.share:hover { color: var(--red-500); }
.share:focus-visible { outline: 2px solid var(--red-500); outline-offset: 2px; }
.share[data-copied="true"] { color: var(--red-500); }

.burger { display: block; background: none; border: 0; padding: 4px; cursor: pointer; order: -1; }
@media (min-width: 900px) { .burger { display: none; } }

/* on mobile the nav collapses, so the burger takes the left slot and the
   CTA stays on the right */
@media (max-width: 899px) {
  .header__in { justify-content: flex-start; }
  .header__end { flex: 1; justify-content: space-between; }
  .share { order: 1; margin-left: auto; }
  .header__end .btn { order: 2; }
  .header__end .btn { height: 32px; padding: 0 14px; font-size: 12px; }
}
.burger span { display: block; width: 20px; height: 2px; margin: 4px 0; background: var(--sand-950); }

/* mobile drawer */
.drawer {
  position: fixed;
  inset: var(--header-h) 0 0;
  z-index: 45;
  background: var(--sand-200);
  padding: 28px var(--pad);
  transform: translateY(-101%);
  transition: transform 0.5s var(--ease);
}
.drawer[data-open="true"] { transform: none; }
.drawer a { display: block; padding: 14px 0; font-size: 32px; font-weight: 400; border-bottom: 1px solid var(--sand-400); }

/* ==== hero banner ===================================================== */

.hero {
  position: relative;
  height: calc(100svh - var(--header-h));
  min-height: 520px;
  overflow: hidden;
  background: var(--sand-950);
}
/* scoped to the backdrop — an unscoped .hero img also caught the wordmark
   and threw it full-bleed behind the scrim */
.hero__deck { position: absolute; inset: 0; }
.hero__bg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.1s var(--ease);
}
/* The FIRST frame is opaque by default. Anything that starts invisible and
   waits for script is a frame nobody sees when the script does not run. */
.hero__bg:first-child, .hero__bg.is-on { opacity: 1; }
/* The painting goes full width like every other frame. `contain` was leaving
   26% dark bars down both sides, and the old note here — that cropping cuts
   the headdress off — was only true of a CENTRED crop. Measured, the paint
   occupies rows 264-1604 of 1605: 16.4% empty black above it and none below.
   Anchored to the bottom, a full-width cover takes its whole 12.4% crop out
   of that empty top margin, so it fills the frame and loses no paint at all. */
/* The canvas is centred: paint sits at 46.1% vertically with 9.4% clear above
   and 17.1% below, so a full-width cover splits its 12.4% crop evenly into
   those margins and loses no brushwork. The previous canvas was bottom-heavy
   and needed a bottom anchor; this one does not. */
.hero__bg--art { object-fit: cover; background: #0a0a0a; }

.hero__dots {
  position: absolute;
  right: var(--pad); bottom: 5.5vh;
  z-index: 3;
  display: flex; gap: 10px;
}
.hero__dot {
  width: 34px; height: 3px;
  padding: 0; border: 0;
  background: #fbfaf966;
  cursor: pointer;
  transition: background 0.3s var(--ease);
}
.hero__dot[aria-current="true"] { background: var(--sand-50); }
.hero__dot:focus-visible { outline: 2px solid var(--sand-50); outline-offset: 4px; }
@media (max-width: 899px) {
  /* the rules take the bottom centre, so the scroll cue would sit on top of
     them — it says nothing the rules do not */
  .hero__dots { right: 0; left: 0; justify-content: center; bottom: 3.4vh; }
  .hero__dot { width: 26px; }
  .scroll-cue { display: none; }
}
/* The wordmark rides every frame. It used to belong to the first one only,
   which left the other two brandless the moment the sticky header scrolled
   away — and the header is the only other place the mark appears.
   The painting is the frame that needed help, not hiding: `contain`
   letterboxes it on the SIDES, so it fills the full height and the wordmark
   landed straight on the wolf's jaw. Lifting the image opens a band of its
   own black ground underneath, which is where the mark now sits. */
.hero__copy { transition: opacity 0.5s var(--ease); }
/* The centred canvas leaves clear black under the fringe, so the mark sits
   at its normal size on its own ground. */
.hero[data-frame="1"] .hero__mark { width: min(480px, 68vw); }
/* the frame's only clean ground is the lower band of porch boards, so the
   scrim weights the bottom-left and stays out of the sky */
.hero::after {
  content: "";
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg, #1a181240 0%, #1a181200 26%, #1a181259 72%, #1a181299 100%);
}
/* the reference centres its script lockup in the frame; ours follows */
.hero__copy {
  position: absolute;
  left: 0; right: 0; bottom: 16vh;
  z-index: 2;
  text-align: center;
  color: var(--sand-100);
  padding-inline: var(--pad);
}
/* the reference's 104/120 is a SINGLE-line headline, where the leading never
   shows. Stacked, and with this face's tall vertical metrics, it opens a gap
   the size of a line — the wordmark sets tight. */
.hero__mark { width: min(560px, 76vw); margin: 0 auto 14px; }
.hero__mark img { width: 100%; height: auto; }
/* 13px under a 104px wordmark is an 8:1 jump — it read as a caption. The
   reference's secondary body is 18/26; this sits there. */
.hero__sub { font-size: 18px; line-height: 26px; letter-spacing: normal; font-weight: 400; opacity: 0.92; margin: 0; }

/* Portrait shows only ~29% of a 16:9 frame's width. Centred, that lands on
   bare torso; pulling the window left keeps the hat, the hand, the phone and
   the hills — the same photograph rather than an anatomy crop. */
@media (max-width: 899px) {
  /* retuned for the replacement frame: the subject sits near the middle of
     this one, where the previous photo had it well right of centre */
  .hero__bg { object-position: 46% center; }
  /* contain is right on a wide screen; on a portrait one it shrinks a 3:2
     picture to a strip in a tall black field. Crop instead — losing the edges
     of the painting beats an empty hero. */
  /* On a phone the box is 0.49 against a 1.50 canvas, so height fits exactly
     and 67% of the WIDTH is cropped — the vertical 42% this used to carry was
     a no-op tuned for the old canvas. Centred is right: the brightness
     centroid sits at 49.6%, so the window holds the face and the snarl and
     gives up only the outer headdress feathers. */
  .hero__bg--art { object-fit: cover; object-position: center; }
  .hero::after {
    background: linear-gradient(180deg, #1a181280 0%, #1a181233 38%, #1a1812cc 100%);
  }
  .hero__copy { bottom: 14vh; }
  .hero__mark { width: min(400px, 82vw); margin: 0 auto 12px; }
  .hero__sub { font-size: 15px; line-height: 22px; letter-spacing: normal; }
}

.scroll-cue {
  position: absolute;
  left: 50%; bottom: 18px;
  transform: translateX(-50%);
  z-index: 3;
  color: var(--sand-100);
  font-size: 10px;
  letter-spacing: normal;
  opacity: 0.7;
  animation: bob 2.4s ease-in-out infinite;
}
@keyframes bob { 50% { transform: translate(-50%, 6px); } }

/* ==== pinned product scroll =========================================== */

.pin { position: relative; height: 700vh; background: var(--ground); }
.pin__stage {
  position: sticky;
  top: var(--header-h);
  height: calc(100svh - var(--header-h));
  overflow: hidden;
  display: grid;
  place-items: center;
  align-content: center;
}

/* The headline splits around the handset rather than sitting behind it —
   the phone was covering its own middle. Left half, phone, right half. */
.pin__grid {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: 1fr;
  justify-items: center;
  gap: 22px;
  width: 100%;
  max-width: var(--wrap);
  padding-inline: var(--pad);
}
.pin__col { display: grid; justify-items: center; gap: 10px; text-align: center; }

/* A hard break that is right at desktop width orphans a word at phone width.
   The break only applies where the line it creates actually fits. */
.brk-wide { display: none; }
@media (min-width: 700px) { .brk-wide { display: inline; } }
@media (min-width: 900px) {
  .pin__grid {
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: clamp(18px, 2.4vw, 44px);
  }
}
.pin__word {
  margin: 0;
  /* sized so the longer half clears the handset at 1280 and up rather than
     sliding under it — the whole point of splitting the headline */
  font-size: clamp(30px, 3.6vw, 53px);
  line-height: 0.88;
  white-space: nowrap;
}
.pin__kicker {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-family: var(--font-util);
  font-size: 13px;
  letter-spacing: 0.14em;
  color: var(--sand-900);
}
.pin__kicker::before,
.pin__kicker::after {
  content: "";
  width: clamp(18px, 3vw, 42px);
  height: 1.5px;
  background: var(--red-500);
}
.pin__rule { width: 62%; max-width: 220px; height: 2px; background: var(--red-500); margin: 4px 0 6px; }
.pin__figure { margin: 0; font-size: clamp(32px, 3.4vw, 52px); line-height: 1; }
.pin__figure--red { color: var(--red-500); }
.pin__hr { width: 100%; max-width: 300px; height: 2px; background: var(--sand-950); margin: 10px 0; }
/* the call to action belongs to the whole composition, not to one half */
.pin__cta { justify-self: center; margin-top: 26px; }
@media (min-width: 900px) { .pin__cta { grid-column: 1 / -1; margin-top: 34px; } }
.pin__col .eyebrow { color: var(--sand-900); }
.pin__note {
  margin: 0;
  max-width: 30ch;
  font-family: var(--font-util);
  font-size: 15px;
  line-height: 22px;
  letter-spacing: 0.03em;
  color: var(--sand-900);
}

/* The halves ease outward as the phone arrives — only where there ARE halves.
   Stacked on a phone this just shoves each block off centre. */
@media (min-width: 900px) {
  .pin__col--l { transform: translateX(calc(var(--split, 0) * -1px)); }
  .pin__col--r { transform: translateX(calc(var(--split, 0) * 1px)); }
}

.pin__mark {
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(150vh, 128vw);
  opacity: 0.9;
  transform: translate(-50%, -50%) scale(var(--markScale, 1));
  transition: transform 0.1s linear;
  pointer-events: none;
}

.pin__phone { display: grid; place-items: center; }
/* the phone rises and settles */
.pin__device {
  position: relative;
  z-index: 3;
  width: clamp(220px, 42vh, 460px);
  transform: translateY(var(--devY, 0px)) scale(var(--devS, 1)) rotate(var(--devR, 0deg));
  opacity: var(--devO, 1);
  transition: transform 0.1s linear, opacity 0.1s linear;
  pointer-events: none;
}
/* On a phone the stacked composition is taller than the viewport, so pinning
   it only means clipping it. It scrolls instead — the rise is a desktop
   nicety, not the content. Safe to go static here because everything in the
   grid is in normal flow; only the watermark is absolute, and it now centres
   on .pin itself. */
@media (max-width: 899px) {
  /* The watermark is wider than the viewport by design. On desktop the sticky
     stage clips it; here the stage is static, so it positions against .pin and
     .pin has to clip instead. This CANNOT go on .pin unconditionally —
     overflow other than visible on an ancestor kills position: sticky, which
     blanked the whole desktop pin. */
  .pin { height: auto; overflow: hidden; }
  .pin__stage {
    position: static;
    height: auto;
    padding-block: 56px 64px;
  }
  .pin__device { width: min(66vw, 260px); }
  .pin__word { font-size: clamp(34px, 10.5vw, 58px); }
  .pin__grid { gap: 18px; }
  .pin__col { gap: 8px; }
  .pin__rule { width: 120px; }
  .pin__hr { max-width: 220px; }
}

/* ---- the handset ----------------------------------------------------
   A photograph of a genuinely used first-generation iPhone, with the live
   screen composited into it. Beats a drawn frame: the manual asks for
   objects that carry history, and no gradient reproduces worn chrome.

   The display rect, as percentages of the frame so it holds at any size:
       left 10.5%   top 12.5%   width 79%   height 62.5%
   Cross-checked against the physical device — 115 x 61 mm body, 49.9 x 74.9 mm
   panel — then matched to the photograph, which wins where the two disagree:
   the shot has a hair of perspective, so the ideal 320:480 would overhang the
   bottom edge by a few pixels. The app fills the panel; earlier values had it
   floating inside with a black margin.

   The glass crop is laid back OVER the live UI on a screen blend, so the
   scratches and dust sit on top of the interface rather than under it. */
.device {
  position: relative;
  aspect-ratio: 700 / 1303;
  background: url(img/iphone-vintage.png) center/contain no-repeat;
  filter: drop-shadow(0 30px 40px rgba(26, 24, 18, 0.34));
}
.device__screen {
  container-type: inline-size;
  position: absolute;
  left: 9.2%;
  top: 11.4%;
  width: 81.6%;
  height: 66.4%;
  background: var(--app-bg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Percentages, NOT cqw. A query container whose own padding is expressed in
     its own container units is a cyclic dependency: the browser breaks the
     cycle and resolves cqw against a wrong width — here 174px on a 294px
     panel, so everything inside rendered ~40% small. Percentage padding
     resolves against the containing block (.pin__device) instead, so there
     is no cycle. */
  padding: 1.3% 2.45% 1.47%;
  font-family: var(--font-display);
  color: var(--app-ink);
}
/* the worn glass, back on top of the interface */
.device__glass {
  position: absolute;
  inset: 0;
  z-index: 5;
  background: url(img/iphone-glass.jpg) center/100% 100% no-repeat;
  mix-blend-mode: screen;
  /* 0.9 -> 0.54: the scratches were reading as damage on the render rather
     than wear on the handset, and they fought the ring for attention. */
  opacity: 0.54;
  pointer-events: none;
}

/* status bar sits either side of the island */
.scr-status {
  flex: 0 0 auto;
  width: 100%;
  display: flex; justify-content: space-between; align-items: center;
  min-height: 9cqw;
  font-size: 4.1cqw; font-weight: 600; letter-spacing: normal;
  padding: 0 4cqw 0 6cqw;
}
.scr-status__r { display: flex; align-items: center; gap: 1.6cqw; }
.scr-status__r svg { height: 3.4cqw; width: auto; }
.scr-batt {
  position: relative;
  width: 6.6cqw; height: 3.3cqw;
  border: 0.45cqw solid currentColor;
  border-radius: 1cqw;
  opacity: 0.9;
}
.scr-batt::before {
  content: ""; position: absolute; inset: 0.55cqw;
  border-radius: 0.4cqw; background: currentColor;
}
.scr-batt::after {
  content: ""; position: absolute; right: -1.1cqw; top: 32%;
  width: 0.6cqw; height: 36%;
  border-radius: 0 1px 1px 0; background: currentColor; opacity: .7;
}

.scr-icons {
  flex: 0 0 auto;
  width: 100%;
  display: flex; justify-content: flex-end; gap: 6cqw;
  padding: 3cqw 2cqw 0 0;
  color: #8b8880;
}
.scr-icons svg { width: 5.6cqw; height: 5.6cqw; }

/* The tally wall. TallyWall: six gates for thirty rounds, four uprights each
   plus a diagonal strike for the fifth — the same hand-kept record as the
   five-week grid on Activity. Unstruck marks sit at 0.12, so a gate reads as
   spent or not from across a room. */
.scr-tally {
  flex: 0 0 auto;
  width: 100%;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 3cqw 3.6cqw;              /* LazyVGrid spacing 12 / columns spacing 14 */
  margin-top: 3.6cqw;            /* .padding(.top, 14) */
}
.gate {
  position: relative;
  height: 10.8cqw;               /* gateHeight 34 x 1.25 */
  display: flex; align-items: center; justify-content: center;
  gap: 1.78cqw;                  /* strokeSpacing 7 */
}
.gate i {
  display: block;
  width: 0.77cqw;                /* strokeWidth 3 */
  height: 8.65cqw;               /* gateHeight 34 */
  border-radius: 0.4cqw;
  background: var(--app-accent);
  opacity: 0.12;
  transition: opacity 0.18s ease-out;
}
.gate i.is-marked { opacity: 1; }
.gate b {                         /* the strike through a full gate */
  position: absolute;
  width: 0.77cqw;
  height: 10.55cqw;              /* height x 1.22 */
  border-radius: 0.4cqw;
  background: var(--app-accent);
  transform: rotate(62deg);
  opacity: 0;
  transition: opacity 0.18s ease-out;
}
.gate.is-struck b { opacity: 1; }

.scr-core {
  flex: 0 0 auto;
  margin-block: auto;
  display: flex; flex-direction: column; align-items: center;
  padding: 2cqw 0 1cqw;
}

/* The coach ring: 306pt across, 12pt stroke, one arc per movement sized by
   reps, drawn from twelve o'clock. The fill is where the app would like you
   to be — not a claim about where you are. */
.scr-ringwrap {
  position: relative;
  width: 77.9cqw;                /* 306 of 393 */
  aspect-ratio: 1;
  display: grid; place-items: center;
}
.scr-ring { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); }
.scr-ring .track { stroke: var(--app-border); }
.scr-ring .fill  { stroke: var(--app-accent); transition: stroke-dasharray 0.12s linear; }
.scr-dial { position: relative; display: flex; flex-direction: column; align-items: center; }

/* Theme.eyebrow(11) is WS Warbler, tracking 2.4 — NOT the mono face.
   BELOW the numeral and shortened to "Secs left". The app runs the full
   "SECONDS LEFT IN THIS ROUND" above it, which fits a real 393pt screen but
   not this render: the ring's bore is ~231px here and the long string ran
   into the stroke on both sides. Short and under the number clears it at
   every device width instead of only the widest. */
.scr-cap {
  margin: 1.2cqw 0 0;
  font-family: var(--font-util);
  font-size: 2.8cqw; font-weight: 400; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--app-ink-label);
  text-align: center;
}
/* TabularNumeral at 118 of 393 — Washington Heights, not mono */
.scr-num {
  margin: 0;
  font-family: var(--font-display);
  font-size: 30cqw; line-height: 0.86; font-weight: 400;
  letter-spacing: normal;
  font-variant-numeric: tabular-nums;
  color: var(--app-ink);
}

/* The callout. Theme.display(40) for the movement the ring is on, 25 and
   muted for the rest — visible, so you can see what is coming, without
   competing with the one you are meant to be doing. */
.scr-move {
  display: flex; flex-direction: column; align-items: center;
  margin-top: 5.6cqw;            /* .padding(.top, 22) */
  font-family: var(--font-display);
  text-transform: uppercase; letter-spacing: normal;
}
.scr-move span {
  font-size: 6.4cqw; line-height: 1.2; font-weight: 400;
  color: var(--app-ink-muted);
  transition: font-size 0.2s ease-out, color 0.2s ease-out;
}
.scr-move span.is-now { font-size: 10.2cqw; color: var(--app-ink); }

.scr-status-line {
  margin: 3.6cqw 0 0;            /* .padding(.top, 14) */
  font-family: var(--font-data);
  font-size: 3.1cqw; font-weight: 500; letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--app-ink-label);
}

/* A hairline with three glyphs on it. The accent on a pause button was never
   carrying meaning, so the glyphs are ink. */
.scr-transport {
  flex: 0 0 auto;
  width: 100%;
  display: grid; grid-template-columns: repeat(3, 1fr);
  border-top: 1px solid var(--app-border);
  color: var(--app-ink-label);
  height: 14.8cqw;               /* .frame(height: 58) */
  margin-top: 1cqw;
}
.scr-transport > * { display: grid; place-items: center; }
.scr-transport svg { width: 5.6cqw; height: 5.6cqw; }
.scr-pause { display: flex; align-items: center; justify-content: center; width: 100%; gap: 1.1cqw; }

/* a small phone still has to hold all thirty coins — the column is the
   progress readout, so tighten the chrome rather than drop rounds */
/* The vintage panel is 3:2 — roughly half the height-per-width the screen
   layout was built for — so the columns and the chrome both come in. Thirty
   coins still have to fit: the column IS the progress readout. */

/* The bell. RunningView draws a full-screen accent rectangle and fades it out
   over 120ms on every round change — it is what gives the screen a heartbeat
   once a minute. Opacity only, so it never touches layout. The app suppresses
   it entirely under Reduce Motion; so do we, below. */
.scr-bell {
  position: absolute;
  inset: 0;
  z-index: 5;
  background: var(--app-accent);
  opacity: 0;
  pointer-events: none;
}
.scr-bell.is-struck { animation: bell 240ms ease-out; }
@keyframes bell {
  0%   { opacity: 0.85; }
  100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .scr-bell.is-struck { animation: none; }
}

.device__screen .scr-num { font-size: 36cqw; line-height: 0.78; }
.device__screen .scr-move { font-size: 8.6cqw; line-height: 1.04; margin-top: 1.2cqw; }
.device__screen .scr-cap { font-size: 3.05cqw; }
.device__screen .scr-status-line { font-size: 3.3cqw; margin-top: 1.8cqw; }
.device__screen .scr-core { padding-block: 0.4cqw 0.3cqw; }
.device__screen .scr-transport > * { padding: 1cqw 0; }
.device__screen .scr-transport { margin-top: 1.2cqw; font-size: 7cqw; }
.device__screen .scr-icons { padding-top: 0.8cqw; gap: 4cqw; }
.device__screen .scr-icons svg { width: 4.2cqw; height: 4.2cqw; }
.device__screen .scr-status { min-height: 0; font-size: 2.8cqw; padding: 0 1cqw; }
.device__screen .scr-status__r svg { height: 2.5cqw; }
.device__screen .scr-batt { width: 5cqw; height: 2.5cqw; border-width: 0.35cqw; }

/* Small panel. Two traps here, both of which bit: these must MATCH the
   .device__screen specificity above, and they must come AFTER it in source —
   equal specificity means the later rule wins. A container also cannot query
   itself, so every selector targets a descendant. */
@container (max-width: 215px) {
  .device__screen .coin { height: 0.36cqw; }
  .device__screen .coins { gap: 0.36cqw; }
  .device__screen .scr-num { font-size: 34cqw; }
  .device__screen .scr-move { font-size: 8cqw; line-height: 1.02; margin-top: 1cqw; }
  .device__screen .scr-cap { font-size: 2.8cqw; }
  .device__screen .scr-status-line { font-size: 3cqw; margin-top: 1cqw; }
  .device__screen .scr-core { padding-block: 0.4cqw 0.3cqw; }
  .device__screen .scr-transport > * { padding: 0.9cqw 0; }
  .device__screen .scr-transport { margin-top: 0.7cqw; font-size: 6.4cqw; }
  .device__screen .scr-icons { padding-top: 0.4cqw; gap: 3cqw; }
  .device__screen .scr-icons svg { width: 3.2cqw; height: 3.2cqw; }
  .device__screen .scr-status { font-size: 2.5cqw; }
  .device__screen .scr-status__r svg { height: 1.9cqw; }
  .device__screen .scr-batt { width: 3.8cqw; height: 1.9cqw; border-width: 0.3cqw; }
}
.scr-pause i { width: 1.6cqw; height: 5.4cqw; background: currentColor; border-radius: 0.3cqw; }

/* ==== full-bleed media band ========================================== */

/* ---- voices ---------------------------------------------------------
   The app's own ground, because this section is the app talking. Five
   rows rather than a picker: with no JS a tabbed control is either five
   hidden panels or a broken widget, and the whole point is that you can
   read all five and hear the difference on the page. */
.voices {
  background: var(--app-bg);
  color: var(--app-ink);
  padding: clamp(104px, 13vw, 190px) var(--pad);
}
.voices > * { max-width: var(--wrap); margin-inline: auto; }

.voices__head { margin-bottom: clamp(40px, 5vw, 72px); }
.voices__eyebrow { color: var(--app-accent); margin-bottom: 14px; }
.voices__head .t2 { color: var(--app-ink); margin-bottom: 20px; }
.voices__head .body { color: var(--app-ink-label); }

.voice {
  display: grid;
  gap: 26px 48px;
  padding: clamp(26px, 3vw, 38px) 0;
  border-top: 1px solid rgba(251, 250, 249, 0.16);
}
.voice:last-child { border-bottom: 1px solid rgba(251, 250, 249, 0.16); }

.voice__name {
  font-family: var(--font-display);
  /* single-weight face: asking for 500+ gets a synthetic bold that smears it */
  font-weight: 400;
  font-size: clamp(30px, 4.4vw, 46px);
  line-height: 1.02;
  margin: 0;
  color: var(--app-ink);
}
.voice__tag {
  font-family: var(--font-util);
  font-size: 12px;
  line-height: 1;
  margin: 12px 0 0;
  padding: 6px 9px;
  display: inline-block;
  color: var(--app-ink);
  background: var(--app-accent);
}
.voice__role {
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 25px;
  margin: 14px 0 0;
  max-width: 34ch;
  color: var(--app-ink);
}
.voice__cast {
  font-family: var(--font-util);
  font-size: 13px;
  line-height: 19px;
  margin: 10px 0 0;
  max-width: 34ch;
  color: var(--app-ink-label);
}

.voice__cueslabel {
  font-family: var(--font-util);
  font-size: 12px;
  line-height: 1;
  margin: 0 0 16px;
  color: var(--app-ink-label);
}
.voice__cues ul { list-style: none; margin: 0; padding: 0; }
.voice__cues li {
  /* the screen's own face — these are the words the phone says */
  font-family: var(--font-data);
  font-size: clamp(14px, 1.35vw, 17px);
  line-height: 1.45;
  padding: 9px 0 9px 22px;
  position: relative;
  color: var(--app-ink);
}
.voice__cues li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.05em;
  width: 12px;
  height: 3px;
  background: var(--app-accent);
}

/* The picker. `is-picker` is added by script, never authored here — without
   JS the five rows stay a readable stack rather than five hidden panels. */
.voices__tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 26px;
  margin-bottom: 8px;
  border-bottom: 1px solid rgba(251, 250, 249, 0.16);
}
.voices__tab {
  appearance: none;
  background: none;
  border: 0;
  padding: 0 0 14px;
  margin: 0;
  cursor: pointer;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(19px, 2.1vw, 26px);
  line-height: 1.1;
  color: var(--app-ink-muted);
  position: relative;
  transition: color 0.18s ease-out;
}
.voices__tab:hover { color: var(--app-ink-label); }
.voices__tab[aria-selected="true"] { color: var(--app-ink); }
.voices__tab[aria-selected="true"]::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: var(--app-accent);
}
.voices__tab:focus-visible { outline: 2px solid var(--app-accent); outline-offset: 3px; }

/* Compacted: the tab already names the voice, so the panel drops the heading
   and the rules that separated the stack. */
/* `display: grid` on .voice outranks the UA's `[hidden] { display: none }`,
   so hiding a panel by attribute alone left all five rendering. */
.is-picker .voice[hidden] { display: none; }
.is-picker .voice { border: 0; padding: 26px 0 0; }
.is-picker .voice:last-child { border-bottom: 0; }
.is-picker .voice__name { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.is-picker .voice__tag { margin-top: 0; }

@media (min-width: 900px) {
  .voice { grid-template-columns: minmax(280px, 40%) 1fr; align-items: start; gap: 10px 48px; }
  .voice__cues { padding-top: 6px; }
}

.band {
  position: relative;
  width: 100%;
  overflow: hidden;
  background: var(--ground);
  padding: clamp(26px, 3.4vw, 54px) clamp(20px, 2.8vw, 48px);
}
/* The photograph sits INSET on the page ground rather than bleeding to the
   edges. That is what joins it to the sections above and below — the cream
   runs continuously around it, so there is no seam to hide. A gradient fade
   was tried first and read as a milky wash over the bottom of the picture;
   a frame does the same job with the photo left crisp. */
.band img { display: block; width: 100%; height: clamp(300px, 68svh, 720px); object-fit: cover; }
.band--tall img { height: clamp(380px, 82svh, 840px); }

/* Art direction, not decoration. The band box goes portrait on a phone
   (393x665 — 0.59) while this frame is 1.78 landscape, so `cover` throws away
   two thirds of the width. Centred, that cut her in half at the left edge and
   filled the frame with road. Her mass sits about a quarter across, so the
   crop window is anchored there instead. Desktop is unaffected: the box is
   wider than the source there, so nothing is cropped horizontally at all. */
.band img.is-left { object-position: 26% 50%; }
.band__cap {
  position: absolute;
  /* inside the frame, measured off the band's own padding */
  left: calc(clamp(20px, 2.8vw, 48px) + 22px);
  bottom: calc(clamp(26px, 3.4vw, 54px) + 22px);
  color: var(--sand-100);
  font-size: 12px;
  letter-spacing: normal;
  font-weight: 400;
  text-shadow: 0 1px 14px #1a1812aa;
}

/* ==== rounds (events analogue) ======================================= */

.rounds { padding-block: 48px 40px; }
@media (min-width: 1024px) { .rounds { padding-block: 72px 64px; } }

.rounds__feature {
  display: grid;
  gap: 24px;
  grid-template-columns: 1fr;
  margin-bottom: 44px;
}
@media (min-width: 900px) {
  .rounds__feature { grid-template-columns: 320px 1fr; gap: 48px; align-items: start; }
}
/* 4:5, not square. Against a headline, a paragraph and a button the square
   sat short and left the row looking top-heavy. */
.rounds__feature img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
.rounds__date { font-size: 13px; letter-spacing: normal; color: var(--sand-800); margin: 0 0 10px; }
.rounds__feature .body { margin: 18px 0 22px; max-width: 62ch; }

.round-row {
  display: grid;
  grid-template-columns: 68px 1fr;
  gap: 6px 20px;
  padding: 26px 0;
  border-top: 1px solid var(--sand-600);
  align-items: start;
}
/* the number owns the left rail; the title and copy stack in the right one */
.round-row .round-num { grid-row: 1 / span 2; }
.round-row .t3 { grid-column: 2; }
.round-row .body { grid-column: 2; }
@media (min-width: 900px) {
  .round-row { grid-template-columns: 88px 240px 1fr; gap: 32px; align-items: center; }
  .round-row .round-num { grid-row: auto; }
  .round-row .t3 { grid-column: auto; }
  .round-row .body { grid-column: auto; }
}
.round-row:last-of-type { border-bottom: 1px solid var(--sand-600); }
.round-num { text-align: center; line-height: 1; }
.round-num b { display: block; font-size: 40px; font-weight: 400; }
.round-num span { font-size: 11px; letter-spacing: normal; color: var(--sand-800); }
.round-row .body { color: var(--sand-900); max-width: 60ch; }

/* ==== about =========================================================== */

.about { padding-block: 56px 64px; }
@media (min-width: 1024px) { .about { padding-block: 96px 104px; } }
.about__grid { display: grid; gap: 36px; grid-template-columns: 1fr; align-items: center; }
@media (min-width: 900px) { .about__grid { grid-template-columns: 1fr 420px; gap: 80px; } }
.about .body { max-width: 56ch; margin-top: 24px; }
/* The photograph is a print on the desk, not a full-bleed image: kraft torn
   under it, a cream mat around it, tape across the top edge. Same asset set
   and the same vocabulary as the Pack card, so the two read as one hand. */
/* The torn scraps deliberately run past the print's edge, which on a phone
   pushed them past the viewport and gave the whole page 27px of horizontal
   scroll. `overflow-x: clip` trims them at the section edge without killing
   the vertical overflow the drop shadow needs. */
.about { overflow-x: clip; }
.mount { position: relative; }
.mount__scrap, .mount__tape { position: absolute; height: auto; pointer-events: none; z-index: 0; }
.mount__scrap--a { left: -7%; top: -30px; width: 54%; transform: rotate(-2.2deg); }
.mount__scrap--b { right: -8%; bottom: -34px; width: 38%; transform: rotate(4.2deg); }
/* the tape holds the print DOWN, so it sits over it, straddling the top edge */
.mount__tape { z-index: 2; left: 16%; top: -20px; width: 176px; transform: rotate(-3.2deg); }
.mount__card {
  position: relative; z-index: 1;
  background: var(--sand-100);
  /* deeper at the foot, the way a mounted print is trimmed */
  padding: 14px 14px 44px;
  box-shadow: 0 26px 62px #1a181236;
}
.mount__card::after {
  content: "";
  position: absolute; inset: 0;
  background-image: url(img/paper.png);
  background-size: 500px 500px;
  opacity: 0.55; mix-blend-mode: multiply; pointer-events: none;
}
.about .mount__card img { display: block; width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
@media (max-width: 899px) {
  .mount__scrap--a { left: -10%; width: 64%; top: -16px; }
  .mount__scrap--b { right: -12%; width: 46%; bottom: -18px; }
  .mount__tape { width: 124px; top: -13px; }
}

/* ==== get the app (store locator analogue) =========================== */

.get {
  position: relative;
  background: var(--ground);
  color: var(--sand-950);
  padding: 40px 0 48px;
  overflow: hidden;
}

.get__in {
  position: relative;
  max-width: 940px;
  margin-inline: auto;
  padding-block: 44px;
  border-top: 2px solid var(--sand-950);
  border-bottom: 2px solid var(--sand-950);
  text-align: center;
}
.get .body { margin-inline: auto; }
.get__shot { margin-inline: auto; }
.get .t2 { color: var(--sand-950); }
.get .body { color: var(--sand-900); margin-top: 18px; max-width: 52ch; }


.appstore {
  display: inline-flex; align-items: center; gap: 10px;
  height: 52px; padding: 0 26px; margin-top: 26px;
  background: var(--red-500); color: var(--sand-50);
  border-radius: 6px; font-size: 15px; font-weight: 400; letter-spacing: normal;
  transition: filter 0.2s var(--ease);
}
.appstore:hover { filter: brightness(1.08); }
.get__meta { margin-top: 14px; font-size: 14px; line-height: 20px; color: var(--sand-800); }

/* ==== newsletter ====================================================== */

.letters { position: relative; padding: 48px 0 44px; background: var(--sand-200); }
.letters__in { display: grid; gap: 32px; grid-template-columns: 1fr; align-items: start; }
@media (min-width: 900px) { .letters__in { grid-template-columns: 1fr 420px; gap: 80px; } }
.letters .body { margin-top: 14px; max-width: 46ch; color: var(--sand-900); }

.signup { display: flex; gap: 10px; }
.signup input {
  flex: 1;
  height: 44px;
  padding: 0 14px;
  border: 2px solid var(--sand-950);
  border-radius: 4px;
  background: var(--sand-50);
  font: inherit;
  font-size: 13px;
  letter-spacing: normal;
  text-transform: uppercase;
}
.signup input::placeholder { color: var(--sand-700); }
.signup input:focus { outline: 2px solid var(--red-500); outline-offset: 1px; }
.letters__fine { margin-top: 12px; font-size: 11px; letter-spacing: normal; color: var(--sand-800); }
.socials { display: flex; gap: 14px; margin-top: 16px; color: var(--red-500); }
.socials svg { width: 20px; height: 20px; }

/* ==== footer ========================================================== */

/* ==== footer ===========================================================
   The page ends on the app's own ground rather than trailing off on sand:
   the mark, the line the whole product is named after, and one button. The
   columns and the sign-up sit underneath it, so the last thing on the page
   is a statement and not a form. */
.footer { background: var(--app-bg); color: var(--app-ink); }

.footer__shout {
  position: relative;
  text-align: center;
  padding: clamp(112px, 15vw, 224px) var(--pad) clamp(84px, 10vw, 144px);
  border-bottom: 1px solid rgba(251, 250, 249, 0.14);
}
.footer__mark {
  width: clamp(112px, 15vw, 196px);
  height: auto;
  margin: 0 auto clamp(24px, 3vw, 40px);
  display: block;
}
.footer__line {
  font-family: var(--font-display);
  /* single-weight face — 500+ would smear it */
  font-weight: 400;
  font-size: clamp(46px, 9.4vw, 152px);
  line-height: 0.94;
  letter-spacing: normal;
  margin: 0 0 clamp(28px, 3.4vw, 46px);
  color: var(--app-ink);
}
/* ---- the QR handoff ---------------------------------------------------
   On a desktop an App Store link is a dead end — the store opens on the wrong
   device. Hovering the button morphs it into a QR you point your phone at.

   Three things make it behave:
   - `.qrcta` keeps the button's ORIGINAL footprint, so the card expands out of
     flow and the footer never reflows under the pointer.
   - `::before` is an invisible pad 8px larger than the open card. Without it
     the pointer can leave the button's own box mid-morph, which unhovers it,
     which shrinks it back under the cursor — a flicker loop.
   - `:focus-within` mirrors every `:hover`, so a keyboard reaches it too.
   Touch never sees it (`hover: hover` + `pointer: fine`), and the QR is
   aria-hidden with the label on the anchor, so a screen reader gets one link. */
.qrcta { position: relative; display: inline-block; z-index: 5; }
.qrcta__btn:focus-visible { outline: 2px solid var(--app-accent); outline-offset: 3px; }
.qrcta__btn { box-sizing: border-box; }
.qrcta__qr {
  position: absolute; inset: 0; z-index: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: 26px;
  opacity: 0; pointer-events: none;
  transition: opacity 160ms 80ms;
}
/* 216, not 190: the baked-in quiet zone is now part of the image, so the
   code itself is 33 of 41 modules wide and needs the extra size to stay
   comfortably scannable. */
.qrcta__qr img { display: block; width: 216px; height: 216px; border-radius: 4px; }
.qrcta__cap {
  font-family: var(--font-util);
  font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink); white-space: nowrap;
}
.qrcta__label { position: relative; z-index: 2; transition: opacity 140ms; }

@media (hover: hover) and (pointer: fine) {
  /* The shell holds the button's ORIGINAL footprint and the card expands out
     of flow from its centre, so nothing below it moves while you hover.
     Rest size comes from the measurement as a custom property; the :hover rule
     below outranks it on specificity, which an inline width would not have
     allowed. */
  .qrcta.is-ready { width: var(--qrw); height: var(--qrh); }
  .qrcta.is-ready .qrcta__btn {
    position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    width: var(--qrw); height: var(--qrh);
  }
  .qrcta.is-ready .qrcta__btn {
    /* the spring is the whole character of it — 1.56 overshoots, so the card
       lands with a small settle instead of easing flatly into place */
    transition: width 280ms cubic-bezier(0.34, 1.56, 0.64, 1),
                height 280ms cubic-bezier(0.34, 1.56, 0.64, 1),
                border-radius 220ms ease,
                background-color 200ms ease;
  }
  .qrcta.is-ready::before {
    content: ""; position: absolute; z-index: 0;
    left: 50%; top: 50%;
    width: 288px; height: var(--qrh);
    transform: translate(-50%, -50%);
    transition: height 280ms ease;
  }
  .qrcta.is-ready:hover::before, .qrcta.is-ready:focus-within::before { height: 320px; }
  .qrcta.is-ready:hover .qrcta__btn, .qrcta.is-ready:focus-within .qrcta__btn {
    width: 280px; height: 308px;
    border-radius: 26px;
    /* cream, not black. Sabbath's card is black because their page is white;
       ours would have been a black card on a near-black footer with only the
       shadow to separate it. Inverted, it reads like a card handed to you. */
    background: var(--app-ink);
    box-shadow: rgba(0,0,0,0.55) 0 60px 140px -20px, rgba(0,0,0,0.4) 0 30px 80px -10px;
  }
  .qrcta.is-ready:hover .qrcta__label, .qrcta.is-ready:focus-within .qrcta__label { opacity: 0; }
  .qrcta.is-ready:hover .qrcta__qr, .qrcta.is-ready:focus-within .qrcta__qr { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .qrcta__btn, .qrcta__label, .qrcta__qr, .qrcta::before { transition: none; }
}

.footer__note {
  font-family: var(--font-util);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--app-ink-label);
  margin: 18px 0 0;
}

.footer__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 52px;
  padding-block: clamp(76px, 9.5vw, 132px);
}
.footer__col { display: flex; flex-direction: column; align-items: flex-start; }
.footer__ch {
  font-family: var(--font-util);
  font-weight: 400;
  font-size: 12px;
  line-height: 1;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--app-accent);
  margin: 0 0 18px;
}
.footer__col a,
.footer__col .footer__link {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  letter-spacing: normal;
  text-transform: none;
  color: var(--app-ink);
  padding: 8px 0;
  transition: color 0.16s ease-out;
}
.footer__col a:hover,
.footer__col .footer__link:hover { color: var(--app-accent); }
.footer__col a:focus-visible,
.footer__col .footer__link:focus-visible { outline: 2px solid var(--app-accent); outline-offset: 3px; }

.footer__blurb {
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  color: var(--app-ink-label);
  margin: 0 0 18px;
  max-width: 38ch;
}
.footer__col--sign .signup { width: 100%; max-width: 380px; }
.footer__col--sign .signup input {
  background: transparent;
  border-color: rgba(251, 250, 249, 0.28);
  color: var(--app-ink);
}
.footer__col--sign .signup input::placeholder { color: var(--app-ink-muted); }
.footer__fine {
  margin-top: 12px;
  font-size: 11px;
  letter-spacing: normal;
  color: var(--app-ink-muted);
}

.footer__base {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 28px;
  justify-content: space-between;
  padding-block: 40px 64px;
  border-top: 1px solid rgba(251, 250, 249, 0.14);
}
.footer__badge { width: 84px; height: auto; }
.footer__copy,
.footer__made {
  font-size: 11px;
  letter-spacing: normal;
  color: var(--app-ink-muted);
  margin: 0;
}

/* the letters modal has one entry point, and this is it */
.footer__link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
}

@media (min-width: 720px) {
  .footer__grid { grid-template-columns: repeat(3, 1fr); }
  .footer__col--sign { grid-column: 1 / -1; }
}
@media (min-width: 1000px) {
  .footer__grid { grid-template-columns: repeat(3, minmax(0, 0.7fr)) minmax(0, 1.3fr); gap: 64px; }
  .footer__col--sign { grid-column: auto; }
}

/* ==== newsletter modal ================================================ */

.modal {
  position: fixed; inset: 0; z-index: 100;
  display: grid; place-items: center;
  background: #1a181280;
  padding: 20px;
  opacity: 0; visibility: hidden;
  transition: opacity 0.4s var(--ease), visibility 0.4s;
}
.modal[data-open="true"] { opacity: 1; visibility: visible; }
.modal__card {
  position: relative;
  width: min(420px, 100%);
  background: var(--sand-100);
  padding: 30px 34px 26px;
  text-align: center;
  box-shadow: 0 30px 70px #1a181259;
}
.modal__card::before {
  content: "";
  position: absolute; inset: 0;
  background-image: url(img/paper.png);
  background-size: 500px 500px;
  opacity: 0.6; mix-blend-mode: multiply; pointer-events: none;
}
.modal__badge { width: 116px; height: auto; margin: 0 auto 14px; }
.modal__card .t3 { margin-bottom: 14px; }
.modal__rule { height: 2px; background: var(--sand-950); margin: 0 0 16px; }
.modal__x {
  position: absolute; top: 8px; right: 10px;
  background: none; border: 0; cursor: pointer;
  color: var(--red-500); font-size: 16px; line-height: 1; padding: 6px;
}

/* ==== reveal ========================================================== */

[data-reveal] { opacity: 0; transform: translateY(22px); transition: opacity 0.7s var(--ease), transform 0.7s var(--ease); }
[data-reveal].is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  /* Nothing animates, so shrink the runway to a single screen — but do NOT
     touch .pin__stage. It is the positioned, centred grid the phone and the
     corner copy are absolutely positioned against; making it static orphans
     them to the left edge and stacks them on top of each other. */
  .pin { height: calc(100svh - var(--header-h)); }
}

/* ==== the pack (community) ===========================================
   The reference's store locator: a centred title over a two-panel card,
   scrolling list on the left, map on the right, the whole thing sitting on
   torn paper. Ours plots sessions instead of stockists. The map is a dot
   grid rasterised from Natural Earth 110m land (public domain) with the
   same equirectangular projection the pin maths uses — LAT0 72, LAT1 -58 —
   so a pin lands where the coastline actually is. */

.pack { position: relative; padding-block: 56px 48px; background: var(--ground); overflow: hidden; }
@media (min-width: 1024px) { .pack { padding-block: 88px 56px; } }

.pack__title { text-align: center; margin-bottom: 26px; }

/* Torn kraft and a strip of tape behind the card, the way the reference
   collages its locator. Decorative only — pointer-events off, aria-hidden,
   and they never intercept a click on the list. */
.pack__collage { position: relative; }
.pack__scrap, .pack__tape {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  user-select: none;
}
.pack__scrap--a { left: -4%; top: -34px; width: 48%; transform: rotate(-1.8deg); }
.pack__scrap--b { right: -5%; bottom: -38px; width: 32%; transform: rotate(4.5deg); }
/* tape holds the card DOWN, so it sits over it, straddling the top edge */
.pack__tape { z-index: 2; left: 13%; top: -22px; width: 200px; transform: rotate(-3.4deg); }
.pack__tape--r { left: auto; right: 15%; top: auto; bottom: -20px; transform: rotate(2.6deg); }
@media (max-width: 899px) {
  .pack__scrap--a { left: -8%; top: -16px; width: 62%; }
  .pack__scrap--b { right: -10%; bottom: -18px; width: 44%; }
  .pack__tape { width: 132px; top: -14px; }
  .pack__tape--r { bottom: -12px; }
}

.pack__card {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr;
  background: var(--sand-100) url(img/paper.png) repeat;
  background-size: auto, 600px 600px;
  border: 1px solid var(--sand-500);
  box-shadow: 0 24px 60px #1a181226;
  overflow: hidden;
}
@media (min-width: 900px) { .pack__card { grid-template-columns: 360px 1fr; } }

.pack__head { padding: 18px 20px 14px; border-bottom: 1px solid var(--sand-400); }
.pack__search { display: flex; gap: 8px; margin: 12px 0 10px; }
.pack__search input {
  flex: 1; min-width: 0; height: 38px; padding: 0 12px;
  border: 1px solid var(--sand-600); border-radius: 3px;
  background: var(--sand-50);
  font: inherit; font-family: var(--font-body); font-size: 13px;
  letter-spacing: normal; text-transform: none;
}
.pack__search input:focus { outline: 2px solid var(--red-500); outline-offset: 1px; }
.pack__search .btn { height: 38px; padding: 0 14px; }
.pack__count { margin: 0; font-family: var(--font-body); font-size: 12px; color: var(--sand-800); letter-spacing: normal; }
.pack__tag {
  display: inline-block; padding: 1px 6px; border-radius: 2px;
  background: var(--sand-400); color: var(--sand-900);
  font-size: 10px; letter-spacing: normal;
}

/* The roster is longer than the card, so the last visible row gets cut. Rows
   are not a fixed height — some wrap to two lines — so snapping the container
   to a whole number of them is not reliable. A fade at the bottom edge makes
   the cut read as "there is more" instead of as a broken crop. */
.pack__rows {
  list-style: none; margin: 0; padding: 0;
  max-height: 420px; overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--sand-600) transparent;
  -webkit-mask-image: linear-gradient(180deg, #000 calc(100% - 54px), transparent 100%);
  mask-image: linear-gradient(180deg, #000 calc(100% - 54px), transparent 100%);
}
@media (min-width: 900px) { .pack__rows { max-height: 520px; } }
.pack__row {
  display: grid; grid-template-columns: 1fr 56px; gap: 12px; align-items: center;
  padding: 13px 20px; border-bottom: 1px solid var(--sand-300);
  cursor: pointer; transition: background 0.15s var(--ease);
}
.pack__row:hover, .pack__row[aria-current="true"] { background: var(--sand-200); }
.pack__row b { display: block; font-size: 16px; font-weight: 400; }
.pack__row span { display: block; font-family: var(--font-body); font-size: 12px; color: var(--sand-800); letter-spacing: normal; }
.pack__stat {
  display: grid; place-items: center; align-content: center;
  width: 56px; height: 56px;
  border: 1px solid var(--sand-500); background: var(--sand-200);
  line-height: 1;
}
.pack__stat b { font-size: 20px; font-weight: 400; }
.pack__stat span { font-size: 9px; letter-spacing: normal; color: var(--sand-800); }
.pack__empty { padding: 26px 20px; font-family: var(--font-body); font-size: 13px; color: var(--sand-800); }

/* the pins are positioned in % of the frame, so the frame has to be the
   image's own aspect — any cover-crop and a pin drifts off its coastline */
.pack__map {
  display: grid; place-items: center;
  background: var(--sand-200);
  padding: 10px;
  min-height: 260px;
}
.pack__mapframe { position: relative; width: 100%; aspect-ratio: 2000 / 1250; }
.pack__mapframe img { width: 100%; height: 100%; display: block; }
.pack__pins { position: absolute; inset: 0; }
.pack__pin {
  position: absolute;
  width: 11px; height: 11px; margin: -5.5px 0 0 -5.5px;
  border-radius: 50%;
  background: var(--red-500);
  box-shadow: 0 0 0 2px #efede7;
  transition: transform 0.2s var(--ease);
}
.pack__pin[data-on="true"] { transform: scale(1.7); z-index: 2; }
.pack__pin::after {
  content: "";
  position: absolute; inset: -7px;
  border-radius: 50%;
  border: 1px solid var(--red-500);
  opacity: 0;
}
.pack__pin[data-on="true"]::after { opacity: 0.7; }

.pack__fine {
  margin: 18px auto 0; max-width: 62ch; text-align: center;
  font-family: var(--font-body); font-size: 12px; line-height: 19px;
  color: var(--sand-800); letter-spacing: normal;
}

/* ---- the widget ------------------------------------------------------
   Same treatment as the pinned product: the device carries the idea and the
   copy flanks it. The screen is real markup rather than a screenshot, so the
   marks can strike themselves in on reveal and the whole thing stays sharp at
   any width. Wallpaper is the wolf — a stock iOS gradient behind our own
   paper cards read like a mockup of somebody else's phone. */
.wgt {
  position: relative;
  background: var(--sand-950);
  color: var(--sand-200);
  padding-block: clamp(120px, 13vw, 200px) clamp(64px, 9vw, 132px);
  overflow: hidden;
}
.wgt::before {                       /* the wolf, ghosted, filling the band */
  content: "";
  position: absolute; inset: -10% -5%;
  background: url(img/wolf-painting.png) center/contain no-repeat;
  opacity: 0.13;
  pointer-events: none;
}
.wgt__in {
  position: relative; z-index: 2;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: clamp(18px, 3vw, 52px);
}
.wgt__col { display: flex; flex-direction: column; gap: 10px; }
.wgt__col--r { text-align: right; align-items: flex-end; }
.wgt__kicker {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--red-500);
}
.wgt__word {
  font-family: "Washington Heights JNL", var(--display, sans-serif);
  font-size: clamp(30px, 4.6vw, 62px); line-height: 0.94;
  color: var(--sand-100);
}
.wgt__rule { display: block; width: 84px; height: 2px; background: var(--red-500); }
.wgt__col--r .wgt__rule { align-self: flex-end; }
.wgt__figure {
  font-family: "Washington Heights JNL", var(--display, sans-serif);
  font-size: clamp(20px, 2.4vw, 32px); color: var(--red-500);
}
.wgt__note {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px; line-height: 19px; letter-spacing: 0.02em;
  color: var(--sand-600); max-width: 30ch;
}
.wgt__col--r .wgt__note { text-align: right; }

/* the handset */
.wgt__phone { display: grid; place-items: center; }
.wgt__device {
  position: relative;
  width: clamp(230px, 34vh, 330px);
  aspect-ratio: 700 / 1303;
  background: url(img/iphone-vintage.png) center/contain no-repeat;
  filter: drop-shadow(0 26px 44px rgba(0,0,0,.55));
}
.wgt__screen {
  position: absolute;
  inset: 11.2% 6.4% 12.6%;
  border-radius: 3px;
  overflow: hidden;
  background: url(img/round-night.jpg) center/cover no-repeat, var(--sand-950);
  display: flex; flex-direction: column; gap: 5%;
  padding: 7% 6% 5%;
  container-type: inline-size;
}
.wgt__screen::after {                /* darken so the paper cards carry */
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(10,9,6,.55), rgba(10,9,6,.78));
}
.wgt__status {
  position: relative; z-index: 2;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 4cqw; color: rgba(255,255,255,.9); letter-spacing: .04em;
}

/* the cards */
.wcard {
  position: relative; z-index: 2;
  background: var(--sand-200);
  color: var(--sand-950);
  border-radius: 4.6cqw;
  padding: 4.4cqw 4.8cqw;
  box-shadow: 0 2cqw 4cqw rgba(0,0,0,.4);
}
.wcard--sm { width: 47%; }
.wcard--md .wcard__body > div:first-child { display: flex; flex-direction: column; }
.wcard__row { display: flex; justify-content: space-between; gap: 4cqw; }
.wcard__body { display: flex; justify-content: space-between; align-items: flex-start; gap: 4cqw; margin-top: 1cqw; }
.wcard__eyebrow {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 2.7cqw; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--sand-800);
}
.wcard__num { display: flex; align-items: baseline; gap: 1.4cqw; margin-top: 1cqw; }
.wcard__num b {
  font-family: "Washington Heights JNL", var(--display, sans-serif);
  font-size: 13cqw; line-height: 0.8; color: var(--red-500); font-weight: 400;
}
.wcard__num span {
  font-family: "Washington Heights JNL", var(--display, sans-serif);
  font-size: 4.2cqw; color: var(--sand-800);
}
.wcard__state {
  font-family: "Washington Heights JNL", var(--display, sans-serif);
  font-size: 4.2cqw; margin-top: 1.6cqw;
}

/* seven uprights, struck on the days that were trained */
.wmarks { display: flex; align-items: center; gap: 1.5cqw; margin-top: 2.6cqw; }
.wmarks i {
  display: block; width: 0.9cqw; height: 7cqw; border-radius: 1cqw;
  background: var(--sand-950); opacity: .16;
  transform: scaleY(.4); transform-origin: bottom;
  transition: transform .42s cubic-bezier(.2,.9,.3,1), opacity .42s ease;
}
.is-in .wmarks i { transform: scaleY(1); }
.wmarks i.on { background: var(--red-500); opacity: 1; }
.wmarks i.today {
  background: none; opacity: 1;
  box-shadow: inset 0 0 0 .22cqw var(--red-500);
}
.wmarks i:nth-child(1){transition-delay:.02s}.wmarks i:nth-child(2){transition-delay:.07s}
.wmarks i:nth-child(3){transition-delay:.12s}.wmarks i:nth-child(4){transition-delay:.17s}
.wmarks i:nth-child(5){transition-delay:.22s}.wmarks i:nth-child(6){transition-delay:.27s}
.wmarks i:nth-child(7){transition-delay:.32s}

/* five weeks, weekday aligned */
.wgrid {
  display: grid;
  grid-template-columns: repeat(7, 2.3cqw);
  gap: .62cqw;
  flex: 0 0 auto;
}
.wgrid i {
  display: block; width: 2.3cqw; height: 2.3cqw; border-radius: .5cqw;
  background: var(--sand-950); opacity: .13;
}
.wgrid i.on { background: var(--red-500); opacity: 1; }
.wgrid i.future { opacity: .05; }

.wgt__dock { position: relative; z-index: 2; margin-top: auto; display: flex; gap: 4cqw; justify-content: center; }
.wgt__dock i { width: 11cqw; aspect-ratio: 1; border-radius: 2.6cqw; background: rgba(255,255,255,.14); }

.wgt__rows { position: relative; z-index: 2; margin-top: clamp(40px, 5vw, 76px); }
.wgt__rows .round-row { border-color: rgba(255,255,255,.14); }
.wgt__rows .round-num b, .wgt__rows .t3 { color: var(--sand-100); }
.wgt__rows .round-num span, .wgt__rows .body p { color: var(--sand-600); }

@media (max-width: 899px) {
  .wgt__in { grid-template-columns: 1fr; justify-items: center; text-align: center; }
  .wgt__col--r { text-align: center; align-items: center; }
  .wgt__col--r .wgt__note { text-align: center; }
  .wgt__note { max-width: 34ch; }
  .wgt__device { width: min(62vw, 260px); }
  .wgt__phone { order: -1; }
}
