/* ==========================================================================
   KACHIPUN TOURNAMENT — stylesheet
   Sections: 1) Tokens  2) Base & reset  3) Backdrop  4) Shell layout
   5) Typography  6) Buttons  7) Loading  8) Lobby  9) Game  10) Ladder tower
   11) Match history  12) Result  13) Motion  14) Responsive  15) Utilities
   ========================================================================== */

/* 1) Tokens -------------------------------------------------------------- */
:root {
  --bg: #0f1117;
  --bg-glow: #1c2240;
  --panel: #1a1d29;
  --panel-2: #232840;
  --panel-3: #262b3f;
  --border: #2a2f45;
  --border-2: #39405f;
  --muted-dot: #3a3f52;

  --text: #e8eaf2;
  --muted: #8b90a5;

  --accent: #ff9500;
  --accent-2: #e60000;
  --win: #26d07c;
  --lose: #ff5c72;
  --draw: #ffcf5c;

  --font-ui: system-ui, -apple-system, sans-serif;
  --font-title: 'MKX Title', var(--font-ui);

  --s1: 0.5rem;
  --s2: 0.75rem;
  --s3: 1rem;
  --s4: 1.5rem;
  --s5: 2rem;

  /* game-screen background (JS picks one per match) */
  --bg-anim: url('/img/bg/pool.webp');
  --bg-static: url('/img/bg/pool-static.webp');
}

/* 2) Base & reset -------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  height: 100%;
  overflow-x: hidden;
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh; /* mobile URL bar: fit the visible viewport */
  overflow-x: hidden;
  display: flex;
  background-color: var(--bg);
  color: var(--text);
  font-family: var(--font-ui);
  -webkit-tap-highlight-color: transparent;
}

/* 3) Backdrop ------------------------------------------------------------
   Pinned to the viewport via position: fixed so an animated game background
   never shifts when content height changes. Replaces background-attachment:
   fixed, which is unreliable and expensive on iOS. */
body::before {
  content: '';
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* Height anchored to the LARGE viewport (toolbar hidden): the backdrop must
     not resize while the mobile URL bar animates, or background-size:cover
     re-fits and the image visibly shifts. 100vh is the pre-lvh fallback. */
  height: 100vh;
  height: 100lvh;
  z-index: -1;
  background: radial-gradient(1200px 600px at 70% -10%, var(--bg-glow), var(--bg));
}

body.in-game::before {
  background-image:
    linear-gradient(rgba(15, 17, 23, 0.55), rgba(15, 17, 23, 0.78)),
    var(--bg-anim);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

@media (prefers-reduced-motion: reduce) {
  body.in-game::before {
    background-image:
      linear-gradient(rgba(15, 17, 23, 0.55), rgba(15, 17, 23, 0.78)),
      var(--bg-static);
  }
}

/* 4) Shell layout --------------------------------------------------------
   main margins to auto center vertically without clipping overflow: when the
   content is taller than the viewport the top stays reachable (no cut-off),
   which removes phantom scrolling caused by align-items:center. */
main {
  width: min(440px, 100vw - 2rem);
  margin: auto;
  padding: var(--s5) 0;
  text-align: center;
}

.view {
  width: 100%;
}

@font-face {
  font-family: 'MKX Title';
  src: url('/MKX-Title.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
  font-display: block;
}

/* 5) Typography ---------------------------------------------------------- */
.mkx-title {
  margin: 0 0 var(--s4);
  font-family: var(--font-title);
  font-size: 2.6rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  line-height: 1.1;
  background: linear-gradient(to bottom, var(--accent), var(--accent-2));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

h2 {
  margin: 0 0 var(--s4);
  color: var(--muted);
  font-weight: 500;
}

/* 6) Buttons ------------------------------------------------------------- */
button {
  padding: 0.9rem 1.5rem;
  font: inherit;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border);
  cursor: pointer;
  transition: transform 0.05s ease, background 0.15s ease, border-color 0.15s ease;
}

button:hover { background: var(--panel-2); border-color: var(--border-2); }
button:active { transform: translateY(1px); }
button:disabled { opacity: 0.35; cursor: default; }
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* A button waiting on a server frame is disabled for the wait, so without this
   it reads as inert; the spinner and the lighter dim say "working". */
button.pending:disabled { opacity: 0.7; cursor: progress; }
button.pending::after {
  content: "";
  display: inline-block;
  width: 0.8em;
  height: 0.8em;
  margin-left: 0.5em;
  vertical-align: -0.1em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}

button.primary {
  background: linear-gradient(120deg, var(--accent), var(--accent-2));
  border: none;
  color: var(--bg);
}
button.primary:hover {
  background: linear-gradient(120deg, var(--accent), var(--accent-2));
  filter: brightness(1.08);
}

.buttons {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

/* 7) Loading ------------------------------------------------------------- */
.spinner {
  width: 42px;
  height: 42px;
  margin: 0 auto var(--s4);
  border: 4px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* 8) Lobby --------------------------------------------------------------- */
/* The raised fist above the title: the tab's icon at landing size, and
   deliberately far larger than the title's own size. An emoji glyph fills only
   part of its em box — roughly three quarters — so a mark set at the title's
   2.6rem reads about level with the wordmark instead of over it, which is why
   this is 5rem rather than something nearer the title's number. The gap below
   is a full `--s3`: the mark is the tallest thing on the screen and the gap
   went out twice by a step each time it was looked at on a real one, from `--s1`
   through `--s2` to here, which is the point at which the fist reads as sitting
   above the wordmark rather than leaning on it. The mark is hidden from the
   accessibility tree (see index.html), so nothing here carries meaning a reader
   needs -- only scale. */
.lobby-fist {
  margin: 0 0 var(--s3);
  font-size: 5rem;
  line-height: 1;
}

.online {
  margin: 0 0 var(--s4);
  color: var(--muted);
  font-size: 0.9rem;
}

.online .dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 0.4rem;
  border-radius: 50%;
  background: var(--win);
}

.online .dot.dim { background: var(--muted-dot); }

.stats {
  margin: 0 0 var(--s4);
  color: var(--muted);
  font-size: 0.9rem;
}

/* The match-mode choice in the lobby: a segmented control above the buttons it
   qualifies. It reads as part of the lobby rather than as a dialog. */
.seg {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  margin: var(--s2) 0;
  font-size: 0.9rem;
  color: var(--muted);
}

.seg-btn {
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--border-2);
  border-radius: 0.6rem;
  background: var(--panel);
  color: var(--muted);
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
}

.seg-btn.selected {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--bg);
  font-weight: 700;
}

/* Cancelled-handshake notice. Sits above whichever view is showing and states
   only what the server can prove — the handshake was cancelled — never which
   player was responsible, which the server cannot know. */
.note {
  margin: 0 0 var(--s4);
  color: var(--muted);
  font-size: 0.9rem;
}

/* Match History, styled out of the mode stack: the column above it reads as
   ways to play, and this opens a record. Still a <button> — the wiring and
   the keyboard both want one — so the base button chrome is what goes: no
   panel, no border, muted until hovered. The padding keeps the tap target
   honest while the text reads as a link. */
.lobby-foot {
  margin: var(--s2) 0 0;
}

.quiet {
  padding: 0.6rem 1rem;
  background: none;
  border: none;
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 0.25em;
}
.quiet:hover {
  background: none;
  border-color: transparent;
  color: var(--text);
}

.fighters {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s2);
  margin-bottom: var(--s4);
}

.fighter {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  padding: var(--s2) var(--s1);
  font-size: 0.8rem;
}

.fighter-emoji {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border: 2px solid var(--border);
  border-radius: 50%;
  background: var(--panel);
  font-size: 1.8rem;
  line-height: 1;
}

.fighter.selected { border-color: var(--accent); background: var(--panel-3); }
.fighter.selected .fighter-emoji { border-color: var(--accent); }

/* 9) Game ---------------------------------------------------------------- */
#game {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

.fighters-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s3);
  min-height: 4rem;
  margin-bottom: var(--s1);
}

.slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  min-width: 5rem;
  font-size: 0.8rem;
  color: var(--text);
}

.slot-emoji {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border: 2px solid var(--border);
  border-radius: 50%;
  background: var(--panel);
  font-size: 1.6rem;
  line-height: 1;
}

.slot-name {
  color: var(--text);
  font-weight: 700;
  text-align: center;
  line-height: 1.1;
}

.slot-tag {
  display: flex;
  align-items: center;
  min-height: 2.5em;
  color: var(--muted);
}

.vs-tag {
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 800;
}

/* The series scoreboard: one pip per round a side still needs, filled as it wins
   them. It sits under the fighter rather than inside the slot, because
   setYouSlot/setOppSlot rewrite the slot's innerHTML on every result and would
   take the pips with it. */
.fighter-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
}

.pips {
  display: flex;
  gap: 0.25rem;
}

.pip {
  width: 0.55rem;
  height: 0.55rem;
  border: 1px solid var(--border-2);
  border-radius: 50%;
  background: var(--panel);
}

.pip.on {
  border-color: var(--accent);
  background: var(--accent);
}

.stage {
  position: relative;
  min-height: 7rem;
  padding: var(--s4) var(--s3);
}

.count {
  display: inline-block;
  font-family: var(--font-title);
  font-size: 3.5rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  line-height: 1;
}

.stage.go .count {
  color: var(--accent-2);
  animation: pulse 0.4s ease;
}

.moves {
  display: flex;
  justify-content: center;
  gap: var(--s2);
}

.move {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: var(--s4) 0.5rem;
  font-size: 2rem;
  line-height: 1;
}

.move.picked { border-color: var(--accent); background: var(--panel-3); }

/* 10) Ladder tower -------------------------------------------------------- */
#ladder {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

/* One row per floor, stacked with no gap, so a row's height IS the pitch between
   floors. The climb animation is expressed in that same length, which is why it
   cannot drift out of step with the layout when the tower gets taller or the rows
   get shorter. column-reverse puts the markup's floor one at the bottom: the
   markup is in match order, and the order the player fights is the only order
   worth keeping.

   The row height is sized against the viewport rather than fixed, so a whole
   ladder stays on one screen at the roster's present size — the climb has to be
   visible, and a scrolling tower puts its scroll origin at the bottom, which is
   where the player has just come from. The overflow is the fallback for a roster
   that outgrows the height available. */
.tower {
  --floor-h: clamp(1.5rem, 3.2vh, 2.6rem);
  display: flex;
  flex-direction: column-reverse;
  max-height: 62vh;
  overflow-y: auto;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: var(--s2);
  background: var(--bg);
}

.floor {
  display: flex;
  align-items: center;
  gap: var(--s2);
  height: var(--floor-h);
  padding: 0 var(--s2);
  border-bottom: 1px solid var(--border);
  background: var(--panel);
  text-align: left;
}

.floor:last-child { border-bottom: 0; }
.floor.cleared { opacity: 0.45; }
.floor.here { background: var(--panel-3); border-bottom-color: var(--accent); }

.floor-no {
  min-width: 1.5rem;
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 700;
  text-align: right;
}

.floor-foe {
  flex: 1;
  overflow: hidden;
  font-size: 0.9rem;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The player, on the floor the run is on, beside the fighter that floor holds. */
.climber {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  color: var(--accent);
  font-size: 0.8rem;
  font-weight: 800;
}

.climber-emoji { font-size: 1.1rem; line-height: 1; }

.ladder-actions {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

/* 11) Match history ------------------------------------------------------ */
#history {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

/* The record scrolls rather than growing: a full one is taller than the
   viewport, and the Back button below it is the only way out of the screen. */
.hist-list {
  display: flex;
  flex-direction: column;
  max-height: 62vh;
  overflow-y: auto;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: var(--s2);
  background: var(--bg);
}

/* One match per row. The summary line *is* the row: the outcome, the mode, the
   two fighters, the score and when are what a record is for, and expanding is
   for the rounds that make it up. The outcome colours the edge of its row the
   way the result screen colours its banner, so a run of wins reads down the
   list without a word of it being read. */
.hist-match {
  border-bottom: 1px solid var(--border);
  background: var(--panel);
  text-align: left;
}

.hist-match:last-child { border-bottom: 0; }
.hist-match.hist-win { border-left: 3px solid var(--win); }
.hist-match.hist-loss { border-left: 3px solid var(--lose); }
.hist-match.hist-draw { border-left: 3px solid var(--draw); }
.hist-match.hist-void { border-left: 3px solid var(--muted-dot); }

.hist-match summary {
  padding: var(--s2);
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 700;
  line-height: 1.4;
  cursor: pointer;
}

.hist-match[open] summary {
  background: var(--panel-3);
  border-bottom: 1px solid var(--border);
}

.hist-body {
  padding: var(--s1) var(--s2) var(--s2);
}

/* The stage the match was fought on, said inside the row rather than in the
   summary: the summary is already five segments long, and the stage matters
   once, when a reader is looking at one match. */
.hist-stage {
  margin: 0 0 var(--s1);
  color: var(--muted);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.hist-rounds {
  margin: 0;
  padding: 0;
  list-style: none;
}

.hist-round {
  padding: 0.35rem 0;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.8rem;
  line-height: 1.5;
}

.hist-rn {
  color: var(--text);
  font-weight: 700;
}

.hist-outcome.hist-win { color: var(--win); }
.hist-outcome.hist-loss { color: var(--lose); }
.hist-outcome.hist-draw { color: var(--draw); }
.hist-outcome.hist-void { color: var(--muted); }

.hist-note { font-style: italic; }
.hist-ms { font-variant-numeric: tabular-nums; }

.hist-empty {
  margin: 0;
  padding: var(--s3) var(--s2);
  color: var(--muted);
  font-size: 0.9rem;
}

/* 12) Result ------------------------------------------------------------- */
.banner { margin: var(--s2) 0 var(--s1); }

.banner p { margin: 0 0 var(--s1); }

.won,
.lost,
.draw {
  font-size: 1.6rem;
  font-weight: 800;
}
.won { color: var(--win); }
.lost { color: var(--lose); }
.draw { color: var(--draw); }

.timing {
  margin-bottom: var(--s2);
  color: var(--muted);
  font-size: 0.95rem;
  line-height: 1.6;
}

.result-actions {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  margin-top: var(--s1);
}

#game-stats { margin: 0 0 var(--s2); }

/* 13) Motion ------------------------------------------------------------- */
@keyframes pulse {
  0% { transform: scale(1.3); opacity: 0.4; }
  100% { transform: scale(1); opacity: 1; }
}

/* The climb: the player arrives on the floor the run moved them to, coming from
   the row below — one row of travel per floor climbed, which is what climbing a
   ladder looks like. A loss gets the same hop from above, because a drop is the
   same movement pointed the other way and the player should be able to see the
   run went the other way.

   The distance is `--floor-h` and nothing else: the markup places the player on
   the final floor and the animation carries them in, so the client computes no
   pixel offsets and cannot compute one that disagrees with the layout. */
.climber.climb { animation: climb 550ms ease-out; }
.climber.climb-down { animation: climb-down 550ms ease-in; }

@keyframes climb {
  0% { transform: translateY(var(--floor-h)); opacity: 0.25; }
  100% { transform: translateY(0); opacity: 1; }
}

@keyframes climb-down {
  0% { transform: translateY(calc(var(--floor-h) * -1)); opacity: 0.25; }
  100% { transform: translateY(0); opacity: 1; }
}

/* The climb is the one thing on this screen that moves on its own, and it is
   decorative: the player is already on the right floor when the hop starts. A
   reader who has asked for less motion gets the tower without the travel. */
@media (prefers-reduced-motion: reduce) {
  .climber.climb,
  .climber.climb-down { animation: none; }
}

/* 14) Responsive ---------------------------------------------------------
   Short screens (most phones in portrait default to heights < 800px): compact
   the game screen so it fits one viewport and avoids nested scroll. */
@media (max-height: 800px) and (max-width: 768px) {
  .mkx-title { font-size: 2.2rem; margin-bottom: var(--s3); }
  /* The emblem shrinks with the title it sits over: the compacting below exists
     to keep the lobby on one screen, and a full-size mark would spend it back.
     Not as far as the title, though -- the sizes stay in the proportions the
     full-size screen sets (mark ~2x its title), so the fist reads as the
     landing screen's mark at every height rather than as a smaller cousin. */
  .lobby-fist { font-size: 4.4rem; }

  .stage {
    min-height: 5.5rem;
    padding-top: var(--s2);
    padding-bottom: var(--s2);
  }

  .count { font-size: 3rem; }

  .move { padding-top: var(--s2); padding-bottom: var(--s2); }
}

@media (max-height: 600px) {
  .mkx-title { font-size: 1.9rem; margin-bottom: var(--s2); }
  .lobby-fist { font-size: 4rem; }
  .stage {
    min-height: 4.5rem;
    padding-top: var(--s2);
    padding-bottom: var(--s2);
  }
  .count { font-size: 2.5rem; }
  /* A phone in landscape has the least height to give, and the tower's rows are
     already at the clamp's floor by then. */
  .tower { --floor-h: 1.5rem; max-height: 66vh; }
}

/* 15) Utilities ---------------------------------------------------------- */
.hidden { display: none !important; }

img { max-width: 100%; }