/* ============================================================================
   FROOTCITY — MOTION PRIMITIVES
   The CSS half of src/core/motion/motion.js. Every duration and easing comes
   from tokens.css; nothing here invents a timing value.

   tokens.css already collapses every duration to 1ms under
   prefers-reduced-motion, so these rules need no separate reduced-motion
   branch — except where a rule would leave something invisible, which is
   handled explicitly at the bottom.
   ========================================================================= */

/* ------------------------------------------------------------------ TRACK */
/* One datum-and-deviation meter. scaleX, never width: tokens.css leaves width
   out of --fc-tx on purpose, because animating layout janks. */
.fc-track {
  position: relative;
  height: 6px;
  border-radius: var(--fc-r-pill);
  background: var(--on-glass-chip);
  overflow: hidden;
  isolation: isolate;
}
.fc-track-fill {
  position: absolute;
  inset: 0;
  display: block;
  transform-origin: left center;
  transform: scaleX(0);
  border-radius: inherit;
  background: var(--accent);
  will-change: transform;
}
.fc-track[data-tone="warn"]   .fc-track-fill { background: var(--fc-warn); }
.fc-track[data-tone="danger"] .fc-track-fill { background: var(--fc-danger); }
.fc-track[data-tone="ok"]     .fc-track-fill { background: var(--fc-success); }
.fc-track[data-tone="info"]   .fc-track-fill { background: var(--fc-info); }
.fc-track[data-tone="quiet"]  .fc-track-fill { background: var(--txt-3); }

/* The tolerance zone: the region in which a reading is still acceptable. */
.fc-track-band {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--fc-band-a, 0%);
  width: var(--fc-band-b, 0%);
  background: var(--fc-success);
  opacity: .22;
  pointer-events: none;
}
/* The reference rule: what the number was supposed to be. */
.fc-track-datum {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--fc-datum, 100%);
  width: 2px;
  margin-left: -1px;
  background: var(--txt-1);
  opacity: .55;
  pointer-events: none;
  z-index: 2;
}

.fc-track--tall { height: 10px; }
.fc-track--thin { height: 3px; }

/* ---------------------------------------------------------------- COUNTER */
.fc-pulse--up,
.fc-pulse--down,
.fc-pulse--land {
  animation: fc-pop var(--fc-m-slow) var(--fc-ease-out);
}
.fc-pulse--up   { animation-name: fc-pop-up; }
.fc-pulse--down { animation-name: fc-pop-down; }

@keyframes fc-pop      { 0% { transform: scale(1); } 34% { transform: scale(1.12); } 100% { transform: scale(1); } }
@keyframes fc-pop-up   { 0% { transform: scale(1); } 34% { transform: scale(1.09) translateY(-2px); } 100% { transform: scale(1) translateY(0); } }
@keyframes fc-pop-down { 0% { transform: scale(1); } 34% { transform: scale(.94) translateY(2px); } 100% { transform: scale(1) translateY(0); } }

/* ----------------------------------------------------------------- FLIGHT */
/* Pooled nodes: add-to-basket is the most-fired animation in the product and it
   must not allocate on every tap. One per arc, because arcs overlap. */
.fc-flight {
  position: fixed;
  top: 0;
  left: 0;
  z-index: var(--fc-z-toast);
  width: var(--fc-fly-size, 48px);
  height: var(--fc-fly-size, 48px);
  pointer-events: none;
  opacity: 0;
  display: grid;
  place-items: center;
  will-change: transform, opacity;
}
.fc-flight.is-live { opacity: 1; }
.fc-flight img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: var(--fc-r-md);
}
.fc-flight[data-kind="dot"] i {
  width: 12px;
  height: 12px;
  border-radius: var(--fc-r-pill);
  background: var(--accent);
  display: block;
}
.fc-flight[data-kind="label"] span {
  font: var(--fc-w-bold) var(--fc-t-sm)/1 var(--fc-font-sans);
  color: var(--txt-inv);
  background: var(--accent);
  padding: 4px 10px;
  border-radius: var(--fc-r-pill);
  white-space: nowrap;
}

/* ---------------------------------------------------------------- STAGGER */
/* Replaces the fixed nth-child ladder: that one stops at child nine, and the
   catalogue grid renders forty-nine cards. */
.fc-stagger-in {
  animation: fc-rise var(--fc-m-medium) var(--fc-ease-out) both;
  animation-delay: var(--fc-delay, 0ms);
}
@keyframes fc-rise {
  from { opacity: 0; transform: translate3d(0, 10px, 0) scale(.985); }
  to   { opacity: 1; transform: none; }
}
/* Settled state. Every animated element must land on identity transform or the
   DOM geometry probes in _test/ measure the wrong box. */
.fc-in { opacity: 1; transform: none; }

.fc-reveal { opacity: 0; transform: translate3d(0, 12px, 0); transition: opacity var(--fc-m-medium) var(--fc-ease-out), transform var(--fc-m-medium) var(--fc-ease-out); }
.fc-reveal.fc-in { opacity: 1; transform: none; }

/* ------------------------------------------------------------------- FLIP */
.fc-flip-move  { transition: transform var(--fc-m-medium) var(--fc-ease-out); }
.fc-flip-enter { animation: fc-rise var(--fc-m-medium) var(--fc-ease-out) both; }

/* ------------------------------------------------------------------ PRESS */
.fc-pressable { transition: transform var(--fc-m-fast) var(--fc-ease-spring); }
.fc-pressable.is-pressing { transform: scale(var(--fc-press)); }
.fc-pressable[data-press="heavy"].is-pressing { transform: scale(.985); }
.fc-pressable.is-locked.is-pressing,
.fc-pressable.is-inert.is-pressing { transform: none; }

/* --------------------------------------------------------------- TIMELINE */
/* The connector draws forward only across states the order actually advanced
   through; the dot lands when it is reached. */
.fc-tl-arriving .tl-dot {
  animation: fc-dot-land var(--fc-m-slow) var(--fc-ease-spring) both;
  animation-delay: var(--fc-delay, 0ms);
}
.fc-tl-arriving::before {
  animation: fc-line-draw var(--fc-m-slow) var(--fc-ease-out) both;
  animation-delay: var(--fc-delay, 0ms);
}
@keyframes fc-dot-land {
  from { transform: scale(.2); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}
@keyframes fc-line-draw {
  from { transform: scaleY(0); transform-origin: top center; }
  to   { transform: scaleY(1); transform-origin: top center; }
}

/* ------------------------------------------------------------ PAGE CHANGE */
/* A same-path re-render (FC.router.reload) passes dir "none", which matches no
   directional rule — and .fc-page-out matches on its base class alone, so the
   old screen would fade out on top of a new one that never faded in. */
.fc-page-in { animation: fc-page-in-fade var(--fc-m-medium) var(--fc-ease-out) both; }
.fc-page-in.is-forward { animation: fc-page-in-fwd var(--fc-m-medium) var(--fc-ease-out) both; }
.fc-page-in.is-back    { animation: fc-page-in-back var(--fc-m-medium) var(--fc-ease-out) both; }
.fc-page-in.is-up      { animation: fc-page-in-up var(--fc-m-medium) var(--fc-ease-out) both; }
.fc-page-out {
  position: absolute;
  inset: 0;
  pointer-events: none;
  animation: fc-page-out var(--fc-m-normal, var(--fc-m-medium)) var(--fc-ease-in) both;
}
@keyframes fc-page-in-fwd  { from { opacity: 0; transform: translate3d(12px, 0, 0) scale(.994); } to { opacity: 1; transform: none; } }
@keyframes fc-page-in-back { from { opacity: 0; transform: translate3d(-12px, 0, 0) scale(.994); } to { opacity: 1; transform: none; } }
@keyframes fc-page-in-up   { from { opacity: 0; transform: translate3d(0, 16px, 0) scale(.99); } to { opacity: 1; transform: none; } }
@keyframes fc-page-out     { from { opacity: 1; transform: none; } to { opacity: 0; transform: scale(.99); } }
@keyframes fc-page-in-fade { from { opacity: 0; } to { opacity: 1; } }

/* -------------------------------------------------------------------- 3D */
.fc3d {
  position: relative;
  display: block;
  touch-action: pan-y;
}
.fc3d-canvas {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  outline: none;
}
/* touch-action stays pan-y from .fc3d. The hero fills half a phone screen, so
   a viewer that owns the vertical gesture freezes the whole page; horizontal
   drag still turns the object, and gl.js up() is wired to pointercancel for
   the moment the browser takes the gesture back. */
.fc3d[data-interactive="true"] { cursor: grab; }
.fc3d[data-interactive="true"]:active { cursor: grabbing; }
.fc3d:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
/* The still that shows until the first frame lands, and the whole picture on
   any device without WebGL. */
.fc3d-still {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* The film tier (FC.film): a Cycles frame sequence on one <canvas>, no GL.
   Frames are square; hosts are not (112×132, 120×152), so the frame is
   contained and its ground line sits on the host's bottom edge. The poster
   <img> sits under the canvas at the same anchor until the first draw. */
.fc-film {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center bottom;
}
.fc-mascot > img.fc3d-still.is-film {
  position: absolute;
  inset: 0;
  object-fit: contain;
  object-position: center bottom;
}
.fc3d-hint {
  position: absolute;
  bottom: 6px;
  left: 0;
  right: 0;
  text-align: center;
  font: var(--fc-w-medium) var(--fc-t-2xs)/1.4 var(--fc-font-sans);
  color: var(--txt-3);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--fc-m-medium) var(--fc-ease);
}
@media (hover: hover) {
  .fc3d:hover .fc3d-hint,
  .fc3d:focus-within .fc3d-hint { opacity: 1; }
}
/* A touch device has neither hover nor focus, and it is the one device where
   the drag is undiscoverable — the hero otherwise reads as a photograph. */
@media (hover: none) {
  .fc3d-hint { opacity: 1; }
}

/* ---------------------------------------------------------------- STATES */
/* Motion must never be the only signal. These pair a movement with a shape or
   a colour that survives prefers-reduced-motion and greyscale. */
.fc-state-ok    { color: var(--fc-success); }
.fc-state-warn  { color: var(--fc-warn); }
.fc-state-error { color: var(--fc-danger); }

.fc-shimmer {
  position: relative;
  overflow: hidden;
}
.fc-shimmer::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 20%, var(--glass-fill-hard) 50%, transparent 80%);
  transform: translateX(-100%);
  animation: fc-shim 1400ms var(--fc-ease) infinite;
}
@keyframes fc-shim { to { transform: translateX(100%); } }

/* ----------------------------------------------------- REDUCED MOTION ---- */
/* tokens.css already forces every duration to 1ms. The only thing left to do
   is make sure nothing is left mid-animation and therefore invisible: `both`
   fill on a 1ms animation lands on the final frame, which is what we want, but
   these two never even start. */
@media (prefers-reduced-motion: reduce) {
  .fc-reveal { opacity: 1; transform: none; }
  .fc-shimmer::after { animation: none; opacity: 0; }
  .fc-flight { display: none; }
}
