/* ── Tower ────────────────────────────────────────────────────────────────────
   Mastermind on a vertical tower. The chrome (.q-chrome / .q-stage / …) is the
   shared play shell from queens.css; everything below is Tower's own — the rows
   of balls, the feedback pegs, and the fill / reveal motion. The gem palette
   lives in the shared foot as option chips (src/ui/play-options.js). */

/* --to-ball (ball / chip size) and --to-side (control column width) live in
   tokens.css; --to-cur is the ball's current colour, --to-i its reveal index. */

/* The stage is a column: the secret sits ANCHORED at the top, the history
   grows downward beneath it (unused guesses drawn as dim placeholder rows, so
   the tower always shows its full height), and the in-progress guess row stays
   DOCKED at the bottom — a long game scrolls its history, never the row you're
   typing into. The stage is a size container: every row metric below derives
   from ITS width (cqw), so the widest row always fits — fold-cover portrait and
   the landscape middle column alike. */
.tower-play .q-stage {
  padding: 0; display: flex; flex-direction: column;
  /* the shared .q-stage sets place-items:center — that would shrink-wrap the
     scroll/dock sections; they must span the stage */
  place-items: stretch;
  position: relative; container-type: inline-size;
}
/* row metrics: gap, control column and ball size all give way together as the
   container narrows; --to-n (set inline by tower.js) is the balls per row */
.to-scroll, .to-currow, .to-way-stage {
  --to-gap: clamp(6px, 2.4cqw, 8px);
  --to-side: clamp(38px, 15cqw, 56px);
  --to-ball: clamp(20px, calc((100cqw - 2 * var(--to-side) - 2 * var(--space-5) - (var(--to-n, 5) + 1) * var(--to-gap)) / var(--to-n, 5)), 40px);
}
/* Guesses are unlimited, so the history has no fixed height: the whole
   ensemble (secret + history + guess row) centres vertically as one block,
   growing downward guess by guess; only once it outgrows the stage does the
   history start scrolling (flex 0 1 auto: shrinkable, never greedy). */
.tower-play .q-stage { justify-content: center; }
.to-scroll {
  flex: 0 1 auto; min-height: 0; overflow-y: auto;
  padding: var(--space-6) var(--space-5) var(--space-4);
  display: flex; flex-direction: column; align-items: center;
}
.to-tower { width: max-content; margin: 0 auto; }
.to-currow {
  flex: none; display: flex; justify-content: center;
  padding: var(--space-4) var(--space-5);
}
.to-currow:empty { display: none; }
/* the divider hugs the row, not the viewport — the tower is a centred block */
.to-currow .to-row { width: max-content; border-top: var(--hairline) solid var(--line); padding-top: var(--space-4); }

/* every row: [spacer] [balls] [controls / pegs], balls always centred */
.to-row {
  display: grid;
  grid-template-columns: var(--to-side) auto var(--to-side);
  align-items: center; gap: var(--to-gap);
}
.to-row-c { display: flex; justify-content: center; gap: var(--to-gap); }
.to-row-l, .to-row-r { display: flex; align-items: center; justify-content: center; }

/* A ball: an empty socket, or a cut gem that zooms in over it. The gem is not a
   decoration on a disc — it IS the ball, and its CUT is a second identity
   channel beside the colour (see src/games/tower/gems.js). So a filled ball has
   no disc left: no radius, no background, just the silhouette.

   .to-fill survives as the animated wrapper — every pop, zoom and reveal drives
   this one element, and the SVG inside comes along for free. */
.to-ball {
  position: relative;
  width: var(--to-ball); height: var(--to-ball); border-radius: 50%;
  background: var(--raised);
  box-shadow: inset 0 0 0 var(--hairline) var(--line);
  flex: none;
}
.to-fill {
  position: absolute; inset: 0;
  transform: scale(0);
}
.to-gem { position: absolute; inset: 0; width: 100%; height: 100%; display: block; overflow: visible; }
.to-ball.filled { background: transparent; box-shadow: none; }
.to-ball.filled .to-fill { transform: scale(1); }

/* map each colour class to its token, for balls, chips and the drag ghost */
.to-c0 { --to-cur: var(--to-c0); }
.to-c1 { --to-cur: var(--to-c1); }
.to-c2 { --to-cur: var(--to-c2); }
.to-c3 { --to-cur: var(--to-c3); }
.to-c4 { --to-cur: var(--to-c4); }
.to-c5 { --to-cur: var(--to-c5); }

.to-fill.zoom-in { animation: to-pop-in var(--duration-2) var(--ease); }
.to-fill.zoom-out { animation: to-pop-out 200ms var(--ease) forwards; }
@keyframes to-pop-in { from { transform: scale(0); } 60% { transform: scale(1.12); } to { transform: scale(1); } }
@keyframes to-pop-out { from { transform: scale(1); } to { transform: scale(0); } }

/* the secret, masked with a ? until the tower opens */
.to-secret { margin-bottom: var(--space-2); }
.to-qm {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--font-mono); font-weight: 700;
  font-size: clamp(11px, calc(var(--to-ball) * 0.42), 15px);   /* rides the ball size */
  color: var(--mute);
}
/* ONE divider idiom everywhere: the same hairline as the docked row's border */
.to-rule { height: var(--hairline); background: var(--line); margin: var(--space-3) 0 var(--space-4); }

/* the current guess row (docked in .to-currow, which draws the hairline);
   submit + clear stand in a small column on its right */
.to-ctlcol { display: flex; flex-direction: column; gap: var(--space-3); align-items: center; }
.to-ctl {
  width: 36px; height: 36px; border-radius: var(--radius-sm);
  display: grid; place-items: center;
  border: var(--hairline) solid var(--line); background: var(--surface);
  color: var(--mute); transition: var(--t);
}
.to-ctl svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: var(--stroke); }
.to-clear:hover { color: var(--danger-text); border-color: var(--danger-line); }

/* Submit arrives rather than lights up: it has nothing to do until the row is
   full, so until then it is not there at all. Fading it in on the last ball
   makes the button the answer to filling the row, instead of a permanently
   greyed control the player has to learn to ignore. */
.to-submit {
  opacity: 0;
  transform: scale(0.7);
  pointer-events: none;
  transition: opacity var(--duration-2) var(--ease), transform var(--duration-2) var(--ease), color var(--t), border-color var(--t);
}
.to-submit.ready {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  color: var(--accent); border-color: var(--accent-line);
}
@media (prefers-reduced-motion: reduce) {
  .to-submit { transition: opacity var(--duration-1) var(--ease); transform: none; }
}

/* past guesses + their feedback pegs (and the dim not-yet-spent rows) */
.to-past-wrap { margin-top: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }
/* a guess not yet made: the same row, dimmed to structure, never mistaken for play */
/* The tray: a real Mastermind board. One socket per ball in the guess, filled
   left to right, the unused ones left showing. That last part is the whole
   point — an empty socket prints the denominator, so "two exact, one misplaced,
   and two balls unaccounted for" is readable without counting. The old style
   showed only the scoring pegs, which meant the most useful number in the game
   had to be worked out in your head every row.

   The pegs are deliberately achromatic (var(--text)): they say how many, never
   which, so they must never look like they belong to a colour. */
.to-pegs {
  display: flex; flex-wrap: wrap; align-items: center; align-content: center;
  justify-content: flex-start; gap: 4px; width: var(--to-side);
}
.to-tray {
  gap: 3px;
  padding: 4px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  box-shadow: inset 0 0 0 var(--hairline) var(--line);
}
/* pegs are ~1/3 the ball, the way a real Mastermind board reads */
.to-peg { width: 12px; height: 12px; border-radius: 50%; flex: none; }
.to-black { background: var(--text); }
.to-white { box-shadow: inset 0 0 0 2.5px var(--text); }
/* an unfilled socket: a sunken hole, clearly not a score */
.to-slot { background: var(--bg); box-shadow: inset 0 0 0 var(--hairline) var(--line); opacity: 0.75; }

/* a freshly submitted row slides in; its tray is animated from the submit
   button by flipSubmit() in tower.js, so it must NOT also key its own entrance */
.to-fresh { animation: to-row-in var(--duration-3) var(--ease) both; }
@keyframes to-row-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }

/* The gem palette now lives in the shared foot (.q-foot, play-options.js);
   each chip wears the board's own cut gem. The chip and the ghost both carry
   .to-chip-gem, so the SVG inside (.to-gem, absolute) has a box to fill. */
.po-chip .to-chip-gem, .ng-ghost.po-ghost .to-chip-gem {
  position: relative; width: 84%; height: 84%; display: block;
}
/* the shared .po-chip svg rule sizes icons at var(--icon) and strokes them —
   a gem is a FILLED illustration that must fill its span, so undo both here
   (and for the drag ghost, which mounts on body outside .tower-play) */
.po-chip .to-chip-gem svg, .ng-ghost.po-ghost .to-chip-gem svg {
  width: 100%; height: 100%; stroke: none;
}

/* a guess-row ball being dragged to another slot */
.to-dragging { opacity: 0.35; }

/* the ball that follows the finger during a drag */
.to-ghost {
  position: fixed; top: 0; left: 0; z-index: var(--z-ghost);
  width: var(--to-ball); height: var(--to-ball); margin: calc(var(--to-ball) / -2);
  border-radius: 50%; background: var(--to-cur, var(--accent)); pointer-events: none;
  box-shadow: 0 0 0 3px var(--bg);
}

/* reveal: on win/lose the secret balls zoom open one after another */
.to-reveal .to-fill { transform: scale(0); }
.to-reveal.go .to-ball .to-fill { animation: to-pop-in var(--duration-3) var(--ease) both; animation-delay: calc(var(--to-i) * 140ms); }

/* ── the win screen's verdict + "show me the best way" ────────────────────
   Sits between the summary line and the cat's menu inside the victory sheet:
   how your guess count compares to the solver's line, what the shape
   guarantees, and the way into the replay. */
.to-verdict {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-2);
  margin: var(--space-4) 0 var(--space-5);
  text-align: center;
}
.to-verdict-say { font-size: var(--text-base); line-height: var(--leading-body); color: var(--text); margin: 0; max-width: 34ch; }
.to-verdict-sub { font-size: var(--text-base); line-height: var(--leading-body); color: var(--mute); margin: 0; max-width: 36ch; }
.to-verdict-go { margin-top: var(--space-2); }

/* ── the replay ───────────────────────────────────────────────────────────
   A full-screen scrub of the solver's line, one frame per guess: the same
   tower the board draws, rebuilt row by row, with a timeline along the bottom.
   It sits ABOVE the victory sheet (z 63 vs the veil's 60) so closing it drops
   the player back into the celebration rather than out of it. */
.to-way {
  position: fixed; inset: 0; z-index: 63;
  background: var(--bg);
  display: flex; flex-direction: column; min-height: 0;
  padding: max(14px, var(--sat)) 18px max(18px, var(--sab));
  animation: fade var(--duration-2) var(--ease) both;
  user-select: none; -webkit-user-select: none;
}
/* the frozen tower — a size container, exactly like the live stage, so the row
   metrics above resolve against the space the replay actually has */
.to-way-stage {
  flex: 1; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: var(--space-5) 0;
  container-type: inline-size;
}
.to-way-stage .to-tower { width: max-content; margin: auto; }
/* the guess this frame is about: everything before it is context, so lift only
   the current row rather than dimming the history into unreadability */
.to-way-now { animation: to-row-in var(--duration-3) var(--ease) both; }
.to-way-now .to-pegs { box-shadow: inset 0 0 0 var(--hairline) var(--accent-line), 0 0 0 3px var(--accent-line); }

.to-way-say {
  flex: none; margin: 0 auto var(--space-4); max-width: 48ch;
  text-align: center; font-size: var(--text-base); line-height: var(--leading-body);
  color: var(--text); min-height: 3.2em;
}

/* the timeline: one tick per guess, wearing that guess's balls in miniature */
.to-way-line {
  flex: none; display: flex; gap: var(--space-2); align-items: stretch;
  overflow-x: auto; padding-bottom: 2px;
  scrollbar-width: none;
}
.to-way-line::-webkit-scrollbar { display: none; }
.to-way-tick {
  flex: none; display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: var(--space-2) var(--space-3);
  border: var(--hairline) solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--mute); transition: var(--t);
}
.to-way-tick.on { border-color: var(--accent); color: var(--accent); background: var(--raised); }
.to-way-n { font-family: var(--font-mono); font-size: var(--text-xs); }
.to-way-dots { display: flex; gap: 3px; }
.to-way-dots .to-mini-ball { width: 8px; height: 8px; }

@media (prefers-reduced-motion: reduce) {
  .to-way { animation: none; }
  .to-way-now { animation: none; }
}

/* the level-card thumbnail: the secret's balls, greyed until solved */
.to-mini { display: flex; gap: 5px; align-items: center; justify-content: center; }
.to-mini-ball { width: 12px; height: 12px; border-radius: 50%; background: var(--to-cur, var(--raised)); flex: none; }
.to-mini-ball.to-blank { background: var(--line-2); }

@media (prefers-reduced-motion: reduce) {
  .to-fill.zoom-in, .to-fill.zoom-out, .to-fresh, .to-fresh .to-pegs,
  .to-reveal.go .to-ball .to-fill { animation: none; }
  .to-reveal .to-fill { transform: scale(1); }
}


/* landscape: same column — history scrolls, the guess row stays docked; the
   shared rails (puzzle.css) frame it, with the gem palette in the right rail.
   The foot IS the palette (optionChips adds .ng-pal to it), and .ng-pal's
   portrait sizing (width:100%; max-width:420px) would turn the right rail into
   a half-screen quadrant — size the rail by its chips instead. */
@media (orientation: landscape) and (max-height: 700px) {
  .tower-play .to-scroll { padding: var(--space-4); }
  .tower-play .to-currow { padding: var(--space-3) var(--space-4); }
  .screen.tower-play .q-foot.ng-pal { width: auto; max-width: none; margin: 0; }
}

/* a hint-unmasked secret gem: shown for good, with a soft ring so it reads as given */
.to-hinted { box-shadow: 0 0 0 2px var(--accent-line, rgba(230,180,60,0.5)); border-radius: 50%; }

/* very narrow portrait (Fold cover): the cat companion's seat is the
   bottom-left corner. Reserve a little extra width in the ball calc (so the
   rows no longer fill the stage edge-to-edge) and lean the docked guess row
   into that slack, right, so its first ball stays out from under her. */
@media (max-width: 340px) {
  .tower-play .to-scroll, .tower-play .to-currow {
    --to-ball: clamp(20px, calc((100cqw - 2 * var(--to-side) - 2 * var(--space-5) - (var(--to-n, 5) + 1) * var(--to-gap) - 32px) / var(--to-n, 5)), 40px);
  }
  .tower-play .to-currow { justify-content: flex-end; }
}
