/*
 * deck.css — the deck's components. Built on deck.tokens.css, which owns every
 * raw value. Nothing in this file may contain a hex colour or a bare px size
 * that isn't a hairline, a shadow, or a shape radius.
 *
 * The 14 stock reveal.js themes were removed when the runtime was vendored
 * (vendor/reveal.js/VENDOR-NOTES.md), so reveal.css handles layout, scaling and
 * the fixed canvas, and EVERYTHING visual is here.
 *
 * ── Direction: THE TABLE ────────────────────────────────────────────────────
 * Photographs are physical prints on a lit surface: rotated, overlapping,
 * piled. Each pile flips through its own images independently, so three piles
 * show twenty photographs without the deck advancing once (M5, F13).
 *
 * Supersedes the scaffold's instruction to design toward "unlike things next to
 * each other". That was written before spec/20-scope.md settled the point and
 * the spec now says the opposite in the owner's own words — "a manufactured
 * adjacency is not a connection". Range is carried by ACCUMULATION.
 *
 * ── Authoring contract ──────────────────────────────────────────────────────
 * Design pixels on 1920×1080. Units are self-contained and position-independent
 * (M10, D-22): a unit may be reordered, parked or restored at any time, so
 * nothing here may inherit spacing or setup from a neighbouring section.
 */

/* ── Canvas ────────────────────────────────────────────────────────────────── */

/* The scroll lock is scoped to the DECK, not to this stylesheet.

   The canvas being locked and unscrollable is the entire point of building this
   instead of a web page (F8) — but design/gallery.html loads the same
   stylesheet to render tiles, and an unscoped `overflow: hidden` silently
   truncates that page to whatever fits one screen. Marking the surface is
   explicit and cannot be inherited by accident. */
html[data-surface="deck"],
html[data-surface="deck"] body {
  overflow: hidden;
  overscroll-behavior: none;
}

html[data-surface="deck"] body { background: var(--table-edge); }

.reveal {
  font-family: var(--font-body);
  color: var(--ink);
}

/* A unit fills the whole canvas and positions its own contents absolutely.
   deck.js sets `center: false` so reveal does not vertically centre — an
   art-directed unit places its own objects. */
.reveal .slides section {
  width: 1920px;
  height: 1080px;
  padding: 0;
  text-align: left;
  overflow: hidden;

  /* The table: a pool of light, not a flat fill. A flat dark background reads
     as "black slide", which is the failure this direction exists to avoid. */
  background:
    radial-gradient(105% 78% at 26% 14%,
      var(--table-lit) 0%, var(--table-mid) 46%, var(--table-edge) 100%);
}

/* Grain, so the surface has tooth. Inline data-URI: it must survive file://
   like everything else, and it costs no request. */
.reveal .slides section::before {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  opacity: var(--grain);
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4'/></filter><rect width='160' height='160' filter='url(%23n)'/></svg>");
}

/* reveal draws frames and shadows on media by default. The photographs carry
   themselves; the print component supplies the only frame in this system. */
.reveal img,
.reveal video {
  display: block;
  border: 0;
  box-shadow: none;
  border-radius: 0;
  max-width: none;
  margin: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   PRINT — the atom of the whole system
   ═══════════════════════════════════════════════════════════════════════════ */

.print {
  position: absolute;
  inset: 0;
  background: var(--paper);
  padding: var(--mat);
  box-shadow: var(--lift);
}
.print::after {
  content: "";
  position: absolute; inset: 0; z-index: 3;
  pointer-events: none;
  background: var(--sheen);
}
.print--back { background: var(--paper-back); }
.print--bare { padding: 0; background: none; }   /* a modern borderless print */

/* The window a photograph lives in. Fixed by its slot, never by the image —
   the source set mixes 4032×3024 phone shots with 1680×2273 scans and at least
   one EXIF-rotated file, and `cover` in a fixed slot is the only thing that
   survives all of them without per-image CSS. */
.win {
  position: relative;
  width: 100%; height: 100%;
  overflow: hidden;
  background: var(--table-edge);
}
.win > img,
.win > video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity var(--crossfade) ease;
}
.win > img.is-on,
.win > video.is-on { opacity: 1; }
.win > img:only-child,
.win > video:only-child { opacity: 1; }

/* An empty slot states which folder fills it, rather than going blank. A blank
   frame reads as a bug; a numbered plate reads as an instruction — and the
   number is the folder name scripts/scan-media expects, so there is nothing to
   work out. Design-time only: any slot still showing one of these on the day is
   a slot with no photograph in it. */
.win--empty {
  display: grid; place-content: center; justify-items: center;
  gap: 10px;
  background:
    repeating-linear-gradient(45deg,
      rgba(43,36,27,0.045) 0 14px, rgba(43,36,27,0.09) 14px 28px),
    var(--paper-back);
  outline: 3px dashed rgba(43,36,27,0.24);
  outline-offset: -14px;
}
.win--empty b {
  font-family: var(--font-display);
  font-weight: var(--disp-weight); letter-spacing: var(--disp-track);
  font-size: 96px; line-height: 0.9;
  color: rgba(43,36,27,0.42);
}
.win--empty span {
  font-family: var(--font-body);
  font-size: 20px; letter-spacing: 0.06em;
  color: rgba(43,36,27,0.5);
}

/* ═══════════════════════════════════════════════════════════════════════════
   PILE — a stack of prints, the top one cycling (M5 / F13)

   F13 is satisfied structurally: the cycle is decorative, consumes NO
   navigation step, restarts from the first image on unit entry, and freezes to
   a single image under prefers-reduced-motion. Those three clauses are what
   keep it compatible with D-09, F6 and N6 — an unspecified carousel is either a
   fragment stack reveal already ships, or a state machine that breaks
   idempotent return.

   Only the TOP print cycles. The backing prints never change, which is both
   cheaper and truer: flipping through a pile on a table does not move the pile.
   ═══════════════════════════════════════════════════════════════════════════ */

.pile {
  position: absolute;
  left: var(--x); top: var(--y);
  width: var(--w); height: var(--h);
  transform: rotate(var(--r, 0deg));
  z-index: var(--z-art);
}
.pile .print--back { transform: rotate(var(--br, 0deg)); }

@media (prefers-reduced-motion: reduce) {
  .win > img { transition: none; }
  .win > img:not(:first-child) { display: none; }
  .win > img:first-child { opacity: 1; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   BLEED — one photograph filling the canvas

   Placed INSIDE the scaled canvas, never via data-background-image (F24, D-45):
   reveal backgrounds render outside the canvas and are cover-cropped to the
   viewport, so they crop differently on the laptop and on the TV.
   ═══════════════════════════════════════════════════════════════════════════ */

.bleed { position: absolute; inset: 0; z-index: 2; }
.bleed .win { background: var(--table-edge); }
/* A scrim only where type sits, so the photograph is not dimmed for nothing. */
.bleed--readable::after {
  content: "";
  position: absolute; inset: 0; z-index: 3;
  background: linear-gradient(to top,
    rgba(0,0,0,0.88) 0%, rgba(0,0,0,0.44) 32%, rgba(0,0,0,0) 60%);
}

/* ═══════════════════════════════════════════════════════════════════════════
   CARD — a print seen from the back, with writing on it.
   How a unit with no photographs stays inside the physical language instead of
   falling back to a bulleted list.
   ═══════════════════════════════════════════════════════════════════════════ */

.card {
  position: absolute;
  left: var(--x); top: var(--y);
  width: var(--w); height: var(--h);
  transform: rotate(var(--r, 0deg));
  background: var(--paper-back);
  padding: var(--mat-card);
  display: grid; place-items: center;
  box-shadow: var(--lift);
  z-index: var(--z-art);
}
.card::after {
  content: ""; position: absolute; inset: 0;
  pointer-events: none; background: var(--sheen);
}
.card span {
  font-family: var(--font-display);
  font-weight: var(--disp-weight);
  letter-spacing: var(--disp-track);
  font-size: var(--fs-print);
  line-height: var(--lh-tight);
  color: var(--paper-ink);
  text-align: center;
}
.card--now { background: #F5ECD8; }
.card--now span { color: var(--mark); }

/* ═══════════════════════════════════════════════════════════════════════════
   RUN — a timeline drawn on the surface

   For a unit that is a SEQUENCE rather than a scatter. Stops alternate above
   and below a drawn spine, each on its own stalk of a different length, so the
   eye travels left to right instead of scanning a block.

   Every stop sizes to ITS OWN CONTENT — a name, or a name plus a detail line.
   That is the point: a fixed card forces every entry to carry equal weight, and
   they do not. A first job and a current company are not the same size.
   ═══════════════════════════════════════════════════════════════════════════ */

.run-spine {
  position: absolute;
  left: var(--edge-tight); right: var(--edge-tight);
  top: var(--spine, 620px);
  height: 2px;
  transform: rotate(-0.4deg);          /* drawn, not ruled */
  background: linear-gradient(90deg,
    rgba(244,238,227,0) 0%, rgba(244,238,227,0.40) 5%,
    rgba(244,238,227,0.40) 93%, rgba(244,238,227,0) 100%);
  z-index: var(--z-spine);
}

.stop {
  position: absolute;
  left: var(--x); width: var(--w);
  transform: rotate(var(--r, 0deg));
  z-index: var(--z-art);
}
/* Anchored to the spine from whichever side the stop sits on, so a taller card
   grows AWAY from the line instead of pushing through it. */
.stop--up { bottom: calc(1080px - var(--spine, 620px) + var(--tick, 58px)); }
.stop--dn { top:    calc(var(--spine, 620px) + var(--tick, 58px)); }

.stop__stalk {
  position: absolute; left: 30px; width: 2px;
  background: rgba(244,238,227,0.30);
}
.stop--up .stop__stalk { top: 100%; height: var(--tick, 58px); }
.stop--dn .stop__stalk { bottom: 100%; height: var(--tick, 58px); }

.stop__dot {
  position: absolute; left: 25px;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--paper);
  box-shadow: 0 0 0 4px var(--table-edge);
}
.stop--up .stop__dot { top: calc(100% + var(--tick, 58px) - 5px); }
.stop--dn .stop__dot { bottom: calc(100% + var(--tick, 58px) - 5px); }

.stop__card {
  position: relative;
  background: var(--paper-back);
  padding: var(--mat-card);
  box-shadow: var(--lift-sm);
}
.stop__card::after {
  content: ""; position: absolute; inset: 0;
  pointer-events: none; background: var(--sheen);
}
.stop__card span {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--disp-weight);
  letter-spacing: var(--disp-track);
  font-size: var(--fs-print);
  line-height: var(--lh-tight);
  color: var(--paper-ink);
}
.stop__card em {
  display: block; margin-top: 7px;
  font-family: var(--font-body); font-style: normal; font-weight: 400;
  font-size: var(--fs-detail); line-height: 1.25;
  color: var(--paper-ink-soft);
}
.stop--now .stop__card { background: #F5ECD8; }
.stop--now .stop__card span { color: var(--mark); }
.stop--now .stop__dot { background: var(--mark); }

/* ═══════════════════════════════════════════════════════════════════════════
   ERA — a run divided into named chapters

   For a sequence that is not one flat list but a few distinct periods. The
   duration is set as a NUMBER, large, because that is the fact the room
   actually retains: four years, then eleven, then twelve.

   Deliberately NOT proportional-width. Making each era's width match its
   duration is the obvious idea and it fails on this content — the earliest era
   is the shortest in years and the longest in jobs, so a truthful width gives
   the most entries the least room. The number carries the proportion instead,
   which reads from across a room where a width ratio does not.
   ═══════════════════════════════════════════════════════════════════════════ */

.era-head {
  position: absolute;
  left: var(--x); top: var(--y); width: var(--w);
  z-index: var(--z-say);
}
.era-head .yrs {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--disp-weight);
  letter-spacing: var(--disp-track);
  font-size: calc(96px * var(--disp-scale));
  line-height: 0.92;
  color: var(--ink);
}
.era-head .yrs i {
  font-family: var(--font-body); font-style: normal; font-weight: 400;
  font-size: 0.34em; letter-spacing: 0.02em;
  color: var(--ink-faint); margin-left: 10px;
}
.era-head .name {
  display: block; margin-top: 10px;
  font-family: var(--font-body); font-size: 32px;
  color: var(--ink-soft);
}

/* The boundary tick where one era hands over to the next. */
.era-mark {
  position: absolute;
  left: var(--x);
  top: calc(var(--spine, 470px) - 15px);
  width: 2px; height: 32px;
  background: rgba(244,238,227,0.55);
  transform: rotate(-0.4deg);
  z-index: var(--z-spine);
}

/* JOB — one entry hanging under an era. Sizes to its own content, so an entry
   that needs a detail line gets one and the rest stay bare. */
.job {
  position: absolute;
  left: var(--x); top: var(--y); width: var(--w);
  transform: rotate(var(--r, 0deg));
  background: var(--paper-back);
  padding: 16px 20px;
  box-shadow: var(--lift-sm);
  z-index: var(--z-art);
}
.job::after {
  content: ""; position: absolute; inset: 0;
  pointer-events: none; background: var(--sheen);
}
.job span {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--disp-weight);
  letter-spacing: var(--disp-track);
  font-size: 32px; line-height: var(--lh-tight);
  color: var(--paper-ink);
}
.job em {
  display: block; margin-top: 6px;
  font-family: var(--font-body); font-style: normal; font-weight: 400;
  font-size: var(--fs-detail); line-height: 1.25;
  color: var(--paper-ink-soft);
}
/* Logo slot. A logo sits on cream paper, so most brand marks work as supplied —
   but a mark with a white knockout or its own white plate will show its box.
   Supply a transparent PNG or SVG trimmed to the artwork. */
.job__logo {
  display: block;
  max-height: 46px; max-width: 100%; width: auto;
  object-fit: contain; object-position: left center;
}
.job--now { background: #F5ECD8; }
.job--now span { color: var(--mark); }

/* A group with no name renders as its roles alone — they are peers, not a
   heading and a detail, so they take equal weight. */
.job--roles em { font-size: 26px; color: var(--paper-ink); margin-top: 4px; }
.job--roles em:first-of-type { margin-top: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   SHEET — a full sheet of paper laid on the table (HUSTLE-B)
   The most legible of the career layouts, and the least like a chart.
   ═══════════════════════════════════════════════════════════════════════════ */

.sheet {
  position: absolute;
  left: var(--x); top: var(--y); width: var(--w); height: var(--h);
  transform: rotate(var(--r, 0deg));
  background: var(--paper);
  padding: 34px 36px;
  box-shadow: var(--lift);
  overflow: hidden;
  z-index: var(--z-art);
  /* Column layout so the logo strip can sit on the sheet's bottom edge while
     the entry list takes whatever height is left. Without this the strip either
     floats mid-sheet or pushes the list off the bottom. */
  display: flex;
  flex-direction: column;
}
.sheet__list { flex: 1 1 auto; min-height: 0; }
.sheet::after {
  content: ""; position: absolute; inset: 0;
  pointer-events: none; background: var(--sheen);
}
.sheet__head {
  border-bottom: 2px solid rgba(43,36,27,0.22);
  padding-bottom: 18px; margin-bottom: 20px;
}
.sheet__head .yrs {
  display: block;
  font-family: var(--font-display); font-weight: var(--disp-weight);
  letter-spacing: var(--disp-track);
  font-size: calc(78px * var(--disp-scale)); line-height: 0.94;
  color: var(--paper-ink);
}
.sheet__head .yrs i {
  font-family: var(--font-body); font-style: normal; font-weight: 400;
  font-size: 0.32em; color: var(--paper-ink-soft); margin-left: 10px;
}
.sheet__head .name {
  display: block; margin-top: 8px;
  font-family: var(--font-body); font-size: 28px; color: var(--paper-ink-soft);
}
.entry { padding: 13px 0; border-bottom: 1px solid rgba(43,36,27,0.14); }
.entry:last-child { border-bottom: 0; }
.entry span {
  display: block;
  font-family: var(--font-display); font-weight: var(--disp-weight);
  letter-spacing: var(--disp-track);
  font-size: 34px; line-height: 1.06; color: var(--paper-ink);
}
.entry em {
  display: block; margin-top: 5px;
  font-family: var(--font-body); font-style: normal;
  font-size: 22px; line-height: 1.3; color: var(--paper-ink-soft);
}
.entry--roles em { font-size: 28px; color: var(--paper-ink); margin-top: 4px; }
.entry--roles em:first-of-type { margin-top: 0; }

/* ── Logo placements on a sheet ──────────────────────────────────────────────
   Two per sheet, each able to cycle. Small on purpose: these are provenance,
   not branding — the room should register "real companies" without stopping to
   read them, and a logo big enough to read is a logo competing with the words.

   `mix-blend-mode: multiply` is doing real work. Several of these files are RGB
   with no alpha channel, so on cream paper they would each show a white box.
   Multiply drops white into the paper and leaves the mark, which is why the
   strip can take supplied logos as-is instead of needing every one re-cut. */
.sheet__logos {
  flex: 0 0 auto;
  display: flex; gap: 18px; align-items: center;
  margin-top: 18px; padding-top: 16px;
  border-top: 1px solid rgba(43,36,27,0.16);
}
.sheet__logo {
  flex: 1 1 0; min-width: 0;
  position: relative;
  height: 62px;
}
.sheet__logo .win {
  position: absolute; inset: 0;
  background: none;              /* not a photo frame — no dark plate behind */
  overflow: visible;
}
.sheet__logo .win > img {
  object-fit: contain;           /* never crop a wordmark */
  object-position: center;
  mix-blend-mode: multiply;
  /* Faster than a photograph's crossfade. At a 3s hold, the 1.1s photo fade
     would spend a third of every cycle mid-dissolve, which reads as a logo
     that is permanently blurry rather than one that changes. */
  transition-duration: 550ms;
}

/* ═══════════════════════════════════════════════════════════════════════════
   BAND — three horizontal lanes down the canvas (HUSTLE-C)
   ═══════════════════════════════════════════════════════════════════════════ */

.band {
  position: absolute; left: 0; right: 0; top: var(--y);
  height: 240px; z-index: var(--z-spine);
}
.band__rule {
  position: absolute; left: var(--edge-tight); right: var(--edge-tight); top: 112px;
  height: 2px;
  background: linear-gradient(90deg,
    rgba(244,238,227,0) 0%, rgba(244,238,227,0.32) 4%,
    rgba(244,238,227,0.32) 96%, rgba(244,238,227,0) 100%);
}
.band__label { position: absolute; left: var(--edge); top: 8px; width: 340px; }
.band__label .yrs {
  display: block;
  font-family: var(--font-display); font-weight: var(--disp-weight);
  letter-spacing: var(--disp-track);
  font-size: calc(70px * var(--disp-scale)); line-height: 0.94; color: var(--ink);
}
.band__label .yrs i {
  font-family: var(--font-body); font-style: normal; font-weight: 400;
  font-size: 0.32em; color: var(--ink-faint); margin-left: 8px;
}
.band__label .name {
  display: block; margin-top: 4px;
  font-family: var(--font-body); font-size: 26px; color: var(--ink-soft);
}

/* ═══════════════════════════════════════════════════════════════════════════
   LADDER — three treads stepping down and across (HUSTLE-D)
   The only career layout that shows a career going somewhere.
   ═══════════════════════════════════════════════════════════════════════════ */

.ladder-line {
  position: absolute; inset: 0; z-index: var(--z-spine);
  background: linear-gradient(to bottom right,
    transparent calc(50% - 1px), rgba(244,238,227,0.14) 50%, transparent calc(50% + 1px));
}
.tread { position: absolute; left: var(--x); top: var(--y); width: var(--w); z-index: var(--z-say); }
.tread .yrs {
  display: block;
  font-family: var(--font-display); font-weight: var(--disp-weight);
  letter-spacing: var(--disp-track);
  font-size: calc(74px * var(--disp-scale)); line-height: 0.94; color: var(--ink);
}
.tread .yrs i {
  font-family: var(--font-body); font-style: normal; font-weight: 400;
  font-size: 0.32em; color: var(--ink-faint); margin-left: 8px;
}
.tread .name {
  display: block; margin-top: 4px;
  font-family: var(--font-body); font-size: 26px; color: var(--ink-soft);
}

/* ═══════════════════════════════════════════════════════════════════════════
   SLIDE NAME — a DESIGN-TIME label, top right.

   Hidden by default and shown only when <html data-labels> is set, which the
   pattern deck does and the real deck does not. It is a handle for talking
   about layouts, never chrome for the room — F22 permits exactly one on-screen
   element during a presentation and that is the countdown.
   ═══════════════════════════════════════════════════════════════════════════ */

.slide-name { display: none; }
html[data-labels] .slide-name {
  display: block;
  position: absolute; top: 30px; right: 40px;
  z-index: var(--z-tick);
  font-family: var(--font-body);
  font-size: 20px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--ink-faint); opacity: 0.6;
  pointer-events: none;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SAY — the only type on the table itself
   ═══════════════════════════════════════════════════════════════════════════ */

.say {
  position: absolute;
  z-index: var(--z-say);
  max-width: var(--sw, 820px);
}
.say h2 {
  margin: 0 0 22px;
  font-family: var(--font-display);
  font-weight: var(--disp-weight);
  font-size: calc(var(--fs-display) * var(--disp-scale));
  line-height: var(--disp-leading);
  letter-spacing: var(--disp-track);
  text-transform: none;          /* reveal uppercases headings by default */
  color: var(--ink);
}
.say p {
  margin: 0 0 12px;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--fs-lede);
  line-height: var(--lh-body);
  color: var(--ink-soft);
}
.say p.soft { color: var(--ink-faint); }

.say .hero {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--disp-weight);
  letter-spacing: var(--disp-track);
  font-size: calc(var(--fs-hero) * var(--disp-scale));
  line-height: 0.9;
  color: var(--ink);
}

/* A unit whose surface is already full needs a smaller voice. This has to be a
   property of the UNIT, never a global size change — a global change would
   retune every other unit, and units are position-independent (M10). */
.say--sm h2 { font-size: calc(var(--fs-display-sm) * var(--disp-scale)); margin-bottom: 18px; }
.say--sm p  { font-size: var(--fs-lede-sm); }

/* ═══════════════════════════════════════════════════════════════════════════
   TICK — the presenter countdown (F26–F28 / D-49)

   ONE number. Small. Lower corner. INDICATOR ONLY: it never advances the deck,
   never blocks navigation, never alters the unit (F26). It resets on unit entry
   including BACKWARD entry (F27), because a timer that remembered previous
   visits would break the idempotent return F6 requires.

   F29's cumulative-drift readout is deliberately not built. F29 is worded
   "recommended, not required" and the owner asked for a single counter, so this
   is a choice the spec allows rather than a deviation from it.

   This is the ONLY on-screen chrome in the deck — the explicit carve-out named
   in F22.
   ═══════════════════════════════════════════════════════════════════════════ */

.tick {
  position: absolute;
  right: 40px; bottom: 30px;
  z-index: var(--z-tick);
  font-family: var(--font-body);
  font-size: var(--fs-tick);
  letter-spacing: 0.05em;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
  opacity: 0.62;
  pointer-events: none;
}
/* N6's recorded exemption: this flash survives prefers-reduced-motion because
   it is the entire point of the instrument. */
.tick.is-out {
  color: var(--mark);
  opacity: 1;
  animation: tick-flash 1s steps(1, end) infinite;
}
@keyframes tick-flash { 0%, 55% { opacity: 1; } 56%, 100% { opacity: 0.2; } }

/* ═══════════════════════════════════════════════════════════════════════════
   MOTION — one orchestrated moment per unit

   Prints land on the table, staggered. transform and opacity only. The stagger
   delay is applied by deck.js rather than in markup, so a unit never needs to
   know its own index (M10).
   ═══════════════════════════════════════════════════════════════════════════ */

.reveal .slides section .pile,
.reveal .slides section .card,
.reveal .slides section .stop,
.reveal .slides section .job,
.reveal .slides section .sheet,
.reveal .slides section .era-head,
.reveal .slides section .era-mark,
.reveal .slides section .tread,
.reveal .slides section .band,
.reveal .slides section .ladder-line,
.reveal .slides section .run-spine,
.reveal .slides section .bleed,
.reveal .slides section .say > * { opacity: 0; }

.reveal .slides section.is-live .pile,
.reveal .slides section.is-live .card,
.reveal .slides section.is-live .stop,
.reveal .slides section.is-live .job,
.reveal .slides section.is-live .sheet {
  animation: land var(--dur-land) var(--ease-land) both;
}
.reveal .slides section.is-live .era-head,
.reveal .slides section.is-live .era-mark,
.reveal .slides section.is-live .tread,
.reveal .slides section.is-live .band,
.reveal .slides section.is-live .ladder-line {
  animation: rise var(--dur-rise) var(--ease-rise) both;
}
.reveal .slides section.is-live .say > * {
  animation: rise var(--dur-rise) var(--ease-rise) both;
}
.reveal .slides section.is-live .bleed {
  animation: fade var(--dur-rise) var(--ease-rise) both;
}
/* The spine is drawn before its stops land on it — the sequence reads as a line
   being ruled and then annotated, which is the idea of the unit. */
.reveal .slides section.is-live .run-spine {
  animation: draw var(--dur-rise) var(--ease-rise) both;
}

@keyframes land {
  from { opacity: 0; transform: rotate(var(--r, 0deg)) translateY(-30px) scale(1.05); }
  to   { opacity: 1; transform: rotate(var(--r, 0deg)) translateY(0) scale(1); }
}
@keyframes rise {
  from { opacity: 0; transform: translateY(22px); }
  to   { opacity: 1; transform: none; }
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes draw {
  from { opacity: 0; clip-path: inset(0 100% 0 0); }
  to   { opacity: 1; clip-path: inset(0 0 0 0); }
}

/* Fragments (M6) — a little travel reads better than opacity alone at TV
   distance, but only a little: this is a reveal, not an entrance. */
.reveal .slides section .fragment {
  transition: opacity 250ms ease, transform 250ms ease;
}
.reveal .slides section .fragment:not(.visible) { transform: translateY(8px); }

@media (prefers-reduced-motion: reduce) {
  .reveal .slides section .pile,
  .reveal .slides section .card,
  .reveal .slides section .stop,
  .reveal .slides section .job,
  .reveal .slides section .sheet,
  .reveal .slides section .era-head,
  .reveal .slides section .era-mark,
  .reveal .slides section .tread,
  .reveal .slides section .band,
  .reveal .slides section .ladder-line,
  .reveal .slides section .run-spine,
  .reveal .slides section .bleed,
  .reveal .slides section .say > * { opacity: 1; }

  .reveal .slides section.is-live .pile,
  .reveal .slides section.is-live .card,
  .reveal .slides section.is-live .stop,
  .reveal .slides section.is-live .job,
  .reveal .slides section.is-live .sheet { animation: none; transform: rotate(var(--r, 0deg)); }
  .reveal .slides section.is-live .era-head,
  .reveal .slides section.is-live .tread,
  .reveal .slides section.is-live .band,
  .reveal .slides section.is-live .ladder-line { animation: none; transform: none; }
  .reveal .slides section.is-live .era-mark { animation: none; transform: rotate(-0.4deg); }
  .reveal .slides section.is-live .run-spine { animation: none; clip-path: none; transform: rotate(-0.4deg); }
  .reveal .slides section.is-live .say > *,
  .reveal .slides section.is-live .bleed { animation: none; transform: none; }

  .reveal .slides section .fragment { transition: none; }
  .reveal .slides section .fragment:not(.visible) { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   COLD-START GATE (M12)
   Covers the canvas until every asset the deck references has loaded. If
   JavaScript fails outright this stays up, which is the correct failure: a
   visible "not ready" beats a half-drawn slide in front of a room.
   ═══════════════════════════════════════════════════════════════════════════ */

.deck-boot {
  position: fixed; inset: 0; z-index: 9999;
  display: grid; place-items: center;
  background: var(--table-edge);
  color: var(--ink-faint);
  font-family: var(--font-body);
  font-size: 28px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   PRESENTER HUD
   A deck shows nothing but the slide (D-09), so these controls live in the
   same place the mouse pointer does: visible while the pointer is moving,
   gone a couple of seconds after it stops. The clicker never moves the
   pointer, so across the whole talk this stays invisible.

   In SCREEN space, not canvas space — it sits outside .reveal so it is not
   scaled by reveal's transform. A 4K panel would otherwise render this at 2×.
   ═══════════════════════════════════════════════════════════════════════════ */

.hud {
  position: fixed; z-index: 9998;
  right: 22px; top: 18px;
  display: flex; gap: 10px;
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 220ms ease, transform 220ms ease;
  pointer-events: none;
}

/* Shown by deck.js on pointer movement. Also shown on keyboard focus so the
   button is reachable without a mouse at all. */
html.hud-on .hud,
.hud:focus-within {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.hud__btn {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 9px 15px 9px 12px;
  font-family: var(--font-body);
  font-size: 15px; line-height: 1;
  color: var(--ink-soft);
  background: rgba(13, 11, 8, 0.82);
  border: 1px solid rgba(244, 238, 227, 0.16);
  border-radius: 7px;
  cursor: pointer;
  backdrop-filter: blur(6px);
  transition: color 160ms ease, border-color 160ms ease, background 160ms ease;
}

.hud__btn:hover  { color: var(--ink); border-color: rgba(244, 238, 227, 0.34); }
.hud__btn:active { background: rgba(13, 11, 8, 0.95); }
.hud__btn:focus-visible { outline: 2px solid var(--mark); outline-offset: 2px; }

/* Corner-bracket glyph, drawn in CSS so there is no icon font and no SVG file
   to go missing over file://. Flips to inward brackets once full screen. */
.hud__ico {
  width: 15px; height: 15px;
  border: 2px solid currentColor;
  border-radius: 2px;
  /* carve the middle of each edge away, leaving four corners */
  -webkit-mask: linear-gradient(#000 0 0) top left    / 5px 5px no-repeat,
                linear-gradient(#000 0 0) top right   / 5px 5px no-repeat,
                linear-gradient(#000 0 0) bottom left / 5px 5px no-repeat,
                linear-gradient(#000 0 0) bottom right/ 5px 5px no-repeat;
          mask: linear-gradient(#000 0 0) top left    / 5px 5px no-repeat,
                linear-gradient(#000 0 0) top right   / 5px 5px no-repeat,
                linear-gradient(#000 0 0) bottom left / 5px 5px no-repeat,
                linear-gradient(#000 0 0) bottom right/ 5px 5px no-repeat;
}

html.is-full .hud__ico { transform: scale(0.72); }

/* The pointer is hidden with the HUD. On a TV a stray arrow parked over a
   photograph is the most visible thing on the slide. */
html[data-surface="deck"].hud-off,
html[data-surface="deck"].hud-off body { cursor: none; }

@media (prefers-reduced-motion: reduce) {
  .hud { transition: none; transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   OVERVIEW (recovery aid, R-02)
   deck.js re-lays reveal's overview out as a GRID instead of the single row a
   flat deck produces. These two rules are the part CSS owns.

   reveal marks the current card with `outline: 10px solid var(--r-link-color)`,
   and --r-link-color ships in reveal's THEME css — which is pruned from this
   vendor copy on purpose. An undefined var() makes the whole declaration
   invalid at computed-value time, so that rule was resolving to NO outline and
   quietly cancelling the grey one above it: in a grid of fifteen, nothing
   showed you where you were. Define it, and the marker works as intended.
   ═══════════════════════════════════════════════════════════════════════════ */

.reveal-viewport { --r-link-color: var(--mark); }

/* The grid camera. deck.js writes --ov-scale/--ov-x/--ov-y; this consumes them.
   !important is load-bearing, not laziness: reveal writes the .slides transform
   as an INLINE style and re-composes it from its own layer state on every
   layout(), so an inline write from us is discarded. An author !important
   declaration outranks a non-important inline style, so this holds regardless
   of how often reveal rewrites its own. Only applies in overview. */
.reveal.overview .slides {
  transform: scale(var(--ov-scale, 0.2))
             translateX(var(--ov-x, 0px))
             translateY(var(--ov-y, 0px)) !important;
}

/* The cards are ~400px wide in the grid. Titles set for a 1920px canvas are
   unreadable at that size, so lean on the photographs — dim everything that is
   not current, and let the accent outline carry "you are here". */
.reveal.overview .slides section        { opacity: 0.62 !important; transition: opacity 140ms ease; }
.reveal.overview .slides section:hover,
.reveal.overview .slides section.present { opacity: 1 !important; }
