/* ==========================================================================
   OASIS INFLATABLES — blocks.css
   3D viewer, hero, editorial bands, stats, steps, press, FAQ, page headers.
   ========================================================================== */

/* --- 3D viewer ------------------------------------------------------------ */
.viewer {
  position: relative;
  width: 100%;
  height: 100%;
  /* `pan-y` HERE, `none` on the canvas, and the split is the whole design.
   *
   * `touch-action` is not inherited — a touch is governed by the element it
   * starts on, intersected with its ancestors. So these two rules say different
   * things about different pixels:
   *
   *   the CANVAS (the model)  owns both axes. A drag in any direction turns the
   *                           deck, and the page does not move underneath it.
   *   everything else here    the camera pills, the auto-rotate and reset
   *                           buttons, the hint — hands vertical panning back
   *                           to the browser, so a thumb that lands on a button
   *                           and slides still scrolls the page.
   *
   * This used to be `pan-y` on the whole viewer, which meant a vertical swipe
   * over the model scrolled the page and only a horizontal one turned the deck.
   * Two axes doing two unrelated things is the confusing part: the gesture that
   * rotates and the gesture that scrolls look identical until they have already
   * done different things.
   *
   * `none` on the whole viewer was tried, reverted, and was wrong for a
   * different reason — it left a phone with no way to scroll if the model
   * filled the screen. That is why the space outside the model is the thing to
   * check before shipping this: the stage is first on a phone and about a third
   * of the fold, so the copy below it is always reachable. qa-viewer.mjs
   * measures that rather than trusting it.
   *
   * Desktop is untouched by any of this. `touch-action` governs touch and pen
   * only; a mouse is unaffected, and there is still no wheel handler. */
  touch-action: pan-y;
  isolation: isolate;
}
.viewer canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  cursor: grab;
  /* The model takes both axes. See the note on `.viewer` above for why this is
     `none` here and `pan-y` there, and what used to be wrong in each
     direction. */
  touch-action: none;
  /* No opacity transition on the canvas itself, and that is a correctness fix
     rather than a style choice.
     Animating a WebGL canvas's own opacity makes the compositor produce frames
     for it faster than the render loop draws them, and the frames it produces in
     between show the drawing buffer's clear colour — so the panel washes out to
     a milky rectangle for the length of the fade and the model snaps in at the
     end. Measured: the composite brightness spiked +6.3 and contrast dropped
     -8.5 against the poster in the frame at canvas opacity 0.86. Forcing
     `preserveDrawingBuffer` did not change it, so it is the animation and not
     the buffer.
     The canvas is therefore always opaque. The poster sits ON TOP of it and is
     what fades — which is the same picture arriving, with nothing animated that
     has to be redrawn to be correct.
     `visibility`, not `opacity`, for the wait: an opacity change would be the
     same animation problem in miniature, and this one is a hard switch with
     nothing to interpolate. `is-drawn` is set by the viewer on the frame that
     first rendered a correctly-sized canvas. */
  opacity: 1;
  visibility: hidden;
}
.viewer.is-drawn canvas { visibility: visible; }
.viewer canvas.is-grabbing { cursor: grabbing; }
.viewer picture { position: absolute; inset: 0; display: block; z-index: 1; }
/*
 * A poster that has not arrived yet shows the panel, not its alt text.
 *
 * Firefox renders an image's alt text while the image is still loading, and this
 * image's alt text is a sentence. On a first visit the product page's 3D slide
 * is `display: none` until it is opened, so its poster only began fetching at
 * the click — and the panel opened as a cream rectangle with "Interactive 3D
 * model of The Oasis Platform" printed across the top of it.
 *
 * `is-pending` is added by blocks.js rather than being the default here, and
 * that ordering is the point: with no JavaScript the poster is the only thing in
 * that panel, so it has to default to visible. The class hides it only while the
 * file is genuinely on its way, and is removed on `error` as well as on `load`,
 * because a poster that will never arrive should say what it was.
 */
.viewer picture { transition: opacity .3s var(--ease); }
.viewer picture.is-pending { opacity: 0; }
/*
 * `cover`, not `contain`.
 *
 * The poster is a capture of this viewer's own first frame, taken wide. The
 * viewer frames the model by fitting its bounding sphere, and for any box wider
 * than it is tall the binding constraint is the VERTICAL field of view — so the
 * model's size and position are the same in a 1.5:1 canvas as in a 1.1:1 one,
 * and the only difference between them is how much of the world is visible to
 * the sides.
 *
 * That is exactly what `cover` does, and exactly what `contain` does not:
 * `contain` letterboxes the wide poster inside the narrower box, so the model
 * shrinks to 72% of the height it occupies in the canvas it is supposed to be
 * indistinguishable from. The poster then cross-faded into a model that was
 * visibly larger — the seam this is here to remove.
 */
.viewer__poster {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
  transition: opacity .5s var(--ease);
}
.viewer.is-ready .viewer__poster { opacity: 0; }
/* The canvas is beneath the poster, so it has to be painted first. */
.viewer canvas { z-index: 0; }

/* --- The corners belong to the layers that have them ----------------------
 *
 * The frame (`.stage`, or `.gallery__stage` on a product page) is the thing
 * with `border-radius` and `overflow: hidden`, and every full-bleed layer
 * inside it is a SQUARE that relies on that ancestor to clip it:
 *
 *     <canvas>        the WebGL surface — always its own compositing layer
 *     <picture>       full-bleed, z-index 1, and it fades, so it is promoted too
 *     .viewer__poster the image inside it, filling the same box
 *
 * Relying on an ancestor's rounded clip to trim a composited descendant is the
 * fragile part. The clip is applied when the browser paints on the main thread
 * and can be skipped for frames the compositor produces on its own — which is
 * exactly what scrolling is. The symptom is the deck's square corners poking
 * past the rounded frame like dog ears, appearing and disappearing as you
 * scroll, which is what this rule removes.
 *
 * Giving each layer the same radius means its own rasterised corners are
 * already round, so there is nothing left for the ancestor to clip and nothing
 * to get wrong on a compositor frame. `inherit` rather than a literal, so each
 * frame keeps its own radius — `--r-xl` on the hero, `--r-lg` on the gallery —
 * and a change to either stays in one place.
 *
 * The frame's 1px border means these are clipped to the padding box, one pixel
 * inside the curve. The two radii therefore differ by that pixel, which is
 * invisible at 32px and always errs toward showing the frame's own background
 * rather than a square corner. */
.viewer picture,
.viewer__poster,
.viewer canvas {
  border-radius: inherit;
}
.viewer__loader {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  pointer-events: none;
  transition: opacity .4s var(--ease);
}
.viewer.is-ready .viewer__loader { opacity: 0; }
.viewer__loader span {
  width: 30px; height: 30px; border-radius: 50%;
  border: 2px solid var(--sand-3); border-top-color: var(--orange);
  animation: spin .9s linear infinite;
}
.viewer--nogl .viewer__poster { opacity: 1 !important; }
/* `.viewer.is-ready .viewer picture` until now, which matches nothing: it asks
   for a `.viewer` INSIDE a ready viewer. That never mattered while the canvas
   painted above the poster and the canvas took the pointer. Putting the poster
   on top — so the canvas never has to animate its own opacity — made it matter,
   and the model stopped responding to a drag. */
.viewer.is-ready picture { opacity: 0; transition: opacity .5s var(--ease); pointer-events: none; }
.viewer--nogl canvas, .viewer--nogl .viewer__loader, .viewer--nogl [data-hotspots], .viewer--nogl .viewer__ui { display: none !important; }

.viewer__ui {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 3;
}
.viewer__ui > * { pointer-events: auto; }
.viewer__hint {
  position: absolute; right: 1rem; bottom: 1rem;
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .5rem .85rem; border-radius: var(--r-pill);
  background: rgba(23,20,15,.78); color: #fff;
  backdrop-filter: blur(8px);
  font-size: var(--fs-xs); font-weight: 550;
  transition: opacity .5s var(--ease);
}
.viewer__hint svg { width: 15px; height: 15px; }
.viewer.is-touched .viewer__hint { opacity: 0; }
.viewer__tools {
  position: absolute; right: 1rem; top: 1rem;
  display: flex; flex-direction: column; gap: .4rem;
  z-index: 4;
}
.vtool {
  width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(255,255,255,.86);
  backdrop-filter: blur(10px);
  border: 1px solid var(--line);
  color: var(--ink-2);
  box-shadow: var(--sh-1);
  transition: all .2s var(--ease);
}
.vtool:hover { background: #fff; color: var(--ink); transform: scale(1.06); }
.vtool[aria-pressed="true"] { background: var(--ink); color: #fff; border-color: var(--ink); }
.vtool svg { width: 17px; height: 17px; }

.viewer__views {
  position: absolute; left: 1rem; bottom: 1rem;
  display: flex; gap: .3rem; padding: .3rem;
  border-radius: var(--r-pill);
  background: rgba(255,255,255,.8);
  backdrop-filter: blur(10px);
  border: 1px solid var(--line);
  box-shadow: var(--sh-1);
  z-index: 4;
}
.viewer__views button {
  padding: .35rem .8rem; border-radius: var(--r-pill);
  font-family: var(--font-display); font-size: .75rem; font-weight: 650;
  color: var(--muted);
  transition: all .18s var(--ease);
}
.viewer__views button:hover { color: var(--ink); }
.viewer__views button[aria-pressed="true"] { background: var(--ink); color: #fff; }

.viewer__spec {
  position: absolute; left: 1rem; top: 1rem;
  display: grid; gap: .1rem; text-align: left;
  font-size: var(--fs-xs); color: var(--muted);
  z-index: 4;
  pointer-events: none;
}
.viewer__spec b { font-family: var(--font-display); font-size: .8125rem; color: var(--ink); font-weight: 700; }

/* Hotspots */
[data-hotspots] { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.hotspot {
  position: absolute; left: 0; top: 0;
  width: 30px; height: 30px;
  display: grid; place-items: center;
  pointer-events: auto;
  transition: opacity .3s var(--ease);
}
.hotspot__dot {
  position: relative;
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--orange);
  box-shadow: 0 0 0 3px rgba(255,255,255,.85), 0 2px 8px rgba(0,0,0,.25);
  transition: transform .25s var(--ease);
}
.hotspot__dot::after {
  content: ''; position: absolute; inset: -6px; border-radius: 50%;
  border: 1.5px solid rgba(204,92,31,.6);
  animation: pulse 2.6s ease-out infinite;
}
@keyframes pulse {
  0% { transform: scale(.7); opacity: .9; }
  70% { transform: scale(1.7); opacity: 0; }
  100% { transform: scale(1.7); opacity: 0; }
}
.hotspot:hover .hotspot__dot { transform: scale(1.15); }
.hotspot__card {
  position: absolute;
  left: 26px; top: 50%;
  z-index: 8;
  transform: translateY(-50%) translateX(-6px);
  width: max-content; max-width: 230px;
  padding: .6rem .8rem;
  border-radius: var(--r-sm);
  background: rgba(23,20,15,.92);
  backdrop-filter: blur(10px);
  color: #fff;
  text-align: left;
  opacity: 0; visibility: hidden;
  transition: opacity .25s var(--ease), transform .25s var(--ease), visibility .25s;
  box-shadow: var(--sh-3);
  pointer-events: none;
}
.hotspot__card b { display: block; font-family: var(--font-display); font-size: .8125rem; font-weight: 700; letter-spacing: -0.01em; }
.hotspot__card i { display: block; font-style: normal; font-size: .75rem; color: rgba(255,255,255,.66); margin-top: .15rem; line-height: 1.4; }
.hotspot:hover .hotspot__card, .hotspot.is-open .hotspot__card, .hotspot:focus-visible .hotspot__card {
  opacity: 1; visibility: visible; transform: translateY(-50%) translateX(0);
}

/* Stage that holds a viewer */
.stage {
  position: relative;
  border-radius: var(--r-xl);
  overflow: hidden;
  background:
    radial-gradient(120% 90% at 62% 18%, #FFFDFA 0%, rgba(255,253,250,0) 55%),
    radial-gradient(90% 70% at 20% 95%, #EFE3D3 0%, rgba(239,227,211,0) 60%),
    linear-gradient(165deg, var(--sand-2) 0%, var(--sand-3) 100%);
  border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.7), var(--sh-2);
}
.stage--deep {
  background:
    radial-gradient(120% 90% at 60% 12%, rgba(46,110,104,.55) 0%, rgba(46,110,104,0) 60%),
    linear-gradient(165deg, #133C38 0%, #0A211F 100%);
  border-color: rgba(255,255,255,.08);
}
.stage--deep .viewer__hint { background: rgba(255,255,255,.14); }

/* --- Hero ----------------------------------------------------------------- */
.hero { position: relative; padding-block: clamp(1.25rem, 2.6vw, 2.5rem) clamp(2rem, 4vw, 3.5rem); }
.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.84fr) minmax(0, 1.16fr);
  gap: clamp(1.5rem, 3.2vw, 3.5rem);
  align-items: center;
}
.hero__copy { display: grid; gap: 1.35rem; align-content: center; max-width: 44rem; }
/* ==========================================================================
   The hero headline is wider than the column of copy beneath it.

   The sentence is the brand's one line and the most-seen thing on the site, so
   it earns the empty space to its right — the gutter, the air in front of the
   3D stage. Everything under it does not: at 42ch the sub-line, the three
   buttons and the four facts are already at a comfortable measure, and
   stretching them to the headline's width would make them harder to read, not
   easier.

   So the COLUMN widens (44rem) and each element inside it keeps its own
   measure. `justify-items: start` is what stops the grid from stretching the
   children to the new column width — a grid item defaults to `stretch`, which
   would quietly undo the measure caps the sub-line and the CTAs already carry.

   Only the headline grows. Nothing under it moves by a pixel, because the
   column's left edge and every child's left edge are unchanged.

   The rem cap on the headline is deliberate rather than `ch`: `ch` is measured
   in the element's OWN font size, so 36ch at 104px is 2059px of allowance that
   does nothing, while the same 36ch on the 16px copy beneath it is 417px.
   ========================================================================== */
.hero__copy { max-width: 44rem; justify-items: start; }
.hero__sub, .hero__ctas, .hero__facts, .hero__eyebrow, .hero__skip, .hero__stock, .hero__proof { max-width: 42ch; }
.hero__title {
  font-size: clamp(3.2rem, 5.12vw, 5.6rem);
  /* Tracking is the reason the headline stopped at 52px being legible and why
     it cannot simply be scaled up.

     At -0.045em — the value copied from `--fs-display-xl`, where the type was
     half this size — the "r" and "t" of "sorted" close up and touch: Archivo's
     `t` has a wide flat crossbar and the negative tracking pulls the pair
     together until the two letterforms read as one mark at 92px. The pulling
     apart is roughly linear in font size, so the tracking has to come back as
     the size goes up. -0.028em holds the tight display rhythm the brand wants
     and leaves a visible gap between every pair, "rt" included. */
  letter-spacing: -0.028em;
  line-height: 0.94;
  /* The headline is allowed to be wider than the column of copy beneath it, so
     the extra width is spent to the RIGHT — over the empty gutter, in front of
     the stage — and nothing under the headline moves by a pixel, because the
     column's left edge and every child's left edge are unchanged.

     Two things had to be true for that to work, and both were found by
     measuring rather than by reasoning:

       a cap in rem, not ch   `ch` is measured in the element's OWN font size,
                              so 36ch at 104px is 2059px of allowance that does
                              nothing, while the same 36ch on the 16px copy
                              beneath it is 417px. 40rem is 640px at every size.

       the cap must exist     `max-width: none` makes the headline wider than
                              the sentence's natural measure, and the browser
                              then sets it on ONE long line at 1440 and over the
                              model, instead of using the shape the sentence
                              already has.

     The cap is a fixed 40rem rather than a share of the column, and that is what
     keeps the headline the same shape at every desktop width: a percentage would
     wrap to four lines on a narrow copy column and three on a wide one, so the
     same sentence would change rhythm as the window is dragged. 640px is the
     number because it is the first cap at which the sentence settles into three
     lines at every size from 1440 up — 592px was still breaking "summer" onto a
     line of its own at 1920.

     It cannot reach the stage. The headline is capped and the copy column is
     not, so at 1440 the headline is 640 where the column is 704; add the 48px
     page inset and the line ends at 688 against a stage starting at 639. At 1280
     the column is only 544, so the cap is not reached at all. */
  max-width: min(40rem, 100%);
}
.hero__copy { min-width: 0; }
.hero__title em { font-style: normal; color: var(--orange); }
/* ==========================================================================
   The one glyph pair that fuses in Archivo Bold.

   "r" followed by "t" — as in "so(r t)ed" — sets as a single mark at display
   sizes: the "t" has a flat crossbar that reaches back over the "r", and the
   two sidebearings together are narrower than the letterforms are wide. It is
   measured, not guessed: at 74px the pair produces ONE ink run where every
   other pair in the word produces two, and its neighbours ("so", "te", "ed")
   show gaps of 1–2px.

   This is why the fix is a span rather than a tracking value. The overlap is a
   property of the glyphs at every weight from 500 to 800 and with `font-kerning`
   both on and off, so there is no kerning setting that opens it, and raising the
   headline's tracking enough to separate it (+0.04em) would also push every
   other pair apart and lose the tight display rhythm the brand uses.

   Letter-spacing is added AFTER each character, so wrapping the "r" alone puts
   the extra bearing between "r" and "t" and nowhere else.

   The value is measured rather than chosen by eye, because "too tight" and "too
   loose" are both failures and neither is visible in a screenshot at these
   sizes. With no extra bearing the pair measures a 1.5px gap at 1440 and 2.0px
   at 1920; the 4.75px and 6.0px that .045em produced read as a gap in the middle
   of a word rather than as letter spacing, so it is halved. .011em measures
   2.25px at 1440 and 3.0px at 1920 — half of the previous spacing, and matched
   to the tighter pairs around it ("te" sits at 2.5px, "so" at 3.3px).
   ========================================================================== */
.hero__title .kern-rt { letter-spacing: .011em; }
.hero__sub { font-size: var(--fs-lead); color: var(--muted); max-width: 42ch; line-height: 1.55; }
.hero__ctas { display: flex; gap: .75rem; flex-wrap: wrap; }
/* three primary routes above the fold: deck, trampoline, both */
.hero__ctas--three { display: grid; gap: .6rem; max-width: 30rem; }
.hero__ctas--three .btn { justify-content: flex-start; width: 100%; }
.hero__cta-bundle {
  position: relative;
  background: var(--ink);
  color: #fff;
  border-color: var(--ink);
}
.hero__cta-bundle:hover { background: #000; }
.hero__cta-badge {
  position: absolute; right: .85rem; top: 50%; transform: translateY(-50%);
  padding: .25rem .55rem; border-radius: var(--r-pill);
  background: var(--orange-btn); color: #fff;
  font-family: var(--font-display); font-size: .625rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; line-height: 1;
}
/* One sentence, so it wraps as one.
 *
 * As a flex row it became four independently-wrapping items, and the
 * storefront's central pre-order fact — nothing is in a warehouse yet, the
 * first run is N decks — rendered as four ragged columns: "Nothing is in a
 * warehouse yet —" / "the first run is" / "498 decks" / ", built to order…".
 * The dot is the only thing that needs to be a separate box; the sentence is
 * left as one inline run so the browser can break it where a sentence breaks. */
.hero__stock {
  display: block;
  font-size: var(--fs-xs); color: var(--muted);
  line-height: 1.55;
  text-wrap: pretty;
}
.hero__stock .dot { margin-right: .4rem; }
.hero__stock .dot { margin-top: .1rem; }
.hero__stock .dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--ok);
  box-shadow: 0 0 0 3px rgba(30,122,76,.16); flex: none;
}
.hero__proof {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  padding-top: .35rem;
  font-size: var(--fs-xs); color: var(--muted);
}
.hero__proof .row { gap: .55rem; }
.hero__stage { aspect-ratio: 1 / 0.82; min-height: 400px; }
.hero__floatchip {
  position: absolute; z-index: 5;
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .55rem .9rem; border-radius: var(--r-pill);
  background: rgba(255,255,255,.92);
  backdrop-filter: blur(10px);
  box-shadow: var(--sh-2);
  font-size: var(--fs-xs); font-weight: 550;
  border: 1px solid var(--line);
}
.hero__floatchip svg { width: 15px; height: 15px; color: var(--orange); }
.hero__floatchip--a { left: 1rem; top: 1rem; animation: floaty 6s ease-in-out infinite; }
.hero__floatchip--b { right: 1rem; bottom: 1rem; animation: floaty 7s ease-in-out infinite .8s; }

.hero__switch {
  position: absolute; left: 50%; top: 1rem;
  transform: translateX(-50%);
  z-index: 6;
  max-width: calc(100% - 2rem);
}
.seg--onstage {
  /* The row of three short labels must stay a row: wrapping it made a
     near-circular bubble at 900 and 1024 that crowded the spec label under it.
     Below 1180 it carries the short labels, and the labels do the shrinking. */
  max-width: 100%;
  flex-wrap: nowrap;
  justify-content: center;
  background: rgba(255,255,255,.82);
  backdrop-filter: blur(12px) saturate(160%);
  box-shadow: var(--sh-2);
  border-color: rgba(255,255,255,.6);
}
.seg--onstage button { padding: .5rem .95rem; }

.seg {
  display: inline-flex; padding: .3rem;
  border-radius: var(--r-pill);
  background: var(--sand-2);
  border: 1px solid var(--line);
  gap: .2rem;
}
.seg button {
  padding: .5rem 1rem; border-radius: var(--r-pill);
  font-family: var(--font-display); font-size: .8125rem; font-weight: 650;
  color: var(--muted);
  transition: all .2s var(--ease);
  white-space: nowrap;
}
.seg button:hover { color: var(--ink); }
.seg button[aria-pressed="true"] { background: var(--ink); color: #fff; box-shadow: var(--sh-1); }

/* --- Trust / USP strip ---------------------------------------------------- */
.usp {
  display: grid; grid-template-columns: repeat(4, minmax(0,1fr));
  gap: 1px;
  background: var(--line);
  border-block: 1px solid var(--line);
}
.usp__item {
  background: var(--sand);
  padding: 1.35rem clamp(.75rem, 1.6vw, 1.5rem);
  display: grid; gap: .35rem; align-content: start;
}
.usp__item svg { width: 21px; height: 21px; color: var(--orange); }
.usp__item b { font-family: var(--font-display); font-size: .875rem; font-weight: 700; letter-spacing: -0.01em; }
.usp__item span { font-size: var(--fs-xs); color: var(--muted); line-height: 1.45; }

/* --- Editorial bands ------------------------------------------------------ */
.band { position: relative; overflow: hidden; }
.band__media { position: absolute; inset: 0; }
.band__media img { width: 100%; height: 100%; object-fit: cover; }
.band__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(100deg, rgba(10,33,31,.88) 0%, rgba(10,33,31,.62) 42%, rgba(10,33,31,.18) 100%);
}
.band__inner { position: relative; z-index: 2; padding-block: clamp(3.5rem, 7vw, 7rem); color: #fff; }
.band__copy { max-width: 46ch; display: grid; gap: 1.1rem; }
.band__copy h2 { color: #fff; font-size: var(--fs-display); }
.band__copy p { color: rgba(255,255,255,.78); font-size: var(--fs-lead); }

.split { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: clamp(1.5rem, 4vw, 4rem); align-items: center; }
.split--media-first .split__media { order: -1; }
.split__media { border-radius: var(--r-lg); overflow: hidden; position: relative; box-shadow: var(--sh-2); }
.split__media img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 4/3.1; }
.split__copy { display: grid; gap: 1.1rem; align-content: center; }
.split__copy h2 { font-size: var(--fs-h2); }
.split__copy p { color: var(--muted); font-size: var(--fs-lead); }

.statrow { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: clamp(1rem,2vw,2rem); }
.stat { display: grid; gap: .3rem; align-content: start; }
.stat__value { font-family: var(--font-display); font-weight: 700; font-size: clamp(2.25rem, 3.6vw, 3.5rem); line-height: .95; letter-spacing: -0.045em; }
.stat__value sup { font-size: .38em; font-weight: 700; vertical-align: super; margin-left: .12em; letter-spacing: 0; }
.stat__label { font-family: var(--font-display); font-size: .875rem; font-weight: 650; }
.stat__note { font-size: var(--fs-xs); color: var(--muted); }
.section--deep .stat__value { color: #fff; }
.section--deep .stat__note { color: rgba(255,255,255,.55); }
.section--deep .stat__label { color: rgba(255,255,255,.85); }

/* --- Steps ---------------------------------------------------------------- */
.steps { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: clamp(1rem, 2.4vw, 2.25rem); counter-reset: step; }
.steps--2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
.step { display: grid; gap: .7rem; align-content: start; position: relative; padding-top: 1.5rem; border-top: 2px solid var(--line); }
.step::before {
  counter-increment: step; content: '0' counter(step);
  position: absolute; top: -.05rem; left: 0;
  transform: translateY(-50%);
  font-family: var(--font-display); font-weight: 700; font-size: .8125rem;
  letter-spacing: .1em;
  background: var(--sand); padding-right: .7rem;
  color: var(--orange);
}
.section--shell .step::before { background: var(--shell); }
.section--sand2 .step::before { background: var(--sand-2); }
.section--deep .step { border-top-color: rgba(255,255,255,.18); }
/* Light eyebrow steps are for DARK surfaces. The .section--deep rule was right;
   the bare .band one was not — a band carries a diagonal scrim that is opaque on
   the left and almost absent on the right, so an eyebrow outside .band__inner
   sits on the page's cream and rendered #E89160 at 2.27:1. Only the copy INSIDE
   the scrim is on a dark ground. */
.section--deep .eyebrow,
.band__inner .eyebrow { color: var(--orange-300); }
/* The light variant is a white eyebrow for dark surfaces and outranks both of
   the above — it is what the copy actually asks for. */
.band__inner .eyebrow--light,
.section--deep .eyebrow--light { color: rgba(255,255,255,.82); }
.section--deep .vcard__icon { background: rgba(255,255,255,.14); color: var(--orange-300); }
.section--deep .step::before { color: var(--orange-300); }
.section--deep .step::before { background: var(--deep); }
.step h3 { font-size: var(--fs-h4); }
.step p { color: var(--muted); font-size: var(--fs-sm); }
.section--deep .step p { color: rgba(255,255,255,.62); }
.step__icon {
  width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--orange-050); color: var(--orange);
}
.step__icon svg { width: 22px; height: 22px; }

/* --- Impact type ---------------------------------------------------------- */
.impact { text-align: center; padding-block: clamp(3rem, 6vw, 6rem); }
.impact p {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.65rem, 4.1vw, 3.75rem);
  line-height: 1.06;
  letter-spacing: -0.04em;
  max-width: 22ch;
  margin-inline: auto;
  text-wrap: balance;
}
.impact p mark { background: none; color: var(--orange); }

/* --- Wordmark marquee ----------------------------------------------------- */
.wordmarquee {
  overflow: hidden;
  padding-block: clamp(1.5rem, 3vw, 2.5rem);
  border-block: 1px solid var(--line);
  background: var(--sand-2);
}
.wordmarquee__track { display: flex; width: max-content; animation: marquee 34s linear infinite; }
.wordmarquee__group { display: flex; align-items: center; flex: none; }
.wordmarquee__item {
  display: inline-flex; align-items: center; gap: clamp(1rem, 2.4vw, 2.5rem);
  padding-inline: clamp(.5rem, 1.2vw, 1.25rem);
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.15rem, 2.1vw, 1.9rem);
  letter-spacing: -0.02em;
  color: var(--ink);
  white-space: nowrap;
}
.wordmarquee__item svg { width: 1em; height: 1em; color: var(--orange); flex: none; }
.wordmarquee--deep { background: var(--deep); border-color: rgba(255,255,255,.1); }
.wordmarquee--deep .wordmarquee__item { color: rgba(255,255,255,.9); }

/* --- Press ---------------------------------------------------------------- */
.press { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: clamp(1rem,2.4vw,2.5rem); }
.press__item { display: grid; gap: .85rem; align-content: start; }
.press__quote { font-family: var(--font-display); font-size: 1.0625rem; font-weight: 500; line-height: 1.45; letter-spacing: -0.015em; }
.press__src { font-size: var(--fs-xs); letter-spacing: .12em; text-transform: uppercase; color: var(--muted); font-weight: 600; }

/* --- FAQ ------------------------------------------------------------------ */
.faq-layout { display: grid; grid-template-columns: 250px minmax(0,1fr); gap: clamp(1.5rem, 4vw, 4rem); align-items: start; }
.faq-nav { position: sticky; top: 110px; display: grid; gap: .15rem; }
.faq-nav a {
  padding: .55rem .8rem; border-radius: var(--r-sm);
  font-size: var(--fs-sm); color: var(--muted);
  transition: all .16s var(--ease);
}
.faq-nav a:hover { background: var(--sand-2); color: var(--ink); }
.faq-nav a.is-active { background: var(--ink); color: #fff; }
.faq-groups { display: grid; gap: clamp(2rem, 4vw, 3.5rem); }
.faq-group__title { display: flex; align-items: center; gap: .85rem; margin-bottom: 1.1rem; }
.faq-group__title h2 { font-size: var(--fs-h3); }
.faq-group__title span {
  min-width: 30px; height: 30px; padding: 0 .5rem;
  display: grid; place-items: center;
  border-radius: var(--r-pill); background: var(--sand-2);
  font-family: var(--font-display); font-size: .75rem; font-weight: 700; color: var(--muted);
}
.faq-cta {
  display: grid; gap: 1rem; justify-items: center; text-align: center;
  padding: clamp(1.75rem, 4vw, 3rem);
  border-radius: var(--r-lg);
  background: var(--shell);
  border: 1px solid var(--line);
  box-shadow: var(--sh-1);
}
.acc__cta {
  display: inline-flex; align-items: center; gap: .45rem;
  margin-top: .9rem;
  font-family: var(--font-display); font-size: var(--fs-sm); font-weight: 650;
  color: var(--orange-600);
  border-bottom: 1.5px solid currentColor; padding-bottom: 2px;
}
.acc__cta:hover { color: var(--orange-700); gap: .75rem; }

/* --- Page header ---------------------------------------------------------- */
.pagehead { padding-block: clamp(2rem, 4vw, 3.5rem) clamp(1.5rem, 3vw, 2.5rem); }
.pagehead__inner { display: grid; gap: 1rem; }
.pagehead__inner > * { max-width: 76ch; }
.pagehead h1 { font-size: var(--fs-display); }
.pagehead p { color: var(--muted); font-size: var(--fs-lead); max-width: 62ch; }
.crumbs { display: flex; align-items: center; gap: .5rem; font-size: var(--fs-xs); color: var(--muted-2); flex-wrap: wrap; }
.crumbs a:hover { color: var(--orange); }

/* --- Timeline (about) ----------------------------------------------------- */
.timeline { display: grid; gap: 0; border-left: 2px solid var(--line); padding-left: clamp(1.25rem, 3vw, 2.5rem); }
.tl-item { position: relative; padding-bottom: clamp(1.75rem, 3.5vw, 3rem); }
.tl-item:last-child { padding-bottom: 0; }
.tl-item::before {
  content: ''; position: absolute; left: calc(-1 * clamp(1.25rem, 3vw, 2.5rem) - 7px); top: .45rem;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--orange); border: 2.5px solid var(--sand);
}
.section--shell .tl-item::before { border-color: var(--shell); }
.tl-item__year { font-family: var(--font-display); font-size: .8125rem; font-weight: 700; letter-spacing: .12em; color: var(--orange); }
.tl-item h3 { font-size: var(--fs-h4); margin-block: .35rem .5rem; }
.tl-item p { color: var(--muted); font-size: var(--fs-sm); max-width: 62ch; }

/* --- Team / value cards --------------------------------------------------- */
.grid-cards {
  display: grid;
  /* 272px keeps a 1344px row at four columns and a 3-item row at three, so a
     four-card section never leaves a two-thirds-empty orphan row */
  grid-template-columns: repeat(auto-fit, minmax(272px, 1fr));
  gap: clamp(1rem, 2.2vw, 1.75rem);
}
.vcard {
  display: grid; gap: .65rem; align-content: start;
  padding: 1.5rem; border-radius: var(--r-md);
  background: var(--shell); border: 1px solid var(--line);
}
.vcard__icon { width: 42px; height: 42px; border-radius: 50%; display: grid; place-items: center; background: var(--orange-050); color: var(--orange); }
.vcard__icon svg { width: 20px; height: 20px; }
.vcard h3 { font-size: var(--fs-h4); }
.vcard p { color: var(--muted); font-size: var(--fs-sm); }

/* --- Reviews --------------------------------------------------------------
   The rating row on a product card and on a product page, and the review
   grid, are hidden until GET /api/reviews has something real for them. All
   three carry their own `display`, which beats the browser's [hidden] rule, so
   the attribute needs a rule of its own or an empty rating row would still
   take up space on every card and every buy box. Nothing styled here is a
   star average or a review count.
   -------------------------------------------------------------------------- */
.pcard__rating[hidden], .grid-reviews[hidden], .buybox__rating [data-rating-slot][hidden] { display: none; }

.reviews__count { font-size: var(--fs-xs); color: var(--muted); text-align: center; }
.reviews__empty {
  display: grid; gap: clamp(1rem, 2.5vw, 1.5rem);
  padding: clamp(1.5rem, 3.5vw, 2.5rem);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  background: var(--shell);
}
.reviews__empty-head { display: grid; gap: .5rem; }
.reviews__empty-head h3 {
  font-family: var(--font-display); font-size: clamp(1.35rem, 2.4vw, 1.85rem);
  font-weight: 700; letter-spacing: -0.025em; line-height: 1.15;
}
.reviews__empty > p { color: var(--muted); max-width: 70ch; }
.reviews__empty .vcard { background: var(--sand-2); }
/* The link colour is set HERE and only here, because this is the last stylesheet
   the pages load. site.css carried a rule to darken these two links and it was
   dead on arrival: same specificity, earlier file, so this line won and they
   kept measuring 3.50:1 while both files said they had been fixed.

   "Read the returns answer" and "What the warranty covers" are the two links a
   hesitant pre-order buyer clicks. `--orange-700` on the #F3ECE2 card is
   6.49:1, against 3.50:1 for `--orange`; the two are visually
   indistinguishable, which is why this took a measurement to find. */
.reviews__empty .vcard a { color: var(--orange-700); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
.reviews__empty-cta { align-items: center; gap: 1.25rem; margin-top: .25rem; }

/* --- Media gallery (about / lifestyle) ------------------------------------ */
.mediagrid { display: grid; grid-template-columns: repeat(12, 1fr); gap: clamp(.6rem, 1.2vw, 1rem); }
.mediagrid > * { border-radius: var(--r-md); overflow: hidden; position: relative; }
.mediagrid img { width: 100%; height: 100%; object-fit: cover; }
.mg-a { grid-column: span 7; aspect-ratio: 16/10; }
.mg-b { grid-column: span 5; aspect-ratio: 4/5; }
.mg-c { grid-column: span 5; aspect-ratio: 4/5; }
.mg-d { grid-column: span 7; aspect-ratio: 16/10; }
.mg-caption {
  position: absolute; left: .85rem; bottom: .85rem;
  padding: .4rem .75rem; border-radius: var(--r-pill);
  background: rgba(23,20,15,.72); color: #fff;
  backdrop-filter: blur(8px);
  font-size: var(--fs-xs); font-weight: 550;
}

/* --- Contact -------------------------------------------------------------- */
.contact-grid { display: grid; grid-template-columns: minmax(0,1.05fr) minmax(0,1fr); gap: clamp(1.5rem, 4vw, 4rem); align-items: start; }
.contact-rail { position: sticky; top: 110px; }
.field__err {
  font-size: var(--fs-xs); color: #B3261E; font-weight: 500;
  display: flex; align-items: center; gap: .35rem;
}
.input.is-invalid, .textarea.is-invalid { border-color: #B3261E; box-shadow: 0 0 0 3px rgba(179,38,30,.12); }
.link-arrow { justify-self: start; }
.contact-card { display: grid; gap: 1.25rem; padding: 1.75rem; border-radius: var(--r-lg); background: var(--shell); border: 1px solid var(--line); }
.contact-line { display: flex; gap: .85rem; align-items: flex-start; }
.contact-line svg { width: 19px; height: 19px; color: var(--orange); flex: none; margin-top: .2rem; }
.contact-line b { display: block; font-family: var(--font-display); font-size: .875rem; font-weight: 650; }
.contact-line span, .contact-line a { font-size: var(--fs-sm); color: var(--muted); }
.contact-line a:hover { color: var(--orange); }
.form-grid { display: grid; gap: 1rem; }
.form-grid--2 { grid-template-columns: repeat(2, minmax(0,1fr)); }

/* --- 404 ------------------------------------------------------------------ */
.err { display: grid; gap: 1rem; justify-items: center; text-align: center; padding-block: 0; position: relative; isolation: isolate; }
.err h1 { font-size: clamp(2.5rem, 5vw, 4rem); }
.err p { font-size: var(--fs-lead); }
/* the numeral is a deliberate ghost device: oversized, bleeding behind the
   copy, tinted with the brand orange rather than the page's own sand */
.err__code {
  position: absolute; left: 50%; top: 46%;
  transform: translate(-50%, -50%);
  z-index: -1;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(11rem, 30vw, 26rem);
  line-height: .7; letter-spacing: -0.07em;
  color: transparent;
  -webkit-text-stroke: 1.5px rgba(204, 92, 31, .30);
  user-select: none; pointer-events: none;
  white-space: nowrap;
}
.err__code::after {
  content: '404';
  position: absolute; inset: 0;
  color: rgba(204, 92, 31, .07);
  -webkit-text-stroke: 0;
}

/* --- Responsive ----------------------------------------------------------- */
@media (max-width: 1180px) {
  .usp { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .statrow { grid-template-columns: repeat(2, minmax(0,1fr)); gap: 1.75rem; }
}
@media (max-width: 1024px) {
  .contact-rail { position: static; }
  .contact-grid .form-grid { max-width: 560px; }
  .contact-grid .stage { max-width: 480px; }
  .grid-cards { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
  .hero__grid { grid-template-columns: 1fr; }
  .hero__copy { max-width: none; }
  /* Single column: the stage no longer sits to the right of the copy, it sits
     below it, so the headline has the whole page width and the 35rem cap that
     keeps it off the model on a desktop would wrap it into five lines on a
     tablet. `.hero__title` still carries the desktop clamp; this only lifts the
     cap and lets the existing type measure do the work. */
  .hero__title { max-width: min(24ch, 100%); }
  .hero__stage { aspect-ratio: 1 / 0.8; min-height: 340px; }
  /* One column: the stage spans the page, and the camera pills anchored to its
     left and right edges overhang by their own border. The phone rule that
     catches this only applies below 720. */
  .viewer__views { left: 1rem; right: auto; max-width: calc(100% - 5.5rem); }
  .split { grid-template-columns: 1fr; }
  .split--media-first .split__media { order: 0; }
  .steps, .press, .grid-cards { grid-template-columns: 1fr; }
  .faq-layout { grid-template-columns: 1fr; }
  .faq-nav { position: static; display: flex; flex-wrap: wrap; gap: .35rem; }
  .contact-grid { grid-template-columns: 1fr; }
  .mediagrid > * { grid-column: span 12 !important; aspect-ratio: 16/10 !important; }
}
@media (max-width: 720px) {
  /* The product, on the first screen.
   *
   * Stacked, the hero read: eyebrow, headline, three CTAs, four fact bullets, a
   * stock line, and THEN the thing being sold — at y=1077 against an 844 fold,
   * so a phone shopper met a screen of text and no product at all. The pre-order
   * is a bet on something the customer cannot touch, and the first screen was
   * the one place the model could have argued for it.
   *
   * Grid order, not a re-emit: the copy column keeps its DOM position so the
   * headline is still the first thing a screen reader reads.
   */
  .hero__grid { display: flex; flex-direction: column; gap: 1rem; }
  .hero__stage { order: -1; min-height: 240px; }
  .hero__copy { order: 1; gap: .85rem; }
  /* on a phone the hint sits above the camera pills rather than on top of them,
     and says something true for touch */
  .viewer__hint { left: 1rem; right: auto; bottom: 4rem; }
  .viewer__hint span { display: none; }
  .viewer__views { left: 1rem; right: 1rem; justify-content: space-between; max-width: calc(100% - 2rem); }
  .viewer__views button { flex: 1; padding-inline: .4rem; }
  .hotspot { width: 44px; height: 44px; }
  .hotspot__card { left: auto; right: 26px; max-width: min(200px, 52vw); }
}
@media (max-width: 640px) {
  .usp { grid-template-columns: 1fr; }
  .form-grid--2 { grid-template-columns: 1fr; }
  .hero__floatchip--a { display: none; }
}

/* --- Hero product switcher: three products ---------------------------------
   The row and the caption are one column, so they share a left edge at every
   width. As two independently positioned elements they were aligned to nothing:
   the row centred, the caption pinned in a corner, and the distance between
   them changing with every viewport. The column is centred on desktop and
   pinned left on a phone. */
.hero__switch {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .5rem;
}
.hero__switch .viewer__spec { position: static; top: auto; left: auto; }

/* Every button carries a full label and a short one; exactly one is displayed,
   so the control tightens on a phone instead of becoming a second control. */
.seg__short { display: none; }

@media (max-width: 720px) {
  /* Pinned left rather than centred: it then shares the caption's edge and
     cannot drift toward the camera tools as the viewport changes. */
  .hero__switch {
    left: 1rem;
    transform: none;
    max-width: calc(100% - 5.5rem);
  }
  .seg--onstage { padding: .2rem; gap: .1rem; }
  /* 44px is the smallest comfortable touch target. The row may be this tall
     because the 3D framing reserves the band above the model for it. */
  .seg--onstage button {
    min-height: 44px;
    padding: .4rem .55rem;
    font-size: .75rem;
    letter-spacing: -0.01em;
  }
  .seg__full { display: none; }
  .seg__short { display: inline; }
  /* The hint floated over the product on a phone. A touch user loses nothing:
     dragging is the obvious gesture and there is no scroll-to-zoom to explain.
     The model gets its face back. */
  .viewer__hint { display: none; }
  .viewer__views button { min-height: 44px; }
}


/* ==========================================================================
   THE PRE-ORDER
   The band that explains the offer, the three cards that answer the three
   objections a pre-order has to clear before anyone spends money, and the
   close-of-window counter.

   Everything here is built out of the existing tokens and the existing type
   scale — this is one more section of the same site, not a campaign page
   bolted onto it. The only new colour usage is the accent eyebrow, which is
   the site's own orange used in the one place the site needs to say "this
   part is different".
   ========================================================================== */

.eyebrow--accent { color: var(--orange-600); }
.eyebrow__dot {
  display: inline-block;
  width: .5rem; height: .5rem;
  border-radius: 50%;
  background: var(--orange);
  margin-right: .5rem;
  vertical-align: .06em;
  /* The one pulse on the site. Slow enough to read as "live", not as an
     alarm, and switched off entirely for reduced motion below. */
  animation: preorder-pulse 2.6s ease-in-out infinite;
}
@keyframes preorder-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(204,92,31,.5); }
  50%      { box-shadow: 0 0 0 .42rem rgba(204,92,31,0); }
}

.section--preorder { background: var(--sand); }

/* Two dark surfaces separated by a thin band of sand read as one section that
   has come apart. The countdown card following the risk band is a card, not a
   band, so it takes the lighter treatment and the two stop rhyming. */
.preclose--page { background: var(--deep-2); }

/* --- the four figures --- */
.statrow--preorder { padding-top: .25rem; }
.stat--preorder { padding-left: clamp(.9rem, 1.4vw, 1.4rem); border-left: 2px solid var(--orange-100); }
.stat--preorder .stat__value { color: var(--ink); }
.stat--preorder .stat__label { color: var(--ink); }

/* --- the three cards --- */
.precards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.75rem);
  /* Cards in the same row are the same height, which is what lets the footer
     rules inside them line up. `1fr` auto rows do it without a fixed height. */
  grid-auto-rows: 1fr;
}
/* Three cards in a row whose footers did not line up: each is as tall as its
   own copy, so the hairline above the closing line and the closing line itself
   sat at three different heights. A grid with a fixed row pattern could not
   fix it, because the two body paragraphs have different heights in each card.
   A flex column with the footer pushed to the bottom can: the footer is the
   last item and `margin-top: auto` puts all three on one line regardless of
   what is above them. */
.precard {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: .6rem;
  padding: clamp(1.25rem, 2vw, 1.75rem);
  background: #fff;
  border: 1px solid var(--sand-3);
  border-radius: var(--r-md);
  box-shadow: 0 1px 2px rgba(23,20,15,.04);
  transition: box-shadow .3s ease, transform .3s ease, border-color .3s ease;
}
.precard:hover {
  border-color: var(--orange-100);
  box-shadow: 0 14px 34px -20px rgba(23,20,15,.28);
  transform: translateY(-2px);
}
.precard__num {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-xs);
  letter-spacing: .1em;
  color: var(--orange-600);
}
.precard__title {
  font-family: var(--font-display);
  font-size: clamp(1.125rem, 1.6vw, 1.375rem);
  letter-spacing: -0.02em;
  margin: 0;
}
.precard p { font-size: var(--fs-sm); color: var(--ink-2); margin: 0; max-width: 46ch; }
.precard__foot {
  margin-top: auto !important;
  padding-top: .85rem;
  border-top: 1px solid var(--sand-2);
  font-size: var(--fs-xs) !important;
  font-weight: 650;
  color: var(--muted) !important;
}
/* The gap between the last body paragraph and the rule is spacing, not the
   auto margin that does the aligning. */
.precard__foot { padding-top: .85rem; }

/* --- the closing date and the counter ---
   One row on a laptop: the reason on the left, the arithmetic on the right.
   The counter is deliberately small — it is a fact about the production run,
   not a klaxon. */
.preclose {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: clamp(1rem, 2.5vw, 2.5rem);
  padding: clamp(1.1rem, 2vw, 1.6rem) clamp(1.25rem, 2.4vw, 2rem);
  background: var(--deep);
  color: #fff;
  border-radius: var(--r-md);
}
/* The deadline sentence and the band copy are the two blocks the running-text
   default missed — one is a flex item beside the counter, the other sits in a
   band whose scrim is a sibling, so neither is a plain `.wrap p`. They were
   running 177 and 95 characters to the line. */
/* The deadline sentence, at its own measure.
 *
 * Written as a direct `max-width` rather than through the inherited `--max-w`
 * ceiling: this element sits inside the countdown card, which the viewport
 * ceiling already bounds, and a percentage max-width inside that chain
 * resolves against the container rather than the text. A plain length is the
 * one form that cannot be reinterpreted. 54ch at 14.5px is about 400px. */
.preclose__copy {
  font-size: var(--fs-sm);
  color: rgba(255,255,255,.78);
  max-width: 54ch;
}
@media (max-width: 900px) {
  .preclose__copy { max-width: 100%; }
}
.preclose__copy b { color: #fff; }
.preclose__count { display: flex; gap: clamp(.6rem, 1.4vw, 1.25rem); }
.preclose__unit { display: grid; justify-items: center; gap: .15rem; min-width: 3.1rem; }
.preclose__unit b {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  line-height: 1;
  letter-spacing: -0.03em;
  /* Tabular figures: without this the counter jitters a pixel or two every
     second as the digit widths change, which reads as a broken page. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}
.preclose__unit small {
  font-size: .625rem;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: rgba(255,255,255,.55);
}
.preclose__done { font-size: var(--fs-sm); color: rgba(255,255,255,.85); max-width: 48ch; }
.preclose__count.is-closed { flex: 1 1 auto; }

/* --- the four facts under the hero buttons ---
   A grid rather than an inline list: at 1440 these sit as two columns under
   the buttons, at 720 as one, and the markers never wrap onto a second line. */
.hero__facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, max-content));
  gap: .45rem clamp(1rem, 2.2vw, 1.75rem);
  margin: .25rem 0 0;
  padding: 0;
  list-style: none;
}
.hero__facts li {
  position: relative;
  padding-left: 1.35rem;
  font-size: var(--fs-sm);
  color: var(--ink-2);
}
.hero__facts li::before {
  content: "";
  position: absolute;
  left: 0; top: .42em;
  width: .7rem; height: .4rem;
  border-left: 2px solid var(--orange);
  border-bottom: 2px solid var(--orange);
  transform: rotate(-45deg) translateY(-.06em);
  transform-origin: left bottom;
}
.hero__facts b { color: var(--ink); font-weight: 650; }

/* --- product card: the delivery promise under the price --- */
.pcard__ship {
  margin: .5rem 0 0;
  font-size: var(--fs-xs);
  color: var(--muted);
}
.pcard__price s { color: var(--muted-2); font-weight: 400; }

/* --- buy box: the price block grown up ---
   A pre-order price needs four things said in order: what it costs, what it
   is against, what is saved, and why. The old block said only the first. */
/* The price block grows by two elements, not by a new component. `.now`,
   `.aud` and `.was` keep the shop.css rules they already had; only the saving
   badge is new, and it is the site's accent rather than the muted green used
   for in-stock badges, because a pre-order saving is the reason to act. */
.buybox__price .now { font-size: clamp(2rem, 3.4vw, 2.75rem); letter-spacing: -0.04em; line-height: 1; }
.buybox__price .price-save {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--orange-700);
  background: var(--orange-050);
  border: 1px solid var(--orange-100);
  border-radius: var(--r-pill);
  padding: .3rem .65rem;
  align-self: center;
}
.price-note { margin: .7rem 0 0; font-size: var(--fs-sm); color: var(--ink-2); max-width: 54ch; }
.price-note b { color: var(--ink); }
.price-delivery {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  margin: .6rem 0 0;
  padding: .6rem .8rem;
  background: var(--sand-2);
  border-radius: 10px;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  max-width: 54ch;
}
.price-delivery svg { width: 16px; height: 16px; flex: none; margin-top: .15rem; color: var(--orange-600); }
.buybox__delivery--window { color: var(--ink); }
.buybox__delivery--window b { color: var(--ink); }

/* --- cart drawer: the reservation line on each item --- */
.lineitem__preorder { font-size: var(--fs-xs); color: var(--orange-700); margin-top: .15rem; }
.lineitem__was { display: block; font-size: var(--fs-xs); color: var(--muted-2); text-decoration: line-through; font-weight: 400; }

/* --- buy bar: the delivery promise --- */
.buybar__preorder { color: var(--orange-700); font-weight: 600; }

@media (max-width: 1024px) {
  .precards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The odd card out spans the row rather than sitting alone in a third
     column with a hole beside it — three cards into two columns is the
     classic way a deliberate grid starts to look accidental. */
  .precards > :last-child:nth-child(odd) { grid-column: 1 / -1; }
  /* Alone on its row, the odd card sets its own height rather than matching
     the row above it, so its footer sits on the card's own baseline instead of
     floating in the middle of a taller box. */
  .precards > :last-child:nth-child(odd) { grid-row: auto; align-self: start; }
  .precards > :last-child:nth-child(odd) .precard__foot { margin-top: .35rem !important; }
}
@media (max-width: 720px) {
  .statrow--preorder { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .precards { grid-template-columns: minmax(0, 1fr); }
  .precards > :last-child:nth-child(odd) { grid-column: auto; }
  .preclose { flex-direction: column; align-items: flex-start; }
  .hero__facts { grid-template-columns: minmax(0, 1fr); }
  .preclose__unit { min-width: 2.75rem; }
}
@media (prefers-reduced-motion: reduce) {
  .eyebrow__dot { animation: none; box-shadow: 0 0 0 .18rem rgba(204,92,31,.28); }
  .precard:hover { transform: none; }
}

/* --- The pre-order page: price comparison table ---
   Four rows of three columns is the one place on the site where a shopper is
   comparing numbers rather than reading sentences, so it gets figures in
   tabular numerals, the regular price struck through, and the pre-order price
   carrying the weight. The bundle row is marked because it is the answer most
   people arrive at, and the markup should say so rather than a badge. */
.pricetable {
  background: #fff;
  border: 1px solid var(--sand-3);
  border-radius: var(--r-md);
  overflow: hidden;
}
.pricetable__head,
.pricetable__row {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, .8fr) minmax(0, 1fr);
  gap: .75rem;
  align-items: baseline;
  padding: .85rem clamp(.9rem, 1.6vw, 1.35rem);
}
.pricetable__head {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--muted);
  background: var(--sand);
  border-bottom: 1px solid var(--sand-3);
}
.pricetable__row + .pricetable__row { border-top: 1px solid var(--sand-2); }
.pricetable__row--best { background: var(--orange-050); }
.pricetable__name { font-size: var(--fs-sm); font-weight: 600; color: var(--ink); }
.pricetable__was {
  font-size: var(--fs-sm);
  color: var(--muted-2);
  text-decoration: line-through;
  font-variant-numeric: tabular-nums;
}
.pricetable__now {
  display: grid;
  gap: .1rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.0625rem;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.pricetable__now small {
  font-family: var(--font-body, inherit);
  font-weight: 600;
  font-size: var(--fs-xs);
  letter-spacing: 0;
  color: var(--orange-700);
}
.pricetable__foot {
  margin: 0;
  padding: .8rem clamp(.9rem, 1.6vw, 1.35rem) .95rem;
  border-top: 1px solid var(--sand-3);
  background: var(--sand);
  font-size: var(--fs-xs);
  color: var(--muted);
}
.preclose--page { background: var(--deep); }
@media (max-width: 640px) {
  .pricetable__head { display: none; }
  .pricetable__row {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "name now" "was now";
    align-items: center;
    gap: .15rem .75rem;
  }
  .pricetable__name { grid-area: name; }
  .pricetable__was { grid-area: was; }
  .pricetable__now { grid-area: now; text-align: right; }
}

/* --- Accordions on a dark section ---
   The accordion was built for the light sections on the product and support
   pages and its trigger is `color: var(--ink)` — near-black. Dropped into
   `.section--deep` (#0F2E2B) that is a contrast ratio of about 1.3:1, so the
   three pre-order objection headers on /preorder.html — "what if the date
   slips", "what if I change my mind", "what if you have not built one before"
   — rendered as three empty rows. They are the questions the page exists to
   answer, so this is the one dark-section override that has to exist.

   The ring, the minus/plus glyphs and the dividers all inherit from the light
   palette for the same reason and need the same treatment. */
.section--deep .acc,
.band .acc { border-top-color: rgba(255,255,255,.18); }
.section--deep .acc__item,
.band .acc__item { border-bottom-color: rgba(255,255,255,.18); }
.section--deep .acc__trigger,
.band .acc__trigger { color: #fff; }
.section--deep .acc__trigger:hover,
.band .acc__trigger:hover { color: var(--orange-300); }
.section--deep .acc__icon,
.band .acc__icon { border-color: rgba(255,255,255,.34); }
.section--deep .acc__icon::before,
.section--deep .acc__icon::after,
.band .acc__icon::before,
.band .acc__icon::after { background: #fff; }
.section--deep .acc__item.is-open .acc__icon,
.band .acc__item.is-open .acc__icon { background: var(--orange); border-color: var(--orange); }
.section--deep .acc__inner,
.band .acc__inner { color: rgba(255,255,255,.76); }
.section--deep .acc__inner a,
.band .acc__inner a { color: #fff; }
.section--deep .acc__inner li::before,
.band .acc__inner li::before { background: var(--orange-300); }

/* --- The pre-order stat band on a phone ---
   Two columns at 390px is narrower than the word "December" set at 36px, so
   the delivery month — on the page whose entire argument is the delivery
   window — was clipped mid-word with its superscript 2026 entirely off screen.
   One tile per row below 480px, and the value is allowed to scale with the
   viewport rather than flooring at a size that cannot fit two columns. */
@media (max-width: 480px) {
  .statrow--preorder { grid-template-columns: minmax(0, 1fr); gap: .85rem 0; }
  .statrow--preorder .stat--preorder { padding-block: .35rem; }
  .stat__value { font-size: clamp(1.75rem, 10vw, 2.5rem); overflow-wrap: anywhere; }
}

/* --- The hero is measured to the fold, at three heights ----------------
   At 1440x900 the first screen used to slice horizontally through the fact
   row's tick marks; at 1280x800 it cut the black bundle pill in half; at
   1366x768 the primary button itself was 38px below the fold. Each is a page
   that ends on a half-drawn element, which reads as broken rather than as
   continuing.

   The hero gives up, in order: the skip link (a convenience — the same
   destination is in the nav), the eyebrow (the offer is in the sub-line), and
   then line-height on the display headline. The three priced CTAs and the four
   facts are the last things to move, because they are the decision. */
@media (max-height: 820px) and (min-width: 861px) {
  .hero__facts { gap: .35rem clamp(1rem, 2vw, 1.5rem); }
  .hero__facts li { font-size: var(--fs-xs); }
  .hero__sub { max-width: min(46ch, 100%); }
  /* The headline gives up size and air first: it is the tallest thing here and
     the only one that can be read from across the room at a smaller size.

     The cap is 64px, and it sits deliberately at the FLOOR of the rule below
     rather than under it. Below 1000px wide the display clamp is already on
     its 4rem floor, so this block only binds on a narrow, short laptop; a
     lower cap — 60px was the old value — would undo the increase on the very
     screens the copy column is now widest for. What this block is really for
     here is the clearance: 13px of it under the facts at 1280x800.

     No local clamp on `--fs-display-xl` below this: it is the site's display
     scale and the original hero used it directly. */
  /* No headline size here. It used to carry a cap, and the cap silently stopped
     working when the base rule moved below it in the file — a media query does
     not add specificity, so the later `.hero__title` rule won at every height.
     The cap now lives in the one block after the base rule, where it applies. */
  .hero__title { line-height: 1.02; }
  .hero__skip { display: none; }
  /* The copy column is a share of a grid whose share grows with the viewport,
     so at 1600 the line is longer but the block is not shorter. Past 1280 the
     column stops widening. */
  /* The copy column is capped so the headline stops widening with the viewport
     — but a hard 38rem plus a 1.16fr stage means the STAGE takes all the
     compression on a short laptop, and at 1280x800 it went portrait (539x777,
     an aspect of 0.69 against the intended 0.82) with the deck floating in dead
     grey above and below it. Both columns yield now, and the stage keeps its
     shape. */
  .hero__grid { grid-template-columns: minmax(0, 34rem) minmax(0, 1.16fr); }
  .hero__copy { gap: 1.1rem; }
}

/* --------------------------------------------------------------------------
   The hero at the two commonest laptop sizes.

   1280x800 and 1366x768 are where the copy column runs out of room: the
   headline takes four lines at 1280 because the column is narrower there, and
   four lines of 59px display type is 235px of a 768px screen. Capping the size
   brings it back to three and, with a tighter block rhythm, the four facts fit
   above the fold again — they were 8px past it at 1280 and 41px past at 1366.
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   The hero headline is sized for the space to the RIGHT of the copy column.

   This rule used to be `clamp(2.4rem, 3.6vw, 4.125rem)`, and the reason was
   sound at the time: the headline set INSIDE `.hero__copy`, which was capped at
   a 40ch measure (417px) and did not grow with the window, so `--fs-display-xl`
   put 104px type in a 417px column on a 1920 screen and broke the sentence into
   SIX lines — 587px of display type, with the four fact bullets 409px below the
   fold. The cap was the fix for that.

   What changed is where the headline is allowed to sit. The sentence is the
   brand's one line and the most-seen thing on the site, while the column
   beneath it — sub-line, three buttons, four facts — is legible at its own
   width and does not need the same measure. So the column widened to 44rem and
   each child inside it kept its own measure; the headline alone now runs wider
   than the copy, spending the empty gutter and the space in front of the 3D
   stage, and nothing under it moves.

   Two independent brakes stop it misbehaving, and both are needed because a
   width cannot see height:

     the 3D stage      at 40rem the line ends at 688px against a stage that
                       starts at 639 at 1440 and 631 at 1280, so widening the
                       headline never covers the model
     the height cap    the headline sets in three lines at every size below the
                       40rem cap, and the hero grows with it, so the four facts
                       are what pay for the new size. The headline clears them
                       on any window 740px high or taller; below that the cap
                       after this rule takes over.

   1440x900 lands on 74px where the original was 52px; 1920x1080 on 90px where it
   was 66px, both in three lines. That is 0.8 of the doubled size — the headline
   was taken to 92px and then brought back 20%.
   -------------------------------------------------------------------------- */
.hero__title { font-size: clamp(3.2rem, 5.12vw, 5.6rem); }

/* ==========================================================================
   The height guard, and why it has to sit BELOW the rule above.

   A media query adds no specificity. Two `.hero__title` rules of equal
   specificity are decided by source order alone, so a guard written above the
   base rule loses at every height, silently, whatever its condition says. That
   is exactly what happened here: the cap looked correct and did nothing, and
   the fold it was protecting was broken from 740px down.

   This is a continuous guard rather than a `max-height` step. A step has to be
   placed at one height for every width, and the headline breaks the fold at a
   different height at each width, so any single threshold either shrinks a
   window that had room to spare — 1366x768 still has 15px at the full size —
   or misses one that does not. `8vh` caps the headline by the height actually
   available, which is what the fold depends on, and it does it without a jump
   as a window is dragged.

   The clamp still decides everything at normal laptop and desktop sizes: 8vh is
   larger than the clamp at 1280x800, 1366x768 and 1440x900, so those are
   untouched, and it only takes over at heights under about 740px. Desktop only,
   because a phone in landscape would otherwise scale the headline to 31px.
   ========================================================================== */
@media (min-width: 861px) {
  .hero__title { font-size: min(clamp(3.2rem, 5.12vw, 5.6rem), 8vh); }
}

/* The short-laptop rhythm: tighter vertical air so the four facts stay above a
   800px fold. It carries no headline size of its own. */
@media (max-height: 820px) and (min-width: 861px) {
  .hero__facts { grid-template-columns: repeat(2, minmax(0, max-content)); gap: .3rem 1rem; }
  .hero__facts li { font-size: var(--fs-xs); }
  .hero__ctas { gap: .55rem; }
  .hero { padding-block: clamp(.75rem, 1.4vw, 1.25rem) clamp(1rem, 2vw, 1.5rem); }
}
/* A short window where the hero has already gone single-column: the stage would
   otherwise take 0.8 of a 1024-wide page — 819px — and go portrait. */
@media (max-height: 800px) and (max-width: 1024px) {
  .hero__stage { aspect-ratio: auto; height: min(52vh, 420px); min-height: 300px; }
}
@media (max-height: 860px) and (min-width: 861px) {
  .hero { padding-block: clamp(.85rem, 1.6vw, 1.4rem) clamp(1.25rem, 2.4vw, 1.85rem); }
  .hero__ctas { gap: .5rem; }
  .hero__skip { display: none; }
  .hero__title { line-height: .98; }
}
@media (max-height: 800px) and (min-width: 861px) {
  /* A 1366x768 laptop, which is still one of the most common screens there
     is. The eyebrow goes: "LAUNCHING SALE · 15% OFF THE FIRST PRODUCTION RUN"
     is a restatement of the sub-line directly beneath it. */
  .hero__copy > .eyebrow { display: none; }
  .hero__sub { font-size: var(--fs-body); }
  .hero__stock { display: none; }
}

/* --- An odd card in a two-column grid spans the row at every width ---
   The rule existed only below 1024px, so on a desktop the bundle — the
   highest-value item on the site — sat alone in the left column with the whole
   right half of the row empty, which reads as a failed grid rather than a
   deliberate full-width spotlight. */
@media (min-width: 1025px) {
  .grid-products--2 > :last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* --- Grid and flex children are allowed to be narrower than their content ---
   `min-width: auto` on a grid item means "at least as wide as my widest
   unbreakable content", and `min-content` on a tracked-out eyebrow is a long
   word. This is the same class of overflow as the buy box and the section
   heading, and rather than keep discovering it one element at a time, every
   content block on the site gets the permission once. Nothing that fits is
   affected; anything that does not now wraps or clips at its own edge instead
   of widening the page. */
.reviews__empty,
.reviews__empty-head,
.reviews__empty-head > *,
.grid-cards > *,
.grid-products > *,
.grid-reviews > *,
.grid-addons > *,
.stack > *,
.split > *,
.steps > *,
.statrow > *,
.acc > *,
.vcard > * { min-width: 0; }

/* The empty-reviews panel is the one padded card that sits directly in a
   content column, so its own children carry the ceiling. */
.reviews__empty > * { max-width: calc(100vw - 2 * var(--gutter) - 3rem); }

/* A counted quantity is one unit. "500 / decks" split across two lines because
   the element that the live count is painted into is its own inline box, and a
   number broken from its noun reads as a layout fault rather than as a figure.
   The number and its unit travel together. */
.hero__count { white-space: nowrap; }

/* The three facts, on the first screen of the pre-order page at every width.
   Before this the earliest static "December 2026" on a 390px phone was 968px
   down — past the fold on the page whose entire subject is the delivery
   window. */
.pagehead__facts {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem .65rem;
  margin-top: .1rem;
}
.pagehead__facts span {
  font-size: var(--fs-xs);
  color: var(--muted);
  padding: .32rem .7rem;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--shell);
  white-space: nowrap;
}
.pagehead__facts b { color: var(--ink); font-weight: 650; }
@media (max-width: 520px) {
  /* Three nowrap pills wrap to three rows; stacked full-width they read as a
     list, which is what they are. */
  .pagehead__facts { display: grid; gap: .35rem; }
  .pagehead__facts span { white-space: normal; }
}

.band__copy p { max-width: min(54ch, 100%); }

/* ==========================================================================
   The model switcher, between a phone and a wide desktop.

   "The Oasis Platform / The Water Hammock / The 2pc Bundle" at full letter
   widths is about 450px, and the stage it is centred over is narrower than
   that from roughly 720px to 1150px. The pill therefore pushed past the right
   edge of the page at 1024 — the breakpoint where exactly one column of the
   hero grid has become the whole width. Below 1025 it takes the short labels
   the phone layout already carries, which are the same three controls.
   ========================================================================== */
@media (max-width: 1180px) {
  .seg--onstage .seg__full { display: none; }
  .seg--onstage .seg__short { display: inline; }
  .seg--onstage { padding: .22rem; gap: .12rem; }
  .seg--onstage button { padding: .42rem .7rem; font-size: .8125rem; }
}

/* The switcher's box may not be narrower than its own buttons.
 *
 * `.hero__switch` is absolutely positioned with `max-width: calc(100% - 2rem)`,
 * so on a stage narrower than the control the box shrank to 147px while the
 * three labels still needed 225px — the labels painted straight onto the model
 * with no pill behind them, and the stage's `overflow: hidden` ate the "P" of
 * Platform and the "e" of Bundle. Measured between 860 and 960; 1024 upward was
 * always clean.
 *
 * `width: max-content` with the centring transform already on the parent means
 * the pill is always exactly as wide as its content, and the short labels come
 * on at 1180 well before it would matter. */
.hero__switch { width: max-content; }
.seg--onstage { min-width: 0; }
.seg--onstage button { min-width: 0; }


/* ==========================================================================
   Card grids, at the width where they stop being readable.

   "The claims we left out." is four cards in one row at 1440 — about 187px
   each, carrying a heading and a paragraph at roughly 31 characters to the
   line. That is the section that answers the objection a pre-order has to
   clear, and it was the least legible block on the page. Two by two gives each
   claim about 380px and a ~55-character measure.

   The accessory grid has the same problem from the other end: nine cards 2-up
   on a phone at a 12px gutter wrap their own names and run their body copy at
   about 18 characters. One up below 480.
   ========================================================================== */
/* The claims grid.

   Two columns made each card 658px wide with an 84-character measure — longer
   than the 62ch the rest of the site uses. Three fixed the measure but orphaned
   the fourth claim alone in row two with 866px of dead space beside it, which
   is a different defect. Four across at desktop gives each claim ~320px and a
   ~52-character measure with no orphan; two columns in the middle; one on a
   phone. */
.grid-cards--2up { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1240px) {
  .grid-cards--2up { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
  .grid-cards--2up { grid-template-columns: minmax(0, 1fr); }
  .grid-addons { grid-template-columns: minmax(0, 1fr); }
}
@media (min-width: 721px) and (max-width: 1024px) {
  .grid-addons { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}


/* ==========================================================================
   The hero's two floating controls, at the widths where they meet.

   The product switcher is a centred pill across the top of the stage and the
   viewer's two circular tools sit in the top-right corner. Between about 860px
   and 1140px the stage is narrow enough that they occupy the same strip: at
   1024 they overlapped by 19px, so the reset chip covered the right end of the
   "Bundle" segment, and at 900 the switcher's own content was 225px inside a
   164px pill — the control overflowed its own background and the tools sat on
   top of two of its three labels. A shopper tapping "Bundle" hit Reset.

   Below 1140px the tools move to the bottom-right, under the switcher and
   clear of the drag strip, which is pinned to the left.
   ========================================================================== */
/* Three controls share the stage: the product switcher across the top, the
   camera pills bottom-left plus the drag hint bottom-right, and the two
   circular tools. In the 860–1140px band the stage is short enough that the
   top and bottom rows are all the room there is, so the tools cannot go in
   either — parking them bottom-right, as the first attempt did, put them
   straight through the camera pills.

   They take the middle of the right edge instead, clear of all three. It is
   also where a hand expects them: out of the way of everything the eye reads,
   and still a fixed target. */
/* Measured, not guessed. The switcher's right edge and the tools' left edge
   meet at 1280 and overlap by 11px there, so the band starts at 1280, not
   1140. The drag hint is hidden across the same range because it shares the
   bottom strip with the camera pills — a clash that predates this change and
   only shows below 1240 — and it is the one control of the four that is pure
   instruction: dragging works whether or not it is on screen. */
/* The band stops at 1180, not 1320.
 *
 * This file is linked after shop.css, so at equal specificity this rule beat
 * the `@media (min-width: 1181px)` rule that moves the tools back to the top
 * right. Between 1181 and 1320 the tools therefore stayed on the middle of the
 * right edge — over the deck's own D-rings: 39.6px of overlap at 900x900 and
 * 8.1px at 1280. The two bands now meet exactly at 1180/1181 and cannot
 * overlap. */
@media (max-width: 1180px) {
  .viewer__tools { top: 50%; bottom: auto; transform: translateY(-50%); }
  .viewer__hint { display: none; }
}

/* --------------------------------------------------------------------------
   Radii, from the scale.

   `.precard` and `.preclose` read `var(--radius-lg, 14px)` and that token was
   defined nowhere in the project — so every pre-order card and the deadline
   card have been rendering at a literal 14px, one of the two values the
   previous round set out to remove. They take the token that matches `.vcard`,
   the card system they sit in.
   -------------------------------------------------------------------------- */
.precard,
.preclose { border-radius: var(--r-md); }

/* --------------------------------------------------------------------------
   The viewer's own controls, at every width.

   The project states 44px as its minimum touch target and applies it to the
   camera pills and the hotspots, but the two circular tools — auto-rotate and
   reset — were 38px at every width, including on a phone. They are the only
   controls that change what the hero does, and they were the smallest thing on
   it.
   -------------------------------------------------------------------------- */
.vtool { width: 44px; height: 44px; }
.vtool svg { width: 18px; height: 18px; }

/* The footer's link rows. On a phone the padding that fixes this is applied;
   on a desktop the same links sit in 19–21px rows, which is under the target
   for a trackpad and well under it for a touchscreen laptop. The padding is
   applied at every width and the column's own rhythm absorbs it. */
.footer__col a,
.footer__legal a { display: inline-block; padding-block: .42rem; }
@media (max-width: 720px) {
  .footer__col a,
  .footer__legal a { padding-block: .68rem; }
}

/* The countdown's own deadline label. It sits above the four numbers, inside
   the same component, so a page that mounts a counter cannot show four bare
   figures with nothing saying what they count to. */
.preclose__count { flex-wrap: wrap; align-items: baseline; }
.preclose__label {
  flex: 1 1 100%;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  opacity: .72;
}


/* --------------------------------------------------------------------------
   Two AA failures the reviews measured, both on tokens used sitewide.

   The footer blurb was #776E61 on the near-black footer: 3.82:1. Muted greys
   are chosen against light backgrounds and this one had no dark-surface
   variant at all.

   The eyebrow is small text — 11.5px/700 — so it needs 4.5:1, and it rendered
   in TWO colours: some instances took the base `--orange` (#B44E14, 4.43:1)
   and some the `--orange-700` I set on three selectors, which missed the ones
   not inside a section head. Half a fix is why the next round still measured a
   failure. The dark step is applied to the component itself, and the dark-band
   override below it — `.section--deep .eyebrow` — already had a lighter step
   for dark surfaces and still wins, because it is more specific.
   -------------------------------------------------------------------------- */
.eyebrow { color: var(--orange-700); }
.footer__blurb { color: #A79E92; }

/* ==========================================================================
   The pre-order timeline.

   Four steps a customer can take in at a glance: reserve today, the code ends
   on a published date, the deck ships across two months, it lands by a third.
   Before this those dates lived in four separate paragraphs.

   A horizontal row on a wide screen with a rule drawn through the dots; a
   vertical list on a phone, where four columns would be four words wide.
   ========================================================================== */
.timeline {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(1rem, 2.4vw, 2.25rem);
  margin: 0; padding: 0;
  list-style: none;
  counter-reset: none;
}
.tl {
  position: relative;
  display: grid;
  gap: .3rem;
  align-content: start;
  padding-top: 1.9rem;
}
/* The rule runs behind the dots, dot to dot — not off the ends of the row,
   which would read as a line leading nowhere. */
.tl::before {
  content: '';
  position: absolute;
  top: .62rem;
  /* Dot to dot. `left: 12px` clears the dot's own 12px width and `right` clears
     the next one plus the gap, so the rule starts at the first dot and stops at
     the last rather than running off both ends of the row. */
  left: 12px;
  right: calc(-1 * clamp(1rem, 2.4vw, 2.25rem) - 12px);
  height: 2px;
  background: var(--line-2);
}
/* No rule after the last dot — a line leading nowhere reads as a fifth step
   that failed to load. */
.tl:last-child::before { display: none; }
.tl__dot {
  position: absolute;
  top: .31rem; left: 0;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--orange-btn);
  box-shadow: 0 0 0 4px var(--sand-1);
}
.tl__when {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--orange-700);
}
.tl__title {
  font-family: var(--font-display);
  font-size: var(--fs-h4);
  font-weight: 650;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.tl__body { font-size: var(--fs-sm); color: var(--ink-2); max-width: 34ch; }

@media (max-width: 900px) {
  .timeline { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 1.75rem; }
  .tl:nth-child(2n)::before { display: none; }
}
@media (max-width: 560px) {
  .timeline { grid-template-columns: 1fr; gap: 1.5rem; }
  .tl { padding-top: 0; padding-left: 1.85rem; }
  /* vertical rule on a phone */
  .tl::before {
    top: 1.1rem; left: .31rem; right: auto; bottom: calc(-1.5rem);
    width: 2px; height: auto;
  }
  .tl:last-child::before { display: none; }
  .tl__body { max-width: none; }
}

/* ==========================================================================
   "On the water in under ten minutes" — three steps and the loop.

   The three steps used to carry three paragraphs of instruction. The steps
   themselves are the whole message; the detail lives in the manual.
   ========================================================================== */
.step__num {
  display: grid;
  place-items: center;
  width: 40px; height: 40px;
  border-radius: 50%;
  background: var(--orange-btn);
  color: #fff;
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 700;
  margin-bottom: .35rem;
}
/* The animation slot. It holds the space its content will need so the page
   does not reflow when the loop is dropped in. */
.loop { margin: clamp(1rem, 2.5vw, 1.75rem) 0 0; }
.loop__frame {
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  border: 1px dashed var(--line-2);
  border-radius: var(--r-lg);
  background: var(--sand-1);
}
.loop__note {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted-2);
}
.loop figcaption {
  margin-top: .7rem;
  font-size: var(--fs-sm);
  color: var(--muted);
  text-align: center;
}

/* ==========================================================================
   Technical diagrams — the three drawings on the about page.

   Inline SVG rather than images, for four reasons that all matter here:
   the labels stay REAL TEXT, so they reflow instead of scaling into
   illegibility on a phone, they can be selected and translated, they inherit
   this page's type and colour tokens, and they cost no extra request and no
   new CSP hash.

   The viewBox is deliberately narrow (~440 units). Type set at 12px in viewBox
   units therefore lands at roughly 11px on a 390px screen — readable — while a
   desktop cap of 560px scales it up to about 15px rather than letting a
   1400-wide column blow the drawing up to twice its intended size.
   ========================================================================== */
.fig {
  margin: 0;
  background: var(--sand);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: clamp(1rem, 2.2vw, 1.6rem);
}
.fig svg { display: block; width: 100%; max-width: 520px; height: auto; margin-inline: auto; }
.fig figcaption {
  margin-top: 1rem;
  font-size: var(--fs-sm);
  color: var(--muted);
  max-width: 46ch;
}

/* Diagram type. Everything here is `fill`, not `color`, because the shapes
   around it are drawn with `fill` and the two must not be confused. */
.fig text { font-family: var(--font-display); fill: var(--ink-2); }
/* These sizes are set by the PHONE, not the desktop. A 440-unit viewBox
   rendering into a 316px column is a scale of 0.72, so 12.5px of drawing type
   arrived on a handset as 9px — measured, and unreadable. At 15.5 it lands at
   about 11px on a phone and 18px on a desktop, which is what a diagram label
   should be on each. The text was cut back to match: fewer labels, and each one
   short enough not to overrun the 100-unit margin it sits in. */
.fig .d-title { font-size: 15px; font-weight: 800; letter-spacing: .1em; fill: var(--muted-2); }
.fig .d-label { font-size: 15.5px; font-weight: 650; }
.fig .d-note { font-size: 13.5px; font-weight: 500; fill: var(--muted); }
.fig .d-dim { font-size: 14px; font-weight: 700; fill: var(--orange-700); }

/* Diagram parts, shared by all three drawings so they read as one set. */
.fig .d-skin { fill: var(--sand-3); stroke: var(--ink); stroke-width: 1.3; }
.fig .d-skin-2 { fill: var(--sand-2); stroke: var(--ink); stroke-width: 1.3; }
.fig .d-glue { fill: var(--orange-100); stroke: var(--orange); stroke-width: 1.1; }
.fig .d-teak { fill: var(--teak); stroke: var(--teak-dark); stroke-width: 1.2; }
.fig .d-thread { stroke: var(--muted-2); stroke-width: 1; }
.fig .d-lead { stroke: var(--line-2); stroke-width: 1; }
.fig .d-rule { stroke: var(--line-2); stroke-width: 1; stroke-dasharray: 4 3; }
.fig .d-accent { stroke: var(--orange); stroke-width: 1.4; fill: none; }
.fig .d-brush { stroke: rgba(255,255,255,.38); stroke-width: 1; }
/* An image or video dropped into the slot fills it and loses the placeholder
   styling without any other change. */
.loop__frame > img,
.loop__frame > video { width: 100%; height: 100%; object-fit: cover; border-radius: var(--r-lg); }
.loop__frame:has(> img, > video) { border: 0; background: none; }
