/*
 * joinus.games
 *
 * THE :root BLOCK IS TRANSCRIBED FROM `src/theme/tokens.ts` AND IS CHECKED
 * AGAINST IT. `site/site.test.ts` imports the app's tokens and fails if any
 * colour or type step here disagrees -- the site and the app are one brand, and
 * "same house, louder room" is the UX Direction's phrase for it. Do not tune a
 * value here; tune the token and re-run the test.
 *
 * The site cannot import the app's stylesheet because the app has none: its
 * styles are React Native `StyleSheet` objects. See docs/plans/homepage-plan.md
 * section 1.3 for why this page redraws rather than shares.
 */

:root {
  /* color, from src/theme/tokens.ts */
  --color-bg: #12101a;
  --color-surface: #1e1b2b;
  --color-surface-raised: #2a2540;
  --color-ink: #f6f4ff;
  --color-ink-muted: #a29fbb;
  --color-accent: #ffb020;
  --color-accent-ink: #1a1305;
  --color-correct: #3dd68c;
  --color-wrong: #ff6b6b;
  --color-double: #c77dff;
  --color-border: #332e47;
  --color-border-strong: #7a7396;

  /* type, from src/theme/tokens.ts */
  --type-display-size: 40px;
  --type-display-line: 48px;
  --type-title-size: 28px;
  --type-title-line: 34px;
  --type-heading-size: 22px;
  --type-heading-line: 28px;
  --type-body-size: 16px;
  --type-body-line: 26px;
  --type-caption-size: 13px;
  --type-caption-line: 18px;

  /* space, radius and the touch target, same source */
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 12px;
  --space-base: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-xxl: 48px;
  --space-xxxl: 64px;
  --space-gutter: 20px;
  --radius-card: 12px;
  --radius-pill: 999px;
  --radius-sheet: 16px;
  --touch-min: 44px;
  --duration-state: 200ms;

  /* The site's own reading column. Wider than the app's 560pt handset column:
     this is a web page read on a laptop, not a screen inside a phone chassis. */
  --column-quiet: 680px;
  --column-wide: 1120px;

  --font-display: 'Bricolage Grotesque', 'Trebuchet MS', 'Segoe UI', sans-serif;
  --font-body: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

/*
 * Self-hosted, and that is a requirement rather than a preference: the approved
 * privacy policy tells the visitor this page asks nothing of another service,
 * and Google Fonts would be two hosts it does not name (PRD criterion 18).
 * Subset by `site/fonts.mjs`; see its header.
 *
 * `swap` on purpose -- the text is readable from the first paint and the fallback
 * stacks above are sized so the swap does not push the page down.
 */
@font-face {
  font-family: 'Bricolage Grotesque';
  src: url('fonts/BricolageGrotesque_800ExtraBold.woff2') format('woff2');
  font-weight: 800;
  font-display: swap;
}

@font-face {
  font-family: 'Bricolage Grotesque';
  src: url('fonts/BricolageGrotesque_600SemiBold.woff2') format('woff2');
  font-weight: 600;
  font-display: swap;
}

@font-face {
  font-family: Inter;
  src: url('fonts/Inter_400Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: Inter;
  src: url('fonts/Inter_500Medium.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* Never hijacked, per the design spec -- this is the only scroll behaviour the
     site sets, and it applies to the quiet pages' contents links. */
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
  -webkit-text-size-adjust: 100%;
}

a {
  color: var(--color-ink);
}

/* Underlined, never colour alone -- docs/standards/design.md forbids colour as
   the only carrier of meaning, and a link is meaning. */
main a {
  text-decoration: underline;
  text-underline-offset: 2px;
}

:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: var(--space-xs);
}

img {
  max-width: 100%;
}

/* ---------------------------------------------------------------- chrome
 *
 * REPEATED VERBATIM IN EVERY PAGE, between the BEGIN/END chrome markers, and
 * `site.test.ts` asserts the copies are byte-identical. There is no templating
 * step and that is deliberate (plan section 1.2); B049 records what it costs and
 * what would change the answer.
 */

.chrome {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-base);
  padding: var(--space-md) var(--space-gutter);
  border-bottom: 1px solid var(--color-border);
  background: var(--color-bg);
}

.chrome-mark {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--type-heading-size);
  line-height: var(--type-heading-line);
  text-decoration: none;
  letter-spacing: -0.01em;
}

.chrome-mark .us {
  color: var(--color-accent);
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch-min);
  padding: 0 var(--space-lg);
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--type-body-size);
  text-decoration: none;
  transition: transform var(--duration-state) ease;
}

.button:active {
  transform: translateY(2px);
}

.button-primary {
  background: var(--color-accent);
  color: var(--color-accent-ink);
}

.button-outline {
  border: 1px solid var(--color-border-strong);
  color: var(--color-ink);
}

.button-compact {
  min-height: var(--touch-min);
  padding: 0 var(--space-base);
  font-size: var(--type-caption-size);
}

.site-footer {
  border-top: 1px solid var(--color-border);
  padding: var(--space-xl) var(--space-gutter) var(--space-xxl);
}

.site-footer nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm) var(--space-md);
}

.site-footer a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-min);
  padding: 0 var(--space-xs);
  color: var(--color-ink-muted);
  font-size: var(--type-caption-size);
  line-height: var(--type-caption-line);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.site-footer .dot {
  color: var(--color-border-strong);
}

/* ------------------------------------------------------------ quiet pages
 *
 * S-HOME2 and S-HOME3. One reading column, no motion at all.
 */

.quiet {
  max-width: var(--column-quiet);
  margin: 0 auto;
  padding: var(--space-xxl) var(--space-gutter);
}

.quiet h1 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--type-title-size);
  line-height: var(--type-title-line);
}

.quiet h2 {
  margin: var(--space-xl) 0 var(--space-sm);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--type-heading-size);
  line-height: var(--type-heading-line);
}

.quiet p,
.quiet li {
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
}

.quiet .date {
  margin-top: var(--space-sm);
  color: var(--color-ink-muted);
  font-size: var(--type-caption-size);
  line-height: var(--type-caption-line);
}

/* A table is the one thing on these pages that cannot reflow, so it scrolls in
   its own container rather than shrinking its type (design spec, S-HOME2). */
.quiet .table-scroll {
  overflow-x: auto;
}

.quiet table {
  border-collapse: collapse;
  min-width: 480px;
}

.quiet th,
.quiet td {
  border-bottom: 1px solid var(--color-border);
  padding: var(--space-sm) var(--space-md);
  text-align: left;
  vertical-align: top;
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
}

.back {
  display: inline-block;
  margin-top: var(--space-xxl);
}

@media (min-width: 600px) {
  .quiet {
    padding-inline: var(--space-xl);
  }
}

/* ------------------------------------------------------------------- hero
 *
 * S-HOME1. Designed at 360pt: the characters, the wordmark, the line and
 * **Play now** all fit the first 640pt of a phone.
 */

.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: var(--space-xl) var(--space-gutter) var(--space-xxl);
}

/* THE TRIO. Each outer character tucks about a fifth of its width behind the
   middle one, which is drawn last so it sits in front, and is raised a little
   so the group reads as a huddle rather than a row. */
.trio {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  margin-bottom: var(--space-lg);
}

/* `flex: none` because a flex item shrinks by default, and these did: every
   width below was being quietly reduced to fit, so the characters came out
   smaller than asked for and their fixed overlap no longer covered a fifth of
   them. They looked like a row standing apart rather than a huddle. */
.ch-slot {
  flex: none;
  width: 96px;
  display: flex;
  justify-content: center;
}

/* The bob's wrapper is a flex item too, so it needed its own width -- without
   it the drawing inside sized to the SVG's own 100 units and every breakpoint
   below did nothing at all. */
.ch-bob {
  display: block;
  flex: none;
  width: 100%;
}

.ch-slot svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

/* Left: smaller, leaning out of the group about its feet, and 3px further out.
   The lean is on the slot rather than the drawing so the tail tucks behind the
   middle one rather than swinging in front of it. */
.ch-0 {
  width: 72px;
  margin-right: -18px;
  transform: translate(-3px, -6px) rotate(-14deg);
  transform-origin: 50% 100%;
}

/* Middle: in front, and raised. */
.ch-1 {
  position: relative;
  z-index: 1;
  transform: translateY(-8px);
}

/* Right: behind, and upright -- it already stands on its point. */
.ch-2 {
  margin-left: -18px;
}

.wordmark {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 64px;
  line-height: 68px;
  letter-spacing: -0.02em;
}

.wordmark .us {
  color: var(--color-accent);
}

.tagline {
  margin: 0;
  color: var(--color-ink-muted);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
}

.hero-line {
  max-width: 22ch;
  margin: var(--space-lg) 0 var(--space-xl);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--type-heading-size);
  line-height: var(--type-heading-line);
}

.button-hero {
  min-width: 220px;
  font-size: var(--type-heading-size);
  min-height: 56px;
}

/* THE OVERLAP SCALES WITH THE CHARACTERS. Each outer one tucks about a fifth of
   its width behind the middle, and a fixed -18px held that only at phone size --
   at 176px they stood apart like a row rather than a huddle. Owner-reported. */
@media (min-width: 600px) {
  .ch-slot {
    width: 148px;
  }
  .ch-0 {
    width: 111px;
    margin-right: -28px;
  }
  .ch-2 {
    margin-left: -28px;
  }
  .hero-line {
    max-width: 28ch;
  }
}

@media (min-width: 1024px) {
  .wordmark {
    font-size: 120px;
    line-height: 120px;
  }
  .ch-slot {
    width: 224px;
  }
  .ch-0 {
    width: 168px;
    margin-right: -42px;
    transform: translate(-5px, -10px) rotate(-14deg);
  }
  .ch-1 {
    transform: translateY(-14px);
  }
  .ch-2 {
    margin-left: -42px;
  }
}

@media (max-width: 340px) {
  .wordmark {
    font-size: 56px;
    line-height: 60px;
  }
}

/* --------------------------------------------------------- the hero's motion
 *
 * EVERY ANIMATION ON THIS SITE IS SWITCHED OFF IN ONE PLACE, at the bottom of
 * this file, and `site.test.ts` fails if a new one is not covered there.
 *
 * Nothing here runs until `site.js` adds `.motion` to the root, and nothing
 * here carries information: the resting state below IS the picture.
 */

/* THE BOB RUNS A WHOLE NUMBER OF CYCLES AND ENDS AT REST.
 *
 * It used to be `infinite` with the class pulled off after five seconds, and
 * that is a stop mid-air: the element snapped back to zero wherever it happened
 * to be. Owner-reported. A finite count ends on the keyframe it started from, so
 * the group settles instead of dropping -- and WCAG 2.2.2's five seconds becomes
 * a property of the CSS rather than a promise the script has to keep.
 *
 * Three periods that do not divide into each other, so the three never sync.
 */
.motion .trio.alive .ch-0 .ch-bob {
  animation: bob 1.4s ease-in-out 3;
}

.motion .trio.alive .ch-1 .ch-bob {
  animation: bob 1.2s ease-in-out 4;
}

.motion .trio.alive .ch-2 .ch-bob {
  animation: bob 1.6s ease-in-out 3;
}

@keyframes bob {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-5px);
  }
}

/* The teardrop leans a further 6 degrees out and back, once, as the page opens.
   ON THE SVG, while the bob is on the wrapper: two `animation` declarations on
   one element means the loser never runs. */
.motion .ch-0 svg {
  animation: lean-out 1.4s ease-in-out 0.2s 1;
  transform-origin: 50% 100%;
}

@keyframes lean-out {
  0%,
  100% {
    rotate: 0deg;
  }
  45% {
    rotate: -6deg;
  }
}

/* ------------------------------------------------------------- the header
 *
 * Over the hero it is out of the way; past it, it slides down and holds the
 * mark and a compact Play now.
 */

/* ONLY THE COVER PAGE HIDES ITS HEADER. The quiet pages are read top to bottom
   and their header is simply always there, in the flow -- a store reviewer
   landing straight on /privacy should not have to scroll to find the way out.
   So this is scoped to `.home` rather than applied to `.chrome` everywhere. */
.home .chrome {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 10;
  background: rgb(18 16 26 / 92%);
  backdrop-filter: blur(6px);
}

.motion .home .chrome,
.home.motion .chrome {
  transform: translateY(-100%);
  transition: transform var(--duration-state) ease;
}

.motion .home.past-hero .chrome,
.motion.past-hero .home .chrome {
  transform: translateY(0);
}

/* The header only joins the tab order while it is on screen. */
.motion:not(.past-hero) .home .chrome {
  visibility: hidden;
}

@media (prefers-reduced-transparency: reduce) {
  .home .chrome {
    background: var(--color-bg);
    backdrop-filter: none;
  }
}

/* ------------------------------------------------- the hero, side by side
 *
 * Desktop puts the trio at 1.5x beside the wordmark and the line, rather than
 * above them (design spec, Responsive Behavior).
 */

@media (min-width: 1024px) {
  .hero {
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: var(--space-xxl);
    max-width: var(--column-wide);
    margin: 0 auto;
    text-align: left;
  }

  .hero .trio {
    margin-bottom: 0;
    flex-shrink: 0;
  }

  .hero-words {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }

  .hero-line {
    max-width: 18ch;
  }
}

/* ------------------------------------------------------- reduced motion
 *
 * ONE BLOCK, AND IT COVERS EVERY ANIMATION ON THE SITE. Every scene and every
 * idle motion rests on its final frame, so this only has to stop things --
 * nothing here has to put anything back.
 */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }

  .home .chrome {
    transform: none;
    visibility: visible;
  }
}

/* ------------------------------------------------- Pick your poison
 *
 * One stage per public game. The words are generated from the registry
 * (`site/build.mjs`); the colours are here, because the alternative is an inline
 * `style` attribute and `_headers` forbids inline styles on purpose.
 *
 * EACH GAME'S ACCENT IS THE REGISTRY'S. `site.test.ts` reads both and fails if
 * they differ, so a retuned accent in the app is a failing test rather than two
 * products that disagree about what colour a game is.
 */

.stage[data-game='singularity'] {
  --accent: #39ff14;
}

.stage[data-game='drift'] {
  --accent: #5ac8fa;
}

.stage[data-game='dead_giveaway'] {
  --accent: #ffb020;
}

.stage[data-game='okey'] {
  --accent: #3dd68c;
}

.stage[data-game='smear'] {
  --accent: #c77dff;
}

.stage[data-game='yacht'] {
  --accent: #f2e7d5;
}

.stage[data-game='frontier'] {
  --accent: #d9734a;
}

.games {
  max-width: var(--column-wide);
  margin: 0 auto;
  padding: var(--space-xl) var(--space-gutter) var(--space-xxl);
}

.games-heading {
  margin: 0 0 var(--space-lg);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--type-title-size);
  line-height: var(--type-title-line);
  text-align: center;
}

.stages {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

/* A `surface` panel with the game's accent as a rail down its left edge. The
   EDGE stays `borderStrong` and the accent goes in the rail -- the front door's
   card rule, so a stage and a dashboard card read as the same object. */
.stage {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--color-border-strong);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius-card);
  background: var(--color-surface);
}

/* The scene's box. The drawing inside it rests at its FINAL FRAME -- the CSS
   below moves pieces in from elsewhere, never out -- so the picture is whole
   before any motion, with reduced motion on, and with the script off. */
/* THE SCENE'S GROUND IS THE STAGE'S OWN `surface`, not `surface-raised`. The
   spec draws the stage as a `surface` panel and the pieces on it as
   `surface-raised`; having both be `surface-raised` made three story cards
   invisible against the ground they were lying on. */
.scene {
  height: 240px;
  padding: var(--space-md);
  background: var(--color-surface);
}

.scene .art {
  display: block;
  width: 100%;
  height: 100%;
}

.scene .faint {
  opacity: 0.45;
}

.stage-words {
  padding: var(--space-lg);
}

.stage-name {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--type-title-size);
  line-height: var(--type-title-line);
}

/* On `surface`, where registry.test.ts already measures every accent at 4.5:1
   or better. Nowhere else on this page does an accent carry text. */
.stage-tagline {
  margin: var(--space-xs) 0 0;
  color: var(--accent);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
}

.stage-line {
  margin: var(--space-md) 0 0;
  color: var(--color-ink);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
}

.stage-meta {
  margin: var(--space-sm) 0 var(--space-lg);
  color: var(--color-ink-muted);
  font-size: var(--type-caption-size);
  line-height: var(--type-caption-line);
}

.stage .button-outline {
  border-color: var(--accent);
}

@media (min-width: 1024px) {
  .scene {
    height: 320px;
  }

  /* Two columns, scene and words, alternating sides from one game to the next
     (design spec, Responsive Behavior). */
  .stage {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: center;
  }

  .stage .scene {
    height: 100%;
    min-height: 320px;
  }

  .stage:nth-child(even) .scene {
    order: 2;
  }

  .stage-words {
    padding: var(--space-xl);
  }
}

/* ------------------------------------------------------------- the scenes
 *
 * EVERY RULE HERE MOVES A PIECE *TO* WHERE IT ALREADY IS. The scene's resting
 * state is its final frame, so each keyframe starts somewhere else and ends at
 * `none`. Nothing is animated out of view, which is what makes the picture whole
 * before the motion, under reduced motion, and with the script off.
 *
 * A scene plays when its stage is 40% in view -- `site.js` adds `.seen` -- and
 * plays once. It replays only if the stage leaves the viewport entirely.
 */

/* The screen's glass: a glow on dark, not a lamp. The glyphs are the accent, so
   the wash has to stay faint enough for them to read against it. */
.sg-wash {
  opacity: 0.14;
}

.stage-words > * {
  /* The words rise and fade in; their resting state is where they belong. */
  animation-fill-mode: both;
}

/* A SCENE MAY NOT SHOW ITS ENDING AND THEN START OVER.
 *
 * The resting state is the final frame, which is what makes the picture whole
 * without motion -- but with motion on it meant the finished picture appeared
 * on load and then snapped back to begin. Owner-reported.
 *
 * So a scene armed and not yet played is simply not there, and its own pieces
 * bring it in. `armed` comes from the script and only where there is an
 * observer to reveal them, so no-script and reduced-motion still show the frame.
 */
.armed .stage:not(.seen) .scene,
.armed .stage:not(.seen) .stage-words > * {
  opacity: 0;
}

.motion .stage.seen .stage-words > * {
  animation: rise 200ms ease-out both;
}

.motion .stage.seen .stage-words > :nth-child(1) {
  animation-delay: 60ms;
}
.motion .stage.seen .stage-words > :nth-child(2) {
  animation-delay: 120ms;
}
.motion .stage.seen .stage-words > :nth-child(3) {
  animation-delay: 180ms;
}
.motion .stage.seen .stage-words > :nth-child(4) {
  animation-delay: 240ms;
}
.motion .stage.seen .stage-words > :nth-child(5) {
  animation-delay: 300ms;
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
}

/* -- Dead Giveaway: dealt in a fan, the middle one turned, three faces watching */
.motion .stage.seen .dg-card {
  animation: deal 900ms cubic-bezier(0.2, 0.8, 0.3, 1) both;
}
.motion .stage.seen .dg-1 {
  animation:
    deal 900ms cubic-bezier(0.2, 0.8, 0.3, 1) 150ms both,
    turn 600ms ease-in-out 1000ms both;
}
.motion .stage.seen .dg-2 {
  animation-delay: 300ms;
}
.motion .stage.seen .dg-face {
  animation: peek 500ms ease-out 1500ms both;
}

@keyframes deal {
  from {
    opacity: 0;
    translate: -120px 40px;
  }
}
@keyframes turn {
  50% {
    transform: scaleX(0.02);
  }
}
@keyframes peek {
  from {
    opacity: 0;
    translate: 0 34px;
  }
}

/* -- Okey: tiles slide in from the right and click into a run, the last from above */
.motion .stage.seen .ok-tile {
  animation: slide-in 500ms cubic-bezier(0.2, 0.9, 0.3, 1) both;
}
.motion .stage.seen .ok-1 {
  animation-delay: 180ms;
}
.motion .stage.seen .ok-2 {
  animation: drop-in 520ms cubic-bezier(0.3, 1.5, 0.5, 1) 420ms both;
}

@keyframes slide-in {
  from {
    opacity: 0;
    translate: 150px 0;
  }
}
@keyframes drop-in {
  from {
    opacity: 0;
    translate: 0 -90px;
  }
}

/* -- Drift: a scribble draws itself, then a second sheet redraws it */
.dr-ink {
  stroke-dasharray: 1;
}
.motion .stage.seen .dr-cat {
  animation: draw 1100ms ease-in-out both;
}
.motion .stage.seen .dr-b {
  animation: slide-in 600ms ease-out 1200ms both;
}
.motion .stage.seen .dr-moon {
  animation: draw 900ms ease-in-out 1700ms both;
}
.motion .stage.seen .dr-flag {
  animation: draw 500ms ease-in-out 2600ms both;
}

@keyframes draw {
  from {
    stroke-dashoffset: 1;
  }
  to {
    stroke-dashoffset: 0;
  }
}

/* -- Smear: three cards fan out of the stack, and the spade slaps back down */
.motion .stage.seen .sm-card {
  animation: fan 600ms cubic-bezier(0.2, 0.9, 0.3, 1) both;
}
.motion .stage.seen .sm-1 {
  animation:
    fan 600ms cubic-bezier(0.2, 0.9, 0.3, 1) 120ms both,
    slap 420ms cubic-bezier(0.3, 0, 0.6, 1.6) 900ms both;
}
.motion .stage.seen .sm-2 {
  animation-delay: 240ms;
}

@keyframes fan {
  from {
    opacity: 0;
    translate: -46px 22px;
    rotate: -18deg;
  }
}
@keyframes slap {
  40% {
    translate: 0 -26px;
  }
}

/* -- Singularity: the glass warms up (twice, never three times), then it looks at you */
.motion .stage.seen .sg-wash {
  animation: warm-up 700ms steps(1, end) both;
}
.motion .stage.seen .sg-eye {
  animation: open-eye 500ms ease-out 800ms both;
  transform-origin: center;
}
.motion .stage.seen .sg-eye-r {
  animation-delay: 950ms;
}
.motion .stage.seen .sg-type {
  animation: type 900ms steps(15, end) 1500ms both;
  clip-path: inset(0 0 0 0);
}

/* WCAG 2.3.1: two flashes, and never a third in any second. */
@keyframes warm-up {
  0%,
  40% {
    opacity: 0;
  }
  20%,
  60% {
    opacity: 0.14;
  }
}
@keyframes open-eye {
  from {
    scale: 1 0.05;
  }
}
@keyframes type {
  from {
    clip-path: inset(0 100% 0 0);
  }
}

/* -- Yacht: five dice tumble in, the last landing a beat late */
.motion .stage.seen .yc-die {
  animation: tumble 600ms cubic-bezier(0.2, 0.9, 0.3, 1) both;
}
.motion .stage.seen .yc-1 {
  animation-delay: 140ms;
}
.motion .stage.seen .yc-2 {
  animation-delay: 280ms;
}
.motion .stage.seen .yc-3 {
  animation-delay: 420ms;
}
.motion .stage.seen .yc-4 {
  animation-delay: 900ms;
}

@keyframes tumble {
  from {
    opacity: 0;
    translate: -130px -40px;
    rotate: -160deg;
  }
}

/* -- Frontier: three hexes click into an island, one road, one keep */
.motion .stage.seen .fr-hex {
  animation: settle 520ms cubic-bezier(0.3, 1.4, 0.5, 1) both;
  transform-origin: center;
}
.motion .stage.seen .fr-1 {
  animation-delay: 180ms;
}
.motion .stage.seen .fr-2 {
  animation-delay: 360ms;
}
.motion .stage.seen .fr-road {
  stroke-dasharray: 1;
  animation: draw 500ms ease-out 900ms both;
}
.motion .stage.seen .fr-keep {
  animation: drop-in 460ms cubic-bezier(0.3, 1.5, 0.5, 1) 1500ms both;
}

@keyframes settle {
  from {
    opacity: 0;
    translate: 0 -40px;
    scale: 0.85;
  }
}

/* ----------------------------------------------------------- S-HOME4
 *
 * Somewhere you arrive by accident. The scene is Drift's final frame and it
 * never plays -- `.seen` is never added here, because there is no stage to
 * observe. A not-found page should not perform.
 */

.lost {
  text-align: center;
}

.lost-scene {
  height: 200px;
  border-radius: var(--radius-card);
  margin-bottom: var(--space-lg);
}

.lost h1 {
  margin-bottom: var(--space-xl);
}

/* ------------------------------------------------------- the closing band
 *
 * The last thing on the page, and the second place **Play now** appears -- the
 * spec repeats it so a visitor who read to the bottom does not have to scroll
 * back up to act.
 *
 * NO STORE ROW IS BUILT. PRD criterion 4: a badge appears only once a store
 * lists the app, and none does. site/README.md holds the block to paste.
 */

.band {
  position: relative;
  margin-top: var(--space-xxl);
  padding: 0 var(--space-gutter) var(--space-xxl);
  background: var(--color-surface-raised);
  text-align: center;
}

/* Heads over a sofa: the group sits on the band's top edge, half above it. */
.band-crew {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  margin-top: -56px;
  padding-bottom: var(--space-lg);
}

.band-crew .ch-slot {
  width: 80px;
}

.band-crew .ch-0 {
  width: 60px;
  margin-right: -15px;
}

.band-crew .ch-2 {
  margin-left: -15px;
}

.band-heading {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--type-title-size);
  line-height: var(--type-title-line);
}

.band-line {
  margin: var(--space-sm) 0 var(--space-lg);
  color: var(--color-ink-muted);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
}

@media (min-width: 600px) {
  .band-crew {
    margin-top: -76px;
  }

  .band-crew .ch-slot {
    width: 108px;
  }

  .band-crew .ch-0 {
    width: 81px;
    margin-right: -20px;
  }

  .band-crew .ch-2 {
    margin-left: -20px;
  }
}
