/* =========================================================================
   BSSF — Heart to tee
   =========================================================================

   GROUND
   The film is dark and the collection is light, and the page transitions
   between them as the garment settles. That is not decoration:

   - All seven garments were photographed on a white studio background. Their
     cut-outs composite onto a near-white ground with no halo at all. On a
     near-black ground every slightly-light edge pixel glows, and you end up
     rim-lighting your way out of a problem you created.
   - Six of the seven garments are black or charcoal. They separate best on
     light and worst on dark.
   - It removes the need for a box. The earlier build put every product on a
     cream mat, which read instantly as a stock photo pasted onto the page.
     With a light ground the page *is* the studio wall, so the garment can sit
     on it directly with nothing around it.

   The film stays dark because the heart is drawn in a void and the orange
   only sings against black.

   COLOUR
   Nothing here is #fff or #000. Orange is the brand and is therefore rationed
   — the heart, the active state, the rule. It is never a full-width slab; an
   earlier build had a solid orange "Add to bag" bar the width of the column,
   which is the single loudest thing on a page that should be selling a
   photograph.

   TYPE
   Clash Display + Switzer, both free from Fontshare, and the same pairing
   approved on the sibling bssf-weave build. The previous Barlow Condensed +
   DM Sans is the default "cinematic" free-font pick and reads as such.
   ========================================================================= */

:root {
  /* Grounds. Warm, not neutral — a pure-grey dark mode with one saturated
     accent is the look this was drifting into. */
  --void: #100d09;
  --paper: #f4f1e9;

  /* Ink. Set per-ground by .viewport / section, never hard-coded. */
  --ink: #1a1611;
  --bone: #ece7dc;

  /* The mark's own ink, sampled from the artwork. Used for the HEART and for
     small active states — never for type. Orange set as a headline word is
     what made this read as glow. */
  --orange: #f68904;

  /* Live ground + foreground. JS lerps these across the settle. */
  --ground: var(--void);
  --fg: var(--bone);
  --fg-dim: #8c857a;
  --rule: #2e2a23;

  --font-display: 'Clash Display', 'Helvetica Neue', Arial, sans-serif;
  --font-body: 'Switzer', 'Helvetica Neue', Arial, sans-serif;

  --gutter: clamp(22px, 4.4vw, 76px);

  font-family: var(--font-body);
  font-synthesis: none;
  color-scheme: dark;
}

* { box-sizing: border-box; }

/* No scroll-behavior:smooth. It makes the browser animate the synchronous
   scrollTo(0) ScrollTrigger issues while measuring, so GSAP measures at the
   old position believing it is at the top, and every cached start/end lands
   short by exactly scrollY — breaking the film on any reload below the fold
   or any resize. goToProduct() scrolls smoothly on purpose instead. */
html { scroll-padding-top: 72px; }

body {
  margin: 0;
  background: var(--void);
  color: var(--fg);
}

h1, h2, h3, p { margin-top: 0; }
button, a, input, summary { -webkit-tap-highlight-color: transparent; }
button, input { font: inherit; }
button, a { touch-action: manipulation; }
button { cursor: pointer; color: inherit; }
a { color: inherit; text-decoration: none; }
img { display: block; }

::selection { background: var(--orange); color: #fff; }
:focus-visible { outline: 2px solid var(--orange); outline-offset: 5px; }

.skip-link {
  position: fixed; top: 14px; left: 14px; z-index: 60;
  background: var(--orange); color: #fff;
  padding: 12px 18px; font-size: 13px; font-weight: 500;
  transform: translateY(-160%);
}
.skip-link:focus { transform: none; }

/* --- Type scale ---------------------------------------------------------
   Clash is a touch condensed and runs tight, so display sizes carry negative
   tracking and body does not. */

/* Clash's own word space is narrow, and negative tracking closes it further
   until "One heart" reads as "Oneheart". Opening the word space fixes that
   without loosening the letterfit, which is the thing worth keeping. */
.hero-copy h1,
.closing .dedication,
.listing-details h2,
.bag-heading h2 { word-spacing: .07em; }

.eyebrow {
  font-size: clamp(10px, .78vw, 11px);
  letter-spacing: .17em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--fg-dim);
  margin: 0;
}

/* =========================================================================
   The pinned surface
   ========================================================================= */

.intro { position: relative; height: 1180svh; }

.viewport {
  position: sticky; top: 0;
  height: 100svh;
  overflow: hidden;
  background: var(--ground);
  color: var(--fg);
}

/* Film grain. Large flat fields of near-black band visibly on 8-bit
   displays; a little noise breaks the steps up. Sits above the ground and
   below everything else. */
/* Vignette, under the grain. Pulls the eye to the centre during the film and
   lifts off entirely once the lights come up for the collection.

   The extra midpoints are not decoration: a two-stop gradient across a near
   black field is quantised into visible concentric rings on an 8-bit display.
   More stops shortens each step, and the grain above finishes the job. */
.viewport::before {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 3;
  opacity: var(--vignette, 0);
  background: radial-gradient(125% 95% at 50% 44%,
    rgba(0, 0, 0, 0)    34%,
    rgba(0, 0, 0, .06)  52%,
    rgba(0, 0, 0, .16)  66%,
    rgba(0, 0, 0, .30)  78%,
    rgba(0, 0, 0, .45)  89%,
    rgba(0, 0, 0, .62) 100%);
}

/* Film grain, and it has to sit ABOVE the vignette and the garment to do its
   job. It was at z-index 1 — underneath both — so it dithered the flat ground
   and nothing else, and the vignette banded into rings exactly as if it were
   not there at all. Dithering only works on top of the thing that bands. */
.viewport::after {
  content: '';
  position: absolute; inset: -50%;
  pointer-events: none;
  z-index: 4;
  opacity: .055;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --- The garment camera -------------------------------------------------
   One 1000x1000 stage holds the tee and its print. JS writes a single matrix
   onto it, so the photograph and the artwork can never drift apart. */

.shirt-stage {
  position: absolute; left: 0; top: 0;
  width: 1000px; height: 1000px;
  transform-origin: 0 0;
  /* The camera is a real 3D transform, and the print plane is lifted off the
     fabric in Z, so this has to preserve depth rather than flatten it. */
  transform-style: preserve-3d;
  will-change: transform;
  visibility: hidden;
  z-index: 2;
}

.hero-shirt {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  opacity: 0;
  filter: drop-shadow(0 26px 44px rgba(16, 12, 8, .42));
}
/* Superseded by the canvas; kept as the no-WebGL fallback. */
.gl .hero-shirt { display: none; }

/* The garment is rendered as cloth on a WebGL plane (fabric.js) — a flat
   photograph rotated in 3D reads as cardboard no matter how good the camera
   is, because the silhouette never changes. The <img> stays underneath as the
   fallback and is hidden the moment the canvas takes over. */
.fabric {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: none;
  filter: drop-shadow(0 26px 44px rgba(16, 12, 8, .42));
}
.fabric.live { display: block; }

/* The print rides the same stage as the photograph, so one camera drives
   both and they cannot desynchronise — an earlier build tilted a render and
   left the logo unrotated on top of it, which is why that lockup read as
   pasted on. It is lifted slightly in Z, the way a thick print sits proud of
   the cloth, so it parallaxes against the fabric as the camera moves. That
   parallax is the thing the eye reads as real depth. */
/* translateZ is re-applied by ridePrint() in app.js, which also tilts the
   print with the fold under it. This is the no-WebGL resting value. */
.print-plane { position: absolute; inset: 0; transform: translateZ(26px); transform-style: preserve-3d; }
/* The first listing's own photograph, parked at exactly the spot the listing
   will draw it so the opening can dissolve into the shop with no cut. Sits
   above the flying shirt (z-index 2) and below the headline (5). */
.landing-shirt {
  position: absolute;
  opacity: 0;
  object-fit: contain;
  pointer-events: none;
  z-index: 4;
  will-change: opacity;
}

.heart {
  position: absolute;
  /* Centred on x=610 — the garment's left chest. Sat 16px lower until the
     mark became a traced vector: the bitmap carried a little transparent
     margin that the outline does not, so the heart's point was crowding the
     wordmark underneath it. */
  left: 560px; top: 320px;
  width: 100px; height: 103px;
  overflow: visible;
  transform-origin: center;
}

.wordmark {
  position: absolute;
  left: 517px; top: 455px;
  width: 187px; height: 55px;
  overflow: visible;
  clip-path: inset(0 100% 0 0);
}

/* A real screen print has slightly soft edges and never reads as vector.
   A fraction of a pixel of blur is the difference between "printed" and
   "SVG sitting on a photo". */
.print-plane svg { filter: blur(.35px); }

/* =========================================================================
   Film copy
   ========================================================================= */

/* Top-left, against the garment at lower-right: the two read as one diagonal
   composition. Both were in the lower-left before and competed for the corner. */
.hero-copy {
  position: absolute;
  left: var(--gutter);
  top: clamp(78px, 12vh, 140px);
  width: min(48%, 680px);
  z-index: 5;
  opacity: 0; visibility: hidden;
}

.hero-copy h1 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(50px, 7.2vw, 118px);
  line-height: .9;
  letter-spacing: -.018em;
  margin: 18px 0 0;
  color: #948d82;
}
/* Emphasis by brightness, not by hue. This line was set in orange, and it was
   the single thing making the page read as neon-on-black. */
.hero-copy h1 em { font-style: normal; color: var(--bone); }


.discover {
  position: absolute;
  left: var(--gutter);
  bottom: clamp(30px, 4.4vh, 48px);
  z-index: 4;
  display: flex; align-items: center; gap: 13px;
  font-size: 11px; letter-spacing: .17em; text-transform: uppercase;
  color: var(--fg-dim);
  opacity: 0; visibility: hidden;
}
.discover i {
  width: 34px; height: 1px; background: currentColor;
  display: block; transform-origin: left;
  animation: reach 2.1s ease-in-out infinite;
}
@keyframes reach { 0%,100% { transform: scaleX(.35); } 50% { transform: scaleX(1); } }

/* =========================================================================
   The collection rail
   ========================================================================= */

.product-rail {
  position: absolute; inset: 0;
  display: flex;
  width: 100%; height: 100%;
  will-change: transform;
  z-index: 2;
}

.product-slide {
  position: relative;
  flex: 0 0 100%;
  width: 100%; height: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1.14fr) minmax(0, .86fr);
  align-items: center;
  column-gap: clamp(28px, 4.4vw, 84px);
  padding: 78px var(--gutter) 92px;
}

/* The garment's half of the panel. On slide 01 it holds nothing and exists
   only so the film camera has a rectangle to fly the tee into; on 02-07 it
   holds the photograph. Same box either way, so every panel is composed
   identically. */
.garment-well {
  position: relative;
  height: 100%;
  min-height: 0;
}

.garment {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  /* Sits straight on the ground. No mat, no card, no white box — a garment
     in a box on a coloured page reads as a stock image pasted in, and was
     the loudest thing wrong with the previous build. */
  filter: drop-shadow(0 30px 34px rgba(26, 20, 12, .17));
}

/* --- Details column ----------------------------------------------------- */

.listing-details {
  position: relative;
  max-width: 470px;
  align-self: center;
}


.listing-details h2 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(36px, 3.9vw, 62px);
  line-height: .98;
  letter-spacing: -.022em;
  margin: 16px 0 20px;
}

.listing-description {
  font-size: clamp(14px, 1.08vw, 16.5px);
  line-height: 1.62;
  color: var(--fg-dim);
  margin: 0 0 28px;
  max-width: 42ch;
}

/* Colourway and item number on one quiet line, so the column reads
   photograph > name > description > facts > action rather than as a stack of
   equally-weighted rows. */
.listing-meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px 18px;
  margin: 0 0 30px;
  font-size: 13px;
  color: var(--fg-dim);
}
.listing-color { display: inline-flex; align-items: center; gap: 10px; }
.listing-color i {
  width: 12px; height: 12px; border-radius: 50%;
  outline: 1px solid var(--rule); outline-offset: 3px;
}
.listing-sku {
  font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--fg-dim); opacity: .75;
}

.listing-price {
  font-family: var(--font-display);
  font-size: 22px; font-weight: 500;
  letter-spacing: -.02em;
  margin: 0 0 24px;
}

/* --- Size + add --------------------------------------------------------- */

.product-form { margin: 0; }
.product-form fieldset { margin: 0 0 20px; padding: 0; border: 0; }
.product-form legend {
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--fg-dim); margin-bottom: 12px; padding: 0;
}

/* A row of hairline-separated cells rather than five outlined boxes. */
.sizes {
  display: flex;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.sizes label { position: relative; flex: 1; cursor: pointer; }
.sizes label + label { border-left: 1px solid var(--rule); }
.sizes input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; margin: 0; cursor: pointer;
}
.sizes span {
  display: grid; place-items: center;
  height: 50px;
  font-size: 14px;
  color: var(--fg-dim);
  transition: color .16s, background .16s;
}
.sizes label:hover span { color: var(--fg); }
.sizes input:checked + span { background: var(--fg); color: var(--ground); }
.sizes input:focus-visible + span { outline: 2px solid var(--orange); outline-offset: -2px; }
/* Out of stock is struck through and dimmed rather than removed: a missing
   size reads as a layout bug, a struck-through one reads as news. */
.sizes label.is-out span { color: var(--fg-dim); opacity: .42; text-decoration: line-through; cursor: not-allowed; }
.sizes label.is-out:hover span { color: var(--fg-dim); }

/* Ink, not orange. The brand colour is worth more as the heart than as a
   button the width of the column. */
.add-button {
  display: inline-flex; align-items: center; gap: 30px;
  background: var(--fg); color: var(--ground);
  border: 0;
  padding: 17px 24px;
  font-size: 12.5px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  transition: background .18s, color .18s, gap .18s;
}
.add-button:hover:not(:disabled) { background: var(--orange); color: #fff; gap: 40px; }
.add-button svg { width: 22px; height: 10px; flex: none; }
.add-button:disabled { background: var(--rule); color: var(--fg-dim); cursor: default; }

.product-caption {
  font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--fg-dim);
  margin: 26px 0 0;
}

/* =========================================================================
   Header + rail navigation
   ========================================================================= */

.site-header {
  position: fixed; z-index: 20; top: 0; left: 0;
  width: 100%;
  padding: 0 var(--gutter);
  height: 72px;
  display: flex; justify-content: space-between; align-items: center;
  opacity: 0; visibility: hidden;
  color: var(--fg);
  /* A hairline and a wash of the ground, so type scrolling underneath never
     collides with the nav. Backdrop blur rather than a solid bar: the ground
     is being interpolated by JS and a solid fill would have to track it. */
  border-bottom: 1px solid var(--rule);
  background: color-mix(in srgb, var(--ground) 82%, transparent);
  backdrop-filter: saturate(140%) blur(14px);
  -webkit-backdrop-filter: saturate(140%) blur(14px);
}

/* The real BSSF heart beside the letters, rather than Arial Black with a
   heart text character nudged into place with absolute pixels. */
.brand { display: flex; align-items: center; gap: 10px; }
.brand img { height: 24px; width: auto; }
.brand span {
  font-family: var(--font-display);
  font-weight: 600; font-size: 23px; letter-spacing: -.03em;
}

.header-nav { display: flex; align-items: center; gap: clamp(14px, 2vw, 28px); }

/* The header is fixed, so it outlives the ground transition and ends up as a
   pale bar sitting on the dark closing section. JS flips this on when the
   closing reaches it.

   This has to re-point --ground as well as the ink: the bar's own background
   is a wash of --ground, so setting only the text colour left a light bar with
   light text on it — which is how it last shipped. */
.site-header.on-void {
  --ground: var(--void);
  --fg: var(--bone);
  --fg-dim: #8c857a;
  --rule: #2e2a23;
  color: var(--bone);
}


.header-link {
  position: relative;
  font-size: 13.5px; letter-spacing: .01em;
  padding: 6px 0;
}
.header-link::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: right;
  transition: transform .26s cubic-bezier(.4, 0, .2, 1);
}
.header-link:hover::after { transform: scaleX(1); transform-origin: left; }

.bag-button {
  display: inline-flex; align-items: center; gap: 9px;
  border: 1px solid var(--rule); background: none;
  border-radius: 999px;
  padding: 8px 8px 8px 16px;
  font-size: 13.5px;
  transition: border-color .18s, background .18s;
}
.bag-button:hover { border-color: var(--fg); }
.bag-count {
  display: grid; place-items: center;
  min-width: 22px; height: 22px; padding: 0 6px;
  border-radius: 999px;
  background: var(--rule); color: var(--fg);
  font-size: 11.5px; font-style: normal; font-weight: 500;
  font-variant-numeric: tabular-nums;
  transition: background .18s, color .18s;
}
.bag-button[data-filled="true"] .bag-count { background: var(--orange); color: #fff; }


/* =========================================================================
   Closing + footer — dark again, bookending the film
   ========================================================================= */

.closing {
  position: relative; z-index: 2;
  background: var(--void); color: var(--bone);
  padding: clamp(96px, 16vh, 170px) var(--gutter) clamp(72px, 11vh, 120px);
}
.closing .dedication {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(42px, 7.4vw, 116px);
  line-height: .94;
  letter-spacing: -.022em;
  margin: 22px 0 0;
}
/* Its own line. A max-width was wrapping this as "For Ben. For / something
   bigger.", which breaks the sentence in the wrong place. */
.closing .dedication span { display: block; color: #6e675c; }

/* The closing statement and the footer are one dark block, so they share a
   rhythm rather than reading as two stacked sections. */
/* =========================================================================
   Donate

   The merch is sold at the printer's cost — it funds nothing. This section
   does, so it is the one place on the page allowed to ask. A light card on
   the dark ground: it reads as a form you interact with rather than more
   page, which is the whole job.
   ========================================================================= */

.donate {
  position: relative; z-index: 2;
  background: var(--void); color: var(--bone);
  padding: clamp(60px, 9vw, 120px) var(--gutter) clamp(64px, 10vw, 130px);
  border-top: 1px solid #231f1b;
}

.donate-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 420px);
  gap: clamp(36px, 6vw, 96px);
  align-items: center;
}

.donate-lede h2 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(34px, 4.6vw, 68px);
  line-height: .98;
  letter-spacing: -.022em;
  word-spacing: .07em;
  margin: 18px 0 20px;
  color: var(--bone);
}
.donate-lede h2 em { font-style: normal; color: var(--orange); }
.donate-body {
  margin: 0;
  max-width: 44ch;
  font-size: clamp(14px, 1.08vw, 16px);
  line-height: 1.62;
  color: #a39d93;
}

/* The card. Paper, square, one shadow — no gradients, no glass. */
.donate-card {
  background: var(--paper);
  color: var(--ink);
  padding: clamp(24px, 2.6vw, 34px);
  box-shadow: 0 30px 70px rgba(0, 0, 0, .42);
}
.donate-card fieldset { margin: 0 0 22px; padding: 0; border: 0; }
.donate-card legend,
.donate-amount-label {
  display: block;
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: #6d6a62;
  margin-bottom: 12px; padding: 0;
}

.donate-presets { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.donate-presets label { position: relative; cursor: pointer; }
.donate-presets input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; margin: 0; cursor: pointer;
}
.donate-presets span {
  display: grid; place-items: center;
  height: 46px;
  border: 1px solid #ddd9cf;
  font-size: 15px;
  transition: border-color .16s, background .16s, color .16s;
}
.donate-presets label:hover span { border-color: var(--ink); }
.donate-presets input:checked + span { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.donate-presets input:focus-visible + span { outline: 2px solid var(--orange); outline-offset: 2px; }

.donate-amount { display: block; margin-bottom: 22px; }
.donate-field {
  display: flex; align-items: center; gap: 10px;
  border: 1px solid #ddd9cf;
  padding: 0 16px;
  transition: border-color .16s;
}
.donate-field:focus-within { border-color: var(--ink); }
.donate-field i {
  font-style: normal;
  font-family: var(--font-display);
  font-size: 20px; font-weight: 600; color: #6d6a62;
}
.donate-field input {
  flex: 1; min-width: 0;
  border: 0; background: none; outline: none;
  padding: 15px 0;
  font-family: var(--font-display);
  font-size: 26px; font-weight: 600; letter-spacing: -.02em;
  color: var(--ink);
}
.donate-field em {
  font-style: normal;
  font-size: 11px; letter-spacing: .12em; color: #8a867d;
}

.donate-button {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%;
  background: var(--ink); color: var(--paper);
  border: 0;
  padding: 18px 22px;
  font-size: 12.5px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  transition: background .18s;
}
.donate-button:hover { background: var(--orange); color: #fff; }
.donate-button svg { width: 22px; height: 10px; flex: none; }
.donate-button:disabled { background: #ddd9cf; color: #8a867d; cursor: default; }

.donate-note {
  margin: 14px 0 0;
  font-size: 11.5px; line-height: 1.6; color: #6d6a62;
}

/* --- Footer social marks ------------------------------------------------
   Real brand glyphs (Simple Icons, CC0) rather than a generic arrow. */
.foot-social a { gap: 12px; font-size: 14px; }
.foot-social li + li { margin-top: 14px; }
/* Big enough to actually read as the brand. Selector has to out-specify
   `.foot-col a svg`, which sizes the little external-link arrows at 8px/0.5 —
   it was silently winning and rendering these as three grey smudges. */
.foot-social a svg.social-mark {
  width: 19px; height: 19px; flex: none;
  opacity: .9;
  transition: opacity .18s, transform .18s;
}
.foot-social a:hover svg.social-mark { opacity: 1; transform: translateY(-1px); }

.site-footer {
  position: relative; z-index: 2;
  background: var(--void); color: #8b867c;
  padding: 0 var(--gutter) 26px;
  font-size: 13.5px;
}

/* Brand, campaign, follow across the width. Hugging the two link columns to
   the right edge left a dead zone through the middle of the footer. */
.foot-main {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(30px, 4vw, 72px);
  align-items: start;
  padding: clamp(54px, 7vw, 86px) 0 clamp(44px, 6vw, 70px);
  border-top: 1px solid #231f1b;
}

.foot-brand { max-width: 46ch; }
.foot-brand img { height: 30px; width: auto; margin-bottom: 22px; }
.foot-line {
  margin: 0 0 26px;
  font-size: clamp(14px, 1.15vw, 16px);
  line-height: 1.62;
  color: #a39d93;
  max-width: 40ch;
}

/* The campaign is where the money actually happens — merch is how it becomes
   visible — so bssf.ca gets the one piece of emphasis down here. */
.foot-cta {
  display: inline-flex; align-items: center; gap: 16px;
  font-family: var(--font-display);
  font-size: clamp(22px, 2.3vw, 34px);
  font-weight: 600; letter-spacing: -.02em;
  color: var(--bone);
  padding-bottom: 10px;
  border-bottom: 1px solid #3a352e;
  transition: color .2s, border-color .2s, gap .2s;
}
.foot-cta svg { width: 30px; height: 10px; flex: none; }
.foot-cta:hover { color: var(--orange); border-color: var(--orange); gap: 24px; }

/* The two link columns are grid items of .foot-main directly, so they share
   its tracks rather than forming a block of their own. */
.foot-cols { display: contents; }

.foot-col h2 {
  font-size: 10.5px; letter-spacing: .17em; text-transform: uppercase;
  font-weight: 500; color: #5f5a52;
  margin: 0 0 16px;
}
.foot-col ul { list-style: none; margin: 0; padding: 0; }
.foot-col li + li { margin-top: 10px; }
.foot-col a {
  display: inline-flex; align-items: center; gap: 7px;
  color: #b4afa5;
  transition: color .18s;
}
.foot-col a:hover { color: var(--orange); }
/* Rides the first line. Centred, it floats in the middle of a link that
   wraps to two lines, which 'Ben Stelter Foundation' does on narrow. */
.foot-col a svg { width: 8px; height: 8px; opacity: .5; align-self: flex-start; margin-top: .5em; }

.foot-base {
  display: flex; justify-content: space-between; gap: 18px;
  padding-top: 22px;
  border-top: 1px solid #231f1b;
  font-size: 11.5px; letter-spacing: .05em;
  color: #57524b;
}

/* =========================================================================
   Bag
   ========================================================================= */

dialog {
  position: fixed; left: auto; right: 0; top: 0;
  height: 100dvh; max-height: none; margin: 0;
  width: min(440px, 100%); max-width: none;
  background: var(--paper); color: var(--ink);
  border: 0; padding: 0;
  box-shadow: -24px 0 90px rgba(10, 8, 6, .4);
  display: flex; flex-direction: column;
}
dialog:not([open]) { display: none; }
dialog::backdrop { background: rgba(8, 7, 6, .62); backdrop-filter: blur(3px); }

.bag-heading {
  display: flex; align-items: center; justify-content: space-between;
  padding: 26px 28px 20px;
  border-bottom: 1px solid #ddd9cf;
}
.bag-heading h2 {
  font-family: var(--font-display);
  margin: 0; font-size: 26px; font-weight: 600; letter-spacing: -.012em;
}
.close-button {
  display: grid; place-items: center;
  width: 34px; height: 34px;
  background: none; border: 1px solid #ddd9cf; border-radius: 999px;
  color: #6d6a62;
  transition: border-color .18s, color .18s;
}
.close-button svg { width: 11px; height: 11px; }
.close-button:hover { color: var(--ink); border-color: var(--ink); }

#bag-items { flex: 1; overflow: auto; min-height: 0; padding: 0 28px; }

/* Each line carries its own photograph. Seven products share four names
   between them, so "Boxy Fleece Hoodie / Charcoal" alone is genuinely
   ambiguous in a list. */
.bag-item {
  display: grid; grid-template-columns: 62px 1fr; gap: 16px;
  padding: 20px 0;
  border-bottom: 1px solid #e4e0d6;
}
.bag-thumb {
  width: 62px; height: 62px;
  background: #e9e5db;
  display: grid; place-items: center;
  overflow: hidden;
}
.bag-thumb img { width: 100%; height: 100%; object-fit: contain; }
.bag-item strong { display: block; font-size: 15px; font-weight: 500; }
.bag-item .bag-variant { font-size: 12.5px; color: #6d6a62; margin: 4px 0 10px; }
.bag-price { font-size: 13.5px; }

.bag-line-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.qty {
  display: inline-flex; align-items: center;
  border: 1px solid #ddd9cf;
}
.qty button {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  background: none; border: 0; color: #6d6a62;
  font-size: 15px; line-height: 1;
}
.qty button:hover:not(:disabled) { color: var(--ink); }
.qty button:disabled { opacity: .3; cursor: default; }
.qty output {
  min-width: 26px; text-align: center;
  font-size: 13px; font-variant-numeric: tabular-nums;
}
.bag-remove {
  background: none; border: 0; padding: 4px 0;
  font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: #8a867d;
}
.bag-remove:hover { color: var(--orange); }

.empty-bag { font-size: 15px; padding: 38px 0; color: #6d6a62; }

.bag-foot { padding: 20px 28px 26px; border-top: 1px solid #ddd9cf; }
.bag-school { margin: 0 0 18px; }
.bag-school[hidden] { display: none; }
.bag-school label { display: block; font-size: 13px; line-height: 1.5; margin-bottom: 8px; }
.bag-school select { box-sizing: border-box; width: 100%; min-width: 0; max-width: 100%; min-height: 44px; padding: 10px 12px; border: 1px solid #bdb8ad; border-radius: 4px; background: #fff; color: var(--ink); font: inherit; font-size: 16px; }
.bag-school select:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.bag-school p { margin: 7px 0 0; color: #6d6a62; font-size: 12px; line-height: 1.5; }
.bag-subtotal {
  display: flex; justify-content: space-between;
  font-family: var(--font-display); font-size: 17px; font-weight: 500;
  margin: 0 0 14px;
}
.bag-note { font-size: 12px; color: #6d6a62; line-height: 1.6; margin: 0 0 16px; }
.bag-note:empty { display: none; }
dialog .add-button { --ground: var(--paper); --fg: var(--ink); width: 100%; justify-content: space-between; }
.bag-continue {
  display: block; width: 100%; text-align: center;
  padding: 14px 0; margin-top: 4px;
  font-size: 11px; letter-spacing: .12em; text-transform: uppercase;
  color: #6d6a62; background: none; border: 0;
}
.bag-continue:hover { color: var(--ink); }

.toast {
  position: fixed; z-index: 40; bottom: 24px; left: 50%;
  transform: translate(-50%, 22px);
  padding: 14px 22px;
  background: var(--bone); color: var(--void);
  font-size: 13px;
  opacity: 0; pointer-events: none;
  transition: opacity .22s, transform .22s;
}
.toast.visible { transform: translate(-50%, 0); opacity: 1; }
.no-script { padding: 24px; color: var(--bone); }

/* =========================================================================
   Narrow
   ========================================================================= */

@media (max-width: 860px) {
  .product-slide {
    grid-template-columns: 1fr;
    grid-template-rows: minmax(0, 1fr) auto;
    row-gap: 18px;
    align-content: center;
    padding: 76px var(--gutter) 92px;
  }
  .listing-details { max-width: none; }
  .listing-description { max-width: none; font-size: 14px; margin-bottom: 20px; }
  .listing-color { margin-bottom: 20px; }
  .listing-details h2 { font-size: clamp(28px, 7.4vw, 40px); margin: 10px 0 12px; }
  .add-button { width: 100%; justify-content: space-between; }
  .product-caption { margin-top: 18px; }
  .hero-copy { width: calc(100% - var(--gutter) * 2); top: clamp(62px, 9vh, 96px); }
  .site-header { height: 62px; }
  .brand img { height: 20px; }
  .brand span { font-size: 19px; }
  .header-link { font-size: 12.5px; }
  .bag-button { font-size: 12.5px; padding: 7px 7px 7px 13px; }
  .donate-inner { grid-template-columns: 1fr; gap: 32px; }
  .donate-lede h2 { font-size: clamp(30px, 8vw, 44px); }
  .foot-main { grid-template-columns: 1fr 1fr; gap: 34px 24px; }
  .foot-brand { grid-column: 1 / -1; max-width: none; }
  .foot-base { flex-direction: column; gap: 8px; }
  .listing-meta { margin-bottom: 20px; }
}

@media (max-width: 560px) {
  .bag-item { grid-template-columns: 52px 1fr; gap: 13px; }
  .bag-thumb { width: 52px; height: 52px; }
}

/* =========================================================================
   Reduced motion / no JS — the film unpins into a plain vertical stack
   ========================================================================= */

.reduce-motion .intro { height: auto !important; min-height: 0; }
.reduce-motion .viewport { position: relative; height: auto; overflow: visible; }
.reduce-motion .viewport::after { display: none; }
.reduce-motion .product-rail { position: relative; display: block; height: auto; transform: none !important; }
.reduce-motion .product-slide { height: auto; min-height: 100svh; }
.reduce-motion .hero-copy { display: none !important; }
.reduce-motion .site-header { opacity: 1 !important; visibility: visible !important; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* =========================================================================
   Timed intro -> normal storefront
   The cinematic heart is only an opening moment. Once .shop-ready is applied,
   all shopping UI is ordinary document flow and fully interactive.
   ========================================================================= */
body.shop-ready .intro { height: auto !important; min-height: 0; }
body.shop-ready .viewport {
  position: relative;
  height: auto;
  overflow: visible;
  background: var(--paper);
  color: var(--ink);
}
/* NB: .shirt-stage is deliberately NOT in this list. It is the opening's
   scroll-driven 3D camera, and it is the only .shirt-stage in the document —
   it always lives inside .opening. Neutralising it here set
   `transform: none !important`, which beat the inline transform opening.js
   writes every frame, so the shirt never actually travelled to the listing: it
   sat at its untransformed position and the storefront simply cut in over it.
   That was the jump at the end of the opening. */
body.shop-ready .viewport::before,
body.shop-ready .viewport::after,
body.shop-ready .hero-copy,
body.shop-ready .product-rail {
  position: relative;
  inset: auto;
  display: block;
  width: 100%;
  height: auto;
  transform: none !important;
  will-change: auto;
}
body.shop-ready .product-slide {
  min-height: min(900px, 100svh);
  height: auto;
  opacity: 1;
  visibility: visible;
}
body.shop-ready .listing-details { opacity: 1 !important; visibility: visible !important; }
body.shop-ready .site-header { opacity: 1 !important; visibility: visible !important; }
body.shop-ready .garment-well { min-height: 540px; }
body:not(.shop-ready) .tee-viewer { display: none; }

/* Gentle hand-off from intro to storefront, without tying navigation to scroll. */
body.shop-ready .product-rail { opacity: 0; transform: translateY(12px) !important; }
body.shop-ready.shop-ready-visible .product-rail {
  opacity: 1;
  transform: none !important;
  transition: opacity .32s ease, transform .32s ease;
}

.tee-viewer {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}
.tee-preview {
  position: relative;
  width: min(100%, 650px);
  height: min(76vh, 650px);
  display: grid;
  place-items: center;
}
.tee-preview > img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 30px 34px rgba(26,20,12,.17));
  transition: transform .3s ease, filter .3s ease;
}
.tee-preview[data-side="back"] > img { transform: none; }
.tee-front-art {
  position: absolute;
  left: 58%;
  top: 34%;
  width: 17%;
  display: grid;
  justify-items: center;
  gap: 4px;
  transition: opacity .2s ease;
  pointer-events: none;
}
.tee-front-art img { width: 42%; height: auto; }
.tee-front-art strong {
  font-family: Arial Black, Arial, sans-serif;
  font-size: clamp(17px, 2vw, 27px);
  line-height: 1;
  letter-spacing: -.08em;
  color: #eee8d3;
}
.tee-preview[data-side="back"] .tee-front-art { opacity: 0; }
.tee-side-note {
  position: absolute;
  left: 50%;
  bottom: 2%;
  transform: translateX(-50%);
  padding: 7px 11px;
  border-radius: 999px;
  background: rgba(244,241,233,.9);
  border: 1px solid #d9d3c7;
  color: #6e695f;
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.tee-view-controls {
  position: absolute;
  left: 50%;
  bottom: 14px;
  transform: translateX(-50%);
  z-index: 6;
  display: flex;
  gap: 6px;
  padding: 5px;
  border-radius: 999px;
  background: rgba(244,241,233,.94);
  border: 1px solid #d9d3c7;
  box-shadow: 0 6px 24px rgba(26,22,17,.08);
}
.tee-view-button {
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: #6e695f;
  padding: 8px 15px;
  font-size: 12px;
  font-weight: 600;
}
.tee-view-button.active { background: #1a1611; color: #f4f1e9; }

@media (max-width: 860px) {
  body.shop-ready .product-slide { min-height: auto; padding-top: 96px; padding-bottom: 72px; }
  body.shop-ready .garment-well { min-height: 430px; }
  .tee-preview { height: 430px; }
}

/* Shopify media-backed front/back product previews */
.product-viewer .product-preview > img{max-width:100%;max-height:100%;object-fit:contain}
.product-viewer .tee-side-note{bottom:8%}

/* Product view uses only the Front / Back toggle buttons. */
.tee-side-note{display:none!important}

/* =========================================================================
   Heart-only intro guard
   Keep all storefront/product imagery out of the first paint on every device.
   The cinematic stage still exists so the heart can render, but the garment,
   product previews and listing copy cannot leak in at the edge on mobile.
   ========================================================================= */
body:not(.shop-ready) .product-slide:not(:first-child),
body:not(.shop-ready) .listing-details,
body:not(.shop-ready) .garment-well > .garment,
body:not(.shop-ready) .garment-well > .tee-viewer,
body:not(.shop-ready) .garment-well > .product-viewer {
  visibility: hidden !important;
  opacity: 0 !important;
  pointer-events: none !important;
}

/* The opening is intentionally heart-only. The garment is revealed only as
   part of the storefront after activateShop(), avoiding the mobile side peek. */
body:not(.shop-ready) .hero-shirt,
body:not(.shop-ready) .fabric {
  opacity: 0 !important;
}

body:not(.shop-ready) .viewport {
  background: #0c0b0a !important;
}


/* =========================================================================
   Mobile interaction hardening
   Product media fills the garment well, but only its Front/Back controls should
   receive taps. Everything else in the image layer lets touches pass through.
   The purchase controls are explicitly raised above any composited media layer.
   ========================================================================= */
body.shop-ready .tee-viewer { pointer-events: none; }
body.shop-ready .tee-view-controls,
body.shop-ready .tee-view-button { pointer-events: auto; }
body.shop-ready .listing-details,
body.shop-ready .product-form,
body.shop-ready .sizes,
body.shop-ready .sizes label,
body.shop-ready .sizes input,
body.shop-ready .add-button {
  position: relative;
  z-index: 8;
  pointer-events: auto;
}
@media (max-width: 860px) {
  body.shop-ready button,
  body.shop-ready a,
  body.shop-ready input,
  body.shop-ready label { touch-action: manipulation; }
  body.shop-ready .listing-details { z-index: 10; }
}

/* Mobile/POD availability hardening: an async Shopify lookup is advisory for
   the catalogue UI. Controls must not visually collapse merely because that
   lookup was slow or unavailable. Explicitly disabled controls still retain
   the normal sold-out treatment. */
@media (max-width: 860px) {
  body.shop-ready .product-form:not([aria-busy="true"]) .add-button:not(:disabled) {
    opacity: 1;
    pointer-events: auto;
  }
  body.shop-ready .sizes input:not(:disabled) + span {
    opacity: 1;
    pointer-events: auto;
  }
}


/* =========================================================================
   Mobile stability pass — 2026-10-05
   The catalogue size row is permanent. Keep it visible, comfortably tappable,
   and above all product media/compositing layers on narrow screens.
   ========================================================================= */
@media (max-width: 860px) {
  body.shop-ready .product-slide {
    display: grid;
    grid-template-columns: minmax(0,1fr);
    grid-template-rows: auto auto;
    align-items: start;
    row-gap: 14px;
    padding-top: 78px;
    padding-bottom: 58px;
  }
  body.shop-ready .garment-well,
  body.shop-ready .tee-preview {
    min-height: 0;
    height: clamp(300px, 47vh, 390px);
  }
  body.shop-ready .listing-details {
    position: relative;
    z-index: 20;
    width: 100%;
    min-width: 0;
  }
  body.shop-ready .product-form,
  body.shop-ready .product-form fieldset,
  body.shop-ready .sizes {
    display: block;
    width: 100%;
    visibility: visible !important;
    opacity: 1 !important;
  }
  body.shop-ready .sizes {
    display: flex;
    position: relative;
    z-index: 25;
    min-height: 52px;
    overflow: visible;
  }
  body.shop-ready .sizes label {
    display: block;
    flex: 1 1 0;
    min-width: 0;
    min-height: 52px;
    visibility: visible !important;
    opacity: 1 !important;
  }
  body.shop-ready .sizes span {
    height: 52px;
    min-width: 0;
    font-size: 13px;
    -webkit-user-select: none;
    user-select: none;
  }
  body.shop-ready .sizes input {
    z-index: 2;
    pointer-events: auto !important;
    -webkit-tap-highlight-color: transparent;
  }
  body.shop-ready .add-button {
    min-height: 52px;
    margin-top: 2px;
    position: relative;
    z-index: 25;
  }
  body.shop-ready .tee-view-controls {
    z-index: 12;
  }
}
@media (max-width: 560px) {
  body.shop-ready .product-slide {
    padding-top: 72px;
    padding-bottom: 48px;
  }
  body.shop-ready .garment-well,
  body.shop-ready .tee-preview {
    height: clamp(280px, 42vh, 340px);
  }
  body.shop-ready .listing-details h2 { margin-top: 7px; }
  body.shop-ready .listing-price { margin-bottom: 18px; }
  body.shop-ready .product-form fieldset { margin-bottom: 16px; }
  body.shop-ready .sizes span { font-size: 12.5px; }
}

/* =========================================================================
   Size selector structural fix — 2026-10-05
   Keep the native radio control layered over its own label instead of letting
   the invisible input participate in document flow. A previous interaction
   hardening rule set .sizes input to position:relative, which created a blank
   100%-sized row and pushed the visible size text underneath it.
   ========================================================================= */
body.shop-ready .sizes {
  display: grid !important;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  width: 100%;
  min-height: 50px;
  overflow: hidden;
}
body.shop-ready .sizes label {
  position: relative !important;
  display: block !important;
  min-width: 0;
  min-height: 50px;
  margin: 0;
  padding: 0;
}
body.shop-ready .sizes input {
  position: absolute !important;
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  margin: 0 !important;
  opacity: 0;
  z-index: 2;
  cursor: pointer;
}
body.shop-ready .sizes span {
  position: relative;
  z-index: 1;
  display: grid !important;
  place-items: center;
  width: 100%;
  height: 50px;
  min-width: 0;
  margin: 0;
  padding: 0 4px;
  box-sizing: border-box;
  line-height: 1;
  white-space: nowrap;
}
@media (max-width: 860px) {
  body.shop-ready .sizes,
  body.shop-ready .sizes label,
  body.shop-ready .sizes span {
    min-height: 52px;
  }
  body.shop-ready .sizes span { height: 52px; }
}

/* One layout owner: vertical touch browsing; native sticky rail on desktop. */
:root {
  --ground: rgb(244 241 233); --fg: rgb(26 22 17);
  --fg-dim: rgb(110 105 95); --rule: rgb(217 211 199); color-scheme: light;
}
body.shop-ready.shop-ready-visible .product-rail { transition: none; }
body.shop-ready .site-header { backdrop-filter: none; -webkit-backdrop-filter: none; }
body.shop-ready .product-slide { contain: layout style; scroll-margin-top: 70px; }
body.shop-ready .product-preview img,
body.shop-ready .tee-preview img { filter: none; transition: none; object-fit: contain; }
body.shop-ready .tee-preview { filter: none; }
body.shop-ready .sizes { grid-auto-flow: unset; grid-template-columns: repeat(auto-fit,minmax(48px,1fr)); }
body.shop-ready .sizes label { touch-action: manipulation; }
.catalog-status { padding: 120px var(--gutter) 30px; margin: 0; }
.catalog-status[hidden] { display: none; }
.intro-splash {
  position: fixed; inset: 0; z-index: 100; display: grid; place-items: center;
  background: #100d09; pointer-events: none;
  animation: intro-out 1.25s ease forwards;
}
.intro-splash img { width: 110px; height: 120px; object-fit: contain; }
@keyframes intro-out { 0%,55% { opacity: 1; } 100% { opacity: 0; visibility: hidden; } }

body.shop-ready.desktop-rail .viewport {
  position: sticky; top: 0; height: 100svh; overflow: hidden;
}
body.shop-ready.desktop-rail .product-rail {
  position: absolute; inset: 0; display: flex; height: 100%; width: 100%;
  will-change: transform;
}
body.shop-ready.desktop-rail .product-slide {
  flex: 0 0 100%; width: 100%; height: 100%; min-height: 0;
  padding-top: 88px; padding-bottom: 36px;
}
body.shop-ready.desktop-rail .garment-well { min-height: 0; height: 100%; }
body.shop-ready.desktop-rail .listing-details h2 { font-size: clamp(32px,3.3vw,54px); }
body.shop-ready.desktop-rail .listing-description { margin-bottom: 18px; }
body.shop-ready.desktop-rail .listing-meta { margin-bottom: 20px; }
body.shop-ready.desktop-rail .tee-preview { height: 100%; min-height: 0; }

@media (max-width: 1180px), (hover: none), (pointer: coarse) {
  body.shop-ready .viewport { overflow: clip; }
  body.shop-ready .product-rail { display: block; transform: none !important; }
  body.shop-ready .product-slide { height: auto; min-height: 0; padding-top: 90px; padding-bottom: 56px; }
  body.shop-ready .garment-well { min-height: 0; height: clamp(320px,50vw,540px); }
}
@media (max-width: 860px) {
  body.shop-ready .product-slide { grid-template-columns: minmax(0,1fr); padding-top: 82px; gap: 20px; }
  body.shop-ready .garment-well, body.shop-ready .tee-preview { height: clamp(270px,80vw,470px); min-height: 0; }
  body.shop-ready .listing-details { max-width: none; }
}
@media (prefers-reduced-motion: reduce) { .intro-splash { display: none; } }
/* Keep the modal tied to the visible viewport, including mobile zoom/layout. */
dialog { width: min(440px, 100vw); max-width: 100vw; min-width: 0; overflow-x: hidden; }
.bag-heading { flex: 0 0 auto; min-width: 0; }
.bag-heading h2 { min-width: 0; }
.close-button { flex: 0 0 44px; width: 44px; height: 44px; }
.bag-item { grid-template-columns: 62px minmax(0, 1fr); }
.bag-item strong { overflow-wrap: anywhere; }
.donate-inner > *, .donate-card fieldset, .donate-field { min-width: 0; }
.donate-field input { min-width: 0; width: 100%; }
/* White product photography belongs on one continuous white shop canvas. */
:root { --paper: #fff; --ground: #fff; --rule: #e7e5e1; }
body.shop-ready .site-header { background: #fff; }
body.shop-ready .site-header.on-void { background: var(--void); }
body.shop-ready .product-slide { background: #fff; }
body.shop-ready .tee-preview[role="button"] { pointer-events: auto; cursor: pointer; touch-action: manipulation; }
body.shop-ready .tee-preview[role="button"]:focus-visible { outline: 2px solid var(--orange); outline-offset: 4px; }
body.shop-ready .tee-preview > .preview-back { opacity: 0; }
body.shop-ready .tee-preview > img { transition: opacity .18s ease; }
body.shop-ready .tee-preview.show-back > .preview-front { opacity: 0; }
body.shop-ready .tee-preview.show-back > .preview-back { opacity: 1; }
.preview-hint { position: absolute; bottom: 2px; left: 0; right: 0; text-align: center; color: #79756e; font-size: 10px; letter-spacing: .1em; text-transform: uppercase; pointer-events: none; }
.donate { background: #f7f7f5; color: var(--ink); border-top: 1px solid #e7e5e1; }
.donate-inner { max-width: 1240px; margin: auto; }
.donate-lede h2 { color: var(--ink); }
.donate-body { color: #68655f; }
.donate-card { background: #fff; border: 1px solid #e5e4df; border-radius: 20px; box-shadow: 0 10px 35px #19171106; }
.donate-card-title { display: flex; justify-content: space-between; align-items: center; font-size: 21px; font-weight: 600; margin: 0 0 8px; letter-spacing: -.03em; }
.donate-card-title span { color: var(--orange); }
.donate-provider { font-size: 12px; color: #79756e; margin: 0 0 30px; }
.donate-presets span { border-radius: 9px; }
.donate-field { border-radius: 10px; }
.donate-button { border-radius: 10px; background: var(--orange); color: #17130f; }
.donate-button:hover { background: #e67c00; color: #17130f; }
.donate-note { font-size: 11px; line-height: 1.6; }
/* Keep giving direct and understated, in the same visual language as the shop. */
.donate { background: #fff; padding: 80px var(--gutter); border-top: 1px solid #deddd9; }
.donate-inner { max-width: 1120px; grid-template-columns: minmax(0,1fr) minmax(0,390px); gap: 70px; }
.donate-lede h2 { font-size: clamp(28px,3.3vw,46px); line-height: 1.08; letter-spacing: -.025em; margin: 18px 0; }
.donate-body { font-size: 15px; max-width: 34ch; }
.donate-card { padding: 0; border: 0; border-radius: 0; box-shadow: none; }
.donate-presets span { border-radius: 0; }
.donate-field { border-radius: 0; }
.donate-button { border-radius: 0; background: var(--ink); color: #fff; }
.donate-button:hover { background: #34312b; color: #fff; }
.donate-note { color: #77736c; margin-bottom: 0; }
.colour-options { margin: 0 0 22px; padding: 0; border: 0; display: flex; flex-wrap: wrap; gap: 7px; }
.colour-options legend { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: #6d6a62; margin-bottom: 10px; }
.colour-option { padding: 10px 13px; min-height: 42px; background: white; border: 1px solid #dedbd4; font-size: 12px; }
.colour-option[aria-pressed="true"] { border-color: var(--ink); background: var(--ink); color: white; }
body.shop-ready .tee-preview > img[hidden] { display: none; }
@media(max-width:860px) {
  .donate { padding: 54px var(--gutter); }
  .donate-inner { grid-template-columns: minmax(0,1fr); gap: 32px; }
}
@media(prefers-reduced-motion:reduce) { body.shop-ready .tee-preview > img { transition: none; } }
/* Restored heart-to-cloth opening, independent from catalog scrolling. */
.opening { position: relative; height: 360svh; }
.opening-screen { position: fixed; inset: 0; overflow: hidden; z-index: 30; background: #100d09; pointer-events: none; }
.opening-screen[hidden] { display: none; }
/* Hide the uninitialized scene, including the complete SVG underneath its mask.
   The first visible frame is the beginning of the stroke, never a static logo. */
.opening-screen:not(.opening-painted)::before {
  content: ''; position: absolute; inset: 0; z-index: 10; background: #100d09;
}
.opening-screen:not(.opening-painted) .shirt-stage { visibility: hidden; }

body.shop-ready .opening .shirt-stage { display: block !important; }
body.shop-ready .opening .hero-copy { display: block !important; }
.opening .hero-shirt, .opening .fabric { filter: none; }
.opening .fabric { display: none; }
.opening .cloth-ready .fabric { display: block; }
.opening .cloth-ready .hero-shirt { display: none; }
/* Sits centred under the heart while the heart is the whole screen. opening.js
   sets --skip-top from the heart's drawn height, and fades this out as soon as
   the visitor starts scrolling — by then the shirt is on its way and a skip
   link parked over it would just be in the way. */
.opening-skip {
  position: absolute;
  left: 50%;
  top: var(--skip-top, 62%);
  /* Offered as soon as the heart stroke has finished drawing. */
  opacity: 0;
  transform: translate(calc(-50% + var(--skip-shift, 0px)), 7px);
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 6px 2px;
  color: #c9c0b0;
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  z-index: 6;
  pointer-events: none;
  transition: opacity .6s ease, transform .6s cubic-bezier(.22, .61, .36, 1), color .3s ease;
}
/* Offered at the heart animationend event, never after an idle-time countdown. */
.opening-skip.is-offered {
  opacity: 1;
  transform: translate(calc(-50% + var(--skip-shift, 0px)), 0);
  pointer-events: auto;
}
.opening-skip-label { position: relative; }
/* Drawn from the left on hover rather than text-decoration, so it can animate
   and sit clear of the baseline. */
.opening-skip-label::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -5px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .34s cubic-bezier(.22, .61, .36, 1);
}
.opening-skip-arrow {
  font-size: 12px;
  transition: transform .34s cubic-bezier(.22, .61, .36, 1), color .3s ease;
}
.opening-skip:hover,
.opening-skip:focus-visible { color: #f4efe4; }
.opening-skip:hover .opening-skip-label::after,
.opening-skip:focus-visible .opening-skip-label::after { transform: scaleX(1); }
.opening-skip:hover .opening-skip-arrow,
.opening-skip:focus-visible .opening-skip-arrow { transform: translateY(3px); color: var(--orange); }
.opening-skip:focus-visible { outline: 2px solid var(--orange); outline-offset: 7px; border-radius: 2px; }
@media (prefers-reduced-motion: reduce) {
  .opening-skip, .opening-skip-label::after, .opening-skip-arrow { transition: none; }
}
body.shop-ready .site-header.opening-header { opacity: 0 !important; pointer-events: none; }
@keyframes opening-heart { from { stroke-dashoffset: var(--heart-length); } to { stroke-dashoffset: 0; } }
@media(max-width:860px) {
  .opening .hero-copy { width: 85%; top: 80px; }
  .opening .hero-copy h1 { font-size: clamp(40px,8vw,72px); }
}
@media(prefers-reduced-motion:reduce) { .opening, .opening-screen { display: none; } }
/* Contain intrinsic Shopify image dimensions inside the reserved photo area. */
body.shop-ready .tee-viewer { min-width: 0; min-height: 0; }
body.shop-ready .tee-preview {
  width: min(100%, 650px); height: 100%; min-width: 0; min-height: 0;
  overflow: hidden;
}
body.shop-ready .tee-preview > img {
  position: absolute; inset: 0; width: 100%; height: 100%;
}
@media (max-width: 860px) {
  .donate-inner { grid-template-columns: minmax(0, 1fr); }
}

/* The caption travels with the product form: no extra scrolling region, and
   no separate fixed-height row that can force the horizontal rail to collapse. */
body.shop-ready .product-slide { grid-template-rows: minmax(0,1fr); }
body.shop-ready .listing-details { overflow: visible; max-height: none; }
body.shop-ready .listing-details > .product-caption {
  margin: 12px 0 0;
  line-height: 1.45;
  letter-spacing: .075em;
}
@media (max-width: 860px) {
  body.shop-ready .product-slide { grid-template-rows: auto auto; }
}
/* Donation text's in-line destination should be recognizable and operable. */
.donate-body a {
  text-decoration: underline;
  text-decoration-color: var(--orange);
  text-underline-offset: 3px;
}
.donate-body a:hover { color: #17130f; }
/* Horizontal storefront: preserve a single scrolling surface. A small amount
   of compact spacing gives the buying controls room even with longer titles. */
body.shop-ready.desktop-rail .product-slide { padding-top: 78px; padding-bottom: 23px; }
body.shop-ready.desktop-rail .listing-details h2 { margin: 8px 0 15px; line-height: 1; }
body.shop-ready.desktop-rail .listing-description { line-height: 1.47; margin-bottom: 12px; }
body.shop-ready.desktop-rail .listing-meta { margin-bottom: 13px; }
body.shop-ready.desktop-rail .listing-price { margin-bottom: 13px; }
body.shop-ready.desktop-rail .product-form fieldset { margin-bottom: 13px; }
body.shop-ready.desktop-rail .product-form legend { margin-bottom: 7px; }
body.shop-ready.desktop-rail .sizes span { height: 43px; }
body.shop-ready.desktop-rail .add-button { padding: 14px 23px; }
/* Keep the original mobile product card proportions — no nested scrolling. */
@media (max-width: 860px) {
  body.shop-ready .listing-details > .product-caption { margin-top: 15px; }
}


/* One composition per breakpoint: copy beside the cloth on desktop, above it
   on touch screens. The camera keeps ownership of the shirt's movement. */
body.shop-ready .opening .hero-copy {
  position: absolute;
  inset: auto;
  left: var(--gutter);
  top: 50%;
  width: 46%;
  height: auto;
  transform: translateY(-50%);
  text-align: left;
}
body.shop-ready .opening .hero-copy h1 {
  font-size: clamp(42px, 4.7vw, 88px);
  line-height: 1.02;
  letter-spacing: -.045em;
  margin: 0;
}
body.shop-ready .opening .hero-copy h1 em {
  display: inline-block;
  font-size: .76em;
  line-height: 1.25;
  margin-top: .15em;
}
@media (max-width: 860px) {
  body.shop-ready .opening .hero-copy {
    top: max(48px, 10svh);
    width: calc(100% - var(--gutter) * 2);
    transform: none;
    text-align: center;
  }
  body.shop-ready .opening .hero-copy h1 {
    font-size: clamp(34px, 7.7vw, 62px);
    line-height: 1.04;
  }
}
.opening-skip { min-height: 44px; }
.opening-skip:focus-visible { outline: 1px solid var(--orange); outline-offset: 6px; }

/* The opening owns the only visible garment until the exact handoff frame.
   Visibility preserves image measurements while avoiding any ghost image. */
/* Keep the real listing in the same layout box throughout the handoff.
   opening.js blends its opacity at the end; it must not change visibility
   when the fixed cloth layer disappears, on desktop or touch. */
body.opening-active #collection .tee-preview { visibility: visible; opacity: 0; }

/* Stable white shop surface, including exposed space during viewport changes. */
html, body, body.shop-ready .intro, body.shop-ready .viewport { background-color: #fff; }
/* Android Chrome can expose the document canvas underneath a fixed intro
   while its address/navigation controls change height. Keep that canvas
   dark for the duration of the opening; restore white at the handoff.
   The viewport, product slides and merchandise design stay white. */
html.opening-active,
html.opening-active body { background-color: var(--void); color-scheme: dark; }
body.shop-ready.desktop-rail .viewport { height: 100dvh; }
/* Keep the front opaque beneath the decoded back: no white dip mid-dissolve. */
body.shop-ready .tee-preview > img { pointer-events: none; }
body.shop-ready .tee-preview.show-back > .preview-front { opacity: 1; }
body.shop-ready .tee-preview > .preview-back { transition: opacity .24s ease; }
/* The message leads the reveal instead of sitting beside its lower edge. */
body.shop-ready .opening .hero-copy { top: 22%; width: 51%; transform: none; }
body.shop-ready .opening .hero-copy h1 { font-size: clamp(54px,6.4vw,108px); line-height: .96; letter-spacing: -.04em; color: #eee8db; }
body.shop-ready .opening .hero-copy h1 em { font-size: .55em; line-height: 1.15; color: #f68904; margin-top: .38em; }
@media(max-width:860px) {
  body.shop-ready .opening .hero-copy { top: max(44px,8svh); width: calc(100% - var(--gutter)*2); text-align: left; }
  body.shop-ready .opening .hero-copy h1 { font-size: clamp(44px,10.8vw,86px); }
}
/* A quiet donation section, revealed in normal document flow. */
.donate { position: relative; min-height: 0; padding: 28px 0 0; border: 0; background: #fff; color: #191919; }
.donate-shell { position: relative; padding: clamp(72px,9vw,140px) var(--gutter); background: #f6f6f4; clip-path: inset(0 var(--donate-inset,0%) round var(--donate-radius,0px)); }
.donate-inner { max-width: 1120px; margin: auto; grid-template-columns: minmax(0,1fr) minmax(0,420px); align-items: center; gap: clamp(48px,8vw,130px); padding: 0; transform: translateY(var(--donate-lift,0px)); }
.donate-lede .eyebrow { color: #777; font-size: 12px; letter-spacing: .08em; }
.donate-lede h2 { font-family: inherit; font-size: clamp(36px,4vw,60px); font-weight: 500; line-height: 1.08; letter-spacing: -.045em; word-spacing: normal; margin: 22px 0; color: #191919; }
.donate-body { font-size: 16px; line-height: 1.65; max-width: 32ch; color: #626262; margin: 0; }
.donate-cause { display: inline-flex; gap: 18px; align-items: center; margin-top: 28px; font-size: 14px; border-bottom: 1px solid #aaa; padding-bottom: 5px; }
.donate-cause:hover { border-color: #191919; }
.donate-card { background: transparent; padding: 0; border: 0; border-radius: 0; box-shadow: none; width: 100%; }
.donate-invitation { font-family: inherit; font-size: 22px; font-weight: 500; line-height: 1.3; letter-spacing: -.02em; margin: 0 0 28px; }
.donate-card legend, .donate-amount-label { font-size: 13px; letter-spacing: 0; text-transform: none; color: #626262; margin-bottom: 10px; }
.donate-presets { gap: 8px; }
.donate-presets span { height: 50px; border-radius: 7px; background: #fff; border-color: #d9d9d5; color: #191919; }
.donate-presets input:checked + span { background: #191919; border-color: #191919; color: #fff; }
.donate-field { border: 1px solid #d9d9d5; background: #fff; padding: 0 16px; border-radius: 7px; gap: 10px; }
.donate-field input { font-family: inherit; font-size: 24px; font-weight: 500; padding: 16px 0; }
.donate-field i { font-family: inherit; font-size: 20px; font-weight: 400; }
.donate-button { border-radius: 7px; background: #191919; padding: 19px 20px; font-size: 15px; letter-spacing: 0; text-transform: none; }
.donate-button:hover { background: #363636; color: #fff; }
.donate-note { color: #737373; max-width: none; font-size: 12px; line-height: 1.6; margin: 16px 0 0; }
@media(max-width:860px) {
  .donate { padding-top: 0; }
  .donate-shell { padding: 64px var(--gutter); }
  .donate-inner { grid-template-columns: minmax(0,1fr); gap: 42px; }
  .donate-lede h2 { font-size: clamp(36px,8vw,56px); }
  .donate-card { max-width: none; }
}
@media(prefers-reduced-motion:reduce) { .donate-shell { clip-path: none; } .donate-inner { transform: none; } }


/* === BSSF Accessories switch (isolated to header; no changes to product rail) === */
.section-switcher {
  display: inline-flex; align-items: center; gap: 2px; padding: 3px;
  border: 1px solid var(--rule); border-radius: 999px;
  white-space: nowrap; flex: 0 0 auto;
}
.section-switcher[hidden] { display: none !important; }
.section-switcher > a {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 31px; padding: 6px 12px; border-radius: 999px;
  font-size: 12px; font-weight: 500; letter-spacing: .01em;
  color: var(--fg); text-decoration: none;
  transition: background .18s ease, color .18s ease;
}
.section-switcher > a:hover:not([aria-current]) { background: color-mix(in srgb, var(--fg) 8%, transparent); }
.section-switcher > a[aria-current="page"] { background: var(--fg); color: var(--ground); }
.section-switcher > a:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
@media (max-width: 560px) {
  .header-nav { gap: 8px; }
  .section-switcher { padding: 2px; }
  .section-switcher > a { padding: 6px 9px; font-size: 11px; min-height: 30px; }
  .bag-button { gap: 5px; padding-left: 10px; }
  .brand { gap: 6px; }
  .brand span { font-size: 19px; }
  .brand img { height: 20px; }
}
@media (max-width: 365px) {
  .site-header { padding-left: 12px; padding-right: 12px; }
  .brand span { font-size: 16px; }
  .header-nav { gap: 5px; }
  .section-switcher > a { padding-left: 7px; padding-right: 7px; font-size: 10px; }
  .bag-button { font-size: 11px; padding-right: 5px; padding-left: 7px; }
}
@media (prefers-reduced-motion: reduce) {
  .section-switcher > a { transition: none; }
}

/* Keep the opening-to-first-card scroll position stable if image decoding or
   dynamic browser chrome changes the measured layout during the handoff. */
.opening, #top { overflow-anchor: none; }

/* v4 animation performance: composite the landing and first listing together.
   Their opacity is controlled by opening.js; avoid layout-triggering changes. */
.opening-screen .landing-shirt { will-change: opacity; transform: translateZ(0); }
body.shop-ready #collection .tee-preview { will-change: opacity; }

/* V5 animation performance: keep the intro's compositing layer stable at the
   handoff; fade a pre-rendered studio backdrop rather than repainting every
   pixel of the viewport background on every scroll event. */
.opening-screen { background-color: var(--void); isolation: isolate; will-change: opacity; }
.opening-screen::after {
  content: ''; position: absolute; inset: 0; z-index: 0;
  background: #fff; opacity: var(--light-reveal,0);
  pointer-events: none; will-change: opacity;
}
/* Mobile uses the same flying stage and original artwork, but without a
   heavyweight 3D canvas/context. Preserve depth transforms & handoff fade. */
.opening-lite .cloth-ready .fabric,
.opening-lite .fabric { display: none !important; }
.opening-lite .cloth-ready .hero-shirt,
.opening-lite .hero-shirt { display: block !important; }

/* ========================================================================
   V10 — TEST ACCESSORIES IMAGE COMPOSITION ONLY
   A shared centred frame gives every Inkedjoy item the same baseline, on
   desktop, tablet and mobile. Per-product artwork sizing reflects the
   different shapes of the products, rather than making a pencil pouch as
   large as a lunch bag. No merchandise or intro selector is affected.
   ======================================================================== */
body.shop-ready .product-slide.accessory-slide .product-viewer,
body.shop-ready .product-slide.accessory-slide .product-preview {
  display: grid;
  place-items: center;
}
body.shop-ready .product-slide.accessory-slide .product-preview > img {
  object-fit: contain;
  object-position: center center;
  transform-origin: 50% 50%;
  transform: scale(var(--accessory-image-scale, .86));
}
body.shop-ready .product-slide.accessory-slide[data-accessory-visual="cap"] {
  --accessory-image-scale: .86;
}
body.shop-ready .product-slide.accessory-slide[data-accessory-visual="mug"] {
  --accessory-image-scale: .85;
}
body.shop-ready .product-slide.accessory-slide[data-accessory-visual="pencil-pouch"] {
  --accessory-image-scale: .73;
}
body.shop-ready .product-slide.accessory-slide[data-accessory-visual="tote"] {
  --accessory-image-scale: .96;
}
body.shop-ready .product-slide.accessory-slide[data-accessory-visual="lunch-bag"] {
  --accessory-image-scale: 1.04;
}
