/* ============================================================
   Fonts — self-hosted on purpose.
   Loading these from fonts.googleapis.com would send every
   visitor's IP address to Google without consent, which LG
   München I ruled a GDPR breach (3 O 17493/20). Hosting them
   here means no request ever leaves this domain.
   Both are SIL Open Font License 1.1; the licence text sits in
   assets/fonts/OFL.txt.
   ============================================================ */
@font-face {
  font-family: "Anton";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/anton-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Anton";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/anton-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;
}
/* Inter ships as a variable font: one file covers 400–800 */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("assets/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("assets/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;
}

/* ============================================================
   BURST — waitlist page styles
   Type carries the page: Anton (display) + Inter (body).
   Palette: black #0A0A0A · white #FFFFFF · orange #E8420E (sparing).
   ============================================================ */

:root {
  /* ---- constants: identical in both colorways ---- */
  --black:  #0A0A0A;
  --white:  #FFFFFF;
  --orange: #E8420E;                        /* the BURST accent — never themed */
  --orange-deep: #D63A0B;                   /* button hover, keeps 4.7:1 on white text */

  /* ============================================================
     COLORWAY: HERO WHITE (default) — white sock on a dark stage.
     Every value below has a Core Black counterpart further down.
     Contrast ratios are noted where they are load-bearing.
     ============================================================ */
  --bg:        #0A0A0A;                     /* page */
  --heading:   #FFFFFF;                     /* display type + strong labels */
  --text:      rgba(255, 255, 255, 0.92);   /* 18.9:1 */
  --text-dim:  rgba(255, 255, 255, 0.60);   /*  7.3:1 */
  --text-faint:rgba(255, 255, 255, 0.50);   /*  5.3:1 — AA floor for 12–13px */
  --line:      rgba(255, 255, 255, 0.10);
  --hex:       rgba(255, 255, 255, 1);      /* grip texture stroke */
  --hex-alpha: 0.05;
  --surface:   #161616;                     /* input pill */
  --surface-2: #1B1B1B;                     /* input pill, focused */
  --hover:     #0F0F0F;                     /* zone tile hover */
  --nav-bg:    rgba(10, 10, 10, 0.72);      /* scrolled nav backdrop */
  --error:     #F2999B;                     /* 9.2:1 */
  --btn-ring:  #FFFFFF;                     /* focus ring on orange buttons */
  --glow:      rgba(232, 66, 14, 0.08);     /* ambience behind the sock */
  --btn-glow:  rgba(232, 66, 14, 0.42);     /* halo around the CTA */
  --ground:    rgba(232, 66, 14, 0.26);     /* shadow under the sole */

  --font-display: "Anton", "Arial Narrow", sans-serif;
  --font-body:    "Inter", -apple-system, "Helvetica Neue", sans-serif;

  --container: 1120px;
  --nav-h: 64px;
  --pill: 999px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);        /* calm — supporting content */
  --ease-burst: cubic-bezier(0.2, 0.9, 0.24, 1); /* attack — section openers */
}

/* ============================================================
   COLORWAY: CORE BLACK — black sock on a soft, warm off-white.
   Only the tokens change; no filters, no inversion. The orange
   accent is deliberately identical in both.
   ============================================================ */
:root[data-colorway="black"] {
  --bg:        #F5F3F1;                     /* soft off-white, not pure #FFF */
  --heading:   #0A0A0A;
  --text:      rgba(10, 10, 10, 0.92);      /* 15.2:1 */
  --text-dim:  rgba(10, 10, 10, 0.68);      /*  6.7:1 */
  --text-faint:rgba(10, 10, 10, 0.58);      /*  4.7:1 — AA floor for 12–13px */
  --line:      rgba(10, 10, 10, 0.12);
  --hex:       rgba(10, 10, 10, 1);
  --hex-alpha: 0.06;
  --surface:   #EAE7E3;
  --surface-2: #E3DFDA;
  --hover:     #EFECE8;
  --nav-bg:    rgba(245, 243, 241, 0.72);
  --error:     #B3261E;                     /* 5.9:1 */
  --btn-ring:  #0A0A0A;                     /* white ring would vanish here */
  --glow:      rgba(232, 66, 14, 0.10);
  --btn-glow:  rgba(232, 66, 14, 0.34);     /* reads stronger on off-white */
  --ground:    rgba(232, 66, 14, 0.18);
}

/* Dark type on a light background renders too thin with "antialiased" —
   that setting exists to stop light-on-dark type from blooming. Hand
   the light colorway back to the platform's normal rendering so 700
   actually looks like 700 in both. */
:root[data-colorway="black"] body {
  -webkit-font-smoothing: auto;
  -moz-osx-font-smoothing: auto;
}

/* Colour changes ride a short fade so the switch reads as one move */
body,
.nav,
.zone,
.footer,
.signup-form__field,
.zones__grid {
  transition: background-color 0.45s var(--ease), color 0.45s var(--ease),
              border-color 0.45s var(--ease);
}

/* ---------- Reset (minimal) ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
img { max-width: 100%; display: block; }
h1, h2, h3, p, ul, figure { margin: 0; }
ul { padding: 0; list-style: none; }
a { color: inherit; }

/* Keyboard focus — the one place orange is loud on purpose.
   No border-radius here on purpose: the outline follows each
   element's own radius, so pills stay pill-shaped. */
:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 3px;
}

/* ---------- Utilities ---------- */
.container {
  width: min(var(--container), 92vw);
  margin-inline: auto;
}
.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: fixed; top: -100%; left: 16px; z-index: 100;
  padding: 10px 18px;
  background: var(--heading); color: var(--bg);
  border-radius: var(--pill);
  font-size: 14px; font-weight: 600; text-decoration: none;
  transition: top 0.2s var(--ease);
}
.skip-link:focus-visible { top: 12px; }
.svg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
/* The hexagon pattern lives in a shared <defs>. CSS overrides its
   presentation attributes, so the texture follows the colorway. */
.svg-defs g { stroke: var(--hex); stroke-opacity: var(--hex-alpha); }

/* Wordmark: both files sit in the DOM, CSS picks the readable one */
.wordmark--on-light { display: none; }
:root[data-colorway="black"] .wordmark--on-dark  { display: none; }
:root[data-colorway="black"] .wordmark--on-light { display: block; }
.accent { color: var(--orange); }

/* Display type: one voice, used everywhere a headline appears */
.hero__title, .hook__title, .zones__title, .signup__title {
  font-family: var(--font-display);
  font-weight: 400;                 /* Anton ships a single weight */
  text-transform: uppercase;
  letter-spacing: 0.005em;
  line-height: 0.94;
  color: var(--heading);
}

/* Eyebrow label */
.eyebrow {
  display: flex; align-items: center; gap: 10px;
  font-size: 13px; font-weight: 600;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--text-dim);
}
.eyebrow__dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--orange);
  flex: none;
}

/* ---------- Hex grip texture (decorative layers) ----------
   Used in three places only: hero (top), behind the product shot,
   and the signup section (bottom). Hook and zones stay plain black
   so the texture reads as an accent, not wallpaper.
   The 32px vertical overhang is slack for the grip-drag in main.js. */
.hex-bg {
  position: absolute; inset: 0;
  overflow: hidden;            /* texture can never bleed into a neighbour */
  pointer-events: none;
  z-index: 0;
}
/* The artwork is taller than its box, so the grip-drag in main.js can
   shift it without ever exposing an edge. Clipped by the rule above. */
.hex-bg svg {
  position: absolute;
  left: 0; top: -32px;
  width: 100%; height: calc(100% + 64px);
  will-change: transform;
}
/* Full-bleed variant for sections that are themselves .container */
.hex-bg--bleed { left: calc(50% - 50vw); right: calc(50% - 50vw); }

.hex-bg--hero {
  -webkit-mask-image: radial-gradient(120% 90% at 50% 0%, #000 0%, transparent 68%);
          mask-image: radial-gradient(120% 90% at 50% 0%, #000 0%, transparent 68%);
}
.hex-bg--product {
  -webkit-mask-image: radial-gradient(62% 58% at 50% 52%, #000 0%, transparent 72%);
          mask-image: radial-gradient(62% 58% at 50% 52%, #000 0%, transparent 72%);
}
.hex-bg--signup {
  -webkit-mask-image: radial-gradient(110% 100% at 50% 100%, #000 0%, transparent 70%);
          mask-image: radial-gradient(110% 100% at 50% 100%, #000 0%, transparent 70%);
}

/* ============================================================
   NAV
   ============================================================ */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  transition: background-color 0.3s ease, border-color 0.3s ease,
              backdrop-filter 0.3s ease;
  border-bottom: 1px solid transparent;
}
.nav.is-scrolled {
  background: var(--nav-bg);
  -webkit-backdrop-filter: blur(14px);
          backdrop-filter: blur(14px);
  border-bottom-color: var(--line);
}
.nav__inner {
  width: min(var(--container), 92vw);
  margin-inline: auto;
  height: var(--nav-h);
  display: flex; align-items: center; justify-content: space-between;
}
.nav__brand { display: inline-flex; align-items: center; }
.nav__wordmark { height: 15px; width: auto; }

/* ---------- Buttons ---------- */
.btn {
  position: relative;
  isolation: isolate;                /* keeps the ping behind the label,
                                        not behind the button itself */
  display: inline-flex; align-items: center; justify-content: center;
  padding: 14px 28px;
  border: 0; border-radius: var(--pill);
  background: var(--orange); color: var(--white);
  font-family: var(--font-body);
  /* Set in caps from CSS, not in the markup — the DOM keeps normal
     sentence case for screen readers, which read all-caps letter
     by letter in some voices. */
  font-size: 14px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em;
  line-height: 1; text-decoration: none; white-space: nowrap;
  cursor: pointer;
  /* Standing halo — presence without motion */
  box-shadow: 0 0 22px -2px var(--btn-glow), 0 3px 12px -4px var(--btn-glow);
  transition: transform 0.25s var(--ease), background-color 0.25s ease,
              box-shadow 0.25s ease;
}
/* Expanding ring: a slow ping rather than a scale-bounce. Scaling the
   button itself reads as a cheap ad; a ring leaving it reads as signal.
   Sits behind the label and never intercepts clicks. */
/* Arrow after the label. ::after already carries the pulse ring, so
   this rides on ::before and is moved to the far side with flex order.
   The empty alt text ("/ \"\"") keeps screen readers from announcing
   "right arrow" after every button. */
.btn::before {
  content: "\2192" / "";
  order: 1;                          /* text is order 0, so this lands after it */
  margin-left: 10px;
  font-size: 1.15em;
  line-height: 0;
  transition: transform 0.25s var(--ease);
}
@media (hover: hover) {
  .btn:hover::before { transform: translateX(3px); }
}

.btn::after {
  content: "";
  position: absolute; inset: 0;
  z-index: -1;
  border-radius: inherit;
  border: 2px solid var(--orange);
  opacity: 0;
  pointer-events: none;
  animation: btn-ping 3s var(--ease) infinite;
}
@keyframes btn-ping {
  0%        { transform: scale(1);    opacity: 0.5; }
  55%, 100% { transform: scale(1.16); opacity: 0; }
}
/* Hover deepens rather than lightens: a lighter orange would drop
   white-text contrast to 3.3:1, this keeps it at 4.7:1. */
@media (hover: hover) {
  .btn:hover {
    background: var(--orange-deep);
    transform: translateY(-1px);
    box-shadow: 0 0 30px -2px var(--btn-glow), 0 6px 18px -4px var(--btn-glow);
  }
}
.btn:active { transform: translateY(0); }
/* White ring on the orange buttons — an orange-on-orange ring would blend */
.btn:focus-visible { outline-color: var(--btn-ring); }
.btn--nav { padding: 10px 18px; font-size: 12px; letter-spacing: 0.07em; }
/* The nav CTA keeps the halo but not the ping: it is on screen the
   whole time, so a permanent pulse in the corner turns into noise —
   and its expanding ring widened the scroll area past the viewport.
   The two form buttons pulse instead; they are the actual moment. */
.btn--nav::after { content: none; }

/* Hero entrance: the page should not arrive fully formed. One short
   staggered rise, on load only — no scroll trigger, no repeat. */
@keyframes hero-in {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}
.hero__inner > * { animation: hero-in 0.7s var(--ease) both; }
.hero .eyebrow     { animation-delay: 0.06s; }
.hero__title       { animation-delay: 0.16s; }
.hero__sub         { animation-delay: 0.30s; }
.hero .signup-form { animation-delay: 0.40s; }
.hero__micro       { animation-delay: 0.50s; }

/* ============================================================
   HERO
   ============================================================ */
.hero {
  position: relative;
  /* Bottom gap belongs to the seam below, so it has one source */
  padding: calc(var(--nav-h) + clamp(64px, 12vh, 128px)) 0 0;
  text-align: center;
}
.hero__inner {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; align-items: center;
}
.hero .eyebrow { justify-content: center; }

.hero__title {
  margin-top: 28px;
  font-size: clamp(64px, 14.5vw, 176px);
}
.hero__sub {
  margin-top: 24px;
  max-width: 34em;
  font-size: clamp(16px, 2vw, 19px);
  color: var(--text-dim);
}

.hero__micro {
  margin-top: 18px;
  font-size: 13px;
  color: var(--text-faint);
}
/* Section-opening tag — centered, sits just above the hero shot */
.product__tag {
  position: relative; z-index: 1;    /* above the texture */
  display: block;
  width: fit-content;
  margin: 0 auto clamp(28px, 5vh, 48px);
  padding: 8px 16px;
  border: 1px solid var(--line);
  border-radius: var(--pill);
  font-size: 12px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-dim);
}

/* ---------- Signup form (shared: hero + founding-drop CTA) ---------- */
.signup-form {
  margin-top: 40px;
  width: min(520px, 100%);
}
/* Opaque on purpose: a translucent fill would let the hex texture
   show through the pill and read as stray hairlines. */
.signup-form__field {
  display: flex; gap: 8px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--pill);
  transition: border-color 0.25s ease, background-color 0.25s ease;
}
.signup-form__field:focus-within {
  border-color: var(--text-dim);
  background: var(--surface-2);
}
.signup-form__input {
  flex: 1; min-width: 0;
  padding: 12px 16px 12px 20px;
  background: transparent; border: 0;
  color: var(--text);
  font-family: var(--font-body); font-size: 16px;
}
.signup-form__input::placeholder { color: var(--text-faint); }
/* Desktop: no orange ring. Text inputs match :focus-visible even on a
   plain mouse click (that is per spec — they take keyboard input), so
   an outline here would flash on every click.
   The focus indicator is the border instead: it jumps from barely
   visible to clearly drawn, which stays perceivable for keyboard users
   without shouting at everyone who simply clicks the field. */
.signup-form__input:focus { outline: none; }
.signup-form__field:has(.signup-form__input:focus-visible) {
  border-color: var(--text-dim);
  background: var(--surface-2);
}
/* Fallback for browsers without :has() — the input keeps a ring rather
   than leaving keyboard users with no indicator at all. */
@supports not selector(:has(*)) {
  .signup-form__input:focus-visible { outline: 2px solid var(--orange); }
}

/* Consent notice. Default (no JS) is simply visible. With JS it starts
   collapsed and opens once the field is touched — the grid-rows trick
   animates the height without hardcoding one. It never collapses again;
   see the note in index.html for why that matters. */
.signup-form__consent {
  display: grid;
  grid-template-rows: 1fr;
  margin-top: 10px;
  opacity: 1;
}
.signup-form__consent > p {
  overflow: hidden;
  min-height: 0;              /* without this a 0fr row still keeps
                                 the paragraph's min-content height */
  margin: 0;
  max-width: 44ch;
  margin-inline: auto;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-faint);
}
.js .signup-form__consent {
  grid-template-rows: 0fr;
  margin-top: 0;
  opacity: 0;
  transition: grid-template-rows 0.4s var(--ease), opacity 0.3s var(--ease),
              margin-top 0.4s var(--ease);
}
.js .signup-form.is-engaged .signup-form__consent {
  grid-template-rows: 1fr;
  margin-top: 10px;
  opacity: 1;
}

/* Collapses while empty: the reserved line was an invisible block
   between the button and the line below it. A message appearing now
   shifts the following line down, which is the better trade. */
.signup-form__msg:empty { display: none; }
.signup-form__msg {
  min-height: 22px;
  margin-top: 10px;
  font-size: 14px;
  color: var(--text-dim);
}
.signup-form__msg.is-error   { color: var(--error); }
.signup-form__msg.is-success { color: var(--heading); }

/* Stack the pill on small screens */
@media (max-width: 480px) {
  /* Stacked layout: a ring around the field would loop around the
     button and the consent notice too, so it hugs the input instead —
     same pill shape as the button below it. */
  .signup-form__field:has(.signup-form__input:focus-visible) {
    outline: none;
    border-color: var(--line);
  }
  .signup-form__input {
    border-radius: var(--pill);
  }
  .signup-form__input:focus-visible {
    outline: 2px solid var(--orange);
    outline-offset: 2px;
  }
  /* The notice joins the pill into one box instead of floating below it */
  .signup-form.is-engaged .signup-form__field {
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
  }
  .js .signup-form.is-engaged .signup-form__consent {
    margin-top: 0;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--line);
    border-top: 0;
    border-radius: 0 0 24px 24px;
  }
  :root[data-colorway="black"] .js .signup-form.is-engaged .signup-form__consent {
    background: rgba(10, 10, 10, 0.04);
  }
  /* Padding only once open — otherwise it keeps the collapsed row
     16px tall and the notice never fully disappears.
     max-width is lifted here: the 44ch cap forces a third line inside
     the narrower mobile box. */
  .signup-form__consent > p {
    padding: 2px 16px 14px;
    max-width: none;
    font-size: 11.5px;
  }
  .js .signup-form:not(.is-engaged) .signup-form__consent > p { padding: 0; }
  .signup-form__field {
    flex-direction: column;
    border-radius: 24px;
    padding: 8px;
  }
  .signup-form__input { padding: 12px 14px; text-align: center; }
  .signup-form__btn { width: 100%; }
}

/* ============================================================
   PRODUCT IMAGE
   ============================================================ */
.product { position: relative; }

/* Stage taller than the viewport gives the pinned block its scroll
   distance. Change --stage-scroll to make the pause longer/shorter. */
.product__stage {
  --stage-scroll: 95vh;
  /* Shared by the frame and the note overlay so the notes' vertical
     percentages line up with the sock. Change in one place only. */
  --frame-h: clamp(280px, calc(100vh - 390px), 600px);
  position: relative;
  height: calc(100vh + var(--stage-scroll));
}
.product__pinned {
  position: sticky; top: 0;
  height: 100vh;
  padding-top: var(--nav-h);         /* clears the fixed nav */
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
}
/* Sentinel: when it crosses the viewport middle the callouts appear */
.product__trigger {
  position: absolute; left: 0; right: 0;
  top: 55%; height: 1px;
}

.product__figure {
  position: relative; z-index: 1;    /* above the texture */
  width: 100%;
  margin-top: clamp(16px, 3vh, 32px);
}
/* Borderless stage: the sock floats directly on the page.
   A soft red ambience sits behind/below it. */
.product__frame {
  position: relative;
  display: flex; justify-content: center;
  /* Viewport-driven so the whole pinned block always fits one screen */
  height: var(--frame-h);
  background: radial-gradient(52% 44% at 50% 62%, var(--glow), transparent 72%);
}
/* This box IS the rendered artwork — same aspect ratio as the files.
   That is what lets the zone dots use plain percentages. */
.product__sock {
  position: relative;
  height: 100%;
  aspect-ratio: 948 / 1100;
}
/* Both colorway shots stack here and cross-fade. */
.product__shot {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  /* Whisper of warmth at the lower contours */
  filter: drop-shadow(0 18px 22px rgba(232, 66, 14, 0.10));
  opacity: 0;
  transition: opacity 0.45s var(--ease);
}
.product__shot.is-active { opacity: 1; }
/* Elliptical ground shadow directly under the sole */
.product__frame::after {
  content: "";
  position: absolute;
  left: 50%; bottom: -6px;
  width: 34%; height: 40px;
  transform: translateX(-50%);
  background: radial-gradient(closest-side, var(--ground), transparent 72%);
  filter: blur(14px);
}
.product__caption {
  margin-top: 12px;
  font-size: 13px;
  text-align: center;
  color: var(--text-faint);
}

/* ---------- Zone callouts ---------- */
/* Target point on the sock. On desktop it is just the anchor the
   leader line runs to — the number lives on the note instead. */
.zone-dot {
  position: absolute;
  left: var(--x); top: var(--y);
  z-index: 3;
  display: grid; place-items: center;
  width: 9px; height: 9px;
  margin: -4.5px 0 0 -4.5px;         /* centre on the coordinate */
  border-radius: 50%;
  background: var(--orange);
  font-size: 0;                      /* hides the digit, keeps the node */
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--orange) 20%, transparent);
  transition: opacity 0.4s var(--ease-burst), transform 0.4s var(--ease-burst);
}

/* Leader lines, drawn into this overlay by main.js */
.zone-lines {
  position: absolute; inset: 0;
  z-index: 2;
  width: 100%; height: 100%;
  overflow: visible;
  pointer-events: none;
}
.zone-lines line {
  stroke: var(--text-faint);
  stroke-width: 1.6;
  stroke-dasharray: var(--len, 200);
  stroke-dashoffset: var(--len, 200);
  transition: stroke-dashoffset 0.6s var(--ease);
}

/* Notes: absolute beside the sock on desktop, a plain list on mobile */
.zone-list {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: var(--frame-h);            /* same box as the frame */
  z-index: 2;
  margin: 0; padding: 0;
  list-style: none;
  counter-reset: zone;
  pointer-events: none;
}
.zone-note {
  position: absolute;
  top: var(--top);
  width: 210px;
  transform: translateY(-50%);
  counter-increment: zone;
  transition: opacity 0.55s var(--ease), transform 0.55s var(--ease);
}
/* --edge nudges each note in from its side, so the five sit scattered
   around the sock instead of lining up in two neat columns. */
.zone-note[data-side="left"]  { left:  var(--edge, 0%); text-align: right; }
.zone-note[data-side="right"] { right: var(--edge, 0%); text-align: left; }

.zone-note__title {
  /* 700 is loaded explicitly in the font request — without it the
     browser would fake the bold and the uppercase would smear. */
  font-size: 13px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--heading);
}
/* Dark type on a light background reads lighter than light type on
   dark at the same weight (irradiation). The variable font lets the
   light colorway carry a touch more so both look equally bold. */
:root[data-colorway="black"] .zone-note__title { font-weight: 780; }

/* The number lives here, not on the sock */
.zone-note__title::before {
  content: counter(zone, decimal-leading-zero);
  margin-right: 8px;
  color: var(--orange);
  font-variant-numeric: tabular-nums;
}
.zone-note__text {
  margin-top: 6px;
  font-size: 13.5px; line-height: 1.5;
  color: var(--text-dim);
}

/* Without JS nothing toggles .is-on, so the callouts are visible by
   default and the .js class hides them until scroll reveals them. */
.js .zone-dot  { opacity: 0; transform: scale(0.4); }
.js .zone-note { opacity: 0; }
.js .zone-note[data-side="left"]  { transform: translate(-10px, -50%); }
.js .zone-note[data-side="right"] { transform: translate(10px, -50%); }

/* Revealed one by one, driven by scroll position in main.js */
.js .zone-dot.is-on  { opacity: 1; transform: scale(1); }
.js .zone-note.is-on { opacity: 1; transform: translate(0, -50%); }
/* ---------- Colorway switcher ---------- */
.swatches__label {
  position: relative; z-index: 1;    /* above the hex texture */
  margin-top: clamp(28px, 5vh, 44px);
  text-align: center;
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--text-dim);
}

/* Segmented control: a visible track with a solid selected pill.
   Reads unmistakably as a control rather than as two labels. */
.swatches {
  position: relative; z-index: 1;    /* above the hex texture */
  display: flex; gap: 6px;
  width: fit-content;
  margin: 16px auto 0;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--pill);
}
.swatch {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 12px 22px 12px 14px;
  background: transparent;
  border: 0;
  border-radius: var(--pill);
  color: var(--text-dim);
  font-family: var(--font-body);
  font-size: 13px; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  line-height: 1; cursor: pointer;
  transition: color 0.25s ease, background-color 0.25s ease;
}
/* The chip literally shows the colorway it selects. Its ring is a
   neutral grey so it stays visible on every fill it can land on. */
.swatch__chip {
  width: 18px; height: 18px;
  border-radius: 50%;
  flex: none;
  border: 1px solid rgba(128, 128, 128, 0.45);
}
.swatch__chip--white { background: #FFFFFF; }
.swatch__chip--black { background: #0A0A0A; }

@media (hover: hover) {
  .swatch:hover { color: var(--heading); background: var(--surface-2); }
}
/* Selected: solid, full-contrast — impossible to miss */
.swatch[aria-pressed="true"] {
  background: var(--heading);
  color: var(--bg);
}

/* ---------- Narrow screens ----------
   Same pinned story as desktop, but there is no room beside the sock:
   the dots carry their numbers and ONE card below the switcher shows
   the active zone, swapping as you scroll.
   Heights use svh (the viewport with the browser bar shown) so iOS
   Safari collapsing its bar cannot resize the pinned stage mid-scroll.
   Browsers without svh fall back to the vh rules above. */
@media (max-width: 900px) {
  .product__stage {
    /* The 470px reserve is the rest of the pinned block: tag, caption,
       card, switcher and their gaps. Keeping the sock inside that
       budget is what stops the pinned view from being clipped. */
    /* ~20% more sock than before. The reserve shrank from 500 to 438px,
       and the gaps below reclaim what that costs — the tag-to-image gap
       alone was 45px, which was more air than the block could afford. */
    --frame-h: clamp(204px, calc(100svh - 438px), 408px);
    height: calc(100svh + var(--stage-scroll));
  }
  /* Tighter vertical rhythm so everything fits one screen */
  .product__tag    { margin-bottom: clamp(14px, 2.5vh, 24px); }
  .swatches__label { margin-top: clamp(14px, 2.5vh, 24px); }
  .product__pinned { height: 100svh; }

  /* Numbers move onto the sock */
  .zone-dot {
    width: 26px; height: 26px;
    margin: -13px 0 0 -13px;
    font-size: 12px; font-weight: 600;
    color: #FFFFFF;
    font-variant-numeric: tabular-nums;
    box-shadow: 0 0 0 5px color-mix(in srgb, var(--orange) 20%, transparent);
  }
  /* Already-passed zones stay on the map but step back */
  .js .zone-dot.is-on            { opacity: 0.45; transform: scale(0.86); }
  .js .zone-dot.is-on.is-current { opacity: 1;    transform: scale(1); }

  .zone-lines { display: none; }     /* no room for leader lines */

  /* Card sits between the sock and the switcher */
  /* Height follows the content instead of filling the viewport. Filling
     it left ~62px of dead space under the switcher, which made the gap
     before the second seam twice the gap after it. With auto height the
     switcher IS the bottom edge, so the seam sits centred by itself.
     The nav is cleared by the sticky offset, not by padding — padding
     here would come back as a gap below the first seam. */
  .product__pinned  {
    top: var(--nav-h);
    height: auto;
    display: flex; flex-direction: column; align-items: center;
    justify-content: flex-start;
    padding-top: clamp(12px, 2vh, 22px);
  }
  /* Tighter vertical rhythm buys the height the larger sock needs */
  .product__tag    { margin-bottom: clamp(8px, 1.4vh, 14px); }
  .product__figure { margin-top: clamp(8px, 1.4vh, 14px); }
  .swatches__label { margin-top: clamp(10px, 1.8vh, 18px); }
  .zone-list       { margin: clamp(14px, 2.5vh, 22px) 0 clamp(6px, 1vh, 10px); }

  .product__tag     { order: 1; }
  .product__figure  { order: 2; }
  .zone-list        { order: 3; }
  .swatches__label  { order: 4; }
  .swatches         { order: 5; margin-top: 10px; }

  /* All five cards share ONE grid cell, so the box is always as tall as
     the tallest card — the text can never spill out, and swapping cards
     never changes the height. */
  .zone-list {
    position: relative;
    top: auto; left: auto; right: auto;
    width: 100%;
    height: auto;
    flex: none;                      /* flex must not squash the box */
    display: grid;
    margin: clamp(14px, 2.5vh, 22px) 0 clamp(16px, 3vh, 26px);
  }
  .zone-note,
  .js .zone-note[data-side="left"],
  .js .zone-note[data-side="right"] {
    position: relative;
    grid-area: 1 / 1;                /* stacked, so only one is ever seen */
    inset: auto;
    width: auto;
    padding: 20px 22px;
    border: 1px solid var(--line);
    border-radius: 18px;
    background: var(--surface);
    text-align: left;
    transform: translateY(10px);
  }
  /* Only the active card is shown — the others are fully hidden so a
     screen reader never reads five overlapping panels. */
  .js .zone-note { visibility: hidden; }
  .js .zone-note.is-on,
  .js .zone-note[data-side="left"].is-on,
  .js .zone-note[data-side="right"].is-on {
    visibility: visible;
    opacity: 1;
    transform: none;
  }
  .zone-note__title::before { margin-right: 7px; }
  .zone-note__text { margin-top: 8px; font-size: 14px; }

}

@media (max-width: 640px) {
  .product__caption { text-align: center; }
  .swatches { gap: 4px; padding: 5px; }
  /* 14px vertical keeps the tap target at ~44px */
  .swatch { padding: 14px 16px 14px 12px; font-size: 12px; letter-spacing: 0.08em; gap: 8px; }
  .swatch__chip { width: 16px; height: 16px; }
}

/* ============================================================
   SECTION SEAM
   A hairline that draws top-to-bottom as it scrolls into view and
   lands on the orange dot. Same language as the zone leader lines,
   so it reads as part of the system rather than an ornament.
   The gap is symmetric BY CONSTRUCTION: the line sits inside a box
   with identical padding above and below, and the sections either
   side hand their touching padding over to this element.
   ============================================================ */
.seam {
  --seam-gap: clamp(34px, 5.25vh, 59px);  /* space below the bar */
  --seam-top: var(--seam-gap);            /* space above — overridable */
  --seam-len: clamp(48px, 7vh, 72px);     /* length of the bar */
  position: relative;
  height: calc(var(--seam-len) + var(--seam-top) + var(--seam-gap));
  pointer-events: none;
}
/* The first seam meets a viewport-centred pinned stage, whose content
   starts well below its own top edge. Extra room above pushes the bar
   down so it reads centred in the gap you actually see. */
/* Both sides set explicitly. Below the bar the gap also carries the
   stage's nav clearance (64px) plus its centring slack, so the seam
   padding there has to be much smaller for the bar to sit centred. */
.seam--hero {
  --seam-top: clamp(80px, 12vh, 130px);
  --seam-gap: clamp(10px, 1.6vh, 20px);
}

/* Same bar as the "Make it make sense" underline, stood upright:
   orange, one stroke, drawn top to bottom. */
.seam__line {
  position: absolute;
  left: 50%; top: var(--seam-top);
  width: clamp(2px, 0.25vw, 4px);
  height: var(--seam-len);
  transform: translateX(-50%);
  background: var(--orange);
  transform-origin: top center;
}

/* The bar itself carries the reveal, not its box: the box reaches
   above the fold on load, so triggering on it would draw the bar
   while it is still off screen. It also opts out of the generic
   slide — it must only grow downward. */
.js .seam__line.reveal {
  opacity: 1;
  transform: translateX(-50%) scaleY(0);
  transition: transform 0.85s var(--ease);
}
.js .seam__line.reveal.is-visible { transform: translateX(-50%) scaleY(1); }

/* ============================================================
   HOOK
   ============================================================ */
.hook {
  padding: clamp(80px, 13vh, 152px) 0 clamp(48px, 8vh, 96px);
  text-align: center;
}
.hook__title { font-size: clamp(36px, 7.5vw, 88px); }
.nowrap { white-space: nowrap; }
/* Drawn as its own bar rather than text-decoration, so it can wipe
   in from the left when the section reveals. */
.underline-accent {
  position: relative;
  display: inline-block;
  text-decoration: none;
}
.underline-accent::after {
  content: "";
  position: absolute;
  /* line-height is 0.94, so the box ends above the glyph feet —
     the offset drops the bar clear of the letters */
  left: 0; right: 0; bottom: -0.16em;
  height: 0.09em;                    /* thickness — tune freely */
  background: var(--orange);
  transform-origin: left center;
  /* Default is fully drawn, so the line still shows without JS */
  transform: scaleX(1);
}
/* With JS the bar starts collapsed and draws once the hook reveals.
   The delay lets the headline land first. */
.js .underline-accent::after {
  transform: scaleX(0);
  transition: transform 0.75s var(--ease-burst);
  transition-delay: 0.25s;
}
.js .reveal.is-visible .underline-accent::after { transform: scaleX(1); }
.hook__line {
  margin: 24px auto 0;
  max-width: 36em;
  font-size: clamp(16px, 2vw, 18px);
  color: var(--text-dim);
}

/* ============================================================
   ZONES
   ============================================================ */
.zones { padding: clamp(64px, 10vh, 128px) 0; }
.zones__head { max-width: 640px; }
.zones__title {
  margin-top: 20px;
  font-size: clamp(36px, 5.5vw, 64px);
}
.zones__grid {
  margin-top: clamp(32px, 5vh, 56px);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;                          /* hairline grid via background */
  background: var(--line);
  border: 1px solid var(--line);
  /* Without this the translucent background paints under the
     translucent border, stacking to ~19% and making the outer frame
     twice as bright as the 10% inner dividers. */
  background-clip: padding-box;
  border-radius: 20px;
  overflow: hidden;
}
.zone {
  position: relative;
  padding: 32px 28px 36px;
  background: var(--bg);
  transition: background-color 0.3s ease;
}
.zone::before {                      /* orange rule, revealed on hover */
  content: "";
  position: absolute; top: 0; left: 28px; right: 28px;
  height: 2px;
  background: var(--orange);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.35s var(--ease);
}
@media (hover: hover) {
  .zone:hover { background: var(--hover); }
  .zone:hover::before { transform: scaleX(1); }
}

.zone__num {
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--text-faint);
}
.zone__name {
  margin-top: 40px;
  font-size: 17px; font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--heading);
}
.zone__text {
  margin-top: 10px;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--text-dim);
}

@media (max-width: 960px) {
  .zones__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
  .zones__grid { grid-template-columns: 1fr; }
  .zone__name { margin-top: 28px; }
}

/* ============================================================
   FOUNDING-DROP CTA
   ============================================================ */
/* No top border on purpose: the headline jump, the padding and the
   hex glow separate this section on their own, and a hairline here
   would stack against the zones grid's own border. */
.signup {
  position: relative;
  padding: clamp(80px, 14vh, 160px) 0;
  text-align: center;
}
.signup__inner {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; align-items: center;
}
.signup__title { font-size: clamp(44px, 8vw, 104px); }
/* Three perks read as a list, not as one run-on sentence. The orange
   marker is the same one the eyebrows use, so it stays in the family.
   Row on wide screens, stacked and left-aligned when space runs out. */
.signup__perks {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 9px 28px;
  margin-top: 20px;
  font-size: 12.5px; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--text-dim);
}
.signup__perks li { display: flex; align-items: center; gap: 9px; }
.signup__perks li::before {
  content: "";
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--orange);
  flex: none;
}
@media (max-width: 620px) {
  .signup__perks {
    flex-direction: column; align-items: flex-start;
    width: fit-content; margin-inline: auto;
    gap: 10px;
  }
}
.signup__micro {
  margin-top: 8px;
  font-size: 13px;
  color: var(--text-faint);
}

/* ============================================================
   FOOTER
   ============================================================ */
.footer {
  position: relative; z-index: 1;    /* stays above any stray texture */
  background: var(--bg);
  border-top: 1px solid var(--line);
  padding: clamp(48px, 7vh, 80px) 0;
}
.footer__inner {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  text-align: center;
}
.footer__wordmark { height: 18px; width: auto; opacity: 0.95; }
.footer__tagline {
  font-size: 13px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--text-dim);
}
.footer__links { font-size: 14px; color: var(--text-dim); }
.footer__links a { text-decoration: none; transition: color 0.2s ease; }
.footer__links a:hover { color: var(--heading); }
.footer__legal-links { font-size: 13px; color: var(--text-dim); }
.footer__legal-links a { text-decoration: none; transition: color 0.2s ease; }
.footer__legal-links a:hover { color: var(--heading); }
.footer__legal-links .sep { margin-inline: 10px; color: var(--text-faint); }
.footer__legal { font-size: 12px; color: var(--text-faint); }

/* ============================================================
   REVEAL-ON-SCROLL (paired with main.js IntersectionObserver)
   Hidden state only applies when <html> has the .js class, so
   content stays visible without JS or if main.js fails to load.
   ============================================================ */
/* Tier 1 — calm: supporting content inside a section */
.js .reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
  transition-delay: var(--d, 0s);
}
.js .reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* Texture blooms in slowly and never moves on reveal — the drag in
   main.js owns its transform. */
.js .hex-bg.reveal {
  transform: none;
  transition: opacity 1.4s ease;
}
.js .hex-bg.reveal.is-visible { transform: none; }

/* Grid follows the section heading by a beat */
.zones__grid      { --d: 0.10s; }
.product__figure  { --d: 0.10s; }

/* Respect reduced-motion: no movement, instant visibility */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js .reveal { opacity: 1; transform: none; transition: none; }
  .js .seam__line.reveal { transform: translateX(-50%); }
  .hero__inner > * { animation: none; }
  .btn::after { animation: none; }
  .btn::before { transition: none; }
  .js .underline-accent::after { transform: scaleX(1); transition: none; }
  .js .zone-dot { opacity: 1; transform: none; transition: none; }
  .js .zone-note { opacity: 1; transform: translate(0, -50%); transition: none; }
  .zone-lines line { stroke-dashoffset: 0; transition: none; }
  .btn, .zone::before { transition: none; }
}

/* ============================================================
   MOBILE SPACING RHYTHM
   Deliberately the LAST block in this file. Section paddings are
   defined further up with the `padding` shorthand, and equal
   specificity means the later rule wins — putting these overrides
   earlier (as I first did) leaves them without effect.
   Desktop keeps the rhythm defined above, untouched.
   ============================================================ */
@media (max-width: 900px) {
  /* The seam owns the gap on both sides, so the bar is centred by
     construction instead of being pushed off by one neighbour. */
  .seam       { --seam-gap: clamp(46px, 6.8vh, 70px); }
  /* First seam is asymmetric on purpose: below it the pinned stage
     centres its own content, which already contributes ~75px of air.
     So the padding below the bar is cut right back, while the space
     above stays as it was. Both values are set explicitly here —
     deriving the top one from --seam-gap would drag it down too. */
  /* Centred: the gap below the bar is the seam padding PLUS the stage's
     own top padding, so the seam padding has to be the smaller of the
     two numbers for the bar to sit in the middle. */
  .seam--hero {
    --seam-top: clamp(34px, 6vh, 58px);
    --seam-gap: clamp(24px, 4vh, 40px);
  }
  .hero       { padding-bottom: 0; }
  .hook       { padding: 0; }
  .signup     { padding-top: 0; }

  /* Sublines sit optically centred between headline and form */
  .hero__sub,
  .signup-form,
  .signup__perks { margin-top: 26px; }

  /* The header CTA jumps to #signup. Without an offset the anchor puts
     the section's top edge at the viewport top, where the fixed nav
     covers the headline — this section has no top padding of its own,
     the seam above owns that gap. */
  #signup { scroll-margin-top: clamp(78px, 12vh, 108px); }

  /* Micro lines close to their form */
  .hero__micro,
  .signup__micro { margin-top: 14px; }
}
