/* stylelint-disable value-keyword-case -- Preserve currentColor and theme font token casing. */
/* ============================================================================
   CINEMATIC UI LIBRARY — the one stylesheet.
   Everything here obeys the merchant THEME tokens (set by theme.js):
     --accent --accent2 --dk --lt --font-d --font-b
   and the per-slide TONE tokens (set on #stage by stage.js):
     --ink --muted --pool --tsh
   The prompt box speaks the engine token names; they are bridged below so the
   component re-skins from the same theme.

   Motion rules (VISUAL_SPEC):
   - interactive moves ride --ease (plateau-then-plunge)
   - AMBIENT crossfades never use --ease — they ride --xfade (soft, near-linear)
   - staggered ENTRANCES fade only (no drift, no blur); blur+drift is reserved
     for storytelling moments (slide leave, dive, blackout)
   - one contrast device: the .pool backdrop. Groups share ONE pool.
   ========================================================================== */

html, body { height: 100%; margin: 0; overflow: hidden; background: var(--dk, #0b0f0c); }
* { box-sizing: border-box; } button { font: inherit; }

:root {
  color-scheme: dark;
  /* ---- theme tokens (rewritten by theme.js per store) ---- */
  --accent: #d8a866; --accent2: #6f7c84; --dk: #0f120e; --lt: #f2ede3;
  /* clearly-light / clearly-dark cuts of the main colors, for grounds the
     base can't hold on (theme.js derives them; stores may hand-tune) */
  --accent-lift: var(--accent); --accent-deep: var(--accent);
  /* the accent when it paints as INK on a ground (not as a filled chip, which
     carries its own --accent-ink). Retoned per measured region — see .c-light
     / .c-dark below. Default stage ground is --dk, so: the light cut. */
  --accent-on: var(--accent-lift);
  --accent2-lift: var(--accent2); --accent2-deep: var(--accent2);
  /* the label ink that reads ON the accent (CTA, quick-add, badges) —
     theme.js picks whichever brand pole (lt / dk) carries more contrast */
  --accent-ink: var(--dk);
  /* the send arrow's ink — judged against the ORB's white-cored gradient
     (≈ accent/white mix), not the raw accent (theme.js sendInk) */
  --send-ink: var(--dk);
  --font-d: Georgia, "Times New Roman", serif;
  --font-b: -apple-system, system-ui, "Segoe UI", Roboto, sans-serif;
  /* ---- system tokens ---- */
  --ease: cubic-bezier(0.76, 0, 0.24, 1);           /* interactive */
  --xfade: cubic-bezier(0.4, 0.12, 0.35, 0.9);      /* ambient crossfades */
  --slow: 1300ms; --mid: 860ms;
  --pad: clamp(32px, 6.5vw, 112px);
  /* the chrome's top-right anchor. The 46px control circles own it; anything
     that must stand exactly ON them (the lightbox ✕) rides these tokens rather
     than solving a second clamp that drifts apart at every other width */
  --chrome-top: clamp(10px, calc(1.9vw - 4px), 21px); --chrome-right: clamp(24px, 3.4vw, 48px);
  /* ---- bridge tokens: the prompt box + chrome speak these names ---- */
  --sans: var(--font-b); --serif: var(--font-d); --mist: var(--lt); --ground: var(--dk);
  --amber: var(--accent); --muted: color-mix(in srgb, var(--lt) 62%, transparent);
  --hi: inset 0 1px 0 rgba(255,255,255,.08);
  --fade-rgb: 5, 8, 6;                              /* the brand's blackout colour */
}

/* tone: which ink rides the slide. Dark scene → light ink, pools of --dk.
   Light scene → dark ink, pools of --lt. Set per slide, or measured from the
   backdrop media (tone:"auto").

   TRANSITION MODEL: the tone tokens are REGISTERED properties, so the stage
   value animates smoothly — everything persistent (chrome, menu, vignette,
   pools in the chrome) fades between modes instead of popping. Slides are
   PINNED: every slide carries its own .tone-dark/.tone-light class, so a
   leaving slide keeps the colors it was born with while it fades out, and an
   entering slide arrives already in the new mode. Only what lives BETWEEN
   slides rides the animated stage value. */
@property --ink { syntax: "<color>"; inherits: true; initial-value: #f2ede3; }
@property --pool { syntax: "<color>"; inherits: true; initial-value: #0f120e; }
@property --muted { syntax: "<color>"; inherits: true; initial-value: #999; }
#stage, .tone-dark { --ink: var(--lt); --muted: color-mix(in srgb, var(--lt) 62%, transparent);
  --pool: var(--dk); }
/* the stage's tone tokens transition on the SAME clock as the background
   crossfade (var(--slow) var(--ease)) — the chrome changes mode exactly as
   fast as the scene behind it does */
/* overflow: clip (after the hidden fallback) is load-bearing, not cosmetic: a
   scroll container that nothing ever resets will keep any programmatic scroll
   forever, and children paint past the viewport by design (tracked eyebrows,
   rail tracks). One focus() without preventScroll used to shift the WHOLE
   frame, chrome half off-screen. clip makes the box unscrollable instead. */
#stage { --tsh: 0 1px 22px rgba(0,0,0,.55);
  position: fixed; inset: 0; overflow: hidden; overflow: clip; background: var(--dk);
  color: var(--ink); font-family: var(--font-b);
  transition: background var(--slow) var(--ease), --ink var(--slow) var(--ease),
              --muted var(--slow) var(--ease), --pool var(--slow) var(--ease); }
#stage.no-scrim { --tsh: 0 1px 10px rgba(0,0,0,.2); }
#stage.tone-light, .tone-light { --ink: color-mix(in srgb, var(--dk) 92%, #000); --muted: color-mix(in srgb, var(--dk) 58%, transparent);
  --pool: var(--lt); }
/* zero-alpha rather than `none`, so text-shadow can interpolate between modes */
#stage.tone-light { --tsh: 0 1px 22px rgba(0,0,0,0); background: var(--lt); }
/* pinned slides resolve their tone locally and NEVER animate it */
.slide.tone-dark, .slide.tone-light { transition-property: opacity, transform, filter; }
/* GOTCHA — never give a chrome element its own short transition on a property
   derived from an animating token: the transition restarts toward a moving
   target every frame and, on the plateau-start --ease curve, visibly STALLS
   until the token animation ends (reads as a long delay, then a jump).
   Token-driven color/background must have NO own transition — they track the
   animating token exactly. Discrete per-element changes (hover lifts, the
   active underline, text-shadow class flips) may keep their own transitions. */

/* ---------------------------------------------------------------- backgrounds */
.bgwrap { position: absolute; inset: -7%; will-change: transform; transform: translate3d(0,0,0);
  backface-visibility: hidden; contain: paint; }
/* per-layer zoom shell: a camera-push lives here, so an outgoing image keeps
   its zoom and just fades — it never zooms back out mid-dissolve */
.bgz { position: absolute; inset: 0; will-change: transform; transform: translate3d(0,0,0); backface-visibility: hidden; }
.bg { position: absolute; inset: 0; background-size: cover; background-position: center; opacity: 0;
  will-change: opacity, transform; transform: scale(1.04); transition: opacity var(--slow) var(--ease);
  animation: kenburns 30s linear infinite alternate; }
.bg.on { opacity: 1; }
@keyframes kenburns { from { transform: scale(1.04) translate3d(0,0,0); } to { transform: scale(1.12) translate3d(1.2%,-1%,0); } }

/* ambient 3-layer abstract field — the no-photo backdrop (store palette only) */
.ambient { position: absolute; inset: -8%; opacity: 0; transition: opacity var(--slow) var(--ease);
  transform: translate3d(0,0,0); backface-visibility: hidden; contain: paint; }
#stage.amb-on .ambient { opacity: 1; }
.al { position: absolute; inset: -10%; display: block; will-change: transform; backface-visibility: hidden; }
.al i { position: absolute; inset: -6%; display: block; }
/* two layers only — a third, sharper "near" layer shimmered against 3D
   canvases and gained nothing over the mid layer.
   GOTCHA: the drift animates the FILTERED element itself, never a child
   inside it — a transform animating under a filter re-rasterizes the blur
   every frame, which reads as background flicker at some viewport sizes
   (worst beside 3D canvases). On the filtered element the blurred texture is
   cached once and the GPU just moves it. */
.al.far  { filter: blur(56px); opacity: .55; animation: adrift-a 34s linear infinite alternate; }
.al.mid  { filter: blur(34px); opacity: .5;  animation: adrift-b 27s linear infinite alternate; }
.al.far  i {
  background: radial-gradient(42% 52% at 22% 28%, color-mix(in srgb, var(--accent2) 75%, transparent), transparent 70%),
              radial-gradient(48% 56% at 82% 72%, color-mix(in srgb, var(--accent) 60%, transparent), transparent 72%); }
.al.mid  i {
  background: radial-gradient(32% 40% at 72% 26%, color-mix(in srgb, var(--accent) 80%, transparent), transparent 70%),
              radial-gradient(30% 40% at 26% 78%, color-mix(in srgb, var(--accent2) 65%, transparent), transparent 70%); }
#stage.tone-light .al { opacity: .28; } #stage.tone-light .al.far { opacity: .34; }
@keyframes adrift-a { from { transform: translate3d(0,0,0) scale(1.04); } to { transform: translate3d(2.4%,-2%,0) scale(1.09); } }
@keyframes adrift-b { from { transform: translate3d(0,0,0) scale(1.07); } to { transform: translate3d(-2.6%,1.8%,0) scale(1.02); } }
/* the backdrop holds still where there is no cursor to answer. Dropping the
   pointer parallax (stage.ts) left these two running, because neither is a rAF
   and neither reads as "parallax" in the source — but they are what a visitor
   on a phone actually SEES moving, and they are where an older GPU actually
   spends the frame: kenburns re-scales a full-viewport PHOTOGRAPH 60 times a
   second, and adrift moves a layer sitting under a 34-56px blur. The camera
   push stays (it lives on .bgz): that is a deliberate move once per scene, not
   a permanent one, and it ends.

   `hover: none` rather than a width query on purpose — a landscape phone is
   812px wide, so every max-width breakpoint in this file would miss it.

   .bg falls back to the scale(1.04) its own rule declares, which is the
   drift's first frame, so the framing is the one the composition was drawn
   for. With nothing left to animate, both layers also hand back the
   compositing layer will-change was holding open for them. */
@media (hover: none) {
  .bg { animation: none; will-change: opacity; }
  .al.far, .al.mid { animation: none; will-change: auto; }
}

/* veil — a screen-side gradient treatment of the photo. TWO layers crossfade,
   so a veil leaves by fading, never by popping. */
.veil { position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity 1.1s var(--xfade); }
.veil.on { opacity: 1; }
.veil.v-left  { background: linear-gradient(to right, var(--dk) 18%, color-mix(in srgb, var(--dk) 72%, transparent) 46%, transparent 72%); }
.veil.v-right { background: linear-gradient(to left,  var(--dk) 18%, color-mix(in srgb, var(--dk) 72%, transparent) 46%, transparent 72%); }
/* portrait / narrow: side veils become a bottom veil — copy moves under the image */
@media (max-aspect-ratio: 9/11), (max-width: 700px) {
  .veil.v-left, .veil.v-right { background: linear-gradient(to top, var(--dk) 22%, color-mix(in srgb, var(--dk) 70%, transparent) 52%, transparent 80%); }
}

.scrim { position: absolute; inset: 0; pointer-events: none; opacity: 1; transition: opacity 1s var(--ease);
  background: linear-gradient(to bottom, rgba(4,7,5,.5), rgba(4,7,5,.1) 26%, rgba(4,7,5,.1) 56%, rgba(4,7,5,.66)); }
#stage.tone-light .scrim, #stage.no-scrim .scrim { opacity: 0; }
/* the copy region measured fine on the luma grid → the scrim relaxes to a
   whisper (chrome legibility) instead of the full cinematic gradient */
#stage.scrim-lite .scrim { opacity: .42; }
.vignette { position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(130% 100% at 50% 42%, transparent 44%, color-mix(in srgb, var(--pool) 55%, transparent) 100%); }
.grain { position: absolute; inset: 0; pointer-events: none; opacity: .04; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>"); }

/* ------------------------------------------------------------------- slides */
#slidehost { position: absolute; inset: 0; z-index: 6;
  -webkit-mask: linear-gradient(to bottom, transparent 0, #000 8vh, #000 calc(100% - 8vh), transparent 100%);
          mask: linear-gradient(to bottom, transparent 0, #000 8vh, #000 calc(100% - 8vh), transparent 100%);
  transition: transform 1s var(--ease), filter 1s var(--ease), opacity 1s var(--ease); }
#stage.pushed #slidehost { transform: scale(1.06); filter: blur(10px); opacity: .3; pointer-events: none; }
#stage.pushed .bgwrap, #stage.pushed .ambient { filter: blur(7px); }
.bgwrap, .ambient { transition: filter var(--slow) var(--ease), opacity var(--slow) var(--ease); }

.slide { position: absolute; inset: 0; display: grid; place-items: center; padding: 12vh var(--pad);
  transition: opacity .46s var(--ease), transform .46s var(--ease), filter .46s var(--ease); }
.slide.leaving { opacity: 0; filter: blur(6px); transform: translateY(-12px); pointer-events: none; }
#stage.navback .slide.leaving { transform: translateY(12px); }
.slide.fullb { padding: 0; }
/* narrow: 12vh top and bottom is the wide-screen rhythm, and on a phone the
   bottom one is not enough to clear the fixed pill plus its murmur line, so
   CTAs ended up behind the prompt. svh rather than vh because Safari's toolbars
   are the whole point: the visitor's real viewport is what has to fit. */
@media (max-width: 700px) {
  .slide { padding: clamp(72px, 11svh, 97px) var(--pad) clamp(112px, 17svh, 140px); }
}
/* short and wide (landscape phones): 12vh top and bottom is only ~45px, and the
   pill needs ~80 of them. Trade the generous top gutter for the room the pill
   actually occupies, so nothing has to sit underneath it. */
@media (max-height: 500px) and (min-aspect-ratio: 1/1) {
  .slide { padding: 56px var(--pad) 84px; }
}
/* scrollable slide variant: content taller than the viewport scrolls in place */
/* the slide scrolls VERTICALLY only: a scroll container's `visible` on the
   cross axis computes to `auto`, so without this the pool's decorative bleed
   (.pool.wide::before reaches 280px past the cluster on each side) becomes a
   real horizontal scroll on narrow viewports. Clipping it costs nothing —
   the bleed is a gradient tail that melts to transparent. (`clip` would be the
   stronger word, but a box can't clip one axis while scrolling the other — the
   UA downgrades it to `hidden` here anyway.) */
.slide.scrolly { display: flex; flex-direction: column; overflow-y: auto; overflow-x: hidden; scrollbar-width: none; overscroll-behavior: contain; }
.slide.scrolly > .inner { margin: auto; flex: 0 0 auto; }
.slide.scrolly::-webkit-scrollbar { display: none; }
/* staggered entrance — PATTERN: entrances FADE only, no drift, no blur */
.stg { transition: opacity var(--mid) var(--ease), transform var(--mid) var(--ease), filter var(--mid) var(--ease);
  transition-delay: calc(var(--i, 0) * 80ms); }
.slide.entering .stg { opacity: 0; }

.inner { max-width: 1160px; width: 100%; }
.l-center { text-align: center; display: grid; justify-items: center; }
.l-left { display: grid; justify-items: start; text-align: left; }
.l-right { display: grid; justify-items: end; text-align: right; }
.l-split { display: grid; gap: clamp(28px, 5vw, 80px); align-items: center; text-align: left; grid-template-columns: 1fr; }
@media (min-width: 920px) { .l-split { grid-template-columns: 1.02fr .98fr; } }

.headline { font-family: var(--font-d); font-weight: 400; line-height: 1.05; letter-spacing: -.012em; text-wrap: balance; margin: 0;
  font-size: clamp(32px, 5.4vw, 66px); text-shadow: var(--tsh); }
.headline.sm { font-size: clamp(24px, 3.4vw, 42px); }
.headline .w { display: inline-block; }
.sub { color: var(--ink); opacity: .9; font-size: clamp(15px, 1.5vw, 19px); line-height: 1.55; max-width: 40ch; margin: 22px 0 0; text-shadow: var(--tsh); }
.l-center .sub { margin-inline: auto; }
.eyebrow { font-size: 10.5px; letter-spacing: .4em; text-transform: uppercase; color: var(--accent); font-weight: 600; margin: 0 0 16px; text-shadow: var(--tsh); }
/* the accent failed the measured region (stage.js, luma grid vs --accent) —
   the eyebrow swaps to the store's clearly-light or clearly-dark cut of the
   same hue, keeping the brand voice instead of abandoning it for plain ink */
.slide.acc-lift .eyebrow, .slide.acc-lift .price, .slide.acc-lift .stat b, .slide.acc-lift .stat-detail .sd-label, .slide.acc-lift .still-more { color: var(--accent-lift); }
.slide.acc-deep .eyebrow, .slide.acc-deep .price, .slide.acc-deep .stat b, .slide.acc-deep .stat-detail .sd-label, .slide.acc-deep .still-more { color: var(--accent-deep); }
.slide.acc-lift .stat[data-stat]::after { background: var(--accent-lift); }
.slide.acc-deep .stat[data-stat]::after { background: var(--accent-deep); }
.price { font-family: var(--font-d); font-size: clamp(20px, 2.2vw, 28px); color: var(--accent); text-shadow: var(--tsh); }
.price.big { font-size: clamp(30px, 3.6vw, 48px); }
.cta { appearance: none; cursor: pointer; font-family: var(--font-b); font-weight: 600; font-size: 15px; color: var(--accent-ink); background: var(--accent); border: 0;
  padding: 15px 26px; border-radius: 4px; margin-top: 28px; box-shadow: 0 16px 40px -8px color-mix(in srgb, var(--accent) 50%, transparent);
  transition: transform .22s var(--ease); }
.cta:hover { transform: translateY(-3px); }
.ghost { appearance: none; cursor: pointer; font-family: var(--font-b); font-weight: 600; font-size: 12px; letter-spacing: .22em; text-transform: uppercase; color: var(--ink);
  background: transparent; border: 0; padding: 12px 0; margin-top: 20px; display: inline-flex; align-items: center; gap: 10px; opacity: .92;
  text-shadow: var(--tsh); transition: gap .26s var(--ease); }
.ghost:hover { gap: 16px; } .ghost .arr { color: var(--accent); }
/* narrow: a tracked door label is often wider than the phone. Left alone the
   inline-flex row refuses to break and pushes its arrow off the right edge,
   where it clips to a sliver. Wrapping keeps the arrow with the last line. */
@media (max-width: 700px) {
  .eyebrow { letter-spacing: .26em; max-width: 100%; }
  .ghost { max-width: 100%; flex-wrap: wrap; justify-content: center; row-gap: 2px; }
}
/* ghosts are never .stg themselves — their own gap transition would fight the
   entrance one. Stagger them by wrapping in a plain <div class="stg"> instead. */

/* the BACKDROP POOL — the one contrast device: a radial gradient melting to
   transparent, colored by the slide's tone.
   PATTERN: when a slide carries a cluster of related text, ONE pool spans the
   whole group (`pool wide` on the container) — never a sole strong backdrop
   behind a single line inside the group. Standalone captions keep their own. */
.pool { position: relative; z-index: 0; }
/* purely decorative, and its bleed runs generously past the cluster (see
   .pool.wide) — so it must never take a click. Without this the bleed reaches
   over whatever sits beside the words and swallows their pointer events (the
   still layout seats the thumb rail directly under the copy column). */
.pool::before { content: ""; position: absolute; z-index: -1; pointer-events: none;
  inset: calc(var(--py, 84px) * -1) calc(var(--px2, 168px) * -1);
  background: radial-gradient(52% 58% at 50% 50%, color-mix(in srgb, var(--pool) 82%, transparent),
              color-mix(in srgb, var(--pool) 46%, transparent) 48%, transparent 74%);
  transition: opacity .8s var(--ease); }
.pool.soft::before { opacity: .7; }
/* the bleed runs generous (double the old box) — the radial melts to nothing
   at its edges, so words at the cluster's rim need the box to extend well
   past them or they sit on the gradient's thin tail. Caption pools (fcap,
   rail-focus) keep their own tight --py. */
.pool.wide { --py: 144px; --px2: 280px; }
/* measured support tiers — stage.js reads the copy region off the backdrop's
   luma grid and grades the pool instead of the blanket .7: a region that
   already carries the words gets NO pool, a treacherous one gets more.
   (No sup-* class — no grid — keeps the legacy default above.) */
/* the tiers are ASYMMETRIC by ink, matching perception: white glyphs pull
   away from a dark ground on their own, so a dark pool only needs a whisper
   — pushed hard it reads as a stamped-on blob. A light pool over a light-mid
   backdrop earns its keep by FLATTENING texture behind dark glyphs, and that
   takes real presence. */
.slide.sup-0 .pool::before { opacity: 0; }
.slide.sup-1 .pool::before { opacity: .3; }
.slide.sup-2 .pool::before { opacity: .5; }
.slide.sup-3 .pool::before { opacity: .68; }
.slide.tone-light.sup-1 .pool::before { opacity: .55; }
.slide.tone-light.sup-2 .pool::before { opacity: .8; }
.slide.tone-light.sup-3 .pool::before { opacity: .95; }
/* the pool joins the entrance: it fades in WITH its words instead of popping
   onto the scene at mount, ahead of the content it backs */
.slide.entering .pool::before { opacity: 0; }
/* on composed layouts the pool rides a content-hugging wrapper, so its
   radial centre sits over the words, not over the middle of the slide */
.poolhug { max-width: 100%; }
.l-left .poolhug, .l-right .poolhug, .l-center .poolhug, .l-ctxfull .poolhug,
.ctx-copy .poolhug, .cmp-copy .poolhug, .still-copy .poolhug { display: grid; justify-items: inherit; }

/* masked floating shot — photography with no card edge */
.shotm { background-size: cover; background-position: center; border-radius: 16px;
  -webkit-mask: radial-gradient(140% 122% at 50% 44%, #000 56%, transparent 100%);
          mask: radial-gradient(140% 122% at 50% 44%, #000 56%, transparent 100%); }

/* hover caption — quiet until asked for */
.fcap { position: absolute; left: 50%; top: calc(100% + 2px); transform: translateX(-50%); white-space: nowrap; text-align: center; font-size: 12.5px;
  color: var(--ink); opacity: 0; transition: opacity .5s var(--ease); pointer-events: none; --py: 22px; --px2: 56px; }
.fcap em { display: block; font-style: normal; font-size: 9.5px; letter-spacing: .18em; text-transform: uppercase; color: var(--accent); margin-top: 3px; }

/* the cursor chip — the pointer-following context label (hoverchip.js).
   Rides the cursor over clickable tiles and says what a click will do; the
   same pill language as the hotspot / marker labels. translate is JS-driven. */
.cursor-chip { position: fixed; left: 0; top: 0; z-index: 58; pointer-events: none; max-width: min(280px, 70vw);
  padding: 10px 15px; border-radius: 13px; font-family: var(--font-b); color: var(--lt);
  background: color-mix(in srgb, var(--dk) 66%, transparent); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  box-shadow: 0 14px 40px -10px rgba(4,7,5,.7), inset 0 0 0 1px color-mix(in srgb, var(--accent) 32%, transparent);
  opacity: 0; scale: .9; transition: opacity .25s var(--ease), scale .3s var(--ease); }
.cursor-chip.on { opacity: 1; scale: 1; }
.cursor-chip b { display: block; font-size: 12.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cursor-chip em { display: block; font-style: normal; font-family: var(--font-d); font-size: 13.5px; color: var(--accent); margin-top: 1px; }
/* block-level flex, not inline-flex: an inline-level hint sits on the chip's
   16px text baseline, whose descender space pushed a hint-only chip's line off
   the vertical centre — block-level, the chip is exactly padding + hint */
.cursor-chip .cchip-hint { display: flex; align-items: center; gap: 7px; white-space: nowrap;
  font-size: 9.5px; font-weight: 700; letter-spacing: .2em; text-transform: uppercase; line-height: 1.2;
  color: color-mix(in srgb, var(--lt) 78%, transparent); }
.cursor-chip b ~ .cchip-hint, .cursor-chip em ~ .cchip-hint { margin-top: 7px; }
.cursor-chip .cchip-hint .arr { color: var(--accent); font-size: 12px; letter-spacing: 0; }

/* ============================ ATOM-SPECIFIC CSS ============================ */

/* statStrip — gestalt-grouped figures, no boxes; optionally expandable */
/* hierarchy (2026-08-20 owner feedback, round 2): the TITLE leads and the
   figures sit clearly one level below it — same-size title and figures read
   as a mess, and oversized figures orphaned the wrapped one. The headline
   keeps its full display size; the figures are the smaller voice, with real
   air above and between them. The .mini strip (recomposer) keeps its own. */
/* per-axis gap: side-by-side figures want air, but a wrapped second row
   already carries the first row's labels between them — a tight row gap
   keeps the strip reading as ONE group instead of two strata */
.statstrip { display: flex; flex-wrap: wrap; gap: clamp(14px, 2.5vh, 24px) clamp(24px, 4vw, 60px); justify-content: center; margin-top: clamp(36px, 6vh, 60px); }
.l-right .statstrip { justify-content: flex-end; } .l-left .statstrip { justify-content: flex-start; }
.stat { display: grid; justify-items: center; gap: 10px; position: relative; border: 0; background: none; padding: 0; color: inherit; font-family: inherit; text-align: center; }
/* the hairline sits at half the strip's gap — the two clamps move together */
.stat + .stat::before { content: ""; position: absolute; left: calc(-1 * clamp(12px, 2vw, 30px)); top: 12%; bottom: 30%; width: 1px;
  background: linear-gradient(to bottom, transparent, color-mix(in srgb, var(--ink) 25%, transparent), transparent); }
/* a figure that WRAPPED to a new line has no neighbor at its left — the atom
   marks it (offsetTop vs the previous figure) and the hairline goes */
.stat.rowstart::before { content: none; }
.stat b { font-family: var(--font-d); font-weight: 400; font-size: clamp(21px, 2.4vw, 32px); line-height: 1; color: var(--accent); text-shadow: var(--tsh); }
.stat span { font-size: 10.5px; letter-spacing: .2em; text-transform: uppercase; color: var(--muted); transition: color .3s var(--ease); }
.statstrip.mini { gap: clamp(14px, 2vw, 30px); margin-top: 26px; }
.statstrip.mini .stat b { font-size: clamp(20px, 2.4vw, 30px); }
/* expandable variant: figures are buttons that reveal their own read-more copy */
.stat[data-stat] { cursor: pointer; transition: transform .3s var(--ease); }
.stat[data-stat]::after { content: ""; height: 2px; width: 0; margin-top: 4px; border-radius: 2px; background: var(--accent); opacity: .9; transition: width .35s var(--ease); }
.stat[data-stat]:hover { transform: translateY(-2px); }
.stat[data-stat]:hover::after { width: 22px; }
.stat[data-stat]:hover span { color: var(--ink); }
.stat[aria-expanded="true"]::after { width: 42px; }
.stat[aria-expanded="true"] span { color: var(--ink); }
.stat[aria-expanded="true"] b { filter: drop-shadow(0 0 18px color-mix(in srgb, var(--accent) 55%, transparent)); }
/* the invitation IS the figures (2026-08-20; the "Tap a figure" caption is
   gone): until the shopper has ever tapped one, each tappable value catches
   the light in turn — the WHOLE word brightens briefly with a soft glow,
   staggered by --si so the light travels across the strip. Plain color
   animation: the resting frame is exactly var(--accent), so glinting figures
   never drift from the non-interactive ones, and nothing clips descenders
   (the earlier background-clip:text sheen cut letters off at line-height 1).
   The atom drops .glint forever after the first tap (localStorage). */
.stat.glint b {
  /* brightness only — the hue IS var(--accent) at every frame, identical to
     the non-interactive figures; it just catches the light now and then */
  animation: stat-glint 11s ease-in-out infinite;
  /* the pulse (~1.5s wide) is three times the stagger step, so neighbours
     brighten while the previous figure is still lit — one travelling wave,
     not four separate blinks */
  animation-delay: calc(var(--si, 0) * .45s); }
@keyframes stat-glint {
  0%, 2%, 16%, 100% { filter: brightness(1); }
  9% { filter: brightness(1.55); }
}
@media (prefers-reduced-motion: reduce) { .stat.glint b { animation: none; } }
.stat-detail { max-width: 540px; margin: 0 auto; max-height: 0; opacity: 0; overflow: hidden;
  transition: max-height .55s var(--ease), opacity .45s var(--ease), margin .45s var(--ease); }
.stat-detail.show { max-height: 320px; opacity: 1; margin-top: 18px; }
.stat-detail.swap { animation: sd-in .5s var(--ease); }
@keyframes sd-in { from { opacity: 0; transform: translateY(8px); filter: blur(4px); } to { opacity: 1; transform: none; filter: none; } }
.stat-detail .sd-label { font-size: 10.5px; letter-spacing: .2em; text-transform: uppercase; color: var(--accent); margin: 0 0 8px; }
.stat-detail .sd-body { color: var(--ink); opacity: .9; font-size: clamp(14px, 1.4vw, 17px); line-height: 1.6; margin: 0; text-shadow: var(--tsh); }

/* featureGrid — titled feature cards with a quiet accent spine */
.featgrid { display: grid; grid-template-columns: 1fr; gap: clamp(12px, 1.6vw, 20px); width: min(100%, 760px); margin: 36px auto 0; text-align: left; }
@media (min-width: 720px) { .featgrid { grid-template-columns: 1fr 1fr; } }
.feat { padding: 20px 22px; border-radius: 14px; background: color-mix(in srgb, var(--lt) 4.5%, transparent); box-shadow: var(--hi); position: relative; overflow: hidden; }
button.feat { appearance: none; border: 0; color: inherit; font: inherit; text-align: left; width: 100%; }
.feat::before { content: ""; position: absolute; left: 0; top: 16px; bottom: 16px; width: 3px; border-radius: 3px; background: var(--accent); opacity: .8; }
.feat b { display: block; font-weight: 600; font-size: 15.5px; margin: 0 0 6px; padding-left: 8px; }
.feat p { margin: 0; padding-left: 8px; color: var(--muted); font-size: 13.5px; line-height: 1.5; }
/* door variant: the shot fills the card's whole right edge and TAPERS out
   behind the words (a mask, not an overlay — the card ground shows through),
   then FLIPs into the lightbox on click */
.feat.door { cursor: pointer; padding-right: clamp(110px, 34%, 170px);
  transition: transform .3s var(--ease), background .3s var(--ease); }
.feat.door:hover { transform: translateY(-2px); background: color-mix(in srgb, var(--lt) 8%, transparent); }
.feat.door:hover p { color: var(--ink); }
.feat.door .ftxt { position: relative; z-index: 1; }
.feat.door .ftxt b, .feat.door .ftxt p { text-shadow: var(--tsh); }
.fthumb { position: absolute; top: 0; right: 0; bottom: 0; width: 62%;
  background-size: cover; background-position: center;
  -webkit-mask-image: linear-gradient(to right, transparent 12%, #000 94%);
  mask-image: linear-gradient(to right, transparent 12%, #000 94%);
  transition: transform .45s var(--ease); }
.feat.door:hover .fthumb { transform: scale(1.05); }

/* hero media — the product hero: the copy cluster beside a 2×2 grid of shots.
   The shots keep a proper card edge (no radial mask) with a corner scrim, so
   the grid reads as a contact sheet the copy is selling from. */
.l-hero { display: grid; gap: clamp(28px, 5vw, 72px); align-items: center; text-align: left; grid-template-columns: 1fr; }
@media (min-width: 920px) { .l-hero { grid-template-columns: .92fr 1.08fr; } }
.l-hero .hero-copy { display: grid; justify-items: start; align-content: center; }
.hero-grid { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(10px, 1.3vw, 16px); width: 100%; }
.hero-grid .hshot { position: relative; height: clamp(130px, 16vw, 196px); cursor: zoom-in;
  transition: transform .45s var(--ease), filter .45s var(--ease); }
.hero-grid .hshot .shotm { position: absolute; inset: 0; -webkit-mask: none; mask: none; border-radius: 14px;
  box-shadow: 0 18px 40px rgba(4,7,5,.35); }
.hero-grid .hshot::after { content: ""; position: absolute; inset: 0; border-radius: 14px; pointer-events: none;
  background: linear-gradient(to top, color-mix(in srgb, var(--dk) 42%, transparent), transparent 48%); }
.hero-grid .hshot:hover { transform: translateY(-5px); filter: drop-shadow(0 24px 44px rgba(4,7,5,.5)); }

/* context section — one image + its copy, laid left / right / full-bleed */
.l-ctx { display: grid; gap: clamp(28px, 5vw, 72px); align-items: center; text-align: left; grid-template-columns: 1fr; }
@media (min-width: 920px) { .l-ctx { grid-template-columns: 1fr 1fr; } }
.l-ctx .ctx-media { display: grid; place-items: center; }
.l-ctx .ctx-copy { display: grid; justify-items: start; align-content: center; }
.l-ctx .ctx-copy .headline { font-size: clamp(28px, 3.6vw, 46px); }
.l-ctx .ctx-copy .sub { max-width: 42ch; }
@media (min-width: 920px) { .l-ctx.ctx-right .ctx-media { order: 2; } .l-ctx.ctx-right .ctx-copy { order: 1; } }
.ctx-shot { position: relative; width: min(100%, 460px); height: clamp(300px, 46vh, 520px); cursor: zoom-in; }
.ctx-shot .shotm { position: absolute; inset: 0; border-radius: 20px; }
.l-ctxfull { display: grid; justify-items: start; align-content: end; text-align: left; min-height: 64vh; }
.l-ctxfull .headline { max-width: 18ch; }
.l-ctxfull .sub { max-width: 46ch; }

/* storyLinks — "dive into something new": quiet growing-gap links.
   In any left-aligned copy cluster they sit on the text's left edge — the
   auto margins would otherwise centre the row inside its grid column. */
/* the gap is per-axis: side by side the links want air, but each one already
   carries 8px of block padding, so the SAME value stacked (which is what a
   narrow hero does with three "Next: …" links) reads as three orphaned rows
   rather than one list. The row gap is the seam between them, not a margin. */
.story-links { display: flex; flex-wrap: wrap; column-gap: clamp(18px, 3vw, 40px); row-gap: 2px; justify-content: center; margin: 30px auto 0; }
.l-left .story-links, .l-right .story-links, .still-copy .story-links, .poolhug .story-links {
  justify-content: flex-start; margin-inline: 0; }
.l-right .story-links { justify-content: flex-end; }
.story-link { appearance: none; cursor: pointer; display: inline-flex; align-items: center; gap: 10px; background: none; border: 0; padding: 8px 0;
  color: var(--ink); font-family: var(--font-b); font-size: 13px; font-weight: 600; letter-spacing: .04em; opacity: .92;
  text-shadow: var(--tsh); transition: gap .26s var(--ease), opacity .26s var(--ease); }
.story-link:hover { gap: 16px; opacity: 1; } .story-link .arr { color: var(--accent); }

/* storyCards — small product-card links embeddable in any copy cluster:
   thumb + name + price, each card a door into its piece. Same alignment
   rules as .story-links so the strip follows the cluster's text edge. */
.story-cards { display: flex; flex-wrap: wrap; gap: clamp(10px, 1.4vw, 16px); justify-content: center; margin: 26px auto 0; }
.l-left .story-cards, .l-right .story-cards, .still-copy .story-cards, .poolhug .story-cards {
  justify-content: flex-start; margin-inline: 0; }
.l-right .story-cards { justify-content: flex-end; }
.story-card { appearance: none; cursor: pointer; display: inline-flex; align-items: center; gap: 12px; text-align: left;
  /* the card carries ITS OWN ground (--pool, the tone verdict's surface color,
     near-solid + blur) instead of a 6% ink tint over the raw photograph: the
     copy around it survives a mid-luma band via pool wash + text shadows, but
     a see-through card had nothing — its ink sat straight on the image
     (2026-08-20 owner report: washed-out product card over a bright backdrop).
     Grounded on --pool, ink-on-card contrast holds in EVERY state, even when
     the tone verdict for the photo itself is late or wrong. */
  background: color-mix(in srgb, var(--pool) 78%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(1.1); backdrop-filter: blur(12px) saturate(1.1);
  border: 1px solid color-mix(in srgb, var(--ink) 16%, transparent);
  box-shadow: 0 14px 40px -18px rgba(4, 7, 5, .45);
  border-radius: 14px; padding: 8px 14px 8px 8px; color: var(--ink); font-family: var(--font-b);
  transition: transform .3s var(--ease), border-color .3s var(--ease); }
.story-card:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.story-card .sc-thumb { width: 46px; height: 56px; border-radius: 9px; background-size: cover; background-position: center; flex: none; }
.story-card .sc-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.story-card .sc-body b { font-weight: 600; font-size: 13px; letter-spacing: .02em; }
.story-card .sc-body em { font-style: normal; font-size: 10px; letter-spacing: .14em; text-transform: uppercase; opacity: .65; }
.story-card .sc-price { font-size: 11px; opacity: .8; }
.story-card .arr { color: var(--accent); font-size: 13px; }

/* gallery — a row of masked floating shots, each opens the lightbox */
.pgal { display: flex; gap: clamp(16px, 2.2vw, 30px); justify-content: center; margin-top: 36px; flex-wrap: wrap; }
.pgal .gshot { position: relative; width: clamp(150px, 18vw, 210px); height: clamp(190px, 23vw, 270px); cursor: zoom-in;
  transition: transform .45s var(--ease); }
.pgal .gshot:hover { transform: translateY(-6px); }
.pgal .gshot .shotm { position: absolute; inset: 0; }

/* productGrid — tiles that dive into their own product */
.pgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: clamp(14px, 1.8vw, 26px); width: min(100%, 1080px); margin: 38px auto 0; }
.pgcard { position: relative; height: clamp(210px, 28vh, 290px); border: 0; padding: 0; cursor: pointer; color: var(--lt); background-size: cover; background-position: center; border-radius: 16px;
  -webkit-mask: radial-gradient(150% 122% at 50% 40%, #000 60%, transparent 100%); mask: radial-gradient(150% 122% at 50% 40%, #000 60%, transparent 100%);
  transition: transform .45s var(--ease); }
.pgcard:hover { transform: translateY(-8px); }
.pgcard .cap { position: absolute; left: 50%; bottom: 6px; transform: translateX(-50%); text-align: center; white-space: nowrap; padding: 28px 30px 12px;
  display: flex; flex-direction: column; gap: 4px; align-items: center; background: radial-gradient(64% 80% at 50% 84%, rgba(4,7,5,.82), transparent 74%); }
.pgcard .cap b { font-weight: 600; font-size: 15px; } .pgcard .cap em { font-style: normal; color: color-mix(in srgb, var(--lt) 72%, transparent); font-size: 10px; letter-spacing: .2em; text-transform: uppercase; }

/* hyperspace — an endless z-tunnel of products; JS owns cam position */
.hyper { position: absolute; inset: 0; perspective: 800px; transform-style: preserve-3d; }
.hitem { position: absolute; left: 50%; top: 50%; width: clamp(120px, 12vw, 190px); aspect-ratio: 3/4; will-change: transform, opacity;
  pointer-events: none; }
.hitem .shotm { position: absolute; inset: 0; }
.hitem .fcap { top: calc(100% + 4px); }
.hitem.hov .fcap { opacity: 1; }
/* width: max-content is the fix for a left:50% box, not a nicety: shrink-to-fit
   inside an absolutely-positioned element measures against the containing
   block MINUS the 50% offset, so the head only ever gets half the viewport and
   wraps a short line early. max-content asks for the line, the cap keeps it
   inside the gutters. (.rail-focus below already works this way.) */
.hyper-head { position: absolute; left: 50%; transform: translateX(-50%); top: 12vh; z-index: 4000; text-align: center; pointer-events: none;
  width: max-content; max-width: calc(100vw - 2 * var(--pad)); }
.hyper-hint { position: absolute; left: 50%; transform: translateX(-50%); bottom: 13vh; z-index: 4000; pointer-events: none;
  font-size: 10.5px; letter-spacing: .26em; text-transform: uppercase; color: var(--muted);
  width: max-content; max-width: min(84vw, 480px); text-align: center; }
/* narrow: the tunnel hint clears the raised pill and takes the WHOLE width bar
   a gutter. A narrower measure was tried first, to break the touch copy at its
   own separator — but on a phone that cost a third line for nothing: the line
   is one uppercase instruction, and every millimetre it does not use is a
   millimetre of height it takes instead. */
@media (max-width: 700px) {
  .hyper-hint { bottom: 124px; max-width: calc(100vw - 24px); }
}

/* river — full-bleed columns counter-drifting; captions on hover */
.river { position: absolute; inset: 0; display: flex; gap: clamp(12px, 1.6vw, 24px); padding: 0 var(--pad);
  -webkit-mask: linear-gradient(to bottom, transparent, #000 14%, #000 86%, transparent);
          mask: linear-gradient(to bottom, transparent, #000 14%, #000 86%, transparent); }
.rcol { flex: 1; min-width: 0; }
.rstack { display: flex; flex-direction: column; gap: clamp(12px, 1.6vw, 24px); animation: river-up var(--d, 50s) linear infinite; }
.rcol.down .rstack { animation-name: river-down; }
.rcol:hover .rstack { animation-play-state: paused; }
.rtile { position: relative; flex: 0 0 auto; height: var(--h, 30vh); cursor: pointer; }
.rtile .shotm { position: absolute; inset: 0; }
.rtile .fcap { top: auto; bottom: 14px; }
.rtile:hover .fcap { opacity: 1; }
@keyframes river-up   { from { transform: translateY(0); } to { transform: translateY(-50%); } }
@keyframes river-down { from { transform: translateY(-50%); } to { transform: translateY(0); } }
.river-head { position: absolute; left: var(--pad); bottom: 24vh; z-index: 2; pointer-events: none; max-width: 34vw; }
/* narrow: 34vw is ~127px on a phone, one word per line. The head takes the
   room the columns can spare instead. */
@media (max-width: 700px) {
  .river-head { max-width: min(70vw, calc(34vw + 200px)); }
}

/* rail — a drifting rail; centre focus, drag, wheel scrub, click-to-centre */
.rail { position: absolute; inset: 0; display: grid; align-items: center; cursor: grab; touch-action: pan-y;
  -webkit-mask: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
          mask: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent); }
.rail.drag { cursor: grabbing; }
.rail-track { display: flex; gap: clamp(30px, 4vw, 60px); width: max-content; will-change: transform; align-items: center; padding: 0 44vw; }
.rcard2 { position: relative; flex: 0 0 auto; width: clamp(170px, 16vw, 240px); aspect-ratio: 3/4; will-change: transform, filter, opacity; }
.rcard2 .shotm { position: absolute; inset: 0; }
.rail.tall .rcard2 { width: clamp(220px, 22vw, 330px); aspect-ratio: 2/3; }
.rail-head { position: absolute; left: 50%; transform: translateX(-50%); top: 12vh; z-index: 3; text-align: center; pointer-events: none;
  width: max-content; max-width: calc(100vw - 2 * var(--pad)); }
.rail-focus { position: absolute; left: 50%; transform: translateX(-50%); bottom: 12vh; z-index: 3; width: max-content; max-width: calc(100vw - 32px); text-align: center; --py: 30px; --px2: 66px; }
.rail-focus b { display: block; font-size: 15px; font-weight: 600; text-shadow: var(--tsh); }
.rail-focus em { display: block; font-style: normal; font-family: var(--font-d); font-size: 17px; color: var(--accent); margin-top: 3px; }
.rail-focus .addbtn { margin-top: 0; white-space: nowrap; }
.rail-focus .rf-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 18px; margin-top: 12px; }
/* the focused piece's own door — spelled out, so touch never needs the chip */
.rf-view { appearance: none; cursor: pointer; background: none; border: 0; padding: 6px 0;
  display: inline-flex; align-items: center; gap: 8px; color: var(--ink); font-family: var(--font-b);
  font-size: 10.5px; font-weight: 700; letter-spacing: .2em; text-transform: uppercase; white-space: nowrap;
  text-shadow: var(--tsh); opacity: .92; transition: gap .26s var(--ease), opacity .26s var(--ease); }
.rf-view:hover { gap: 13px; opacity: 1; } .rf-view .arr { color: var(--accent); letter-spacing: 0; }
/* narrow: the focus row is absolutely positioned off 12vh, which on a
   Safari-height phone is ~75px, and the pill plus its murmur need more than
   that. It sits above them instead, like the hint does. */
@media (max-width: 700px) {
  .rail-focus { bottom: 124px; }
}
.rail-focus.swap b, .rail-focus.swap em { animation: fade-up .45s var(--ease); }
@keyframes fade-up { from { opacity: 0; transform: translateY(12px); filter: blur(5px); } to { opacity: 1; transform: none; filter: none; } }

/* veiled listing — copy over the image half, scrolling product grid on the veiled half */
.vlist { display: grid; gap: clamp(28px, 5vw, 80px); align-items: center; grid-template-columns: 1fr; }
@media (min-width: 900px) { .vlist { grid-template-columns: .9fr 1.1fr; } }
.vlist-copy { display: grid; justify-items: start; text-align: left; }
.vlist-grid { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(14px, 1.8vw, 24px);
  justify-self: end; width: min(100%, 520px); max-height: 86vh; overflow-y: auto; scrollbar-width: none;
  /* PATTERN: masked scroll containers carry generous bottom padding so the
     last row can scroll fully clear of the fade */
  padding: 26px 4px clamp(70px, 12vh, 130px); overscroll-behavior: contain;
  -webkit-mask: linear-gradient(to bottom, transparent, #000 9%, #000 91%, transparent);
          mask: linear-gradient(to bottom, transparent, #000 9%, #000 91%, transparent); }
.vlist-grid::-webkit-scrollbar { display: none; }
/* narrow: the shelf ends well above the prompt box, so the last row is
   always reachable instead of resting behind it */
@media (max-width: 700px) {
  .vlist-grid { max-height: 44svh; padding-bottom: clamp(120px, 20vh, 180px); }
}
.vtile { position: relative; aspect-ratio: 4/5; cursor: pointer; transition: transform .45s var(--ease); }
.vtile:hover { transform: translateY(-6px); }
.vtile .shotm { position: absolute; inset: 0; }
/* a corner pool, not a caption bar — melts up into the photograph */
.vcap { position: absolute; right: 0; bottom: 0; z-index: 1; text-align: right; padding: 38px 14px 10px 48px; border-radius: 0 0 16px 0;
  background: radial-gradient(130% 140% at 100% 100%, color-mix(in srgb, var(--dk) 82%, transparent), color-mix(in srgb, var(--dk) 46%, transparent) 55%, transparent 78%); }
.vcap b { display: block; font-size: 13px; font-weight: 600; color: var(--lt); }
.vcap em { display: block; font-style: normal; font-family: var(--font-d); font-size: 14.5px; color: var(--accent); margin-top: 2px; }

/* the index — the listing as typography; the image follows the pointer */
.idxlist { display: grid; justify-items: start; }
.idxrow { display: flex; align-items: baseline; gap: 20px; appearance: none; background: none; border: 0; cursor: pointer; padding: 8px 0;
  color: var(--muted); text-shadow: var(--tsh); transition: color .35s var(--ease); text-align: left; }
.idxname { order: 1; font-family: var(--font-d); font-weight: 400; font-size: clamp(30px, 4.6vw, 56px); line-height: 1.12; }
.idxtag, .idxprice { font-family: var(--font-b); font-size: 11px; font-weight: 600; letter-spacing: .18em; text-transform: uppercase;
  color: var(--muted); opacity: .7; transition: color .35s var(--ease), opacity .35s var(--ease); }
.idxtag { order: 2; } .idxprice { order: 3; }
.idxrow:hover { color: var(--ink); }
.idxrow:hover .idxtag, .idxrow:hover .idxprice { color: var(--accent); opacity: 1; }
@media (min-width: 701px) { .idxprice::before { content: "· "; } }
/* narrow: the tag becomes the line's eyebrow, the price holds the right edge */
@media (max-width: 700px) {
  .idxrow { display: grid; grid-template-columns: 1fr auto; grid-template-areas: "tag tag" "name price";
    column-gap: 14px; row-gap: 2px; width: 100%; padding: 11px 0; }
  .idxtag { grid-area: tag; font-size: 9px; letter-spacing: .26em; color: var(--accent); opacity: .85; }
  .idxname { grid-area: name; font-size: clamp(24px, 7vw, 34px); }
  .idxprice { grid-area: price; justify-self: end; align-self: baseline;
    font-family: var(--font-d); font-size: 16px; letter-spacing: 0; text-transform: none; color: var(--accent); opacity: 1; }
  /* the narrow row is the ONE place these two speak in the accent rather than
     in muted ink, so they follow the slide's accent verdict exactly like the
     eyebrow above them — a base accent that lost its ground took the row's
     category and its price down with it, while the eyebrow swapped and lived */
  .slide.acc-lift .idxtag, .slide.acc-lift .idxprice { color: var(--accent-lift); }
  .slide.acc-deep .idxtag, .slide.acc-deep .idxprice { color: var(--accent-deep); }
  .slide.acc-lift .idxtag, .slide.acc-deep .idxtag { opacity: 1; }
}
.idximg { position: fixed; left: 0; top: 0; z-index: 7; width: clamp(260px, 26vw, 400px); aspect-ratio: 3/4; pointer-events: none;
  opacity: 0; transform: scale(.9); transition: opacity .4s var(--ease), transform .4s var(--ease); will-change: translate; }
.idximg.on { opacity: 1; transform: scale(1); }
.idximg .shotm { position: absolute; inset: 0; }
/* no hover, no follower — on touch the rows speak for themselves */
@media (hover: none) { .idximg { display: none; } }

/* ensemble — up to four pieces around a central anchor; hover NEVER flips
   z-order: a hovered companion steps SIDEWAYS out of the overlap (--dx points
   away from centre) while scaling up — reads as coming forward with no z flip */
.duet { display: flex; justify-content: center; align-items: center; margin-top: 34px;
  --ov: clamp(28px, 3.2vw, 54px); }
.dcard2 { position: relative; flex: 0 0 auto; width: clamp(140px, 14vw, 200px); aspect-ratio: 3/4; cursor: pointer;
  transform: translate(var(--hx, 0px), calc(var(--sy, 0px) + var(--hy, 0px))) scale(var(--hs, 1));
  transition: transform .55s var(--ease), filter .55s var(--ease); }
.dcard2 + .dcard2 { margin-left: calc(-1 * var(--ov)); }
/* the set deal — a quiet savings line over the CTA; the struck sum inside it */
.duet-save { display: inline-block; margin: 16px 0 0; padding: 6px 13px 5px; border-radius: 30px;
  font-size: 9.5px; font-weight: 700; letter-spacing: .24em; text-transform: uppercase;
  color: var(--accent-ink); background: var(--accent);
  box-shadow: 0 10px 26px -8px color-mix(in srgb, var(--accent) 55%, transparent); }
.cta .was { text-decoration: line-through; text-decoration-thickness: 1.5px; opacity: .52; font-weight: 500; margin: 0 2px; }

/* ---- resume (the welcome-back slide) — trail doors + re-ask links + the set ---- */
.resume-qs { flex-direction: column; align-items: center; gap: 4px; }
.resume-set { margin-top: 18px; display: flex; flex-direction: column; align-items: center; gap: 10px; }
.resume-set .duet-save { margin: 0; }
/* the set card stays a CARD, not a banner: capped width, one-line sub */
.resume-set .story-card { max-width: min(520px, 88vw); }
.resume-set .sc-body em { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 44ch; }
.resume-set .story-card .rs-thumbs { display: flex; flex: none; }
.resume-set .rs-thumbs .sc-thumb { width: 40px; height: 52px; border-radius: 8px; background-size: cover; background-position: center;
  border: 1.5px solid color-mix(in srgb, var(--lt) 30%, transparent); }
.resume-set .rs-thumbs .sc-thumb + .sc-thumb { margin-left: -14px; }
.resume-set .sc-price .was { text-decoration: line-through; opacity: .5; margin-right: 4px; }
.dcard2 .shotm { position: absolute; inset: 0; }
.dcard2.main  { width: clamp(190px, 20vw, 280px); z-index: 3; }
.dcard2.side  { z-index: 2; --sy: 18px; }
.dcard2.outer { width: clamp(120px, 12vw, 170px); z-index: 1; --sy: 42px; }
.dcard2.cl .vcap { right: auto; left: 0; text-align: left; border-radius: 0 0 0 16px; padding: 38px 48px 10px 14px;
  background: radial-gradient(130% 140% at 0% 100%, color-mix(in srgb, var(--dk) 82%, transparent), color-mix(in srgb, var(--dk) 46%, transparent) 55%, transparent 78%); }
/* both filter states share the same function list so the transition interpolates */
.duet:hover .dcard2 { filter: brightness(.72) drop-shadow(0 20px 34px rgba(0,0,0,0)); }
.duet .dcard2:hover { filter: brightness(1) drop-shadow(0 26px 44px rgba(0,0,0,.42));
  --hx: calc(var(--dx, 0) * (var(--ov) + 22px)); --hy: -8px; --hs: 1.05; }
.duet .dcard2.main:hover { --hs: 1.03; }
/* narrow screens: up to five pieces are SIZED to the screen — viewport-
   relative widths, small overlaps, the outermost may kiss the edges, the
   anchor nudged to the true centre (init). Only a LARGER set becomes a
   scrollable shelf (.scrollx, set by the atom), opened on the anchor with
   minimal side padding so as much as possible stays in view.
   (This block lives BELOW the base .main/.side/.outer rules — equal
   specificity, so source order is what lets the narrow widths win.) */
@media (max-width: 700px) {
  /* the ensemble's rhythm tightens so the CTA under it still clears the pill on
     a Safari-height viewport, where the bars eat ~180px of the 812 */
  .duet { --ov: 10px; margin-top: 18px; }
  .duet-save { margin-top: 12px; }
  /* the set's own paragraph is the tallest thing after the cards: five lines of
     it pushed the CTA behind the pill at 629. Three lines say the same thing,
     and the CTA takes the wide-screen margin down with it. */
  .inner:has(> .duet) .sub { margin-top: 14px;
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
  .inner:has(> .duet) .cta { margin-top: 16px; }
  /* a 116px caption on a 90px card wraps to one word per line and blankets the
     photograph it is meant to name. The flanking pieces are recognisable from
     the picture at this size; only the anchor still says what it is. */
  .dcard2.side .vcap, .dcard2.outer .vcap { display: none; }
  .dcard2 { width: 24vw; } .dcard2.main { width: 37vw; } .dcard2.outer { width: 19vw; }
  .dcard2.side { --sy: 10px; } .dcard2.outer { --sy: 22px; }
  .duet.scrollx { justify-content: flex-start; max-width: 100%; overflow-x: auto; scrollbar-width: none;
    overscroll-behavior-x: contain; padding: 14px 8px 20px; scroll-snap-type: x proximity;
    -webkit-mask: linear-gradient(to right, transparent, #000 10px, #000 calc(100% - 10px), transparent);
            mask: linear-gradient(to right, transparent, #000 10px, #000 calc(100% - 10px), transparent); }
  .duet.scrollx::-webkit-scrollbar { display: none; }
  .duet.scrollx .dcard2 { scroll-snap-align: center; }
}

/* still life — the piece held still; other angles one hover away.
   Wide screens: the classic composition — photograph left; eyebrow, headline,
   the price on its OWN line, copy, CTA, and the thumb row all in the right
   column (grid areas seat the thumbs under the copy; .still-media dissolves
   via display:contents). Narrow screens (≤899, incl. the in-between widths):
   one dense screen — the photograph fills everything the copy doesn't need,
   thumbs ride its side, price shares the headline's line, the description
   folds behind "More". */
.still { display: grid; gap: clamp(20px, 4vw, 64px); align-items: center; grid-template-columns: 1fr; }
.still-main { position: relative; width: min(100%, 460px); aspect-ratio: 4/5; cursor: zoom-in; }
.still-main .shotm { position: absolute; inset: 0; opacity: 0; transition: opacity .8s var(--xfade); }
.still-main .shotm.on { opacity: 1; }
.still-copy { display: grid; justify-items: start; text-align: left; }
.still-copy .eyebrow { margin-bottom: 10px; }
.still-head { display: flex; align-items: baseline; gap: 18px; flex-wrap: wrap; }
.still-copy .sub { margin-top: 12px; }
.still-copy .cta { margin-top: 18px; }
.still-more { appearance: none; cursor: pointer; background: none; border: 0; padding: 4px 0 0;
  font-family: var(--font-b); font-size: 10px; font-weight: 700; letter-spacing: .22em; text-transform: uppercase;
  color: var(--accent); text-shadow: var(--tsh); justify-self: start; }
@media (min-width: 900px) { .still-more, .still-sub + .still-more { display: none !important; } }
.sthumbs { display: flex; gap: 14px; }
.sthumb { position: relative; width: 64px; height: 80px; cursor: pointer; border: 0; padding: 0; background: none; }
.sthumb .shotm { position: absolute; inset: 0; border-radius: 10px; }
.sthumb::after { content: ""; position: absolute; left: 18%; right: 18%; bottom: -8px; height: 2px; border-radius: 2px; background: var(--accent); opacity: 0; transition: opacity .3s var(--ease); }
.sthumb.cur::after { opacity: 1; }
/* wide: the classic composition — grid areas seat the thumbs UNDER the copy
   column and the price takes its own line. (Below the base rules on purpose:
   equal specificity, source order decides.) */
@media (min-width: 900px) {
  .still { grid-template-columns: 1.05fr .95fr; gap: 18px clamp(28px, 5vw, 80px);
    grid-template-rows: 1fr auto auto 1fr;
    grid-template-areas: "img ." "img copy" "img thumbs" "img ."; }
  .still-media { display: contents; }
  .still-main { grid-area: img; justify-self: center; }
  .still-copy { grid-area: copy; }
  .sthumbs { grid-area: thumbs; justify-self: start; }
  .still-head { display: block; }
  .still-head .price { display: inline-block; margin-top: 12px; }
}
/* narrow + in-between: the piece OWNS the screen. The block takes the whole
   slide box (viewport minus the 12vh paddings), the copy keeps only what it
   needs and the photograph fills the rest — from just under the header down
   to where the copy begins, with the last line still clear of the prompt box.
   The cover-fit crop replaces the aspect box; parallax is dropped in init. */
@media (max-width: 899px) {
  /* "the whole slide box" is exactly what 100% of the slide's content area means,
     so say that instead of re-deriving it from the viewport. The old
     calc(100svh - 24vh) guessed at the padding and guessed wrong the moment the
     narrow padding stopped being 12vh: on a Safari-height viewport the block
     ended up taller than the room it sat in and pushed its last row (the
     "See it in context" link) down behind the prompt pill. */
  /* the gap is the ONLY air between the photograph's bottom edge and the "IN
     FOCUS" eyebrow. The image row is the one that gives (minmax(0, 1fr)), so on
     a short viewport — or under a long description — the copy grew until 14px
     was all that separated the eyebrow from the piece, and the two read as one
     smudged block. 24px is the smallest gap that still holds as a seam there. */
  .still { gap: 24px; height: 100%; max-height: 100%; min-height: 0;
    grid-template-rows: minmax(0, 1fr) auto; align-items: stretch; }
  /* the thumb column carries an intrinsic height (three 58px thumbs and their
     gaps). Left to itself it SET the media row rather than fitting into it, the
     row overflowed the block, and on a Safari-height viewport the photograph
     painted straight down through the copy — the eyebrow and the headline came
     out ON the boots. Bounding the row and letting the thumbs give way keeps the
     seam honest at every height: the picture gets the room the copy can spare,
     and not one pixel it cannot. */
  .still-media { display: grid; grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: minmax(0, 1fr); gap: 12px; min-height: 0; align-items: stretch; }
  .still-main { width: 100%; height: 100%; min-height: 0; max-width: none; aspect-ratio: auto; }
  .sthumbs { flex-direction: column; gap: 10px; align-self: stretch; justify-content: center; min-height: 0; }
  .sthumb { width: 46px; flex: 0 1 58px; height: 58px; min-height: 0; }
  .sthumb::after { left: auto; right: -7px; top: 18%; bottom: 18%; width: 2px; height: auto; }
  .still-head { width: 100%; justify-content: space-between; gap: 12px; }
  .still .price.big { font-size: clamp(27px, 8vw, 38px); }
  /* the head row is full-width here — the ch cap is a wide-layout device */
  .still-copy .headline.sm { font-size: clamp(22px, 6.4vw, 30px); max-width: none !important; }
  .still-sub { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
  .still-sub.open { -webkit-line-clamp: unset; display: block; }
  .still-copy .cta { margin-top: 12px; padding: 12px 22px; }
}
/* landscape phones: stacking a photograph over the copy needs vertical room the
   viewport does not have, and the block was running its CTA and its links under
   the prompt pill. A landscape phone is short, not narrow: the columns go
   SIDEWAYS instead, which is the wide composition the ≥900px block already
   describes, minus the grid-areas that seat the thumbs under the copy (there is
   no spare row for them here, so they keep riding the photograph's side). */
@media (max-height: 430px) and (max-width: 899px) and (min-aspect-ratio: 1/1) {
  .still { grid-template-columns: 1.05fr .95fr; gap: 14px clamp(20px, 3vw, 44px);
    grid-template-rows: minmax(0, 1fr); align-items: stretch; }
  /* the column takes the row's height so its own scroller owns any tail, rather
     than centring and spilling past the pill at both ends. align-content stays
     START: centring an overflowing scroller opens it already scrolled, and the
     visitor would arrive at a piece whose name is above the fold. */
  .still-copy { height: 100%; min-height: 0; align-content: start;
    overflow-y: auto; scrollbar-width: none; overscroll-behavior: contain;
    /* the row ends where the prompt pill begins, so the tail needs its own room
       to travel: without it the last link stops scrolling while still under the
       pill and stays unreachable however far the visitor drags. */
    padding-bottom: 56px; }
  .still-copy::-webkit-scrollbar { display: none; }
  /* a scroller is the safety net, not the plan: every row down to the CTA has to
     land above the pill on arrival, so the column spends its 235px carefully.
     The narrow block's display type sizes (a 38px price, three lines of copy)
     were written for a screen with twice this height. */
  .still-copy .eyebrow { margin-bottom: 5px; }
  .still-copy .headline.sm { font-size: clamp(18px, 2.4vw, 24px); }
  .still .price.big { font-size: clamp(22px, 2.9vw, 30px); }
  .still-head { gap: 12px; }
  .still-copy .sub { margin-top: 5px; }
  .still-sub { -webkit-line-clamp: 2; }
  .still-copy .cta { margin-top: 8px; padding: 9px 18px; font-size: 14px; }
  .still-copy .story-links { margin-top: 12px; }
  .still-copy .story-link { padding-block: 5px; }
}
@media (max-width: 700px) {
  .still-more { padding: 10px 12px 10px 0; margin-top: -6px; }
  .linkrow a { padding-block: 8px; }
}

/* ledger — key/value rows in two columns, hairlines instead of boxes */
.ledger { columns: 2; column-gap: clamp(44px, 6vw, 96px); width: min(100%, 880px); margin-top: 34px; text-align: left; }
@media (max-width: 760px) { .ledger { columns: 1; } }
.lrow { display: flex; justify-content: space-between; align-items: baseline; gap: 20px; padding: 12px 2px 11px;
  break-inside: avoid; position: relative; }
.lrow::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: linear-gradient(to right, color-mix(in srgb, var(--ink) 24%, transparent), color-mix(in srgb, var(--ink) 5%, transparent)); }
.lrow span { font-size: 10.5px; letter-spacing: .18em; text-transform: uppercase; color: var(--muted); white-space: nowrap; }
.lrow b { font-family: var(--font-d); font-weight: 400; font-size: 15.5px; color: var(--ink); text-align: right; }

/* comparison — the header row (image, name, price) holds still OUTSIDE the
   scroll container; only attribute rows scroll, melting out before the header */
.cmpwrap { display: grid; grid-template-columns: 1fr; gap: clamp(24px, 3.5vw, 56px);
  align-items: center; justify-items: center; text-align: center; }
@media (min-width: 1080px) {
  .cmpwrap { grid-template-columns: .72fr 1.28fr; justify-items: start; text-align: left; }
  .cmpwrap .cmp { justify-self: end; margin-top: 0; }
}
.cmp-copy { display: grid; }
.cmp { width: min(100%, 940px); margin-top: 30px;
  --cgrid: minmax(92px, .8fr) repeat(var(--cols, 3), minmax(120px, 1fr));
  --cgap: clamp(10px, 2vw, 28px); }
.cmp-head { display: grid; grid-template-columns: var(--cgrid); column-gap: var(--cgap); align-items: end; }
.chead { display: grid; justify-items: center; gap: 8px; padding: 0 6px 16px; text-align: center; }
.chead .cimg { position: relative; width: clamp(72px, 8vw, 108px); aspect-ratio: 3/4; }
.chead .cimg .shotm { position: absolute; inset: 0; border-radius: 10px; }
.chead b { font-size: 13px; font-weight: 600; }
.chead em { font-style: normal; font-family: var(--font-d); font-size: 14.5px; color: var(--accent); margin-top: -4px; }
.chead.corner { justify-items: start; }
.chead.corner span { font-size: 10px; letter-spacing: .22em; text-transform: uppercase; color: var(--muted); }
.cmp-rows { display: grid; grid-template-columns: var(--cgrid); column-gap: var(--cgap);
  max-height: 44vh; overflow-y: auto; scrollbar-width: none; overscroll-behavior: contain;
  padding-bottom: clamp(52px, 8vh, 90px);
  -webkit-mask: linear-gradient(to bottom, transparent, #000 36px, #000 86%, transparent);
          mask: linear-gradient(to bottom, transparent, #000 36px, #000 86%, transparent); }
.cmp-rows::-webkit-scrollbar { display: none; }
.clabel, .ccell { padding: 14px 4px 13px; border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
  font-size: 13.5px; line-height: 1.45; display: flex; align-items: center; }
.clabel { font-size: 10.5px; letter-spacing: .18em; text-transform: uppercase; color: var(--muted); }
.ccell { justify-content: center; text-align: center; color: var(--ink); }
/* narrow screens: the copy hands the room to the table — full line length
   (the ch clamps are a wide-layout device), centred like the rest of the
   column; the sheet itself pans sideways instead of overflowing the slide,
   and the rows keep enough tail padding to scroll clear of the prompt box */
@media (max-width: 700px) {
  .cmpwrap { gap: 16px; }
  .cmp-copy { justify-items: center; }
  .cmp-copy .headline { max-width: none !important; }
  .cmp-copy .sub { max-width: 40ch !important; margin-top: 12px; }
  .cmp { margin-top: 10px; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain;
    -webkit-mask: linear-gradient(to right, transparent, #000 14px, #000 calc(100% - 14px), transparent);
            mask: linear-gradient(to right, transparent, #000 14px, #000 calc(100% - 14px), transparent); }
  .cmp::-webkit-scrollbar { display: none; }
  .cmp-head, .cmp-rows { min-width: 540px; padding-inline: 14px; }
  .cmp-rows { max-height: 34svh; padding-bottom: clamp(90px, 15vh, 150px); }
}

/* buyRow + the cart-flight layer */
.buyrow { display: flex; gap: clamp(24px, 4vw, 60px); justify-content: center; flex-wrap: wrap; margin-top: 40px; }
.buycard { display: grid; justify-items: center; gap: 2px; width: clamp(170px, 17vw, 230px); }
/* `background: none` is part of the reset, not decoration: everything the tile
   paints (.shotm, .vcap) is rounded to 16px, so the UA's own square ButtonFace
   was showing as a pale nub at each of the four corners. */
.buycard .btile { appearance: none; border: 0; background: none; color: inherit; font: inherit; padding: 0; text-align: left; position: relative; width: 100%; aspect-ratio: 4/5; cursor: pointer;
  transition: transform .45s var(--ease); }
.buycard .btile:hover { transform: translateY(-5px); }
.buycard .btile .shotm { position: absolute; inset: 0; }
/* narrow screens: never a single column — a two-up grid that scrolls in place,
   fading out at the bottom, with tail room to clear the prompt box */
@media (max-width: 700px) {
  .buyrow { display: grid; grid-template-columns: 1fr 1fr; gap: 18px 14px; width: 100%;
    margin-top: 26px; max-height: 50svh; overflow-y: auto; scrollbar-width: none; overscroll-behavior: contain;
    padding: 8px 4px clamp(80px, 13vh, 140px);
    -webkit-mask: linear-gradient(to bottom, transparent, #000 8%, #000 88%, transparent);
            mask: linear-gradient(to bottom, transparent, #000 8%, #000 88%, transparent); }
  .buyrow::-webkit-scrollbar { display: none; }
  .buycard { width: 100%; }
}
.addbtn { appearance: none; cursor: pointer; border: 0; margin-top: 10px; font-family: var(--font-b); font-weight: 700; font-size: 11px;
  letter-spacing: .18em; text-transform: uppercase; color: var(--accent-ink); background: var(--accent); border-radius: 30px; padding: 9px 18px;
  transition: transform .25s var(--ease), box-shadow .25s var(--ease); box-shadow: 0 10px 26px -8px color-mix(in srgb, var(--accent) 55%, transparent); }
.addbtn:hover { transform: translateY(-2px); }
#flylayer { position: fixed; inset: 0; z-index: 90; pointer-events: none; }

/* recomposer — two copy layers stacked on one grid cell, crossfading. Each
   layer carries its OWN pool (not the wrap), so the backdrop fades and zooms
   with its copy instead of sitting still under the swap. The transition MUST
   live here, not inline: the swap suspends it inline (transition:none) to
   place the incoming start frame, then clears the inline value so this rule
   takes back over. An inline transition would be deleted by that clear and
   every step after the first would snap. */
.rec-wrap { display: grid; }
.rec-stage { grid-area: 1/1; --xfade: cubic-bezier(.4,.12,.35,.9); display: grid; justify-items: center;
  text-align: center; min-height: 34vh; align-content: center;
  transition: opacity 1.05s var(--xfade), transform 1.05s var(--xfade), filter 1.05s var(--xfade); }
.flyer { position: fixed; left: 0; top: 0; width: 64px; height: 64px; border-radius: 50%; background-size: cover; background-position: center;
  -webkit-mask: radial-gradient(circle, #000 55%, transparent 100%); mask: radial-gradient(circle, #000 55%, transparent 100%); will-change: transform; }

/* ------------------------------------------------------------------- chrome */
.chrome { position: absolute; z-index: 8; }
/* one optical centerline for brand mark, menu glyphs and the control circles.
   The circles centre at --chrome-top + 23px (46px controls), so the brand's
   top is DERIVED from that same line minus half the logo's own height —
   independent clamps only agreed at their desktop maximums and drifted 4px
   apart on mobile. */
.brand { top: calc(var(--chrome-top) + 23px - clamp(15px, 1.6vw, 19px)); left: clamp(24px,3.4vw,48px); font-weight: 700; letter-spacing: .32em; font-size: 13px;
  color: var(--ink); text-shadow: var(--tsh); cursor: pointer;
  transition: text-shadow var(--slow) var(--ease), --ink var(--slow) var(--ease),
    --muted var(--slow) var(--ease), --pool var(--slow) var(--ease); }
.brand small { display: block; font-weight: 500; letter-spacing: .24em; font-size: 9px; color: var(--muted); margin-top: 5px; }
/* a proper logo (inline SVG from the store data) replaces the text lockup;
   currentColor lets it ride the animated ink between tone modes */
.brand svg { display: block; height: clamp(30px, 3.2vw, 38px); width: auto;
  filter: drop-shadow(0 1px 10px rgba(0,0,0,.35)); transition: filter var(--slow) var(--ease); }
#stage.tone-light .brand svg { filter: drop-shadow(0 1px 10px rgba(0,0,0,0)); }
/* fixed-palette logo CUTS: both variants mount stacked and crossfade with
   the header's measured tone (c-light / c-dark from the luma grid, else the
   stage tone) on the same clock as the scene behind them */
.brand .logo2 { position: relative; display: block; }
.brand .lg { display: block; transition: opacity var(--slow) var(--ease); }
.brand .lg-d { position: absolute; inset: 0; opacity: 0; }
.brand .lg-d svg { filter: drop-shadow(0 1px 10px rgba(0,0,0,0)); }
.brand.c-light .lg-l, #stage.tone-light .brand:not(.c-dark) .lg-l { opacity: 0; }
.brand.c-light .lg-d, #stage.tone-light .brand:not(.c-dark) .lg-d { opacity: 1; }
/* NOT derived from --chrome-top: this is its own clamp, tuned by eye and
   QA'd visually, and it sits a few px above the control centerline
   (--chrome-top + 23px), closing to ~3px at the desktop maximums. The menu is
   hidden under 860px, so the widths that matter are the ones where the two
   nearly agree. Anything that moves --chrome-top must re-check this by eye
   rather than assume the two track each other. */
.menu { top: clamp(20px, calc(2.9vw - 4px), 35px); left: 50%; transform: translateX(-50%); display: flex; gap: clamp(18px, 2.8vw, 38px); white-space: nowrap; }
/* color tracks the animating ink token directly (no own transition — see the
   token gotcha above); the shadow flip is discrete, so it animates here */
.menu a, .menu button { position: relative; padding-bottom: 7px; font-size: 11px; letter-spacing: .22em; text-transform: uppercase; color: var(--muted);
  cursor: pointer; text-shadow: var(--tsh); transition: text-shadow var(--slow) var(--ease); }
/* an unrouted item is a real button (see chrome.ts setMenu) and wears the
   anchor's exact clothes — the shopper must not be able to tell them apart */
.menu button { appearance: none; background: none; border: 0; font-family: inherit; font-weight: inherit; line-height: inherit; padding-inline: 0; }
.menu a, .menu button { text-decoration: none; }
.menu a:hover, .menu a.on, .menu button:hover, .menu button.on { color: var(--ink); }
/* the page you're on names itself: full ink + a small accent underline */
.menu a::after, .menu button::after { content: ""; position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
  width: 0; height: 2px; border-radius: 2px; background: var(--accent); transition: width .35s var(--ease); }
.menu a.on::after, .menu button.on::after { width: 16px; }
@media (max-width: 860px) { .menu { display: none; } }
.topright { top: var(--chrome-top); right: var(--chrome-right); display: flex; gap: 10px; align-items: center; }
/* background derives from the animating --pool token → no own background
   transition (it must track the token on the token's clock, like the color) */
.ctl { appearance: none; cursor: pointer; width: 46px; height: 46px; border-radius: 50%; color: var(--ink); border: 0;
  background: color-mix(in srgb, var(--pool) 42%, transparent); backdrop-filter: blur(12px);
  display: grid; place-items: center; transition: transform .3s var(--ease); position: relative; }
.ctl:hover { transform: translateY(-2px); background: color-mix(in srgb, var(--pool) 62%, transparent); }
.ctl svg { width: 18px; height: 18px; }
.ctl .badge { position: absolute; top: -3px; right: -3px; min-width: 18px; height: 18px; border-radius: 10px; background: var(--accent); color: var(--accent-ink);
  font-size: 10px; font-weight: 700; display: grid; place-items: center; padding: 0 4px; opacity: 0; transform: scale(.5); transition: transform .35s var(--ease), opacity .3s var(--ease); }
.ctl .badge.show { opacity: 1; transform: scale(1); }
.ctl.bump { animation: cartbump .5s var(--ease); }
@keyframes cartbump { 0%,100% { transform: scale(1); } 40% { transform: scale(1.22) rotate(-6deg); } }
.ctl.back { width: 0; padding: 0; opacity: 0; transform: scale(.6); pointer-events: none; }
/* the one control that speaks in accent rather than ink, so it takes the
   ground-aware cut (--accent-on) instead of the raw brand accent. The raw
   accent ignores the ground entirely: measured over every photo-backed slide
   in the Nord store it lands at 1.2–2.4:1 against the composited top band,
   where its light cut holds 3.0–5.9:1. */
#stage.inproduct ~ * .ctl.back, .chrome .ctl.back.on { width: 46px; opacity: 1; transform: scale(1); pointer-events: auto; color: var(--accent-on); }
.cart-toast { position: absolute; z-index: 8; top: clamp(78px, 9vh, 100px); right: clamp(24px,3.4vw,48px); font-size: 12px; color: var(--ink);
  padding: 10px 16px; border-radius: 30px; background: color-mix(in srgb, var(--pool) 60%, transparent); backdrop-filter: blur(10px);
  opacity: 0; transform: translateY(-6px); transition: opacity .4s var(--ease), transform .4s var(--ease),
    --ink var(--slow) var(--ease), --pool var(--slow) var(--ease); pointer-events: none;
  max-width: calc(100vw - 48px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cart-toast.show { opacity: 1; transform: none; }

/* header tone — stage.js reads the topmost luma-grid cells EACH header group
   (brand / menu / controls) actually overlaps and pins that group's ink
   independently: a mixed top edge (shadow left, glare centre) tones each
   piece for its own ground. Tokens re-transition on the stage clock, so the
   header changes mode with the crossfade; no grid → no class → the header
   follows the stage tone. */
.menu, .topright { transition: --ink var(--slow) var(--ease), --muted var(--slow) var(--ease),
  --pool var(--slow) var(--ease); }
/* --accent-on rides the same verdict as --ink: accent-as-ink in the chrome
   (the back arrow) needs the store's clearly-light cut on a dark band and its
   clearly-dark cut on a bright one, exactly like accent copy on a slide
   (.slide.acc-lift / .acc-deep). Unconditional here is safe BECAUSE the cuts
   are identity for an accent that already clears mid (see accentVariants) —
   only a mid-luminance accent, the kind that actually fails, moves. */
.c-light {
  --ink: color-mix(in srgb, var(--dk) 92%, #000);
  --muted: color-mix(in srgb, var(--dk) 58%, transparent);
  --accent-on: var(--accent-deep);
  --pool: var(--lt); --tsh: 0 1px 22px rgba(0,0,0,0); }
.c-dark {
  --ink: var(--lt);
  --muted: color-mix(in srgb, var(--lt) 62%, transparent);
  --accent-on: var(--accent-lift);
  --pool: var(--dk); --tsh: 0 1px 10px rgba(0,0,0,.25); }
/* no grid → no verdict → follow the stage tone, like .brand's logo cuts do */
#stage.tone-light .chrome:not(.c-dark) { --accent-on: var(--accent-deep); }
.brand.c-light svg { filter: drop-shadow(0 1px 10px rgba(0,0,0,0)); }
.brand.c-dark svg { filter: drop-shadow(0 1px 10px rgba(0,0,0,.35)); }
.burger i { display: block; width: 16px; height: 2px; background: currentColor; border-radius: 2px; position: relative; }
.burger i::before, .burger i::after { content: ""; position: absolute; left: 0; width: 16px; height: 2px; background: currentColor; border-radius: 2px; }
.burger i::before { top: -5px; } .burger i::after { top: 5px; }
/* the story pagination: a quiet vertical dot column, right-centre. Each dot
   is one slide; its label only speaks when asked (hover). Click jumps. */
.spine { right: clamp(18px, 2.6vw, 36px); top: 50%; transform: translateY(-50%);
  display: flex; flex-direction: column; align-items: flex-end;
  transition: opacity .4s var(--ease); }
.spine .ticks { display: flex; flex-direction: column; gap: 6px; align-items: center; }
.spine .tick { appearance: none; border: 0; background: none; cursor: pointer; position: relative;
  padding: 5px; display: grid; place-items: center; }
.spine .tick i { display: block; width: 5px; height: 5px; border-radius: 50%;
  background: color-mix(in srgb, var(--ink) 32%, transparent);
  transition: transform .4s var(--ease); }
.spine .tick:hover i { background: color-mix(in srgb, var(--ink) 70%, transparent); }
.spine .tick.on i { background: var(--accent); transform: scale(1.6); }
.spine .tick .tlabel { position: absolute; right: calc(100% + 10px); top: 50%; transform: translateY(-50%) translateX(4px);
  white-space: nowrap; font-size: 9px; letter-spacing: .26em; text-transform: uppercase; color: var(--ink);
  text-shadow: var(--tsh); opacity: 0; pointer-events: none;
  transition: opacity .3s var(--ease), transform .3s var(--ease), text-shadow var(--slow) var(--ease); }
.spine .tick:hover .tlabel { opacity: 1; transform: translateY(-50%); }
/* narrow: the dots KEEP the desktop column at the right edge, only closer in.
   They used to lie down as a horizontal row at the very bottom, which put them
   in the same floor strip the murmur suggestions live in — the two painted over
   each other, and raising the pill only moved the collision rather than ending
   it. The right edge is empty on every atom, so the column owns it outright.
   The padding IS the tap target: the dot stays 5px, the button stays 17px so a
   thumb can land on one slide instead of its neighbour. Labels are a hover
   device and stay away. */
@media (max-width: 700px) {
  .spine { right: 6px; }
  .spine .ticks { gap: 1px; max-height: 62vh; flex-wrap: wrap; }
  .spine .tick { padding: 6px; }
  .spine .tick .tlabel { display: none; }
  /* …except while the pill is MID-PAGE. It spans 94vw there, so the gutter it
     leaves on the right is thinner than one tap target and the centred column
     would run behind the glass. The column used to STAND ON the pill's top
     edge instead — which meant it jumped between that perch and the centre on
     every dock/undock (owner report 2026-08-20: dots leaping on the homepage's
     first scroll and on the end-of-story overscroll). The dots never move now:
     they hold the centre and simply stand down while the pill owns the middle,
     the same courtesy the overlays get. */
  body:has(.pbox.docked) #stage .spine,
  body:has(.pbox.centered) #stage .spine {
    opacity: 0; pointer-events: none; }
}
.hint { position: absolute; left: 50%; bottom: clamp(98px,13vh,116px); transform: translateX(-50%); z-index: 6; font-size: 10.5px; letter-spacing: .26em; text-transform: uppercase; color: var(--muted);
  transition: opacity .6s var(--ease); display: flex; align-items: center; gap: 12px; pointer-events: none; }
/* narrow: 13vh is 82px on a Safari-height viewport, which lands the hint ON the
   pill and its murmur. Pin it clear of both instead, and let a long line wrap
   centred across the WHOLE width bar a gutter (same reasoning as .hyper-hint:
   an early break only buys extra height). */
@media (max-width: 700px) {
  /* width: max-content for the same reason .hyper-head carries it — shrink-to-fit
     inside a `left: 50%` box measures against the containing block MINUS the
     offset, so a max-width alone can never buy back the half it already lost */
  .hint { bottom: 116px; width: max-content; max-width: calc(100vw - 24px);
    text-align: center; flex-wrap: nowrap; }
  .hint .pulse { flex: none; }
}
/* landscape and other short viewports have no strip to spare: the hint and the
   pill were sharing one band. The pill is the one that has to be there. */
@media (max-height: 500px) {
  .hint { display: none; }
}
/* a line that was offered and stood down before it ever reached the screen must
   leave WITHOUT the fade (chrome.js paintHint): the .6s above would otherwise
   animate it away from its default opacity 1, and the visitor would watch a
   hint the caller already withdrew cross the surface that took its lane.
   Nothing paints it, so nothing has to fade it. */
.hint.nofade { transition: none; }
.hint .pulse { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); animation: hintpulse 2.4s var(--ease) infinite; }
@keyframes hintpulse { 0%,100%{opacity:.3; transform: scale(.8);} 50%{opacity:1; transform: scale(1.25);} }

/* -------- overlays (universe menu + bag) — the same zoom-out language ------- */
/* overlays are siblings of #stage, so they must carry the body font themselves */
.overlay { position: fixed; inset: 0; z-index: 70; opacity: 0; pointer-events: none; font-family: var(--font-b); }
.overlay .uback { position: absolute; inset: 0; -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  background: radial-gradient(120% 100% at 50% 40%, color-mix(in srgb, var(--dk) 55%, transparent), color-mix(in srgb, var(--dk) 88%, transparent) 70%); }
.overlay .uwrap { position: relative; height: 100%; overflow-y: auto; scrollbar-width: none; padding: clamp(70px, 12vh, 130px) var(--pad) 8vh;
  display: grid; grid-template-columns: minmax(0, 1fr); align-content: start; gap: clamp(30px, 5vh, 54px); transform: scale(.94); filter: blur(6px);
  transition: transform 1s var(--ease), filter 1s var(--ease); color: var(--lt);
  -webkit-mask: linear-gradient(to bottom, transparent, #000 44px, #000 calc(100% - 32px), transparent);
          mask: linear-gradient(to bottom, transparent, #000 44px, #000 calc(100% - 32px), transparent); }
.overlay.open { opacity: 1; pointer-events: auto; transition: opacity .7s var(--ease); }
.overlay:not(.open) { transition: opacity .5s var(--ease); }
.overlay.open .uwrap { transform: scale(1); filter: none; }
/* an overlay takes the whole screen, but the prompt pill, its murmurs and the
   story's dots are fixed siblings of #stage and kept painting through the
   translucent sheet: "YOUR TRAIL" landed on top of a murmur, and on a phone a
   half-cut pill sat above the feedback sheet. While something is open over the
   story, the story's bottom chrome stands down. */
body:has(.overlay.open) .pbox,
body:has(#feedback[data-open="true"]) .pbox,
body:has(.overlay.open) #stage .spine,
body:has(#feedback[data-open="true"]) #stage .spine {
  opacity: 0; pointer-events: none; }
/* ...except when the pill is SAYING something (walk 3, X4c). The rule above is
   right about the resting pill and wrong about a note: the murmur surface is
   this pill, so while the menu was open every dead end the store tried to
   explain was painted at opacity 0. The owner's "press the winter coats link"
   with the menu up got a perfectly good answer that nobody could see.

   A note is the one thing on this pill that belongs ABOVE the overlay, so it
   rises past it while it is up and drops back the moment it clears. Still
   pointer-events:none: it is something the store is saying, not something to
   click, and the overlay underneath keeps every one of its own controls. */
body:has(.overlay.open) .pbox.noted,
body:has(.overlay.open) .pbox.note-out,
body:has(#feedback[data-open="true"]) .pbox.noted,
body:has(#feedback[data-open="true"]) .pbox.note-out {
  opacity: 1; pointer-events: none; z-index: 80; }
/* same courtesy while the phone keyboard is up: the pill has moved into the
   visual viewport and the scroll hint would otherwise sit under it, telling a
   shopper who is mid-sentence to swipe */
#stage:has(~ .pbox.kbd) .hint { opacity: 0; }
/* the ✕ stands exactly where the control that opened the overlay stands
   (top-right, on the header centerline) — the burger for the universe, the
   cart circle (one 46px control + 10px gap further left) for the bag — so
   closing is the same gesture as opening, in place */
.overlay .uclose { position: absolute; right: clamp(24px,3.4vw,48px); top: clamp(14px,1.9vw,25px); z-index: 2; color: var(--lt);
  background: color-mix(in srgb, var(--lt) 10%, transparent); }
#bag .uclose { right: calc(clamp(24px,3.4vw,48px) + 56px); }
/* the overlay ground is always the dk radial — accent touches take the LIFT cut */
.usec .ulabel { font-size: 10px; letter-spacing: .34em; text-transform: uppercase; color: var(--accent-lift); margin: 0 0 16px; font-weight: 700; }
.unav { display: grid; gap: 6px; justify-items: start; grid-template-columns: minmax(0, 1fr); }
/* long doors WRAP (tight leading — a two-line door reads as one) and the
   arrow flows inline, trailing the last word instead of hugging the far edge */
.unav button, .unav a { appearance: none; cursor: pointer; text-decoration: none; background: none; border: 0; color: var(--lt); font-family: var(--font-d); font-weight: 400;
  font-size: clamp(26px, 3.6vw, 44px); padding: 4px 0; display: block;
  max-width: 100%; text-align: left; line-height: 1.12; text-wrap: balance; }
.unav button .arr, .unav a .arr { color: var(--accent-lift); font-size: .7em; display: inline-block; margin-left: 14px; transition: margin-left .3s var(--ease); }
.unav button:hover .arr, .unav a:hover .arr { margin-left: 24px; }
/* grouped doors: a quiet sub-label names each group, and the doors step down a
   size so three groups still read as one screen rather than a scrolling wall */
.unav .ugroup { display: grid; gap: 2px; justify-items: start; width: 100%; }
.unav .ugroup + .ugroup { margin-top: 22px; }
.usec .ulabel.sub { font-size: 9px; letter-spacing: .26em; font-weight: 600;
  color: color-mix(in srgb, var(--lt) 42%, transparent); margin: 0 0 8px; }
.unav .ugroup button, .unav .ugroup a { font-size: clamp(21px, 2.5vw, 32px); }
.ucols { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 26px; max-width: 860px; }
.ucols b { display: block; font-size: 10px; letter-spacing: .26em; text-transform: uppercase; color: color-mix(in srgb, var(--lt) 55%, transparent); margin-bottom: 10px; }
/* the entries are real BUTTONS (they ask the store a question or open a place),
   so they are focusable and keyboard-reachable — the bare <a> they replaced was
   neither. Same typographic voice as before. */
.ucols a, .ucols button { display: block; color: var(--lt); opacity: .85; text-decoration: none; font-size: 13.5px; padding: 4px 0; cursor: pointer;
  line-height: 1.3; overflow-wrap: anywhere; }
.ucols button { appearance: none; background: none; border: 0; font-family: inherit; text-align: left; width: 100%; }
.ucols a:hover, .ucols button:hover, .ucols a:focus-visible, .ucols button:focus-visible { opacity: 1; color: var(--accent-lift); }
/* the trail — one stop per visited experience */
.trail { display: flex; gap: 14px; overflow-x: auto; scrollbar-width: none; padding: 4px 0 10px;
  -webkit-mask: linear-gradient(to right, #000 88%, transparent); mask: linear-gradient(to right, #000 88%, transparent); }
.trail::-webkit-scrollbar { display: none; }
.tstop { flex: 0 0 auto; appearance: none; cursor: pointer; border: 0; background: none; color: var(--lt); text-align: left; display: grid; gap: 8px; width: 148px; }
.tstop .timg { display: block; width: 148px; height: 92px; border-radius: 12px; background-size: cover; background-position: center;
  -webkit-mask: radial-gradient(160% 140% at 50% 45%, #000 58%, transparent 100%); mask: radial-gradient(160% 140% at 50% 45%, #000 58%, transparent 100%);
  transition: transform .4s var(--ease); }
.tstop:hover .timg { transform: translateY(-3px); }
.tstop b { font-size: 11.5px; font-weight: 600; line-height: 1.3; }
.tstop em { font-style: normal; display: block; font-size: 9.5px; letter-spacing: .14em; color: color-mix(in srgb, var(--lt) 50%, transparent); margin-top: 2px; }
.trail-empty { color: color-mix(in srgb, var(--lt) 45%, transparent); font-size: 13px; }
/* the mini-listing (breadcrumb waypoints on a slide): the same trail stops,
   centred as a quiet row — no scroll, no edge mask; ink follows the slide tone */
.trail.mini { justify-content: center; overflow: visible; -webkit-mask: none; mask: none;
  gap: clamp(16px, 2.2vw, 28px); margin-top: 34px; padding: 4px 0; flex-wrap: wrap; }
.trail.mini .tstop { color: var(--ink); text-align: center; justify-items: center; }
.trail.mini .tstop em { color: var(--muted); }
.trail.mini .tstop .timg { width: 148px; height: 92px; }

/* bag overlay content */
.bagwrap { max-width: 620px; margin-inline: auto; width: 100%; align-content: center; }
.citem { display: flex; align-items: center; gap: 18px; padding: 12px 0; }
.citem .cthumb { flex: 0 0 auto; width: 68px; height: 84px; }
.citem .cinfo { flex: 1; min-width: 0; }
.citem .cinfo b { display: block; font-size: 15px; font-weight: 600; }
.citem .cinfo em { font-style: normal; font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: color-mix(in srgb, var(--lt) 55%, transparent); }
.citem .cqty { font-size: 13px; color: color-mix(in srgb, var(--lt) 65%, transparent); font-variant-numeric: tabular-nums; }
.citem .cprice { font-family: var(--font-d); font-size: 17px; color: var(--accent-lift); min-width: 70px; text-align: right; }
/* the row lets go — a quiet ✕ that lifts only when asked for. A real 30px tap
   target, but it rides inside the row's own height and pulls its own gap back,
   so adding it does not widen or inflate the line. */
.citem .cremove { flex: 0 0 auto; display: grid; place-items: center; width: 30px; height: 30px; margin: 0 -8px 0 -6px;
  appearance: none; background: none; border: 0; padding: 0; cursor: pointer; font-size: 13px; line-height: 1;
  color: color-mix(in srgb, var(--lt) 35%, transparent); transition: color .3s var(--ease); }
.citem .cremove:hover, .citem .cremove:focus-visible { color: var(--accent-lift); }
/* narrow: the one-line table has no room — the row breaks into a grid. The
   thumb anchors both rows, the name/tag keep the first, and the qty + price
   take a line of their own beneath, so nothing has to shrink to fit */
@media (max-width: 700px) {
  .citem { display: grid; grid-template-columns: auto 1fr auto; column-gap: 14px; row-gap: 2px;
    grid-template-areas: "thumb info remove" "thumb meta meta"; align-items: start; }
  .citem .cthumb { grid-area: thumb; align-self: center; }
  .citem .cinfo { grid-area: info; }
  .citem .cqty { grid-area: meta; align-self: end; }
  .citem .cprice { grid-area: meta; align-self: end; justify-self: end; text-align: right; min-width: 0; }
  .citem .cremove { grid-area: remove; margin: -4px -8px 0 0; }
}
.bagfoot { display: flex; align-items: baseline; justify-content: space-between; gap: 20px; margin-top: 10px;
  padding-top: 22px; position: relative; }
.bagfoot::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px;
  background: linear-gradient(to right, transparent, color-mix(in srgb, var(--lt) 25%, transparent), transparent); }
/* renderBag() hides the footer with the `hidden` attribute, but the author
   `display: flex` above outranks the UA sheet's `[hidden] { display: none }` —
   so an empty bag kept its total and its Checkout button on screen. Say it. */
.bagfoot[hidden] { display: none; }
.bagfoot .total { font-family: var(--font-d); font-size: clamp(20px, 2.4vw, 28px); }
.bagfoot .cta { margin-top: 0; }
.bag-empty { color: color-mix(in srgb, var(--lt) 45%, transparent); font-size: 14px; }

/* ------------------- fullscreen media (FLIP) + info panel ------------------ */
#lb { position: fixed; inset: 0; z-index: 60; pointer-events: none; }
.lb-scrim { position: absolute; inset: 0; opacity: 0; pointer-events: none; cursor: zoom-out;
  background: color-mix(in srgb, var(--dk) 55%, transparent); transition: opacity .8s var(--ease); }
#lb.open .lb-scrim { opacity: 1; pointer-events: auto; }
.lb-img { position: fixed; object-fit: cover; object-position: center; margin: 0; border-radius: 16px; cursor: zoom-out; pointer-events: none;
  opacity: 0;
  transition: top 1s var(--ease), left 1s var(--ease), width 1s var(--ease), height 1s var(--ease), border-radius 1s var(--ease),
              opacity .35s var(--ease) .62s; }
#lb.open .lb-img { pointer-events: auto; opacity: 1;
  transition: top 1s var(--ease), left 1s var(--ease), width 1s var(--ease), height 1s var(--ease), border-radius 1s var(--ease),
              opacity .15s var(--ease); }
#stage.lbopen #slidehost { filter: blur(8px); transform: scale(1.05); opacity: .5; pointer-events: none; }
#stage.lbopen .bgwrap, #stage.lbopen .ambient { filter: blur(6px); }
/* the explicit way back — a quiet ✕ that arrives once the flight has landed */
/* it stands exactly on the chrome's top-right control, so it reads as the
   burger handing over to the way back — never two stacked circles. Same 46px
   circle, so it takes the chrome's own anchor tokens: a second clamp of its
   own never lands on the same centerline at ANY width. */
.lb-close { position: fixed; z-index: 64; top: var(--chrome-top); right: var(--chrome-right);
  width: 46px; height: 46px; border-radius: 50%; border: 0; cursor: pointer; display: grid; place-items: center;
  color: var(--lt); background: color-mix(in srgb, var(--dk) 55%, transparent);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lt) 14%, transparent);
  opacity: 0; pointer-events: none; transform: scale(.7); transition: opacity .4s var(--ease), transform .45s var(--ease), background .3s var(--ease); }
#lb.open .lb-close { opacity: 1; pointer-events: auto; transform: none; transition-delay: .55s; }
.lb-close:hover { background: color-mix(in srgb, var(--dk) 78%, transparent); }
.lb-close svg { width: 17px; height: 17px; }
/* the slideshow chrome (only when a SET rides the dive — #lb.set): side
   arrows flanking the media (settle() positions them off the measured box)
   and a quiet counter under it. Narrow screens swipe instead. */
.lb-arr { position: fixed; z-index: 63; width: 46px; height: 46px; border-radius: 50%; border: 0;
  cursor: pointer; display: grid; place-items: center;
  color: var(--lt); background: color-mix(in srgb, var(--dk) 55%, transparent);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lt) 14%, transparent);
  opacity: 0; pointer-events: none; transition: opacity .4s var(--ease), background .3s var(--ease); }
#lb.open.set .lb-arr { opacity: 1; pointer-events: auto; transition-delay: .55s; }
.lb-arr:hover { background: color-mix(in srgb, var(--dk) 78%, transparent); }
.lb-arr svg { width: 19px; height: 19px; }
.lb-count { position: fixed; z-index: 63; transform: translateX(-50%); pointer-events: none;
  font-family: var(--sans); font-size: 11.5px; letter-spacing: .22em; color: var(--mist);
  text-shadow: 0 1px 10px rgba(0, 0, 0, .55);
  opacity: 0; transition: opacity .4s var(--ease); }
#lb.open.set .lb-count { opacity: .85; transition-delay: .55s; }
@media (max-width: 859px) { .lb-arr { display: none; } }
/* the swap dip: the outgoing frame fades before the sibling fades back in */
#lb.swapping .lb-img { opacity: 0 !important; }
/* the info panel — no box: content over a soft radial pool melting into the image */
.lb-info { position: fixed; z-index: 62; color: var(--lt); font-family: var(--font-b);
  display: flex; flex-direction: column; overflow-y: auto; scrollbar-width: none;
  opacity: 0; pointer-events: none; transition: opacity .7s var(--ease), transform .8s var(--ease);
  /* PATTERN: scrollable containers fade out at their edges — px-sized fades,
     so content that FITS (inside the padding) never loses ink */
  -webkit-mask: linear-gradient(to bottom, transparent, #000 34px, #000 calc(100% - 30px), transparent);
          mask: linear-gradient(to bottom, transparent, #000 34px, #000 calc(100% - 30px), transparent); }
.lb-info::-webkit-scrollbar { display: none; }
.lb-info.on { opacity: 1; pointer-events: auto; transform: none; }
/* the drawer shell is inert on wide screens — the panel IS the right column */
.lb-drawer { display: contents; }
.lb-tab { display: none; }
@media (min-width: 860px) {
  .lb-info { top: 0; right: 0; height: 100%; width: min(58vw, 680px); justify-content: flex-start; align-items: flex-start;
    padding: 7vh clamp(30px, 3.5vw, 64px) 7vh clamp(52px, 10vw, 196px); transform: translateX(40px);
    background: radial-gradient(86% 74% at 84% 50%, color-mix(in srgb, var(--dk) 90%, transparent), color-mix(in srgb, var(--dk) 60%, transparent) 40%, transparent 76%); }
  .lb-info > * { max-width: 340px; width: 100%; flex: 0 0 auto; }
  /* safe centring: auto margins collapse when the column overflows, so long
     content scrolls from its top instead of losing it above the viewport */
  .lb-info::before, .lb-info::after { content: ""; flex: 0 0 auto; }
  .lb-info::before { margin-top: auto; }
  .lb-info::after { margin-bottom: auto; }
  /* the prompt box stays a live way to ask, above the scrim */
  #stage.lbopen ~ .pbox { z-index: 64; }
}
/* the in-between widths: the column pulls in and sheds most of its melt
   padding, so the media keeps real size (region() measures this — no JS twin) */
@media (min-width: 860px) and (max-width: 1279px) {
  .lb-info { width: min(44vw, 520px); padding-left: clamp(34px, 5vw, 80px); }
  .lb-info > * { max-width: 300px; }
}
/* narrow screens: the media keeps the viewport; the info waits in a right-edge
   DRAWER — a vertical tab hints at it, a pull (or tap) brings it across */
@media (max-width: 859px) {
  .lb-drawer { display: block; position: fixed; top: 0; bottom: 0; right: 0; z-index: 62;
    width: min(84vw, 380px); transform: translateX(100%); transition: transform .65s var(--ease);
    pointer-events: none; }
  #lb.info-open .lb-drawer { transform: none; }
  .lb-info { position: relative; inset: auto; width: 100%; height: 100%;
    justify-content: flex-start; align-items: stretch; transform: none;
    padding: max(11vh, 74px) 22px 170px 28px;
    background: color-mix(in srgb, var(--dk) 82%, transparent);
    -webkit-backdrop-filter: blur(20px) saturate(1.1); backdrop-filter: blur(20px) saturate(1.1);
    box-shadow: -30px 0 80px -30px rgba(4,7,5,.75), inset 1px 0 0 color-mix(in srgb, var(--lt) 9%, transparent); }
  #lb.info-open .lb-info.on { pointer-events: auto; }
  .lb-tab { display: flex; position: absolute; right: 100%; top: 50%; transform: translateY(-50%);
    flex-direction: column; align-items: center; gap: 9px; padding: 15px 6px 17px;
    border: 0; border-radius: 14px 0 0 14px; cursor: pointer; pointer-events: auto; touch-action: none;
    color: var(--lt); background: color-mix(in srgb, var(--dk) 62%, transparent);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    box-shadow: 0 12px 34px -10px rgba(4,7,5,.7), inset 0 0 0 1px color-mix(in srgb, var(--accent) 34%, transparent);
    opacity: 0; transition: opacity .5s var(--ease); }
  #lb.open .lb-tab { opacity: 1; transition-delay: .85s; }
  .lb-tab svg { width: 14px; height: 14px; color: var(--accent-lift); transition: transform .5s var(--ease); }
  #lb.info-open .lb-tab svg { transform: rotate(180deg); }
  .lb-tab span { writing-mode: vertical-rl; font-family: var(--font-b); font-size: 9px; font-weight: 700;
    letter-spacing: .3em; text-transform: uppercase; }
  /* the compact ask: the prompt box stays reachable under the fullscreen media,
     shrunk to a murmur so the image keeps the room */
  #stage.lbopen ~ .pbox { z-index: 64; bottom: 16px; height: 46px; width: min(440px, calc(100vw - 76px)); padding: 0 6px 0 18px; }
  #stage.lbopen ~ .pbox .pbox-send { width: 34px; height: 34px; }
  #stage.lbopen ~ .pbox .pbox-input { font-size: 14px; }
  /* with the mic on there are two trailing buttons at this size too */
  #stage.lbopen ~ .pbox.voiced .pbox-echo, #stage.lbopen ~ .pbox.voiced .pbox-note { right: 96px; }
}
/* the dive panel always sits on the dk scrim / drawer — accent touches use
   the LIFT cut (identity when the store's accent already reads on dark) */
.lb-eyebrow { font-size: 10.5px; letter-spacing: .4em; text-transform: uppercase; color: var(--accent-lift); font-weight: 600; margin: 0 0 12px; text-shadow: 0 1px 18px rgba(0,0,0,.55); }
.lb-title { font-family: var(--font-d); font-weight: 400; font-size: clamp(24px, 2.6vw, 34px); line-height: 1.12; margin: 0 0 14px; text-shadow: 0 2px 30px rgba(0,0,0,.7); }
.lb-desc { color: var(--lt); opacity: .92; font-size: 15px; line-height: 1.6; margin: 0 0 18px; text-shadow: 0 1px 20px rgba(0,0,0,.6); }
.lb-where { color: color-mix(in srgb, var(--lt) 62%, transparent); font-size: 13.5px; line-height: 1.6; margin: 0 0 22px; text-shadow: 0 1px 18px rgba(0,0,0,.6); }
.lb-where-k { display: block; font-size: 9.5px; letter-spacing: .26em; text-transform: uppercase; color: var(--accent-lift); opacity: .85; margin: 0 0 5px; }
.lb-aspects { display: grid; gap: 4px; margin: 2px 0 22px; }
.lb-aspect-head { appearance: none; cursor: pointer; width: 100%; display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  background: transparent; border: 0; color: var(--lt); font-family: var(--font-b); font-weight: 600; font-size: 14px; text-align: left; padding: 9px 0;
  text-shadow: 0 1px 16px rgba(0,0,0,.6); }
.lb-aspect-head:hover { color: var(--accent-lift); }
.lb-mk { color: var(--accent-lift); font-size: 19px; line-height: 1; flex: 0 0 auto; transition: transform .3s var(--ease); }
.lb-aspect.open .lb-mk { transform: rotate(45deg); }
.lb-aspect-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .42s var(--ease); }
.lb-aspect.open .lb-aspect-body { grid-template-rows: 1fr; }
/* the padding rides the SAME clock as the row growth — applied instantly it
   reads as a jump before the height transition (the jagged open) */
.lb-aspect-body > p { overflow: hidden; margin: 0; padding: 0; color: color-mix(in srgb, var(--lt) 62%, transparent); font-size: 13.5px; line-height: 1.6;
  text-shadow: 0 1px 16px rgba(0,0,0,.55); transition: padding .42s var(--ease); }
.lb-aspect.open .lb-aspect-body > p { padding: 2px 0 10px; }
.lb-links { display: grid; justify-items: start; gap: 12px; margin: 10px 0 0; }
.lb-link { appearance: none; cursor: pointer; display: inline-flex; align-items: center; gap: 10px; background: none; border: 0; padding: 0;
  color: var(--lt); font-family: var(--font-b); font-size: 13.5px; font-weight: 600; text-align: left; opacity: .92;
  text-shadow: 0 1px 16px rgba(0,0,0,.6); transition: gap .26s var(--ease), opacity .26s var(--ease); }
.lb-link:hover { gap: 16px; opacity: 1; }
.lb-link .arr { color: var(--accent-lift); flex: 0 0 auto; }

/* ===================== spotlight: the image AS the section ================
   depthfx draws the photograph on one WebGL quad (framing + focus fade +
   region glow + depth parallax); the DOM adds the copy, the feature markers
   and their floating label. The .shotm fallback stays for SSR and no-WebGL. */
.spot-stage { position: absolute; inset: 0; overflow: hidden; }
.spot-stage.focused { cursor: zoom-out; }
/* the SSR/no-WebGL poster — removed from the DOM the moment GL attaches */
.spot-stage .spot-fallback { position: absolute; inset: 0; border-radius: 0; }
.dfx-canvas { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; transition: opacity .8s var(--ease); }
.dfx-canvas.on { opacity: 1; }
/* 24px: the WCAG 2.5.8 target-size floor — centroids can crowd on a dense map */
.spot-dot { position: absolute; z-index: 3; width: 24px; height: 24px; border-radius: 50%; transform: translate(-50%, -50%);
  appearance: none; padding: 0; cursor: pointer;
  border: 1.5px solid color-mix(in srgb, var(--lt) 82%, transparent);
  background: color-mix(in srgb, var(--dk) 32%, transparent);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 16%, transparent);
  /* invisible until the frame first settles (.placed) — never paints mid-glide */
  opacity: 0; pointer-events: none;
  transition: opacity .5s var(--ease) .15s, transform .3s var(--ease); }
.spot-stage.placed .spot-dot { opacity: 1; pointer-events: auto; }
.spot-dot::after { content: ""; position: absolute; inset: 6px; border-radius: 50%; background: var(--accent); }
.spot-dot:hover, .spot-dot.cur { transform: translate(-50%, -50%) scale(1.25); }
.spot-stage.placed.focused .spot-dot:not(.cur) { opacity: .45; }
/* the floating name of the hovered/framed feature — the 3D marker language */
.spot-tag { position: absolute; z-index: 4; pointer-events: none; transform: translate(-50%, calc(-100% - 20px));
  font-family: var(--font-b); font-size: 11px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  color: var(--lt); background: color-mix(in srgb, var(--dk) 62%, transparent); backdrop-filter: blur(8px); border-radius: 30px;
  padding: 8px 16px; white-space: nowrap; box-shadow: 0 10px 30px -8px rgba(4,7,5,.7), inset 0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent);
  opacity: 0; transition: opacity .3s var(--ease); }
.spot-tag.on { opacity: 1; }
/* the copy rides OVER the quiet half; only it catches the pointer */
.l-spotlight { position: relative; z-index: 2; align-self: stretch; pointer-events: none;
  display: grid; grid-template-columns: minmax(0, 42%) 1fr; align-content: center; align-items: center; text-align: left; }
.l-spotlight .spot-copy-wrap { grid-column: 1; pointer-events: auto; display: grid; }
.l-spotlight.spot-right .spot-copy-wrap { grid-column: 2; justify-self: end; }
.l-spotlight .headline { font-size: clamp(26px, 3.2vw, 44px); }
.l-spotlight .sub { max-width: 38ch; }
/* overview copy and the feature article stack on the same ground; focusing
   crossfades between them */
.spot-copy-wrap > * { grid-area: 1 / 1; }
/* `visibility` rides the crossfade so the hidden half leaves the TAB ORDER and
   the accessibility tree (it holds real buttons, and .spot-detail is a live
   region — an invisible panel must not take focus or announce itself).
   Delaying it by the fade keeps the transition visible on the way out. */
.spot-copy, .spot-detail { display: grid; justify-items: start; align-content: center;
  transition: opacity .45s var(--ease), transform .45s var(--ease), visibility 0s linear .45s; }
.spot-detail { opacity: 0; pointer-events: none; visibility: hidden; transform: translateY(12px); }
.spot-copy-wrap.detail .spot-copy { opacity: 0; pointer-events: none; visibility: hidden; transform: translateY(-12px); }
.spot-copy-wrap.detail .spot-detail { opacity: 1; pointer-events: auto; visibility: visible; transform: none;
  transition-delay: 0s, 0s, 0s; }
.spot-copy-wrap:not(.detail) .spot-copy { visibility: visible; transition-delay: 0s, 0s, 0s; }
.spot-back { appearance: none; cursor: pointer; width: 40px; height: 40px; border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--lt) 30%, transparent);
  background: color-mix(in srgb, var(--dk) 40%, transparent); color: var(--lt);
  display: grid; place-items: center; margin: 0 0 18px; font-size: 15px;
  transition: transform .25s var(--ease), border-color .25s var(--ease); }
.spot-back:hover { transform: translateX(-3px); border-color: color-mix(in srgb, var(--accent) 60%, transparent); }
.spot-d-title { margin: 8px 0 0; }
.spot-d-note { margin-top: 14px; }
.spot-d-note[hidden] { display: none; }
/* a framed feature's onward door — explicit, never a surprise second tap */
.spot-more[hidden] { display: none; }
/* portrait: the feature takes the TOP half, the words the bottom */
@media (max-aspect-ratio: 9/11), (max-width: 700px) {
  .l-spotlight { grid-template-columns: 1fr; align-content: end; }
  .l-spotlight .spot-copy-wrap, .l-spotlight.spot-right .spot-copy-wrap { grid-column: 1; justify-self: start; }
  .l-spotlight .sub { max-width: 44ch; }
}

/* hotspots: the shimmer canvas + its floating label ride the image box */
.hot-canvas { position: fixed; z-index: 61; pointer-events: none; }
.hot-label { position: fixed; z-index: 63; pointer-events: none; transform: translate(-50%, -50%);
  font-family: var(--font-b); font-size: 11px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  color: var(--lt); background: color-mix(in srgb, var(--dk) 62%, transparent); backdrop-filter: blur(8px); border-radius: 30px;
  padding: 8px 16px; white-space: nowrap; box-shadow: 0 10px 30px -8px rgba(4,7,5,.7), inset 0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent);
  opacity: 0; transition: opacity .3s var(--ease), transform .3s var(--ease); }
.hot-label::after { content: " →"; color: var(--accent); }
.hot-label.on { opacity: 1; }

/* ===================== the product in the round (model3d) ================== */
/* The canvas is transparent — the piece floats in the stage's own atmosphere.
   Markers and hairlines are drawn INSIDE the scene (depth-tested); only the
   labels and the spot card live in the DOM. */
.m3wrap { position: absolute; inset: 0; }
.m3host { position: absolute; inset: 0; }
.m3-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  opacity: 0; transition: opacity 1.3s var(--xfade); touch-action: none; }
.m3-canvas.on { opacity: 1; }
/* a visible pano backdrop melts out toward the canvas edges, so the scene
   settles into whatever stands behind it (the blurred shot, the stage) */
.m3-canvas.hasbg {
  /* closest-side: the fade REACHES every edge — the midpoint of each side
     sits exactly at the gradient's 100% */
  -webkit-mask-image: radial-gradient(ellipse closest-side at 50% 48%, #000 48%, rgba(0,0,0,.72) 76%, transparent 99%);
          mask-image: radial-gradient(ellipse closest-side at 50% 48%, #000 48%, rgba(0,0,0,.72) 76%, transparent 99%);
}
.m3-dom { position: absolute; inset: 0; pointer-events: none; }
.m3label { position: absolute; pointer-events: none; transform: translate(0, -50%); white-space: nowrap;
  font-family: var(--font-b); font-size: 11px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  color: var(--lt); background: color-mix(in srgb, var(--dk) 62%, transparent); backdrop-filter: blur(8px); border-radius: 30px;
  padding: 7px 14px; box-shadow: 0 10px 30px -8px rgba(4,7,5,.7), inset 0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent);
  opacity: 0; transition: opacity .14s var(--ease); }
.m3label.flip { transform: translate(-100%, -50%); }
.m3label.on { opacity: 1; }

/* the tour's copy column — the recomposer's two stacked layers, poolside */
.m3copy { position: absolute; inset: 0; display: grid; align-items: center; justify-items: start;
  padding: 0 clamp(24px, 7vw, 110px); pointer-events: none; }
.m3copy .m3stage { grid-area: 1 / 1; max-width: min(44ch, 38vw); pointer-events: auto;
  transition: opacity .8s var(--ease), transform .8s var(--ease); }
/* side lives on each LAYER, not the container — the outgoing copy keeps the
   side it was born on and fades in place; only the incoming layer takes the
   new section's side */
.m3copy .m3stage.side-right { justify-self: end; }
@media (max-width: 919px) {
  .m3copy { align-items: end; padding-bottom: 16vh; }
  .m3copy .m3stage { max-width: min(44ch, 82vw); }
}
/* font-family is explicit — hints also live inside #lb, which (unlike #stage)
   inherits no library font */
.m3hint { position: absolute; left: clamp(24px, 7vw, 110px); bottom: 9vh; z-index: 5; pointer-events: none;
  margin: 0; font-family: var(--font-b); font-size: 10px; letter-spacing: .28em; text-transform: uppercase;
  color: color-mix(in srgb, var(--ink) 48%, transparent); text-shadow: var(--tsh); white-space: nowrap; }

/* the model3d slide — the piece in one hand, the words in the other */
.l-m3 { display: grid; gap: clamp(28px, 4vw, 60px); align-items: center; grid-template-columns: 1fr; text-align: left; }
@media (min-width: 920px) { .l-m3 { grid-template-columns: 1.15fr .85fr; } }
.m3side { position: relative; height: clamp(340px, 62vh, 720px); }
.m3side .m3hint { bottom: 6px; }
.l-m3 .m3copyside { display: grid; justify-items: start; align-content: center; }
@media (max-width: 919px) { .m3side { height: 44vh; } }

/* the spot card — little text, links out; opens AT the cursor, flipped and
   clamped so it never leaves the viewport */
.spotcard { position: fixed; z-index: 80; width: min(300px, 78vw); pointer-events: none;
  padding: 18px 20px 16px; border-radius: 14px;
  color: var(--lt); font-family: var(--font-b);
  background: color-mix(in srgb, var(--dk) 74%, transparent); backdrop-filter: blur(16px) saturate(1.1);
  box-shadow: 0 26px 70px -18px rgba(4,7,5,.8), inset 0 0 0 1px color-mix(in srgb, var(--accent) 26%, transparent);
  opacity: 0; transform: scale(.9); transition: opacity .3s var(--ease), transform .35s var(--ease); }
.spotcard.on { opacity: 1; transform: none; pointer-events: auto; }
.spotcard.pop { animation: sc-pop .38s var(--ease); }
@keyframes sc-pop { from { opacity: 0; transform: scale(.86); } }
.sc-eyebrow { font-size: 9.5px; letter-spacing: .32em; text-transform: uppercase; color: var(--accent); font-weight: 600; margin: 0 0 8px; }
.sc-title { font-family: var(--font-d); font-weight: 400; font-size: 20px; line-height: 1.15; margin: 0 0 8px; }
.sc-body { margin: 0; color: color-mix(in srgb, var(--lt) 78%, transparent); font-size: 13px; line-height: 1.55; }
.sc-links { display: grid; justify-items: start; gap: 9px; margin-top: 12px; }
.sc-links .lb-link { font-size: 12.5px; }

/* the lightbox takeover: the photograph becomes the WHOLE backdrop (a
   blurred, darkened cover copy fills the viewport, vignetted at the edges)
   and the piece takes the full media region beside the info panel */
.lb-m3 { position: fixed; z-index: 61; opacity: 0; transition: opacity .9s var(--xfade); pointer-events: none; }
.lb-m3.on { opacity: 1; pointer-events: auto; }
.lb-m3 .m3-canvas { transition: none; opacity: 1; }
.lb-m3bg { position: fixed; inset: -4vmax; pointer-events: none;
  background-size: cover; background-position: center;
  filter: blur(42px) brightness(.32) saturate(.75);
  opacity: 0; transition: opacity 1.1s var(--xfade); }
.lb-m3bg.on { opacity: 1; }
.lb-m3bg::after { content: ""; position: absolute; inset: 0;
  background: radial-gradient(115% 100% at 42% 46%, transparent 42%, rgba(4, 6, 5, .55) 100%); }
/* a model is coming: the shot keeps its FLIP flight but fades out ACROSS it,
   dissolving into the backdrop of itself — the piece is all that lands.
   An ANIMATION (not a transition): the image must start from full presence
   even though its resting opacity never got a chance to reach 1. */
#lb.m3wait .lb-img { opacity: 0; cursor: default;
  z-index: 1;      /* the flying shot rides ABOVE the backdrop while it dissolves */
  transition: top 1s var(--ease), left 1s var(--ease), width 1s var(--ease), height 1s var(--ease), border-radius 1s var(--ease);
  animation: lbm3-dissolve 1.05s var(--xfade) forwards; }
@keyframes lbm3-dissolve { from { opacity: 1; } to { opacity: 0; } }
.lbm3-hint { left: 50%; transform: translateX(-50%); bottom: 3.6vh; }

/* the 3D chip — a shot whose twin is a model wears a quiet badge */
.m3chip { position: absolute; right: 10px; bottom: 10px; z-index: 2; pointer-events: none;
  font-family: var(--font-b); font-size: 9px; font-weight: 700; letter-spacing: .22em;
  color: var(--lt); background: color-mix(in srgb, var(--dk) 55%, transparent); backdrop-filter: blur(6px);
  padding: 5px 8px 4px 10px; border-radius: 20px;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent); }

/* ============================== the veil =================================== */
/* One full-screen sheet in the brand's fade colour. JS drives its opacity in
   phases: a slow linear creep while the agent works, a quick eased blackout
   when the answer lands (scene + route swap underneath), then an eased fade-out
   over the new scene. Below the prompt box: at full fade the pulsing query and
   the orb are the only living things on screen. */
#veil { position: fixed; inset: 0; z-index: 50; pointer-events: none; background: rgb(var(--fade-rgb)); opacity: 0; }

/* ======================= the follow-up flow ================================ */
.fu-backdrop { position: fixed; inset: 0; z-index: 44; opacity: 0; pointer-events: none;
  background: radial-gradient(120% 95% at 50% 78%, rgba(4, 7, 5, .5), rgba(4, 7, 5, .78) 58%, rgba(4, 7, 5, .9));
  -webkit-backdrop-filter: blur(13px); backdrop-filter: blur(13px);
  transition: opacity .8s var(--ease); }
.fu-backdrop.on { opacity: 1; }
.fu { position: fixed; inset: 0; z-index: 46; display: flex; flex-direction: column; align-items: center;
  justify-content: flex-end; padding: 7vh var(--pad) 128px; text-align: center;
  opacity: 0; pointer-events: none; transition: opacity .7s var(--ease);
  font-family: var(--font-b); color: var(--lt); }
.fu.on { opacity: 1; pointer-events: auto; }
.fu-frame-slot { margin-bottom: clamp(38px, 9vh, 96px); min-height: 1em; transition: opacity .6s var(--ease); }
.fu-frame-slot.off { opacity: 0; }
.fu-frame { font-family: var(--font-d); font-size: clamp(17px, 1.9vw, 22px); line-height: 1.55;
  color: color-mix(in srgb, var(--lt) 84%, transparent); max-width: 48ch; margin: 0;
  text-shadow: 0 1px 22px rgba(0, 0, 0, .65); text-wrap: balance; }
.fu-dots { display: flex; gap: 9px; margin-bottom: 18px; }
.fu-dot { appearance: none; border: 0; padding: 0; width: 6px; height: 6px; border-radius: 50%;
  background: color-mix(in srgb, var(--lt) 26%, transparent); transition: background .35s var(--ease), transform .35s var(--ease); }
.fu-dot.past { background: color-mix(in srgb, var(--accent) 60%, transparent); cursor: pointer; }
.fu-dot.past:hover { transform: scale(1.5); background: var(--accent); }
.fu-dot.on { background: var(--accent); transform: scale(1.5); }
.fu-step { transition: opacity .4s var(--ease), transform .4s var(--ease), filter .4s var(--ease); }
.fu-step.leaving { opacity: 0; transform: translateY(-10px); filter: blur(6px); }
.fu-step .w { transition: opacity .75s var(--ease), transform .75s var(--ease), filter .75s var(--ease); }
.fu-step.entering .w { opacity: 0; transform: translateY(7px); filter: blur(6px); }
.fu-q { font-family: var(--font-d); font-weight: 400; font-size: clamp(26px, 3.6vw, 44px); line-height: 1.1;
  letter-spacing: -.012em; margin: 0; text-shadow: 0 2px 50px rgba(0, 0, 0, .7); text-wrap: balance; }
.fu-q .w { display: inline-block; }

/* ================= cold-start furniture (slot, ghosts, links) ============== */
/* the space content holds open for the docked prompt box */
/* measured against the column, not the viewport: 94vw ignores the slide's side
   padding, so the slot outgrew its track and dragged every centered sibling
   off-axis. a percentage max-width cannot fix that, it resolves against the
   track the slot itself inflated */
.pbox-slot { height: 74px; width: min(760px, 100%); margin: clamp(26px, 4.5vh, 46px) auto 0; }
/* ghost placeholders — sections not composed yet; they breathe, never shimmer */
.ghost-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(14px, 1.8vw, 26px);
  width: min(100%, 1080px); margin: clamp(30px, 5vh, 48px) auto 0; }
.ghost-card { height: clamp(140px, 19vh, 200px); border-radius: 16px; background: color-mix(in srgb, var(--lt) 2.8%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lt) 7%, transparent); animation: ghost-breath 6s ease-in-out infinite; }
.ghost-card:nth-child(2) { animation-delay: 1s; } .ghost-card:nth-child(3) { animation-delay: 2s; }
.ghost-secs { width: min(100%, 720px); margin: clamp(26px, 4vh, 42px) auto 0; display: grid; gap: clamp(26px, 4vh, 40px); }
.ghost-sec { display: grid; gap: 12px; justify-items: center; animation: ghost-breath 6s ease-in-out infinite; }
.ghost-sec:nth-child(2) { animation-delay: 1.4s; }
.ghost-sec i { display: block; height: 12px; border-radius: 6px; background: color-mix(in srgb, var(--lt) 5%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lt) 4%, transparent); width: min(100%, 560px); }
.ghost-sec i.t { width: 150px; height: 9px; background: color-mix(in srgb, var(--lt) 7%, transparent); }
.ghost-sec i.s { width: min(100%, 420px); }
@keyframes ghost-breath { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }
.ghost-note { font-family: var(--font-d); font-style: italic; font-size: 14.5px; color: var(--muted);
  margin: clamp(18px, 3vh, 30px) auto 0; max-width: 52ch; text-align: center; text-shadow: var(--tsh); }
/* the quiet editorial row — other doors for the visitor who'd rather browse */
.linkrow { display: flex; flex-wrap: wrap; gap: clamp(20px, 3.5vw, 46px); justify-content: center;
  margin: clamp(26px, 4.5vh, 44px) auto 0; }
.linkrow a { font-size: 11px; letter-spacing: .22em; text-transform: uppercase; color: var(--muted);
  cursor: pointer; display: inline-flex; align-items: center; gap: 9px; transition: color .3s var(--ease); }
.linkrow a:hover { color: var(--ink); }
.linkrow .arr { color: var(--accent); transition: transform .3s var(--ease); }
.linkrow a:hover .arr { transform: translateX(5px); }

/* =========================== the prompt box ================================ */
/* One fixed pill at bottom-centre — the intent driver. The visual chrome lives
   on its own child so the working state can fade the whole chrome (including
   the backdrop blur) as one opacity. */
/* bottom 16px (was 26): the murmur strip below the pill is gone (2026-08-19),
   so the pill reclaims that floor room */
.pbox { position: fixed; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 55;
  width: min(640px, 92vw); height: 52px; display: flex; align-items: center; gap: 10px;
  padding: 0 6px 0 24px; border-radius: 999px; font-family: var(--sans);
  transition: bottom .85s var(--ease), width .85s var(--ease), height .85s var(--ease), padding .85s var(--ease); }
.pbox.still, .pbox.still * { transition: none !important; }
.pbox-chrome { position: absolute; inset: 0; z-index: -1; border-radius: inherit;
  background: rgba(11, 15, 12, .6); -webkit-backdrop-filter: blur(18px) saturate(1.25); backdrop-filter: blur(18px) saturate(1.25);
  box-shadow: 0 24px 60px -18px rgba(0, 0, 0, .72), inset 0 0 0 1px rgba(242, 244, 238, .13), inset 0 1px 0 rgba(255, 255, 255, .06);
  transition: opacity .65s var(--ease); }
.pbox:focus-within .pbox-chrome { background: rgba(15, 20, 17, .68); }
.pbox-halo { position: absolute; inset: -120px -180px; z-index: -2; pointer-events: none; opacity: 0;
  background: radial-gradient(50% 54% at 50% 56%, rgba(4, 7, 5, .68), rgba(4, 7, 5, .3) 52%, transparent 74%);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  -webkit-mask: radial-gradient(50% 54% at 50% 56%, #000 30%, transparent 74%);
          mask: radial-gradient(50% 54% at 50% 56%, #000 30%, transparent 74%);
  transition: opacity .9s var(--ease); }
body[data-fade="light"] .pbox-halo { background: radial-gradient(50% 54% at 50% 56%, rgba(var(--fade-rgb), .78), rgba(var(--fade-rgb), .34) 52%, transparent 74%); }
.pbox.working .pbox-chrome,
.pbox.noted .pbox-chrome, .pbox.note-out .pbox-chrome { opacity: 0; }
.pbox.working .pbox-halo,
.pbox.noted .pbox-halo, .pbox.note-out .pbox-halo { opacity: 1; }
.pbox.noted .pbox-send, .pbox.note-out .pbox-send { background: transparent; box-shadow: none; cursor: default; transform: none; }
.pbox.noted .pbox-send .arr, .pbox.note-out .pbox-send .arr { opacity: 0; transform: scale(.5); }
.pbox-input { flex: 1; min-width: 0; background: transparent; border: 0; outline: none;
  color: var(--mist); font-family: var(--sans); font-size: 15.5px;
  transition: opacity .3s var(--ease), font-size .85s var(--ease); }
.pbox-input::placeholder { color: var(--muted); }
.pbox.working .pbox-input, .pbox.noted .pbox-input, .pbox.note-out .pbox-input { opacity: 0; }
.pbox.working .pbox-input { pointer-events: none; }
.pbox-echo { position: absolute; left: 24px; right: 72px; z-index: 1; pointer-events: none;
  color: var(--mist); font-size: 15.5px; white-space: nowrap; overflow: hidden;
  text-shadow: 0 1px 16px rgba(0, 0, 0, .55); opacity: 0; transition: opacity .45s var(--ease);
  -webkit-mask: linear-gradient(to right, #000 86%, transparent); mask: linear-gradient(to right, #000 86%, transparent); }
.pbox.working .pbox-echo { opacity: 1; }
/* the caption (voice legibility D1): the SAME band, one register quieter. The
   words voice is hearing are not an answer yet, so they sit at --muted and just
   over half opacity, with no per-character pulse (that belongs to the commit)
   and a shorter ease-in so the first words do not creep in. Nothing is added to
   the pill, so nothing can shift: the echo already owns this line. */
.pbox.captioned .pbox-echo { opacity: .55; color: var(--muted); transition: opacity .2s var(--ease); }
.pbox.captioned .pbox-input { opacity: 0; pointer-events: none; }
.pbox-echo i { font-style: normal; display: inline-block; animation: char-pulse 1.7s ease-in-out infinite;
  animation-delay: calc(var(--ci) * 55ms); }
@keyframes char-pulse { 0%, 100% { opacity: .95; } 50% { opacity: .28; } }
body[data-fade="light"] .pbox.working .pbox-echo { color: #23271f; text-shadow: none; }
.pbox-note { position: absolute; left: 18px; right: 72px; z-index: 1; pointer-events: none;
  display: flex; align-items: center; gap: 11px; opacity: 0; transform: translateY(6px);
  transition: opacity .35s var(--ease), transform .35s var(--ease); }
.pbox.noted .pbox-note { opacity: 1; transform: none; transition: opacity .35s var(--ease) .4s, transform .35s var(--ease) .4s; }
.pbox-note .txt { margin-left: 33px; font-family: var(--serif); font-style: italic; font-size: 15.5px; color: var(--mist);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  -webkit-mask-image: linear-gradient(to right, transparent 0 45%, #000 65% 100%);
          mask-image: linear-gradient(to right, transparent 0 45%, #000 65% 100%);
  -webkit-mask-size: 280% 100%; mask-size: 280% 100%;
  -webkit-mask-position: 0 0; mask-position: 0 0; }
.pbox.noted .pbox-note .txt { animation: note-sweep .95s var(--ease) .45s both; }
@keyframes note-sweep {
  from { -webkit-mask-position: 0 0; mask-position: 0 0; }
  to { -webkit-mask-position: 100% 0; mask-position: 100% 0; }
}
/* the mic adds a second trailing button: the absolute text bands (echo, note)
   must end before it, in every pill size */
.pbox.voiced .pbox-echo, .pbox.voiced .pbox-note { right: 96px; }
.pbox.docked .pbox-echo, .pbox.docked .pbox-note,
.pbox.centered .pbox-echo, .pbox.centered .pbox-note { right: 78px; }   /* pre-existing 4px underlap */
.pbox.docked.voiced .pbox-echo, .pbox.docked.voiced .pbox-note,
.pbox.centered.voiced .pbox-echo, .pbox.centered.voiced .pbox-note { right: 120px; }
.pbox-send { position: relative; flex: 0 0 auto; width: 42px; height: 42px; border-radius: 50%; border: 0;
  overflow: hidden; cursor: pointer; color: var(--send-ink);
  background: color-mix(in srgb, var(--amber) 32%, rgb(12, 15, 12)); display: grid; place-items: center;
  box-shadow: 0 10px 24px -8px color-mix(in srgb, var(--amber) 55%, transparent), var(--hi);
  transition: background .55s var(--ease), box-shadow .55s var(--ease), transform .26s var(--ease), filter .2s var(--ease),
    width .85s var(--ease), height .85s var(--ease); }
.pbox-send.pressed { transform: scale(.88); filter: brightness(.82); }
.pbox-send .btn-ai { position: absolute; inset: -7px; animation: none; transform: scale(1.6);
  transition: transform .7s var(--ease), opacity .45s var(--ease); }
.pbox-send .btn-ai i { animation-play-state: paused; }
.pbox-send:hover .btn-ai { transform: scale(1.08); }
.pbox-send:hover .btn-ai i { animation-play-state: running; }
.pbox.working .pbox-send .btn-ai, .pbox.noted .pbox-send .btn-ai, .pbox.note-out .pbox-send .btn-ai { opacity: 0; }
.pbox-send .arr { position: relative; z-index: 1; width: 19px; height: 19px; transition: opacity .3s var(--ease), transform .45s var(--ease); }
/* voice: the mic inside the pill. Hidden until the app activates voice; while
   on it wears the accent and a ring that breathes with the live level. */
.pbox-mic { position: relative; flex: 0 0 auto; width: 42px; height: 42px; border-radius: 50%; border: 0;
  cursor: pointer; color: var(--muted); background: transparent; display: grid; place-items: center;
  /* the circle stays a real 42px tap target, but its transparent padding must
     not cost the input its width: the negative margins pull the neighbours in
     over the circle's quiet edges, so the placeholder band runs almost to the
     mic's icon instead of stopping a whole button-radius short of it */
  margin: 0 -6px 0 -12px;
  transition: color .4s var(--ease), background .4s var(--ease), box-shadow .12s linear,
    width .85s var(--ease), height .85s var(--ease); }
.pbox-mic[hidden] { display: none; }
.pbox-mic svg { width: 19px; height: 19px; }
.pbox-mic:hover { color: var(--mist); }
.pbox[data-voice] .pbox-mic { color: var(--send-ink);
  /* Match the bright ground that sendInk() is measured against in theme.ts.
     The send orb covers its flat under-layer, so the mic must use this
     visible pair directly for the same per-merchant contrast guarantee. */
  background: color-mix(in srgb, var(--amber) 45%, #fff);
  box-shadow: 0 0 0 calc(var(--voice-level, 0) * 9px) color-mix(in srgb, var(--amber) 38%, transparent),
    0 10px 24px -8px color-mix(in srgb, var(--amber) 55%, transparent), var(--hi); }
.pbox[data-voice="transcribing"] .pbox-mic svg, .pbox[data-voice="hold"] .pbox-mic svg { animation: ai-breath 1.4s ease-in-out infinite; }
.pbox.working .pbox-mic { opacity: .55; }
.pbox.docked .pbox-mic, .pbox.centered .pbox-mic { width: 46px; height: 46px; }
#stage.lbopen ~ .pbox .pbox-mic { width: 34px; height: 34px; }
@media (prefers-reduced-motion: reduce) {
  .pbox[data-voice] .pbox-mic { box-shadow: 0 10px 24px -8px color-mix(in srgb, var(--amber) 55%, transparent), var(--hi); }
  .pbox[data-voice="transcribing"] .pbox-mic svg, .pbox[data-voice="hold"] .pbox-mic svg { animation: none; }
}
.ai { border-radius: 50%; animation: ai-sway 5.2s ease-in-out infinite; }
.ai i { position: absolute; inset: 0; border-radius: 50%; }
.ai .core { background: radial-gradient(circle at 50% 50%, #fff, color-mix(in srgb, var(--amber) 80%, #fff) 26%, color-mix(in srgb, var(--amber) 50%, transparent) 52%, transparent 70%);
  animation: ai-breath 2.3s ease-in-out infinite; }
.ai .swirl { background: conic-gradient(from 0deg, transparent 0deg 60deg, color-mix(in srgb, var(--amber) 85%, #fff) 140deg, transparent 220deg 360deg);
  filter: blur(3px); mix-blend-mode: screen; animation: ai-spin 1.9s linear infinite; }
.ai .swirl2 { background: conic-gradient(from 180deg, transparent 0deg 90deg, color-mix(in srgb, var(--amber) 55%, #fff) 170deg, transparent 240deg 360deg);
  filter: blur(4.5px); mix-blend-mode: screen; opacity: .8; animation: ai-spin 3.1s linear infinite reverse; }
.ai .mote { inset: 2px; background: radial-gradient(22% 22% at 68% 26%, rgba(255, 255, 255, .7), transparent 78%);
  filter: blur(3px); opacity: .6; mix-blend-mode: screen; animation: ai-spin 2.6s ease-in-out infinite; }
.pbox-orb { position: absolute; z-index: 2; left: calc(100% - 44px); top: 9px; width: 34px; height: 34px;
  opacity: 0; pointer-events: none;
  transition: opacity .5s var(--ease), left .5s var(--ease), top .5s var(--ease), width .5s var(--ease), height .5s var(--ease); }
.pbox.working .pbox-orb { opacity: 1; }
.pbox.noted .pbox-orb { opacity: 1; left: 18px; top: 15px; width: 22px; height: 22px;
  transition: opacity .45s var(--ease), left .95s var(--ease) .35s, top .95s var(--ease) .35s,
              width .95s var(--ease) .35s, height .95s var(--ease) .35s; }
.pbox.working .pbox-send { background: transparent; box-shadow: none; cursor: default; transform: none; }
.pbox.working .pbox-send .arr { opacity: 0; transform: scale(.5); }
@keyframes ai-spin { to { transform: rotate(360deg); } }
@keyframes ai-breath { 0%, 100% { transform: scale(.78); opacity: .85; } 50% { transform: scale(1.08); opacity: 1; } }
@keyframes ai-sway { 0%, 100% { transform: scale(.96); } 33% { transform: scale(1.04) translateY(-1px); } 66% { transform: scale(1) translateY(1px); } }
/* docked (cold-start): the same pill glides up into the scene's .pbox-slot */
.pbox.docked { bottom: var(--dock-bottom, 40vh); width: min(760px, 94vw); height: 74px; padding: 0 10px 0 30px; }
/* the scroll cue: while the pill is docked up in the landing slide the floor
   is empty — a quiet bobbing chevron says "this scrolls" (it is also a real
   button: tapping it takes the first step). Gone the moment the pill returns
   to the floor, a compose starts, or any overlay opens. */
.scroll-cue { position: fixed; left: 50%; bottom: 14px; transform: translateX(-50%); z-index: 54;
  width: 44px; height: 44px; display: grid; place-items: center; border: 0; padding: 0;
  background: transparent; color: var(--mist); cursor: pointer;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .6s var(--ease), visibility 0s linear .6s; }
.scroll-cue svg { width: 26px; height: 26px; animation: cue-bob 2.2s ease-in-out infinite;
  filter: drop-shadow(0 1px 6px rgba(0, 0, 0, .5)); }
body[data-fade="light"] .scroll-cue { color: #3a3f36; }
body[data-fade="light"] .scroll-cue svg { filter: none; }
body:has(.pbox.docked:not(.working):not(.noted):not(.note-out)) .scroll-cue {
  opacity: .85; visibility: visible; pointer-events: auto;
  transition: opacity .6s var(--ease) .4s, visibility 0s; }
body:has(.overlay.open) .scroll-cue,
body:has(#feedback[data-open="true"]) .scroll-cue,
body:has(#lb.open) .scroll-cue { opacity: 0 !important; pointer-events: none !important; }
@keyframes cue-bob { 0%, 100% { transform: translateY(-3px); opacity: .75; } 50% { transform: translateY(4px); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .scroll-cue svg { animation: none; } }
/* keyboard up (iOS): promptbox.ts drives an inline top in visual-viewport space,
   because a fixed element keeps layout coordinates while Safari scrolls the
   visual viewport to reveal the field. The pill must land on the keyboard's edge
   the same frame the keyboard moves, so the .85s pill clock stands down here. */
.pbox.kbd { transition: none; }
/* centered (the end-of-story ask): the same grown pill, dead centre — the
   page dims beneath it and the box asks what to go deeper on */
.pbox.centered { bottom: calc(50vh - 37px); width: min(760px, 94vw); height: 74px; padding: 0 10px 0 30px; }
.pbox.centered .pbox-chrome { background: rgba(11, 15, 12, .52);
  box-shadow: 0 34px 90px -24px rgba(0, 0, 0, .8), inset 0 0 0 1px rgba(242, 244, 238, .18), inset 0 1px 0 rgba(255, 255, 255, .08),
    0 0 70px -16px color-mix(in srgb, var(--amber) 38%, transparent); }
.pbox.centered .pbox-input { font-size: 17.5px; }
.pbox.centered .pbox-input::placeholder { color: color-mix(in srgb, var(--mist) 62%, transparent); }
.pbox.centered .pbox-send { width: 56px; height: 56px; }
.pbox.docked .pbox-chrome { background: rgba(11, 15, 12, .52);
  box-shadow: 0 34px 90px -24px rgba(0, 0, 0, .8), inset 0 0 0 1px rgba(242, 244, 238, .18), inset 0 1px 0 rgba(255, 255, 255, .08),
    0 0 70px -16px color-mix(in srgb, var(--amber) 38%, transparent); }
.pbox.docked .pbox-input { font-size: 17.5px; }
.pbox.docked .pbox-input::placeholder { color: color-mix(in srgb, var(--mist) 62%, transparent); }
.pbox.docked .pbox-send { width: 56px; height: 56px; }
/* narrow: the grown pill states (docked cold-start, centred end-of-story ask)
   keep the pill's REGULAR size — the 74px/17.5px growth was sized for a wide
   screen, and on a phone it only made the placeholder overflow its own band */
@media (max-width: 700px) {
  .pbox.docked, .pbox.centered { height: 52px; padding: 0 6px 0 20px; }
  .pbox.centered { bottom: calc(50vh - 26px); }
  .pbox.docked .pbox-input, .pbox.centered .pbox-input { font-size: 15.5px; }
  .pbox.docked .pbox-send, .pbox.centered .pbox-send,
  .pbox.docked .pbox-mic, .pbox.centered .pbox-mic { width: 42px; height: 42px; }
  .pbox.docked .pbox-echo, .pbox.docked .pbox-note,
  .pbox.centered .pbox-echo, .pbox.centered .pbox-note { right: 72px; }
  .pbox.docked.voiced .pbox-echo, .pbox.docked.voiced .pbox-note,
  .pbox.centered.voiced .pbox-echo, .pbox.centered.voiced .pbox-note { right: 100px; }
  .pbox-sugg { gap: 8px; }
  .pbox-sugg button { font-size: 12.5px; padding: 2px 6px; }
  /* A murmur has to be READABLE on a phone, and on a 345px pill it was not: the
     text band reserved room for the mic AND for a send button that the noted
     state has already made invisible, so a sentence the store means got cut to
     "The store only follows …", which is worse than silence. Found on the
     iPhone-mini plate of the voice-legibility lane (section 2.5: a state that
     reads as bolted-on is a defect even with green tests), and it was already
     true of the murmurs shipped before it.
     Three changes, all inside the pill's own vocabulary: while a line is up the
     band stops reserving the invisible button's room (it keeps the mic's, plus
     air), the type steps down to about the size the chips take at this width,
     and a sentence that still does not fit takes a second line instead of an
     ellipsis. Two lines is the ceiling, and it is what the pill's own height
     allows: a murmur is one thought, not a paragraph. */
  .pbox.noted:not(.docked):not(.centered) .pbox-note,
  .pbox.note-out:not(.docked):not(.centered) .pbox-note { right: 78px; }
  /* The cold-start and end-of-story pills keep their band exactly as it was:
     their two buttons are 56px each, so there is no room to reclaim without
     printing the line over the mic. They are also 74px tall, which is where the
     second line goes. */
  .pbox-note .txt { font-size: 12.5px; line-height: 1.3; white-space: normal;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
}

/* ------------------------------------------------------------ motion (video twins) */
/* backdrop twin: rides inside the .bg layer, so it inherits the crossfade,
   the zoom shell AND the ken-burns drift; the still stays painted underneath */
.bg .bgvid { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity 1100ms var(--xfade); }
.bg .bgvid.on { opacity: 1; }
/* the ken-burns drift over live footage reads as camera shake — freeze it
   while a twin plays (paused, not none: none would snap back to scale 1.04) */
.bg.vlive { animation-play-state: paused; }
/* inline twin: fills the hovered .shotm — the parent's mask + radius clip it */
.shotm { position: relative; }
.shotm .shotvid { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  border-radius: inherit; opacity: 0; transition: opacity .7s var(--xfade); pointer-events: none; }
.shotm .shotvid.on { opacity: 1; }
/* no breathing "this can move" dot on stills any more (2026-08-19): the mark
   read as a stray artifact, not an affordance — the hover/tap play speaks for
   itself */
/* lightbox twin: sized to the image's settled box by lightbox.js */
.lb-vid { position: fixed; object-fit: cover; border-radius: 10px; cursor: zoom-out; z-index: 61;
  opacity: 0; transition: opacity .9s var(--xfade); }
.lb-vid.on { opacity: 1; }

/* ------------------------------------------------------------ reduced motion */
@media (prefers-reduced-motion: reduce) {
  .bg, .al, .al i, .rstack { animation: none !important; }
  .pbox-echo i { animation: none; }
  /* the caption becomes plain show/hide: no ease-in on the first words, no fade
     out after a command, the line is simply there and then not */
  .pbox.captioned .pbox-echo { transition: none; }
  .ai, .ai i { animation-duration: 6s; }
  .pbox.noted .pbox-note .txt { animation: none; -webkit-mask-image: none; mask-image: none; }
  .ghost-card, .ghost-sec { animation: none; opacity: .75; }
  * { transition-duration: .25s !important; }
}
