/* ==========================================================================
   base.css — Reset, base typography, layout primitives, reveal-state CSS,
   accessibility, and the reduced-motion fallback. Consumes tokens.css.
   ========================================================================== */

/* ---- Modern reset ---- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  background: #f6efe3; /* warm default, prevents flash before hero paints */
  -webkit-tap-highlight-color: transparent;
}
body {
  font-family: var(--font-sans);
  font-size: var(--step-0);
  line-height: 1.62;
  font-weight: 400;
  color: var(--fg);
  background: transparent;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-synthesis: none;
  overflow-x: clip;
}
img, picture, svg, canvas, video {
  display: block;
  max-width: 100%;
}
canvas { height: auto; }
input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; padding: 0; }
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; padding: 0; }
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}
::selection { background: var(--accent); color: var(--on-accent); }

/* ---- Display typography defaults ---- */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 380;
  line-height: 1.04;
  letter-spacing: -0.018em;
  text-wrap: balance;
  font-optical-sizing: auto;
  color: var(--fg);
}
p { text-wrap: pretty; }
strong, b { font-weight: 600; }
em, i { font-style: italic; }

/* ---- Layout primitives ---- */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--wide { max-width: var(--container-wide); }
.container--narrow { max-width: var(--container-narrow); }

.measure { max-width: var(--measure); }
.measure-wide { max-width: var(--measure-wide); }

.stack { display: flow-root; }
.stack > * + * { margin-block-start: var(--flow, 1.1em); }

/* A "moment": a full-viewport centered statement that lands hard. */
.moment {
  position: relative;
  min-height: 100vh; /* fallback for engines without small-viewport units */
  min-height: 100svh;
  display: grid;
  align-content: center;
  padding-block: var(--section-pad);
}
.section {
  position: relative;
  padding-block: var(--section-pad);
}

/* Per-section background layer (holds the network canvas). */
.act__bg {
  position: absolute;
  inset: 0;
  z-index: var(--z-bg);
  overflow: clip;
  pointer-events: none;
}
/* Canvas sticks to the viewport within its act, so it only ever paints a
   viewport-sized surface no matter how tall the act is (performance). */
.act__bg > canvas {
  position: sticky;
  top: 0;
  width: 100%;
  height: 100vh; /* fallback so the sticky canvas never collapses to its intrinsic height */
  height: 100svh;
}

/* Content sits above the background layer. */
.act > .container,
.act > .moment,
.act > .section,
.layer { position: relative; z-index: var(--z-content); }

/* ---- Accessibility helpers ---- */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}
.skip-link {
  position: fixed;
  top: 0.75rem; left: 0.75rem;
  z-index: var(--z-skip);
  padding: 0.6rem 1rem;
  background: var(--fg);
  color: var(--bg);
  border-radius: var(--radius-s);
  font-size: var(--step--1);
  font-weight: 600;
  transform: translateY(-160%);
  transition: transform var(--dur-1) var(--ease-out);
}
.skip-link:focus { transform: translateY(0); }

/* ==========================================================================
   REVEAL STATE — only active when JS is present (html.js). reveal.js toggles
   .is-visible; the transition lives here. Without JS, everything is visible.
   ========================================================================== */
html.js [data-reveal] {
  opacity: 0;
  transform: translate3d(0, var(--reveal-shift), 0);
  /* Desynchronized resolve: opacity lands first (legible early), the transform
     keeps settling after, blur clears on its own track. One curve per property,
     never one curve for the element. */
  transition:
    opacity calc(var(--dur-2) * 0.55) var(--ease-soft),
    transform var(--dur-2) var(--ease-out),
    filter calc(var(--dur-2) * 0.7) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
  /* will-change is applied dynamically by reveal.js while a reveal is in flight,
     then cleared — so we don't hold ~40 compositor layers for the whole session. */
}
html.js [data-reveal="left"]  { transform: translate3d(calc(var(--reveal-shift) * -1.4), 0, 0); }
html.js [data-reveal="right"] { transform: translate3d(calc(var(--reveal-shift) *  1.4), 0, 0); }
html.js [data-reveal="scale"] { transform: scale(0.94); }
html.js [data-reveal="blur"]  { filter: blur(14px); opacity: 0; transform: translate3d(0, calc(var(--reveal-shift) * 0.6), 0); }
html.js [data-reveal="fade"]  { transform: none; }

/* Object entrances arrive with mass: a true damped-spring settle on the scale
   variant (scale passes through 1.0, kisses ~1.01, settles back). Behind
   @supports so engines without linear() keep the expo-out settle. The delay
   longhand is re-declared AFTER the shorthand — the shorthand resets it. */
@supports (transition-timing-function: linear(0, 1)) {
  html.js [data-reveal="scale"] {
    transition:
      opacity calc(var(--dur-2) * 0.55) var(--ease-soft),
      transform calc(var(--dur-2) * 1.15) var(--ease-spring);
    transition-delay: var(--reveal-delay, 0ms);
  }
}

/* Clip / line reveal — text wipes up. The clip lives on an inner .line-wipe span
   (added by reveal.js), NOT the observed element: a fully clipped element reports
   ZERO IntersectionObserver intersection, so the observer would never fire and the
   line would stay hidden forever. The observed element hides via opacity instead
   (IO ignores opacity), so it still triggers on scroll. */
html.js [data-reveal="line"] {
  opacity: 0;
  transform: none;
  transition: opacity 0.01s linear;
  transition-delay: var(--reveal-delay, 0ms);
}
html.js [data-reveal="line"] > .line-wipe {
  display: block;
  clip-path: inset(0 0 105% 0);
  transition: clip-path var(--dur-3) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}

/* Revealed state */
html.js [data-reveal].is-visible {
  opacity: 1;
  transform: none;
  filter: none;
}
html.js [data-reveal="line"].is-visible > .line-wipe { clip-path: inset(0 0 -10% 0); }

/* Stagger group: reveal.js sets --i on each child; CSS spaces the delays. */
html.js [data-reveal-group] > * {
  opacity: 0;
  transform: translate3d(0, var(--reveal-shift), 0);
  transition:
    opacity calc(var(--dur-2) * 0.55) var(--ease-soft),
    transform var(--dur-2) var(--ease-out);
  transition-delay: calc(var(--group-stagger, var(--stagger)) * var(--i, 0));
}
html.js [data-reveal-group].is-visible > * {
  opacity: 1;
  transform: none;
}

/* Presenter flight mode — while main.js glides between stops, every reveal
   resolves instantly: passed-over content never plays a late cascade behind
   the presenter, and the arrival replay can reset builds without a visible
   un-reveal. Covers nested animators (.word__inner, .line-wipe, em underlines). */
html.js.is-jumping [data-reveal],
html.js.is-jumping [data-reveal] *,
html.js.is-jumping [data-reveal-group] > * {
  transition-duration: 0s !important;
  transition-delay: 0s !important;
}

/* Proximity snap on the presenter spine: resting scroll settles on a moment
   when it's already close — never fights free scrolling between moments,
   and never fights the glide (flight mode suspends it; the glide lands on
   snap-aligned positions anyway). */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-snap-type: y proximity; }
  html.is-jumping { scroll-snap-type: none; }
  [data-stop] { scroll-snap-align: start; }
}

/* ==========================================================================
   REDUCED MOTION — honor the viewer's setting with a graceful static fallback.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  html.js [data-reveal],
  html.js [data-reveal-group] > *,
  html.js .line-wipe {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    clip-path: none !important;
  }
}
