/* ═══════════════════════════════════════════════════════════════════
   VOLTEX — motion
   ═══════════════════════════════════════════════════════════════════

   Dials: MOTION 4. Not cinematic. A driver glances at a mounted phone for
   about a second, in traffic, possibly at a light. Every animation here
   has to earn its place or it is a distraction.

   THE FOUR RULES
   1. Movement is TRANSFORM AND OPACITY ONLY. Animating width/height/top
      forces layout on every frame and stutters on a mid-range phone.
   2. Enter fast, leave slower. Things that appear must not make a driver
      wait; things that disappear should not vanish abruptly either.
   3. Nothing loops. A looping animation in peripheral vision is a nag.
      The only continuous motion is the score shimmer, at 1.6s, and it
      only runs while a value is actually changing.
   4. It is a preference, not a courtesy. prefers-reduced-motion is
      honoured for EVERY animation here, including the ones that look
      decorative, because vestibular triggers are not a preference.

   STAGGER: a list of 16 zones appearing at once reads as a wall. They
   cascade in over ~400ms total, so the driver's eye is drawn to the top
   of the list — which is the highest-scoring zone, which is the one
   they should look at first. The stagger is functional, not decorative.
   ═══════════════════════════════════════════════════════════════════ */

/* ── Entrances ───────────────────────────────────────────────────── */

/* Tabs: a short rise. Directionless, because a driver arrives from
   anywhere and direction would imply a relationship that does not exist. */
@keyframes voltexTabIn {
  from { opacity: 0; transform: translate3d(0, 8px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

/* Zone rows: same motion as the tab, but the container sets a per-row
   animation-delay so the list cascades. */
@keyframes voltexZoneIn {
  from { opacity: 0; transform: translate3d(0, 10px, 0) scale(0.985); }
  to   { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }
}

/* Bottom sheet: rises from the bottom edge it is anchored to. */
@keyframes voltexSheetUp {
  from { transform: translate3d(0, 100%, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

/* Overlay scrim: opacity only. */
@keyframes voltexScrimIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Toast: enters from the top, exits upward. It sits near the header so a
   driver's eye is already there. */
@keyframes voltexToastIn {
  from { opacity: 0; transform: translate3d(0, -10px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

/* A value that just changed: a brief lift, no colour flash. A colour
   flash is the classic dashboard cliché and reads as an alarm. */
@keyframes voltexValueBump {
  0%   { transform: translate3d(0, 0, 0); }
  35%  { transform: translate3d(0, -2px, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* The only looping motion in the app, and it is 1.6s. Applied to a score
   that is actively updating, so it reads as "live" rather than
   "decorative". */
@keyframes voltexLivePulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

@keyframes voltexSpin {
  to { transform: rotate(360deg); }
}

/* ── Wiring ──────────────────────────────────────────────────────── */

.tab-content.active {
  animation: voltexTabIn var(--slow) both;
}

/* The cascade. 16 zones at 22ms apart finishes in ~350ms — long enough
   to read as a sequence, short enough not to delay anyone. Capped at 12
   rows so a slow device is not still animating a second later. */
.zone-item {
  animation: voltexZoneIn var(--base) both;
}
.zone-item:nth-child(1)  { animation-delay: 0ms; }
.zone-item:nth-child(2)  { animation-delay: 22ms; }
.zone-item:nth-child(3)  { animation-delay: 44ms; }
.zone-item:nth-child(4)  { animation-delay: 66ms; }
.zone-item:nth-child(5)  { animation-delay: 88ms; }
.zone-item:nth-child(6)  { animation-delay: 110ms; }
.zone-item:nth-child(7)  { animation-delay: 132ms; }
.zone-item:nth-child(8)  { animation-delay: 154ms; }
.zone-item:nth-child(9)  { animation-delay: 176ms; }
.zone-item:nth-child(10) { animation-delay: 198ms; }
.zone-item:nth-child(11) { animation-delay: 220ms; }
.zone-item:nth-child(12) { animation-delay: 242ms; }

.detail-overlay.open .overlay-scrim { animation: voltexScrimIn var(--base) both; }
.detail-overlay.open .detail-sheet  { animation: voltexSheetUp var(--slow) both; }

.toast {
  animation: voltexToastIn var(--base) both;
}
.toast.out {
  animation: voltexToastIn var(--fast) reverse both;
}

/* A score that has just ticked. Applied by JS for one cycle, then
   removed — so it never becomes ambient motion. */
.score-bump { animation: voltexValueBump 380ms cubic-bezier(0.2, 0, 0.2, 1); }

/* The freshness dot. Present ONLY while data is being fetched. */
.is-syncing .sync-dot { animation: voltexLivePulse 1.6s ease-in-out infinite; }

.spinning { animation: voltexSpin 620ms linear; }

/* ── Interactive feedback ────────────────────────────────────────── */
/* Tactile, not decorative: every pressable surface acknowledges the
   touch in under 100ms. 0.975 reads as a physical push without the
   element visibly shrinking. */
button,
.nav-item,
.zone-item,
.fb-btn,
.traffic-row,
.refresh-btn {
  transition:
    transform var(--fast),
    background-color var(--fast),
    border-color var(--fast),
    color var(--fast),
    box-shadow var(--fast),
    opacity var(--fast);
  will-change: transform;
}

:is(button, .nav-item, .zone-item, .fb-btn, .traffic-row, .refresh-btn):active {
  transform: var(--press);
}

/* Hover only where a pointer actually exists — on a phone a hover style
   sticks after a tap and leaves the element looking selected forever. */
@media (hover: hover) and (pointer: fine) {
  .zone-item:hover {
    background: var(--surface-hover);
    border-color: var(--border);
  }
  .traffic-row:hover { background: var(--surface-hover); }
  :is(button, .nav-btn, .fb-btn):not(:disabled):hover {
    background: var(--surface-hover);
  }
  .nav-btn.primary:hover { background: var(--accent); filter: brightness(1.06); }
  .refresh-btn:hover { background: var(--surface-hover); color: var(--text); }
}

/* ── The one honest use of motion: reduced-motion ───────────────── */
/* Not bolted on. Vestibular triggers are a health condition, and the
   cascade + sheet are exactly the large-translation patterns that
   trigger them. */
@media (prefers-reduced-motion: reduce) {
  .tab-content.active,
  .zone-item,
  .detail-overlay.open .overlay-scrim,
  .detail-overlay.open .detail-sheet,
  .toast,
  .toast.out,
  .score-bump,
  .is-syncing .sync-dot,
  .spinning {
    animation: none !important;
  }
  :is(button, .nav-item, .zone-item, .fb-btn, .traffic-row, .refresh-btn) {
    transition: opacity 90ms linear !important;
  }
  :is(button, .nav-item, .zone-item, .fb-btn, .traffic-row, .refresh-btn):active {
    transform: none !important;
  }
}
