/* ============================================================================
   The board: play surface, chain grid, tiles, connectors, stars, keyboard
   ========================================================================== */

.board-shell {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: clamp(0.75rem, 3vw, 2rem) clamp(0.5rem, 3vw, 1rem) 1.5rem;
}

/* Room below everything for the fixed keyboard. This sits on the footer rather
   than the board so the legal links clear the keyboard too: they were the one
   thing left permanently underneath it. Padding, not margin, because a bottom
   margin here collapses out through <body> and adds no scrollable height. */
.board-shell ~ .site-footer {
  padding-bottom: calc(2.5rem + var(--kb-h));
}

.board {
  width: 100%;
  max-width: var(--board-max);
  padding: clamp(1rem, 3.5vw, 2rem) clamp(0.75rem, 3vw, 2rem)
    clamp(1.25rem, 3vw, 2rem);
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg);
}

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

.board__head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

.puzzle-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

.puzzle-date {
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.6rem;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-2);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
}

.chip--encore {
  color: var(--row-2-ink);
  background: var(--row-2-wash);
  border-color: color-mix(in srgb, var(--row-2-line) 45%, transparent);
}

.game-title {
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 11vw, 4rem);
  line-height: 1;
  text-align: center;
  padding-block: 0.15em 0.2em;
  background: linear-gradient(
    170deg,
    var(--ink) 0%,
    color-mix(in srgb, var(--ink) 72%, var(--brand-600)) 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ------------------------------------------------------------------- stars */

.star-row {
  display: flex;
  justify-content: center;
  gap: 0.6rem;
}

.star {
  position: relative;
  width: 2.1rem;
  height: 2.1rem;
  color: var(--star-empty);
  transition: color var(--t-med) var(--ease),
    transform var(--t-slow) var(--ease-spring), filter var(--t-med) var(--ease);
}

.star svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentcolor;
  stroke-width: 1.6;
  stroke-linejoin: round;
}

.star.is-filled svg {
  fill: currentcolor;
  stroke: color-mix(in srgb, currentcolor 60%, black 40%);
}

.star.is-filled {
  animation: star-pop var(--t-slow) var(--ease-spring) both;
  filter: drop-shadow(0 2px 5px color-mix(in srgb, currentcolor 45%, transparent));
}

.star.tier-gold {
  color: var(--gold-b);
}

.star.tier-gold.is-filled {
  color: var(--gold-a);
}

.star.tier-silver.is-filled {
  color: var(--silver-a);
}

.star.tier-bronze.is-filled {
  color: var(--bronze-a);
}

@keyframes star-pop {
  0% {
    transform: scale(0.5) rotate(-20deg);
    opacity: 0.4;
  }

  60% {
    transform: scale(1.28) rotate(6deg);
  }

  100% {
    transform: scale(1) rotate(0);
  }
}

/* -------------------------------------------------------------- tier badge */

.tier-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.85rem;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  transition: background var(--t-med) var(--ease), color var(--t-med) var(--ease);
}

.tier-badge[data-tier='gold'] {
  color: var(--gold-ink);
  background: linear-gradient(140deg, var(--gold-a), var(--gold-b));
  border-color: color-mix(in srgb, var(--gold-b) 60%, transparent);
}

.tier-badge[data-tier='silver'] {
  color: var(--silver-ink);
  background: linear-gradient(140deg, var(--silver-a), var(--silver-b));
  border-color: color-mix(in srgb, var(--silver-b) 60%, transparent);
}

.tier-badge[data-tier='bronze'] {
  color: var(--bronze-ink);
  background: linear-gradient(140deg, var(--bronze-a), var(--bronze-b));
  border-color: color-mix(in srgb, var(--bronze-b) 60%, transparent);
}

/* ---------------------------------------------------------------- the goal */

.goal {
  margin-bottom: 1.25rem;
  padding: 1rem clamp(0.75rem, 3vw, 1.5rem);
  text-align: center;
  background: linear-gradient(
    180deg,
    var(--surface-2),
    color-mix(in srgb, var(--surface-2) 40%, var(--surface))
  );
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

.goal__label {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.5rem;
}

.goal__phrase {
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 5.2vw, 1.75rem);
  line-height: 1.35;
  color: var(--ink);
  word-break: break-word;
}

.goal__phrase.is-solved {
  color: var(--row-3-ink);
  animation: solved-flash var(--t-slow) var(--ease) both;
}

/* The same meaning, inside the completion modal. Capped in height because it
   sits above the share card and must not push it off a short screen, and
   tinted like the panel under the grid so the two places a meaning appears
   read as the same thing. row-2 is used because it is the one accent with a
   value defined in both themes. */
.win-story {
  margin: 0.9rem 0 0.2rem;
  padding: 0.85rem 0.9rem 0.9rem;
  text-align: start;
  background: var(--row-2-wash);
  border: 1px solid color-mix(in srgb, var(--row-2-line) 45%, transparent);
  border-radius: var(--r-md);
  max-height: 40vh;
  overflow-y: auto;
}

/* The heading. In the interface face, not the handwriting one: the display font
   is for the game's own voice, the Phrasicle title and the phrase itself, and
   using it for a section label made the label compete with the answer. Weight
   and colour do the work of standing out instead. */
.win-story__label {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  font-family: var(--font-ui);
  font-size: 0.86rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.3;
  color: var(--row-2-ink);
  margin-bottom: 0.55rem;
  padding-bottom: 0.5rem;
  border-bottom: 2px solid color-mix(in srgb, var(--row-2-line) 35%, transparent);
}

.win-story__label::before {
  content: '📖';
  font-family: var(--font-ui);
  font-size: 0.9em;
  flex: 0 0 auto;
}

.win-story__phrase {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 5vw, 1.35rem);
  line-height: 1.3;
  color: var(--ink);
  margin-bottom: 0.4rem;
}

.win-story__meaning {
  font-size: 0.92rem;
  line-height: 1.45;
  color: var(--ink);
}

.win-story__fact {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--border);
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--ink-2);
}

.win-story__link {
  display: inline-block;
  margin-top: 0.55rem;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--brand-600);
}

/* The help-panel shortcut into extra hints, lit up while they are on. */
.btn.is-active {
  color: var(--row-2-ink);
  background: var(--row-2-wash);
  border-color: color-mix(in srgb, var(--row-2-line) 55%, transparent);
}

/* ------------------------------------------------------- the phrase's story

   Appears under the goal once the phrase is solved or revealed. Deliberately
   quieter than the goal panel: it is a reward to read, not another thing to
   do.                                                                       */

.story {
  margin: -0.5rem 0 1.25rem;
  padding: 0.9rem clamp(0.85rem, 3vw, 1.4rem) 1rem;
  background: var(--row-2-wash);
  border: 1px solid color-mix(in srgb, var(--row-2-line) 40%, transparent);
  border-radius: var(--r-lg);
  animation: story-in var(--t-slow) var(--ease) both;
}

.story__label {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--row-2-ink);
  margin-bottom: 0.4rem;
}

.story__meaning {
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--ink);
}

.story__fact {
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px dashed color-mix(in srgb, var(--row-2-line) 40%, transparent);
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--ink-2);
}

.story__link {
  display: inline-block;
  margin-top: 0.7rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--row-2-ink);
}

@keyframes story-in {
  0% {
    opacity: 0;
    transform: translateY(-4px);
  }

  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

.goal__hint {
  margin-top: 0.5rem;
  font-size: 0.85rem;
  color: var(--ink-3);
}

@keyframes solved-flash {
  0% {
    transform: scale(0.96);
  }

  55% {
    transform: scale(1.05);
  }
}

/* ------------------------------------------------------------ chain grid --
   A real CSS grid. The old build absolutely positioned a table inside a
   pinch-zoom transform; this scales by clamping the cell type instead, so the
   browser keeps its own zoom, text selection and scrolling behaviour.        */

.grid-frame {
  padding: clamp(0.5rem, 2.5vw, 1rem);
  background: var(--surface-inset);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: inset 0 1px 3px rgb(23 33 46 / 4%);
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

.chain-grid {
  display: grid;
  gap: clamp(0.3rem, 1.4vw, 0.55rem);
  min-width: 288px;
}

.chain-row {
  --line: var(--row-0-line);
  --wash: var(--row-0-wash);
  --word-ink: var(--row-0-ink);

  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(0.3rem, 1.4vw, 0.55rem);
}

.chain-row[data-row='1'] {
  --line: var(--row-1-line);
  --wash: var(--row-1-wash);
  --word-ink: var(--row-1-ink);
}

.chain-row[data-row='2'] {
  --line: var(--row-2-line);
  --wash: var(--row-2-wash);
  --word-ink: var(--row-2-ink);
}

.chain-row[data-row='3'] {
  --line: var(--row-3-line);
  --wash: var(--row-3-wash);
  --word-ink: var(--row-3-ink);
}

.cell {
  position: relative;
}

/* The connector line lives on the cell, so it costs no grid width. It lights up
   once the word to its left is in place, which is what makes the chain read as
   a chain rather than four rows of boxes. */
.cell:not(:last-child)::after {
  content: '';
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 100%;
  width: clamp(0.3rem, 1.4vw, 0.7rem);
  height: 3px;
  border-radius: 2px;
  background: color-mix(in srgb, var(--line) 28%, transparent);
  transform: translateY(-50%);
  transition: background var(--t-med) var(--ease);
}

.cell.is-linked:not(:last-child)::after {
  background: var(--line);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--line) 35%, transparent);
}

/* Easy mode: how many letters the answer has, in the corner of the box.

   A numeral rather than a row of dots. Six dots cannot be counted at a glance
   and seven of them were wider than the tile; a digit is instant at any length.
   The corner placement is deliberate too, so it reads as a note about the box
   rather than as part of the word.

   ::before, because ::after on the cell is the chain connector. */
.cell[data-letters]::before {
  content: attr(data-letters);
  position: absolute;
  /* Above the tile: selecting or hovering a tile transforms it, which lifts it
     into its own paint layer and would otherwise hide this at exactly the
     moment the player is looking at that box. */
  z-index: 2;
  inset-block-start: 0.1rem;
  inset-inline-end: 0.2rem;
  min-width: 0.95em;
  padding: 0.05em 0.15em;
  font-family: var(--font-ui);
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 1.15;
  text-align: center;
  color: var(--ink-3);
  background: color-mix(in srgb, var(--surface-inset) 75%, transparent);
  border-radius: 4px;
  pointer-events: none;
}

/* Nudge the word down so a tall letter never touches the number. */
.cell[data-letters] .tile {
  padding-top: 0.55rem;
}

/* The shake moved here from the tile, so the box and its letter count move
   together and no transform is applied to the element holding the text, which
   is what iOS was rendering badly. */
.cell.shake {
  animation: shake 0.34s var(--ease);
}

/* A button, so it needs its own reset: no UA border, no UA background, and the
   text centred by flex rather than by an input's internal line box. */
.tile {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-width: 0;
  height: clamp(2.5rem, 9.2vw, 3.1rem);
  padding: 0.2rem 0.15rem;
  appearance: none;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: clamp(0.6rem, 2.55vw, 0.95rem);
  font-weight: 600;
  line-height: 1.1;
  text-align: center;
  text-transform: lowercase;
  color: var(--ink);
  background: var(--surface);
  border: 2px solid color-mix(in srgb, var(--line) 45%, transparent);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-tile);
  transition: background var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}

.tile:disabled {
  cursor: default;
}

.tile:not(:disabled):not(.is-correct):hover {
  border-color: var(--line);
  transform: translateY(-1px);
}

.tile.is-selected {
  border-color: var(--line);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--line) 28%, transparent),
    var(--shadow-md);
  transform: translateY(-1px);
}

.tile.is-hint {
  color: var(--hint-ink);
  font-weight: 500;
}

.tile.is-correct {
  color: var(--word-ink);
  background: var(--wash);
  border-color: var(--line);
  font-weight: 700;
  cursor: default;
}

/* Words the puzzle hands you. They recede: the border is muted and there is no
   lift, so the words the player earned are the ones that carry weight. */
.tile.is-given {
  color: color-mix(in srgb, var(--word-ink) 68%, transparent);
  background: color-mix(in srgb, var(--wash) 55%, var(--surface-2));
  border-color: color-mix(in srgb, var(--line) 40%, transparent);
  border-style: solid;
  font-weight: 600;
  box-shadow: none;
}

.tile.just-solved {
  animation: tile-pop var(--t-slow) var(--ease-spring) both;
}

.chain-row.row-complete .tile {
  animation: row-wave 0.5s var(--ease) both;
}

.chain-row.row-complete .tile:nth-child(2) {
  animation-delay: 60ms;
}

.chain-row.row-complete .tile:nth-child(3) {
  animation-delay: 120ms;
}

.chain-row.row-complete .tile:nth-child(4) {
  animation-delay: 180ms;
}

.chain-row.row-complete .tile:nth-child(5) {
  animation-delay: 240ms;
}

@keyframes tile-pop {
  0% {
    transform: scale(0.86);
  }

  55% {
    transform: scale(1.12);
  }
}

@keyframes row-wave {
  0%,
  100% {
    transform: translateY(0);
  }

  45% {
    transform: translateY(-7px);
  }
}

/* The red flash stays on the tile, driven by the class on its cell, so the
   colour and the movement are declared next to each other. */
.cell.shake .tile {
  border-color: var(--wrong);
  background: var(--wrong-wash);
}

@keyframes shake {
  0%,
  100% {
    transform: translateX(0);
  }

  20% {
    transform: translateX(-5px);
  }

  40% {
    transform: translateX(5px);
  }

  60% {
    transform: translateX(-3px);
  }

  80% {
    transform: translateX(3px);
  }
}

/* --------------------------------------------------------------- guess bar */

.guess-bar {
  display: flex;
  gap: 0.5rem;
  margin-top: 1rem;
}

.guess-input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 48px;
  padding: 0.6rem 1rem;
  font-size: 1rem;
  font-weight: 500;
  text-align: center;
  color: var(--ink);
  background: var(--surface-inset);
  border: 2px solid var(--border-strong);
  border-radius: var(--r-pill);
  transition: border-color var(--t-fast) var(--ease),
    box-shadow var(--t-fast) var(--ease);
}

.guess-input::placeholder {
  color: var(--ink-3);
  font-weight: 400;
}

.guess-input:focus {
  outline: none;
  border-color: var(--brand-500);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand-400) 25%, transparent);
}

.guess-target {
  font-size: 0.8rem;
  color: var(--ink-3);
  text-align: center;
  margin-top: 0.5rem;
  min-height: 1.2em;
}

.guess-target strong {
  color: var(--ink-2);
}

/* ----------------------------------------------------------------- actions */

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
  margin-top: 1rem;
}

.scoring-note {
  margin-top: 0.85rem;
  font-size: 0.8rem;
  text-align: center;
  color: var(--ink-3);
}

/* --------------------------------------------------------------- misses --- */

.misses {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}

.misses__title {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.6rem;
}

.misses__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  list-style: none;
}

.misses__list li {
  padding: 0.2rem 0.6rem;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--wrong);
  background: var(--wrong-wash);
  border: 1px solid color-mix(in srgb, var(--wrong) 30%, transparent);
  border-radius: var(--r-pill);
  text-decoration: line-through;
}

/* -------------------------------------------------------------- share card */

.share-card {
  margin: 1rem 0;
  padding: 1rem;
  font-family: ui-monospace, 'SF Mono', 'Cascadia Mono', Menlo, monospace;
  font-size: 0.9rem;
  line-height: 1.6;
  white-space: pre-wrap;
  text-align: center;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-md);
}

/* --------------------------------------------------------------- keyboard */

.keyboard {
  position: fixed;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  width: 100%;
  max-width: var(--board-max);
  margin-inline: auto;
  padding: 0.55rem clamp(0.25rem, 1.5vw, 0.5rem)
    calc(0.55rem + env(safe-area-inset-bottom, 0px));
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-top: 1px solid var(--border);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: 0 -6px 24px rgb(23 33 46 / 10%);
}

.keyboard__row {
  display: flex;
  gap: 0.28rem;
  justify-content: center;
}

.key {
  flex: 1 1 0;
  min-width: 0;
  height: 46px;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-xs);
  box-shadow: var(--shadow-key);
  transition: background var(--t-fast) var(--ease), transform 60ms var(--ease);
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.key:active,
.key.is-pressed {
  background: var(--surface-3);
  transform: translateY(2px);
  box-shadow: none;
}

.key--wide {
  flex: 1.35 1 0;
  font-size: 1.05rem;
  white-space: nowrap;
}

.key--go {
  flex: 1.35 1 0;
  color: var(--action-ink);
  background: var(--action);
  border-color: transparent;
  font-size: 1.1rem;
  white-space: nowrap;
}

.key--go:active,
.key--go.is-pressed {
  background: var(--action-hover);
}

/* ---------------------------------------------------------------- confetti */

.confetti-canvas {
  position: fixed;
  inset: 0;
  z-index: 110;
  pointer-events: none;
}

/* ------------------------------------------------------- desktop refinement */

@media (min-width: 720px) {
  .board__head {
    margin-bottom: 1.5rem;
  }

  .tile {
    height: 3.5rem;
    font-size: 1rem;
    border-radius: var(--r-md);
  }

  .chain-grid {
    gap: 0.7rem;
  }

  .chain-row {
    gap: 0.9rem;
  }

  .cell:not(:last-child)::after {
    width: 0.9rem;
  }
}

/* Physical keyboard is available, so the on-screen one is opt-in only. */
@media (min-width: 1025px) {
  .keyboard {
    display: none;
  }

  .keyboard.is-forced {
    display: flex;
  }

  .board-shell ~ .site-footer {
    padding-bottom: 2.5rem;
  }

  /* Unless a desktop player has explicitly asked for the on-screen keyboard. */
  body:has(.keyboard.is-forced) .board-shell ~ .site-footer {
    padding-bottom: calc(2.5rem + var(--kb-h));
  }
}

/* ---------------------------------------------------------------------------
   Touch devices already have a keyboard.

   Focusing the guess field summons the phone's own keyboard, which then sits
   on top of this one: two keyboards stacked, and the board pushed off screen.
   The phone's is also the better one, with its autocorrect, its swipe typing
   and the layout the player already knows. So on anything with a coarse
   pointer this is hidden by default, and the space reserved for it goes with
   it, which is what was leaving a gap under the board.

   The menu's on-screen keyboard toggle still forces it back, for whoever
   prefers it.
   --------------------------------------------------------------------------- */
@media (hover: none) and (pointer: coarse) {
  .keyboard {
    display: none;
  }

  .keyboard.is-forced {
    display: flex;
  }

  .board-shell ~ .site-footer {
    padding-bottom: 2.5rem;
  }

  body:has(.keyboard.is-forced) .board-shell ~ .site-footer {
    padding-bottom: calc(2.5rem + var(--kb-h));
  }
}
