.wordmark {
  display: inline-flex;
  align-items: center;
  gap: .48em;
  min-height: 44px;
  font-size: 1.04rem;
  font-weight: 700;
  letter-spacing: -.04em;
  text-decoration: none;
}

/* The mark takes currentColor rather than a value of its own, so it is
   white on the dark header and black under the print stylesheet without
   either being written down twice. Sized in em: it tracks the wordmark. */
.wordmark svg {
  width: 1.25em;
  height: 1.25em;
  flex: none;
  fill: currentColor;
}

.site-header nav a {
  display: inline-flex;
  min-height: 48px;
  align-items: center;
  color: var(--inverse-soft);
  font-size: .94rem;
  text-decoration: none;
  white-space: nowrap;
  transition: color 180ms ease;
}

.header-actions a {
  color: var(--inverse-soft);
  font-size: .94rem;
  text-decoration: none;
  white-space: nowrap;
}

/* The mark stays a 44px circle so it remains a peer of the pills, but it
   no longer carries a filled field: a filled blurple circle was the only
   off-palette colour on the site and outranked the text-only Buy beside
   it, so blurple now appears on hover. It stays the mark alone at every
   width: with the word it measured 100px against Download's 93 for a
   shorter label, which made the community link the widest control in a
   bar whose one action is Download. Its name comes from aria-label;
   there is no text to remove. */
.header-actions .header-discord {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  color: inherit;
  background: transparent;
  box-shadow: inset 0 0 0 1px rgb(247 247 243 / .28);
  box-shadow: inset 0 0 0 1px oklch(from var(--on-inverse, #f7f7f3) l c h / .28);
  border-radius: 999px;
  transition: background 180ms ease;
}

/* Buy is a text link between two 44px controls and it measured 24x21 — the
   smallest thing in the group carrying the same 14px of air as the biggest,
   and a tap target under the 44px floor. The padding is the target, not
   decoration; the type size is unchanged. */
.header-actions .header-buy {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--space-1);
  margin-right: var(--space-3);
}

h3 {
  margin-bottom: var(--space-2);
  font-size: clamp(1.45rem, 2.2vw, 2rem);
  font-weight: 500;
  letter-spacing: -.035em;
  line-height: 1.08;
}

.button-row { display: flex; flex-wrap: wrap; gap: .75rem; }

.button-secondary { background: transparent; color: inherit; }

.button-unavailable { background: transparent; color: var(--inverse-soft); cursor: default; }

/* Tux is full colour and lives in its own file: hidden plus lazy means
   the Windows and macOS visitors who never see him never fetch him.
   He answers to the label, not to the 1.15rem platform squares he is
   never shown beside. */
.hero-actions img[data-primary-icon="linux"] { width: auto; height: 1.45rem; }

.hero {
  position: relative;
  isolation: isolate;
  background: var(--inverse);
  color: var(--on-inverse, #f7f7f3);
}

/* One fixed field behind the page, visible only through the transparent
   hero -- it was the hero's canvas first, then a per-section treatment, and
   veiling every section over it hazed the surfaces that carry the product.
   --ap is the audio pulse from home.js. */
.hero-aurora {
  position: fixed;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  filter: brightness(var(--ap, 1)) saturate(1.05);
  transition: filter .8s ease;
}

@media print {
  .hero-aurora { display: none; }
}

.hero-grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: clamp(3rem, 5vw, 4.5rem);
  padding-block: clamp(5rem, 9vw, 8rem) 0;
}

.hero-copy {
  position: relative;
  z-index: 1;
  width: min(100%, 46rem);
  margin-inline: auto;
  text-align: center;
}

.hero-copy h1 { animation: hero-enter 720ms cubic-bezier(.22, .8, .28, 1) both; }

.hero-actions { animation: hero-enter 720ms 90ms cubic-bezier(.22, .8, .28, 1) both; }

/* Release the opacity layer after entry so the paragraph can blur the aurora. */
.hero-demo-details { animation: hero-enter 720ms 210ms cubic-bezier(.22, .8, .28, 1) backwards; }

/* Last in the hero's own order. It is below the fold's copy but inside the
   hero, so the scroll observer would have fired for it before anyone could
   see it move -- it belongs to the load choreography, not to the reveal. */
.hero-facts-band { animation: hero-enter 720ms 270ms cubic-bezier(.22, .8, .28, 1) both; }

.hero h1 {
  max-width: 17ch;
  margin: 0 auto var(--space-3);
  font-size: clamp(2.55rem, 4.2vw, 4.2rem);
  line-height: 1.02;
}

.hero .lede { max-width: 33rem; margin-bottom: var(--space-4); color: var(--inverse-soft); }

.hero-actions { align-items: center; justify-content: center; }

.hero-actions .button {
  min-height: 3.25rem;
  gap: .6rem;
  padding: .75rem 1.25rem;
  border-radius: .6rem;
}

.hero-actions .button-secondary {
  border-color: rgb(247 247 243 / .28);
  border-color: oklch(from var(--on-inverse, #f7f7f3) l c h / .28);
  background: rgb(247 247 243 / .04);
  background: oklch(from var(--on-inverse, #f7f7f3) l c h / .04);
}

.hero-actions svg {
  width: 1rem;
  height: 1rem;
  flex: none;
  fill: currentColor;
}

.hero-assurance {
  margin: var(--space-2) auto 0;
  color: var(--inverse-soft);
  font-size: .82rem;
}

.hero-demo-details {
  max-width: 32rem;
  margin-top: .25rem;
  margin-inline: auto;
  color: var(--inverse-soft);
  font-size: .82rem;
}

.hero-demo-details summary {
  padding-block: .8rem;
  margin-block: -.4rem;
  width: max-content;
  max-width: 100%;
  margin-inline: auto;
  color: var(--on-inverse, #f7f7f3);
  cursor: pointer;
}

.hero-demo-details summary:focus { outline: none; }

.hero-demo-details summary:focus-visible {
  text-decoration: underline;
  text-decoration-color: var(--inverse-soft);
  text-decoration-thickness: 1px;
  text-underline-offset: .25em;
}

.hero-demo-details ul {
  margin: .65rem 0 0;
  padding: var(--space-2) var(--space-3) var(--space-2) var(--space-4);
  text-align: left;
  color: #e1e1db;
  background: rgb(18 17 16 / .86);
  background: oklch(from var(--flow-black) l c h / .86);
  border: 1px solid rgb(247 247 243 / .08);
  border: 1px solid oklch(from var(--on-inverse, #f7f7f3) l c h / .08);
  border-radius: var(--radius);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

.hero-demo-details li::marker { content: "−  "; }

.hero-visual {
  position: relative;
  z-index: 1;
  isolation: isolate;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: visible;
  width: 100%;
  aspect-ratio: 16 / 9;
  padding: clamp(1.25rem, 4vw, 4rem);
  background: var(--inverse);
  border-radius: var(--radius) var(--radius) 0 0;
}

.hero-visual::after {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 10% 14% 4%;
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(0, 0, 0, .64), transparent 68%);
  filter: blur(1.4rem);
  opacity: .52;
  pointer-events: none;
  transform: translateY(.15rem);
  transition: opacity 120ms cubic-bezier(.22, .8, .28, 1), transform 120ms cubic-bezier(.22, .8, .28, 1);
}

.hero-product {
  position: relative;
  z-index: 2;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  margin-inline: auto;
  object-fit: contain;
  outline: 1px solid rgba(255, 255, 255, .08);
  box-shadow: 0 1.35rem 3rem rgba(0, 0, 0, .42), 0 .25rem .8rem rgba(0, 0, 0, .3);
  transform: translateY(0) scale(1);
  transform-origin: center;
  transition: transform 260ms cubic-bezier(.22, .8, .28, 1),
              box-shadow 260ms cubic-bezier(.22, .8, .28, 1),
              outline-color 260ms ease;
}

.hero-facts-band { background: var(--flow-black, #121110); }

.hero-facts {
  position: relative;
  z-index: 3;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  width: min(100%, calc(var(--content) + (2 * var(--gutter))));
  margin-inline: auto;
  margin-block: 0;
}

.hero-facts div { padding: 1.1rem var(--gutter); }

.hero-facts div:nth-child(2) { text-align: center; }

.hero-facts div:nth-child(3) { text-align: right; }

.hero-facts dt { margin-bottom: .25rem; color: var(--on-inverse, #f7f7f3); font-size: .78rem; font-weight: 700; }

.hero-facts dd { margin: 0; color: var(--inverse-soft); font-size: .85rem; }

.proof-visual {
  position: relative;
  z-index: 1;
  width: min(calc(100% - (2 * var(--gutter))), var(--content));
  aspect-ratio: 5 / 1;
  margin: 0 auto clamp(3rem, 6vw, 5rem);
  overflow: hidden;
  border-radius: var(--radius);
  background: #0a0908;
}

.proof-visual img,
.proof-visual video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 35%;
}

/* The dry take is the floor and never fades. Cross-fading BOTH layers dips
   dark at the midpoint -- two half-opacity images over the dark box
   composite darker than either one -- so only the wet take fades, over an
   already-opaque picture. These 220 ms are also the cover the player script
   hands decoding over under, which is why that timing is read there. */
.proof-visual .proof-take { position: absolute; inset: 0; }

/* Each take is two copies of the same clip. The renderer eases the motion to
   a stop at both ends and the two ends are not quite the same pose, so a
   plain loop reads as freeze-bump-freeze. Crossfading one copy into the
   other across that near-static region hides the join completely. */
.proof-visual .proof-take video {
  position: absolute;
  inset: 0;
  opacity: 0;
}

.proof-visual .proof-take video:first-child { opacity: 1; }

/* The still is the floor: it shows until the takes are playing, and stays
   put for reduced-motion and for anything that cannot decode the video. */
.proof-visual .proof-still { position: relative; }

@media (prefers-reduced-motion: reduce) {
  .proof-visual .proof-take { display: none; }
  .proof-visual .proof-still { opacity: 1; }
}

.proof { padding-block: clamp(4.5rem, 8vw, 7rem); background: var(--paper-bright); }

.proof-inner { max-width: 70rem; }

.proof-heading {
  max-width: 54rem;
  margin-bottom: clamp(2.5rem, 5vw, 4.5rem);
}

.proof-heading h2 {
  max-width: 14ch;
  margin-bottom: var(--space-3);
  font-size: clamp(2.75rem, 4.5vw, 4.15rem);
}

.proof-heading > p { max-width: 28rem; margin-bottom: 0; color: var(--ink-soft); font-size: 1.05rem; }

.comparison-player {
  display: grid;
  gap: 1.25rem;
}

.comparison-head {
  display: grid;
  justify-items: center;
  gap: .35rem;
}

.comparison-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  /* A column: the switch on its own line, the transport centered under it.
     Volume left this row on 2026-08-20 and lives on the position row, so
     nothing here fights for width any more and the old wrap guard (which
     existed because a range input will not shrink) went with it. */
  flex-direction: column;
  gap: .9rem;
  width: min(100%, 30rem);
}

/* Always visible since it moved to the position row (2026-08-20). It used
   to fade in on hover while holding its 4.5rem of space, which was the
   right call beside the switch and the wrong one here: the reserved width
   pushed the duration in off the right edge and left a gap nothing filled. */
.transport-volume {
  display: flex;
  align-items: center;
  gap: .35rem;
  flex: 0 0 auto;
  min-width: 0;
}

.volume-toggle {
  display: grid;
  place-items: center;
  /* Icon-only button: the box is the tap target, so it takes the site's
     44px floor. The glyph keeps its own size below. */
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  transition: color 120ms linear;
}

.volume-toggle svg { width: 1.25rem; height: 1.25rem; }

.volume-toggle .volume-cross { opacity: 0; }

.transport-volume[data-muted="true"] .volume-wave { opacity: 0; }

.transport-volume[data-muted="true"] .volume-cross { opacity: 1; }

.transport-volume[data-muted="true"] .volume-toggle { color: var(--ink); }

.volume-slider {
  width: 4.5rem;
  height: 44px;
  margin: 0;
  accent-color: var(--ink);
  cursor: pointer;
  opacity: 1;
}

/* The centering correction that used to live here is gone with the reason
   for it. Volume shared the control row, was invisible until hover, and
   still took 4.5rem out of the measurement, so the three visible controls
   sat half a slider left of center and a min-width:44rem rule pushed them
   back. On the position row none of that applies. */
.transport-volume { margin-left: .35rem; }

.volume-toggle { display: inline-flex; }

.volume-toggle svg { width: 1.05rem; height: 1.05rem; }

.audio-switch {
  position: relative;
  display: inline-grid;
  grid-template-columns: 1fr 1fr;
  flex: 0 0 auto;
  width: min(100%, 24rem);
  min-width: 0;
  padding: .34rem;
  background: var(--line);
  border-radius: 999px;
  /* Horizontal drags belong to the thumb; the page keeps the vertical. */
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
}

.audio-switch::before {
  content: "";
  position: absolute;
  top: .3rem;
  bottom: .3rem;
  left: .3rem;
  width: calc(50% - .3rem);
  background: var(--ink);
  border-radius: 999px;
  box-shadow: 0 .35rem 1rem rgba(0, 0, 0, .16);
  transform: translateX(0);
  transition: transform 140ms cubic-bezier(.2, .85, .3, 1);
}

/* rubber.js writes the thumb's edges as --rl/--rw on the switch; the class
   flips geometry from the transition rule above to the variables, so a
   no-JS or pre-attach render keeps the old behaviour. Specificity has to
   match the wet-state transform rule in site.css (class + attribute +
   class) and win on file order. */
.comparison-player .audio-switch.rubber-on { cursor: grab; }

.comparison-player .audio-switch.rubber-on[data-held] { cursor: grabbing; }

.comparison-player .audio-switch.rubber-on::before {
  left: var(--rl, .3rem);
  width: var(--rw, calc(50% - .3rem));
  transform: none;
  transition: none;
}

.audio-switch button {
  position: relative;
  z-index: 1;
  min-height: 3.2rem;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: #4f4e4a;
  cursor: pointer;
  font-size: 1.1rem;
  font-weight: 700;
  letter-spacing: -.015em;
  transition: color 80ms linear, transform 180ms ease;
}

.audio-switch button[aria-checked="true"] {
  background: transparent;
  color: #fff;
  box-shadow: none;
}

/* No ring on the pill, deliberately: the owner traded the keyboard-focus
   indicator away here (WCAG 2.4.7 fails on this control). The thumb still
   marks where selection sits; arrows and X still work. */
.audio-switch button:focus-visible { outline: none; }

.comparison-caption { max-width: 26rem; margin: 0; color: var(--ink-soft); font-size: .74rem; text-align: center; opacity: 1; }

/* The state word swaps at the blurred midpoint of the keyframe, the moment
   the eye cannot read it. JS adds the class and changes the text on a
   timer; reduced motion never gets the class, so the word just changes. */
.comparison-state { display: inline-block; }

.comparison-state.is-swapping { animation: state-swap var(--duration-quick, 150ms) var(--ease-in-out, ease-in-out); }

@keyframes state-swap {
  0%, 100% { opacity: 1; filter: blur(0); transform: none; }
  50% { opacity: 0; filter: blur(var(--blur-small, 2px)); transform: translateY(var(--distance-micro, 4px)); }
}

/* The hint names keys a touch device does not have; the segmented control
   is the touch affordance. */
@media (hover: none) {
  .comparison-caption { display: none; }
}

.comparison-caption kbd {
  display: inline-grid;
  min-width: 1.45rem;
  min-height: 1.45rem;
  place-items: center;
  padding-inline: .35rem;
  border: 1px solid var(--line);
  border-radius: .3rem;
  background: var(--paper-bright);
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  box-shadow: 0 .12rem 0 var(--line);
}

.transport { display: block; }

.transport-play {
  width: 2.8rem;
  min-width: 2.8rem;
  aspect-ratio: 1;
  border: 1px solid var(--ink);
  border-radius: 50%;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  font-size: .8rem;
  transition: transform 180ms ease, background-color 180ms ease, color 180ms ease, box-shadow 180ms ease;
}

.transport-play[data-playing="true"] { background: var(--ink); color: #fff; }

/* Was the character U+25B6, which iOS renders with EMOJI presentation: a
   blue rounded play button on iPhone, a plain triangle everywhere else.
   A path is the same shape on every platform and inherits currentColor,
   so it inverts with the button when playing. */
.play-glyph {
  width: 1.25rem; height: 1.25rem;
  fill: currentColor;
  display: block; margin-inline: auto;
}

.transport-play[aria-busy="true"] { animation: transport-wait 1.4s ease-in-out infinite; }

@keyframes transport-wait { 0%, 100% { opacity: .55; } 50% { opacity: .9; } }

.transport-timeline { display: grid; gap: .15rem; min-width: 0; }

.transport-timeline input {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  /* 44px stays for the touch target; the margin pulls back the part of it
     the track does not draw, which was reading as blank space. */
  height: 44px;
  margin-block: -13px;
  margin-inline: 0;
  background: transparent;
  accent-color: var(--ink);
  cursor: pointer;
}

.transport-timeline input::-webkit-slider-runnable-track {
  height: 4px; border-radius: 999px; background: var(--line);
}

.transport-timeline input::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 13px; height: 13px; margin-top: -4.5px;
  border: 0; border-radius: 50%; background: var(--ink);
  /* The knob is a control: it acknowledges the press by growing, the way
     the plugin's own knobs respond to a grab. */
  transition: transform var(--duration-quick, 150ms) var(--ease-bounce, cubic-bezier(.34, 1.36, .64, 1));
}

.transport-timeline input::-moz-range-track {
  height: 4px; border-radius: 999px; background: var(--line);
}

.transport-timeline input::-moz-range-thumb {
  width: 13px; height: 13px; border: 0; border-radius: 50%; background: var(--ink);
  transition: transform var(--duration-quick, 150ms) var(--ease-bounce, cubic-bezier(.34, 1.36, .64, 1));
}

.transport-timeline input:active::-webkit-slider-thumb { transform: scale(1.35); }

.transport-timeline input:active::-moz-range-thumb { transform: scale(1.35); }

/* Indicate keyboard focus on the knob, not the full invisible hit area. */
.transport-timeline input:focus-visible { outline: none; }

.transport-timeline input:focus-visible::-webkit-slider-thumb {
  outline: .12rem solid var(--ink); outline-offset: .15rem;
}

.transport-timeline input:focus-visible::-moz-range-thumb {
  outline: .12rem solid var(--ink); outline-offset: .15rem;
}

.transport-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--ink-soft);
  font-size: .78rem;
  font-variant-numeric: tabular-nums;
}

.transport-meta [data-current-time] { margin-right: auto; }

/* The switch already names the take; a second readout under the scrubber
   said it again. Kept in the DOM because it carries aria-live. */
.comparison-state {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -4px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.latency {
  --content: 52rem;
  padding-block: clamp(3rem, 5vw, 4rem);
}

.latency-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: .75rem;
  align-items: start;
  margin-bottom: var(--space-4);
}

.latency-head h2,
.walk-intro h2 {
  margin-bottom: 0;
  font-size: clamp(1.8rem, 2.8vw, 2.25rem);
  letter-spacing: -.035em;
  line-height: 1.15;
}

.latency-head p { max-width: 65ch; margin-bottom: 0; color: var(--ink-soft); }

.mode-families { display: grid; }

.mode-family-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-1);
  color: var(--ink-soft);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .12em;
  line-height: 1.3;
  text-transform: uppercase;
}

.mode-family-title::after {
  content: "";
  flex: 1;
  min-width: var(--space-3);
  height: 1px;
  background: var(--line);
}

.mode-family + .mode-family { margin-top: var(--space-4); }

.mode-family-new .mode-family-title { color: var(--ink); }

.mode-family-new .mode-family-title::before {
  content: "";
  width: .45rem;
  height: .45rem;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--signal);
}

.mode-list { margin: 0; }

.latency-more { margin: clamp(2rem, 3.5vw, 3rem) 0 .1rem; max-width: 34rem; color: var(--ink-soft); }

.latency-cta {
  margin-top: clamp(1.1rem, 2vw, 1.5rem);
  padding-inline: 1.6rem;
  border-radius: .6rem;
  /* The one green FIELD on the site. Green is an accent everywhere else;
     owner's call 2026-08-26, on a single button rather than a surface. */
  border-color: var(--signal);
  background: var(--signal);
  color: #173222;
  box-shadow: 0 .35rem .9rem rgb(71 112 81 / .28);
}

.cta-arrow {
  width: 1rem;
  height: 1rem;
  margin-left: .7rem;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 180ms ease;
}

.latency-cta:hover .cta-arrow,
.latency-cta:focus-visible .cta-arrow { transform: translateX(.2rem); }

.latency-cta:hover, .latency-cta:focus-visible { transform: translateY(-1px); box-shadow: 0 .6rem 1.4rem rgb(71 112 81 / .38); }

/* The same knob mark the controls page uses for its five knobs. */
.cta-knob {
  width: 1.3rem;
  height: 1.3rem;
  margin-right: .7rem;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.25;
  stroke-linecap: round;
}

.cta-knob-pointer {
  transform-box: view-box;
  transform-origin: 6px 6px;
  transition: transform 180ms ease;
}

.latency-cta:hover .cta-knob-pointer,
.latency-cta:focus-visible .cta-knob-pointer { transform: rotate(72deg); }

@media (prefers-reduced-motion: reduce) {
  .cta-knob-pointer, .cta-arrow { transition: none; }
  .latency-cta:hover .cta-knob-pointer,
  .latency-cta:focus-visible .cta-knob-pointer,
  .latency-cta:hover .cta-arrow,
  .latency-cta:focus-visible .cta-arrow { transform: none; }
}

.mode-list > div {
  display: grid;
  grid-template-columns: minmax(7rem, .6fr) minmax(8rem, 1fr) minmax(9rem, 1.3fr);
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  padding-block: 1rem;
}

.mode-list dt { font-size: clamp(1.125rem, 1.6vw, 1.25rem); }

.mode-list dd { margin: 0; }

.mode-ms { font-size: clamp(1.65rem, 2.4vw, 2rem); letter-spacing: -.03em; line-height: 1.1; }

.mode-role { color: var(--ink-soft); }

/* Bar widths are latency / 40 ms. The scale is the argument, so it gets
   drawn rather than left to the reader to infer from four numbers. */
.mode-bar {
  grid-column: 1 / -1;
  align-self: center;
  height: 3px;
  margin-top: clamp(.6rem, 1vw, .85rem);
  background: var(--line);
}

.mode-bar i { display: block; height: 100%; background: var(--ink); }

/* Bars read as meters: under the reveal contract they fill from zero as
   the list arrives, staggered down the rows so the fill sweeps the modes
   in order. The width stays the layout truth -- transform draws the fill,
   so reduced motion and no-JS see the final values instantly. */
html.js-reveal .mode-families .mode-bar i { transform: scaleX(0); transform-origin: left; }

html.js-reveal .mode-families.motion-visible .mode-bar i {
  transform: scaleX(1);
  transition: transform var(--duration-very-slow, 500ms) var(--ease-smooth-out, cubic-bezier(.22, 1, .36, 1));
  transition-delay: 320ms;
}

html.js-reveal .mode-families.motion-visible .mode-list > div:nth-child(2) .mode-bar i { transition-delay: calc(320ms + var(--duration-stagger, 40ms)); }

html.js-reveal .mode-families.motion-visible .mode-list > div:nth-child(3) .mode-bar i { transition-delay: calc(320ms + (2 * var(--duration-stagger, 40ms))); }

html.js-reveal .mode-families.motion-visible .mode-list > div:nth-child(4) .mode-bar i { transition-delay: calc(320ms + (3 * var(--duration-stagger, 40ms))); }

.walkthrough {
  --walk-radius: .65rem;
  position: relative;
  padding-block: clamp(3rem, 6vw, 5rem);
  color: var(--on-inverse);
}

/* Quiet the field behind the copy while leaving the aurora at the edges. */
.walkthrough::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, rgb(0 0 0 / .7), transparent 80%);
  pointer-events: none;
}

.walkthrough > .section-inner { position: relative; }
.walk-intro { margin-bottom: clamp(1.75rem, 3vw, 2.75rem); }
.walk-intro .eyebrow { color: var(--inverse-soft); }
.walk-intro h2 { margin: 0; font-size: clamp(2rem, 3.4vw, 3rem); }

.walk-tour {
  display: grid;
  grid-template-columns: minmax(15rem, 1fr) minmax(0, 2.3fr);
  grid-template-rows: auto 1fr;
  grid-template-areas: "topics visual" "copy visual";
  column-gap: clamp(2rem, 5vw, 4rem);
  row-gap: var(--space-4);
  align-items: start;
}

.walk-topics { grid-area: topics; }
.walk-topic {
  /* Positioned so the rows paint above the walk-glide marker, which is
     also positioned but earlier in DOM order. */
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 3.5rem;
  padding: .75rem 0;
  border: 0;
  border-bottom: 1px solid rgb(247 247 243 / .12);
  background: transparent;
  color: var(--inverse-soft);
  text-align: left;
  cursor: pointer;
  transition: color 160ms ease;
}
.walk-topic:hover, .walk-topic[aria-pressed="true"] { color: var(--on-inverse); }
.walk-topic[aria-pressed="true"] { border-bottom-color: var(--signal); }
.walk-number { font-size: .75rem; font-variant-numeric: tabular-nums; color: var(--inverse-soft); }
.walk-topic[aria-pressed="true"] .walk-number { color: var(--signal); }
.walk-topic-arrow { margin-left: auto; opacity: 0; }
.walk-topic[aria-pressed="true"] .walk-topic-arrow { opacity: 1; color: var(--signal); }

.walk-copy { grid-area: copy; }
.walk-step h3 { margin-bottom: var(--space-2); font-size: 1.2rem; }
.walk-step p { margin: 0; max-width: 32ch; color: var(--inverse-soft); line-height: 1.6; }
.walk-step + .walk-step { margin-top: var(--space-3); }
/* Reserve the tallest explanation without measuring or fixing its height. */
.walk-tour[data-ready] .walk-copy { display: grid; }
.walk-tour[data-ready] .walk-step { grid-area: 1 / 1; margin: 0; }
.walk-tour[data-ready] .walk-step[hidden] { display: block !important; visibility: hidden; }

.walk-visual { grid-area: visual; min-width: 0; }
.walk-frame {
  position: relative;
  overflow: hidden;
  aspect-ratio: 1350 / 1110;
  background: #080807;
  border: 1px solid rgb(247 247 243 / .12);
  border-radius: var(--walk-radius);
  box-shadow: 0 1.5rem 4rem rgb(0 0 0 / .35);
}
.walk-base { width: 100%; height: 100%; transition: filter 220ms ease; }
.walk-tour[data-ready] .walk-base { filter: brightness(.78); }
.walk-focus, .walk-box { display: none; position: absolute; pointer-events: none; }
/* The focus crop and its ring travel between regions like a lens finding
   its subject: the overshoot easing is the arrival, an entrance motion,
   which is the one place the token scale allows a spring. */
.walk-tour[data-ready] .walk-focus { display: block; inset: 0; transition: clip-path 260ms var(--ease-bounce, cubic-bezier(.34, 1.36, .64, 1)); }
.walk-focus img { width: 100%; height: 100%; }
.walk-tour[data-ready] .walk-box { display: block; border: 1px solid var(--signal); border-radius: .35rem; transition: inset 260ms var(--ease-bounce, cubic-bezier(.34, 1.36, .64, 1)); }

/* One hover marker for the whole topic list: it glides to the row under
   the pointer or the focus ring and stays where it was left, so re-entry
   slides from there instead of blinking in. JS writes --gx/--gy/--gw/--gh
   and flips data-glide; nothing here exists without the script. */
.walk-topics { position: relative; }

.walk-glide {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--gw, 0);
  height: var(--gh, 0);
  transform: translate(var(--gx, 0), var(--gy, 0));
  border-radius: .45rem;
  background: rgb(247 247 243 / .07);
  opacity: 0;
  pointer-events: none;
  transition: transform var(--duration-fast, 250ms) var(--ease-smooth-out, cubic-bezier(.22, 1, .36, 1)),
              width var(--duration-fast, 250ms) var(--ease-smooth-out, cubic-bezier(.22, 1, .36, 1)),
              height var(--duration-fast, 250ms) var(--ease-smooth-out, cubic-bezier(.22, 1, .36, 1)),
              opacity var(--duration-quick, 150ms) var(--ease-out, ease-out);
}

.walk-topics[data-glide] .walk-glide { opacity: 1; }

.walk-progress { display: inline-block; }

.walk-progress.is-popping { animation: walk-pop var(--duration-fast, 250ms) var(--ease-smooth-out, cubic-bezier(.22, 1, .36, 1)); }

@keyframes walk-pop {
  0% { opacity: .3; transform: scale(.86); filter: blur(var(--blur-small, 2px)); }
  100% { opacity: 1; transform: none; filter: blur(0); }
}
.walk-visual.is-full .walk-base { filter: none; }
.walk-visual.is-full .walk-focus, .walk-visual.is-full .walk-box { display: none; }
.walk-detail { display: none; width: 100%; height: auto; aspect-ratio: 16 / 9; background: #080807; border: 1px solid rgb(247 247 243 / .12); border-radius: var(--walk-radius); }
.walk-tour[data-ready] .walk-frame { display: none; }
.walk-tour[data-ready] .walk-detail { display: block; }
.walk-tour[data-ready] .is-full .walk-frame {
  display: block;
  aspect-ratio: auto;
  overflow: visible;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.walk-visual.is-full .walk-base { height: auto; }
.walk-tour[data-ready] .is-full .walk-detail { display: none; }
.walk-controls { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); padding-top: var(--space-2); }
.walk-full, .walk-navigation button { min-height: 2.75rem; border: 0; background: transparent; color: var(--inverse-soft); cursor: pointer; }
.walk-full { display: inline-flex; align-items: center; gap: .65rem; padding: 0; font-size: .85rem; text-align: left; }
.walk-full:hover, .walk-navigation button:hover { color: var(--on-inverse); }
.walk-navigation { display: flex; align-items: center; gap: .5rem; }
.walk-navigation button { min-width: 2.75rem; font-size: 1.25rem; }
.walk-navigation button:disabled { opacity: .25; cursor: default; }
.walk-progress { color: var(--inverse-soft); font-size: .75rem; font-variant-numeric: tabular-nums; white-space: nowrap; }

@media (max-width: 58rem) {
  .walk-tour { grid-template-columns: minmax(0, 1fr); grid-template-areas: "topics" "visual" "copy"; gap: var(--space-3); }
  .walk-topics { display: flex; flex-wrap: wrap; column-gap: var(--space-3); }
  .walk-topic { width: auto; max-width: 100%; min-height: 2.75rem; gap: .5rem; }
  .walk-topic-arrow { display: none; }
  .walk-step p { max-width: 48ch; }
}

@media (prefers-reduced-motion: reduce) {
  .walk-topic, .walk-base, .walk-focus, .walk-box { transition: none; }
}

@media (forced-colors: active) {
  .walkthrough::before { display: none; }
  .walk-topic[aria-pressed="true"], .walk-box { border-color: Highlight; }
  .walk-tour[data-ready] .walk-base { filter: none; }
  /* The thumb is a background paint, which forced colours drops; the
     selection has to survive as the system selection pair, the same way
     the walkthrough's pressed ring uses Highlight. */
  .audio-switch::before { background: Highlight; }
  .audio-switch button[aria-checked="true"] { color: HighlightText; }
}

@media print {
  .walkthrough::before, .walk-topics, .walk-controls, .walk-detail, .walk-focus, .walk-box { display: none !important; }
  .walk-tour, .walk-tour[data-ready] .walk-copy { display: block; }
  .walk-tour[data-ready] .walk-step[hidden] { visibility: visible; }
  .walk-tour .walk-frame { display: block !important; }
  .walk-tour[data-ready] .walk-base { filter: none; }
}

/* Warm paper, not a green wash. Green tinting a whole full-height section
   is what made the accent read as the brand color; it is an accent again,
   carried here by the wire diagram and the lane tags only. */
.ear {
  --ear-bg: var(--flow-paper);
  padding-block: var(--section);
  background: var(--ear-bg);
  color: var(--ink);
}

/* The one boundary on the page that cannot take the standard bottom
   padding: this section ends on a folded one-line disclosure, so
   var(--section) under it left a screen of paper after a single sentence.
   It follows the panel -- tighter shut, more air once the table is out --
   and moves with the same easing, so the gap and the panel read as one
   gesture rather than the page jumping under the click. */
.ear { transition: padding-bottom 320ms cubic-bezier(.32, .9, .3, 1); }

.ear:has(.bench:not([open])) { padding-bottom: clamp(2.5rem, 5vw, 4rem); }

/* Keyed on the closing class, not on [open] alone. The attribute cannot
   come off until the panel has finished collapsing, so keying the gap to
   it left the section holding its open height and then dropping 24px in
   one frame after the panel was already gone -- the snap at the end. The
   class lands on the click, so both move together. */
.ear:has(.bench[open]) { padding-bottom: clamp(3.5rem, 7vw, 5.5rem); }

.ear.is-closing:has(.bench[open]) { padding-bottom: clamp(2.5rem, 5vw, 4rem); }

.ear .eyebrow { display: inline-flex; align-items: center; gap: .5rem; }

.ear-icon {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.04;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ear-intro {
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) minmax(16rem, 1fr);
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: center;
}

.ear-photo {
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: var(--radius);
  background: #0b0a09;
}

.ear-photo img { width: 100%; height: 100%; object-fit: cover; }

.ear-diagram {
  max-width: 70rem;
  margin: clamp(3.5rem, 7vw, 6rem) auto 0;
}

.ear-copy { padding-top: var(--space-2); }

.ear-copy h2 { max-width: 13ch; }

.ear-copy p { max-width: var(--reading); color: var(--ink-soft); }

.ear-copy .offer-line {
  padding-left: var(--space-2);
  border-left: .16rem solid var(--ink);
  color: var(--ink);
  font-weight: 700;
}

/* Session map: three track lanes, one key wire in the right gutter.
   Every connector piece is positioned as a percentage of its OWN gutter
   cell, so row height and chip width are free to change. */
.mix-wrap { min-width: 0; }

.mix-map {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 4rem;
}

.lane {
  display: grid;
  grid-template-columns: minmax(5.5rem, auto) 1fr auto;
  align-items: center;
  gap: .75rem;
  margin-block: .3rem;
  padding: .8rem 1rem;
  border: 1px solid rgba(71, 112, 81,.18);
  border-radius: var(--radius);
  background: rgba(255,255,255,.4);
}

.lane-name {
  font-size: .75rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink);
}

.lane-tag {
  display: flex; align-items: center; gap: .75rem;
  font-size: .75rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-soft);
}

.lane-tag::before {
  content: ""; flex: 1;
  border-top: 1px dotted rgba(71, 112, 81,.32);
}

.lane-tag-rec { color: var(--signal-ink); }

.chip {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .45rem .85rem; border: 1px solid var(--line-dark);
  border-radius: 999px; background: var(--paper-bright);
  font-size: .8rem; font-weight: 700; line-height: 1.2;
  color: var(--ink); white-space: nowrap;
}

.chip .ear-icon { width: 1rem; height: 1rem; }

.chip-ghost { border-style: dashed; border-color: rgba(90, 89, 85, .5); background: transparent; color: var(--ink-soft); }

.wire { position: relative; }

.wire::before {
  content: ""; position: absolute; left: 50%; width: 2px; margin-left: -1px;
  background: repeating-linear-gradient(to bottom, var(--signal-ink) 0 6px, transparent 6px 11px);
}

.wire-in::before  { top: 50%; bottom: 0; }

.wire-run::before { top: 0; bottom: 0; }

.wire-out::before { top: 0; bottom: 50%; }

.wire-in::after,
.wire-out::after {
  content: ""; position: absolute; top: 50%; left: 0; width: 50%; height: 2px; margin-top: -1px;
  background: repeating-linear-gradient(90deg, var(--signal-ink) 0 6px, transparent 6px 11px);
}

.tip { position: absolute; left: 0; top: 50%; }

.tip-arrow {
  margin: -.3rem 0 0 -.34rem;
  border: .3rem solid transparent;
  border-right: .34rem solid var(--signal-ink);
}

.tip-dot {
  width: .4rem; height: .4rem; margin: -.2rem 0 0 -.2rem;
  border-radius: 50%; background: var(--signal-ink);
}

.wire-label {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  /* A haze rather than a solid chip. The dashes ran through the glyphs at
     the old .2rem padding, and a hard rectangle would have read as a gap
     cut in the wire; fading the field out lets the line pass behind the
     word instead of stopping at it. */
  padding: .75rem .7rem;
  /* Fallback because --ear-bg is defined on .ear, not here: with an
     undefined custom property the whole gradient is invalid and the
     background silently drops to none, which is a hard failure to see. */
  background: radial-gradient(ellipse 82% 62% at 50% 50%,
                              var(--ear-bg, var(--flow-paper)) 0 62%,
                              transparent 100%);
  color: var(--signal-ink);
  font-size: .75rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
}

.mix-note { margin: 1.1rem 0 0; font-size: .78rem; color: var(--ink-soft); }

/* Folded shut by default. The numbers are the honest backing for the claim
   above, not the claim itself, and a reader who wants them will open it.
   Styled for the light Ear field, unlike the two dark-field disclosures. */
/* Left edge shared with the diagram it annotates, rather than centred
   under it: 46rem of reading width centred inside a 63rem column started a
   third left edge in one section, which is what made the open state read
   as floating. Spacing below is deliberately uneven -- title and lede as
   one thing, a wider run to the evidence, a shorter one back to the
   reading of it, and the sourcing fenced off behind a rule. */
.bench {
  width: min(100%, 46rem);
  margin: clamp(2.5rem, 5vw, 4rem) 0 0;
}

.bench summary {
  padding-block: .8rem;
  margin-block: -.35rem;
  color: var(--ink);
  font-weight: 700;
  cursor: pointer;
}

/* The gap under the summary lives inside the collapsing track, not on the
   summary: as a margin it belonged to an element that is never collapsing,
   so it appeared and vanished in one frame at each end of the animation. */
/* 0fr to 1fr rather than a height, because the panel's height depends on
   how the table wraps and nothing here should have to know that number.
   The inner element is the one that clips; the outer one is the track. */
.bench-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 320ms cubic-bezier(.32, .9, .3, 1);
}

.bench[open] .bench-body { grid-template-rows: 1fr; }

/* Padding here, not on this element: padding is part of the box and a box
   cannot shrink below it, so .55rem of padding-top left a 9px sliver
   standing after the track reached zero, which then vanished in one frame
   with the attribute. A child margin is content, and content is what the
   track clips. */
.bench-inner { min-height: 0; overflow: hidden; }

.bench-inner > :first-child { margin-top: .55rem; }

.bench-lede { max-width: 34rem; margin: 0; color: var(--ink-soft); }

/* Belt and braces only: below 35rem the table stops being a table (see the
   stacked rules further down), so this never actually scrolls. It stays as
   the guard against a future column making the row wider than the page. */
.bench-scroll { overflow-x: auto; }

.bench-table {
  min-width: 27rem;
  width: 100%;
  margin: 1.9rem 0 1.1rem;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.bench-table th, .bench-table td { padding: .6rem .5rem; text-align: right; }

.bench-table th:first-child,
.bench-table tbody th { text-align: left; font-weight: 400; }

.bench-table thead th {
  border-bottom: 1px solid var(--line);
  font-size: .75rem; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-soft);
}

.bench-table tbody tr + tr th,
.bench-table tbody tr + tr td { border-top: 1px solid var(--line); }

/* The superseded column reads as a footnote, not as a second result. */
.bench-table td:last-child { color: var(--ink-soft); }

.bench-note { margin: 0; max-width: 40rem; font-size: .85rem; }

.bench-credit {
  margin: 1.4rem 0 0;
  padding-top: .85rem;
  border-top: 1px solid var(--line);
  max-width: 40rem;
  font-size: .72rem;
  color: var(--ink-soft);
}

/* Narrow: the table stacks into one block per corpus rather than scrolling
   sideways or dropping columns. Every value keeps its own label, so a phone
   reader gets all six numbers, which is the whole point of the group. */
@media (max-width: 35rem) {
  .bench-table { min-width: 0; font-size: .9rem; }
  /* Kept for assistive tech, out of the visual flow. */
  .bench-table thead {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .bench-table tr { display: block; padding: .85rem 0; }
  .bench-table tbody tr + tr th,
  .bench-table tbody tr + tr td { border-top: 0; }
  .bench-table tbody tr + tr { border-top: 1px solid var(--line); }
  .bench-table tbody th {
    display: block; padding: 0 0 .45rem; font-weight: 700;
  }
  .bench-table td {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 1rem; padding: .2rem 0;
  }
  .bench-table td::before {
    content: attr(data-label);
    color: var(--ink-soft);
    font-size: .75rem; font-weight: 700;
    letter-spacing: .09em; text-transform: uppercase;
  }
}

.fit { padding-block: clamp(3.5rem, 5vw, 4.5rem); }

.fit-grid {
  display: grid;
  grid-template-columns: minmax(18rem, .7fr) minmax(0, 1.3fr);
  gap: clamp(2rem, 4vw, 3rem);
  align-items: start;
}

.fit-copy p { max-width: 28rem; color: var(--ink-soft); }

.fit-list { margin: 0; }

.fit-list div {
  display: grid;
  grid-template-columns: minmax(6rem, .45fr) minmax(0, 1.55fr);
  gap: var(--space-2) var(--space-3);
  padding-block: 1.5rem;
}

.fit-list div + div { border-top: 1px solid var(--line); }

.fit-list dt { font-weight: 700; }

.fit-list dd { margin: 0; color: var(--ink-soft); }

.maker {
  display: grid;
  grid-template-columns: max-content minmax(0, 1.6fr);
  gap: var(--space-4);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
}

/* start, not the default stretch: the label box should measure one line,
   not the height of the paragraph beside it. */
.maker strong { align-self: start; font-size: .78rem; text-transform: uppercase; letter-spacing: .08em; }

.maker p { max-width: 44rem; margin-bottom: 0; }

.interlude { padding-block: var(--section); text-align: center; }

.interlude-title {
  max-width: 20ch;
  margin: 0 auto var(--space-2);
  font-size: clamp(1.6rem, 2.6vw, 2.4rem);
}

.interlude-lede { max-width: 30ch; margin: 0 auto; text-align: center; }

.interlude-visual {
  width: min(80vw, 76rem);
  aspect-ratio: 3 / 2;
  margin: clamp(3rem, 6vw, 5rem) auto 0;
  overflow: hidden;
  border-radius: var(--radius);
  background: #0a0908;
}

.interlude-visual img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 32% 28%;
}

/* One purchase moment: price, one button, then the terms as fine print.
   The page already asks three times above this and need not ask twice
   more on the way out. */
/* The hero's own ground, so the page reads as two surfaces, dark and
   paper, rather than three. It used to be its own green-black. */

/* :Community — the Discord ask, grouped by spacing alone. No panel and no
   rule: the band reads as part of the page the way the interlude does, and
   the blurple pill is the only colour on it, so it is the only thing to
   look at. It sits before the offer because it is the cheaper ask of the
   two. The mark is the header's, at the header's blurple. */
.discord-band { padding-block: clamp(2.5rem, 5vw, 3.5rem); text-align: center; }

.discord-shell { width: min(100%, 40rem); margin-inline: auto; padding-inline: var(--gutter); }

.discord-title { margin: 0 0 var(--space-2); font-size: clamp(1.6rem, 2.6vw, 2.4rem); letter-spacing: -.02em; }

.discord-lede { max-width: 30rem; margin: 0 auto var(--space-3); color: var(--ink-soft); }

.discord-btn {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  min-height: 52px;
  padding: .75rem 1.5rem;
  background: #5865f2;
  color: #fff;
  border-radius: 999px;
  font-weight: 700;
  text-decoration: none;
  transition: background 180ms ease, transform 180ms ease, box-shadow 180ms ease;
}

.discord-btn svg { width: 1.25rem; height: 1.25rem; flex: none; fill: currentColor; }

.offer {
  position: relative;
  isolation: isolate;
  padding-block: clamp(3rem, 5vw, 4.5rem);
  background: var(--flow-black);
  color: var(--on-inverse, #f7f7f3);
}

.offer .offer-shell { position: relative; z-index: 1; }

.offer-shell { width: min(100%, 48rem); margin-inline: auto; text-align: center; }

/* Hidden below 66rem: the phone already met the editor in the hero and a
   second screen of it here would push the button down the page. */
.offer-visual { display: none; margin: 0; }

/* Wide screens: copy left, editor right, the hero's composition again at
   the ask. The figure is placed by explicit grid rows because it must
   span every copy row and implicit rows cannot be addressed with -1; the
   count (7) is the number of copy children before the figure. Add a
   child, bump the count. */
@media (min-width: 66rem) {
  .offer-shell {
    width: min(100%, calc(var(--content) + (2 * var(--gutter))));
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 34rem);
    grid-template-rows: repeat(7, auto);
    column-gap: var(--space-5);
    align-items: start;
    text-align: left;
  }
  .offer-shell > :not(.offer-visual) { grid-column: 1; }
  .offer .lede { margin-inline: 0; }
  .offer .term-list { grid-template-columns: minmax(0, 1fr); }
  .offer-buy { justify-self: start; width: auto; }
  .offer-license-note { margin-inline: 0; text-align: left; }
  .offer .license-details { margin-inline: 0; text-align: left; }
  .offer .license-details summary { margin-inline: 0; }
  .offer .license-details p { margin-inline: 0; }
  .offer-visual {
    display: block;
    grid-column: 2;
    grid-row: 1 / -1;
    align-self: center;
  }
  .offer-visual img {
    display: block; width: 100%; height: auto;
    border-radius: calc(var(--radius) / 2);
    outline: 1px solid rgba(255, 255, 255, .08);
    box-shadow: 0 2.5rem 5.5rem rgba(0,0,0,.5), 0 .25rem 1rem rgba(0,0,0,.32);
    transform: translateY(0) scale(1);
    transform-origin: center;
    transition: transform 260ms cubic-bezier(.22, .8, .28, 1),
                box-shadow 260ms cubic-bezier(.22, .8, .28, 1),
                outline-color 260ms ease;
  }
}

/* Same lift as the hero editor (:hero-product-hovered), so the two renders
   of the plug-in answer the pointer the same way. */
@media (hover: hover) and (min-width: 66rem) {
  .offer-visual:hover img {
    transform: translateY(-.35rem) scale(1.015);
    outline-color: rgba(255, 255, 255, .2);
    box-shadow: 0 2.9rem 6rem rgba(0, 0, 0, .58), 0 .45rem 1.15rem rgba(0, 0, 0, .38);
  }
}

.offer h2 { margin-bottom: var(--space-3); font-size: clamp(3.4rem, 7vw, 5.5rem); }

/* Brighter than the list text, not a different hue: the tick still reads
   as affirmative and still separates from the body, without color doing
   the work. */
.term-list li::before { content: "✓" / ""; color: var(--on-inverse, #f7f7f3); }

.license-details {
  width: min(100%, 32rem);
  margin: var(--space-2) auto 0;
  color: var(--inverse-soft);
  font-size: .88rem;
  text-align: center;
}

footer a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: #a7a7a1;
  text-decoration: none;
}

/* The mark draws itself tail to head, along the direction its own taper
   grows. The clip is a rotated band swept across the glyph; the end state
   is deliberately larger than the box so the wipe finishes clear of the
   head instead of easing to a stop on top of it. */
@keyframes mark-draw {
  from { clip-path: polygon(0 100%, 0 100%, 0 100%, 0 100%); }
  to   { clip-path: polygon(-40% 140%, 140% -40%, 200% 20%, 20% 200%); }
}

.wordmark svg { animation: mark-draw 900ms 200ms cubic-bezier(.3, .75, .35, 1) both; }

@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

/* The buy block arrives in order rather than all at once. The container's
   own reveal is handed to its children so nothing animates twice; without
   js-reveal the container reveal still applies exactly as before, which is
   also the no-JS and reduced-motion path.

   Total is 820ms. An earlier cut ran 1420ms and the section sat visibly
   empty for the first 800 of them. */
html.js-reveal .offer-shell.motion-reveal { opacity: 1; transform: none; }

html.js-reveal .mix-map.motion-reveal { opacity: 1; transform: none; }

html.js-reveal .offer-shell.motion-reveal > :not(.term-list):not(.license-details),
html.js-reveal .offer-shell.motion-reveal .term-list li { opacity: 0; }

html.js-reveal .offer-shell.motion-visible > .eyebrow   { animation: rise 380ms   0ms cubic-bezier(.22,.8,.28,1) both; }

html.js-reveal .offer-shell.motion-visible > h2         { animation: rise 460ms  60ms cubic-bezier(.22,.8,.28,1) both; }

html.js-reveal .offer-shell.motion-visible > .lede      { animation: rise 380ms 150ms cubic-bezier(.22,.8,.28,1) both; }

/* The button fills backwards, not both: a forwards fill would keep the
   keyframe's transform: none over the hover lift for good. Opacity is set
   here instead, at a specificity above the motion-reveal opacity: 0 rule. */
html.js-reveal .offer-shell.motion-reveal.motion-visible > .offer-buy { opacity: 1; animation: rise 380ms 220ms cubic-bezier(.22,.8,.28,1) backwards; }

html.js-reveal .offer-shell.motion-visible > .offer-license-note { animation: rise 320ms 580ms cubic-bezier(.22,.8,.28,1) both; }

html.js-reveal .offer-shell.motion-visible > .offer-visual { animation: rise 460ms 120ms cubic-bezier(.22,.8,.28,1) both; }

html.js-reveal .offer-shell.motion-visible .term-list li {
  animation: rise 320ms cubic-bezier(.22,.8,.28,1) both;
  animation-delay: calc(300ms + (var(--i) * 40ms));
}

html.js-reveal .offer-shell.motion-visible .term-list li:nth-child(1) { --i: 0; }

html.js-reveal .offer-shell.motion-visible .term-list li:nth-child(2) { --i: 1; }

html.js-reveal .offer-shell.motion-visible .term-list li:nth-child(3) { --i: 2; }

html.js-reveal .offer-shell.motion-visible .term-list li:nth-child(4) { --i: 3; }

html.js-reveal .offer-shell.motion-visible .term-list li:nth-child(5) { --i: 4; }

html.js-reveal .offer-shell.motion-visible .term-list li:nth-child(6) { --i: 5; }

/* Signal moving down the routing diagram. The wires are already dashes cut
   from a repeating gradient on an 11px period, so marching the gradient one
   period is a seamless loop and needs no extra element.

   It starts only once the diagram has been scrolled to, and then runs on:
   the reveal observer unobserves after the first intersection, so there is
   no hook to stop it on the way out. It is a 2px line, animating one
   compositable property. */
@keyframes wire-flow-up    { to { background-position-y: -11px; } }

@keyframes wire-flow-right { to { background-position-x:  11px; } }

@keyframes wire-flow-left  { to { background-position-x: -11px; } }

/* Direction is per segment, and it is not decoration: the path runs dot ->
   right -> up -> left -> arrow, so .wire-out (the dot, the source) marches
   right, the verticals march up, and .wire-in (the arrow, the destination)
   marches left. Marching them all one way read as the key flowing OUT of
   the plugin, which is the opposite of what Ear does. */
html.js-reveal .mix-map.motion-visible .wire::before    { animation: wire-flow-up    900ms linear infinite; }

html.js-reveal .mix-map.motion-visible .wire-out::after { animation: wire-flow-right 900ms linear infinite; }

html.js-reveal .mix-map.motion-visible .wire-in::after  { animation: wire-flow-left  900ms linear infinite; }

/* What Ear is doing while the wire carries the key: audio arriving,
   confidence climbing, the readout resolving from a question mark to a
   key, then falling back and going again. One 6s loop, and every part of
   it is on that one clock so the confidence bar and the readout cannot
   disagree about when Ear became sure. */
.ear-readout {
  /* Both columns. On `grid-column: 1` it consumed the first cell and every
     lane auto-placed into the 4rem wire column instead. */
  grid-column: 1 / -1;
  display: flex; align-items: center; justify-content: center; gap: .7rem;
  /* No frame. Boxed, it spanned the full width to hold four short things,
     so it read as a container with nothing in it rather than as a readout
     above the lanes. The lanes below are the boxes here. */
  margin-bottom: 1.1rem;
  font-size: .75rem; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-soft);
}

.er-label { font-weight: 700; white-space: nowrap; }

.er-meter { display: inline-flex; align-items: flex-end; gap: 2px; height: .85rem; }

.er-meter i { width: 3px; height: 22%; background: var(--signal-ink); opacity: .55; border-radius: 1px; }

/* Keep the centered bar at its nominal width, but let it shrink when
   the translated labels and key need room on a narrow screen. */
.er-conf {
  position: relative; width: 7rem; min-width: 1rem; flex: 0 1 7rem; height: 3px;
  border-radius: 2px; background: rgba(71, 112, 81, .18); overflow: hidden;
}

.er-conf i { position: absolute; left: 0; top: 0; bottom: 0; width: 0; background: var(--signal-ink); }

/* Reserves the width of the longer of the two readouts, so the row does
   not resize every time the question mark resolves. */
.er-key { position: relative; min-width: 4.4rem; height: 1em; color: var(--ink); }

.er-key b { position: absolute; left: 0; top: 0; font-weight: 700; }

.er-val { opacity: 0; }

@keyframes er-bar  { 0%, 100% { height: 22%; } 50% { height: 100%; } }

@keyframes er-fill { 0% { width: 0; } 40% { width: 100%; } 80% { width: 100%; } 100% { width: 0; } }

@keyframes er-q    { 0%, 36% { opacity: 1; } 44%, 80% { opacity: 0; } 88%, 100% { opacity: 1; } }

@keyframes er-val  { 0%, 36% { opacity: 0; } 44%, 80% { opacity: 1; } 88%, 100% { opacity: 0; } }

html.js-reveal .mix-map.motion-visible .er-conf i { animation: er-fill 6s cubic-bezier(.3,.7,.4,1) infinite; }

html.js-reveal .mix-map.motion-visible .er-q      { animation: er-q    6s linear infinite; }

html.js-reveal .mix-map.motion-visible .er-val    { animation: er-val  6s linear infinite; }

/* Uneven durations on purpose: five bars on one period read as a bar chart
   stepping, not as a level meter. */
html.js-reveal .mix-map.motion-visible .er-meter i { animation: er-bar 420ms ease-in-out infinite; }

html.js-reveal .mix-map.motion-visible .er-meter i:nth-child(2) { animation-duration: 300ms; }

html.js-reveal .mix-map.motion-visible .er-meter i:nth-child(3) { animation-duration: 530ms; }

html.js-reveal .mix-map.motion-visible .er-meter i:nth-child(4) { animation-duration: 360ms; }

html.js-reveal .mix-map.motion-visible .er-meter i:nth-child(5) { animation-duration: 470ms; }

/* While the audio proof plays, the meter stops its loop and listens: home.js
   writes the analyser level as --erl each frame and marks .ear-live. Each
   bar gets its own gain so they rise together but never in step -- the same
   reason the loop's durations are uneven. Level 0 parks them at the 22%
   floor the loop starts from, so the handoff has no seam. The doubled
   selector keeps this rule ahead of the loop's js-reveal specificity. */
.er-meter.ear-live i,
html.js-reveal .mix-map.motion-visible .er-meter.ear-live i {
  animation: none;
  height: 100%;
  transform-origin: bottom;
  transform: scaleY(calc(.22 + (var(--erl, 0) * var(--er-gain, .78))));
  transition: transform 90ms linear;
}

.er-meter.ear-live i:nth-child(1) { --er-gain: .62; }
.er-meter.ear-live i:nth-child(2) { --er-gain: .88; }
.er-meter.ear-live i:nth-child(3) { --er-gain: .71; }
.er-meter.ear-live i:nth-child(4) { --er-gain: .95; }
.er-meter.ear-live i:nth-child(5) { --er-gain: .78; }

@media (hover: hover) {
  .button:hover { transform: translateY(-.12rem); box-shadow: 0 .7rem 1.8rem rgba(0, 0, 0, .18); }
  .header-discord:hover { background: #5865f2; color: #fff; }
  .discord-btn:hover { background: #4752c4; transform: translateY(-.12rem); box-shadow: 0 .7rem 1.8rem rgba(0, 0, 0, .18); }
  /* Paper, not the signal colour. Green is what the Ear diagram uses to
     mean a key travelling between plug-ins; spending it on a hover state
     and on three stat labels made it decoration up here and semantics
     down there, and the mint read as an alert either way. */
  .site-header nav a:hover { color: var(--on-inverse, #f7f7f3); }
  footer a:hover { color: var(--on-inverse, #f7f7f3); text-decoration: underline; text-underline-offset: .18em; }
  .language-control:hover { color: var(--on-inverse, #f7f7f3); background: rgba(247, 247, 243, .08); background: oklch(from var(--on-inverse, #f7f7f3) l c h / .08); }
  .button-secondary:hover { border-color: var(--on-inverse, #f7f7f3); background: rgba(247, 247, 243, .1); background: oklch(from var(--on-inverse, #f7f7f3) l c h / .1); }
  .button:active { transform: scale(.98); }
  .transport-play:hover { transform: scale(1.07); box-shadow: 0 .65rem 1.5rem rgba(0, 0, 0, .18); }
  .transport-play:active { transform: scale(.96); }
  .audio-switch button:hover:not([aria-checked="true"]) { color: var(--ink); transform: scale(.985); }
  .hero-visual.hero-product-hovered::after { opacity: .72; transform: translateY(.45rem) scale(.94); }
  .hero-visual.hero-product-hovered .hero-product {
    transform: translateY(-.35rem) scale(1.015);
    outline-color: rgba(255, 255, 255, .2);
    box-shadow: 0 2.2rem 5rem rgba(0, 0, 0, .58), 0 .45rem 1.15rem rgba(0, 0, 0, .38);
  }
}

@media (max-width: 81rem) {
  .site-header { grid-template-columns: minmax(0, 1fr) auto; }
}

@media (hover: none) {
  /* Hover-to-reveal has no gesture here, so the slider just stays up. */
  .volume-slider { opacity: 1; }
}

@media (max-width: 58rem) {
  .site-header { grid-template-columns: minmax(0, 1fr) auto; }
  .site-header nav { display: none; }
  .header-actions .header-buy { display: none; }
  .hero-grid,
  .proof-heading,
  .audio-stage,
  .latency-head,
  .ear-intro,
  .fit-grid { grid-template-columns: minmax(0, 1fr); }
  .hero-grid { min-height: auto; }
  .hero-product { width: 100%; max-width: 100%; }
  .proof-heading, .latency-head { align-items: start; }
  .latency-head { gap: var(--space-3); }
  .latency-head h2 { margin-bottom: 0; }

}

@media (max-width: 35rem) {
  .latency { padding-block: 3rem; }
  .latency-head { margin-bottom: var(--space-2); }
  .fit { padding-block: 3rem; }
  .fit-list div {
    grid-template-columns: 5.5rem minmax(0, 1fr);
    padding-block: 1rem;
    font-size: .9rem;
  }
  .maker { grid-template-columns: minmax(0, 1fr); margin-top: var(--space-3); }
  .button-row .button { width: 100%; }
  .discord-btn { width: 100%; justify-content: center; }
  .comparison-controls { width: 100%; }
  .audio-switch { width: 100%; }
  .audio-switch button { min-height: 2.9rem; font-size: .95rem; }
  .comparison-caption { max-width: none; }
  .transport-play { width: 3.25rem; min-width: 3.25rem; }
  .term-list { grid-template-columns: minmax(0, 1fr); }
  .hero-actions { display: grid; grid-template-columns: minmax(0, 1fr); }
  .hero-actions .button { width: 100%; }
  .hero-facts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .hero-facts div { padding: var(--space-3) var(--space-1); }
  .hero-facts dt { font-size: .75rem; line-height: 1.15; }
  .hero-facts dd { font-size: .75rem; line-height: 1.25; }
  .mix-map { grid-template-columns: minmax(0, 1fr) 2.25rem; }
  .lane {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: .25rem;
    margin-block: .15rem;
    padding: .5rem .7rem;
  }
  .lane-name { grid-column: 1 / -1; }
  .lane-tag::before { display: none; }
  .chip { padding: .35rem .65rem; font-size: .75rem; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .hero-copy h1,
  .hero-actions,
  .hero-demo-details,
  .hero-facts-band,
  .hero-visual { animation: none !important; }
  .bench-body,
  .ear { transition: none !important; }
  .hero-visual::after { transition: none !important; }
  .motion-reveal { opacity: 1 !important; transform: none !important; }
  /* This block enumerates rather than blanket-killing, so anything added
     above has to be named here too or it keeps moving. */
  .wordmark svg,
  .offer-shell > *,
  .term-list li,
  .wire::before,
  .wire-in::after,
  .wire-out::after,
  .er-meter i,
  .er-conf i,
  .er-q,
  .er-val { animation: none !important; }
  /* Settled on the answer rather than the question: with no motion, the
     readout has to show the state Ear ends in. */
  .er-conf i { width: 100% !important; }
  .er-q { opacity: 0 !important; }
  .er-val { opacity: 1 !important; }
  .er-meter i { height: 60% !important; }
  .offer-shell > *,
  .term-list li { opacity: 1 !important; transform: none !important; }
  .wordmark svg { clip-path: none !important; }
  .transport-play[aria-busy="true"] { animation: none; opacity: .6; }
  .button,
  .transport-play,
  .audio-switch button,
  .audio-switch::before,
  .hero-product,
  .offer-visual img,
  .walk-focus,
  .walk-box,
  .walk-copy,
  .walk-step { transition: none !important; }
}

/* HERO — product first. No illustrative backdrop. */
.hero { background: var(--flow-black); }

.hero-grid {
  gap: clamp(2.5rem, 4vw, 4rem);
  padding-top: clamp(4.5rem, 8vw, 7.5rem);
}

.hero-copy { width: min(100%, 44rem); }

.hero h1 {
  max-width: 15ch;
  font-size: clamp(3rem, 5vw, 5.4rem);
  letter-spacing: -.072em;
  line-height: .94;
  text-wrap: balance;
}

/* The frame states its height and the render states its own. No
   percentage survives anywhere in this chain, and that is the whole point:
   WebKit does not resolve a percentage height against a height it derived
   itself, so with aspect-ratio: 16 / 8.7 here and max-height: 90% on the
   render, Safari dropped the cap, fell back to the 57rem width cap, and
   drew the render 694px tall inside a 411px box. The overflow below cut
   the title bar off the top and the keyboard off the bottom.

   Stating the frame's height outright was not enough. Measured on the
   load timeline in Safari 26.5, render against a 1184x644 frame: 912x750
   at DOMContentLoaded, at load, at +100ms and at +600ms, then 522x429 by
   +2500ms, and correct immediately after any style change -- which is why
   hovering the render appeared to fix it. WebKit resolves the percentage
   on a later pass, not on the first layout, so the percentage has to go.

   The render's height is the frame's content box -- its own height less
   the 1px border and the padding -- times the .9 that used to be
   max-height: 90%. Width follows from the 1350x1110 intrinsic ratio.
   @Robustness: 100vw counts a classic scrollbar where 100% does not, so
   between roughly 930 and 1264px without overlay scrollbars the frame can
   run 8px off the ratio, and Chromium draws the render 1px shorter than
   the percentage did. Both are invisible against a 449px render. */
.hero-visual {
  --hero-w: min(100vw - (2 * var(--gutter)), 74rem);
  --hero-pad: clamp(2rem, 5vw, 5.5rem);
  width: min(100%, var(--hero-w));
  margin-inline: auto;
  height: calc(var(--hero-w) * 8.7 / 16);
  overflow: hidden;
  padding: var(--hero-pad);
  background:
    radial-gradient(ellipse at 50% 52%, rgba(255,255,255,.055) 0, rgba(255,255,255,.018) 35%, transparent 67%),
    linear-gradient(180deg, #161514 0%, #121110 100%);
  border: 1px solid rgba(255,255,255,.055);
  border-bottom: 0;
}

/* Painted canvas behind the render. Held back with a scrim so the plugin
   stays the brightest thing in the frame. */
.hero-visual::before {
  content: "";
  position: absolute;
  z-index: 0;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(18,17,16,.62) 0%, rgba(18,17,16,.78) 100%),
    url("assets/hero-canvas.webp") center / cover no-repeat;
  background:
    linear-gradient(180deg, oklch(from var(--flow-black) l c h / .62) 0%, oklch(from var(--flow-black) l c h / .78) 100%),
    url("assets/hero-canvas.webp") center / cover no-repeat;
  pointer-events: none;
}

.hero-visual::after {
  inset: 18% 19% 6%;
  background: radial-gradient(ellipse, rgba(0,0,0,.42), transparent 70%);
  filter: blur(1.8rem);
  opacity: .55;
}

.hero-product {
  height: calc((var(--hero-w) * 8.7 / 16 - 2px - (2 * var(--hero-pad))) * .9);
  width: auto;
  max-width: min(100%, 57rem);
  max-height: none;
  outline-color: rgba(255,255,255,.065);
  box-shadow: 0 2.5rem 5.5rem rgba(0,0,0,.5), 0 .25rem 1rem rgba(0,0,0,.32);
}

/* Continuous with the hero rather than a band of its own: --flow-black is
   the exact value the hero visual's gradient ends on, so the strip reads
   as the bottom of the hero and only the hairline separates them. It was
   a filled green (#162219), which spent the signal color on a whole
   surface -- the accent stops meaning anything once it is also a
   background, and the strip read as a foreign object under a near-black
   hero. The green now survives where it is doing work: the three labels. */
.hero-facts-band {
  border-top: 1px solid rgba(255, 255, 255, .07);
  background: var(--flow-black);
}

/* HUMAN MOMENT — the image still gets a breath of its own, but a short one:
   the old 10rem tail read as a dead field under the transport. */
.proof {
  padding-top: clamp(1.25rem, 3vw, 2.5rem);
  padding-bottom: clamp(3rem, 5vw, 4.5rem);
  background: var(--flow-paper-bright);
}

/* Sized so the picture and the Dry / Corrected switch share one screen --
   her face has to be visible at the moment you hit the toggle, otherwise the
   reaction the shot exists for happens off-screen. */
.proof-visual {
  width: min(calc(100% - (2 * var(--gutter))), 64rem);
  height: min(42vh, 26rem);
  aspect-ratio: auto;
  margin: 0 auto clamp(1.5rem, 3vw, 2.5rem);
  border-radius: var(--radius);
  background: #0b0a09;
  box-shadow: 0 2rem 6rem rgba(24,24,24,.08);
}

.proof-visual img,
.proof-visual video {
  object-position: 50% 33%;
}

.proof-inner { max-width: 64rem; }

.proof-heading {
  margin-bottom: clamp(1.25rem, 2.5vw, 2rem);
}

.proof-heading h2 { max-width: 12ch; }

/* The audio proof itself should feel like one focused instrument. */
.comparison-player {
  width: min(100%, 46rem);
  margin-inline: auto;
}

.transport-timeline { margin-top: 0; }

/* ENGINEERING — intentionally quieter after the human image. */
.latency {
  background: var(--flow-paper);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.mode-list > div {
  grid-template-columns: minmax(7rem,.52fr) minmax(9rem,.9fr) minmax(12rem,1.35fr);
}

.mode-ms { font-variant-numeric: tabular-nums; }

.offer h2 { letter-spacing: -.065em; }

@media (max-width: 58rem) {
  /* The framed canvas is a desktop device. Narrow, the plugin shrinks but
     the frame does not, so the painted rectangle becomes the biggest thing
     on screen -- exactly what the hero is not allowed to be. Drop the card
     and let the render sit on the page at full width; it also buys the UI
     back the ~90px of padding it could not spare at this size. */
  .hero-visual {
    height: auto;
    aspect-ratio: auto;
    width: calc(100% + (2 * var(--gutter)));
    margin-inline: calc(-1 * var(--gutter));
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
  }
  .hero-visual::before,
  .hero-visual::after { display: none; }
  .hero-product {
    height: auto;
    max-width: 100%;
    max-height: none;
    outline: 0;
    box-shadow: none;
  }
  /* 4/5 is sized for a phone and keeps scaling with the viewport, so by
     900px these were 1106px tall -- taller than the screen. The cap holds
     them to one comfortable band; object-fit crops the rest. Phones are
     unchanged: 30rem is already shorter than 4/5 of a 390px viewport. */
  .proof-visual,
  .interlude-visual,
  .ear-photo { max-height: min(60vh, 30rem); }
  /* 4/5 made this 480px tall on a 390px phone, and image-top to
     player-bottom spanned 1032px against about 699px of usable height in
     Safari once its chrome is counted. Her face and the Dry/Corrected
     switch could not be on screen at the same time, which is the entire
     point of the section: you scrolled her away to reach the control.
     16/9 lands near the source frame's own 1.75:1, so it also crops less
     than the portrait box did. */
  .proof-visual {
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
    margin: 0 0 clamp(1.25rem, 4vw, 2rem);
    border-radius: 0;
    box-shadow: none;
  }
  /* Once the visuals run edge to edge they read as the section's own
     opening, and the section padding above them reads as a hole. Flush. */
  .proof { padding-top: 0; }
  .ear { padding-top: 0; }
  .proof-visual img,
  .proof-visual video {
    object-position: 50% 32%;
  }
  /* The heading sits between the picture and the player, so its margins
     are part of the same budget. */
  .proof-heading { margin-bottom: clamp(.75rem, 3vw, 1.25rem); }
  .proof-heading > p { margin-bottom: 0; }
  /* Part of the same budget as the picture above it: at 1.5rem the
     section ran 21px past what Safari leaves visible with its URL
     bar showing. */
  .interlude-visual {
    width: 100%;
    aspect-ratio: 4 / 5;
    margin-top: clamp(2rem, 8vw, 3rem);
    border-radius: 0;
  }
  .ear-photo {
    aspect-ratio: 4 / 5;
    border-radius: 0;
    margin: 0 calc(-1 * var(--gutter));
    width: calc(100% + (2 * var(--gutter)));
  }
  .ear-intro { row-gap: clamp(1.5rem, 5vw, 2.5rem); }
  /* 68% pinned her against the left edge and spent the right half of
     the frame on the amp and the laptop. The box is 390x480 and the
     source is 1536x1024, so cover scales it to 720x480 and crops 330px
     horizontally; at 68% that showed 31-85% of the frame. 35% keeps
     her, the notebook and the writing hand. */
  .ear-photo img { object-position: 35% 30%; }
  .interlude-visual img { object-position: 50% 22%; }

}

@media (max-width: 35rem) {
  .hero h1 { font-size: clamp(2.5rem, 11.5vw, 3.2rem); line-height: 1.02; }
  .hero-grid { padding-top: 3rem; gap: var(--space-4); }
  .mode-list > div {
    grid-template-columns: minmax(0, 1fr) auto;
    padding-block: 1rem;
  }
  .mode-ms { grid-column: 2; grid-row: 1 / 3; align-self: center; margin: 0; }
  .mode-role { grid-column: 1; }
  .mode-bar { grid-row: 3; }
  .ear-photo { aspect-ratio: 3 / 2; max-height: none; }
  .ear-intro { row-gap: var(--space-4); }
  .ear-diagram { margin-top: var(--space-4); }
}

/* Keep the complete instrument beside the introduction on wide screens.
   Intrinsic image sizing also avoids percentage-height layout dependencies. */
@media (min-width: 58.001rem) {
  .hero-grid {
    grid-template-columns: minmax(0, .9fr) minmax(0, 1.2fr);
    align-items: center;
    gap: clamp(2rem, 4vw, 4rem);
    padding-block: clamp(4rem, 7vw, 6.5rem);
  }

  .hero-copy { text-align: left; }

  .hero h1 {
    margin-inline: 0;
    margin-bottom: var(--space-4);
    font-size: clamp(3rem, 4.3vw, 4.8rem);
    letter-spacing: -.055em;
    line-height: 1.02;
  }

  .hero-actions { justify-content: flex-start; }
  .hero-demo-details,
  .hero-demo-details summary { margin-inline: 0; }

  .hero-visual {
    width: 100%;
    height: auto;
    aspect-ratio: auto;
    padding: 0;
    overflow: visible;
    border: 0;
    background: none;
  }

  .hero-visual::before,
  .hero-visual::after { display: none; }

  .hero-product {
    width: 100%;
    height: auto;
    max-width: 100%;
  }
}

/* Mobile only, and only once the hero is out of view: a fixed bar holding
   the two hero actions so they stay reachable without scrolling back.
   hidden until the script marks it, display:none above the breakpoint. */
.sticky-cta { display: none; }

@media (max-width: 58rem) {
  .sticky-cta {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 30;
    display: flex;
    gap: .6rem;
    padding: .6rem max(.8rem, env(safe-area-inset-left)) max(.6rem, env(safe-area-inset-bottom));
    background: rgba(18, 17, 16, .92);
    background: oklch(from var(--flow-black) l c h / .92);
    backdrop-filter: blur(10px);
    border-top: 1px solid rgba(247, 247, 243, .12);
    border-top: 1px solid oklch(from var(--on-inverse, #f7f7f3) l c h / .12);
    transform: translateY(100%);
    /* Off-screen is not off-page: translateY leaves both links in the tab
       order while invisible. hidden removes them, and the delayed flip
       keeps the slide-out running instead of vanishing at frame one. */
    visibility: hidden;
    transition: transform .28s cubic-bezier(.22, 1, .36, 1), visibility 0s linear .28s;
  }
  .sticky-cta .button { flex: 1; justify-content: center; }
  .sticky-cta .button-secondary {
    border-color: rgb(247 247 243 / .28);
    border-color: oklch(from var(--on-inverse, #f7f7f3) l c h / .28);
    background: rgb(247 247 243 / .04);
    background: oklch(from var(--on-inverse, #f7f7f3) l c h / .04);
    color: var(--on-inverse, #f7f7f3);
  }
  .sticky-cta.is-shown {
    transform: none;
    visibility: visible;
    /* Arrival gets the overshoot; the departure above stays a plain ease --
       the toast rises through its edge, it does not spring out of view. */
    transition: transform .42s var(--ease-bounce, cubic-bezier(.34, 1.36, .64, 1)), visibility 0s;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sticky-cta { transition: none; }
}

/* CONTINUOUS FIELD -- one fixed aurora canvas IS the background of the
   page's dark surfaces: hero, facts band, walkthrough, offer and footer all
   open to it, and the editor renders sit on the field the way the hero
   render always did. The light sections keep solid paper -- a translucent
   veil over the field just reads as grey. main/footer need a real stacking
   context: position:relative with z-index:auto still lets in-flow
   descendants (the editor images) paint UNDER the canvas. */
body { background: #0d0c0b; }

main, footer { position: relative; z-index: 0; }

.hero, .hero-facts-band, .walkthrough, .offer, footer {
  background: transparent;
}

/* These three had no background of their own -- they showed the old paper
   body. The field's dark base sits under them now, so they need the paper
   explicitly. */
.fit, .discord-band, .interlude { background: var(--flow-paper); }
