/* ==========================================================================
   GOYPH shared styles
   Used by every page except the homepage (index.html keeps its own inline
   styles to stay byte-for-byte close to the original single-screen design).
   Aesthetic: white background, black text, Courier New, square corners,
   thin borders, uppercase nav, generous whitespace, no gradients/shadows.
   ========================================================================== */

:root {
  --bg: #ffffff;
  --ink: #000000;
  --line: rgba(0, 0, 0, 0.18);
  --muted: rgba(0, 0, 0, 0.55);
  --header-h: 56px;
}

* {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: 'Courier New', Courier, monospace;
  -webkit-font-smoothing: antialiased;
}

/* Only the homepage locks scrolling; every other page scrolls normally. */
body.scroll-page {
  min-height: 100vh;
}

a {
  color: inherit;
}

img {
  max-width: 100%;
  display: block;
}

::selection {
  background: var(--ink);
  color: var(--bg);
}

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

.skip-link {
  position: absolute;
  left: 12px;
  top: -48px;
  background: var(--ink);
  color: var(--bg);
  padding: 10px 16px;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  z-index: 200;
  transition: top 0.15s ease;
}

.skip-link:focus {
  top: 12px;
}

/* ---------- Site header / global nav ----------
   One shared header at every width. All five destinations stay visible on
   a single row — no wrapping, stacking, hiding, menu button or horizontal
   scrolling — with MISSION / COMMUNITY / EVENTS / SHOP on the left (MOVEMENT
   / SHOP below 660px) and ACCOUNT then CART on the right. On inner pages
   the eye sits between them.

   Everything that decides whether the row fits — label size, tracking, key
   padding, gaps and the eye — is driven by clamp() rather than by
   breakpoints, so the row tightens continuously as the viewport narrows
   instead of falling off a cliff at particular widths. --nav-* below are
   the single place to adjust that. */

.site-header {
  /* Tuned so the widest case — every key, a multi-digit cart count and the
     centred eye — still fits one row with room to spare. */
  --nav-size: clamp(8px, 2.45vw, 13px);
  --nav-track: clamp(0em, 0.22vw, 0.1em);
  --key-pad-x: clamp(2.5px, 0.95vw, 12px);
  /* Vertical space isn't contested by the one-row rule, so keys stay
     comfortably tall even where they have to get narrow. */
  --key-pad-y: clamp(9px, 1.1vw, 11px);
  --key-depth: clamp(2px, 0.45vw, 4px);
  --key-radius: clamp(3px, 0.5vw, 5px);
  --nav-gap: clamp(2.5px, 0.85vw, 18px);
  --nav-eye: clamp(22px, 7vw, 36px);
  /* Wide screens carry six caps — MISSION, COMMUNITY, EVENTS, SHOP on the
     left, ACCOUNT, CART on the right — and on inner pages the eye has to sit
     on the page's centre line between them. The left group (6.84x the key
     height) is far wider than the right (3.90x), so the binding constraint
     is the left group fitting in the left HALF of the header, not the row
     fitting the screen. 6.5vw - 4px keeps it there at every width from
     660px up, with a 15px classic scrollbar allowed for; the caps reach
     their full 66px at 1077px and stay there. (Narrow screens override
     this below.) */
  --key-art-h: clamp(28px, calc(6.5vw - 4px), 66px);

  position: sticky;
  top: 0;
  z-index: 100;
  min-height: var(--header-h);
  padding: 10px clamp(6px, 1.6vw, 20px);
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}

.site-header .site-nav {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  column-gap: var(--nav-gap);
  min-height: 36px;
}

.site-header .nav-group {
  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  min-width: 0;
  /* Keys never squash; the clamps above keep the row within the viewport. */
  flex: 0 0 auto;
}

.site-header .nav-left { margin-right: auto; }
.site-header .nav-right { margin-left: auto; }

/* ---------- Centred header eye (every page except the homepage) ----------
   Grid, so the eye is centred on the header — and therefore the viewport —
   regardless of the two groups having different widths. The centre column
   is sized to the eye, so its animations (which only move pixels inside
   the artwork) can never shift the row. */

.site-header .site-nav.has-eye {
  display: grid;
  /* Wide screens: two equal side columns put the eye on the header's centre
     line — which is the page's centre line, and so the centre of the mission
     statement and of every other centred inner-page column. The groups'
     unequal widths (four caps left, two right) sit inside those equal
     columns without pushing the eye, because --key-art-h above is sized so
     the wider left group always fits its half.

     max-content is a guarantee rather than a nicety: a group can never be
     squeezed narrower than its keys, so nothing can overlap the eye even if
     a label grows — a three-digit cart count, say.

     Narrow screens balance the eye between the key groups instead; see the
     660px block below. */
  grid-template-columns: minmax(max-content, 1fr) auto minmax(max-content, 1fr);
  align-items: center;
  column-gap: var(--nav-gap);
}

.site-header .site-nav.has-eye .nav-left {
  justify-self: start;
  margin-right: 0;
}

.site-header .site-nav.has-eye .nav-right {
  justify-self: end;
  margin-left: 0;
}

.header-eye {
  justify-self: center;
  display: flex;
  align-items: center;
  /* Space is reserved before the artwork loads, so nothing shifts. */
  width: var(--nav-eye);
  height: calc(var(--nav-eye) * 241 / 256);
  text-decoration: none;
  color: inherit;
  /* The eye artwork has the same blank margin left and right (19.92% each),
     so the box's centre IS the visible eye's centre — no nudge is needed on
     wide screens. The narrow-screen nudge lives in the 660px block below. */
}

.header-eye:focus-visible {
  outline: 1px dashed var(--ink);
  outline-offset: 3px;
}

.header-eye .goyph-eye {
  width: var(--nav-eye);
}

/* The live cart count is set in the same face as the lettering printed on
   the supplied keycaps — a 3.7KB subset of it holding only digits, the
   parentheses and a space. Without this the count renders in Courier New,
   which is noticeably heavier beside the artwork's lettering. */
@font-face {
  font-family: 'GOYPH Keycap';
  src: url('/assets/nav-keys/keycap-digits.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---------- Navigation keys ----------
   The supplied keycap artwork is the navigation at every screen size —
   MISSION / COMMUNITY / SHOP exactly as provided, ACCOUNT and CART the
   same artwork relettered (scripts/generate-nav-keys.py). Aspect ratios
   come straight from the files and are never altered:
       mission / account   907 x 517   (1.754)
       community / cart   1108 x 517   (2.143)
       shop                615 x 517   (1.190)

   The five together are 8.98x their own height wide, so --key-art-h is
   what keeps the row inside the viewport: it scales with the viewport
   until it reaches the artwork's comfortable maximum. Scaling is by
   height only, so proportions are exact — never stretched, never cropped.

   TRADE-OFF, stated plainly: the artwork carries a lot of blank plastic
   around its printed lettering (the label is 15.1% of a key's height), so
   on narrow phones the keys must be short and the printed labels get very
   small — roughly 4.5px of cap height at 320px. The labels remain in the
   DOM as each link's accessible name, so screen readers and keyboard users
   are unaffected, but the printed text is decorative at that size. */

.site-header .nav-link,
.site-header .nav-button {
  /* A bare wrapper around the artwork — no CSS button of any kind. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  line-height: 0;
  font-family: inherit;
  color: transparent;
  cursor: pointer;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  transform-origin: 50% 62%;
  transition: transform 0.08s ease-out;
}

/* Scaled by height only, so the artwork keeps its exact proportions. */
.nav-key-img {
  display: block;
  height: var(--key-art-h);
  width: auto;
  max-width: none;
  pointer-events: none;
}

/* The label stays in the DOM at every width as the accessible name. */
.site-header .nav-key-text {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

/* The live cart count, set on the blank face to the right of the word
   printed on the CART artwork, in the same face as that lettering. Placed
   from the artwork's own geometry (assets/nav-keys/geometry.json) so it
   shares the printed baseline, and it sits inside the key so the press
   transform carries it along. */
.nav-key-count {
  display: block;
  position: absolute;
  left: 48.15%;
  top: 32.26%;
  font-family: 'GOYPH Keycap', 'Courier New', Courier, monospace;
  font-size: calc(var(--key-art-h) * 0.259);
  line-height: 1;
  letter-spacing: 0;
  color: #212121;
  pointer-events: none;
}

/* The current page reads as a key left slightly settled. */
.site-header .nav-link[aria-current="page"] {
  transform: translateY(1px) scale(0.985);
}

.site-header .nav-link:focus-visible,
.site-header .nav-button:focus-visible {
  outline: 2px dashed var(--ink);
  outline-offset: 1px;
}

/* The press: the whole key — artwork and cart count together — shrinks
   briefly, then pops back, before anything activates. It is a transform,
   so no neighbouring key ever moves. */
.site-header .nav-link.is-pressed,
.site-header .nav-button.is-pressed {
  transform: scale(0.93);
}

@media (prefers-reduced-motion: reduce) {
  .site-header .nav-link,
  .site-header .nav-button {
    transition: none !important;
  }
}

/* ====================================================================
   NARROW SCREENS — one breakpoint: 660px.
   ====================================================================
   Below 660px MISSION and COMMUNITY are gathered into a MOVEMENT key,
   which reveals MISSION, COMMUNITY and EVENT in a short column directly
   underneath it. The column is only as big as the three keys and the two
   gaps between them: no panel, no enclosure, no backdrop, nothing dimmed,
   and nothing on the page moves while it is open.

   The row then carries four caps instead of five — 7.23x their own height
   rather than 8.98x — which is what pays for a key about a quarter taller
   here, with correspondingly larger printed lettering.

   At 660px and above every rule in this block is inert and the header is
   exactly what it was.
   ==================================================================== */

/* Both sets are in the markup and each width shows one of them; display:none
   also takes the hidden set out of the accessibility tree, so nothing is
   announced twice. Same specificity as the shared key rule above, so it
   actually wins. */
.site-header .nav-narrow-only {
  display: none;
}

@media (min-width: 660px) {
  /* Belt and braces: the drop can never appear on a wide screen, even if
     the viewport is widened while it is open. */
  .movement-drop {
    display: none !important;
  }

  /* Six caps have to be a little shorter than five were between 660px and
     1077px (see --key-art-h). The header stays exactly as tall as it was —
     this is the previous key height — and the caps centre within it, so
     nothing below the header moves on any page at any width. */
  .site-header .site-nav {
    min-height: clamp(28px, 9.2vw, 66px);
  }
}

@media (max-width: 659.98px) {
  .site-header .nav-wide-only {
    display: none;
  }

  .site-header .nav-narrow-only {
    display: inline-flex;
  }

  .site-header {
    /* Four caps instead of five. 11.5vw keeps MOVEMENT + SHOP + the eye +
       ACCOUNT + CART on one row at 320px with room to spare. */
    --key-art-h: clamp(34px, 11.5vw, 66px);
    /* The keys in the drop are the same physical key as the row above:
       identical rendered height, so identical lettering scale and identical
       visual depth. Only their widths differ, exactly as the row's do,
       because the artwork's widths follow the labels. */
    --drop-key-h: var(--key-art-h);
  }

  /* ---------- The eye, balanced between the key groups ---------------
     (Unchanged from the approved narrow-screen positioning; it lives here
     now so that wide screens can centre the eye on the page instead.)

     Each column is sized to its own keys and the leftover width is split
     equally into the two gaps, which puts the eye midway between the key
     groups rather than on the viewport's centre line: MOVEMENT + SHOP is
     narrower than ACCOUNT + CART, so equal side columns left visibly more
     air on one side of the eye than the other. */
  .site-header .site-nav.has-eye {
    grid-template-columns: max-content max-content max-content;
    justify-content: space-between;
  }

  /* Optical, not geometric. The keycap photographs carry blank plastic
     around the cap itself, and not the same amount on each side: the SHOP
     cap to the eye's left ends 7.97% of its width early, while the ACCOUNT
     cap to its right starts only 0.77% in. Measured against the visible
     artwork that leaves 0.0813 x key-height more air on the left than on
     the right. Half of that, given back as a nudge — it moves nothing in
     layout, and it scales with the keys because the insets are fractions
     of them. */
  .header-eye {
    transform: translateX(calc(var(--key-art-h) * -0.0407));
  }

  /* ---------- The current page reads as a settled key ----------------
     A small downward offset and a flatter cast shadow. That shadow is
     baked into the photograph — mid-grey against an almost-white cap face
     — so raising brightness flattens it noticeably while the face, already
     near white, barely moves: the key reads as sunk into the board rather
     than standing proud of it.

     Deliberately unlike the press animation (a brief scale to 0.93 with no
     colour change), unlike the drop's open state (which changes nothing
     about MOVEMENT's key), and unlike CART's drawer-open state below. */
  .site-header .nav-link[aria-current="page"],
  .site-header .nav-button[data-section-current="true"],
  .movement-drop .drop-key[aria-current="page"] {
    transform: translateY(2px) scale(0.985);
    filter: brightness(1.07) contrast(0.95);
  }

  /* CART with its drawer open: held down and darker, the way a key looks
     under a finger. `scroll-locked` is set on <body> by the drawer and by
     nothing else (assets/cart.js), so it is an exact proxy for "the cart
     drawer is open" without touching the cart's own code. */
  body.scroll-locked .site-header #cart-count-trigger {
    transform: translateY(3px) scale(0.965);
    filter: brightness(0.9) contrast(1.04);
  }

  /* The press wins while it is running. */
  .site-header .nav-link.is-pressed,
  .site-header .nav-button.is-pressed,
  .movement-drop .drop-key.is-pressed {
    transform: scale(0.93);
    filter: none;
  }

  /* ---------- The MOVEMENT drop -------------------------------------
     A second, shorter row of keys reading across from under MOVEMENT:
     MISSION, EVENTS, COMMUNITY. It is anchored to the left key group — whose
     left edge and bottom edge are MOVEMENT's own — so it hangs directly off
     that key. Being out of flow, it cannot move the homepage composition or
     anything else on the page.

     The three keys together are 5.65x their own height wide, which at the
     narrow-screen key size is 208px at 320px of viewport: the row fits with
     room to spare at every width this rule applies to. */
  .site-header .nav-left {
    position: relative;
  }

  /* Closed means gone. The display:flex below would otherwise override the
     browser's own [hidden] rule, leaving a "closed" row that was invisible
     but still tappable and still reachable with Tab — and, with reduced
     motion, never hidden at all. */
  .movement-drop[hidden] {
    display: none;
  }

  .movement-drop {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 120;
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    flex-wrap: nowrap;
    /* Between the keys in the row: unchanged. */
    gap: 6px;
    /* Under MOVEMENT: the same VISIBLE gap as between MOVEMENT and SHOP in
       the row above, measured on the cap artwork rather than the image
       boxes. Beside each other those two leave --nav-gap plus MOVEMENT's
       blank right margin (7.76% of its 2.143h width) plus SHOP's blank left
       margin (1.14% of its 1.190h width) = --nav-gap + 0.1799h. Stacked,
       the blank plastic is MOVEMENT's bottom margin (10.25% of h) plus the
       child key's top margin (1.35% of h) = 0.1160h. The difference, set as
       padding, makes the two gaps read the same at every width. */
    padding: calc(var(--nav-gap) + var(--key-art-h) * 0.0639) 0 0;
    margin: 0;
    background: none;
    border: 0;
    box-shadow: none;
    /* The keys are the only thing that can be clicked. */
    pointer-events: none;
  }

  .movement-drop .drop-key {
    pointer-events: auto;
    display: block;
    position: relative;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    line-height: 0;
    color: transparent;
    text-decoration: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transform-origin: 50% 62%;
    opacity: 0;
    transform: translateY(-5px);
    transition: transform 0.14s ease-out, opacity 0.12s linear, filter 0.08s ease-out;
  }

  .movement-drop.is-open .drop-key {
    opacity: 1;
    transform: none;
  }

  /* A touch of stagger, so the three read as separate keys dropping. */
  .movement-drop.is-open .drop-key:nth-child(2) { transition-delay: 0.03s; }
  .movement-drop.is-open .drop-key:nth-child(3) { transition-delay: 0.06s; }

  /* Scaled by height only, so the artwork keeps its exact proportions. */
  .movement-drop .drop-key .nav-key-img {
    height: var(--drop-key-h);
  }

  .movement-drop .drop-key:focus-visible {
    outline: 2px dashed var(--ink);
    outline-offset: 2px;
  }

  /* The settled current-page key keeps its offset when the drop opens. */
  .movement-drop.is-open .drop-key[aria-current="page"] {
    opacity: 1;
    transform: translateY(2px) scale(0.985);
  }

  @media (prefers-reduced-motion: reduce) {
    /* Immediate open and close. */
    .movement-drop .drop-key {
      transition: none;
      transition-delay: 0s !important;
      opacity: 1;
      transform: none;
    }

    .movement-drop.is-open .drop-key[aria-current="page"] {
      transform: translateY(2px) scale(0.985);
    }
  }
}

/* ---------- The eye itself (shared by the header and the wordmark) -------
   Two generated layers: the silhouette with an empty opening, and the
   pupil on top. The pupil and the blink lid live inside a clip box that
   matches the eye's opening, so the pupil can look around without
   escaping and the lid can close over the opening. The percentages come
   from scripts/generate-eye-assets.py, which prints them. */

.goyph-eye {
  position: relative;
  display: block;
  aspect-ratio: 256 / 241;
}

.goyph-eye-base {
  display: block;
  width: 100%;
  height: 100%;
}

.goyph-eye-clip {
  position: absolute;
  left: 19.531%;
  top: 32.365%;
  width: 60.938%;
  height: 32.365%;
  overflow: hidden;
}

.goyph-eye-pupil {
  position: absolute;
  left: -32.051%;
  top: -100%;
  width: 164.103%;
  height: 308.974%;
  /* Overrides the global img{max-width:100%} — this layer is deliberately
     larger than its clip box. */
  max-width: none;
}

.goyph-eye-lid {
  position: absolute;
  inset: 0;
  background: var(--ink);
  transform: translateY(-100%);
}

/* Reduced motion: the eye is drawn neutral and never animates. */
@media (prefers-reduced-motion: reduce) {
  .goyph-eye-pupil,
  .goyph-eye-lid {
    transition: none !important;
  }
}





/* The cart drawer locks background scrolling while it's open (set by
   assets/cart.js). This rule previously lived with the removed mobile
   menu — it is kept here, renamed, because the drawer still needs it. */
body.scroll-locked {
  overflow: hidden;
}

/* Shared close control for the cart drawer. */
.close-button {
  font-family: inherit;
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  background: none;
  border: 1px solid var(--ink);
  color: var(--ink);
  padding: 8px 14px;
  cursor: pointer;
}

/* ---------- Page shell ---------- */

.page {
  max-width: 1200px;
  margin: 0 auto;
  padding: 48px 20px 96px;
}

.page-heading {
  font-size: clamp(28px, 6vw, 44px);
  font-weight: 400;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  text-align: center;
  margin: 0 0 48px;
}

/* ---------- Shop grid ---------- */

.product-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 32px 24px;
}

@media (max-width: 1024px) {
  .product-grid { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 720px) {
  .product-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 420px) {
  .product-grid { grid-template-columns: 1fr; }
}

.product-tile {
  display: block;
  text-decoration: none;
  color: var(--ink);
}

.product-tile .product-image-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 4;
  background: #f4f4f4;
  overflow: hidden;
}

.product-tile .product-image-wrap img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  position: absolute;
  inset: 0;
  transition: opacity 0.2s ease;
}

.product-tile .product-image-wrap img.secondary {
  opacity: 0;
}

.product-tile:hover .product-image-wrap img.secondary,
.product-tile:focus-visible .product-image-wrap img.secondary {
  opacity: 1;
}

.product-tile .product-meta {
  margin-top: 12px;
  font-size: 13px;
  letter-spacing: 0.04em;
}

.product-tile .product-name {
  text-transform: uppercase;
}

.product-tile .product-price {
  margin-top: 4px;
  color: var(--muted);
}

.badge-sold-out {
  display: inline-block;
  margin-top: 6px;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border: 1px solid var(--ink);
  padding: 2px 8px;
}

.state-message {
  text-align: center;
  font-size: 14px;
  letter-spacing: 0.04em;
  color: var(--muted);
  padding: 64px 20px;
  border: 1px dashed var(--line);
}

/* ---------- Product detail ---------- */

.product-detail {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: 56px;
  align-items: start;
}

@media (max-width: 860px) {
  .product-detail {
    grid-template-columns: 1fr;
    gap: 32px;
  }
}

.product-gallery-main {
  width: 100%;
  aspect-ratio: 3 / 4;
  background: #f4f4f4;
  overflow: hidden;
}

.product-gallery-main img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.product-gallery-thumbs {
  display: flex;
  gap: 10px;
  margin-top: 12px;
  flex-wrap: wrap;
}

.product-gallery-thumbs button {
  width: 64px;
  height: 84px;
  padding: 0;
  border: 1px solid var(--line);
  background: #f4f4f4;
  cursor: pointer;
  overflow: hidden;
}

.product-gallery-thumbs button[aria-current="true"] {
  border-color: var(--ink);
}

.product-gallery-thumbs img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.product-info .product-title {
  font-size: clamp(22px, 3.4vw, 30px);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin: 0 0 8px;
}

.product-info .product-price {
  font-size: 16px;
  margin: 0 0 24px;
}

.product-info .product-description {
  font-size: 14px;
  line-height: 1.7;
  border-top: 1px solid var(--line);
  padding-top: 20px;
  margin-bottom: 24px;
}

.option-group {
  margin-bottom: 20px;
}

.option-group legend {
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 8px;
  padding: 0;
}

.option-values {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.option-values input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

.option-values label {
  border: 1px solid var(--ink);
  padding: 8px 14px;
  font-size: 13px;
  letter-spacing: 0.04em;
  cursor: pointer;
  text-transform: uppercase;
}

.option-values input:checked + label {
  background: var(--ink);
  color: var(--bg);
}

.option-values input:disabled + label {
  opacity: 0.35;
  cursor: not-allowed;
  text-decoration: line-through;
}

.option-values input:focus-visible + label {
  outline: 1px dashed var(--ink);
  outline-offset: 2px;
}

.quantity-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 20px;
}

.quantity-row label {
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.quantity-stepper {
  display: flex;
  align-items: center;
  border: 1px solid var(--ink);
}

.quantity-stepper button {
  width: 36px;
  height: 36px;
  background: none;
  border: none;
  font-family: inherit;
  font-size: 16px;
  cursor: pointer;
}

.quantity-stepper input {
  width: 44px;
  border: none;
  border-left: 1px solid var(--ink);
  border-right: 1px solid var(--ink);
  text-align: center;
  font-family: inherit;
  font-size: 14px;
  height: 36px;
}

/* ---------- Buttons (square, outlined, no fills except hover-invert) ---------- */

.btn {
  display: inline-block;
  width: 100%;
  padding: 16px 24px;
  border: 2px solid var(--ink);
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  text-decoration: none;
  text-align: center;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease, opacity 0.2s ease;
}

.btn:hover:not(:disabled),
.btn:focus-visible:not(:disabled) {
  background: var(--ink);
  color: var(--bg);
}

.btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.btn[data-state="loading"] {
  opacity: 0.7;
  cursor: wait;
}

.form-status {
  margin-top: 10px;
  font-size: 12px;
  letter-spacing: 0.04em;
  min-height: 1.4em;
}

.form-status[data-tone="error"] {
  color: #b00000;
}

.form-status[data-tone="success"] {
  color: var(--ink);
}

.details-block {
  border-top: 1px solid var(--line);
  padding: 16px 0;
}

.details-block summary {
  cursor: pointer;
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.details-block p {
  font-size: 13px;
  line-height: 1.7;
  margin: 12px 0 0;
  color: var(--muted);
}

/* ---------- Mission page ----------
   The statement is the page's only visible content: one centred block of
   text on white, with generous space around it. */

.mission-page {
  padding-top: clamp(64px, 12vh, 140px);
  padding-bottom: clamp(64px, 12vh, 140px);
}

.mission-statement {
  max-width: 660px;
  margin: 0 auto;
  text-align: center;
  font-size: clamp(18px, 2.2vw, 23px);
  line-height: 1.6;
  letter-spacing: 0.01em;
  color: var(--ink);
}

/* The statement's three blocks. Nothing typographic is set here on purpose:
   family, size, weight, colour, tracking, line height and centring are all
   inherited from .mission-statement above, exactly as they were when the
   statement was a single paragraph. The only new thing is the gap. */
.mission-statement p {
  margin: 0;
}

.mission-statement p + p {
  margin-top: 0.95em;
}

/* ---------- Account ---------- */

.account-shell {
  max-width: 520px;
  margin: 0 auto;
  text-align: center;
}

.account-shell .btn {
  margin-top: 24px;
}

.account-dashboard {
  max-width: 760px;
  margin: 0 auto;
}

.account-section {
  border-top: 1px solid var(--line);
  padding: 24px 0;
}

.account-section h2 {
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0 0 16px;
}

.order-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 13px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
}

.config-note {
  border: 1px dashed var(--line);
  padding: 20px;
  font-size: 12px;
  line-height: 1.7;
  color: var(--muted);
  max-width: 520px;
  margin: 24px auto 0;
  text-align: left;
}

/* ---------- Cart (drawer + full page share these rules) ---------- */

.cart-line {
  display: grid;
  grid-template-columns: 72px 1fr auto;
  gap: 14px;
  padding: 16px 0;
  border-bottom: 1px solid var(--line);
}

.cart-line-image {
  width: 72px;
  height: 96px;
  background: #f4f4f4;
  overflow: hidden;
}

.cart-line-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.cart-line-title {
  font-size: 13px;
  text-transform: uppercase;
}

.cart-line-options {
  font-size: 12px;
  color: var(--muted);
  margin-top: 4px;
}

.cart-line-price {
  font-size: 13px;
  margin-top: 6px;
}

.cart-line-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}

.cart-line-remove {
  background: none;
  border: none;
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: underline;
  cursor: pointer;
  padding: 0;
}

.cart-subtotal-row {
  display: flex;
  justify-content: space-between;
  font-size: 14px;
  padding: 20px 0;
  border-top: 1px solid var(--ink);
}

.cart-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(420px, 100vw);
  background: var(--bg);
  border-left: 1px solid var(--ink);
  z-index: 400;
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform 0.22s ease;
}

.cart-drawer[data-open="true"] {
  transform: translateX(0);
}

.cart-drawer-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.25);
  z-index: 390;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
}

.cart-drawer-backdrop[data-open="true"] {
  opacity: 1;
  pointer-events: auto;
}

.cart-drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--line);
}

.cart-drawer-header h2 {
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0;
}

.cart-drawer-body {
  flex: 1;
  overflow-y: auto;
  padding: 0 20px;
}

.cart-drawer-footer {
  padding: 16px 20px 20px;
  border-top: 1px solid var(--line);
}

@media (prefers-reduced-motion: reduce) {
  .cart-drawer,
  .cart-drawer-backdrop,
  .site-header .nav-link::after,
  .site-header .nav-button::after,
  .btn,
  .product-tile .product-image-wrap img {
    transition: none !important;
  }
}
