/* stoke.to — embers under a night sky. Tokens first; everything derives.
   No control chrome exists: the card is the interface (see app.ts). */

:root {
  --night: #10141f;
  --night-2: #1a2030;
  --ember: #ff9d5c;
  --ember-deep: #e2622b;
  --ash: #9aa3b5;
  --paper: #f2ede4;
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  /* Hold progress, written by app.ts; the card warms as the ember gathers. */
  --gather: 0;
}

* {
  box-sizing: border-box;
  margin: 0;
}

html,
body {
  height: 100%;
}

body {
  background: radial-gradient(120% 90% at 50% 110%, var(--night-2) 0%, var(--night) 60%);
  color: var(--paper);
  font-family: var(--sans);
  overflow: hidden;
}

/* The world's container: the pool engine mounts its stage canvas here, and the
   glory overlay is appended on top. Both canvases stack at the top-left origin. */
#sky {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  overflow: hidden;
}

/* The optional bloom overlay (withGlory) rides above the stage canvas, never
   intercepting the gesture that belongs to the card. */
.pool-glory {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.wordmark {
  position: fixed;
  top: 1.25rem;
  left: 1.5rem;
  z-index: 1;
  font-family: var(--serif);
  font-size: 1.05rem;
  letter-spacing: 0.14em;
  color: color-mix(in srgb, var(--ember) 55%, transparent);
  pointer-events: none;
}

/* The whole viewport takes the gesture — there is no smaller target to find. */
.stage {
  position: relative;
  z-index: 1;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 1.5rem;
  touch-action: none;
  cursor: grab;
  -webkit-user-select: none;
  user-select: none;
}

.stage:active {
  cursor: grabbing;
}

/* Law 1: turn.ts drives --turn-rot/--turn-x/--turn-shadow directly, once per
   animation frame — never a CSS transition on transform here, since a
   transition would fight turn.ts's own eased tween instead of letting it
   own the timing (the fixed MIN_TURN_MS pace IS the law). The unset
   defaults (0deg / 0px / none) are a flat, resting page. */
.card {
  max-width: 34rem;
  text-align: center;
  opacity: 0;
  transform: perspective(900px) rotateY(var(--turn-rot, 0deg)) translateX(var(--turn-x, 0px));
  transition: opacity 600ms ease, filter 300ms ease;
  filter: drop-shadow(0 0 calc(var(--gather) * 26px) color-mix(in srgb, var(--ember) 60%, transparent));
  box-shadow: var(--turn-shadow, none);
}

.card.lit {
  opacity: 1;
}

/* While the finger is down the card tracks it 1:1 — no easing in the way. */
.card.dragging {
  transition: filter 300ms ease;
}

/* Arrival hint: the card drifts once, teaching the gesture without a word. */
.card.hinting {
  animation: drift 900ms ease-in-out;
}

@keyframes drift {
  0%,
  100% {
    transform: none;
  }
  50% {
    transform: translateX(-14px);
  }
}

/* Taken: one flare as the ember leaves with you. */
.card.taken {
  animation: taken 700ms ease-out;
}

@keyframes taken {
  0% {
    filter: drop-shadow(0 0 0 transparent);
  }
  30% {
    filter: drop-shadow(0 0 30px color-mix(in srgb, var(--ember) 85%, transparent));
    transform: translateY(-6px) scale(1.015);
  }
  100% {
    filter: drop-shadow(0 0 0 transparent);
    transform: none;
  }
}

/* The ember sank back: the hold was heard, but nothing left with you. */
.card.sank {
  animation: sank 700ms ease-out;
}

@keyframes sank {
  0%,
  100% {
    filter: drop-shadow(0 0 0 transparent);
    opacity: 1;
  }
  40% {
    filter: drop-shadow(0 0 12px color-mix(in srgb, var(--ember-deep) 40%, transparent));
    opacity: 0.72;
    transform: translateY(3px);
  }
}

/* Law 5, the memory palace: keeping a quote collapses the card toward the very
   sky point its star lands at (--keep-dx/--keep-dy, written by app.ts from
   world.landingPoint), released as the star over 480ms, then the class is
   dropped (app.ts's animationend) and the kept card fades back via .card's own
   opacity transition — still readable, now with its star overhead. Recall from
   the sky is the opposite and instant (paint({instant}), no animation at all).
   ease-in so the words gather speed as they're drawn up into the sky. */
.card.keeping {
  animation: keeping 480ms ease-in forwards;
}

@keyframes keeping {
  0% {
    transform: perspective(900px) translate(0, 0) scale(1);
    opacity: 1;
  }
  100% {
    transform: perspective(900px) translate(var(--keep-dx, 0px), var(--keep-dy, 0px)) scale(0.12);
    opacity: 0;
  }
}

/* The one word on the card that answers to touch: it tunes the trail to its
   own kind. Styled as part of the writing, never as a control. */
.category {
  display: inline-block;
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ember-deep);
  margin-bottom: 1.25rem;
  cursor: pointer;
  transition: color 300ms ease, text-shadow 300ms ease;
}

.category:hover {
  color: var(--ember);
}

/* Tuned: this kind is lit, and the trail holds to it. */
.category.tuned {
  color: var(--ember);
  text-shadow: 0 0 14px color-mix(in srgb, var(--ember) 65%, transparent);
}

/* Kept: these words already have a star in your sky, on top of the same
   material shadow every quote carries (see .quote below and tilt.ts). */
.card.kept .quote {
  text-shadow:
    0 0 26px color-mix(in srgb, var(--ember) 22%, transparent),
    calc(var(--tilt-x, 0.6px)) calc(var(--tilt-y, 0.9px)) 6px rgba(16, 20, 31, 0.45);
}

/* Law 3: the words are material. tilt.ts writes --tilt-x/--tilt-y/--tilt-track
   at most once a frame from the device's orientation, low-pass filtered and
   scaled down for heavier quotes (see cardWeight) — the shadow drifts and the
   letters breathe as the card tilts. Every var default below is the same
   fixed, gentle look tilt.ts itself applies when the device offers nothing to
   read (no sensor, permission not granted, prefers-reduced-motion): the
   degraded state is designed, not an absence, and needs no JS at all to look
   intentional. */
.quote {
  font-family: var(--serif);
  font-size: clamp(1.5rem, 4.5vw, 2.3rem);
  line-height: 1.35;
  font-weight: 400;
  letter-spacing: calc(0em + var(--tilt-track, 0.01em));
  text-shadow: calc(var(--tilt-x, 0.6px)) calc(var(--tilt-y, 0.9px)) 6px rgba(16, 20, 31, 0.45);
}

/* Law 4: each phrase of the quote fades in on its own, a beat after the
   last (see unveil.ts). A kept card re-expanding from its star paints the
   words directly, with no spans at all — instant is its own honest state. */
.phrase {
  display: inline-block;
  opacity: 0;
  transform: translateY(2px);
  transition: opacity 320ms ease-out, transform 320ms ease-out;
}

.phrase.lit {
  opacity: 1;
  transform: translateY(0);
}

.author {
  margin-top: 1.5rem;
  font-size: 0.95rem;
  color: var(--ash);
  font-style: italic;
  opacity: 0;
  transition: opacity 320ms ease-out;
}

.author.lit {
  opacity: 1;
}

/* Present to assistive tech and to the keyboard, invisible to the world. */
.sr-only button {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.sr-only button:focus-visible {
  position: fixed;
  bottom: 1rem;
  left: 50%;
  transform: translateX(-50%);
  width: auto;
  height: auto;
  clip: auto;
  padding: 0.55rem 1.3rem;
  border-radius: 999px;
  border: 1px solid var(--ember);
  background: var(--night);
  color: var(--ember);
  font-family: var(--sans);
  font-size: 0.9rem;
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--ember) 40%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .card,
  .card.hinting,
  .card.taken,
  .card.sank,
  .card.keeping,
  .phrase,
  .author {
    transition: none;
    animation: none;
  }

  /* turn.ts never writes --turn-rot/--turn-x in this mode (progress() is a
     no-op there) and drives a plain opacity crossfade for commit() instead
     — this is belt-and-suspenders so the card is flat even if some other
     path ever left a stale var behind. */
  .card {
    transform: none;
  }
}
