/*
 * App-wide interaction primitives. See INTERACTIONS.md for the rules these
 * implement; see tokens.css for the values.
 */

/*
 * Press feedback. The pressed state only sets --press, and the base rule
 * applies it — so an element that owns its own transform (something sliding in
 * from a stack, say) can compose the press into that transform rather than
 * having it clobbered. Elements with no transform of their own get it free.
 */
[data-pressable] {
  touch-action: manipulation;
  transform: scale(var(--press, 1));
  transition: transform var(--spring-duration) var(--spring);
}

[data-pressable][data-pressed="true"] {
  --press: var(--press-scale);
  /* Down is quick and linear; the spring is for coming back. */
  transition-duration: var(--duration-press);
  transition-timing-function: var(--easing);
}

/*
 * Reduced motion still has to answer the touch — it just does it without
 * movement, rather than swallowing the tap silently.
 */
@media (prefers-reduced-motion: reduce) {
  [data-pressable][data-pressed="true"] {
    --press: 1;
    opacity: 0.6;
  }
}

/*
 * Error feedback: shake the element that was actually wrong, so the user can
 * see what to fix without reading a message. Add/remove `is-invalid` — the
 * animation replays each time the class is re-added.
 */
.is-invalid {
  animation: shake var(--duration-slow) var(--easing);
}

@keyframes shake {
  0%,
  100% {
    transform: translateX(0);
  }
  20% {
    transform: translateX(-6px);
  }
  40% {
    transform: translateX(5px);
  }
  60% {
    transform: translateX(-3px);
  }
  80% {
    transform: translateX(2px);
  }
}

/*
 * The toggle pop, for a glyph that has just changed what it means — a star
 * filling in, an icon swapping to its solid twin. Add `is-toggled` to the icon
 * only on the tap that changed it, never on first paint: a list that popped
 * every star it drew would be animating a state nobody just set.
 *
 * It lives here rather than in a component because three surfaces now want it —
 * the fixture row's star, and the fixture page's favourite button and its two
 * team stars — and the alternative is the same keyframe under three names.
 *
 * Put it on the glyph, not on the button. `pressable()` owns the button's
 * transform, and a second animation on the same element would take the press
 * scale with it.
 */
.icon.is-toggled {
  animation: icon-toggle var(--spring-duration) var(--spring);
}

@keyframes icon-toggle {
  0% {
    transform: scale(0.7);
  }
  55% {
    transform: scale(1.2);
  }
  100% {
    transform: scale(1);
  }
}

/*
 * The prime on a running clock, blinking.
 *
 * It is the one mark in the app that moves on its own, and it earns that: a
 * minute that has stopped is indistinguishable from one still counting until
 * something says which, and the mark that means "minutes" is the honest place
 * to say it. Render it only while the clock is actually running — half time
 * gets no prime at all, so a stopped clock sits still beside a ticking one and
 * the difference reads.
 *
 * Steps rather than a fade: a clock tick is on or off, and an eased pulse reads
 * as breathing. Well under the three-per-second that would make it a flash
 * hazard, and it stops entirely under reduced motion — left visible, never
 * hidden, since it is punctuation before it is an animation.
 *
 * Shared because two surfaces show the same clock: the fixture row's status
 * column and the fixture page's centre block.
 */
.live-prime {
  animation: live-prime 1.4s steps(1, end) infinite;
}

@keyframes live-prime {
  50% {
    opacity: 0;
  }
}

/*
 * Skeletons. Compose these into the shape of the real content — a row of
 * skeleton blocks sized like a match card, never one generic grey rectangle
 * and never a bare spinner.
 */
.skeleton {
  background: var(--surface-sunken);
  border-radius: var(--radius-sm);
  animation: skeleton-pulse 1.2s ease-in-out infinite;
}

.skeleton--text {
  height: 0.7em;
  border-radius: var(--radius-full);
}

.skeleton--circle {
  border-radius: var(--radius-full);
}

@keyframes skeleton-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.55;
  }
}

@media (prefers-reduced-motion: reduce) {
  .is-invalid,
  .icon.is-toggled,
  .live-prime,
  .skeleton {
    animation: none;
  }
}
