/* ==========================================================================
   css/merch.css — THE SHOP ON merch.html. Sept 20 2026; rebuilt Oct 4 2026.

   WHY A SEVENTH STYLESHEET AND NOT MORE RULES IN css/purchase.css (Sept 20,
   still true): purchase.html would download rules for objects it never
   shows, and merch.html keeps its no-inline-<style> rule. A file that one
   page loads, for rules that one page uses, is the honest shape.

   WHAT IS IN IT SINCE OCT 4 2026. The four-across garment cards, the cart
   panel under them and the bar pinned to the bottom are gone. In their
   place, each with its own section below:
     THE SHOWROOM      one object at a time on a large stage, a rail of every
                       object beside it (under it on a phone), the name,
                       price, options and Add in the third column;
     THE STAGE         hover or swipe to turn the object, tap to open the
                       viewer, a pinch on a phone to zoom it in place;
     THE RAIL          the page's spine: numbered vertebrae, lit when the
                       object is in the cart;
     THE CART BUTTON   moved to css/components.css (every header has it);
     THE DRAWER        the cart, from the right edge;
     THE VIEWER        the full-screen print file, gestures only.

   WHAT IT KEPT. The hairlines (0.14 frame, 0.30 control), the square
   corners, the Plex Mono readouts and the white-filled "chosen" state of
   the old Front/Back toggle (now the size buttons and the view tabs), the
   display-face price, the dark field ground (rgba(3,4,15,0.62)), the 44px
   floor on every target, and the crimson left rule for a refusal.

   NOTHING HERE REACHES ANOTHER PAGE. The header's cart button, which
   every page now carries, is styled in css/components.css with the rest
   of the nav.
   ========================================================================== */

/* ==========================================================================
   THE SHOP SECTION. The page's h1 now heads the showroom directly; the copy
   that used to open the page sits in "The Line" under it (see merch.html).
   ========================================================================== */
.ks-shop {
  /* Same clearance the old intro section used for the fixed nav. */
  padding-top: calc(var(--space-24) + 3rem);
  /* A pinch on the stage scales the picture past the screen's sides, and a
     transform counts as scrollable overflow: in the first touch capture
     (Oct 4 2026, 390 emulated) the layout viewport widened to fit it and
     the fixed nav's Menu button slid from x 308 to 698 mid-pinch. clip,
     not hidden: it makes no scroll container, so the sticky stage and
     rail still stick, and it leaves the vertical axis alone. */
  overflow-x: clip;
}
.ks-shop__head { margin-bottom: var(--space-12); }
.ks-shop__head h1 { font-size: var(--fs-h1); }
@media (max-width: 768px) {
  .ks-shop { padding-top: calc(var(--space-16) + 3rem); }
  .ks-shop__head { margin-bottom: var(--space-8); }
}
.ks-shop__terms {
  margin: var(--space-12) 0 0;
  padding-top: var(--space-4);
  border-top: 1px solid rgba(214, 213, 208, 0.14);
  color: var(--text-muted);
}

/* ==========================================================================
   THE SHOWROOM.

   TWO LAYOUTS FROM ONE MARKUP. Without the script (.ks-showroom has no
   .is-live) every <article> is a row of its own: stage and info side by
   side, every view of the object shown, nothing hidden that a buyer
   needs. js/merch-showroom.js adds .is-live and the page becomes a single
   stage: the articles' wrapper and the chosen article are display:contents,
   so the chosen object's stage and info become cells of the showroom's own
   grid, next to the rail -- that is how the rail sits BETWEEN the stage and
   the info on a phone without living inside every article.

   900 IS THE FOLD, not 768: at 768 the third column would be 300px and the
   stage under 400, smaller than it is stacked at full width.
   ========================================================================== */
.ks-product {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: var(--space-8) clamp(var(--space-8), 4vw, var(--space-16));
  align-items: start;
  margin-bottom: var(--space-16);
}
@media (max-width: 899px) {
  .ks-product { grid-template-columns: minmax(0, 1fr); }
}

.ks-showroom.is-live {
  display: grid;
  grid-template-columns: 84px minmax(0, 1.4fr) minmax(300px, 1fr);
  grid-template-areas: "rail stage info";
  column-gap: clamp(var(--space-6), 3.2vw, var(--space-12));
  align-items: start;
}
.ks-showroom.is-live .ks-products { display: contents; }
.ks-showroom.is-live .ks-product { display: none; }
.ks-showroom.is-live .ks-product.is-active { display: contents; }
.ks-showroom.is-live .ks-product__stage { grid-area: stage; }
.ks-showroom.is-live .ks-product__info { grid-area: info; }
.ks-showroom.is-live .ks-rail { grid-area: rail; }

/* Desktop: the rail and the stage hold still while a long info column
   scrolls past them. --nav-h-max, never --nav-h: see tokens.css. */
@media (min-width: 900px) {
  .ks-showroom.is-live .ks-product__stage,
  .ks-showroom.is-live .ks-rail {
    position: sticky;
    top: calc(var(--nav-h-max) + var(--space-4));
  }
}

@media (max-width: 899px) {
  .ks-showroom.is-live {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "stage" "rail" "info";
  }
}

/* The kinds filter (Tees / Hats / Patches). Built by the script only when
   the page carries two or more kinds; until then it does not exist. Same
   chip face as the size buttons. */
.ks-kinds {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-6);
}
.ks-kinds__btn {
  min-height: 44px;
  padding: 0 var(--space-4);
  background: transparent;
  border: 1px solid rgba(214, 213, 208, 0.30);
  border-radius: var(--radius-sharp);
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  color: var(--text-secondary);
  cursor: pointer;
}
.ks-kinds__btn[aria-pressed="true"] { background: var(--color-white); border-color: var(--color-white); color: var(--color-black); }
.ks-kinds__btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* ON THE RAIL'S COLUMN (owner, Oct 5 2026: the boxes were "offset from the
   column" of objects). Each box is at least a vertebra wide -- 64px, the
   desktop .ks-rail__item -- and at the desktop grid the row steps in by
   half the difference between the rail's 84px track and that 64px, so All
   sits square over the column. Measured before: All at the track's edge
   (104 at 1440), the vertebrae 10px in (114), All 58.5px wide. Change the
   84px, the 64px or the 60px (the phone vertebra) and change these. */
.ks-kinds__btn { min-width: 64px; }
@media (min-width: 900px) {
  .ks-kinds { margin-left: calc((84px - 64px) / 2); }
}
@media (max-width: 899px) {
  /* The phone row: the bar and the vertebrae already share the content's
     left edge; the box matches the 60px vertebra. */
  .ks-kinds__btn { min-width: 60px; }
}

/* ==========================================================================
   THE STAGE.

   A square, because the mockups are 1000x1000. The ground is the edition
   card's own scrim with a low light behind the object -- the "aura" -- that
   rises a little under the pointer, and four corner ticks in place of a
   frame: the stage reads as an instrument looking at the object, the same
   register as the archive's readouts, rather than as a box around it.

   THE VIEWS ARE STACKED, AND THE SCRIPT PICKS HOW THEY MOVE (data-mode):
     slide  touch screens. Each view sits at (its index - the current one)
            stage-widths across and follows the thumb by --ks-dx px while it
            drags; let go and it settles. Overflow is clipped, so the
            neighbours wait just off the edge.
     fade   a mouse. Hovering crossfades to the next view; leaving crossfades
            back. A slide under a pointer that is only passing over reads as
            the page lurching; a fade reads as the object turning.
   One view: nothing to turn to. The mouse gets a slow push toward the
   print instead, so the stage still answers the pointer.

   touch-action: pan-y hands the page its vertical scroll and keeps every
   sideways movement (the swipe) and the pinch for the script.
   ========================================================================== */
.ks-product__stage {
  position: relative;
  aspect-ratio: 1 / 1;
  width: 100%;
  overflow: hidden;
  background:
    radial-gradient(58% 52% at 50% 46%, rgba(228, 232, 235, 0.075) 0%, rgba(228, 232, 235, 0) 72%),
    linear-gradient(180deg, rgba(3, 4, 15, 0.42) 0%, rgba(3, 4, 15, 0.70) 100%);
  border: 1px solid rgba(214, 213, 208, 0.08);
}
/* The no-script row: every view side by side, no stacking. */
.ks-showroom:not(.is-live) .ks-product__stage {
  aspect-ratio: auto;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
}
.ks-product__view {
  margin: 0;
}
.ks-product__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
}

/* Corner ticks, one pseudo-element, eight gradients. Above the views
   (z-index 2) so a mockup's edge never covers them. */
.ks-product__stage::before {
  content: '';
  position: absolute;
  inset: 12px;
  z-index: 2;
  pointer-events: none;
  --ks-tick: rgba(214, 213, 208, 0.42);
  --ks-tick-l: 16px;
  background:
    linear-gradient(var(--ks-tick), var(--ks-tick)) left top / var(--ks-tick-l) 1px no-repeat,
    linear-gradient(var(--ks-tick), var(--ks-tick)) left top / 1px var(--ks-tick-l) no-repeat,
    linear-gradient(var(--ks-tick), var(--ks-tick)) right top / var(--ks-tick-l) 1px no-repeat,
    linear-gradient(var(--ks-tick), var(--ks-tick)) right top / 1px var(--ks-tick-l) no-repeat,
    linear-gradient(var(--ks-tick), var(--ks-tick)) left bottom / var(--ks-tick-l) 1px no-repeat,
    linear-gradient(var(--ks-tick), var(--ks-tick)) left bottom / 1px var(--ks-tick-l) no-repeat,
    linear-gradient(var(--ks-tick), var(--ks-tick)) right bottom / var(--ks-tick-l) 1px no-repeat,
    linear-gradient(var(--ks-tick), var(--ks-tick)) right bottom / 1px var(--ks-tick-l) no-repeat;
}
/* The aura's rise: a second light, faded in under the pointer. */
.ks-product__stage::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(46% 42% at 50% 44%, rgba(228, 232, 235, 0.09) 0%, rgba(228, 232, 235, 0) 70%);
  opacity: 0;
  transition: opacity var(--motion-slow) var(--ease-standard);
}

.ks-showroom.is-live .ks-product__stage {
  touch-action: pan-y;
  cursor: zoom-in;
  -webkit-tap-highlight-color: transparent;
}
.ks-showroom.is-live .ks-product__view {
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* ==========================================================================
   LAYERS: MADE AT THE FIRST PAINT, KEPT, NEVER SWAPPED. Oct 4 2026.

   The owner, on Android (Brave), the night of the release: the picture
   "shows for a second, then glitches, and then reprints". The home hero and
   the Music carousel did the same on Sept 25 (V2HANDOFF 68, and the long
   note at the top of scripts/test-hero-layers.py): a phone puts a frame on
   screen before a compositor layer that was JUST created has been
   rastered, so anything that is given a layer of its own after the first
   paint -- or loses one while it is being shown -- is missing for a frame.
   Desktop Chromium waits for the raster, so no capture here shows the
   glitch; the precondition can be checked, and test-hero-layers.py now
   checks it on this page too.

   Traced before this fix (CDP LayerTree at 412x839, 2.625, Android's
   --enable-prefer-compositing-to-lcd-text, the hero test's set-up), each a
   layer made or dropped while its content was on screen:
     - the shirt: painted into the page at first paint, moved into a new
       layer 6-65ms later when the script set data-mode (a translate3d);
     - a pinch: the stage, the picture and the veil got layers at the first
       touch and lost them at the settle;
     - choosing another object: the picture's materialise and each line of
       the info column's rise ran on a layer of its own and dropped it when
       the animation ended -- the reprint -- and took the footer with them;
     - the rail's thumbnails, the stage hint, the Add sweep, the drawer's new
       line and its ticks, the header's vertebrae, the drawer's backdrop.

   The carousel's rule, applied: what needs a layer has it from the first
   paint, in CSS that applies before any script (the inline script after
   the showroom sets .is-live, data-mode, each view's --ks-i and the first
   .is-current, so the scripts change nothing the first paint showed);
   every animation runs on an element that already owns its layer, or on
   properties that need none (fill, background, box-shadow, left); and
   nothing that has to come back is taken to opacity 0, which is not
   rastered -- 0.01 is. A new object's layers are made while it is still
   at opacity 0 (its arrival), so their first, unrastered frame is the
   frame that was meant to be empty.
   ========================================================================== */
.ks-showroom.is-live .ks-product__view { will-change: transform; }
.ks-showroom.is-live .ks-product__stage::before,
.ks-stage__num,
.ks-stage__zoom,
.ks-stage__views,
.ks-stage__hint { will-change: opacity; }

/* slide */
.ks-product__stage[data-mode="slide"] .ks-product__view {
  transform: translate3d(calc((var(--ks-i, 0) - var(--ks-cur, 0)) * 100% + var(--ks-dx, 0) * 1px), 0, 0);
  transition: transform 560ms cubic-bezier(0.16, 1, 0.3, 1);
}
.ks-product__stage[data-mode="slide"].is-dragging .ks-product__view { transition: none; }

/* fade */
.ks-product__stage[data-mode="fade"] .ks-product__view {
  /* 0.01, not 0: the side not shown stays rastered, so the turn under the
     pointer is an opacity change on a ready layer (the LAYERS note).
     Measured at 1440 with the sky frozen and the stage made opaque: the
     back at 0.01 against the back at 0 differs by 2 levels at most, 0
     pixels by more than 2. No ghost. */
  opacity: 0.01;
  transform: scale(0.985);
  transition: opacity 620ms var(--ease-standard), transform 900ms cubic-bezier(0.16, 1, 0.3, 1);
}
.ks-product__stage[data-mode="fade"] .ks-product__view.is-current {
  opacity: 1;
  transform: none;
}

@media (hover: hover) and (pointer: fine) {
  .ks-showroom.is-live .ks-product__stage::after { will-change: opacity; }
  .ks-showroom.is-live .ks-product__stage:hover::after { opacity: 1; }
  /* On the view, which owns the layer, not on the <img> inside it, which
     would be given one for the push and lose it at the end. */
  .ks-product__stage[data-mode="fade"][data-views="1"]:hover .ks-product__view.is-current { transform: scale(1.045); }
}

/* PINCH IN PLACE (phones). While two fingers are down the stage stops
   clipping and the CURRENT VIEW -- which has owned its layer since the
   first paint -- rises over the veil; the script scales and moves that
   view (inline transform, .is-pinching: no transition). On release the
   inline transform goes and .is-peeking's own transition takes it home.
   The stage itself is not raised: until Oct 4 it was, and that made it a
   new layer at every pinch (the LAYERS note). The other side is held at
   0.01, never display: none, which dropped its layer. */
.ks-product__stage.is-peeking {
  overflow: visible;
  cursor: default;
}
.ks-product__stage.is-peeking .ks-product__view.is-current { z-index: 70; }
.ks-product__stage.is-peeking .ks-product__view:not(.is-current) { opacity: 0.01; }
.ks-product__stage.is-peeking .ks-product__view { transition: transform 420ms cubic-bezier(0.16, 1, 0.3, 1); }
.ks-product__stage.is-pinching .ks-product__view { transition: none; }
.ks-product__stage.is-peeking::before,
.ks-product__stage.is-peeking .ks-stage__num,
.ks-product__stage.is-peeking .ks-stage__zoom,
.ks-product__stage.is-peeking .ks-stage__views,
.ks-product__stage.is-peeking .ks-stage__hint { opacity: 0; }
.ks-peek-veil {
  position: fixed;
  inset: 0;
  z-index: 65;
  background: rgba(3, 4, 15, 0.86);
  opacity: 0;
  pointer-events: none;
  transition: opacity 260ms var(--ease-standard);
  will-change: opacity;
}
.ks-peek-veil.is-on { opacity: 1; }

/* The stage's own controls, put there by the script: the object's number
   (top left), Zoom (top right), the view tabs (bottom centre) and a hint
   (bottom left) that fades once the stage has been used. */
.ks-stage__num,
.ks-stage__hint {
  position: absolute;
  z-index: 3;
  margin: 0;
  pointer-events: none;
  color: var(--text-muted);
}
.ks-stage__num { top: 22px; left: 26px; color: var(--text-secondary); }
/* Bottom left, and allowed to wrap: the tabs hold the bottom right, and
   at 390 the touch hint is two lines beside them rather than one line
   under them (they collided at 1440 when the tabs were centred, seen in
   the first capture on Oct 4 2026). */
.ks-stage__hint {
  left: 26px;
  bottom: 22px;
  max-width: calc(100% - 210px);
  line-height: 1.5;
}
.ks-product__stage[data-views="1"] .ks-stage__hint { max-width: calc(100% - 52px); }
/* THREE SIDES (the hats, Oct 5 2026): a third tab is 68px more of the
   bottom right (64px min-width + the 4px gap), so the hint gives up the
   same 68px and keeps the two-tab stages' clearance. Measured before this
   rule: the hint ran 46px under the tabs at 320-412 and 5-38px at
   900-1024. */
.ks-product__stage[data-views="3"] .ks-stage__hint { max-width: calc(100% - 278px); }
.ks-product__stage.is-used .ks-stage__hint { opacity: 0; }
.ks-stage__zoom {
  position: absolute;
  z-index: 3;
  top: 10px;
  right: 10px;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  color: var(--text-secondary);
  cursor: pointer;
  transition: color var(--motion-fast) var(--ease-standard);
}
.ks-stage__zoom:hover { color: var(--text-primary); }
.ks-stage__zoom:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -4px; }
.ks-stage__zoom svg { width: 18px; height: 18px; }

.ks-stage__views {
  position: absolute;
  z-index: 3;
  right: 22px;
  bottom: 12px;
  display: flex;
  gap: var(--space-1);
}
.ks-stage__view {
  min-width: 64px;
  min-height: 44px;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 0 var(--space-2);
  background: transparent;
  border: 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--motion-base) var(--ease-standard);
}
.ks-stage__view::after {
  content: '';
  display: block;
  width: 100%;
  height: 2px;
  background: rgba(214, 213, 208, 0.22);
  transition: background var(--motion-base) var(--ease-standard), box-shadow var(--motion-base) var(--ease-standard);
}
.ks-stage__view[aria-pressed="true"] { color: var(--text-primary); }
.ks-stage__view[aria-pressed="true"]::after {
  background: var(--color-white);
  box-shadow: 0 0 8px rgba(228, 232, 235, 0.55);
}
.ks-stage__view:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* Arrival: a newly chosen object's stage and info come up out of the dark
   instead of snapping in -- the weight the owner asked for is mostly this,
   and the hairline sweep on Add. */
/* ON THE VIEW, NOT THE <img> (Oct 4 2026): the view has owned a layer
   since it was shown, and an animation on it changes nothing about the
   layer tree when it ends. On the <img> it ran on a layer of its own and
   dropped it at the end -- the owner's "reprint". The scale went with the
   move: the view's transform is the slide's. The info column's staggered
   rise is gone for the same reason -- each line was a layer for 640ms and
   then wasn't -- and the text now changes at once; the ticks and the stage
   controls come up with the picture, from 0, so their first unrastered
   frame is an empty one. */
.ks-product.is-arriving .ks-product__view.is-current {
  animation: ks-materialise 760ms cubic-bezier(0.16, 1, 0.3, 1);
}
.ks-product.is-arriving .ks-product__stage::before,
.ks-product.is-arriving .ks-stage__num,
.ks-product.is-arriving .ks-stage__zoom,
.ks-product.is-arriving .ks-stage__views,
.ks-product.is-arriving .ks-stage__hint {
  animation: ks-appear 420ms var(--ease-standard) backwards;
}
@keyframes ks-materialise {
  from { opacity: 0; filter: blur(6px) brightness(0.6); }
  to   { opacity: 1; filter: none; }
}
@keyframes ks-appear {
  from { opacity: 0; }
}

/* ==========================================================================
   THE RAIL — the page's spine.

   A column of vertebrae at desktop, a row on a phone, with the spine's
   line running through their centres. Each is a button: the object's
   picture on the stage's ground, its number in the corner. The chosen one
   is ringed in Spine Glow. One in the cart is LIT: two short processes
   reach out from it, across the line, in the light the header's vertebrae
   use. The rail scrolls when it outgrows the stage; it never wraps.
   ========================================================================== */
.ks-showroom:not(.is-live) .ks-rail { display: none; }
.ks-rail {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: 2px 0;
  max-height: min(680px, calc(100svh - var(--nav-h-max) - var(--space-8)));
  overflow-y: auto;
  scrollbar-width: none;
}
.ks-rail::-webkit-scrollbar { display: none; }
.ks-rail::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 1px;
  background: linear-gradient(180deg, rgba(214, 213, 208, 0) 0%, rgba(214, 213, 208, 0.22) 10%, rgba(214, 213, 208, 0.22) 90%, rgba(214, 213, 208, 0) 100%);
  pointer-events: none;
}
.ks-rail__item {
  position: relative;
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  padding: 0;
  background: var(--color-black);
  border: 1px solid rgba(214, 213, 208, 0.16);
  border-radius: var(--radius-sharp);
  cursor: pointer;
  transition: border-color var(--motion-base) var(--ease-standard), box-shadow var(--motion-base) var(--ease-standard);
}
.ks-rail__item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* No opacity transition: it gave each thumbnail a layer for 300ms on
     every choice (the LAYERS note). The ring and the processes still
     ease -- border, box-shadow and background need no layer. */
  opacity: 0.62;
}
.ks-rail__item:hover { border-color: rgba(214, 213, 208, 0.42); }
.ks-rail__item:hover img { opacity: 0.9; }
.ks-rail__item[aria-current="true"] {
  border-color: var(--color-white);
  box-shadow: 0 0 0 1px rgba(228, 232, 235, 0.25), 0 0 18px rgba(228, 232, 235, 0.16);
}
.ks-rail__item[aria-current="true"] img { opacity: 1; }
.ks-rail__item:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }
.ks-rail__n {
  position: absolute;
  left: 4px;
  bottom: 3px;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: var(--tracking-mono);
  color: var(--text-muted);
  pointer-events: none;
}
.ks-rail__item[aria-current="true"] .ks-rail__n { color: var(--text-primary); }
/* The lit processes. Horizontal across a vertical spine. */
.ks-rail__item::before,
.ks-rail__item::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 9px;
  height: 2px;
  margin-top: -1px;
  background: rgba(214, 213, 208, 0.22);
  transition: background var(--motion-slow) var(--ease-standard), box-shadow var(--motion-slow) var(--ease-standard);
}
.ks-rail__item::before { right: 100%; margin-right: 1px; }
.ks-rail__item::after { left: 100%; margin-left: 1px; }
.ks-rail__item.is-lit::before,
.ks-rail__item.is-lit::after {
  background: var(--color-white);
  box-shadow: 0 0 6px rgba(228, 232, 235, 0.9);
}
.ks-rail__item.is-lit { border-color: rgba(228, 232, 235, 0.55); }
.ks-rail__item.is-lit[aria-current="true"] { border-color: var(--color-white); }

@media (max-width: 899px) {
  /* The row: the spine runs sideways, so the processes stand up and down.
     Bled to the screen edges so a long row scrolls under the thumb from
     edge to edge; the padding puts the first vertebra on the content line. */
  .ks-rail {
    flex-direction: row;
    gap: var(--space-4);
    max-height: none;
    overflow-x: auto;
    overflow-y: visible;
    margin: var(--space-4) calc(-1 * var(--container-pad)) var(--space-6);
    padding: 14px var(--container-pad);
    scroll-snap-type: x proximity;
    scroll-padding-inline: var(--container-pad);
  }
  .ks-rail::before {
    left: 0;
    right: 0;
    top: 50%;
    bottom: auto;
    width: auto;
    height: 1px;
    background: linear-gradient(90deg, rgba(214, 213, 208, 0) 0%, rgba(214, 213, 208, 0.22) 8%, rgba(214, 213, 208, 0.22) 92%, rgba(214, 213, 208, 0) 100%);
  }
  /* The row's height is held while the script fills it, so the info
     column does not jump down when the vertebrae arrive. */
  .ks-rail { min-height: 88px; }
  .ks-rail__item { width: 60px; height: 60px; scroll-snap-align: start; }
  .ks-rail__item::before,
  .ks-rail__item::after {
    left: 50%;
    top: auto;
    width: 2px;
    height: 9px;
    margin: 0 0 0 -1px;
    right: auto;
  }
  .ks-rail__item::before { bottom: 100%; margin-bottom: 1px; }
  .ks-rail__item::after { top: 100%; margin-top: 1px; }
}

/* ==========================================================================
   THE INFO COLUMN. The edition card's furniture without its frame: the
   inset hairline over the number, the number sliding on hover, the
   display-face price over its own rule. The name is set larger than a card
   title because it is the only one on screen.
   ========================================================================== */
.ks-product__info::before {
  content: '';
  display: block;
  height: 1px;
  margin-bottom: var(--space-4);
  background: rgba(214, 213, 208, 0.22);
}
.ks-product__cat {
  margin: 0 0 var(--space-4);
  color: var(--text-secondary);
}
.ks-product__name {
  margin: 0 0 var(--space-4);
  font-size: clamp(1.9rem, 3.1vw, 3rem);
  line-height: 0.95;
  letter-spacing: var(--tracking-display);
  color: var(--text-primary);
}
.ks-product__price {
  margin: 0 0 var(--space-6);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid rgba(214, 213, 208, 0.14);
  font-family: var(--font-display);
  font-weight: var(--display-wght);
  font-stretch: var(--display-stretch);
  font-variation-settings: "wdth" var(--display-wdth), "wght" var(--display-wght);
  font-size: 2.25rem;
  line-height: 1;
  letter-spacing: var(--tracking-display);
  color: var(--text-primary);
}
.ks-product__desc {
  margin: 0 0 var(--space-6);
  max-width: 46ch;
  color: var(--text-secondary);
}

/* THE SIZE BUTTONS. Radios, for the reason the old Front/Back pair gave:
   one-of-many is what a radio group is, arrow keys move through it and it
   announces itself with no script. The input is visually hidden but
   focusable; the label is what the eye and the thumb meet, 48px tall, and
   the chosen one is filled white -- the toggle's face. They replace a
   native <select>, which on a phone meant the OS picker sheet for a choice
   of five. */
.ks-product__options {
  min-width: 0;
  margin: 0 0 var(--space-4);
  padding: 0;
  border: 0;
}
.ks-product__legend {
  display: block;
  padding: 0;
  margin-bottom: var(--space-3);
}
.ks-chips {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(52px, 1fr));
  gap: var(--space-2);
}
.ks-chip__input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.ks-chip {
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 0 var(--space-2);
  border: 1px solid rgba(214, 213, 208, 0.30);
  border-radius: var(--radius-sharp);
  font-family: var(--font-mono);
  font-size: var(--fs-caption);
  letter-spacing: var(--tracking-mono);
  color: var(--text-primary);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  transition: background var(--motion-fast) var(--ease-standard),
              color var(--motion-fast) var(--ease-standard),
              border-color var(--motion-fast) var(--ease-standard);
}
.ks-chip:hover { border-color: rgba(214, 213, 208, 0.62); }
.ks-chip__plus {
  font-size: 0.625rem;
  color: var(--text-muted);
}
.ks-chip__input:checked + .ks-chip {
  background: var(--color-white);
  border-color: var(--color-white);
  color: var(--color-black);
}
.ks-chip__input:checked + .ks-chip .ks-chip__plus { color: rgba(3, 4, 15, 0.62); }
.ks-chip__input:focus-visible + .ks-chip { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* Add pressed before a size: the buttons draw the eye once, in the red the
   site keeps for an interruption, and the status line says why. */
.ks-product__options.is-wanting .ks-chip {
  animation: ks-want 900ms var(--ease-standard);
}
@keyframes ks-want {
  0%, 100% { border-color: rgba(214, 213, 208, 0.30); }
  25%, 60% { border-color: rgba(161, 51, 62, 0.95); }
}

/* ADD. Full width, a little taller than a standard button. On a good add
   a band of light crosses it once (.is-added) -- the press lighting
   something -- while the drawer opens with the vertebra it lit. */
.ks-product__add {
  position: relative;
  width: 100%;
  min-height: 52px;
  justify-content: center;
  overflow: hidden;
}
.ks-product__add::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -40%;
  width: 40%;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.85) 50%, rgba(255, 255, 255, 0) 100%);
  pointer-events: none;
}
/* `left` only, no opacity (Oct 4 2026): an opacity keyframe gave the band a
   layer for the length of the sweep. Parked at -40% it is outside the
   button, which clips it, and its own gradient fades its edges in. */
.ks-product__add.is-added::after { animation: ks-sweep 720ms cubic-bezier(0.4, 0, 0.2, 1); }
@keyframes ks-sweep {
  from { left: -40%; }
  to   { left: 110%; }
}
.ks-product__add[aria-busy="true"] { opacity: 0.6; cursor: progress; }

/* The status line under a control: a margin note with a single left rule,
   crimson for a refusal, moonlight for a confirmation. Shared with the
   drawer's checkout status. */
.ks-product__status {
  margin: var(--space-3) 0 0;
  padding-left: var(--space-3);
  border-left: 1px solid rgba(161, 51, 62, 0.55);
  font-size: var(--fs-caption);
  line-height: 1.5;
  color: var(--text-secondary);
}
/* EMPTY, NOT HIDDEN (Oct 4 2026, V2HANDOFF 73 L10). The line is a role=status
   live region, and a region shown and filled in the same tick is one some
   screen readers never announce. So it stays in the page and empty, and an
   empty one takes no room and draws no rule. display: none would take it out
   of the accessibility tree again; [hidden] stays only as a fallback. */
.ks-product__status:empty { margin: 0; padding: 0; border: 0; }
.ks-product__status[hidden] { display: none; }
.ks-product__status--ok { border-left-color: rgba(157, 178, 192, 0.55); }

/* The particulars, as readouts. */
.ks-product__spec {
  margin: var(--space-8) 0 0;
  border-top: 1px solid rgba(214, 213, 208, 0.14);
}
.ks-product__spec > div {
  display: grid;
  grid-template-columns: 6.5em 1fr;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: 1px solid rgba(214, 213, 208, 0.08);
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
}
.ks-product__spec dt { color: var(--text-muted); }
.ks-product__spec dd { margin: 0; color: var(--text-secondary); }

@media (max-width: 899px) {
  /* A phone: the stage runs edge to edge -- the object is the page -- and
     the info column follows the rail. */
  .ks-showroom.is-live .ks-product__stage {
    width: auto;
    margin-inline: calc(-1 * var(--container-pad));
    border-inline: 0;
  }
  .ks-stage__num { top: 18px; left: 20px; }
  .ks-stage__hint { left: 20px; bottom: 18px; max-width: calc(100% - 190px); }
  .ks-product__stage[data-views="3"] .ks-stage__hint { max-width: calc(100% - 258px); }
  .ks-stage__views { right: 16px; bottom: 10px; }
  .ks-product__name { font-size: clamp(1.9rem, 8.4vw, 2.6rem); }
}

/* ==========================================================================
   THE CART BUTTON lives in css/components.css since Oct 4 2026, when it
   went into every page's header; this file kept it only while the merch
   page was the one page with a cart. The drawer it opens is still here.
   ========================================================================== */

/* ==========================================================================
   THE DRAWER — the cart.

   From the right edge, the full height; the whole width on a phone. The
   panel is a column: the heading, the seven ticks and their chakra, the
   lines (the only part that scrolls), and the foot with the totals and
   Checkout, which never leaves the bottom of the screen. The ground is
   Archive Black itself, opaque, so the page does not ghost through.

   It slides in on [open] with a keyframe rather than a transition, because
   a <dialog> goes from display:none to shown in one step and a transition
   has no "from" to run from; closing is .is-closing, run by the script,
   which calls close() when it ends.
   ========================================================================== */
.ks-drawer {
  /* Its own layer for as long as it is open, so the slide ends on the
     layer it started on; the backdrop likewise (the LAYERS note). */
  will-change: transform;
  position: fixed;
  inset: 0 0 0 auto;
  width: min(460px, 100vw);
  max-width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-height: none;
  margin: 0 0 0 auto;
  padding: 0;
  border: 0;
  border-left: 1px solid rgba(214, 213, 208, 0.18);
  background: var(--color-black);
  color: var(--text-primary);
  overflow: hidden;
}
.ks-drawer:not([open]) { display: none; }
.ks-drawer[open] { animation: ks-drawer-in 560ms cubic-bezier(0.16, 1, 0.3, 1); }
.ks-drawer.is-closing { animation: ks-drawer-out 300ms cubic-bezier(0.4, 0, 1, 1) forwards; }
.ks-drawer::backdrop {
  will-change: opacity;
  background: rgba(3, 4, 15, 0.74);
  animation: ks-fade-in 400ms var(--ease-standard);
}
.ks-drawer.is-closing::backdrop { animation: ks-fade-out 300ms var(--ease-standard) forwards; }
@keyframes ks-drawer-in { from { transform: translateX(100%); } to { transform: none; } }
@keyframes ks-drawer-out { from { transform: none; } to { transform: translateX(100%); } }
@keyframes ks-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ks-fade-out { from { opacity: 1; } to { opacity: 0; } }

.ks-drawer__panel {
  display: flex;
  flex-direction: column;
  height: 100%;
}
.ks-drawer__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-6) var(--space-6) var(--space-4);
}
.ks-drawer__head .label { display: block; margin-bottom: var(--space-2); color: var(--text-muted); }
.ks-drawer__title {
  margin: 0;
  font-size: var(--fs-h2);
  line-height: 1;
}
.ks-drawer__count {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-mono);
  font-stretch: normal;
  font-variation-settings: normal;
  letter-spacing: var(--tracking-mono);
  color: var(--text-muted);
  vertical-align: 0.5em;
  margin-left: var(--space-2);
}
.ks-drawer__close,
.ks-detail__close {
  flex: none;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: calc(-1 * var(--space-2)) calc(-1 * var(--space-2)) 0 0;
  background: transparent;
  border: 1px solid rgba(214, 213, 208, 0.30);
  border-radius: var(--radius-sharp);
  color: var(--text-secondary);
  cursor: pointer;
}
.ks-drawer__close:hover,
.ks-detail__close:hover { background: rgba(214, 213, 208, 0.10); color: var(--text-primary); }
.ks-drawer__close:focus-visible,
.ks-detail__close:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.ks-drawer__close svg,
.ks-detail__close svg { width: 14px; height: 14px; }

/* The seven ticks: the header's vertebrae laid on their side, root at the
   left. */
.ks-drawer__spine {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0 var(--space-6) var(--space-4);
  border-bottom: 1px solid rgba(214, 213, 208, 0.14);
}
.ks-drawer__ticks {
  display: flex;
  gap: 5px;
  flex: none;
}
.ks-drawer__ticks span {
  display: block;
  width: 16px;
  height: 6px;
  background: rgba(214, 213, 208, 0.16);
  transition: background var(--motion-slow) var(--ease-standard), box-shadow var(--motion-slow) var(--ease-standard);
}
.ks-drawer__ticks span.is-lit {
  background: var(--color-white);
  box-shadow: 0 0 8px rgba(228, 232, 235, 0.7);
}
.ks-drawer__ticks span.is-igniting { animation: ks-tick-ignite 1100ms cubic-bezier(0.16, 1, 0.3, 1); }
/* Background and glow only, no scale: the scale made each tick a layer
   for the length of the flare (the LAYERS note). */
@keyframes ks-tick-ignite {
  0%   { background: rgba(214, 213, 208, 0.16); box-shadow: none; }
  20%  { background: #FFFFFF; box-shadow: 0 0 18px 4px rgba(255, 255, 255, 0.95); }
  100% { background: var(--color-white); box-shadow: 0 0 8px rgba(228, 232, 235, 0.7); }
}
.ks-drawer__chakra {
  margin: 0;
  min-width: 0;
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ks-drawer__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0 var(--space-6);
}
.ks-drawer__empty {
  padding: var(--space-12) 0;
  color: var(--text-secondary);
}
.ks-drawer__empty[hidden] { display: none; }
.ks-drawer__empty p { margin: 0 0 var(--space-6); max-width: 30ch; }

.ks-cart__lines {
  list-style: none;
  margin: 0;
  padding: 0;
}
.ks-cart__lines[hidden] { display: none; }
.ks-cart__line {
  display: grid;
  grid-template-columns: 76px minmax(0, 1fr) auto;
  grid-template-areas:
    "thumb main  total"
    "thumb qty   remove";
  align-items: center;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-4) 0;
  border-bottom: 1px solid rgba(214, 213, 208, 0.10);
}
/* A tint that fades, not a rise: the rise was a layer for 560ms that then
   went away (the LAYERS note). Background is paint only. */
.ks-cart__line.is-new { animation: ks-line-new 1400ms var(--ease-standard); }
@keyframes ks-line-new {
  from { background-color: rgba(228, 232, 235, 0.10); }
  to   { background-color: rgba(228, 232, 235, 0); }
}
.ks-cart__thumb {
  grid-area: thumb;
  align-self: start;
  width: 76px;
  height: 76px;
  padding: 0;
  border: 1px solid rgba(214, 213, 208, 0.14);
  background: radial-gradient(60% 55% at 50% 46%, rgba(228, 232, 235, 0.08) 0%, rgba(228, 232, 235, 0) 72%), rgba(3, 4, 15, 0.9);
  cursor: pointer;
}
.ks-cart__thumb img { display: block; width: 100%; height: 100%; object-fit: contain; }
.ks-cart__thumb:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.ks-cart__line-main {
  grid-area: main;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}
.ks-cart__line-name {
  color: var(--text-primary);
  font-weight: 600;
  line-height: 1.3;
}
.ks-cart__line-meta {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  color: var(--text-muted);
}
.ks-cart__line-total {
  grid-area: total;
  align-self: start;
  text-align: right;
  font-family: var(--font-mono);
  color: var(--text-primary);
}
.ks-cart__qty {
  grid-area: qty;
  justify-self: start;
  display: inline-flex;
  align-items: center;
  border: 1px solid rgba(214, 213, 208, 0.30);
  border-radius: var(--radius-sharp);
}
.ks-cart__qty-btn {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--fs-body-lg);
  line-height: 1;
  cursor: pointer;
}
.ks-cart__qty-btn:hover { background: rgba(214, 213, 208, 0.10); }
.ks-cart__qty-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.ks-cart__qty-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.ks-cart__qty-n {
  min-width: 2em;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--fs-body);
  color: var(--text-primary);
}
.ks-cart__remove,
.ks-cart__clear {
  min-height: 44px;
  padding: 0 var(--space-1);
  background: transparent;
  border: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  color: var(--text-muted);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
}
.ks-cart__remove { grid-area: remove; justify-self: end; }
.ks-cart__remove:hover,
.ks-cart__clear:hover { color: var(--text-primary); }
.ks-cart__remove:focus-visible,
.ks-cart__clear:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

.ks-drawer__foot {
  flex: none;
  padding: var(--space-4) var(--space-6) calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid rgba(214, 213, 208, 0.30);
  background: var(--color-black);
}
.ks-drawer__foot[hidden] { display: none; }
.ks-cart__totals { margin: 0 0 var(--space-4); }
.ks-cart__totals > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-1) 0;
}
.ks-cart__totals dt { color: var(--text-secondary); font-size: var(--fs-caption); }
.ks-cart__totals dd {
  margin: 0;
  font-family: var(--font-mono);
  color: var(--text-primary);
}
.ks-cart__hint {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  color: var(--text-muted);
}
.ks-cart__total {
  margin-top: var(--space-2);
  padding-top: var(--space-3) !important;
  border-top: 1px solid rgba(214, 213, 208, 0.14);
}
.ks-cart__total dt { color: var(--text-primary); font-size: var(--fs-body); }
.ks-cart__total dd {
  font-family: var(--font-display);
  font-weight: var(--display-wght);
  font-stretch: var(--display-stretch);
  font-variation-settings: "wdth" var(--display-wdth), "wght" var(--display-wght);
  font-size: 1.75rem;
  line-height: 1;
  letter-spacing: var(--tracking-display);
}
.ks-cart__checkout {
  width: 100%;
  min-height: 52px;
  justify-content: center;
}
.ks-cart__checkout[aria-busy="true"] { opacity: 0.6; cursor: progress; }
.ks-drawer__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-2);
}
.ks-drawer__note { margin: 0; color: var(--text-muted); font-size: 0.6875rem; }
.ks-drawer__meta .ks-cart__clear { flex: none; white-space: nowrap; }

/* ==========================================================================
   THE VIEWER (js/merch-detail.js). Sept 21 2026; gestures only since
   Oct 4 2026.

   THE DIALOG IS THE VIEWPORT: opaque Archive Black (at 0.98 the page's
   heading ghosted through the stage in a 1440 capture, Sept 21 2026), the
   bar on top with the name, the view tabs and Close, the stage below
   taking every pointer (touch-action: none, so a pinch is ours and not
   the page's). The image moves by transform alone with no transition --
   a zoom that eases lags the finger -- except when .is-settling springs
   it back after a rubber-band or a release.

   --ks-pull (0..1) is the swipe-down: the script writes it while the
   finger drags the picture down at fit, and the ground thins with it so
   the page shows through before the dialog closes.
   ========================================================================== */
.ks-detail {
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: rgba(3, 4, 15, calc(1 - 0.75 * var(--ks-pull, 0)));
  color: var(--text-primary);
  display: none;
  flex-direction: column;
}
.ks-detail[open] { display: flex; animation: ks-fade-in 280ms var(--ease-standard); will-change: opacity; }
.ks-detail::backdrop { background: rgba(3, 4, 15, 0.7); }

/* The bar and the hint own their layers while the viewer is open (Oct 4
   2026). Both sit above the picture, so when a pinch or a swipe down took
   the picture under them they were given layers there and then, and the
   dialog's own layer was rebuilt with them -- a whole-viewer flash on a
   phone (traced; the LAYERS note under THE SHOWROOM). Owned from the
   open, the picture can pass under them and nothing is made. */
.ks-detail__bar,
.ks-detail__hint { will-change: opacity; }
.ks-detail__bar {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--space-3) var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid rgba(214, 213, 208, 0.18);
  opacity: calc(1 - var(--ks-pull, 0) * 2);
}
.ks-detail__title {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: var(--fs-h3);
  letter-spacing: var(--tracking-heading);
  line-height: var(--lh-heading);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ks-detail__views {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.ks-detail__views[hidden] { display: none; }
.ks-detail__tab {
  min-height: 44px;
  min-width: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--space-3);
  background: transparent;
  border: 1px solid rgba(214, 213, 208, 0.30);
  border-radius: var(--radius-sharp);
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  color: var(--text-secondary);
  cursor: pointer;
}
.ks-detail__tab:hover { background: rgba(214, 213, 208, 0.10); color: var(--text-primary); }
.ks-detail__tab:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.ks-detail__tab[aria-pressed="true"] { background: var(--color-white); border-color: var(--color-white); color: var(--color-black); }
.ks-detail__close { margin: 0; }

.ks-detail__stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  touch-action: none;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}
.ks-detail__stage.is-dragging { cursor: grabbing; }
.ks-detail__img {
  position: absolute;
  top: 0;
  left: 0;
  max-width: none;
  transform-origin: 0 0;
  will-change: transform;
  opacity: 0;
  pointer-events: none;
}
.ks-detail__stage.is-loaded .ks-detail__img { opacity: 1; }
.ks-detail__img.is-settling { transition: transform 360ms cubic-bezier(0.16, 1, 0.3, 1); }
.ks-detail__status {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  color: var(--text-muted);
}
.ks-detail__status[hidden] { display: none; }
/* The zoom level, shown only while it changes, then gone: a reading, not a
   control. */
.ks-detail__level {
  position: absolute;
  top: var(--space-4);
  left: 50%;
  transform: translateX(-50%);
  padding: var(--space-1) var(--space-3);
  background: rgba(3, 4, 15, 0.8);
  border: 1px solid rgba(214, 213, 208, 0.18);
  color: var(--text-secondary);
  opacity: 0;
  transition: opacity 400ms var(--ease-standard);
  will-change: opacity;
  pointer-events: none;
}
.ks-detail__level.is-showing { opacity: 1; transition-duration: 80ms; }
.ks-detail__hint {
  margin: 0;
  padding: var(--space-2) var(--space-4) calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid rgba(214, 213, 208, 0.18);
  color: var(--text-muted);
  text-align: center;
}
.ks-detail.is-used .ks-detail__hint { opacity: 0; }
.ks-detail__hint--touch { display: none; }
@media (hover: none), (pointer: coarse) {
  .ks-detail__hint--fine { display: none; }
  .ks-detail__hint--touch { display: inline; }
}
@media (max-width: 600px) {
  .ks-detail__bar { flex-wrap: wrap; }
  .ks-detail__title { flex-basis: calc(100% - 60px); }
  .ks-detail__close { position: absolute; top: var(--space-3); right: var(--space-4); }
}

/* ==========================================================================
   REDUCED MOTION. Every movement above is decoration on a state change
   that also shows without it; with the preference set, the states arrive
   at once.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .ks-product__stage[data-mode="slide"] .ks-product__view,
  .ks-product__stage[data-mode="fade"] .ks-product__view,
  .ks-product__stage.is-peeking .ks-product__view,
  .ks-detail__img.is-settling { transition: none; }
  .ks-product.is-arriving .ks-product__view.is-current,
  .ks-product.is-arriving .ks-product__stage::before,
  .ks-product.is-arriving .ks-stage__num,
  .ks-product.is-arriving .ks-stage__zoom,
  .ks-product.is-arriving .ks-stage__views,
  .ks-product.is-arriving .ks-stage__hint,
  .ks-product__add.is-added::after,
  .ks-product__options.is-wanting .ks-chip,
  .ks-drawer__ticks span.is-igniting,
  .ks-cart__line.is-new,
  .ks-drawer[open],
  .ks-drawer.is-closing,
  .ks-drawer::backdrop,
  .ks-detail[open] { animation: none; }
}

@media (forced-colors: active) {
  .ks-chip, .ks-kinds__btn, .ks-stage__zoom, .ks-detail__tab, .ks-detail__close,
  .ks-drawer__close, .ks-cart__qty, .ks-rail__item { border-color: ButtonBorder; }
  .ks-chip__input:checked + .ks-chip,
  .ks-kinds__btn[aria-pressed="true"],
  .ks-detail__tab[aria-pressed="true"] { background: Highlight; color: HighlightText; border-color: Highlight; }
  .ks-rail__item[aria-current="true"] { outline: 2px solid Highlight; }
  .ks-drawer, .ks-drawer__foot { background: Canvas; border-color: CanvasText; }
  .ks-product__status, .ks-product__status--ok { border-left-color: CanvasText; }
}
