/* SPDX-License-Identifier: AGPL-3.0-only */

/* MulGlyph: Noto Sans Cuneiform (SIL OFL 1.1) subset to the single MUL sign
   𒀯 U+1202F — almost no system ships a cuneiform font, so we carry our own */
@font-face {
  font-family: "MulGlyph";
  src: url("mul.woff2") format("woff2");
  unicode-range: U+1202F;
}

:root {
  --bg: #0b0e1a;
  --bg2: #121631;
  --tile-border: #3a3f5c;
  --tile-filled-border: #6b7194;
  --text: #e8eaf2;
  --dim: #9aa0b8;
  --correct: #4c9a4c;
  --present: #b8a038;
  --absent: #3a3f52;
  --key-bg: #454a63;
  --accent: #7ea6e0;
  /* fluid sizes: full-size on desktop, shrinking so the 8 tiles / 10 keys
     of the widest rows fit small phone screens (2rem = horizontal padding) */
  --tile-gap: 6px;
  --key-gap: 6px;
  --tile-size: min(3rem, calc((100vw - 2rem - 7 * var(--tile-gap)) / 8));
  --key-size: min(2.4rem, calc((100vw - 2rem - 9 * var(--key-gap)) / 10));
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* keep the hidden attribute working on elements we give a display value */
[hidden] { display: none !important; }

body {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: "Segoe UI", system-ui, sans-serif;
  color: var(--text);
  background: var(--bg);
}

/* subtle star field + page gradient (a fixed layer, because
   background-attachment: fixed on body is broken on iOS Safari) */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image:
    radial-gradient(1px 1px at 12% 22%, #fff8 50%, transparent 50%),
    radial-gradient(1px 1px at 78% 12%, #fff6 50%, transparent 50%),
    radial-gradient(1.5px 1.5px at 55% 35%, #fff9 50%, transparent 50%),
    radial-gradient(1px 1px at 32% 68%, #fff5 50%, transparent 50%),
    radial-gradient(1px 1px at 88% 55%, #fff7 50%, transparent 50%),
    radial-gradient(1.5px 1.5px at 8% 85%, #fff6 50%, transparent 50%),
    radial-gradient(1px 1px at 65% 82%, #fff8 50%, transparent 50%),
    radial-gradient(1px 1px at 42% 8%, #fff5 50%, transparent 50%),
    radial-gradient(1px 1px at 95% 90%, #fff6 50%, transparent 50%),
    radial-gradient(1px 1px at 25% 45%, #fff4 50%, transparent 50%),
    radial-gradient(ellipse at 20% 10%, var(--bg2) 0%, var(--bg) 60%);
}

header {
  text-align: center;
  padding: 1.2rem 1rem 0.6rem;
}

h1 {
  font-size: 2rem;
  letter-spacing: 0.35em;
  margin-right: -0.35em;
  color: var(--text);
  text-shadow: 0 0 18px #7ea6e055;
}

.mul-glyph {
  font-family: "MulGlyph", sans-serif;
  letter-spacing: normal;
  color: var(--present);
  cursor: help;
}
.about-name { color: var(--dim); font-style: italic; }
.about-name .mul-glyph { cursor: inherit; }

.tagline { color: var(--dim); font-size: 0.9rem; margin-top: 0.2rem; }
.puzzle-info { color: var(--dim); font-size: 0.8rem; margin-top: 0.3rem; }

main {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0.5rem 1rem;
  width: 100%;
  max-width: 560px;
}

.board-area {
  display: flex;
  gap: 1.2rem;
  align-items: flex-start;
  margin: 0.8rem 0;
}

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

/* post-game object viewer (Aladin Lite), sized from JS to match the board */
.object-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
}

#aladin-lite-div, #abc-aladin-div {
  border: 2px solid var(--tile-border);
  border-radius: 4px;
  overflow: hidden;
  background: #000;
}

/* image + survey picker sit side by side; align-items:stretch makes the picker
   column match the (JS-sized) image height, caption stays below */
.viewer-row {
  display: flex;
  gap: 0.4rem;
  align-items: stretch;
}

/* stacked square buttons: flex:1 splits the column height across the buttons,
   aspect-ratio:1 makes each square, so button side = imageHeight / N */
.survey-picker {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.survey-btn {
  flex: 1 1 0;
  aspect-ratio: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--text);
  background: transparent;
  border: 2px solid var(--tile-border);
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s, color 0.2s, opacity 0.2s;
}
.survey-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #000;
}
.survey-btn.disabled, .survey-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
@media (hover: hover) {
  .survey-btn:not(:disabled):not(.active):hover { border-color: var(--accent); }
}

.object-caption {
  color: var(--dim);
  font-size: 0.8rem;
  text-align: center;
}
.object-caption a { color: var(--accent); }

/* "guess" / "target" badge in front of the caption */
.object-role {
  text-transform: uppercase;
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  border: 1px solid var(--dim);
  border-radius: 3px;
  padding: 0 0.35em;
}
.object-role.target { color: var(--accent); border-color: var(--accent); }

/* the sky view's Constellations toggle (constellations.js), under the view:
   filled when on, as the survey buttons beside it */
.sky-toggle {
  min-height: 28px; /* above WCAG 2.5.8's 24 px target, also on phones */
  padding: 0.2rem 0.8rem;
  background: transparent;
  color: var(--dim);
  border: 2px solid var(--tile-border);
  border-radius: 4px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s, color 0.2s;
  -webkit-tap-highlight-color: transparent;
}
.sky-toggle[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #000; }
.sky-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (hover: hover) {
  .sky-toggle:not([aria-pressed="true"]):hover { border-color: var(--accent); color: var(--text); }
}
/* forced colours drop the fill: mark the pressed toggle in the system's
   highlight colours, as the Daily | Unlimited toggle */
@media (forced-colors: active) {
  .sky-toggle[aria-pressed="true"] { forced-color-adjust: none; background: Highlight; color: HighlightText; }
}

/* per-guess object-properties hints, rows aligned with the board rows */
.hint-panel {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.hint-row { display: flex; gap: 4px; }

.hint-cell {
  height: 3rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  border: 1px solid var(--tile-border);
  border-radius: 4px;
  background: transparent;
  transition: background 0.25s, border-color 0.25s;
}
.hint-cell.con  { width: 5.9rem; }
.hint-cell.type { width: 4.8rem; }
.hint-cell.mag  { width: 3.6rem; }
.hint-cell.dist { width: 4.2rem; }

/* full constellation names may need two lines ("Triangulum Australe") */
.hint-cell.con .hint-value {
  white-space: normal;
  font-size: 0.72rem;
  line-height: 1.1;
  text-align: center;
}

.hint-value a { color: inherit; text-decoration: underline dotted; }
.hint-value a:hover { text-decoration: underline; }

.hint-label {
  font-size: 0.55rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
}

.hint-value {
  font-size: 0.85rem;
  font-weight: 600;
  min-height: 1em;
  white-space: nowrap;
  max-width: calc(100% - 6px);
  overflow: hidden;
  text-overflow: ellipsis;
}

.hint-cell.match { background: var(--correct); border-color: var(--correct); }
.hint-cell.near  { background: var(--present); border-color: var(--present); }
.hint-cell.match .hint-label, .hint-cell.near .hint-label { color: var(--text); }

/* touch screens: no hover — a tapped key/row would stay highlighted */
@media (hover: hover) {
  .key:hover { filter: brightness(1.15); }
  .row.guessed:hover .tile { filter: brightness(1.2); }
}

/* ID's and ABC's message and keyboard as one dock (Backlog #25): on a screen
   too short for the board, message and keyboard (a laptop's browser at
   1366x657, a phone under 760 px tall), pinned to the bottom of the screen
   as OMNI's (.omni-dock), the board scrolling under it, so Enter stays in
   reach and a refusal in sight above the keys; game.js and abc.js keep the
   row being typed above it. Sticky only holds it once its own place would be
   below the screen; a finished puzzle (the keyboard hidden) leaves it in the
   page's flow. */
.dock {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}
@media (max-height: 760px) {
  .dock:not(:has(> .keyboard[hidden])) {
    position: sticky;
    bottom: 0;
    z-index: 2;
    /* the screen's width (the flip scene clips it): on a wide screen the
       board area, hints and sky view beside the board, is wider than main */
    width: 100vw;
    margin-bottom: 0.4rem;
    padding: 4px 0 calc(6px + env(safe-area-inset-bottom, 0px));
    background: var(--bg);
  }
  .dock:not(:has(> .keyboard[hidden])) .message { margin-bottom: 0.3rem; }
  .dock:not(:has(> .keyboard[hidden])) .keyboard { margin-bottom: 0; }
}
/* a phone held sideways: shorter keys leave room for the row being typed (as
   OMNI's) */
@media (max-height: 500px) {
  .dock .keyboard { gap: 5px; }
  .dock .key { height: 2.5rem; }
}

@media (max-width: 900px) {
  /* stack everything in main's column, keyboard right under the board so
     the game is playable without scrolling; hints and sky view below */
  .board-area { display: contents; }
  .board { order: 0; margin: 0.8rem 0 0; }
  .dock { order: 1; }
  .post-game { order: 2; }
  .hint-panel { order: 4; margin-bottom: 0.8rem; }
  .object-panel { order: 5; margin-bottom: 0.8rem; }
  .help { order: 6; }
}

.row { display: flex; gap: var(--tile-gap); touch-action: manipulation; }

.tile {
  width: var(--tile-size);
  height: var(--tile-size);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(var(--tile-size) / 2);
  font-weight: 700;
  border: 2px solid var(--tile-border);
  border-radius: 4px;
  background: transparent;
  transition: background 0.25s, border-color 0.25s, transform 0.1s;
}

.tile.filled { border-color: var(--tile-filled-border); animation: pop 0.1s; }

/* hard mode: known-green tiles prefilled in the row being typed */
.tile.locked { border-color: var(--correct); color: var(--correct); }

/* the row being typed: free tiles can be clicked to move the cursor, which
   marks where the next character goes (it overwrites a filled tile) */
.tile.editable { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.tile.cursor { border-color: var(--accent); box-shadow: inset 0 -3px 0 var(--accent); }

/* info line: rejected guesses this puzzle (✖ n) */
.rejected-count { color: var(--text); font-variant-numeric: tabular-nums; }

.tile.correct { background: var(--correct); border-color: var(--correct); }
.tile.present { background: var(--present); border-color: var(--present); }
.tile.absent  { background: var(--absent);  border-color: var(--absent); }

@keyframes pop { 50% { transform: scale(1.08); } }

/* submitted rows can be clicked to show that object in the sky view */
.row.guessed { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.row.viewing {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

.row.shake { animation: shake 0.4s; }
@keyframes shake {
  20% { transform: translateX(-5px); }
  40% { transform: translateX(5px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(3px); }
}

.message {
  min-height: 1.6rem;
  font-size: 1rem;
  color: var(--accent);
  text-align: center;
  margin-bottom: 0.4rem;
}
.message.reveal { font-size: 1.1rem; font-weight: 600; }
/* text only screen readers hear (OMNI's new-day notice in its message) */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* the reveal's quieter second line: the object's other established names */
.message .other-names {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--dim);
}

/* post-game block: emoji-grid share + countdown to the next daily puzzle */
.post-game {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.6rem;
}
.share-button {
  padding: 0.45rem 1.3rem;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #000;
  background: var(--correct);
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: filter 0.15s;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.share-button:hover { filter: brightness(1.1); }
.share-button:active { transform: scale(0.97); }
.countdown {
  color: var(--dim);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}

/* small inline spinner shown while a SIMBAD query is in flight */
.spinner {
  display: inline-block;
  width: 0.9em;
  height: 0.9em;
  border: 2px solid var(--tile-border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  vertical-align: -0.12em;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; }
}

.keyboard {
  display: flex;
  flex-direction: column;
  gap: 7px;
  align-items: center;
  margin-bottom: 1rem;
  user-select: none;
}

.krow { display: flex; gap: var(--key-gap); }

.key {
  min-width: var(--key-size);
  height: 3.2rem;
  padding: 0 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text);
  background: var(--key-bg);
  border: none;
  border-radius: 5px;
  cursor: pointer;
  transition: background 0.15s;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.key:active { transform: scale(0.95); }
.key.wide { min-width: 3.9rem; font-size: 0.85rem; }
.key.correct { background: var(--correct); }
.key.present { background: var(--present); }
.key.absent  { background: #23263a; color: var(--dim); }
/* every copy found ("Mark completed keys"): --correct's hue and saturation at
   the grey key's HSV value (#4c9a4c -> V 22.7%), so it reads green but spent */
body.done-keys .key.correct.done { background: #1d3a1d; color: var(--dim); }

.help {
  max-width: 30rem;
  color: var(--dim);
  font-size: 0.88rem;
  line-height: 1.5;
  margin-bottom: 1rem;
}
.help summary { cursor: pointer; color: var(--text); margin-bottom: 0.4rem; }
.help p { margin: 0.4rem 0; }
.help code { background: #1c2038; padding: 0 0.3em; border-radius: 3px; }

.mini {
  display: inline-block;
  padding: 0 0.4em;
  border-radius: 3px;
  color: var(--text);
  font-weight: 600;
}
.mini.correct { background: var(--correct); }
.mini.present { background: var(--present); }
.mini.absent  { background: var(--absent); }

/* start screen (start.js): title, Play, What's new — the game is hidden meanwhile */
.start-screen {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: 30rem;
  padding: 3.5rem 1rem 1rem;
  text-align: center;
}
.start-screen .tagline { margin-top: 0.4rem; }
.start-puzzle { color: var(--dim); font-size: 0.85rem; margin-top: 1.6rem; }
.play-button {
  margin-top: 0.7rem;
  padding: 0.7rem 3.2rem;
  font-size: 1.2rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: #000;
  background: var(--correct);
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: filter 0.15s;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.play-button:hover { filter: brightness(1.1); }
.play-button:active { transform: scale(0.97); }
.play-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.whats-new {
  margin-top: 2rem;
  width: 100%;
  text-align: left;
  color: var(--dim);
  font-size: 0.88rem;
  line-height: 1.45;
  background: #121631cc;
  border: 1px solid var(--tile-border);
  border-radius: 8px;
  padding: 0.6rem 0.9rem;
}
.whats-new summary { cursor: pointer; color: var(--text); font-weight: 600; }
.whats-new-date { color: var(--dim); font-weight: 400; font-size: 0.8rem; margin-left: 0.3rem; }
.whats-new ul { margin: 0.5rem 0 0.1rem 1.1rem; }
.whats-new li { margin: 0.25rem 0; }
/* On the horizon and Earlier updates sit close under What's new; an entry is
   a title line (Earlier: its own fold) over its items */
.whats-new.news-more { margin-top: 0.8rem; }
.news-title { color: var(--text); margin-top: 0.45rem; }
.news-entry { margin-top: 0.55rem; }
.news-entry > .news-title { margin-top: 0; }
.whats-new .news-entry > summary { font-weight: 400; }
.whats-new .news-entry ul { margin-top: 0.3rem; }
@media (max-width: 430px) {
  .start-screen { padding-top: 2rem; }
  .start-puzzle { margin-top: 1.1rem; }
  .whats-new { margin-top: 1.4rem; font-size: 0.84rem; }
}

footer {
  padding: 0.8rem;
  color: var(--dim);
  font-size: 0.75rem;
  text-align: center;
}
footer a { color: var(--accent); }

/* settings */
.settings-button {
  position: absolute;
  top: 0.9rem;
  right: 1rem;
  background: none;
  border: none;
  color: var(--dim);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}
.settings-button:hover { color: var(--text); }

/* The way back to the hub, top left on every screen: the hub's star and its
   name, small and dim. Copied from the hub's web/shared.css (.hub-mark; the
   family's one source for it, user 08-10-2026): Muldle loads nothing from
   another repo. The star is a mask, so it takes the text's colour. One mark
   for the page, mirroring the settings gear; below 640px the title row
   reaches it, so phones show the star alone (the name stays for screen
   readers) and nothing moves. */
.hub-mark {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--dim);
  font-size: 0.85rem;
  text-decoration: none;
}
.hub-mark::before {
  content: "";
  flex: none;
  width: 1em;
  height: 1em;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath d='M32 8l6 15.5 16.5 1.2-12.7 10.6 4 16.2L32 42.6l-13.8 8.9 4-16.2L9.5 24.7l16.5-1.2z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath d='M32 8l6 15.5 16.5 1.2-12.7 10.6 4 16.2L32 42.6l-13.8 8.9 4-16.2L9.5 24.7l16.5-1.2z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.hub-mark:hover, .hub-mark:focus-visible { color: var(--text); }
/* Forced colours (Windows High Contrast) would paint the star's background
   in Canvas, so it would vanish, and with it a phone's star-only link: keep
   it in the system's link colour. */
@media (forced-colors: active) {
  .hub-mark::before { forced-color-adjust: none; background: LinkText; }
}
body > .hub-mark {
  position: absolute;
  top: 1.05rem;
  left: 1rem;
  z-index: 2;
}
@media (max-width: 640px) {
  body > .hub-mark { top: 0.75rem; left: 0.5rem; padding: 0.35rem; font-size: 1.05rem; }
  .hub-mark-name {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
}
/* the tiniest phones: the title row starts near the edge, so the star sits higher */
@media (max-width: 340px) {
  body > .hub-mark { top: 0.3rem; }
}
/* the other daily sky puzzles, under a finished puzzle */
.more-games { color: var(--dim); font-size: 0.8rem; text-align: center; }
.more-games a { color: var(--accent); }

.settings-dialog {
  margin: auto; /* recenter: the global margin reset removes the UA default */
  background: var(--bg2);
  color: var(--text);
  border: 2px solid var(--tile-border);
  border-radius: 8px;
  padding: 1.2rem 1.4rem;
  width: min(21rem, 90vw);
}
.settings-dialog::backdrop { background: #000b; }
.settings-dialog h2 {
  font-size: 1.05rem;
  letter-spacing: 0.15em;
  text-align: center;
  margin-bottom: 0.9rem;
}

.settings-toggle {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin: 0.45rem 0 0.7rem;
  padding: 0.55rem 0.8rem;
  background: var(--key-bg);
  border-radius: 5px;
  cursor: pointer;
  font-size: 0.95rem;
  font-weight: 600;
}
.settings-toggle input { margin-top: 0.25rem; accent-color: var(--correct); }
.settings-toggle-text span {
  display: block;
  margin-top: 2px;
  color: var(--dim);
  font-size: 0.78rem;
  font-weight: 400;
}

.settings-action {
  display: block;
  width: 100%;
  margin: 0.45rem 0;
  padding: 0.55rem 0.8rem;
  background: var(--key-bg);
  color: var(--text);
  border: none;
  border-radius: 5px;
  cursor: pointer;
  text-align: left;
  font-size: 0.95rem;
  font-weight: 600;
}
.settings-action span {
  display: block;
  margin-top: 2px;
  color: var(--dim);
  font-size: 0.78rem;
  font-weight: 400;
}
.settings-action:hover { filter: brightness(1.15); }

.settings-close {
  position: absolute;
  top: 0.4rem;
  right: 0.6rem;
  background: none;
  border: none;
  color: var(--dim);
  font-size: 1.3rem;
  cursor: pointer;
}
.settings-close:hover { color: var(--text); }

/* ---- stats & history (shared by the settings dialog + the inline post-game
   view rendered below a finished board) ---- */
.stats-dialog { width: min(24rem, 94vw); }
/* inline copy: same content, no clear control; sized to the board area */
.post-game-stats {
  width: min(24rem, 94vw);
  text-align: left;
}
.post-game-stats:empty { display: none; }
#stats-content h2,
.post-game-stats h2 {
  font-size: 1.05rem;
  letter-spacing: 0.15em;
  text-align: center;
  margin-bottom: 0.9rem;
}
#stats-content h3,
.post-game-stats h3 {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--dim);
  margin: 1rem 0 0.5rem;
}

.stats-tiles { display: flex; gap: 0.4rem; }
.stat-tile {
  flex: 1;
  background: var(--key-bg);
  border-radius: 5px;
  padding: 0.5rem 0.2rem;
  text-align: center;
}
.stat-val { font-size: 1.5rem; font-weight: 700; line-height: 1.1; }
.stat-label {
  margin-top: 2px;
  font-size: 0.66rem;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.stats-dist { display: flex; flex-direction: column; gap: 4px; }
.dist-row { display: flex; align-items: center; gap: 0.5rem; }
.dist-num { width: 0.8rem; text-align: right; color: var(--dim); font-size: 0.85rem; }
.dist-bar {
  min-width: 1.4rem;
  padding: 1px 6px;
  background: var(--absent);
  border-radius: 3px;
  color: var(--text);
  font-size: 0.8rem;
  font-weight: 700;
  text-align: right;
  box-sizing: border-box;
}
.dist-bar.current { background: var(--correct); }

.history-list {
  display: flex;
  flex-direction: column;
  gap: 3px;
  max-height: 12rem;
  overflow-y: auto;
}
.history-row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  width: 100%;
  padding: 0.4rem 0.6rem;
  background: var(--key-bg);
  border: none;
  border-radius: 5px;
  color: var(--text);
  font-size: 0.9rem;
  text-align: left;
  cursor: pointer;
}
.history-row:hover { filter: brightness(1.15); }
.history-swatch {
  width: 0.8rem;
  height: 0.8rem;
  border-radius: 2px;
  flex-shrink: 0;
}
.history-swatch.solved { background: var(--correct); }
.history-swatch.lost { background: var(--absent); }
.history-label { flex: 1; font-weight: 600; }
.history-outcome { color: var(--dim); font-variant-numeric: tabular-nums; }
.history-tag {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--dim);
  border: 1px solid var(--tile-border);
  border-radius: 3px;
  padding: 0 4px;
}

.stats-empty { color: var(--dim); font-size: 0.85rem; }
.stats-note {
  margin-top: 1rem;
  color: var(--dim);
  font-size: 0.72rem;
  text-align: center;
}
.stats-clear {
  display: block;
  width: 100%;
  margin-top: 0.5rem;
  padding: 0.45rem;
  background: none;
  border: 1px solid var(--tile-border);
  border-radius: 5px;
  color: var(--dim);
  font-size: 0.82rem;
  cursor: pointer;
}
.stats-clear:hover { color: var(--text); border-color: var(--tile-filled-border); }
.stats-clear.armed { color: var(--present); border-color: var(--present); font-weight: 600; }

@media (max-width: 430px) {
  /* tighter gaps + padding buy the fluid tiles/keys some extra width */
  :root { --tile-gap: 4px; --key-gap: 4px; }
  main { padding: 0.5rem; }
  .board { gap: 4px; }
  .key { padding: 0 0.2rem; }
  .hint-cell { height: 2.6rem; }
  .hint-value { font-size: 0.78rem; }
  .title-row { gap: 0.5rem; }
  .mode-seg { padding: 0.15rem 0.55rem; }
  /* shave the header so the navigator row still clears the fold on ~667px-tall
     phones (iPhone SE): smaller title, tighter padding + margins, compact nav */
  header { padding: 0.5rem 1rem 0.3rem; }
  h1 { font-size: 1.55rem; letter-spacing: 0.2em; margin-right: -0.2em; }
  .tagline { margin-top: 0.12rem; font-size: 0.82rem; }
  .puzzle-nav { margin-top: 0.22rem; }
  .nav-btn { padding: 0.08rem 0.6rem; font-size: 0.75rem; }
  .puzzle-info { margin-top: 0.16rem; }
}

/* very small / old phones (e.g. 320x568): shave header + key height so the
   board and full keyboard still fit above the fold */
@media (max-width: 340px) {
  header { padding-top: 0.8rem; }
  h1 { font-size: 1.5rem; letter-spacing: 0.2em; margin-right: -0.2em; }
  .key { height: 2.9rem; }
  .keyboard { gap: 5px; }
  /* keep title + toggle on one line and drop the tagline so the board and
     full keyboard still clear the fold once the toggle is in the header */
  .title-row { gap: 0.45rem; flex-wrap: nowrap; }
  .mode-seg { padding: 0.12rem 0.45rem; font-size: 0.68rem; letter-spacing: 0.08em; }
  .tagline { display: none; }
}

/* ============ ID <-> ABC mode flip ============ */

/* The whole game is a card flipping around a central vertical axis: face-id on
   the front, face-abc on the back. Both faces stay in the DOM, stacked on top
   of each other.

   We do NOT rely on backface-visibility to hide the rotated-away face:
   Firefox/LibreWolf don't cull the backface here (it showed through mirrored
   beneath the active face — the reported bug), and even a non-grid 3D context
   doesn't restore culling in that engine. Instead the away face is hidden
   outright (.face-back), and during an animated flip that show/hide is deferred
   to the halfway point (.flip-anim, edge-on) so the outgoing face rotates fully
   away before the incoming one appears — a clean flip in every browser.
   backface-visibility:hidden is kept as a harmless extra where it does work. */
#flip-scene {
  width: 100%;
  display: flex;
  justify-content: center;
  perspective: 2200px;
  perspective-origin: 50% 40%;
  /* a leg about the horizontal axis (ABC <-> OMNI) swings the card's near
     edge wider than the screen: a phone's browser would keep the page that
     wide after the flip (release 2's review, R2-10). clip, unlike hidden,
     makes no scroll container, so OMNI's pinned dock still sticks. */
  overflow-x: clip;
}
#flipper {
  position: relative;        /* the preserve-3d element stays a plain block */
  width: 100%;
  transform-style: preserve-3d;
  /* one leg of the flip (flip.js sets --flip-ms: two legs, ID-OMNI, run at
     double speed); the pose is always rotateY rotateX, so a leg turns one */
  transition: transform var(--flip-ms, 0.85s) cubic-bezier(0.45, 0.05, 0.2, 1);
  transform: rotateY(0deg) rotateX(0deg);
}
/* ID -> ABC: the left edge swings towards the viewer (positive rotateY);
   ABC -> ID replays the same transition in reverse */
#flipper.flipped { transform: rotateY(180deg) rotateX(0deg); }
/* ABC -> OMNI: a turn about the horizontal axis (OMNI -> ABC replays it) */
#flipper.flipped.flipped-x { transform: rotateY(180deg) rotateX(180deg); }
#flipper.no-anim { transition: none; }

.face {
  position: absolute;        /* stack both faces at the same spot */
  top: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  min-width: 0;
  backface-visibility: hidden;
  transition: visibility 0s linear 0s;
}
/* the active face flows normally so #flipper (and the footer below it) is as
   tall as whichever face is showing; the other face overlays it absolutely */
.face.face-active { position: relative; }
/* the face currently rotated away is hidden so it can't ghost through */
.face.face-back { visibility: hidden; }
/* during an animated flip, defer each face's show/hide to the flip's midpoint
   (0.85s / 2), i.e. when the card is edge-on and both faces are ~invisible */
#flipper.flip-anim .face { transition-delay: calc(var(--flip-ms, 850ms) / 2); }
#face-abc { transform: rotateY(180deg); }
/* upright when the card shows rotateY(180) rotateX(180): its inverse */
#face-omni { transform: rotateX(-180deg) rotateY(-180deg); }
/* A sky view (Aladin) is a WebGL <canvas>. In Firefox a canvas inside a
   preserve-3d, rotated subtree is composited on its own layer and ignores the
   away face's visibility:hidden (and 3D transform), so its reticle/crosshair
   keeps painting, mirrored to the left. Group-composite the away panel to
   transparent so the escaped canvas is culled too. Only at rest (:not(.flip-anim));
   mid-flip the visibility timing above handles it and flattening would break the
   spin. Panel stays live, so flipping back restores the view + crosshair. */
#flipper:not(.flip-anim) .face.face-back .object-panel { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  #flipper { transition: none; }
}

/* title + mode toggle side by side */
.title-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  flex-wrap: wrap;
}

.mode-toggle {
  display: inline-flex;
  border: 1px solid var(--tile-filled-border);
  border-radius: 999px;
  overflow: hidden;
}
.mode-seg {
  min-height: 24px; /* WCAG 2.5.8's target size, also on phones */
  min-width: 24px;
  background: transparent;
  color: var(--dim);
  border: none;
  padding: 0.2rem 0.75rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
  -webkit-tap-highlight-color: transparent;
}
.mode-seg + .mode-seg { border-left: 1px solid var(--tile-filled-border); }
.mode-seg[aria-pressed="true"] { background: var(--accent); color: var(--bg); }
@media (hover: hover) {
  .mode-seg:not([aria-pressed="true"]):hover { color: var(--text); }
}

/* puzzle navigator: a joined group « · Puzzle #N · », clamped to <= today
   (next disabled on the newest). No fill — the site background shows through,
   with just the frame + divider lines. Hidden by .hidden in random mode. */
.puzzle-nav {
  display: inline-flex;
  margin-top: 0.4rem;
  border: 1px solid var(--tile-filled-border);
  border-radius: 0.4rem;
  overflow: hidden;
}
.puzzle-nav.hidden { display: none; }
.nav-btn {
  background: transparent;   /* site background, no button colour */
  color: var(--dim);
  border: none;
  padding: 0.2rem 0.85rem;
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1.4;
  cursor: pointer;
  transition: color 0.2s;
  -webkit-tap-highlight-color: transparent;
}
.nav-btn + .nav-btn { border-left: 1px solid var(--tile-filled-border); } /* divider line */
.nav-step { font-size: 0.95rem; }
.nav-num { letter-spacing: 0.04em; color: var(--text); } /* the number reads as active, never greyed */
.nav-btn:disabled { color: var(--tile-border); cursor: default; }
/* the group's overflow: hidden clips an outline drawn outside a button: draw
   it inside (Retractle's B3, 09-10-2026), in the toggles' groups too, where
   the pressed segment's accent fill takes a ring in the background colour
   (release 2.1's review, B2) */
.nav-btn:focus-visible, .play-seg:focus-visible, .mode-seg:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.play-seg[aria-pressed="true"]:focus-visible, .mode-seg[aria-pressed="true"]:focus-visible { outline-color: var(--bg); }
@media (hover: hover) {
  .nav-btn:not(:disabled):hover { color: var(--text); } /* brighten only, no fill */
}

/* The navigator's row: « Puzzle #N » and, once today's daily is finished, the
   Daily | Unlimited toggle beside it; in Unlimited the navigator gives way to
   the session's stopwatch. Each part carries its own top margin, so a row
   with nothing shown takes no room. */
.nav-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  column-gap: 0.5rem;
}
.play-toggle {
  display: inline-flex;
  margin-top: 0.4rem;
  border: 1px solid var(--tile-filled-border);
  border-radius: 0.4rem;
  overflow: hidden;
}
.play-seg {
  min-height: 24px; /* WCAG 2.5.8's target size, also on phones */
  background: transparent;
  color: var(--dim);
  border: none;
  padding: 0.2rem 0.7rem;
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
  -webkit-tap-highlight-color: transparent;
}
.play-seg + .play-seg { border-left: 1px solid var(--tile-filled-border); }
.play-seg[aria-pressed="true"] { background: var(--accent); color: var(--bg); }
/* Forced colours (Windows High Contrast) drop the fill that marks the
   pressed side of a toggle: mark it in the system's highlight colours */
@media (forced-colors: active) {
  .play-seg[aria-pressed="true"], .mode-seg[aria-pressed="true"] {
    forced-color-adjust: none;
    background: Highlight;
    color: HighlightText;
  }
  .play-seg[aria-pressed="true"]:focus-visible, .mode-seg[aria-pressed="true"]:focus-visible { outline-color: HighlightText; }
}
@media (hover: hover) {
  .play-seg:not([aria-pressed="true"]):hover { color: var(--text); }
}
.unlimited-clock {
  margin-top: 0.4rem;
  color: var(--text);
  font-size: 0.8rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.unlimited-clock.waiting { color: var(--dim); }
@media (max-width: 430px) {
  .play-toggle, .unlimited-clock { margin-top: 0.22rem; }
  .play-seg { padding: 0.08rem 0.5rem; font-size: 0.75rem; }
}
/* the tiniest phones: the navigator and the toggle still share one row, so
   an unsolved older puzzle's keyboard stays above the fold */
@media (max-width: 340px) {
  .nav-row { column-gap: 0.35rem; }
  .nav-btn { padding-left: 0.4rem; padding-right: 0.4rem; }
  .play-seg { padding-left: 0.35rem; padding-right: 0.35rem; font-size: 0.7rem; letter-spacing: 0.02em; }
}

/* In Unlimited the message area always holds two lines, so the reveal of the
   puzzle just played shows in full (the longest, at 320 px, takes two) and
   the keyboard below never moves while the next puzzle is typed */
.message.in-unlimited {
  min-height: 2.5em;
  font-size: 0.9rem;
  line-height: 1.25;
}
/* A solve in Unlimited shows (user, 08-10-2026; unlimited.js, celebrate):
   the reveal line glows gold for a moment and the clock's "n solved" pops.
   The reveal itself stays until the next message. Reduced motion: the text
   only. */
.message.celebrate { animation: solve-glow 1.6s ease-out; }
.unlimited-clock.celebrate { display: inline-block; animation: solve-pop 0.7s ease-out; }
@keyframes solve-glow {
  0% { color: var(--present); text-shadow: 0 0 0 transparent; transform: scale(1); }
  20% { color: var(--present); text-shadow: 0 0 14px var(--present); transform: scale(1.06); }
  60% { color: var(--present); text-shadow: 0 0 6px #b8a03866; transform: scale(1); }
  100% { text-shadow: 0 0 0 transparent; transform: scale(1); }
}
@keyframes solve-pop {
  0% { transform: scale(1); }
  35% { transform: scale(1.3); color: var(--present); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .message.celebrate, .unlimited-clock.celebrate { animation: none; }
}

/* Unlimited's totals in Stats & history: a session's length fits its tile */
.unlimited-tiles .stat-val { font-size: 1.2rem; line-height: 1.375; }
/* five tiles since the average per solve (user, 09-10-2026): below 380 px
   they don't fit one row (the labels set each tile's least width), so three
   then two */
@media (max-width: 380px) {
  .unlimited-tiles { flex-wrap: wrap; }
  .unlimited-tiles .stat-tile { flex: 1 1 calc((100% - 0.8rem) / 3); }
}

/* Entering or leaving Unlimited (unlimited.js, starTransition): the two
   views' snapshots swap inside an eight-pointed star that grows from the
   screen's centre (or shrinks back to it); no fade. The star's thick border
   is the image pair's background, showing in the gap between the inside's
   edge and the outside's. */
html[data-star]::view-transition-old(root),
html[data-star]::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
html[data-star]::view-transition-image-pair(root) { background: var(--present); }

/* ============ ABC board (the whole name on one line, word-spaced) ============ */

/* ABC needs more width than ID's 8-tile board (names run to ~25 tiles), so its
   face gets a wider main; the flip axis stays screen-centred either way */
#face-abc main { max-width: min(64rem, 100%); }

.abc-board {
  /* size tiles so the entire name fits ONE line within the available width
     (capped at 3rem, floored at 1.5rem — longer than that and the board just
     scrolls). cols = all slots, fixed = punctuation slots (drawn 0.4 tile
     wide), wgaps = between-word gaps (an extra 0.5 tile each), all set from
     JS. width = (cols - 0.6*fixed + 0.5*wgaps)*tile + (cols-1)*tile-gap */
  --abc-tile-size: clamp(1.5rem,
    calc((min(100vw - 1.5rem, 60rem) - (var(--abc-cols) - 1) * var(--tile-gap))
      / (var(--abc-cols) - 0.6 * var(--abc-fixed, 0) + 0.5 * var(--abc-wgaps))),
    3rem);
  /* rows centre by their auto margins, which drop to 0 when a long name is
     wider than the board: it then overflows to the right only, where the
     scroll reaches (centred flex overflow would put its first tiles left
     of the scroll origin, out of reach) */
  align-items: flex-start;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
}
.abc-row {
  display: flex;
  flex-wrap: nowrap;               /* the name stays on one line */
  width: max-content;
  margin-inline: auto;
  justify-content: center;
  /* between-word gap: half a tile more than between letters, so a word break
     reads as a space and never as a missing letter */
  gap: calc(var(--tile-gap) + var(--abc-tile-size) * 0.5);
  touch-action: manipulation;
}
.abc-word { display: flex; gap: var(--tile-gap); }

.abc-board .tile {
  width: var(--abc-tile-size);
  height: var(--abc-tile-size);
  font-size: calc(var(--abc-tile-size) * 0.5);
}
/* spaces are gaps; apostrophes / hyphens are shown but not typed or scored.
   Drawn narrow and pulled in to their letters, like punctuation in print
   (MIRACH'S, BOW-TIE), so they never look like a gap between words */
.abc-board .tile.fixed {
  width: calc(var(--abc-tile-size) * 0.4);
  margin: 0 calc(var(--tile-gap) * -0.5);
  border-color: transparent;
  background: transparent;
  color: var(--text);
  font-size: calc(var(--abc-tile-size) * 0.65);
  font-weight: 700;
}

/* a guessed row that matches a real named object is clickable (sky view) */
.abc-row.guessed { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.abc-row.viewing {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}
@media (hover: hover) {
  .abc-row.guessed:hover .tile:not(.fixed) { filter: brightness(1.2); }
}

/* ABC's bottom key row is Enter + 7 letters + Back (9 keys), and so is ID
   mode's when the letters split over two rows (v2: Enter + 6 letters + Back),
   so their wide keys must scale with the fluid key size rather than ID mode's
   fixed 3.9rem (which would overflow narrow phones) */
#abc-keyboard .key.wide,
#keyboard.split .key.wide {
  min-width: calc(var(--key-size) * 1.5);
  padding: 0 0.15rem;
  font-size: 0.7rem;
}

/* ============ OMNI (omni.js): any identifier or name ============ */

/* every width stacks like a phone: the board takes the width, the hints and
   the sky view go below the keyboard */
#face-omni main { max-width: min(64rem, 100%); }
#face-omni .board-area { display: contents; }
#face-omni .omni-closed { order: -1; }
#face-omni .board { order: 0; margin: 0.6rem 0 0; }
#face-omni .omni-dock { order: 3; }
#face-omni .hint-panel { order: 4; margin: 0.4rem 0 0.8rem; }
#face-omni .object-panel { order: 5; margin-bottom: 0.8rem; }
#face-omni .help { order: 6; }
#face-omni.omni-closed-state .board,
#face-omni.omni-closed-state .keyboard,
#face-omni.omni-closed-state .hint-panel,
#face-omni.omni-closed-state .object-panel { display: none; }
/* the dock stays, its keys hidden: its message is the card's notice (a new
   day: reload; the session gone to another tab; a puzzle's last reveal),
   below the card in the page's flow, not pinned (the re-check's R1) */
#face-omni.omni-closed-state .omni-dock { position: static; padding: 0; background: none; }

/* while OMNI is closed (no daily solved today): a card in the board's place */
.omni-closed {
  max-width: 28rem;
  margin: 1.5rem 0 1rem;
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--tile-border);
  border-radius: 8px;
  background: #121631cc;
  color: var(--dim);
  font-size: 0.9rem;
  line-height: 1.45;
  text-align: center;
}
.omni-closed-lead { color: var(--text); font-weight: 600; margin-bottom: 0.4rem; }

/* The board: 28 tiles a row (the longest entry) in one line where they fit
   at ~26 px or more, else two lines of 14; Mini's 6 tiles at the usual size.
   Each guess row stays the full width whatever the answer, so the board
   gives nothing away; blanks are typed and scored. */
.omni-board {
  --omni-gap: 3px;
  --omni-cols: var(--omni-len, 28);
  --omni-tile: min(2.1rem, calc((min(100vw, 64rem) - 2rem - (var(--omni-cols) - 1) * var(--omni-gap)) / var(--omni-cols)));
  gap: 6px;
  align-items: center;
}
@media (max-width: 840px) {
  .omni-board { --omni-cols: var(--omni-half, 14); --omni-gap: 2px; }
}
.omni-board.mini { --omni-cols: 6; --omni-gap: var(--tile-gap); --omni-tile: var(--tile-size); }
.omni-row {
  display: grid;
  grid-template-columns: repeat(var(--omni-cols), var(--omni-tile));
  gap: 2px var(--omni-gap);
  touch-action: manipulation;
}
.omni-board .tile {
  position: relative;
  width: var(--omni-tile);
  height: var(--omni-tile);
  font-size: calc(var(--omni-tile) * 0.55);
}
.omni-board:not(.mini) .tile { border-width: 1.5px; border-radius: 3px; }
/* a typed blank tile says so */
.omni-board .tile.typed-blank::after { content: "␣"; color: var(--dim); font-size: 0.75em; font-weight: 400; }
/* Marks are never tiles (#17 Q4): the answer's show from the start between
   the tiles of the row being typed and those to come, a guessed row shows
   its own; drawn on the gap before the tile they stand before */
.omni-board .tile[data-mark]::before {
  content: attr(data-mark);
  position: absolute;
  z-index: 1;
  left: calc(var(--omni-gap) / -2 - 1.5px);
  top: -0.3em;
  transform: translateX(-50%);
  color: var(--present);
  font-size: calc(var(--omni-tile) * 1.05);
  font-weight: 900;
  line-height: 1;
  text-shadow: 0 0 2px var(--bg), 0 0 2px var(--bg);
  pointer-events: none;
}
/* forced colours would put a backplate behind the large mark that hides the
   borders of the tiles beside it (R2-15): the mark keeps its own colours */
@media (forced-colors: active) {
  .omni-board .tile[data-mark]::before { forced-color-adjust: none; color: CanvasText; text-shadow: none; }
}
.omni-board .tile[data-mark="-"]::before { top: 50%; transform: translate(-50%, -55%); font-size: calc(var(--omni-tile) * 0.8); }
.omni-board .tile[data-mark="."]::before { top: auto; bottom: -0.2em; }
.omni-row.guessed { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.omni-row.viewing { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
@media (hover: hover) {
  .omni-row.guessed:hover .tile { filter: brightness(1.2); }
}

/* the message and the keyboard (four rows: every letter, the digits, * and a
   blank key) as one dock; on a phone or tablet, and on any screen too short
   for the board, message and keyboard (a phone held sideways, a laptop's
   browser at 1366x657), pinned to the bottom of the screen, the board
   scrolling under it: so the reveal and refusals stay in sight above the
   keys (release 2's review, R2-9, R2-13). Sticky only holds it once its own
   place would be below the screen. */
.omni-dock {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}
@media (max-width: 840px), (max-height: 760px) {
  .omni-dock {
    position: sticky;
    bottom: 0;
    z-index: 2;
    margin-bottom: 0.4rem;
    padding: 4px 0 calc(6px + env(safe-area-inset-bottom, 0px));
    background: var(--bg);
  }
  .omni-dock .message { margin-bottom: 0.3rem; }
  .omni-dock .keyboard { margin-bottom: 0; }
}
#omni-keyboard .key.wide {
  min-width: calc(var(--key-size) * 1.25);
  padding: 0 0.1rem;
  font-size: 0.68rem;
}
/* a phone held sideways: shorter keys leave room for the row being typed */
@media (max-height: 500px) {
  #omni-keyboard { gap: 5px; }
  #omni-keyboard .key { height: 2.5rem; }
}

/* Three modes in the title row on a phone, the 𒀯 kept at every width (user,
   08-10-2026): smaller segments, and on the tiniest phones a slightly smaller,
   tighter title, so the row stays on one line, clear of the hub mark (its
   star, and from 641 px its name) and the gear at every width (release 2's
   review, R2-11, R2-12: a sweep in test_mobile) */
@media (max-width: 700px) {
  .mode-seg { padding: 0.15rem 0.38rem; font-size: 0.68rem; letter-spacing: 0.06em; }
}
@media (max-width: 450px) {
  h1 { font-size: 1.55rem; letter-spacing: 0.2em; margin-right: -0.2em; }
}
@media (max-width: 370px) {
  h1 { font-size: 1.4rem; letter-spacing: 0.1em; margin-right: -0.1em; }
  .title-row { gap: 0.35rem; }
  .mode-seg { padding: 0.12rem 0.3rem; font-size: 0.62rem; letter-spacing: 0.03em; }
}
/* narrower than WCAG's 320 px reflow width (a phone at a large display
   zoom): the row needs 244 px at 370 px's sizes, more than the 228 px left
   between the hub star and the gear at 300 px */
@media (max-width: 319px) {
  h1 { font-size: 1.25rem; letter-spacing: 0.06em; margin-right: -0.06em; }
  .title-row { gap: 0.25rem; }
  .mode-seg { padding: 0.12rem 0.22rem; }
}
