/* Frame motion system. Owner: Motion & Fluidity Lead. Spec: /workspace/team/assessments/motion.md.
   Loaded LAST (after common.css). Tokens, choreography, reduced-motion alternative. Original work.
   Rule: movement animates the individual `translate` / `scale` / `rotate` properties and `opacity` only,
   so it composes with layout transforms (e.g. .toast's translateX(-50%)) and stays on the compositor. */

/* ===== 1. Tokens ===== */
:root {
  /* durations */
  --dur-press: 90ms;          /* :active press-in */
  --dur-micro: 120ms;         /* colour/state change, label swap, content fade-in */
  --dur-short: 180ms;         /* view enter, popover, toast, list insert */
  --dur-medium: 240ms;        /* confirmations (like settle), modal enter */
  --dur-long: 280ms;          /* bottom sheet enter (largest travel) */
  --dur-exit-short: 120ms;    /* toast/popover/view exit */
  --dur-exit-medium: 180ms;   /* modal/sheet exit */
  --dur-skel-delay: 150ms;    /* skeleton stays invisible this long, so fast loads never flash */
  --dur-skel-pulse: 1400ms;   /* the only loop in the system; opacity only; 3 cycles (≈4.4s, under the 5s WCAG 2.2.2 line), then static */
  --dur-highlight: 1200ms;    /* your own new post/message: background settles to normal */
  /* easings */
  --ease-standard: cubic-bezier(.2, 0, 0, 1);    /* on-screen change */
  --ease-enter: cubic-bezier(.05, .7, .1, 1);    /* decelerate: things arriving */
  --ease-exit: cubic-bezier(.3, 0, .8, .15);     /* accelerate: things leaving */
  --ease-settle: cubic-bezier(.34, 1.36, .64, 1); /* spring-like, 4.3% overshoot; confirmations only */
  /* distances */
  --dist-1: 2px;  --dist-2: 4px;  --dist-3: 8px;  --dist-4: 12px;  --dist-5: 16px;
  /* scales */
  --scale-press: .96;  --scale-settle-from: .86;  --scale-exit: .98;
  /* stagger: 24ms per item, never more than 5 items (120ms total) */
  --stagger-step: 24ms;  --stagger-max: 120ms;
  /* aliases so existing rules (frame.css:15, minimal.css) read from the same tokens */
  --ease: var(--ease-standard);  --ease-in: var(--ease-enter);  --ease-out: var(--ease-exit);
}

/* ===== 2. Reduced motion: no travel, no scale, short fades; state feedback stays ===== */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dist-1: 0px; --dist-2: 0px; --dist-3: 0px; --dist-4: 0px; --dist-5: 0px;
    --scale-press: 1; --scale-settle-from: 1; --scale-exit: 1; --ease-settle: var(--ease-standard);
    --dur-short: 120ms; --dur-medium: 150ms; --dur-long: 150ms; --dur-exit-short: 100ms; --dur-exit-medium: 100ms;
    --stagger-step: 0ms; --stagger-max: 0ms;
  }
}
html[data-rm] {
  --dist-1: 0px; --dist-2: 0px; --dist-3: 0px; --dist-4: 0px; --dist-5: 0px;
  --scale-press: 1; --scale-settle-from: 1; --scale-exit: 1; --ease-settle: var(--ease-standard);
  --dur-short: 120ms; --dur-medium: 150ms; --dur-long: 150ms; --dur-exit-short: 100ms; --dur-exit-medium: 100ms;
  --stagger-step: 0ms; --stagger-max: 0ms;
}

/* ===== 3. Keyframes (all token-driven; distances collapse to 0 under reduced motion) ===== */
@keyframes mo-fade-in { from { opacity: 0; } }
@keyframes mo-fade-out { to { opacity: 0; } }
@keyframes mo-enter-fwd { from { opacity: 0; translate: var(--dist-4) 0; } }
@keyframes mo-enter-back { from { opacity: 0; translate: calc(-1 * var(--dist-4)) 0; } }
@keyframes mo-rise-in { from { opacity: 0; translate: 0 var(--dist-5); } }
@keyframes mo-sink-out { to { opacity: 0; translate: 0 var(--dist-3); } }
@keyframes mo-drop-in { from { opacity: 0; translate: 0 calc(-1 * var(--dist-3)); } }
@keyframes mo-sheet-in { from { translate: 0 100%; } }
@keyframes mo-sheet-out { to { translate: 0 100%; } }
@keyframes mo-settle { from { scale: var(--scale-settle-from); } }
@keyframes mo-ring { from { opacity: .55; scale: .7; } to { opacity: 0; scale: 1.45; } }
@keyframes mo-turn { from { rotate: -180deg; } }
@keyframes mo-count { from { opacity: 0; translate: 0 var(--dist-2); } }
@keyframes mo-nudge { 40% { translate: var(--dist-2) calc(-1 * var(--dist-2)); } }
@keyframes mo-pulse { 50% { opacity: .55; } }
@keyframes mo-highlight { from { opacity: 1; } to { opacity: 0; } }
@keyframes mo-tap-heart { 0% { opacity: 0; scale: .8; } 25% { opacity: 1; scale: 1; } 70% { opacity: 1; } 100% { opacity: 0; scale: 1; } }

/* ===== 4. View transitions (core.js swapView sets html[data-vt]) ===== */
#main { view-transition-name: main; overflow-x: clip; }           /* clip also fixes the 12px mobile overflow (design-review.md:40) */
::view-transition-old(root), ::view-transition-new(root) { animation: none; } /* rail + sidebar swap in place */
::view-transition-group(main) { animation-duration: var(--dur-short); animation-timing-function: var(--ease-standard); }
::view-transition-old(main) { animation: mo-fade-out var(--dur-exit-short) var(--ease-exit) both; }
::view-transition-new(main) { animation: mo-fade-in var(--dur-short) var(--ease-standard) both; }   /* type "tab": lateral cross-fade */
html[data-vt="forward"]::view-transition-new(main) { animation: mo-enter-fwd var(--dur-short) var(--ease-enter) both; }
html[data-vt="back"]::view-transition-new(main) { animation: mo-enter-back var(--dur-short) var(--ease-enter) both; }
/* With view transitions available, content that replaces a skeleton only fades (the slide already happened). */
@supports (view-transition-name: none) {
  #main > * { animation: mo-fade-in var(--dur-micro) var(--ease-standard) both; }
}

/* ===== 5. Skeletons (motion.js App.skeleton) ===== */
.skel { animation: mo-fade-in var(--dur-short) var(--ease-standard) var(--dur-skel-delay) both; }
.skel-bar, .skel-circ, .skel-tile, .skel-slide { display: block; background: var(--surface-1); border-radius: 6px; animation: mo-pulse var(--dur-skel-pulse) ease-in-out var(--dur-skel-delay) 3; }
.skel-circ { border-radius: 50%; flex: none; }
.skel-tile { aspect-ratio: 1; border-radius: var(--r-thumb, 6px); }
.skel-row { display: flex; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--divider, var(--border)); }
.skel-col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; padding-top: 2px; }
.skel-acts { display: flex; justify-content: space-between; max-width: 420px; padding: 6px 0 4px; }
.skel-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px; }
.skel-prof { padding: 16px; display: flex; flex-direction: column; gap: 10px; border-bottom: 1px solid var(--divider, var(--border)); }
.skel-watch { position: relative; height: calc(100dvh - 120px); min-height: 360px; background: #000; border-radius: var(--r-media, 12px); overflow: hidden; }
.skel-watch .skel-slide { position: absolute; inset: 0; background: #141418; border-radius: 0; }
.skel-watch .skel-rail { position: absolute; right: 12px; bottom: 96px; display: flex; flex-direction: column; gap: 18px; }
.skel-watch .skel-cap { position: absolute; left: 16px; right: 80px; bottom: 24px; display: flex; flex-direction: column; gap: 8px; }
.skel-watch .skel-bar, .skel-watch .skel-circ { background: rgba(255,255,255,.12); }
.skel-bubble { height: 36px; border-radius: 18px; }
.skel-bubble.mine { align-self: flex-end; }
@media (prefers-reduced-motion: reduce) { .skel-bar, .skel-circ, .skel-tile, .skel-slide { animation: none; } }
html[data-rm] .skel-bar, html[data-rm] .skel-circ, html[data-rm] .skel-tile, html[data-rm] .skel-slide { animation: none; }

/* Flat style (common.css, DESIGN.md §C): confirmations use the state colour (text), not purple; purple stays on the
   primary action, active tab and links. No drop shadows on in-content controls; skeleton radii follow --r-* tokens. */

/* ===== 6. Press, hover and focus ===== */
.btn, .icon-btn, .act .ic, .post-btn, .chip, .tab, .gp-tile, .follow-btn { transition-duration: var(--dur-micro); transition-timing-function: var(--ease-standard); }
.act .ic { transition: scale var(--dur-press) var(--ease-standard), color var(--dur-micro) var(--ease-standard); }
.act:active .ic { transform: none; scale: .88; }                 /* replaces frame.css:221 transform with the composable property */
.btn:active:not([disabled]), .post-btn:active, .icon-btn:active { transform: none; scale: var(--scale-press); }
.gp-tile:active { scale: var(--scale-press); transition: scale var(--dur-press) var(--ease-standard); }
:focus-visible { transition: outline-offset var(--dur-micro) var(--ease-standard); }

/* ===== 7. Like / repost / bookmark / GIF reaction: confirmations for the user's own action ===== */
.act.like.on .ic, .act.like.pop svg { animation: none; }         /* stop frame.css:222 replaying on every render + styles.css:170 */
.act .bubble { position: relative; }
.act.like.pop .ic, .act.bm.pop .ic, .act.gr.pop .ic { animation: mo-settle var(--dur-medium) var(--ease-settle) both; }
.act.like.pop .bubble::after { content: ""; position: absolute; inset: 5px; border-radius: 50%; border: 1.5px solid currentColor; opacity: 0; pointer-events: none; animation: mo-ring var(--dur-long) var(--ease-enter) both; }
.act.rt.pop .ic { animation: mo-turn var(--dur-medium) var(--ease-standard) both; }
.post-actions .act .n { display: inline-block; }
.act.pop .n { animation: mo-count var(--dur-short) var(--ease-enter) both; }
.gif-strip.mo-new .gs-stack { animation: mo-settle var(--dur-medium) var(--ease-settle) both; }
html[data-rm] .act.like.pop .bubble::after { display: none; }
@media (prefers-reduced-motion: reduce) { .act.like.pop .bubble::after { display: none; } }

/* ===== 8. Follow: label swap without reflow ===== */
.follow-btn, .follow-btn.following { display: inline-block; box-sizing: border-box; width: 112px; min-width: 112px; max-width: 112px; padding-inline: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; line-height: 36px; vertical-align: middle; }
.follow-btn.btn-sm { line-height: 32px; }          /* fixed width: Follow / Following / Requested / Follow back all fit, so the label swap never reflows */
.follow-btn.mo-swap > *, .follow-btn.mo-swap:not(:has(> *)) { animation: mo-fade-in var(--dur-micro) var(--ease-standard) both; }

/* ===== 9. Messages: optimistic bubble, send nudge ===== */
.bubble-row.is-pending { opacity: .6; animation: mo-rise-in var(--dur-short) var(--ease-enter) both; }
.bubble-row.is-failed .msg { opacity: .6; }
.bubble-row.is-failed .msg-time { color: var(--danger, #E5484D); }
.dm-compose .icon-btn.mo-sent .ic { animation: mo-nudge var(--dur-medium) var(--ease-standard) both; }

/* ===== 10. List insert / remove ===== */
.mo-insert { position: relative; isolation: isolate; animation: mo-drop-in var(--dur-short) var(--ease-enter) both; }
.mo-insert::before { content: ""; position: absolute; inset: 0; background: var(--surface-1); pointer-events: none; animation: mo-highlight var(--dur-highlight) var(--ease-standard) both; z-index: -1; }
.mo-remove { pointer-events: none; animation: mo-fade-out var(--dur-exit-short) var(--ease-exit) both; }
.new-posts-pill:not([hidden]) button { animation: mo-drop-in var(--dur-short) var(--ease-enter) both; }

/* ===== 11. Modals, sheets, toasts ===== */
.modal-back { animation: mo-fade-in var(--dur-short) var(--ease-standard) both; }
.modal { animation: mo-rise-in var(--dur-medium) var(--ease-enter) both; }
.modal-back.is-closing { pointer-events: none; animation: mo-fade-out var(--dur-exit-medium) var(--ease-exit) both; }
.modal-back.is-closing .modal { animation: mo-sink-out var(--dur-exit-medium) var(--ease-exit) both; }
@media (max-width: 767px) {
  .modal { animation: mo-sheet-in var(--dur-long) var(--ease-enter) both; }
  .modal-back.is-closing .modal { animation: mo-sheet-out var(--dur-exit-medium) var(--ease-exit) both; }
}
.toast { animation: mo-rise-in var(--dur-short) var(--ease-enter) both; }   /* minimal.css:47 mn-up ended at transform:none and dropped translateX(-50%) */
.toast.is-closing { pointer-events: none; animation: mo-sink-out var(--dur-exit-short) var(--ease-exit) both; }

/* ===== 12. Watch, stories, live ===== */
.v-heart { --rot: 0deg; animation: mo-tap-heart 600ms var(--ease-standard) forwards; }   /* replaces styles.css:451-453 */
.sv-bar i { width: 100% !important; transform-origin: left center; transform: scaleX(0); }
[dir="rtl"] .sv-bar i { transform-origin: right center; }
.live-dot.on { animation: none; box-shadow: none; position: relative; }
.live-dot.on::after { content: ""; position: absolute; inset: 0; border-radius: inherit; background: var(--accent); animation: mo-ring 1600ms var(--ease-standard) 3; pointer-events: none; }

/* ===== 13. Pull to refresh (Home, touch only; motion.js App.motion.pullToRefresh) ===== */
html[data-page="home"] { overscroll-behavior-y: contain; }
.ptr { position: sticky; top: 0; height: 0; z-index: 8; display: flex; justify-content: center; pointer-events: none; }
.ptr-ind { width: 32px; height: 32px; margin-top: -40px; border-radius: 50%; background: var(--surface-1); color: var(--muted); display: grid; place-items: center; border: 1px solid var(--border); box-shadow: none; opacity: 0; }
.ptr-ind .ic svg { width: 18px; height: 18px; }
.ptr.is-armed .ptr-ind { color: var(--text); }
.ptr.is-loading .ptr-ind .ic { animation: spin 800ms linear infinite; }
.ptr.is-settling .ptr-ind { transition: translate var(--dur-short) var(--ease-standard), opacity var(--dur-short) var(--ease-standard); }

/* ===== 14. Reduced motion: re-enable short fades the global kill switches remove ===== */
/* styles.css:418 (html[data-rm] * { animation: none !important }) and frame.css:41 (.01ms !important) stay as a
   safety net for legacy rules. These selectors are more specific, so the reduced alternative is a real fade, not a cut. */
@media (prefers-reduced-motion: reduce) {
  html .modal-back, html .modal, html .toast, html .skel, html .bubble-row.is-pending, html .mo-insert {
    animation-name: mo-fade-in !important; animation-duration: var(--dur-micro) !important; animation-timing-function: linear !important; animation-delay: 0s !important; }
  html .skel { animation-delay: var(--dur-skel-delay) !important; }
  html .modal-back.is-closing, html .modal-back.is-closing .modal, html .toast.is-closing, html .mo-remove {
    animation-name: mo-fade-out !important; animation-duration: var(--dur-exit-short) !important; }
  ::view-transition-group(main) { animation-duration: 0s; }
  ::view-transition-old(main), html[data-vt]::view-transition-old(main) { animation: mo-fade-out 100ms linear both; }
  ::view-transition-new(main), html[data-vt]::view-transition-new(main) { animation: mo-fade-in 120ms linear both; }
}
html[data-rm] .modal-back, html[data-rm] .modal, html[data-rm] .toast, html[data-rm] .skel, html[data-rm] .bubble-row.is-pending, html[data-rm] .mo-insert {
  animation: mo-fade-in var(--dur-micro) linear both !important; }
html[data-rm] .skel { animation-delay: var(--dur-skel-delay) !important; }
html[data-rm] .modal-back.is-closing, html[data-rm] .modal-back.is-closing .modal, html[data-rm] .toast.is-closing, html[data-rm] .mo-remove {
  animation: mo-fade-out var(--dur-exit-short) linear both !important; }
html[data-rm] .mo-insert::before { animation: none !important; opacity: 0; }
html[data-rm]::view-transition-group(main) { animation-duration: 0s; }
html[data-rm]::view-transition-old(main) { animation: mo-fade-out 100ms linear both; }
html[data-rm]::view-transition-new(main) { animation: mo-fade-in 120ms linear both; }

/* ===== 15. Touch: no system tap flash; the :active press states give feedback ===== */
html { -webkit-tap-highlight-color: transparent; }
.ptr.is-armed .ptr-ind { background: var(--surface-2); }
