/* MOTION — keyframes, interactive motion, and the reduced-motion overrides.
   Every duration and distance comes from theme.css. */

/* a single move from small to full size; the spring curve adds the bounce */
@keyframes nintendoPop {
  0% {
    opacity: 0;
    transform: var(--motion-pop-from);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}

@keyframes iconBob {
  0%, 100% { transform: none; }
  50% { transform: var(--motion-bob); }
}

@keyframes hintBounce {
  0%, 100% { transform: none; opacity: 0.75; }
  50% { transform: var(--motion-hint); opacity: 1; }
}

/* below freezing the white temperature chills to the low-temp cyan */
@keyframes tempFreezeIn {
  from { color: var(--temp-current); }
  to { color: var(--temp-low); }
}

@keyframes tempShiver {
  0%, 100% { transform: none; }
  20% { transform: translate(calc(-1 * var(--motion-shiver)), calc(0.5 * var(--motion-shiver))) rotate(-0.35deg); }
  40% { transform: translate(var(--motion-shiver), calc(-0.4 * var(--motion-shiver))) rotate(0.3deg); }
  60% { transform: translate(calc(-0.75 * var(--motion-shiver)), calc(-0.5 * var(--motion-shiver))) rotate(-0.25deg); }
  80% { transform: translate(var(--motion-shiver), calc(0.4 * var(--motion-shiver))) rotate(0.35deg); }
}

@keyframes tempHeatShake {
  0%, 100% { transform: none; }
  15% { transform: translate(calc(var(--heat-amp, var(--heat-amp-default)) * -0.8), calc(var(--heat-amp, var(--heat-amp-default)) * 0.5)) rotate(-0.5deg); }
  30% { transform: translate(var(--heat-amp, var(--heat-amp-default)), calc(var(--heat-amp, var(--heat-amp-default)) * -0.7)) rotate(0.6deg); }
  45% { transform: translate(calc(var(--heat-amp, var(--heat-amp-default)) * -1), calc(var(--heat-amp, var(--heat-amp-default)) * -0.4)) rotate(-0.4deg); }
  60% { transform: translate(calc(var(--heat-amp, var(--heat-amp-default)) * 0.9), calc(var(--heat-amp, var(--heat-amp-default)) * 0.6)) rotate(0.5deg); }
  80% { transform: translate(calc(var(--heat-amp, var(--heat-amp-default)) * -0.6), calc(var(--heat-amp, var(--heat-amp-default)) * 0.3)) rotate(-0.3deg); }
}

/* a quick squash down, then a spring back up that overshoots a little */
@keyframes wiiSquashPress {
  0% {
    transform: none;
    animation-timing-function: var(--ease-press);
  }
  30% {
    transform: var(--motion-squash-down);
    animation-timing-function: var(--spring-pop);
  }
  100% {
    transform: none;
  }
}

/* the loading dots hop like little balls and squash when they land */
@keyframes dotBounce {
  0%, 100% {
    transform: none;
    animation-timing-function: var(--ease-dot-up);
  }
  45% {
    transform: translateY(var(--motion-dot-jump)) var(--motion-dot-stretch);
    animation-timing-function: var(--ease-dot-down);
  }
  82% {
    transform: var(--motion-dot-squash);
  }
}

/* reduced motion: the dots glow in turn instead of hopping */
@keyframes dotGlow {
  0%, 100% { opacity: var(--dot-dim); }
  50% { opacity: 1; }
}

@keyframes fadeIn {
  from { opacity: 0; }
}

/* a small side-to-side "nope" */
@keyframes nopeShake {
  0%, 100% { transform: none; }
  15% { transform: translateX(calc(-1 * var(--motion-shake))); }
  35% { transform: translateX(var(--motion-shake)); }
  55% { transform: translateX(calc(-0.6 * var(--motion-shake))); }
  75% { transform: translateX(calc(0.3 * var(--motion-shake))); }
}

/* a soft band of light crossing a glossy face */
@keyframes shineSweep {
  0% {
    opacity: 0;
    transform: translateX(-100%) skewX(var(--shine-skew));
  }
  25% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translateX(var(--shine-travel)) skewX(var(--shine-skew));
  }
}

/* the search button glows while pressed, then settles into the searching halo */
@keyframes searchPressGlow {
  0%, 28% { box-shadow: var(--press-glow); }
  100% { box-shadow: var(--wii-searching-shadow); }
}

@keyframes dockScanDrift {
  0% { transform: none; }
  100% { transform: var(--motion-scan-drift); }
}

@keyframes dockScanStatic {
  0%, 100% { opacity: 0.24; }
  50% { opacity: 0.32; }
}

@keyframes alertBarDown {
  0% { opacity: 0; transform: translateY(-100%); }
  100% { opacity: 1; transform: none; }
}

/* the warning triangle gives one little wiggle as the bar lands */
@keyframes alertWiggle {
  0%, 100% { transform: none; }
  20% { transform: rotate(calc(-1 * var(--motion-wiggle))); }
  45% { transform: rotate(var(--motion-wiggle)); }
  65% { transform: rotate(calc(-0.5 * var(--motion-wiggle))); }
  82% { transform: rotate(calc(0.25 * var(--motion-wiggle))); }
}

@keyframes alertBarUp {
  0% { opacity: 0; transform: translateY(100%); }
  100% { opacity: 1; transform: none; }
}

@keyframes channelColonBlink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0.2; }
}

/* ---------- Tactile buttons ---------- */

/* Highlight layers fade in on hover; a press lights them at once. */
.frame-btn::before,
.chip-btn::before,
.fiveday-col::before,
.frame-search-go-face {
  transition: opacity var(--dur-fast) var(--ease-fade);
}

.frame-btn:active::before,
.chip-btn:active::before,
.fiveday-col:active::before,
.frame-search-go:active .frame-search-go-face {
  transition: none;
}

/* Hover grow uses the scale property and the press squash uses transform,
   so the two stack instead of fighting over one value. */
.frame-btn {
  transition: color var(--dur-fast) var(--ease-fade);
}

.chip-btn {
  transition:
    scale var(--dur-spring-hover) var(--spring-hover),
    color var(--dur-fast) var(--ease-fade);
}

.frame-location,
.fiveday-col,
.home-badge,
.frame-btn > *,
.frame-location-text,
.alert-bar-more,
.alert-bar .alert-icon,
.globe-marker > * {
  transition: scale var(--dur-spring-hover) var(--spring-hover);
}

.view-dot::before {
  transition:
    scale var(--dur-spring-hover) var(--spring-hover),
    transform var(--dur-spring-pop) var(--spring-pop);
}

/* opening the search: the field stretches out and Go pops in after it */
@keyframes fieldOpen {
  0% {
    opacity: 0;
    transform: var(--motion-field-from);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}

.frame-search:not(.hidden) .frame-search-well {
  transform-origin: right center;
  animation: fieldOpen var(--dur-spring-pop) var(--spring-pop) both;
}

.frame-search:not(.hidden) .frame-search-go {
  animation: nintendoPop var(--dur-spring-pop) var(--spring-pop) var(--stagger-gap) both;
}

/* a pointer (not a finger) makes things grow and shine */
@media (hover: hover) and (pointer: fine) {
  /* row buttons sit edge to edge, so their label and arrow grow instead */
  .frame-btn:not(:disabled):hover > :not(.shine) {
    scale: var(--grow-label);
  }

  .frame-location:hover {
    scale: var(--grow-wide);
  }

  .frame-search-go:hover,
  .chip-btn:hover,
  .home-badge:hover,
  .alert-bar:hover .alert-bar-more,
  .alert-bar:hover .alert-icon {
    scale: var(--grow);
  }

  .fiveday-col:hover {
    z-index: 1;
    scale: var(--grow-col);
  }

  .view-dot:hover::before {
    scale: var(--grow-dot);
  }

  .globe-marker:hover > *,
  .globe-marker:focus-visible > * {
    scale: var(--grow-marker);
  }

  :hover > .shine::before {
    animation: shineSweep var(--dur-shine) var(--ease-fade);
  }
}

.shine {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
  pointer-events: none;
}

.shine::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--shine-width);
  background: var(--shine-band);
  opacity: 0;
  transform: translateX(-100%) skewX(var(--shine-skew));
}

/* Press: feel.js adds .is-squashing and takes it off when the squash ends,
   so even a quick click plays the whole squash and bounce. */
.is-squashing {
  animation: wiiSquashPress var(--dur-squash) linear;
}

/* Row buttons, the location pill, and the alert bar squash their contents;
   globe markers are placed with transform, so their parts squash instead. */
.frame-btn.is-squashing,
.frame-location.is-squashing,
.alert-bar.is-squashing,
.globe-marker.is-squashing,
.view-dot.is-squashing {
  animation: none;
}

.frame-btn.is-squashing > :not(.shine),
.frame-location.is-squashing .frame-location-text,
.alert-bar.is-squashing > :not(.shine),
.globe-marker.is-squashing > * {
  animation: wiiSquashPress var(--dur-squash) linear;
}

.frame-search-well {
  transition:
    border-color var(--dur-fast) var(--nintendo-soft),
    box-shadow var(--dur-fast) var(--nintendo-soft);
}

.frame-search-go {
  transition:
    scale var(--dur-spring-hover) var(--spring-hover),
    box-shadow var(--dur-medium) var(--ease-fade),
    color var(--dur-fast) var(--ease-fade);
}

.frame-search-go::before {
  transition: opacity var(--dur-fast) var(--nintendo-soft);
}

/* ---------- Weather icons: small idle animations ----------
   Each motion uses the rotate / scale / translate properties, so two can
   run at once (the sun turns and pulses). The big icons' wrapper adds a
   glow behind the sun or moon and drops or flakes below rain and snow. */

@keyframes sunTurn {
  to { rotate: 360deg; }
}

@keyframes sunPulse {
  0%, 100% { scale: 1; }
  50% { scale: var(--motion-sun-pulse); }
}

@keyframes cloudDrift {
  0%, 100% { translate: 0 0; }
  50% { translate: var(--motion-drift) 0; }
}

@keyframes moonGlow {
  0%, 100% { opacity: var(--moon-dim); }
  50% { opacity: 1; }
}

@keyframes fogDrift {
  0%, 100% { translate: 0 0; opacity: 1; }
  50% { translate: var(--motion-fog-drift) 0; opacity: var(--fog-dim); }
}

@keyframes snowSway {
  0%, 100% { rotate: calc(-1 * var(--motion-snow-sway)); }
  50% { rotate: var(--motion-snow-sway); }
}

@keyframes glowPulse {
  0%, 100% { opacity: var(--glow-dim); scale: var(--glow-small); }
  50% { opacity: 1; scale: var(--glow-big); }
}

@keyframes dripFall {
  0% { opacity: 0; translate: 0 0; }
  25% { opacity: 1; }
  100% { opacity: 0; translate: 0 var(--motion-drip-fall); }
}

img[data-motion="sun"] {
  animation:
    sunTurn var(--dur-sun-turn) linear infinite,
    sunPulse var(--dur-sun-pulse) ease-in-out infinite;
}

img[data-motion="cloud"],
img[data-motion="rain"] {
  animation: cloudDrift var(--dur-cloud-drift) ease-in-out infinite;
}

img[data-motion="moon"] {
  animation: moonGlow var(--dur-moon-glow) ease-in-out infinite;
}

img[data-motion="fog"] {
  animation: fogDrift var(--dur-fog-drift) ease-in-out infinite;
}

img[data-motion="snow"] {
  animation: snowSway var(--dur-snow-sway) ease-in-out infinite;
}

/* small icons in a row start at different points, so they don't move in step */
.fiveday li:nth-child(2) img,
.sixhour-list li:nth-child(2) img {
  animation-delay: calc(-0.2 * var(--dur-sun-pulse));
}

.fiveday li:nth-child(3) img,
.sixhour-list li:nth-child(3) img {
  animation-delay: calc(-0.45 * var(--dur-sun-pulse));
}

.fiveday li:nth-child(4) img,
.sixhour-list li:nth-child(4) img {
  animation-delay: calc(-0.7 * var(--dur-sun-pulse));
}

.fiveday li:nth-child(5) img {
  animation-delay: calc(-0.9 * var(--dur-sun-pulse));
}

/* a soft glow breathes behind a big sun or moon */
.wx-icon[data-motion="sun"]::before,
.wx-icon[data-motion="moon"]::before {
  content: "";
  position: absolute;
  inset: var(--glow-inset);
  z-index: -1;
  border-radius: var(--radius-pill);
  background: var(--sun-glow);
  animation: glowPulse var(--dur-sun-pulse) ease-in-out infinite;
  pointer-events: none;
}

.wx-icon[data-motion="moon"]::before {
  background: var(--moon-glow);
  animation-duration: var(--dur-moon-glow);
}

/* two sets of drops (or flakes) take turns falling from under the cloud */
.wx-icon[data-motion="rain"]::before,
.wx-icon[data-motion="rain"]::after,
.wx-icon[data-motion="snow"]::before,
.wx-icon[data-motion="snow"]::after {
  content: "";
  position: absolute;
  top: var(--drip-top);
  right: var(--drip-side);
  left: var(--drip-side);
  height: var(--drip-height);
  background: var(--rain-drops);
  opacity: 0;
  animation: dripFall var(--dur-drip) ease-in infinite;
  pointer-events: none;
}

.wx-icon[data-motion="rain"]::after,
.wx-icon[data-motion="snow"]::after {
  background: var(--rain-drops-2);
  animation-delay: calc(var(--dur-drip) / -2);
}

.wx-icon[data-motion="snow"]::before {
  background: var(--snow-flakes);
  animation-duration: var(--dur-flake);
}

.wx-icon[data-motion="snow"]::after {
  background: var(--snow-flakes-2);
  animation-duration: var(--dur-flake);
  animation-delay: calc(var(--dur-flake) / -2);
}

/* ---------- Idle attract: the satellite picture drifts and pans ---------- */

@keyframes satPan {
  0%, 100% { transform: none; }
  50% { transform: scale(var(--attract-zoom)) translate(var(--attract-pan-x), var(--attract-pan-y)); }
}

.frame-panel.is-attract .panel-sat {
  animation: satPan var(--dur-attract-pan) ease-in-out infinite;
}

/* markers fade while the globe moves and come back when it settles */
.globe-markers {
  transition: opacity var(--dur-fast) var(--ease-fade);
}

.screen--globe.is-moving .globe-markers {
  opacity: 0;
}

/* ---------- Easter eggs ---------- */

/* the winking sun: a face fades onto the sun, then the right eye winks */
.sun-face {
  position: absolute;
  width: var(--sun-face-size);
  height: var(--sun-face-size);
  pointer-events: none;
  animation: fadeIn var(--dur-medium) var(--ease-fade) both;
}

.sun-face-eye {
  fill: var(--sun-face-ink);
}

.sun-face-cheek {
  fill: var(--sun-face-cheek);
}

.sun-face-smile,
.sun-face-wink {
  fill: none;
  stroke: var(--sun-face-ink);
  stroke-linecap: round;
  stroke-width: var(--sun-face-stroke);
}

.sun-face-wink {
  opacity: 0;
}

@keyframes winkClose {
  0%, 30%, 70%, 100% { opacity: 1; }
  40%, 60% { opacity: 0; }
}

@keyframes winkLine {
  0%, 30%, 70%, 100% { opacity: 0; }
  40%, 60% { opacity: 1; }
}

.sun-face .sun-face-eye--open {
  animation: winkClose var(--dur-wink) linear var(--dur-medium) both;
}

.sun-face .sun-face-wink {
  animation: winkLine var(--dur-wink) linear var(--dur-medium) both;
}

/* snow piles up on the frame's ledges while it snows (body.is-snowing) */
@keyframes snowSettle {
  from { transform: scaleY(0); }
}

body.is-snowing .frame-row--bottom .frame-btn::after,
body.is-snowing .frame-title-block::after,
body.is-snowing .frame-location::after {
  content: "";
  position: absolute;
  top: 0;
  right: var(--snow-cap-inset);
  left: var(--snow-cap-inset);
  height: var(--snow-cap-height);
  background: var(--snow-cap);
  filter: var(--snow-cap-shadow);
  transform-origin: top center;
  animation: snowSettle var(--dur-snow-settle) var(--ease-fade) both;
  pointer-events: none;
}

body.is-snowing .frame-location::after {
  right: var(--snow-cap-pill-inset);
  left: var(--snow-cap-pill-inset);
}

/* views out of sight rest */
.view[aria-hidden="true"] img[data-motion],
.view[aria-hidden="true"] .wx-icon::before,
.view[aria-hidden="true"] .wx-icon::after {
  animation-play-state: paused;
}

/* temperature moods on the current temperature (classes set from views.js) */
.temp--freezing {
  color: var(--temp-low);
  animation:
    tempFreezeIn var(--dur-freeze) ease-in-out both,
    tempShiver var(--dur-shiver) linear infinite var(--dur-freeze);
}

.temp--hot {
  animation: tempHeatShake var(--heat-speed, var(--dur-heat)) linear infinite;
}

.panel-status:not(.hidden) {
  animation: nintendoPop var(--dur-spring-pop) var(--spring-pop) both;
}

/* ---------- Loading and errors ---------- */

.loader-dot {
  animation: dotBounce var(--dur-dot) infinite;
}

.loader-dot:nth-child(2) {
  animation-delay: calc(var(--dur-dot) * 0.15);
}

.loader-dot:nth-child(3) {
  animation-delay: calc(var(--dur-dot) * 0.3);
}

/* the dimmed panel waits a moment, so quick loads never flicker */
.panel-loading:not(.hidden) {
  animation: fadeIn var(--dur-fade-quick) var(--ease-fade) var(--loading-delay) backwards;
}

.panel-error:not(.hidden) {
  animation: nintendoPop var(--dur-spring-pop) var(--spring-pop) both;
}

.is-shaking {
  animation: nopeShake var(--dur-shake) ease-in-out;
}

/* the search field keeps its opening animation while it shakes */
.frame-search:not(.hidden) .frame-search-well.is-shaking {
  animation:
    fieldOpen var(--dur-spring-pop) var(--spring-pop) both,
    nopeShake var(--dur-shake) ease-in-out;
}

/* the alert bar springs down out of the header when alerts arrive */
.alert-bar {
  animation: alertBarDown var(--dur-spring-pop) var(--spring-pop) both;
}

.alert-bar .alert-icon {
  animation: alertWiggle var(--dur-wiggle) ease-in-out var(--dur-medium) both;
}

/* ---------- Desktop Home menu ---------- */

@media (min-width: 768px) {
  .channel-letterbox {
    animation: alertBarDown var(--dur-entrance) var(--nintendo-spring) both;
  }

  .channel-letterbox::before {
    animation: dockScanDrift var(--dur-scan-drift) linear infinite;
  }

  .channel-tray {
    animation: alertBarUp var(--dur-entrance) var(--nintendo-spring) both;
  }

  .channel-tray-crt {
    animation: dockScanStatic var(--dur-scan-static) steps(8) infinite;
  }

  /* tiles pop in one after another; the empty ones follow after a beat */
  .channel-grid--enter .channel-tile {
    animation: nintendoPop var(--dur-spring-pop) var(--spring-pop) backwards;
  }

  .channel-grid--enter .channel-tile:nth-child(1) { animation-delay: calc(1 * var(--stagger-step)); }
  .channel-grid--enter .channel-tile:nth-child(2) { animation-delay: calc(2 * var(--stagger-step)); }
  .channel-grid--enter .channel-tile:nth-child(3) { animation-delay: calc(3 * var(--stagger-step)); }
  .channel-grid--enter .channel-tile:nth-child(4) { animation-delay: calc(4 * var(--stagger-step)); }
  .channel-grid--enter .channel-tile:nth-child(5) { animation-delay: calc(5 * var(--stagger-step)); }
  .channel-grid--enter .channel-tile:nth-child(6) { animation-delay: calc(6 * var(--stagger-step)); }
  .channel-grid--enter .channel-tile:nth-child(7) { animation-delay: calc(var(--stagger-gap) + 6 * var(--stagger-step)); }
  .channel-grid--enter .channel-tile:nth-child(8) { animation-delay: calc(var(--stagger-gap) + 7 * var(--stagger-step)); }
  .channel-grid--enter .channel-tile:nth-child(9) { animation-delay: calc(var(--stagger-gap) + 8 * var(--stagger-step)); }
  .channel-grid--enter .channel-tile:nth-child(10) { animation-delay: calc(var(--stagger-gap) + 9 * var(--stagger-step)); }
  .channel-grid--enter .channel-tile:nth-child(11) { animation-delay: calc(var(--stagger-gap) + 10 * var(--stagger-step)); }
  .channel-grid--enter .channel-tile:nth-child(12) { animation-delay: calc(var(--stagger-gap) + 11 * var(--stagger-step)); }

  .channel-tile--live {
    position: relative;
    transition: scale var(--dur-spring-hover) var(--spring-hover), box-shadow var(--dur-medium) var(--nintendo-soft);
  }

  .channel-tile--live:hover {
    z-index: 2;
    scale: var(--grow-tile);
    box-shadow: var(--cyan-glow);
  }

  .channel-today-icon {
    animation: iconBob var(--dur-bob) var(--nintendo-soft) infinite;
  }

  .channel-grid-hint {
    animation: hintBounce var(--dur-hint) var(--nintendo-soft) infinite;
  }

  .channel-search button,
  .channel-tray-button {
    transition:
      translate var(--dur-spring-hover) var(--spring-hover),
      scale var(--dur-spring-hover) var(--spring-hover),
      box-shadow var(--dur-medium) var(--nintendo-soft),
      border-color var(--dur-fast) var(--nintendo-soft);
  }

  .channel-search button:hover {
    border-color: var(--cyan-rim-hover);
    translate: var(--lift);
    scale: var(--grow-search);
  }

  .channel-tray-button:not(:disabled):hover {
    translate: var(--lift-tray);
    scale: var(--grow-tray);
  }

  .channel-search button.search-press-feedback {
    animation: searchPressGlow var(--dur-press-glow) var(--nintendo-spring) both;
  }

  .channel-search button.is-searching {
    box-shadow: var(--wii-searching-shadow);
  }

  .channel-clock-colon {
    animation: channelColonBlink var(--dur-blink) steps(1) infinite;
  }
}

/* ---------- Reduced motion: everything rests ---------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: var(--dur-none) !important;
    animation-iteration-count: 1 !important;
    transition-duration: var(--dur-none) !important;
  }

  .panel-track {
    transition: none !important;
  }

  img[data-motion],
  .frame-panel.is-attract .panel-sat,
  .sun-face,
  .sun-face *,
  body.is-snowing .frame-btn::after,
  body.is-snowing .frame-title-block::after,
  body.is-snowing .frame-location::after,
  .alert-bar,
  .alert-bar .alert-icon,
  .temp--freezing,
  .temp--hot,
  .is-squashing,
  .is-squashing > *,
  .is-squashing::before,
  .is-squashing .frame-location-text,
  .channel-letterbox,
  .channel-letterbox::before,
  .channel-tray,
  .channel-tray-crt,
  .channel-grid--enter .channel-tile,
  .channel-today-icon,
  .channel-grid-hint,
  .channel-search button.search-press-feedback,
  .channel-clock-colon,
  .frame-search:not(.hidden) .frame-search-well,
  .frame-search:not(.hidden) .frame-search-go,
  .panel-error:not(.hidden),
  .is-shaking {
    animation: none !important;
  }

  /* the loading dots glow in turn instead of hopping */
  .loader-dot {
    animation-name: dotGlow !important;
    animation-duration: var(--dur-dot) !important;
    animation-iteration-count: infinite !important;
  }

  /* no shine and nothing grows; highlights still change color */
  .shine,
  .wx-icon::before,
  .wx-icon::after {
    display: none;
  }

  .frame-location,
  .frame-search-go,
  .chip-btn,
  .fiveday-col,
  .home-badge,
  .frame-btn > *,
  .frame-location-text,
  .alert-bar-more,
  .alert-bar .alert-icon,
  .globe-marker > *,
  .view-dot::before,
  .channel-tile--live,
  .channel-search button,
  .channel-tray-button {
    scale: none !important;
    translate: none !important;
  }
}
