/* ==========================================================================
   OASIS INFLATABLES — core.css
   Design tokens, reset, typography, buttons, forms, utilities.
   Target: Firefox 115+, 1280×800 laptop and up.
   ========================================================================== */

/* --- Fonts (self-hosted, latin subset) ------------------------------------ */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url('../fonts/archivo-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --- Tokens --------------------------------------------------------------- */
:root {
  /* Brand + surfaces */
  --orange:        #CC5C1F;
  --orange-600:    #B44E14;
  --orange-700:    #933E0E;
  --orange-100:    #F6DCC9;
  --orange-050:    #FBEEE4;
  --orange-300:    #E89160;   /* accent for dark surfaces (5.9:1 on --deep) */
  --orange-btn:    #C0541A;   /* button fill that keeps white label text at 4.6:1 */
  --teak:          #C08A4E;
  --teak-dark:     #8A5A2B;

  --sand:          #FAF7F2;
  --sand-2:        #F3ECE2;
  --sand-3:        #EAE1D4;
  --shell:         #FFFFFF;
  --deep:          #0F2E2B;
  --deep-2:        #0A211F;
  --sea:           #1D5B56;
  --foam:          #E7F0ED;

  --ink:           #17140F;
  --ink-2:         #3A342B;
  --muted:         #6E655A;
  --muted-2:       #776E61;

  --line:          rgba(23, 20, 15, 0.10);
  --line-2:        rgba(23, 20, 15, 0.18);
  --line-invert:   rgba(255, 255, 255, 0.18);

  --ok:            #1E7A4C;
  --star:          #E0A32E;

  /* Type */
  --font-display: 'Archivo', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  --fs-display-xl: clamp(2.75rem, 6.2vw, 6.5rem);
  --fs-display:    clamp(2.25rem, 4.4vw, 4rem);
  --fs-h2:         clamp(1.75rem, 2.9vw, 2.75rem);
  --fs-h3:         clamp(1.25rem, 1.9vw, 1.75rem);
  --fs-h4:         clamp(1.0625rem, 1.3vw, 1.25rem);
  --fs-lead:       clamp(1.0625rem, 1.25vw, 1.1875rem);
  --fs-body:       1.03125rem;
  --fs-sm:         0.90625rem;
  --fs-xs:         0.8125rem;
  --fs-eyebrow:    0.71875rem;

  /* Space */
  --gutter:  clamp(1.25rem, 3.6vw, 3rem);
  --container: 1560px;
  --container-narrow: 1160px;
  --sec-y: clamp(3.5rem, 7vw, 7.5rem);
  --sec-y-lg: clamp(4.5rem, 9vw, 9.5rem);

  /* Radii */
  --r-xs: 6px;
  --r-sm: 10px;
  --r-md: 16px;
  --r-lg: 22px;
  --r-xl: 32px;
  --r-pill: 999px;

  /* Motion */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --dur: 0.42s;

  /* Depth */
  --sh-1: 0 1px 2px rgba(23,20,15,.05), 0 2px 8px rgba(23,20,15,.04);
  --sh-2: 0 2px 6px rgba(23,20,15,.06), 0 12px 28px -8px rgba(23,20,15,.12);
  --sh-3: 0 6px 18px rgba(23,20,15,.08), 0 30px 60px -18px rgba(23,20,15,.20);
  --sh-4: 0 10px 30px rgba(23,20,15,.10), 0 50px 90px -30px rgba(23,20,15,.28);

  --sticky-h: 0px;
}

/* --- Reset ---------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 120px;
  /* No horizontal scrolling, ever.
   *
   * Several things on this site are legitimately wider than a phone screen and
   * are clipped by their own containers: the announcement marquee is a couple
   * of thousand pixels of track, and the mobile navigation panel waits off the
   * right-hand edge. Clipping stops them being *seen* past the edge, but in
   * Firefox the scrollable overflow they create still sets the document's
   * scroll width — so the layout viewport measured 435px on a 390px screen and
   * every full-width section sat 45px off centre, with the whole page able to
   * slide sideways under a thumb.
   *
   * `clip` rather than `hidden` is the important part: `overflow: hidden` on
   * the root element makes the viewport a scroll container, which silently
   * breaks `position: sticky` on the header. `clip` does not, so the header
   * keeps working and the sideways scroll is gone. */
  overflow-x: clip;
}

body {
  background: var(--sand);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.62;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-synthesis-weight: none;
  /* BOTH AXES ARE CLIPPED, AND NEITHER IS `auto`.
   *
   * This read `overflow-x: hidden` and left y at `auto`, which made <body> a
   * scroll container in its own right: `overflow-x: hidden` forces the used
   * value of `overflow-y` to `auto`. The document is 11,764px tall and the body
   * box is also 11,764px tall, so the body's own scroll range was a few pixels
   * of margin — and that is the two-scrollbar effect. The browser gave the
   * viewport a thumb sized for the body's near-zero scroll range, then handed
   * over to the real document once the gesture carried past it, and the
   * indicator shrank from the full-height stub to the true page.
   *
   * `clip` on both axes leaves the body a plain block that cannot scroll, so
   * the viewport is the only scroll container, which is what every other rule
   * on this site already assumes. `clip` rather than `hidden` for the same
   * reason it is used above: `hidden` on an ancestor establishes a scroll
   * container and would take the header's `position: sticky` with it. */
  overflow: clip;
  min-height: 100%;
}

body.is-locked { overflow: hidden; }

img, svg, video, canvas { display: block; max-width: 100%; }
img { height: auto; }

input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; padding: 0; }
table { border-collapse: collapse; width: 100%; }

h1, h2, h3, h4, h5 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.028em;
  text-wrap: balance;
  color: var(--ink);
}
h1 { font-size: var(--fs-display); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }
h4 { font-size: var(--fs-h4); }
h5 { font-size: var(--fs-h4); }

p { text-wrap: pretty; }

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

::selection { background: var(--orange); color: #fff; }

/* Scrollbar (Firefox) */
html { scrollbar-color: var(--sand-3) transparent; scrollbar-width: thin; }

/* --- Layout helpers ------------------------------------------------------- */
.wrap {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--narrow { max-width: var(--container-narrow); }
.wrap--wide { max-width: 1760px; }

.section { padding-block: var(--sec-y); }
.section--lg { padding-block: var(--sec-y-lg); }
.section--tight { padding-block: clamp(2.5rem, 4.5vw, 4.5rem); }
.section--deep { background: var(--deep); color: #fff; }
.section--deep h1, .section--deep h2, .section--deep h3 { color: #fff; }
.section--sand2 { background: var(--sand-2); }
.section--shell { background: var(--shell); }
.section--flush { padding-block: 0; }

.stack { display: grid; gap: var(--gap, 1.25rem); }
.row { display: flex; align-items: center; gap: var(--gap, 1rem); }
.row--between { justify-content: space-between; }
.row--wrap { flex-wrap: wrap; }
.center { text-align: center; }
.mt-0 { margin-top: 0; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: 1rem; top: -100px; z-index: 200;
  background: var(--ink); color: #fff; padding: .75rem 1.25rem;
  border-radius: var(--r-pill); font-size: var(--fs-sm); font-weight: 600;
  transition: top .2s var(--ease);
}
.skip-link:focus { top: 1rem; }

/* --- Section headers ------------------------------------------------------ */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-family: var(--font-display);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--orange-600);
  line-height: 1;
}
.eyebrow--muted { color: var(--muted); }
.eyebrow--light { color: rgba(255,255,255,.72); }
.eyebrow::before {
  content: '';
  width: 22px; height: 1.5px;
  background: currentColor; opacity: .55;
}
.eyebrow--bare::before { display: none; }

/* `max-width: 62ch` is a measure, not a size: at 18px Inter it resolves to
   about 435px, so on a 390px phone the grid track grew to fit the measure and
   the heading, its eyebrow and its sub-line all ran 45px past the screen edge
   on four pages. A measure that is wider than the column it sits in is not a
   measure. */
.sec-head { display: grid; gap: 1rem; max-width: min(62ch, 100%); min-width: 0; }

/* The one structural guard on the whole site.
 *
 * A `max-width` in `ch` resolves against the element's own font size, so 56ch
 * on a 19.5px heading is 583px — wider than every phone. Capping it with
 * `min(…, 100%)` should be enough, but a percentage max-width inside a grid
 * track is resolved against the track's own contribution in Firefox, so the
 * element still measured 415px inside a 390px wrapper and its eyebrow and
 * heading ran past the screen edge.
 *
 * Rather than chase the resolution rule, the content column simply refuses to
 * paint outside itself. This is containment, not sizing: nothing that fits is
 * affected, and anything that does not fit is clipped at the gutter instead of
 * at the edge of the screen — which is also what reflow (WCAG 1.4.10) asks
 * for. `clip` rather than `hidden` so no scroll container is created. */
.wrap { overflow-x: clip; }
.sec-head > * { min-width: 0; }
.sec-head--center { justify-items: center; text-align: center; margin-inline: auto; }
.sec-head__title { font-size: var(--fs-h2); }
.sec-head__sub { color: var(--muted); font-size: var(--fs-lead); max-width: 56ch; }
.section--deep .sec-head__sub { color: rgba(255,255,255,.66); }

.sec-head--split {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
  max-width: none;
  flex-wrap: wrap;
  min-width: 0;
}
.sec-head--split > * { min-width: 0; }

/* --- Buttons -------------------------------------------------------------- */
/* A button's label wraps or it is cut. `.btn--block` is full width with 26px
   of padding each side, so a 60-character label had about 290px to live in on a
   390px phone; `white-space: normal` was not enough on its own because the
   inner flex line box still could not break without `overflow-wrap`. */
.btn {
  overflow-wrap: anywhere;
  --btn-bg: var(--ink);
  --btn-fg: #fff;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .55rem;
  /* A button is a flex item in most of the places it appears, and a flex item
     defaults to `min-width: auto` — so a pill with 26px of padding and a
     40-character label ("Read the questions people ask about pressure") refused
     to shrink below its text and pushed itself 63px past its own container on a
     phone. Allowed to shrink and to wrap, a long label becomes two centred
     lines inside the pill instead of a button hanging off the page. */
  min-width: 0;
  max-width: 100%;
  white-space: normal;
  text-align: center;
  padding: 0.95rem 1.65rem;
  min-height: 50px;
  border-radius: var(--r-pill);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-weight: 650;
  letter-spacing: -0.005em;
  /* Not `1`: a label that wraps to two lines needs leading between them, and
     at line-height 1 the two lines touched. Declared once — this rule used to
     carry `white-space: nowrap` and a second `text-align` in its tail as well,
     which won silently and undid the wrapping the comment above promises. */
  line-height: 1.15;
  cursor: pointer;
  border: 1px solid transparent;
  transition: transform .16s var(--ease), background-color .2s var(--ease),
              color .2s var(--ease), border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn--primary { --btn-bg: var(--orange-btn); --btn-fg: #fff; box-shadow: 0 6px 18px -6px rgba(204,92,31,.5); }
.btn--primary:hover { --btn-bg: var(--orange-700); box-shadow: 0 10px 26px -8px rgba(204,92,31,.6); }

.btn--ink { --btn-bg: var(--ink); --btn-fg: #fff; }
.btn--ink:hover { --btn-bg: #000; }

.btn--ghost {
  --btn-bg: transparent; --btn-fg: var(--ink);
  border-color: var(--line-2);
}
.btn--ghost:hover { --btn-bg: var(--ink); --btn-fg: #fff; border-color: var(--ink); }

.btn--light {
  --btn-bg: #fff; --btn-fg: var(--ink);
  box-shadow: var(--sh-2);
}
.btn--light:hover { --btn-bg: var(--sand); }

.btn--outline-light {
  --btn-bg: rgba(255,255,255,.06); --btn-fg: #fff;
  border-color: rgba(255,255,255,.42);
  backdrop-filter: blur(6px);
}
.btn--outline-light:hover { --btn-bg: #fff; --btn-fg: var(--ink); border-color: #fff; }

.btn--lg { padding: 1.15rem 2.1rem; min-height: 58px; font-size: 0.9375rem; }
.btn--sm { padding: 0.65rem 1.15rem; min-height: 40px; font-size: 0.84375rem; }
.btn--xs { padding: 0.5rem 0.95rem; min-height: 34px; font-size: 0.78125rem; }
.btn--block { width: 100%; }
.btn--icon { padding: 0 0.9rem; }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .45; pointer-events: none; }

.btn__arrow { transition: transform .28s var(--ease); }
.btn:hover .btn__arrow { transform: translateX(3px); }

.link-arrow {
  display: inline-flex; align-items: center; gap: .45rem;
  font-family: var(--font-display);
  font-weight: 650; font-size: var(--fs-sm);
  border-bottom: 1.5px solid currentColor;
  padding-bottom: 2px;
  transition: gap .2s var(--ease), color .2s var(--ease);
}
.link-arrow:hover { gap: .8rem; color: var(--orange); }

.textlink { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.textlink:hover { color: var(--orange); }

/* --- Chips / pills / badges ----------------------------------------------- */
.chip {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .45rem .85rem;
  border-radius: var(--r-pill);
  background: rgba(255,255,255,.9);
  border: 1px solid var(--line);
  font-size: var(--fs-xs);
  font-weight: 550;
  line-height: 1.2;
  color: var(--ink-2);
  backdrop-filter: blur(8px);
}
.chip--solid { background: var(--ink); color: #fff; border-color: transparent; }
.chip--orange { background: var(--orange); color: #fff; border-color: transparent; }
.chip--outline { background: transparent; }
.chip--plain { background: var(--sand-2); border-color: transparent; }

.badge {
  display: inline-block;
  padding: .35rem .6rem;
  border-radius: var(--r-xs);
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  line-height: 1;
  background: var(--ink); color: #fff;
}
/* The pre-order badge is a chip, not a button.
 *
 * `.badge--orange` used to resolve to `--orange-btn`, the same token
 * `.btn--primary` fills with — so on the product page the "PRE-ORDER · 15% OFF"
 * badge and the "Pre-order now" button were the same solid orange, sixty pixels
 * apart. An accent cannot mean "this is a pre-order" and "this is the action" at
 * once; the badge stops reading as a badge and the button stops reading as the
 * button, which is the one confusion a pre-order store cannot afford.
 *
 * `--orange-btn` now belongs to the button alone. The badge takes the outline
 * treatment the build already uses for a saving — orange-700 on orange-050 with
 * an orange-100 hairline — which keeps it unmistakably the accent while leaving
 * exactly one solid orange object per screen. */
.badge--orange {
  background: var(--orange-050);
  color: var(--orange-700);
  border: 1px solid var(--orange-100);
  padding-block: .15rem;
}
.badge--ok { background: var(--ok); }
.badge--soft { background: var(--sand-2); color: var(--ink-2); }
.badge--save { background: var(--foam); color: var(--sea); }

.stars { display: inline-flex; align-items: center; gap: .12rem; color: var(--star); }
.stars svg { width: 14px; height: 14px; }
.stars--lg svg { width: 17px; height: 17px; }
.rating-line {
  display: inline-flex; align-items: center; gap: .55rem;
  font-size: var(--fs-xs); color: var(--muted); font-weight: 500;
}

/* --- Forms ---------------------------------------------------------------- */
.field { display: grid; gap: .5rem; }
.label {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.input, .select, .textarea {
  width: 100%;
  padding: 0.9rem 1.1rem;
  min-height: 52px;
  background: var(--shell);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  font-size: var(--fs-body);
  color: var(--ink);
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.input::placeholder, .textarea::placeholder { color: var(--muted-2); }
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--orange);
  box-shadow: 0 0 0 3px rgba(204,92,31,.16);
}
.textarea { min-height: 148px; resize: vertical; line-height: 1.6; }
.select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%236E655A' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1.05rem center;
  background-size: 12px;
  padding-right: 2.5rem;
  cursor: pointer;
}
.checkbox { display: flex; gap: .65rem; align-items: flex-start; font-size: var(--fs-sm); color: var(--muted); cursor: pointer; }
.checkbox input { width: 18px; height: 18px; margin-top: .18rem; accent-color: var(--orange); flex: none; }

.radio-tile {
  position: relative;
  display: grid;
  gap: .15rem;
  padding: .85rem 1rem;
  border: 1.5px solid var(--line-2);
  border-radius: var(--r-sm);
  cursor: pointer;
  background: var(--shell);
  transition: border-color .18s var(--ease), background-color .18s var(--ease), box-shadow .18s var(--ease);
}
.radio-tile:hover { border-color: var(--ink-2); }
.radio-tile input { position: absolute; opacity: 0; pointer-events: none; }
.radio-tile__name { font-family: var(--font-display); font-weight: 650; font-size: var(--fs-sm); }
/* The variant tile used to carry a struck compare-at next to the live price,
   which needed a colour of its own or two full-weight black numbers sat side by
   side. The tile now shows the shelf price alone — the discounted figure belongs
   to the checkout — so the rule had nothing left to style. */
.radio-tile__meta { font-size: var(--fs-xs); color: var(--muted); text-wrap: balance; }
.radio-tile__price { font-weight: 600; font-size: var(--fs-sm); }
.radio-tile:has(input:checked) {
  border-color: var(--orange);
  background: var(--orange-050);
  box-shadow: 0 0 0 3px rgba(204,92,31,.12);
}
.radio-tile:has(input:focus-visible) { outline: 2px solid var(--orange); outline-offset: 2px; }
.radio-tile--soldout { opacity: .5; cursor: not-allowed; }
.radio-tile:has(.radio-tile__tag) { padding-top: 1.2rem; }
/* The tag that sits on the corner of a deck tile.
 *
 * It was a filled `--orange-btn` pill — the same fill as the primary button
 * below the tiles — and `white-space: nowrap` on "PRE-ORDER · 15% OFF" gave the
 * tile an intrinsic width of 236px. Three of those at 390px pushed the whole
 * `.opt-group` to 496px inside a 350px column, so the deck chooser and the buy
 * button ran off the right edge of a phone with nothing able to pan to them.
 * Shrinking the grid did not help: the width came from this label.
 *
 * So: the outline treatment the badge now uses, and it is allowed to wrap. It
 * is also dropped entirely at the widths where the buy box is one narrow
 * column — at that point the price and the struck shelf price beside it are
 * already saying what the tag says, and the space is worth more than the
 * repetition. */
.radio-tile__tag {
  position: absolute; top: -.6rem; left: .85rem;
  padding: .2rem .5rem; border-radius: var(--r-pill);
  background: var(--orange-050); color: var(--orange-700);
  border: 1px solid var(--orange-100);
  font-family: var(--font-display); font-size: .59375rem; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; line-height: 1.1;
  white-space: normal;
  max-width: calc(100% - 1.7rem);
}
@media (max-width: 1180px) {
  .opt-tiles .radio-tile__tag--save { display: none; }
  .radio-tile:has(.radio-tile__tag) { padding-top: .85rem; }
}

.swatches { display: flex; gap: .55rem; flex-wrap: wrap; }
.swatch {
  width: 38px; height: 38px;
  border-radius: 50%;
  border: 1.5px solid var(--line-2);
  cursor: pointer;
  padding: 3px;
  background: var(--shell);
  transition: border-color .18s var(--ease), transform .18s var(--ease);
  position: relative;
}
.swatch span { display: block; width: 100%; height: 100%; border-radius: 50%; overflow: hidden; }
.swatch:hover { transform: scale(1.06); }
.swatch[aria-pressed="true"] { border-color: var(--ink); }
.swatch[aria-pressed="true"]::after {
  content: ''; position: absolute; inset: -4px; border-radius: 50%;
  border: 1.5px solid var(--ink);
}

/* Quantity stepper */
.qty {
  display: inline-flex; align-items: center;
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  background: var(--shell);
  overflow: hidden;
  height: 50px;
}
.qty button {
  width: 44px; height: 100%;
  display: grid; place-items: center;
  font-size: 1.1rem; color: var(--ink-2);
  transition: background-color .15s var(--ease), color .15s var(--ease);
}
.qty button:hover { background: var(--sand-2); color: var(--ink); }
.qty input {
  width: 42px; height: 100%; border: 0; text-align: center;
  background: transparent; font-weight: 600; font-size: var(--fs-sm);
  -moz-appearance: textfield;
}
.qty input::-webkit-outer-spin-button, .qty input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* --- Price ---------------------------------------------------------------- */
.price { display: inline-flex; align-items: baseline; gap: .55rem; flex-wrap: wrap; }
.price__now { font-family: var(--font-display); font-weight: 700; letter-spacing: -0.02em; }
.price__was { color: var(--muted-2); text-decoration: line-through; font-size: .875em; }
.price__save { color: var(--ok); font-weight: 600; font-size: .8125rem; }
.price__from { font-size: var(--fs-xs); color: var(--muted); font-weight: 500; letter-spacing: .04em; text-transform: uppercase; }

/* --------------------------------------------------------------------------
   The `hidden` attribute has to win.

   The browser's own rule is `[hidden] { display: none }`, at user-agent weight,
   so any class that sets `display` silently overrides it. On the contact page
   that meant all three validation messages were permanently on screen under
   fields the reader had not filled in yet, and it would have done the same to
   every other `hidden` element the moment a class gave it a display.
   -------------------------------------------------------------------------- */
[hidden] { display: none !important; }

/* --- Reveal on scroll ----------------------------------------------------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .8s var(--ease), transform .8s var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}
[data-reveal].is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1 !important; transform: none !important; }
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* --- Motion utilities ----------------------------------------------------- */
@keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@keyframes floaty { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes shimmer { from { background-position: -180% 0; } to { background-position: 180% 0; } }

/* --- Misc ----------------------------------------------------------------- */
.hr { height: 1px; background: var(--line); border: 0; }
.hr--light { background: var(--line-invert); }
.divider-dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: .4; }
.skeleton {
  background: linear-gradient(100deg, var(--sand-2) 30%, var(--sand-3) 50%, var(--sand-2) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.4s linear infinite;
}
.noscript-bar {
  background: var(--orange); color: #fff; text-align: center;
  padding: .75rem 1rem; font-size: var(--fs-sm); font-weight: 600;
}

/* ==========================================================================
   Money numerals
   Every price in this store used to end in .00, so the type system never had
   to carry a decimal point. It does now: $849.15 and $1,614.15 are the prices
   the whole build exists to communicate, and at display size the 0.04em of
   negative tracking that flatters a headline welds the digits either side of
   the point together — $149.85 reads as "14985" with a speck in it.

   Two rules, applied wherever a figure is set large:
     · the cents drop the display tracking and step down in size, which is the
       convention the rest of the site already uses for %, m², cm and 2026;
     · nothing that is compared against anything else is set in proportional
       digits.
   ========================================================================== */
.money__c {
  /* Undo the display tracking: this is the whole fix. At -0.04em the point and
     the two digits around it occupy almost no space, which is why they read as
     one number rather than a number and a fraction. */
  letter-spacing: 0;
  /* 0.62em of a 44px price is 27px and reads perfectly. 0.62em of a 13px
     saving is 8px, where the decimal point is a speck and "Save $149.85"
     reads as "Save $149 85". Stepped digits only work when there is room to
     step: below about 18px the cents are just the price and stay full size. */
  font-size: max(.62em, 11px);
  font-weight: 700;
  /* Raised, not superscripted to the cap height: a price has to stay on one
     baseline so two prices can be compared down a column. */
  vertical-align: .12em;
  margin-left: .02em;
}
/* Where the figure is small, the stepped form is switched off entirely rather
   than floored, so the whole number is set at one size and one weight. */
.pcard__price .money__c,
.pcard__ship-save .money__c,
.price-save .money__c,
.pricetable__now small .money__c,
.stat__note .money__c,
.cartrow__preorder .money__c,
.buybox__assure .money__c,
.delivstrip .money__c { font-size: 1em; vertical-align: baseline; font-weight: inherit; }

/* Any figure that sits above another figure gets tabular digits. Proportional
   digits make a column of prices ragged and make a live total jitter. */
.buybox__price .now,
.buybox__price .was,
.stat__value,
.pricetable__now,
.pricetable__was,
.pcard__price,
.cartrow__price,
.summary__row,
.buybar__price,
.lineitem__price,
.totals__row,
.delivstrip { font-variant-numeric: tabular-nums lining-nums; }

/* Superscript units sit against the glyph before them with no space at all,
   so "December" and its 2026 touch, and "500" and its "decks" touch. */
.stat__value sup { margin-left: .14em; }

/* --------------------------------------------------------------------------
   The content column, bounded by the viewport rather than by its container.

   `max-width` in `ch` is a measure, and a measure wider than the column it sits
   in is not a measure — 56ch on a 19.5px heading is 583px. Capping that with
   `100%` is the obvious fix and it is not enough: a percentage max-width on a
   grid item is resolved against the track's own contribution in Firefox, so
   the heading still measured 415px inside a 390px wrapper and ran past the
   screen edge on five pages.

   `100vw - the gutters` is definite at every level, so it holds regardless of
   how the track resolved. It is a ceiling, not a width: nothing that fits is
   touched, and the site's own measures continue to do the real work above it.
   -------------------------------------------------------------------------- */
/* One ceiling, declared as a variable so components can lower it without
   fighting it.
 *
 * The obvious version of this rule — a plain `max-width` on every descendant —
 * has a specificity of (0,4,0) once the `:not()` clauses are counted, which
 * silently outranks every component rule it sits above: `.pagehead p` is
 * (0,1,1), lost, and the lede on all fifteen page heads ran the full 1344px
 * column at roughly 149 characters a line.
 *
 * Declaring the ceiling as a fallback value on a custom property means a
 * component sets its own measure simply by setting `--max-w`, with no
 * specificity contest and no list of exceptions to keep in sync. The padded
 * containers are excluded because a `max-width` that ignores a box's own
 * padding makes the box wider than its own maximum; their children carry the
 * ceiling instead, which is where the overflow comes from anyway. */
/* `:where()` carries ZERO specificity, and that is the point.
 *
 * Three `:not()` clauses — or any class or `:where()`-less pseudo — give this
 * rule a specificity no component can match: `.preclose__copy { max-width:
 * 54ch }` is (0,1,0) and lost to a (0,4,0) ceiling, so the element kept its
 * 177-character line no matter what it declared. `:where(...)` lets the ceiling
 * be a ceiling: it applies by default and any component that sets its own
 * measure — by `max-width` or by `--max-w` — simply overrides it, at the
 * specificity its author would expect. */
.wrap > *:where(:not([class*="__empty"], .precard, .summary)),
.wrap > * > *:where(:not([class*="__empty"], .precard, .summary)) {
  max-width: var(--max-w, calc(100vw - 2 * var(--gutter)));
}

/* A readable default for running text.
 *
 * Enumerating the components that had a measure each was the wrong shape: it
 * fixed the ones on the list and left the same defect everywhere else.
 * `.preclose__copy` ran 107 characters to the line and `.band__copy p` ran 142
 * — both the identical fault the list was meant to cure. Defaulting on the
 * element inside a content column catches everything, including components
 * written later, and a component with a measure of its own still just sets
 * `--max-w` and wins. */
.wrap p,
.wrap h1,
.wrap h2,
.wrap h3,
.wrap h4,
.wrap li { --max-w: min(75ch, 100%); }
/* Except inside the components whose own measure is already set and narrower. */
.buybox p,
.delivstrip li,
.footer p,
.megamenu p,
.drawer p,
.acc__inner p,
.acc__inner li { --max-w: none; }

/* The measures, expressed the other way round — each one lowers the ceiling
   rather than replacing it, so it can never exceed the viewport. */
.sec-head__sub,
.sec-head__title,
.hero__sub,
.prose p,
.acc__inner,
.faq-group__title,
.pagehead p,
.pagehead h1,
.faq-cta p,
.faq-cta h2,
.vcard p,
.vcard h3,
.precard p,
.precard h3,
.step p,
.step h3,
.split__copy p,
.split__copy h2,
.footer__blurb,
.summary__note,
.newsletter__note { --max-w: min(62ch, 100%); }
/* And the components that had a measure of their own keep it, at whatever
   value it was: the variable is inherited, so a later rule setting `--max-w`
   on any of these wins without a specificity fight. */
.pagehead p { --max-w: min(62ch, 100%); }
.hero__sub { --max-w: min(42ch, 100%); }
.sec-head__sub { --max-w: min(56ch, 100%); }
.acc__inner { --max-w: min(68ch, 100%); }
.reviews__empty > p { --max-w: min(70ch, 100%); }

/* --------------------------------------------------------------------------
   Supported widths.

   320px is the narrowest phone still in meaningful use and the design targets
   360px and up. One unbreakable string on the product page's delivery strip
   ("Deliveries begin December 2026") sets a 326px min-content at 320px; it is
   clipped at the gutter rather than allowed to widen the page, and every
   viewport from 360px up is clean at every width tested. Recorded here so the
   next person measuring does not treat it as a regression.
   -------------------------------------------------------------------------- */


/* ==========================================================================
   Money.

   Every figure in the build is set in the display face at 600 or heavier, and
   only the size changes. The first attempt at this was a blanket selector that
   also caught prose — a paragraph containing "$999" was pulled into Archivo 700
   mid-sentence, which is worse than the inconsistency it fixed. So this list
   names only the components that render a figure *as* a figure, and leaves
   every element that renders a sentence alone.

   Size is deliberately not normalised. A price at 13px on a card and the same
   price at 44px on a product page are the same voice at two volumes; forcing
   one size on both would be a different mistake.
   ========================================================================== */
.pcard__price,
.pcard__price s,
.pcard__ship-save,
.price-save,
.pricetable__was,
.pricetable__now,
.stat__note,
.cartrow__price,
.cartrow__unit,
.cartrow__was,
.cartrow__preorder,
.summary__row,
.lineitem__price,
.lineitem__was,
.lineitem__preorder,
.totals__row,
.buybar__price,
.megamenu__list small,
.megamenu__promo .badge,
.hero__facts b,
.hero__stock b,
.buybox__assure,
.trust-strip b,
.vcard b,
.precard__foot {
  font-family: var(--font-display);
  font-weight: 600;
}
/* The four places a figure is the subject rather than part of a phrase. */
.buybox__price .now,
.buybox__price .was,
.stat__value,
.pricetable__now,
.pcard__price { font-weight: 700; }
/* A price column is compared down its length, so it never uses proportional
   digits. */
.buybox__price .now,
.pcard__price,
.pricetable__was,
.pricetable__now,
.stat__note,
.cartrow__price,
.summary__row,
.totals__row,
.buybar__price { font-variant-numeric: tabular-nums lining-nums; }

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

   The scale is 6 / 10 / 16 / 22 / 32, plus the pill. Two values sat outside it:
   a 5px used on five inline spans — including the savings badge, which carries
   the accent colour and sits beside 6px siblings — and a 14px on the pre-order
   cards. A 5px radius next to a 6px one is not a visible error; it is visible
   as *almost* consistent, which is worse.
   -------------------------------------------------------------------------- */
.badge,
.badge--orange,
.badge--save,
.badge--soft,
.chip { border-radius: var(--r-xs); }

/* --------------------------------------------------------------------------
   The primary button's shadow derives from its own fill.

   It filled with `--orange-btn` (#C0541A) and cast `--orange` (#CC5C1F) — a
   redder, lighter orange, hard-coded twice. A coloured shadow that does not
   come from its fill reads as a slightly dirty edge under the most-looked-at
   object on the page, and it means the accent cannot be retuned in one place,
   which is the only reason to have a separate button token at all.
   -------------------------------------------------------------------------- */
.btn--primary {
  box-shadow: 0 6px 18px -6px color-mix(in srgb, var(--orange-btn) 50%, transparent);
}
.btn--primary:hover {
  box-shadow: 0 10px 26px -8px color-mix(in srgb, var(--orange-btn) 60%, transparent);
}
