/* MEK design system · motion
   The rules: things move in whole frames (steps()), by whole pixels. Hover dissolves with a dither; slides move 1px per display frame; other
   state changes are instant. No state or animation may change layout (see DESIGN.md "Stable layout").
   Nothing glides, springs or bounces on a curve. Every animation stops under prefers-reduced-motion. */

/* ---------- Hover dissolve ----------
   Everything that reacts to hover dissolves into its hover look with a pixel dither, one frame per step:
   25% of pixels, 50% (checkerboard), 75%, 100%, and back out the same way. Text steps between its two inks in sync.
   --dz is the mix (0 = rest, 1 = hover). It's registered so it can be transitioned; steps() keeps it on whole frames.
   Built in for .btn, .link, .pick-btn and .pick-menu buttons. Add .dissolve to anything else: a .px-box with
   --edge-to / --fill-to set, and/or text with --ink / --ink-to set.
   .dissolve-links on a container does the same for every link and button inside it, for markup you can't class
   (text from a CMS or Markdown); set --ink / --ink-to on the container and they inherit.
   .dissolve-ink is text inside a dissolving element that flips with it (a box's title), from its own --ink / --ink-to.
   Focus and selected stay instant: keyboard focus jumps straight to the hover look. */
@property --dz { syntax: "<number>"; inherits: true; initial-value: 0; }

:where(.btn, .link, .pick-btn, .pick-menu button, .menu > button, .check, .range, .disclose > summary, .tree-group > summary, .dissolve, .dissolve-links :is(a, button)) {
  --dz: 0;
  transition: --dz var(--dur-dissolve) var(--ease-dissolve);
}
:is(.btn, .link, .pick-btn, .pick-menu button, .menu > button, .check, .disclose > summary, .tree-group > summary, .dissolve, .dissolve-links :is(a, button), .dissolve-ink) {
  /* Text colour comes from --ink (rest) and --ink-to (hover); set those, not color. Without them it inherits.
     :is() so it outranks the base reset (button, a { color: inherit }). */
  /* Flips at the halfway frame rather than blending, so text is always a real theme colour and stays legible. */
  color: color-mix(in srgb, var(--ink-to, currentColor) calc(clamp(0, (var(--dz) - .45) * 10000, 1) * 100%), var(--ink, currentColor));
}
/* :is(), not :where(): these must outrank the rest state (--dz: 0) that every .px-box sets. */
:is(.btn, .link, .pick-btn, .pick-menu button, .menu > button, .disclose > summary, .tree-group > summary, .dissolve, .dissolve-links :is(a, button)):hover:not(:disabled) { --dz: 1; }
.menu > button:focus { --dz: 1; transition-duration: 0s; }   /* a menu row takes focus from the arrows: it shows as hovered, at once */
:is(.btn, .link, .pick-btn, .pick-menu button, .disclose > summary, .tree-group > summary, .dissolve, .dissolve-links :is(a, button)):focus-visible { --dz: 1; transition-duration: 0s; }
/* A checkbox or radio (.check) dissolves from its label: the input inside it is what's disabled or focused. */
.check:hover:not(:has(> input:disabled)) { --dz: 1; }
.check:has(> input:focus-visible) { --dz: 1; transition-duration: 0s; }
/* A slider (.range) dissolves its handle while the pointer is on the input. */
.range:has(> input:hover:not(:disabled)) { --dz: 1; }
.range:has(> input:focus-visible) { --dz: 1; transition-duration: 0s; }
/* The ">" marker of .link.caret appears one frame into the dissolve. */
.link.caret:hover::before { transition: visibility 0s var(--frame); }

/* ---------- Theme wipe ----------
   Changing theme dithers the new theme in as a circle growing from the control you used (js/mek.js builds the mask
   and animates --wipe-r). The old page stays still underneath; nothing fades or blends.
   --wipe-r is registered so it interpolates smoothly, frame by display frame. */
@property --wipe-r { syntax: "<length>"; inherits: false; initial-value: 0px; }
html.mek-theme-wipe::view-transition-old(root),
html.mek-theme-wipe::view-transition-new(root) { animation: none; mix-blend-mode: normal; }

/* Idle loops */
@keyframes mek-bop   { 50% { translate: 0 -4px; } }            /* hint sprite, "look here" */
@keyframes mek-blink { 50% { opacity: 0; } }                   /* cursor, recording dot */
@keyframes mek-scan  { from { background-position: 0 0; } to { background-position: 0 4px; } }

/* Entrances */
@keyframes mek-pop-in  { 0% { opacity: 0; } 50% { opacity: var(--fade); translate: 0 2px; } 100% { opacity: 1; translate: 0 0; } }
/* A busy progress bar (.meter): 3 lit cells from just off the left to just off the right, one cell a frame. */
@keyframes mek-meter-march { from { background-position: calc(-3 * (var(--meter-cell) + var(--meter-gap))) 0, 0 0; } to { background-position: calc(var(--meter-span) + var(--meter-gap)) 0, 0 0; } }   /* span + gap: a whole number of cells */
/* A tag's glint (.tag): a 4px dither band from just off the left to 96px in, 1px a step, in the first 40% (1.6s of
   4s, a display frame a pixel); then it rests off the tag. Wider tags than 96px get no glint past that. */
@keyframes mek-tag-glint {
  0% { -webkit-mask-position: -4px 0; mask-position: -4px 0; animation-timing-function: steps(100, end); }
  40%, 100% { -webkit-mask-position: 96px 0; mask-position: 96px 0; }
}
/* The spinner's keyframes (mek-spin) are drawings: they live in css/components/states.css, by the Spinner editor. */
@keyframes mek-pop-out { 0% { opacity: 1; } 50% { opacity: var(--fade); translate: 0 2px; } 100% { opacity: 0; translate: 0 2px; } }   /* a toast leaving */
@keyframes mek-wipe-in { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes mek-type-in { from { width: 0; } }

/* Feedback */
@keyframes mek-shake { 25% { translate: -2px 0; } 50% { translate: 2px 0; } 75% { translate: -1px 0; } }
@keyframes mek-flash { 50% { background: var(--accent); color: var(--100); } }

.anim-bop   { animation: mek-bop var(--dur-loop) var(--ease-step) infinite; }
.anim-blink { animation: mek-blink var(--dur-loop) var(--ease-step) infinite; }
.anim-pop   { animation: mek-pop-in var(--dur-bar) var(--ease-frames-4) both; }   /* 4 frames */
.anim-leave { animation: mek-pop-out var(--dur-bar) var(--ease-frames-4) both; }  /* 4 frames: .anim-pop backwards (a toast leaves in 2) */
.anim-wipe  { animation: mek-wipe-in var(--dur-bar) var(--ease-frames-4) backwards; }   /* no clip left on after it ends */
.anim-shake { animation: mek-shake var(--dur-bar) var(--ease-frames-4); }
.anim-flash { animation: mek-flash var(--dur-beat) var(--ease-step) 2; }
/* Typewriter: set --chars to the text length. Needs a mono face for an exact fit. */
.anim-type  { display: inline-block; overflow: hidden; white-space: nowrap; width: calc(var(--chars) * 1ch);
  animation: mek-type-in calc(var(--chars) * var(--dur-type)) steps(var(--chars), end) both; }
/* Scanlines overlay for CRT moments. Put on a positioned element; it draws over its content. */
.scanlines { position: relative; }
.scanlines::after { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: var(--scanlines);   /* a faithful theme: solid ground rows */
  animation: mek-scan var(--dur-loop) steps(4) infinite; }

/* Idle loops out of view: js/mek.js adds .mek-paused, so nothing repaints where no one can see it. */
.mek-paused, .mek-paused::before, .mek-paused::after { animation-play-state: paused !important; }

/* Stagger children in by frames: <ul class="stagger"> with style="--i: n" on each child (or nth-child below). */
.stagger > * { animation: mek-pop-in var(--dur-bar) var(--ease-frames-4) both; animation-delay: calc(var(--i, 0) * var(--frame) * 2); }
.stagger > :nth-child(2) { --i: 1; } .stagger > :nth-child(3) { --i: 2; } .stagger > :nth-child(4) { --i: 3; }
.stagger > :nth-child(5) { --i: 4; } .stagger > :nth-child(6) { --i: 5; } .stagger > :nth-child(7) { --i: 6; }

/* Keys: the key of a control just pressed by its key, or of a switch button just chosen, blinks in the full accent
   for two frames, then settles. js/mek.js adds .flip (never on load) and removes it when the blink ends. */
@keyframes mek-key-blink { from { color: var(--accent); } to { color: var(--accent); } }
.flip > kbd { animation: mek-key-blink var(--dur-beat) var(--ease-step); }

/* Dialogs and menus appear in two frames. */
.dialog[open], .pick-menu:not([hidden]), .flyout-panel:not([hidden]) { animation: mek-pop-in var(--dur-beat) var(--ease-frames-2) both; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0s !important; animation-iteration-count: 1 !important; animation-delay: 0s !important; transition: none !important; }
  .cursor::after, .anim-blink { animation: none !important; }
}
