@font-face {
  font-family: 'Goma';
  src: url('fonts/gomarice_goma_block.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Plus Jakarta Sans';
  src: url('/editors/assets/fonts/Plus_Jakarta_Sans/PlusJakartaSans-VariableFont_wght.ttf') format('truetype');
  font-weight: 200 800;
  font-style: normal;
}

/* The word-face options for the Font setting (see FONTS in
   Textris.js). Shared faces are served from /editors/assets/fonts;
   heavier static weights are declared as `normal` so the atlas's default-weight
   canvas draw picks up the bolder, more display-friendly glyphs. */
@font-face {
  font-family: 'Special Elite';
  src: url('/editors/assets/fonts/SpecialElite-Regular.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}
@font-face {
  font-family: 'Quicksand';
  src: url('/editors/assets/fonts/Quicksand/static/Quicksand-Bold.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}
@font-face {
  font-family: 'Comfortaa';
  src: url('/editors/assets/fonts/Comfortaa/static/Comfortaa-Bold.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}
/* Lookgood — from 1001fonts, personal-use license; provenance and the EULA
   sit beside the file in /editors/assets/fonts/Lookgood/. */
@font-face {
  font-family: 'Lookgood';
  src: url('/editors/assets/fonts/Lookgood/Lookgood.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

/* The word blocks themselves are drawn by the WebGL stage (src/Stage.js).
   The --color-<key>-darker variables below are its tint palette — the block
   renderer looks keys (parts of speech, constraint values, source names) up
   here, so recoloring the game is a CSS edit. */
:root {
  /* --background-color and --accent-color are the source-of-truth PALETTE
     values in src/colors.js, injected at startup by applyPalette(); these are
     just the pre-JS fallbacks — keep them in sync with PALETTE. */
  --mistake-color: #ff8989;
  --background-color: #fdf388;
  --primary-color: var(--accent-color);
  --accent-color: #4a48b2;
  --text-color: var(--accent-color);
  --secondary-color: #edbc68;

  /* Parts of Speech — the classic tetromino palette, nudged darker where
     needed to stay readable on the white page. */
  --color-noun-darker: #e5150f;        /* Z red */
  --color-verb-darker: #00a8c8;        /* I cyan */
  --color-adjective-darker: #35b711;   /* S green */
  --color-adverb-darker: #ff7308;      /* L orange */
  --color-determiner-darker: #9b1fe8;  /* T purple */
  --color-preposition-darker: #2038e8; /* J blue */
  --color-conjunction-darker: #efaf00; /* O yellow, golden for contrast */
  --color-propernoun-darker: #d6218f;  /* magenta — the rare exotic piece */
  --color-interjection-darker: #86c90f;
  --color-value-darker: #6e6e6e;       /* garbage-block gray */
  --color-linear-darker: #8e40d6;
  --color-random-darker: #dc1e1e;

  /* Special Types */
  --color-delete-darker: #a8280e;
  --color-word-darker: #4a48b2; /* the 'word' constraint label — the UI indigo */
  --color-mistake-darker: var(--mistake-color);
  --color-interlace-darker: #00a8c8;

  --small-radius: 5px;
}

body {
  font-family: 'Plus Jakarta Sans', sans-serif;
  background-color: var(--background-color);
  /* The active brush tints the page (setBrush sets --background-color to the
     brush's PALETTE background) — ease between tints. */
  transition: background-color 0.5s ease;
  margin: 0px;
  height: 90vh;
  overflow: hidden;
}

/* Loading screen — shown from the first paint; the game syncs its
   background to the Column Color setting and drives the bar. */
#loading {
  position: fixed;
  inset: 0;
  z-index: 3000;
  background: var(--background-color); /* the page background, all through the boot */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  transition: opacity 0.4s ease-out;
}

#loading.hidden {
  opacity: 0;
  pointer-events: none;
}

.loading-label {
  font-family: 'Goma', sans-serif;
  color: var(--accent-color);
  font-size: 18px;
  letter-spacing: 0.1em;
}

.loading-track {
  width: min(320px, 60vw);
  height: 10px;
  border: 2px solid var(--accent-color);
  border-radius: var(--small-radius);
  padding: 2px;
}

#loading-bar {
  height: 100%;
  width: 0%;
  background: var(--accent-color);
  border-radius: 2px;
  transition: width 0.25s ease-out;
}

#game-banner {
  width: 100%;
  height: 4vh;
  min-height: 12px;

  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;

  box-sizing: border-box;
  font-family: 'Goma';
  color: var(--accent-color);
  border-bottom: 2px solid var(--accent-color);

  padding: 0.5vh 1vw;
  z-index: 1000;
}

/* The page's top chrome — the banner and the drifts settings bar under it —
   stacks over the stage canvas (z-index 1000, src/Stage.js), so the game's
   sprites draw beneath it rather than across it. z-index needs a position to
   bite; the banner's own 1000 above never had one. Modals (10000) stay on top. */
#game-banner,
#settings-bar {
  position: relative;
  z-index: 1001;
  /* Opaque, or the sprites simply show through — the chrome was transparent
     over the page. The page color, so the brush tint carries across it (and
     the same transition the body has, so the two never change out of step). */
  background-color: var(--background-color);
  transition: background-color 0.5s ease;
}

#game {
  width: 100%;
  height: 96vh;
}

.controls {
  display: flex;
  align-items: center;
  gap: 15px;
}

.control-btn {
  font-family: 'Goma';
  background-color: transparent;
  border: 2px solid var(--accent-color);
  color: var(--accent-color);
  padding: 2px 8px;
  font-size: 12px;
  cursor: pointer;
  outline: none;
  height: 80%;
  transition: background-color 0.2s, color 0.2s;
}

.control-btn:hover,
.control-btn:active {
  background-color: var(--accent-color);
  color: var(--primary-color);
}

.control-btn:active {
  transform: scale(0.98);
}

.back-link {
  color: var(--accent-color);
  text-decoration: none;
  cursor: pointer;
  font-size: 11px;
  font-weight: normal;
  font-family: 'Goma';
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s;
}

.back-link:hover {
  border-bottom-color: var(--accent-color);
}

.text {
  display: flex;
  align-items: center;
  height: 100%;
}

#instruction-text {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  max-width: 60%;
  z-index: 1500;
  color: var(--accent-color);
  font-family: 'Plus Jakarta Sans', sans-serif;
  user-select: none;
  font-size: 1.5em;
  text-align: center;
  transition: opacity 1s ease-out;
}

#instruction-text.hidden {
  opacity: 0;
  pointer-events: none;
}

.subtitle {
  margin-left: 15px;
  font-weight: bold;
  color: var(--accent-color);
  text-transform: uppercase;
  font-family: 'Goma';
}

/* Default input styles — clean accent-on-white, no bevels */
input, select, textarea {
  font-family: 'Plus Jakarta Sans', sans-serif;
  background-color: transparent;
  border: 1px solid var(--accent-color);
  border-radius: var(--small-radius);
  color: var(--text-color);
  padding: 6px 8px;
  font-size: 13px;
  outline: none;
  box-sizing: border-box;
}

input::placeholder, textarea::placeholder {
  color: var(--accent-color);
  opacity: 0.4;
}

label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 12px;
}

input[type="checkbox"] {
  width: 16px;
  height: 16px;
  margin: 0;
  cursor: pointer;
}

/* Make dropdown and checkbox appear on same line */
.option {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: 'Goma';
}

.option select, .option input, .option .dropdown {
  font-family: 'Goma';
  font-size: 10px;
  padding: 2px 6px;
  background-color: transparent;
  border: 1px solid var(--accent-color);
  color: var(--accent-color);
  cursor: pointer;
}

.option label {
  font-family: 'Goma';
  font-size: 10px;
  color: var(--accent-color);
}

#completed-wrapper {
  display: flex;
  flex-direction: column;
  position: absolute;
  width: fit-content;
  gap: 10px;
  background-color: inherit;
}

#submit {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100%;
}

#submit-button {
  z-index: 1000;
  background-color: var(--accent-color) !important;
  color: var(--background-color) !important;
  border: 2px solid var(--primary-color) !important;
  justify-content: center;
  font-size: 22px !important;
}

#submit-button:not(.visible) {
  display: none !important;
}

#submit-button.visible {
  display: flex !important;
}

/* Modal styles — the page's own modals (instructions, end game) and the
   corpus-select / loading windows built by shared/src/configuration (which
   expect .modal / .modal-content / .modal-header / .modal-body / .modal-footer
   from the host page). The drifts Modal component (.modal.meta-game) carries
   its own !important-scoped stylesheet and is unaffected. */
.modal {
  position: fixed;
  z-index: 2000;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.4);
  display: flex;
  justify-content: center;
  align-items: center;
  /* Everything inside modals is Jakarta — no block font here */
  font-family: 'Plus Jakarta Sans', sans-serif;
}

.modal .control-btn {
  font-family: inherit;
  height: auto;
  padding: 6px 12px;
  border-radius: var(--small-radius);
}

.modal-content {
  position: relative;
  background-color: var(--background-color);
  border: 2px solid var(--accent-color);
  max-width: 700px;
  width: 90%;
  max-height: 85vh;
  overflow: auto;
}

.modal-header {
  font-family: 'Plus Jakarta Sans', sans-serif;
  background-color: var(--accent-color);
  color: var(--background-color);
  padding: 12px 20px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.modal-header h1,
.modal-header h3 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
}

.modal-header .option-description {
  color: inherit;
  opacity: 0.85;
  margin: 0;
}

.modal-header .close-btn {
  position: absolute;
  right: 12px;
  top: 10px;
  background: none;
  border: none;
  color: inherit;
  font-size: 18px;
  cursor: pointer;
  padding: 0;
}

.modal-body {
  font-family: 'Plus Jakarta Sans', sans-serif;
  color: var(--accent-color);
  padding: 15px;
  font-size: 14px;
  box-sizing: border-box;
}

.modal-body h4 {
  font-weight: 600;
  margin-top: 10px;
  margin-bottom: 5px;
}

.modal-body p {
  margin-top: 2px;
  margin-bottom: 2px;
}

.modal-footer {
  padding: 12px 15px;
  border-top: 1px solid var(--accent-color);
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

/* Corpus-select window (sections from shared/src/configuration) */
.corpus-select-modal {
  max-width: 900px;
}

.corpus-select-body {
  display: flex;
  flex-direction: row;
  gap: 24px;
}

.corpus-select-section {
  flex: 1;
  min-width: 0;
}

.corpus-select-section h4 {
  margin: 0 0 12px 0;
  font-size: 15px;
}

.corpus-select-group {
  margin-bottom: 16px;
}

.corpus-select-label {
  display: block;
  margin-bottom: 6px;
  font-weight: 600;
  font-size: 13px;
  cursor: default;
}

.corpus-select-loading {
  text-align: center;
  padding: 10px;
  opacity: 0.6;
}

.corpus-select-list {
  max-height: 180px;
  overflow-y: auto;
}

.corpus-select-option {
  cursor: pointer;
  padding: 8px 10px;
  margin: 6px 0;
  border: 1px solid rgba(74, 72, 178, 0.3);
  border-radius: var(--small-radius);
  transition: background-color 0.15s ease;
}

.corpus-select-option:hover {
  background-color: rgba(74, 72, 178, 0.08);
}

.corpus-select-option p {
  margin: 3px 0 0 0;
  font-size: 0.85em;
  opacity: 0.7;
}

.modal input[type="text"],
.modal textarea {
  width: 100%;
  cursor: text;
}

.modal textarea {
  min-height: 80px;
  resize: vertical;
}

.corpus-select-btn {
  width: 100%;
  margin-top: 8px;
}

/* Inline refusal under the wiki URL-or-name field (src/WikiSelect.js):
   "no such page", answered in place instead of a modal or alert. */
.corpus-select-error {
  margin-top: 6px;
  font-size: 12px;
  color: #b3261e;
}

/* The new-document landing page (src/Landing.js): covers the whole page —
   banner and settings bar included — until Start; under the modals (the
   corpus loaders open over it) and the loading screen. The page wears the
   insert brush's yellows (colors.js PALETTE.brush.insert): its color as the
   page, its secondary as the bands drawn on it. */
#landing {
  --landing-bg: #ffed7d;
  --landing-band: #fbe13a;
  position: fixed;
  inset: 0;
  z-index: 1900;
  overflow-y: auto;
  background-color: var(--landing-bg);
  color: var(--accent-color);
  font-family: 'Plus Jakarta Sans', sans-serif;
  display: flex;
  justify-content: center;
  padding: 24px 16px;
  box-sizing: border-box;
}

#landing.hidden {
  display: none;
}

.landing-panel {
  width: min(960px, 100%);
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.landing-head {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.landing-head-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
}

.landing-description {
  margin: 0;
  font-size: 13px;
  line-height: 1.45;
  opacity: 0.8;
  max-width: 70ch;
}

.landing-description a {
  color: inherit;
}

.landing-title {
  font-size: 44px;
  line-height: 1;
  font-weight: 700;
  margin: 0;
}

.landing-subtitle {
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin: 0;
  opacity: 0.7;
}

/* Sections the level choice puts away (Landing.applyLevelChoice) stay away,
   whatever display their own rule sets. */
#landing [hidden] {
  display: none;
}

/* The levels: the prewritten ones in a row, then Custom on its own row
   (which opens the board row and the text cards below). Every option is
   bare words — no box, no fill: a level's name set word by word in Goma,
   each word stretched wide and tinted from the level's own color
   (--level-color, set inline from levels.js), leaning a step further toward
   the accent per word (--word-step). Custom is the same kind of big bubble
   word in the page's accent. The one picked stands at full strength; the
   rest sit back, faded. */
.landing-levels {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 28px;
}

.landing-levels-custom {
  margin-top: -4px;
}

#landing .landing-level-option {
  --level-color: var(--accent-color);
  display: inline-flex;
  align-items: baseline;
  gap: 0.9em;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--level-color);
  font-family: 'Goma', sans-serif;
  font-size: 48px;
  line-height: 1;
  text-transform: uppercase;
  cursor: pointer;
  overflow: visible;
  opacity: 0.4;
  transition: opacity 0.15s ease;
}

#landing .landing-level-option:hover {
  opacity: 0.75;
}

#landing .landing-level-option[aria-checked="true"] {
  opacity: 1;
}

.landing-level-word {
  display: inline-block;
  transform: scaleX(1.45);
  transform-origin: left center;
  margin-right: 0.45em; /* room for the stretch, which layout doesn't see */
  color: color-mix(in srgb, currentColor, var(--accent-color) calc(var(--word-step, 0) * 18%));
}

/* The board row: columns and rows. */
.landing-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
  border: 2px solid var(--accent-color);
  border-radius: var(--small-radius);
  padding: 10px 14px;
  background-color: var(--landing-band);
}

/* The question over the two text cards. */
.landing-sources-intro {
  margin: 6px 0 -4px;
  font-size: 15px;
  font-weight: 600;
}

.landing-toolbar label {
  font-size: 13px;
  gap: 8px;
}

.landing-toolbar input {
  width: 3.5em;
  text-align: center;
  font-size: 13px;
  padding: 4px 6px;
}

.landing-toolbar input.invalid {
  border-color: #b3261e;
}

.landing-fieldset-name,
.landing-source-name,
.landing-row-name {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* The two sources, side by side, each its own compact picker. Each card is
   drawn in its text's tint (--slot-color, set inline by Landing.refreshSlot
   from sources.js slotColors — the color its words carry on the board). */
.landing-sources {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

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

.landing-source {
  --tone: var(--slot-color, var(--accent-color));
  color: var(--tone);
  border: 2px solid var(--tone);
  border-radius: var(--small-radius);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

.landing-source.busy {
  opacity: 0.7;
}

.landing-source-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}

.landing-source-title {
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Each source's Randomize: a band under the title with a switch at its
   left — on, the band fills with the slot's tint and the knob sits right;
   off, an outline with the knob at rest on the left. The state is the
   switch itself, so the text says nothing more. */
.landing-shuffle {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  font-weight: 600;
  border: 2px solid var(--tone);
  border-radius: var(--small-radius);
  padding: 6px 10px;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.landing-shuffle:has(.landing-shuffle-box:checked) {
  background-color: var(--tone);
  color: var(--landing-bg);
}

/* The switch: a track in the band's text color (inputs don't inherit color,
   so it's told to), the knob sliding right and the track filling when on so
   the knob shows in the slot's tint against it. */
#landing .landing-shuffle-box {
  appearance: none;
  -webkit-appearance: none;
  color: inherit;
  width: 44px;
  height: 24px;
  margin: 0;
  flex: none;
  border: 2px solid currentColor;
  border-radius: 12px;
  background-color: transparent;
  position: relative;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

#landing .landing-shuffle-box::before {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background-color: currentColor;
  transition: transform 0.15s ease, background-color 0.15s ease;
}

#landing .landing-shuffle-box:checked {
  background-color: currentColor;
}

#landing .landing-shuffle-box:checked::before {
  transform: translateX(20px);
  background-color: var(--tone);
}

.landing-source-status {
  font-size: 12px;
  opacity: 0.8;
}

.landing-source-status.error {
  color: #b3261e;
  opacity: 1;
}

/* A picker row: a small caption, the control, and its button. */
.landing-row {
  display: grid;
  grid-template-columns: 5.5em minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  cursor: default;
}

.landing-row-paste {
  align-items: start;
}

.landing-row-paste .landing-row-name {
  padding-top: 6px;
}

.landing-row select,
.landing-row input[type="text"],
.landing-row textarea {
  width: 100%;
  min-width: 0;
  font-size: 13px;
  padding: 4px 6px;
  color: var(--tone);
  border-color: var(--tone);
}

.landing-row input::placeholder,
.landing-row textarea::placeholder {
  color: var(--tone);
}

.landing-row select {
  grid-column: 2 / -1;
  cursor: pointer;
}

.landing-row input[type="text"],
.landing-row textarea {
  cursor: text;
}

.landing-row textarea {
  resize: vertical;
  min-height: 2.6em;
}

/* Random Wikipedia offers under the Wikipedia row: each a row with the
   article's title and its opening lines. */
.landing-offers {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-left: calc(5.5em + 8px);
}

.landing-offers:empty {
  display: none;
}

.landing-offer {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  font-family: inherit;
  font-size: 12px;
  line-height: 1.35;
  text-align: left;
  background-color: transparent;
  border: 1px solid color-mix(in srgb, var(--tone) 35%, transparent);
  border-radius: var(--small-radius);
  color: var(--tone);
  padding: 5px 8px;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.landing-offer:hover {
  background-color: color-mix(in srgb, var(--tone) 10%, transparent);
}

.landing-offer strong {
  font-size: 12px;
}

.landing-offer span {
  opacity: 0.7;
}

#landing .control-btn {
  font-family: inherit;
  height: auto;
  padding: 4px 10px;
  font-size: 12px;
  border-radius: var(--small-radius);
}

/* A card's Load / Use buttons in the card's tint. */
#landing .landing-source .control-btn {
  border-color: var(--tone);
  color: var(--tone);
}

#landing .landing-source .control-btn:hover:not(:disabled),
#landing .landing-source .control-btn:active:not(:disabled) {
  background-color: var(--tone);
  color: var(--landing-bg);
}

/* Start: the last thing on the page, full width, filled. */
.landing-footer {
  display: flex;
  padding-bottom: 24px;
}

#landing .landing-start {
  width: 100%;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 12px 26px;
  background-color: var(--accent-color);
  color: var(--landing-bg);
}

#landing .landing-start:hover:not(:disabled) {
  background-color: transparent;
  color: var(--accent-color);
}

#landing .landing-start:disabled,
#landing .control-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

/* Skeleton rows for the wiki section's random offers (src/WikiSelect.js):
   option-shaped placeholders that shimmer until the articles land, each a
   beat behind the last. */
.wiki-skeleton {
  pointer-events: none;
}

.wiki-skeleton-bar {
  display: block;
  height: 12px;
  border-radius: var(--small-radius);
  background: linear-gradient(90deg,
    rgba(74, 72, 178, 0.10), rgba(74, 72, 178, 0.24), rgba(74, 72, 178, 0.10));
  background-size: 200% 100%;
  animation: wiki-shimmer 1.2s linear infinite;
}

.wiki-skeleton-title { width: 45%; }

.wiki-skeleton-desc {
  width: 85%;
  height: 9px;
  margin-top: 8px;
}

.wiki-skeleton:nth-child(2) .wiki-skeleton-bar { animation-delay: 0.15s; }
.wiki-skeleton:nth-child(3) .wiki-skeleton-bar { animation-delay: 0.3s; }

@keyframes wiki-shimmer {
  from { background-position: 200% 0; }
  to { background-position: 0% 0; }
}

/* The "Randomize word order" checkbox row in the corpus-select window
   (src/ShuffledCorpusSelect.js), tucked above the footer. Dressed as one of
   the window's own option rows — same border, radius, hover, and type — so
   it reads as part of the corpus list rather than a stray control. */
.shuffle-option {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 15px 12px;
  padding: 8px 10px;
  border: 1px solid rgba(74, 72, 178, 0.3);
  border-radius: var(--small-radius);
  transition: background-color 0.15s ease;
  cursor: pointer;
  color: var(--accent-color);
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-size: 13px;
  font-weight: 600;
}

.shuffle-option:hover {
  background-color: rgba(74, 72, 178, 0.08);
}

.shuffle-option input[type="checkbox"] {
  /* Drawn by hand — the native widget reads as the OS, not the window. */
  appearance: none;
  -webkit-appearance: none;
  width: 15px;
  height: 15px;
  margin: 0;
  border: 1px solid rgba(74, 72, 178, 0.5);
  border-radius: 3px;
  background: transparent;
  cursor: pointer;
  display: grid;
  place-content: center;
  flex: none;
}

.shuffle-option input[type="checkbox"]:checked {
  background-color: var(--accent-color);
  border-color: var(--accent-color);
}

.shuffle-option input[type="checkbox"]:checked::before {
  content: '';
  width: 9px;
  height: 9px;
  background: var(--background-color);
  clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}

/* The always-on control legend below the play area (index.html
   #controls-legend, filled by Textris.updateControlsLegend). One
   coloured brush name, then a row of key-cap + action chips. The bar is
   click-through, but its key caps are live controls — see .legend-key. */
#controls-legend {
  position: fixed;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1001;
  pointer-events: none;
  display: flex;
  /* One line, always — the longest brush rows (homophone) were folding the
     bar to two. The items refuse to shrink instead of wrapping their text. */
  flex-wrap: nowrap;
  white-space: nowrap;
  align-items: center;
  justify-content: center;
  /* The column gap is deliberately tighter than the items' text is large: the
     labels were raised for legibility and the space between items pays for
     it, so the bar reads bigger without growing. */
  gap: 7px 12px;
  max-width: 96vw;
  /* Named so the uses number outside the bar can line up with the content
     inside it rather than repeating the values (see .legend-uses). */
  --legend-pad-y: 8px;
  --legend-pad-x: 18px;
  padding: var(--legend-pad-y) var(--legend-pad-x);
  border-radius: 12px;
  /* The white surround, sourced from PALETTE.controlBackground (injected as
     --control-bg by applyPalette); kept slightly translucent. */
  /* The bar itself takes the active brush's full color (--brush-color, set per
     brush by updateControlsLegend); text/keys sit on it in the control white.
     Solid, not translucent — over the page it otherwise muted toward the pale
     accent. */
  background: var(--brush-color, var(--accent-color));
  font-family: 'Plus Jakarta Sans', sans-serif;
  /* --brush-ink is a readable contrast color for the current brush color
     (dark on light brushes like corpus, white on dark ones). */
  color: var(--brush-ink, var(--control-bg, #ffffff));
}
#controls-legend:empty { display: none; }

/* Debug-only points readout (toggled by updatePointsDisplay). */
#points {
  position: fixed;
  top: 8px;
  left: 8px;
  z-index: 1001;
  display: none;
  padding: 3px 8px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--control-bg, #ffffff) 85%, transparent);
  color: var(--accent-color);
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-size: 13px;
  font-weight: 700;
  pointer-events: none;
}

.legend-brush {
  font-weight: 800;
  font-size: 18px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.legend-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  /* The action wording (Choose, Row, Select …). Sized to read at a glance from
     playing distance while still clearing the key caps' height, so the bar
     keeps the height it had — the caps below stay at their own smaller size.
     Set a little under the mixed-case size that would match, because the
     labels are uppercased (see .legend-label): caps read larger at a given
     size and run wider, and the bar has no width to spare. */
  font-size: 16px;
  flex: none; /* hold width — the bar stays one line rather than squeezing items */
}

.legend-keys {
  display: inline-flex;
  gap: 4px;
}

/* Key caps are the game's on-screen controls: clicking or tapping one plays
   that key (Textris.watchLegendClicks reads data-key). The legend itself stays
   click-through — only the caps take pointer events, so the board underneath
   is still reachable everywhere else. */
.legend-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 5px;
  border: 1.5px solid currentColor;
  border-radius: 5px;
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  pointer-events: auto;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.12s ease, color 0.12s ease, transform 0.12s ease;
}
/* Pressed / hovered: the cap fills in, inverting against the legend bar. */
.legend-key:hover {
  background: currentColor;
  color: var(--brush-color, var(--accent-color));
}
.legend-key:active {
  transform: translateY(1px) scale(0.96);
}
.legend-key-wide { min-width: 54px; }

.legend-label {
  font-weight: 500;
  /* Uppercase, matching the brush tag beside it: it carries the legibility the
     bar needed without the extra weight that made the wording shout, and caps
     hold their shape better than lowercase at this size against a saturated
     brush color. */
  text-transform: uppercase;
  /* Full strength against the brush color — the bar is read mid-play, and the
     old wash cost more legibility than the recession was worth. */
  opacity: 1;
  /* Below the key caps' height, so raising the font size leaves the bar's own
     height alone. */
  line-height: 1.2;
  /* Labels that can change wording (phases, barren lines) stack their
     alternatives invisibly in the same grid cell, so the slot keeps the
     width of its widest wording and the bar stays put as the text changes
     (Textris renders them as .legend-ghost — see brushControls). */
  display: inline-grid;
  text-align: left;
}
.legend-label .legend-now,
.legend-label .legend-ghost {
  grid-area: 1 / 1;
}
.legend-label .legend-ghost {
  visibility: hidden;
}

/* A limited brush's remaining uses (BRUSH_USE_LIMITS) — a bare number, no
   wording, standing just off the bar's right edge. It wears the bar's own
   background color, which reads only out here against the page, so it looks
   like a piece set down beside the controls rather than more chrome. Textris
   builds it as an SVG whose viewBox is the digits' ink (see usesMark) and
   gives it its block proportions; with the height pinned below, the digits
   stretch to fill that block-shaped box in both axes exactly as a word fills
   its cell on the board.

   It stands to the content's height, not the bar's: inset by the same padding
   the bar keeps inside its own edge, so it lines up with the wording rather
   than with the bubble, and held off the edge by that same measure. */
.legend-uses {
  /* A stated height, never auto: an SVG is a replaced element, and an auto
     height on one of those comes from its own intrinsic sizing (an SVG with
     no width/height attributes behaves as 100% of its container). That left
     the box the full width of the bar, ignoring the aspect ratio, and
     carried it off the screen edge. */
  height: 100%;
  width: auto;
  display: block;
  overflow: visible;
  color: var(--brush-color, var(--accent-color));
}

/* What stands beside the bar — the uses number and the waiting bonuses — in
   one row off its right edge, at the content's height. */
.legend-aside {
  position: absolute;
  left: 100%;
  top: var(--legend-pad-y);
  height: calc(100% - var(--legend-pad-y) * 2);
  margin-left: var(--legend-pad-x);
  display: flex;
  align-items: stretch;
  gap: var(--legend-pad-x);
}

/* The brush bonuses waiting to be taken (Textris bonusQueue), standing off
   the bar's right edge past the uses number: one chip per bonus in its
   brush's color with its tag in Goma, the bonus-block look. 'r' takes the
   first, and its chip blooms into the brush (Textris.takeBonus). */
.legend-bonuses {
  display: flex;
  align-items: center;
  gap: 6px;
  /* The r cap leads the row and reads in the bar's color, out here against
     the page, as the uses number does. */
  color: var(--brush-color, var(--accent-color));
}
.legend-bonuses .legend-key:hover {
  color: var(--control-bg, #ffffff);
}
.legend-bonus-arriving { visibility: hidden; }
.legend-bonus {
  display: inline-flex;
  align-self: stretch;
  align-items: center;
  justify-content: center;
  padding: 0 10px;
  border-radius: 5px;
  color: var(--control-bg, #ffffff);
  font-family: 'Goma', sans-serif;
  font-size: 16px;
  line-height: 1;
  text-transform: uppercase;
}

/* A bonus's arrival card (Textris.showBonusArrival): a column the game
   sizes and places in the band under the grid. Top to bottom: the heading
   plate in the brush's secondary color with white wording; an open slot the
   cap sprite stands in (the game sets its height from the cell's
   proportions); and a white body — the bonus's blurb, then the key line —
   in the secondary color. Both brush colors in play: the secondary on the
   plates, the brush color on the cap between them. Taken down the moment
   the cap travels off to the legend. */
/* The card itself is a plate in the secondary color: the heading is its
   head, and the cap and the white body sit inset in it, the plate showing
   around them as a frame. */
#bonus-note {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1002;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0 8px 8px;
  border-radius: 12px;
  background: var(--bonus-secondary, var(--accent-color));
  pointer-events: none;
  color: var(--bonus-secondary, var(--accent-color));
  font-family: 'Plus Jakarta Sans', sans-serif;
}
#bonus-note .bonus-heading,
#bonus-note .bonus-body {
  box-sizing: border-box;
  border-radius: 8px;
}
#bonus-note .bonus-heading {
  padding: 9px 16px 4px;
  color: var(--control-bg, #ffffff);
  text-align: center;
  text-transform: uppercase;
  font-size: 17px;
  font-weight: 800;
  letter-spacing: 0.08em;
  white-space: nowrap;
}
#bonus-note .bonus-body {
  padding: 10px 16px 9px;
  background: var(--control-bg, #ffffff);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* The cap tile: the bonus-block look in DOM — the brush's color, the name
   in white stretched to fill the tile within the same insets a sprite cap
   gives its label (Block.apply). */
#bonus-note .bonus-tile {
  position: relative;
  border-radius: 5px;
  background: var(--bonus-color, var(--accent-color));
  color: var(--control-bg, #ffffff);
}
#bonus-note .bonus-tile-name {
  position: absolute;
  left: 6%;
  right: 6%;
  top: 16%;
  bottom: 16%;
  width: 88%;
  height: 68%;
  display: block;
  overflow: visible;
}

/* The description: the brush's own blurb, allowed its lines. */
#bonus-note .bonus-blurb {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.3;
  text-align: center;
}
#bonus-note .bonus-blurb:empty { display: none; }
/* The key line: a hairline above it in the brush's color, then the words
   around the r cap, small caps. */
#bonus-note .bonus-note-line {
  padding-top: 8px;
  border-top: 2px solid var(--bonus-color, var(--accent-color));
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
#bonus-note .legend-key {
  min-width: 18px;
  height: 18px;
  font-size: 11px;
}
#bonus-note .legend-key:hover {
  color: var(--control-bg, #ffffff);
}

/* Keys that would do nothing right now stay listed but fade (Textris
   .markDeadKeys) — dropping them would reflow the bar mid-aim, and a dim cap
   still teaches the key. A dead Space dims its whole item, label included
   (it may wear a barren wording like "No rhymes"). */
.legend-key.legend-dim { opacity: 0.35; }
.legend-item.legend-dim .legend-label { opacity: 0.45; }

/* Once the brush has acted, its Move On pill promotes (Textris marks the
   item .legend-acted): the ↵ cap fills in, and the pill announces itself with
   one swell on arrival and a beat every few seconds after. The emphasis
   persists — Enter stays the right next move. */
.legend-acted .legend-key {
  background: var(--brush-ink, #ffffff);
  color: var(--brush-color, var(--accent-color));
}
/* Nothing changes about the label on promotion — bolding would widen it and
   nudge the bar, and it already sits at full strength; the filled cap and the
   movement carry the emphasis. */

/* The arrival, played once. Textris adds this class only on the rewrite that
   promotes the pill, so the later rewrites that rebuild the bar don't replay
   it (see updateControlsLegend). */
.legend-arrive {
  animation: legend-acted-in 0.45s ease;
}
@keyframes legend-acted-in {
  0% { transform: scale(1); }
  40% { transform: scale(1.18); }
  100% { transform: scale(1); }
}

/* Then the reminder: every few seconds the ↵ cap knocks twice, so a player who
   has finished with a brush is told — by movement, in the corner of the eye —
   that Enter moves them on. Two beats rather than one so it reads as a prompt
   rather than a glitch, and the cycle is mostly rest so it never nags.

   Flat throughout: a plain scale, no glow, blur or gradient. It rides the key
   WRAPPER, not the cap, so the cap keeps its own hover and press transforms —
   those still answer the pointer while the beat is running. Textris owns both
   the period and the phase (see SHIMMER_PERIOD_MS); the phase is a negative
   delay that carries the cycle across the bar's rewrites. */
.legend-acted .legend-keys {
  animation: legend-shimmer var(--shimmer-period) ease-in-out infinite;
  animation-delay: var(--shimmer-phase);
}
@keyframes legend-shimmer {
  0%, 100% { transform: scale(1); }
  4%       { transform: scale(1.28); }
  9%       { transform: scale(1); }
  13%      { transform: scale(1.28); }
  18%      { transform: scale(1); }
}

/* Movement is the whole point of the beat, so when the player has asked for
   less of it, the pill keeps its color change and drops the animation. */
@media (prefers-reduced-motion: reduce) {
  .legend-arrive,
  .legend-acted .legend-keys {
    animation: none;
  }
}

/* The wild picker (Textris.openWildPicker): a DOM panel over the word tail's
   room — game sets its top/height to the screen rect above the grid (below
   it when flipped) and its colors from PALETTE.brush.wild. It sits at the
   canvas's own z level and is appended after it, so it draws over the sprites
   and the banner there while the grid below stays untouched. */
#wild-picker {
  position: fixed;
  left: 0;
  width: 100%;
  z-index: 1001; /* over the canvas (1000) — nothing drawn there shows through */
  box-sizing: border-box;
  /* Two rows: the tiles taking all there is, then one footer line holding
     the prompt and the blurb side by side — one line, not two, so the tiles
     keep the height. A grid (not flex) so the tiles' row is a definite
     height for the tile grid to divide up. */
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  justify-items: center;
  align-items: center;
  gap: 4px;
  padding: 4px 20px;
  background: var(--wild-color);
  color: var(--accent-color);
  font-family: 'Plus Jakarta Sans', sans-serif;
}
/* Flipped, the control legend shares the room at the screen's bottom — the
   game sets the exact padding-bottom from the legend's height. */

/* The footer: the prompt at the left, the blurb filling the rest, on the
   tiles' width. */
.wild-foot {
  width: min(100%, 1200px);
  display: flex;
  align-items: baseline;
  gap: 18px;
  min-height: 1.2em;
}
.wild-prompt {
  flex: none;
  font-weight: 800;
  font-size: clamp(12px, 1.8vh, 18px);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* The tiles take every pixel above the footer line: the grid stretches to
   fill the panel's top row, and the game sizes the names in
   px from the tiles' measured height (openWildPicker). No gaps: the tiles
   butt together into one block of color. */
.wild-tiles {
  width: min(100%, 1200px);
  align-self: stretch;
  min-height: 0;
  /* One strip per brush family (Textris.openWildPicker, WILD_GROUPS), equal
     in height; the gap between strips is what tells the families apart. */
  display: grid;
  grid-template-rows: repeat(var(--wild-rows, 4), 1fr);
  gap: 6px;
}
/* A family's strip: its tiles side by side, dividing the strip's width — a
   shorter family's tiles run wider. Each strip is its own rounded block. */
.wild-group {
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(var(--wild-group-cols, 4), 1fr);
  gap: 0;
  border-radius: 8px;
  overflow: hidden;
}

/* A tile is the bonus-block look: the brush's color, its name in Goma in the
   UI indigo (the panel's own text color), at the grid's font size. */
.wild-tile {
  min-width: 0;
  min-height: 0;
  border: 0;
  border-radius: 0;
  padding: 0;
  color: var(--accent-color);
  font-family: 'Goma', sans-serif;
  font-size: inherit;
  line-height: 1;
  text-transform: uppercase;
  cursor: pointer;
  /* The ring sits inside the tile, since there is no gap to draw it in. */
  outline: 4px solid transparent;
  outline-offset: -4px;
  transition: outline-color 0.15s ease;
}
/* The highlight: the selection ring in wild's secondary color, breathing like
   the board's own rings. */
.wild-tile.wild-selected {
  outline-color: var(--wild-secondary);
  position: relative;
  z-index: 1; /* over its neighbours while it swells */
  animation: wild-breathe 2.4s ease-in-out infinite;
}
@keyframes wild-breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.03); }
}

.wild-blurb {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  font-size: clamp(13px, 2vh, 22px);
  text-align: left;
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .wild-tile.wild-selected { animation: none; }
}

