/* MODULE: css/motion.css — the shared motion system (PHASE3 §3).

   ── THE PRINCIPLE ─────────────────────────────────────────────────────────
   Motion exists to confirm a physical event. It never entertains. If an
   animation plays when the user did not act and nothing changed, it should
   not exist — so there is not one looping, breathing, shimmering or
   attention-seeking rule in this file. Every animation below is one-shot and
   every transition is the answer to a press, a switch, or a drop.

   Depends on css/tokens.css for its durations and curves. Depends on nothing
   else and loads nothing.

   ── WHAT LIVES HERE, AND WHAT DOES NOT ────────────────────────────────────
   Two kinds of rule:

     1. The VOCABULARY — .mo-* utilities and --mo-* tokens that any agent can
        put on any element. This is the interface; use it rather than writing
        a second opinion about how long a drawer takes.
     2. The five named behaviours PHASE3 §3 specifies but that have no other
        home: the press, the mode switch, the start handoff, the milestone
        pulse, the panel. Where an owner already implements one correctly
        (css/base.css for .btn/.chip, src/dial.js for the scrub detent and its
        own impulse, css/layout.css for the FLIP) this file leaves it alone
        and simply publishes the numbers, so there is exactly one place to
        change them.

   ── LINKING ───────────────────────────────────────────────────────────────
   This sheet must be linked LAST, after every other stylesheet, because
   several rules here refine a transition another sheet declares — the mode
   keys (css/chassis.css), the layout's grip and resize keys (css/layout.css),
   the dial's knob (css/dial.css) — and they have to win those ties on source
   order. index.html does that, and sw.js precaches it.

   It is additive by construction: everything that consumes it names a token
   with a fallback, so if the link is ever dropped the app loses polish and
   nothing else. css/layout.css's FLIP is the example — it reads
   `var(--mo-dur-flip, var(--dur-med))`, so modules still glide correctly on
   the design system's own value.

   ── REDUCED MOTION IS NOT REDUCED FEEDBACK ────────────────────────────────
   §9 collapses every duration here to an instant state change. It does NOT
   touch haptics or audio: src/haptics.js and src/audio.js fire exactly as
   they did. A user who cannot tolerate movement still gets the tick, the
   milestone chime and the completion fall. */

/* ============================================================== *
 * 1. THE VOCABULARY
 *    Namespaced --mo-* so it cannot collide with css/materials.css's
 *    --m-* material tokens.
 * ============================================================== */

:root {
  /* THE PRESS is asymmetric, and that asymmetry is the whole trick: a real
     switch drops fast under your finger and springs back slower. Down on
     --ease-out, up on --ease-spring. */
  --mo-dur-press: var(--dur-press); /* 70ms  — the key travelling down */
  --mo-dur-release: var(--dur-fast); /* 120ms — and back up, with overshoot */
  --mo-travel: 1px; /* how far a key actually moves */

  /* THE HANDOFF. PHASE3 §3 names 200ms for the start settle and tokens.css
     has no 200ms step — 120ms reads as a flinch and 240ms as a flourish, and
     this is neither. Declared once, here, and consumed by name so no call
     site ever writes a bare duration. */
  --mo-dur-settle: 200ms;

  /* ONE MOVEMENT: the segmented indicator, the panel, the FLIP. All 240ms so
     the app has a single sense of how long "a thing moved" takes. */
  --mo-dur-slide: var(--dur-med);
  --mo-dur-flip: var(--dur-med);
  --mo-ease-flip: var(--ease-spring);

  /* ONE PULSE. Never repeating, never flashing. */
  --mo-dur-pulse: var(--dur-slow);
}

/* --- .mo-press --------------------------------------------------------
   A key that physically travels. css/base.css already gives .btn and .chip
   this behaviour with the same two durations; put .mo-press on anything
   else that should feel like a switch — a Panel tab, a sheet grip, a
   tutorial control.

   Uses the `translate` property rather than `transform` so it composes with
   a transform another sheet may already own on the same element.

   [data-pressed="true"] is the JS hook for the same state: :active is
   unreliable across a pointer capture, and PHASE3 §3 asks for the press on
   POINTERDOWN specifically. Either one works; both together are fine. */

.mo-press {
  translate: 0 0;
  transition:
    translate var(--mo-dur-release) var(--ease-spring),
    box-shadow var(--mo-dur-release) var(--ease-out),
    background-color var(--mo-dur-release) var(--ease-out);
}

.mo-press:active:not(:disabled):not([aria-disabled="true"]),
.mo-press[data-pressed="true"] {
  translate: 0 var(--mo-travel);
  transition-duration: var(--mo-dur-press);
  transition-timing-function: var(--ease-out);
}

/* --- .mo-pulse --------------------------------------------------------
   ONE soft pulse. Add the class, remove it on animationend. A milestone is
   a fact being reported, not an alarm going off, so this must never be
   given an iteration count above 1 — hence the explicit declaration rather
   than relying on the shorthand's default. */

@keyframes mo-pulse {
  0% {
    scale: 1;
    opacity: 1;
  }
  38% {
    scale: 1.012;
    opacity: 0.88;
  }
  100% {
    scale: 1;
    opacity: 1;
  }
}

.mo-pulse {
  animation-name: mo-pulse;
  animation-duration: var(--mo-dur-pulse);
  animation-timing-function: var(--ease-out);
  animation-iteration-count: 1;
  animation-fill-mode: none;
}

/* ============================================================== *
 * 2. THE PRESS, APPLIED
 *    css/base.css owns .btn and .chip and already gets this exactly
 *    right. The controls below are the ones nobody else covers.
 * ============================================================== */

/* THE OUTGOING LABEL CROSS-FADES (PHASE3 §3). css/chassis.css fades the mode
   keys' colour over --dur-fast, which is half the time the engaged face takes
   to slide: the label finishes changing before the face arrives, and the eye
   catches two events instead of one. Matching the two durations is what makes
   it read as ONE movement. Everything else about the key — its material, its
   press behaviour, its focus ring — stays css/chassis.css's. */
:root .segmented__key {
  transition:
    color var(--mo-dur-slide) var(--ease-out),
    box-shadow var(--mo-dur-slide) var(--ease-out),
    background-color var(--mo-dur-slide) var(--ease-out);
}

:root .segmented__key .segmented__led {
  transition: opacity var(--mo-dur-slide) var(--ease-out);
}

/* The layout engine's own furniture: grip, hide, resize. Small keys, same
   physics. */
.tt-mod__btn,
.tt-mod__resize {
  translate: 0 0;
  transition:
    translate var(--mo-dur-release) var(--ease-spring),
    background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.tt-mod__btn:active,
.tt-mod__resize:active {
  translate: 0 var(--mo-travel);
  transition-duration: var(--mo-dur-press);
  transition-timing-function: var(--ease-out);
}

/* ============================================================== *
 * 3. THE MODE SWITCH
 *
 *    ONE movement. The engaged segment SLIDES between the three positions
 *    on a spring and the labels cross-fade underneath it. No bounce, no
 *    flash, no second thing appearing where the old one was.
 *
 *    WHAT SLIDES IS THE POCKET. css/chassis.css §3 builds the switcher as a
 *    billet of brushed aluminium with the engaged segment MILLED INTO it,
 *    which is what a real machined selector looks like. So the moving part
 *    is that recess, not a raised key: the same alloy in shadow, travelling
 *    along the billet. It carries css/chassis.css's own --ch-machined-recess
 *    and the pressed-metal image, and falls back to --shadow-press and a
 *    flat face if either sheet is absent.
 *
 *    Mechanism: the indicator is .segmented::before, placed as a grid item
 *    in column 1 of the switcher's own three-column grid — so it is exactly
 *    one key wide, with no arithmetic about the track's padding, border or
 *    chrome frame — and translated by whole columns. `translate: calc(100%
 *    + gap)` resolves 100% against the indicator's own border box, i.e. one
 *    key, so the step is right at every width. --mo-seg-gap is the contract
 *    css/chassis.css §3 names by hand: it declares the same gap so the slide
 *    lands exactly on each key.
 *
 *    :has() reads which key is engaged, which is what makes this work with
 *    zero JavaScript: the shell already sets aria-selected, and that single
 *    attribute drives the whole animation.
 *
 *    Because the pocket moves off the key and onto the indicator, the key's
 *    own face has to come off. That rule is written at (0,5,0) so it beats
 *    css/chassis.css §3's (0,4,0) and css/materials.css §6's (0,3,0)
 *    whatever the link order.
 *
 *    ALL OF IT is inside @supports selector(:has(*)) and gated on the
 *    control having exactly three keys. Without :has, or with a switcher
 *    the shell has rebuilt differently, nothing here applies and the milled
 *    pocket simply stops sliding and starts appearing — which is what the
 *    app does today.
 * ============================================================== */

@supports selector(:has(*)) {
  .segmented:has(> .segmented__key:nth-child(3)):not(:has(> .segmented__key:nth-child(4))) {
    /* Must match the gap css/chassis.css §3 declares on .segmented — its
       author names this variable by hand there for exactly that reason. The
       indicator steps by one column plus one gap. */
    --mo-seg-gap: calc(var(--space-1) / 2);
    position: relative;
  }

  .segmented:has(> .segmented__key:nth-child(3)):not(:has(> .segmented__key:nth-child(4)))::before {
    content: "";
    grid-row: 1;
    grid-column: 1;
    z-index: 0;
    border-radius: var(--radius-pill);

    /* The milled pocket: the same alloy, in shadow. */
    background-color: var(--m-face-press, var(--surface-2));
    background-image: var(--m-metal-image-press, none);
    background-size: var(--m-metal-size, auto);
    background-repeat: var(--m-metal-repeat, no-repeat);
    background-position: var(--m-metal-pos-press, 0 0);
    background-blend-mode: var(--m-metal-blend, normal);
    box-shadow: var(--ch-machined-recess, var(--shadow-press));

    translate: 0;
    transition: translate var(--mo-dur-slide) var(--ease-spring);
    pointer-events: none;
  }

  /* The keys sit on top of it and explicitly hold columns 1–3, so the
     pseudo-element cannot push them along the auto-flow. */
  :root .segmented:has(> .segmented__key:nth-child(3)) > .segmented__key {
    position: relative;
    z-index: 1;
    grid-row: 1;
    background-color: transparent;
    background-image: none;
    box-shadow: none;
  }

  :root .segmented > .segmented__key:nth-child(1) { grid-column: 1; }
  :root .segmented > .segmented__key:nth-child(2) { grid-column: 2; }
  :root .segmented > .segmented__key:nth-child(3) { grid-column: 3; }

  .segmented:has(> .segmented__key:nth-child(2)[aria-selected="true"])::before {
    translate: calc(100% + var(--mo-seg-gap, 0px));
  }

  .segmented:has(> .segmented__key:nth-child(3)[aria-selected="true"])::before {
    translate: calc(200% + var(--mo-seg-gap, 0px) * 2);
  }

  /* No press state on the engaged segment: a key already at the bottom of
     its pocket has nowhere to travel, which is css/chassis.css §3's rule and
     the right one. Pressing an UNENGAGED key is still a press, and that
     rule is css/chassis.css's too. */
}

/* ============================================================== *
 * 4. THE START HANDOFF
 *
 *    "The disc takes hold": ONE 200ms settle as the timer starts, and the
 *    knob recedes with it. A handoff, not a flourish — you are giving the
 *    instrument the time you just set, and it is taking it.
 *
 *    src/dial.js writes .is-running onto .dial, so the animation fires on
 *    start AND on resume — both are the same moment — and never when the
 *    user did nothing. It is applied to .dial__svg, which nothing else
 *    animates: .dial__disc already carries the approach breathe from
 *    css/dial.css, and two `animation` declarations on one element is one
 *    of them silently winning.
 * ============================================================== */

@keyframes mo-dial-settle {
  from {
    scale: 0.994;
  }
  to {
    scale: 1;
  }
}

.dial.is-running .dial__svg {
  animation: mo-dial-settle var(--mo-dur-settle) var(--ease-out) 1;
}

/* css/dial.css fades the knob out over --dur-med; bring it into the settle
   so the two are one gesture rather than two overlapping ones. */
.dial.is-running .dial__knob {
  transition-duration: var(--mo-dur-settle);
}

/* ============================================================== *
 * 5. THE PANEL
 *
 *    THE RULE: transform and opacity only. Never inline-size, never a
 *    height, never inset — a sheet that animates its height is a sheet that
 *    drops frames, and the instrument behind it must not reflow while it
 *    opens.
 *
 *    THE IMPLEMENTATION IS css/panel.css, which follows that rule exactly
 *    (its own §MOTION note says so), slides the sheet inside a clip box so
 *    it cannot cross the transport even mid-animation, and turns the
 *    transition off outright while a finger is dragging it. There is
 *    nothing left for this sheet to add, and a second set of .mo-drawer /
 *    .mo-sheet classes would only be a second owner for one animation.
 *
 *    What IS shared is the duration: --mo-dur-slide in §1 is the same 240ms
 *    the mode indicator and the FLIP use, so the app has one sense of how
 *    long "a thing moved" takes. Any future overlay should reach for it
 *    rather than pick its own.
 * ============================================================== */

/* ============================================================== *
 * 6. LAYOUT REFLOW
 *
 *    FLIP, 240ms, spring. Modules glide; they never teleport and never
 *    jiggle. The implementation is css/layout.css's .tt-mod transition and
 *    src/layout.js's First-Last-Invert-Play; both read --mo-dur-flip and
 *    --mo-ease-flip from §1, so this is where the numbers are argued about
 *    and the only place they need to change.
 *
 *    Nothing to declare here. The comment is the point.
 * ============================================================== */

/* ============================================================== *
 * 7. THE INSTRUMENT'S OWN MOTION — deliberately absent
 *
 *    The dial's scrub detent (90ms micro-impulse per minute crossed), its
 *    approach breathe, its completion settle and its iOS visual-haptic
 *    surrogate all belong to src/dial.js and css/dial.css, which implement
 *    them against per-frame state this sheet cannot see. Re-declaring them
 *    here would produce two owners for one animation, which is how a disc
 *    ends up pulsing twice. They follow the same principle and the same
 *    curves; that is the coordination, not a shared rule.
 * ============================================================== */

/* ============================================================== *
 * 8. NEVER — and where the ban is written
 *
 *    The readout is the composition (PHASE4 §3) and the one thing in the app
 *    that must hold perfectly still: no shimmer, no count-up flicker, no
 *    attention pulse on a number already changing ten times a second.
 *
 *    css/type.css §5 already states `animation: none` on
 *    .tt-timer__readout and .tt-clock__hm, at :root strength, alongside the
 *    rest of the display role. Restating it here would give one ban two
 *    owners. It has one. Never put .mo-pulse on those two elements.
 * ============================================================== */

/* ============================================================== *
 * 9. PREFERENCE OVERRIDES
 *
 *    Every duration above becomes an instant state change. 0.01ms rather
 *    than 0 so transitionend / animationend still fire and no state machine
 *    stalls waiting for an event that never arrives — the same convention
 *    css/base.css uses.
 *
 *    HAPTICS AND AUDIO STILL FIRE. Nothing in this block reaches
 *    src/haptics.js or src/audio.js, and nothing should: reduced MOTION is
 *    not reduced FEEDBACK. The tick on a scrubbed minute, the milestone
 *    chime, the completion fall and the iOS visual impulse are all still
 *    delivered — they simply stop being accompanied by travel.
 * ============================================================== */

@media (prefers-reduced-motion: reduce) {
  :root {
    --mo-dur-press: 0.01ms;
    --mo-dur-release: 0.01ms;
    --mo-dur-settle: 0.01ms;
    --mo-dur-slide: 0.01ms;
    --mo-dur-flip: 0.01ms;
    --mo-dur-pulse: 0.01ms;
    --mo-travel: 0px;
  }

  .mo-pulse,
  .dial.is-running .dial__svg {
    animation: none;
  }
}

@media (forced-colors: active) {
  /* The system owns every surface: the sliding face has no material left to
     carry, so it stops being a face and the engaged key gets its own
     treatment back from the UA. */
  .segmented::before {
    display: none;
  }

  :root .segmented > .segmented__key[aria-selected="true"] {
    background-color: Highlight;
    color: HighlightText;
    forced-color-adjust: none;
  }
}
