/* Tools Nearby — Dynamic Platform motion tokens (Phase 1, Oct 21, 2026).
   One semantic motion system, web carrier. Spec: Dynamic Platform design
   memo §3. Values MUST stay identical to the mobile carrier
   (mobile/src/theme.ts `motion`) — a token-equality test guards this.

   This file is the staged carrier while css/app.css is owned by another
   build; the :root block below is the exact block the parent merges into
   app.css at sequencing time (see the Phase 1 journal). Loaded after
   app.css so token-driven overrides win on equal specificity.

   Rails: transform/opacity only; animation never gates an action;
   prefers-reduced-motion collapses every duration to 0ms (the .tn-motion-off
   kill switch does the same without editing components). */

:root {
  --tn-motion-reduced: 0ms;
  --tn-motion-press: 120ms;
  --tn-motion-fast: 180ms;
  --tn-motion-base: 260ms;
  --tn-motion-panel: 360ms;
  --tn-motion-number: 450ms;
  --tn-motion-meter: 600ms;
  --tn-motion-ambient: 1100ms;
  --tn-ease-standard: cubic-bezier(.2, .85, .25, 1);
  --tn-ease-out: cubic-bezier(.16, 1, .3, 1);
  --tn-ease-cross: cubic-bezier(.4, 0, .2, 1);
  --tn-rise-micro: 4px;
  --tn-rise-card: 8px;
  --tn-rise-view: 10px;
  --tn-rise-step: 14px;
  --tn-rise-panel: 24px;
  --tn-scale-press-button: 0.97;
  --tn-scale-press-card: 0.985;
  --tn-stagger-step: 24ms;
}

/* Emergency brake / reduced path: every token duration becomes instant. */
.tn-motion-off {
  --tn-motion-press: 0ms;
  --tn-motion-fast: 0ms;
  --tn-motion-base: 0ms;
  --tn-motion-panel: 0ms;
  --tn-motion-number: 0ms;
  --tn-motion-meter: 0ms;
  --tn-motion-ambient: 0ms;
}

/* R1 — View reveal: normalize the shell's drifted 220ms onto motion.base.
   #view.view-enter + @keyframes view-in live in app.css; only the timing
   is retokenized here (transform/opacity keyframes unchanged). */
#view.view-enter { animation-duration: var(--tn-motion-base); animation-timing-function: var(--tn-ease-out); }

/* R2 — List/card reveal: applied by Motion.revealGroup() to first-mount
   groups only. Items beyond the stagger cap (6) are simply present. */
.tn-reveal-item {
  animation: tn-reveal-in var(--tn-motion-base) var(--tn-ease-out) both;
  animation-delay: calc(var(--tn-i, 0) * var(--tn-stagger-step));
}
@keyframes tn-reveal-in {
  from { opacity: 0; transform: translateY(var(--tn-rise-card)); }
  to { opacity: 1; transform: translateY(0); }
}

/* R4 — State morph (same-frame swaps). */
.tn-morph { animation: tn-morph-in var(--tn-motion-fast) var(--tn-ease-cross) both; }
@keyframes tn-morph-in { from { opacity: 0; } to { opacity: 1; } }

/* Press feedback (transform/opacity only; the action fires on the tap,
   never after the animation). */
.tn-press { transition: transform var(--tn-motion-press) var(--tn-ease-standard), opacity var(--tn-motion-press) var(--tn-ease-standard); }
.tn-press:active { transform: scale(var(--tn-scale-press-card)); }

/* Number rule: tabular figures always on tweened values. */
.tn-number { font-variant-numeric: tabular-nums; }

/* Skeleton rows (S1 layout skeleton for lists — notifications / inbox):
   geometry matches .notif-card rows: dot + title line + meta line. */
.tn-skel-row {
  display: flex; gap: 10px; align-items: flex-start;
  background: var(--mist-surface, #fff); border: 1px solid var(--mist-hairline, #E8E4DC);
  border-radius: var(--radius-md, 16px); padding: 16px; margin-bottom: 16px;
}

/* Card-skeleton geometry alignment (S1): the pre-existing .skel-card
   primitive (app.css) used a 150px inset image; final .tool-card cards
   run a 180px flush image with inset text. Retokenized here — app.css
   itself is merged by the parent at sequencing time. */
.skel-card { padding: 0 0 21px; overflow: hidden; }
.skel-card .skel-img { height: 180px; border-radius: 0; margin-bottom: 12px; }
.skel-card .skel-line { margin-left: 12px; margin-right: 12px; }
.tn-skel-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; margin-top: 4px; }
.tn-skel-lines { flex: 1; }

/* Static quiet skeleton (S3): same geometry, no shimmer. Motion.reduced()
   adds this class; the media query below does the same. */
.tn-skel-static .skel, .tn-skel-static .tn-skel-dot { animation: none !important; }

/* Capacity Watch meter (admin-advanced.js capwMeter): fill now runs on
   transform scaleY (bottom origin) over motion.meter instead of height.
   The label stays authoritative and updates immediately (memo §3.6). */
.capw-fill {
  transform-origin: bottom;
  transition: transform var(--tn-motion-meter) var(--tn-ease-standard);
}

/* Badge settle: new counts land with a press-speed opacity/scale settle. */
.notif-badge { transition: transform var(--tn-motion-press) var(--tn-ease-standard), opacity var(--tn-motion-press) var(--tn-ease-standard); }

/* Phase 2A-4 micro-interaction completion (memo §3.7), tokenized here
   so app.css stays untouched: reaction buttons get press feedback at
   motion.press (the tap's work still fires on the tap, never after);
   toggle knobs settle at motion.fast instead of the legacy 200ms;
   tab/chip state changes crossfade at motion.fast. */
.reaction-btn { transition: transform var(--tn-motion-press) var(--tn-ease-standard), background-color var(--tn-motion-fast) var(--tn-ease-standard), border-color var(--tn-motion-fast) var(--tn-ease-standard), color var(--tn-motion-fast) var(--tn-ease-standard); }
.reaction-btn:active { transform: scale(var(--tn-scale-press-button)); }
.switch .knob { transition: background-color var(--tn-motion-fast) var(--tn-ease-standard); }
.switch .knob::before { transition: transform var(--tn-motion-fast) var(--tn-ease-standard); }
.msgx-tab, .pilot-pill, .chip { transition: background-color var(--tn-motion-fast) var(--tn-ease-cross), color var(--tn-motion-fast) var(--tn-ease-cross), border-color var(--tn-motion-fast) var(--tn-ease-cross); }

/* LiveSurface freshness line (L4): stale surfaces say so quietly. */
.tn-stale { color: #b3261e !important; }

/* Pop-up sweep (Donald's Oct 21, 2026 rule — "All pop up boxes ease in
   and out"): the shared modal path (js/ui.js UI.modal/closeModal) eases
   IN and OUT on the panel token — scrim fades, card fades + gentle
   rise/scale; sheets rise from the bottom edge; exit is the reverse on
   ease.cross. The close registers immediately in JS; these animations
   are visual only and the node removes at animation end. Reduced
   motion zeroes the token durations above, collapsing both ways to
   instant (ui.js also short-circuits removal in that state). */
@keyframes tn-pop-scrim-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes tn-pop-scrim-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes tn-pop-card-in {
  from { opacity: 0; transform: translateY(var(--tn-rise-panel)) scale(.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes tn-pop-card-out {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to { opacity: 0; transform: translateY(var(--tn-rise-panel)) scale(.98); }
}
@keyframes tn-pop-sheet-in {
  from { opacity: .6; transform: translateY(48px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes tn-pop-sheet-out {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: .6; transform: translateY(48px); }
}
/* Enter (keyframe names are shared with the call-overlay utilities). */
#modal-root .modal-backdrop { animation: tn-pop-scrim-in var(--tn-motion-panel) var(--tn-ease-out) both; }
#modal-root .modal-backdrop .modal { animation: tn-pop-card-in var(--tn-motion-panel) var(--tn-ease-out) both; }
#modal-root .modal-backdrop.sheet .modal { animation-name: tn-pop-sheet-in; }
@media (max-width: 640px) {
  #modal-root .modal-backdrop .modal { animation-name: tn-pop-sheet-in; }
}
/* Exit: closeModal() adds .tn-pop-out; the node removes at animation end. */
#modal-root .modal-backdrop.tn-pop-out { animation: tn-pop-scrim-out var(--tn-motion-panel) var(--tn-ease-cross) both; }
#modal-root .modal-backdrop.tn-pop-out .modal {
  animation-name: tn-pop-card-out;
  animation-duration: var(--tn-motion-panel);
  animation-timing-function: var(--tn-ease-cross);
  animation-fill-mode: both;
}
#modal-root .modal-backdrop.tn-pop-out.sheet .modal { animation-name: tn-pop-sheet-out; }
@media (max-width: 640px) {
  #modal-root .modal-backdrop.tn-pop-out .modal { animation-name: tn-pop-sheet-out; }
}
/* Modal-replaces-modal (e.g. lightbox photo nav): the scrim stays
   settled — no re-fade — and the card crossfades at the fast token. */
#modal-root .modal-backdrop.tn-pop-swap { animation: none; }
#modal-root .modal-backdrop.tn-pop-swap .modal { animation: tn-morph-in var(--tn-motion-fast) var(--tn-ease-cross) both; }

/* Call overlays (js/app.js) build their own .modal-backdrop markup
   outside #modal-root; the same keyframes apply via these utilities.
   .tn-pop-settled marks a same-state re-render (per-second call ticks)
   so the enter animation never replays. */
.tn-pop-in-bd { animation: tn-pop-scrim-in var(--tn-motion-panel) var(--tn-ease-out) both; }
.tn-pop-in-card { animation: tn-pop-card-in var(--tn-motion-panel) var(--tn-ease-out) both; }
.tn-pop-out-bd { animation: tn-pop-scrim-out var(--tn-motion-panel) var(--tn-ease-cross) both; }
.tn-pop-out-card { animation: tn-pop-card-out var(--tn-motion-panel) var(--tn-ease-cross) both; }
.tn-pop-settled { animation: none !important; }

/* Helper chat panel: enter retokenized from the legacy chat-panel-in
   (220ms) onto the panel token; chatbot.js adds .tn-chat-out to close. */
#chat-panel { animation: tn-pop-card-in var(--tn-motion-panel) var(--tn-ease-out) both; }
#chat-panel.tn-chat-out { animation: tn-pop-card-out var(--tn-motion-panel) var(--tn-ease-cross) both; }

/* Toast: enter on the fast token; the opacity transition makes
   UI.toast's existing JS fade-out real (180ms fade < 300ms removal). */
.toast { animation: tn-toast-in var(--tn-motion-fast) var(--tn-ease-out) both; transition: opacity var(--tn-motion-fast) var(--tn-ease-cross); }
@keyframes tn-toast-in {
  from { opacity: 0; transform: translateY(var(--tn-rise-micro)); }
  to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --tn-motion-press: 0ms;
    --tn-motion-fast: 0ms;
    --tn-motion-base: 0ms;
    --tn-motion-panel: 0ms;
    --tn-motion-number: 0ms;
    --tn-motion-meter: 0ms;
    --tn-motion-ambient: 0ms;
  }
  .tn-reveal-item, .tn-morph { animation: none !important; }
  .skel { animation: none !important; }
  .capw-fill { transition: none !important; }
}
