/* FedEx Insights — Shipment trends dashboard
   Ported from the Claude Design "Shipment Insights" prototype.
   The views carry their own inline styles (kept faithful to the design);
   this file only holds global resets, fonts, keyframes and a couple of
   things inline styles can't express (::selection, range tracks). */

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body { font-family: 'IBM Plex Sans', system-ui, sans-serif; }

::selection { background: #ff6a0033; }

@keyframes dashflow { to { stroke-dashoffset: -32; } }
@keyframes softpulse { 0%, 100% { opacity: .45; } 50% { opacity: .9; } }

select { font-family: 'IBM Plex Sans', system-ui, sans-serif; }

/* The dc prototype relied on a style-hover attribute handled by its runtime.
   We reproduce the same hover affordances here with real CSS. */
[data-hover]:hover { background: var(--hover-bg, transparent) !important; }

/* ====================================================================
   ANIMATION SYSTEM — foundation (plan item #0)
   --------------------------------------------------------------------
   The app re-renders the whole view via innerHTML on every state change,
   so entrance animations must be GATED or they replay on every slider
   tick / keystroke. app.js stamps #app with `data-anim` tokens for the
   render it just produced:
       view  → the view was switched      (container reveal)
       data  → filters / shown data changed
       none  → minor tick (toggle, slider, dropdown open/search)
   Future animation items hook in by scoping their rules to a token, e.g.
       #app[data-anim~="data"] .trend-ship-line { animation: drawLine .6s; }
       #app[data-anim~="view"] .map-bubble       { animation: popIn .4s; }
   Because elements are recreated each render, a freshly-stamped token
   restarts the animation automatically. No token → the rule never
   matches → the element simply appears, no motion.
   ==================================================================== */

/* Baseline: reveal the view container when the view is switched. Richer
   crossfade is plan item #7; this is the reference hook that proves the gate. */
#app[data-anim~="view"] > div { animation: viewIn .24s ease both; }

@keyframes viewIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* The drawn layer (area + lines + dots) gently fades in so nothing pops. Fires
   on first paint, view switch, and any data change (the "data" token rides
   along on a "view" render too) — but not on minor ticks like a slider drag. */
#app[data-anim~="data"] .trend-draw { animation: trendFade .45s ease both; }
@keyframes trendFade { from { opacity: 0; } to { opacity: 1; } }

/* Plan item #1 — the shipments line "draws on" via stroke-dashoffset. With
   pathLength="1" the path length is normalised to 1, so dasharray:1 covers the
   whole line and the offset slides it from hidden (1) to drawn (0). This is the
   robust, every-browser way to animate a line (works for the solid line; the
   dotted pieces line keeps its own dasharray and just fades in with the group). */
#app[data-anim~="data"] .trend-ship-line {
  stroke-dasharray: 1;
  animation: drawLine .8s ease both;
}
@keyframes drawLine {
  from { stroke-dashoffset: 1; }
  to   { stroke-dashoffset: 0; }
}

/* The dotted pieces line can't use a dash-draw (its dash pattern IS the dots),
   so it "draws on" with a left-to-right clip-path reveal instead — kicked off a
   touch later than the shipments line so the two stagger in. */
#app[data-anim~="data"] .trend-pieces-line { animation: drawPieces .7s ease .22s both; }
@keyframes drawPieces {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* Plan item #2 — the gradient area lifts from the baseline and fades in on its
   own, slower timeline so the fill pours in gently after the lines are drawn. */
.trend-area { transform-box: fill-box; transform-origin: bottom; }
#app[data-anim~="data"] .trend-area { animation: areaRise 1.2s ease both; }
@keyframes areaRise {
  from { opacity: 0; transform: scaleY(.9); }
  to   { opacity: 1; transform: scaleY(1); }
}

/* Plan item #4 — staggered list reveal. Each list row fades + slides up, with a
   per-rank animation-delay set inline (rank 1 first). Bars inside the rows grow
   from the left a beat later. Gated on data/view so it replays on filter changes
   and view switches, but not on minor ticks. */
#app[data-anim~="data"] .li-row { animation: rowIn .42s ease both; }
@keyframes rowIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
.li-bar { transform-box: fill-box; transform-origin: left; }
#app[data-anim~="data"] .li-bar { animation: barGrow .5s ease both; }
@keyframes barGrow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

/* Plan item #5 — map bubble entrance + active-bubble pulse.
   Bubbles scale up from their own centre with a per-bubble stagger (inline
   animation-delay) on data/view renders. Selected bubbles get a continuous
   "radar ping" ring that is NOT gated, so it keeps pulsing between renders. */
.map-bubble { transform-box: fill-box; transform-origin: center; }
#app[data-anim~="data"] .map-bubble { animation: bubbleIn .5s ease both; }
@keyframes bubbleIn {
  0%   { transform: scale(0);    opacity: 0; }
  70%  { transform: scale(1.08); opacity: 1; }
  100% { transform: scale(1); }
}
#app[data-anim~="data"] .map-blabel { animation: trendFade .5s ease both; }

.map-pulse { transform-box: fill-box; transform-origin: center; animation: pulseRing 1.6s ease-out infinite; }
@keyframes pulseRing {
  0%   { transform: scale(1);   opacity: .75; }
  100% { transform: scale(1.9); opacity: 0; }
}

/* Plan item #8 — filter dropdown open + checkbox tick. The panel scales/fades
   in from its top edge (only on first open, not on every keystroke), and the ✓
   pops in only on the option just toggled on. Both are driven by one-render
   flags in app.js, so they don't replay while searching. */
.fs-panel-anim { animation: panelIn .16s ease both; transform-origin: top left; }
@keyframes panelIn {
  from { opacity: 0; transform: translateY(-6px) scale(.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.fs-check-anim { display: inline-block; animation: checkPop .22s cubic-bezier(.2, 1.4, .4, 1) both; }
@keyframes checkPop {
  from { transform: scale(0); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}

/* Accessibility: honour the OS "reduce motion" setting. Kills decorative
   loops (dashflow / softpulse) and collapses transitions + entrances. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* Full-screen Daily-volume chart: center the card and let the SVG fill the
   viewport (it keeps its own viewBox aspect ratio). */
#trend-card:fullscreen {
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 100vw;
  height: 100vh;
  padding: 4vh 4vw;
  overflow: auto;
}
#trend-card:fullscreen svg { max-height: 82vh; }
