/* jam.css — the consolidation layer, loaded after base.css on every page.
   Adds: cross-document view transitions, the seeded hero, the machines floor,
   the store, the vitals strip, the boot wrapper. Tokens come from base.css —
   nothing here invents a colour. */

/* ── 1. view transitions (progressive: Chrome/Edge 126+, Safari 18.2+) ────── */
@media (prefers-reduced-motion: no-preference){
  @view-transition{ navigation: auto; }
}
/* base.css opts in UNCONDITIONALLY — absence of our gated rule doesn't turn it
   off, so reduced-motion needs an explicit opt-OUT that wins by source order. */
@media (prefers-reduced-motion: reduce){
  @view-transition{ navigation: none; }
}
::view-transition-old(root){ animation-duration:.26s; animation-timing-function:ease; }
::view-transition-new(root){ animation-duration:.26s; animation-timing-function:ease; }
#nav{ view-transition-name: site-nav; }
footer{ view-transition-name: site-foot; }
/* ── 1b. shared-element continuity ────────────────────────────────────────
   Both ends of every morph are declared HERE, together, on purpose. The five
   service pairs used to be duplicated verbatim inside index.html AND
   services/index.html; a pair whose halves live in two files drifts, and a
   drifted pair fails silently — the browser just crossfades and nobody notices.

   Two hard rules:
   1. A view-transition-name must be UNIQUE within a document. Two elements
      sharing one name disables the transition for BOTH. Hence the tight
      scoping below: .ink-b appears 3x on the homepage and 2x on /jam-os/,
      so the selectors name exactly one element per page.
   2. Both ends must resolve to the SAME name, or you get a fade-out plus an
      unrelated fade-in rather than a morph. */

/* services: index tile → detail hero. The destination half stays on each
   service page (.svc-hero h1) — that one is not duplicated, each page owns
   exactly its own. */
.svc a[href="/services/websites/"]      { view-transition-name:svc-websites }
.svc a[href="/services/apps/"]          { view-transition-name:svc-apps }
.svc a[href="/services/graphic-design/"]{ view-transition-name:svc-graphic-design }
.svc a[href="/services/marketing/"]     { view-transition-name:svc-marketing }
.svc a[href="/services/branding/"]      { view-transition-name:svc-branding }

/* ideas: card title → article title */
.idea a[href="/ideas/asks-before-it-acts/"] h2,
h1[data-vt="idea-asks"]                 { view-transition-name:idea-asks }
.idea a[href="/ideas/typed-four-times/"] h2,
h1[data-vt="idea-typed"]                { view-transition-name:idea-typed }

/* the product name: homepage band heading → /jam-os/ hero. Both ends are the
   literal words "JAM OS" in the same face, so this reads as the title flying
   into place rather than as two things crossfading. */
#jam-os h2 .ink-b,
.os-hero h1 .ink-b                      { view-transition-name:jam-os-name }

/* store: homepage cover → the artifact shot on /products/. The two images are
   different photographs (cover vs artifact), so this is a transform plus a
   crossfade rather than a true morph — deliberate, and it lands because the
   plate geometry matches. */
.prod[href="/products/#standing-orders"] img,
#standing-orders .sku-plate img         { view-transition-name:prod-standing-orders }
.prod[href="/products/#agentic-os-kit"] img,
#agentic-os-kit .sku-plate img          { view-transition-name:prod-agentic-os-kit }
.prod[href="/products/#nothing-lost"] img,
#nothing-lost .sku-plate img            { view-transition-name:prod-nothing-lost }

/* ── 1c. direction-aware page transitions ─────────────────────────────────
   jam.js tags each navigation with a type (vt-deeper / vt-shallower /
   vt-lateral) derived from path depth, so going INTO a section pushes forward
   and coming back reverses. Progressive on two axes: browsers without
   :active-view-transition-type() ignore these blocks and keep the .26s
   crossfade above, and jam.js adds no type at all when motion is off.

   Named elements (#nav, footer, the pairs above) are their own transition
   groups, so the chrome stays put while only the page body travels — which is
   what makes it read as one app rather than two documents. */
@media (prefers-reduced-motion: no-preference){
  html:active-view-transition-type(vt-deeper)::view-transition-old(root){
    animation:.34s var(--ease-inout) both vt-out-fwd;
  }
  html:active-view-transition-type(vt-deeper)::view-transition-new(root){
    animation:.42s var(--ease-out) both vt-in-fwd;
  }
  html:active-view-transition-type(vt-shallower)::view-transition-old(root){
    animation:.30s var(--ease-inout) both vt-out-back;
  }
  html:active-view-transition-type(vt-shallower)::view-transition-new(root){
    animation:.38s var(--ease-out) both vt-in-back;
  }
  html:active-view-transition-type(vt-lateral)::view-transition-old(root){
    animation:.26s var(--ease-inout) both vt-out-lat;
  }
  html:active-view-transition-type(vt-lateral)::view-transition-new(root){
    animation:.34s var(--ease-out) both vt-in-lat;
  }
}
/* Travel is deliberately small. The old page leaving is faster than the new one
   arriving, so the incoming page is never chasing a gap. Percentages, not px,
   so the gesture is the same size on a phone and a 32" display. */
@keyframes vt-out-fwd { to  { opacity:0; transform:translateX(-2.5%) scale(.985) } }
@keyframes vt-in-fwd  { from{ opacity:0; transform:translateX(4%)    scale(.99)  } }
@keyframes vt-out-back{ to  { opacity:0; transform:translateX(2.5%)  scale(.985) } }
@keyframes vt-in-back { from{ opacity:0; transform:translateX(-4%)   scale(.99)  } }
@keyframes vt-out-lat { to  { opacity:0; transform:translateY(-1.2%)             } }
@keyframes vt-in-lat  { from{ opacity:0; transform:translateY(1.6%)              } }

/* ── 1d. the motion toggle has the last word ──────────────────────────────
   The site's own toggle wins even where the OS allows motion, and it must
   reach every transition group — named nav/footer/tile morphs included —
   because base.css's motion-off guard matches real elements, never VT pseudos.

   This block sits AFTER 1c deliberately, and moving it back up breaks it.
   `html.motion-off::view-transition-old(*)` and
   `html:active-view-transition-type(vt-deeper)::view-transition-old(root)`
   have IDENTICAL specificity — one element + one pseudo-class each — so the
   later rule wins outright. With this block above 1c, flipping the toggle off
   still played the directional animations, and the only thing preventing that
   was jam.js declining to add a type. That is one guard, in another language,
   in another file; this makes it two, independently. */
html.motion-off::view-transition-group(*),
html.motion-off::view-transition-image-pair(*),
html.motion-off::view-transition-old(*),
html.motion-off::view-transition-new(*){ animation:none; }

/* ── 1e. ambient field ────────────────────────────────────────────────────
   base.css's .bg was a deliberately STATIC layer; see its comment for why the
   cursor-reactive predecessor was deleted. Jake authorised bringing ambient
   motion back on 2026-08-20, disciplined so it cannot repeat that failure.

   Three constraints, all load-bearing:
   1. Imperceptible as motion. 82s per cycle and under 2% travel — slower than
      the eye registers as movement, so it reads as the light being alive
      rather than as something happening.
   2. Stopped while reading. jam.js sets html.at-rest after ~2s without a
      scroll, which is the cheapest reliable proxy for "the reader has stopped
      moving and started reading". Nothing animates behind a paragraph anyone
      is actually on.
   3. Off means off. Both the site toggle and OS reduced-motion kill it —
      the latter for free, via base.css's global animation-duration:.01ms
      !important rule.

   scale() stays above 1 throughout so the translate never exposes an edge. */
@media (prefers-reduced-motion: no-preference){
  .bg{ animation:bg-drift 82s var(--ease-inout) infinite alternate; }
}
html.motion-off .bg,
html.at-rest .bg{ animation-play-state:paused; }

@keyframes bg-drift{
  from{ transform:translate3d(0,0,0)         scale(1.02); }
  to  { transform:translate3d(-1.6%,1.1%,0)  scale(1.06); }
}

/* ── 1f. overlay menu (≤760px) ────────────────────────────────────────────
   Replaces the dropdown that base.css drops below the bar with a full-screen
   overlay. Technique adapted from the Animmaster pack (Navigation Menus 16):
   a curtain wipe, then links rising with a slight rotation, staggered.
   Rebuilt in this site's tokens and in plain CSS — the original drives it with
   a GSAP timeline, and 73 KB of dependency to stagger seven links is not a
   trade worth making when nth-child does it for free.

   Deliberately reuses base.css's #nav.menu-open state class rather than
   introducing its own. base.js already owns the hard part — aria-expanded,
   Escape to close, returning focus to the burger instead of dropping it on
   <body>, closing on link click — and none of that needed to change. A new
   state class would have meant reimplementing all of it, which is how
   overlay menus usually end up inaccessible.

   Motion is handled for free: base.css kills every animation under both
   prefers-reduced-motion and html.motion-off, so the overlay still opens,
   just instantly. */
@media(max-width:760px){
  /* base.css glasses the bar while the menu is open, and `backdrop-filter`
     ESTABLISHES A CONTAINING BLOCK for fixed-position descendants — so the
     overlay's inset:0 resolved against the 88px bar instead of the viewport
     and the sheet opened 112px tall. Worth knowing that the computed value was
     blur(0px), not blur(14px): a zero-length filter function still creates the
     containing block, so "it's effectively off" is not the same as `none`.
     The overlay covers the whole screen at 96% opacity and carries its own
     blur, so the bar has nothing left to glass. */
  #nav.menu-open{ backdrop-filter:none; }

  #nav.menu-open .nav-links{
    position:fixed; inset:0; z-index:1;
    flex-direction:column; justify-content:center; align-items:flex-start;
    gap:0; padding:calc(var(--gutter) + 72px) var(--gutter) var(--gutter);
    /* Solid, not --scrim-4. At .96 the hero headline, sub-copy, CTA and seed
       badge all stayed legible straight through the sheet — 4% of near-white
       type on a near-black ground is well above the threshold of noticing, and
       it read as an unfinished overlay rather than a translucent one. A
       full-screen modal has nothing to gain from showing the page it replaced.
       Dropping backdrop-filter with it also removes a full-viewport GPU pass on
       exactly the devices least able to afford one. */
    background:var(--s0); backdrop-filter:none;
    border-bottom:0;
    animation:menu-curtain .42s var(--ease-out) both;
  }
  /* the bar's own controls stay above the sheet they opened */
  #nav.menu-open .wordmark,
  #nav.menu-open .nav-burger{ position:relative; z-index:2; }

  #nav.menu-open .nav-links a.nl{
    display:block; width:100%;
    font-family:var(--display); font-weight:800; text-transform:uppercase;
    font-size:clamp(1.9rem,9vw,2.9rem); line-height:1.06; letter-spacing:-.02em;
    color:var(--t-hi); padding:.14em 0; border-bottom:0;
    animation:menu-rise .52s var(--ease-out) both;
  }
  /* Stagger without touching markup on 20 pages — the links are the first six
     children, the CTA is the seventh. */
  #nav.menu-open .nav-links a.nl:nth-child(1){ animation-delay:.10s }
  #nav.menu-open .nav-links a.nl:nth-child(2){ animation-delay:.15s }
  #nav.menu-open .nav-links a.nl:nth-child(3){ animation-delay:.20s }
  #nav.menu-open .nav-links a.nl:nth-child(4){ animation-delay:.25s }
  #nav.menu-open .nav-links a.nl:nth-child(5){ animation-delay:.30s }
  #nav.menu-open .nav-links a.nl:nth-child(6){ animation-delay:.35s }
  #nav.menu-open .nav-links .btn{
    margin-top:28px; justify-content:center;
    animation:menu-rise .52s var(--ease-out) both; animation-delay:.42s;
  }
  /* The underline base.css draws on hover is meaningless at this size. */
  #nav.menu-open .nav-links a.nl::after{ display:none }
}
/* html is the scroll container on this site, not body — locking body does
   nothing here, which the ⌘K palette already learned the hard way. Scoped with
   :has() so it needs no JS; browsers without :has() just scroll behind the
   overlay, which is untidy rather than broken. */
@media(max-width:760px){
  html:has(#nav.menu-open){ overflow:hidden }
}
@keyframes menu-curtain{ from{ clip-path:inset(0 0 100% 0) } to{ clip-path:inset(0 0 0 0) } }
@keyframes menu-rise{ from{ opacity:0; transform:translateY(115%) rotate(2deg) } to{ opacity:1; transform:none } }

/* ── 1g. heading line-mask reveal ─────────────────────────────────────────
   Section headings wipe up from behind their own baseline instead of sliding
   in like body content. Technique from the pack's on-scroll typography set
   (Text Animations 5/6), driven by the scroll-driven engine base.css already
   ships — no GSAP, no ScrollTrigger, no observer.

   This overrides animation-name ONLY, which is the whole trick. Every other
   longhand — duration:auto, timing, fill, timeline, and the per-delay
   animation-range values on .d1-.d4 — keeps cascading from base.css's
   `.st .reveal`, so headings inherit their staggering for free and there is
   no comma-separated list to keep in sync. Do NOT add the `animation`
   shorthand here: base.css documents twice that it resets
   animation-duration to 0s, which leaves a scroll-driven animation stuck on
   its from-keyframe forever.

   :not(.step) because process steps run a hand-composed two-animation list
   (reveal-in + step-glow-in) that a single animation-name would silently
   delete — the exact failure base.css's .st .step.reveal block exists to fix.

   The end state clips to inset(... -14% ...) rather than 0, so descenders and
   the .ink gradient's bleed are never shaved once the heading has arrived. */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    .st h2.reveal:not(.step),
    .st .page-hero h1.reveal:not(.step){ animation-name:head-rise; }
  }
}
@keyframes head-rise{
  from{ opacity:0; transform:translateY(.16em); clip-path:inset(0 0 108% 0); }
  to  { opacity:1; transform:none;              clip-path:inset(0 0 -14% 0); }
}

/* ── 2. shared sub-page hero (products / about) — mirrors .svcs-hero ─────── */
.page-hero{
  min-height:auto;padding-top:var(--hero-pad);padding-bottom:var(--sp-4);
  background:linear-gradient(180deg,rgba(8,8,13,.55),rgba(8,8,13,.9)),var(--s0);
}
.page-hero h1{
  font-family:var(--display);font-weight:800;text-transform:uppercase;
  font-size:var(--step-4);line-height:1;letter-spacing:-.01em;
}
.page-hero h1 .ink{
  background:linear-gradient(95deg,var(--brand-1) 5%,var(--brand-2) 60%,var(--brand-3) 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
.page-hero .sub{ max-width:620px;color:var(--t-up);line-height:1.7;margin-top:18px; }

/* ── 3. seeded hero ──────────────────────────────────────────────────────── */
.hero{ position:relative; overflow:hidden; }
.hero .shell{ position:relative; z-index:2; }
.hero-canvas{ position:absolute; inset:0; z-index:1; mix-blend-mode:screen; pointer-events:none; }
.seed-badge{
  position:absolute; z-index:3; right:clamp(16px,4vw,48px); bottom:20px;
  display:flex; align-items:center; gap:10px;
  font-family:var(--mono); font-size:.66rem; letter-spacing:.12em; text-transform:uppercase;
  color:var(--t-lo); background:var(--scrim-2); border:1px solid var(--line-2);
  /* Vertical padding trimmed .5em -> .15em to offset the 24px control height below, so the
     badge keeps close to its original height instead of growing ~11px. Horizontal unchanged. */
  border-radius:var(--r-pill); padding:.15em 1.05em; backdrop-filter:blur(8px);
}
.seed-badge b{ color:var(--t-up); font-weight:500; }
.seed-badge button{
  background:none; border:0; font:inherit; cursor:pointer; color:var(--t-lo);
  /* WCAG 2.2 SC 2.5.8 (Target Size Minimum): 24x24 CSS px floor. These were ~12x13px
     (padding:0 .1em on .66rem type) — the only audit keeping accessibility off 100.
     The glyph itself is unchanged; only the hit area grows to meet the floor. */
  min-width:24px; min-height:24px; padding:0;
  display:inline-flex; align-items:center; justify-content:center;
}
.seed-badge button:hover{ color:var(--t-hi); }

/* ── 4. the machines ─────────────────────────────────────────────────────── */
.mx-grid{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:26px; margin-top:44px; }
@media (max-width:900px){ .mx-grid{ grid-template-columns:1fr; } }
.mx{
  background:var(--s2); border:1px solid var(--line-1); border-radius:var(--r-card);
  overflow:hidden; display:flex; flex-direction:column; transition:border-color .25s;
}
.mx:hover{ border-color:var(--line-2); }
.mx-head{
  display:flex; justify-content:space-between; gap:14px; padding:12px 18px;
  border-bottom:1px solid var(--line-1);
  font-family:var(--mono); font-size:.66rem; letter-spacing:.14em; text-transform:uppercase; color:var(--t-lo);
}
.mx-head .on{ color:var(--ok); }
.mx-body{ display:grid; grid-template-columns:minmax(120px,164px) minmax(0,1fr); flex:1; }
.mx-plate{ position:relative; background:var(--s3); min-height:120px; }
.mx-plate img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; filter:saturate(.85) contrast(1.05); }
.mx-live{
  background:var(--s1); border-left:1px solid var(--line-1);
  padding:16px 18px; min-height:200px; position:relative; overflow:hidden;
}
@media (max-width:520px){
  .mx-body{ grid-template-columns:1fr; }
  .mx-plate{ aspect-ratio:16/7; }
  .mx-live{ border-left:0; border-top:1px solid var(--line-1); }
}
.mx-foot{ padding:12px 18px 16px; border-top:1px solid var(--line-1); font-size:.86rem; line-height:1.6; color:var(--t-mid); }
.mx-foot b{ color:var(--t-up); font-weight:600; }

/* split-flap */
.sf{ display:flex; flex-wrap:wrap; gap:5px; }
.sf b{
  width:1.35ch; display:grid; place-items:center; padding:.4em 0;
  font:500 .8rem var(--mono); color:var(--t-hi);
  background:var(--s3); border:1px solid var(--line-2); border-radius:var(--r-xs);
}
.sf b.hot{ color:var(--brand-1); }
@keyframes sfFlip{ from{ transform:scaleY(.15); } to{ transform:scaleY(1); } }
.sf b.flip{ animation:sfFlip .13s ease-out; }
html.motion-off .sf b.flip{ animation:none; }
.sf-xl b{ font-size:clamp(.9rem,2.1vw,1.25rem); padding:.5em 0; width:1.4ch; }

/* ledger */
.lg{ font-family:var(--mono); font-size:.73rem; letter-spacing:.02em; color:var(--t-up); }
.lg-row{
  display:grid; grid-template-columns:1fr auto auto; gap:14px; padding:.42em 0;
  border-bottom:1px dashed var(--line-1);
  opacity:0; transform:translateY(4px); transition:opacity .3s,transform .3s;
}
.lg-row.in{ opacity:1; transform:none; }
html.motion-off .lg-row{ transition:none; }
.lg-row .cr{ color:var(--cat-r); }
.lg-row .dr{ color:var(--cat-w); }
.lg-var{ display:flex; justify-content:space-between; padding-top:.7em; color:var(--t-lo); }
.lg-var b{ color:var(--t-hi); font-weight:500; }
.lg-var b.zero{ color:var(--ok); }
.lg-stamp{
  position:absolute; right:14px; bottom:12px; opacity:0;
  transform:rotate(-8deg) scale(1.6);
  font:600 .78rem var(--mono); letter-spacing:.22em; color:var(--ok);
  border:2px solid var(--ok); border-radius:var(--r-xs); padding:.35em .7em;
  transition:transform .18s cubic-bezier(.2,1.4,.4,1),opacity .18s;
}
.lg-stamp.in{ opacity:1; transform:rotate(-8deg) scale(1); }
html.motion-off .lg-stamp{ transition:none; }

/* terminal */
.tm{ font:400 .76rem/1.75 var(--mono); color:var(--t-up); white-space:pre-wrap; }
.tm .ok{ color:var(--ok); }
.tm .dim{ color:var(--t-lo); }
.tm .acc{ color:var(--brand-1); }
.tm-cursor{
  display:inline-block; width:.6em; height:1em; background:var(--brand-1);
  vertical-align:text-bottom; animation:tmBlink 1s steps(1) infinite;
}
@keyframes tmBlink{ 50%{ opacity:0; } }
html.motion-off .tm-cursor{ animation:none; }

/* patch-bay */
.pb{ width:100%; height:100%; min-height:180px; display:block; }
.pb .jack{ cursor:pointer; }
.pb .cable{ cursor:pointer; }

/* ── 5. the store ────────────────────────────────────────────────────────── */
.prod-grid{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:26px; margin-top:44px; }
@media (max-width:960px){ .prod-grid{ grid-template-columns:1fr; } }
.prod{
  background:var(--s2); border:1px solid var(--line-1); border-radius:var(--r-card);
  overflow:hidden; display:flex; flex-direction:column; transition:border-color .25s;
}
.prod:hover{ border-color:var(--line-2); }
.prod > img{ width:100%; aspect-ratio:16/9; object-fit:cover; display:block; border-bottom:1px solid var(--line-1); }
.prod-pad{ padding:22px 22px 24px; display:flex; flex-direction:column; gap:10px; flex:1; }
.prod-pad h3{ margin:0; font-family:var(--display); font-weight:800; font-size:1.3rem; text-transform:uppercase; letter-spacing:.01em; }
.prod-one{ font-family:var(--mono); font-size:.68rem; letter-spacing:.1em; text-transform:uppercase; color:var(--t-lo); line-height:1.6; }
.prod-pad p{ margin:0; color:var(--t-mid); font-size:.92rem; line-height:1.65; }
.buy-row{ margin-top:auto; padding-top:14px; display:flex; align-items:center; justify-content:space-between; gap:14px; }
.price{ font:500 1.3rem var(--mono); color:var(--t-hi); }
.bundle{
  margin-top:30px; background:var(--s2); border:1px solid var(--line-2); border-radius:var(--r-card);
  padding:26px 28px; display:grid; grid-template-columns:minmax(0,1fr) auto; gap:24px; align-items:center;
}
@media (max-width:760px){ .bundle{ grid-template-columns:1fr; } }
.eyebrow-heat{ font-family:var(--mono); font-size:.68rem; letter-spacing:.16em; text-transform:uppercase; color:var(--heat); }
.bundle h3{ margin:.45em 0 .35em; font-family:var(--display); font-weight:800; text-transform:uppercase; font-size:clamp(1.3rem,3vw,1.9rem); }
.bundle h3 .price-hi{ color:var(--brand-1); }
.bundle p{ margin:0; color:var(--t-mid); line-height:1.65; max-width:560px; }
.buying{ margin-top:54px; border:1px solid var(--line-1); border-radius:var(--r-card); padding:6px 26px; background:var(--s1); }
.buying > div{
  display:grid; grid-template-columns:2.4em 1fr; gap:12px; padding:16px 0;
  border-bottom:1px solid var(--line-1); align-items:baseline;
}
.buying > div:last-child{ border-bottom:0; }
.buying .n{ font:500 .72rem var(--mono); color:var(--brand-1); }
.buying p{ margin:0; color:var(--t-mid); line-height:1.65; }
.buying p b{ color:var(--t-hi); font-weight:600; }

/* ── 5b. product shelf (products page) ───────────────────────────────────── */
/* Each product is an exhibit row: artifact photo on a plate (left), spec sheet
   (right). Alternating sides. The .prod-* classes above stay untouched — the
   home strip and the about page still use them. */
.sku-shelf{ display:flex; flex-direction:column; gap:30px; margin-top:44px; }
.sku{
  background:var(--s1); border:1px solid var(--line-1); border-radius:var(--r-card);
  overflow:hidden; display:grid; grid-template-columns:minmax(0,5fr) minmax(0,6fr);
  transition:border-color .25s;
}
.sku:hover{ border-color:var(--line-2); }
.sku-plate{
  position:relative; background:var(--s0); border-right:1px solid var(--line-1);
  display:flex; align-items:center; justify-content:center; padding:30px; min-height:360px;
}
.sku:nth-child(even) .sku-plate{ order:2; border-right:0; border-left:1px solid var(--line-1); }
/* The artifact photos carry their own near-black backdrop; the radial mask
   melts their edges into the plate so no rectangle seam ever shows. */
.sku-plate img{
  width:min(100%,430px); aspect-ratio:1; object-fit:cover; display:block;
  -webkit-mask-image:radial-gradient(ellipse 92% 92% at 50% 50%, #000 52%, transparent 76%);
  mask-image:radial-gradient(ellipse 92% 92% at 50% 50%, #000 52%, transparent 76%);
  transition:transform .5s var(--ease-out);
}
.sku:hover .sku-plate img{ transform:scale(1.03); }
html.motion-off .sku:hover .sku-plate img{ transform:none; }
@media (prefers-reduced-motion: reduce){ .sku:hover .sku-plate img{ transform:none; } }
.sku-tag{
  position:absolute; top:16px; left:18px; font:500 .62rem var(--mono);
  letter-spacing:.16em; text-transform:uppercase; color:var(--t-lo);
}
.sku-info{ padding:30px 32px 32px; display:flex; flex-direction:column; gap:12px; }
.sku-line{ font:500 .66rem var(--mono); letter-spacing:.16em; text-transform:uppercase; color:var(--brand-1); }
.sku-info h3{ margin:0; font-family:var(--display); font-weight:800; font-size:clamp(1.4rem,2.4vw,1.9rem); text-transform:uppercase; letter-spacing:.01em; }
.sku-info > p{ margin:0; color:var(--t-mid); font-size:.94rem; line-height:1.65; max-width:60ch; }
.sku-specs{ display:flex; flex-wrap:wrap; gap:8px; }
.sku-chip{
  font:500 .62rem var(--mono); letter-spacing:.1em; text-transform:uppercase;
  color:var(--t-lo); border:1px solid var(--line-1); border-radius:var(--r-pill); padding:5px 11px;
}
.sku .buy-row{ padding-top:16px; }
.sku .price{ font-size:1.55rem; }
.sku-fine{ font:400 .62rem var(--mono); letter-spacing:.12em; text-transform:uppercase; color:var(--t-lo); }
@media (max-width:920px){
  .sku{ grid-template-columns:1fr; }
  .sku:nth-child(even) .sku-plate{ order:0; border-left:0; }
  .sku-plate{ border-right:0; border-bottom:1px solid var(--line-1); min-height:0; }
  .sku-plate img{ width:min(100%,340px); }
}

/* product preview motifs — the old covers' panels, rebuilt native */
.sku-motif{
  border:1px solid var(--line-1); border-radius:var(--r-xs); background:var(--s2);
  font-family:var(--mono); font-size:.76rem; color:var(--t-up); overflow:hidden;
}
/* A: the standing orders themselves */
.so-row{ display:flex; gap:12px; padding:10px 16px; border-bottom:1px solid var(--line-1); letter-spacing:.02em; }
.so-row:last-child{ border-bottom:0; }
.so-row .n{ color:var(--gold); font-weight:500; }
.so-stop{
  background:var(--heat); color:var(--s0); text-align:center; padding:8px 16px;
  font-weight:500; letter-spacing:.22em; text-transform:uppercase; font-size:.7rem;
}
/* B: the blocked terminal */
.kit-term{ position:relative; padding:14px 16px 16px; background:var(--s0); }
.kt-line{ padding:4px 0; letter-spacing:.02em; }
.kt-line .p{ color:var(--ok); margin-right:8px; }
.kt-line s{ color:var(--heat); text-decoration-color:var(--heat); }
.kt-line.blocked{ color:var(--heat); }
.kt-line.blocked .x{ margin-right:8px; }
.kit-stamp{
  position:absolute; right:18px; bottom:14px; transform:rotate(-6deg);
  border:1.5px solid var(--heat); color:var(--heat); border-radius:var(--r-xs);
  padding:3px 10px; font-size:.64rem; letter-spacing:.22em; text-transform:uppercase; font-weight:500;
}
/* C: the commit counter */
.nl-row{ display:flex; align-items:baseline; gap:14px; padding:12px 16px; border-bottom:1px solid var(--line-1); font-size:.66rem; letter-spacing:.12em; text-transform:uppercase; color:var(--t-lo); }
.nl-row b{ font:800 1.5rem var(--display); color:var(--t-hi); letter-spacing:0; }
.nl-row b.bad{ color:var(--heat); }
.nl-mid{ text-align:center; padding:7px 16px; font-size:.62rem; letter-spacing:.2em; text-transform:uppercase; color:var(--gold); border-bottom:1px solid var(--line-1); }
.nl-cap{ padding:10px 16px; font-size:.7rem; color:var(--t-mid); letter-spacing:.02em; }

/* the receipt — same three facts as the old 01/02/03 strip, printed */
.receipt{
  margin:54px auto 0; max-width:470px; background:var(--s1);
  border:1px solid var(--line-1); border-radius:var(--r-xs);
  padding:24px 26px 20px; font-family:var(--mono); color:var(--t-mid);
  transform:rotate(-.4deg);
}
.rc-head{ text-align:center; color:var(--t-up); font-size:.72rem; letter-spacing:.22em; text-transform:uppercase; }
.rc-sub{ text-align:center; color:var(--t-lo); font-size:.6rem; letter-spacing:.16em; text-transform:uppercase; margin-top:4px; }
.rc-rule{ border:none; border-top:1px dashed var(--line-2); margin:14px 0; }
.rc-line{ display:flex; align-items:baseline; gap:10px; font-size:.7rem; letter-spacing:.1em; text-transform:uppercase; padding:3px 0; }
.rc-line b{ color:var(--t-hi); font-weight:500; }
.rc-dots{ flex:1; border-bottom:1px dotted var(--line-2); transform:translateY(-3px); }
.rc-note{ font-size:.64rem; color:var(--t-lo); margin:1px 0 9px; line-height:1.6; }
.rc-total{ display:flex; align-items:baseline; gap:10px; font-size:.72rem; letter-spacing:.14em; text-transform:uppercase; color:var(--t-hi); }
.rc-foot{ text-align:center; margin-top:14px; font-size:.6rem; color:var(--t-lo); letter-spacing:.18em; text-transform:uppercase; }
.rc-foot a{ color:inherit; }

/* floor cross-sell: machine + index of the floor */
.floor-grid{ display:grid; grid-template-columns:minmax(0,7fr) minmax(0,4fr); gap:26px; align-items:stretch; margin-top:44px; }
@media (max-width:920px){ .floor-grid{ grid-template-columns:1fr; } }
.floor-index{
  list-style:none; margin:0; padding:10px 0; border:1px solid var(--line-1);
  border-radius:var(--r-card); background:var(--s1); display:flex; flex-direction:column;
}
.floor-index li{ flex:1; display:flex; }
.floor-index a{
  flex:1; display:flex; align-items:center; gap:14px; padding:13px 22px;
  font:500 .7rem var(--mono); letter-spacing:.14em; text-transform:uppercase;
  color:var(--t-mid); text-decoration:none; border-bottom:1px solid var(--line-1);
  transition:color .2s, background .2s;
}
.floor-index li:last-child a{ border-bottom:0; }
.floor-index a:hover{ color:var(--t-hi); background:var(--s2); }
.floor-index .fx-n{ color:var(--brand-1); }
.floor-index .fx-role{ margin-left:auto; color:var(--t-lo); font-size:.62rem; }

/* ── 6. vitals strip (appended by jam.js, Chromium only) ─────────────────── */
.vitals{ font-family:var(--mono); font-size:.66rem; letter-spacing:.1em; text-transform:uppercase; color:var(--t-lo); }
.vitals b{ color:var(--t-up); font-weight:500; }

/* ── 7. about page bits ──────────────────────────────────────────────────── */
.op-grid{ display:grid; grid-template-columns:minmax(0,1fr) minmax(260px,340px); gap:44px; align-items:start; }
@media (max-width:860px){ .op-grid{ grid-template-columns:1fr; } }
.op-plate{
  background:var(--s2); border:1px solid var(--line-1); border-radius:var(--r-card); overflow:hidden;
}
.op-plate img{ width:100%; aspect-ratio:4/5; object-fit:cover; display:block; }
.op-plate figcaption{
  display:flex; justify-content:space-between; padding:12px 16px;
  font-family:var(--mono); font-size:.66rem; letter-spacing:.12em; text-transform:uppercase; color:var(--t-lo);
}
.cred{ border-top:1px solid var(--line-2); margin-top:22px; }
.cred > div{
  display:grid; grid-template-columns:1fr auto; gap:18px; padding:13px 0;
  border-bottom:1px solid var(--line-1); align-items:baseline;
}
.cred .who{ font-family:var(--mono); font-size:.68rem; letter-spacing:.12em; text-transform:uppercase; color:var(--t-lo); }

/* ── 8. boot easter egg ──────────────────────────────────────────────────── */
.boot-wrap{ position:fixed; inset:0; background:#000; }
.boot-wrap iframe{ width:100%; height:100%; border:0; }
.boot-back{
  position:fixed; top:16px; left:16px; z-index:9;
  font-family:var(--mono); font-size:.7rem; letter-spacing:.14em; text-transform:uppercase;
  color:var(--t-up); background:var(--scrim-3); border:1px solid var(--line-2);
  border-radius:var(--r-pill); padding:.55em 1.1em; text-decoration:none;
}
.boot-back:hover{ color:var(--t-hi); border-color:var(--line-3); }

/* ── 9. the brand mark (adopted 2026-08-20) ──────────────────────────────── */
/* The vintage-machine mark sits where the ◆ diamond used to, stamped by
   patch-shell.py step 6 into both wordmarks. It inherits the diamond's
   hover delight: the screen cursor blinks. */
/* -.23em: JAM is uppercase, so its optical middle is its cap-height middle,
   which sits above the line-box middle that align-self:center gives the svg */
.wordmark .wm-mark{ width:1.5em; height:1.5em; align-self:center; flex:none; display:block; transform:translateY(-.23em); }
@media (hover:hover){
  .wordmark:hover .wm-cur{ animation:wmBlink .9s steps(1) infinite; }
}
@keyframes wmBlink{ 50%{ opacity:0; } }
html.motion-off .wordmark:hover .wm-cur{ animation:none; }
@media (prefers-reduced-motion: reduce){
  .wordmark:hover .wm-cur{ animation:none; }
}
/* The mark added ~40px to the nav wordmark; between the burger breakpoint and
   ~1280px that left the RESOURCES label ~4px from the first nav link. With the
   mark carrying identity, mark+JAM is a complete lockup — drop the mono suffix
   in the NAV at mid widths (footer keeps it; base.css still owns <430px). */
@media (max-width:1280px){
  #nav .wordmark .res{ display:none; }
}
