/* ============================================================
   GetEYT motion layer
   Loads LAST, after app.css and v2.css.

   Two ideas hold the whole file together.

   1. Motion is a token, not a number. Durations and easings are
      named by the job they do, so a reviewer argues about whether
      something is an enter or an exit rather than about whether it
      should be 180ms or 220ms.

   2. Enter and exit are not the same gesture. A thing arriving is
      asking for attention and decelerates into place. A thing
      leaving is getting out of the way and should be gone before
      you have finished deciding to dismiss it. Every exit here is
      shorter than its own enter, and on a different curve.

   Almost every rule is gated behind `html.m-on`, a class that
   assets/motion.js sets on boot. Three reasons: the layer can never
   fight a component when the script fails to load, the gate lifts
   specificity so a motion property wins a tie without redefining
   anybody's component class, and every rule in here is additive by
   construction. Nothing below re-declares a class that app.css or
   v2.css already owns.
   ============================================================ */

:root {
  /* ---- durations, named by role ----------------------------
     Nothing on screen may exceed --m-slow. The one exception is
     the opening sequence in v2.js, which runs once per session
     and is deliberately theatrical. It is not owned by this file.

       instant  the acknowledgement. A press must be visible
                inside 100ms or the control feels broken.
       fast     every exit, every tooltip, every hover state.
       base     the standard enter. Menus, sheets, panes, pages.
       slow     long travel only. A 520px drawer crossing the
                screen, and the once per card number reveal.       */
  --m-instant: 90ms;
  --m-fast: 150ms;
  --m-base: 230ms;
  --m-slow: 340ms;

  /* ---- easings, named by direction -------------------------
       e-out    enters. Fast off the line, long settle. The curve
                that makes a thing look like it was already on its
                way before you asked.
       e-in     exits. Slow to commit, then gone. Reads as the
                object accelerating away rather than dissolving.
       e-inout  a move between two known points, where both ends
                are anchored: the tab underline, the rail.
       e-spring the release of a press. Overshoots by about half a
                percent and settles. Below, upgraded to a real
                spring curve where linear() is supported.          */
  --e-out: cubic-bezier(0.16, 1, 0.3, 1);
  --e-in: cubic-bezier(0.55, 0, 1, 0.45);
  --e-inout: cubic-bezier(0.65, 0, 0.35, 1);
  --e-spring: cubic-bezier(0.34, 1.4, 0.64, 1);

  /* how far an entering layer travels. Small on purpose: distance
     is the most common way motion turns into lag. */
  --m-lift: 8px;
  --m-nudge: 4px;

  /* the row stagger step, and the row after which staggering stops.
     Both are read by motion.js as well, keep them in sync there. */
  --m-step: 18ms;
}

/* A real spring, sampled to a linear() curve, wherever the browser
   can take one. Two overshoots, heavily damped, settles by 40%.
   The cubic above is the fallback and is close enough that nobody
   would name the difference in a screenshot. */
@supports (animation-timing-function: linear(0, 1)) {
  :root {
    --e-spring: linear(
      0,
      0.0088,
      0.0348 2.34%,
      0.1436 5.35%,
      0.4103 10.7%,
      0.5279,
      0.6467,
      0.7538,
      0.845 17.06%,
      0.8815,
      0.9127,
      0.9385,
      0.9595,
      0.9755 22.4%,
      0.9917 25.4%,
      1.0011 29.08%,
      1.0046 33.09%,
      1.0002 40.11%,
      1
    );
  }
}

/* ============================================================
   Shared keyframes.
   Every animation in the file resolves to one of these, which is
   what makes the reduced motion switch below a two line change
   rather than an audit.
   ============================================================ */

@keyframes m-fade-in {
  from {
    opacity: 0;
  }
}
@keyframes m-fade-out {
  to {
    opacity: 0;
  }
}
@keyframes m-rise-in {
  from {
    opacity: 0;
    transform: translateY(var(--m-lift));
  }
}
@keyframes m-rise-out {
  to {
    opacity: 0;
    transform: translateY(calc(var(--m-nudge) * -1));
  }
}
@keyframes m-pop-in {
  from {
    opacity: 0;
    transform: translateY(var(--m-nudge)) scale(0.965);
  }
}
@keyframes m-pop-out {
  to {
    opacity: 0;
    transform: scale(0.985);
  }
}
@keyframes m-slide-in {
  from {
    opacity: 0;
    transform: translateX(calc(var(--m-lift) * -1));
  }
}
@keyframes m-slide-out {
  to {
    opacity: 0;
    transform: translateX(calc(var(--m-lift) * -1));
  }
}

/* ============================================================
   1. Interactive physics.

   The difference between "has transitions" and "feels native" is
   entirely in this block. A press registers in --m-instant, which
   is under the 100ms threshold where a person stops attributing
   the change to their own finger. The release is what carries the
   character: it runs on the spring, over --m-base, and overshoots
   slightly, so the control reads as a physical object being let go
   rather than a rectangle changing size.

   motion.js stamps data-m-press on anything pressable, including
   nodes injected later. Using an attribute rather than a class list
   means this file never has to name, and therefore never has to
   redefine, somebody else's component.

     data-m-press="sm"    small controls, full scale
     data-m-press="wide"  wide controls, where 3.5% reads as a jolt
     data-m-press="row"   table rows, where transform breaks a
                          sticky header and a colour shift is the
                          honest affordance anyway
   ============================================================ */

[data-m-press] {
  transition:
    transform var(--m-base) var(--e-spring),
    background-color var(--m-instant) linear,
    border-color var(--m-instant) linear,
    box-shadow var(--m-instant) linear,
    color var(--m-instant) linear,
    opacity var(--m-instant) linear;
}
[data-m-press]:active {
  transform: scale(var(--m-press-s, 0.965));
  transition-duration: var(--m-instant);
  transition-timing-function: var(--e-out);
}
[data-m-press="wide"] {
  --m-press-s: 0.99;
}
[data-m-press="row"] {
  --m-press-s: 1;
}
[data-m-press]:disabled:active,
[data-m-press].btn--disabled:active {
  transform: none;
}

/* the hotkey pills already lift on hover in v2.css. Pressing one
   has to keep the lift or the pill drops as it is pushed, which
   reads as two separate things happening. */
html.m-on .hot:active {
  transform: translateY(-1px) scale(0.96);
}

/* a pressed row darkens instead of scaling */
html.m-on tr.clickable[data-m-press]:active > td {
  background: var(--surface-2);
}
html.m-on tr.clickable[data-m-press] > td {
  transition: background-color var(--m-instant) linear;
}

/* focus should arrive instantly. A ring that eases in is a ring
   that is not there when a keyboard user needs it. */
html.m-on [data-m-press]:focus-visible {
  transition-duration:
    0s, var(--m-instant), var(--m-instant), var(--m-instant), var(--m-instant),
    var(--m-instant);
}

/* ============================================================
   2. Every layer that appears, and how it leaves.

   Origin aware throughout. motion.js measures the trigger against
   the popover it opens and writes --m-ox / --m-oy, so a menu grows
   out of the control that summoned it rather than out of its own
   middle. That is the single cheapest thing on this list and the
   one people notice without being able to say why.
   ============================================================ */

/* ---- the panel sheet, drawer and modal, from actions.js ----
   actions.js removes the node 220ms after it drops .on, so the
   exit has to finish inside that window. It does, at 150ms. */
html.m-on .pnback {
  transition: background-color var(--m-fast) var(--e-in);
}
html.m-on .pnback.on {
  transition-duration: var(--m-base);
  transition-timing-function: var(--e-out);
}
html.m-on .pn--drawer {
  transition: transform var(--m-fast) var(--e-in);
}
html.m-on .pnback.on .pn--drawer {
  transition: transform var(--m-slow) var(--e-out);
}
html.m-on .pn--modal {
  transform-origin: var(--m-ox, 50%) var(--m-oy, 50%);
  transition:
    transform var(--m-fast) var(--e-in),
    opacity var(--m-fast) var(--e-in);
}
html.m-on .pnback.on .pn--modal {
  transition:
    transform var(--m-base) var(--e-out),
    opacity var(--m-fast) linear;
}

/* the panel body arrives a beat after its own container, so the
   sheet reads as a surface carrying content rather than as a
   rectangle that was always full */
html.m-on .pnback.on .pn__body {
  animation: m-rise-in var(--m-base) var(--e-out) 60ms both;
}

/* ---- drawers and the Chat EYT slide over, from app.js ---- */
html.m-on .drawer,
html.m-on .chat {
  transition: transform var(--m-fast) var(--e-in);
}
html.m-on .drawer.open,
html.m-on .chat.open {
  transition: transform var(--m-slow) var(--e-out);
}
html.m-on .scrim {
  transition: opacity var(--m-fast) var(--e-in);
}
html.m-on .scrim.open {
  transition: opacity var(--m-base) var(--e-out);
}
/* a chat message is the one place a small pop is correct: it is a
   new object arriving, not a panel being revealed */
html.m-on .msg {
  animation: m-pop-in var(--m-base) var(--e-out) both;
}

/* ---- the role switcher menu, top right of the topbar ----
   v2.css already anchors it top right. This adds the exit, which
   was previously the same curve and duration as the enter. */
html.m-on .rsmenu {
  transform-origin: var(--m-ox, 100%) var(--m-oy, 0%);
  transition:
    opacity var(--m-fast) var(--e-in),
    transform var(--m-fast) var(--e-in);
}
html.m-on .rswrap.open .rsmenu {
  transition:
    opacity var(--m-instant) linear,
    transform var(--m-base) var(--e-out);
}
html.m-on .rsmenu__go {
  transition:
    transform var(--m-base) var(--e-spring),
    color var(--m-instant);
}
html.m-on .rsmenu__i:hover .rsmenu__go {
  transform: translateX(2px);
}

/* ---- the studio switcher dropdown ---- */
html.m-on .menu {
  transform-origin: var(--m-ox, 0%) var(--m-oy, 0%);
  transform: translateY(calc(var(--m-nudge) * -1)) scale(0.98);
  transition:
    opacity var(--m-fast) var(--e-in),
    transform var(--m-fast) var(--e-in);
}
html.m-on .menu.open {
  transform: none;
  transition:
    opacity var(--m-instant) linear,
    transform var(--m-base) var(--e-out);
}
/* ---- the hotkey bar ----
   roles.js hides it with display:none, which cannot be animated.
   motion.js intercepts the class change, plays this, and only then
   lets the node go. Same path in reverse when it comes back. */
html.m-on .hotbar {
  transform-origin: 50% 100%;
  transition:
    transform var(--m-base) var(--e-out),
    opacity var(--m-fast) linear,
    padding var(--m-base) var(--e-out),
    box-shadow var(--m-base) var(--e-out);
}
html.m-on .hotbar.m-bar-out {
  opacity: 0;
  transform: translateX(-50%) translateY(10px) scale(0.96);
  transition:
    transform var(--m-fast) var(--e-in),
    opacity var(--m-fast) var(--e-in);
}
html.m-on .hotbar.m-bar-in {
  animation: m-bar-up var(--m-base) var(--e-out) both;
}
@keyframes m-bar-up {
  from {
    opacity: 0;
    transform: translateX(-50%) translateY(10px) scale(0.96);
  }
  to {
    opacity: 1;
    transform: translateX(-50%) translateY(0) scale(1);
  }
}
/* the flash on a number key press. v2.css rings it, this gives the
   ring a body so the bar acknowledges the key rather than glowing. */
html.m-on .hotbar.flash {
  animation: m-bar-tick var(--m-base) var(--e-spring);
}
@keyframes m-bar-tick {
  40% {
    transform: translateX(-50%) scale(1.022);
  }
}
html.m-on .hot > span,
html.m-on .hot__x,
html.m-on .hotbar__off {
  transition:
    max-width var(--m-base) var(--e-inout),
    opacity var(--m-fast) linear;
}
html.m-on .hotpick {
  transform-origin: 50% 100%;
  animation: m-pop-in var(--m-base) var(--e-out) both;
}

/* ---- the PIN gate ----
   roles.js calls back.remove() with no exit. motion.js re-inserts
   an inert clone for exactly as long as it takes to leave. The
   clone must not replay the enter animation it was born with. */
html.m-on .pinback.m-ghost,
html.m-on .pinback.m-ghost .pin {
  animation: none;
  pointer-events: none;
}
html.m-on .pinback.m-ghost {
  animation: m-fade-out var(--m-fast) var(--e-in) both;
}
html.m-on .pinback.m-ghost .pin {
  animation: m-pop-out var(--m-fast) var(--e-in) both;
}
/* the enter, retuned onto the token scale. v2.css owns the
   keyframes, this owns only the timing. */
html.m-on .pinback {
  animation-duration: var(--m-fast);
  animation-timing-function: var(--e-out);
}
html.m-on .pin {
  animation-duration: var(--m-base);
  animation-timing-function: var(--e-out);
}

/* ---- tooltips ---- */
html.m-on #cmp-tip {
  transform-origin: var(--m-ox, 50%) 100%;
  transition:
    opacity var(--m-instant) var(--e-in),
    transform var(--m-instant) var(--e-in);
}
html.m-on #cmp-tip.show {
  transition:
    opacity var(--m-fast) var(--e-out),
    transform var(--m-fast) var(--e-out);
}

/* ============================================================
   3. Lists and tables.

   The stagger is pure CSS. motion.js writes --m-i on the first N
   visible rows and nothing at all on the rest, so a 300 row table
   costs 14 delays and 286 plain fades rather than six seconds of
   choreography. The cap is enforced in the JS, not here, because
   a CSS cap would still pay the cost of setting the variable.

   Removal animates. people.js sets display:none directly, so
   motion.js catches the change, restores the row for the length of
   one exit, and only then lets it go.
   ============================================================ */

html.m-on .m-row-in {
  animation: m-slide-in var(--m-base) var(--e-out) both;
  animation-delay: calc(var(--m-i, 0) * var(--m-step));
}
html.m-on .m-row-out {
  animation: m-fade-out var(--m-fast) var(--e-in) both;
}
/* a table row cannot take a transform without breaking the sticky
   header above it, so its cells carry the movement instead */
html.m-on tr.m-row-in > td,
html.m-on tr.m-row-in > th {
  animation: m-slide-in var(--m-base) var(--e-out) both;
  animation-delay: calc(var(--m-i, 0) * var(--m-step));
}
html.m-on tr.m-row-in {
  animation-name: m-fade-in;
}

/* kanban cards are real objects being carried, so they get the
   pop rather than the slide */
html.m-on .kanban-card.m-row-in {
  animation-name: m-pop-in;
}
html.m-on .kanban-card {
  transition:
    transform var(--m-base) var(--e-spring),
    box-shadow var(--m-fast) var(--e-out),
    opacity var(--m-fast) linear;
}
html.m-on .kanban-card.dragging {
  transition: none;
}
html.m-on .kanban-col.drop {
  transition: background-color var(--m-fast) var(--e-out);
}

/* ============================================================
   4. Page switches.

   There is no View Transitions API here, on purpose, and there must
   not be one again. It shipped, and it ate user clicks: while a
   transition runs the live DOM is not hit tested, so every click in
   that window targets <html> instead of the button under the
   cursor. Measured lost at 100ms, 200ms and 300ms after a nav
   click. The full diagnosis, including the three fixes that do not
   work and why, is in the matching block in motion.js.

   This is the only page transition, it animates the incoming page
   with transform and opacity, and it creates no overlay to block
   anything. motion.js tags the page when it loses .hidden, so it
   covers the rail, the hotkey bar, the number keys and a pasted URL
   equally, without capturing a single event.
   ============================================================ */

html.m-on .page.m-page-in {
  animation: m-rise-in var(--m-base) var(--e-out) both;
}

/* ============================================================
   5. Tabs.

   The underline is a single element that travels, rather than a
   border switching off on one tab and on at another. It is the
   cheapest way to say "these are the same control in two states"
   and it is the detail that separates a considered tab bar from a
   set of links.

   motion.js only adds .m-tabs once it has successfully measured,
   so a failure leaves the native border in place rather than a
   tab bar with no indicator at all.
   ============================================================ */

html.m-on .tabs.m-tabs {
  position: relative;
}
html.m-on .tabs.m-tabs .tab.active {
  border-bottom-color: transparent;
}
html.m-on .m-tabline {
  position: absolute;
  bottom: -1px;
  left: 0;
  height: 2px;
  width: 1px;
  background: var(--plum);
  transform-origin: 0 50%;
  transform: translateX(var(--m-tx, 0px)) scaleX(var(--m-tw, 0));
  pointer-events: none;
}
html.m-on .m-tabline.m-ready {
  transition: transform var(--m-base) var(--e-inout);
}
html.m-on .tabpane.m-pane-in {
  animation: m-rise-in var(--m-base) var(--e-out) both;
}

/* ============================================================
   6. Numbers.

   Counting and sweeping happen once, when the card first crosses
   into the viewport, and never again. Scrolling back to a metric
   you have already read and watching it recount is the single
   most common way this effect turns from craft into noise.

   The transition is declared here and driven by motion.js, so the
   sweep respects the same tokens as everything else.
   ============================================================ */

html.m-on .arcm.m-sweep .arcm__ring circle:not(.bg) {
  transition: stroke-dashoffset var(--m-slow) var(--e-out);
  transition-delay: calc(var(--m-i, 0) * 45ms);
}
html.m-on .m-counting {
  font-variant-numeric: tabular-nums;
}

/* the metric row underline, retuned. The bar is the chart telling
   you which column it is drawing, so it should land with the same
   authority as the number changing above it. */
html.m-on .metric::after {
  transition: transform var(--m-base) var(--e-out);
}
html.m-on .metricchart .bar {
  transition: opacity var(--m-fast) var(--e-out);
}

/* ============================================================
   7. Chrome that was already moving, brought onto the scale.
   ============================================================ */

html.m-on .app {
  transition: grid-template-columns var(--m-base) var(--e-inout);
}
html.m-on .rail-toggle svg,
html.m-on .rs__chev,
html.m-on .nav-fold {
  transition: transform var(--m-base) var(--e-inout);
}
/* A rule like `html.m-on .nav-item { transition: ... }` looks harmless and
   is not: at 0,2,1 it outranks [data-m-press] at 0,1,0 and silently drops
   the press transform off every sidebar row, every menu row and every role
   in the switcher. Anything already covered by the press block is left to
   the press block, deliberately. */
html.m-on .card {
  transition: box-shadow var(--m-fast) var(--e-out);
}

/* will-change is set by motion.js immediately before an element
   animates and removed the moment it finishes. A permanent
   will-change on a drawer is a permanent compositor layer. */
.m-will {
  will-change: transform, opacity;
}

/* ============================================================
   8. Reduced motion.

   Off, not shortened. Every transform based animation in this file
   is re-pointed at a plain opacity fade, every travel distance
   collapses to zero, and every duration drops to a single frame.
   The fade stays because a hard swap between two full screens of
   content is its own accessibility problem: it removes the cue
   that anything happened at all.
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  :root {
    --m-instant: 0.01ms;
    --m-fast: 16ms;
    --m-base: 16ms;
    --m-slow: 16ms;
    --e-out: linear;
    --e-in: linear;
    --e-inout: linear;
    --e-spring: linear;
    --m-lift: 0px;
    --m-nudge: 0px;
    --m-step: 0ms;
  }

  /* the blanket. Anything this file did not author, including the
     transitions already living in app.css and v2.css, collapses to
     one frame with no delay. */
  html.m-on *,
  html.m-on *::before,
  html.m-on *::after {
    animation-duration: 16ms !important;
    animation-delay: 0ms !important;
    transition-duration: 16ms !important;
    transition-delay: 0ms !important;
  }

  /* transforms are switched off rather than sped up */
  html.m-on [data-m-press]:active,
  html.m-on .hot:active,
  html.m-on .rsmenu__i:hover .rsmenu__go {
    transform: none;
  }
  html.m-on .m-tabline,
  html.m-on .m-tabline.m-ready {
    transition: none;
  }
  html.m-on .arcm.m-sweep .arcm__ring circle:not(.bg) {
    transition: none;
  }

  /* every authored animation resolves to the fade */
  html.m-on .m-row-in,
  html.m-on tr.m-row-in > td,
  html.m-on tr.m-row-in > th,
  html.m-on .kanban-card.m-row-in,
  html.m-on .page.m-page-in,
  html.m-on .tabpane.m-pane-in,
  html.m-on .pnback.on .pn__body,
  html.m-on .msg,
  html.m-on .hotbar.m-bar-in {
    animation-name: m-fade-in;
  }
  html.m-on .m-row-out,
  html.m-on .pinback.m-ghost .pin {
    animation-name: m-fade-out;
  }
  html.m-on .hotbar.flash {
    animation: none;
  }
  html.m-on .hotbar.m-bar-out {
    transform: translateX(-50%);
  }
}
