/*
 * SlavBot.com: motion. One language for the whole site, written down once here.
 *
 * It is the poster's: fast, hard-edged, and only where it explains or rewards (an arrival, a state change, a result).
 *   slam    a block arrives from an offset with a hard ease-out, 120 to 280 ms, at most a hair of overshoot
 *   wipe    a reveal along the diagonal cut (clip-path), on small things: a headline line, a bar, a toast
 *   stamp   scale 1.12 to 1 with a 2 degree settle: a result, a state that just changed
 *   tick    a number rolling to its value (motion.js)
 *   press   a notched button shifts 2px while it is held
 * Not here, on purpose: blur, glow, floating shapes, parallax, typewriter text, confetti, a bounce on everything, and any loop
 * (the ticker in site.css is the one loop the site has).
 *
 * The rules this file keeps (test/motion.test.js pins most of them):
 *   - only transform and opacity animate; clip-path animates on small elements only. Nothing that moves the layout.
 *   - the durations and easings are the custom properties below. A raw time or cubic-bezier outside this block is a bug.
 *   - nothing is hidden until a script has run: the only hidden states are the classes motion.js adds (.pre, .counting),
 *     and what a CSS animation starts from lasts a few hundred milliseconds and ends in the final state by itself.
 *   - prefers-reduced-motion: reduce means no motion at all: the last block of this file, plus motion.js, which then does nothing.
 */

:root {
  --m-quick: 120ms; /* a press, a hover, a mark that confirms */
  --m-slam: 220ms; /* a block arriving */
  --m-wipe: 280ms; /* a reveal along the cut */
  --m-count: 700ms; /* a number rolling to its value (motion.js reads this one too) */
  --m-step: 50ms; /* the gap between neighbours that arrive one after another */
  --m-out: cubic-bezier(0.2, 0.8, 0.2, 1); /* fast in, hard stop */
  --m-over: cubic-bezier(0.2, 0.9, 0.3, 1.1); /* the same with a hair of overshoot */
}

/* ---------- the shapes ---------- */

/* The wipe's leading edge is the poster's diagonal, not a straight line. */
@keyframes m-wipe {
  from {
    clip-path: polygon(0 0, 0 0, -14% 100%, 0 100%);
  }

  to {
    clip-path: polygon(0 0, 114% 0, 100% 100%, 0 100%);
  }
}

@keyframes m-up {
  from {
    opacity: 0;
    transform: translateY(16px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes m-left {
  from {
    opacity: 0;
    transform: translateX(-18px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* Large pictures and blocks move but never fade: a fade would hold back the page's largest paint. */
@keyframes m-from-right {
  from {
    transform: translateX(9%);
  }

  to {
    transform: none;
  }
}

@keyframes m-from-left {
  from {
    transform: translate(-22%, 10%);
  }

  to {
    transform: none;
  }
}

@keyframes m-from-below {
  from {
    translate: 0 7%;
  }

  to {
    translate: 0 0;
  }
}

@keyframes m-stamp {
  0% {
    transform: scale(1.12) rotate(-2deg);
  }

  60% {
    transform: scale(0.985) rotate(0.6deg);
  }

  100% {
    transform: none;
  }
}

@keyframes m-toast {
  from {
    opacity: 0;
    transform: translate(-50%, 18px);
    clip-path: polygon(0 0, 0 0, -24px 100%, 0 100%);
  }

  to {
    opacity: 1;
    transform: translate(-50%, 0);
    clip-path: polygon(0 0, calc(100% + 24px) 0, 100% 100%, 0 100%);
  }
}

/* ---------- on load: the home page, the page headers, the gate pages ---------- */

.hero h1 span,
.soon h1 span,
.top h1,
.unlock h1 {
  width: fit-content;
  animation: m-wipe var(--m-wipe) var(--m-out) backwards;
}

.hero h1 span:nth-child(2),
.soon h1 span:nth-child(2) {
  animation-delay: var(--m-step);
}

.hero h1 span:nth-child(3) {
  animation-delay: calc(var(--m-step) * 2);
}

.hero .tag,
.soon .tag,
.top .tag,
.unlock .tag {
  animation: m-left var(--m-slam) var(--m-out) backwards;
}

.hero .lede,
.soon .lede,
.top .lede {
  animation: m-up var(--m-slam) var(--m-out) calc(var(--m-step) * 3) backwards;
}

.hero .actions,
.top .actions,
.unlock .actions {
  animation: m-up var(--m-slam) var(--m-out) calc(var(--m-step) * 4) backwards;
}

/* The hero's blocks slam into place one after another. The girl uses the translate property because her transform is taken. */
.sun {
  animation: m-from-right var(--m-wipe) var(--m-out) backwards;
}

.wedge {
  animation: m-from-left var(--m-wipe) var(--m-out) var(--m-step) backwards;
}

.girl {
  animation: m-from-below var(--m-wipe) var(--m-out) calc(var(--m-step) * 2) backwards;
}

.top-art .disc {
  animation: m-from-right var(--m-wipe) var(--m-out) backwards;
}

.top-art img,
.top .crest,
.games-girl {
  animation: m-from-below var(--m-wipe) var(--m-out) var(--m-step) backwards;
}

/* A wrong password lands as a stamp. */
.gate-error {
  animation: m-stamp var(--m-slam) var(--m-over) backwards;
}

/* ---------- on scroll: motion.js adds .pre to what is still below the fold, and .in when it arrives ---------- */

.pre {
  opacity: 0;
}

.pre.fx-up {
  transform: translateY(18px);
}

.pre.fx-left {
  transform: translateX(-28px);
}

/* Not everything rises: some blocks slam in from the side they read from, some from the other side. */
.pre.fx-right {
  transform: translateX(28px);
}

.pre.in {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--m-slam) var(--m-out),
    transform var(--m-slam) var(--m-out);
}

.pre.in.d1 {
  transition-delay: var(--m-step);
}

.pre.in.d2 {
  transition-delay: calc(var(--m-step) * 2);
}

.pre.in.d3 {
  transition-delay: calc(var(--m-step) * 3);
}

/* ---------- numbers: motion.js wraps a big number so it can roll without moving anything ---------- */

/* The real number stays in the page, final, all the time. While it counts, the rolling one is drawn over it in a box that is
   zero wide, so neither the page nor the number's own box can move. */
.cu {
  display: inline-grid;
  justify-items: end;
}

.cu > * {
  grid-area: 1 / 1;
}

.cu-live {
  display: none;
  width: 0;
  direction: rtl;
  white-space: nowrap;
}

.cu.counting .cu-live {
  display: block;
}

.cu.counting .cu-real {
  opacity: 0;
}

/* The map's counter takes a stamp each time the number changes. */
.counter {
  --origin: 0 50%;
}

.counter b.stamp,
[data-medal].stamp {
  animation: m-stamp var(--m-slam) var(--m-over);
  transform-origin: var(--origin, 50% 50%);
}

@media (min-width: 900px) {
  .counter {
    --origin: 100% 50%;
  }
}

/* A country taken on the map pops. A pin is small, so it pops harder. */
.map .land.claimed,
.map .pin.claimed {
  transform-box: fill-box;
  transform-origin: 50% 50%;
}

.map .land.claimed {
  animation: m-claim-land var(--m-slam) var(--m-over);
}

.map .pin.claimed {
  animation: m-claim-pin var(--m-slam) var(--m-over);
}

@keyframes m-claim-land {
  from {
    transform: scale(1.12);
  }

  to {
    transform: none;
  }
}

@keyframes m-claim-pin {
  from {
    transform: scale(2.2);
  }

  to {
    transform: none;
  }
}

/* ---------- buttons, rows and small controls ---------- */

/* A row of "Five bots you can kick" on hover: the struck-out bot and what Slav does instead step in from either side. Made of
   transforms, so nothing in the layout moves. */
.row > .row-bot,
.row > .row-body {
  transition: transform var(--m-quick) var(--m-out);
}

.row:hover > .row-bot,
.row:focus-visible > .row-bot {
  transform: translateX(16px);
}

.row:hover > .row-body,
.row:focus-visible > .row-body {
  transform: translateX(-12px);
}

/* The gold strike is drawn across each struck-out bot as its row arrives: a wipe from the left, in the row's own time. */
.pre .row-bot::after {
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.pre.in .row-bot::after {
  transform: none;
  transition: transform var(--m-slam) var(--m-out);
}

/* Colours change at once, as a hard cut (site.css fades them for 0.15 s); only what moves is eased. */
.btn,
.cta {
  transition: transform var(--m-quick) var(--m-out);
}

.strike,
.soon-link {
  transition: none;
}

.btn::after {
  transition: transform var(--m-quick) var(--m-out);
}

.btn:not(:disabled):active,
.cta:active,
.tab:active,
.perm-btn:active,
.try:active,
.copy:active,
.toc a:active,
.nav a:active {
  transform: translate(2px, 2px);
}

/* A copy chip confirms: it is stamped and its label flips to gold. motion.js adds .copied for a moment. */
.try.copied,
.copy.copied {
  animation: m-stamp var(--m-slam) var(--m-over);
}

.try.copied::after {
  background: var(--gold);
  color: var(--ink);
}

.copy.copied {
  background: var(--gold);
  border-color: var(--ink);
  color: var(--ink);
}

/* The toast slams up with a wipe along its edge. The fade out stays a short fade. */
.toast {
  transition:
    opacity var(--m-quick) var(--m-out),
    transform var(--m-quick) var(--m-out);
}

.toast.on {
  animation: m-toast var(--m-slam) var(--m-over) backwards;
}

/* ---------- the pieces that change when you use them ---------- */

.poster[open] > .poster-body,
.faq details[open] > p,
.fold[open] > .fold-body {
  animation: m-up var(--m-slam) var(--m-out) backwards;
}

/* The rarity ladder is drawn bar by bar when its poster opens. */
.poster[open] .rarity li {
  animation: m-wipe var(--m-wipe) var(--m-out) backwards;
}

.poster[open] .rarity li:nth-child(2) {
  animation-delay: var(--m-step);
}

.poster[open] .rarity li:nth-child(3) {
  animation-delay: calc(var(--m-step) * 2);
}

.poster[open] .rarity li:nth-child(4) {
  animation-delay: calc(var(--m-step) * 3);
}

.poster[open] .rarity li:nth-child(5) {
  animation-delay: calc(var(--m-step) * 4);
}

.poster[open] .rarity li:nth-child(6) {
  animation-delay: calc(var(--m-step) * 5);
}

/* The permission explainer draws the answer again each time you pick a permission. */
.explain-detail > * {
  animation: m-up var(--m-quick) var(--m-out) backwards;
}

.explain-detail > :nth-child(n + 2) {
  animation-delay: calc(var(--m-step) * 0.5);
}

.explain-detail > :nth-child(n + 4) {
  animation-delay: var(--m-step);
}

.strike.on {
  animation: m-stamp var(--m-slam) var(--m-over);
}

/* ---------- from page to page ---------- */

/* A cross-document view transition, for the browsers that have one: the page cross-fades fast and the header stays where it is.
   A browser without it just changes page. Off for anyone who asked for less motion (the last block below says so again). */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }

  .bar {
    view-transition-name: bar;
  }

  /* The default is a cross-dissolve; only its length and its easing are the site's. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: var(--m-slam);
    animation-timing-function: var(--m-out);
  }
}

/* ---------- print, and anyone who asked for no motion ---------- */

@media print {
  .pre {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* No motion at all: every animation and transition off, so each thing stands in its final state at once. The pseudo-elements
   are named too, because * does not match them. motion.js reads the same query and then does nothing. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after,
  ::view-transition-group(*),
  ::view-transition-image-pair(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
    transition: none !important;
  }
}
