/* Motion. Opt-in, never opt-out.
 *
 * THE INVARIANT: every animation in this file lives under
 * html[data-fx="full"]. The attribute is set by a small script in <head>, and
 * only when the visitor has not asked for reduced motion. With scripting off
 * the attribute is absent and nothing moves at all.
 *
 * That ordering matters. The common pattern -- animate by default, switch off
 * under prefers-reduced-motion -- fails open: a browser that does not report
 * the preference, or a stylesheet that loads before the query is evaluated,
 * animates anyway. Here motion can only appear once the page has affirmatively
 * decided it is welcome.
 *
 * fxlint.py enforces the invariant across static/css/ and static/svg/ and fails
 * the build on any keyframe that escapes it, so this is a checked property
 * rather than a convention.
 */

/* Hold everything on its first frame until the document has finished parsing.
 *
 * The drawings are two-frame flip-books: frame 1 visible, frame 2 hidden, and
 * an animation that swaps their opacity. Both frames live in the same inlined
 * SVG, tens of kilobytes apart in the byte stream -- erik.svg alone spans 36 KB
 * of the homepage. The browser starts animating frame 1 the moment it is
 * parsed, so on a slow first visit the animation reaches its 50% mark and hides
 * frame 1 while frame 2 has not arrived yet. The drawing then vanishes
 * completely for half a cycle. That is the flicker: not too much motion, but
 * motion starting against a document that is only half there.
 *
 * A paused animation renders its 0% keyframe, which is exactly the resting
 * state -- frame 1 shown, frame 2 hidden -- so this holds the correct picture
 * rather than an arbitrary one. data-ready is set at DOMContentLoaded, by which
 * time every frame of every drawing exists.
 *
 * Note this is a *pause*, not a suppression: animation-play-state does not
 * match fxlint's animation/animation-name check, and the rule stays under the
 * data-fx="full" guard, so the opt-in invariant above is untouched. */
html[data-fx="full"]:not([data-ready]) *,
html[data-fx="full"]:not([data-ready]) *::before,
html[data-fx="full"]:not([data-ready]) *::after {
  animation-play-state: paused;
}

/* Backstop. Belt and braces: even if something is added wrongly, an explicit
 * preference for reduced motion still wins. */
@media (prefers-reduced-motion: reduce) {
  html[data-fx] *,
  html[data-fx] *::before,
  html[data-fx] *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* -- cursor ---------------------------------------------------------------- */

/* Rendered as a block that blinks in hard steps, the way a terminal cursor
 * does -- not a fade. Decoration only; it carries no information, so it is
 * aria-hidden in the markup and simply sits still when motion is off. */
.cursor {
  display: inline-block;
  width: 1ch;
  height: 1em;
  vertical-align: text-bottom;
  background: currentColor;
}

html[data-fx="full"] .cursor {
  animation: cursor-blink 1.06s steps(1, end) infinite;
}

@keyframes cursor-blink {
  0%, 50%   { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

/* -- inline diagrams ------------------------------------------------------- */

/* Hand-authored SVGs are inlined into the page, so they inherit currentColor
 * and are reachable by this attribute. An <img src="x.svg"> would be reachable
 * by neither. */
html[data-fx="full"] .diagram [data-anim="marker"] {
  animation: marker-tour 12s ease-in-out infinite;
}

@keyframes marker-tour {
  0%,  18% { transform: translate(0, 0); }
  25%, 43% { transform: translate(var(--anim-x, 0), 0); }
  50%, 68% { transform: translate(var(--anim-x, 0), var(--anim-y, 0)); }
  75%, 93% { transform: translate(0, var(--anim-y, 0)); }
  100%     { transform: translate(0, 0); }
}

/* When motion is off the marker must still be somewhere sensible -- a defined
 * resting position, not wherever the first keyframe happened to put it. */
.diagram [data-anim="marker"] { transform: translate(0, 0); }

/* -- terminal recordings --------------------------------------------------- */

/* Two images per recording: the animation and a still of the FINAL frame, so
 * the reduced-motion view shows finished output rather than an empty prompt.
 * The template marks both; this picks one. Only the visible one carries alt
 * text, so nothing is announced twice. */
.cast--motion { display: none; }
.cast--still  { display: block; }

html[data-fx="full"] .cast--motion { display: block; }
html[data-fx="full"] .cast--still  { display: none; }
