/* ============================================================
   EMPIRE OS — Motion foundation (motion.css)
   ------------------------------------------------------------
   A tasteful, GPU-cheap motion layer that sits ON TOP of the
   base design system. Pure CSS: transitions, keyframes, and
   linear() spring easings — NO runtime animation library.

   Loaded LAST (after layout.css) so it cascades over existing
   component styles without editing them. Everything here is:
     • additive (namespaced `--mo-*` tokens, `.mo-*` utilities)
     • transform/opacity/color/background/border only — never
       animating width/height/top/left/margin (no layout thrash)
     • fully neutralized under prefers-reduced-motion (bottom).

   How to apply on a surface (for later per-view polish passes):
     - Reveal a list/grid:  add `.mo-stagger` to the container and
       `style="--i:N"` (0-based) to each child, OR just use the
       nth-child fallback (works up to 16 items with no --i).
     - Reveal one element:  `.mo-enter` (slide-up+fade),
       `.mo-fade` (opacity only), or `.mo-pop` (scale-in).
     - Interactive affordance: add `.mo-tap` for a satisfying
       press, `.mo-lift` for a hover raise (both opt-in; core
       buttons/cards already get polish globally below).
   ============================================================ */

/* The `--mo-*` scale now lives in tokens.css, not here.
   Reason: components.css and forms.css reference these tokens, and BOTH are
   loaded by pages that do NOT load motion.css (login.html, affiliate.html).
   An undefined custom property in a `transition` shorthand makes the whole
   declaration invalid at computed-value time, which silently removed every
   transition on those pages. Tokens belong in the token file. */


/* ============================================================
   1. REUSABLE ENTER ANIMATIONS  (keyframes + utility classes)
   Content-only opacity + transform. Safe to drop on any element.
   ============================================================ */
@keyframes mo-fade-in    { from { opacity: 0; } to { opacity: 1; } }
@keyframes mo-slide-up   { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes mo-scale-in   { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: none; } }

.mo-fade  { animation: mo-fade-in  var(--mo-dur-3) var(--mo-ease-emphasized) both; }
.mo-enter { animation: mo-slide-up var(--mo-dur-3) var(--mo-spring-smooth)   both; }
.mo-pop   { animation: mo-scale-in var(--mo-dur-3) var(--mo-spring-bouncy)   both; }

/* Index-based stagger helper. Preferred: set `style="--i:N"` (0-based)
   on each child so the cascade scales to any list length. Falls back to
   nth-child offsets (1..16) when --i is not provided. */
.mo-stagger > * {
  animation: mo-slide-up var(--mo-dur-3) var(--mo-ease-emphasized) both;
  animation-delay: calc(var(--i, 0) * var(--mo-stagger-step));
}
.mo-stagger[data-i-auto] > *:nth-child(1)  { --i: 0; }
.mo-stagger[data-i-auto] > *:nth-child(2)  { --i: 1; }
.mo-stagger[data-i-auto] > *:nth-child(3)  { --i: 2; }
.mo-stagger[data-i-auto] > *:nth-child(4)  { --i: 3; }
.mo-stagger[data-i-auto] > *:nth-child(5)  { --i: 4; }
.mo-stagger[data-i-auto] > *:nth-child(6)  { --i: 5; }
.mo-stagger[data-i-auto] > *:nth-child(7)  { --i: 6; }
.mo-stagger[data-i-auto] > *:nth-child(8)  { --i: 7; }
.mo-stagger[data-i-auto] > *:nth-child(9)  { --i: 8; }
.mo-stagger[data-i-auto] > *:nth-child(10) { --i: 9; }
.mo-stagger[data-i-auto] > *:nth-child(11) { --i: 10; }
.mo-stagger[data-i-auto] > *:nth-child(12) { --i: 11; }
.mo-stagger[data-i-auto] > *:nth-child(13) { --i: 12; }
.mo-stagger[data-i-auto] > *:nth-child(14) { --i: 13; }
.mo-stagger[data-i-auto] > *:nth-child(15) { --i: 14; }
.mo-stagger[data-i-auto] > *:nth-child(16) { --i: 15; }

/* ============================================================
   2. OPT-IN INTERACTION UTILITIES
   ============================================================ */
/* Satisfying press — firm, never a collapse. */
.mo-tap { transition: transform var(--mo-dur-1) var(--mo-ease-standard); }
.mo-tap:active { transform: scale(0.97); }

/* Hover raise — transform only (no layout, no heavy shadow animation). */
.mo-lift { transition: transform var(--mo-dur-2) var(--mo-spring-smooth); will-change: transform; }
.mo-lift:hover { transform: translateY(-2px); }
.mo-lift:active { transform: translateY(0) scale(0.995); }

/* ============================================================
   3. GLOBAL INTERACTION POLISH on EXISTING selectors
   Layered on top so components.css / layout.css stay untouched.
   Only fills gaps (missing easing, missing press) and refines
   overlay entrances with the smooth spring. Restrained — Linear/
   Vercel, not bouncy toys.
   ============================================================ */

/* Rail nav items: give the color/bg change the house easing (base uses
   the browser default `ease`) and add a barely-there press. */
.rail-item {
  transition:
    color var(--mo-dur-1) var(--mo-ease-standard),
    background-color var(--mo-dur-1) var(--mo-ease-standard);
}
.rail-item:active { transform: scale(0.985); }

/* Buttons already transition + press; keep their timing but ensure the
   press reads as a spring-y settle on release for the primary CTA. */
.btn:active,
.btn-icon:active { transform: scale(0.97); }

/* Chips / tags used as interactive filters: smooth their color/bg swaps
   and add a press when they are actually clickable. */
.tag {
  transition:
    color var(--mo-dur-1) var(--mo-ease-standard),
    background-color var(--mo-dur-1) var(--mo-ease-standard),
    border-color var(--mo-dur-1) var(--mo-ease-standard);
}
.tag[role="button"]:active,
button.tag:active,
a.tag:active { transform: scale(0.96); }

/* Inputs / selects / textareas: smooth focus-ring + border transitions
   (base declares the transition; this just aligns the easing globally). */
.input, .textarea, .select {
  transition:
    border-color var(--mo-dur-2) var(--mo-ease-standard),
    background-color var(--mo-dur-2) var(--mo-ease-standard),
    box-shadow var(--mo-dur-2) var(--mo-ease-standard);
}

/* Overlays — upgrade the entrance easing to the SMOOTH spring so modals
   and the shared right drawer arrive with life but no jank. Base already
   defines the transform/opacity transition + .in state; we only retime. */
.modal {
  transition:
    transform var(--mo-dur-4) var(--mo-spring-smooth),
    opacity var(--mo-dur-3) var(--mo-ease-emphasized);
}
.drawer {
  transition:
    transform var(--mo-dur-4) var(--mo-spring-smooth),
    opacity var(--mo-dur-3) var(--mo-ease-emphasized);
}

/* ============================================================
   4. ROUTE / VIEW TRANSITION
   The router already toggles `#view.view-enter` on real navigation
   (after `body.nav-ready`). Retime that single container entrance
   with the smooth spring + a gentle rise for a premium view swap.
   No router edit required — this only restyles the existing hook.
   A standalone `.mo-view-enter` class is also exposed for any other
   container that wants the same treatment.
   ============================================================ */
@keyframes mo-view-enter { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
/* NOTE: intentionally does NOT target `#view.view-enter`. The route/view swap is
   owned by layout.css (`eos-view-enter`, a crisp 120ms fade) — a longer springy
   translate here made every tab switch read like a full reload. This class is
   left as an opt-in utility for any OTHER container that wants the spring. */
.mo-view-enter {
  animation: mo-view-enter var(--mo-dur-3) var(--mo-spring-smooth) both;
}

/* ============================================================
   4b. THE MOTION SPEC (P5-05)
   ------------------------------------------------------------
   Five purposes. Every animated thing in either product is one of
   them, and each one has exactly one duration and one easing. If a
   new interaction doesn't fit a row, it isn't a new row — it's the
   wrong interaction.

     PURPOSE          DURATION            EASING                  APPLIES TO
     ─────────────────────────────────────────────────────────────────────────
     Micro-feedback   --mo-dur-1  120ms   --mo-ease-standard      hover, press,
                                                                  checkbox, toggle
     Transition       --mo-dur-2  200ms   --mo-ease-standard      modal in/out,
                                                                  tab switch, popover
     Entrance         --mo-dur-3  320ms   --mo-ease-emphasized    drawer, sheet, page
     Celebration      --mo-dur-3  320ms   --mo-spring-bouncy      deal won, agreement
                                                                  signed, habit done
     Ambient          --mo-dur-4  480ms   --mo-ease-standard      brand recolour,
                                                                  build cascade

   THE HARD RULES
   1. Spring easing is for SUCCESS ONLY. Never for navigation, never for
      chrome. A bouncy menu feels cheap; a bouncy "deal won" feels earned.
      That is what `.celebrate` is for, and it is the only bouncy thing.
   2. Nothing animates longer than --mo-dur-4 (480ms) except the one-time
      build cascade. A status loop (spinner, skeleton, typing dots) is not
      an animation in this sense — it reports, it doesn't decorate.
   3. Everything animates FROM WHERE IT CAME FROM. A drawer slides from the
      right edge. A popover scales from its trigger's corner, which is why
      `.popover` sets `transform-origin` off its measured placement rather
      than from the middle of the screen.
   4. prefers-reduced-motion is honoured globally (section 5 below).
   5. Never animate on first paint. Content appears; it does not perform.
      `.rise` / `#view.view-enter` fire on ROUTE CHANGE only — the router
      gates them behind `body.nav-ready` for exactly this reason.
   ============================================================ */

/* CELEBRATION — the one bouncy thing, and the only correct use of the
   underdamped spring. Add `.celebrate` for one beat when something the
   person worked for actually lands. */
.celebrate { animation: mo-celebrate var(--mo-dur-3) var(--mo-spring-bouncy) both; }
@keyframes mo-celebrate { 0% { transform: scale(0.9); } 60% { transform: scale(1.04); } 100% { transform: none; } }

/* RULE 1, applied. Two pieces of chrome were using the bouncy spring:
   the switch knob (micro-feedback) and the bulk-action bar (an entrance).
   Both are retimed onto the rows they belong to, so "springy" once again
   means "something good happened" rather than "something moved". */
.toggle::after,
.toggle[data-tip]::after {
  transition: left var(--mo-dur-1) var(--mo-ease-standard),
              background-color var(--mo-dur-1) var(--mo-ease-standard);
}
.selbar { animation: selbar-in var(--mo-dur-3) var(--mo-ease-emphasized); }

/* RULE 3, applied. A popover grows out of the edge nearest its trigger —
   placeFloating() stamps data-placement, so the origin can follow it. */
.popover { transform-origin: top left; transition: opacity var(--mo-dur-2) var(--mo-ease-standard), transform var(--mo-dur-2) var(--mo-ease-standard); }
.popover[data-placement="top"] { transform-origin: bottom left; }

/* ============================================================
   4c. DATA DISPLAY RULES (P5-10)
   ------------------------------------------------------------
   1 · NUMBERS TABULAR-ALIGN. A figure that changes must not shift the
       glyphs around it. Every place a number is shown on its own — a
       metric, a count, a badge, a duration, a currency — is lining and
       tabular by default. Prose keeps proportional figures.
   2 · DATES ARE MACHINE-READABLE. A rendered date is a <time datetime>
       so it can be copied, parsed and read out. `fmt.timeTag()` in ui.js
       is the one way to emit one.
   3 · TEXT NEVER TRUNCATES MID-WORD. `.ellip` cuts at the box with a real
       ellipsis and keeps the full value in `title`. Nothing slices a
       string to a character count.
   4 · A SPINNER IS NEVER CENTRED IN AN EMPTY BOX. If the shape of what is
       loading is known, show that shape (`.skeleton`). A centred spinner
       says "wait" and nothing else.
   ============================================================ */
:where(
  .kpi-value, .kpi-delta, .badge, .badge-soft, .tag, .chip, .u-chip,
  .drawer-tab-badge, .tab__count, .ring span, .notif-dot, .ip-count,
  .selbar-count, .table .num, kbd, time, [data-num], .num, .mono
) { font-variant-numeric: tabular-nums lining-nums; }

/* Rule 4 lives in components.css now (`.skeleton-line` / `.skeleton-row` /
   `.skeleton-list` / `.skeleton-card`). It used to be re-declared here as a
   1em bar, which — because motion.css loads last — silently overrode the
   real row height in components.css. One shape, one file. */

/* ============================================================
   5. ACCESSIBILITY — prefers-reduced-motion (NON-NEGOTIABLE)
   Neutralize everything above: kill animations, remove transforms,
   collapse transition/animation timings to a hair. Opacity-only
   cross-fades are still allowed (they don't cause vestibular issues)
   but durations are minimized so the UI feels instant.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  /* Global clamp (mirrors base.css / layout.css — kept here so motion.css
     is self-contained and safe even if loaded standalone). */
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  /* Strip motion from the utilities/enter animations entirely so nothing
     translates or scales — just render in place. */
  .mo-fade, .mo-enter, .mo-pop,
  .mo-stagger > *,
  body.nav-ready #view.view-enter,
  .mo-view-enter { animation: none !important; }

  .celebrate { animation: none !important; }
  .mo-lift { will-change: auto; }
  .mo-lift:hover,
  .mo-tap:active,
  .mo-lift:active,
  .rail-item:active,
  .btn:active, .btn-icon:active, .icon-btn:active,
  .tag[role="button"]:active, button.tag:active, a.tag:active { transform: none !important; }
}
