/* Motion — the one place that says how long and how things move. Read docs/reference/motion.md before adding an animation.
   Rule of thumb: an animation explains a *change of state* (something appears, finishes, changed, was saved).
   It never decorates, never replays on a plain repaint, never blocks the next action, and is off when the person
   asked for reduced motion. Durations/curves come from the tokens below — no one-off numbers in components.
   Loaded before every other stylesheet so its tokens are available everywhere; static/js/motion.js mirrors the
   durations (MO.ms) and tests/test_motion_sheet.py keeps the two in step. */
:root{
  /* How long */
  --mo-fast:150ms;      /* hover, press, small toggles */
  --mo-base:240ms;      /* a state changing in place (chip colour, check drawn, number settles) */
  --mo-slow:380ms;      /* something entering or leaving (a pill, a row, a card) */
  --mo-long:620ms;      /* one expressive gesture, used sparingly (the bell's swing) */
  --mo-flash:1400ms;    /* "this just changed" highlight fading out */
  --mo-loop:2800ms;     /* a quiet repeating cycle (the connected ring) */
  /* How it moves */
  --mo-out:cubic-bezier(.22,1,.36,1);     /* entering: fast start, soft landing */
  --mo-in:cubic-bezier(.4,0,1,1);         /* leaving: slow start, quick exit */
  --mo-io:cubic-bezier(.4,0,.2,1);        /* changing in place */
}
@media (prefers-reduced-motion:reduce){
  :root{--mo-fast:1ms;--mo-base:1ms;--mo-slow:1ms;--mo-long:1ms;--mo-flash:1ms;--mo-loop:1ms}
}

/* ── Building blocks (add the class, or call the helper in motion.js) ─────────────────────────────── */

/* Something appeared: fade up a few pixels. */
.mo-enter{animation:mo-enter var(--mo-slow) var(--mo-out) both}
@keyframes mo-enter{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}

/* Something is leaving: moExit() adds this, then collapses the height and removes the node. */
.mo-exit{animation:mo-exit var(--mo-base) var(--mo-in) both;pointer-events:none}
@keyframes mo-exit{from{opacity:1;transform:none}to{opacity:0;transform:translateX(14px)}}

/* A small pop for a number or badge that just changed. */
.mo-pop{animation:mo-pop var(--mo-base) var(--mo-out)}
@keyframes mo-pop{0%{transform:scale(.82)}60%{transform:scale(1.08)}100%{transform:scale(1)}}

/* "This just changed": a soft accent wash that fades out. Rows and cards use it when a refresh brought something new. */
.mo-flash{animation:mo-flash var(--mo-flash) var(--mo-io) both}
@keyframes mo-flash{0%{background-color:color-mix(in srgb,var(--accent) 22%,transparent)}100%{background-color:transparent}}

/* A tick that draws itself: put .mo-check on the <svg>, give the path pathLength="1". */
.mo-check path{stroke-dasharray:1;stroke-dashoffset:1;animation:mo-draw var(--mo-base) var(--mo-out) both}
@keyframes mo-draw{to{stroke-dashoffset:0}}

/* A new notification: the bell swings once (the badge pops via .mo-pop). Quieter than the status bell's swing. */
.mo-bell-ring{transform-origin:50% 15%;animation:mo-bell var(--mo-long) var(--mo-out)}
@keyframes mo-bell{0%{transform:rotate(0)}18%{transform:rotate(-14deg)}38%{transform:rotate(11deg)}58%{transform:rotate(-6deg)}78%{transform:rotate(3deg)}100%{transform:rotate(0)}}

/* A new chat message: the speech bubble tips once (pivot at its tail). Same weight as the bell's swing, never a loop. */
.mo-nudge{transform-origin:25% 85%;animation:mo-nudge var(--mo-long) var(--mo-out)}
@keyframes mo-nudge{0%{transform:rotate(0)}20%{transform:rotate(-13deg)}42%{transform:rotate(9deg)}62%{transform:rotate(-5deg)}82%{transform:rotate(2deg)}100%{transform:rotate(0)}}

/* Skeletons replace "Loading…": the shape of what is coming, with a slow sheen. */
.mo-skel{display:block;border-radius:var(--r-sm);background:linear-gradient(90deg,var(--bg3) 0%,var(--bg4) 45%,var(--bg3) 90%);background-size:220% 100%;animation:mo-sheen 1.6s linear infinite}
.mo-skel-line{height:12px;margin:8px 0}
.mo-skel-line.s{width:38%}.mo-skel-line.m{width:64%}.mo-skel-line.l{width:92%}
.mo-skel-block{height:64px}
@keyframes mo-sheen{from{background-position:120% 0}to{background-position:-120% 0}}

/* A change shown next to the number it changed ("−2 mg") that drifts up and fades. */
.mo-delta{position:absolute;pointer-events:none;font-size:var(--ui-caption);font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap;animation:mo-delta 1.1s var(--mo-out) both}
.mo-delta.neg{color:var(--red)}.mo-delta.pos{color:var(--green)}
@keyframes mo-delta{0%{opacity:0;transform:translateY(4px)}18%{opacity:1}100%{opacity:0;transform:translateY(-16px)}}

/* Height that opens and closes without measuring: a grid whose one row goes 0fr ↔ 1fr. Wrap the content in ONE child.
   moGrow(el, open) toggles it. */
.mo-grow{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--mo-base) var(--mo-io)}
.mo-grow.mo-open{grid-template-rows:1fr}
.mo-grow>*{min-height:0;overflow:hidden}

/* A line that draws itself from the left (put .mo-draw-x on the element whose ::before is the line). */
.mo-draw-x::before{transform-origin:left center;animation:mo-draw-x var(--mo-slow) var(--mo-out) both}
@keyframes mo-draw-x{from{transform:scaleX(0)}to{transform:none}}

/* A side panel arriving from, or leaving to, the right. */
.mo-slide-in{animation:mo-slide-in var(--mo-slow) var(--mo-out) both}
@keyframes mo-slide-in{from{opacity:0;transform:translateX(24px)}to{opacity:1;transform:none}}
.mo-slide-out{animation:mo-slide-out var(--mo-base) var(--mo-in) both;pointer-events:none}
@keyframes mo-slide-out{from{opacity:1;transform:none}to{opacity:0;transform:translateX(24px)}}

/* A number that changed rolls in from below. */
.mo-roll{display:inline-block;animation:mo-roll var(--mo-base) var(--mo-out)}
@keyframes mo-roll{from{opacity:0;transform:translateY(60%)}to{opacity:1;transform:none}}

/* Something opens from a point (a lightbox picture, a menu): a short scale-up. */
.mo-zoom{animation:mo-zoom var(--mo-base) var(--mo-out) both}
@keyframes mo-zoom{from{opacity:0;transform:scale(.94)}to{opacity:1;transform:none}}

/* ── Time state icon (Home clock line): zzz while out, clock → hourglass while working, cup while on break ──
   The layers cross-fade on a state change; loops are quiet (--mo-loop). The fast clock is a single gesture (--mo-flash)
   started by time-home.js when the state *changes to working*, never by a repaint. */
.th-ico{position:relative;width:18px;height:18px;flex:0 0 auto;color:var(--text3)}
.th-ico svg{position:absolute;inset:0;width:18px;height:18px;opacity:0;transition:opacity var(--mo-slow) var(--mo-io);overflow:visible}
.th-ico svg *{animation-play-state:paused}
.home-time[data-state="out"] .th-zzz,.home-time[data-state="break"] .th-cup,.home-time[data-state="work"] .th-glass{opacity:1}
.home-time[data-state="work"].th-spin .th-glass{opacity:0}
.home-time[data-state="work"].th-spin .th-clock{opacity:1}
.home-time[data-state="out"] .th-zzz *,.home-time[data-state="break"] .th-cup *,.home-time[data-state="work"] .th-glass *,
.home-time.th-spin .th-clock *{animation-play-state:running}
.home-time[data-state="work"] .th-ico{color:var(--green)}
.home-time[data-state="break"] .th-ico{color:var(--orange)}
.th-ico .th-s{fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.th-ico .th-f{fill:currentColor;stroke:none}
/* zzz */
.th-z{font-family:var(--font,system-ui);font-weight:700;fill:currentColor;opacity:0;animation:th-z var(--mo-loop) var(--mo-io) infinite}
.th-z:nth-child(2){animation-delay:calc(var(--mo-loop)/3)}.th-z:nth-child(3){animation-delay:calc(var(--mo-loop)*2/3)}
@keyframes th-z{0%{opacity:0;transform:translate(0,3px)}30%{opacity:1}100%{opacity:0;transform:translate(2px,-4px)}}
/* fast clock (one gesture) */
.th-hand-m,.th-hand-h{transform-origin:12px 12px;animation:th-spin var(--mo-long) linear infinite}
.th-hand-h{animation-duration:calc(var(--mo-long)*6)}
@keyframes th-spin{to{transform:rotate(360deg)}}
/* hourglass: the sand runs down, then the glass turns over */
.th-glass-body{transform-origin:12px 12px;animation:th-flip calc(var(--mo-loop)*2) var(--mo-io) infinite}
.th-sand-top{transform-origin:12px 10px;animation:th-top calc(var(--mo-loop)*2) linear infinite}
.th-sand-bot{transform-origin:12px 19px;animation:th-bot calc(var(--mo-loop)*2) linear infinite}
.th-grain{stroke-dasharray:1.5 2.5;animation:th-grain var(--mo-base) linear infinite}
@keyframes th-flip{0%,86%{transform:rotate(0)}100%{transform:rotate(180deg)}}
@keyframes th-top{0%{transform:scaleY(1)}86%,100%{transform:scaleY(.12)}}
@keyframes th-bot{0%{transform:scaleY(.12)}86%,100%{transform:scaleY(1)}}
@keyframes th-grain{to{stroke-dashoffset:-4}}
/* coffee: steam rises */
.th-steam{opacity:0;animation:th-steam var(--mo-loop) var(--mo-io) infinite}
.th-steam:nth-of-type(2){animation-delay:calc(var(--mo-loop)/3)}.th-steam:nth-of-type(3){animation-delay:calc(var(--mo-loop)*2/3)}
@keyframes th-steam{0%{opacity:0;transform:translateY(2px)}35%{opacity:.85}100%{opacity:0;transform:translateY(-4px)}}

@keyframes th-remind{0%,100%{opacity:1}50%{opacity:.3}}

/* Reduced motion: nothing moves; what a block expresses still shows (a skeleton stays a flat placeholder). */
@media (prefers-reduced-motion:reduce){
  .th-z,.th-hand-m,.th-hand-h,.th-glass-body,.th-sand-top,.th-sand-bot,.th-grain,.th-steam{animation:none}
  .th-z{opacity:.7}.th-steam{opacity:.7}.th-sand-top{transform:scaleY(.6)}.th-sand-bot{transform:scaleY(.5)}
  .th-ico svg{transition:none}
  .nav-timer.is-remind::before{animation:none}
  .mo-enter,.mo-exit,.mo-pop,.mo-flash,.mo-delta,.mo-bell-ring,.mo-nudge,.mo-draw-x::before,.mo-slide-in,.mo-slide-out,.mo-roll,.mo-zoom{animation:none}
  .mo-grow{transition:none}
  .mo-check path{animation:none;stroke-dashoffset:0}
  .mo-skel{animation:none;background:var(--bg3)}
}
