/* ==========================================================================
   GIANTCLEAN — shared site styles
   Direction: clean / clinical. Cool white space, hairline structure, teal as a
   surgical accent. Outfit for display + labels, Hanken Grotesk for body.
   Reused across all five pages.
   ========================================================================== */

@import url("../fonts/fonts.css");

:root {
  /* palette — cool, clean, clinical */
  --paper:     #FFFFFF;
  --cool:      #EEF3F5;   /* cool off-white panel */
  --cool2:     #E4EBEE;
  --ink:       #161E22;   /* cool near-black text */
  --muted:     #6E7A80;   /* cool grey secondary text */
  --teal:      #00A49B;   /* accent only */
  --teal-deep: #007A73;
  --teal-wash: #D6EAE7;   /* pale teal panel — a section surface, not an accent */
  --teal-mist: #EFF8F7;   /* teal at ~7% — the lightest section surface */
  --hair-teal: rgba(0, 122, 115, 0.22);
  /* --muted only reaches 3.5:1 on the pale teal surfaces, so it fails AA there.
     This is the same role tuned for a light wash. Use it instead of --muted on
     any --teal-wash / --teal-mist background. */
  --muted-wash: #556166;
  --logo-blue: #1D75B8;   /* logo wordmark only */
  /* The dark tier, derived from the logo blue (same 206° hue, dropped to ~13%
     lightness). Teal and the logo blue sit only ~30° apart, so used flat at the
     same brightness they read as a near miss rather than a pair. Splitting them
     by value instead of by hue is what makes the second colour work: teal owns
     every light surface and accent, navy owns every dark surface. Nothing on
     the site should be flat #1D75B8 outside the logo and the wordmark device. */
  --navy:      #10293A;   /* dark surfaces: footer, ink buttons, inverted cards */
  --navy-deep: #0A1B27;   /* the pressed/hover step below --navy */
  --hair:      rgba(22, 30, 34, 0.13);
  --hair-soft: rgba(22, 30, 34, 0.07);
  --on-dark:   rgba(255, 255, 255, 0.72);
  --hair-dark: rgba(255, 255, 255, 0.16);

  /* type */
  --sans:    "Hanken Grotesk", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  --display: "Outfit", "Hanken Grotesk", system-ui, sans-serif;

  /* structure */
  --wrap: 1280px;
  --gutter: clamp(22px, 4vw, 52px);
  --header-h: 74px;        /* sticky header height; grows on small screens (two rows) */
  /* The label rail. One grammar — label left, value right, hairline between rows —
     at two scales. It started as the spec table inside the buy box and is now the
     page's structure too, which is what ties the top of Products to the bottom:
       --rail       component scale, the spec rows inside a column
       --rail-page  page scale, the section rows that carry the whole band
     Both are fixed px, not fr: the point is that every row on the page shares one
     left edge, and an fr column would drift with the content beside it. */
  --rail: 120px;
  --rail-page: 240px;
}

*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* in-page anchors stop below the sticky header instead of under it */
  scroll-padding-top: var(--header-h);
}
body {
  margin: 0;
  font-family: var(--sans);
  font-weight: 400;
  font-size: 1.0625rem;
  line-height: 1.62;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
h1, h2, h3, h4 { margin: 0; font-family: var(--display); font-weight: 400; letter-spacing: -0.02em; line-height: 1.05; }

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter); }

/* ---- shared type devices ------------------------------------------------ */
.lbl {                                   /* uppercase teal micro-label */
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: 11px;
  color: var(--teal-deep);
  display: inline-block;
}
.lbl--muted { color: var(--muted); }
/* a phrase that must not be split across lines. Used in headlines where a break
   would land inside a name or a figure. It sets where the break goes without a
   hard <br>: the phrase stays whole, so the line above it takes the break, and
   when the whole headline fits on one line nothing is forced. Anything wearing
   this has to be narrow enough to survive a 320px phone on its own, so the type
   size at that width is the constraint to check. */
.nb { white-space: nowrap; }
.meta {                                  /* thin uppercase spec text */
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 10.5px;
  color: var(--muted);
}
.h2 { font-size: clamp(1.9rem, 3.4vw, 2.7rem); line-height: 1.06; }
.lede { color: var(--muted); font-size: 18px; line-height: 1.62; }

/* ---- teal accents inside type -------------------------------------------
   The Glossier reference's second half: after the pale tint carries the
   surface, saturated colour carries the type. Two devices, both deliberately
   small so the colour is sprinkled through the page rather than banded.
     .accent  one phrase of a headline, always the payoff phrase at the end
     .num     a spec figure inside body copy, the datasheet marks in colour
   teal-deep, not --teal: #00A49B is 2.9:1 on white and fails AA for text. --- */
.accent { color: var(--teal-deep); }
.num { color: var(--teal-deep); font-weight: 600; }

/* An inline link inside body copy. The site had no such style until Wholesale,
   where the email address sits inside a sentence rather than on a button. Ink
   with a teal-tinted underline, the same treatment a.contact__val carries, so a
   link in a paragraph reads as the same kind of thing as a link in a rail. */
.inlink {
  color: inherit;
  border-bottom: 1px solid var(--hair-teal);
  transition: color 0.18s ease, border-color 0.18s ease;
}
.inlink:hover { color: var(--teal-deep); border-color: var(--teal); }

/* ---- buttons / links (sharp, clinical) ---------------------------------- */
.btn {
  font-family: var(--display);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  font-size: 12px;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 15px 26px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.btn--teal  { background: var(--teal); color: #fff; }
.btn--teal:hover  { background: var(--teal-deep); }
.btn--ink   { background: var(--navy); color: #fff; }
.btn--ink:hover { background: var(--navy-deep); }
.btn--line  { background: transparent; color: var(--ink); border-color: var(--hair); }
.btn--line:hover { border-color: var(--ink); }
.btn--onink { background: #fff; color: var(--ink); }
.btn--onink:hover { background: var(--cool); }
.tlink {
  font-family: var(--display);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  font-size: 12px;
  color: var(--ink);
  border-bottom: 1px solid var(--hair);
  padding-bottom: 4px;
  transition: border-color 0.18s ease;
}
.tlink:hover { border-color: var(--teal); }
/* optional trailing arrow; 7px echoes the .btn icon gap */
.tlink__arrow {
  display: inline-block; margin-left: 7px;
  transition: transform 0.18s ease;
}
.tlink:hover .tlink__arrow { transform: translateX(3px); }
button.tlink {                            /* .tlink used as a modal trigger, not a link */
  appearance: none; -webkit-appearance: none;
  background: none; border: none;
  border-bottom: 1px solid var(--hair);
  padding: 0 0 4px;
}

/* ==========================================================================
   HEADER (shared)
   ========================================================================== */
/* White, kept slightly translucent so it still reads as glass over whatever
   surface scrolls under it. The teal-tinted rule stays: on the mist pages a
   grey hairline is the only thing that would read as a different colour
   family, and the edge should belong to the bar rather than to the page. */
.site-header {
  position: sticky; top: 0; z-index: 20;
  background: rgba(255, 255, 255, 0.86);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--hair-teal);
}
/* Two zones only: the logo holds the left, the nav flushes right against the
   gutter. No standing CTA up here, so the header's one note of color is the
   logo mark and the nav stays the only thing competing with the page. */
.site-header .wrap { display: flex; align-items: center; justify-content: space-between; height: var(--header-h); }
.brand img { height: 38px; width: auto; }
.nav { display: flex; }
.nav__list { display: flex; gap: clamp(18px, 2.4vw, 32px); }
.nav a {
  /* block, so the link box is the full line box the way it was when the anchors
     were flex children directly: as an inline inside <li> it collapsed to the
     glyph height and took the focus ring in with it */
  display: block;
  font-family: var(--display); font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.16em; font-size: 12px;
  color: var(--ink); opacity: 0.72; transition: opacity 0.18s ease;
}
.nav a:hover, .nav a[aria-current="page"] { opacity: 1; }
/* The phone disclosure control. Hidden at this scale and only revealed inside
   the 680px block, and only when assets/js/nav.js has set .js on <html>, so a
   scripting failure can never leave a dead button in the header. */
.nav__toggle { display: none; }

/* ==========================================================================
   HERO — product presentation
   ========================================================================== */
.hero {
  /* ice used as a faint texture layer (like a low-opacity Photoshop layer) over the cool field.
     The product panel's opaque white sits on top, so the texture reads only on the copy field — inverted.
     The field is teal-tinted at the mist level (#EFF8F7, teal at ~7%) rather than the wash, so the
     first screen reads as brand colour without the tint sitting on top of the photo. Held at 0.9 so
     the ice photo still shows through underneath. */
  background:
    linear-gradient(rgba(239, 248, 247, 0.9), rgba(239, 248, 247, 0.9)),
    url("../img/hero-ice.jpg") center / cover no-repeat;
  border-bottom: 1px solid var(--hair);
}
.hero .wrap {
  /* contained + centered (inherits max-width/padding from .wrap), product centered in its panel.
     Full-height so the hero fills the viewport below the sticky header for a dramatic first screen. */
  display: grid;
  grid-template-columns: 1.02fr 1.05fr;
  gap: clamp(28px, 5vw, 72px);
  align-items: stretch;
  min-height: calc(100svh - var(--header-h));
}
.hero__l { padding: clamp(48px, 6vw, 96px) 0; display: flex; flex-direction: column; justify-content: center; max-width: 560px; }
/* max font capped so "Pamper your" stays on one line inside the contained column (2-line headline) */
.hero h1 { font-weight: 500; letter-spacing: -0.025em; line-height: 0.98; font-size: clamp(3.4rem, 6.2vw, 6rem); }
.hero__sub { color: var(--muted); font-size: 18px; line-height: 1.62; margin-top: 26px; max-width: 40ch; }
.hero__cta { display: flex; align-items: center; gap: 24px; margin-top: 38px; flex-wrap: wrap; }

.hero__panel {
  position: relative;
  /* frosted glass: translucent white with a soft sheen + a highlight behind the product,
     blurring the faint ice field behind it. Reads as a subtle pane of glass. */
  background:
    radial-gradient(52% 48% at 50% 46%, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0) 72%),
    linear-gradient(140deg, rgba(255, 255, 255, 0.58) 0%, rgba(255, 255, 255, 0.32) 48%, rgba(255, 255, 255, 0.46) 100%);
  -webkit-backdrop-filter: blur(20px) saturate(112%);
  backdrop-filter: blur(20px) saturate(112%);
  border-left: 1px solid rgba(255, 255, 255, 0.7);
  box-shadow: -1px 0 0 var(--hair-soft);
  display: grid; place-items: center;
  /* background/backdrop stay clipped to the box; visible overflow lets the callout
     labels sit just outside the pane (in the column gap) without being cut off. */
  overflow: visible;
}
.prod { position: relative; z-index: 1; width: min(58%, 360px); }
.prod::after {                            /* grounded studio-shoot floor shadow */
  content: ""; position: absolute; z-index: 0;
  left: 50%; bottom: 4%; transform: translateX(-50%);
  width: 74%; height: 46px;
  background: radial-gradient(ellipse at center, rgba(22, 30, 34, 0.38) 0%, rgba(22, 30, 34, 0.16) 46%, rgba(22, 30, 34, 0) 74%);
  filter: blur(10px);
}
.prod > img { position: relative; z-index: 1; width: 100%; filter: drop-shadow(0 8px 16px rgba(22, 30, 34, 0.16)); }
/* zoom-in callouts: SVG leader lines + dots sit on the skate, text in the field */
.leaders { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; z-index: 2; pointer-events: none; }
.leaders .ln { stroke: var(--teal); stroke-width: 1; vector-effect: non-scaling-stroke; }
.leaders .pt { fill: var(--teal); }
.cal { position: absolute; z-index: 3; font-family: var(--display); white-space: nowrap; transform: translateY(-50%); }
.cal b { display: block; text-transform: uppercase; letter-spacing: 0.14em; font-size: 10px; color: var(--teal-deep); font-weight: 600; }
.cal span { font-size: 13px; color: var(--ink); font-weight: 500; }
/* Text anchors to each line's outer end (same %-of-image space as the SVG), with a
   small gap so labels never crowd the glass column. */
.cal--tr { top: 12%; left: 108%; }
.cal--l  { top: 53%; right: 105%; text-align: right; }
.cal--br { top: 85%; left: 108%; }

/* ==========================================================================
   REVIEWS — proof panel: a focal 4.8 rating anchored left, a still masonry of
   quotes right (one pulled into a dark feature card). A vertical, anchored
   counterpoint to the sideways color rail below it — no second horizontal scroll.
   ========================================================================== */
.reviews__grid {
  display: grid; grid-template-columns: 0.82fr 1.5fr;
  gap: clamp(40px, 6vw, 88px); align-items: start;
}
.reviews__stat { position: sticky; top: calc(var(--header-h) + 24px); }   /* clears the sticky header */
.reviews__score {
  font-family: var(--display); font-weight: 500; letter-spacing: -0.04em;
  /* the number sits in navy so it carries the weight of a headline, with the
     teal star as the single accent beside it */
  line-height: 0.82; font-size: clamp(4.6rem, 9vw, 8rem); color: var(--navy);
  margin-top: 16px;
  display: flex; align-items: center; gap: clamp(10px, 1.6vw, 18px);
}
.reviews__star {
  width: clamp(2.4rem, 4.6vw, 4rem); height: clamp(2.4rem, 4.6vw, 4rem);
  fill: var(--teal); stroke: none;
}
.reviews__meta { color: var(--muted-wash); font-size: 15px; margin-top: 18px; max-width: 24ch; }
.reviews__stat .tlink { display: inline-block; margin-top: 22px; }

/* The quotes sit in a fixed-height well instead of running the full length of the
   page: ten reviews at full height would bury the color rail below. The well shows
   about two rows of cards and clips the next one mid-card, so it's visibly a
   scrollable region rather than a short list.
   The two columns are explicit rather than CSS multicol: inside a scroller, a card
   too tall for a balanced column box spills past the multicol box and the scroll
   range never accounts for it, which cut the last review off. Two flow columns
   split in the markup measure honestly, so the scroll always reaches the end. */
/* the padding-bottom is the hint's lane, so the fade can stay pinned to the
   scroller's own bottom edge rather than the wrapper's */
.reviews__well { position: relative; }
.reviews__scroll {
  max-height: min(72vh, 560px);
  overflow-y: auto;
  /* the scrollbar sits in this gutter instead of over the right column's cards */
  padding-right: 14px;
  /* clears the 56px fade, so at the end of the scroll the last review isn't
     sitting half-faded */
  padding-bottom: 56px;
  scrollbar-width: thin;
  scrollbar-color: var(--hair-teal) transparent;
}
.reviews__scroll::-webkit-scrollbar { width: 8px; }
.reviews__scroll::-webkit-scrollbar-track { background: transparent; }
.reviews__scroll::-webkit-scrollbar-thumb { background: var(--hair-teal); border-radius: 99px; }
.reviews__scroll::-webkit-scrollbar-thumb:hover { background: var(--teal-deep); }
/* The fade rides the wrapper, not the scroller, so it stays pinned to the bottom
   edge as the cards move under it. Fades into the mist band the section sits on. */
.reviews__well::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 56px;
  background: linear-gradient(to top, var(--teal-mist), rgba(239, 248, 247, 0));
  pointer-events: none;
}
/* At the end of the scroll there is nothing more to show, so the fade retires over
   the last stretch of it. Where scroll-linked animation isn't supported it simply
   stays put, which is the state it was designed in. The reduced-motion rule at the
   foot of the file switches this off. */
.reviews__well { timeline-scope: --reviews-scroll; }
.reviews__scroll { scroll-timeline-name: --reviews-scroll; }
/* Scoped above 680px: on phones the well becomes a sideways carousel with no fade,
   so there is nothing here to retire. */
@media (min-width: 681px) {
  @supports (animation-timeline: scroll()) {
    .reviews__well::after {
      animation: reviews-end linear both;
      animation-timeline: --reviews-scroll;
      animation-range: 82% 100%;
    }
  }
}
@keyframes reviews-end { to { opacity: 0; } }
.reviews__masonry { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
.reviews__col { display: flex; flex-direction: column; gap: 20px; }
.review-card {
  /* paper, not the cool panel: on the mist band a neutral grey card sits at
     almost the same value as the field and reads as muddy. White sits forward. */
  background: var(--paper); border: 1px solid var(--hair); padding: 26px;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}
.review-stars { color: var(--teal); letter-spacing: 3px; font-size: 13px; margin-bottom: 14px; }
/* Scoped to .review-card on purpose: the lead is a <p>, so as a bare .review-lead
   it lost to .review-card p below on specificity and the 17px step never rendered
   at any width. Two classes beat one class + one type. */
.review-card .review-lead {                /* verbatim Amazon review title, as a bold lead-in */
  font-family: var(--display); font-weight: 600; font-size: 17px;
  letter-spacing: -0.01em; line-height: 1.35; color: var(--ink);
  margin: 0 0 8px; transition: color 0.2s ease;
}
.review-card p { margin: 0; font-size: 15px; line-height: 1.6; color: var(--ink); transition: color 0.2s ease; }
.review-card cite {
  display: block; margin-top: 18px; font-family: var(--display); font-style: normal;
  text-transform: uppercase; letter-spacing: 0.14em; font-size: 10px; color: var(--muted);
  transition: color 0.2s ease;
}
/* On hover the card inverts to the dark navy treatment. Overrides come after the
   base rules. Gated on a real pointer: on a touchscreen :hover latches after a
   tap, so without this the card a reader last touched stays inverted, which in
   the phone carousel below means a random navy card sitting in the track. */
@media (hover: hover) {
  .review-card:hover { background: var(--navy); border-color: var(--navy); }
  .review-card:hover p { color: #fff; }
  .review-card:hover cite { color: var(--on-dark); }
}

/* ==========================================================================
   SECTION SCAFFOLD
   ========================================================================== */
/* Page background rhythm. Every section takes one of four surfaces and no two
   adjacent sections share one, so the page always reads as separate bands:
     paper  #FFFFFF   default
     cool   #EEF3F5   neutral panel
     mist   #EFF8F7   teal at ~7%, the lightest tinted surface
     wash   #D6EAE7   pale teal panel, the strongest surface teal gets
     teal   #00A49B   flooded, at most once per page (products: the spex band)
     navy   #10293A   the dark end, from the logo blue (footer, inverted cards)
   Home alternates paper / mist strictly from the pillars down, then closes on
   cool. Teal at surface scale stays at the lightest tint and none of it is
   flooded. */
/* Two section surfaces, and only two: paper and the teal mist. --cool was the
   third, but at L* 95.5 against mist's 96.9 it was a 1.03 contrast ratio away
   from it, the same value in a different hue, so stacking the two read as an
   inconsistency rather than a change of surface. Grey now lives only inside
   components (card heads, the PDP stage, chips), where it never meets mist.
   Sections alternate paper and mist down the page; a section takes whichever
   one the section above it isn't. */
.section { padding-block: clamp(64px, 9vw, 110px); }
/* A mist band is bounded on both edges, not just the one it opens on: the teal
   hairline draws where the surface changes, and it changes twice. Without the
   bottom rule the band trailed off into paper on a 1.03 contrast step, which
   read as the mist fading rather than ending. The exception is a mist band that
   closes the page, where the navy footer is already the harder edge. */
.section--mist { background: var(--teal-mist); border-block: 1px solid var(--hair-teal); }
main > .section--mist:last-child { border-bottom: 0; }
.sec-head { display: flex; justify-content: space-between; align-items: baseline; gap: 20px; flex-wrap: wrap; margin-bottom: clamp(40px, 6vw, 56px); }
.sec-head h2 { max-width: 20ch; margin-top: 14px; }

/* ---- logo wordmark treatment (GIANT blue + CLEAN teal, per the logo) ------ */
.wordmark { letter-spacing: 0; white-space: nowrap; }
.wordmark__giant { color: var(--logo-blue); }
.wordmark__clean { color: var(--teal); }

/* ---- "Why GIANTCLEAN" — three cards --------------------------------------
   Replaces the earlier ledger (three horizontal rows in a two-column grid).
   The ledger read as a spec table: the rows scanned all at once, the left
   column sat half empty under a 15ch title, and the pillar titles outranked
   the section header.

   Each pillar is now a card whose top third is a tinted block carrying the
   eyebrow and the title, with the body copy below on paper. That block is
   what stops three text cards from reading as a generic three-column feature
   grid: it gives every card a top edge and an internal division.

   Every title is set to two lines via a <br> in the markup, so the three
   tinted blocks are the same height on their own. That is why nothing here
   forces alignment — an earlier pass used a min-height plus space-between to
   match the heads, which is what pushed the eyebrow and title far apart.

   No 01/02/03. The pillars are parallel claims, not a sequence, so numbering
   them implies an order that isn't there.
   ------------------------------------------------------------------------- */
.why { padding-block: clamp(64px, 8vw, 104px); }
.why__head { margin-bottom: clamp(38px, 5vw, 56px); }
/* a step above the card titles (48px against 26px at 1440px) so the section
   header leads. Weight and tracking stay on the site's own heading values. */
.why__h {
  font-family: var(--display); font-weight: 400; letter-spacing: -0.02em;
  line-height: 1.02; font-size: clamp(2.1rem, 3.6vw, 3rem); margin: 0;
  display: flex; align-items: baseline; gap: 0.28em; flex-wrap: wrap;
}
/* The real wordmark replaces the typed one here. logo-wordmark.png is the
   letters cropped tight out of the full lockup, no aperture mark: at heading
   scale the icon outweighed the line.
   Baseline alignment, not centring: a replaced element's baseline is its
   bottom edge, and the crop has no padding under the letters, so the logo's
   baseline sits on the heading's by construction at any size. That leaves
   height free to be set optically. 0.78em runs a touch over Outfit's cap
   height (0.68em) because the logo's strokes are lighter and its letters
   wider, so a literal cap match reads smaller than the word beside it. */
.why__logo { height: 0.78em; width: auto; }
/* runs the full width of the card row below, tying the header to the boxes */
.why__rule { width: 100%; height: 1px; background: var(--teal); margin-top: 24px; }

.why__cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; align-items: stretch; }
/* The card is drawn, not floated: a hairline box on the white field, with the
   title zone filled in --cool so each card reads as a header over a body rather
   than one undivided block. The borderless version only worked while the
   section sat on the mist, where the tint supplied the edge; back on paper the
   outline is what makes the three cards a set. */
.why__card {
  background: var(--paper); border: 1px solid var(--hair);
  display: flex; flex-direction: column;
}
.why__card-head {
  background: var(--cool); border-bottom: 1px solid var(--hair);
  padding: clamp(22px, 2vw, 30px);
  display: flex; flex-direction: column; gap: 14px;
}
.why__eyebrow {
  font-family: var(--display); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.2em; font-size: 11px; color: var(--teal-deep);
}
.why__title {
  font-family: var(--display); font-weight: 500; letter-spacing: -0.02em; line-height: 1.16;
  font-size: clamp(1.35rem, 1.7vw, 1.6rem); color: var(--ink); margin: 0;
}
.why__body { padding: clamp(22px, 2vw, 30px); display: flex; flex-direction: column; flex: 1; }
.why__body p { color: var(--muted); font-size: 15px; line-height: 1.6; margin: 0; }
/* "Meet Emily Zhu" uses the shared .tlink, so it carries the buttons' exact
   type: Outfit 500, 12px, uppercase, 0.09em. It sits at the bottom of its
   card, pushed there by the paragraph above it. */
.why__body .tlink { margin-top: auto; padding-top: 20px; align-self: flex-start; }
@media (prefers-reduced-motion: reduce) {
  .tlink__arrow { transition: none; }
  .tlink:hover .tlink__arrow { transform: none; }
}

/* ---- microfiber difference (clinical comparison) ------------------------ */
.diff .wrap { display: grid; grid-template-columns: 0.95fr 1.05fr; gap: clamp(30px, 5vw, 68px); align-items: start; }
.diff__copy p { color: var(--muted); margin-top: 16px; }
.diff__copy .h2 { margin-bottom: 4px; }
.cmp { border-top: 1px solid var(--ink); }
.cmp__row { display: grid; grid-template-columns: 30px 1fr; gap: 18px; padding: 24px 0; border-bottom: 1px solid var(--hair); }
.cmp__mark { font-family: var(--display); font-weight: 600; font-size: 15px; }
.cmp__row--us .cmp__mark { color: var(--teal-deep); }
.cmp__row--them .cmp__mark { color: var(--muted); }
.cmp__row h4 { font-family: var(--display); font-weight: 500; font-size: 1.1rem; letter-spacing: -0.01em; }
.cmp__row p { color: var(--muted); font-size: 14.5px; line-height: 1.55; margin-top: 4px; }

/* ---- color rail ----------------------------------------------------------
   The colors are a parade, not a grid: the pillars and reviews above answer
   "why", and this section answers "which". A swipeable strip, CTA below.
   ------------------------------------------------------------------------- */
/* Plain paper: the colour rail is where the product's own colours perform, so
   the field under them stays neutral and the only structure is the teal-tinted
   hairline grid of the rail itself. The cards carry no fill of their own (see
   .cc), so the white field runs unbroken behind the parade. */
.colors {
  background: var(--paper); border-top: 1px solid var(--hair-teal);
  padding-block: clamp(56px, 8vw, 96px);
  display: grid; gap: clamp(32px, 4.5vw, 52px);
}
.colors .sec-head { margin-bottom: 0; justify-content: center; text-align: center; }
.colors__rail {
  overflow: hidden;
  border-block: 1px solid var(--hair-teal);
}
/* Four identical copies of the color set, so one loop is a -25% shift. Duration
   holds the old pace (~37px/s) over the shorter 5-card distance. */
.colors__track {
  display: flex; width: max-content;
  animation: colors-marquee 30s linear infinite;
}
@keyframes colors-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-25%); }
}
.colors__track .cc { flex: 0 0 clamp(168px, 19vw, 224px); border-right: 1px solid var(--hair-teal); }
.cc { padding: 24px 14px; text-align: center; }
.cc__img { aspect-ratio: 1; display: grid; place-items: center; }
.cc__img img { width: 84%; filter: drop-shadow(0 12px 16px rgba(22, 30, 34, 0.14)); }
/* No per-photo alignment here any more. The five shots each came off the camera
   at a different distance and angle, and that correction is now baked into
   assets/img/product-*.png by tools/build-products.py, so every product photo
   arrives already level, already the same size, and already on a shared
   baseline. Nudging one in CSS would double-correct it. */
@media (prefers-reduced-motion: reduce) {
  .colors__track { animation: none; }
  .colors__rail { overflow-x: auto; }
}
.colors__cta {
  padding-inline: var(--gutter);
  display: flex;
  justify-content: center;
}
.colors__cta .btn {
  font-weight: 700;
  font-size: 13px;
  padding: 18px 40px;
}

/* ---- wholesale -----------------------------------------------------------
   Sits on the teal mist. It's the one section addressed to shops rather than
   skaters, so it gets its own surface instead of continuing the grey of the
   colour rail above it.

   Two columns: the question holds the left, the answer and the action sit
   together on the right. Nothing is drawn between them. The band's own edge and
   the column gap carry the structure that the numbered 01/02/03 list used to.
   ------------------------------------------------------------------------- */
.wholesale .wrap {
  display: grid; grid-template-columns: 1fr 1fr; gap: clamp(30px, 5vw, 72px);
  align-items: start;
}
.wholesale h2 { max-width: 13ch; margin-top: 10px; }
/* --muted-wash, not --muted: on the mist surface the standard grey only reaches
   3.5:1 and fails AA, so this is the same role tuned for a tinted background. */
.wholesale__side p { color: var(--muted-wash); font-size: 18px; max-width: 44ch; }
.wholesale__side .btn { margin-top: 28px; }

/* ---- final CTA ----------------------------------------------------------
   One component, same shape on every page that closes with it. Only the copy,
   the destination, and the surface change. The surface is set per page by the
   alternation rule: add section--mist when the block above the close is paper
   (Products), leave it off when the block above is mist (Home, About), where the
   mist band's own bottom hairline is what separates it from the close:

     <section class="section cta-final">
       <div class="wrap">
         <span class="lbl">eyebrow</span>
         <h2>headline with the <span class="accent">payoff phrase</span></h2>
         <a class="btn btn--teal" href="...">Primary action</a>            <- see below
         <p class="cta-final__alt"><a class="tlink">secondary path</a></p>  <- optional
       </div>
     </section>

   The eyebrow and headline are always present. The .accent span is only for the
   product close, where the payoff phrase is the promise being sold; About asks a
   question instead, and colouring part of a question emphasises nothing.

   The block closes on either the teal button or the alt link, whichever action
   the page hasn't already offered: Products drops the button because the PDP
   above it already carries Buy on Amazon. --- */
.cta-final { text-align: center; }
.cta-final .wrap { max-width: 720px; }
.cta-final h2 { font-size: clamp(2.2rem, 4.4vw, 3.4rem); margin: 18px auto 34px; max-width: 16ch; }
/* the product name now carries the eyebrow slot: one line on desktop, wraps on small screens */
.cta-final .lbl { line-height: 1.5; }
/* About closes on a full sentence rather than a short promise, so the heading
   steps down a size and takes a wider measure than the 16ch default. Everything
   else (eyebrow, teal button) is the shared component, so only the measure and
   size change here. The headline carries no .accent: see the note above.

   It also runs left rather than centred, the one structural break from the
   component. About is a first-person essay set on a left rag, and this block
   sits on the same axis as the prose above it, so the close reads as the last
   beat of the essay instead of a band bolted to the bottom. The centred
   treatment stays on Home and Products, which close on a product promise.
   These come after the .cta-final rules on purpose: same specificity, so
   source order is what lets them win. */
.cta-final--ask { text-align: left; }
.cta-final--ask .wrap { max-width: var(--wrap); }
.cta-final--ask h2 {
  margin-inline: 0;
  font-size: clamp(1.65rem, 3vw, 2.35rem);
  max-width: 26ch;
  line-height: 1.14;
}
.cta-final p { color: var(--muted); font-size: 18px; margin: 18px auto 34px; max-width: 40ch; }
/* when the close lands on mist instead of paper, the secondary grey steps to
   its tinted-surface value, where --muted would only reach 3.5:1 */
.cta-final.section--mist p { color: var(--muted-wash); }

/* ==========================================================================
   CONTACT — minimal: heading + direct contact info, no form.
   Reuses the two-column editorial split (copy left, hairline detail list right)
   established on the wholesale and pillars sections. Fills the viewport so the
   footer doesn't ride up under a short page.
   ========================================================================== */
.contact {
  /* one surface for the whole page, the same move About makes: a single block
     between the header and the footer has nothing to alternate against, so it
     takes the mist rather than leaving the field untreated white. */
  background: var(--teal-mist);
  border-bottom: 1px solid var(--hair-teal);
  display: flex; align-items: center;
  min-height: calc(100svh - var(--header-h));
  padding-block: clamp(64px, 10vw, 128px);
}
.contact .wrap {
  display: grid; grid-template-columns: 1.1fr 0.9fr;
  gap: clamp(40px, 7vw, 104px); align-items: start;
}
.contact h1 {
  font-weight: 500; letter-spacing: -0.03em; line-height: 0.98;
  font-size: clamp(3rem, 6vw, 5rem); margin-top: 22px;
}
/* --muted-wash, not --muted: on the mist the standard grey only reaches 4.1:1 */
.contact__intro { color: var(--muted-wash); font-size: 18px; line-height: 1.6; margin-top: 26px; max-width: 34ch; }

/* hairline detail list: each row is a label + a value, mirroring cmp/wholesale rows */
.contact__list { border-top: 1px solid var(--ink); }
.contact__row {
  display: grid; grid-template-columns: 88px 1fr; gap: 20px;
  /* teal-tinted hairlines, like every other rule drawn on a tinted surface: a
     grey line here would be the only thing on the page in a different family */
  padding: 24px 0; border-bottom: 1px solid var(--hair-teal); align-items: baseline;
}
.contact__row dt {
  font-family: var(--display); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.16em; font-size: 11px; color: var(--teal-deep); margin: 0;
}
/* min-width:0 so the value track can shrink below the email's min-content width
   instead of forcing the row wider than the grid it sits in */
.contact__row dd { margin: 0; min-width: 0; }
.contact__val {
  font-family: var(--display); font-weight: 500; letter-spacing: -0.01em;
  font-size: clamp(1.1rem, 1.7vw, 1.35rem); color: var(--ink);
  transition: color 0.18s ease;
  /* the email is one unbreakable 31-character string. `anywhere` rather than
     `break-word` because only `anywhere` also lowers the min-content
     contribution, which is what a grid track actually floors at. Last resort:
     the 680px rule below gives it the full measure so this shouldn't fire. */
  overflow-wrap: anywhere;
}
a.contact__val { border-bottom: 1px solid var(--hair-teal); padding-bottom: 3px; }
a.contact__val:hover { color: var(--teal-deep); border-color: var(--teal); }

@media (max-width: 960px) {
  .contact .wrap { grid-template-columns: 1fr; gap: clamp(32px, 6vw, 48px); }
  .contact { min-height: 0; }
}
/* On a 375px phone the 88px label column plus its 20px gap left the value 223px,
   and the email sets at 255px, so the row ran past the gutter and took the whole
   document horizontal. Stacked, the value gets the full measure and the label
   reads as an eyebrow over it, which is already what it looks like. Same phone
   breakpoint the rest of the site turns at. */
@media (max-width: 680px) {
  .contact__row { grid-template-columns: 1fr; gap: 10px; padding: 20px 0; }
}

/* ==========================================================================
   PRODUCTS
   The product panel mirrors the home hero (product left, copy right) so the two
   pages read as one system. The signature here is the color chip: each swatch is
   a crop of the real product photography, so you see the microfiber pile itself
   rather than a flat hex circle. Below it, one flooded teal panel carries the
   usage sequence, and everything else stays on the hairline datasheet language.
   ========================================================================== */
.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;
}

/* ---- product panel -------------------------------------------------------
   The section is the grid, not a .wrap inside it, so the photo panel can run off
   the left edge of the viewport while the buy column still lands on the wrap's
   right gutter. Before this the stage was an inset box with a hairline on both
   sides, sitting above a full-bleed band, which left a sliver of paper down each
   side and made the two sections look like they belonged to different pages.

   The stage carries --teal-mist, not --cool. Grey at L*95.5 directly above the
   band's mist at L*96.9 is a 1.03 contrast ratio: the same value in a different
   hue, which is the exact pairing the surface note above pulled out of the
   rotation. Grey now stays inside components on this page too (chips, card
   heads), and the page runs mist / paper / mist / navy from here down. */
/* The right gutter lives on the grid container, not on the column inside it.
   A grid item's percentages resolve against its own grid area, so the same
   expression on .pdp__infocol measured against ~684px instead of the section and
   collapsed to the bare gutter, pushing the buy column 66px past the header nav.
   Here 100% is the section's width, so it resolves to the wrap's outer margin
   plus its gutter, and max() holds it at the plain gutter once the viewport is
   narrower than --wrap and the expression would go negative. Percentages, never
   100vw: vw counts the scrollbar and would overflow by its width. */
.pdp {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  align-items: stretch;
  min-height: calc(100svh - var(--header-h));
  padding-right: max(var(--gutter), calc((100% - var(--wrap)) / 2 + var(--gutter)));
  border-bottom: 1px solid var(--hair);
}
.pdp__stage {
  background: var(--teal-mist);
  border-right: 1px solid var(--hair-teal);
  display: grid; place-items: center;
  padding: clamp(28px, 4vw, 56px);
}
/* flex-end, not the default start: the buy copy is capped to a reading measure,
   so left-aligning it inside a wider column would leave its right edge floating
   a few px shy of the site grid. Anchored right, it lands on the same edge as
   the header nav at every width and the slack falls in the middle gutter. */
.pdp__infocol {
  display: flex; align-items: center; justify-content: flex-end;
  padding-left: clamp(28px, 4vw, 64px);
}
.pdp__figure { margin: 0; width: min(72%, 520px); position: relative; }
.pdp__figure::after {                     /* same grounded studio floor shadow as the hero */
  content: ""; position: absolute; z-index: 0;
  left: 50%; bottom: 11%; transform: translateX(-50%);
  width: 74%; height: 44px;
  background: radial-gradient(ellipse at center, rgba(22, 30, 34, 0.34) 0%, rgba(22, 30, 34, 0.14) 46%, rgba(22, 30, 34, 0) 74%);
  filter: blur(10px);
}
.pdp__figure img {
  position: relative; z-index: 1; width: 100%;
  filter: drop-shadow(0 8px 16px rgba(22, 30, 34, 0.16));
}
.pdp__caption {
  position: relative; z-index: 1;
  margin-top: clamp(18px, 2.4vw, 30px); text-align: center;
  font-family: var(--display); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.2em; font-size: 11px;
  color: var(--teal-deep);
}

/* The buy column is a flex column so `order` can drive it, because the phone and
   the desktop want two different reading orders out of one source order. Source
   is the phone's (name, price, photo, colour, sell, specs); the map below puts the
   lede back above the price and closes the two rails up, which is the order this
   column has always had at this width. Nothing here changes what desktop renders.
   The phone order lives in the 1040px block, where the photo joins the sequence. */
.pdp__info {
  padding-block: clamp(48px, 6vw, 88px);
  display: flex; flex-direction: column; justify-content: center;
  max-width: 560px;
}
.pdp__info > .lbl              { order: 1; }
.pdp__info > h1                { order: 2; }
.pdp__info > .pdp__lede        { order: 3; }
.pdp__info > .pdp__price       { order: 4; }
.pdp__info > .pdp__quick--buy  { order: 5; }
.pdp__info > .pdp__quick--specs{ order: 6; }
.pdp__info > .pdp__cta         { order: 7; }
/* The product name is rank 1 on this page. It was 50.4px against the closing
   headline's 54.4px, so the page's biggest type was the sign-off rather than the
   thing being sold. Raised until it clears the close, which stays put because
   .cta-final is shared across pages. */
.pdp h1 {
  font-weight: 500; letter-spacing: -0.025em; line-height: 1.0;
  font-size: clamp(2.3rem, 3.9vw, 3.6rem); margin-top: 20px;
}
.pdp__lede { color: var(--muted); font-size: 17px; line-height: 1.62; margin-top: 22px; max-width: 42ch; }
.pdp__price { display: flex; align-items: baseline; gap: 12px; margin-top: 26px; }
.pdp__amount {
  font-family: var(--display); font-weight: 500; letter-spacing: -0.03em;
  font-size: clamp(1.9rem, 3vw, 2.4rem); line-height: 1;
}

/* Matches .pdp__quick span exactly. The chosen colour is a value in the rail
   like any other, and setting it in the display face made the one dynamic row
   look like a different kind of thing; the teal ring already marks the choice. */
.pdp__chosen { font-size: 14.5px; color: var(--ink); }
/* the Color row's value: the chosen name, then the swatches under it, both
   inside the rail's value column so they share the other rows' left edge */
.pdp__colorcell { display: flex; flex-direction: column; gap: 12px; }

/* the chip: a square of the actual microfiber, hairline framed, teal ring when on */
.chips { display: flex; flex-wrap: wrap; gap: 10px; }
.chip {
  width: 40px; height: 40px; padding: 0; cursor: pointer;
  background-color: var(--cool);
  background-position: center; background-size: cover; background-repeat: no-repeat;
  border: 1px solid var(--hair);
  box-shadow: 0 0 0 0 var(--teal);
  transition: box-shadow 0.18s ease, border-color 0.18s ease;
}
.chip:hover { border-color: var(--ink); }
.chip.is-on { border-color: var(--paper); box-shadow: 0 0 0 2px var(--teal); }
.chip__sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

.pdp__cta { display: flex; align-items: center; gap: 24px; margin-top: clamp(28px, 3.4vw, 38px); flex-wrap: wrap; }

/* The rail at component scale: label left, value right, hairline between rows.
   The same grammar the accordion rows use at page scale below, which is what
   ties the top of this page to the bottom. */
.pdp__quick { margin-top: clamp(30px, 4vw, 44px); border-top: 1px solid var(--hair); }
.pdp__quick li {
  display: grid; grid-template-columns: var(--rail) minmax(0, 1fr); gap: 16px;
  padding: 13px 0; border-bottom: 1px solid var(--hair-soft); align-items: baseline;
}
.pdp__quick b {
  font-family: var(--display); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.14em; font-size: 10px; color: var(--teal-deep);
}
.pdp__quick span { font-size: 14.5px; color: var(--ink); }
/* The buy rail carries the two rows you act on (Color, Size) above the three you
   only read, so it opens on the ink rule the choose-block used to own and gives
   its interactive rows more room than a plain spec line needs. */
.pdp__quick--buy { border-top: 1px solid var(--ink); }
.pdp__quick--buy > li:first-child { padding-block: 18px; align-items: start; }
.pdp__quick--buy > li:first-child b { position: relative; top: 5px; }
/* The specs are a second <ul> only so the colour row can be moved out from
   over them on a phone. Here they sit straight under the colour row with no
   rule and no gap of their own, so the five rows are the one rail the comment
   above describes. The phone gives this list its own ink head. */
.pdp__quick--specs { margin-top: 0; border-top: 0; }

/* ---- product detail band -------------------------------------------------
   All three detail sections as one accordion. Structure is built from materials
   the page already uses: the teal hairline, the rail, and a marker drawn from
   the same 1px rule as the dividers.

   Paper, not the mist this band used to carry. The stage above it took the tint
   when it went full-bleed, and two tinted bands in a row would erase the seam
   between them. The page runs mist (product) / paper (this) / mist (the close). */
.spex {
  background: var(--paper); color: var(--ink);
  padding-block: clamp(64px, 9vw, 110px);
}
.spex__row { border-top: 1px solid var(--hair-teal); }
.spex__row:last-of-type { border-bottom: 1px solid var(--hair-teal); }
/* the open row lights up: its rule goes from the teal hairline to solid teal */
.spex__row[open] { border-top-color: var(--teal-deep); }

/* The row is the page's structure, so it takes the rail grammar at page scale:
   title in the label column, summary in the value column, marker flush right.
   Same shape as a .pdp__quick row two sections up, one size larger.

   The summary line is what makes a closed accordion work. Before it, each row
   was a 113px box holding 44px of type and the band read as empty; now the
   substance of all three sections is on the page without a click, and opening a
   row is for the detail rather than for the gist. */
.spex__sum {
  display: grid;
  grid-template-columns: var(--rail-page) minmax(0, 1fr) 26px;
  /* baseline, not start: the title and the summary are different sizes, so
     matching their box tops leaves their first lines sitting a few px apart.
     The marker has no text to baseline against, so it opts out below. */
  align-items: baseline; gap: 24px;
  padding-block: clamp(24px, 3vw, 34px); cursor: pointer;
  list-style: none;                       /* kills the native triangle */
}
.spex__sum::-webkit-details-marker { display: none; }
/* teal-deep, not --teal: at #00A49B the ring is only 2.87:1 on this pale
   surface, under the 3:1 a focus indicator needs. teal-deep clears it at 4.8. */
.spex__sum:focus-visible { outline: 2px solid var(--teal-deep); outline-offset: 4px; }
/* Ink, not teal: the row titles are structure here, not accent, so the colour is
   left to the marker and the open-row rule. Sized to sit inside the 240px rail
   on one line, and to land below the price in the page's type ladder. */
.spex__title {
  font-family: var(--display); font-weight: 500;
  letter-spacing: -0.02em; line-height: 1.2;
  font-size: clamp(1.25rem, 1.6vw, 1.45rem);
  color: var(--ink); margin: 0;
  transition: opacity 0.18s ease;
}
/* --muted-wash, a step darker than the --muted the panels use. The summary is a
   lead-in that has to hold its own beside the title, and the panel copy under it
   is the secondary read, so the row opens on the stronger grey and steps down
   once you're inside it. Held to a measure so it stays one or two lines. */
.spex__sumline {
  color: var(--muted-wash); font-size: 15.5px; line-height: 1.55;
  max-width: 52ch; margin: 0;
  transition: opacity 0.18s ease;
}
.spex__row:not([open]) .spex__sum:hover .spex__title,
.spex__row:not([open]) .spex__sum:hover .spex__sumline { opacity: 0.72; }
/* Once the row is open the summary is restating the panel under it, so it steps
   back rather than competing. Kept in the flow, not hidden: it holds the row's
   height steady so the marker doesn't jump as rows open and close. */
.spex__row[open] .spex__sumline { opacity: 0.45; }

/* a plus made of two hairlines, rotating 45deg into an x when the row opens */
.spex__mark {
  position: relative; width: 26px; height: 26px;
  /* opts out of the summary's baseline alignment (no text to align), and sits
     so the crossbar lands on the title's x-height rather than its box top */
  align-self: start; margin-top: 1px;
  transition: transform 0.28s ease;
}
.spex__mark::before,
.spex__mark::after {
  content: ""; position: absolute; inset: 50% 0 auto 0;
  height: 1px; background: var(--teal-deep);
}
.spex__mark::after { transform: rotate(90deg); }
.spex__row[open] .spex__mark { transform: rotate(45deg); }

/* The panel starts at the value column, not back at the band's left edge. The
   closed rows establish a spine at the rail; an opened panel that ignored it
   would break the one line the page has. +24px matches the summary's grid gap. */
.spex__panel {
  padding-bottom: clamp(32px, 4.5vw, 52px);
  padding-left: calc(var(--rail-page) + 24px);
}
.spex__row[open] .spex__panel { animation: spexIn 0.3s ease both; }
@keyframes spexIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* what's included — two of Home's pillar cards, so the only styling here is the
   grid they sit in. Everything else comes from .why__card. */
.spex__note { color: var(--muted); font-size: 15px; max-width: 40ch; margin-bottom: clamp(28px, 3.4vw, 40px); }
.spex__cards { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: stretch; }

/* the material
   One column, not the old 0.85fr/1.15fr split. The rail indent already divides
   this panel from the page, so an internal two-column split was a second
   structure doing the first one's job, and it squeezed the prose to ~358px. */
.spex__prose { display: block; }
.spex__lede {
  font-family: var(--display); font-weight: 400;
  font-size: clamp(1.05rem, 1.35vw, 1.2rem); line-height: 1.5;
  letter-spacing: -0.01em; color: var(--ink); max-width: 46ch;
  margin-bottom: clamp(20px, 2.4vw, 28px);
}
.spex__prosebody p { font-size: 16.5px; line-height: 1.62; max-width: 62ch; color: var(--muted); }
.spex__prosebody p + p { margin-top: 22px; }

/* how to use it: a real sequence, so the numbering carries information */
.spex__steps {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(20px, 3.4vw, 44px);
}
.spex__steps li { border-top: 1px solid var(--hair-teal); padding-top: 24px; }
/* shared with the Wholesale process track: same token, one rule */
.spex__num, .works__num {
  display: block; font-family: var(--display); font-weight: 500;
  font-size: 13px; letter-spacing: 0.06em; font-variant-numeric: tabular-nums;
  color: var(--teal-deep); margin-bottom: 18px;
}
.spex__steps h3, .works__step h3 {
  font-family: var(--display); font-weight: 500; letter-spacing: -0.02em;
  font-size: clamp(1.4rem, 2.2vw, 1.85rem); color: var(--ink);
}
.spex__steps p { margin-top: 12px; color: var(--muted); font-size: 15.5px; line-height: 1.6; max-width: 34ch; }

@media (prefers-reduced-motion: reduce) {
  .spex__mark,
  .spex__row[open] .spex__panel { transition: none; animation: none; }
}

/* ---- size guide popup ----------------------------------------------------
   A modal rather than a page section: opened from the "Size guide" link in
   the buy box. Same hairline/clinical language as the rest of the page. */
.sizemodal { position: fixed; inset: 0; z-index: 50; display: none; }
.sizemodal.is-open { display: block; }
.sizemodal__backdrop { position: absolute; inset: 0; background: rgba(16, 41, 58, 0.5); }  /* --navy at 50% */
.sizemodal__panel {
  position: relative; z-index: 1;
  background: var(--paper);
  width: calc(100% - 48px); max-width: 560px;
  margin: clamp(64px, 12vh, 120px) auto 0;
  padding: clamp(32px, 5vw, 48px);
  border: 1px solid var(--hair);
  /* svh, matching every other viewport-height calc on the site. With 100vh the
     panel's cap exceeds what iOS Safari actually shows while the toolbars are
     up, so the bottom of the table and the "Ask about sizing" link sit under the
     browser chrome with no scroll left to reach them. */
  max-height: calc(100svh - clamp(64px, 12vh, 120px) - 40px);
  overflow-y: auto;
  overscroll-behavior: contain;           /* scrolling the panel doesn't chain to the page */
}
.sizemodal__close {
  position: absolute; top: 18px; right: 18px;
  width: 34px; height: 34px;
  display: grid; place-items: center;
  background: none; border: 1px solid var(--hair); cursor: pointer;
  font-family: var(--display); font-size: 20px; line-height: 1; color: var(--ink);
  transition: border-color 0.18s ease, color 0.18s ease;
}
.sizemodal__close:hover { border-color: var(--ink); color: var(--teal-deep); }
.sizemodal__panel .h2 { margin-top: 14px; font-size: clamp(1.5rem, 2.6vw, 2rem); }
.sizemodal__panel > p { color: var(--muted); margin-top: 14px; max-width: 46ch; }
.sizemodal__panel .sizetable { margin-top: clamp(22px, 3vw, 30px); }
.sizemodal__panel .tlink { display: inline-block; margin-top: 22px; }
body.modal-open { overflow: hidden; }
.sizetable { width: 100%; border-collapse: collapse; }
.sizetable th, .sizetable td { text-align: left; padding: 22px 0; }
.sizetable thead th {
  font-family: var(--display); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.16em; font-size: 10.5px; color: var(--muted);
  padding-top: 0; padding-bottom: 14px; border-bottom: 1px solid var(--ink);
}
.sizetable tbody tr { border-bottom: 1px solid var(--hair); }
.sizetable tbody th {
  font-family: var(--display); font-weight: 500; letter-spacing: -0.01em;
  font-size: clamp(1.1rem, 1.7vw, 1.35rem); color: var(--ink); width: 34%;
}
.sizetable tbody td { color: var(--muted); font-size: 15.5px; }

/* ---- sticky buy bar (phone only) -----------------------------------------
   Paper with a hairline, the same glass the header uses. The teal is on the
   button and nowhere else: a full-bleed teal bar is the flooded-section move
   this site doesn't make, and it would fight the header for attention besides.

   Off by default at every width and only switched on inside the 680px block, so
   the desktop page never renders it. It also stays down until the inline buy
   button has scrolled away, which is the JS in products.html. */
.buybar {
  display: none;
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  /* Solid, not the header's glass. The bar spends most of its life over the navy
     footer, where translucent white goes grey and reads as a dimmed overlay
     rather than a surface. The hairline does the separating. */
  background: var(--paper);
  border-top: 1px solid var(--hair-teal);
  transform: translateY(100%);
  transition: transform 0.26s ease;
}
.buybar.is-up { transform: none; }
.buybar__inner { display: flex; align-items: center; gap: 16px; padding: 11px var(--gutter); }
/* min-width 0 so the name can ellipsis instead of shoving the button off */
.buybar__meta { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.buybar__name {
  font-family: var(--display); font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.1em; font-size: 9.5px;
  color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.buybar__price {
  font-family: var(--display); font-weight: 500;
  letter-spacing: -0.02em; font-size: 17px; line-height: 1.2;
}
.buybar__btn { margin-left: auto; flex: 0 0 auto; padding: 14px 20px; }

.cta-final__alt { margin-top: 26px; }
.cta-final__alt .tlink { color: var(--ink); }
/* The slot is a <p>, so .cta-final p catches it: that rule is 0-1-1 and wins on
   specificity over the line above no matter which comes last in the file, and it
   would hand a one-line alt link the 40ch measure and centred auto margins meant
   for a paragraph of body copy. In the left-aligned --ask variant that centred a
   40ch block inside a full-width wrap, which is why the override is scoped to at
   least the same specificity and the axis is reset for --ask. */
.cta-final p.cta-final__alt { margin: 26px auto 0; max-width: none; font-size: 16px; }
.cta-final--ask p.cta-final__alt { margin-inline: 0; }

@media (max-width: 1040px) {
  /* One column. The stage is already full-bleed by construction now, so it just
     stops being a grid column and hands its side edge to a bottom rule.
     padding-right goes back to 0: stacked, the section's right gutter would hold
     the stage that far off the edge instead of letting it bleed, so the gutter
     moves onto the buy column, which is the only thing that still needs one. */
  /* Stacked, the photo joins the buy column's sequence instead of sitting above
     it, so the whole section becomes one flex column and the two wrappers step
     out of the way with display:contents. That flattens the buy column's children
     into siblings of the stage, which is what lets the order below interleave
     them: `order` only reaches siblings, and the colour row has to land directly
     under the photo it controls. Reading order is the point here. On a phone the
     chips used to sit ~600px below the shot, so changing colour meant scrolling
     back up to see what changed. */
  .pdp { display: flex; flex-direction: column; min-height: 0; padding-right: 0; }
  .pdp__infocol, .pdp__info { display: contents; }
  /* The gutter as margin, not padding: these blocks carry the rail's hairlines,
     and padding would leave the rules running to the screen edge under the text. */
  .pdp__info > * { margin-inline: var(--gutter); }

  .pdp__info > .lbl               { order: 1; margin-top: clamp(30px, 5vw, 44px); }
  .pdp__info > h1                 { order: 2; }
  .pdp__info > .pdp__price        { order: 3; }
  /* The price sits directly under the product name here, where on desktop the
     lede separates them. Its own clamp floors at 1.9rem, within 2px of the h1's
     cap at this width, so the two read as one tier instead of a name and its
     price. Held near h1/1.45 across the stacked range. */
  .pdp__amount { font-size: clamp(1.35rem, 5.6vw, 1.75rem); }
  .pdp__stage                     { order: 4; }
  /* No ink head on the colour rail here. It reads as the rail's opening rule on
     desktop, but stacked it lands just under the photo band's own bottom edge and
     the two rules stack up doing one job. The band's edge is the head now, so the
     rail keeps only the hairline under its row. */
  .pdp__info > .pdp__quick--buy   { order: 5; margin-top: clamp(22px, 3.4vw, 28px); border-top: 0; }
  .pdp__info > .pdp__lede         { order: 6; }
  /* No ink head either, matching the colour rail. Stacked, the rows are the only
     structure the rail needs: the teal label opens each one and the hairline under
     it closes it. The ink rules were doing desktop's job of heading a column, and
     on a phone they read as arbitrary black lines across the copy. */
  .pdp__info > .pdp__quick--specs { order: 7; margin-top: clamp(24px, 4vw, 32px); border-top: 0; }
  .pdp__info > .pdp__cta          { order: 8; margin-bottom: clamp(36px, 6vw, 56px); }

  /* A tinted band between two stretches of paper, so it takes a rule on both
     edges rather than just the one it needed when it was the section's top. */
  .pdp__stage {
    border-right: 0;
    border-block: 1px solid var(--hair-teal);
    margin-top: clamp(26px, 4vw, 34px);
  }
  /* 82%, not 56%. The percentage only matters once it falls under the 380px cap,
     which happens below a ~519px viewport, so nothing from here to 1040 moves.
     Below that the old 56% left the product at 187px on a 390px phone, under half
     the screen, ringed by mist. The photo is the page's opening statement. */
  .pdp__figure { width: min(82%, 380px); }
  /* The rail is a desktop structure. Below this the band is one column: title
     over summary, marker still flush right, and the panel drops its indent. */
  .spex__sum { grid-template-columns: minmax(0, 1fr) 26px; gap: 10px 20px; }
  .spex__title { grid-column: 1; }
  .spex__sumline { grid-column: 1; }
  .spex__mark { grid-column: 2; grid-row: 1 / span 2; }
  .spex__panel { padding-left: 0; }
  .spex__steps { grid-template-columns: 1fr; gap: 28px; }
  .spex__steps p { max-width: 48ch; }
}
@media (max-width: 680px) {
  .spex__sum { gap: 8px 16px; }
  /* The rail becomes label over value. Below the breakpoint the vw term in the h1
     is dead, so it sat at its 36.8px floor and ran to five lines at line-height 1;
     the cap here equals that floor, so the size is continuous across 680, and the
     tracking loosens because -0.025em is a correction for display sizes. */
  .pdp h1 { font-size: clamp(2rem, 8.4vw, 2.3rem); line-height: 1.06; letter-spacing: -0.02em; }
  .pdp__quick li { grid-template-columns: 1fr; gap: 3px; }
  /* The 5px nudge on the buy rail's label is an optical fix for the side-by-side
     row. Stacked, it eats the whole 3px gap and drops COLOR onto its own value. */
  .pdp__quick--buy > li:first-child { padding-block: 16px 20px; }
  .pdp__quick--buy > li:first-child b { top: 0; }
  /* 44px targets. Five chips plus their gaps still run 260px against the 276px of
     content a 320px phone leaves, so the row holds one line. */
  .chip { width: 44px; height: 44px; }
  /* "Size guide" is a button.tlink: 12px type on 4px of padding, so under 20px
     tall. Its underline is a border-bottom, so padding would detach the rule from
     the text. The hit area grows on a pseudo-element and the type stays put. */
  .pdp__cta .tlink { position: relative; }
  .pdp__cta .tlink::after { content: ""; position: absolute; inset: -14px -10px -12px -10px; }

  /* The modal at phone width: 48px of inset plus 32px of padding left 208px of
     content on a 320px screen. Less inset, less padding, and the top clears the
     close button now that it's a 44px target. */
  .sizemodal__panel {
    width: calc(100% - 32px);
    margin-top: clamp(24px, 6vh, 64px);
    max-height: calc(100svh - clamp(24px, 6vh, 64px) - 24px);
    padding: 44px 22px 30px;
  }
  .sizemodal__close { top: 10px; right: 10px; width: 44px; height: 44px; }
  .sizetable th, .sizetable td { padding: 18px 0; }
  .sizetable tbody th { width: 40%; }

  /* The bar exists only here. .has-buybar goes on <body> once at setup, not on
     the is-up class, for two reasons: the footer reserves the bar's height from
     load so revealing it never reflows the page under the reader's thumb, and the
     footer is shared, so the four pages without a bar keep their normal padding. */
  .buybar { display: block; }
  body.has-buybar .site-footer { padding-bottom: 96px; }
}

/* ==========================================================================
   ABOUT — one essay, no sections
   The founder story is the whole page: title, deck, body, nothing else. The
   founder facts live on Contact and in the footer, so nothing is repeated
   here. Everything is carried by the type. One reading column held to the
   left with open margin, matching how the rest of the site aligns.
   ========================================================================== */
.essay {
  /* the teal mist, not paper: About is a single continuous read with no bands to
     alternate, so the page takes one surface end to end and the tint is what
     makes it read as a piece rather than as an untreated document. Mist is the
     lightest tinted surface, so it stays quiet under a long body of prose. */
  background: var(--teal-mist);
  /* the bottom is tighter than the top: the CTA band that follows brings its
     own padding and a surface change, so the piece doesn't need to trail off */
  padding-block: clamp(72px, 10vw, 148px) clamp(72px, 9vw, 124px);
}

.essay__head { margin-bottom: clamp(40px, 5vw, 64px); }
.essay h1 {
  font-weight: 500; letter-spacing: -0.03em; line-height: 0.98;
  /* The floor is the hero's floor, 3.4rem. Both are the first line of a page and
     the only headline on their screen, so a phone shouldn't get a smaller one
     here than it gets on Home. The vw term is dead below ~907px, which means the
     floor is what the whole phone-and-tablet range renders at. */
  font-size: clamp(3.4rem, 6vw, 5.2rem);
  margin-top: 24px;
  max-width: 20ch;
  text-wrap: balance;
}

/* Deck and body share one column width rather than each capping at its own ch
   measure, so the two blocks agree on a left and right edge. Sized in px for
   that reason: a ch cap would land differently at each font size. */
.essay__col { max-width: 760px; }

/* the deck: one paragraph of display type standing in for a standfirst,
   then everything below settles into a single body size and stays there.
   The whole paragraph carries the colour: saturated teal on type, never on a
   word inside a sentence.

   No rule under it. The page is one continuous read with no sections, and the
   teal-to-ink shift already marks the turn from standfirst to body, so a
   hairline doing the same job was the one element arguing with the premise.
   The gap below is deliberately smaller than the old rule's clearance: at the
   full clamp(38px, 5vw, 58px) on both sides, the 116px with nothing in it
   read as a hole rather than as breathing room. */
.essay__deck {
  font-family: var(--display); font-weight: 400;
  font-size: clamp(1.35rem, 2.3vw, 1.9rem);
  line-height: 1.36; letter-spacing: -0.02em;
  color: var(--teal-deep);
}

.essay__body {
  margin-top: clamp(30px, 3.2vw, 42px);   /* see the note on .essay__deck */
  /* a real reading size, larger than the site's default body, on a measure
     tuned for continuous prose rather than for a card or a column */
  font-size: clamp(1.1rem, 1.45vw, 1.25rem);
  line-height: 1.75;
}
.essay__body p { text-wrap: pretty; }
.essay__body p + p { margin-top: 1.35em; }

@media (max-width: 680px) {
  /* "I’m Emily Zhu." is a .nb, so it can't wrap out of trouble: at the 3.4rem the
     base rule holds down to here it runs 303px, and a 320px phone leaves 276px of
     content. So the vw term comes back for the narrow end only. It reaches the
     3.4rem cap at 363px, which means every phone from an iPhone SE up renders the
     full size and only the 320px class steps down, to 48px. Continuous across the
     breakpoint: the cap equals the size at 681px. */
  .essay h1 { font-size: clamp(2.75rem, 15vw, 3.4rem); }
  .essay__body { line-height: 1.68; }
}

/* ==========================================================================
   WHOLESALE (page)
   The one page addressed to shops rather than skaters, and the only one with no
   product photography: a buyer wants the terms, not another studio shot, and
   there is no shop-facing imagery to use. So the page is carried by type and by
   the hairline rail, and its single visual moment is the five real microfiber
   crops sitting inside the spec rail, which is also the most useful thing a
   buyer looks at. Surfaces run mist / paper / mist / paper down the page.
   ========================================================================== */

/* ---- hero ----------------------------------------------------------------
   One left column, the same shape the About essay head takes: headline across
   the measure, supporting copy beneath it. Not the two-column split the Home
   wholesale teaser uses, which is where this started.

   The headline is why. "A proven skate-care essential" needs about 690px at the
   hero size, and half of the wrap is 552px, so in a two-column hero it broke
   after "skate-care" and left "essential" alone on its own line. The only sizes
   that fit that phrase into half a wrap are under 2.8rem, which puts the hero
   headline level with the section heads and flattens the page's type ladder.
   Given the full measure the line fits at 4.4rem, and on a page with no
   photography the type is the only thing that can carry the first screen.

   Contained, not the 100svh the Home and Products heroes take. The rail below is
   the most valuable thing on the page, so the first screen has to reach it.
   ------------------------------------------------------------------------- */
.whero {
  background: var(--teal-mist);
  border-bottom: 1px solid var(--hair-teal);
  padding-block: clamp(64px, 8vw, 104px);
}
.whero h1 {
  font-weight: 500; letter-spacing: -0.03em; line-height: 1.0;
  font-size: clamp(2.5rem, 5.2vw, 4.4rem);
  margin-top: 22px; max-width: 24ch;
}
/* --muted-wash, not --muted: on the mist the standard grey only reaches 3.5:1 */
.whero__side { margin-top: clamp(32px, 4vw, 44px); }
.whero__side p { color: var(--muted-wash); font-size: 18px; line-height: 1.62; max-width: 52ch; }
.whero__actions { display: flex; align-items: center; gap: 20px 24px; margin-top: 30px; flex-wrap: wrap; }

/* ---- wholesale at a glance ----------------------------------------------
   The label rail at page scale, the same grammar the Products accordion rows and
   the Contact detail list use: term left inside --rail-page, value right,
   hairline between rows, opening on the solid ink rule. Five terms that a buyer
   scans in one pass, in one column each, so nothing has to be hunted for.
   ------------------------------------------------------------------------- */
.glance__list { border-top: 1px solid var(--ink); margin: 0; }
.glance__row {
  display: grid; grid-template-columns: var(--rail-page) minmax(0, 1fr);
  /* baseline, not start: the term and the value are different sizes, so matching
     their box tops would leave their first lines a few px apart */
  align-items: baseline; gap: 24px;
  padding-block: clamp(22px, 2.8vw, 32px);
  border-bottom: 1px solid var(--hair);
}
/* matched to .spex__title, so the site's two page-scale rails agree on the size
   of a term. Ink, not teal: the terms are this section's structure, not accent. */
.glance__row dt {
  font-family: var(--display); font-weight: 500;
  letter-spacing: -0.02em; line-height: 1.2;
  font-size: clamp(1.2rem, 1.55vw, 1.4rem);
  color: var(--ink); margin: 0;
}
/* matched to .spex__sumline for the same reason, but on a wider measure: that one
   is a lead-in sitting beside an accordion marker, and this one is the whole
   value of the row. At 52ch the longest of the five broke to a second line, which
   turned a list built to be read in one pass into five two-line paragraphs. 68ch
   holds every value on one line, so the rail scans as five rows, not ten. */
.glance__row dd {
  color: var(--muted-wash); font-size: 15.5px; line-height: 1.6;
  max-width: 68ch; margin: 0;
}

/* The swatches are the .chip from the buy box with the interaction taken out:
   no cursor, no hover, no ring. Each one is a crop of the real product
   photography, so the row shows the microfiber pile itself rather than a flat
   hex circle. Decorative by construction, the row's own value text names all
   five colors, so the markup hides them from assistive tech. */
.glance__colors { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 16px; }
.glance__sw {
  width: 40px; height: 40px;
  border: 1px solid var(--hair);
  background-color: var(--cool);
  background-position: center; background-size: cover; background-repeat: no-repeat;
}

/* ---- how it works -------------------------------------------------------
   Numbered, because this one actually is a sequence: you cannot build an
   assortment before you have the pricing. Same test that keeps 01/02/03 on
   "How to use it" and keeps numbering off the pillars.

   The difference from that block is the rule. There, each step carries its own
   top hairline with a gap either side, because the steps sit inside an accordion
   panel. Here the process is the whole section, so the four rules abut into one
   unbroken line running gutter to gutter and the numbers read as stops along it.
   That is what the gap:0 is for: the columns are opened by padding-right on each
   step instead of by a grid gap, so the rules meet.
   ------------------------------------------------------------------------- */
.works__track {
  /* the ul reset doesn't reach an ol, and the numbers are already drawn by
     .works__num, so the native markers have to go explicitly */
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 0;
}
.works__step {
  border-top: 1px solid var(--hair-teal);
  padding: clamp(26px, 3vw, 34px) clamp(20px, 2.6vw, 40px) 0 0;
}
.works__step:last-child { padding-right: 0; }
/* A step below the three-up titles on Products. Four columns inside the wrap
   leaves ~254px of text, and at the shared 1.85rem "Build your assortment" was
   the only title to take two lines, which pushed one body copy out of step with
   the other three. Sized so all four titles set on one line instead. */
.works__step h3 { font-size: clamp(1.2rem, 1.5vw, 1.45rem); line-height: 1.14; }
/* --muted-wash on the mist, which is the one thing this can't share with
   .spex__steps p: that block sits on paper, where --muted clears AA */
.works__step p { margin-top: 12px; color: var(--muted-wash); font-size: 15.5px; line-height: 1.6; }

@media (max-width: 1040px) {
  /* two up, and the rules still abut across each pair */
  .works__track { grid-template-columns: 1fr 1fr; }
  .works__step:nth-child(2n) { padding-right: 0; }
  .works__step:nth-child(2n + 1) { padding-right: clamp(20px, 2.6vw, 40px); }
}
@media (max-width: 900px) {
  /* the rail is a desktop structure: stacked, the term sits over its value and
     the row keeps only the hairline between them, like .pdp__quick on a phone */
  .glance__row { grid-template-columns: 1fr; gap: 8px; }
}
@media (max-width: 680px) {
  .works__track { grid-template-columns: 1fr; }
  /* the :nth-child selector has to be repeated, not just overridden by the plain
     class: the 1040px rule above is still in force at this width and outranks a
     bare .works__step on specificity */
  .works__step, .works__step:nth-child(2n + 1) { padding-right: 0; }
}

/* ==========================================================================
   FOOTER (shared)
   ========================================================================== */
.site-footer { background: var(--navy); color: #fff; padding-block: clamp(56px, 7vw, 84px) 30px; }
.footer-top { display: grid; grid-template-columns: 1.5fr 1fr 1fr; gap: clamp(28px, 5vw, 56px); padding-bottom: 44px; border-bottom: 1px solid var(--hair-dark); }
.footer-brand img { height: 24px; margin-bottom: 20px; filter: brightness(0) invert(1); }
.footer-brand p { color: var(--on-dark); font-size: 14.5px; max-width: 32ch; }
.footer-col h5 { font-family: var(--display); text-transform: uppercase; letter-spacing: 0.16em; font-size: 11px; color: rgba(255,255,255,0.5); margin: 0 0 16px; font-weight: 600; }
.footer-col a, .footer-col p { display: block; color: var(--on-dark); font-size: 14.5px; padding-block: 6px; }
.footer-col a:hover { color: var(--teal); }
.footer-bottom { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 10px; padding-top: 26px; }
.footer-bottom span { font-family: var(--display); text-transform: uppercase; letter-spacing: 0.14em; font-size: 10.5px; color: rgba(255,255,255,0.5); }

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width: 960px) {
  /* stack, but keep the hero filling the whole small screen (panel takes the remaining height) */
  .hero .wrap { grid-template-columns: 1fr; grid-template-rows: auto 1fr; min-height: calc(100svh - var(--header-h)); }
  .hero__l { padding-bottom: clamp(36px, 6vw, 64px); max-width: none; }
  .hero__panel { border-left: 0; border-top: 1px solid var(--hair); min-height: 340px; margin: 0 calc(var(--gutter) * -1); }
  .diff .wrap { grid-template-columns: 1fr; }
  /* Both grids of .why__card break together: the pair inside the Products
     accordion is the same component as Home's pillars, and holding it 2-up to
     680 left two ~300px cards through the whole tablet band. */
  .why__cards,
  .spex__cards { grid-template-columns: 1fr; }
  /* stacked, the cards are full width and there's nothing left to align to,
     so the forced two-line title break would just look arbitrary */
  .why__title br { display: none; }
  .wholesale .wrap { grid-template-columns: 1fr; }
  /* minmax(0, …) not 1fr: a bare 1fr track floors at min-content, and on phones
     this column holds the reviews carousel — a nowrap flex row whose min-content
     width is the sum of all ten cards. That floor dragged the track to 1492px and
     took the whole page horizontal. */
  .reviews__grid { grid-template-columns: minmax(0, 1fr); gap: clamp(28px, 5vw, 44px); }
  .reviews__stat { position: static; }
  .footer-top { grid-template-columns: 1fr 1fr; }
  .footer-brand { grid-column: span 2; }
}
@media (max-width: 680px) {
  /* ---- header: two-tier fallback ----------------------------------------
     What renders with scripting off. The header splits into two tiers rather
     than dropping the nav: logo on top, the five links spread across a second
     row under a hairline that matches the header's own bottom rule. All five
     labels sit on one line down to a 320px phone, where only 276px of content
     width is left, which is what forces the ~10px size and the tracking drop
     from the brand's 0.16em. Legible enough to be reachable, not good enough
     to be the design: the .js block below replaces it with the disclosure
     menu. --header-h keeps every 100svh section in step with the tier count. */
  :root { --header-h: 116px; }
  .site-header .wrap {
    flex-direction: column; align-items: stretch; justify-content: center; gap: 0;
  }
  .brand { display: flex; align-items: center; height: 64px; }
  .nav {
    border-top: 1px solid var(--hair-teal);
    margin-inline: calc(var(--gutter) * -1); padding-inline: var(--gutter);
  }
  .nav__list {
    justify-content: space-between; align-items: center; gap: 6px;
    height: 52px;
  }
  .nav a { font-size: clamp(9.75px, 2.75vw, 10.5px); letter-spacing: 0.07em; }

  /* ---- header: disclosure menu (enhanced) -------------------------------
     .js is set by assets/js/nav.js before first paint. html.js is (0,1,1) and
     beats the :root rule above at (0,1,0), so the header collapses back to one
     64px row and the links move into an overlay panel at a readable 16px with
     48px rows. This is what phones actually get; everything above is the
     no-script floor. */
  html.js { --header-h: 64px; }
  .js .site-header .wrap { flex-direction: row; align-items: center; justify-content: space-between; }
  .js .brand { height: auto; }
  .js .nav { border-top: 0; margin-inline: 0; padding-inline: 0; position: static; }
  /* Bars only, no label: the icon is the one thing in the header that has to
     read as a control, so it carries the teal instead of a word. */
  .js .nav__toggle {
    display: flex; align-items: center; justify-content: flex-end;
    /* 44px of target in both axes, with the bars ending on the same gutter
       line the logo starts from */
    min-height: 44px; min-width: 44px; padding: 0;
    appearance: none; -webkit-appearance: none;
    background: none; border: 0; cursor: pointer;
    color: var(--teal-deep);
  }
  /* three hairlines, the same 1px the rest of the header is drawn with */
  .js .nav__bars {
    position: relative; display: block; width: 20px; height: 2px;
    background: currentColor;
  }
  .js .nav__bars::before,
  .js .nav__bars::after {
    content: ""; position: absolute; left: 0; width: 20px; height: 2px;
    background: currentColor; transition: transform 0.18s ease, top 0.18s ease;
  }
  .js .nav__bars::before { top: -6px; }
  .js .nav__bars::after  { top: 6px; }
  /* open: the outer bars close into an X and the middle one gets out of the way */
  .js .nav--open .nav__bars { background: transparent; }
  .js .nav--open .nav__bars::before { top: 0; transform: rotate(45deg); }
  .js .nav--open .nav__bars::after  { top: 0; transform: rotate(-45deg); }

  .js .nav__list {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    z-index: 21;                          /* over the sticky header's own 20 */
    /* align-items/justify-content are reset explicitly: the fallback rule above
       sets them for a centred 52px row, and left in place they shrink-wrap each
       link and centre it instead of running it to the gutter */
    flex-direction: column; gap: 0; height: auto;
    align-items: stretch; justify-content: flex-start;
    background: var(--paper);
    border-bottom: 1px solid var(--hair-teal);
    padding-inline: var(--gutter);
    box-shadow: 0 14px 24px rgba(22, 30, 34, 0.08);
  }
  .js .nav--open .nav__list { display: flex; }
  .js .nav__list li + li a { border-top: 1px solid var(--hair); }
  .js .nav a {
    display: block; padding-block: 14px;   /* 48px rows */
    font-size: 16px; letter-spacing: 0.12em;
  }

  /* ---- hero ------------------------------------------------------------- */
  /* Centred on phones only. The desktop hero is a two-column split where the
     copy holds a left axis against the product panel; stacked, there is no
     second column to hold that axis and the product below is centred in its
     own panel, so a left rag reads as off-centre from it. align-items does the
     work for the block-level children (.hero__l is a flex column) and
     text-align for the lines inside them. */
  .hero__l { align-items: center; text-align: center; }
  .hero__sub { margin-inline: auto; }
  .hero__cta { justify-content: center; }

  /* ---- centred heading blocks (phones) ----------------------------------
     Every section that leads with an eyebrow-and-heading pair centres to match
     the hero, so the page reads on one axis instead of alternating. The close
     and the colour rail were already centred at every width, so this brings the
     three that weren't into line. The measures that were capped in ch need
     margin-inline to actually sit centred rather than just having centred text.
     Card interiors stay left: .why__card and .review-card are multi-line body
     copy, and a centred rag there costs more than the symmetry is worth. */
  .why__head { text-align: center; }
  .why__h { justify-content: center; }

  .reviews__stat { text-align: center; }
  .reviews__score { justify-content: center; }
  .reviews__meta { margin-inline: auto; }

  /* scoped to the two teaser columns, which are Home-only: the bare .wholesale
     class also sits on sections of the Wholesale page itself */
  .wholesale__copy { text-align: center; }
  .wholesale__copy h2 { margin-inline: auto; }
  .wholesale__side { text-align: center; }
  .wholesale__side p { margin-inline: auto; }
  /* The glass pane dissolves here. On desktop it is a column of its own holding an
     annotated product, and the callout labels sit outside the photo in the gap
     beside it. Stacked on a phone there is no gap to put them in, so the labels
     come down into a row underneath and the pane has nothing left to be: its
     hairline, its own white surface and its 340px floor turned the product into a
     separate band with one object floating in the middle of it. Without them the
     photo sits straight on the hero's field and the spec row gives it something to
     be next to. The leader lines stay off, having nothing left to point at. */
  .hero__panel {
    background: none;
    -webkit-backdrop-filter: none; backdrop-filter: none;
    border-top: 0; box-shadow: none;
    min-height: 0;
    padding-bottom: clamp(30px, 8vw, 52px);
  }
  .hero__l { padding-bottom: 10px; }
  .leaders { display: none; }
  /* photo across the top, the three specs as columns under it */
  .prod {
    width: min(90%, 360px);
    display: grid; grid-template-columns: repeat(3, 1fr);
    gap: 20px 10px; align-items: start;
  }
  /* The photo steps in from the grid's full width while the spec row keeps it: the
     three columns need every pixel to hold their values on one line at 320px, so
     the image is narrowed rather than .prod itself. */
  .prod > img {
    grid-column: 1 / -1;
    width: min(86%, 310px); justify-self: center;
  }
  /* sized for a product centred in a tall pane; under a grid it reads as a smudge */
  .prod::after { display: none; }
  /* the callouts stop being callouts: no anchor to the photo's edges, no vertical
     centring on a leader line that no longer exists */
  .cal { position: static; transform: none; text-align: center; }
  .cal b { font-size: 9.5px; letter-spacing: 0.1em; margin-bottom: 3px; }
  .cal span { font-size: 12px; }
  .colors__track .cc { flex: 0 0 40vw; }
  /* the marquee runs ~8x the viewport wide and never stops, so keep it on its
     own compositor layer rather than repainting the band */
  .colors__track { will-change: transform; }
  /* one column of phone width isn't wide enough to hold the accent phrase on
     its own line, so these headings go back to reflowing */
  .cta-final h2 br,
  .whero h1 br { display: none; }

  /* ---- reviews: sideways carousel ---------------------------------------
     The vertical well was a 440px nested scroller holding ten full-length
     reviews across nearly the full width, which left almost no page gutter to
     swipe past it. Here the same ten cards become one snapping row instead, so
     a vertical swipe always belongs to the page and a horizontal one to the
     reviews. display:contents dissolves the two column wrappers so the cards
     become flex items of the track directly, which is why this needs no markup
     of its own. */
  .reviews__masonry { display: flex; gap: 16px; align-items: start; min-width: 0; }
  .reviews__col { display: contents; }
  .reviews__scroll {
    max-height: none;
    min-width: 0;                          /* see the minmax note in the 960 block */
    /* overflow-y stays hidden rather than visible: with one axis scrollable a
       computed `visible` would become `auto` and add a stray vertical bar. The
       block padding is the focus ring's room, since hidden would clip it. */
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x mandatory;
    /* The snapport is the scrollport, and padding is not part of it: without this
       the first card's start edge snaps to the viewport edge, which scrolls the
       gutter away and lands the card flush against it. scroll-padding insets the
       snapport to match the padding so the cards snap onto the gutter instead. */
    scroll-padding-inline: var(--gutter);
    overscroll-behavior-x: contain;        /* don't chain the swipe to the page */
    padding: 4px var(--gutter);
    margin-inline: calc(var(--gutter) * -1);   /* full-bleed track, cards on the gutter */
    scrollbar-width: none;
  }
  .reviews__scroll::-webkit-scrollbar { display: none; }
  /* 86%, so the next card always peeks: that edge is the affordance, in place of
     the dots a carousel would otherwise need JavaScript to keep in sync */
  .review-card {
    flex: 0 0 86%;
    scroll-snap-align: start; scroll-snap-stop: always;
  }
  /* the fade was built for a vertical well */
  .reviews__well::after { display: none; }

  /* ---- micro-type and tap targets ---------------------------------------
     The smallest labels get a step up, and the heavily tracked ones lose some
     letter-spacing: at 11px the tracking costs more legibility than the size
     does. Link targets come up to the 44px floor. */
  .review-card cite,
  .footer-bottom span { font-size: 11px; }
  .lbl, .why__eyebrow, .footer-col h5 { letter-spacing: 0.14em; }
  .tlink { display: inline-block; padding-block: 13px 12px; }   /* 45px target */
  .footer-col a, .footer-col p { padding-block: 11px; }

  /* ---- footer ----------------------------------------------------------- */
  .footer-top { grid-template-columns: 1fr; }
  .footer-brand { grid-column: span 1; }
}

:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
/* --teal is only 2.9:1 on the mist and 2.6:1 on the wash, under the 3:1 a focus
   indicator needs. Same hue a step darker on the two tinted surfaces. */
.colors :focus-visible,
.reviews :focus-visible,
.wholesale :focus-visible,
.whero :focus-visible,
.works :focus-visible { outline-color: var(--teal-deep); }
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; scroll-behavior: auto !important; } }
