/* ═══════════════════════════════════════════════════════════════════════════
 * M3E Motion Roles — utility classes that assign the motion tokens to jobs.
 *
 * Loaded AFTER motion.css (which owns the tokens, the global keyframes, and the
 * global prefers-reduced-motion kill-switch). This file ships ONLY role classes;
 * it never redefines a token, never redefines the reduced-motion behaviour, and
 * never introduces a bespoke cubic-bezier or a magic duration. Everything below
 * resolves to `--ease-*` / `--easing-*` and `--duration-*` (colors_and_type.css)
 * or `--md-sys-motion-*` (motion.css).
 *
 * Reference: docs/plans/2026-07-17-refinement-motion-spec.md — "the nine motions".
 *
 * Reduced motion: NOT re-handled here. motion.css Part 1 sets every
 * animation-duration/transition-duration to ~0 under prefers-reduced-motion, so
 * these classes become inert automatically. The one design rule we honour to
 * stay safe is: never leave content stuck at opacity:0 when the animation is
 * killed. The stagger below is opt-in via a JS-applied class, so its children
 * are visible by default (see "Stagger contract"). The reveal/collapse classes
 * only ever animate opacity between visible end-states or are JS-lifecycle-bound.
 * ═══════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════
 * MOTION 1 — Boosted-navigation cross-fade  (.m3e-page-crossfade)
 *
 * #page-content owns the CSS `page-enter` animation in motion.css, which fires
 * only on FULL document loads (the element persists across boosted innerHTML
 * swaps). m3e-motion.js applies this class on the settled #page-content after a
 * boosted swap to re-run the `vt-fade-in` keyframe (opacity + 4px rise) — the
 * cross-fade that replaces the hard cut. Same easing/duration family as
 * page-enter so full loads and boosted swaps feel identical. One entrance per
 * navigation; they never stack.
 *
 * Scoped to `#page-content.m3e-page-crossfade` so it out-specifies the base
 * `#page-content { animation: page-enter }` ID rule in motion.css (a bare class
 * would lose to that ID selector and never apply).
 * ═══════════════════════════════════════════════════════════════════════════ */

#page-content.m3e-page-crossfade {
  animation: vt-fade-in var(--md-sys-motion-duration-medium1) var(--easing-effects) backwards;
}


/* ═══════════════════════════════════════════════════════════════════════════
 * MOTION 2 — List / grid stagger  (.m3e-stagger)
 *
 * Direct children rise in with --ease-serve, 25ms increments, capped at 8
 * (children 9+ appear instantly), animation-fill-mode: backwards.
 *
 * ── Stagger contract (dashboard agents: read this) ──────────────────────────
 * Add `.m3e-stagger` to the CONTAINER of a list/grid. Its DIRECT children
 * animate in ONCE, the first time the container appears. That's it — no other
 * markup or JS needed.
 *
 * ANTI-RETRIGGER (why refreshes don't dance):
 *   The rise animation is gated behind `.m3e-stagger--enter`, which the motion
 *   module (m3e-motion.js) adds to each container EXACTLY ONCE and then removes.
 *   A container is stamped `data-stagger-done` the first time it is seen and is
 *   never entered again. Consequences:
 *     • First paint / full page load  → children stagger once.               ✔
 *     • Whole container swapped in by HTMX (a NEW element, e.g. outerHTML or a
 *       boosted #page-content swap) → it is unseen, so it staggers once.     ✔
 *     • Individual rows inserted/replaced inside an EXISTING container
 *       (innerHTML refresh, append, row swap) → the container is already
 *       stamped, so nothing re-enters and rows do NOT dance.                 ✔
 *   Therefore: put `.m3e-stagger` only where "the list first appears" — never
 *   on a container you refresh row-by-row expecting the new rows to be quiet.
 *
 * DEFAULT STATE is fully visible: children carry no opacity:0 until the JS adds
 * `.m3e-stagger--enter`. So with JS disabled, or under reduced motion (the JS
 * skips adding the class), the content is present and complete — never blank.
 * ═══════════════════════════════════════════════════════════════════════════ */

@keyframes m3e-stagger-rise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

.m3e-stagger.m3e-stagger--enter > * {
  animation: m3e-stagger-rise var(--duration-quick) var(--ease-serve) backwards;
}

/* 25ms increments, capped at 8 children (child 1 = 0ms … child 8 = 175ms). */
.m3e-stagger.m3e-stagger--enter > *:nth-child(1) { animation-delay: 0ms; }
.m3e-stagger.m3e-stagger--enter > *:nth-child(2) { animation-delay: 25ms; }
.m3e-stagger.m3e-stagger--enter > *:nth-child(3) { animation-delay: 50ms; }
.m3e-stagger.m3e-stagger--enter > *:nth-child(4) { animation-delay: 75ms; }
.m3e-stagger.m3e-stagger--enter > *:nth-child(5) { animation-delay: 100ms; }
.m3e-stagger.m3e-stagger--enter > *:nth-child(6) { animation-delay: 125ms; }
.m3e-stagger.m3e-stagger--enter > *:nth-child(7) { animation-delay: 150ms; }
.m3e-stagger.m3e-stagger--enter > *:nth-child(8) { animation-delay: 175ms; }

/* Children 9+ appear instantly (no rise, no cascade). */
.m3e-stagger.m3e-stagger--enter > *:nth-child(n+9) { animation: none; }


/* ═══════════════════════════════════════════════════════════════════════════
 * MOTION 4 — Row actions reveal  (.m3e-reveal-actions)
 *
 * A container whose action cluster (`.m3e-reveal-actions__actions`) sits at
 * opacity 0 and fades in on hover / focus-within in 150ms with --easing-effects.
 * The actions are ALWAYS in the DOM and remain focusable — keyboard Tab into
 * them triggers :focus-within, which reveals them. On touch (no hover), they
 * are shown permanently so they are never unreachable.
 * ═══════════════════════════════════════════════════════════════════════════ */

.m3e-reveal-actions__actions {
  opacity: 0;
  transition: opacity var(--md-sys-motion-duration-short3) var(--easing-effects);
}
.m3e-reveal-actions:hover .m3e-reveal-actions__actions,
.m3e-reveal-actions:focus-within .m3e-reveal-actions__actions {
  opacity: 1;
}

/* Coarse pointers (touch) can't hover — keep the actions visible so they are
 * never stranded off-screen for touch users. */
@media (hover: none) {
  .m3e-reveal-actions__actions { opacity: 1; }
}


/* ═══════════════════════════════════════════════════════════════════════════
 * MOTION 7 — State confirmation
 *
 * .m3e-confirm-pop   — one-shot badge-pop on a row/badge after a successful
 *                      HTMX action (applied by m3e-motion.js on [data-motion-
 *                      confirm], removed on animationend so it can re-fire).
 * .m3e-collapse-out  — decisive removal: opacity + translateY out with
 *                      --ease-drop, held (forwards) until the swap removes it.
 *                      Transform/opacity only — no height collapse.
 * ═══════════════════════════════════════════════════════════════════════════ */

.m3e-confirm-pop {
  animation: badge-pop var(--duration-med) var(--easing-effects) 1;
}

@keyframes m3e-collapse-out {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-4px); }
}
.m3e-collapse-out {
  animation: m3e-collapse-out var(--duration-quick) var(--ease-drop) forwards;
}


/* ═══════════════════════════════════════════════════════════════════════════
 * MOTION 5 — Skeleton helpers (backing core/_partials/_skeleton.html)
 *
 * The base `.m3e-skeleton` (+ --text/--title/--card/--circle) and the
 * `m3e-shimmer` keyframe live in components.css and are reused as-is; reduced
 * motion already stops the shimmer there. These add only the pieces the
 * parameterised partial needs on top of that base:
 *   .m3e-skeleton--row    a table-row-height bar
 *   .m3e-skeleton-block   a vertical rhythm wrapper for a group of bars
 * ═══════════════════════════════════════════════════════════════════════════ */

.m3e-skeleton--row {
  height: 44px;
  border-radius: var(--md-sys-shape-corner-small);
  margin-bottom: var(--md-sys-spacing-2);
}
.m3e-skeleton-block {
  display: flex;
  flex-direction: column;
  gap: var(--md-sys-spacing-2);
}

/* Skeleton-as-hx-indicator: collapsed (no layout footprint) until HTMX marks
 * the element `.htmx-request` for an in-flight swap, then it shows and fades in.
 * Unlike the default opacity-only `.htmx-indicator`, this reclaims its space
 * when idle so a full-height skeleton never leaves a gap. */
.m3e-skeleton-indicator { display: none; }
.m3e-skeleton-indicator.htmx-request {
  display: block;
  animation: menu-backdrop-in var(--md-sys-motion-duration-short3) var(--easing-effects);
}
