/* ==========================================================================
   VOCABULARY GAMES -- the parts of the games section that cannot be Tailwind.

   This file used to be 708 lines and carried its own palette (blue-950 ink,
   emerald eyebrows, coral/blue/green pastel cards, a maroon-and-orange play
   stage). None of it came from a theme token, so the games read as a different
   product from the rest of the site. The page chrome is now Tailwind on brand
   tokens, in show.html.erb.

   What is left is only what the chrome cannot express: the round UI, which is
   built element by element in app/javascript/vocabulary_games/game.js and so
   has no source text for Tailwind to scan. Every class name below is part of
   that contract with the JS -- rename one and the game renders unstyled.

   Colours here are theme variables, never hex. See /styleguide.
   ========================================================================== */

/* The stage the round sits in. The round itself is the panel, so the wrapper
   only reserves height and stacking. */
[data-game-root] {
  min-height: 32rem;
}

/* The gap under the round title. It was a flat 24px, which read as cramped
   next to the empty screen the stage reserves below the panel -- all of the
   slack was pooling at the bottom. This is a share of the viewport instead,
   so a tall screen spends some of its spare height above the panel, and it is
   capped so the title never floats off on its own. Not centring the panel in
   the leftover space: the panel changes height between game types, and a
   heading that moves when you start a different round is worse than a gap. */
.vocabulary-play-stage [data-game-root] {
  position: relative;
  min-height: 0;
  margin-top: clamp(1.5rem, 8vh, 5rem);
}

/* A round should own the screen for as long as it lasts: the footer -- sitemap,
   legal links, a call to action to go somewhere else -- used to sit in view the
   whole time you were playing.

   The two numbers are both measured off the page this screen replaces.

   Padding: the trail has to land on the same line as the space eyebrow it
   stands in for, so that starting a round does not jog the page. The wrapper
   around every phase already contributes 2rem, and the hero that folds away
   carries 2.5rem of its own (3rem from md up), so this is only the difference.

   Height: 7rem is where the stage starts -- the 5rem site header plus that same
   2rem -- so taking it off the viewport puts the bottom of the stage exactly on
   the fold whatever the round contains. It used to be a guess, which meant a
   short round could still leave the footer showing.
   svh, not vh: on iOS vh is the tallest the viewport ever gets, which would
   push the footer off by the height of the address bar and leave a gap. */
.vocabulary-play-stage {
  padding-top: 0.5rem;
  padding-bottom: clamp(1.5rem, 3vw, 3rem);
  min-height: calc(100vh - 7rem);
  min-height: calc(100svh - 7rem);
}

@media (min-width: 768px) {
  .vocabulary-play-stage {
    padding-top: 1rem;
  }
}

.vocabulary-live-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--space-accent, var(--color-primary));
  box-shadow: 0 0 0 0.25rem color-mix(in oklab, var(--space-accent, var(--color-primary)) 20%, transparent);
}

.vocabulary-loader {
  width: 2.5rem;
  height: 2.5rem;
  margin: 0 auto;
  border: 4px solid var(--color-base-300);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: vocabulary-spin 800ms linear infinite;
}

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

/* --- Space accents ------------------------------------------------------
   The round borrows the colour of the space it is being played in, so the
   card you tapped on the hub is still the colour of the game. Set on <main>
   in show.html.erb. */
.vocabulary-space--pre-k {
  --space-accent: var(--color-secondary);
  --space-accent-content: var(--color-secondary-content);
}

.vocabulary-space--k12 {
  --space-accent: var(--color-primary);
  --space-accent-content: var(--color-primary-content);
}

.vocabulary-space--everyday-english {
  --space-accent: var(--color-accent);
  --space-accent-content: var(--color-accent-content);
}

/* --- The round ----------------------------------------------------------
   This was a dark slate panel: a game stage is a fair convention, but on a
   bright product for three-year-olds it was the one thing on the page that
   looked borrowed from somewhere else, and every colour on it had to be
   re-mixed toward white to survive.

   It is a light card now, on the same tokens as the page around it. The rule
   that keeps it looking deliberate: nothing in the resting state is
   saturated. One accent, from the space, and the strong colours -- green and
   red -- are held back for marking, so colour on this panel only ever means
   right or wrong. */
.vocabulary-round {
  --round-accent: var(--space-accent, var(--color-primary));
  --round-accent-content: var(--space-accent-content, var(--color-primary-content));
  --round-ink: var(--color-base-content);
  /* The accent is a light brand colour and cannot carry small text on white.
     Anywhere it has to be read rather than looked at, it is darkened into the
     page ink instead. */
  --round-accent-ink: color-mix(in oklab, var(--round-accent) 30%, var(--color-base-content));
  /* 60% measured 4.3:1, and everything wearing it -- the question eyebrow, the
     word counter, the number in an answer -- is 12px, which needs 4.5. */
  --round-muted: color-mix(in oklab, var(--color-base-content) 72%, transparent);
  --round-line: var(--color-base-300);
  --round-inset: var(--color-base-200);

  max-width: 60rem;
  margin: 0 auto;
  border: 1px solid var(--round-line);
  border-radius: var(--radius-lg);
  /* A wash of the space colour at the top, fading out by the question. Enough
     to mark the panel as the stage without tinting the words on it. */
  background:
    linear-gradient(to bottom,
      color-mix(in oklab, var(--round-accent) 9%, var(--color-base-100)) 0,
      var(--color-base-100) 9rem);
  /* Lighter at the foot: the feedback row below the answers is always there,
     holding its height so marking cannot resize the panel, and a full pad
     under an empty row left the card looking half-used. */
  padding: clamp(1.25rem, 4vw, 2.5rem);
  padding-bottom: clamp(1rem, 3vw, 1.5rem);
  color: var(--round-ink);
  box-shadow: 0 1rem 2.5rem color-mix(in oklab, var(--color-neutral) 12%, transparent);
}

/* --- Score bar ----------------------------------------------------------
   Where you are, how it is going, and what it is worth -- the three things a
   player checks between answers, on one line. */
.vocabulary-round__bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.vocabulary-round__counter {
  margin-right: auto;
  color: var(--round-muted);
  white-space: nowrap;
}

.vocabulary-round__streak,
.vocabulary-round__score {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border: 1px solid var(--round-line);
  border-radius: 999px;
  background: var(--color-base-100);
  padding: 0.35rem 0.7rem;
  color: var(--round-ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The streak only exists once there is one to talk about -- and until then it
   takes no room either. Held at width zero rather than just faded out: on a
   phone the invisible chip was still reserving its ~90px in the bar, which
   pushed "Word 1 of 8" onto two lines. */
.vocabulary-round__streak {
  max-width: 0;
  overflow: hidden;
  border-width: 0;
  padding-right: 0;
  padding-left: 0;
  opacity: 0;
  transform: scale(0.85);
  transition: max-width 220ms ease, padding 220ms ease, opacity 200ms ease, transform 200ms ease;
}

.vocabulary-round__streak.is-live {
  max-width: 12rem;
  border-width: 1px;
  padding-right: 0.7rem;
  padding-left: 0.7rem;
  border-color: color-mix(in oklab, var(--color-accent) 55%, transparent);
  background: color-mix(in oklab, var(--color-accent) 22%, var(--color-base-100));
  color: color-mix(in oklab, var(--color-accent) 28%, var(--color-base-content));
  opacity: 1;
  transform: none;
}

.vocabulary-round__streak-flame {
  font-size: 0.85rem;
  line-height: 1;
}

/* Sound on or off. Sized like the chips beside it and quiet: it is a setting
   on a screen that is otherwise all game, and it should be findable without
   competing with the score. */
.vocabulary-round__sound {
  display: inline-flex;
  width: 2rem;
  height: 2rem;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--round-line);
  border-radius: 999px;
  background: var(--color-base-100);
  font-size: 0.85rem;
  line-height: 1;
  opacity: 0.75;
  transition: opacity 160ms ease, background 160ms ease;
}

.vocabulary-round__sound:hover {
  background: var(--color-base-200);
  opacity: 1;
}

.vocabulary-round__sound.is-off {
  opacity: 0.5;
}

.vocabulary-round__sound:focus-visible {
  outline: 2px solid var(--round-accent, var(--color-primary));
  outline-offset: 2px;
}

.vocabulary-round__score {
  position: relative;
  border-color: color-mix(in oklab, var(--round-accent) 45%, transparent);
  transition: transform 200ms ease, box-shadow 200ms ease;
}

.vocabulary-round__score-star {
  color: var(--color-accent);
}

.vocabulary-round__score.is-bumped {
  transform: scale(1.12);
  box-shadow: 0 0 0 0.3rem color-mix(in oklab, var(--round-accent) 22%, transparent);
}

/* The points earned, floating off the total they were added to. */
.vocabulary-round__burst {
  position: absolute;
  top: -0.2rem;
  right: 0.5rem;
  color: var(--color-success);
  font-size: 0.8rem;
  font-weight: 700;
  pointer-events: none;
  animation: vocabulary-burst 900ms ease-out forwards;
}

@keyframes vocabulary-burst {
  from { opacity: 0; transform: translateY(0.2rem); }
  25%  { opacity: 1; }
  to   { opacity: 0; transform: translateY(-1.6rem); }
}

/* --- Progress dots ------------------------------------------------------
   One per word, so the round has a visible length and a visible record. */
.vocabulary-round__dots {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.9rem;
}

.vocabulary-round__dot {
  height: 0.4rem;
  flex: 1 1 0;
  min-width: 0.75rem;
  border-radius: 999px;
  background: var(--color-base-300);
  transition: background 250ms ease, transform 250ms ease;
}

.vocabulary-round__dot.is-correct { background: var(--color-success); }
.vocabulary-round__dot.is-wrong   { background: var(--color-error); }

.vocabulary-round__dot.is-current {
  background: var(--round-accent);
  transform: scaleY(1.6);
}

/* --- The question -------------------------------------------------------
   The instruction is the same on every word and the content is not, so the
   instruction is the small line. It used to be the other way round: a 2.25rem
   "Which word matches the clue?" over a 1rem clue. */
.vocabulary-round__ask {
  margin: 2rem auto 0;
  color: var(--round-muted);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-align: center;
  text-transform: uppercase;
}

.vocabulary-round__focus {
  max-width: 34rem;
  margin: 0.75rem auto 0;
  color: var(--round-ink);
  text-align: center;
  text-wrap: balance;
  animation: vocabulary-rise 300ms ease-out;
}

/* A meaning to solve: sentence case, read as a phrase. */
.vocabulary-round__focus.is-clue {
  font-size: clamp(1.3rem, 3vw, 1.75rem);
  font-weight: 600;
  line-height: 1.35;
}

/* A word to explain: the single most important thing on the screen. Set in
   the page ink, not the accent -- the brand colours are light, and a word at
   this size in mint on white is a decoration rather than something to read. */
.vocabulary-round__focus.is-word {
  color: var(--round-ink);
  font-size: clamp(2rem, 6vw, 3.25rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.1;
  overflow-wrap: anywhere;
}

@keyframes vocabulary-rise {
  from { opacity: 0; transform: translateY(0.5rem); }
  to   { opacity: 1; transform: none; }
}

/* --- Answers ------------------------------------------------------------
   These used to be four different brand colours -- mint, pink, amber, blue --
   one per answer. It made the panel a rainbow and, worse, it implied the
   colours meant something: four options that are identical in every way that
   matters were painted as four different kinds of thing.

   They are one thing now, and the only colour that appears on an answer is
   the colour of being right or wrong. */
.vocabulary-choice-grid,
.vocabulary-definition-grid {
  display: grid;
  gap: 0.75rem;
  margin-top: 2rem;
}

.vocabulary-choice-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.vocabulary-choice,
.vocabulary-definition-choice {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  /* The heavier bottom edge is the whole of the "this is a button" signal --
     it reads as a key with a lip on it, and it costs no colour. */
  border: 1px solid var(--round-line);
  border-bottom-width: 3px;
  border-radius: var(--radius-md);
  background: var(--color-base-100);
  color: var(--round-ink);
  font-weight: 600;
  text-align: left;
  transition: transform 160ms ease, background 160ms ease, border-color 160ms ease, box-shadow 160ms ease;
}

.vocabulary-choice {
  min-height: 4.75rem;
  padding: 0.8rem 1rem;
  font-size: 1.25rem;
}

.vocabulary-definition-choice {
  min-height: 4rem;
  padding: 0.9rem 1rem;
  font-size: 1rem;
  line-height: 1.45;
}

.vocabulary-choice:hover:not(:disabled),
.vocabulary-definition-choice:hover:not(:disabled) {
  transform: translateY(-0.15rem);
  border-color: var(--round-accent);
  background: color-mix(in oklab, var(--round-accent) 12%, var(--color-base-100));
  box-shadow: 0 0.6rem 1rem color-mix(in oklab, var(--color-neutral) 14%, transparent);
}

/* The number doubles as the key to press. Quiet until the answer is under the
   pointer, then it is the accent -- so the colour follows attention instead
   of sitting there being colourful. */
.vocabulary-choice__key {
  display: flex;
  width: 1.75rem;
  height: 1.75rem;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--color-base-200);
  color: var(--round-muted);
  font-size: 0.75rem;
  font-weight: 700;
  transition: background 160ms ease, color 160ms ease;
}

.vocabulary-choice:hover:not(:disabled) .vocabulary-choice__key,
.vocabulary-definition-choice:hover:not(:disabled) .vocabulary-choice__key {
  background: var(--round-accent);
  color: var(--round-accent-content);
}

.vocabulary-choice__text {
  overflow-wrap: anywhere;
}

/* --- Marking ------------------------------------------------------------
   The answer is shown on the answers. A line of text underneath leaves the
   learner matching a word back to a button they have stopped looking at. */
.vocabulary-choice:disabled,
.vocabulary-definition-choice:disabled,
.vocabulary-picture-choice:disabled,
.vocabulary-letter:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

.vocabulary-choice.is-chosen,
.vocabulary-definition-choice.is-chosen,
.vocabulary-picture-choice.is-chosen {
  opacity: 1;
}

.vocabulary-choice.is-right,
.vocabulary-definition-choice.is-right,
.vocabulary-picture-choice.is-right,
.vocabulary-choice.is-right:disabled,
.vocabulary-definition-choice.is-right:disabled,
.vocabulary-picture-choice.is-right:disabled {
  border-color: var(--color-success);
  background: color-mix(in oklab, var(--color-success) 16%, var(--color-base-100));
  opacity: 1;
  animation: vocabulary-pop 320ms ease-out;
}

.vocabulary-choice.is-right .vocabulary-choice__key,
.vocabulary-definition-choice.is-right .vocabulary-choice__key,
.vocabulary-picture-choice.is-right .vocabulary-choice__key {
  background: var(--color-success);
  color: white;
}

.vocabulary-choice.is-wrong,
.vocabulary-definition-choice.is-wrong,
.vocabulary-picture-choice.is-wrong,
.vocabulary-choice.is-wrong:disabled,
.vocabulary-definition-choice.is-wrong:disabled,
.vocabulary-picture-choice.is-wrong:disabled {
  border-color: var(--color-error);
  background: color-mix(in oklab, var(--color-error) 14%, var(--color-base-100));
  opacity: 1;
  animation: vocabulary-shake 320ms ease-in-out;
}

.vocabulary-choice.is-wrong .vocabulary-choice__key,
.vocabulary-definition-choice.is-wrong .vocabulary-choice__key,
.vocabulary-picture-choice.is-wrong .vocabulary-choice__key {
  background: var(--color-error);
  color: white;
}

/* --- Picture match ------------------------------------------------------
   The Pre-K round. Everything else in this file asks the learner to read
   something -- a clue, a definition, a word to spell. This one does not: the
   word is spoken, and the four answers are pictures. It is the only game a
   child who cannot yet read can play without an adult beside them.

   The word is written on the play button as well as spoken. A pre-reader
   ignores it; a child starting to read gets word, sound, and picture in one
   place, which is the pairing that teaches reading -- and it is what keeps
   the round playable when a clip will not load. */
.vocabulary-say {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  width: 100%;
  margin-top: 1.5rem;
  padding: 1rem 1.25rem;
  border: 1px solid var(--round-line);
  border-bottom-width: 3px;
  border-radius: var(--radius-md);
  background: var(--color-base-100);
  color: var(--round-ink);
  transition: transform 160ms ease, background 160ms ease, border-color 160ms ease;
}

.vocabulary-say:hover:not(:disabled) {
  transform: translateY(-0.15rem);
  border-color: var(--round-accent);
  background: color-mix(in oklab, var(--round-accent) 12%, var(--color-base-100));
}

.vocabulary-say:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

.vocabulary-say__icon {
  font-size: 1.5rem;
  line-height: 1;
}

.vocabulary-say__word {
  font-size: clamp(1.75rem, 6vw, 2.5rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.1;
  overflow-wrap: anywhere;
}

.vocabulary-picture-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
  margin-top: 1.5rem;
}

.vocabulary-picture-choice {
  position: relative;
  display: block;
  padding: 0.5rem;
  border: 1px solid var(--round-line);
  border-bottom-width: 3px;
  border-radius: var(--radius-md);
  background: var(--color-base-100);
  transition: transform 160ms ease, background 160ms ease, border-color 160ms ease, box-shadow 160ms ease;
}

.vocabulary-picture-choice:hover:not(:disabled) {
  transform: translateY(-0.15rem);
  border-color: var(--round-accent);
  box-shadow: 0 0.6rem 1rem color-mix(in oklab, var(--color-neutral) 14%, transparent);
}

/* Out of the way in the corner: on the other games the number sits in the
   reading order before the answer, but here it must not cover the picture,
   which is the answer. */
.vocabulary-picture-choice .vocabulary-choice__key {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 1;
}

.vocabulary-picture-choice__frame {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Square, so four pictures of different shapes make an even grid and none of
     them is the odd one out -- a picture that stands out for being a different
     size is a hint, and the wrong sort. */
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: calc(var(--radius-md) - 0.25rem);
  background: var(--color-base-200);
}

.vocabulary-picture-choice__frame img {
  width: 100%;
  height: 100%;
  /* contain, not cover: these are illustrations of a single object, and
     cropping one to fill a square can cut the thing being named out of it. */
  object-fit: contain;
}

.vocabulary-picture-choice__fallback {
  padding: 0.5rem;
  font-size: 1.125rem;
  font-weight: 700;
  text-align: center;
  overflow-wrap: anywhere;
}

/* Marking has to be louder here than on the other games. Those mark an answer
   by tinting the button behind the text; on this one the picture fills the
   button, so the tint is hidden and a 1px border is all that is left -- on the
   screen of a child who cannot read the feedback line underneath. A ring around
   the whole tile and a mark in the corner say it without words. */
.vocabulary-picture-choice.is-right,
.vocabulary-picture-choice.is-right:disabled,
.vocabulary-picture-choice.is-wrong,
.vocabulary-picture-choice.is-wrong:disabled {
  border-width: 2px;
  border-bottom-width: 3px;
}

.vocabulary-picture-choice.is-right,
.vocabulary-picture-choice.is-right:disabled {
  box-shadow: 0 0 0 4px color-mix(in oklab, var(--color-success) 45%, transparent);
}

.vocabulary-picture-choice.is-wrong,
.vocabulary-picture-choice.is-wrong:disabled {
  box-shadow: 0 0 0 4px color-mix(in oklab, var(--color-error) 45%, transparent);
}

.vocabulary-picture-choice.is-right::after,
.vocabulary-picture-choice.is-wrong::after {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 1;
  display: flex;
  width: 1.75rem;
  height: 1.75rem;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: white;
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1;
}

.vocabulary-picture-choice.is-right::after {
  content: "✓";
  background: var(--color-success);
}

.vocabulary-picture-choice.is-wrong::after {
  content: "✕";
  background: var(--color-error);
}

/* Four pictures at once is a lot of screen. From tablet up they can be a row,
   which keeps the whole question in one glance instead of a scroll. */
@media (min-width: 768px) {
  .vocabulary-picture-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@keyframes vocabulary-pop {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.035); }
  100% { transform: scale(1); }
}

@keyframes vocabulary-shake {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-0.3rem); }
  75%      { transform: translateX(0.3rem); }
}

/* --- Letter builder -----------------------------------------------------
   One box per letter, so the length of the word is part of the puzzle rather
   than something to guess at. */
.vocabulary-spelling-answer {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.4rem;
  margin-top: 2rem;
  transition: transform 200ms ease;
}

.vocabulary-spelling-slot {
  display: flex;
  width: 2.75rem;
  height: 3.25rem;
  align-items: center;
  justify-content: center;
  border: 2px dashed var(--round-line);
  border-radius: var(--radius-md);
  color: var(--round-ink);
  font-size: 1.5rem;
  font-weight: 800;
  transition: border-color 160ms ease, background 160ms ease, color 160ms ease;
}

.vocabulary-spelling-slot.is-filled {
  border-style: solid;
  border-color: color-mix(in oklab, var(--round-accent) 70%, transparent);
  background: color-mix(in oklab, var(--round-accent) 12%, var(--color-base-100));
}

.vocabulary-spelling-answer.is-right .vocabulary-spelling-slot {
  border-color: var(--color-success);
  background: color-mix(in oklab, var(--color-success) 16%, var(--color-base-100));
}

.vocabulary-spelling-answer.is-wrong {
  animation: vocabulary-shake 320ms ease-in-out;
}

.vocabulary-spelling-answer.is-wrong .vocabulary-spelling-slot {
  border-color: color-mix(in oklab, var(--color-error) 70%, transparent);
}

/* The word spelled back after a wrong answer. Written last and at the
   specificity of the wrong-state rule above, or the red border stays on the
   letters that are now the right ones. */
.vocabulary-spelling-answer .vocabulary-spelling-slot.is-answer,
.vocabulary-spelling-answer.is-wrong .vocabulary-spelling-slot.is-answer {
  border-style: solid;
  border-color: var(--color-success);
  background: color-mix(in oklab, var(--color-success) 16%, var(--color-base-100));
  color: color-mix(in oklab, var(--color-success) 35%, var(--color-base-content));
}

.vocabulary-letter-tray {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.55rem;
  max-width: 36rem;
  margin: 1.75rem auto 0;
}

.vocabulary-letter {
  display: flex;
  width: 3.25rem;
  height: 3.25rem;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--round-line);
  border-bottom-width: 3px;
  border-radius: var(--radius-md);
  background: var(--color-base-100);
  color: var(--round-ink);
  font-size: 1.25rem;
  font-weight: 700;
  transition: transform 120ms ease, background 160ms ease, border-color 160ms ease;
}

.vocabulary-letter:hover:not(:disabled) {
  transform: translateY(-0.15rem);
  border-color: var(--round-accent);
  background: color-mix(in oklab, var(--round-accent) 14%, var(--color-base-100));
}

.vocabulary-letter:active:not(:disabled) {
  transform: translateY(0.1rem);
}

/* A spent tile is a hole in the tray, not another box to press -- it kept the
   solid look of a live tile and the tray read as a second answer row. */
.vocabulary-letter.is-used {
  border-style: dashed;
  border-bottom-width: 1px;
  background: transparent;
  color: transparent;
}

.vocabulary-spelling-controls {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  margin-top: 1.75rem;
}

.vocabulary-primary-button,
.vocabulary-secondary-button {
  border-radius: var(--radius-md);
  padding: 0.85rem 1.4rem;
  font-size: 0.875rem;
  font-weight: 600;
  transition: background 160ms ease, box-shadow 200ms ease, opacity 160ms ease;
}

.vocabulary-primary-button {
  /* Transparent rather than absent, so the border the disabled state needs
     does not make the button 2px bigger the moment it turns off. */
  border: 1px solid transparent;
  background: var(--round-accent);
  color: var(--round-accent-content);
}

/* Not the accent at 40%: a washed-out amber button still looks like a button
   you may press, and this one does nothing until every slot is filled. Off the
   accent entirely, it reads as waiting. */
.vocabulary-primary-button:disabled {
  cursor: not-allowed;
  border: 1px solid var(--round-line);
  background: var(--color-base-200);
  color: var(--round-muted);
}

.vocabulary-primary-button:hover:not(:disabled) {
  background: color-mix(in oklab, var(--round-accent) 88%, black);
}

/* Every letter placed: the button is the only thing left to do. */
.vocabulary-primary-button.is-ready:not(:disabled) {
  box-shadow: 0 0 0 0.3rem color-mix(in oklab, var(--round-accent) 25%, transparent);
}

.vocabulary-secondary-button {
  border: 1px solid var(--round-line);
  background: var(--color-base-100);
  color: var(--round-ink);
}

.vocabulary-secondary-button:disabled {
  cursor: not-allowed;
  opacity: 0.4;
}

.vocabulary-secondary-button:hover:not(:disabled) {
  background: var(--color-base-200);
}

/* Once the answer is marked the tray has nothing left to offer, but removing
   it would move everything under it. */
.vocabulary-round.is-marked .vocabulary-letter-tray,
.vocabulary-round.is-marked .vocabulary-spelling-controls {
  opacity: 0.35;
  transition: opacity 200ms ease;
}

/* Shown on the first word only, and only to something with a keyboard. */
.vocabulary-round__hint {
  display: none;
  margin-top: 1rem;
  color: var(--round-muted);
  font-size: 0.75rem;
  text-align: center;
}

@media (hover: hover) and (pointer: fine) {
  .vocabulary-round__hint {
    display: block;
  }
}

.vocabulary-round.is-marked .vocabulary-round__hint {
  opacity: 0;
}

/* The row is always present: as an appended element it changed the height of
   the panel on every answer. */
.vocabulary-round__feedback {
  display: flex;
  min-height: 2.5rem;
  align-items: center;
  justify-content: center;
  margin-top: 0.9rem;
  border-radius: var(--radius-md);
  padding: 0.5rem 1rem;
  font-size: 1rem;
  font-weight: 700;
  text-align: center;
}

.vocabulary-round__feedback.is-correct {
  background: color-mix(in oklab, var(--color-success) 14%, var(--color-base-100));
  color: color-mix(in oklab, var(--color-success) 55%, black);
  animation: vocabulary-pop 320ms ease-out;
}

.vocabulary-round__feedback.is-incorrect {
  background: color-mix(in oklab, var(--color-error) 12%, var(--color-base-100));
  color: color-mix(in oklab, var(--color-error) 55%, black);
}

/* --- Scorecard ----------------------------------------------------------
   Rendered into the complete panel in show.html.erb by index.js. */
.vocabulary-result-stars {
  display: flex;
  justify-content: center;
  gap: 0.35rem;
  font-size: 2.5rem;
  line-height: 1;
}

.vocabulary-result-star {
  color: var(--color-base-300);
  transition: color 200ms ease;
}

.vocabulary-result-star.is-earned {
  color: var(--color-accent);
  animation: vocabulary-star-in 420ms cubic-bezier(0.2, 1.4, 0.5, 1) backwards;
}

@keyframes vocabulary-star-in {
  from { opacity: 0; transform: scale(0.3) rotate(-25deg); }
  to   { opacity: 1; transform: none; }
}

/* Focus rings for the JS-built controls, matching the Tailwind chrome. */
.vocabulary-choice:focus-visible,
.vocabulary-definition-choice:focus-visible,
.vocabulary-letter:focus-visible,
.vocabulary-primary-button:focus-visible,
.vocabulary-secondary-button:focus-visible {
  outline: 2px solid var(--round-accent, var(--color-primary));
  outline-offset: 2px;
}

/* Playing is a full-attention state. On a phone the page heading and the space
   blurb pushed the round almost entirely below the fold; on a desktop they
   titled the screen twice over -- "Word detective for Pre-K" above the round,
   and the name of the set being played inside it. The round brings its own
   heading, so the page heading stands down while one is running. */
.is-playing [data-space-hero] {
  display: none;
}

@media (max-width: 640px) {
  .vocabulary-choice-grid {
    grid-template-columns: 1fr;
  }

  .vocabulary-choice {
    min-height: 3.75rem;
    font-size: 1.1rem;
  }

  .vocabulary-round {
    border-radius: var(--radius-md);
  }

  .vocabulary-round__ask {
    margin-top: 1.5rem;
  }

  .vocabulary-round__counter {
    letter-spacing: 0.02em;
  }

  .vocabulary-spelling-slot {
    width: 2.25rem;
    height: 2.75rem;
    font-size: 1.2rem;
  }

  .vocabulary-letter {
    width: 2.9rem;
    height: 2.9rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}
