/* ============================================================================
   Moduloa: the expression layer, v0.2 (2026-10-03)
   ---------------------------------------------------------------------------
   editorial.css gives the site its paper, type and grid, and it is still. This
   file adds the one thing it did not allow: motion and interaction. It extends
   the brand, it does not replace it. A page opts in by loading this file and
   /js/expression.js; nothing changes anywhere else.

   THE ONE IDEA. Motion carries meaning, never decoration. Every animation shows
   something true about the material on the page: a quantity filling, an order
   of events, a state changing. If removing an animation would lose no
   information, it should not exist.

   THE RULES
   1. Truth first. The HTML holds the true final state and reads correctly with
      no script at all. Script only animates TOWARD that state, never away from
      it. (The no-load-animation rule for .ed-mi in editorial.css is the same
      principle; this layer keeps it.)
   2. In step. A number and the picture of that number move in the same frame,
      from the same progress value, or neither moves. A counter at 12,000 next
      to a grid showing 150 squares is a correctness bug.
   3. Once. Entrances play one time, when they first come into view. No
      ambient loops, no parallax, no autoplay that keeps running.
   4. Answer a question. Interaction exists to answer something the reader is
      already asking ("where would I land?"). Every control has a static
      equivalent on the same page.
   5. Quiet. One easing family. Durations 240 to 1,100 ms. Stagger at most
      40 ms. One thing moving at a time in any viewport.
   6. Reduced motion means final state, at once. Interactions still work;
      only the travel is removed. editorial.css section 16 already neutralises
      transitions, and the script checks the same media query.
   7. Reachable. Every interactive element works by keyboard, announces its
      readout politely (aria-live), and keeps the token contrast ratios.
   8. CSP-safe. External scripts only, no inline handlers, and no em dash in a
      string literal (verify-build check 12).
   9. Light. No libraries. Expression scripts stay under about 6 KB per page.

   THE MOTIFS. Built from what this brand has instead of photographs: ledgers,
   records and schedules.
   - The ledger grid: a quantity drawn as equal squares, written in a sweep.
   - The staircase: a schedule drawn as steps, with a marker you can move.
   - The stamp: a status word that lands once, like ink.
   - The inspector: point at any square or step and it says exactly what it is.
   ============================================================================ */

.ed {
  --ex-ease: var(--ed-ease);
  --ex-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ex-quick: 240ms;
  --ex-base: 520ms;
  --ex-long: 1100ms;
  --ex-stagger: 40ms;
}

/* ---- Reveal: rises once into place. Only armed once the script runs, so a
   page without script shows everything at once. */
.ex-armed [data-ex-reveal] { opacity: 0; transform: translateY(14px); }
.ex-armed [data-ex-reveal].ex-in {
  opacity: 1; transform: none;
  transition: opacity var(--ex-base) var(--ex-ease-out), transform var(--ex-base) var(--ex-ease-out);
  transition-delay: calc(var(--ex-i, 0) * var(--ex-stagger));
}

/* ---- Stamp: lands once, like ink. */
.ex-armed [data-ex-stamp] { opacity: 0; transform: scale(1.35) rotate(-4deg); }
.ex-armed [data-ex-stamp].ex-in {
  opacity: 1; transform: none;
  transition: opacity var(--ex-quick) var(--ex-ease), transform var(--ex-base) var(--ex-ease-out);
}

/* ---- Entrance on load, CSS only: for what sits above the fold. A script
   arming these would show the content, hide it, then animate it in. A CSS
   animation runs from the first paint instead, needs no script at all, and
   section 16 of editorial.css removes the travel under reduced motion. Load
   expression.css BEFORE the element in the body, so it is never painted
   unstyled first. */
@keyframes ex-land { from { opacity: 0; transform: scale(1.35) rotate(-4deg); } to { opacity: 1; transform: none; } }
@keyframes ex-rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
.ex-anim-stamp { display: inline-block; animation: ex-land var(--ex-base, 520ms) var(--ex-ease-out) 260ms both; }
.ex-anim-rise { animation: ex-rise var(--ex-base, 520ms) var(--ex-ease-out) both; animation-delay: calc(380ms + var(--ex-i, 0) * 120ms); }

/* ---- Inspector readout: the line that says what you are pointing at. */
.ex-readout {
  min-height: 1.6em; margin: var(--space-3) 0 0;
  font-family: var(--font-mono); font-size: var(--ed-small, 13px); letter-spacing: .02em;
  color: var(--ed-quiet);
}
.ex-readout strong { color: var(--ed-ink); font-weight: 500; }

/* ---- Focus ring for composite widgets (grids, steppers). */
.ex-focusable:focus-visible { outline: 2px solid var(--ed-accent); outline-offset: 4px; }

@media (prefers-reduced-motion: reduce) {
  .ex-armed [data-ex-reveal], .ex-armed [data-ex-stamp] { opacity: 1; transform: none; }
}
