/* Tonaut — the landing page only.  The shared shell lives in site.css.
   Implemented from design_handoff_site/README.md with
   "Tonaut Site.dc.html" as the authority. */

/* ---------- 1. the scroll stage (handoff §3) ---------- */
/* 240vh with a sticky child.  Scrolling scrubs a four-scene animation of
   the instrument, drawn live in vector.  Nothing between here and <html>
   may carry overflow-x: hidden, or the sticky child has no scrollport. */

.stage {
  position: relative;
  /* Four scenes, each held for a quarter of the distance the sticky child is held
     (hero.js measures it): this height less the bottom padding, the border and the
     sticky child's own height, 240vh - var(--block) - 1px - (100vh - 64px).  That is
     about 34vh of scrolling a scene on a laptop screen, half the 68vh between scenes
     when this was 300vh with three. */
  height: 240vh;
  /* The wash layer below is sticky with a negative bottom margin, which is the usual way to
     overlay a viewport-tall backdrop without adding a viewport of height. The margin removes
     its LAYOUT height but not its PAINTED box, so it kept painting 1310 px past this section
     and washed out the ticker and the top of the dark block beneath it. `clip` confines it.
     It has to be `clip` rather than `hidden`: `hidden` would make this a scroll container and
     `.stage__sticky` would stop sticking, which is the whole scroll effect. */
  overflow: clip;
  padding-bottom: var(--block);
  border-bottom: 1px solid var(--rule);
  /* The base, and what shows if the video never arrives: the gradients this section
     has always had, unchanged. */
  background:
    radial-gradient(72% 92% at 20% 14%, #ffffff 0%, rgba(255, 255, 255, 0) 66%),
    radial-gradient(66% 86% at 84% 82%, #f0f4f8 0%, rgba(240, 244, 248, 0) 70%),
    radial-gradient(130% 120% at 50% 48%, rgba(201, 210, 220, 0.5) 0%, rgba(201, 210, 220, 0) 62%),
    linear-gradient(140deg, #eaeef3 0%, #e2e7ee 100%);
}

/* ── the video wash, and the gradient film that washes it out ───────────────────── */
.stage__bg {
  position: sticky; top: var(--head-h);
  height: calc(100vh - var(--head-h));
  /* cancels its own height, so the scenes are not pushed down a viewport */
  margin-bottom: calc(-1 * (100vh - var(--head-h)));
  overflow: hidden;
  z-index: 0;
  pointer-events: none;
}
.stage__vid { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Richard: the old gradient, over the video, to wash it out a bit. The last layer of
   that stack is opaque, so the film carries the whole stack at part strength rather
   than any one layer being made transparent — the gradient keeps its shape and only
   its weight changes. */
.stage__film {
  position: absolute; inset: 0;
  opacity: 0.62;
  background:
    radial-gradient(72% 92% at 20% 14%, #ffffff 0%, rgba(255, 255, 255, 0) 66%),
    radial-gradient(66% 86% at 84% 82%, #f0f4f8 0%, rgba(240, 244, 248, 0) 70%),
    radial-gradient(130% 120% at 50% 48%, rgba(201, 210, 220, 0.5) 0%, rgba(201, 210, 220, 0) 62%),
    linear-gradient(140deg, #eaeef3 0%, #e2e7ee 100%);
}
/* Motion behind text is the clearest case for this preference. The still frame is the
   video's own poster, so nothing looks different except that it stops. */
@media (prefers-reduced-motion: reduce) {
  .stage__vid { display: none; }
  .stage__bg {
    background: url("../img/hero-wash-1600.webp") center / cover no-repeat;
  }
}

.stage__sticky {
  position: sticky; top: var(--head-h); z-index: 1;
  height: calc(100vh - var(--head-h));
  max-width: var(--col); margin: 0 auto;
  display: flex; flex-direction: column; justify-content: center;
  gap: clamp(12px, 2vh, 22px);
  padding: clamp(18px, 3vh, 34px) var(--gutter);
  overflow: hidden;
}

/* caption — scene number, title, note, progress line */
.scene { width: 100%; display: flex; align-items: flex-start; gap: clamp(12px, 1.8vw, 22px); flex-wrap: wrap; }
.scene__no {
  font-family: "Martian Mono", ui-monospace, monospace; font-weight: 600;
  font-size: clamp(22px, 2.8vw, 38px); line-height: 0.82; color: var(--accent);
}
.scene__body {
  display: flex; flex-direction: column; gap: 5px; flex: 1;
  min-width: min(100%, 260px);
  border-left: 1px solid var(--rule); padding-left: clamp(12px, 1.8vw, 22px);
}
.scene__line { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.scene__title { font-weight: 600; font-variation-settings: "wdth" 115; font-size: clamp(11px, 1.15vw, 13px); letter-spacing: 0.16em; text-transform: uppercase; }
.scene__prog { flex: 1; min-width: 60px; height: 1px; background: var(--rule); position: relative; top: -3px; }
.scene__prog i { position: absolute; left: 0; top: 0; height: 1px; background: var(--accent); width: 0; }
.scene__count { font-family: "Martian Mono", ui-monospace, monospace; font-weight: 600; font-size: 9px; color: var(--ink-3); }
.scene__note { font-size: 13px; line-height: 1.5; color: var(--ink-3); max-width: 74ch; text-wrap: pretty; }

/* The caption reserves one height for all four scenes.  Scene 01's note is
   one line at 820 px and up where 02 and 03 take two, and at phone width
   the Seq title is the one that takes two - so the caption grew mid
   animation, the device box below it (flex: 1, centred) lost that height,
   and the instrument re-centred about 10 px lower.  Stacking all three
   captions in a single grid cell makes the block as tall as the longest at
   whatever the current width is, with no reserved magic number to go stale
   when the copy changes.  visibility keeps the hidden ones measured and out
   of the accessibility tree. */
[data-cap] { display: grid; }
[data-cap] > span { grid-area: 1 / 1; visibility: hidden; }
[data-cap] > span[data-on] { visibility: visible; }

/* the device is the only block that flexes */
.stage__device { flex: 1; min-height: 0; width: 100%; display: flex; align-items: center; }
.stage__device svg { display: block; width: 100%; height: 100%; }

/* Wide and short: the caption moves beside the instrument.
   The stage is 100vh tall, so on a short window the drawing runs out of
   HEIGHT before it runs out of width and stops short of the measure.  Taking
   the caption out of the vertical stack hands the device the ~83 px the
   caption and its gap were using.

   It is deliberately not a plain "wide screen" rule.  The drawing is 1040 x
   540, so width is expensive: every pixel the caption column takes costs
   0.84 px of instrument, while height only pays until width becomes the
   binding constraint.  Past that point a side column makes the hero SMALLER
   - measurably so at 1440x1080, 1680x1050 and 1920x1080.  The condition
   below is the one derived from the drawing's own aspect: switch only where
   the stacked layout is height-limited AND there is enough spare width that
   the column costs less than the height buys.  Across the range that works
   out at a viewport ratio of about 5/3, and the max-height stops it applying
   on big tall screens where the 1440 measure is already the cap.  The
   min-height keeps it off a landscape phone, where the caption column is
   taller than the row it would have to sit in. */
@media (min-aspect-ratio: 5/3) and (max-height: 1000px) and (min-height: 500px) {
  .stage__sticky {
    display: grid;
    grid-template-columns: clamp(140px, 11vw, 156px) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) auto;
    column-gap: 18px;
    /* NOT align-items: center.  Centring the items stops the device being
       stretched to its row, so the svg's height: 100% resolves against an
       auto height, the drawing takes its natural size and overflows the
       sticky - on a short window the pitch block lands on top of the
       instrument.  The device stays stretched; only the caption centres. */
  }
  .scene { grid-area: 1 / 1; flex-direction: column; gap: 12px; align-self: center; }
  .scene__no { font-size: clamp(24px, 2.2vw, 32px); }
  /* the rule moves from the side of the caption to the top of it */
  .scene__body { min-width: 0; border-left: 0; padding-left: 0;
                 border-top: 1px solid var(--rule); padding-top: 10px; }
  .scene__count { display: none; }
  .scene__prog { flex: 1 0 100%; min-width: 100%; top: 0; margin-top: 5px; }
  .stage__device { grid-area: 1 / 2; }
  .pitch { grid-area: 2 / 1 / 3 / -1; }
}

/* pitch — eyebrow, h1, price, CTA */
.pitch {
  width: 100%; display: flex; align-items: flex-end;
  gap: clamp(16px, 3vw, 40px); flex-wrap: wrap;
  border-top: 1px solid var(--ink); padding-top: clamp(12px, 1.8vh, 20px);
}
.pitch__left { flex: 1; min-width: min(100%, 300px); display: flex; flex-direction: column; gap: 9px; }
.pitch__eyebrow { display: flex; align-items: center; gap: 9px; color: var(--accent); }
.pitch__eyebrow::before { content: ""; width: 7px; height: 7px; background: var(--accent); flex: none; }
.pitch__eyebrow span { font-weight: 600; font-variation-settings: "wdth" 115; font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase; }
.pitch__right { display: flex; align-items: flex-end; gap: clamp(14px, 2vw, 22px); flex-wrap: wrap; }
.price { display: flex; flex-direction: column; gap: 3px; }
.price b { font-family: "Martian Mono", ui-monospace, monospace; font-weight: 600; font-size: clamp(24px, 2.6vw, 34px); line-height: 1; }
.price span { font-weight: 600; font-variation-settings: "wdth" 115; font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3); }

/* ---------- 2. the ticker ---------- */

.ticker { background: var(--dark); color: var(--dark-ink); overflow: hidden; white-space: nowrap; }
.ticker__rail { display: inline-flex; width: max-content; }
.ticker__rail[data-tnt-anim] { animation: tnt-slide 38s linear infinite; }
.ticker__set {
  display: flex; align-items: center; gap: 26px; padding: 12px 13px;
  font-weight: 600; font-variation-settings: "wdth" 115;
  font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
}
.ticker__set i { color: var(--accent-text); font-style: normal; }

/* ---------- 3. plain sections ---------- */
/* Space above only; the next section owns its own space, so margins never
   collapse into a guess (handoff §2). */

.sec { max-width: var(--col); margin: 0 auto; padding: var(--block) var(--gutter) 0; }
.sec__body { padding-top: var(--half); }

/* Three up, then two, then one.  This was auto-fit on a 260 px floor, which
   counted columns off the measure - so widening the measure to 1440 silently
   made it four up and the five mode shots broke 4 + 2.  Both card grids hold
   three or six items and are designed three up, so the column count is stated
   rather than derived.  The two widths are where auto-fit used to turn over,
   so 390 / 820 / 1440 still get 1 / 2 / 3. */
.cards { display: grid; grid-template-columns: 1fr; gap: clamp(18px, 2.4vw, 32px); }
@media (min-width: 37em) { .cards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 57em) { .cards { grid-template-columns: repeat(3, 1fr); } }
.card { margin: 0; display: flex; flex-direction: column; gap: 11px; }
.card__img { width: 100%; aspect-ratio: 370 / 137; background: var(--screen); border-radius: var(--r-img); overflow: hidden; }
.card__img img { width: 100%; height: 100%; object-fit: cover; }
.card figcaption, .card__cap { display: flex; flex-direction: column; gap: 5px; border-top: 1px solid var(--rule); padding-top: 9px; }
.card__cap--ink { border-top-color: var(--ink); }
.card__row { display: flex; align-items: baseline; gap: 10px; border-top: 1px solid var(--ink); padding-top: 9px; }
.card__row b { font-family: "Martian Mono", ui-monospace, monospace; font-weight: 600; font-size: 10px; color: var(--accent); }

/* the panel shots in #modes keep their own ratio, set on the element */
.card--shot .card__img { aspect-ratio: 874 / 402; }

/* the dark text panel that closes the #modes grid */
/* The last cell of the six.  The grid already stretches the CARD to the row,
   but its one child kept its own content height and was pinned to the bottom,
   so the black box started ~50 px below the screenshots beside it.  Letting
   the child grow is what makes its top and bottom edges the row's own, at
   every column count. */
.card--text > .panel-note { flex: 1 1 auto; }
.panel-note { background: var(--dark); color: var(--dark-ink); padding: 20px; display: flex; flex-direction: column; gap: 11px; border-radius: var(--r-img); }
.panel-note .t-label { color: var(--accent-text); letter-spacing: 0.18em; }
.panel-note p { margin: 0; font-size: 13px; line-height: 1.55; color: #c7ccd2; text-wrap: pretty; }
.panel-note hr { border: 0; height: 1px; background: var(--dark-rule); margin: 0; }

/* ---------- 4. the animated dark section (handoff §4) ---------- */

.dark { margin-top: var(--block); background: var(--dark); color: var(--dark-ink); padding: var(--block) 0; }
.dark .wrap { display: flex; flex-direction: column; gap: var(--half); }
.dark__lead { display: flex; gap: clamp(18px, 3vw, 44px); align-items: flex-end; flex-wrap: wrap; }
.dark__lead h2 { flex: 1; min-width: min(100%, 380px); font-variation-settings: "wdth" 118; font-size: clamp(22px, 2.9vw, 38px); line-height: 1.08; letter-spacing: -0.005em; text-wrap: balance; }
.dark__lead p { margin: 0; max-width: 42ch; font-size: 14.5px; line-height: 1.6; color: var(--dark-ink-2); text-wrap: pretty; }
.dark__cap { display: flex; gap: 14px; align-items: baseline; flex-wrap: wrap; border-top: 1px solid var(--dark-rule); padding-top: 11px; }
.dark__cap .fig { font-family: "Martian Mono", ui-monospace, monospace; font-weight: 600; font-size: 10px; color: var(--accent-text); }
.dark__cap .t-label { color: var(--dark-ink-2); }

/* the rail exists so the panel can stay legible on a phone: below 48em the
   board is held at 720px and dragged sideways, instead of being squeezed to
   358px where the reels are 40px across and the playhead is a hair. */
.rail { width: 100%; }
.panel { position: relative; width: 100%; border-radius: var(--r-panel); overflow: hidden; }
.panel > img { width: 100%; height: auto; }
.panel svg { position: absolute; display: block; }
.drag-hint { display: none; }

/* ---------- 5. the red block ---------- */

.red { margin-top: var(--block); background: var(--accent); color: #fff; padding: var(--block) 0; }
.red .wrap { display: flex; flex-direction: column; gap: var(--half); }
.red__lead { display: flex; gap: clamp(20px, 4vw, 56px); align-items: flex-end; flex-wrap: wrap; }
.red__lead h2 { flex: 1; min-width: min(100%, 400px); font-variation-settings: "wdth" 118; font-size: clamp(28px, 4.4vw, 62px); line-height: 0.98; letter-spacing: -0.012em; text-wrap: balance; }
.red__lead p { margin: 0; max-width: 38ch; font-size: 15px; line-height: 1.6; color: var(--accent-soft); text-wrap: pretty; }
.numbers { display: flex; flex-wrap: wrap; border-top: 1px solid #e8776b; }
.numbers > div {
  flex: 1 1 200px; border-bottom: 1px solid #e8776b;
  padding: clamp(14px, 2vw, 22px) 0;
  display: flex; align-items: baseline; gap: 14px;
}
.numbers > div + div { border-left: 1px solid #e8776b; padding-left: clamp(16px, 2.4vw, 28px); }
.numbers b { font-family: "Martian Mono", ui-monospace, monospace; font-weight: 600; font-size: clamp(40px, 6vw, 86px); line-height: 0.8; }
.numbers span { font-weight: 600; font-variation-settings: "wdth" 115; font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--accent-soft); }

/* ---------- 6. the CTA band ---------- */

/* A red band, edge to edge, with white type and a white button and no rule above
   (Richard, 2026-09-24). The section drops the .sec column so the red can run full width;
   the band keeps the column. Body copy uses the red block's own soft tint, as .red does. */
#buy { max-width: none; margin-top: var(--block); padding: var(--block) 0; background: var(--accent); color: #fff; }
#buy .buy__words b { color: #fff; }
#buy .buy__words span, #buy .buy__note { color: var(--accent-soft); }
#buy a { color: #fff; border-bottom-color: rgba(255, 255, 255, 0.55); }
#buy a:hover { color: #fff; border-bottom-color: #fff; }
#buy .cta { background: #fff; color: var(--accent); }
.buy__band {
  max-width: var(--col); margin: 0 auto; padding: 0 var(--gutter);
  display: flex; gap: clamp(20px, 4vw, 56px);
  align-items: center; flex-wrap: wrap;
}
.buy__words { flex: 1; min-width: min(100%, 360px); display: flex; flex-direction: column; gap: 10px; }
.buy__words b { font-variation-settings: "wdth" 118; font-weight: 600; font-size: clamp(18px, 2.2vw, 28px); letter-spacing: 0.01em; line-height: 1.15; text-wrap: balance; }
.buy__words span { font-size: 14px; line-height: 1.55; color: var(--ink-2); max-width: 46ch; text-wrap: pretty; }
.buy__act { display: flex; flex-direction: column; gap: 10px; }
.buy__note { font-weight: 600; font-variation-settings: "wdth" 115; font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3); text-align: right; }

/* ---------- 5b. share the song ---------- */
/* The lead and the list side by side, and stacked on a phone. The list is the specifications' own rows, in one
   column because it sits in half the measure. */
.share { padding-top: var(--half); display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(24px, 4vw, 64px); align-items: start; }
.share__lead { display: flex; flex-direction: column; gap: 14px; }
.share__lead p { margin: 0; max-width: 46ch; font-size: 14.5px; line-height: 1.6; color: var(--ink-2); text-wrap: pretty; }
.share__lead .share__line { max-width: 22ch; font-variation-settings: "wdth" 118; font-weight: 600; font-size: clamp(20px, 2.4vw, 32px); line-height: 1.1; letter-spacing: -0.005em; color: var(--ink); text-wrap: balance; }
.share .rows { grid-template-columns: 1fr; }

/* ---------- 6a. the launch alert ---------- */
/* The field is 16 px because iOS zooms the page into any smaller input it focuses. The button is the App Store
   statement's own red block, and here it is a real button, so it takes a pointer. The trap is off-screen, not
   display:none, which some form-filling bots know to skip. */
.notify { padding-top: var(--half); display: flex; gap: clamp(20px, 4vw, 56px); align-items: flex-start; flex-wrap: wrap; }
.notify__lead { flex: 1; min-width: min(100%, 320px); margin: 0; max-width: 46ch; font-size: 14.5px; line-height: 1.6; color: var(--ink-2); text-wrap: pretty; }
/* Side by side, the paragraph starts level with the email field rather than with the EMAIL label above it (Richard):
   down by the label's own line box (10 px x 1.3) and the form's 8 px gap. The two sit side by side from 60em and are
   stacked below it, decided here rather than left to wrapping, because a wrap point moves with the scrollbar and the
   offset has to be on exactly when they are side by side. */
@media (max-width: 59.99em) {
  .notify { flex-direction: column; align-items: stretch; }
  .notify__lead, .notify__form { flex: none; min-width: 0; }
}
@media (min-width: 60em) { .notify__lead { margin-top: calc(10px * 1.3 + 8px); } }
.notify__form { flex: 1; min-width: min(100%, 420px); display: flex; flex-direction: column; gap: 8px; }
.notify__label { color: var(--ink-3); }
.notify__row { display: flex; gap: 10px; }
.notify__row input {
  flex: 1; min-width: 0; height: 46px; padding: 0 14px;
  border: 1px solid var(--ink); border-radius: var(--r-img); background: #fff; color: var(--ink);
  font: inherit; font-size: 16px;
}
.notify__row input::placeholder { color: var(--ink-3); }
.notify__row .cta { cursor: pointer; }
.notify__row .cta:disabled { opacity: 0.55; cursor: default; }
.notify__status { margin: 2px 0 0; min-height: 1.5em; }
.notify__form[data-state="error"] .notify__status { color: var(--accent); }
.notify__form[data-state="done"] .notify__status { color: var(--ink); font-weight: 600; }
.notify__trap { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
@media (max-width: 47.99em) {
  .share { grid-template-columns: 1fr; }
  .notify__row { flex-direction: column; }
  /* stacked, `flex: 1` would size the field's height and collapse it; it takes the row's width instead */
  .notify__row input { flex: none; width: 100%; }
  .notify__row .cta { width: 100%; }
}

/* ---------- 7. keyframes ---------- */

@keyframes tnt-slide { from { transform: translateX(0) } to { transform: translateX(-50%) } }
@keyframes tnt-reel-a { from { transform: rotate(0) } to { transform: rotate(360deg) } }
@keyframes tnt-reel-b { from { transform: rotate(0) } to { transform: rotate(-360deg) } }
@keyframes tnt-head { from { transform: translateX(-208.5px) } to { transform: translateX(137.5px) } }
@keyframes tnt-count { from { transform: translateY(0) } to { transform: translateY(-320px) } }
@keyframes tnt-remain { from { transform: translateY(0) } to { transform: translateY(-280px) } }
@keyframes tnt-pos { from { transform: translateY(0) } to { transform: translateY(-240px) } }
@keyframes tnt-out-l {
  0%   { transform: translateY(128px) }
  15%  { transform: translateY(32px)  }
  31%  { transform: translateY(94px)  }
  47%  { transform: translateY(10px)  }
  63%  { transform: translateY(75px)  }
  81%  { transform: translateY(44px)  }
  100% { transform: translateY(128px) }
}
@keyframes tnt-out-r {
  0%   { transform: translateY(102px) }
  13%  { transform: translateY(58px)  }
  27%  { transform: translateY(16px)  }
  41%  { transform: translateY(88px)  }
  56%  { transform: translateY(35px)  }
  73%  { transform: translateY(113px) }
  89%  { transform: translateY(50px)  }
  100% { transform: translateY(102px) }
}

/* ---------- 8. responsive ---------- */

@media (max-width: 47.99em) {
  /* Less scroll to get through: 195vh instead of 240vh.  Worked the same way
     as the desktop height, it holds each of the four scenes for about 24vh,
     half the 48vh between scenes when this was 240vh with three.  The drawing
     is re-framed per breakpoint in hero.js rather than scaled down — a whole
     874 pt board at 358 px is a grey strip. */
  .stage { height: 195vh; }
  .scene__no { font-size: 26px; }
  .scene__note { font-size: 12px; }
  /* the scene counter costs a whole line at this width and says nothing the
     number on the left does not; the progress rule takes its own line. */
  .scene__count { display: none; }
  .scene__prog { flex: 1 0 100%; min-width: 100%; top: 0; margin-top: 3px; }

  /* An SVG root clips to its element box, not to its viewBox.  Without a box
     that matches the re-framed viewBox, the space left over by xMidYMid meet
     reveals the drawing outside the frame — half a row of keys, cut at a
     height nobody chose.  Matching the ratio gives the crop a clean edge. */
  .stage__device svg { height: auto; max-height: 100%; aspect-ratio: 402 / 332; }

  .pitch { gap: 14px; }
  /* the price reads first, then the button takes the full measure: at 358 px
     the two will not sit side by side without shrinking the button past the
     point where its words are comfortable. */
  .pitch__right { display: grid; gap: 11px; width: 100%; }
  .pitch .cta--lg { height: 44px; padding: 0 16px; font-size: 9.5px; letter-spacing: 0.09em; gap: 9px; }
  .pitch .cta--lg .cta__mark { width: 13px; height: 15px; }

  .rail {
    overflow-x: auto; overflow-y: hidden;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  .rail::-webkit-scrollbar { display: none; }
  /* It used to be 720 wide in a ~370 column and you dragged it sideways. The hint that
     said so sat *under* the panel, so the first thing you met was a picture cut off at
     the screen edge. Richard read it as a bug, which is the answer: an affordance you
     have to scroll past the damage to discover is not one. It fits now, and the hint
     goes with the drag it described. */
  .rail .panel { width: 100%; max-width: none; }
  .rail { overflow-x: visible; }

  /* phones get the pre-cropped detail, not the whole 874 pt board shrunk
     to 358 px — so the card's ratio changes with the source. */
  .card--shot .card__img { aspect-ratio: 370 / 137; }

  /* Two real columns rather than flex wrapping. `div + div` draws the divider, and an
     adjacent-sibling selector cannot see a row break, so the second row's first cell
     kept a left border and its indent — which is why the 2 sat right of the 4 above it.
     With the columns declared, "first in the row" is every odd child, and it can be
     said. */
  .numbers { display: grid; grid-template-columns: 1fr 1fr; }
  .numbers > div:nth-child(odd) { border-left: 0; padding-left: 0; }
  .numbers b { font-size: clamp(34px, 11vw, 52px); }
  .buy__act { width: 100%; }
  .buy__act .cta { width: 100%; }
}

/* Reduced motion: the stage stops being a stage.  It collapses to one
   screenful showing scene 01, the page keeps its own scrollbar, and every
   [data-tnt-anim] node is frozen by the rule in site.css.
   `relative`, not `static`: the still wash behind is positioned, and a static
   block paints under it whatever its z-index says, so the caption and the
   drawing were hidden behind the wash.  Relative stays in flow and keeps the
   z-index: 1 that lifts it over; top: auto drops the sticky offset, which
   relative would otherwise apply as a 64 px shift down. */
@media (prefers-reduced-motion: reduce) {
  .stage { height: auto; padding-bottom: 0; }
  .stage__sticky { position: relative; top: auto; height: auto; min-height: calc(100vh - var(--head-h)); }
}

/* ---------- section entrances (assets/js/reveal.js) ----------
   One motion for the whole page: a short rise and fade that starts quickly and lands softly, with no bounce --
   a panel settling into place, not an app bouncing. The numbered heads add one mechanical detail: their rule
   draws itself left to right while the title rises.

   Every rule here is inside `rv`, which only the head snippet in index.html sets, and only when the browser has
   IntersectionObserver and the visitor has not asked for reduced motion. So with no JavaScript, with reduced
   motion (including switching it on mid-visit, because these rules are inside the no-preference query), or in
   print, the page is exactly as it was. Only opacity and transform move: nothing reflows, and nothing moves
   sideways, so a phone can never gain a horizontal scroll from this. */
@media (prefers-reduced-motion: no-preference) {
  .rv [data-rv] {
    opacity: 0;
    transform: translate3d(0, 22px, 0);
    transition: opacity .7s cubic-bezier(.2, .7, .2, 1) var(--rv-d, 0ms),
                transform .7s cubic-bezier(.2, .7, .2, 1) var(--rv-d, 0ms);
  }
  /* the spec rows are a readout filling in: shorter travel, quicker */
  .rv [data-rv="row"] { transform: translate3d(0, 10px, 0); transition-duration: .45s; }
  .rv [data-rv].is-in { opacity: 1; transform: none; }

  /* a numbered head: the head itself stays put, its parts rise in order, and its rule draws across. The rule is
     the head's own 1px border, redrawn as a pseudo-element on the same pixel so nothing shifts. */
  .rv [data-rv="head"] { opacity: 1; transform: none; position: relative; border-bottom-color: transparent; }
  .rv [data-rv="head"] > * {
    opacity: 0; transform: translate3d(0, 14px, 0);
    transition: opacity .6s cubic-bezier(.2, .7, .2, 1), transform .6s cubic-bezier(.2, .7, .2, 1);
  }
  .rv [data-rv="head"] > :nth-child(2)    { transition-delay: 70ms; }
  .rv [data-rv="head"] > :nth-child(n+3)  { transition-delay: 180ms; }
  .rv [data-rv="head"]::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
    background: var(--ink); transform: scaleX(0); transform-origin: 0 50%;
    transition: transform .9s cubic-bezier(.6, 0, .2, 1) 120ms;
  }
  .rv [data-rv="head"].is-in > *     { opacity: 1; transform: none; }
  .rv [data-rv="head"].is-in::after  { transform: scaleX(1); }
}

@media print {
  .rv [data-rv], .rv [data-rv="head"] > * { opacity: 1 !important; transform: none !important; }
  .rv [data-rv="head"] { border-bottom-color: var(--ink) !important; }
  .rv [data-rv="head"]::after { display: none; }
}

/* ---------- the hero's entrance, on load ----------
   The page assembles itself in the order it is read, on top of the background wash: the header settles down, the
   scene's rule, number, title and note rise one after another, the instrument lifts into place, and then the pitch --
   its rule, eyebrow, headline, price, button -- follows it. The whole sequence is over in about 1.8 s.

   Two things it deliberately does not do. The wash is NOT faded in: it is the page's largest paint, and fading it
   from 0 held back Largest Contentful Paint by about 0.66 s on a throttled phone (review, 2026-09-23). And the two
   rules -- the scene's left border and the pitch's top border -- are not left out: they fade with their own parts,
   because a lone full-width line on an empty wash was what the first frame used to show.

   Pure CSS, no JavaScript, so nothing here can ever be left hidden: each element starts in its "from" state only for
   the length of its own animation (fill-mode backwards) and then has no animation style at all.

   It moves with the individual `translate` and `scale` properties rather than `transform`, so it composes with, and
   can never override, anything else positioning these elements. hero.js is untouched by it: the script only writes
   attributes inside the drawing's SVG and the scene progress bar's width, never opacity or position on these
   containers. Only vertical movement, so a phone can gain no horizontal scroll. Reduced motion: none of this. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes tnt-in-down { from { opacity: 0; translate: 0 -10px; } }
  @keyframes tnt-in-rule-l { from { border-left-color: transparent; } }
  @keyframes tnt-in-rule-t { from { border-top-color: transparent; } }
  @keyframes tnt-in-up   { from { opacity: 0; translate: 0 18px; } }
  @keyframes tnt-in-rise { from { opacity: 0; translate: 0 30px; scale: .985; } }

  .site-head      { animation: tnt-in-down .6s  cubic-bezier(.2, .7, .2, 1)          backwards; }
  .scene__body    { animation: tnt-in-rule-l .6s  ease-out                   250ms    backwards; }
  .scene__no      { animation: tnt-in-up   .7s  cubic-bezier(.2, .7, .2, 1) 200ms    backwards; }
  .scene__line    { animation: tnt-in-up   .7s  cubic-bezier(.2, .7, .2, 1) 300ms    backwards; }
  .scene__note    { animation: tnt-in-up   .7s  cubic-bezier(.2, .7, .2, 1) 400ms    backwards; }
  .stage__device  { animation: tnt-in-rise 1s   cubic-bezier(.2, .7, .2, 1) 450ms    backwards; }
  .pitch          { animation: tnt-in-rule-t .8s  ease-out                   650ms    backwards; }
  .pitch__eyebrow { animation: tnt-in-up   .7s  cubic-bezier(.2, .7, .2, 1) 750ms    backwards; }
  .pitch .t-h1    { animation: tnt-in-up   .7s  cubic-bezier(.2, .7, .2, 1) 850ms    backwards; }
  .price          { animation: tnt-in-up   .7s  cubic-bezier(.2, .7, .2, 1) 980ms    backwards; }
  .pitch .cta     { animation: tnt-in-up   .7s  cubic-bezier(.2, .7, .2, 1) 1080ms   backwards; }
}

/* ---------- specifications: a dark band, so the bottom of the page is not one long run of grey rows ---------- */
.specband { margin-top: var(--block); background: var(--dark); color: var(--dark-ink); padding: var(--block) 0; }
.specband .head { border-bottom-color: var(--dark-ink); }
.specband .head__no { color: var(--dark-ink-2); }
.bignums { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr)); margin-top: var(--half); border-top: 1px solid var(--dark-rule); }
.bignums > div { display: flex; flex-direction: column; gap: 14px; padding: 22px 16px 8px 0; }
.bignums b { font-family: "Martian Mono", ui-monospace, monospace; font-weight: 600; font-size: clamp(34px, 4.4vw, 64px); line-height: 0.85; color: #fff; }
.bignums span { font-weight: 600; font-variation-settings: "wdth" 115; font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--accent-text); line-height: 1.35; }
.specgroups { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr)); gap: 30px clamp(24px, 3vw, 48px); margin-top: calc(var(--half) * 1.5); }
.specgroup h3 { margin: 0 0 4px; color: var(--dark-ink-2); letter-spacing: 0.18em; }
.specgroup .rows { grid-template-columns: 1fr; border-top: 1px solid var(--dark-ink); }
.specgroup .rows > div { border-bottom-color: var(--dark-rule); }
.specgroup .rows dt { color: var(--dark-ink-2); }
.specgroup .rows dd { color: var(--dark-ink); }

/* ---------- share: the picture of a song going out and coming back ---------- */
.share__flow { margin: var(--half) 0 0; }
.share__flow img { display: block; width: 100%; height: auto; }

/* ---------- 02 · engines: one big screen and a list to pick from ---------- */
.engsel { display: grid; grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr); gap: clamp(20px, 3vw, 48px); padding-top: var(--half); align-items: start; }
.engsel__screen { position: relative; aspect-ratio: 1110 / 411; background: var(--screen); border-radius: 12px; overflow: hidden; box-shadow: 0 22px 50px rgba(43, 51, 60, 0.2); }
.engsel__screen img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity 0.35s ease; }
.engsel__screen img.is-on { opacity: 1; }
.engsel__cap { display: flex; gap: 18px; align-items: baseline; margin-top: 18px; border-top: 1px solid var(--ink); padding-top: 14px; }
.engsel__no { font-family: "Martian Mono", ui-monospace, monospace; font-weight: 600; font-size: clamp(28px, 3vw, 44px); line-height: 0.9; color: var(--accent); }
.engsel__cap b { display: block; font-variation-settings: "wdth" 118; font-weight: 600; font-size: clamp(20px, 2.2vw, 30px); letter-spacing: 0.04em; text-transform: uppercase; line-height: 1.05; }
.engsel__cap p { margin: 8px 0 0; max-width: 56ch; font-size: 14.5px; line-height: 1.55; color: var(--ink-2); text-wrap: pretty; }
.engsel__list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--ink); }
.engsel__list li { border-bottom: 1px solid var(--rule); }
.engsel__list button { all: unset; box-sizing: border-box; width: 100%; display: grid; grid-template-columns: 30px 1fr auto; align-items: baseline; gap: 12px; padding: 12px 12px; border-radius: 6px; cursor: pointer; transition: background-color 0.2s ease; }
.engsel__list button span { font-family: "Martian Mono", ui-monospace, monospace; font-weight: 600; font-size: 11px; color: var(--ink-3); }
.engsel__list button b { font-variation-settings: "wdth" 118; font-weight: 600; font-size: 15px; letter-spacing: 0.08em; text-transform: uppercase; }
.engsel__list button i { font-style: normal; font-size: 12px; color: var(--ink-3); text-align: right; }
.engsel__list button:hover { background: rgba(22, 24, 27, 0.06); }
.engsel__list button[aria-selected="true"] { background: var(--dark); color: var(--dark-ink); }
.engsel__list button[aria-selected="true"] span { color: var(--accent-text); }
.engsel__list button[aria-selected="true"] i { color: var(--dark-ink-2); }
.engsel__list button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.engsel__edit { margin: 18px 0 0; font-size: 13px; line-height: 1.55; color: var(--ink-2); text-wrap: pretty; }
.engsel__edit b { color: var(--ink); font-weight: 600; }
@media (max-width: 59.99em) {
  .engsel { grid-template-columns: 1fr; }
  .engsel__list { display: grid; grid-template-columns: 1fr 1fr; column-gap: 12px; }
  .engsel__list button { grid-template-columns: 26px 1fr; padding: 11px 8px; }
  .engsel__list button i { display: none; }
}
@media (prefers-reduced-motion: reduce) { .engsel__screen img { transition: none; } }

/* ---------- 03 · drums: tiles of different sizes ---------- */
.bento { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: clamp(12px, 1.6vw, 20px); padding-top: var(--half);
  grid-template-areas: "g g g g s16 s16" "g g g g s24 s24" "t t k k l l" "s7 s7 s7 s17 s17 s17"; }
.b-groove { grid-area: g; } .b-s16 { grid-area: s16; } .b-s24 { grid-area: s24; }
.b-tone { grid-area: t; } .b-knock { grid-area: k; } .b-slice { grid-area: l; }
.b-s7 { grid-area: s7; } .b-s17 { grid-area: s17; }
.bento__tile { margin: 0; border-radius: 14px; }
.bento__img { background: #fff; padding: 12px; display: flex; flex-direction: column; gap: 12px; }
.bento__img img { display: block; width: 100%; height: auto; border-radius: 8px; }
.bento__img figcaption { display: flex; flex-direction: column; gap: 5px; padding: 0 4px 4px; }
.bento__img figcaption b { font-variation-settings: "wdth" 115; font-weight: 600; font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; }
.bento__img figcaption span { font-size: 13px; line-height: 1.5; color: var(--ink-2); text-wrap: pretty; }
.b-groove figcaption b { font-size: 13px; }
.b-groove figcaption span { font-size: 14.5px; max-width: 60ch; }
.bento__stat { display: flex; flex-direction: column; justify-content: space-between; gap: 16px; padding: clamp(18px, 2vw, 26px); background: #fff; }
.bento__stat b { font-family: "Martian Mono", ui-monospace, monospace; font-weight: 600; font-size: clamp(56px, 7vw, 108px); line-height: 0.8; color: var(--accent); }
.bento__stat span { display: block; font-weight: 600; font-variation-settings: "wdth" 115; font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; }
.bento__stat p { margin: 8px 0 0; font-size: 13px; line-height: 1.5; color: var(--ink-2); max-width: 52ch; text-wrap: pretty; }
.b-s7, .b-s17 { flex-direction: row; align-items: flex-end; justify-content: flex-start; gap: clamp(18px, 2.4vw, 32px); }
.bento__stat--red { background: var(--accent); }
.bento__stat--red b, .bento__stat--red span { color: #fff; }
.bento__stat--dark { background: var(--dark); }
.bento__stat--dark b { color: #fff; }
.bento__stat--dark span { color: var(--accent-text); }
@media (max-width: 59.99em) {
  .bento { grid-template-columns: 1fr 1fr; grid-template-areas: "g g" "s16 s24" "t t" "k k" "l l" "s7 s7" "s17 s17"; }
}

/* ---------- section rhythm (Richard, 2026-09-24) ----------
   150px above and below every section's content except the hero, so the content of two
   neighbouring sections sits 300px apart. The coloured bands drop their top margin so the
   gap stays 300 and not 300 plus a margin. On phones the gap is 100px, so 50px a side
   (Richard). This block sits last so it wins over the earlier .sec, .dark, .red,
   .specband and #buy rules and their media queries. */
:root { --sec-pad: 150px; }
@media (max-width: 47.99em) { :root { --sec-pad: 50px; } }
.sec { padding-top: var(--sec-pad); padding-bottom: var(--sec-pad); }
.dark, .red, .specband, #buy { margin-top: 0; padding-top: var(--sec-pad); padding-bottom: var(--sec-pad); }
