/* ==========================================================================
   anim.css — the shared look of every hand-drawn animation in the notes.

   Pair it with assets/anim.js. A page supplies a scene array; this file
   supplies the paper, the pen and the motion.

   COLOUR TOKENS
   -------------
   The site theme (scss/minimal.scss) is the authority: it declares
   --anim-ink, --anim-ink-soft, --anim-ink-faint, --anim-paper, --anim-white,
   --anim-accent, --anim-accent-soft, --anim-amber, --anim-amber-soft and
   --rule on :root. Every one of those is re-declared on .anim-stage below
   *as a fallback only*, so a stage pasted into a bare HTML file with no
   theme still renders exactly right.

   Inside a stage, write var(--_accent), var(--_ink), … — the short private
   names resolved once on .anim-stage. Never write a literal colour.

   FONT
   ----
   The hand face is self-hosted: Excalifont (Excalidraw's redesign of
   Virgil), SIL Open Font License 1.1 — see assets/fonts/excalifont-LICENSE.
   Subset to Latin-1 + the punctuation the notes use, 30 kB WOFF. Without
   it, Linux and ChromeOS fell through to generic `cursive` and every
   reserved height in this file drifted. Every hand-font rule also carries
   an explicit line-height, so the reserved heights hold whichever face
   actually wins.
   ========================================================================== */

@font-face {
  font-family: "Excalifont";
  src: url("fonts/excalifont-hand.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   1. The stage
   -------------------------------------------------------------------------- */
.anim-stage {
  /* Theme first, literal second. Do not reorder. */
  --_ink: var(--anim-ink, #2c2a3a);
  --_ink-soft: var(--anim-ink-soft, #6b6880);
  --_ink-faint: var(--anim-ink-faint, #9c95b5);
  --_paper: var(--anim-paper, #fffdf8);
  --_white: var(--anim-white, #ffffff);
  --_accent: var(--anim-accent, #593196);
  --_accent-soft: var(--anim-accent-soft, #f0e9fb);
  --_amber: var(--anim-amber, #c2410c);
  --_amber-soft: var(--anim-amber-soft, #fdeadf);
  --_rule: var(--rule, #e7e3f0);

  /* Terminal palette. Nine values that used to be hardcoded; the dim one is
     raised from #8f89a6 (4.82:1) to #a49ebd (~6.6:1 on #221f30), because it
     is set at 12.5px italic. */
  --_term-bg: var(--anim-term-bg, #221f30);
  --_term-fg: var(--anim-term-fg, #ece9f5);
  --_term-title-bg: var(--anim-term-title-bg, #322c46);
  --_term-title-fg: var(--anim-term-title-fg, #cdc7e0);
  --_term-light: var(--anim-term-light, #d8d3e6);
  --_term-tutor: var(--anim-term-tutor, #f2b544);
  --_term-you: var(--anim-term-you, #86c5ff);
  --_term-dim: var(--anim-term-dim, #a49ebd);
  /* No green anywhere: the typed command takes a pale tint of the accent,
     the success line a pale tint of the amber. */
  --_term-cmd: var(--anim-term-cmd, #cbb4f0);
  --_term-ok: var(--anim-term-ok, #e8c27a);
  --_term-caret: var(--anim-term-caret, #86c5ff);

  --_hand: var(--anim-hand, "Excalifont", "Bradley Hand", "Chalkboard SE",
    "Segoe Print", "Comic Sans MS", "Comic Neue", "Trebuchet MS", "Verdana",
    sans-serif);
  --_mono: var(--anim-mono, ui-monospace, SFMono-Regular, Menlo, Consolas,
    "Liberation Mono", monospace);
  --_shade: rgba(44, 42, 58, 0.18);

  /* A <figure> picks up Bootstrap's .figure from Quarto; pin the box. */
  display: block;
  position: relative;
  margin: 1.6rem 0 2.2rem;
  padding: 0.9rem 0.9rem 1.1rem;
  color: var(--_ink);
  background-color: var(--_paper);
  background-image:
    linear-gradient(rgba(89, 49, 150, 0.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(89, 49, 150, 0.055) 1px, transparent 1px);
  background-size: 24px 24px;
  border: 2px solid var(--_ink);
  border-radius: 14px 7px 16px 9px / 9px 16px 7px 14px;
  box-shadow: 4px 5px 0 -1px var(--_shade);
}

/* --------------------------------------------------------------------------
   2. Control bar
   -------------------------------------------------------------------------- */
/* The 24px dot targets below cost the row ~40px against the old 13px ones, so
   the gap and the label's basis are trimmed to keep the bar on one line. */
.anim-bar {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex-wrap: wrap;
  margin-bottom: 0.75rem;
}

.anim-step {
  font-family: var(--_hand);
  font-size: 1.05rem;
  line-height: 1.35;
  font-weight: 700;
  letter-spacing: 0.01em;
  flex: 1 1 9rem;
  min-width: 0;
}
.anim-step .anim-num {
  display: inline-block;
  min-width: 1.35rem;
  margin-right: 0.3rem;
  padding: 0 0.3rem;
  text-align: center;
  color: #fff;
  background: var(--_accent);
  border-radius: 12px 6px 12px 6px / 6px 12px 6px 12px;
}

/* The dots are 24x24 hit areas (WCAG 2.5.8) carrying a 13px ink mark, so the
   target grew without the drawing growing. gap:0 keeps neighbours from
   overlapping each other's target. */
.anim-dots { display: flex; gap: 0; }
.anim-dot {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.anim-dot::before {
  content: "";
  width: 13px;
  height: 13px;
  border: 1.6px solid var(--_ink);
  border-radius: 60% 40% 55% 45% / 45% 55% 40% 60%;
  background: transparent;
}
.anim-dot:hover::before { background: var(--_accent-soft); }
.anim-dot[aria-current="true"]::before {
  background: var(--_accent);
  border-color: var(--_accent);
}

.anim-btn {
  font-family: var(--_hand);
  font-size: 0.85rem;
  line-height: 1.25;
  padding: 0.25rem 0.55rem;
  color: var(--_ink);
  background: var(--_white);
  border: 1.8px solid var(--_ink);
  border-radius: 14px 6px 14px 6px / 6px 14px 6px 14px;
  box-shadow: 2px 2px 0 -0.5px rgba(44, 42, 58, 0.35);
  cursor: pointer;
}
.anim-btn:hover { background: var(--_accent-soft); }
.anim-btn:active { transform: translate(1px, 1px); box-shadow: none; }

/* These buttons are the widget's only controls and Bootstrap's focus ring is
   scoped to .btn, so without this a keyboard user gets no indication at all. */
.anim-btn:focus-visible,
.anim-dot:focus-visible,
.anim-knob:focus-visible {
  outline: 3px solid var(--_accent);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   3. Layout helper
   -------------------------------------------------------------------------- */
.anim-grid-2 {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.9rem;
  align-items: start;
}

/* --------------------------------------------------------------------------
   4. Hand-drawn panels
   .anim-panel              a padded card — the common case
   .anim-panel--frame       an unpadded window frame with its own chrome
   .anim-panel--term        dark, for a terminal (composes with either)
   .anim-panel--dashed      an empty slot waiting to be filled
   A .anim-stack container tilts its panels alternately, like a real pile.
   -------------------------------------------------------------------------- */
.anim-panel {
  /* Re-declared, not inherited: a panel nested in a tilted panel stays level. */
  --tilt: 0deg;
  position: relative;
  margin: 0 0 0.7rem;
  padding: 0.55rem 0.65rem 0.6rem;
  background: var(--_white);
  border: 1.8px solid var(--_ink);
  border-radius: 10px 5px 12px 6px / 6px 12px 5px 10px;
  box-shadow: 2px 3px 0 -1px var(--_shade);
  transform: rotate(var(--tilt));
}
.anim-stack > .anim-panel:nth-child(even) { --tilt: -0.3deg; }
.anim-stack > .anim-panel:nth-child(odd) { --tilt: 0.25deg; }

.anim-panel--frame {
  margin: 0;
  padding: 0;
  overflow: hidden;
  border-width: 2px;
  border-radius: 12px 6px 14px 8px / 8px 14px 6px 12px;
  box-shadow: 3px 4px 0 -1px rgba(44, 42, 58, 0.25);
}

.anim-panel--term {
  background: var(--_term-bg);
  color: var(--_term-fg);
}

.anim-panel--dashed {
  background: #fbf8ff;
  border: 2.2px dashed var(--_ink);
  border-radius: 12px 6px 14px 7px / 7px 14px 6px 12px;
  box-shadow: none;
}

/* --------------------------------------------------------------------------
   5. Captions and prose
   -------------------------------------------------------------------------- */
.anim-caption {
  font-family: var(--_hand);
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--_ink-soft);
  margin-top: 0.25rem;
}

.anim-quote {
  font-family: var(--_hand);
  font-size: 0.8rem;
  line-height: 1.4;
  padding-left: 0.5rem;
  border-left: 3px solid var(--_accent);
}

.anim-readout {
  font-family: var(--_mono);
  font-size: 0.72rem;
  display: flex;
  gap: 0.9rem;
  flex-wrap: wrap;
}
.anim-readout b { font-weight: 700; }
/* Three readouts in one half of .anim-grid-2 overrun a ~285px column by a
   hair at the default size and orphan the last one onto its own line. This
   buys back enough to hold the row together on any font stack. */
.anim-readout--tight { gap: 0.5rem; font-size: 0.64rem; }

/* One line of the drawing's own bookkeeping — a tally, a count, a
   before/after. Darker and larger than .anim-caption, which is an aside.
   Two children land as label-left / value-right:
     <div class="anim-tally"><span>triangles</span><b>20 → 19</b></div> */
.anim-tally {
  display: flex;
  justify-content: space-between;
  gap: 0.8rem;
  font-family: var(--_hand);
  font-size: 0.92rem;
  line-height: 1.55;
  color: var(--_ink);
}
.anim-tally b { font-weight: 700; }

/* The one number a scene is about, hand-lettered at headline size. */
.anim-big {
  font-family: var(--_hand);
  font-size: 2.4rem;
  line-height: 1.15;
  font-weight: 700;
  color: var(--_ink);
}

/* The per-scene prose. It is the screen-reader account of an animation that
   is otherwise aria-hidden, and it carries aria-live="polite".
   line-height is set explicitly so the two reserved lines hold whichever
   face wins the stack — with `min-height: 2.4em` alone the box collapsed on
   any platform that fell back to generic `cursive`. */
.anim-note {
  font-family: var(--_hand);
  font-size: 0.88rem;
  line-height: 1.35;
  margin-top: 0.75rem;
  padding-left: 0.4rem;
  border-left: 3px solid var(--_accent);
  min-height: calc(2 * 1.35em);
}

/* --------------------------------------------------------------------------
   6. SVG ink
   -------------------------------------------------------------------------- */
/* Capped so the drawing's own type stays near the surrounding type size.
   310px is right for a square diagram of a dozen nodes and wrong for a wide
   one — a two-panel figure, a labelled scatter, a map, a chart with an axis.
   Seven stages needed a different cap, so the cap is a variable: set it on
   the stage (or on whichever wrapper holds the wide drawing) rather than
   re-declaring .anim-svg and fighting the cascade.

     #my-stage { --anim-svg-max: 100%; }
     #my-stage .chart { --anim-svg-max: 460px; }
     @media (max-width: 820px) { #my-stage { --anim-svg-max: 100%; } } */
.anim-svg {
  display: block;
  width: 100%;
  max-width: var(--anim-svg-max, 310px);
  height: auto;
  margin: 0 auto;
}

.anim-edge {
  stroke: var(--_ink);
  stroke-width: 2.2;
  stroke-linecap: round;
  fill: none;
}
.anim-node {
  fill: var(--_accent);
  stroke: var(--_ink);
  stroke-width: 1.8;
}
.anim-node-off {
  fill: var(--_white);
  stroke: var(--_ink-faint);
  stroke-width: 1.8;
}

.anim-accent-stroke { stroke: var(--_accent); stroke-width: 2.4; fill: none; stroke-linejoin: round; }
.anim-accent-fill { fill: var(--_accent); }
.anim-amber-stroke { stroke: var(--_amber); stroke-width: 2.4; fill: none; stroke-linejoin: round; }
.anim-amber-fill { fill: var(--_amber); }

.anim-axis { stroke: var(--_ink); stroke-width: 1.4; }
/* A dashed guide line dropped onto a chart: a mean, a threshold, the knob's
   own position. Amber by default, because usually the marker is the thing
   being pointed at. When amber is already spoken for as a data colour — two
   dashed means at once, or a cursor beside an amber curve — take a modifier
   instead of inventing a private class. Three stages needed exactly this. */
.anim-marker { stroke: var(--_amber); stroke-width: 1.6; stroke-dasharray: 3 3; }
.anim-marker--accent { stroke: var(--_accent); }
.anim-marker--faint { stroke: var(--_ink-faint); }
.anim-label { font-family: var(--_hand); font-size: 11px; fill: var(--_ink); }
.anim-faint { stroke: var(--_ink-faint); fill: var(--_ink-faint); }

/* Type inside a drawing sits on top of the ink and has to survive it — a
   name that travels with its dot, a rank printed over an edge. Knock the
   paper out behind the letters instead of dodging the ink. Add-on class:
   keep fill and font-size on your own rule.
     <text class="anim-label anim-knockout"> */
.anim-knockout {
  paint-order: stroke fill;
  stroke: var(--_paper);
  stroke-width: var(--anim-knockout-w, 3px);
  stroke-linejoin: round;
}

/* A faintly filled region: a shaded triangle, a community, a band under a
   curve. Draw them into their own <g> below the edges. Pair with .anim-fade
   / .anim-fade-out to bring one in and out, or drive style.opacity directly
   when a knob has to raise and lower it repeatedly. */
.anim-shade { fill: var(--_accent); fill-opacity: 0.16; stroke: none; }

/* A route laid over the ink — a shortest path, a random walk, a tour. Wide
   and translucent so the edges it covers still read underneath. */
.anim-trail {
  stroke: var(--_amber);
  stroke-width: 4.5;
  stroke-opacity: 0.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
}

/* The thing doing the walking. Put it in a <g class="anim-glide"> and move
   the group, so the circle itself keeps its geometry. */
.anim-token { fill: var(--_amber); stroke: var(--_ink); stroke-width: 1.8; }

/* --------------------------------------------------------------------------
   7. Motion primitives
   Each is a plain add-on class, so ink and motion stay separable:
     <line class="anim-edge anim-draw">, <div class="anim-panel anim-pop">
   Stagger a set of them with style="animation-delay:.3s".
   --dash must exceed the path's own length.
   -------------------------------------------------------------------------- */
.anim-draw {
  stroke-dasharray: var(--dash, 240);
  stroke-dashoffset: var(--dash, 240);
  animation: anim-draw 0.4s ease forwards;
}
@keyframes anim-draw { to { stroke-dashoffset: 0; } }

.anim-fade { opacity: 0; animation: anim-fade 0.3s ease forwards; }
@keyframes anim-fade { to { opacity: 1; } }

/* The reverse, for something the drawing takes away — an edge that leaves, a
   triangle that stops being one. `from` is stated so it still starts opaque
   when it replaces .anim-fade on an element that already faded in. */
.anim-fade-out { animation: anim-fade-out 0.45s ease forwards; }
@keyframes anim-fade-out { from { opacity: 1; } to { opacity: 0; } }

/* Anything that moves from one place to another. Set style.transform on the
   element; inside an SVG the lengths are user units, so
   translate(110px, 26px) lands on the point you computed in the viewBox. */
.anim-glide { transition: transform 0.34s cubic-bezier(0.45, 0.05, 0.55, 0.95); }

.anim-pop { animation: anim-pop 0.45s cubic-bezier(0.34, 1.4, 0.64, 1) both; }
@keyframes anim-pop { from { opacity: 0; transform: translateY(10px) scale(0.97); } }

.anim-caret {
  display: inline-block;
  width: 0.55em;
  height: 1em;
  margin-left: 1px;
  vertical-align: -0.15em;
  background: var(--_term-caret);
  animation: anim-blink 1s step-end infinite;
}
@keyframes anim-blink { 50% { opacity: 0; } }

/* --------------------------------------------------------------------------
   8. The knob — see mountKnob() in anim.js
   -------------------------------------------------------------------------- */
.anim-range { max-width: 310px; margin: 0.45rem auto 0.2rem; }

.anim-track {
  position: relative;
  height: 8px;
  margin: 0.9rem 0.4rem 0.5rem;
  background: var(--_rule);
  border: 1.6px solid var(--_ink);
  border-radius: 8px 4px 8px 4px / 4px 8px 4px 8px;
  touch-action: none;
  cursor: pointer;
}

.anim-knob {
  position: absolute;
  top: 50%;
  left: 0;
  width: 20px;
  height: 20px;
  margin: -10px 0 0 -10px;
  background: var(--_accent);
  border: 1.8px solid var(--_ink);
  border-radius: 58% 42% 50% 50% / 50% 50% 42% 58%;
  transition: left 0.12s linear;
  touch-action: none;
  cursor: grab;
}
/* While a finger or pointer is on it, follow instantly — a 120ms ease reads
   as lag when you are the one dragging. */
.anim-knob-live { transition: none; cursor: grabbing; }

/* --------------------------------------------------------------------------
   9. Small screens
   -------------------------------------------------------------------------- */
@media (max-width: 820px) {
  .anim-grid-2 { grid-template-columns: 1fr; }
  .anim-panel--frame { --tilt: 0deg; }
  .anim-svg { max-width: 100%; }
}

/* --------------------------------------------------------------------------
   10. Reduced motion
   anim.js also builds the whole sequence in one shot for these readers, but
   the CSS ran regardless: the pop, the edge draw, the node fade, the knob
   glide and — worst — an infinite cursor blink. All neutralised here, at
   their finished state.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  /* `transition-property` defaults to `all`, so shortening the duration here
   * does not remove the transition — it grants one. A stage that animates SVG
   * geometry by writing cx/cy/r then gets a 1ms transition on every one of
   * them, which is both unasked for and a source of half-drawn frames. Turn
   * transitions off outright and let the shortened animation carry the
   * end state. */
  .anim-stage *,
  .anim-stage *::before,
  .anim-stage *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-property: none !important;
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
  }
  .anim-draw { stroke-dashoffset: 0 !important; animation: none !important; }
  .anim-fade { opacity: 1 !important; animation: none !important; }
  .anim-fade-out { opacity: 0 !important; animation: none !important; }
  .anim-pop { animation: none !important; }
  .anim-caret { animation: none !important; opacity: 1; }
  .anim-knob, .anim-glide { transition: none !important; }
}

/* --------------------------------------------------------------------------
   11. Print
   -------------------------------------------------------------------------- */
@media print {
  .anim-bar { display: none !important; }
  .anim-stage {
    break-inside: avoid;
    box-shadow: none;
    background-image: none;
  }
  .anim-panel { box-shadow: none; }
  .anim-caret { display: none !important; }
  .anim-draw { stroke-dashoffset: 0 !important; animation: none !important; }
  .anim-fade { opacity: 1 !important; animation: none !important; }
  .anim-fade-out { opacity: 0 !important; animation: none !important; }
  .anim-pop { animation: none !important; }
}
