/* Swatchle prototype: motion.
   An exploration, not in Paper or the spec yet. Everything here is built on the
   sticker idea: things get stuck on (drop, tilt, settle), stamped, or popped, and
   the board ripples. It keeps spec 6.1's rule: springy but quick, never blocking input.

   Loads after app.css and before desktop.css. The whole file sits inside a
   no-preference query, so with prefers-reduced-motion none of it applies and the
   game falls back to app.css's fades. js/motion.js skips its parts the same way.

   Contents: shared keyframes, screen changes (view transitions), home, how to play,
   clue, pips, board and picking, reveal, day complete, confetti. */

@media (prefers-reduced-motion: no-preference) {

/* ---------- Shared keyframes ----------
   Each only says where a move starts. It ends wherever the element normally
   rests, so resting tilts and sizes stay in app.css and desktop.css. */
@keyframes stick-on { from { opacity: 0; scale: 1.3; rotate: -12deg; translate: 0 -16px; } }
@keyframes stick-left { from { opacity: 0; scale: 1.25; rotate: -28deg; translate: -30px -12px; } }
@keyframes stick-right { from { opacity: 0; scale: 1.25; rotate: 28deg; translate: 30px -12px; } }
@keyframes stamp {
  0% { opacity: 0; scale: 2; rotate: -12deg; }
  50% { opacity: 1; scale: 0.9; }
  75% { scale: 1.06; }
}
@keyframes land { from { scale: 1.2; } }
@keyframes fade-up { from { opacity: 0; translate: 0 10px; } }
@keyframes rise { from { opacity: 0; scale: 0.92; translate: 0 26px; } }

/* ---------- Screen changes ----------
   js/motion.js runs each screen change as a view transition. Elements that share a
   view-transition-name morph from the old screen to the new one:
     logo      the home logo shrinks into the header
     pips      the round pips hold still while the screen changes under them
     cta       the main button stays put and swaps its label
     badge     the round badge (it moves to the clue's corner on desktop)
     clue-a/b  the clue card shrinks into the round's clue note (js/game.js sets
               the name, alternating by round, so the next clue arrives as a new sticker)
     sheet     the phone's result sheet, which drops away
   Everything else fades out and rises in. */
.logo-hero, .game-header .logo { view-transition-name: logo; }
.pips { view-transition-name: pips; }
#startBtn, #clueGo, .round:not(.is-reveal) #lockBtn, .round.is-reveal #nextBtn, #shareBtn { view-transition-name: cta; }
.round-badge { view-transition-name: badge; }
/* A screen sitting under How to play keeps out of it. */
.screen[inert] :is(.logo-hero, .logo, .pips, .btn, .round-badge, .clue-card, .clue-note, .result-sheet) { view-transition-name: none !important; }
/* The transition is the entrance now, so the screens' own fade and the badge's pop stand down. */
html.vt .screen:not(.howto), html.vt .clue-stage .round-badge { animation: none; }

::view-transition { pointer-events: none; }   /* the page under it still can't be touched: js/game.js hands a tap on */
::view-transition-group(*) { animation-duration: 360ms; animation-timing-function: var(--ease-out); }
::view-transition-group(cta) { animation-timing-function: var(--ease-spring); }
::view-transition-old(root) { animation: vt-out 120ms linear both; mix-blend-mode: normal; }
::view-transition-new(root) { animation: vt-in 280ms var(--ease-out) both; mix-blend-mode: normal; }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; transform: translateY(10px); } }
@keyframes vt-fade-in { from { opacity: 0; } }

/* The card's words and the note's are set at different sizes, so swap them early in the morph instead of ghosting one over the other the whole way. */
::view-transition-old(clue-a), ::view-transition-old(clue-b) { animation-duration: 140ms; }
::view-transition-new(clue-a), ::view-transition-new(clue-b) { animation-duration: 180ms; }
/* A clue with nothing to morph from sticks on. One with nowhere to go peels away. */
::view-transition-new(clue-a):only-child,
::view-transition-new(clue-b):only-child { animation: vt-stick var(--dur-stick) var(--ease-bounce) 60ms both, vt-fade-in 140ms linear 60ms both; }
::view-transition-old(clue-a):only-child,
::view-transition-old(clue-b):only-child { animation: vt-peel 180ms ease-in both; }
@keyframes vt-stick { from { transform: translateY(-22px) rotate(-5deg) scale(1.1); } }
@keyframes vt-peel { to { opacity: 0; transform: translateY(-14px) rotate(4deg) scale(0.94); } }

/* The round badge is stamped onto a new clue. It stays on top of the clue the whole way:
   a next clue is new to the transition, so it would otherwise be layered above the badge. */
::view-transition-group(badge) { z-index: 1; }
::view-transition-new(badge):only-child { animation: vt-stamp 420ms var(--ease-out) 220ms both; }
::view-transition-old(badge):only-child { animation: vt-pop-out 150ms ease-in both; }
@keyframes vt-stamp {
  0% { opacity: 0; transform: scale(1.9) rotate(-16deg); }
  55% { opacity: 1; transform: scale(0.92); }
  78% { transform: scale(1.05); }
}
@keyframes vt-pop-out { to { opacity: 0; transform: scale(0.6); } }

/* Phones: the result sheet drops away under the next clue. (Desktop has no sheet.) */
@media (max-width: 1023.98px), (pointer: coarse), (pointer: none) {
  .round.is-reveal .result-sheet { view-transition-name: sheet; }
}
::view-transition-old(sheet):only-child { animation: vt-drop 260ms cubic-bezier(.5, 0, .9, .4) both; }
@keyframes vt-drop { to { transform: translateY(105%); } }

/* ---------- 1 · Home ----------
   On opening (js/game.js adds .is-intro): the logo sticks on, then the pitch and
   the buttons follow. The Start arrow keeps nudging toward the game. */
.home.is-intro .logo-hero { animation: stick-on 560ms var(--ease-bounce) 80ms backwards; }
.home.is-intro .tagline { animation: fade-up 360ms var(--ease-out) 340ms backwards; }
.home.is-intro .tagline-sub { animation: fade-up 360ms var(--ease-out) 420ms backwards; }
.home.is-intro #startBtn { animation: rise 520ms var(--ease-settle) 500ms backwards; }
.home.is-intro #howBtn { animation: fade-up 320ms var(--ease-out) 680ms backwards; }
.home.is-intro .home-foot { animation: fade-up 320ms var(--ease-out) 800ms backwards; }
#startBtn svg { animation: nudge 2.2s var(--ease-out) 1.6s infinite; }
@keyframes nudge {
  0%, 55%, 100% { translate: 0 0; }
  70% { translate: 6px 0; }
  82% { translate: -1px 0; }
}
/* The loose stickers drop on one by one (--n is each one's turn), then drift. */
.home-sticker:nth-child(2) { --n: 1; }
.home-sticker:nth-child(3) { --n: 2; }
.home-sticker:nth-child(4) { --n: 3; }
.home-sticker:nth-child(5) { --n: 4; }
.home-sticker:nth-child(6) { --n: 5; }
.home-sticker { animation: drift 5.2s ease-in-out calc(var(--n, 0) * -1.3s) infinite alternate; }
.home-sticker:nth-child(even) { animation-duration: 6.4s; }
.home.is-intro .home-sticker {
  animation:
    sticker-drop 620ms var(--ease-bounce) calc(var(--n, 0) * 80ms + 160ms) backwards,
    drift 5.2s ease-in-out calc(var(--n, 0) * -1.3s) infinite alternate;
}
@keyframes sticker-drop { from { opacity: 0; scale: 1.5; rotate: 0deg; } }
@keyframes drift { from { translate: 0 5px; } to { translate: 0 -7px; } }

/* ---------- 2 · How to play: the steps arrive in order ---------- */
.howto .howto-head { animation: fade-up 300ms var(--ease-out) 60ms backwards; }
.howto .steps li { animation: fade-up 320ms var(--ease-out) 120ms backwards; }
.howto .steps li:nth-child(2) { animation-delay: 190ms; }
.howto .steps li:nth-child(3) { animation-delay: 260ms; }
/* The scoring example plays a round in miniature. The board patch is stuck on and a
   swatch is picked. Then the reveal's patch arrives (--at is when its answer shows):
   the answer pops, the points ripple out a ring at a time, and the score is stamped
   on. The answer's dashes keep marching afterward, like the real ones. */
.howto-body { overflow-x: hidden; }   /* the stickers start oversized, which would reach past a phone's edge and let the page scroll sideways */
.howto .mini-board { animation: stick-on var(--dur-stick) var(--ease-bounce) 450ms backwards; }
.howto .mini-board:not(.is-revealed) .is-pick { animation: mini-pick 360ms var(--ease-spring) 950ms backwards; }
/* The arrow draws itself from the pick toward the reveal, shaft then head. */
.howto .demo-arrow path { stroke-dasharray: 1 2; }
.howto .demo-arrow .shaft { animation: draw-in 380ms var(--ease-out) 1150ms backwards; }
.howto .demo-arrow .head { animation: draw-in 160ms var(--ease-out) 1500ms backwards; }
.howto .mini-board.is-revealed { --at: 1900ms; animation-delay: 1400ms; }
.howto .is-revealed .mini-chip {
  --delay: calc(var(--at) + var(--n, 0) * var(--dur-ring) * 2);
  animation: mini-bump 340ms var(--ease-out) var(--delay);
}
.howto .is-revealed .mini-chip[data-pts="2"] { --n: 1; }
.howto .is-revealed .mini-chip[data-pts="1"] { --n: 2; }
.howto .is-revealed .mini-chip[data-pts="3"] { z-index: 1; animation: mini-answer 520ms var(--ease-out) var(--delay); }
.howto .is-revealed .mini-chip::before { animation: fade-in 160ms linear var(--delay) backwards; }
/* The 1s fade back as their ring lands, like the real reveal. */
.howto .is-revealed .mini-chip[data-pts="1"]:not(.is-pick) {
  animation: mini-bump 340ms var(--ease-out) var(--delay), mini-fade 240ms linear var(--delay) backwards;
}
.howto .mini-marks rect { transform-box: fill-box; transform-origin: center; }
.howto .mini-marks .ring.is-inner { animation: ring-out 320ms var(--ease-out) calc(var(--at) + var(--dur-ring) * 2) backwards; }
.howto .mini-marks .ring:not(.is-inner) { animation: ring-out 320ms var(--ease-out) calc(var(--at) + var(--dur-ring) * 4) backwards; }
.howto .mini-marks .casing { animation: mini-ring-in 320ms var(--ease-spring) var(--at) backwards; }
.howto .mini-marks .answer {
  animation:
    mini-ring-in 320ms var(--ease-spring) var(--at) backwards,
    mini-march 900ms linear infinite;
}
.howto .mini-score { animation: stamp 440ms var(--ease-out) calc(var(--at) + 420ms) backwards; }
/* Then the score scale is stamped on, smallest first. The gold star then rocks gently
   around its tilt, and a glint of light keeps running through its gradient: each stop
   brightens in turn, top left to bottom right. The stamp starts smaller than the usual
   one: at twice the size, Chrome kept the overflow and How to play scrolled on a tall phone. */
.howto .score-scale .badge { animation: scale-stamp 440ms var(--ease-out) calc(2600ms + var(--n, 0) * 140ms) backwards; }
.howto .score-scale .badge:nth-child(2) { --n: 1; }
.howto .score-scale .badge:nth-child(3) {
  --n: 2;
  animation:
    scale-stamp 440ms var(--ease-out) calc(2600ms + var(--n) * 140ms) backwards,
    rock 3200ms ease-in-out 3300ms infinite;
}
.howto .scale-hint { animation: fade-up 320ms var(--ease-out) 3150ms backwards; }
@keyframes scale-stamp {
  0% { opacity: 0; scale: 1.4; rotate: -12deg; }
  50% { opacity: 1; scale: 0.9; }
  75% { scale: 1.06; }
}
@keyframes rock {
  0%, 100% { rotate: var(--tilt); }
  25% { rotate: calc(var(--tilt) - 5deg); }
  75% { rotate: calc(var(--tilt) + 5deg); }
}
/* The gradient lives in the page's defs, so it only runs while How to play is open. */
body:has(.howto:not([hidden])) #medal-gold-glint stop { animation: glint 1600ms ease-in-out calc(3300ms + var(--n, 0) * 50ms) infinite; }
#medal-gold-glint stop:nth-child(2) { --n: 1; }
#medal-gold-glint stop:nth-child(3) { --n: 2; }
#medal-gold-glint stop:nth-child(4) { --n: 3; }
#medal-gold-glint stop:nth-child(5) { --n: 4; }
#medal-gold-glint stop:nth-child(6) { --n: 5; }
@keyframes glint {
  0%, 13%, 100% { stop-color: var(--base); }
  6.5% { stop-color: color-mix(in oklab, var(--base) 45%, #fff); }
}
/* Starts as a plain chip: no ring, no tilt. The shadow list matches .mini-chip.is-pick's, so the ring grows on. */
@keyframes mini-pick {
  from {
    rotate: 0deg;
    box-shadow:
      inset 0 -2px 0 rgb(0 0 0 / 22%), inset 0 1px 0 rgb(255 255 255 / 26%),
      0 0 0 0 var(--color-surface), 0 0 0 0 var(--color-selection), 0 0 0 rgb(0 0 0 / 0%);
  }
  45% { scale: 1.3; }
}
@keyframes draw-in { from { stroke-dashoffset: 1; opacity: 0; } 1% { opacity: 1; } }
@keyframes mini-fade { from { opacity: 1; } }
@keyframes mini-bump { 40% { scale: 1.14; } }
@keyframes mini-answer { 35% { scale: 1.45; } 70% { scale: 0.95; } }
@keyframes mini-ring-in { from { opacity: 0; scale: 1.5; } }
@keyframes mini-march { to { stroke-dashoffset: -6.087; } }   /* one dash and gap (css/app.css) */

/* ---------- 2b · Loading ----------
   js/motion.js moves the swatches (playLoad). Here, only the words arrive. */
.load-label { animation: fade-up 360ms var(--ease-out) 250ms both; }
.load-hint { animation: fade-up 360ms var(--ease-out) 500ms both; }

/* ---------- 3 · Clue: read out a word at a time ---------- */
.clue-card .word {
  display: inline-block;
  animation: word-in 300ms var(--ease-out) calc(var(--i) * 26ms + 200ms) backwards;
}
@keyframes word-in { from { opacity: 0; translate: 0 8px; } }
.clue .hint { animation: fade-in 260ms linear 420ms backwards; }

/* Primary buttons: the arrow leans in on hover. */
.btn-primary svg { transition: translate 160ms var(--ease-out); }
@media (hover: hover) {
  .btn-primary:hover:not(:disabled) svg:last-child { translate: 4px 0; }
}

/* ---------- Pips: the round just played pops in as its badge ---------- */
.pip.is-played.is-new { animation: pip-pop 480ms var(--ease-bounce) 280ms backwards; }
.pip.is-current.is-new { animation: pop-in var(--dur-pop) var(--ease-spring) 420ms backwards; }
@keyframes pip-pop { from { scale: 0; } }

/* ---------- 4 to 6 · Board and picking ----------
   Chips sit where --x and --y put them, so their keyframes restate the whole
   transform rather than using the separate scale property (which would scale the
   position too). */
/* A new round: the board blooms from left to right, a column at a time. js/lens.js gives each chip its --wave delay. */
.field.is-entering .chip { animation: chip-in 320ms var(--ease-spring) calc(var(--wave) + 120ms) both; }
@keyframes chip-in { from { opacity: 0; transform: translate(var(--x), var(--y)) scale(0.3); } }
/* The bloom outlasts the screen change (360ms), and a phone freezes for a few frames
   while it takes a view transition down, which used to land mid-bloom. So this screen
   change stays up until the last chip is in (120ms + the longest --wave, 224ms + 320ms,
   and a few frames to spare) and the freeze falls where nothing is moving. The root
   group never moves, so holding it longer changes nothing on screen. */
html:has(.field.is-entering)::view-transition-group(root) { animation-duration: 740ms; }
/* The zoom tip comes in with the board: it fades in and rises into place at full size, just
   after the bloom has passed the middle columns it hangs under. This takes over from
   app.css's delayed fade, which would hold it small while it arrives. */
.zoom-tip.is-on { transition: none; animation: tip-in 360ms var(--ease-out) 300ms backwards; }
@keyframes tip-in { from { opacity: 0; translate: -50% calc(100% + 10px); } }
/* Picking a chip at full zoom: a quick pop as it tilts. */
.lens[data-level="2"] .field:not(.is-revealed) .chip.is-selected { animation: chip-pick 300ms var(--ease-out); }
@keyframes chip-pick { 45% { transform: translate(var(--x), var(--y)) rotate(var(--tilt, 0deg)) scale(1.18); } }
/* The first pick wakes the lock button. */
.btn.wake { animation: wake var(--dur-stick) var(--ease-bounce); }
.btn.wake svg { animation: jiggle 460ms ease-in-out; }
@keyframes wake { from { scale: 0.94; } }
@keyframes jiggle { 30% { rotate: -16deg; } 60% { rotate: 11deg; } }

/* ---------- 7 · Reveal ----------
   In order: the board settles (--dur-zoom), the pick is stamped, the connector
   travels from the pick to the answer (--lead), the answer pops (--answer-at), and
   the scoring rings ripple out from it (--delay, --dur-ring apart). js/lens.js sets
   the timings and adds .is-live only for a fresh reveal, never when a finished
   round is reopened. */
.field.is-live.is-revealed .chip[data-pts] { animation: ring-bump 340ms var(--ease-out) var(--delay); }
.field.is-live.is-revealed .chip[data-pts]::before { animation: fade-in 160ms linear var(--delay) backwards; }
.field.is-live.is-revealed .chip.is-selected { animation: chip-stamp 320ms var(--ease-out) var(--dur-zoom); }
.field.is-live.is-revealed .chip.is-selected[data-pts] {
  animation: chip-stamp 320ms var(--ease-out) var(--dur-zoom), ring-bump 340ms var(--ease-out) var(--delay);
}
.field.is-live.is-revealed .chip.is-target {
  animation:
    answer-pop 520ms var(--ease-out) var(--delay),
    answer-pulse 1.8s var(--ease-out) calc(var(--delay) + 420ms) infinite;
}
@keyframes ring-bump { 40% { transform: translate(var(--x), var(--y)) rotate(var(--tilt, 0deg)) scale(1.14); } }
@keyframes chip-stamp {
  35% { transform: translate(var(--x), var(--y)) rotate(var(--tilt, 0deg)) scale(0.82); }
  70% { transform: translate(var(--x), var(--y)) rotate(var(--tilt, 0deg)) scale(1.06); }
}
@keyframes answer-pop {
  35% { transform: translate(var(--x), var(--y)) rotate(var(--tilt, 0deg)) scale(1.45); }
  70% { transform: translate(var(--x), var(--y)) rotate(var(--tilt, 0deg)) scale(0.95); }
}
/* The ring outlines ripple out from the answer, each one landing with its ring of chips. */
.field.is-live .score-rings.is-drawing { transition: none; }
.field.is-live .score-rings.is-drawing rect {
  transform-box: fill-box;
  transform-origin: center;
  animation: ring-out 320ms var(--ease-out) calc(var(--answer-at) + var(--n) * var(--dur-ring)) backwards;
}
@keyframes ring-out { from { opacity: 0; scale: 0.92; } }
/* The connector draws itself from the pick to the answer, a dot landing at each end. */
.field.is-live .miss-line.is-drawing { transition: none; }
.field.is-live .is-drawing path {
  stroke-dasharray: 1 3;   /* the path's length is set to 1, so this is one dash the length of the line */
  animation: line-draw var(--lead) var(--ease-out) var(--dur-zoom) backwards;
}
.field.is-live .is-drawing circle {
  transform-box: fill-box;
  transform-origin: center;
  animation: dot-in 240ms var(--ease-spring) backwards;
}
.field.is-live .is-drawing .at-pick { animation-delay: calc(var(--dur-zoom) - 60ms); }
.field.is-live .is-drawing .at-answer { animation-delay: var(--answer-at); }
@keyframes line-draw { from { stroke-dashoffset: 1; } }
@keyframes dot-in { from { scale: 0; } }

/* The result: the two swatches are stuck on from either side, the score is stamped
   between them on the X, then counts up through the medals (js/game.js) and lands.
   --sheet-at, --score-at, and --copy-at come from js/game.js. */
.round.is-live .result-sheet .tilt-left { animation: stick-left var(--dur-stick) var(--ease-bounce) calc(var(--score-at) - 200ms) backwards; }
.round.is-live .result-sheet .tilt-right { animation: stick-right var(--dur-stick) var(--ease-bounce) calc(var(--score-at) - 110ms) backwards; }
.round.is-live .result-sheet .stamp-label { animation: fade-in 200ms linear var(--score-at) backwards; }
.round.is-live .result-sheet .score-sticker { animation: stamp 440ms var(--ease-out) var(--score-at) backwards; }
.round.is-live .result-sheet .score-sticker.is-landed { animation: land 420ms var(--ease-bounce); }
.round.is-live .result-sheet .score-sticker.is-shrug { animation: shrug 560ms ease-in-out; }
.round.is-live .result-copy { animation: fade-up 280ms var(--ease-out) var(--copy-at) backwards; }
.score-sticker .badge.tick { animation: tick 180ms var(--ease-spring); }
@keyframes tick { from { scale: 1.4; } }
/* Gold and silver shimmer: once the count settles, a band of light sweeps across the star
   every few seconds (app.css parks it off the left edge). Bronze has no band. */
.score-sticker .badge-shine { animation: shine 2800ms ease-in-out 700ms infinite; }
@keyframes shine {
  from { transform: translateX(-70px) skewX(-20deg); }
  40%, to { transform: translateX(140px) skewX(-20deg); }
}
@keyframes shrug { 25% { rotate: -7deg; } 60% { rotate: 13deg; } }

/* ---------- 12 · Day complete ----------
   Right after the last round (.is-fresh): the total counts up (js/game.js), the
   rounds deal in, and the share grid fills a square at a time. */
.done.is-fresh .results li { animation: row-in 380ms var(--ease-settle) calc(var(--i) * 70ms + 200ms) backwards; }
.done.is-fresh .result-swatch { animation: stick-on var(--dur-stick) var(--ease-bounce) calc(var(--i) * 70ms + 260ms) backwards; }
.done.is-fresh .result-points { animation: pop-in var(--dur-pop) var(--ease-spring) calc(var(--i) * 70ms + 400ms) backwards; }
.done.is-fresh .everyone { animation: fade-up 320ms var(--ease-out) 300ms backwards; }
/* Opening "How everyone scored": the rows step in and their bars grow from the baseline. */
.everyone[open] .everyone-rounds li { animation: fade-up 260ms var(--ease-out) calc(var(--i) * 40ms) backwards; }
.everyone[open] .everyone-rounds .everyone-bars i { transform-origin: bottom; animation: column-grow 420ms var(--ease-settle) calc(var(--i) * 40ms + 60ms) backwards; }
.done.is-fresh .share-card { animation: stick-on var(--dur-stick) var(--ease-bounce) 640ms backwards; }
.done.is-fresh .share-grid i { animation: pop-in 260ms var(--ease-spring) calc(var(--n) * 22ms + 820ms) backwards; }
.done.is-fresh .countdown, .done.is-fresh .text-btn { animation: fade-in 260ms linear 1000ms backwards; }
.done-total.is-landed { animation: land 420ms var(--ease-bounce); }
.share-card.is-copied { animation: wobble 460ms ease-in-out; }
@keyframes row-in { from { opacity: 0; translate: 0 14px; } }
@keyframes column-grow { from { scale: 1 0; } }
@keyframes wobble { 25% { rotate: -6deg; scale: 1.03; } 60% { rotate: 1deg; } }

/* ---------- Confetti (js/motion.js): tiny swatch stickers ---------- */
.confetti { position: absolute; inset: 0; z-index: 30; overflow: hidden; pointer-events: none; }
.confetti i {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--size);
  height: var(--size);
  border: 2px solid var(--color-surface);
  border-radius: 30%;
  background: var(--c);
  box-shadow: 0 2px 4px rgb(0 0 0 / 14%);
  will-change: transform;
}

}
