/* ==========================================================================
   Letterdrop.

   The look is meant to read as a printed puzzle page rather than a game UI:
   warm paper, one serif doing all the heavy lifting, a single accent, and no
   decoration that is not carrying information. Everything that moves, moves
   because it is telling the player something.

   Two design rules are load-bearing rather than cosmetic:

   1. A letter's remaining life is shown twice — by opacity and by the depleting
      rule under the glyph. Colour alone would fail anyone who cannot see the
      fade, and opacity alone goes unreadable before it goes away.
   2. Bank positions are fixed slots. A letter leaving never shifts its
      neighbours, because a tile that moves as you reach for it is the single
      most annoying thing a game like this can do.
   ========================================================================== */

:root {
  --paper: #faf7f2;
  --panel: #fffdfa;
  --tile: #fffdf8;
  --tile-edge: #e6dfd1;
  --ink: #1d1b16;
  --ink-soft: #6d6659;
  --ink-faint: #a9a294;
  --rule: #e4dfd4;
  --accent: #2f6b56;
  --accent-soft: #e8f0eb;
  --warn: #a8453a;
  --warn-soft: #f6e7e4;

  --slot: clamp(23px, 7.4vw, 36px);
  --tile-size: clamp(44px, 13vw, 56px);
  --radius: 10px;
  --lift: 0 1px 0 var(--tile-edge), 0 3px 8px rgba(40, 32, 18, 0.08);
  --lift-high: 0 2px 0 var(--tile-edge), 0 14px 28px rgba(40, 32, 18, 0.18);

  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, ui-serif, serif;
  --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  --enter: 200ms;
  --travel: 280ms;
  --ease: cubic-bezier(0.2, 0.8, 0.25, 1);
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper: #16150f;
    --panel: #1e1d16;
    --tile: #262419;
    --tile-edge: #100f0a;
    --ink: #f2ede2;
    --ink-soft: #a49c8b;
    --ink-faint: #6b6455;
    --rule: #2e2c22;
    --accent: #7cc0a4;
    --accent-soft: #1e2c26;
    --warn: #dd8478;
    --warn-soft: #2f201d;
    --lift: 0 1px 0 var(--tile-edge), 0 3px 8px rgba(0, 0, 0, 0.4);
    --lift-high: 0 2px 0 var(--tile-edge), 0 14px 28px rgba(0, 0, 0, 0.55);
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  -webkit-tap-highlight-color: transparent;
  -webkit-text-size-adjust: 100%;
  overscroll-behavior: none;
}

#app {
  position: relative;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------------------------------------------------------------- views --- */

.view {
  display: none;
  flex: 1;
  padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom));
}

.view.is-active {
  display: flex;
}

.view--centred {
  align-items: center;
  justify-content: center;
}

.view--play {
  flex-direction: column;
  gap: clamp(12px, 2vh, 20px);
  max-width: 860px;
  width: 100%;
  margin: 0 auto;
}

/*
 * The playing surface. Bank and board are centred as a pair in whatever the
 * header leaves behind, with a deliberate gap between them — letters above,
 * words below, and the two visibly part of the same object. Letting each take
 * its own share of a tall window pushes them to opposite ends with a void in
 * between.
 */
.stage {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(28px, 8vh, 76px);
  /* Optical centring. Padding at the foot lifts the whole group above the
     true middle, which is where a composition with a header above it wants to
     sit — and it keeps the words in the lower half without stranding them at
     the bottom of a tall window. */
  padding-bottom: clamp(0px, 9vh, 90px);
}

/* -------------------------------------------------------------- lettering --- */

.wordmark {
  font-family: var(--serif);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  margin: 0;
}

.wordmark--large {
  font-size: clamp(30px, 8vw, 44px);
  letter-spacing: 0.16em;
  line-height: 1.1;
}

.dim {
  color: var(--ink-faint);
}

/* ----------------------------------------------------------------- panel --- */

.panel {
  width: 100%;
  max-width: 460px;
  text-align: center;
}

.tagline {
  font-family: var(--serif);
  font-size: clamp(16px, 4.2vw, 19px);
  line-height: 1.5;
  color: var(--ink);
  margin: 20px 0 0;
}

.rules {
  margin: 28px auto 32px;
  padding: 20px 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  color: var(--ink-soft);
  font-size: 14.5px;
  line-height: 1.65;
  text-align: left;
  max-width: 380px;
}

.rules p {
  margin: 0;
}

.rules p + p {
  margin-top: 12px;
}

.fineprint {
  margin: 20px 0 0;
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
}

/* --------------------------------------------------------------- buttons --- */

.btn {
  appearance: none;
  font: inherit;
  font-size: 15px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  padding: 13px 34px;
  border-radius: 999px;
  border: 1px solid var(--ink);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: background 140ms, color 140ms, transform 140ms var(--ease);
}

.btn:hover {
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}

.btn:active {
  transform: translateY(1px);
}

.btn--primary {
  background: var(--ink);
  color: var(--paper);
}

.btn--primary:hover {
  background: color-mix(in srgb, var(--ink) 85%, var(--paper));
}

.actions {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 30px;
}

.iconbtn {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--ink-faint);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  padding: 8px;
  border-radius: 8px;
  transition: color 140ms;
}

.iconbtn:hover {
  color: var(--ink);
}

.iconbtn[aria-pressed="true"] .iconbtn__glyph {
  /* Muted: the note is struck through rather than swapped for another glyph,
     so the control reads as one thing in two states. */
  text-decoration: line-through;
  opacity: 0.5;
}

:where(button, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* ------------------------------------------------------------------- hud --- */

.hud {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--rule);
  padding-bottom: 12px;
}

.hud .wordmark {
  color: var(--ink);
}

.hud__number {
  font-size: 12px;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.hud__spacer {
  flex: 1;
}

.hud__clock {
  font-family: var(--serif);
  font-size: 19px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  color: var(--ink);
}

/* The last ten seconds. Colour plus weight, never colour alone. */
.hud__clock.is-urgent {
  color: var(--warn);
  font-weight: 700;
}

/* ------------------------------------------------------------------ bank --- */

.bank {
  display: grid;
  grid-template-columns: repeat(var(--slots, 6), var(--tile-size));
  gap: clamp(6px, 2vw, 12px);
  justify-content: center;
  min-height: calc(var(--tile-size) + 10px);
  align-items: start;
}

/* An empty slot holds its ground so nothing shuffles when a letter goes. */
.bank__slot {
  width: var(--tile-size);
  height: var(--tile-size);
}

.tile {
  appearance: none;
  width: var(--tile-size);
  height: var(--tile-size);
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  font-family: var(--serif);
  font-size: calc(var(--tile-size) * 0.46);
  line-height: 1;
  color: var(--ink);
  background: var(--tile);
  border: 1px solid var(--tile-edge);
  border-radius: var(--radius);
  box-shadow: var(--lift);
  cursor: grab;
  /* The tile owns the gesture. Without this a drag scrolls the page instead. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;

  /* --age is written per frame by the render loop, 0 (new) to 1 (gone). Both
     of the urgency channels are derived from it here rather than in JS. */
  --age: 0;
  opacity: calc(1 - var(--age) * 0.55);
  /*
   * `backwards`, never `both`. A forwards-filling animation stays in the
   * cascade after it ends, and its implicit 100% keyframe holds `transform:
   * none` — which silently outranks the lift on .is-selected and the scale on
   * .is-dragging for the rest of the tile's life.
   */
  animation: tile-in var(--enter) var(--ease) backwards;
}

/* The depleting rule: the second, non-colour channel for time left. */
.tile::after {
  content: "";
  position: absolute;
  left: 7px;
  right: 7px;
  bottom: 6px;
  height: 2px;
  border-radius: 2px;
  background: var(--ink);
  opacity: 0.22;
  transform: scaleX(calc(1 - var(--age)));
  transform-origin: left center;
}

.tile:hover {
  border-color: var(--ink-faint);
}

.tile.is-selected {
  border-color: var(--ink);
  box-shadow: var(--lift-high), 0 0 0 2px var(--ink);
  transform: translateY(-3px);
}

/*
 * Out of the layout: in hand, in flight, or on its way out. Fixed positioning
 * is set here and the coordinates come from the measured rect, so the tile
 * does not jump at the moment it is lifted.
 */
.tile.is-free {
  position: fixed;
  z-index: 40;
  margin: 0;
  opacity: 1; /* a tile in hand is not fading */
  animation: none;
  /* Invisible to hit testing, so elementFromPoint reports the word underneath
     rather than the tile the player is holding. Pointer capture keeps the
     events coming regardless. */
  pointer-events: none;
}

.tile.is-dragging {
  cursor: grabbing;
  box-shadow: var(--lift-high);
  transform: scale(1.1);
}

.tile.is-leaving {
  animation: tile-out 200ms var(--ease) both;
  pointer-events: none;
}

.tile.is-rejected {
  animation: tile-reject 160ms ease-in both;
  pointer-events: none;
}

@keyframes tile-in {
  from {
    opacity: 0;
    transform: translateY(-7px) scale(0.94);
  }
}

@keyframes tile-out {
  to {
    opacity: 0;
    transform: translateY(5px) scale(0.9);
  }
}

@keyframes tile-reject {
  to {
    opacity: 0;
    transform: scale(0.7);
  }
}

/* ----------------------------------------------------------------- board --- */

.board {
  display: grid;
  gap: clamp(10px, 2.4vh, 20px) clamp(20px, 4vw, 52px);
  /* Columns hug their words rather than splitting the width. Equal halves put
     CAKE and ICING at opposite ends of the screen with a hole between them. */
  justify-content: center;
  justify-items: center;
  align-content: center;
}

@media (min-width: 760px) {
  :root {
    --slot: 34px;
  }

  .board {
    grid-template-columns: auto auto;
  }
}

.word {
  appearance: none;
  border: 0;
  background: transparent;
  padding: 10px 12px;
  border-radius: 14px;
  display: flex;
  gap: clamp(3px, 1vw, 5px);
  cursor: pointer;
  transition: opacity 200ms, background 160ms, transform 160ms var(--ease);
}

/* Generous target: the whole word takes the letter, not a single position. */
.word.is-target {
  background: var(--accent-soft);
  transform: translateY(-2px);
}

.word.is-locked {
  opacity: 0.4;
  cursor: not-allowed;
}

.word.is-wrong {
  animation: shake 320ms ease-in-out;
}

.word.is-complete {
  cursor: default;
  opacity: 0.62;
}

.word.is-complete .slot {
  border-color: transparent;
  background: transparent;
  color: var(--accent);
}

.word.just-completed {
  animation: complete 420ms var(--ease);
}

@keyframes shake {
  10%, 50%, 90% { transform: translateX(-4px); }
  30%, 70% { transform: translateX(4px); }
}

@keyframes complete {
  40% { transform: scale(1.045); }
}

/* Understated, and after the letters rather than over them. */
.word__check {
  align-self: center;
  margin-left: 6px;
  font-size: calc(var(--slot) * 0.5);
  color: var(--accent);
  opacity: 0;
}

.word.is-complete .word__check {
  opacity: 1;
  transition: opacity 260ms 160ms;
}

/* ------------------------------------------------------------------ slot --- */

.slot {
  width: var(--slot);
  height: calc(var(--slot) * 1.16);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--serif);
  font-size: calc(var(--slot) * 0.58);
  line-height: 1;
  border-radius: 7px;
  border: 1px solid transparent;
  color: var(--ink);
}

/* Hidden: a recessed well with a dot. Graphical, not a Hangman underscore. */
.slot--hidden {
  background: color-mix(in srgb, var(--ink) 5%, transparent);
}

.slot--hidden::after {
  content: "";
  width: calc(var(--slot) * 0.14);
  height: calc(var(--slot) * 0.14);
  border-radius: 50%;
  background: var(--ink);
  opacity: 0.3;
}

/* Filled: a piece sitting on the page. */
.slot--filled {
  background: var(--tile);
  border-color: var(--tile-edge);
  box-shadow: var(--lift);
}

/* Held back until the flying tile lands on it; see board.js. */
.slot.is-landing {
  opacity: 0;
}

.slot.just-filled {
  animation: land 300ms var(--ease);
}

@keyframes land {
  0% {
    transform: scale(0.72);
    opacity: 0;
  }
  60% {
    transform: scale(1.06);
  }
}

/* ---------------------------------------------------------------- result --- */

.verdict {
  font-family: var(--serif);
  font-size: clamp(26px, 7vw, 36px);
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 14px 0 0;
}

.verdict__time {
  font-family: var(--serif);
  font-size: clamp(40px, 13vw, 58px);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  margin: 4px 0 0;
}

.stats {
  margin: 30px 0 0;
  padding: 20px 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  display: grid;
  gap: 9px;
  text-align: left;
  font-size: 14px;
}

.stats > div {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.stats dt {
  color: var(--ink-soft);
}

/* A leader rule, so the eye can run from label to figure. */
.stats > div::before {
  content: "";
  order: 1;
  flex: 1;
  border-bottom: 1px dotted var(--rule);
  transform: translateY(-3px);
}

.stats dd {
  order: 2;
  margin: 0;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.reveal {
  margin-top: 22px;
  text-align: left;
}

.reveal__label {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 8px;
}

.reveal__word {
  font-family: var(--serif);
  font-size: 18px;
  letter-spacing: 0.18em;
  margin: 0;
  color: var(--warn);
}

.theme {
  margin: 22px 0 0;
  font-family: var(--serif);
  font-size: 16px;
  color: var(--ink-soft);
}

.theme strong {
  color: var(--ink);
  letter-spacing: 0.06em;
}

/* ----------------------------------------------------------------- debug --- */

.debug {
  position: fixed;
  right: 12px;
  bottom: 12px;
  z-index: 60;
  width: 268px;
  max-height: min(74dvh, 620px);
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: 12px;
  box-shadow: var(--lift-high);
  font-size: 12px;
  padding: 12px 14px 10px;
}

.debug[hidden] {
  display: none;
}

/*
 * Make room for the panel rather than letting it sit on the board — the one
 * thing a tuning tool must never do is hide the thing being tuned. Only where
 * there is width to give up; on a narrow screen it overlays and the tester can
 * close it.
 */
@media (min-width: 1180px) {
  body.is-tuning .view--play {
    margin-right: 300px;
  }
}

.debug__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: 10.5px;
  color: var(--ink-soft);
  margin-bottom: 10px;
}

.debug__row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 6px 8px;
  margin-bottom: 9px;
}

.debug__row label {
  color: var(--ink-soft);
}

.debug__row output {
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.debug__row input[type="range"] {
  grid-column: 1 / -1;
  width: 100%;
  accent-color: var(--accent);
}

.debug__row--inline {
  grid-template-columns: auto 1fr;
  justify-items: start;
}

.debug select {
  grid-column: 1 / -1;
  width: 100%;
  font: inherit;
  padding: 5px 6px;
  border-radius: 7px;
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--ink);
}

.debug__toggles {
  border-top: 1px solid var(--rule);
  padding-top: 10px;
  display: grid;
  gap: 7px;
}

.debug__toggles label {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ink-soft);
  cursor: pointer;
}

.debug__hint {
  margin: 10px 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--rule);
  color: var(--ink-faint);
  font-size: 11px;
}

kbd {
  font: inherit;
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 0 4px;
}

/* Debug-only board annotation: the answer, ghosted behind the slots. */
.word__answer {
  position: absolute;
  inset: auto 0 -13px;
  font-size: 10px;
  letter-spacing: 0.2em;
  color: var(--ink-faint);
  text-align: center;
  pointer-events: none;
}

.word {
  position: relative;
}

/* ---------------------------------------------------------------- flyer --- */

#flyer {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 30;
}

/* --------------------------------------------------------------- motion --- */

/*
 * The fade is not decoration — it is how the game states a letter's remaining
 * life — so it survives here. What goes is everything that only moves for
 * pleasure: flights, shakes, lifts and pulses.
 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }

  .tile.is-selected,
  .word.is-target {
    transform: none;
  }
}
