/* ============================================================================
   ara-data.com · /how-we-build — the three-act motion system
   ARA Data & AI Consulting, 2026-08-05.

   Canon: team/brand/ara-logo/ARA_design_philosophy.md — brand v1.2 IN FORCE.
   Extends the existing Datum Frame language in ara-data.com/public/styles.css;
   invents no new visual language, no new colors, no new type.

   LOAD ORDER (production): styles.css FIRST, then this file. Every token this
   sheet reads (--orange, --paper, --graphite, --ease-draw, --u, ...) is
   declared there. This file adds no @font-face and no @import — it makes zero
   network requests of its own.

   THE THREE HARD RULES THIS SHEET ENFORCES
   1. Property whitelist (canon v1.2b): the ONLY animated properties are
      `opacity`, `stroke-dashoffset`, and `transform` (translate only).
      Nothing here repaints layout. Static dimming uses fill-opacity /
      stroke-opacity so it never collides with an animated `opacity`.
   2. One-shot only (canon v1.2c): every act plays once on entry and rests in
      a finished composition. Nothing loops, nothing idles. The REPLAY control
      is a user event, which is why a replay is permitted at all.
   3. Orange scarcity (canon v1.2f): at any instant at most ONE orange element
      is moving in the viewport — held by (a) one orange event per act and
      (b) the serial play queue in how-we-build.js, which never runs two acts
      at once. At rest each figure holds exactly ONE small orange mark.

   FALLBACK CONTRACT: the SVG markup IS the finished end-state. Every rule
   that hides or offsets anything is scoped to `html.anim`, a class only
   how-we-build.js adds, and only when prefers-reduced-motion is not set. With
   no JS, or under reduced motion, the page renders complete and correct.
   ============================================================================ */

/* ---- Diagram tokens --------------------------------------------------------
   Derived per ground from the locked palette. No new colors: the dark-ground
   structure hairline is canon GREY_LT #C9CED6 held back with alpha, exactly as
   styles.css already does for --hair-dark. */
.act {
  --dg-line: var(--hair-light);            /* #C9CED6 structure hairline */
  --dg-line-2: var(--graphite);            /* emphasized structure */
  --dg-text: var(--graphite);              /* supporting labels (5.74:1 AA) */
  --dg-text-2: var(--ink);                 /* primary labels */
  --dg-signal: var(--orange);              /* graphical orange — needs 3:1 */
  --dg-signal-text: var(--orange-ink);     /* small orange TEXT on light: AA */
  --dg-field: var(--paper);                /* ground, for label knockouts */

  /* functional alert palette — canon §3, held OUTSIDE the orange budget.
     Never colour-alone: every use pairs field + glyph + mono label. */
  --fn-red: #A4161A;
  --fn-red-edge: #6E0D10;
  --fn-amber: #8A5A00;
  --fn-amber-edge: #5C3C00;
  --fn-type: #FFFFFF;

  /* act timing — deterministic, expressed once so the whole system tunes
     together and durations can be verified against the handoff table.
     The marker below is machine-read: tools/scale-timing.py refuses to run
     against a sheet that already carries one, because a second pass would
     double-scale the envelope and nothing else would catch it. */
  /* timing-scale: 1.20 applied 2026-08-05 */
  --t-draw-short: 504ms;
  --t-draw-long: 864ms;
  --t-stamp: 360ms;
  --t-swap: 264ms;
}
.band-dark .act {
  --dg-line: rgba(201, 206, 214, 0.26);
  --dg-line-2: var(--grey);
  --dg-text: var(--grey);
  --dg-text-2: var(--paper);
  --dg-signal: var(--orange-hi);
  --dg-signal-text: var(--orange-hi);      /* ~7:1 on Navy DK — no ink variant needed */
  --dg-field: var(--navy-dk);
}

/* ---- Figure shell ---------------------------------------------------------- */
.act {
  margin: 0;
  position: relative;
}
.act-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 8px;
}
/* The act headline is an h2 so the page outline runs h1 -> h2 -> h2 -> h2 -> h2
   with no skipped level. It keeps the h3 SIZE: this is a semantics change, not
   a type change, so every value below is the one styles.css gives h3, plus the
   margin reset that cancels the h2 default the promotion would otherwise
   inherit. */
.act-head h2 {
  font-size: var(--fs-h3);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.005em;
  margin-bottom: 0;
}
.act-idea {
  max-width: 66ch;
  margin: 0 0 32px;
  color: var(--dg-text);
  font-size: var(--fs-body);
  line-height: 1.6;
  /* pretty over balance: these are multi-line body paragraphs, and balance
     would even out the last line at the cost of the ragged edge above it. */
  text-wrap: pretty;
}
.band-light .act-idea { color: var(--graphite); }
.band-dark .act-idea { color: var(--grey-lt); }

/* The scroll shell. Below ~900px the diagrams are held at a legible minimum
   width and scrolled rather than squashed — a 10px mono label at 380px wide
   would render at 3.8px, which is not a design decision, it is a failure.
   The shell is focusable and labelled so keyboard and AT users can reach it
   (WCAG 2.1.1 — a scrollable region must be operable). */
.act-scroll {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  /* 48px of air above every figure: 32px from .act-idea's bottom margin plus
     this. PADDING, not margin — a sibling margin here would collapse into the
     paragraph's and the gap would silently stay at 32. */
  padding-top: 16px;
}
.act-svg {
  display: block;
  width: 100%;
  height: auto;
  /* 900px is not arbitrary: the figures are 1000-unit viewBoxes whose
     smallest annotation is 9.5 units. Below 900px rendered width that label
     drops under 8.5px, which is no longer a design decision — it is illegible
     type. Hold the figure and scroll it instead of squashing it. */
  min-width: 900px;
}
.act-scroll:focus-visible {
  outline: 2px solid var(--orange-hi);
  outline-offset: 4px;
}
.band-light .act-scroll:focus-visible { outline-color: var(--orange-ink); }

.act-hint {
  display: none;
  margin-top: 12px;
  color: var(--dg-text);
}
@media (max-width: 900px) {
  .act-hint { display: block; }
}

/* Foot line: the figure's own caption + the replay control. */
.act-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  margin-top: 20px;
  padding-top: 12px;
  border-top: 1px solid var(--dg-line);
}
.act-caption {
  margin: 0;
  max-width: 74ch;
  color: var(--dg-text);
}
.act-replay {
  flex: none;
  appearance: none;
  background: none;
  border: 1px solid var(--dg-line-2);
  border-radius: 0;
  color: inherit;
  padding: 8px 16px;
  cursor: pointer;
  font-family: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--fs-micro);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  transition: border-color var(--t-hover) linear;
}
.act-replay:hover { border-color: var(--dg-text-2); }
/* Without JS the control cannot do anything, so it is not rendered at all
   rather than shown dead. */
html:not(.anim) .act-replay { display: none; }
.act-replay[disabled] { opacity: 0.4; cursor: default; }

/* ============================================================================
   SVG PRIMITIVES — one vocabulary, shared by all three acts
   ============================================================================ */
/* Base fill is set on the <svg> itself, NOT on `.act-svg text`, so that a
   single-class rule like `.t-name` can still win. `fill` inherits in SVG;
   every shape below declares its own fill or stroke class, so nothing picks
   this up by accident. */
.act-svg {
  font-family: "IBM Plex Sans", -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  fill: var(--dg-text);
}
/* mono label — the instrument-readout register */
.t-lbl {
  font-family: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.t-micro {
  font-family: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  fill-opacity: 0.85;
}
/* literal code — filenames and paths are quoted exactly, so this variant
   deliberately does NOT uppercase and runs tighter tracking. */
.t-code {
  font-family: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 10px;
  letter-spacing: 0.02em;
  fill-opacity: 0.9;
}
.t-idx {
  font-family: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 10.5px;
  letter-spacing: 0.14em;
  font-variant-numeric: tabular-nums;
  fill-opacity: 0.7;
}
.t-name {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.01em;
  fill: var(--dg-text-2);
}
/* a single measured figure, allowed to dominate its cell — canon's
   "one element is allowed to dominate, everything else recedes to a whisper
   of small technical labels". Used once, for a number worth staring at. */
.t-figure {
  font-size: 44px;
  font-weight: 600;
  letter-spacing: -0.02em;
  fill: var(--dg-text-2);
  font-variant-numeric: tabular-nums;
}
.t-strong { fill: var(--dg-text-2); }
.t-dim { fill-opacity: 0.62; }
.t-signal { fill: var(--dg-signal-text); }

/* strokes */
.s-hair { stroke: var(--dg-line); stroke-width: 1; fill: none; }
.s-line { stroke: var(--dg-line-2); stroke-width: 1; fill: none; }
.s-line-2 { stroke: var(--dg-line-2); stroke-width: 1.5; fill: none; }
.s-dash { stroke: var(--dg-line-2); stroke-width: 1; fill: none; stroke-dasharray: 3 4; }
.s-signal { stroke: var(--dg-signal); stroke-width: 1.5; fill: none; }
.s-red { stroke: var(--fn-red); stroke-width: 1.5; fill: none; }

/* fills */
.f-node { fill: var(--dg-field); stroke: var(--dg-line-2); stroke-width: 1; }
.f-node-on { fill: var(--dg-text-2); stroke: none; }
.f-signal { fill: var(--dg-signal); stroke: none; }
.f-none { fill: none; }

/* chips — functional status only (red / amber). Field + edge + white mono
   type + a glyph, per the canon's never-colour-alone rule. */
.chip-red { fill: var(--fn-red); stroke: var(--fn-red-edge); stroke-width: 1; }
.chip-amber { fill: var(--fn-amber); stroke: var(--fn-amber-edge); stroke-width: 1; }
/* white type on the alert fields — held at FULL opacity so the measured AA
   contrast (7.75:1 on red, 5.93:1 on amber) is the contrast that actually
   ships; .t-micro's 0.85 dimming must not apply here. */
.chip-type { fill: var(--fn-type); fill-opacity: 1; }
.chip-quiet { fill: none; stroke: var(--dg-line-2); stroke-width: 1; }

/* every drawn path carries pathLength="1" so one dash unit == the whole path */
.draw { stroke-dasharray: 1; stroke-dashoffset: 0; }

/* ============================================================================
   THE MOTION LAYER — everything below is scoped to html.anim.
   Remove the class (no JS / reduced motion) and the composition above stands
   complete on its own.
   ============================================================================ */
@keyframes dgm-draw { from { stroke-dashoffset: 1; } }
@keyframes dgm-fade { from { opacity: 0; } }
@keyframes dgm-out { from { opacity: 1; } to { opacity: 0; } }

/* The orange `live` stroke exists ONLY while an act is playing: it draws, then
   retires into the graphite `spent` record underneath it. Its resting state is
   therefore invisible, which is why opacity:0 is its BASE style, not a
   motion-scoped one — with no JS at all it correctly never appears. */
.act [data-a="live"] { opacity: 0; }

/* pre-play state */
html.anim .act:not(.is-playing) [data-a="draw"],
html.anim .act:not(.is-playing) [data-a="live"] { stroke-dashoffset: 1; }
html.anim .act:not(.is-playing) [data-a="fade"],
html.anim .act:not(.is-playing) [data-a="spent"] { opacity: 0; }

/* play state */
html.anim .act.is-playing [data-a="draw"] {
  animation: dgm-draw var(--t, var(--t-draw-short)) var(--ease-draw) var(--d, 0ms) both;
}
html.anim .act.is-playing [data-a="fade"] {
  animation: dgm-fade var(--t, var(--t-stamp)) var(--ease-settle) var(--d, 0ms) both;
}
/* the orange event: draws, then retires to the graphite record beneath it.
   This is what keeps orange a MOVING SIGNAL and never a resting field. */
html.anim .act.is-playing [data-a="live"] {
  animation:
    dgm-draw var(--t, var(--t-draw-short)) var(--ease-draw) var(--d, 0ms) both,
    dgm-out var(--t-swap) linear calc(var(--d, 0ms) + var(--t, var(--t-draw-short))) both;
}
html.anim .act.is-playing [data-a="spent"] {
  animation: dgm-fade var(--t-swap) linear
             calc(var(--d, 0ms) + var(--t, var(--t-draw-short))) both;
}

/* ---- Belt and braces -------------------------------------------------------
   how-we-build.js already withholds `.anim` under reduced motion. This block
   is the second lock: if the class ever arrives by another route, the acts
   still resolve instantly to their complete end-state rather than to a
   half-drawn frame. */
@media (prefers-reduced-motion: reduce) {
  html.anim .act *,
  html.anim .act *::before,
  html.anim .act *::after {
    animation: none !important;
    transition: none !important;
  }
  html.anim .act [data-a="draw"],
  html.anim .act [data-a="live"] { stroke-dashoffset: 0; }
  html.anim .act [data-a="fade"],
  html.anim .act [data-a="spent"] { opacity: 1; }
  html.anim .act [data-a="live"] { opacity: 0; } /* retired by design */
}

/* ---- Narrow viewports ------------------------------------------------------ */
@media (max-width: 720px) {
  .act-head { flex-direction: column; gap: 4px; }
  .act-foot { flex-direction: column; align-items: flex-start; gap: 16px; }
}
