/* Bouncy motion (the springy feel of morphicons.com): a pressed control squashes and springs back with an overshoot, small markers pop
   in, the active tab's icon pops when a screen opens, the toast bounces up. Icons that change meaning (the level card chevron, copy ->
   check) are morphed by morphicons in ui-icons.js. Everything here is transform-only, so nothing moves the layout, and it is switched
   off for people who ask their phone for reduced motion. Loaded last. */
:root { --bounce: cubic-bezier(.34, 1.56, .64, 1); }

/* Press feedback: squash on press, spring back on release. */
:is(button, .primary, .secondary, .soft):not(:disabled) { transition-property: transform; transition-duration: .42s; transition-timing-function: var(--bounce); }
:is(button, .primary, .secondary, .soft):not(:disabled):active { transform: scale(.94); transition-duration: .1s; transition-timing-function: ease-out; }
/* Big tappable cards move less. */
:is(.fr-card, .pn-user, .path-row, .path-row.current, .vocab-list article, details.level-card > summary):not(:disabled):active { transform: scale(.985); }
/* Buttons that already lift or slide themselves keep their own transform. */
.bottom-nav { transition-property: transform; }
#to-top:active, .bottom-nav button:active { transform: scale(.9); }
.tutor-mic:not(:disabled):active { transform: scale(.9); }

/* The active tab's icon pops each time a screen opens. */
@keyframes tab-pop { 0% { transform: scale(.55) rotate(-8deg); } 55% { transform: scale(1.25) rotate(4deg); } 100% { transform: scale(1) rotate(0); } }
#app[data-entering] .bottom-nav button.active .ni { animation: tab-pop .62s var(--bounce); animation-delay: var(--anim-skip, 0ms); }

/* Attention dots pop in. */
@keyframes dot-pop { 0% { transform: scale(0); } 60% { transform: scale(1.5); } 100% { transform: scale(1); } }
/* Only when a screen opens (and carrying on, not restarting, if the screen redraws itself), or when a dot appears while you look. */
#app[data-entering] :is(.nav-badge, .dot-badge) { animation: dot-pop .5s var(--bounce); animation-delay: var(--anim-skip, 0ms); }
.nav-badge.pop, .dot-badge.pop { animation: dot-pop .5s var(--bounce); }

/* The toast bounces up from below. */
@keyframes toast-in { 0% { transform: translate(-50%, 24px) scale(.85); } 100% { transform: translate(-50%, 0) scale(1); } }
.toast { animation: toast-in .5s var(--bounce); }

/* The go-to-top arrow bounces when the button appears, and the admin gear turns with a bounce when pressed. */
@keyframes arrow-hop { 0% { transform: translateY(8px); } 60% { transform: translateY(-4px); } 100% { transform: translateY(0); } }
#to-top.is-visible .ic { animation: arrow-hop .55s var(--bounce); }
.admin-pill .gear { transition: transform .55s var(--bounce); }
.admin-pill:active .gear { transform: rotate(60deg); transition-duration: .12s; }

/* The copy icon and the chevron are morphed in place; give the button a little hop when the icon turns into a check. */
.btn-ic .ic { transition: transform .4s var(--bounce); }
button:active .btn-ic .ic { transform: scale(1.2); }

/* "Something is happening" (busy.js): a thin bar across the very top of the screen while the app waits for the server after a press, and a
   sliding stripe along the bottom edge of the pressed button. Both are overlays, so no label, icon or neighbour moves. */
#busy-bar { position: fixed; z-index: 60; top: env(safe-area-inset-top, 0px); left: 0; right: 0; height: 5px; overflow: hidden; background: #c9c1f7; visibility: hidden; pointer-events: none; }
#busy-bar.is-on { visibility: visible; }
#busy-bar i { display: block; height: 100%; width: 40%; border-radius: 0 4px 4px 0; background: linear-gradient(100deg, var(--landing-violet, #6b65db), var(--landing-sky, #67c9f3)); animation: busy-slide 1.1s ease-in-out infinite; }
@keyframes busy-slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(250%); } }
.is-busy { position: relative; overflow: hidden; pointer-events: none; cursor: progress; }
.is-busy::after { content: ''; position: absolute; left: 0; bottom: 0; width: 40%; height: 4px; background: #6b65db; animation: busy-slide 1.1s ease-in-out infinite; }
:is(.primary, .flow-shell .actions .primary).is-busy::after { background: #fff; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* The "searching for an opponent" dot breathes (transform only). */
@keyframes db-breathe { 0%, 100% { transform: scale(.72); } 50% { transform: scale(1); } }
.db-pulse { animation: db-breathe 1.4s ease-in-out infinite; }
