/* ═══════════════════════════════════════════════════════════════════
   cinema.css — scroll-driven layer
   ═══════════════════════════════════════════════════════════════════

   Shared across every page, loaded alongside i18n.js. cinema.js writes one
   value per element and nothing else:

     --p   0 → 1   travel progress through that element's scroll range

   Every visual decision lives here, in CSS, so the whole system themes and
   degrades without touching JS. Pinning is native `position: sticky`, not a
   JS pin: no pin-spacing bugs, no fighting Lenis for scroll ownership.

   Effects animate `transform`, `scale`, `opacity` and `clip-path` only.
   No width/height/top/left animation anywhere in this file.
   ═════════════════════════════════════════════════════════════════ */

/* Resting value. If cinema.js never runs (JS off, reduced motion, an old
   browser), every effect below evaluates to its finished state and the page
   reads as a normal, fully-visible document. Progressive enhancement is the
   default, not a fallback path. */
:root {
  --p: 1;
}

/* ═══ CLIP-PATH WIPE ═════════════════════════════════════════════════
   A diagonal edge travels across the still as it rises into view. Used on
   product shots: the wipe reads as the page uncovering evidence. */

[data-cine="wipe"] {
  clip-path: polygon(
    0 0,
    calc(var(--p) * 132%) 0,
    calc(var(--p) * 132% - 32%) 100%,
    0 100%
  );
}

/* ═══ SCROLL-SCRUBBED COUNTER ════════════════════════════════════════
   cinema.js writes the digits. The only styling needed is a stable digit
   width so the layout does not jitter as the number climbs. */

[data-cine="count"] {
  font-variant-numeric: tabular-nums;
}

/* ═══ STICKY CARD STACK ══════════════════════════════════════════════
   Each card pins at the same offset and the one behind shrinks and cools
   as the next arrives. Progress is measured on the slot (normal flow), so
   the sticky card inside can read --p through inheritance without the
   measurement stalling the moment it pins. */

.cine-stack { position: relative; }

/* Rests at 0, not the global 1. If cinema.js never runs, these slots must
   present unshrunk, fully opaque cards. */
.cine-stack-slot {
  position: relative;
  --p: 0;
}

/* `scale`, not `transform: scale()`. These cards also carry the site's
   `.reveal` class, whose `.reveal.visible { transform: none }` rule is more
   specific and would silently win, leaving the stack flat. The independent
   scale property sidesteps the conflict instead of escalating specificity. */
.cine-stack-card {
  position: sticky;
  top: var(--cine-stack-top, 8rem);
  scale: calc(1 - var(--p) * 0.06);
  transform-origin: center top;
  will-change: scale;
}

/* The card behind dims rather than disappearing: the reader can still see
   the stack has depth. */
.cine-stack-card > * { opacity: calc(1 - var(--p) * 0.55); }

/* Last card never shrinks: nothing arrives to displace it. */
.cine-stack-slot:last-child .cine-stack-card { scale: 1; }
.cine-stack-slot:last-child .cine-stack-card > * { opacity: 1; }

/* ═══ CHAPTER RAIL ═══════════════════════════════════════════════════
   A floating pill on the right edge. Tells the reader where they are in
   the story without a table of contents. Built at runtime from headings
   already on the page, so it carries no copy of its own and re-reads on
   language change.

   Right edge, not left: the site's text column is left-aligned, so the
   right margin is the genuinely empty one. On the left the pill sat over
   the article's own eyebrows. */

.cine-rail {
  position: fixed;
  right: clamp(0.75rem, 2.2vw, 1.75rem);
  /* Stated explicitly. This file loads before each page's inline styles, so
     any broad rule that sets a left offset would otherwise win the tie and
     drag the pill back across the page. */
  left: auto;
  bottom: auto;
  top: 50%;
  z-index: 700;

  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.62rem;

  /* The pill itself. */
  padding: 0.85rem 0.6rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--paper);
  box-shadow: 0 8px 30px -18px var(--plate-shadow, rgba(0, 0, 0, 0.4));

  /* Own geometry, stated explicitly. Shrink-to-fit on a fixed element
     resolves against the space beside it, and this file loads before each
     page's inline styles, so it loses every specificity tie; without this
     the pill stretched and its dots drifted into the article. */
  width: max-content;

  /* translateY keeps it centred; the -8px slide is the entrance. */
  transform: translateY(-50%) translateX(8px);
  opacity: 0;
  pointer-events: none;
  transition:
    opacity 0.55s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)),
    transform 0.55s var(--ease, cubic-bezier(0.16, 1, 0.3, 1));
}

.cine-rail.is-visible {
  opacity: 1;
  transform: translateY(-50%) translateX(0);
  pointer-events: auto;
}

.cine-rail-step {
  display: block;
  position: relative;
  width: 5px;
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  line-height: 0;
}

/* Idle: a small dot. Active: the dot grows into a vertical capsule, the
   same language as an iOS page indicator, so the reader's position in the
   sequence is legible at a glance without any text. */
.cine-rail-tick {
  display: block;
  width: 5px;
  height: 5px;
  border-radius: 999px;
  background: var(--ink-mute);
  opacity: 0.45;
  transition:
    height 0.5s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)),
    background-color 0.35s ease,
    opacity 0.35s ease;
}

.cine-rail-step:hover .cine-rail-tick {
  opacity: 0.85;
  background: var(--seal);
}

.cine-rail-step.is-on .cine-rail-tick {
  height: 20px;
  background: var(--seal);
  opacity: 1;
}

/* Label sits to the LEFT of the pill, since the pill hugs the right edge.
   Absolutely positioned, so it adds no width and no hit area: the rail's
   clickable region stays the 5px dot column rather than a wide invisible
   strip over the article. */
.cine-rail-label {
  position: absolute;
  right: calc(100% + 0.85rem);
  top: 50%;
  translate: 0 -50%;

  font-family: var(--sans);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1.4;
  white-space: nowrap;
  color: var(--ink-soft);

  padding: 0.32rem 0.6rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--paper);
  box-shadow: 0 6px 22px -16px var(--plate-shadow, rgba(0, 0, 0, 0.4));

  opacity: 0;
  transform: translateX(6px);
  pointer-events: none;
  transition:
    opacity 0.35s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)),
    transform 0.35s var(--ease, cubic-bezier(0.16, 1, 0.3, 1));
}

.cine-rail-step:hover .cine-rail-label {
  opacity: 1;
  transform: translateX(0);
  color: var(--seal);
}

/* Desktop affordance. Below 1100px there is no right margin to spare, and
   on touch there is no hover to reveal the labels. */
@media (max-width: 1100px), (hover: none) {
  .cine-rail { display: none; }
}

/* ═══ MOBILE ═════════════════════════════════════════════════════════ */

@media (max-width: 700px) {
  /* Card stacking is a large-screen luxury. On a phone the cards simply
     flow, which is what a reader on a phone actually wants. */
  .cine-stack-card { position: static; scale: 1; }
  .cine-stack-card > * { opacity: 1; }
}

/* ═══ REDUCED MOTION ═════════════════════════════════════════════════
   Mandatory. cinema.js also bails out before its rAF loop ever starts, so
   --p stays at its resting value and every effect below evaluates to its
   finished state. The rail still builds: it is navigation, not decoration,
   and it animates nothing on its own. */

@media (prefers-reduced-motion: reduce) {
  [data-cine="wipe"] { clip-path: none; }
  .cine-stack-card { position: static; scale: 1; }
  .cine-stack-card > * { opacity: 1; }
  .cine-rail,
  .cine-rail-tick,
  .cine-rail-label { transition: none; }
}
