/* Imported in cascade order: later files may rely on (and override) earlier
   ones. dialogs.css is the part shared word for word with Rungs, Forage and
   Threads. */
/* Palette, fonts and sizing tokens shared by every other stylesheet. */
/* The solved frame's two widths (below), registered as lengths because
   SolvedFrame reads them from script to place its line: a registered length
   computes to px whatever unit it's written in, where an unregistered custom
   property comes back exactly as written. */
@property --highlight-frame {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}
@property --highlight-line {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}
:root {
  --directional-text: 0.9rem;
  /* This game's own purple scheme, from the designer's game-page mockup.
     --ink-strong and --line have no counterpart there yet, so they are still
     the warm values lifted from Threads. Token names match the vocabulary
     shared across Rungs/Forage/Threads (see shared-ui-patterns.md); --line is
     this game's own addition to that shared set, not an overload of an
     existing name. */
  --bg: #dcd7e0;
  --bg-dark: #706686;
  --tile: #ffffff;
  --ink: #231f20;
  /* Secondary text, a step back from --ink: Rungs' grey for its hint count
     (Tailwind's gray-600 there). */
  --ink-muted: #4b5563;
  --ink-strong: oklch(.15 .018 50);
  /* Keyboard focus on a control that sits on a light surface. */
  --focus-ring-ink-width: 2px;
  --focus-ring-ink: var(--focus-ring-ink-width) solid var(--ink);
  --focus-ring-ink-offset: 3px;
  /* How far the ring paints outside its element. */
  --focus-ring-ink-reach: calc(var(--focus-ring-ink-width) + var(--focus-ring-ink-offset));
  /* The dark purple marks everything solved or selected; one value, so the
     page's dark purple and the solved tiles never drift apart. */
  --accent: var(--bg-dark);
  --accent-ink: #ffffff;
  /* The accent taken toward --ink: a purple dark enough to stand out on the
     page and the strip, for what's drawn straight on them. */
  --accent-deep: color-mix(in oklch, var(--accent) 80%, var(--ink));
  /* The game's links, readable on the page as well as on a white card.
     Underlined, so they needn't stand apart from the ink by colour. */
  --link: var(--accent-deep);
  --line: oklch(.85 .022 60);
  /* Derived from --ink rather than restated as its own hex value, so the two
     never drift independently. */
  --hairline: color-mix(in oklch, var(--ink) 15%, transparent);
  /* The back-to-portal bar's warm near-white. */
  --portal-bar-bg: #f9f7f2;
  /* The mockup's lighter purples, each for one job (see game.css):
     --tile-placed  the solver's squares — a tile they've put on the board,
                    and an empty blank waiting for one — so they read as
                    theirs rather than as a given letter;
     --word-strip   the colour between a word's tiles, marking "there's a word
                    to find here" — the acrostic column and every row;
     --cursor-fill  the empty cell the next typed letter will land in. Every
                    empty cell shows the strip, so this is the strip pushed
                    toward --accent, to stand out from it;
     --tile-faded   on a solved board, a tile outside the acrostic and the
                    themed row, stepping back so those two stand out: the
                    accent at 50% opacity over the page, worked out as a
                    solid colour;
     --difficulty-level-idle  a difficulty button that isn't the selected
                    one: a hint of the accent in a tile's white, pale enough
                    for its accent label to read on it. */
  --tile-placed: #e2e0e7;
  /* #b2adc1 at 75% opacity over the page, worked out as a solid colour: a
     see-through strip would darken where a row's strip overlaps the
     acrostic's, and would make --cursor-fill see-through too. */
  --word-strip: color-mix(in srgb, #b2adc1 75%, var(--bg));
  --cursor-fill: color-mix(in oklch, var(--accent) 40%, var(--word-strip));
  --difficulty-level-idle: color-mix(in oklch, var(--accent) 10%, var(--tile));
  --tile-faded: color-mix(in srgb, var(--accent) 50%, var(--bg));
  /* Shared font convention: serif for game content, sans for anything
     directional (instructions, controls, etc). */
  --font-serif: "Roboto Serif", "Iowan Old Style", "Palatino Linotype", Palatino, Cambria, Georgia, serif;
  --font-sans: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  /* The gap between cells in a row and between the bank's rows. The board's
     rows sit a multiple of it apart (see .board in game.css), so each word's
     strip reads on its own. */
  --tile-gap: .3125rem;

  /* From the board's bottom row to the bank's tiles, or on a solved level
     (which has no bank) to Share. The difficulty buttons, after
     the bank, sit a quarter further from it. */
  --board-bank-gap: 2rem;
  /* Below each thing that takes the bank's place under a solved board
     (Share, the feedback link, a notice): the board's own gap, so they're
     spaced as evenly as the board is from the first of them. */
  --below-board-gap: var(--board-bank-gap);

  /* The line framing the solved board's acrostic and themed row:
     --highlight-line wide, its outer side --highlight-frame out from their
     tiles. Fixed, so it reads the same on a 9-letter board as on a 4-letter
     one. Drawn in the gaps around the tiles, so --highlight-frame must stay
     under --tile-gap (5px), the gap beside the acrostic column — checked in
     Board.css.test.ts. */
  --highlight-frame: 4px;
  --highlight-line: 2px;

  /* Each row's Hint and Clear buttons, lifted from Threads. The room they
     take beside the longest possible row is a token (rather than left to
     layout) because the tile-size rule has to reserve it before it can size
     the tiles. */
  --answer-icon-size: 1.125rem;
  --icon-button-padding: 7px;
  --icon-button-size: calc(var(--answer-icon-size) + 2 * var(--icon-button-padding));
  /* An icon button at rest — and its icon, wherever it's pictured. */
  --icon-button-opacity: 0.55;
  /* Clear's ✕, sized to stand as tall as Hint's "?": a "?" is about 0.71 of
     its font size tall, and the ✕'s strokes span 14 of its icon's 24 units
     (6–18, plus their round ends). As a multiple of the "?"'s font size, so
     the instructions picture the pair in the same proportion. On the board
     it spills a little into the button's padding, leaving the button's size,
     and so the tiles', as it was. */
  --clear-icon-scale: calc(.71 * 24 / 14);
  --clear-icon-size: calc(var(--answer-icon-size) * var(--clear-icon-scale));
  /* How far Clear and Hint may overlap: Clear follows its word, so they only
     meet in a row that fills the width. Their padding plus the blank margins
     inside both icons (the ✕ path spans only the middle half of its viewBox,
     and "?" is a narrow glyph) left the icons visibly far apart there, and
     that width came out of the tiles. Applied as a negative margin on Clear,
     not as a negative `gap`: CSS rejects negative gaps outright, silently
     leaving the buttons where they were. Hint, drawn later, keeps its full
     tap area where they overlap; Clear keeps the rest of its own. */
  --answer-actions-overlap: 8px;
  --answer-actions-width: calc(2 * var(--icon-button-size) - var(--answer-actions-overlap));

  /* Colour and font are the only app-specific parts of the toast's styling;
     keeping them in tokens leaves the rule itself free of local names. */
  --toast-bg: var(--bg-dark);
  --toast-fg: var(--tile);
  --toast-font: var(--font-sans);
}
/* Page reset, the app column and its tile-sizing rule, and the chrome
   around the game: the portal back-link and the masthead. */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { overscroll-behavior-y: none; }
body {
  font-family: var(--font-sans);
  background: var(--bg);
}
.app {
  position: relative;
  width: 100%;
  /* The shared page column (shared-ui-patterns.md, "Page column"): 384px
     including 16px padding each side, the same as Threads and Forage, so
     switching games in the portal doesn't move the content's edges. Was
     360px with 8px padding. The tiles size from this column (cqw), so on a
     360px phone they come out about 5% smaller than before, and on wider
     phones slightly larger. */
  --page-column: 384px;
  max-width: var(--page-column);
  /* Centred on the whole window (100vw counts a desktop scroll bar) rather
     than on the space beside the scroll bar, so the column stays put when the
     page starts or stops scrolling (seen in Forage when the portal's notice
     was dismissed). Capped at the space actually there, so a narrow window
     never pushes it under the scroll bar. */
  margin-left: clamp(0px, calc((100vw - var(--page-column)) / 2), calc(100% - var(--page-column)));
  margin-right: 0;
  padding: 12px 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* Establishes the containment context that the cqw-based tile sizing
     (below) resolves against — the same context for both .board and .bank,
     which is what keeps a bank tile and a board tile the same size. */
  container-type: inline-size;
}
/* Tile sizing, set on the element that carries --max-cols (App.tsx sets it
   inline from the active difficulty level's longest row) so the board and the bank
   size from one definition.

   --tile-width is what makes a row of --tile-cols cells exactly fill the
   width left beside the Hint/Clear column (and one --tile-gap before it),
   with --tile-gap between its cells. --tile-cols is the longest row's length,
   but never under 8: a shorter puzzle's tiles stop at an 8-letter row's size
   rather than growing to fill the row, which made a 6- or 7-letter puzzle's
   tiles noticeably bigger than an 8-letter one's. Its rows then end short of
   the Hint/Clear column, which stays at the right edge. --tile-slot is the
   square the
   drawn tile sits inset in: more on left/right than on top/bottom, so the
   tile is slightly taller than wide. The font scales off the slot, not the
   drawn tile, so the insets trim the tile without shrinking its letter, while
   a 9-letter word's smaller tiles still don't overflow theirs. */
.app > main {
  --tile-inset-x: 2px;
  --tile-inset-y: 1px;
  --tile-cols: max(var(--max-cols, 1), 8);
  --tile-width: calc(
    (100cqw - var(--answer-actions-width) - var(--tile-gap) - (var(--tile-cols) - 1) * var(--tile-gap))
    / var(--tile-cols)
  );
  --tile-slot: calc(var(--tile-width) + 2 * var(--tile-inset-x));
  --tile-height: calc(var(--tile-slot) - 2 * var(--tile-inset-y));
  --tile-font-size: calc(var(--tile-slot) * 0.56);
}
/* Thin back-link to the portal landing page. See
   back-link-to-portal-design-spec.md for the full visual spec and rationale.
   Rendered outside .app so it spans the full viewport width and sits flush
   at the very top of the page, unconstrained by .app's width cap/padding. */
.back-to-portal-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  width: 100%;
  /* 6px each way: 2px came out of the top padding for the line above, so the
     bar is no taller for it, and 1px went back from the bottom so the icon
     and caption sit midway between the two lines. */
  padding: 6px 0 6px;
  /* The bar spans only the space beside a desktop scroll bar, so centring its
     content in the bar would move it when the page starts or stops scrolling.
     Padding the left by the scroll bar's width (100vw counts it; 100% doesn't)
     centres the icon and caption on the whole window, like .app. */
  padding-left: calc(100vw - 100%);
  background: var(--portal-bar-bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 0.95rem;
  text-decoration: none;
  /* The line above sets the near-white bar off from a phone browser's own
     controls. 2px, since 1px was too thin to see. */
  border-top: 2px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.back-to-portal-bar svg {
  position: relative;
  top: 0;
}
.back-to-portal-bar:focus { outline: none; }
.back-to-portal-bar:focus-visible {
  /* Unseen beside the box-shadow, but drawn where a contrast theme (forced
     colours) drops box-shadows. */
  outline: 2px solid transparent;
  box-shadow: 0 0 0 2px var(--portal-bar-bg), 0 0 0 4px var(--ink);
}
/* Masthead — wordmark | instructions, worked example of the shared layout */
.masthead {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  column-gap: 0.5rem;
}
.wordmark {
  font-family: var(--font-serif);
  font-size: 1.1rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink);
}
/* Same as every game's wordmark mark (Threads, Forage). */
.wordmark-tm {
  font-size: 0.45em;
  vertical-align: super;
  margin-left: 0.05em;
  font-weight: 400;
  letter-spacing: 0;
}
/* Instructions trigger — small circled "i" in the masthead, the same in
   every game (shared-ui-patterns.md, "Instructions trigger"). */
.instructions-trigger {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid var(--ink);
  background: transparent;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.instructions-trigger:hover {
  border-color: var(--ink-strong);
  color: var(--ink-strong);
}
.instructions-trigger:focus { outline: none; }
.instructions-trigger:focus-visible {
  /* Unseen beside the box-shadow, but drawn where a contrast theme (forced
     colours) drops box-shadows. */
  outline: 2px solid transparent;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ink);
}
/* ── Dialogs ───────────────────────────────────────────────────────────────
   Three roles, one vocabulary — see components/dialogRoles.ts, which is what
   decides who wears which. Rungs/Forage/Threads carry the same class names
   and the same --modal-* token names, so a look-and-feel change is the same
   edit in each game.

     .modal-panel                    sheet   — instructions
     .modal-panel--confirm           confirm — e.g. start over, restart
     .modal-panel--info              info    — e.g. hint, share

   The base rules only ever read tokens; a role modifier rebinds those tokens
   rather than restating the properties. So retheming a role means editing
   values in one block, and a new property added to the base rule is picked up
   by every role at once instead of needing to be repeated per modifier. */
:root {
  --modal-backdrop-bg: rgba(0, 0, 0, 0.5);
  --modal-backdrop-z: 50;
  --modal-backdrop-pad: 0;
  --modal-backdrop-animation: modal-backdrop-in 0.15s ease-out;

  --modal-panel-bg: #fff;
  --modal-panel-fg: inherit;
  --modal-panel-radius: 0.75rem;
  --modal-panel-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
  --modal-panel-width: 100%;
  --modal-panel-max-width: 24rem;
  --modal-panel-margin: 0 1rem;
  --modal-panel-pad: 1.5rem;
  /* Tall dialogs stop here and scroll inside .modal-body. 100% is the
     backdrop, which is fixed to the visible screen — no 100vh problem. */
  --modal-panel-max-height: calc(100% - 2rem);
  --modal-panel-animation: modal-panel-in 0.15s ease-out;

  --modal-close-inset: 9px;
  --modal-close-pad: 0;
  --modal-close-color: #6b7280;
  --modal-close-hover-color: #374151;
  --modal-close-opacity: 1;
  --modal-close-hover-opacity: 1;
  /* The focus ring sits just outside the sheet's ✕ glyph. */
  --modal-close-ring-offset: 2px;

  /* The info ✕'s box, and how far down the text must start to clear it. */
  --modal-close-info-inset: 3px;
  --modal-close-info-pad: 6px;
  --modal-close-info-icon: 20px;
  --modal-close-info-clearance: calc(var(--modal-close-info-inset) + 2 * var(--modal-close-info-pad) + var(--modal-close-info-icon) + 4px);

  --confirm-message-fg: #374151;
  --confirm-message-font-size: 0.95rem;
  --confirm-message-line-height: 1.55;
  --confirm-message-margin-bottom: 1.25rem;
  --confirm-btn-fg: var(--ink);
  --confirm-btn-border: 2px solid var(--ink);
  --confirm-btn-bg: transparent;
  --confirm-btn-hover-bg: color-mix(in srgb, var(--ink) 8%, transparent);
  --confirm-btn-radius: 6px;
  --confirm-btn-pad: 0.45rem 1.1rem;
  --confirm-btn-font-size: var(--directional-text);
  --confirm-btn-weight: 400;
}
@keyframes modal-backdrop-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes modal-panel-in {
  from { opacity: 0; transform: scale(0.9); }
  to { opacity: 1; transform: scale(1); }
}
/* For a player who asks for less motion, a panel fades in without growing. */
@media (prefers-reduced-motion: reduce) {
  @keyframes modal-panel-in {
    from { opacity: 0; }
  }
}
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--modal-backdrop-z);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--modal-backdrop-pad);
  background: var(--modal-backdrop-bg);
  animation: var(--modal-backdrop-animation);
}
.modal-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  max-height: var(--modal-panel-max-height);
  background: var(--modal-panel-bg);
  color: var(--modal-panel-fg);
  border-radius: var(--modal-panel-radius);
  box-shadow: var(--modal-panel-shadow);
  max-width: var(--modal-panel-max-width);
  width: var(--modal-panel-width);
  margin: var(--modal-panel-margin);
  padding: var(--modal-panel-pad);
  font-family: "Roboto", var(--font-sans);
  font-size: var(--directional-text);
  animation: var(--modal-panel-animation);
}
/* A dialog that opens on its own focuses its panel (see useDialogFocus). It
   is a resting place for focus, not a control, so it never rings. */
.modal-panel:focus { outline: none; }
/* The content scrolls; the panel doesn't. Deliberately not positioned, so
   the ✕ (absolute, measured from the panel) stays on the panel frame
   instead of scrolling away with the text.
   A scroller clips what paints past its edges, so it pads by a focus ring's
   reach — or the ring on a button at its edge is cut off — and gives the
   room back as a negative margin so the content doesn't move. */
.modal-body {
  overflow-y: auto;
  min-height: 0;
  overscroll-behavior: contain;
  padding: var(--focus-ring-ink-reach);
  margin: calc(-1 * var(--focus-ring-ink-reach));
}
/* confirm — a question and buttons, narrower than a sheet: the share
   dialog's choices. */
.modal-panel--confirm {
  --modal-panel-max-width: 18rem;
}
/* info — a short statement that hugs its content, on a lighter backdrop */
.modal-backdrop--info {
  --modal-backdrop-bg: rgba(0, 0, 0, 0.4);
  --modal-backdrop-z: 100;
  --modal-backdrop-pad: 16px;
  --modal-backdrop-animation: none;
}
.modal-panel--info {
  --modal-panel-bg: var(--tile);
  --modal-panel-fg: var(--ink);
  --modal-panel-radius: 8px;
  --modal-panel-shadow: none;
  --modal-panel-width: fit-content;
  --modal-panel-max-width: none;
  --modal-panel-margin: 0;
  /* Its text is centred and runs the full width, so it starts below the ✕
     rather than running under it. */
  --modal-panel-pad: var(--modal-close-info-clearance) 32px 28px;
  --modal-panel-animation: none;
}
.modal-close {
  position: absolute;
  top: var(--modal-close-inset);
  right: var(--modal-close-inset);
  background: transparent;
  border: 0;
  padding: var(--modal-close-pad);
  color: var(--modal-close-color);
  opacity: var(--modal-close-opacity);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  transition: color 0.15s ease, opacity 0.15s ease;
}
.modal-close:focus { outline: none; }
/* Full strength: the info ✕ is dimmed at rest, and so would its ring be. */
.modal-close:focus-visible {
  outline: var(--focus-ring-ink);
  outline-offset: var(--modal-close-ring-offset);
  opacity: 1;
}
.modal-close--info {
  --modal-close-inset: var(--modal-close-info-inset);
  --modal-close-pad: var(--modal-close-info-pad);
  --modal-close-color: var(--ink);
  --modal-close-hover-color: var(--ink);
  --modal-close-opacity: 0.6;
  /* Inside the ✕'s box, so the ring reaches no nearer the text than it. */
  --modal-close-ring-offset: calc(-1 * var(--focus-ring-ink-width));
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-tap-highlight-color: transparent;
}
/* Sized here rather than by XIcon's own attributes, from the token the
   panel's clearance above is worked out from. */
.modal-close--info svg {
  width: var(--modal-close-info-icon);
  height: var(--modal-close-info-icon);
}
@media (hover: hover) {
  .modal-close:hover {
    color: var(--modal-close-hover-color);
    opacity: var(--modal-close-hover-opacity);
  }
  .modal-close--info:hover {
    --modal-close-hover-opacity: 1;
  }
}
/* confirm-role parts. The share dialog uses the buttons; .confirm-message
   waits for a dialog that asks its question in words, and stays so the
   --confirm-* tokens match the other games'. */
.confirm-message {
  font-family: var(--font-sans);
  font-size: var(--confirm-message-font-size);
  color: var(--confirm-message-fg);
  line-height: var(--confirm-message-line-height);
  margin-bottom: var(--confirm-message-margin-bottom);
  text-align: center;
}
.confirm-buttons {
  display: flex;
  gap: 0.625rem;
  justify-content: center;
}
.confirm-btn {
  padding: var(--confirm-btn-pad);
  border-radius: var(--confirm-btn-radius);
  font-family: var(--font-sans);
  font-size: var(--confirm-btn-font-size);
  font-weight: var(--confirm-btn-weight);
  cursor: pointer;
  border: var(--confirm-btn-border);
  background: var(--confirm-btn-bg);
  color: var(--confirm-btn-fg);
  transition: background 0.15s;
}
.confirm-btn:focus { outline: none; }
.confirm-btn:focus-visible {
  outline: var(--focus-ring-ink);
  outline-offset: var(--focus-ring-ink-offset);
}
@media (hover: hover) {
  .confirm-btn:hover {
    background: var(--confirm-btn-hover-bg);
  }
}
.instructions-title {
  font-size: 1.125rem;
  font-weight: 600;
  color: #111827;
  margin-bottom: 0.75rem;
}
/* The title is semibold; the mark after the name is not, as in every game. */
.instructions-tm {
  font-weight: 400;
}
.instructions-body {
  font-size: var(--directional-text);
  color: #374151;
  line-height: 1.625;
}
/* The global reset zeroes paragraph margins, so the gaps are put back here:
   the same gap as Forage's between the rule paragraphs and before the tips,
   and a tighter one within the tips so they read as one group. */
.instructions-body > * + * {
  margin-top: 0.5rem;
}
.instructions-tips p + p {
  margin-top: 0.25rem;
}
/* The keyboard and screen reader notes: Forage's collapsible, as Forage has it. */
.instructions-body .a11y-notes {
  margin-top: 0.75rem;
}
.instructions-body .a11y-notes summary {
  cursor: pointer;
  font-weight: 600;
}
.instructions-body .a11y-notes dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.25rem 0.75rem;
  margin: 0.5rem 0 0;
}
.instructions-body .a11y-notes dt {
  font-weight: 600;
}
.instructions-body .a11y-notes dd {
  margin: 0;
}
/* A flex box of fixed height, so a key's label — a word or a drawn arrow —
   is centred by layout rather than by wherever its font puts it. */
.instructions-body .a11y-notes kbd {
  font: inherit;
  font-size: 0.8em;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.5em;
  vertical-align: middle;
  padding: 0 0.3em;
  border: 1px solid currentColor;
  border-radius: 3px;
  white-space: nowrap;
}
.instructions-body .a11y-notes kbd svg {
  width: 1em;
  height: 1em;
}
/* Words hidden inside a sentence here — a pictured button's or an arrow
   key's name — stay where they're read, rather than pinned to the screen's
   corner as .visually-hidden is for live regions: a screen reader exploring
   by touch finds things by where they are. */
.instructions-body .visually-hidden {
  position: absolute;
  top: auto;
  left: auto;
}
/* Clear's ✕ and Hint's "?", pictured in a line of text as the board draws
   them (.icon-button): the same faded ink, the "?" at the text's own size,
   and the ✕ in the same proportion to it as on the board. */
.instructions-body .pictured-button {
  display: inline-flex;
  color: var(--ink);
  opacity: var(--icon-button-opacity);
}
.instructions-body .pictured-button .hint-glyph {
  font-size: 1em;
}
/* An inline-flex box holding only the icon sits with its bottom on the
   baseline; lowered from there just enough to centre its ink on the "?"
   and the capitals. Centred on the lowercase letters (middle), it sat on
   the baseline and looked dropped beside the punctuation after it. */
.instructions-body .pictured-button--clear {
  vertical-align: -0.25em;
}
/* The strokes span only 14 of the icon's 24 units, so 5 units either side are
   blank — which, on top of the word spaces around the ✕, spread it further
   from its neighbours than the "?" is from its own. Pulled in by 3.5 of
   them, keeping 1.5 (about .075em): what a letter keeps beside its ink. */
.instructions-body .pictured-button--clear svg {
  width: calc(1em * var(--clear-icon-scale));
  height: calc(1em * var(--clear-icon-scale));
  margin-inline: calc(-1em * var(--clear-icon-scale) * 3.5 / 24);
}
.instructions-body .a11y-notes dl + p {
  margin-top: 0.5rem;
}
/* Copyright and feedback link, as in Forage and Threads. */
.instructions-footer {
  margin-top: 1rem;
  font-size: 0.75rem;
  color: #6b7280;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.instructions-feedback {
  color: #2563eb;
  text-decoration: underline;
}
.instructions-feedback:hover {
  color: #1e40af;
}
/* The share button, its choices dialog, and the toast it reports with. */
/* Share button + its fallback modal's read-only text field */
.share-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin: 0 auto var(--below-board-gap);
  padding: 8px 18px;
  font-family: var(--font-sans);
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--ink);
  background: transparent;
  border: 1.5px solid var(--ink);
  border-radius: 8px;
  cursor: pointer;
  opacity: 0.85;
  transition: opacity 0.15s ease;
}
.share-button:hover { opacity: 1; }
/* Every share control's focus: the ink ring, on keyboard focus only. */
.share-button:focus,
.share-choice input:focus { outline: none; }
.share-button:focus-visible,
.share-choice input:focus-visible {
  outline: var(--focus-ring-ink);
  outline-offset: var(--focus-ring-ink-offset);
}
/* The choices dialog: two checkboxes, the preview, and Share (the dialogs'
   shared .confirm-btn, in dialogs.css). */
.share-choices {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 0;
  border: 0;
}
/* The dialog's text, choices, caption and message alike, at one size. */
.share-choice,
.share-preview-caption,
.share-preview {
  font-family: var(--font-sans);
  font-size: var(--directional-text);
}
.share-choice {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ink);
  cursor: pointer;
}
.share-choice input {
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}
/* Says what the box below is, quieter than the message itself. */
.share-preview-caption {
  margin: 18px 0 6px;
  text-align: left;
  color: var(--ink-muted);
}
/* What will be sent, in a box of the page's colour: the message itself, set
   apart from the dialog's own wording. Every message the choices can make is
   in it, in one grid cell, the shown one over unseen copies, so the box is
   as tall as the longest and the dialog keeps its height as boxes are
   ticked. */
.share-preview {
  display: grid;
  margin: 0 0 18px;
  padding: 10px 12px;
  line-height: 1.45;
  text-align: left;
  color: var(--ink);
  background: var(--bg);
  border-radius: 6px;
}
.share-preview > * {
  grid-area: 1 / 1;
}
.share-preview-copy {
  visibility: hidden;
}
.share-modal-text {
  display: block;
  width: 240px;
  max-width: 100%;
  padding: 8px 10px;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: 6px;
}
/* A transient status message, not a modal (no focus-trap machinery needed).
   Fades itself out; the leaving class drives the fade. */
@keyframes toast-fadein {
  from { opacity: 0; transform: translate(-50%, -50%) translateY(-10px); }
  to   { opacity: 1; transform: translate(-50%, -50%) translateY(0); }
}
.toast {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 200;
  background: var(--toast-bg);
  color: var(--toast-fg);
  padding: 0.625rem 1.25rem;
  border-radius: 0.5rem;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.2);
  font-family: var(--toast-font);
  font-size: 0.9rem;
  font-weight: 500;
  white-space: nowrap;
  pointer-events: none;
  opacity: 1;
  animation: toast-fadein 0.3s ease-out;
  transition: opacity 0.5s ease;
}
.toast--leaving {
  opacity: 0;
  pointer-events: none;
}
/* ── Game ─────────────────────────────────────────────────────────────────
   Ported from the prototype's style.css (itself lifted from Threads), with
   the color/font values now sourced from the tokens in tokens.css instead
   of restated locally, and updated to this app's own class names. */
/* Plain --ink, as Forage does: a lighter grey all but vanished on the purple
   page, and these messages may be all the player sees. */
.loading,
.load-error {
  font-family: var(--font-sans);
  color: var(--ink);
  text-align: center;
  margin: 1rem 0;
}
/* Separate filled buttons, all one width, as in the designer's mockup. The
   selected one and the others are each other's reverse — a light label on the
   accent, an accent label on a pale fill — so which is picked shows at a
   glance. Last on the screen: under the bank, or on a solved level under the
   feedback link and notice. */
.difficulty-level-toggle {
  display: flex;
  gap: 7px;
  margin: 0 auto 1.5rem;
  width: fit-content;
}
.difficulty-level-btn {
  appearance: none;
  min-width: 3.75rem;
  padding: .55rem .75rem;
  border: none;
  border-radius: 6px;
  background: var(--difficulty-level-idle);
  color: var(--accent);
  font-family: var(--font-sans);
  font-size: .8rem;
  /* Fixed, at what Roboto's normal line height comes to: a solved level's ✓,
     drawn from a fallback font with taller metrics, would otherwise grow the
     button and nudge the board below it down. */
  line-height: .9375rem;
  cursor: pointer;
  transition: background .14s ease, color .14s ease, box-shadow .14s ease;
  /* A caption, not text: a double-click in the empty space beside the bank
     selects the nearest word, which would otherwise be this one. */
  -webkit-user-select: none;
  user-select: none;
  /* No phone tap highlight (blue on Android): a colour from outside the
     palette, flashed before the press turns the button to the accent. */
  -webkit-tap-highlight-color: transparent;
}
/* A preview of picking it: an accent edge, with the fill left pale so the
   label stays readable. */
.difficulty-level-btn:hover {
  box-shadow: inset 0 0 0 2px var(--accent);
}
.difficulty-level-btn[aria-pressed="true"],
.difficulty-level-btn[aria-pressed="true"]:hover {
  background: var(--accent);
  color: var(--accent-ink);
}
/* Keyboard focus on the screen's controls: the ink ring Share wears
   (share.css), set off the control, not the browser's blue. One rule, so no
   control's ring drifts from the others'. */
.difficulty-level-btn:focus,
.feedback-link a:focus,
.notice-link:focus,
.cross-promo-link:focus { outline: none; }
.difficulty-level-btn:focus-visible,
.feedback-link a:focus-visible,
.notice-link:focus-visible,
.cross-promo-link:focus-visible {
  outline: var(--focus-ring-ink);
  outline-offset: var(--focus-ring-ink-offset);
}
.board {
  display: flex;
  flex-direction: column;
  /* Also the stretch of the acrostic's strip between rows (below). */
  --board-row-gap: calc(var(--tile-gap) * 4);
  /* How far the strips reach past their tiles (below): half the gap between
     tiles, so a strip's edge falls midway between a tile and the next. */
  --bleed: calc(var(--tile-gap) / 2);
  gap: var(--board-row-gap);
  margin-bottom: var(--board-bank-gap);
  /* On solving, how long the other words take to fade (below), before the
     frame is traced. */
  --others-fade: .4s;
}
/* A word's strip: --word-strip behind every word to find — each row's word
   and the acrostic column — showing between its tiles and through its empty
   cells, so the words stand out from the checkerboard. It bleeds --bleed
   (half the gap between tiles) past the word on every side, the same all
   round.
   Isolating the hosts keeps the strips (z-index -1) behind the cells but in
   front of the page. The board also holds the solved frame's svg, laid over
   it. */
.board,
.word-cells {
  position: relative;
  isolation: isolate;
}
.word-row {
  position: relative;
}
/* The strips' pieces: behind the word's tiles, its own strip (::before) and
   the acrostic column's band behind its first letter (::after), as tall as
   each other and inside .word-cells, so a shaking word takes both along;
   and the column between rows (.word-row::after, below). */
.word-cells::before,
.word-cells::after,
.word-row::after {
  content: "";
  position: absolute;
  z-index: -1;
  background: var(--word-strip);
}
.word-cells::before,
.word-cells::after {
  inset: calc(-1 * var(--bleed));
}
.word-cells::before {
  transition: background-color .3s ease;
}
.word-cells::after,
.word-row::after {
  width: calc(var(--tile-width) + 2 * var(--bleed));
}
.word-cells::after {
  right: auto;
}
/* The acrostic column's strip in the gap below a row, from its band to the
   next row's, overlapping each by half a pixel so no seam shows. Drawn by
   the row, which stays put while its word shakes. */
.word-row::after {
  top: calc(100% + var(--bleed) - .5px);
  left: calc(-1 * var(--bleed));
  height: calc(var(--board-row-gap) - 2 * var(--bleed) + 1px);
}
/* No gap below the last row. By element type: a solved board ends with its
   frame's svg, so a :last-child rule would stop matching at the moment of
   solving, poking the column out through the frame. */
.word-row:last-of-type::after {
  content: none;
}
/* Solved, there's no word left to find: the strips fade away, but for the
   acrostic's and the themed row's, inside their frame (below). */
.board.solved .word-row:not(.themed) .word-cells::before {
  background-color: transparent;
}
/* The solved board's frame round the acrostic and the themed row, from the
   designer's mockup (SolvedFrame draws it): a deep purple line, dark enough
   to stand out from the page outside it and the strip inside, its outer side
   --highlight-frame out from their tiles. Laid over the board, after its
   rows. */
.solved-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  fill: none;
}
.solved-frame .frame-line {
  stroke: var(--accent-deep);
  stroke-width: var(--highlight-line);
}
/* Traced at the moment of solving (see Board), as if drawn with a pencil: one
   stroke clockwise round both words (frameCorners has the route). The line is
   one unit long (pathLength), so the dash starts as all gap and ends as the
   whole frame. */
@keyframes trace {
  from { stroke-dashoffset: 1; }
  to { stroke-dashoffset: 0; }
}
/* Only for a player fine with motion; otherwise it is simply there. Held
   undrawn (backwards) while the other words fade. */
@media (prefers-reduced-motion: no-preference) {
  .board.just-solved .solved-frame path {
    stroke-dasharray: 1;
    animation: trace 1.5s ease-in-out var(--others-fade) backwards;
  }
}
/* A row: its word's cells, then its buttons, all --tile-gap apart. */
.word-row,
.word-cells {
  display: flex;
  gap: var(--tile-gap);
}
/* The rest of the row after the word: Clear first, a tile gap after the
   word's last letter, and Hint pushed to the row's right edge (below), so
   every row's Hint lines up in one column however long the word. Exactly as
   tall as a tile, and the buttons no taller than that, so they never change a
   row's height. */
.answer-actions {
  display: flex;
  flex: 1;
  align-items: center;
  height: var(--tile-height);
  /* Controls, not text — like the tiles, a mouse drag across the row must
     never highlight the "?" or the ✕. */
  -webkit-user-select: none;
  user-select: none;
}
.answer-actions .icon-button {
  height: min(var(--icon-button-size), var(--tile-height));
}
.answer-actions .clear-button {
  margin-right: calc(-1 * var(--answer-actions-overlap));
}
/* Its placeholder on a solved row, too, which takes the same class. */
.answer-actions .hint-button {
  margin-left: auto;
}
/* Played once when a row was just filled completely but doesn't match the
   solution — a "this looks done but isn't" cue, since nothing else
   distinguishes that state from mid-progress. Transient only: no lasting
   marker is left behind once the animation ends. Its duration,
   --shake-duration, is set by Row from SHAKE_DURATION_MS in useRowShake.ts,
   which clears the triggering state after it. */
@keyframes row-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-6px); }
  40% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(4px); }
}
/* For a player who asks for less motion, the same cue stands still: the word
   dims once and comes back. Still the only sign the row is wrong, so it
   changes form rather than going. */
@media (prefers-reduced-motion: reduce) {
  @keyframes row-shake {
    50% { opacity: .4; }
  }
}
/* Just the word, with its strips: its Hint and Clear, and the acrostic's
   strip in the gaps around it, stay put. */
.word-row.shake .word-cells {
  animation: row-shake var(--shake-duration) ease-in-out;
}
/* Sized by the tile tokens on .app > main (in layout.css). */
.cell,
.tile {
  width: var(--tile-width);
  height: var(--tile-height);
  border-radius: 0;
  font-family: var(--font-serif);
  font-size: var(--tile-font-size);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .14s ease, color .14s ease, border-color .14s ease;
  /* Letters are game pieces, not text — dragging over them (a tile drag, or
     just a stray mouse gesture around the board) must never start a native
     text selection. */
  -webkit-user-select: none;
  user-select: none;
  /* Nor, pressed, show the phone's own tap highlight (blue on Android) over
     a tile or an empty blank: a colour from outside the palette. */
  -webkit-tap-highlight-color: transparent;
}
/* Rounded up to a whole pixel so letters render at an integer size. Gated by
   @supports rather than written as a second font-size fallback line: because
   it contains var(), a browser without round() would still accept it at parse
   time, then discard it at computed time and reset the letter to the
   inherited size instead of keeping the unrounded one. */
@supports (font-size: round(up, 1.5px, 1px)) {
  .cell,
  .tile {
    font-size: round(up, var(--tile-font-size), 1px);
  }
}
.cell.fixed {
  background: var(--tile);
  color: var(--ink);
}
/* An empty blank is a placed tile without its letter — a white frame filled
   with --tile-placed — so every position in a word is a tile, and a blank
   shows by having no letter. */
.cell.blank {
  border: 2px solid var(--tile);
  background: var(--tile-placed);
  padding: 0;
}
/* A blank cell keeps its frame whether it is empty or holds a tile, so filling
   it changes nothing about the outline. */
.cell.blank .tile {
  width: 100%;
  height: 100%;
  border: 0;
}
/* What tells a provisional placement from a permanent given letter (plain
   --tile): the placed tile's own lighter purple. Kept at two classes, apart
   from the rule above, so the solved and drop-target looks below still
   override it. */
.cell .tile {
  background: var(--tile-placed);
}
/* The one "solved" look, for a whole solved row and for a solved acrostic
   cell — the acrostic's first-letter column can solve (and lock)
   independently of its row's own word. Blank cells are fully covered by the
   tile dropped into them, so the look goes on that tile too, not just on the
   (now hidden) cell underneath it. Tiles have no border, so border-color only
   ever shows on cells. */
.word-row.solved .cell,
.word-row.solved .tile,
.cell.acrostic-solved,
.cell.acrostic-solved .tile {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
}
/* On a solved board, every tile but the acrostic's and the themed row's
   fades, so the two framed words stand out; a board shown already solved
   starts out faded. Dark letters on the softer tile, the reverse of the
   framed words' light-on-accent, keep the rest of the solution readable. */
.board.solved .word-row:not(.themed) .cell:not(.acrostic-solved),
.board.solved .word-row:not(.themed) .cell:not(.acrostic-solved) .tile {
  background: var(--tile-faded);
  border-color: var(--tile-faded);
  color: var(--ink);
}
/* Faded at the moment of solving, before the frame is traced: all those
   tiles together, from the solved look, letters and all. A transition would
   start from each one's colour before solving (an earlier row's accent, the
   last row's white and pale tiles) and not run at all on the tile just
   placed, which is new to the page, so they would fall out of step. */
@keyframes fade-others {
  from {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
  }
}
.board.just-solved .word-row:not(.themed) .cell:not(.acrostic-solved),
.board.just-solved .word-row:not(.themed) .cell:not(.acrostic-solved) .tile {
  animation: fade-others var(--others-fade) ease;
}
/* Stacked, centred rows — Bank splits the tiles into them (bottom-heavy, see
   bankRows.ts) rather than leaving it to flex-wrap, which fills the top rows
   first. */
.bank {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--tile-gap);
  /* Inset by a bit less than a tile on each side, so the bank reads as its
     own area rather than lining up neatly with a column of the board. */
  margin: 0 calc(var(--tile-slot) * .75) calc(var(--board-bank-gap) * 1.25);
  /* As tall as the puzzle's full set of tiles makes it (--bank-rows, from
     Bank), so what follows it stays put as it empties. */
  min-height: calc(var(--bank-rows) * var(--tile-height) + (var(--bank-rows) - 1) * var(--tile-gap));
}
.bank-row {
  display: flex;
  gap: var(--tile-gap);
}
/* Sizing/font/layout for .tile are covered by the combined ".cell, .tile"
   rule above — this block is only the properties unique to being a tile
   (as opposed to a plain cell). */
.tile {
  appearance: none;
  border: none;
  background: var(--tile);
  color: var(--ink);
  cursor: grab;
  touch-action: none;
}
.tile:disabled {
  cursor: default;
}
/* The tile being dragged (useTileDrag marks it), over the tiles it passes.
   Its word's cells are isolated (for the strips, above), so that only lifts
   it within its own word: the word it's dragged out of is raised too, or a
   later word would be drawn over it. */
.tile[data-dragging] {
  z-index: 10;
}
.word-cells:has(.tile[data-dragging]) {
  z-index: 1;
}
/* The cell (or row button) currently targeted by typing — a ring drawn right
   outside the cell's frame, so it shows the same whether or not a tile covers
   the cell, and, while the cell is empty, a fill (a placed tile covers it).
   The ring stays within --tile-gap so neighbours' rings never collide. */
.cell.typing-cursor,
.icon-button.typing-cursor {
  outline: 3px solid var(--accent);
}
.cell.typing-cursor {
  background: var(--cursor-fill);
}
/* On a row's Clear or Hint (reached with the arrow keys), the faint icon
   comes up to full strength too, as on hover. */
.icon-button.typing-cursor {
  opacity: 1;
}
/* Where a dragged tile will land if let go now — so a player whose finger
   hides the drop point can still see the outcome before releasing. The accent
   border marks it, and a fill a step darker than the blank or placed tile it
   covers, noticeably but not jarringly (it shows only mid-drag). Empty and
   filled cells look alike, so one look marks both; a filled cell's tile
   covers the cell, so the tile takes the tint too. The typing cursor, which
   can show mid-drag, stays the darker mark, drawn outside the cell. */
.cell.drop-target,
.cell.drop-target .tile {
  background: color-mix(in oklch, var(--accent) 50%, var(--tile));
}
.cell.drop-target {
  border-color: var(--accent);
}
/* A placed tile that letting go now would send back to the bank — faded on
   the tile itself rather than marking the bank, since anywhere off the board
   will do. */
.tile.returning {
  opacity: 0.6;
  scale: 0.9;
}
/* Unseen, but there for a screen reader and for focus — display:none and
   visibility:hidden would take it away from both. The hidden typing input
   exists to take focus (a physical keyboard types through it; touch screens
   never focus it, see useTypingCursor); the live region (LiveRegion), to be
   heard.

   Pinned to the top of the screen rather than left in the page flow, so
   focusing the input never scrolls the board, and neither takes up room. */
.typing-input,
.visually-hidden {
  position: fixed;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
/* Unseen but still on the page, so a double-click in empty space near it
   would select its words. Not the typing input: in Safari, an input that
   can't be selected may not take typing either. */
.visually-hidden {
  -webkit-user-select: none;
  user-select: none;
}
/* 16px text, because iOS Safari zooms the page in on focusing any input with
   smaller text. */
.typing-input {
  font-size: 16px;
}
/* The time and the hint count (SolvedLines), between the wordmark and the
   board, as Rungs has them. Room for both lines is kept from the start, so
   the board never moves when they appear; .5rem under the wordmark (on top
   of the column's gap), and 1rem above the board. */
.solved-lines {
  --win-line-height: 1.5rem;
  --tally-line-height: 1.25rem;
  min-height: calc(var(--win-line-height) + var(--tally-line-height));
  margin: .5rem 0 1rem;
  text-align: center;
  /* Appears the moment the last tile is dropped, mid mouse gesture — without
     this a native text selection from that drag can sweep it up. */
  -webkit-user-select: none;
  user-select: none;
}
.win {
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 500;
  line-height: var(--win-line-height);
  color: var(--ink);
}
.hint-tally {
  font-family: var(--font-sans);
  font-size: .875rem;
  line-height: var(--tally-line-height);
  color: var(--ink-muted);
}
/* Faded in when solved on screen, with Rungs' timings: the hint count
   starting a little after the time. Held hidden (backwards) until each
   starts. */
@keyframes fade-in {
  from { opacity: 0; }
}
.solved-lines.just-solved .win {
  animation: fade-in .4s ease-in-out .1s backwards;
}
.solved-lines.just-solved .hint-tally {
  animation: fade-in .3s ease-in-out .25s backwards;
}
/* The ask for feedback while in development (FeedbackLink), under the board
   on a solved level, in the place the bank had. */
.feedback-link {
  margin: 0 0 var(--below-board-gap);
  font-family: var(--font-sans);
  font-size: .875rem;
  line-height: 1.25rem;
  color: var(--ink);
  text-align: center;
}
/* A notice from the portal (design-for-notices.md, Part 3, Look), under the
   feedback link on a solved difficulty level. A card in the tiles' white on the
   page's purple, so it stands out from the page. */
.notice {
  position: relative;
  width: 100%;
  margin: 0 0 var(--below-board-gap);
  background: var(--tile);
  border-radius: 8px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
/* Centred like the rest of the screen, with equal margins: the ✕ sits in the
   corner, over the padding, not in a column that would push the text over.
   A fixed line height, so a line holding the ↗ (drawn from a fallback font
   with taller metrics) isn't spaced further from its neighbour. */
.notice-text {
  padding: 12px 32px;
  font-family: var(--font-sans);
  font-size: .875rem;
  line-height: 1.25rem;
  color: var(--ink);
  text-align: center;
}
/* A small caps line above the text, in the link's colour: a label for whoever
   is previewing, not part of the message. */
.notice-preview {
  padding: 10px 32px 0;
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--link);
  text-align: center;
}
.notice-preview + .notice-text {
  padding-top: 2px;
}
/* The game's links, under a solved level and in a notice. Never break
   inside: the line may break only in the space before one. */
.feedback-link a,
.notice-link {
  font-weight: 500;
  color: var(--link);
  text-decoration: underline;
  white-space: nowrap;
}
/* Still a 44px target, since it sits near the link, where a slip gives the
   wrong answer; the glyph is drawn in the target's corner so it reads as the
   card's own close control. */
.notice-dismiss {
  position: absolute;
  top: 0;
  right: 0;
  min-width: 44px;
  min-height: 44px;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  padding: 8px 10px 0 0;
  font-size: .875rem;
  color: var(--ink);
  background: none;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
}
.notice-dismiss:hover { color: var(--accent); }
.notice-dismiss:focus { outline: none; }
.notice-dismiss:focus-visible {
  /* Unseen beside the box-shadow, but drawn where a contrast theme (forced
     colours) drops box-shadows. */
  outline: 2px solid transparent;
  box-shadow: 0 0 0 2px var(--tile), 0 0 0 4px var(--ink);
}
/* Two of the portal's other games (CrossPromoFooter), last on a solved
   level's screen, below the level selector, as the other games end with
   theirs. Lifted from Threads, which promotes the same two. */
.cross-promo {
  width: 100%;
  margin: 0 0 var(--below-board-gap);
  display: flex;
  flex-direction: column;
  align-items: center;
}
.cross-promo-caption {
  margin: 0;
  font-family: var(--font-sans);
  font-size: .875rem;
  color: var(--ink-muted);
  text-align: center;
}
/* Each link is as wide as it needs, not half the row, so a tagline wraps
   under its own logo; space-between sets the pair at the row's two edges. */
.cross-promo-row {
  width: 100%;
  display: flex;
  justify-content: space-between;
  margin-top: 12px;
}
.cross-promo-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
  text-decoration: none;
}
/* Forage's wordmark is one row of tiles; Rungs' is a staircase of the same
   tile across three rows. Each is sized so Rungs' middle "U N G" row comes
   out the same tile height as Forage's row, which makes Rungs' logo taller
   overall; Forage's link is nudged down so its row of tiles lines up with
   "U N G". The figures are Threads'.
   Wider than Forage's 108px logo, which is narrower than "Make words from"
   (~112px): at the logo's width the tagline took 3 lines; at 140px, 2, with
   the logo centred above it. */
.cross-promo-link--forage { width: 140px; margin-top: 29px; }
/* Wider than its 71px logo, so its tagline wraps across about two lines,
   not four; the logo centres in it. */
.cross-promo-link--rungs { width: 140px; }
.cross-promo-icon { display: block; }
.cross-promo-icon--forage { width: 108px; height: 32px; }
.cross-promo-icon--rungs { width: 71px; height: 91px; }
.cross-promo-tagline {
  font-family: var(--font-sans);
  font-size: .9rem;
  line-height: 1.3;
  color: var(--ink-muted);
}
/* Hint and Clear buttons — lifted from Threads. */
.icon-button {
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--icon-button-padding);
  width: var(--icon-button-size);
  height: var(--icon-button-size);
  box-sizing: border-box;
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: var(--icon-button-opacity);
  transition: opacity 0.15s;
  -webkit-tap-highlight-color: transparent;
}
@media (hover: hover) {
  .icon-button:hover { opacity: 1; }
}
.hint-glyph {
  font-family: var(--font-sans);
  font-size: var(--answer-icon-size);
  font-weight: 700;
  /* A glyph's normal line box is taller than its font size; kept to the
     font size so the button's own height (see .answer-actions) holds. */
  line-height: 1;
}
.clear-button svg {
  width: var(--clear-icon-size);
  height: var(--clear-icon-size);
  flex-shrink: 0;
}
.icon-button--placeholder {
  visibility: hidden;
}
.hint-content {
  text-align: center;
}
/* Windows' contrast themes (forced colours) replace every colour with a few
   system ones and drop backgrounds, so whatever a background alone shows is
   redrawn here in system colours, which the theme picks. Placed letters need
   nothing: they sit in their blank's frame, a border, which the theme keeps,
   and given letters have none. */
@media (forced-colors: active) {
  /* The share dialog's preview is set apart by its background alone. */
  .share-preview {
    border: 1px solid CanvasText;
  }

  /* Solved: a solved row's tiles are disabled buttons, which the theme would
     grey out as unavailable, so they keep these colours instead. */
  .word-row.solved .cell,
  .word-row.solved .tile,
  .cell.acrostic-solved,
  .cell.acrostic-solved .tile {
    forced-color-adjust: none;
    background: Highlight;
    color: HighlightText;
    border-color: Highlight;
  }

  /* On a solved board, everything outside the frame steps back to plain. */
  .board.solved .word-row:not(.themed) .cell:not(.acrostic-solved),
  .board.solved .word-row:not(.themed) .cell:not(.acrostic-solved) .tile {
    background: Canvas;
    color: CanvasText;
    border-color: CanvasText;
    animation: none;
  }

  .solved-frame .frame-line {
    stroke: CanvasText;
  }

  .difficulty-level-btn[aria-pressed="true"] {
    forced-color-adjust: none;
    background: Highlight;
    color: HighlightText;
  }

  .cell.typing-cursor,
  .icon-button.typing-cursor {
    outline-color: Highlight;
  }

  .cell.drop-target {
    border-color: Highlight;
  }
}
