/* Motion: the loading throbber, page turns and content arriving. docs/DESIGN.md → Motion.
   Everything here stands down under reduced motion (the global rule in app.css stops the loops; the page turn
   becomes a plain swap). */

/* ---- the throbber: the countdown dial, sweeping ------------------------------------------------- */
@property --sweep { syntax: "<angle>"; inherits: true; initial-value: 0deg; }
.throbber { display: inline-flex; align-items: center; gap: var(--s3); color: var(--ink); }
.tdial {
  --tsize: 44px; position: relative; flex: none; width: var(--tsize); height: var(--tsize); border-radius: 50%;
  background: conic-gradient(var(--topic) var(--sweep), transparent 0) var(--paper-2);
  box-shadow: inset 0 0 0 2px var(--ink), 2px 2px 0 var(--ink);
  animation: sweep-round 1.5s cubic-bezier(.55, .1, .35, 1) infinite;
}
.tdial svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.tdial .tt { stroke: var(--ink); stroke-width: 1.6; stroke-linecap: round; fill: none; }
.tdial .th { stroke: var(--ink); stroke-width: 2.6; stroke-linecap: round; transform-origin: 24px 24px; transform: rotate(var(--sweep)); }
.tdial .hub { fill: var(--ink); }
/* One full sweep, a beat's pause at twelve o'clock, then round again: a clock, not a spinner. */
@keyframes sweep-round { 0% { --sweep: 0deg; } 82% { --sweep: 360deg; } 100% { --sweep: 360deg; } }
.tlabel { font: italic 600 1rem/1.2 var(--display); color: var(--ink-2); }
.tlabel::after { content: ""; display: inline-block; width: 1.1em; text-align: left; animation: ellipsis 1.5s steps(4) infinite; }
@keyframes ellipsis { 0% { content: ""; } 25% { content: "."; } 50% { content: ".."; } 75% { content: "..."; } }
.throbber.sm .tdial { --tsize: 18px; box-shadow: inset 0 0 0 1.5px currentColor; background: conic-gradient(currentColor var(--sweep), transparent 0); }
.throbber.sm .tt { display: none; }
.throbber.sm .th, .throbber.sm .hub { display: none; }
.throbber.lg .tdial { --tsize: 72px; box-shadow: inset 0 0 0 2.5px var(--ink), 3px 3px 0 var(--ink); }
.throbber.lg .tlabel { font-size: 1.2rem; }
.stage .tdial, .party .tdial { background-color: var(--stage-2); }

/* The route loader: shown only when a page takes more than a moment (app.js), as a pill under the top edge. */
.route-loader {
  position: fixed; left: 50%; top: max(14px, env(safe-area-inset-top)); z-index: 45; translate: -50% 0;
  padding: 6px 14px 6px 6px; border: 1.5px solid var(--ink); border-radius: 999px; background: var(--paper-2); box-shadow: var(--press);
  animation: drop .25s var(--ease-out) both; pointer-events: none;
}
.route-loader .tdial { --tsize: 30px; box-shadow: inset 0 0 0 1.5px var(--ink); }
.route-loader .tlabel { font-size: .9rem; }
/* First paint, before any script runs (index.html). */
.boot { min-height: 100dvh; display: grid; place-items: center; align-content: center; gap: var(--s4); }
.boot .logo { font-size: 2.2rem; }

/* Busy buttons: the label stays, a small dial sweeps beside it. */
button[aria-busy="true"] { position: relative; }
button[aria-busy="true"]::before {
  content: ""; width: 1em; height: 1em; border-radius: 50%; flex: none; margin-right: .1em;
  background: conic-gradient(currentColor var(--sweep), transparent 0); box-shadow: inset 0 0 0 1.5px currentColor;
  animation: sweep-round 1.2s cubic-bezier(.55, .1, .35, 1) infinite;
}

/* Skeletons breathe instead of sitting there (a slow ink-in, not a shimmer). */
.skeleton { animation: breathe 1.6s ease-in-out infinite; }
@keyframes breathe { 50% { opacity: .3; } }

/* ---- page turns (View Transitions; app.js sets data-nav) --------------------------------------- */
nav.tabs { view-transition-name: tabs; }
.shell > main { view-transition-name: page; }
::view-transition-group(tabs) { animation: none; }
::view-transition-old(root), ::view-transition-new(root) { animation: none; }
/* Deeper: the new page comes in from the right over the old one, which steps back a little. */
/* The old page clears almost at once and the new one follows a beat later, so the two never sit on top of each
   other as a double exposure. */
::view-transition-old(page) { animation: .16s cubic-bezier(.4, 0, 1, 1) both page-back; }
::view-transition-new(page) { animation: .34s var(--ease-out) .07s both page-in-right; }
/* Back: the reverse. */
:root[data-nav="back"]::view-transition-old(page) { animation: .16s cubic-bezier(.4, 0, 1, 1) both page-out-right; }
:root[data-nav="back"]::view-transition-new(page) { animation: .34s var(--ease-out) .07s both page-in-left; }
/* Between tabs: no direction, just a quick cross-fade with a small lift. */
:root[data-nav="tab"]::view-transition-old(page) { animation: .1s ease-in both fade-out; }
:root[data-nav="tab"]::view-transition-new(page) { animation: .26s var(--ease-out) .1s both lift-in; }
@keyframes page-back { to { opacity: 0; transform: translateX(-4%) scale(.99); } }
@keyframes page-in-right { from { opacity: 0; transform: translateX(9%); } }
@keyframes page-out-right { to { opacity: 0; transform: translateX(6%); } }
@keyframes page-in-left { from { opacity: 0; transform: translateX(-6%) scale(.985); } }
@keyframes fade-out { to { opacity: 0; } }
@keyframes lift-in { from { opacity: 0; transform: translateY(10px); } }
@media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; } }
:root[data-motion="reduce"]::view-transition-group(*), :root[data-motion="reduce"]::view-transition-old(*), :root[data-motion="reduce"]::view-transition-new(*) { animation: none !important; }

/* Content arriving after a fetch (app.js marks the new blocks): a short rise, staggered by --i. */
.arrive { animation: arrive .38s var(--ease-out) both; animation-delay: calc(var(--i, 0) * 45ms); }
@keyframes arrive { from { opacity: 0; transform: translateY(10px); } }
