/* ==========================================================================
   graphics.css — the illustrated scenes from lib/graphics.js
   --------------------------------------------------------------------------
   Two rules govern everything below.

   1. The finished state is the default. Every scene is fully drawn, fully
      legible and correctly laid out with no classes applied. Animation only
      exists inside `.is-live`, which assets/js/graphics.js adds on scroll.
      A scene can therefore never end up half-drawn or invisible because a
      script failed, was blocked, or never fired.

   2. Nothing here pins a colour. Scenes inherit the ink/paper tokens, so a
      palette change carries through without touching this file.
   ========================================================================== */

.scene {
  margin: 0;
  width: 100%;
}
.scene svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

/* A scene is drawn at one viewBox size but rendered anywhere from ~340px in a
   split column to ~620px in a band. Without this, a 1.4-unit hairline lands at
   0.8 device pixels in the narrow case and the whole drawing greys out.
   non-scaling-stroke pins stroke widths to CSS pixels, so the linework keeps
   the same weight at every size.

   Deliberately excluded: the two elements whose thickness is meant to scale
   with the drawing — the handwriting strokes and the tree branches read as
   brush weight, not as rules. */
.scene :is(line, rect, circle, path):not(.ink):not(.sig):not(.branch):not(.leaf):not(.nib-body) {
  vector-effect: non-scaling-stroke;
}

/* Shared vocabulary ------------------------------------------------------- */

.scene .sheet {
  fill: var(--n-0);
  stroke: var(--n-400);
  stroke-width: 1.4;
}
.scene .col-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .12em;
  fill: var(--n-400);
}
.scene .col-label.dim { fill: var(--n-200); }
.scene .col-label.end { text-anchor: end; }
.scene .col-rule { stroke: var(--n-200); stroke-width: 1; }
.scene .col-rule.dim { stroke: var(--n-100); }

/* .fx marks an element that exists only while the scene is animating — a
   sweeping highlight, a trail. Unlike everything else in a scene it is
   *supposed* to be invisible at rest, so it is the one thing exempt from the
   "complete without JavaScript" rule that tools/verify-frontend.js enforces. */
.scene .fx { opacity: 0; }

/* --------------------------------------------------------- 1. drafting --- */

.scene-drafting .margin { stroke: var(--n-200); stroke-width: 1; }
.scene-drafting .rule { stroke: var(--n-100); stroke-width: 1; }
.scene-drafting .ink {
  fill: none;
  stroke: var(--n-800);
  stroke-width: 6;
  stroke-linecap: round;
}
.scene-drafting .sig {
  fill: none;
  stroke: var(--n-950);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.scene-drafting .nib-body { fill: var(--n-950); }
.scene-drafting .nib-slit { stroke: var(--n-0); stroke-width: 1.6; }
/* The nib parks at the end of the signature when nothing is animating. */
.scene-drafting .nib { transform: translate(200px, 232px); }

.is-live.scene-drafting .ink {
  stroke-dasharray: 460;
  stroke-dashoffset: 460;
  animation: sceneDraw .7s cubic-bezier(.4, 0, .2, 1) forwards;
  animation-delay: calc(var(--i) * .13s);
}
.is-live.scene-drafting .sig {
  stroke-dasharray: 340;
  stroke-dashoffset: 340;
  animation: sceneDraw 2.1s cubic-bezier(.55, 0, .3, 1) .74s forwards;
}
.is-live.scene-drafting .nib {
  offset-path: path("M52 234 c 18 -22, 34 -22, 40 -2 s -8 26, 4 26 s 26 -34, 44 -30 s -2 30, 14 28 s 30 -22, 52 -18");
  offset-rotate: 0deg;
  transform: none;
  animation: nibRun 2.1s cubic-bezier(.55, 0, .3, 1) .74s forwards;
}
@keyframes nibRun {
  from { offset-distance: 0%; }
  to   { offset-distance: 100%; }
}

/* --------------------------------------------------------- 2. contrast --- */

.scene-contrast .line { fill: var(--n-800); }
.scene-contrast .line.flat { fill: var(--n-200); }
.scene-contrast .divider { stroke: var(--n-100); stroke-width: 1; }
.scene-contrast .strike { stroke: var(--n-950); stroke-width: 1.6; }
.scene-contrast .line.struck { fill: var(--n-400); }
.scene-contrast .caret {
  fill: none; stroke: var(--n-950); stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
.scene-contrast .insert { fill: var(--n-950); }
.scene-contrast .note-brace { fill: none; stroke: var(--n-400); stroke-width: 1.4; }
.scene-contrast .note-dot { fill: var(--n-400); }

.is-live.scene-contrast .line {
  transform-origin: left center;
  animation: lineIn .42s cubic-bezier(.2, .8, .3, 1) backwards;
  animation-delay: calc(var(--i) * .06s);
}
.is-live.scene-contrast .line.flat { animation-delay: calc(1.1s + var(--i) * .04s); }
.is-live.scene-contrast .strike {
  stroke-dasharray: 220; stroke-dashoffset: 220;
  animation: sceneDraw .3s ease-out .95s forwards;
}
.is-live.scene-contrast .mark-caret,
.is-live.scene-contrast .mark-note {
  animation: popIn .34s cubic-bezier(.2, .9, .3, 1.2) 1.15s backwards;
}
@keyframes lineIn {
  from { transform: scaleX(0); opacity: .2; }
  to   { transform: scaleX(1); opacity: 1; }
}
@keyframes popIn {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}

/* --------------------------------------------------------- 3. detector --- */

.scene-detector .doc-line { fill: var(--n-200); }
.scene-detector .scanbar { fill: var(--n-950); }
.scene-detector .chip-box { fill: var(--n-0); stroke: var(--n-400); stroke-width: 1.2; }
.scene-detector .chip-tick {
  fill: none; stroke: var(--n-950); stroke-width: 2.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.scene-detector .verdict-box { fill: var(--n-950); }
.scene-detector .verdict-text {
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 500;
  letter-spacing: .09em; fill: var(--n-0);
}

.is-live.scene-detector .scanbar { animation: scan 1.5s cubic-bezier(.5, 0, .5, 1) forwards; }
.is-live.scene-detector .chip-tick {
  stroke-dasharray: 22; stroke-dashoffset: 22;
  animation: sceneDraw .3s ease-out forwards;
  animation-delay: calc(.35s + var(--i) * .085s);
}
.is-live.scene-detector .chip-box {
  animation: chipSettle .3s ease-out backwards;
  animation-delay: calc(.35s + var(--i) * .085s);
}
.is-live.scene-detector .verdict { animation: popIn .4s cubic-bezier(.2, .9, .3, 1.2) 1.5s backwards; }
@keyframes scan {
  0%   { opacity: 0; transform: translateY(0); }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(218px); }
}
@keyframes chipSettle {
  from { opacity: .35; }
  to   { opacity: 1; }
}

/* ------------------------------------------------------------ 4. money --- */

.scene-money .fig-big {
  font-family: var(--font-display); font-size: 40px; font-weight: 700;
  letter-spacing: -.04em; fill: var(--n-950);
}
.scene-money .fig-note { font-size: 13px; fill: var(--n-600); }
.scene-money .seg-writer { fill: var(--n-950); }
.scene-money .seg-trees  { fill: var(--n-400); }
.scene-money .seg-ops    { fill: var(--n-200); }
.scene-money .leader { stroke: var(--n-200); stroke-width: 1; }
.scene-money .leg-fig {
  font-family: var(--font-display); font-size: 19px; font-weight: 700;
  letter-spacing: -.03em; fill: var(--n-950);
}
.scene-money .leg-fig.sm { font-size: 13px; fill: var(--n-600); }
.scene-money .leg-lab { font-size: 12px; fill: var(--n-600); }

.is-live.scene-money .seg {
  transform-origin: left center;
  animation: lineIn .6s cubic-bezier(.2, .8, .3, 1) backwards;
  animation-delay: calc(.1s + var(--i) * .12s);
}
.is-live.scene-money .leg {
  animation: popIn .38s ease-out backwards;
  animation-delay: calc(.5s + var(--i) * .12s);
}
.is-live.scene-money .invoice { animation: popIn .45s ease-out backwards; }

/* ------------------------------------------------------------- 5. tree --- */

.scene-tree .ground { stroke: var(--n-200); stroke-width: 1.5; stroke-linecap: round; }
.scene-tree .branch {
  fill: none; stroke: var(--n-950); stroke-width: 5;
  stroke-linecap: round;
}
.scene-tree .leaf { fill: var(--n-950); opacity: .09; }
.scene-tree .mound { fill: none; stroke: var(--n-300, var(--n-200)); stroke-width: 2; }

.is-live.scene-tree .branch {
  stroke-dasharray: 130; stroke-dashoffset: 130;
  animation: sceneDraw .8s cubic-bezier(.35, 0, .3, 1) forwards;
  animation-delay: calc(var(--i) * .16s);
}
.is-live.scene-tree .leaf {
  transform-origin: center;
  animation: leafIn .6s cubic-bezier(.2, .9, .3, 1.25) backwards;
  animation-delay: calc(.7s + var(--i) * .1s);
}
@keyframes leafIn {
  from { opacity: 0; transform: scale(.2); }
  to   { opacity: .09; transform: scale(1); }
}

/* --------------------------------------------------------- 6. timeline --- */

.scene-timeline .track { stroke: var(--n-100); stroke-width: 2; }
.scene-timeline .track-fill { stroke: var(--n-950); stroke-width: 2; }
.scene-timeline .node { fill: var(--n-0); stroke: var(--n-950); stroke-width: 2.4; }
.scene-timeline .node-end { fill: var(--n-950); }
.scene-timeline .stop-t {
  font-family: var(--font-display); font-size: 13px; font-weight: 600;
  letter-spacing: -.02em; fill: var(--n-950); text-anchor: middle;
}
.scene-timeline .stop-s { font-size: 11px; fill: var(--n-600); text-anchor: middle; }

.is-live.scene-timeline .track-fill {
  stroke-dasharray: 480; stroke-dashoffset: 480;
  animation: sceneDraw 1.5s cubic-bezier(.4, 0, .3, 1) forwards;
}
.is-live.scene-timeline .stop {
  animation: popIn .34s cubic-bezier(.2, .9, .3, 1.2) backwards;
  animation-delay: calc(.16s + var(--i) * .23s);
}

/* ---------------------------------------------------------- 7. formats --- */

.scene-formats .baseline { stroke: var(--n-400); stroke-width: 1.5; }
.scene-formats .tile-box { fill: var(--n-0); stroke: var(--n-800); stroke-width: 1.4; }
.scene-formats .tile-line { fill: var(--n-200); }
.scene-formats .tile-label {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: .04em;
  fill: var(--n-600); text-anchor: middle;
}

.is-live.scene-formats .tile {
  transform-origin: center bottom;
  animation: growUp .5s cubic-bezier(.2, .9, .3, 1.1) backwards;
  animation-delay: calc(var(--i) * .07s);
}
/* Grows off the shared baseline rather than fading in, so the eye is drawn to
   the thing that actually differs between them: their height. */
@keyframes growUp {
  from { opacity: 0; transform: scaleY(.05); }
  to   { opacity: 1; transform: scaleY(1); }
}

/* ------------------------------------------------------- signature rule --- */

.rule-mark {
  display: block;
  margin-top: 6px;
  overflow: visible;
}
.rule-mark path {
  fill: none;
  stroke: var(--n-950);
  stroke-width: 3;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.is-live .rule-mark path,
.rule-mark.is-live path {
  stroke-dasharray: 260; stroke-dashoffset: 260;
  animation: sceneDraw .6s cubic-bezier(.4, 0, .2, 1) .15s forwards;
}

/* -------------------------------------------------------------- shared --- */

@keyframes sceneDraw { to { stroke-dashoffset: 0; } }

/* Inside an SVG, transform-origin resolves against the viewBox by default, so
   "left center" or "center bottom" means the corner of the whole drawing, not
   of the shape being transformed — every scaled element then flies toward one
   shared point. transform-box: fill-box makes the origin local to each shape,
   which is what all the scale animations above assume. */
.scene [style*="--i"],
.scene .line,
.scene .seg,
.scene .leaf,
.scene .tile { transform-box: fill-box; }

/* ------------------------------------------------------------- layout ---- */

/* A scene sitting beside prose. Falls to a single column early, because
   these lose their detail below roughly 380px of width. */
.scene-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 48px;
  align-items: center;
}
.scene-split.narrow { grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); }
@media (max-width: 860px) {
  .scene-split, .scene-split.narrow { grid-template-columns: 1fr; gap: 30px; }
}

/* A scene as a full-width band, boxed off from the copy around it. */
.scene-band {
  border: 1px solid var(--n-100);
  border-radius: 14px;
  background: var(--n-25);
  padding: 34px 38px;
}
.scene-band .scene { max-width: 620px; margin: 0 auto; }
@media (max-width: 620px) {
  .scene-band { padding: 22px 18px; border-radius: 12px; }
}

/* On dark sections the ink/paper roles swap. */
.section-navy .scene .sheet { fill: rgba(255,255,255,.05); stroke: rgba(255,255,255,.16); }
.section-navy .scene .col-label { fill: rgba(255,255,255,.5); }
.section-navy .scene-money .fig-big,
.section-navy .scene-money .leg-fig { fill: #fff; }
.section-navy .scene-money .fig-note,
.section-navy .scene-money .leg-lab,
.section-navy .scene-money .leg-fig.sm { fill: rgba(255,255,255,.62); }
.section-navy .scene-money .seg-writer { fill: #fff; }
.section-navy .scene-money .seg-trees  { fill: rgba(255,255,255,.5); }
.section-navy .scene-money .seg-ops    { fill: rgba(255,255,255,.22); }
.section-navy .scene-money .leader     { stroke: rgba(255,255,255,.22); }
.section-navy .scene-band { background: rgba(255,255,255,.04); border-color: rgba(255,255,255,.1); }

/* A line of explanation under a scene. Deliberately small and quiet — the
   scene should carry itself; this only names what is being shown. */
.scene-caption {
  margin: 14px 0 0;
  font-size: .84rem;
  line-height: 1.5;
  color: var(--n-600);
  max-width: 54ch;
}
.scene-caption.centered { margin-inline: auto; text-align: center; }

/* The masthead illustration sits opposite the lede and actions. It is
   decorative reinforcement of the headline, so it is the first thing to go
   when the column collapses. */
.masthead-scene { width: min(360px, 100%); }
.masthead-scene .scene svg { max-height: 220px; }
@media (max-width: 900px) {
  .masthead-scene { display: none; }
}

/* ----------------------------------------------------- motion, opted out --- */

/* Belt and braces: the runtime already declines to add .is-live when reduced
   motion is set, but a scene that somehow got the class must still settle
   immediately rather than animate. */
@media (prefers-reduced-motion: reduce) {
  .scene *, .rule-mark path {
    animation: none !important;
    stroke-dashoffset: 0 !important;
    offset-distance: 100% !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .scene-tree .leaf { opacity: .09 !important; }
  .scene-detector .scanbar { opacity: 0 !important; }
}
