/**
 * Boggle — tool styles
 *
 * Cards, tabs, tables, buttons, meters and dialogs all come from frikwork
 * untouched. What is here is the 4x4 grid geometry, the found/missed word
 * lists, the clock's three states, and the two keyframes frikwork has no
 * equivalent for — both switched off in the reduced-motion block at the foot of
 * the file, matching what frikwork already does for its own.
 *
 * THE COLOUR RULE, AND WHY IT IS THE SAME ONE WORDLE LANDED ON
 * -----------------------------------------------------------
 * Every accent here is TEXT or a BORDER on a card, never a knockout on a
 * saturated fill — so unlike Wordle this file needs no ink token. What it does
 * share is the problem underneath: --fw-success and --fw-danger flip lightness
 * between themes (light ships a dark olive and a deep red, dark ships a bright
 * lime and a bright red), so a raw token that reads well on one theme's card can
 * fall under AA on the other's.
 *
 * The fix is Wordle's, unchanged: deepen each accent TOWARD --fw-text. --fw-text
 * is by definition the colour furthest from the card behind it — near-black on a
 * white card, near-white on a dark one — so the same mix raises contrast against
 * the card whichever theme is live, with no per-theme values to keep in step.
 *
 * FOUR THEME STATES, NOT TWO
 * --------------------------
 * frikwork declares its light palette twice: once at [data-fw-theme="light"],
 * and again inside
 * `@media (prefers-color-scheme: light) { :root:not([data-fw-theme]) { … } }`.
 * That second block re-declares the WHOLE palette, --fw-success, --fw-danger,
 * --fw-bg-card and --fw-text included. So "no attribute" is not dark — on an OS
 * set to light it is fully light. Because every accent below is derived from
 * those same tokens with color-mix() rather than hardcoded per theme, all four
 * states follow frikwork automatically and this file needs no media query of its
 * own for colour. Measured rather than assumed, in all four states, against the
 * real page in Chrome — the numbers and the probe are in the task report. Two of
 * those measurements changed the design: --fw-brand-text and an 86% green both
 * came in under 4.5:1 and were deepened. See the token comments below.
 *
 * --fw-bg-2 is deliberately NOT used to separate a tile or a chip from the panel
 * it sits on: it resolves to the same #ffffff as --fw-bg-card in the light
 * theme, so it cannot separate two adjacent surfaces. --fw-bg-hover is what does
 * the separating here, as it does for Wordle's keyboard.
 */

:root {
  /**
   * The app's accent, deepened.
   *
   * --fw-brand-text is NOT safe as normal text on a light card: it resolves to
   * the same olive as --fw-success (#5c8a1e) and measures 4.11:1 on #ffffff,
   * under the 4.5:1 bar. Measured, not assumed — the numbers are in the task
   * report. 86% toward --fw-text takes it to 5.05:1 in light and raises it
   * further in dark, and it still reads as the brand colour.
   */
  --bog-accent: color-mix(in srgb, var(--fw-brand-text) 86%, var(--fw-text));

  /**
   * An accepted word: its border, and the points it earned.
   *
   * Deepened harder than Wordle's 86% because this one carries SMALL text on a
   * background that is itself faintly green (--bog-good-wash), so the floor it
   * has to clear is higher than plain white. At 86% the points badge measured
   * 4.38:1 on the wash — under AA; at 72% it is 5.43:1. One token does the
   * border and the badge rather than two, because a deeper border is no loss.
   */
  --bog-good: color-mix(in srgb, var(--fw-success) 72%, var(--fw-text));

  /* The last minute, and the last twenty seconds. 86% is enough for both: this
     text sits on the card, not on a wash.
     --bog-warn was previously the raw --fw-warning token, left undeepened so a
     test probe had a control to measure. Test scaffolding should not shape a
     production colour, and it left .is-warn visibly inconsistent with the
     .is-critical beside it — so both are deepened by the same rule now, and the
     probe builds its own synthetic swatch for the control instead. */
  --bog-warn: color-mix(in srgb, var(--fw-warning) 86%, var(--fw-text));
  --bog-alert: color-mix(in srgb, var(--fw-danger) 86%, var(--fw-text));

  /* The barely-there wash behind a found word. 12% is enough to read as a
     group without putting --fw-text on a coloured field. */
  --bog-good-wash: color-mix(in srgb, var(--fw-success) 12%, var(--fw-bg-card));
}

.bog-layout {
  display: grid;
  grid-template-columns: minmax(280px, 340px) 1fr;
  gap: var(--fw-space-lg);
  align-items: start;
}

.bog-play {
  display: flex;
  flex-direction: column;
  gap: var(--fw-space-lg);
  min-width: 0;
}

/* --- Settings --------------------------------------------------------------
   .fw-tabs--segmented is an inline-flex track that scrolls when it runs out of
   room. In a 340px sidebar that would become a scroll strip, so the track is
   re-laid as a full-width grid and keeps its own chrome. */

.bog-seg {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  overflow-x: visible;
  width: 100%;
}

.bog-actions {
  display: grid;
  gap: var(--fw-space-sm);
}

.bog-keys {
  margin-top: var(--fw-space-md);
  line-height: 1.7;
}

/* --- The clock ------------------------------------------------------------- */

.bog-clock {
  display: flex;
  align-items: center;
  gap: var(--fw-space-md);
  margin-bottom: var(--fw-space-lg);
}

.bog-clock__value {
  flex: 0 0 auto;
  min-width: 4.2rem;
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--fw-text);
  font-variant-numeric: tabular-nums;
}

/* The last minute, then the last twenty seconds. The meter fill changes with
   them (frikwork's own --warn / --critical modifiers), so the two halves of the
   clock always say the same thing. */
.bog-clock__value.is-warn { color: var(--bog-warn); }
.bog-clock__value.is-critical { color: var(--bog-alert); }

.bog-clock__meter { flex: 1 1 auto; }

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

.bog-board {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--fw-space-xs);
  width: 100%;
  max-width: 22rem;
  margin: 0 auto;
}

.bog-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  min-width: 0;
  border: 2px solid var(--fw-border);
  border-radius: var(--fw-radius-sm);
  /* --fw-bg-hover, not --fw-bg-2: the tiles sit on a card, and --fw-bg-2 is the
     same #ffffff as --fw-bg-card in the light theme, so the whole grid would
     disappear into the panel. */
  background: var(--fw-bg-hover);
  color: var(--fw-text);
  font-size: clamp(1.1rem, 6vw, 2rem);
  font-weight: 700;
  line-height: 1;
  /* NO text-transform HERE. It was `uppercase`, and that quietly undid the one
     piece of typography on this board that carries meaning: script.js writes the
     Qu die as "Qu" precisely so it does not read as two separate letters, and
     uppercasing it rendered "QU" again. The faces arrive from the server already
     capitalised, so there is nothing for a transform to do except break that. */
  -webkit-user-select: none;
  user-select: none;
}

/* Qu is one square and two characters, so it has to come down a size or it
   bursts the tile. Applied by script.js from the face's own length rather than
   by matching on the letters, so a future two-character face needs no edit. */
.bog-tile__face { display: block; text-transform: none; }
.bog-tile--wide .bog-tile__face { font-size: 0.62em; letter-spacing: -0.02em; }

/* Before the round: no letters to show, and the grid says so rather than
   sitting there looking broken. */
.bog-board.is-idle .bog-tile {
  border-style: dashed;
  background: transparent;
}

/* --- Word entry ------------------------------------------------------------ */

.bog-entry {
  display: flex;
  gap: var(--fw-space-sm);
  margin-top: var(--fw-space-lg);
}

.bog-entry__input {
  flex: 1 1 auto;
  min-width: 0;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: 600;
}

.bog-entry__submit { flex: 0 0 auto; }

/* A word that counted: the field flashes its border rather than its fill, so
   nothing is ever typed onto a coloured background. */
.bog-entry__input.is-good { border-color: var(--bog-good); }
.bog-entry__input.is-bad { border-color: var(--bog-alert); }

.bog-status {
  margin: var(--fw-space-lg) 0 0;
  padding: var(--fw-space-sm) var(--fw-space-md);
  border: 1px solid var(--fw-border);
  border-radius: var(--fw-radius-md);
  background: var(--fw-bg-2);
  color: var(--fw-text-2);
  text-align: center;
  min-height: 2.75rem;
}

/* --- Words found ----------------------------------------------------------- */

.bog-tally {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--fw-space-md);
  margin-bottom: var(--fw-space-md);
  flex-wrap: wrap;
}

.bog-tally__count { color: var(--fw-text-2); }

.bog-tally__score {
  font-weight: 600;
  color: var(--bog-accent);
}
.bog-tally__score .fw-num {
  font-size: 1.5rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.bog-words {
  display: flex;
  flex-wrap: wrap;
  gap: var(--fw-space-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.bog-word {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  padding: 0.2rem 0.55rem;
  border: 1px solid var(--bog-good);
  border-radius: var(--fw-radius-sm);
  background: var(--bog-good-wash);
  color: var(--fw-text);
  font-weight: 600;
  letter-spacing: 0.04em;
}

.bog-word__pts {
  color: var(--bog-good);
  font-size: 0.78rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* A chip the server did not accept when the round was rescored. Struck through
   and drained of the green rather than removed: "that one did not count" is more
   use to a player than a word quietly vanishing. Reachable at a midnight
   crossing, where finish() derives tomorrow's board and rejects everything. */
.bog-word.is-void {
  border-color: var(--fw-border);
  background: var(--fw-bg-hover);
  color: var(--fw-text-2);
  text-decoration: line-through;
}
.bog-word.is-void .bog-word__pts {
  color: var(--fw-text-2);
  text-decoration: line-through;
}

/* --- Leaderboard ---------------------------------------------------------- */

.bog-board__scope {
  margin-top: 0;
  font-weight: 600;
  color: var(--bog-accent);
}

/* --fw-text-2, not --fw-text-muted: muted measures 3.75:1 on the light theme's
   card, under AA for normal text. frikwork's own `.fw-table td` rule outranks a
   single class here anyway, so in practice this only decides the colour if that
   rule ever changes — which is exactly when you want the safe value already in
   place. */
.bog-board__rank { width: 3rem; color: var(--fw-text-2); }
.bog-board__initials { font-weight: 700; letter-spacing: 0.06em; }
.bog-board__num { text-align: right; font-variant-numeric: tabular-nums; }
.bog-board__note { margin-bottom: 0; }

/* --- Dialog content ------------------------------------------------------- */

.bog-verdict {
  margin: 0 0 var(--fw-space-md);
  padding: var(--fw-space-md);
  border: 1px solid var(--fw-border);
  border-radius: var(--fw-radius-md);
  background: var(--fw-bg-2);
  line-height: 1.5;
}

.bog-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
  gap: var(--fw-space-sm);
  margin: 0 0 var(--fw-space-md);
  padding: var(--fw-space-md);
  border: 1px solid var(--fw-border);
  border-radius: var(--fw-radius-md);
  background: var(--fw-bg-2);
}

.bog-facts__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  text-align: center;
}

/* --fw-text-2 rather than --fw-text-muted: these are labels a player reads, and
   muted measures 3.75:1 on the light theme's card. */
.bog-facts dt {
  font-size: 0.8rem;
  color: var(--fw-text-2);
}

.bog-facts dd {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--bog-accent);
}

.bog-missed__title {
  margin: 0 0 var(--fw-space-sm);
  font-size: 1rem;
}

/**
 * ONE RULE ABOUT frikwork's `.fw-help`: this file never restyles it.
 *
 * Its colour is --fw-text-muted, which measures 3.75:1 on the light theme's
 * card — under AA for normal text. That is a framework-level issue affecting
 * every tool in the suite (Wordle overrides it zero times across ten uses), and
 * darkening it from one tool's stylesheet would make Boggle's help text differ
 * from twenty others' for reasons nobody could see. So it is left alone, and it
 * is being carried to the framework rather than patched here.
 *
 * This count was previously an exception to that — a `.fw-help` span with a
 * colour override, which is the inconsistent middle ground. Where Boggle needs
 * different styling it now uses its OWN class instead, which keeps both halves of
 * the rule intact: the framework's value is never touched, and the count is still
 * readable.
 */
.bog-missed__count {
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--fw-text-2);
}

/* Up to a few hundred words, so the box scrolls rather than the dialog growing
   past the viewport. tabindex="0" in the markup makes it keyboard-scrollable,
   which a scroll container holding no focusable children otherwise is not. */
.bog-missed {
  display: flex;
  flex-wrap: wrap;
  gap: var(--fw-space-xs);
  max-height: 14rem;
  overflow-y: auto;
  padding: var(--fw-space-sm);
  border: 1px solid var(--fw-border);
  border-radius: var(--fw-radius-md);
  background: var(--fw-bg-2);
}

.bog-missed__word {
  padding: 0.15rem 0.45rem;
  border: 1px solid var(--fw-border);
  border-radius: var(--fw-radius-sm);
  background: var(--fw-bg-card);
  color: var(--fw-text-2);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
}

.bog-rules {
  display: grid;
  gap: var(--fw-space-sm);
  margin: 0 0 var(--fw-space-md);
  padding-left: var(--fw-space-lg);
}

.bog-scoretable { margin-bottom: var(--fw-space-md); }
.bog-scoretable__num { text-align: right; }

.bog-initials {
  text-transform: uppercase;
  letter-spacing: 0.3em;
  text-align: center;
  font-size: 1.4rem;
  font-weight: 700;
}

/* --- Motion ---------------------------------------------------------------- */

/* A word that did not count wobbles rather than silently doing nothing. */
@keyframes bog-shake {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(-6px); }
  40%      { transform: translateX(6px); }
  60%      { transform: translateX(-4px); }
  80%      { transform: translateX(4px); }
}
.bog-entry.is-shake { animation: bog-shake 0.42s ease-in-out; }

/* The newest word arrives rather than appearing. Scale only — no colour change,
   so nothing about the animation carries information a reduced-motion player
   would lose. */
@keyframes bog-pop {
  from { transform: scale(0.82); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}
.bog-word.is-new { animation: bog-pop 220ms ease-out both; }

/* --- Responsive ---------------------------------------------------------- */

@media (max-width: 960px) {
  .bog-layout { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
  .bog-board { max-width: 100%; }
  .bog-clock__value { font-size: 1.4rem; min-width: 3.4rem; }
  .bog-keys { display: none; }
}

/* Same contract frikwork honours for its own animations: no motion, but every
   state still reads — the states are classes and text, not keyframes, so
   nothing below costs a player information. */
@media (prefers-reduced-motion: reduce) {
  .bog-entry.is-shake,
  .bog-word.is-new { animation: none; }
}
