/* MBK v1.2 — MOTION kit CSS (addendum §"the motion kit API").
   Everything visual the kit needs: reveals, marquee, timecode HUD, grain, Ken-Burns preview,
   status dot, and the reduced-motion/coarse-pointer bail-out law. Pairs with js/motion.js —
   motion.js sets html[data-motion="on"|"off"]; @media(prefers-reduced-motion) is the CSS-only
   safety net in case JS never runs. */

/* ---- Lenis smooth-scroll resets (no-op when Lenis isn't vendored/active) ---- */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }

/* =========================================================
   REVEALS — [data-reveal] / [data-reveal-stagger]
   autoAlpha 0→1, y 24→0, .62s, power3.out-equivalent ease.
   Progressive-enhancement gate: the hidden initial state only
   applies once motion.js has run and stamped html.mbk-motion —
   if the kit never loads, everything is simply visible.
   ========================================================= */
html.mbk-motion [data-reveal] {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity .62s cubic-bezier(.16, 1, .3, 1), transform .62s cubic-bezier(.16, 1, .3, 1);
  will-change: opacity, transform;
}
/* v2 fix (jank source): the Cue Sheet alone can carry 35+ revealed rows.
   Leaving will-change:opacity,transform on every one of them forever (the v1
   rule had no release) pins each row to its own compositor layer for the rest
   of the page's life — dozens of idle layers the GPU still has to composite
   on every scroll frame. Releasing it once a row has settled costs nothing
   (the transition has already finished) and frees those layers. */
html.mbk-motion [data-reveal].is-revealed {
  opacity: 1;
  transform: translateY(0);
  will-change: auto;
}

/* =========================================================
   COUNT-UP — [data-countup="270"]
   ========================================================= */
[data-countup] {
  font-variant-numeric: tabular-nums;
}

/* =========================================================
   MARQUEE — .marquee > .marquee-track (content duplicated once
   by motion.js so the true loop length is 200%; translate -50%
   for a seamless infinite scroll). 30s linear. Pause on hover.
   ========================================================= */
.marquee {
  overflow: hidden;
  white-space: nowrap;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 4%, #000 96%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 4%, #000 96%, transparent 100%);
}
.marquee-track {
  display: inline-flex;
  align-items: center;
  width: max-content;
  /* compositor-only: transform-driven keyframes + will-change keep this off the
     main thread for the entire scroll, so a continuously running marquee never
     competes with scroll/paint work elsewhere on the page. */
  will-change: transform;
  backface-visibility: hidden;
}
/* The -50% loop is only seamless after JS duplicates the track's children
   (motion.js initMarquees). With scripts blocked the single copy would slide
   out and hard-snap every 30s — so the animation itself is gated on the JS
   stamp, and the no-JS resting state is a plain static row. */
html.mbk-motion .marquee-track {
  animation: mbk-marquee 30s linear infinite;
}
.marquee:hover .marquee-track {
  animation-play-state: paused;
}

@keyframes mbk-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* =========================================================
   TIMECODE HUD — #tc-hud
   Fixed corner, small, grey, tabular. Motion.js writes the text.
   ========================================================= */
#tc-hud {
  position: fixed;
  right: 18px;
  bottom: 16px;
  z-index: 60;
  font-family: var(--font-sans, "Helvetica Neue", Arial, sans-serif);
  font-size: 10.5px;
  font-weight: 400;
  letter-spacing: .14em;
  color: var(--grey-1, #8f897d);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  user-select: none;
  opacity: .85;
}
/* Below tablet width the HUD collides with the hero's chapter chips — and the
   pinned scene doesn't run on touch anyway, so the scroll-mapped timecode loses
   its meaning there. Hide it. */
@media (max-width: 820px) {
  #tc-hud { display: none; }
}

/* =========================================================
   GRAIN — .grain (dark sections only). motion.js injects a
   child .grain-layer; pure CSS/SVG fractal-noise data-URI.
   Opacity capped at .05 (spec ceiling .06). Only renders under
   .room-dark — a .grain class on a paper section is a no-op by
   design (protects the single-accent/no-fandom paper discipline).
   ========================================================= */
.grain {
  position: relative;
  overflow: hidden;
}
.grain-layer {
  position: absolute;
  inset: -25%;
  width: 150%;
  height: 150%;
  pointer-events: none;
  z-index: 1;
  opacity: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: 180px 180px;
}
.room-dark .grain-layer {
  opacity: .05;
  animation: mbk-grain 1.2s steps(4) infinite;
}

@keyframes mbk-grain {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(-2%, 1%); }
  50%  { transform: translate(1%, -2%); }
  75%  { transform: translate(-1%, 2%); }
  100% { transform: translate(0, 0); }
}

/* =========================================================
   KEN-BURNS PREVIEW — .kb-preview
   Image layer fades in .5s / scales 1.08→1 over 8s ease-out.
   Works two ways: self-hover (desktop, direct) OR a JS-toggled
   .is-active class (e.g. a fixed preview plate driven by a
   cue-sheet row hover elsewhere in the DOM — SHEETS' pattern).
   ========================================================= */
.kb-preview {
  position: relative;
  overflow: hidden;
  display: block;
}
.kb-preview img,
.kb-preview .kb-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity .5s ease, transform 8s ease-out;
  will-change: opacity, transform;
}
/* The hidden resting state is gated on the JS stamp, same contract as
   [data-reveal] above: if motion.js never runs, nothing can activate a
   kb-preview, so without this gate every project-page poster would rest
   as a solid ink box for no-JS visitors. */
html.mbk-motion .kb-preview img,
html.mbk-motion .kb-preview .kb-img {
  opacity: 0;
  transform: scale(1.08);
}
html.mbk-motion .kb-preview:hover img,
html.mbk-motion .kb-preview:hover .kb-img,
html.mbk-motion .kb-preview.is-active img,
html.mbk-motion .kb-preview.is-active .kb-img {
  opacity: 1;
  transform: scale(1);
}

/* =========================================================
   STATUS DOT — .status-dot (8px, red, pulse 2.6s)
   ========================================================= */
.status-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--red, #EB1C24);
  flex: none;
  animation: mbk-pulse 2.6s ease-in-out infinite;
}
@keyframes mbk-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(235, 28, 36, .55); }
  55%      { box-shadow: 0 0 0 6px rgba(235, 28, 36, 0); }
}

/* =========================================================
   THE SLATE — triple-click Room Time (wink #3). A hard cut
   to the clapper, two seconds, gone.
   ========================================================= */
.mbk-slate {
  position: fixed; inset: 0; z-index: 60;
  background: #050505;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 16px; text-align: center; padding: 24px;
}
.mbk-slate .slate-rule { width: 74px; height: 6px; background: var(--red); }
.mbk-slate p {
  color: #F5F1E4; font-family: var(--font-sans); font-weight: 300;
  text-transform: uppercase; letter-spacing: 0.34em; font-size: 12px; margin: 0;
}
.mbk-slate .slate-main { font-size: 15px; font-weight: 400; }

/* =========================================================
   THE BAIL-OUT LAW — prefers-reduced-motion, pointer:coarse,
   or missing APIs → final resting states, marquee stops, HUD
   freezes at 00:00:00:00, no pinned scenes.
   motion.js is authoritative (sets html[data-motion="off"]);
   the @media query below is the CSS-only fallback if JS never
   runs at all (e.g. blocked, or file:// script error upstream).
   ========================================================= */
html[data-motion="off"] [data-reveal] {
  transition: none !important;
  opacity: 1 !important;
  transform: none !important;
}
/* live loops — JS duplicated the track and stamped .is-live on the parent — keep scrolling even
   under data-motion="off": phones bail the heavy kit but the marquee still runs (KOVAS 2026-07-10,
   "on other sites we've built, that scroll works fine"). Non-live marquees (reduced-motion, or JS
   never ran the duplication) still collapse to the wrapped static list. The prefers-reduced-motion
   block below stays unconditional — accessibility outranks the loop. */
html[data-motion="off"] .marquee:not(.is-live) { overflow: visible; }
html[data-motion="off"] .marquee:not(.is-live) .marquee-track {
  animation: none !important;
  transform: none !important;
  display: flex;
  flex-wrap: wrap;
  white-space: normal;
  width: auto;
}
html[data-motion="off"] .grain-layer { animation: none !important; }
html[data-motion="off"] .kb-preview img,
html[data-motion="off"] .kb-preview .kb-img { transition: none !important; }
html[data-motion="off"] .status-dot { animation: none !important; }

@media (prefers-reduced-motion: reduce) {
  [data-reveal] {
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .marquee { overflow: visible; }
  .marquee-track {
    animation: none !important;
    transform: none !important;
    display: flex;
    flex-wrap: wrap;
    white-space: normal;
    width: auto;
  }
  .grain-layer { animation: none !important; }
  .kb-preview img,
  .kb-preview .kb-img { transition: none !important; }
  .status-dot { animation: none !important; }
}
