/* ============================================================
   Sudoku · Noir — Styles
   ============================================================ */
:root {
  /* Farb-Tokens kommen jetzt aus css/tokens.css (Alias-Schicht: --bg, --accent,
     --text, --panel, --accent2, --danger …). Hier bleiben nur Nicht-Farb-Tokens
     bzw. solche ohne Theme-Abhängigkeit. */
  --radius: 18px;
  --shadow: 0 20px 60px -15px rgba(0, 0, 0, 0.75);
  /* Einheitliche Bedien-Oberfläche (Tasten, Werkzeuge, HUD, Pause) */
  --ctrl: linear-gradient(180deg, rgba(86, 58, 50, 0.85), rgba(42, 27, 27, 0.88));
  --ctrl-hover: linear-gradient(180deg, rgba(120, 84, 66, 0.9), rgba(60, 38, 36, 0.9));
  --ctrl-brd: color-mix(in srgb, var(--accent) 26%, rgba(255, 240, 220, 0.08));
  --ctrl-sh: inset 0 1px 0 rgba(255, 240, 220, 0.08), 0 3px 12px -6px rgba(0, 0, 0, 0.65);
  --ctrl-sh-hover: inset 0 1px 0 rgba(255, 240, 220, 0.14), 0 10px 24px -10px color-mix(in srgb, var(--accent) 55%, transparent);
}

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}
/* BEFUND-2 3.1/4.3: ein globaler Reset statt einzeln nachgeholter Werte. Ohne
   ihn erben Formularelemente die Systemschrift (die Woche/Monat/Gesamt-Reiter
   rendern dann in Arial) und ungestylte Links das Browser-Blau — beides fällt
   aus der Wein/Kupfer/Gold-Palette. Klassenregeln sind spezifischer und gewinnen. */
button, input, select, textarea { font: inherit; }
/* Schrift-Ton, nicht Flächen-Ton: `--accent` ist im hellen Theme #a9762a und
   ergibt als Linkschrift auf Creme nur 3.03:1 (verlangt 4.5). Im Dunkeln sind
   beide Töne derselbe, die Umstellung ändert dort also nichts. Begründung des
   Tokens: css/themes.css. */
a { color: var(--color-primary-text); }
/* DAS QUERSCROLLEN WIRD GENAU EINMAL UNTERBUNDEN, und zwar hier.

   Bis zum 03.09.2026 stand `overflow-x: hidden` an BEIDEN Elementen, und
   das sah nach doppelter Sicherheit aus. Tatsaechlich gibt der Browser die
   Angabe am Wurzelelement an den Viewport weiter; die zweite am Body wird
   deshalb nicht mehr weitergereicht, sondern macht den Body zu einem
   eigenen Scroll-Container. Zusammen mit `height: 100%` war seine Box damit
   exakt so hoch wie der sichtbare Bereich im Moment des Ladens.

   In Chrome am Handy stand deshalb beim Oeffnen der Startseite unten ein
   dunkler Streifen zwischen Inhalt und Werkzeugleiste: Faehrt die Leiste
   beim Scrollen ein Stueck ein, waechst der sichtbare Bereich, die Box
   bleibt stehen -- und darunter liegt nur noch `html { background:
   #1c0c0f }` aus index.html, die Farbe gegen das weisse Aufblitzen.

   Der Body waechst jetzt mit seinem Inhalt (`min-height: 100%` steht
   unten). Damit deckt sein Verlauf die ganze Seite, und der Streifen kann
   nicht mehr entstehen. Am Aussehen aendert das nichts -- der Verlauf
   bleibt ueber die volle Inhaltshoehe gestreckt, waehrend ein Verlegen auf
   das Wurzelelement ihn auf Viewport-Hoehe gestaucht und die Startseite
   sichtbar aufgehellt haette (gemessen und verworfen).

   Geprueft in test/design-01-tokens-und-layout.test.mjs, Block 336. */
html {
  margin: 0;
  height: 100%;
  overflow-x: hidden;
}
body {
  margin: 0;
}
/* 5.3 A11y: einheitlicher, gut sichtbarer Tastatur-Fokus (nur bei Keyboard-Nav). */
:focus-visible {
  /* Nicht --color-primary: das ist der Flaechen-Akzent und haette im hellen
     Theme nur 2,79:1 — WCAG 1.4.11 verlangt 3:1 fuer Nicht-Text. */
  outline: 2px solid var(--color-primary-text);
  outline-offset: 2px;
}
/* 5.3 A11y: visuell versteckt, aber für Screenreader lesbar (Live-Region). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
body {
  font-family: var(--font-body);
  /* Deko-Ausnahme: atmosphärische Wein/Braun-Glows (bewusst Literale).
     Für Light-Mode in themes.css überschrieben (warme, helle Variante). */
  /* DIE GEOMETRIE HAENGT AN FESTEN LAENGEN, NICHT AN DER BOX -- und das ist
     keine Kosmetik, sondern der Kern dieser Aenderung.

     Die Glows standen bei `at 20% -10%` und `at 100% 10%`, waren also an die
     HOEHE ihrer Box gebunden. Solange der Body ein Scroll-Container in
     Bildschirmhoehe war, ergaben das 760px und die Glows lagen im Bild. Seit
     er mit dem Inhalt waechst, waeren es 8753px: `-10%` liegt dann 875px
     ueber der Kante, der 800px-Radius reicht nicht mehr herein, und die
     Startseite wurde sichtbar dunkler (gemessen und fotografiert am
     03.09.2026). Feste Pixel setzen die Glows dorthin, wo sie vorher lagen,
     und lassen sie dort -- unabhaengig davon, wie hoch die Box ist.

     WARUM NICHT `background-attachment: fixed`: Das war der erste Anlauf und
     sah in Chrome am Rechner richtig aus. iOS behandelt `fixed` aber seit
     jeher wie `scroll` -- in Safari, in Chrome fuers iPhone (das WebKit
     benutzt) und in unserer App-Huelle waere die Seite also genau dort
     dunkler geworden, wo der Fehler gemeldet wurde. Eine Behebung, die von
     einer Browser-Eigenheit abhaengt, ist keine.

     Der lineare Verlauf laeuft ueber eine Bildschirmhoehe und wird nicht
     wiederholt; darunter traegt `background-color` seinen Endton weiter.
     Ohne diese beiden Zeilen zoege er sich ueber die ganze Seitenhoehe und
     waere oben fast einfarbig. */
  background: radial-gradient(1200px 800px at 20% -76px, #4a1922 0%, transparent 55%),
    radial-gradient(1000px 700px at 100% 76px, #3a2416 0%, transparent 50%),
    linear-gradient(160deg, var(--bg), var(--bg2));
  background-repeat: no-repeat;
  background-size: auto, auto, 100% 100vh;
  background-color: var(--bg2);
  color: var(--text);
  /* Fuellt den Bildschirm auch bei kurzen Seiten -- ohne den Body auf
     Viewport-Hoehe festzunageln. Kein `overflow-x` hier, siehe oben. */
  min-height: 100%;
  position: relative;
}

/* Ambient orbs — Deko-Ausnahme: bewusst feste Atmosphären-Farben (blurred,
   opacity 0.5). Im Light-Mode über themes.css abgedämpft.

   z-index NEGATIV, und das ist keine Kosmetik: `position: fixed` macht die
   Ebene zu einem positionierten Kasten, der bei z-index 0 NACH dem
   gewöhnlichen Inhaltsfluss gemalt wird — also über dem Text. Auf der
   Startseite fiel das nie auf, weil deren Inhalt in einem eigenen
   Stapelkontext liegt und gewinnt; die Inhaltsseiten haben keinen. Dort
   färbte die Deko den Fließtext mit: gemessen 6.04 statt 6.62 auf der
   Regelseite. Kein Sturz unter den Grenzwert, aber Zierrat gehört hinter
   den Text. `pointer-events: none` verdeckt den Fehler zusätzlich — es
   macht die Ebene für Klicks und für `elementFromPoint` unsichtbar, obwohl
   sie sichtbar darüberliegt. */
.bg-orbs {
  position: fixed;
  inset: 0;
  overflow: hidden;
  z-index: -1;
  pointer-events: none;
}
.orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  opacity: 0.5;
  animation: float 18s ease-in-out infinite;
}
.orb.o1 {
  width: 420px;
  height: 420px;
  background: #7a2230;
  top: -120px;
  left: -80px;
}
.orb.o2 {
  width: 380px;
  height: 380px;
  background: #b5863f;
  bottom: -140px;
  right: -60px;
  animation-delay: -6s;
}
.orb.o3 {
  width: 320px;
  height: 320px;
  background: #5a1620;
  top: 40%;
  left: 55%;
  animation-delay: -11s;
}
@keyframes float {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(30px, -40px) scale(1.1); }
}

.glass {
  background: var(--panel);
  border: 1px solid var(--panel-brd);
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  box-shadow: var(--shadow);
}

/* ---- Screens ---- */
.screen {
  display: none;
  position: relative;
  z-index: 1;
  min-height: 100vh;
  animation: fade 0.4s ease;
}
.screen.active {
  display: block;
}
@keyframes fade {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

.container {
  max-width: 760px;
  margin: 0 auto;
  padding: 24px 20px 60px;
}

/* ============================================================
   Phase 2: Home-Dashboard. Mobile/Tablet = Einzelspalte wie bisher
   (Seiten-Panels aus). Ab 1024px 3-spaltiges Grid.
   ============================================================ */
/* Am Handy stehen die Dashboard-Karten unter dem Center-Inhalt (also unter der
   Rangliste). `display: contents` löst die beiden <aside> auf, damit sich die
   einzelnen Karten frei einsortieren lassen — sonst käme die linke Spalte
   komplett vor dem Center. Ab 1024px übernehmen wieder die Spalten. */
.dash-side { display: contents; }
@media (max-width: 1023px) {
  .home-dash {
    display: flex;
    flex-direction: column;
    gap: 16px;
  }
  /* Mobil zuerst die Spieloptionen: wer sich einloggt, soll so schnell wie
     möglich spielen können (Nutzerwunsch). Der Center-Block (Fortsetzen, Tages-
     Challenge, Spielen-Kacheln, Fortschritt) steht daher ganz oben. */
  .dash-center { order: 0; }
  /* Die Desktop-Rangliste bleibt mobil aus: sie steht schon im Center. Hart
     ausblenden statt nur leer lassen — sonst käme sie (und ihr "Alle
     Ranglisten"-Button) mangels order als order:0 ganz oben, doppelt zum
     Button in der Progress-Card (#home-ranking-btn). */
  #dash-ranking,
  #dash-ranking-btn { display: none; }
  /* Serie samt Erinnerungs-Knopf steht jetzt direkt UNTER dem Spielen-Bereich
     (vorher ganz oben, BEFUND 4.3). Das Opt-in bleibt gut sichtbar, verdrängt
     aber nicht mehr die Spieloptionen vom ersten Blick. */
  #dash-streak { order: 1; }
  #dash-stats { order: 2; }
  #dash-activity { order: 3; }
  #dash-milestone { order: 4; }
  .home-dash > .app-footer { order: 5; }
}
.dash-card { padding: 16px 18px; border-radius: var(--radius-lg); }
.dash-card:empty { display: none; }
.dash-title {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  margin: 0 0 10px;
}

@media (min-width: 1024px) {
  .home-dash {
    max-width: 1280px;
    display: grid;
    grid-template-columns: 280px minmax(0, 1fr) 280px;
    gap: 32px;
    align-items: start;
    padding: 24px 24px 60px;
  }
  .dash-left { grid-column: 1; grid-row: 1; }
  .dash-center { grid-column: 2; grid-row: 1; }
  .dash-right { grid-column: 3; grid-row: 1; }
  .home-dash > .app-footer { grid-column: 1 / -1; }
  .dash-side { display: flex; flex-direction: column; gap: 16px; }
  /* 4.7.2: Rangliste ist auf Desktop in die rechte Spalte gewandert ->
     Progress-Card im Center verschlankt sich auf Level-Strip + Statistik-Link. */
  #home-ranking { display: none; }
}
/* Enger Desktop-Breakpoint: schlankere Asides -> mehr Luft in der Mitte */
@media (min-width: 1024px) and (max-width: 1179px) {
  .home-dash { grid-template-columns: 250px minmax(0, 1fr) 250px; gap: 24px; }
}
@media (min-width: 1280px) {
  .home-dash { padding: 40px 40px 60px; gap: 40px; }
}

/* Dashboard-Karten-Inhalte */
.dash-stat-row { display: flex; justify-content: space-between; text-align: center; }
.dash-stat-row > div { display: flex; flex-direction: column; gap: 2px; }
.dash-stat-row b { font-family: var(--font-numeric); font-size: 22px; color: var(--accent); }
.dash-stat-row > div > span { font-size: 11px; color: var(--muted); }

.dash-streak-big { font-family: var(--font-numeric); font-size: 32px; font-weight: 700; color: var(--text); }
.dash-sub { font-size: 12px; color: var(--muted); margin-top: 4px; }
.dash-note-sm { font-size: 11px; color: var(--muted); margin-top: 8px; line-height: 1.4; opacity: 0.82; }

/* Serien-Erinnerung (Web-Push, opt-in) */
.reminder-toggle { margin-top: 12px; }
.reminder-toggle:empty { display: none; }
.reminder-btn {
  display: inline-flex; align-items: center; gap: 8px; width: 100%;
  padding: 9px 12px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface-2, transparent); color: var(--text);
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.reminder-btn:hover { border-color: var(--accent); }
.reminder-btn:disabled { opacity: 0.6; cursor: default; }
.reminder-btn.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.reminder-ico { font-size: 16px; line-height: 1; }
.reminder-hint { font-size: 11px; color: var(--muted); margin-top: 8px; line-height: 1.4; }

.dash-ms { display: flex; gap: 12px; align-items: flex-start; }
.dash-ms-ico { font-size: 26px; line-height: 1; }
.dash-ms-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.dash-ms-txt b { font-size: 14px; color: var(--text); }
.dash-ms-txt > span { font-size: 12px; color: var(--muted); }
.dash-ms-bar { height: 6px; border-radius: var(--radius-pill); background: var(--surface-2); overflow: hidden; margin-top: 4px; }
.dash-ms-bar > span { display: block; height: 100%; background: var(--accent); border-radius: var(--radius-pill); }
.dash-ms-prog { font-size: 11px; color: var(--muted); }

.dash-rank { display: flex; align-items: center; gap: 8px; padding: 5px 0; font-size: 13px; }
.dash-rank-pos { width: 18px; color: var(--muted); font-variant-numeric: tabular-nums; }
.dash-rank-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
.dash-rank-score { font-family: var(--font-numeric); color: var(--accent); font-variant-numeric: tabular-nums; }
.dash-rank.me { font-weight: 700; }
.dash-rank.me .dash-rank-name { color: var(--accent); }

.dash-act { display: flex; justify-content: space-between; gap: 8px; padding: 5px 0; font-size: 12px; }
.dash-act-lbl { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-act-time { color: var(--muted); flex: 0 0 auto; }
.dash-empty { font-size: 12px; color: var(--muted); opacity: 0.7; padding: 4px 0; line-height: 1.4; }

/* 4.5: Empty-State-Hero für neue Nutzer */
.home-empty { text-align: center; padding: 28px 22px; margin-bottom: 8px; }
.he-title { font-family: var(--font-display); font-size: 24px; font-weight: 600; color: var(--text); }
.he-sub { font-size: 14px; color: var(--muted); margin: 8px 0 18px; line-height: 1.5; }

/* 4.3: Streak-Freeze-Prompt */
.freeze-prompt {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: 12px; padding: 13px 18px; margin-bottom: 18px;
  border-left: 3px solid var(--color-info); font-size: 14px;
}
.freeze-prompt span { color: var(--text); }
.freeze-prompt .btn { flex: 0 0 auto; }

.app-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  margin-top: 34px;
  color: var(--muted);
  font-size: 12px;
  letter-spacing: 0.4px;
}
.footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 2px 4px;
}
.help-link,
.legal-link {
  background: none;
  border: none;
  color: var(--accent);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  /* 8px statt 4px: 12px Schrift mit 4px Polsterung ergibt rund 22px
     Trefferhoehe, unter dem Mindestmass von 24px — und es traf ausgerechnet
     „Datenschutz", also den Link, den man auf einem Anmeldebildschirm
     anfasst. Denselben Fehler hatte der Rueckweg schon einmal. */
  padding: 8px;
  border-radius: 7px;
  letter-spacing: 0.4px;
}
.help-link:hover,
.legal-link:hover { text-decoration: underline; }
.help-link:focus-visible,
.legal-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Ohne Deckkraft: `--color-text-muted` haelt allein die geforderten 4,5:1,
   die 0,7 drueckten die Zeile auf 3,26:1. Eine Deckkraft, die einen Kontrast
   unter die Schwelle zieht, ist kein Gestaltungsmittel, sondern eine
   unsichtbare Verschlechterung — sie sieht im Entwurf ruhiger aus und ist am
   Handy in der Sonne nicht mehr da. */
.footer-copy { color: var(--color-text-muted); }

/* Auszufüllende Platzhalter in den Rechtstexten sichtbar markieren. */
.legal-fill {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-radius: 4px;
  padding: 0 4px;
  font-style: normal;
  font-weight: 600;
}
.legal-note {
  margin: 10px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
}

/* Hilfe-Button in der Spiel-Kopfzeile */
/* KEINE FESTE BREITE. Hier standen 70px, und die zweite Regel weiter unten
   (`gap: 8px`, seit dem Ueberlauf-Menue) hat sie nicht zurueckgenommen. Zwei
   44er Knoepfe plus Abstand wollen 96 Pixel in 70 -- gemessen im Handy-
   Viewport 393x852 war der Hilfe-Knopf danach 24 Pixel breit bei 44 Pixeln
   Hoehe, also ein hochkant stehendes Oval, und er ragte sechs Pixel links aus
   seinem Kasten heraus. Auf allen drei geprueften Groessen sichtbar.
   Nachpruefung vom 09.09.2026, Befund 6. */
.topbar-end { display: flex; justify-content: flex-end; }
.game-help {
  /* `flex: 0 0 44px` und nicht nur `width`: In einem Flex-Kasten schrumpft
     ein Kind mit `width` bereitwillig, sobald es eng wird -- genau das ist
     hier passiert. */
  flex: 0 0 44px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--panel-brd);
  background: var(--surface-1);
  color: var(--muted);
  font-family: inherit;
  font-size: 17px;
  font-weight: 700;
  cursor: pointer;
  transition: color 0.2s, border-color 0.2s, background 0.2s;
}
.game-help:hover { color: var(--text); border-color: var(--accent); background: var(--surface-3); }
.game-help:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* 1.7: Overflow-Menü im Spiel-Header */
.topbar-end { display: flex; align-items: center; gap: 8px; }
.overflow-wrap { position: relative; }
.overflow-menu {
  position: absolute;
  top: 44px;
  right: 0;
  z-index: 30;
  min-width: 190px;
  padding: 6px;
  display: flex;
  flex-direction: column;
  background: var(--color-bg-surface-raised);
  border: 1px solid var(--color-panel-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  animation: fade 0.15s ease;
}
.overflow-menu[hidden] { display: none; }
.ov-item {
  text-align: left;
  padding: 10px 12px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text);
  font-family: var(--font-body);
  font-size: 14px;
  cursor: pointer;
  transition: background 0.15s;
}
.ov-item:hover { background: var(--surface-2); }
.ov-item:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }

/* 1.8: Spielfeld bei Pause ausblenden (Blur) */
.board.blurred { filter: blur(9px); pointer-events: none; }

/* Hilfe-Overlay-Inhalt */
.help-body { display: flex; flex-direction: column; overflow-y: auto; min-height: 0; }
.help-sec { padding: 13px 0; border-bottom: 1px solid var(--panel-brd); }
.help-sec:last-child { border-bottom: none; }
.help-sec h4 { margin: 0 0 6px; font-size: 15px; font-weight: 700; color: var(--text); }
.help-sec p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--muted); }

/* Das Widerspruchsrecht muss laut Art. 21 Abs. 4 DSGVO hervorgehoben und von
   den übrigen Angaben getrennt stehen. Deshalb ein eigener Kasten und nicht
   nur ein fetter Satz. */
/* Mit `.help-sec` davor, nicht nur `.ds-widerspruch`: Sonst gewinnt die
   bestehende Regel `.help-sec p` (Farbe) und `.help-sec p + p` (Abstand), und
   zwar unbemerkt — Rahmen und Hintergrund kamen durch, Textfarbe und Abstand
   nicht. Im Browser nachgemessen: Der Kasten hatte exakt dieselbe Farbe wie
   gewoehnlicher Fliesstext, die gesetzlich verlangte Hervorhebung lief also
   ins Leere. */
.help-sec p.ds-widerspruch {
  margin: 12px 0 0; padding: 12px 14px;
  border: 1px solid var(--accent); border-radius: 10px;
  background: var(--panel);
  font-size: 14px; line-height: 1.55; color: var(--text);
}

/* Sprachhinweis über den Rechtstexten. Bewusst leiser als der Inhalt: Er ist
   eine Auskunft über den Text, nicht Teil davon. */
.legal-lang {
  margin: 0 0 4px; padding: 8px 10px;
  font-size: 13px; line-height: 1.5; color: var(--muted);
  background: var(--panel); border: 1px solid var(--panel-brd);
  border-radius: 8px;
}
.help-sec p + p { margin-top: 6px; }
.help-sec ul { margin: 7px 0 0; padding-left: 18px; }
.help-sec li { margin: 4px 0; font-size: 14px; line-height: 1.5; color: var(--muted); }
.help-sec b { color: var(--text); font-weight: 700; }
.help-sec a { color: var(--color-primary-text); text-decoration: underline; word-break: break-word; }
/* Wie der globale Fokusring: --accent traegt im hellen Theme nur 3.03:1 und
   liegt damit auf der 3:1-Grenze von WCAG 1.4.11. */
.help-sec a:focus-visible { outline: 2px solid var(--color-primary-text); outline-offset: 2px; }
.help-sec code { font-size: 12px; background: var(--surface-2); padding: 0 4px; border-radius: 4px; }

/* ---- Buttons ---- */
.btn {
  font-family: inherit;
  font-weight: 600;
  border: 1px solid transparent;
  border-radius: 12px;
  padding: 12px 20px;
  cursor: pointer;
  color: var(--text);
  transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease, opacity 0.2s;
  font-size: 15px;
  /* Flex-Box statt inline: sonst bleibt ein <a class="btn"> inline, ignoriert
     btn-block (width:100%) und zerfaellt bei umbrechendem Text (z. B. der langen
     2FA-Gate-Beschriftung) in fragmentierte Hintergrund-Zeilen. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  text-decoration: none; /* Button-Optik auch fuer <a class="btn"> (kein Link-Unterstrich). */
}
.btn:active { transform: scale(0.96); }
/* Handy-Durchgang 10.8.2026: Gesperrte Hauptknoepfe sahen aus wie freigegebene.
   In der Einfuehrung ist „Weiter“ absichtlich gesperrt, bis man die Mechanik
   einmal probiert hat — aber bei voller Deckkraft und gleichem roten Verlauf
   sieht man das nicht. Auf dem Handy gibt es kein Hover, also blieb gar kein
   Hinweis: tippen, nichts passiert. Werkzeuge (.tool) hatten die Abstufung
   laengst; die Hauptknoepfe bekommen sie jetzt auch. */
.btn:disabled {
  opacity: 0.45;
  cursor: default;
  box-shadow: none;
}
.btn:disabled:active { transform: none; }
/* [hidden] muss jede display-Regel schlagen — sonst bleiben Elemente sichtbar,
   deren Klasse ein display setzt (z. B. .btn-sm { display: inline-flex }). */
[hidden] { display: none !important; }
.btn-primary {
  background: linear-gradient(135deg, var(--accent2), color-mix(in srgb, var(--accent2) 72%, #000));
  /* NICHT --color-text-primary: das kippt im hellen Theme auf Dunkelbraun und
     ergibt 2,26:1 auf dem Rot. Der Knopfgrund ist in beiden Themes derselbe,
     also ist es die Schrift darauf auch (tokens.css). */
  color: var(--on-accent-deep);
  box-shadow: var(--shadow-primary);
}
.btn-primary:hover { box-shadow: var(--shadow-primary-hover); }
.btn-ghost {
  background: var(--surface-1);
  border-color: var(--panel-brd);
}
.btn-ghost:hover { background: var(--surface-3); }
.btn-block { width: 100%; }
/* 5.3 A11y: 44px-Touch-Target-Boden, Text bleibt vertikal zentriert. */
/* `white-space: nowrap`: Ohne das zerfiel „‹ Zurueck" am Handy in zwei
   Zeilen -- der Pfeil oben, das Wort darunter. Ein Knopf, der umbricht,
   sieht aus wie zwei. */
.btn-sm { padding: 8px 14px; font-size: 13px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; white-space: nowrap; }

/* ============================================================
   AUTH
   ============================================================ */
#screen-auth {
  display: none;
  place-items: center;
  min-height: 100vh;
  padding: 24px;
}
#screen-auth.active { display: grid; }

/* ===== Landing / Gast-Teaser ===== */
/* Ohne `place-items`: Die Startseite war einmal ein Grid, seit dem
   SEO-Umbau ist sie `display: block` (weiter unten überschrieben). Lange war
   die Regel dadurch wirkungslos — Chrome setzt `justify-items` inzwischen
   aber auch im Blocklayout um, und seither schrumpfte sie jedes Kind auf
   Inhaltsbreite statt auf die volle Spalte. Gemessen im Gegenversuch:
   Kopfzeile 776 → 1080 px, Fußzeile 504 → 1080. Eine Regel, die jahrelang
   folgenlos dastand und durch ein Browser-Update Wirkung bekam. */
#screen-landing { display: none; min-height: 100vh; padding: 24px; }
#screen-landing.active { display: grid; }
.landing { width: 100%; max-width: 440px; border-radius: 24px; padding: 32px 28px 26px; text-align: center; }
/* .landing-brand/-logo/-word entfernt (BEFUND-2 4.6): keine zweite Wortmarke in
   der Einstiegskarte mehr. */
.landing-title { font-family: var(--font-display); font-size: 27px; font-weight: 600; color: var(--text); margin: 0 0 6px; text-wrap: balance; }
.landing-sub { color: var(--muted); font-size: 14px; margin: 0 0 16px; }
.landing-free { display: inline-block; font-size: 12px; font-weight: 600; color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); border-radius: 100px; padding: 5px 13px; margin-bottom: 18px; }
.landing-tiles { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
/* Zweite Reihe „Mehr als Sudoku": die neuen Arten als Gast-Kacheln (3.3). Klarer
   Abstand + kleiner Titel als Trenner zur Schwierigkeitswahl darüber. */
.landing-more { margin-top: 22px; }
.landing-more-title { font-family: var(--font-display); font-size: 20px; font-weight: 600; margin: 0 0 2px; color: var(--text); }
.landing-more-sub { color: var(--muted); font-size: 13px; line-height: 1.4; margin: 0 0 12px; }
.landing-more[hidden] { display: none; }
/* Instant Play: spielbares Gitter oben auf der Landing. Eigene Klasse (nicht
   .board), damit dessen Mobile-Regeln mit --board-size nicht hineingreifen. */
/* BREITE AM BEHAELTER, NICHT AM FENSTER (1.17.118).

   Korbi am Geraet: „Das Spielbrett haengt ueber dem Rand." Gemessen im
   Handy-Viewport: Karte 24..369, ihr Inhalt 52..341, das Brett 53..373 --
   32 px darueber hinaus, und zwar im Browser genauso wie in der App.

   Hier stand `width: min(86vw, 320px)`. `vw` misst das FENSTER; die Karte
   `.landing` hat aber 28 px Polster auf jeder Seite, die das Fenster nicht
   kennt. Bei 393 px sind 86vw gleich 338, gedeckelt auf 320 -- bei 289 px
   verfuegbarem Platz. Die Rechnung stimmte fuer sich und war am falschen
   Ding gemessen. Sie faellt auch nur in einem Fensterbereich auf: Auf
   breiten Bildschirmen greift der Deckel von 320 px und alles sieht gut aus.

   `aspect-ratio` STATT EINER ZWEITEN RECHNUNG: Vorher stand die Hoehe
   nochmal als `min(86vw, 320px)` daneben. Quadratisch war das nur, solange
   beide Zeilen dasselbe sagen -- eine Doppelung, die beim naechsten Anfassen
   auseinanderlaeuft, und ein Sudoku mit rechteckigen Zellen sieht kaputt
   aus, ohne dass die Ursache zu sehen waere. Block 311. */
.instant-board {
  display: grid;
  grid-template-columns: repeat(9, minmax(0, 1fr));
  width: min(100%, 320px);
  aspect-ratio: 1;
  margin: 4px auto 12px;
  background: var(--board-frame);
  border: 2px solid var(--board-frame);
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 16px 44px -18px rgba(0, 0, 0, 0.7);
  cursor: pointer;
}
.instant-board .cell { font-size: clamp(13px, 3.4vw, 18px); }
.instant-hint { color: var(--muted); font-size: 14px; text-align: center; margin: 0 0 16px; }
/* Tagesarchiv: statisches 9x9-Gitter (Raetsel + Loesung), Bestzeiten, Tag-Nav. */
.daily-grid {
  display: grid; grid-template-columns: repeat(9, minmax(0, 1fr));
  width: min(92vw, 100%, 380px); aspect-ratio: 1; margin: 8px 0 4px;
  background: var(--board-frame); border: 2px solid var(--board-frame);
  border-radius: 12px; overflow: hidden;
  font-family: var(--font-numeric); font-variant-numeric: tabular-nums;
}
.daily-grid .dcell {
  display: flex; align-items: center; justify-content: center;
  background: var(--board-cell-base); color: var(--num-user);
  font-size: clamp(14px, 3.6vw, 20px);
  border-right: 1px solid var(--grid-line); border-bottom: 1px solid var(--grid-line);
}
.daily-grid .dcell.given { color: var(--num-given); font-weight: 700; }
.daily-grid .dcell.sol { color: var(--num-user); }
.daily-grid .dcell.bl { border-left: 2px solid var(--grid-line-block); }
.daily-grid .dcell.bt { border-top: 2px solid var(--grid-line-block); }
.daily-grid .dcell.br { border-right: none; }
.daily-grid .dcell.bb { border-bottom: none; }
.daily-times { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; max-width: 360px; }
.daily-times li { display: flex; justify-content: space-between; gap: 12px; padding: 8px 12px; background: var(--panel); border: 1px solid var(--panel-brd); border-radius: 10px; }
.daily-times li span:first-child { color: var(--text); font-weight: 600; }
.daily-times li span:last-child { color: var(--accent); font-variant-numeric: tabular-nums; }
.daily-daynav { display: flex; justify-content: space-between; gap: 12px; margin: 4px 0 16px; }
/* Interaktives Technik-Demo-Gitter (Technikseiten). */
.tech-demo { margin: 8px 0 4px; }
.td-grid {
  display: grid; grid-template-columns: repeat(9, minmax(0, 1fr));
  width: min(92vw, 100%, 400px); aspect-ratio: 1;
  background: var(--board-frame); border: 2px solid var(--board-frame);
  border-radius: 12px; overflow: hidden;
}
.td-cell {
  display: flex; align-items: center; justify-content: center; position: relative;
  background: var(--board-cell-base);
  border-right: 1px solid var(--grid-line); border-bottom: 1px solid var(--grid-line);
  font-family: var(--font-numeric); transition: background 0.15s ease;
}
.td-cell.bl { border-left: 2px solid var(--grid-line-block); }
.td-cell.bt { border-top: 2px solid var(--grid-line-block); }
.td-cell.br { border-right: none; }
.td-cell.bb { border-bottom: none; }
.td-cell.given { color: var(--num-given); font-weight: 700; font-size: clamp(14px, 3.6vw, 20px); }
.td-cell.hl { background: color-mix(in srgb, var(--color-primary) 26%, var(--board-cell-base)); }
.td-cell.elimcell { background: color-mix(in srgb, var(--color-error) 20%, var(--board-cell-base)); }
.td-cands { display: grid; grid-template-columns: repeat(3, 1fr); width: 100%; height: 100%; }
/* BEFUND-2 3.7: Kandidaten in den Technik-Beispielen waren klein und blass ->
   größer und kräftiger, damit das Muster (Streichung/Highlight) klar lesbar ist. */
.td-c { display: flex; align-items: center; justify-content: center; font-size: clamp(10px, 2.4vw, 14px); color: color-mix(in srgb, var(--num-pencil) 62%, var(--text)); line-height: 1; }
.td-cell.hl .td-c { color: color-mix(in srgb, var(--num-pencil) 62%, var(--text)); }
.td-c.elim { color: var(--num-error); font-weight: 800; }
.td-c.hlc { color: var(--gold); font-weight: 800; }
.td-c.struck { visibility: hidden; }
.td-desc { color: var(--text); font-size: 15px; line-height: 1.55; margin: 12px 0 10px; min-height: 2.6em; }
.td-bar { display: flex; gap: 10px; }
/* Binaer-Teaser auf der Landing: sichtbarer Einstieg zur Binaer-SEO-Seite. */
.landing-binary {
  display: flex; align-items: center; gap: 12px; margin-top: 12px;
  padding: 12px 14px; text-decoration: none;
  background: var(--panel); border: 1px solid var(--panel-brd); border-radius: 14px;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.landing-binary:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--panel-brd)); background: color-mix(in srgb, var(--accent) 6%, var(--panel)); }
.landing-binary .lb-badge {
  flex: none; font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--on-accent, #1c0c0f); background: var(--accent); border-radius: 6px; padding: 3px 7px;
}
.landing-binary .lb-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; text-align: left; }
.landing-binary .lb-title { color: var(--text); font-weight: 600; font-size: 15px; }
.landing-binary .lb-sub { color: var(--muted); font-size: 12px; line-height: 1.4; }
.landing-binary .lb-arrow { flex: none; margin-left: auto; color: var(--accent); font-size: 22px; line-height: 1; }
/* Gast-Fortschritt (Verlust-Aversion): Rang + Punkte + Sterne, in Landing & Gewinn. */
.landing-progress { margin: 0 0 18px; }
.gw-progress:empty { display: none; }
.gw-progress { margin: 10px 0 4px; }
.gp-eyebrow { display: block; font-size: 11px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); margin-bottom: 6px; }
.gp-card { display: inline-flex; align-items: center; gap: 12px; padding: 7px 15px 7px 7px; border-radius: 100px; border: 1px solid color-mix(in srgb, var(--gold) 36%, transparent); background: color-mix(in srgb, var(--gold) 10%, transparent); }
.gp-badge { width: 42px; height: 42px; border-radius: 50%; object-fit: cover; box-shadow: 0 0 0 2px color-mix(in srgb, var(--gold) 60%, transparent); }
.gp-txt { display: flex; flex-direction: column; line-height: 1.22; text-align: left; }
.gp-rank { font-family: var(--font-display); font-weight: 600; font-size: 15px; color: var(--text); }
.gp-nums { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.gp-next { font-size: 12px; color: var(--gold); font-variant-numeric: tabular-nums; margin-top: 1px; }
.gp-loss { margin: 8px 2px 0; font-size: 12px; line-height: 1.45; color: var(--muted); }
/* Read-only Gast-Rangliste + Aspirations-Zeile */
.landing-board { margin: 0 0 18px; text-align: left; }
.lb-hook { font-size: 14px; color: var(--text); margin-bottom: 8px; }
.lb-hook b { color: var(--gold); font-variant-numeric: tabular-nums; }
.lb-list { border: 1px solid var(--panel-brd); border-radius: 12px; overflow: hidden; background: color-mix(in srgb, var(--panel) 60%, transparent); }
/* DER NAME HAT VORRANG (1.17.118).

   Korbi am Geraet: „die Namen in der Rangliste sind nicht lesbar." Auf dem
   Bild stand „Ca..." statt eines Namens.

   Vorher: `26px 1fr auto auto`. Rang UND Punktzahl nahmen sich `auto`, also
   was sie brauchen -- der Name bekam den Rest. Bei einem langen Rang wie
   „Legende von Sagax" blieben dem Namen 44 px, Raum fuer rund sechs
   Zeichen -- auf Korbis Bild stand deshalb „Ca...".

   Jetzt schrumpft der RANG. Er ist die verzichtbarste Angabe der Zeile: Man
   sucht sich selbst oder jemanden, den man kennt, nicht einen Rangnamen. Die
   Punktzahl bleibt `auto` und damit ganz -- eine abgeschnittene Zahl waere
   schlimmer als gar keine.

   DER ERSTE VERSUCH WAR `26px minmax(0, 1fr) minmax(0, auto) auto` UND HAT
   NICHTS BEWIRKT. Grid gibt `auto`-Spuren ihren Inhalt, BEVOR eine
   `fr`-Spur etwas bekommt -- „Carolina" hatte weiter 37 px bei 56 noetigen.
   Erst die Umkehrung wirkt: Der NAME nimmt sich seinen Inhalt, der RANG ist
   die `fr`-Spur. Fuenf Varianten im Handy-Viewport durchgemessen.

   UND DER RANG BRAUCHT EINEN BODEN. Namen duerfen 24 Zeichen haben
   (server.js); mit einem solchen fiel die Rangspalte auf 0 px und
   verschwand ganz, was kaputt aussieht. Bei 56 px behaelt sie „Legende…",
   waehrend der lange Name auf rund elf Zeichen kuerzt. Bei gewoehnlichen
   Namen bleibt alles ganz. Block 311e bis 311n. */
.lb-row { display: grid; grid-template-columns: 26px minmax(0, 1fr) auto; align-items: baseline; gap: 10px; padding: 8px 12px; border-bottom: 1px solid var(--panel-brd); font-size: 14px; }
.lb-row:last-child { border-bottom: 0; }
/* ZWEI BAUFORMEN, weil es zwei Erzeuger gibt.

   Die Grundform oben hat DREI Spuren: Platz, Name, Punktzahl. So baut
   `renderBoard` die zehn Spieltyp-Ranglisten -- dort gibt es keinen
   Rangnamen. Der Name nimmt sich den freien Platz, die Zahl steht rechts.

   Die Landing-Rangliste hat einen VIERTEN Wert, den Rangnamen, und fordert
   deshalb `lb-row-rang` an. Hier gibt der RANG nach, nicht der Name -- siehe
   die lange Begruendung darueber.

   BEIDES WAR EINMAL EINE REGEL, und das ging beinahe schief: Drei Kinder in
   vier Spuren heisst, dass die Punktzahl in der Rang-Spur landet. Gemessen:
   Der Name wurde auf 56 px geklemmt, obwohl 200 frei waren, und die Zahl
   sprang rund 166 px nach links an den Namen. Auf zehn Seiten. Die Zahl der
   Kinder entscheidet jetzt ueber die Vorlage, und Block 311t/311u haelt
   beide Erzeuger gegen ihre Spurenzahl. */
.lb-row.lb-row-rang { grid-template-columns: 26px minmax(0, auto) minmax(56px, 1fr) auto; }
/* DIE PLATZNUMMER, ueberall gleich seit 1.17.118.

   Die zehn Spieltyp-Ranglisten zeichneten sie vorher mit `.lb-rank` aus --
   derselben Klasse, mit der die Landing den RANGNAMEN auszeichnet. Bei der
   Umbenennung aendert sich dort das Aussehen mit: Die Ziffer wird von 12 auf
   14 px groesser und rueckt rechtsbuendig, also rund 17 px nach rechts. Die
   Spur ist gleich breit, es verschiebt sich sonst nichts.

   BEWUSST SO ANGENOMMEN und nicht mit einer dritten Klasse zurueckgedreht:
   Die Landing sah schon immer so aus, die zehn Listen gleichen sich damit an,
   und rechtsbuendige Tabellenziffern sind fuer eine Platznummer das
   Richtige -- die Einer stehen untereinander. Von der Nachpruefung am
   30.08.2026 gemessen und benannt, damit es nicht als unbemerkte Nebenwirkung
   durchgeht. */
.lb-pos { color: var(--muted); font-variant-numeric: tabular-nums; text-align: right; }
.lb-name { color: var(--text); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Abschneiden statt umbrechen: Ohne das legt die jetzt schrumpfende Spalte
   den Rangnamen auf zwei Zeilen und die Zeilenhoehe springt. */
.lb-rank { color: var(--muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-score { color: var(--gold); font-weight: 600; font-variant-numeric: tabular-nums; }
/* Claim-Bestätigung auf der Home (einmalig nach dem Anmelden). */
.claim-banner { padding: 12px 16px; margin-bottom: 12px; border-radius: 14px; font-size: 14px; font-weight: 600;
  color: var(--text); border: 1px solid color-mix(in srgb, var(--gold) 40%, transparent);
  background: color-mix(in srgb, var(--gold) 12%, transparent); }
/* Home-Ticker: oberstes Kaestchen, rotiert alle 20 s (Fade via .ticker-fade). */
/* .container gibt sonst 60px Boden-Padding -> hier schmal halten. Breite und
   seitliches Padding folgen exakt dem .home-dash je Breakpoint, damit der Ticker
   buendig ueber den Dashboard-Spalten sitzt. Das direkt folgende Dashboard
   braucht oben weniger Abstand (der Ticker uebernimmt ihn). */
.ticker-wrap { max-width: 760px; margin: 0 auto; padding: 16px 20px 0; }
.ticker-wrap + .home-dash { padding-top: 12px; }
@media (min-width: 1024px) {
  .ticker-wrap { max-width: 1280px; padding: 16px 24px 0; }
}
@media (min-width: 1280px) {
  .ticker-wrap { padding: 16px 40px 0; }
}
.home-ticker { padding: 12px 16px; border-radius: 14px; transition: opacity 0.28s var(--ease-out-quart); }
.home-ticker.ticker-fade { opacity: 0; }
.ticker-msg { display: flex; align-items: center; gap: 12px; }
.ticker-msg[hidden] { display: none; }
.ticker-icon { font-size: 20px; flex: none; line-height: 1; }
/* Fliesstext-Typografie exakt wie der Rest der App: Inter, Normalgewicht (nicht
   medium), damit der Ticker sich nahtlos einfuegt. */
.ticker-text { flex: 1 1 auto; font-family: var(--font-body); font-size: 14px; font-weight: 400; color: var(--text); }
.ticker-btn { flex: none; white-space: nowrap; }
@media (max-width: 560px) {
  /* Mobil kompakter, damit der Ticker weniger Platz einnimmt. */
  .home-ticker { padding: 10px 14px; }
  .ticker-msg { flex-wrap: wrap; gap: 6px 10px; }
  .ticker-icon { font-size: 17px; }
  .ticker-text { flex: 1 1 100%; font-size: 12px; }
  .ticker-btn { font-size: 12px; min-height: 38px; padding: 6px 12px; }
}
.land-tile { position: relative; border: 1px solid var(--panel-brd); border-radius: 16px; overflow: hidden; cursor: pointer; padding: 0; background: var(--panel); aspect-ratio: 1; transition: transform 0.15s, border-color 0.2s, box-shadow 0.2s; container-type: inline-size; /* Titel-cqi bezieht sich auf die Kachel, nicht den Viewport */ }
.land-tile:hover { transform: translateY(-4px); border-color: var(--accent); box-shadow: 0 18px 44px -20px var(--accent); }
.land-tile img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Landing-Kacheln haben keine Info-Leiste -> Titel direkt an den unteren Bildrand. */
.land-tile .diff-title { bottom: 12px; }
.landing-login { display: inline-block; margin-top: 20px; background: none; border: none; color: var(--muted); font-family: inherit; font-size: 14px; cursor: pointer; text-decoration: underline; text-underline-offset: 3px; }
.landing-login:hover { color: var(--text); }
.landing-note { font-size: 12px; color: var(--muted); margin: 12px 0 0; }
/* Seit 1.17.117 ein Bild statt eines Emojis (siehe Kommentar in index.html).
   `border-radius: 50%` und die feste Groesse, damit es dasselbe runde Zeichen
   ist wie in der Kopfzeile -- die Datei ist quadratisch. */
.wall-fox { display: block; width: 56px; height: 56px; margin: 0 auto 6px; border-radius: 50%; }
.wall-perks { list-style: none; margin: 16px 0 20px; padding: 0; text-align: left; display: grid; gap: 10px; }
.wall-perks li { display: flex; align-items: center; gap: 11px; font-size: 14px; color: var(--text); }
.wall-perks li span { font-size: 18px; flex: none; }
.gw-stars { font-size: 28px; letter-spacing: 4px; margin-bottom: 2px; }

/* ============================================================
   SEO/GEO Landing-Content (Phase 2). Gilt nur auf #screen-landing:
   der Gast-Hero bleibt zentriert (erster Viewport), darunter folgt
   scrollbarer, crawlbarer Content. Alle Farben aus den Theme-Tokens.
   ============================================================ */
#screen-landing.active { display: block; padding: 0; }
.lp-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; max-width: 1080px; margin: 0 auto; padding: 16px 24px;
}
.lp-logo { font-family: var(--font-display); font-weight: 700; letter-spacing: 2px; color: var(--text); font-size: 17px; }
.lp-nav { display: flex; gap: 20px; }
.lp-nav a { color: var(--muted); text-decoration: none; font-size: 14px; font-weight: 600; }
.lp-nav a:hover { color: var(--accent); }
.lp-header-nav { display: flex; align-items: center; gap: 18px; }
/* Sprachumschalter DE/EN (Landing-Header) */
.lp-lang { display: inline-flex; border: 1px solid var(--panel-brd); border-radius: 100px; overflow: hidden; }
.lp-lang button {
  background: none; border: none; color: var(--muted); font: inherit; font-size: 12px;
  font-weight: 700; letter-spacing: 0.5px; padding: 5px 11px; cursor: pointer; line-height: 1;
}
.lp-lang button.active { background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--accent); }
.lp-lang button:not(.active):hover { color: var(--text); }
.lp-lang button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.lp-login {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  font-family: inherit; font-size: 13px; font-weight: 700; letter-spacing: 0.3px;
  padding: 7px 16px; border-radius: 100px; cursor: pointer; white-space: nowrap;
}
.lp-login:hover { background: color-mix(in srgb, var(--accent) 24%, transparent); }
.lp-login:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.lp-hero {
  min-height: calc(100vh - 66px);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 26px; padding: 8px 24px 56px; text-align: center;
}
.lp-hero-copy { max-width: 640px; }
.lp-h1 {
  font-family: var(--font-display); font-size: clamp(28px, 5vw, 44px); font-weight: 600;
  line-height: 1.12; color: var(--text); margin: 0 0 14px; text-wrap: balance;
}
.lp-lead { color: var(--muted); font-size: clamp(15px, 2.2vw, 18px); line-height: 1.6; margin: 0; }
.lp-content { max-width: 820px; margin: 0 auto; padding: 8px 24px 72px; display: flex; flex-direction: column; gap: 52px; }

/* Landing am Desktop: Text und Einstiegskarte nebeneinander statt untereinander.
   Vorher stand die Seite als rund 490px schmale Saeule auf einem 1495er-Fenster,
   links und rechts je ein Drittel leer — die erste Begegnung mit dem Produkt
   wirkte wie eine unfertige mobile Ansicht. Nebeneffekt und eigentlicher Zweck:
   Die Kachelreihe wird vierspaltig, damit die Artenvielfalt ueber die Falz kommt
   statt sieben Scroll-Rasten tiefer.

   Bewusst GRID mit benannten Feldern, nicht flex-direction:row. Der Hero hat
   drei Kinder, und der Fliesstext steht im Markup ABSICHTLICH hinter der
   Einstiegskarte: Am Handy soll das spielbare Gitter hoch stehen, nicht hinter
   einem Absatz Erklaerung. Ein Row-Layout haette die drei nebeneinander gelegt
   (Ueberschrift, Karte, Text) und die Ueberschrift auf 225px gequetscht. Mit
   Grid bleibt die DOM-Reihenfolge fuers Handy erhalten, und am Desktop wandert
   der Text unter die Ueberschrift.
   Nur ab 1024px — darunter bleibt alles wie es war. */
@media (min-width: 1024px) {
  .lp-hero {
    display: grid;
    grid-template-columns: minmax(0, 520px) minmax(0, 560px);
    /* Dritte, leere Zeile mit `1fr`: Die Einstiegskarte ueberspannt alle Zeilen
       und ist hoeher als Ueberschrift plus Fliesstext zusammen. Ohne die
       Auffangzeile verteilt Grid ihre Ueberhoehe auf die belegten Zeilen — der
       Fliesstext rutschte dadurch an den unteren Rand, hunderte Pixel unter die
       Ueberschrift, zu der er gehoert. */
    grid-template-areas:
      "kopf karte"
      "text karte"
      "rest karte";
    grid-template-rows: auto auto 1fr;
    /* Beide Spalten oben beginnen. Die Einstiegskarte traegt Brett UND Kacheln
       und wird ueber 1200px hoch; mit `center` zog es die zwei Textzeilen
       daneben so weit auseinander, dass der Fliesstext unter die Falz rutschte. */
    align-content: start;
    justify-content: center;
    gap: 18px 56px;
    text-align: left;
    padding-inline: 40px;
    padding-block-start: 40px;
  }
  .lp-hero-copy { grid-area: kopf; max-width: none; align-self: start; }
  .lp-lead { grid-area: text; align-self: start; }
  .landing { grid-area: karte; align-self: start; max-width: 560px; }
  .landing-tiles { grid-template-columns: repeat(4, 1fr); }
}
.lp-sec > h2 {
  font-family: var(--font-display); font-size: clamp(22px, 3.5vw, 30px); font-weight: 600;
  color: var(--text); margin: 0 0 18px; text-wrap: balance;
}
.lp-sec h3 { font-family: var(--font-display); font-size: 18px; color: var(--text); margin: 0 0 6px; }
.lp-sec h3.tech-h3 { margin-top: 24px; } /* Abstand zwischen Technik-Blöcken innerhalb einer H2-Gruppe */
.lp-sec p { color: var(--muted); font-size: 15px; line-height: 1.65; margin: 0 0 12px; }
.lp-sec p:last-child { margin-bottom: 0; }
.lp-lede { font-size: 17px; color: var(--text); }
/* Aufzaehlungen (Regeln, Tipps) auf den Content-Seiten. */
.lp-list { color: var(--muted); font-size: 15px; line-height: 1.65; margin: 0 0 12px; padding-left: 22px; display: flex; flex-direction: column; gap: 10px; }
.lp-list li { padding-left: 4px; }
.lp-list strong { color: var(--text); }
/* Auffaelliger Call-to-Action-Link (z. B. "Jetzt spielen"). */
/* BEFUND-2 12: Ein Primärknopf für App UND Content-Seiten — der Content-CTA
   übernimmt exakt den roten Verlaufsknopf der App (.btn-primary) statt der alten
   goldenen Fläche mit dunkler Schrift. */
/* Schrift aus `--on-accent-deep`, nicht aus `--color-text-primary`: Der rote
   Verlauf darunter ist in BEIDEN Themes derselbe, die Textfarbe wechselte
   aber mit — im hellen Theme ergab das fast schwarze Schrift auf Rot,
   gemessen 2.30:1 gegen ein Soll von 4.5. Auffallen konnte es hier kaum,
   weil im dunklen Theme entwickelt wird und es dort richtig aussieht. */
.lp-cta {
  display: inline-block; text-decoration: none; font-weight: 600; font-size: 15px;
  color: var(--on-accent-deep);
  background: linear-gradient(135deg, var(--accent2), color-mix(in srgb, var(--accent2) 72%, #000));
  box-shadow: 0 10px 30px -8px color-mix(in srgb, var(--accent2) 75%, transparent);
  padding: 12px 20px; border-radius: 12px;
}
.lp-cta:hover { box-shadow: 0 14px 36px -8px color-mix(in srgb, var(--accent2) 85%, transparent); }
/* Marken-Lockup als Link (Content-Kopfzeilen) — ohne Unterstreichung/Link-Farbe. */
a.brand-lockup { text-decoration: none; cursor: pointer; }
.lp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
.lp-card { background: var(--panel); border: 1px solid var(--panel-brd); border-radius: 16px; padding: 14px; }
.lp-card img { width: 100%; height: auto; border-radius: 10px; margin-bottom: 12px; display: block; aspect-ratio: 1; object-fit: cover; }
.lp-ranks { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px 16px; }
.lp-ranks li {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 8px 12px; background: var(--panel); border: 1px solid var(--panel-brd); border-radius: 10px;
}
.lp-ranks li span:first-child { color: var(--text); font-weight: 600; font-size: 14px; }
.lp-ranks li span:last-child { color: var(--accent); font-size: 13px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.lp-faq { display: flex; flex-direction: column; gap: 10px; }
.lp-faq details { background: var(--panel); border: 1px solid var(--panel-brd); border-radius: 12px; overflow: hidden; }
.lp-faq summary {
  cursor: pointer; padding: 14px 44px 14px 16px; font-weight: 600; color: var(--text);
  font-size: 15px; list-style: none; position: relative;
}
.lp-faq summary::-webkit-details-marker { display: none; }
.lp-faq summary::after {
  content: "+"; position: absolute; right: 16px; top: 50%; transform: translateY(-50%);
  color: var(--accent); font-size: 20px; line-height: 1;
}
.lp-faq details[open] summary { color: var(--accent); }
.lp-faq details[open] summary::after { content: "–"; }
.lp-faq p { padding: 0 16px 15px; margin: 0; color: var(--muted); font-size: 15px; line-height: 1.6; }
#screen-landing > .app-footer { max-width: 1080px; margin: 0 auto; }
@media (max-width: 640px) {
  /* BEFUND 4.2: Nav nicht mehr ersatzlos ausblenden. Header umbrechen lassen und
     die Navigation als volle, horizontal scrollbare Zeile darunter zeigen — so
     behalten Gaeste auf dem Handy den Zugang zu Regeln, Binaer, Raengen und FAQ
     (Landing UND eigenstaendige Content-Seiten). */
  .lp-header { flex-wrap: wrap; row-gap: 10px; padding: 12px 16px; }
  .lp-header-nav { flex-wrap: wrap; row-gap: 10px; }
  .lp-nav {
    display: flex;
    flex-basis: 100%;
    order: 5;
    gap: 18px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 2px;
    /* Verlaufskante wie die anderen Scroll-Leisten (BEFUND-2 4.7); nur mobil,
       wo die Nav umbricht und scrollt — am Desktop passt sie ohne Fade. */
    -webkit-mask-image: var(--scroll-fade-mask);
    mask-image: var(--scroll-fade-mask);
  }
  .lp-nav::-webkit-scrollbar { display: none; }
  .lp-nav a { white-space: nowrap; font-size: 13px; }
  .lp-grid-2, .lp-ranks { grid-template-columns: 1fr; }
}

/* ---- Eigenständige Content-Seiten (/sudoku-regeln, /fuchs-raenge) ---- */
a.lp-logo { text-decoration: none; }
a.lp-logo:hover { color: var(--accent); }
.lp-page { padding-top: 28px; }
.lp-page > h1 {
  font-family: var(--font-display); font-size: clamp(28px, 5vw, 40px); font-weight: 600;
  line-height: 1.14; color: var(--text); margin: 0 0 10px; text-wrap: balance;
}
.lp-page > .lp-lead { margin: 0 0 8px; }
.lp-inline-link, .lp-more {
  display: inline-block; color: var(--color-primary-text); text-decoration: none; font-weight: 600; font-size: 15px;
}
.lp-inline-link:hover, .lp-more:hover { text-decoration: underline; }
/* Footer-Links (Anker) exakt wie die .legal-link-Buttons — einheitliches Bild. */
.footer-links a {
  color: var(--color-primary-text);
  text-decoration: none;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.4px;
  /* 8px statt 4px, aus demselben Grund wie bei `.legal-link`: 12px Schrift
     mit 4px Polsterung ergibt rund 22px Trefferhoehe und liegt damit unter
     dem Mindestmass von 24px. */
  padding: 8px;
  border-radius: 7px;
}
.footer-links a:hover { text-decoration: underline; }
/* Rang-Detailzeilen */
.rk-list { display: flex; flex-direction: column; gap: 14px; }
.rk-row {
  display: grid; grid-template-columns: 88px 1fr; gap: 16px; align-items: center;
  background: var(--panel); border: 1px solid var(--panel-brd); border-radius: 14px; padding: 14px;
}
/* Galerie nutzt bereits abgedunkelte Bilddateien (assets/rank-N-dark.jpg), damit
   auch ein Download dunkel ist. Die erspielten Ränge in der App-Sammlung nutzen
   weiter die hellen Originale und bleiben unangetastet. */
.rk-row img { width: 88px; height: 88px; border-radius: 12px; object-fit: cover; display: block; }
.rk-row h3 { margin: 0 0 3px; font-family: var(--font-display); font-size: 18px; color: var(--text); }
.rk-min { color: var(--accent); font-size: 13px; font-weight: 600; }
.rk-row p { margin: 6px 0 0; font-size: 14px; }
@media (max-width: 520px) {
  .rk-row { grid-template-columns: 64px 1fr; gap: 12px; }
  .rk-row img { width: 64px; height: 64px; }
}
.auth-card {
  width: 100%;
  max-width: 400px;
  border-radius: 24px;
  padding: 40px 34px;
  text-align: center;
}
.brand { margin-bottom: 24px; }
.brand-logo {
  width: 100%;
  /* Siehe .diff-img: Ohne height:auto haelt das height-Attribut (832) die
     Hoehe fest, waehrend die Breite auf 340 gedeckelt wird. */
  height: auto;
  max-width: 340px;
  display: block;
  margin: 0 auto;
  border-radius: 18px;
  border: 1px solid var(--panel-brd);
  box-shadow: 0 16px 46px -12px rgba(0, 0, 0, 0.75);
}
.brand-title {
  font-family: var(--font-display);
  font-size: 40px;
  letter-spacing: 8px;
  margin: 0;
  font-weight: 700;
}
.brand-sub {
  color: var(--accent);
  letter-spacing: 4px;
  font-size: 12px;
  text-transform: uppercase;
  margin: 4px 0 0;
}
.auth-tabs {
  display: flex;
  background: var(--surface-1);
  border-radius: 12px;
  padding: 4px;
  margin-bottom: 22px;
}
.auth-tab {
  flex: 1;
  padding: 10px;
  border: none;
  background: transparent;
  color: var(--muted);
  font-family: inherit;
  font-weight: 600;
  border-radius: 9px;
  cursor: pointer;
  transition: background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s, transform 0.2s;
}
/* Gleiche Flaeche, gleicher Grund: siehe `.lp-cta` weiter oben. */
.auth-tab.active {
  background: linear-gradient(135deg, var(--accent2), color-mix(in srgb, var(--accent2) 72%, #000));
  color: var(--on-accent-deep);
}
.user-quick {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 18px;
}
.user-quick:empty { display: none; }
.user-quick-btn {
  flex: 1 1 auto;
  min-width: 90px;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid var(--panel-brd);
  background: var(--surface-1);
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: border 0.2s, background 0.2s, transform 0.1s;
}
.user-quick-btn:hover {
  border-color: var(--accent);
  background: var(--surface-2);
}
.user-quick-btn:active {
  transform: scale(0.97);
}
.auth-form { text-align: left; }
.field { display: block; margin-bottom: 16px; }
.field span {
  display: block;
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 7px;
  letter-spacing: 0.5px;
}
.field input {
  width: 100%;
  padding: 13px 15px;
  border-radius: 12px;
  border: 1px solid var(--panel-brd);
  background: var(--scrim);
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
  transition: border 0.2s, box-shadow 0.2s;
}
.field input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}
.auth-error {
  color: var(--danger);
  font-size: 13px;
  min-height: 18px;
  margin-bottom: 8px;
}
.auth-hint {
  color: var(--muted);
  font-size: 12px;
  margin: 18px 0 0;
}
.auth-lead { color: var(--text); font-size: 14px; line-height: 1.5; margin: 0 0 14px; }
/* ---- Erst-Einrichtung: Nickname + Profilbild ---- */
#screen-setup { display: none; place-items: center; min-height: 100vh; padding: 24px; }
#screen-setup.active { display: grid; }
.setup-card { max-width: 460px; text-align: left; }
/* Kleineres Logo als auf dem Auth-Screen — hier ist Platz für Inhalt. */
.setup-card .brand { margin-bottom: 18px; }
.setup-card .brand-logo { max-width: 150px; }
.setup-title { font-family: var(--font-display); font-size: 26px; margin: 6px 0 2px; }
.setup-sub { color: var(--muted); font-size: 14px; margin: 0 0 18px; }
.setup-pic {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--panel-brd);
}
.setup-avatar {
  width: 72px;
  height: 72px;
  flex: none;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid color-mix(in srgb, var(--gold) 55%, transparent);
}
.setup-pic-text { min-width: 0; }
.setup-pic-title { font-weight: 600; font-size: 14px; margin-bottom: 4px; }
.setup-pic-hint { color: var(--muted); font-size: 12px; line-height: 1.5; margin: 0; }
@media (max-width: 420px) {
  .setup-pic { flex-direction: column; align-items: center; text-align: center; }
  .setup-pic-actions { justify-content: center; }
}

/* Zwei-Schritt-Hinweis: erst Hub-Login, dann Einladungscode. */
.auth-steps {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  text-align: left;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
}
.auth-steps b {
  color: var(--gold);
  margin-right: 6px;
}

/* ============================================================
   TOPBAR
   ============================================================ */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 22px;
  border-bottom: 1px solid var(--panel-brd);
  background: var(--scrim-panel);
  backdrop-filter: blur(14px);
  position: sticky;
  top: 0;
  z-index: 10;
}
/* `min-width: 0` und die Kuerzung: Der Kasten sitzt in einem Flex-Kopf und
   wuerde sonst den Zurueck-Knopf zusammendruecken, statt selbst nachzugeben.
   Die Ellipse ist hier die Notbremse fuer einen sehr langen Namen, nicht der
   Normalfall -- seit „Level ·" weg ist, passen alle Raetselarten. */
.topbar-brand {
  font-family: var(--font-display);
  font-size: 22px;
  letter-spacing: 3px;
  font-weight: 700;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 18px statt 14px: Solange „Level ·" davorstand, war der Name die
   Nebensache. Jetzt IST er die Ueberschrift und darf nicht kleiner sein als
   das, was er ersetzt hat. Das Sperren bleibt kleiner als am Elter, sonst
   wird ein langer Name unnoetig breit. */
.topbar-brand span { color: var(--accent); font-size: 18px; letter-spacing: 2px; }
/* Marken-Lockup: runder Fuchs + Schriftzug */
.brand-lockup { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
.brand-fox {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid color-mix(in srgb, var(--accent) 45%, transparent);
  box-shadow: 0 4px 16px -4px rgba(0, 0, 0, 0.6);
}
.brand-word-wrap { display: flex; flex-direction: column; line-height: 1.05; }
.brand-word {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 700;
  letter-spacing: 5px;
  color: var(--text);
}
.brand-tag {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: 7px;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 700;
  margin-top: -1px;
}
@media (max-width: 560px) {
  .brand-word { font-size: 22px; letter-spacing: 3px; }
  .brand-tag { font-size: 12px; letter-spacing: 5px; }
  .brand-fox { width: 42px; height: 42px; }
  /* Header-User-Button schlank: Rang-Name entfällt (Platz reicht nicht),
     es bleiben Avatar + „Lv X“ (+ Name, solange er passt) + Zahnrad. */
  .ubl-rank { display: none; }
  /* Handy-Durchgang 10.8.2026: Bei 393px passen Schriftzug + Abmelden + Avatar
     + Name + Rang + Zahnrad nicht nebeneinander. Weil .brand-lockup nicht
     schrumpft, landete die ganze Enge auf dem Nutzerknopf — 118px, davon 36px
     fuer den Text. Der eigene Abmelden-Knopf ist der verzichtbarste Posten:
     dieselbe Aktion steht hinter dem Zahnrad in den Einstellungen, mit
     derselben Rueckfrage. Das schafft 56px und der Name passt wieder. */
  #header-logout { display: none; }
}
.topbar-right { display: flex; align-items: center; gap: 12px; min-width: 0; }
.user-chip {
  font-size: 13px;
  color: var(--muted);
  background: var(--surface-1);
  padding: 7px 12px;
  border-radius: 20px;
}
.user-chip b { color: var(--text); }

/* Zusammengeführter Header-Button: Avatar + Name + Level -> Einstellungen */
.user-btn {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 5px 12px 5px 5px;
  border-radius: 26px;
  border: 1px solid var(--panel-brd);
  background: var(--surface-1);
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  min-width: 0; /* darf schrumpfen -> Name kürzt via Ellipsis, Header läuft nicht über */
  max-width: 62vw;
  transition: background 0.2s, border-color 0.2s;
}
.user-btn:hover { background: var(--surface-3); border-color: var(--accent); }
.user-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* 4.6.3: Icon-only Header-Buttons (z. B. Abmelden) */
.icon-btn {
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--panel-brd);
  background: var(--surface-1);
  color: var(--muted);
  cursor: pointer;
  transition: color 0.2s, border-color 0.2s, background 0.2s;
}
.icon-btn:hover { color: var(--text); border-color: var(--accent); background: var(--surface-3); }
.icon-btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.icon-btn svg { display: block; }
.user-btn-avatar {
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--panel-brd);
}
.user-btn-info {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
  line-height: 1.15;
  /* Handy-Durchgang 10.8.2026: Ohne diese Zeile durfte das Kaestchen zwar
     schrumpfen (min-width:0), begrenzte seine Kinder aber nicht. Bei 393px war
     es 36px breit, der Name darin 63px — er lief hinaus und lag 11px auf dem
     Zahnrad. Am Schreibtisch faellt das nie auf, dort ist Platz genug. */
  overflow: hidden;
}
.user-btn-name {
  font-weight: 700;
  font-size: 14px;
  /* Am Kaestchen deckeln, nicht an der Fensterbreite: 40vw sind bei 393px
     157px — weit mehr, als der Knopf je hergibt. Der Deckel griff darum nie
     und die Auslassungspunkte kamen nicht zum Zuge. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.user-btn-lvl {
  font-size: 11px;
  color: var(--accent);
  font-weight: 600;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* BEFUND-2 3.7: Zahnrad war zu klein/übersehbar -> größer. */
.user-btn-gear { flex: none; color: var(--muted); font-size: 18px; }

/* Einstellungs-Bereiche */
.set-section { margin-bottom: 22px; }
.set-section:last-child { margin-bottom: 0; }
.set-title {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--accent);
  font-weight: 700;
  margin-bottom: 10px;
}
.set-label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
.set-name-edit { display: flex; gap: 8px; align-items: stretch; }
.set-input {
  flex: 1;
  min-width: 0;
  padding: 10px 13px;
  border-radius: 11px;
  border: 1px solid var(--panel-brd);
  background: var(--scrim);
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
}
.set-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}
.set-name-edit .btn { flex: none; }
.set-pic-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 0; }
/* `.av-strip` STAND HIER und ist am 07.09.2026 entfallen. Die Regel machte aus
   den freigespielten Rang-Bildern eine waagerecht scrollende Reihe -- gedacht
   gegen eine unbrauchbar lange Liste bei 19 Rängen. In der Messung war sie
   genau das Problem: Von elf Bildern lagen sechs ausserhalb, hinter einer
   Geste, die auf einer senkrecht scrollenden Seite niemand sucht. Beide
   Reihen liegen jetzt im selben `.av-gitter`, das in Spalten umbricht statt
   zu scrollen. Die Klasse steht nirgends mehr im Markup; sie hier stehen zu
   lassen, hiesse nur, dass jemand sie spaeter wieder benutzt. */

/* Die Zwischenueberschrift ueber der zweiten Reihe.

   Sie ist der ganze Unterschied zwischen den beiden Saetzen: Raenge sind
   verdient, die freien Bilder nicht. Ohne sie saehe es aus, als haette man
   elf Raenge auf einmal freigespielt. Deshalb klein und ruhig -- sie ordnet
   ein, sie wirbt nicht. */
.av-head {
  margin-top: 16px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--color-text-secondary, var(--muted));
}

/* DIE FREIEN BILDER LIEGEN IM GITTER, nicht in einer Leiste.

   Korbis Befund am 07.09.2026: „man erkennt die Bilder nicht gut ... und den
   Scrollbalken von oben nach unten." Beides stimmt und haengt zusammen. In
   der waagerechten Leiste waren die Kacheln 56 px gross -- fuer detailreiche
   Portraets zu wenig, um einen Fennek von einem Polarfuchs zu unterscheiden
   -- und sechs der elf Bilder lagen ausserhalb des Bildes, hinter einer Geste,
   die man auf einer senkrecht scrollenden Seite nicht erwartet.

   Im Gitter passen alle untereinander, und die Kachel wird so gross, wie die
   Breite hergibt.

   SEIT 1.17.152 GILT DAS AUCH FUER DIE RANG-BILDER, auf Korbis ausdrueckliche
   Ansage („Ich will das Avatar-Auswahlfeld sehr komfortabel, auch wenn man es
   ueber das Menue aufruft. Hier kommen natuerlich auch die freigespielten
   Rangbilder zum Vorschein."). Das dreht die aeltere Entscheidung „keine
   lange Rangleiter mehr, kompakte Auswahl" um -- deshalb steht hier, WER sie
   umgedreht hat. Bei 19 freigespielten Raengen ist eine waagerechte Leiste
   ohnehin kein Angebot mehr, sondern ein Versteck.

   KEIN EIGENER SCROLLBEREICH. Das Gitter waechst, und gescrollt wird die
   Flaeche, in der es liegt -- der Einstellungsdialog oder das Sheet. Zwei
   Scroller ineinander sind das Gegenteil von komfortabel: Man trifft mit dem
   Finger den falschen und wundert sich, warum nichts weitergeht. */
.av-gitter {
  display: grid;
  /* 78 statt 84: Bei 360 px breiten Geraeten bleiben in den Einstellungen
     272 px Inhalt, und drei Spalten braeuchten mit 84 px genau 280 -- es
     waeren zwei geworden. 78 nimmt die Schwelle auf 262 px und damit auch
     diese Geraete mit. Immer noch deutlich mehr als die 56 px der alten
     Leiste. Aus der Sicherheitspruefung zu 1.17.152. */
  grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
  gap: 14px;
  margin-top: 12px;
  padding: 2px;            /* sonst schneidet der Rahmen der Kachel am Rand ab */
}
.av-gitter .av-pick {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
}
/* BEFUND-2 4.7: gemeinsame Klasse für waagerecht scrollbare Leisten — versteckter
   Scrollbalken plus weiche Verlaufskante rechts (statt hart abgeschnitten). */
.scroll-fade {
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-mask-image: var(--scroll-fade-mask);
  mask-image: var(--scroll-fade-mask);
}
.scroll-fade::-webkit-scrollbar { display: none; }
.av-pick {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid var(--panel-brd);
  background: none;
  cursor: pointer;
  overflow: hidden;
  transition: border-color 0.2s, transform 0.15s;
}
.av-pick img { width: 100%; height: 100%; object-fit: cover; display: block; }
.av-pick:hover { transform: translateY(-2px); }
.av-pick.chosen {
  border-color: var(--gold);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 30%, transparent);
}
.set-msg { font-size: 12px; min-height: 15px; margin-top: 7px; color: var(--accent); }
.set-msg.err { color: var(--danger); }
.set-msg.ok { color: var(--gold); }
.set-section .btn-block { margin-top: 9px; }
.btn-danger {
  background: color-mix(in srgb, var(--danger) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
  color: var(--danger);
}
.btn-danger:hover { background: color-mix(in srgb, var(--danger) 26%, transparent); }
.set-danger-confirm {
  margin-top: 10px;
  padding: 13px 14px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent);
  font-size: 13px;
  color: var(--text);
}
/* Tages-Challenge-Ergebnis-Recap (BEFUND 1.3) */
.dr-time {
  font-family: var(--font-numeric);
  font-size: 40px;
  font-weight: 700;
  color: var(--success, var(--accent));
  margin: 6px 0 2px;
}
/* BEFUND-2 4.1: Häkchen/Flamme als eigene Marker mit sicherem Abstand — das
   schmale Leerzeichen der Ziffernschrift ließ "✓"/"🔥" an der Zahl kleben. Das
   Häkchen zusätzlich kleiner, damit es als Marker und nicht als erste Ziffer liest. */
.dr-check { font-size: 0.72em; margin-right: 12px; vertical-align: 3px; }
.dr-fire { margin-right: 5px; }
/* BEFUND-2 4.2: Nebenausgänge des Tages-Ergebnisses als schlichte Textlinks in
   einer Zeile — ein großer Knopf (Teilen), ein sekundärer (Nochmal), Rest leicht. */
.dr-links { display: flex; justify-content: center; gap: 22px; margin-top: 14px; }
.dr-textlink { background: none; border: 0; padding: 4px 2px; color: var(--muted); font: inherit; font-size: 14px; cursor: pointer; text-decoration: none; }
.dr-textlink:hover { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
#daily-recap-overlay .pause-card > .btn { margin-top: 9px; }

.set-danger-actions { display: flex; gap: 8px; margin-top: 11px; }
.set-about { font-size: 12px; color: var(--muted); line-height: 1.7; }

/* Gefahrenzone (BEFUND 3.8): destruktive Aktion optisch abgesetzt, ganz am Ende.
   Der Ausloeser ist bewusst KEIN roter Blockknopf, sondern ein dezenter Link;
   Rot bleibt fuer die Bestaetigung reserviert. */
.set-danger-zone {
  margin-top: 26px;
  padding: 14px 15px;
  border-radius: 12px;
  border-top: 2px solid color-mix(in srgb, var(--danger) 55%, transparent);
  background: color-mix(in srgb, var(--danger) 6%, transparent);
}
.set-danger-title { color: color-mix(in srgb, var(--danger) 80%, var(--text)); }
.set-danger-link {
  margin-top: 4px;
  padding: 4px 0;
  background: none;
  border: none;
  font: inherit;
  font-size: 13px;
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.set-danger-link:hover { color: color-mix(in srgb, var(--danger) 80%, var(--text)); }
/* Konto endgueltig loeschen: derselbe Bereich, aber sichtbar abgesetzt vom
   Zuruecksetzen des Fortschritts. Wer das eine will, soll das andere nicht
   aus Versehen treffen -- die Trennlinie ist hier kein Zierrat. */
.set-danger-rule {
  margin: 18px 0 14px;
  border: none;
  border-top: 1px solid color-mix(in srgb, var(--danger) 28%, transparent);
}
.set-purge-label {
  display: block;
  margin-bottom: 6px;
  font-size: 13px;
  color: var(--text);
}
.set-purge-input {
  width: 100%;
  box-sizing: border-box;
  min-height: 44px;
  padding: 9px 11px;
  border-radius: 10px;
  border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent);
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  color: var(--text);
  font: inherit;
  font-size: 14px;
}
.set-purge-input:focus-visible {
  outline: 2px solid var(--danger);
  outline-offset: 1px;
}
.set-purge-fehler {
  margin-top: 8px;
  font-size: 13px;
  color: var(--danger);
}

/* Theme-Umschalter (Auto / Dunkel / Hell) */
.theme-toggle {
  display: flex;
  gap: var(--space-1);
  margin-top: 10px;
  padding: var(--space-1);
  background: var(--color-panel);
  border: 1px solid var(--color-panel-border);
  border-radius: var(--radius-pill);
}
.theme-opt {
  flex: 1;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-2) var(--space-3);
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-secondary);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out-quart), color var(--dur-fast) var(--ease-in-out);
}
.theme-opt:hover { color: var(--color-text-primary); }
.theme-opt.active {
  background: var(--color-primary);
  color: var(--color-bg-base);
}
.theme-opt:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

/* Ein/Aus-Schalter (Settings) */
.set-toggle { display: flex; align-items: center; gap: 10px; margin-top: 10px; cursor: pointer; user-select: none; }
.set-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.set-switch {
  position: relative;
  width: 42px;
  height: 24px;
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  border: 1px solid var(--color-panel-border);
  transition: background 0.2s;
}
.set-switch::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--muted);
  transition: transform 0.2s, background 0.2s;
}
.set-toggle input:checked + .set-switch { background: color-mix(in srgb, var(--color-primary) 45%, transparent); }
.set-toggle input:checked + .set-switch::after { transform: translateX(18px); background: var(--color-primary); }
.set-toggle input:focus-visible + .set-switch { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.set-toggle-lbl { font-size: 14px; color: var(--text); }
/* Schalter tragen jetzt einen erklaerenden Satz. Die Beschriftungen nannten
   bisher den Mechanismus („Ziffer zuerst waehlen"), nicht die Wirkung — man
   musste sie ausprobieren, um sie zu verstehen. */
.set-toggle-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.set-toggle-desc { font-size: 13px; line-height: 1.45; color: var(--muted); }

/* Gruppen nach Gueltigkeitsbereich. Ersetzt das zweimal wiederholte
   Kleingedruckte „Gilt nur auf diesem Geraet" durch eine Ueberschrift, die
   einmal gilt — und macht sichtbar, dass Name und Bild kontoweit wirken. */
.set-group { margin: 26px 0 12px; padding-top: 18px; border-top: 1px solid var(--panel-brd); }
.set-group:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.set-group-title {
  font-family: var(--font-display); font-size: 17px; font-weight: 600; color: var(--text);
}
.set-group-hint { font-size: 13px; color: var(--muted); margin-top: 2px; }

/* „Gespeichert" am Abschnitt, nicht als Meldung ueber dem Bildschirm: Die
   Rueckmeldung gehoert dorthin, wo die Aenderung passiert ist. */
.set-saved {
  margin-left: 10px; font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  font-weight: 700; color: var(--color-success, #7FBF8C);
  opacity: 0; transition: opacity 0.18s var(--ease-out-quart);
}
.set-saved.show { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .set-saved { transition: none; } }

.set-reset-row { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }

/* Anzeigename: normalerweise nur der Name plus „Namen aendern". Das Eingabefeld
   erscheint erst auf Knopfdruck — vorher stand es samt Speichern-Knopf dauerhaft
   offen, und dieser Knopf wurde fuer den Speichern-Knopf des ganzen Dialogs
   gehalten. */
.set-name-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.set-name-current { font-size: 15px; font-weight: 600; color: var(--text); }
.set-msg.warn { color: var(--color-warning, #D9A94E); }
.set-range { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 10px; }
.set-range input[type="range"] { flex: 1; max-width: 170px; accent-color: var(--color-primary); }

/* Einladungen */
.inv-head { font-size: 12px; color: var(--muted); margin-bottom: 9px; }
.inv-head b { color: var(--accent); }
.inv-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  border-radius: 9px;
  margin-bottom: 6px;
  background: var(--surface-1);
  border: 1px solid var(--panel-brd);
}
.inv-row.used { opacity: 0.55; }
.inv-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 1.5px;
  color: var(--text);
}
.inv-status { font-size: 12px; color: var(--accent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.inv-copy {
  flex: none;
  border: 1px solid var(--panel-brd);
  background: var(--surface-1);
  color: var(--accent);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  padding: 6px 11px;
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
}
.inv-copy:hover { border-color: var(--accent); }
.inv-copy.done { color: var(--color-success); border-color: var(--color-success); }
.user-lvl {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-bg-base);
  background: linear-gradient(135deg, var(--accent), #b5823a);
  padding: 5px 11px;
  border-radius: 20px;
  white-space: nowrap;
}
.user-lvl .ul-avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--scrim);
}
.user-lvl .ul-num { font-weight: 800; }
.user-lvl .ul-name::before { content: "· "; opacity: 0.7; }
@media (max-width: 560px) {
  .user-lvl .ul-name { display: none; }
}

/* ============================================================
   HOME
   ============================================================ */
/* ============================================================
   RANKING
   ============================================================ */
/* ============================================================
   TÄGLICHE CHALLENGE
   ============================================================ */
/* Kompakte Challenge-Karte: Bild · Kurzstatus · Button in einer Reihe */
.daily-card {
  display: flex;
  align-items: center;
  gap: 14px;
  border-radius: var(--radius);
  padding: 12px 14px;
  margin-bottom: 16px;
  border-left: 3px solid var(--accent);
}
.daily-thumb {
  flex: none;
  width: 62px;
  height: 62px;
  border-radius: 13px;
  object-fit: cover;
  border: 1px solid var(--panel-brd);
}
.daily-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
/* Titel der Tages-Challenge als HTML-Eyebrow (früher im Kachelbild eingebacken).
   Farbe fest über --color-primary (Gold), nicht --accent: die Daily-Karte setzt
   --accent lokal auf die Tages-Schwierigkeit (Extrem = rot), sonst wäre der Titel rot. */
.daily-eyebrow { font-family: var(--font-display); font-size: 12px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--color-primary); }
.daily-diff-line {
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.daily-status-line { font-size: 14px; line-height: 1.3; }
.daily-done { color: var(--accent); font-weight: 600; }
.daily-todo { color: var(--muted); }
.daily-risk { color: var(--color-warning); font-weight: 700; }
.daily-date { font-size: 11px; color: var(--muted); text-transform: capitalize; }
.daily-play { flex: none; }

/* Serie dezent als Text neben der Schwierigkeit */
.daily-streak {
  font-size: 12px;
  font-weight: 700;
  color: var(--color-warning);
  margin-left: 7px;
  white-space: nowrap;
}

/* Prominenter Zustand, solange heute noch nicht gespielt (fällt mehr auf) */
.daily-card.daily-todo-state {
  padding: 16px;
  gap: 16px;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 28%, transparent),
    0 14px 34px -18px var(--accent);
}
.daily-card.daily-todo-state .daily-thumb {
  width: 104px;
  height: 104px;
  border-radius: 16px;
}
.daily-card.daily-todo-state .daily-diff-line { font-size: 17px; }

.profile-streak { font-size: 12px; color: var(--color-warning); font-weight: 600; margin-top: 3px; }

/* ============================================================
   FORTSCHRITT (Level-Streifen + Rangliste in einer Karte)
   ============================================================ */
.progress-card {
  border-radius: var(--radius);
  padding: 16px 18px;
}
/* NUR DIE ZWEI WEGE, weil das Konto noch keine Punkte hat. `.stats-link`
   bringt `margin-top: 14px` mit, gedacht als Abstand zur Rangliste darueber;
   ist die aus, steht der Abstand ins Leere und die Karte ist oben 30px, unten
   16px. Die Klasse setzt js/app.js (renderHome), damit der nicht-leere Fall
   unberuehrt bleibt -- `:first-of-type` allein haette ihn mitgenommen.
   Angemerkt in der zweiten Sicherheitspruefung zu 1.17.161. */
.progress-card.nur-wege .stats-link:first-of-type { margin-top: 0; }
/* Level-Streifen oben, Trennlinie, darunter die Rangliste */
.level-strip {
  display: flex;
  align-items: center;
  gap: 16px;
  padding-bottom: 14px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--panel-brd);
}
.level-card {
  display: flex;
  align-items: center;
  gap: 16px;
  border-radius: var(--radius);
  padding: 16px 18px;
  margin-bottom: 16px;
}
.lc-badge {
  flex: none;
  width: 60px;
  height: 60px;
  border-radius: 15px;
  object-fit: cover;
  border: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: 0 8px 22px -8px color-mix(in srgb, var(--accent) 70%, transparent);
}
.lc-info { flex: 1; min-width: 0; }
/* Die Stufe steht als kleine Zeile UEBER dem Rangnamen, nicht daneben.

   Bis 1.17.96 teilten sich beide eine Zeile (`justify-content: space-between`),
   und der Name behielt davon 171 px. Im Handy-Viewport gemessen braucht
   „Legende von Sagax" 216 px -- ab Rang 10 wurde damit JEDER Titel gekuerzt,
   in beiden Sprachen. Kurios war die Folge: eine Zeile tiefer stand der
   naechste Rang vollstaendig („noch 230.000 bis Sternenfuchs"), die Vorschau
   las sich also, die erreichte Belohnung nicht.

   Mit der ganzen Breite passt jeder heutige Rang. `overflow-wrap: anywhere`
   statt Ellipse: Kommt spaeter ein laengerer Name dazu, bricht er um, statt
   zu verschwinden -- ein zweizeiliger Titel ist immer noch lesbar, ein
   abgeschnittener nicht. */
.lc-top {
  display: block;
  margin-bottom: 8px;
}
.lc-name {
  display: block;
  font-family: var(--font-display);
  font-size: 23px;
  font-weight: 700;
  color: var(--text);
  line-height: 1.15;
  overflow-wrap: anywhere;
}
.lc-lvl {
  display: block;
  margin-bottom: 2px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--accent);
  font-weight: 700;
}
.lc-bar {
  height: 8px;
  background: var(--surface-2);
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 7px;
}
.lc-bar span {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 55%, var(--gold)));
  transition: width 0.6s;
}
.lc-sub { font-size: 12px; color: var(--muted); }
.lc-sub b { color: var(--text); font-weight: 600; }

.profile-level {
  font-size: 12px;
  color: var(--accent);
  font-weight: 600;
  letter-spacing: 0.3px;
  margin-top: 4px;
}

.ranking {
  border: 1px solid var(--panel-brd);
  background: var(--panel);
  backdrop-filter: blur(16px);
  border-radius: var(--radius);
  padding: 16px 18px;
  margin-bottom: 22px;
}
/* „20 von 137" unter der Liste, und die Luecke davor.

   Beides gibt es erst, seit die Rangliste abgeschnitten wird (1.17.162).
   Der Hinweis ist Beiwerk und traegt deshalb die gedaempfte Farbe; die
   Luecke ist reine Zierde und steht im Markup mit `aria-hidden`. */
/* Die Blaetterleiste unter dem Ranglisten-Bildschirm (1.17.162). Sie gibt es
   nur, wenn es mehr als eine Seite gibt -- bei zweiundzwanzig Nutzern soll
   niemand ein Bedienelement sehen, das nichts tut. */
.rk-blaetter {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
  padding: 14px 0 4px;
}
.rk-blaetter-btn {
  min-width: 44px;
  min-height: 44px;
  padding: 0 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--panel-brd);
  background: var(--surface-1);
  color: var(--text);
  font-size: 16px;
  cursor: pointer;
}
.rk-blaetter-btn[disabled] { opacity: .35; cursor: default; }
.rk-blaetter-mir { font-size: 13px; color: var(--accent); }
.rk-blaetter-txt { font-size: 13px; color: var(--muted); min-width: 8.5em; text-align: center; }
.ranking-mehr {
  text-align: center;
  font-size: 12px;
  color: var(--muted);
  padding: 8px 0 2px;
}
.rank-luecke {
  text-align: center;
  color: var(--muted);
  letter-spacing: 3px;
  line-height: 1;
  padding: 4px 0;
  user-select: none;
}
.ranking-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}
.ranking-title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--accent);
}
.ranking-empty {
  color: var(--muted);
  font-size: 13px;
  padding: 4px 2px 2px;
}
/* Zeitraum-Umschalter (Gesamt / Woche / Monat) ueber der Home-Rangliste. */
.hr-scope {
  display: flex;
  gap: 4px;
  margin-bottom: 10px;
  background: color-mix(in srgb, var(--panel-brd) 40%, transparent);
  border-radius: 10px;
  padding: 3px;
}
.hr-scope-btn {
  flex: 1;
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  padding: 6px 8px;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
  /* Handy-Durchgang 10.8.2026: gemessen 100x27px. Drei Ziele direkt
     nebeneinander bei 27px Hoehe — dort traegt die Fehlgriffquote am meisten. */
  min-height: 44px;
}
.hr-scope-btn:hover { color: var(--text); }
.hr-scope-btn.active {
  background: var(--panel);
  color: var(--accent);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}
.ranking-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.rank-row {
  display: grid;
  grid-template-columns: 26px 36px 1fr auto auto;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-radius: 12px;
  background: var(--surface-1);
  font-size: 14px;
}
.rank-img {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--panel-brd);
}
.rank-row.me {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
}
.rank-pos {
  font-size: 15px;
  font-weight: 700;
  color: var(--muted);
  text-align: center;
}
.rank-name {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  line-height: 1.2;
}
.rank-nm {
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rank-lvl {
  font-size: 11px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rank-name em {
  font-style: normal;
  color: var(--accent);
  font-weight: 500;
  font-size: 12px;
}
.rank-stars {
  color: var(--gold);
  font-size: 13px;
  white-space: nowrap;
}
.rank-score {
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  min-width: 56px;
  text-align: right;
}

/* 4.7.1 Kompakt-Rangliste für die 280px-Spalte: zweizeilig, ohne Avatar. */
.rank-row-c {
  grid-template-columns: 22px 30px minmax(0, 1fr);
  align-items: center;
  gap: 9px;
  padding: 9px 11px;
}
.rank-row-c .rank-pos { font-size: 14px; line-height: 1.4; align-self: center; }
.rank-img-c { width: 30px; height: 30px; }
.rank-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rc-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  min-width: 0;
}
.rc-line .rank-nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.rc-line .rank-lvl { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: var(--muted); }
.rc-line .rank-stars { flex: 0 0 auto; color: var(--gold); font-size: 12px; }
.rc-line .rank-score { flex: 0 0 auto; min-width: 0; font-size: 13px; }

.section-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 30px;
  text-align: center;
  margin: 10px 0 26px;
}
/* Kompaktes Abschnitts-Label mit auslaufender Linie */
.sec-label {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--muted);
  font-weight: 700;
  margin: 26px 0 14px;
}
.sec-label::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--panel-brd);
}
/* 4.7.3 Spielen-Header: mobil/tablet wie ein sec-label (klein + Trennlinie,
   Subtitle versteckt) -> keine Regression; ab 1024px dominanter Fraunces-Titel
   mit dezenter Subtitle rechts. Der sec-label-Style anderer Sektionen bleibt. */
.play-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 26px 0 14px;
}
.play-title {
  margin: 0;
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--muted);
}
.play-header::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--panel-brd);
}
.play-sub { display: none; }
@media (min-width: 1024px) {
  .play-header { align-items: baseline; gap: 14px; margin: 6px 0 18px; }
  .play-title {
    font-family: var(--font-display);
    font-size: 34px;
    font-weight: 600;
    letter-spacing: normal;
    text-transform: none;
    color: var(--text);
    line-height: 1.05;
  }
  .play-header::after { display: none; }
  .play-sub { display: inline; margin-left: auto; font-size: 14px; color: var(--muted); }
  /* Kein fester height-Override mehr: der zog die Illustration je nach Spaltenzahl
     unterschiedlich (Startseite 4-spaltig = hochkant beschnitten, „Alle Spieltypen"
     2-spaltig = breiter). Das Bild bleibt überall quadratisch (aspect-ratio:1/1 aus
     der Basis) → identischer Zuschnitt in beiden Rastern (4.4). */
}

.difficulty-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
}
@media (min-width: 620px) {
  .difficulty-grid { grid-template-columns: repeat(4, 1fr); }
}
/* Bild-Kachel: Bild dominiert (mit eingebackenem Label), nur Fortschritt darunter */
.diff-card {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--panel-brd);
  background: var(--panel);
  backdrop-filter: blur(16px);
  border-radius: 16px;
  padding: 0;
  cursor: pointer;
  color: var(--text);
  font-family: inherit;
  transition: transform 0.15s, border-color 0.2s, box-shadow 0.2s;
  /* Query-Container, damit der Titel (cqi) mit der KACHELBREITE skaliert — egal ob
     4-spaltig (Startseite) oder 2-spaltig ("Alle Spieltypen"). position:relative
     bleibt der Bezug fuer den absolut gesetzten Titel. */
  container-type: inline-size;
}
.diff-card:hover {
  transform: translateY(-4px);
  border-color: var(--accent);
  box-shadow: 0 18px 44px -20px var(--accent);
}
.diff-img {
  display: block;
  width: 100%;
  /* height:auto ist hier Pflicht, nicht Kosmetik: Das <img> traegt seit 1.17.38
     width/height-Attribute (Platz reservieren, damit die Seite beim Laden nicht
     springt). Die wirken wie `height: 512px`, und `aspect-ratio` schuetzt NICHT
     davor — es greift nur, solange eine Richtung `auto` ist. Ohne diese Zeile
     war jede Kachel der angemeldeten Startseite 514 statt 190 Pixel hoch und
     das quadratische Motiv auf 188 x 512 gequetscht. */
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  /* Der Titel ist unten ins Motiv eingebacken. Wird das Bild in einem nicht-
     quadratischen Kasten via cover beschnitten (Desktop-Kacheln, height:250px),
     MUSS der Crop unten verankert sein, sonst schneidet er den Titel ab
     (gemeldeter Bug: „Schwer/Extrem abgeschnitten"). Oben liegt nur Hintergrund. */
  object-position: center bottom;
  border-bottom: 1px solid var(--panel-brd);
}
.diff-info {
  padding: 9px 11px 11px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.diff-bar {
  height: 6px;
  background: var(--surface-2);
  border-radius: 3px;
  overflow: hidden;
}
.diff-bar span {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 60%, var(--gold)));
  border-radius: 3px;
  transition: width 0.5s;
}
/* FESTE HOEHE VON ZWEI ZEILEN, und das ist kein Schoenheitsgriff.

   `.diff-title` sitzt `position: absolute` mit `bottom` von der
   KARTENUNTERKANTE. Wuchs dieser Block, weil der Text umbrach, wanderte der
   Titel mit -- unter das Bild und auf den Fortschrittsbalken. Gemessen im
   Handy-Viewport: „0 · ★ 0 · erstes Ziel: 20" passt in eine Zeile
   (Info-Block 48 px, Titel 9 px ueber dem Balken), „144 · ★ 432 · noch 56
   bis 200" braucht zwei (63 px, Titel MINUS 6 px, also darauf). Der Fehler
   trat also genau dann auf, wenn jemand Fortschritt hatte.

   Mit fester Hoehe ist jeder Info-Block gleich hoch. Das raeumt zugleich
   auf, dass in einer Reihe zwei Titel auf verschiedener Hoehe standen. */
.diff-meta {
  color: var(--muted);
  font-size: 12px;
  /* Zeilenhoehe AUSGESCHRIEBEN, damit die Rechnung darunter aufgeht: zwei
     Zeilen zu 15 px sind genau die 30 px, die der Kasten hoch ist. Ohne
     Angabe haenge sie an der Schriftart und waere nicht nachrechenbar. */
  line-height: 15px;
  text-align: center;
  /* WIRKLICH FEST, nicht nur eine Untergrenze.
     Hier stand zuerst `min-height`. Die Sicherheitspruefung hat darauf
     hingewiesen, dass das den Kommentar nicht einloest: Eine dritte Zeile --
     schmaleres Geraet, laengerer Text in einer anderen Sprache -- laesst den
     Kasten wieder wachsen, und der Titel liegt erneut auf dem Balken. Der
     Fehler waere um eine Zeile verschoben gewesen, nicht behoben.
     Mit fester Hoehe kann das nicht passieren. Der Preis ist, dass eine
     dritte Zeile abgeschnitten wird -- die zweitrangige Zahl daneben ist
     der kleinere Verlust als ein Titel auf dem Balken. */
  height: 30px;
  overflow: hidden;
}
/* Titel liegt als HTML-Text (Fraunges, graviert) oben auf der textfreien Kachel,
   statt ins Bild gebacken zu sein: einheitliche Typografie, eine Datei je Kachel,
   neue Sprachen gratis, und die Binär-Kachel fällt nicht mehr aus der Palette. */
.diff-title {
  position: absolute;
  /* 71 px = die Hoehe des Info-Blocks darunter (9 + 6 + 7 + 30 + 11 = 63)
     plus 8 px Luft. Bis 1.17.101 standen hier 52 und im Handy-Block 48 --
     beide kleiner als der Block hoch ist, sobald die Zeile darunter umbrach.
     Der Titel rutschte dann unter das Bild und auf den Fortschrittsbalken,
     gemessen minus 6 px. Genau dann, wenn jemand Fortschritt hatte. */
  bottom: 71px;
  left: 8px;
  right: 8px;
  text-align: center;
  font-family: var(--font-display);
  /* Skaliert mit der Kachelbreite (cqi vom .diff-card-Container) und bleibt EINZEILIG:
     lange Titel wie „Chaos-Sudoku" oder „X-Sudoku" brechen nicht mehr mittendrin um,
     sondern schrumpfen. Grenzen 13–22px halten die Lesbarkeit. */
  font-size: clamp(13px, 8.5cqi, 22px);
  white-space: nowrap;
  font-weight: 700;
  letter-spacing: 0.5px;
  color: #f4e6c8;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.9), 0 1px 2px rgba(0, 0, 0, 0.8);
  pointer-events: none;
  z-index: 1;
}
/* Alle Kachel-Titel auf EINER Hoehe (Nutzerwunsch: einheitlich in „Weitere
   Spieltypen"). Kein Motiv-spezifischer Sonderfall mehr — jeder Titel sitzt an
   derselben Position knapp ueber der Info-Leiste. Groesse kommt jetzt aus cqi
   (oben), hier nur die Hoehe auf schmalen Screens. */
@media (max-width: 560px) {
  /* Der Handy-Block hatte hier eine EIGENE, kleinere Zahl (48 statt 52).
     Zwei geratene Werte fuer dieselbe Kante sind einer zu viel; der Abstand
     haengt am Info-Block, und der ist in beiden Breiten gleich hoch. */
}
/* Dezenter Einstieg zu weiteren Spieltypen (unter dem Schwierigkeits-Raster).
   Zurueckhaltend, damit er die Schwierigkeitswahl nicht stoert. */
.more-types-btn {
  display: block; width: 100%; margin-top: 12px; padding: 11px 14px;
  background: var(--surface-1); border: 1px solid var(--panel-brd); border-radius: 12px;
  color: var(--muted); font-family: inherit; font-size: 14px; font-weight: 600;
  cursor: pointer; transition: color 0.2s, border-color 0.2s, background 0.2s;
}
.more-types-btn:hover { color: var(--text); border-color: var(--accent); background: var(--surface-2); }
/* Unterseite "Weitere Spieltypen" */
.gt-sub { color: var(--muted); font-size: 14px; line-height: 1.5; margin: 0 0 18px; max-width: 640px; }
/* Alle Rätselarten auf einen Blick: Das Raster war hart auf zwei Spalten und
   520px gedeckelt — bei 1263px Fensterbreite blieben 472px ungenutzt, und die
   13 Kacheln ergaben sieben Bildschirmlängen Scrollen. Ausgerechnet auf der
   Seite, die zeigen soll, was das Spiel alles kann.
   `auto-fill` mit Mindestbreite: am Desktop werden es fünf bis sechs Spalten.

   NACHGERECHNET, denn die 180px stimmten am Telefon nicht. Bei 393 Pixeln
   Fenster bleiben im `.container` (24px 20px Polster) 353 Pixel innen; zwei
   Spalten mit 14px Abstand brauchen 374. Es blieb also EINE Spalte, und der
   Kommentar behauptete das Gegenteil. Bei 14 Kacheln à rund 400 Pixel ist die
   Seite dann 5600 Pixel lang, und Fuchsbau steht als achte Kachel rund 2800
   Pixel unten. 150px kippt den Umschlagpunkt auf 314 und damit unter jedes
   Telefon. */
.difficulty-grid.gt-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); max-width: 1120px; }
/* Die Übersicht braucht eine breitere Spalte als der Rest. `.container` ist mit
   760px auf Lesetexte ausgelegt, und daran scheiterte auch das entdeckelte
   Raster: Es blieb bei drei Spalten, weil der Elterncontainer schon zumachte.
   Nur diese eine Seite verbreitern statt `.container` global anzufassen. */
.container.gt-wrap { max-width: 1160px; }
/* Wunsch-Kachel: klickbar wie eine echte Kachel (BEFUND-2 4.9), nicht mehr
   abgedunkelt/„disabled". Hover erbt vom .diff-card. */
.gt-soon { cursor: pointer; }
.gt-board { max-width: 520px; margin-top: 24px; }
.gt-board[hidden] { display: none; }
.gt-board-title { font-family: var(--font-display); font-size: 16px; font-weight: 600; margin: 0 0 10px; color: var(--text); }

/* Resume banner */
.pw-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  border-radius: 14px;
  padding: 13px 18px;
  margin-bottom: 18px;
  border-left: 3px solid var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  font-size: 14px;
}
.pw-banner b { color: var(--text); }
.pw-banner .btn { flex: 0 0 auto; }
.resume-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  border-radius: 14px;
  padding: 14px 18px;
  margin-bottom: 22px;
  border-left: 3px solid var(--accent);
}
.resume-info { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-width: 0; }
/* Fortsetzen-Panels an das CI angleichen: Eyebrow wie die Tages-Challenge
   (Fraunces, gold), Level-Zeile wie die Daily-Schwierigkeitszeile (Inter 15/700).
   Gold fest über --color-primary (nicht --accent, das kann lokal überschrieben sein). */
.resume-label {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--color-primary);
}
#resume-text,
#resume-text-binary { font-size: 15px; font-weight: 700; color: var(--text); }
.resume-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ============================================================
   LEVEL SELECT
   ============================================================ */
/* Weiterspielen-Button über dem Level-Raster. */
/* Aktions-Buttons (Fortsetzen/Starten/Zufall/Tutorial) nebeneinander im Web,
   Umbruch auf schmalen Screens. Ausgeblendete Buttons (hidden) belegen keinen
   Platz, sodass die Reihe je nach Modus nur die relevanten Aktionen zeigt. */
.levels-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin: 4px auto 18px;
  max-width: 660px;
}
.levels-continue {
  flex: 1 1 200px;
  max-width: 320px;
  margin: 0;
}
@media (max-width: 520px) {
  .levels-continue { flex-basis: 100%; } /* auf sehr schmalen Screens gestapelt */
}
/* ---- Feedback (Fehler / Idee melden) ---- */
.feedback-link { background: none; border: 0; padding: 0; font: inherit; font-size: inherit;
  color: var(--gold); cursor: pointer; }
.feedback-link:hover { text-decoration: underline; }
.fb-type { display: flex; gap: 8px; margin-bottom: 12px; }
.fb-type-btn {
  flex: 1; padding: 9px 8px; border-radius: 12px; cursor: pointer;
  background: var(--ctrl); border: 1px solid var(--ctrl-brd); color: var(--muted);
  font-family: inherit; font-size: 14px; transition: background 0.2s, color 0.2s, border-color 0.2s;
}
.fb-type-btn.active {
  background: color-mix(in srgb, var(--gold) 20%, transparent);
  border-color: color-mix(in srgb, var(--gold) 55%, transparent);
  color: var(--gold); font-weight: 600;
}
/* 16px UND KEIN PIXEL WENIGER, und das ist keine Geschmacksfrage.

   iOS vergroessert die ganze Seite, sobald ein Textfeld unter 16px den Fokus
   bekommt. js/app.js setzt diesen Fokus beim Oeffnen des Dialogs selbst --
   auf einem iPhone hiess das also: Dialog auf, Seite gezoomt. Und heraus kam
   man seit 1.17.153 nur noch mit zwei Fingern, weil der Doppeltipp-Zoom
   bewusst weg ist. Vorher standen hier 14px. Block 370 haelt die Grenze. */
#feedback-message {
  width: 100%; box-sizing: border-box; resize: vertical; min-height: 90px;
  padding: 10px 12px; border-radius: 12px; font-family: inherit; font-size: 16px;
  background: var(--surface-1); border: 1px solid var(--panel-brd); color: var(--text);
}
/* Der Admin-Eingang im Melde-Dialog: zugeklappt, damit „Fehler melden" fuer
   Admin und Gast gleich hoch aufgeht. Aufbau wie die FAQ-Aufklapper oben
   (`.lp-faq`), damit es in der App nur eine Machart gibt. */
.fb-admin { margin-top: 18px; border-top: 1px solid var(--panel-brd); }
.fb-admin summary {
  cursor: pointer; list-style: none; position: relative;
  padding: 15px 28px 15px 0; min-height: 44px;
  font-size: 11px; text-transform: uppercase; letter-spacing: 2px;
  color: var(--accent); font-weight: 700;
}
.fb-admin summary::-webkit-details-marker { display: none; }
.fb-admin summary::after {
  content: "+"; position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  font-size: 20px; line-height: 1;
}
.fb-admin[open] summary::after { content: "–"; }
.feedback-list { display: flex; flex-direction: column; gap: 8px; max-height: 320px; overflow-y: auto; }
.fb-item { padding: 10px 12px; border-radius: 10px; background: var(--surface-1); border: 1px solid var(--panel-brd); }
.fb-item-head { font-size: 13px; display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.fb-item-meta { color: var(--muted); font-size: 11px; margin-left: auto; }
.fb-item-msg { font-size: 14px; color: var(--text); margin-top: 5px; white-space: pre-wrap; word-break: break-word; }

/* ---- Nonogramm-Einfuehrung: die eine Uebungszeile ----

   Die Felder sind 40 Pixel und damit deutlich groesser als im echten Raetsel
   (dort 30,1 px, siehe js/nono-input.js). Das ist Absicht: Hier lernt jemand
   eine Regel und soll sich nicht verdruecken; im Raetsel zaehlt die Flaeche.
   Ueber die Projektregel von 44 px Tippziel geht die Hoehe mit dem Abstand
   der Zeile hinaus. */
.ni-row {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 12px 0 8px;
  flex-wrap: wrap;
}
.ni-clue {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--gold);
  min-width: 42px;
  text-align: right;
  padding-right: 6px;
  letter-spacing: 2px;
}
.ni-cell {
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: 8px;
  cursor: pointer;
  background: var(--surface-1);
  border: 1px solid var(--panel-brd);
  transition: background 0.15s, border-color 0.15s;
}
.ni-cell.an {
  background: var(--gold);
  border-color: var(--gold);
}
/* Fertig: die ganze Zeile bestaetigt, nicht nur der Satz darunter. Der Satz
   wird gelesen, der Farbwechsel gesehen. */
.ni-row.ni-fertig .ni-cell.an {
  background: var(--color-success);
  border-color: var(--color-success);
}
.ni-msg { font-size: 13px; color: var(--muted); margin: 0; min-height: 18px; }
.ni-msg.gut { color: var(--color-success); font-weight: 600; }

/* ---- Ranglisten-Detailseite ---- */
/* BEFUND 3.3/4.5: Reiter nicht hart am Containerrand abschneiden. Statt einer
   scrollbaren, angeschnittenen Zeile brechen die Reiter jetzt um — auf dem
   Desktop (wo man nicht wischt) sieht man so alle, ohne Layout-Fehler-Anmutung. */
.ranking-tabs, .ranking-sub {
  display: flex; flex-wrap: wrap; gap: 8px;
  padding-bottom: 6px; margin-bottom: 12px;
}
.rk-tab, .rk-sub {
  flex: 0 0 auto; padding: 8px 15px; border-radius: 20px; cursor: pointer;
  background: var(--ctrl); border: 1px solid var(--ctrl-brd);
  color: var(--muted); font-family: inherit; font-size: 14px; white-space: nowrap;
  transition: background 0.2s, color 0.2s, border-color 0.2s;
  /* Handy-Durchgang 10.8.2026: gemessen 66x35. Dieselbe Fingerfläche wie beim
     Umschalter auf der Startseite — die Pillenform bleibt, sie wird nur hoch
     genug. inline-flex haelt die Beschriftung dabei mittig. */
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px;
}
.rk-sub { padding: 6px 13px; font-size: 12px; border-radius: 16px; }
.rk-tab:hover, .rk-sub:hover { color: var(--text); }
.rk-tab.active, .rk-sub.active {
  background: color-mix(in srgb, var(--gold) 20%, transparent);
  border-color: color-mix(in srgb, var(--gold) 55%, transparent);
  color: var(--gold); font-weight: 600;
}
.ranking-caption { font-size: 12px; color: var(--muted); margin: 0 2px 10px; letter-spacing: 0.3px; }
.ranking-detail-list { display: flex; flex-direction: column; gap: 6px; }
/* Der Einstiegs-Knopf zur Detailseite unter der Desktop-Rangliste. */
.dash-ranking-btn { margin-top: 8px; }

/* Ein Pfeil, eine Liste, ein Pfeil -- statt eines Knopfes je Seite.

   Hier stand bis 1.17.97 ein waagerecht scrollender Streifen mit 16
   Schaltflaechen. Am Handy war er rechts hart abgeschnitten, ohne Hinweis
   auf die Scrollbarkeit, und 34 px hoch. Jetzt passt die Zeile in jede
   Breite, und die Liste oeffnet das Betriebssystem in Fingergroesse. */
.levels-pager {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 4px;
  margin-bottom: 16px;
}
/* Die Liste nimmt den Platz, die Pfeile behalten ihre Groesse. */
.levels-pager .pager-wahl { flex: 1 1 auto; min-width: 0; text-align: center; }
.levels-pager .pager-pfeil { flex: 0 0 auto; width: 52px; font-size: 20px; line-height: 1; }
.pager-btn {
  padding: 8px 16px;
  /* 44px: die Mindestgroesse, an der ein Finger sicher trifft. Vorher kam die
     Hoehe allein aus Polsterung und Schriftgroesse und landete bei 34. */
  min-height: 44px;
  border-radius: 20px;
  border: 1px solid var(--panel-brd);
  background: var(--surface-1);
  color: var(--muted);
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s, transform 0.2s;
}
.pager-btn:disabled { opacity: 0.35; cursor: default; }
.pager-btn.active {
  background: var(--accent);
  color: var(--color-text-primary);
  border-color: var(--accent);
}
.levels-grid {
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  gap: 8px;
}
#levels-ranking { margin-top: 26px; }
.level-cell {
  aspect-ratio: 1;
  border-radius: 12px;
  border: 1px solid var(--panel-brd);
  background: var(--panel);
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  transition: transform 0.15s, border-color 0.2s, background 0.2s;
  position: relative;
}
.level-cell:hover:not(:disabled) {
  transform: translateY(-2px) scale(1.05);
  border-color: var(--accent);
  background: var(--surface-2);
}
.level-cell.done {
  border-color: color-mix(in srgb, var(--accent) 50%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
/* Das naechste offene Level: der Einstiegspunkt, den der Hauptknopf oben nennt.
   Bewusst ein Kupferring statt einer Fuellung — geloeste Level tragen bereits
   eine Fuellung in der Akzentfarbe, und zwei gefuellte Zustaende nebeneinander
   wuerden sich streiten. Der Ring liest sich als „hier geht es weiter", nicht
   als „erledigt". */
.level-cell.next {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 1px var(--color-primary) inset;
}
.level-cell.locked {
  opacity: 0.4;
  cursor: not-allowed;
}
.lvl-num { font-weight: 600; font-size: 15px; }
.lvl-stars { display: flex; gap: 1px; font-size: 7px; }
.lock { font-size: 13px; }
.star { color: var(--star-empty); }
.star.on { color: var(--gold); }

/* ============================================================
   GAME
   ============================================================ */
.game-title { text-align: center; }
.game-diff {
  display: block;
  font-size: 12px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--accent);
}
.game-level {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
}
.game-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(14px, 2.4vw, 30px);
  height: calc(100vh - 66px);
  padding: 16px 20px;
}
.game-left,
.game-right {
  display: flex;
  flex-direction: column;
  flex: 0 0 auto;
}
.game-left {
  gap: 12px;
  width: clamp(150px, 20vw, 200px);
}
.game-right {
  gap: 12px;
  width: clamp(96px, 11vw, 130px);
}
.stat {
  border-radius: 16px;
  padding: 16px 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  background: var(--ctrl);
  border: 1px solid var(--ctrl-brd);
  box-shadow: var(--ctrl-sh);
  backdrop-filter: none;
}
.hud-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--accent);
  font-weight: 700;
}
.hud-val {
  font-size: clamp(22px, 3vw, 30px);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
/* Zusatz unter einem HUD-Wert (heute: die Strafzeit je Fehler). Der Kasten ist
   clamp(96px, 11vw, 130px) breit; in der Größe von .hud-val stand „3 (+01:30)"
   über den Rand hinaus. Eigene Zeile, kleine Schrift, gedämpfte Farbe: die
   Zahl bleibt die Hauptsache, der Preis steht daneben. Block 328. */
.hud-extra {
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: .3px;
  line-height: 1.1;
  color: var(--muted);
  text-align: center;
}
/* Ohne Strafe darf nicht einmal eine Lücke bleiben: .stat ist eine Spalte mit
   gap: 5px, ein leeres Element schöbe den Kasten auf. */
.hud-extra:empty { display: none; }

/* Board */
.board {
  display: grid;
  grid-template-columns: repeat(9, minmax(0, 1fr));
  flex: 0 0 auto;
  width: min(76vh, 58vw, 100%, 640px);
  height: auto;
  aspect-ratio: 1;
  /* DIE ZEILEN TEILEN SICH DIE HOEHE, statt sie sich vom Inhalt geben zu
     lassen. Die Spalten waren schon so (`minmax(0, 1fr)` oben), die Zeilen
     nicht -- sie entstehen implizit, und `grid-auto-rows` stand auf `auto`.

     GEMESSEN im Querformat 844x390, Nachpruefung vom 09.09.2026, Teil 2,
     Befund 7: Das Brett ist dort 296x296, die Zellen aber 32 hoch, macht 320
     Zeilenhoehe in einem 292 hohen Innenraum. `overflow: hidden` schnitt die
     unterste Reihe ab, und die Blocklinien zerfielen in Balken.

     `minmax(0, 1fr)` und nicht `1fr`: Die Null als Mindestmass ist der
     eigentliche Punkt -- ein blosses `1fr` hat `min-content` als Untergrenze
     und gaebe wieder nach. Dieselbe Ueberlegung wie bei den Spalten. */
  grid-auto-rows: minmax(0, 1fr);
  background: var(--board-frame);
  border: 2px solid var(--board-frame);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 20px 60px -20px rgba(0, 0, 0, 0.8);
}
/* Variable Groessen: die Spaltenzahl setzt buildBoard inline. Hier nur Brettflaeche
   und Zifferngroesse anpassen. 16×16 braucht mehr Flaeche + kleinere, zell-relative
   Ziffern (cqi ueber container-type der .cell), damit auch 10..16 passen. */
/* 16×16: das Brett SELBST ist die Query-Container-Referenz fuer die Zellziffer.
   (Ein Element kann sich nicht selbst abfragen — cqi auf .cell bezog sich daher
   NICHT auf die Zelle, die Ziffer blieb bei 20px und sprengte am Handy die
   ~23px-Zelle: zweistellige Zahlen liefen an die Zellraender, „abgeschnitten"/
   schwer lesbar.) Mit container am Brett skaliert die Ziffer mit der Brettbreite:
   ~11px am Handy (zweistellig passt), bis 20px am Desktop. Die Notiz-Ziffern
   nutzen weiter den naeheren Zell-Container (unveraendert). */
#board.n16 { width: min(84vh, 96vw, 100%, 760px); height: auto; aspect-ratio: 1; container-type: inline-size; }
#board.n16 .cell { font-size: clamp(11px, 3cqi, 20px); }
#board.n6 { width: min(60vh, 74vw, 100%, 480px); height: auto; aspect-ratio: 1; }
/* AUF DEN SPIELFLAECHEN ZOOMT EIN DOPPELTIPP NICHT MEHR.

   Korbis Befund vom 08.09.2026: Wer beim Auswaehlen einer Zelle versehentlich
   zweimal tippt, zoomt das Bild -- „wenn ich richtig Pech habe bin ich dann
   auf Button-Ebene". Es kostet nicht nur Nerven: Die Uhr laeuft weiter,
   waehrend man sich zurueckzoomt, und der Weg dorthin fuehrt ueber dieselbe
   Flaeche, auf der man gerade Ziffern setzt.

   DER ZWEITE BEFUND HAENGT AM SELBEN NAGEL: „Nachdem ich rein und
   rausgezoomt hatte, war die Auswahl der Zellen auf einmal zeitverzoegert."
   Das ist kein zweiter Fehler. Solange ein Doppeltipp zoomen KANN, muss der
   Browser nach jedem Tipp rund 300 ms abwarten, ob ein zweiter folgt. Bei
   Massstab 1 verzichten die Browser darauf, nach einem Zoom nicht mehr --
   deshalb trat es genau in dieser Reihenfolge auf.

   `manipulation` UND NICHT `none`: Zwei-Finger-Zoom und Wischen bleiben.
   Wer die Ziffern schlecht sieht, muss das Brett weiter vergroessern
   koennen, und ein Brett, das laenger ist als der Bildschirm, muss scrollen.

   AM BILDSCHIRM UND NICHT AN DER ZELLE. Die wirksame Geste ergibt sich aus
   dem ganzen Elternpfad; eine Regel am Behaelter deckt Brett, Zahlenpad und
   Werkzeuge zugleich ab. An den Zellen einzeln waere es dieselbe Wirkung mit
   dem Risiko, die naechste Spielart zu vergessen. Block 362 fuehrt die Liste
   und wird rot, wenn eine dazukommt. */
#screen-game,
#screen-fuchsbau,
#screen-binary,
#screen-queens {
  touch-action: manipulation;
}

.cell {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Damit die Zelle unter ihre Inhaltshoehe darf. Ohne diese Zeile bleibt
     `grid-auto-rows: minmax(0, 1fr)` am Brett wirkungslos: Ein Flex-Element
     hat `min-height: auto` und gibt seine Ziffer als Untergrenze mit.
     Siehe die Begruendung an `.board`. */
  min-height: 0;
  font-family: var(--font-numeric);
  font-size: clamp(18px, 5.5vw, 28px);
  font-weight: 400;
  color: var(--accent);
  cursor: pointer;
  position: relative;
  background: var(--board-cell-base);
  /* Container fuer die Notiz-Ziffern: font-size skaliert mit der Zellbreite (cqi). */
  container-type: inline-size;
  border-right: 1px solid var(--grid-line);
  border-bottom: 1px solid var(--grid-line);
  user-select: none;
  transition: background 0.12s, box-shadow 0.08s var(--ease-out-quart);
  aspect-ratio: 1;
}
/* Alternierende 3x3-Bloecke (Schachbrett): ungerade Bloecke einen Tick heller.
   Vor den Zustands-Hintergruenden (peer/same/selected), damit diese gewinnen. */
.cell.blk-alt { background: var(--board-cell-alt); }
/* X-Sudoku: beide Diagonalen dauerhaft dezent violett tönen (Orientierung).
   Vor den Zustands-Hintergruenden (peer/same/selected/wrong), damit ein aktiver
   Zustand die Diagonale sichtbar übertönt. Halbtransparent -> wirkt in beiden Themes. */
.cell.xdiag { background-image: linear-gradient(rgba(109, 90, 168, 0.16), rgba(109, 90, 168, 0.16)); }
/* Chaos-Sudoku: dicke Regionsraender (jede innere Grenze genau einmal — vom
   rechten/unteren Nachbarn aus als linke/obere Kante gezeichnet). */
.cell.crb-l { border-left: 2px solid var(--grid-line-block); }
.cell.crb-t { border-top: 2px solid var(--grid-line-block); }
/* „Rätsel wird vorbereitet" — Overlay für selten längere Erzeugung (Killer).
   Erscheint vor der (blockierenden) Erzeugung, damit ein Tap nicht tot wirkt. */
.gen-loading {
  position: fixed; inset: 0; z-index: 200;
  display: none; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.55); backdrop-filter: blur(2px);
}
.gen-loading.show { display: flex; }
.gen-loading-card {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  padding: 26px 34px; border-radius: 16px; text-align: center;
}
.gen-loading-text { font-family: var(--font-display); color: var(--text); font-size: 15px; }
.gen-spinner {
  width: 34px; height: 34px; border-radius: 50%;
  border: 3px solid color-mix(in srgb, var(--color-primary) 30%, transparent);
  border-top-color: var(--color-primary);
  animation: gen-spin 0.8s linear infinite;
}
@keyframes gen-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .gen-spinner { animation: none; } }

/* Killer-Sudoku: 3x3-Raster bleibt; zusätzlich pro Käfig eine gestrichelte,
   nach innen versetzte Umrandung (::before, Kanten via ke-*-Klassen) und das
   Summen-Label oben links (::after, Inhalt aus data-ksum). Pseudoelemente
   überleben das textContent-Neurendern der Ziffer. Nur im Killer-Board aktiv. */
/* Jede Killer-Zelle bekommt einen eigenen Stacking-Context, damit die (grosse)
   Ziffer ueber Kaefiglinie UND Summen-Chip liegt. Sonst verdeckte der Chip die
   Ziffer im Anker-Feld (gemeldet: Hinweis-Ziffern 1/2 hinter der Summe). Die
   Pseudoelemente wandern dafuer hinter die Ziffer (negatives z), bleiben aber
   ueber der Zell-Flaeche. */
#board.killer .cell { isolation: isolate; }
#board.killer .cell::before {
  content: ""; position: absolute; inset: 3px; pointer-events: none; z-index: -2;
  border: 0 dashed rgba(244, 230, 200, 0.6);
}
#board.killer .cell.ke-l::before { border-left-width: 2px; }
#board.killer .cell.ke-t::before { border-top-width: 2px; }
#board.killer .cell.ke-r::before { border-right-width: 2px; }
#board.killer .cell.ke-b::before { border-bottom-width: 2px; }
/* Summen-Label nur in Anker-Zellen (data-ksum gesetzt). Auf einem kleinen dunklen
   Chip mit hellem Gold -> klar lesbar auch am Handy, und der Chip verdeckt die
   gestrichelte Käfiglinie, die sonst durch die Zahl lief. */
#board.killer .cell[data-ksum]::after {
  content: attr(data-ksum);
  position: absolute; top: 0; left: 0; z-index: -1; pointer-events: none;
  font-family: var(--font-numeric); font-size: clamp(7px, 2.1vw, 9px);
  font-weight: 700; line-height: 1; color: var(--killer-sum-fg);
  padding: 0 3px; border-radius: 0 0 4px 0;
  background: var(--killer-sum-bg);
  border: none; width: auto; height: auto; /* etwaige .wrong::after-Dreieck-Regeln überschreiben */
}
/* UND DIE NOTIZEN FANGEN DARUNTER AN.

   Korbis Befund vom 08.09.2026: „Gerade die 1 ist in Zellen wo auch die
   Summe angezeigt wird fast nicht zu erkennen." Im Handy-Viewport
   nachgemessen (393 px): Die Zelle ist 41,9 px gross, die Notiz-Ziffer 1
   sitzt bei 2,9/2,9 und ist 11,7 px breit, der Chip beginnt bei 0/0 und ist
   rund 14 px hoch. Er verdeckt die 1 zu ueber neunzig Prozent, bei
   zweistelligen Summen auch die 2.

   Es war also kein Kontrastproblem, sondern zwei Zahlen uebereinander. Der
   fruehere Anlauf hat den Chip HINTER die Ziffer gelegt (negatives z, siehe
   oben); das half der grossen Ziffer und nicht den Notizen, die klein und
   duenn sind und genau dort liegen.

   DERSELBE AUSDRUCK WIE DER CHIP, absichtlich: Zwei getrennt gepflegte
   Zahlen laufen auseinander, und die zweite merkt niemand, weil sie nur
   eine Notiz-Ziffer kostet. Aendert jemand die Chipgroesse, wandert der
   Abstand mit. Block 363 haelt beides gegeneinander.

   Nur in Anker-Zellen. Ohne Summe nutzt das Gitter die ganze Zelle -- alle
   Notizen im Killer kleiner zu machen um eines Chips willen, den es dort
   gar nicht gibt, waere der schlechtere Tausch. */
#board.killer .cell[data-ksum] .notes {
  /* NUR DER OBERE ABSTAND. Unten bleiben die 7 Prozent aus `.notes`, und die
     Ziffern behalten ihre normale Groesse.

     HIER STAND DAS GEGENTEIL, und es war die falsche Stellschraube. Am
     08.09.2026 habe ich den Notizen unten Luft weggenommen (4 statt 7
     Prozent) und sie verkleinert (19 statt 22cqi), um sie unter einen
     14 Pixel hohen Chip zu bekommen. Korbi am 13.09.2026: „die Notizen sind
     immer noch teilweise fast nicht lesbar." Er hat recht, und die Zahlen
     sagen, warum -- im Handy-Viewport 393 px gemessen:

         Notiz in einer Summenzelle    7,13 px
         Notiz in einer normalen Zelle 8,70 px

     Also achtzehn Prozent kleiner, in einer Zeile von 7,3 Pixeln. Nicht der
     Kontrast war das Problem (6,7:1 auf der Zelle, 7,2:1 auf dem Chip),
     sondern die Groesse -- und die hatte ich selbst weggenommen.

     JETZT SCHRUMPFT DER CHIP STATT DER NOTIZEN. Die Summe ist zwei bis drei
     Zeichen lang und fett; sie vertraegt 9 Pixel. Drei Notizreihen
     vertragen keine 7. Nachgemessen mit einem Range um die TINTE, nicht um
     das Kaestchen:

                                    vorher   nachher
         Notiz-Schriftgroesse       7,13     8,26 px
         Tinte ueber der Kaefiglinie 1,84     0,53 px
         Luft unter dem Chip         0,96     0,11 px

     Die neue Fassung ist also auf beiden Achsen besser: groessere Ziffern
     UND weniger Ueberlauf. Dass die alte schon 1,8 Pixel ueber die Linie
     ragte, stand im alten Kommentar nicht -- dort war nur gegen die
     Zellkante gerechnet, nicht gegen die Linie, die man sieht.

     ZWEI WEGE, DIE ICH VERWORFEN HABE, damit sie niemand noch einmal geht:
     Die Notizen in voller Groesse UEBER den Chip zu legen (Korbis erster
     Gedanke) setzt die Notiz-1 direkt an die Summe -- „18" und „1"
     nebeneinander liest sich als „181". Und den Chip nur halb zu
     verkleinern half nicht genug; der Zusammenstoss blieb. Beides ist
     gerendert und angesehen worden, nicht ueberlegt. */
  padding-top: calc(clamp(7px, 2.1vw, 9px) + 1px);
}
/* ---- Kakuro (Kreuzsummen): Wände mit Klauen-Dreieck + Eingabefelder ----
   Eingabefelder erben die normale Zell-Optik (selected/peer/same/wrong/filled).
   Wände sind dunkel; eine Diagonale trennt rechte Summe (oben) und untere (unten). */
#board.kakuro .cell { aspect-ratio: 1; position: relative; border: 1px solid var(--grid-line); }
#board.kakuro .cell.kwall { background: #2a2420; cursor: default; }
/* LEERE WÄNDE OHNE GITTERLINIE.

   Gemeldet: „drumherum stehen leere graue Zellen." Der Vorschlag war, das
   Brett auf den belegten Bereich zuzuschneiden. Am Erzeuger nachgerechnet
   lohnt das nicht — einmalig über 200 Level gemessen wären die Zellen im
   Schnitt drei Prozent größer, bei 156 von 200 änderte sich gar nichts, und
   für „Normal" gilt derselbe Wert (der tiefe Lauf rechnet 60 Level nach). Es ist also kein Problem der leichten Stufe. Der Preis wäre
   ein Brett, das von Level zu Level seine Größe wechselt (Block 334t).

   Und der Zuschnitt träfe die Sache nicht: Die leere Fläche ist keine
   Umrandung, sondern eine Ecke — eine L-Form bekommt kein Rechteck weg.

   Was wirklich stört, ist das Raster darauf. Eine leere Wand trug dieselbe
   Linie wie alles andere und las sich als Kasten, in den etwas gehört. Ohne
   sie wird die Ecke eine ruhige Fläche und die Form des Rätsels tritt hervor.

   NUR die Linie: Zwei Versuche mit hellerem beziehungsweise durchsichtigem
   Grund sahen im Bild schlechter aus als vorher — das Brett hat einen hellen
   Eigengrund, den die Wände verdecken, und beides gab der leeren Ecke mehr
   Gewicht statt weniger. Block 334. */
#board.kakuro .cell.kwall:not(.kclued) { border-color: transparent; }
#board.kakuro .cell.kclued {
  background-image:
    linear-gradient(to bottom right,
      transparent calc(50% - 0.7px),
      rgba(233, 220, 194, 0.45) calc(50% - 0.7px),
      rgba(233, 220, 194, 0.45) calc(50% + 0.7px),
      transparent calc(50% + 0.7px));
}
#board.kakuro .kclue {
  position: absolute; font-family: var(--font-numeric); font-weight: 700;
  /* 2.5vw sind bei 393 px nur 9,8 px. Die Zelle selbst ist mit 41,5 px
     fingerfreundlich und die eingetragene Ziffer 21,6 px gross — klein war hier
     allein die Lauf-Summe. Die Obergrenze von 13 px stand schon immer im Code,
     erreicht wurde sie erst ab 520 px Bildschirmbreite. */
  font-size: clamp(12px, 3.4vw, 14px); line-height: 1; color: #e9dcc2; pointer-events: none;
}
#board.kakuro .kclue-r { top: 7%; right: 9%; }   /* rechte Lauf-Summe: obere Dreieckshälfte */
#board.kakuro .kclue-d { bottom: 7%; left: 9%; }  /* untere Lauf-Summe: untere Dreieckshälfte */
/* Starthilfe klar als vorgegeben markieren (getönt, kräftiger). */
#board.kakuro .cell.kentry.given {
  background-image: linear-gradient(color-mix(in srgb, var(--accent) 16%, transparent), color-mix(in srgb, var(--accent) 16%, transparent));
  color: var(--num-given); font-weight: 700;
}
/* ---- Nonogramm (Bilderrätsel): Zahlenleisten oben und links + Spielfelder ----
   Das Brett ist ein 11×11-Raster (Ecke, Spalten-Zahlen, Zeilen-Zahlen, Felder);
   die Spaltenbreiten setzt buildNonogramBoard inline. Die Leisten sind bewusst
   ruhiger als das Spielfeld — gelesen wird das Gitter, nicht die Zahlen. */
#board.nonogram { width: min(80vh, 70vw, 100%, 700px); height: auto; aspect-ratio: 1; container-type: inline-size; }
#board.nonogram .ncorner { background: var(--color-bg-surface); }
#board.nonogram .nclue {
  display: flex; gap: 0.35em; align-items: center;
  background: var(--color-bg-surface);
  font-family: var(--font-numeric); font-weight: 600;
  /* Der untere Anschlag ist das, was auf dem Handy zaehlt: 2.2cqi ergibt bei
     einem 377 px breiten Brett nur 8,3 px — es galten also die 9 px, und die
     waren zu klein zum Lesen. Ueber den Zahlen liegt sichtbar ungenutzter
     Platz, weil die Hinweiszone auf die maximale Zahl an Hinweisen ausgelegt
     ist; die Erhoehung passt dort hinein. */
  font-size: clamp(12px, 2.8cqi, 15px); line-height: 1;
  color: var(--color-text-secondary);
  padding: 0.25em; overflow: hidden;
}
/* Zeilen-Zahlen enden am Gitter (rechtsbündig), Spalten-Zahlen stehen darüber
   (unten bündig) — so liest man sie ohne Sprung ins jeweilige Feld hinein. */
/* Dichte Spalten: Der Hinweisstreifen ist fest hoch (72 px bei 393 px Schirm,
   59 px bei 320 px), eine Spalte kann aber bis zu FUENF Zahlen tragen — im
   Generator nachgerechnet: 5 von 24 000 Spalten ueber alle 2400 Level, das
   frueheste Beispiel ist Normal Level 93. Fuenf Zahlen zu 12 px passen dort
   nicht; weil der Streifen von unten fuellt und abschneidet, verschwaende die
   OBERSTE. Abgeschnitten ist schlimmer als klein, also werden diese seltenen
   Spalten enger gesetzt und etwas kleiner — die 99,5 % uebrigen Spalten
   behalten die groessere Schrift. */
#board.nonogram .nclue-col[data-n="4"] {
  gap: 0.12em; padding: 0.1em;
  font-size: clamp(11px, 3cqi, 14px);
}
#board.nonogram .nclue-col[data-n="5"] {
  gap: 0.12em; padding: 0.1em;
  font-size: clamp(10px, 3cqi, 12px);
}
/* Querformat: Der Streifen ist dort noch flacher — 60 px bei 852x393, nur 49 px
   bei 568x320. Selbst die reduzierte Schrift passte da nicht (gemessen 7 px
   Ueberlauf; mit den alten 9 px waren es rund 13). Die App bittet im Manifest um
   Hochformat, aber das gilt nur fuer die INSTALLIERTE App — im Browser kann
   jeder drehen. */
@media (max-height: 430px) {
  #board.nonogram .nclue-col[data-n="4"] { font-size: clamp(9px, 3cqi, 14px); }
  #board.nonogram .nclue-col[data-n="5"] { font-size: clamp(8px, 3cqi, 12px); }
}
#board.nonogram .nclue-row { flex-direction: row; justify-content: flex-end; }
#board.nonogram .nclue-col { flex-direction: column; justify-content: flex-end; }
#board.nonogram .nclue.hl { color: var(--color-text-primary); background: color-mix(in srgb, var(--accent) 14%, var(--color-bg-surface)); }
/* Erledigte Reihe ausgrauen: zeigt auf einen Blick, was noch offen ist. */
#board.nonogram .nclue.done { color: var(--color-text-disabled); text-decoration: line-through; }
#board.nonogram .nclue .nzero { opacity: 0.5; }
#board.nonogram .cell.ncell {
  aspect-ratio: auto; border: 1px solid var(--grid-line);
  font-size: clamp(10px, 2.6cqi, 18px); color: var(--color-text-muted);
  cursor: pointer;
}
/* Fünfer-Trennung wie im gedruckten Rätsel — ohne sie verzählt man sich. */
#board.nonogram .cell.n5r { border-right-width: 2px; border-right-color: var(--board-frame); }
#board.nonogram .cell.n5b { border-bottom-width: 2px; border-bottom-color: var(--board-frame); }
#board.nonogram .cell.ncell.filled { background: var(--num-user); }
/* Die Markierung („sicher leer") ist beim Nonogramm das Hauptwerkzeug zum
   Mitdenken — sie muss auf einen Blick lesbar sein. In der Farbe fuer
   Deaktiviertes war sie am Handy kaum zu erkennen. */
#board.nonogram .cell.ncell.marked { color: var(--color-text-secondary); font-weight: 700; }
/* Falsch gefülltes Feld: rot, aber weiter als gefüllt erkennbar. */
#board.nonogram .cell.ncell.wrong { background: var(--color-error); }
#board.nonogram .cell.ncell.peer { box-shadow: inset 0 0 0 100px color-mix(in srgb, var(--accent) 10%, transparent); }
#board.nonogram .cell.ncell.selected { outline: 2px solid var(--accent); outline-offset: -2px; z-index: 1; }
/* Am Handy die volle Breite nehmen — wie 16×16 es schon tut. Das Nonogramm war
   die einzige Art ohne mobile Breite und blieb bei 70vw; auf 402px Bildschirm
   ergab das 22,4px je Feld, weil die Zahlenleiste zusätzlich 2,4 von 12,4
   Rasterspalten belegt. Ein Fehlgriff kostet hier sofort einen Fehler, der Preis
   fürs Verdrücken ist also höher als bei Arten, wo man einfach neu tippt.
   Mit 96vw sind es 30,8px (+37 %). Die 44pt aus Apples Richtlinie bleiben bei
   10 Spalten plus Leiste auf einem Handy unerreichbar — dafür bräuchte das Brett
   rund 546px. Es wird deutlich besser, nicht regelkonform.
   Diese Regel MUSS hinter der Grundregel stehen: gleiche Spezifität, und der
   Media-Query erhöht sie nicht — weiter oben stünde sie wirkungslos da. */
@media (max-width: 520px) {
  #board.nonogram { width: min(84vh, 96vw, 100%, 700px); height: auto; aspect-ratio: 1; }
}
/* Werkzeugleiste statt Ziffernfeld: zwei gleich breite Tasten. */
.keypad.kp-tools { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2); }
.keypad.kp-tools .key.ntool {
  aspect-ratio: auto; width: auto; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; padding: var(--space-3) var(--space-2);
}
.keypad.kp-tools .key.ntool .ntool-ico { font-size: 1.25em; line-height: 1; }
.keypad.kp-tools .key.ntool .ntool-lbl { font-size: 0.75em; opacity: 0.85; }
/* Nonogramm kennt keine Notizen — der Knopf wäre eine Sackgasse. */
.screen.variant-nonogram #notes-toggle { display: none; }

/* Schwierigkeits-Umschalter in der Level-Auswahl. Bewusst als Segment-Leiste:
   drei gleichrangige Stufen, die aktive deutlich abgesetzt. */
.levels-diffs { display: flex; gap: 6px; background: var(--color-bg-surface);
  border: 1px solid var(--panel-brd); border-radius: var(--radius-pill); padding: 4px;
  margin: 0 auto 14px; width: fit-content; max-width: 100%; }
.levels-diffs button { flex: 1 1 auto; min-width: 92px; border: 0; background: transparent;
  color: var(--color-text-secondary); font: 600 14px inherit; padding: 8px 16px;
  border-radius: var(--radius-pill); cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast), color var(--dur-fast); }
.levels-diffs button:hover { color: var(--color-text-primary); }
.levels-diffs button.on { background: var(--accent); color: var(--color-bg-base); }
.levels-diffs button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.levels-diffs .ld-sub { display: block; font-size: 11px; font-weight: 500; opacity: .8; }

/* ---- Hub „Alle Rätselarten": Karten mit Kachelbild, Titel und zwei Sätzen ----
   Nur auf den statischen Content-Seiten verwendet, nicht in der App. */
.lp-types { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: var(--space-4); }
.lp-type {
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--color-bg-surface); border: 1px solid var(--panel-brd);
  border-radius: var(--radius-lg); text-decoration: none; color: inherit;
  transition: border-color var(--dur-fast) var(--ease-out-quart);
}
.lp-type:hover, .lp-type:focus-visible { border-color: var(--accent); }
/* height:auto ist Pflicht, nicht Kosmetik: die width/height-Attribute am <img>
   (gegen Layout-Sprünge beim Laden) wirken als Praesentations-Hinweis. Ohne
   height:auto gewinnt height=1024px gegen aspect-ratio, und die Kachel wird
   meterhoch statt quadratisch. */
.lp-type img { display: block; width: 100%; height: auto; aspect-ratio: 1; object-fit: cover; }
.lp-type-body { display: flex; flex-direction: column; gap: 6px; padding: var(--space-3) var(--space-4) var(--space-4); }
.lp-type-title { font-family: var(--font-display); font-weight: 700; font-size: 17px; color: var(--color-text-primary); }
.lp-type-desc { font-size: 14px; line-height: 1.5; color: var(--color-text-secondary); }

/* Jede Karte der Uebersicht bekommt einen ZWEITEN Weg: direkt ins Spiel.

   Bis 1.17.145 fuehrten alle elf Karten in Beschreibungstexte und keine
   einzige ins Spiel -- auf der Seite, die man am ehesten „die Spieleseite"
   nennt. Gemessen: null von elf. Wer spielen wollte, brauchte drei Tipps und
   eine Seite Lesen dazwischen.

   Die Karte bleibt, was sie war (der Text ist fuer Suchmaschinen geschrieben
   und traegt sie); der Spiel-Weg steht als eigener, kleinerer Knopf darunter.
   Deshalb das Paar: In ein `<a>` laesst sich kein zweites `<a>` legen. */
.lp-type-paar { display: flex; flex-direction: column; gap: 8px; }
.lp-type-paar .lp-type { flex: 1; }
.lp-type-spielen {
  display: block; text-align: center; padding: 10px 12px;
  font-size: 14px; font-weight: 600; text-decoration: none;
  color: var(--accent); background: var(--color-bg-surface);
  border: 1px solid var(--panel-brd); border-radius: var(--radius-lg);
  transition: border-color var(--dur-fast) var(--ease-out-quart);
}
.lp-type-spielen:hover, .lp-type-spielen:focus-visible { border-color: var(--accent); }
/* Motivbild auf einer Artseite (Fuchsbau). height:auto aus demselben Grund wie
   oben: Sonst gewinnt das height-Attribut gegen aspect-ratio. */
.lp-motiv {
  display: block; width: 100%; max-width: 320px; height: auto; aspect-ratio: 1;
  margin: var(--space-4) 0; border-radius: var(--radius-lg); object-fit: cover;
}

/* 3.1: gesetzte Ziffer „poppt" (scale 0.7->1 + opacity 0.5->1) */
.cell.pop { animation: cell-pop 0.15s var(--ease-out-back); }
@keyframes cell-pop {
  0% { transform: scale(0.7); opacity: 0.5; }
  100% { transform: scale(1); opacity: 1; }
}
.cell.bl { border-left: 2px solid var(--grid-line-block); }
.cell.bt { border-top: 2px solid var(--grid-line-block); }
.cell.br { border-right: none; }
.cell.bb { border-bottom: none; }
/* 1.4: Startzahl vs. eigene Eingabe klar unterscheidbar */
.cell.given {
  color: var(--num-given);
  font-weight: 600;
}
.cell.filled:not(.given) { color: var(--num-user); font-weight: 500; }
/* Priority-Stack (schwach -> stark): peer < same < selected < conflict < hint.
   Quelltext-Reihenfolge = Gewinner bei gleicher Spezifität.
   Die Zustandsfarben sind transluzent und liegen als background-image ueber der
   deckenden Zell-Base (background-color), damit die alternierende Blockfaerbung
   durchscheint und nicht ersetzt wird. */
.cell.peer { background-image: linear-gradient(var(--board-highlight-line), var(--board-highlight-line)); }
.cell.same { background-image: linear-gradient(var(--cell-same-number), var(--cell-same-number)); }
.cell.selected {
  background-image: linear-gradient(var(--cell-selected), var(--cell-selected));
  box-shadow: inset 0 0 0 1px var(--color-primary);
}
.cell.wrong { color: var(--num-error) !important; background-image: linear-gradient(var(--cell-conflict), var(--cell-conflict)); }
/* 5.3d Farbenblind: kleine Ecken-Markierung an falschen Zellen — Form-Signal
   unabhängig von der Farbwahrnehmung (nur wenn „Fehler beim Tippen" aktiv). */
.cell.wrong::after {
  content: "";
  position: absolute;
  top: 2px;
  right: 2px;
  width: 0;
  height: 0;
  border-top: 7px solid var(--color-error);
  border-left: 7px solid transparent;
  pointer-events: none;
}
.cell.conflict { background-image: linear-gradient(var(--cell-conflict), var(--cell-conflict)); }
.cell.hint { background-image: linear-gradient(var(--cell-hint), var(--cell-hint)); box-shadow: inset 0 0 0 1px var(--color-warning); }
.cell.shake { animation: shake 0.2s; }
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(-3px); }
  40%, 80% { transform: translateX(3px); }
}
/* 1.5: Konflikt-Zellen pulsen kurz rot */
.cell.pulse-err { animation: pulse-err 0.4s ease; }
@keyframes pulse-err {
  0%, 100% { box-shadow: inset 0 0 0 0 transparent; }
  50% { box-shadow: inset 0 0 0 2px var(--color-error); }
}
/* 3.1: Hinweis-Zelle pulst 3× (Board hat overflow:hidden -> inset-Ring statt Glow) */
.cell.hint-pulse { animation: hint-pulse 0.9s ease; }
@keyframes hint-pulse {
  0%, 100% { box-shadow: inset 0 0 0 0 transparent; background: transparent; }
  16%, 50%, 84% { box-shadow: inset 0 0 0 2px var(--color-warning); background: var(--cell-hint); }
}
/* 3.2: Zeile/Spalte komplett -> Sweep-Glow (sequenziell); Box -> Scale-Puls */
.cell.sweep { animation: sweep 0.52s ease; }
@keyframes sweep {
  0%, 100% { background: transparent; box-shadow: inset 0 0 0 0 transparent; }
  40% { background: var(--cell-success); box-shadow: inset 0 0 0 2px var(--color-success); }
}
.cell.box-done { animation: box-done 0.3s ease; z-index: 2; }
@keyframes box-done {
  0% { transform: scale(1); }
  50% { transform: scale(1.06); background: var(--cell-success); }
  100% { transform: scale(1); }
}
.key.pad-done { animation: pad-done 0.64s ease; }
@keyframes pad-done {
  0%, 100% { box-shadow: var(--ctrl-sh); }
  25%, 75% { box-shadow: 0 0 0 2px var(--color-success), var(--ctrl-sh); }
}
.notes {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  /* 7% Innenabstand pro Seite: Randziffern (1,3,7,9) beruehren den Zell-Border
     nicht mehr (behebt den Overflow im Extrem-Modus, v.a. auf Mobile). */
  padding: 7%;
}
.notes span {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-pencil);
  /* Font-Size skaliert mit der Zellbreite (cqi ueber container-type der .cell),
     ~22% der Zellbreite, nach oben/unten geklammert. line-height strikt, damit
     Ober-/Unterlaengen nicht vertikal auslaufen. */
  font-size: clamp(7px, 22cqi, 14px);
  line-height: 1;
  color: var(--num-pencil);
  font-weight: 600;
}
/* Notiz-Ziffer, die der aktuell hervorgehobenen Zahl entspricht (1.2) */
.notes span.note-hi { color: var(--color-primary); font-weight: 700; }

/* ---------------- Binär-Modus (Binairo 8x8) ---------------- */
.bin-hint {
  max-width: 560px; margin: 4px auto 0; padding: 0 16px;
  font-family: var(--font-body); font-size: 12px; line-height: 1.4;
  color: var(--muted); text-align: center;
}
.binary-board {
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  width: min(70vh, 92vw, 100%, 540px);
  height: auto;
  aspect-ratio: 1;
  background: var(--board-frame);
  border: 2px solid var(--board-frame);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 20px 60px -20px rgba(0, 0, 0, 0.8);
  flex: 0 0 auto;
}
.binary-board.blurred { filter: blur(9px); pointer-events: none; }
.bcell {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 1;
  font-family: var(--font-numeric);
  font-size: clamp(16px, 5vw, 26px);
  color: var(--num-pencil);
  background: var(--board-cell-base);
  border-right: 1px solid var(--grid-line);
  border-bottom: 1px solid var(--grid-line);
  cursor: pointer;
  user-select: none;
  transition: background 0.12s, color 0.12s;
}
/* Konflikt-Anzeige (BEFUND 2.3): rot, sobald eine Regel verletzt wird. */
#binary-conflicts-stat.has-conflicts .hud-val { color: var(--danger); }
#binary-conflicts-stat.has-conflicts { border-color: color-mix(in srgb, var(--danger) 45%, transparent); }

.bcell.bmid { border-left: 2px solid var(--grid-line-block); }
.bcell.bmid-row { border-top: 2px solid var(--grid-line-block); }
/* 0 = dunkler Chip, 1 = heller Gold-Chip -> starker Hell/Dunkel-Kontrast fuers
   Muster, bleibt in der Wein/Gold-Palette. */
.bcell.zero { background: color-mix(in srgb, #000 30%, var(--board-cell-base)); color: var(--num-pencil); }
.bcell.one { background: color-mix(in srgb, var(--gold) 34%, var(--board-cell-base)); color: var(--gold); font-weight: 700; }
.bcell.given { font-weight: 800; }
.bcell.given.zero { color: var(--num-given); }
.bcell.given.one { color: var(--num-given); }
/* Givens dezent markieren (nicht editierbar, fester Vorgabewert). */
.bcell { position: relative; }
.bcell.given { box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--gold) 22%, transparent); }
/* Auswahl + Zeile/Spalte der aktiven Zelle (hilft beim Zaehlen der 0/1). */
.bcell.peer { background-image: linear-gradient(var(--board-highlight-line), var(--board-highlight-line)); }
.bcell.sel { box-shadow: inset 0 0 0 2px var(--color-primary); }
.bcell.conflict { background: var(--cell-conflict); color: var(--num-error); box-shadow: inset 0 0 0 2px var(--color-error); }
/* Bleistift-Marken (Notizen): kleine 0/1 nebeneinander in leeren Zellen. */
.bcell.has-notes { gap: clamp(3px, 1.4vw, 7px); }
.bnote { font-size: clamp(10px, 2.8vw, 14px); line-height: 1; font-weight: 600; color: var(--num-pencil); opacity: 0.72; }
/* Ziffernblock nutzt die Sudoku-Keys (.keypad/.key); nur die Farben spiegeln die
   Chip-Logik (1 = gold) und die Loeschtaste ist dezent. */
#binary-pad .bkey-1 .key-num { color: var(--gold); }
#binary-pad .bkey-erase .key-num { color: var(--muted); font-family: var(--font-body); }
/* Mobiles Stapeln des Binär-Layouts steht am Dateiende (eigene Media-Query),
   damit es den Spielscreen-Mobile-Block nicht verschiebt. */

/* ===== Queens (Kronen-Rätsel) ===== */
.queens-board {
  display: grid;
  grid-template-columns: repeat(var(--qn, 8), minmax(0, 1fr));
  width: min(70vh, 92vw, 100%, 540px);
  height: auto;
  aspect-ratio: 1;
  background: var(--board-frame);
  border: 2px solid var(--board-frame);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 20px 60px -20px rgba(0, 0, 0, 0.8);
  flex: 0 0 auto;
}
.queens-board.blurred { filter: blur(9px); pointer-events: none; }
/* DAS BRETT STAND 80 PIXEL LINKS DER MITTE.

   Gemeldet als „rund 40 px"; bei 1440x900 nachgemessen sind es 80: Brett bei
   x 370, 540 breit, Mitte also 640 gegen eine Fenstermitte von 720.

   `.game-wrap` ist eine zentrierte Zeile, und beim Kronen stehen darin nur
   Brett und Statusspalte. Die ZEILE ist damit mittig, das Brett nicht.

   Zum Vergleich, damit die Größenordnung stimmt: Beim Sudoku steht das Brett
   ebenfalls nicht genau mittig, sondern 35 Pixel rechts — Ziffernblock 200,
   Statusspalte 130. Die Regel des Hauses ist „die Zeile ist mittig". Beim
   Kronen fällt die Abweichung nur doppelt so groß aus, weil links gar nichts
   steht.

   Der Platzhalter trägt GENAU die Breite der Statusspalte, nicht irgendeine:
   So bleibt die Zeile symmetrisch, auch wenn sich die Spalte später ändert.

   Nur oberhalb von 760 Pixeln, denn dort wird `.game-wrap` zur Spalte (der
   Block für schmale Schirme steht weiter unten) — als Spaltenkind wäre der
   Platzhalter ein leerer Kasten von 130 Pixeln Höhe mitten im Bild.

   BEKANNTE LUECKE, damit sie niemand fuer einen Fehler haelt: Zwischen 760
   und 761 liegt ein Streifen gebrochener Breiten (Zoom, krummes
   Geraeteverhaeltnis), in dem WEDER der Handy-Block NOCH dieser greift. Dort
   steht das Brett wieder wie vorher. Das ist rein kosmetisch, und die Luecke
   gibt es an jedem max-/min-width-Paar im Stylesheet — sie zu schliessen
   waere ein eigener Vorgang, kein Beifang von hier.

   UND DIE ANGABE STEHT HIER BEWUSST IN WORTEN. Acht Prüfungen finden den
   Handy-Block, indem sie nach seinem Anfang als Zeichenfolge suchen. Stünde
   er hier im Kommentar, schnitten sie an dieser Stelle statt am Block — genau
   das ist beim ersten Anlauf passiert. Block 335. */
@media (min-width: 761px) {
  #screen-queens .game-wrap::before {
    content: "";
    flex: 0 0 clamp(96px, 11vw, 130px);
  }
}
.qcell {
  display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(0, 0, 0, 0.28);
  cursor: pointer; user-select: none;
  aspect-ratio: 1 / 1;
  font-size: clamp(18px, 5.2vw, 34px); line-height: 1;
  transition: filter 0.1s;
}
.qcell:active { filter: brightness(1.12); }
/* Dicke Regionsränder (jede innere Grenze genau einmal). */
.qcell.q-rb-l { border-left: 2px solid var(--board-frame); }
.qcell.q-rb-t { border-top: 2px solid var(--board-frame); }
/* Acht gut unterscheidbare Regionsfarben (gedeckt, zum warmen CI passend). */
.qcell.q-r0 { background: #b0492f; }
.qcell.q-r1 { background: #d9a84e; }
.qcell.q-r2 { background: #4e7d6a; }
.qcell.q-r3 { background: #6d5aa8; }
.qcell.q-r4 { background: #c77d3c; }
.qcell.q-r5 { background: #4a6d99; }
.qcell.q-r6 { background: #9c5a7a; }
.qcell.q-r7 { background: #7a8a3c; }
/* Krone: helles Gold mit dunklem Schein -> lesbar auf allen Regionsfarben. */
.qcell.q-crown { color: #ffe6a3; text-shadow: 0 0 3px rgba(0, 0, 0, 0.9), 0 1px 2px rgba(0, 0, 0, 0.75); font-weight: 700; }
/* Merker (✕): dezent, klein. */
.qcell.q-mark { color: rgba(0, 0, 0, 0.55); text-shadow: 0 0 2px rgba(255, 255, 255, 0.35); font-size: clamp(11px, 3vw, 18px); }
/* Regelverstoß: roter Innenrahmen. */
.qcell.q-conflict { box-shadow: inset 0 0 0 3px var(--color-error, #e5484d); }

/* Keypad */
.keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
/* 16×16: 16 Tasten. Am Desktop steht das Ziffernfeld in einer schmalen Spalte
   links -> 4x4-Raster. „N uebrig" ausgeblendet (kein Platz), flachere Tasten. */
.keypad.kp-wide { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; }
.keypad.kp-wide .key { aspect-ratio: 3 / 2; border-radius: 10px; min-width: 0; }
.keypad.kp-wide .key-left { display: none; }
.keypad.kp-wide .key-num { font-size: clamp(13px, 1.6vw, 20px); }
.key {
  aspect-ratio: 1;
  position: relative;
  border: 1px solid var(--ctrl-brd);
  background: var(--ctrl);
  box-shadow: var(--ctrl-sh);
  border-radius: 13px;
  color: var(--text);
  font-family: var(--font-numeric); /* Zahlenpad-Ziffern */
  cursor: pointer;
  transition: transform 0.1s, background 0.2s, border-color 0.2s, box-shadow 0.2s, color 0.2s, opacity 0.25s;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
}
.key-num { font-size: clamp(21px, 2.6vw, 30px); font-weight: 600; color: var(--text); line-height: 1; }
.key-left {
  font-family: var(--font-body);
  font-size: 9px;
  font-weight: 500;
  color: var(--muted);
  letter-spacing: 0.2px;
  line-height: 1;
}
.key:hover {
  background: var(--ctrl-hover);
  border-color: var(--accent);
  box-shadow: var(--ctrl-sh-hover);
}
.key:hover .key-num { color: var(--gold); }
.key:active { transform: scale(0.93); }
/* 1.3: complete = alle 9 gesetzt (kein Hover), over = zu viele/Konflikt */
.key.complete { opacity: 0.4; pointer-events: none; }
/* 5.3d Farbenblind: Häkchen zusätzlich zum Ausblenden (Form, nicht nur Farbe) */
.key.complete::after {
  content: "✓";
  position: absolute;
  top: 3px;
  right: 6px;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-success);
}
.key.over { border-color: var(--color-error); }
/* „Ziffer zuerst" (BEFUND 2.5): die vorgewaehlte Ziffer deutlich markieren. */
.key.armed {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 26%, var(--ctrl, transparent));
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent);
}
/* Timer ausblenden (BEFUND 2.5): visibility statt display -> Layout/Brettgroesse
   (haengt an der HUD-Zeilenhoehe) bleibt unveraendert. */
body.pref-hide-timer .hud-time-stat { visibility: hidden; }
/* 5.3d Farbenblind: Warnzeichen zusätzlich zum roten Rand */
.key.over::after {
  content: "!";
  position: absolute;
  top: 1px;
  right: 7px;
  font-size: 13px;
  font-weight: 800;
  color: var(--color-error);
}

/* Tools */
.tools {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.tool:disabled { opacity: 0.4; cursor: default; box-shadow: none; }
/* Ein aufgebrauchter Hinweis soll auf einen Blick als aufgebraucht lesbar sein.
   Deckkraft allein reicht dafür nicht: Die Glühbirne ist ein Emoji und bleibt
   auch bei 0.4 bunt und einladend — Farbangaben erreichen sie nicht. */
.tool:disabled .tool-ico { filter: grayscale(1); }
/* Die verbleibende Zahl steht golden für „so viele hast du noch". Die Null ist
   keine Restmenge, sondern eine Auskunft — also gedämpft statt golden. */
.tool:disabled .tool-badge {
  background: color-mix(in srgb, var(--muted) 45%, transparent);
  color: var(--text);
}
.tool {
  position: relative; /* Anker fürs Hinweis-Abzeichen */
  border: 1px solid var(--ctrl-brd);
  background: var(--ctrl);
  box-shadow: var(--ctrl-sh);
  border-radius: 13px;
  padding: 11px 4px;
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  transition: background 0.2s, border-color 0.2s, box-shadow 0.2s;
}
/* `:not(:disabled)`, weil `:hover` auch gesperrte Knöpfe trifft und diese Regel
   im Dokument hinter `.tool:disabled` steht: Ohne die Ausnahme leuchtet ein
   aufgebrauchter Hinweis beim Darüberfahren auf, als wäre er noch zu haben. */
.tool:hover:not(:disabled) {
  background: var(--ctrl-hover);
  border-color: var(--accent);
  box-shadow: var(--ctrl-sh-hover);
}
.tool-ico { font-size: 18px; }
.tool-lbl { font-size: 10px; letter-spacing: 0.5px; color: var(--muted); }
.tool.active {
  border-color: var(--accent);
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 30%, transparent), color-mix(in srgb, var(--accent) 14%, transparent));
}
.tool.active .tool-lbl,
.tool.active .tool-ico { color: var(--accent); }

/* Auf Touch-Geräten bleibt `:hover` nach dem Tippen kleben, bis man woanders
   hintippt. Für diese beiden Bedienelemente ist das kein Schönheitsfehler,
   sondern eine Falschaussage: Der Hover-Rand ist `--accent` — dieselbe Farbe,
   mit der `.tool.active` „Notizen sind an" und `.key.armed` „diese Ziffer ist
   vorgewählt" sagen. Wer einen Hinweis geholt hat, sah den Knopf danach
   dauerhaft hervorgehoben und musste raten, ob da noch etwas aktiv ist.
   Gemeldet für X-Sudoku, wo der Akzent ein kräftiges Lila ist und der
   Unterschied deshalb am deutlichsten ins Auge fällt.
   Am Zeigegerät bleibt der Hover unverändert — dort ist er echte Rückmeldung. */
@media (hover: none) {
  /* Die `:not()` sind der Kern und nicht Zierrat: Ohne sie trifft diese
     Zurücknahme auch die ECHTEN Ein-Zustände. Weil sie im Dokument hinter
     `.tool.active` und `.key.armed` steht und dieselbe Spezifität hat, würde
     sie gewinnen — „Notizen an" und die vorgewählte Ziffer wären auf dem Handy
     unsichtbar. Genau das ist beim ersten Versuch passiert und am emulierten
     Gerät aufgefallen: Klasse gesetzt, Rand unverändert. */
  /* `:not(:disabled)` doppelt gemoppelt — die Basisregel oben schliesst
     gesperrte Knöpfe bereits aus. Bleibt trotzdem stehen, damit diese Regel für
     sich gelesen keine Ausnahme aufmacht, die sie nicht meint. */
  .tool:hover:not(.active):not(:disabled) {
    background: var(--ctrl);
    border-color: var(--ctrl-brd);
    box-shadow: var(--ctrl-sh);
  }
  .key:hover:not(.armed):not(.over) {
    background: var(--ctrl);
    border-color: var(--ctrl-brd);
    box-shadow: var(--ctrl-sh);
  }
  .key:hover:not(.armed) .key-num { color: var(--text); }
}

/* Win Overlay */
/* Pause-Button (in der HUD-Spalte) */
.pause-btn {
  border: 1px solid var(--ctrl-brd);
  background: var(--ctrl);
  box-shadow: var(--ctrl-sh);
  color: var(--text);
  border-radius: 16px;
  padding: 14px 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  cursor: pointer;
  font-family: inherit;
  transition: background 0.2s, border-color 0.2s, box-shadow 0.2s;
}
.pause-btn:hover {
  background: var(--ctrl-hover);
  border-color: var(--accent);
  box-shadow: var(--ctrl-sh-hover);
}
.pause-ico { font-size: 18px; color: var(--accent); }
.pause-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: 1.5px; color: var(--muted); }

/* Pause-Overlay (verdeckt das Spielfeld) */
.pause-overlay {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: none;
  align-items: center;
  justify-content: center;
  background: var(--scrim-panel);
  backdrop-filter: blur(14px);
  padding: 24px;
}
.pause-overlay.show { display: flex; animation: fade 0.3s ease; }
.pause-card {
  width: 100%;
  max-width: 340px;
  border-radius: 24px;
  padding: 34px 30px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 13px;
  animation: pop 0.4s cubic-bezier(0.2, 1.3, 0.4, 1);
}
.pause-mark { font-size: 46px; color: var(--accent); line-height: 1; }
/* Fuchs-Portrait im Pause-Overlay: der dunkle Wein-Hintergrund des Bildes
   fliesst in die Glass-Karte, ein dezenter Gold-Ring (wie das Mandala im Bild)
   rahmt es. Auf Mobile etwas kleiner. */
.pause-fox {
  width: 150px;
  height: 150px;
  object-fit: cover;
  border-radius: 22px;
  margin: 0 auto 2px;
  border: 1px solid color-mix(in srgb, var(--gold) 42%, transparent);
  box-shadow: 0 14px 34px -14px rgba(0, 0, 0, 0.7),
    0 0 0 5px color-mix(in srgb, var(--gold) 9%, transparent);
}
@media (max-width: 560px) {
  .pause-fox { width: 118px; height: 118px; border-radius: 18px; }
}

/* Versionsanzeige im Footer (nur eingeloggt) — führt zu den Änderungsnotizen. */
.version-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-size: 12px;
  color: var(--muted);
  cursor: pointer;
  opacity: 0.75;
}
.version-link:hover { color: var(--gold); opacity: 1; }
.version-link:empty { display: none; }

/* Restliche Hinweise als Abzeichen — hält die Kachel einzeilig wie die anderen. */
.tool-badge {
  position: absolute;
  top: 3px;
  right: 5px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--gold) 85%, transparent);
  color: #2a1206;
  font-size: 10px;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
}
.tool-badge:empty { display: none; }

/* Hinweis-Zeitstrafe im Gewonnen-Overlay */
.win-hint-note {
  margin-top: 8px;
  font-size: 12px;
  color: var(--muted);
  text-align: center;
}

/* Knopf mit Zeichen: Ohne Abstand klebte das Google-G an der Beschriftung
   („GMit Google anmelden"). Flex statt reinem Textfluss, damit Zeichen und
   Text eine Zeile bilden und mittig stehen. */
.btn-mit-zeichen {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.btn-zeichen { flex: 0 0 auto; display: block; }

/* Die Ueberschrift des Anmeldebildschirms. Er hatte gar keine: erst ein
   220px-Bild, dann Fliesstext. */
.auth-titel {
  margin: 0 0 10px;
  font-size: clamp(22px, 6vw, 30px);
  line-height: 1.15;
  text-wrap: balance;
}

/* ---- Profilbild zuschneiden ---- */
/* Liegt über den Einstellungen (.profile-overlay, z-index 110) — von dort
   aus wird der Zuschnitt geöffnet. */
#crop-overlay { z-index: 120; }

/* ---- Abmelden bestätigen ---- */
/* Aus demselben Grund wie der Zuschnitt darüber: Der Dialog wird aus den
   Einstellungen heraus geöffnet (`#set-logout`) und muss deshalb über ihnen
   liegen.

   Er erbte von `.pause-overlay` den z-index 90 — gedacht für die Spielpause,
   wo nichts darüber liegt. In den Einstellungen war die Folge: Auf „Abmelden“
   tippen, und es passiert scheinbar nichts. Die Sicherheitsabfrage stand die
   ganze Zeit dahinter und tauchte erst auf, nachdem man die Einstellungen
   geschlossen hatte — also an einer Stelle, an der man sie nicht mehr
   erwartet und ihr Ja nicht mehr sicher zuordnen kann.

   Nicht durch Schließen der Einstellungen gelöst: Wer „Nein“ wählt, soll dort
   weitermachen, wo er war, und nicht auf der Startseite landen. */
#logout-overlay { z-index: 120; }

/* ---- Rang-Aufstieg: der Freude-Moment ---- */
.rankup-overlay {
  position: fixed; inset: 0; z-index: 130; /* über dem Gewinn-Overlay (100) */
  display: none; align-items: center; justify-content: center;
  padding: 24px; background: var(--scrim-panel); backdrop-filter: blur(14px);
}
.rankup-overlay.show { display: flex; animation: fade 0.3s ease; }
.rankup-card {
  position: relative; z-index: 1;
  width: 100%; max-width: 340px; border-radius: 24px;
  padding: 30px 28px; text-align: center;
  display: flex; flex-direction: column; gap: 12px; align-items: center;
  animation: rankup-card-in 0.5s cubic-bezier(0.2, 1.3, 0.4, 1);
}
.rankup-eyebrow {
  font-size: 13px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--gold); font-weight: 700;
}
.rankup-imgwrap { position: relative; width: 148px; height: 148px; }
.rankup-img {
  width: 100%; height: 100%; border-radius: 50%; object-fit: cover;
  border: 3px solid color-mix(in srgb, var(--gold) 70%, transparent);
  animation: rankup-pop 0.7s cubic-bezier(0.2, 1.4, 0.4, 1) 0.1s both;
}
/* Goldener Strahlenkranz, der einmal aufblitzt und pulsiert. */
.rankup-imgwrap::before {
  content: ""; position: absolute; inset: -14px; border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--gold) 55%, transparent) 0%, transparent 68%);
  animation: rankup-glow 1.8s ease-in-out infinite;
  z-index: -1;
}
.rankup-name { font-family: var(--font-display); font-size: 24px; color: var(--text); }
.rankup-sub { font-size: 13px; color: var(--muted); margin-bottom: 4px; }
/* Erfolg-Feier: großes Emoji-Abzeichen + Aktionsreihe (Teilen/Weiter). */
.ach-cel-icon { font-size: 68px; line-height: 1; margin: 4px 0 2px;
  filter: drop-shadow(0 6px 16px color-mix(in srgb, var(--gold) 45%, transparent)); }
.ach-cel-actions { display: flex; gap: 10px; width: 100%; margin-top: 6px; }
.ach-cel-actions .btn { flex: 1; }

@keyframes rankup-card-in {
  from { opacity: 0; transform: translateY(24px) scale(0.94); }
  to   { opacity: 1; transform: none; }
}
@keyframes rankup-pop {
  0%   { transform: scale(0.3) rotate(-12deg); opacity: 0; }
  60%  { transform: scale(1.12) rotate(4deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); }
}
@keyframes rankup-glow {
  0%, 100% { opacity: 0.5; transform: scale(1); }
  50%      { opacity: 0.9; transform: scale(1.08); }
}
/* Reduced Motion: kein Springen/Pulsieren, nur ruhig einblenden. */
@media (prefers-reduced-motion: reduce) {
  .rankup-card, .rankup-img { animation: none !important; }
  .rankup-imgwrap::before { animation: none !important; opacity: 0.6; }
}
.crop-card { max-width: 360px; }
.crop-stage {
  position: relative;
  width: 288px;
  max-width: 100%;
  aspect-ratio: 1;
  margin: 4px auto 0;
  border-radius: 16px;
  overflow: hidden;
  background: #000;
}
.crop-stage canvas {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none; /* Gesten gehören dem Cropper, nicht dem Scroll */
  cursor: grab;
}
.crop-stage canvas:active { cursor: grabbing; }
/* Kreis-Vorschau: außerhalb abdunkeln, Rand andeuten. Klicks gehen durch.
   (Gespeichert wird das volle Quadrat — der Kreis zeigt nur, wie es rund
   dargestellt aussieht.) */
.crop-mask {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: 16px;
  background: radial-gradient(
    circle at 50% 50%,
    transparent 0 49.4%,
    color-mix(in srgb, #000 55%, transparent) 50%
  );
}
.crop-mask::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--gold) 70%, transparent);
}
.crop-zoom { display: flex; align-items: center; gap: 12px; margin-top: 4px; }
.crop-zoom-lbl { font-size: 12px; color: var(--muted); }
/* Gleiche Regler-Optik wie die Lautstärke in den Einstellungen. */
.crop-zoom input[type="range"] { flex: 1; accent-color: var(--color-primary); }

/* Difficulty-Accent ist bei Schwer/Extrem rot und als Schrift schlecht lesbar.
   Text in Daily-Card & Spiel-Screen daher auf Gold — hebt sich weiter schön ab;
   Ränder/Glows bleiben in der Difficulty-Farbe (Stimmung). */
.daily-done,
.game-diff,
.tool.active .tool-lbl,
.tool.active .tool-ico,
.pause-mark,
.pause-ico { color: var(--gold); }
.pause-title {
  font-family: var(--font-display);
  font-size: 34px;
  font-weight: 700;
}
.pause-sub { color: var(--muted); font-size: 13px; margin-top: -8px; }
.pause-time { display: flex; flex-direction: column; gap: 2px; margin: 4px 0 6px; }

/* Profil-Popup (schnellste Zeiten je Schwierigkeit) */
.rank-row.clickable { cursor: pointer; }
.rank-row.clickable:hover {
  background: color-mix(in srgb, var(--accent) 14%, var(--surface-1));
}
.rank-row.clickable:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.profile-overlay {
  position: fixed;
  inset: 0;
  z-index: 110;
  display: none;
  align-items: center;
  justify-content: center;
  background: var(--scrim-panel);
  backdrop-filter: blur(12px);
  padding: 20px;
}
.profile-overlay.show { display: flex; animation: fade 0.25s ease; }
/* Nicht die KARTE scrollt, sondern ihr Inhalt. Vorher trug die Karte selbst
   `overflow-y: auto`, und der absolut gesetzte Schließen-Knopf saß mit darin —
   bei einem zehn Bildschirme langen Rechtstext fuhr er nach oben aus dem Bild,
   und ab Abschnitt 2 gab es am Handy kein sichtbares „Zurück" mehr. Jetzt ist
   die Karte eine Spalte mit fester Höhe, der Knopf bleibt stehen, und das
   Scrollen übernimmt `.profile-body` / `.help-body`. */
.profile-card {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 460px;
  max-height: 86vh;
  overflow: hidden;
  border-radius: 24px;
  padding: 26px 24px 22px;
  animation: pop 0.35s cubic-bezier(0.2, 1.2, 0.4, 1);
}
/* BEFUND-2 3.7: Die Statistik/Profil-Ansicht hat viel Inhalt (Werte je Stufe,
   Erfolge) und wirkte im schmalen 460er-Overlay gequetscht -> breiter. */
#profile-overlay .profile-card { max-width: 640px; }
.profile-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--panel-brd);
  background: var(--surface-1);
  color: var(--muted);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
}
.profile-close:hover { color: var(--text); border-color: var(--accent); }
.profile-head {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
  padding-right: 40px;
}
.profile-avatar {
  flex: none;
  width: 66px;
  height: 66px;
  border-radius: 16px;
  object-fit: cover;
  border: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
}
.profile-head-text { min-width: 0; }
.profile-eyebrow {
  font-size: 11px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--color-primary-text);
  font-weight: 700;
}
.profile-title {
  font-family: var(--font-display);
  /* Feste 30px hielten „Datenschutzerklärung" nicht aus: Bei 393px Fenster
     bleiben dem Titel rund 265px (die Karte hält rechts 40px für den
     Schließen-Knopf frei), das Wort braucht etwa 330 — es lief über den
     Kartenrand hinaus, das „g" war abgeschnitten.

     Die Obergrenze ist gemessen, nicht geraten: Bei 393px stehen dem Titel
     263px zur Verfügung, und „Datenschutzerklärung" braucht 249px bei 22px
     Schriftgröße (272px bereits bei 24px). 5.5vw ergibt bei 393px genau
     21.6px. `hyphens` und `overflow-wrap` sind das Netz für den Fall, dass
     doch einmal ein längerer Titel kommt — dann wird wenigstens getrennt
     statt mitten im Wort abgehackt. */
  font-size: clamp(20px, 5.5vw, 30px);
  hyphens: auto;
  overflow-wrap: break-word;
  margin: 2px 0 0;
  font-weight: 700;
}
/* `min-height: 0` ist hier kein Feinschliff, sondern Bedingung: Ein Flex-Kind
   schrumpft ohne diese Zeile nicht unter seine Inhaltshoehe, und dann liefe
   `overflow-y: auto` ins Leere -- die Karte waere wieder so hoch wie ihr Text
   und der Schliessen-Knopf wieder weg. */
.profile-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  overflow-y: auto;
  min-height: 0;
}
/* ZWEI MELDUNGEN AUS DEM EINGANG, EINE URSACHE: „feedback eingabe fenster
   wird links und rechts abgeschnitten" und „Scroll balken beim feedback sieht
   kake aus".

   Beide Koerper oben tragen nur `overflow-y: auto`. Nach CSS-Regel hebt das
   die andere Achse von `visible` auf `auto` -- eine Angabe fuer senkrecht
   macht die Waagerechte stillschweigend zum Schneiderahmen. Ihre Kinder sind
   volle Breite, also wird alles abgeschnitten, was ueber die Kante hinaus
   zeichnet: der Fokusrahmen des Textbereichs (`outline` 2px bei
   `outline-offset` 2px, also vier Pixel draussen) und der Schatten des
   Absenden-Knopfes.

   Innen Luft, aussen zurueckgenommen: Die Inhaltsbreite bleibt damit auf den
   Pixel dieselbe -- ohne den negativen Rand waere jedes Overlay der App
   zwoelf Pixel schmaler geworden. Test 263b vergleicht die beiden Zahlen.

   Der Scrollbalken darf hier NICHT verschwinden, anders als bei den
   waagerechten Leisten mit `scrollbar-width: none`: Er ist der einzige
   Hinweis darauf, dass unter dem Formular noch etwas steht. Er soll nur
   aussehen wie der Rest der App. */
.help-body, .profile-body {
  padding-inline: 6px;
  margin-inline: -6px;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--muted) 45%, transparent) transparent;
}
/* Fuer Safari vor 18.2, das `scrollbar-width` noch nicht kennt. Dieselbe
   Doppelung wie bei den waagerechten Leisten weiter oben. */
.help-body::-webkit-scrollbar, .profile-body::-webkit-scrollbar { width: 8px; }
.help-body::-webkit-scrollbar-track, .profile-body::-webkit-scrollbar-track { background: transparent; }
.help-body::-webkit-scrollbar-thumb, .profile-body::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--muted) 45%, transparent);
  border-radius: 4px;
}
.prof-block-title {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--accent);
  font-weight: 700;
  margin: 8px 0 2px;
}
.prof-block-title:first-child { margin-top: 0; }
.prof-times-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}
.prof-hint { font-size: 12px; color: var(--muted); margin: -2px 0 8px; }

/* Home-Button zum eigenen Profil (Statistik & Erfolge) */
.stats-link {
  display: block;
  width: 100%;
  margin-top: 14px;
  padding: 11px;
  border: 1px solid var(--panel-brd);
  background: var(--surface-1);
  color: var(--accent);
  font-family: inherit;
  font-weight: 600;
  font-size: 13px;
  border-radius: 11px;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}
.stats-link:hover { border-color: var(--accent); background: var(--surface-2); }

/* Statistik-Block im Profil */
.stat-tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 12px; }
.stat-tile {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 10px 4px; border-radius: 10px;
  background: var(--surface-1); border: 1px solid var(--panel-brd);
}
.st-num { font-family: var(--font-numeric); font-size: 23px; font-weight: 700; color: var(--text); }
.st-lbl { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
.stat-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.stat-table th { text-align: left; font-size: 10px; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); font-weight: 700; padding: 4px 6px; }
.stat-table td { padding: 6px; border-top: 1px solid var(--panel-brd); font-variant-numeric: tabular-nums; color: var(--text); }
.stat-table td:first-child { font-weight: 600; }
.stat-table th:not(:first-child), .stat-table td:not(:first-child) { text-align: right; }
.stat-daily { margin-top: 11px; font-size: 12px; color: var(--muted); }
.stat-daily b { color: var(--text); }

/* Erfolge */
/* ---- Rang-Sammlung: Kopfzeile + Raster (statt langer Liste) ---- */
.rankhead {
  display: flex;
  gap: 14px;
  align-items: center;
  padding: 14px;
  border-radius: var(--radius-lg);
  background: var(--surface-1);
  border: 1px solid var(--panel-brd);
  margin-bottom: 12px;
}
.rankhead-img {
  width: 68px; height: 68px; flex: none;
  border-radius: 50%; object-fit: cover;
  border: 2px solid color-mix(in srgb, var(--gold) 60%, transparent);
}
.rankhead-txt { min-width: 0; flex: 1; }
.rankhead-score { font-size: 11px; color: var(--muted); letter-spacing: 0.5px; }
.rankhead-name { font-family: var(--font-display); font-size: 19px; color: var(--text); }
.rankhead-sub { font-size: 11px; color: var(--muted); margin-bottom: 6px; }
.rankhead-bar {
  height: 6px; border-radius: 3px; overflow: hidden;
  background: color-mix(in srgb, var(--text) 12%, transparent);
}
.rankhead-bar span { display: block; height: 100%; background: var(--gold); }
.rankhead-next { font-size: 12px; color: var(--muted); margin-top: 5px; display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.rankhead-nextimg { width: 18px; height: 18px; border-radius: 50%; object-fit: cover; filter: brightness(0.55); }

/* 5 Kacheln je Reihe -> 19 Ränge sind vier Reihen statt 19 Zeilen. */
.rank-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
.rank-tile {
  position: relative;
  padding: 0; border: 0; background: none; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
}
.rank-tile img {
  /* Siehe .diff-img: Ohne height:auto haelt das height-Attribut (512) die
     Hoehe fest und `aspect-ratio` bleibt wirkungslos. Gemessen waren die
     Rang-Kacheln im Profil 128 x 514 statt quadratisch. */
  width: 100%; height: auto; aspect-ratio: 1; object-fit: cover;
  border-radius: 12px;
  border: 1px solid var(--panel-brd);
}
.rank-tile-name {
  font-size: 9px; line-height: 1.15; color: var(--muted);
  text-align: center; overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
/* Gesperrt: abgedunkelt ZEIGEN — man soll sehen, was einen erwartet. */
.rank-tile.locked img { filter: brightness(0.32) saturate(0.5); }
.rank-lock {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -60%);
  font-size: 15px; opacity: 0.85; pointer-events: none;
}
.rank-sheet-img {
  width: 120px; height: 120px; object-fit: cover;
  border-radius: 16px; margin: 0 auto 10px; display: block;
  border: 2px solid color-mix(in srgb, var(--gold) 50%, transparent);
}
.rank-sheet-img.locked { filter: brightness(0.32) saturate(0.5); }
@media (max-width: 380px) { .rank-grid { grid-template-columns: repeat(4, 1fr); } }

.ach-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
.ach {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 9px;
  padding: 8px 10px; border-radius: 10px;
  background: var(--surface-1); border: 1px solid var(--panel-brd);
  opacity: 0.45;
}
.ach.got {
  opacity: 1;
  background: color-mix(in srgb, var(--accent) 11%, transparent);
  border-color: color-mix(in srgb, var(--accent) 38%, transparent);
}
.ach-ico { font-size: 20px; }
.ach-txt { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.ach-txt b { font-size: 13px; color: var(--text); }
.ach-desc { font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ach-check { color: var(--accent); font-weight: 800; font-size: 15px; }
.ach-prog { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* 4.1: Badges klickbar (Button-Reset, Locked-Silhouette) */
.ach { cursor: pointer; width: 100%; font: inherit; color: inherit; text-align: left; appearance: none; -webkit-appearance: none; transition: border-color 0.15s, opacity 0.15s; }
.ach.locked .ach-ico { filter: grayscale(1); }
.ach:hover { border-color: var(--accent); }
.ach:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

/* 4.1: Meilenstein-Sheet (Bottom-Sheet mobil / Popover Desktop) */
.ms-sheet-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: none;
  align-items: flex-end;
  justify-content: center;
  background: var(--scrim);
}
.ms-sheet-overlay.show { display: flex; animation: fade 0.2s ease; }
.ms-sheet {
  position: relative;
  width: 100%;
  max-width: 460px;
  /* HOEHER, und NICHT DIE KARTE SCROLLT, SONDERN IHR INHALT.

     Der Hinweis auf die freien Bilder zeigt ein Gitter aus elf Kacheln; mit
     der alten Hoehe blieb davon eine Reihe uebrig. `90vh` laesst oben genug
     stehen, dass man sieht, worueber das Sheet liegt.

     DER ERSTE ANLAUF SETZTE `overflow-y: auto` HIERHER, und das war derselbe
     Fehler, der rund 270 Zeilen weiter oben bei `.profile-card` schon einmal
     behoben ist: Der Schliessen-Knopf ist absolut gesetzt und sitzt mit in
     der scrollenden Flaeche -- bei langem Inhalt faehrt er nach oben aus dem
     Bild, und am Handy gibt es kein sichtbares „Zurueck" mehr. Aus der
     Sicherheitspruefung zu 1.17.152, die den Praezedenzfall gefunden hat.

     Also dieselbe Bauweise wie dort: die Karte klemmt und scrollt nicht, der
     Koerper scrollt. `min-height: 0` steht am Koerper, sonst weigert sich das
     Flex-Kind zu schrumpfen und die Karte waechst doch ueber die Grenze. */
  /* ZWEIMAL, und die Reihenfolge ist der Punkt: `dvh` gewinnt, wo es
     verstanden wird. `.ms-sheet-overlay` ist `position: fixed` und liegt bei
     eingeblendeter Safari-Werkzeugleiste auf dem KLEINEN Viewport, waehrend
     `vh` gegen den grossen misst. Der Ueberschuss laeuft dann oben heraus --
     und oben sitzt der Schliessen-Knopf. Aus der Sicherheitspruefung zu
     1.17.152. */
  max-height: 90vh;
  max-height: 90dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 30px 24px 34px;
  border-radius: 20px 20px 0 0;
  text-align: center;
  animation: sheet-up 0.25s var(--ease-out-quart);
}
@keyframes sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
@media (min-width: 640px) {
  .ms-sheet-overlay { align-items: center; }
  .ms-sheet { border-radius: 20px; max-width: 380px; animation: fade 0.2s ease; }
}
.ms-ico { font-size: 56px; line-height: 1; margin-bottom: 8px; }
.ms-ico.locked { filter: grayscale(1); opacity: 0.4; }
/* Der scrollende Koerper des Sheets, siehe die Begruendung an `.ms-sheet`. */
#ms-sheet-body {
  overflow-y: auto;
  min-height: 0;
  overscroll-behavior: contain;
  /* `overflow-y` hebt die Waagerechte per CSS-Regel auf `auto` und macht sie
     zum Schneiderahmen -- der goldene Ring der gewaehlten Kachel
     (`box-shadow 0 0 0 3px`) und die Fokus-Umrandung wuerden angeschnitten.
     Dieselbe Loesung wie bei `.profile-body`: innen Luft, aussen
     zurueckgenommen. */
  padding-inline: 6px;
  margin-inline: -6px;
  scrollbar-width: thin;
}
.ms-name { font-family: var(--font-display); font-size: 22px; font-weight: 600; color: var(--text); }
.ms-req { font-size: 14px; color: var(--muted); margin: 8px 0 16px; }

/* Der Erklaersatz im Hinweis auf die freien Profilbilder.

   NICHT `.ms-req`, obwohl er an derselben Stelle steht. Jene Klasse gehoert
   zum Rang-Blatt und traegt `--muted`: dort steht eine Nebenangabe („Rang 4
   · ab 18.000 Punkten") unter einer grossen Ueberschrift. Hier ist der Satz
   die eigentliche Nachricht, und gedaempft ueber dem durchscheinenden
   Hintergrund des Sheets war er im Bild bei 390 px kaum zu lesen -- gesehen
   im Screenshot, nicht im Code. */
.av-hinweis-text {
  font-size: 15px;
  line-height: 1.45;
  color: var(--text);
  margin: 10px 0 18px;
}
.ms-bar { height: 8px; border-radius: var(--radius-pill); background: var(--surface-2); overflow: hidden; }
.ms-bar > span { display: block; height: 100%; background: var(--color-primary); border-radius: var(--radius-pill); }
.ms-prog { font-size: 13px; color: var(--muted); margin-top: 8px; font-variant-numeric: tabular-nums; }
.ms-date { font-size: 13px; color: var(--color-success); margin-top: 12px; font-weight: 600; }

/* 4.4: Onboarding */
.onb-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: var(--color-bg-base);
}
.onb-overlay.show { display: flex; animation: fade 0.3s ease; }
.onb-card { width: 100%; max-width: 380px; padding: 32px 26px; text-align: center; border-radius: 24px; }
.onb-logo { width: 92px; height: 92px; border-radius: 20px; object-fit: cover; margin-bottom: 14px; }
.onb-title { font-family: var(--font-display); font-size: 24px; font-weight: 600; color: var(--text); margin: 0 0 8px; text-wrap: balance; }
.onb-sub { font-size: 14px; color: var(--muted); line-height: 1.55; margin: 0 0 20px; }
.onb-demo { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; width: 208px; margin: 0 auto 14px; }
.onb-cell {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-numeric);
  font-size: 22px;
  font-weight: 500;
  border: 1px solid var(--hairline-soft);
  border-radius: 8px;
  background: var(--surface-1);
  color: var(--num-user);
  cursor: pointer;
  transition: background 0.12s;
}
.onb-cell.given { color: var(--num-given); font-weight: 600; }
.onb-cell.peer { background: var(--cell-peer); }
.onb-cell.sel { background: var(--cell-selected); box-shadow: inset 0 0 0 1px var(--color-primary); }
.onb-cell.correct { background: var(--cell-success); color: var(--color-success); }
.onb-cell.shake { animation: shake 0.3s; }
.onb-demo-pad { display: flex; gap: 8px; justify-content: center; margin-bottom: 14px; }
.onb-key {
  width: 46px;
  height: 46px;
  border-radius: 10px;
  font-family: var(--font-numeric);
  font-size: 20px;
  font-weight: 600;
  background: var(--ctrl);
  border: 1px solid var(--ctrl-brd);
  color: var(--accent);
  cursor: pointer;
}
.onb-key:active { transform: scale(0.93); }
@media (max-width: 560px) { .ach-grid { grid-template-columns: 1fr; } }
.prof-section {
  background: var(--surface-1);
  border: 1px solid var(--panel-brd);
  border-left: 3px solid var(--accent);
  border-radius: 12px;
  padding: 12px 14px;
}
.prof-diff {
  font-weight: 700;
  font-size: 13px;
  color: var(--accent);
  margin-bottom: 8px;
  letter-spacing: 0.5px;
}
.prof-time {
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
}
.prof-rank {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 24%, transparent);
  color: var(--text);
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 700;
}
.prof-clock { font-variant-numeric: tabular-nums; font-weight: 700; font-size: 15px; }
.prof-lvl { color: var(--muted); font-size: 12px; }
.prof-stars { font-size: 11px; letter-spacing: 1px; white-space: nowrap; }
.prof-empty { color: var(--muted); font-size: 12px; font-style: italic; padding: 2px 0; }

@media (max-width: 560px) {
  .prof-times-grid { grid-template-columns: 1fr; }
}

.win-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: none;
  align-items: center;
  justify-content: center;
  background: var(--scrim-panel);
  backdrop-filter: blur(8px);
  padding: 24px;
}
.win-overlay.show { display: flex; animation: fade 0.4s ease; }
.confetti { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.win-card {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 400px;
  border-radius: 24px;
  padding: 36px 32px;
  text-align: center;
  animation: pop 0.5s cubic-bezier(0.2, 1.3, 0.4, 1);
}
@keyframes pop {
  from { transform: scale(0.8); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}
.win-rankup {
  display: none;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  margin-bottom: 18px;
  border-radius: 16px;
  text-align: left;
  background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 26%, transparent), color-mix(in srgb, var(--accent2) 22%, transparent));
  border: 1px solid color-mix(in srgb, var(--accent) 50%, transparent);
}
.win-rankup.show { display: flex; animation: rankPulse 0.6s cubic-bezier(0.2, 1.3, 0.4, 1); }
.win-rankup-img {
  flex: none;
  width: 58px;
  height: 58px;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid var(--accent);
  box-shadow: 0 0 20px -2px color-mix(in srgb, var(--accent) 75%, transparent);
}
.win-rankup-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.win-rankup-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--accent);
  font-weight: 700;
}
.win-rankup-name {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 700;
  color: var(--text);
}
@keyframes rankPulse {
  0% { transform: scale(0.8); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}

.win-title {
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  margin-bottom: 10px;
  background: linear-gradient(135deg, var(--gold), var(--accent2));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.win-stars { font-size: 36px; letter-spacing: 6px; margin-bottom: 16px; }
.win-stars .star { color: var(--star-empty); }
.win-stars .star.on {
  color: var(--gold);
  text-shadow: 0 0 18px rgba(255, 210, 63, 0.7);
  animation: starpop 0.5s ease backwards;
}
.win-stars .star.on:nth-child(1) { animation-delay: 0.1s; }
.win-stars .star.on:nth-child(2) { animation-delay: 0.25s; }
.win-stars .star.on:nth-child(3) { animation-delay: 0.4s; }
@keyframes starpop {
  from { transform: scale(0) rotate(-40deg); opacity: 0; }
  to { transform: scale(1) rotate(0); opacity: 1; }
}
.win-score-big {
  font-size: 52px;
  font-weight: 700;
  font-family: var(--font-display);
  line-height: 1;
}
.win-score-big small {
  display: block;
  font-size: 12px;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--muted);
  font-family: var(--font-body);
  margin-top: 4px;
}
.win-best {
  color: var(--gold);
  font-weight: 600;
  font-size: 13px;
  min-height: 18px;
  margin-top: 8px;
}
.win-stats {
  display: flex;
  justify-content: center;
  gap: 32px;
  margin: 20px 0 26px;
}
.win-stats > div { display: flex; flex-direction: column; gap: 3px; }
.ws-label { font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); }
.ws-val { font-size: 22px; font-weight: 600; }
/* Die beiden Store-Hinweise im Gewonnen-Overlay (index.html, #win-app und
   #win-bewerten). Bewusst dieselbe Bauform wie der Ticker auf der Startseite
   -- Zeichen, Text, Knopf in einer Zeile: Wer den Ticker kennt, erkennt hier
   einen Hinweis und keine Aufforderung.

   ABGESETZT NACH OBEN durch eine duenne Linie, nicht durch eine eigene Karte.
   Eine Karte in der Karte machte aus dem Hinweis ein zweites Ergebnis, und
   das Ergebnis ist das, was oben steht.

   Auf schmalen Geraeten bricht der Text in eine eigene Zeile (genau wie beim
   Ticker), damit der Knopf nicht auf zwei Zeichen zusammenschrumpft. Das
   Schliesskreuz behaelt seine Groesse: Es ist die Zusicherung, dass der
   Hinweis nur einmal kommt, und ein zu kleines Ziel macht sie wertlos. */
.win-store {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--panel-brd);
  text-align: left;
}
.win-store[hidden] { display: none; }
.win-store-icon { font-size: 18px; flex: none; line-height: 1; }
.win-store-text { flex: 1 1 auto; font-family: var(--font-body); font-size: 13px; font-weight: 400; color: var(--muted); }
.win-store-btn { flex: none; white-space: nowrap; }
/* 44x44 ist das kleinste Ziel, das Apple fuer antippbare Flaechen nennt --
   und ein Kreuz, das man dreimal treffen muss, ist schlimmer als keins. */
.win-store-zu {
  flex: none;
  width: 44px;
  height: 44px;
  margin: -10px -12px -10px 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.win-store-zu:hover { color: var(--text); }
@media (max-width: 560px) {
  /* GEMESSEN im Handy-Viewport (393x852): In der Karte bleiben rund 280 px
     Innenbreite. Zeichen, Knopf und Kreuz brauchen davon knapp 180 -- fuer
     den Text blieben hundert, und „Sudoku Sagax gibt es auch als App" stuende
     vierzeilig neben einem Knopf.

     Deshalb der Text auf eine eigene erste Zeile und darunter Zeichen, Knopf
     und Kreuz. Der Knopf bleibt dabei SCHMAL. Ueber die volle Breite gezogen
     sah er aus wie „Nächstes ›" darunter, also wie die erwartete Handlung --
     und das ist er nicht: Es ist ein Hinweis, kein Angebot. */
  .win-store { flex-wrap: wrap; gap: 8px 10px; }
  .win-store-icon { order: 1; }
  /* Die Breite ist gerechnet und einmal nachgemessen: Zeichen plus Abstand
     brauchen rund 32 px, ein Emoji ist breiter als seine Schriftgroesse. Mit
     `calc(100% - 28px)` passte das Zeichen NICHT mehr in dieselbe Zeile und
     stand allein darueber -- gesehen im Handy-Viewport, nicht gerechnet.
     40 px lassen Luft; die zweite Zeile traegt dann Knopf und Kreuz. */
  .win-store-text { order: 2; flex: 1 1 calc(100% - 40px); font-size: 12px; }
  .win-store-btn { order: 3; }
  .win-store-zu { order: 4; margin-left: auto; }
}
.win-actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.win-actions .btn { flex: 1 1 40%; padding: 12px 8px; font-size: 14px; }
.win-actions .btn-primary { flex-basis: 100%; order: 9; }

/* 3.3: Level-Fortschrittsbalken im Solved-Screen */
.win-levelbar { margin: 4px 0 14px; }
.win-lv-head { display: flex; justify-content: space-between; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
.win-lv-head span:first-child { color: var(--text); font-weight: 600; }
.win-lv-track { height: 8px; border-radius: var(--radius-pill); background: var(--surface-2); overflow: hidden; }
.win-lv-fill { display: block; height: 100%; width: 0; border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--color-primary), var(--color-primary-hover));
  transition: width 0.85s var(--ease-out-quart); }

/* ---- Responsive ---- */
@media (max-width: 760px) {
  /* Spiel-Kopfzeile schlanker und nicht sticky, damit Feld + Buttons
     komplett auf den Bildschirm passen. */
  #screen-game .topbar { position: static; padding: 5px 14px; }
  /* Kopfzeile schlank: der Platz gehört dem Spielbrett. */
  #screen-game .game-diff { font-size: 10px; }
  #screen-game .game-level { font-size: 15px; }
  /* Der Spielscreen füllt genau einen Bildschirm: Kopfzeile + Spielbereich.
     Vorher hatte allein .game-wrap min-height:100svh — PLUS Kopfzeile ergab
     das immer eine Kopfzeilenhöhe Überhang, den man wegscrollen musste,
     um an die Werkzeuge zu kommen. */
  /* EIN BUDGETPOSTEN FUER DIE SICHEREN BEREICHE, drei Bildschirme.

     Am iPhone 16 war im Spiel die unterste Reihe angeschnitten (30.08.2026,
     erster echter Geraetetest). Die Paddings oben und unten waren da; was
     fehlte, war der Abzug in der BRETT-RECHNUNG darunter. Die 292px (bei
     Queens 210px) sind das Budget fuer alles ausser dem Brett -- ohne die
     Einzuege sind das auf einem Geraet mit Dynamic Island und Home-Indikator
     rund 93px zu viel, und genau so viel schiebt sich unten aus dem Bild.

     Im Browser und im Simulator ohne Notch ist `env(...)` gleich 0, dort war
     nichts zu sehen. Deshalb steht die Groesse hier EINMAL und wird von allen
     drei Bildschirmen benutzt: Zwei zu flicken und den dritten zu vergessen
     ist der Fehler, der an diesem Tag schon viermal passiert ist.

     Der Rueckfall `, 0px` ist keine Zierde: Ohne ihn liefert `env()` auf
     Geraeten ohne sichere Bereiche gar nichts, und ein `calc()` mit leerem
     Glied ist UNGUELTIG -- das Brett haette dann ueberhaupt keine Groesse. */
  #screen-game.active,
  #screen-binary.active,
  #screen-queens.active {
    --sicher-hoch: calc(env(safe-area-inset-top, 0px) + env(safe-area-inset-bottom, 0px));
  }
  #screen-game.active {
    display: flex;
    flex-direction: column;
    min-height: 100svh;
    /* BEFUND 4.4: viewport-fit=cover -> Inhalt reicht sonst in den iPhone-
       Gestenbereich (Home-Indicator) bzw. unter die Notch. Kopfzeile absetzen. */
    padding-top: env(safe-area-inset-top);
  }

  /* ---- IN DER APP RECHNET CSS FALSCH, und das ist gemessen, nicht vermutet.

     Auf einem iPhone 16 meldet die WebView (30.08.2026, Messung am Geraet):

         window.innerHeight   759
         100svh               852
         env(top) 59, env(bottom) 34

     Die WebView beginnt also BEREITS unterhalb der Dynamic Island und endet
     ueber dem Home-Indikator -- CSS meldet trotzdem die volle Bildschirm-
     hoehe. Damit war jede `100svh`-Regel um genau 93px zu gross, UND das
     `padding-top: env(...)` darueber zaehlte dieselben 59px ein zweites Mal.
     Beides zusammen: der leere Streifen oben und die abgeschnittene
     Werkzeugreihe unten.

     Im Browser gilt das NICHT: dort reicht der Viewport bei `viewport-fit=cover`
     wirklich unter die sicheren Bereiche, und das Padding ist richtig. Deshalb
     haengt die Korrektur an `body.ist-app` und nicht an einer Bildschirmbreite.

     WENN SICH DIE HUELLE AENDERT (Capacitor-Einstellung `contentInset`, eine
     neue Capacitor-Fassung), muss diese Messung wiederholt werden: Sobald die
     WebView doch unter die sicheren Bereiche reicht, ist die Korrektur hier
     falsch herum. ---- */
  body.ist-app #screen-game.active,
  body.ist-app #screen-binary.active,
  body.ist-app #screen-queens.active {
    min-height: calc(100svh - var(--sicher-hoch, 0px));
    padding-top: 0;
  }

  /* ---- UND DER FALL, DEN DER KOMMENTAR OBEN VORHERGESAGT HAT.

     Dort steht: „Sobald die WebView doch unter die sicheren Bereiche reicht,
     ist die Korrektur hier falsch herum." Genau das ist am 08.09.2026
     eingetreten, gemeldet von Amadeus mit einem Bildschirmfoto vom Pixel 9
     Pro -- Systemuhr ueber dem Logo, Akkustand ueber dem Zahnrad, die
     Einstellungen unerreichbar.

     ES LIEGT NICHT AN DER PLATTFORM, SONDERN AN DER WEBVIEW-FASSUNG. Capacitor
     reicht die sicheren Bereiche erst ab WebView 140 durch (`SystemBars.java`,
     `WEBVIEW_VERSION_WITH_SAFE_AREA_FIX`). Auf einem aelteren Android-Telefon
     und auf iOS beginnt die WebView unterhalb der Leiste, auf einem neueren
     nicht. Dasselbe Paket, zwei Verhalten.

     `body.ist-app.randlos` sagt, welche Lage vorliegt; die Entscheidung steht in
     js/sicherer-rand.js. Sie haengt seit 1.17.158 an zwei Dingen: Android, und
     `env(safe-area-inset-top)` groesser null. Die Fassung davor rechnete mit
     der Fensterhoehe und einer Schwelle -- die war zu eng und liess auf zwei
     echten Geraeten genau den Fall durchrutschen, fuer den sie gebaut war.
     Hier wird die Entscheidung nur angewandt: Erst in dieser Lage zaehlt das
     Polster nicht doppelt, sondern fehlt. */
  body.ist-app.randlos #screen-game.active,
  body.ist-app.randlos #screen-binary.active,
  body.ist-app.randlos #screen-queens.active {
    min-height: 100svh;
    padding-top: env(safe-area-inset-top, 0px);
  }

  /* DIE KOPFZEILE WAR NIE ABGEDECKT, auch nicht in der frueheren Korrektur:
     Die drei Regeln oben gelten den SPIELBILDSCHIRMEN, die Startseite und
     die Ranglisten haben ihre eigene `.topbar`. Dort ist es Amadeus
     aufgefallen, und dort ist der Schaden am groessten -- die Einstellungen
     haengen an dem Zahnrad, das unter der Statusleiste verschwindet. */
  /* NICHT AUF DEN SPIELBILDSCHIRMEN, und das ist kein Schoenheitsfehler:
     Bei `#screen-binary` und `#screen-queens` ist die Kopfzeile ein DIREKTES
     Kind der Sektion. Beide Regeln zusammen polsterten dort zweimal -- rund
     2 x 48px statt 48 + 16. Bei `#screen-game` faellt es nicht auf, weil
     dessen eigene `.topbar`-Regel spezifischer ist und gewinnt; von den drei
     Spielbildschirmen waere also ausgerechnet der behandelte richtig
     geblieben und die beiden anderen zu weit unten. Gefunden hat es die
     Sicherheitspruefung zu 1.17.156, kein Test. */
  /* DIE ALLGEMEINE REGEL STEHT NICHT MEHR HIER DRIN, sondern unter diesem
     Media-Block (Suchwort: „AUCH AUF DEM iPAD"). Sie war bis 1.17.160 hier,
     also nur bis 760px breit -- und damit auf dem iPad wirkungslos. Die
     Ruecknahmen unten bleiben, wo sie sind: Sie gehoeren zu Regeln, die es
     ebenfalls nur in diesem Block gibt. */
  /* JEDER BILDSCHIRM BEKOMMT SEINEN EIGENEN WERT ZURUECK, nicht pauschal
     16px. Die pauschale Zahl war ein Fehler: `#screen-game .topbar` steht in
     diesem Block auf `padding: 5px 14px`, und die Ruecknahme machte die
     Kopfzeile im Spiel um elf Pixel hoeher, als sie sein soll. Auf Android
     ist das seit 1.17.158 so; aufgefallen ist es erst, als dieselbe Regel am
     11.09.2026 auch fuer iOS gelten sollte, wo die 5px heute stimmen. */
  body.ist-app.randlos #screen-game .topbar {
    padding-top: 5px;
  }
  body.ist-app.randlos #screen-binary .topbar,
  body.ist-app.randlos #screen-queens .topbar {
    padding-top: 16px;
  }
  /* Und das Brett-Budget, ebenfalls NUR fuer die App.

     416px ist gemessen (topbar 55 + wrap 738 - brett 377 am iPhone 16), die
     292px daneben waren geraten. Die Zahl gehoert aber genau hierher und
     NICHT in die Regel fuer alle Handys: Mein erster Anlauf stellte sie im
     allgemeinen Block um, und die Sicherheitspruefung hat nachgemessen, was
     das im mobilen Browser anrichtet -- bei 393x745 schrumpft das Brett von
     377px auf 240px, also um 36 Prozent. Auf diese Fassung zeigt der bezahlte
     Verkehr.

     Der Grund fuer den Unterschied steht in der Regel darueber: In der App
     meldet `100svh` die volle Bildschirmhoehe, waehrend der Viewport kleiner
     ist. Im Browser stimmen beide ueberein, dort trug die alte Rechnung. */
  body.ist-app #screen-game .game-wrap {
    --board-size: min(100%, max(240px, calc(100svh - 416px - var(--sicher-hoch, 0px))));
  }
  /* BINAER UND QUEENS SIND NICHT GEMESSEN. Sie bekommen den Abzug fuer die
     sicheren Bereiche -- der ist in jedem Fall richtig und macht das Brett
     kleiner, nie groesser -- aber ihre Konstanten bleiben, wie sie waren.

     Eine Zahl zu erfinden, die man nicht gemessen hat, ist genau der Fehler,
     der beim Sudoku-Bildschirm zweimal danebenging. Ob hier noch etwas
     absteht, sagt ein Blick aufs Geraet; bis dahin steht hier lieber eine
     ehrliche Luecke als eine erfundene Zahl. */
  body.ist-app #screen-binary .game-wrap {
    --board-size: min(100%, max(240px, calc(100svh - 292px - var(--sicher-hoch, 0px))));
  }
  body.ist-app #screen-queens .game-wrap {
    --board-size: min(100%, max(240px, calc(100svh - 210px - var(--sicher-hoch, 0px))));
  }
  #screen-game .game-wrap {
    /* Eine gemeinsame Brettgröße für Brett UND Uhr-Reihe — sonst laufen die
       beiden Breiten auseinander und die Reihe steht nicht mehr bündig.
       max(240px, …) hält das Brett im Querformat benutzbar: dort ist
       100svh klein, ohne Untergrenze käme man auf ~20px Kantenlänge. */
    --board-size: min(100%, max(240px, calc(100svh - 292px)));
    flex: 1 1 auto;
    flex-direction: column;
    height: auto;
    min-height: 0;
    gap: 10px;
    /* Werkzeugreihe unten aus dem Gestenbereich halten (BEFUND 4.4). */
    padding: 8px 12px max(12px, env(safe-area-inset-bottom));
  }
  /* Board an den tatsächlich verfügbaren Platz koppeln. Kopfzeile, Uhr-Reihe
     und Werkzeuge sind bewusst schlank gehalten, damit auf üblichen Geräten
     die Breite (96vw) gewinnt und das Brett voll ausnutzt. Zielgrößen bleiben
     fingerfreundlich: Werkzeuge und Zifferntasten >= 44px hoch. */
  .board {
    width: var(--board-size);
    height: auto;
    aspect-ratio: 1 / 1;
    order: 2;
  }
  /* Binär-Screen: exakt dieselbe Mobil-Anordnung wie Sudoku (HUD-Reihe oben,
     Board Mitte, Ziffernblock+Tools unten). Eigene Selektoren, damit die
     Sudoku-Regeln oben unveraendert bleiben; .game-left/.game-right/.keypad/
     .tools sind global und greifen automatisch mit. */
  #screen-binary.active { display: flex; flex-direction: column; min-height: 100svh; padding-top: env(safe-area-inset-top); }
  #screen-binary .game-wrap {
    --board-size: min(100%, max(240px, calc(100svh - 292px)));
    flex: 1 1 auto; flex-direction: column; height: auto; min-height: 0;
    gap: 10px; padding: 8px 12px max(12px, env(safe-area-inset-bottom));
  }
  .binary-board { width: var(--board-size); height: auto; aspect-ratio: 1 / 1; order: 2; }
  /* Queens-Screen: gleiche Mobil-Anordnung wie Binär (HUD-Reihe oben, Board unten).
     Ohne diesen Block behielt das Queens-Brett die Desktop-Größe und die HUD-Spalte
     stand daneben -> lief seitlich aus dem Bild („wird nicht ganz angezeigt"). Kein
     Ziffernblock/Werkzeuge -> mehr Platz, daher weniger Abzug als Sudoku/Binär. */
  #screen-queens.active { display: flex; flex-direction: column; min-height: 100svh; padding-top: env(safe-area-inset-top); }
  #screen-queens .game-wrap {
    --board-size: min(100%, max(240px, calc(100svh - 210px)));
    flex: 1 1 auto; flex-direction: column; height: auto; min-height: 0;
    gap: 10px; padding: 8px 12px max(12px, env(safe-area-inset-bottom));
  }
  .queens-board { width: var(--board-size); height: auto; aspect-ratio: 1 / 1; order: 2; margin: 0 auto; }
  /* Kompakte Kopfzeile: Zeit · Fehler · Pause schlank in einer Reihe */
  .game-right {
    order: 1;
    width: 100%;
    max-width: var(--board-size); /* bündig zum Brett */
    flex-direction: row;
    gap: 8px;
  }
  .game-right .stat { flex: 1; padding: 3px 8px; gap: 0; }
  /* 9px ist die Untergrenze, die wir hier zumuten — darunter wird es auf
     kleinen Displays unleserlich. Ein Test hält das fest. */
  .game-right .hud-label { font-size: 9px; letter-spacing: 1px; }
  .game-right .hud-val { font-size: 16px; }
  /* Pause wird oft und zielsicher gebraucht -> bleibt breit und >= 44px hoch. */
  .pause-btn { flex: 0 0 auto; padding: 4px 22px; gap: 0; min-height: 44px; }
  .pause-ico { font-size: 16px; }
  .pause-lbl { font-size: 9px; }
  .game-left {
    order: 3;
    width: 100%;
    max-width: min(96vw, 56vh);
    gap: 8px;
  }
  /* Werkzeuge deutlich flacher als die Zifferntasten — sie standen vorher
     klobig unter den Zahlen. 44px bleibt die Untergrenze zum Treffen. */
  .tools .tool { padding: 5px 4px; gap: 1px; min-height: 44px; border-radius: 11px; }
  .tools .tool-ico { font-size: 15px; }
  .tools .tool-lbl { font-size: 9px; }
  /* Zweireihiges Zahlenfeld (5 + 4), breite Tasten, letzte Reihe zentriert */
  .keypad {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
  }
  .key {
    aspect-ratio: auto;
    width: calc(20% - 8px);
    height: clamp(46px, 6.4vh, 58px);
    border-radius: 12px;
  }
  .key-num { font-size: clamp(24px, 6.5vw, 32px); }
  /* 16×16 am Handy: 16 Tasten als 8er-Grid statt 5er-Flex (sonst 4 Reihen).
     minmax(0,1fr) + min-width:0, damit die Spalten UNTER die Inhaltsbreite
     schrumpfen duerfen (sonst laufen die 8 Tasten aus dem Container -> Tasten
     1/8/9/16 abgeschnitten, 16x16 unspielbar). Enger Abstand fuer mehr Platz. */
  .keypad.kp-wide { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 5px; }
  /* aspect-ratio MUSS aus: sonst leitet die Taste ihre Breite aus der Hoehe ab
     (Hoehe x 1.5) und sprengt die Grid-Spalte -> 8 Tasten laufen ueber. Mit
     aspect-ratio:auto + width:auto fuellt die Taste ihre Spalte. */
  .keypad.kp-wide .key { aspect-ratio: auto; width: auto; min-width: 0; height: clamp(38px, 5vh, 50px); }
  .keypad.kp-wide .key-num { font-size: clamp(14px, 4vw, 20px); }
}
@media (max-width: 560px) {
  .levels-grid { grid-template-columns: repeat(6, 1fr); }
}
@media (max-width: 380px) {
  .levels-grid { grid-template-columns: repeat(5, 1fr); }
}

/* AUCH AUF DEM iPAD, und deshalb steht diese eine Regel ausserhalb jeder
   Breitenbedingung.

   Bis 1.17.160 lag sie im Block `@media (max-width: 760px)`, zusammen mit
   allem anderen, was mit den sicheren Bereichen zu tun hat. Am 11.09.2026 im
   Viewport 820x1180 nachgemessen: Kopfzeile 16px, also gar kein Polster --
   auf genau dem Geraet, auf dem Apple die App prueft.

   Der Riegel ist `body.ist-app.randlos` und nicht die Breite. Die Klasse gibt
   es nur in der Huelle und nur dort, wo die WebView unter die Systemleisten
   zeichnet (js/sicherer-rand.js); im Browser und am Schreibtisch aendert sich
   dadurch nichts. Die Ruecknahmen fuer die Spielbildschirme bleiben im
   760er-Block: Was sie zuruecknehmen, gibt es nur dort. Oberhalb von 760px
   traegt keine Sektion ein eigenes `padding-top: env(...)`, also darf und
   muss es die Kopfzeile tragen. */
body.ist-app.randlos .topbar {
  padding-top: calc(16px + env(safe-area-inset-top, 0px));
}

/* UND UNTEN DASSELBE, fuer die Bildschirme, die scrollen.

   Bis 1.17.161 stand in app/capacitor.config.json `contentInset: always`.
   Damit rueckte die WebView ihren Inhalt SELBST ein, oben wie unten -- und
   oben kam unser Polster ein zweites Mal obendrauf. Seit 1.17.162 steht dort
   `never` (der Capacitor-Standard), die WebView deckt den ganzen Bildschirm,
   und das CSS ist die einzige Quelle der Abstaende. Im Simulator gemessen:
   `innerHeight` ging von 799 auf 874, also auf die volle Bildschirmhoehe.

   Der obere Rand wird von der Regel darueber getragen. Der untere fiel mit
   der Umstellung weg und muss hier nachkommen, sonst laeuft die letzte Zeile
   unter den Home-Indikator.

   NICHT FUER DIE SPIELBILDSCHIRME: Dort traegt ihn schon die Tastenreihe
   (`padding: … max(12px, env(safe-area-inset-bottom))`) beziehungsweise
   `.fb-wrap`, und sie rechnen mit `100svh`. Eine zweite Polsterung machte
   daraus eine Seite, die sich scrollen laesst, obwohl sie auf einen
   Bildschirm passen soll. `#screen-appstart` bringt seinen eigenen mit. */
body.ist-app.randlos #screen-home,
body.ist-app.randlos #screen-levels,
body.ist-app.randlos #screen-ranking,
body.ist-app.randlos #screen-gametypes,
body.ist-app.randlos #screen-landing {
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
/* MIT `max()`, WEIL DIESE BEIDEN SCHON EINEN ABSTAND HABEN. `#screen-auth`
   und `#screen-setup` stehen auf `padding: 24px`; die Regel oben haette ihn
   ERSETZT statt ergaenzt. Auf einem iPhone faellt das nicht auf (34 > 24),
   auf einem randlosen Android-Geraet mit Gestensteuerung meldet `env()`
   unten aber oft 0 -- dort waeren aus 24px plötzlich null geworden. Aus der
   Sicherheitspruefung zu 1.17.162. */
body.ist-app.randlos #screen-auth,
body.ist-app.randlos #screen-setup {
  padding-bottom: max(24px, env(safe-area-inset-bottom, 0px));
}

/* UND OBEN FUER DIE BILDSCHIRME OHNE KOPFZEILE.

   Der obere sichere Bereich haengt an `.topbar`. Vier Bildschirme haben
   keine: Einstieg, Landing, Anmeldung, Einrichtung. Solange die Huelle auf
   `contentInset: always` stand, rueckte sie deren Inhalt selbst ein; mit
   `never` fiel das weg, und die Umstellung haette damit eine Regression
   eingebaut statt einer zu beheben.

   AM SCHLIMMSTEN DIE LANDING, weil sie als einzige nicht zentriert ist:
   `#screen-landing.active` steht auf `display: block; padding: 0`, die
   Kopfzeile beginnt also bei null. Bei 62 Pixeln sicherem Bereich laegen
   Wortmarke, Sprachumschalter UND der Anmelden-Knopf hinter Uhr und
   Dynamic Island -- und die Landing ist in der App die Flaeche, die ein
   Gast als erstes sieht. Gefunden von der Sicherheitspruefung zu 1.17.162,
   bevor das Paket gebaut war.

   Anmeldung und Einrichtung sind zentriert und haben `min-height: 100vh`;
   sie laufen erst auf kurzen Fenstern nach oben in die Leiste. Der Einstieg
   bringt seinen unteren Abstand schon mit, der obere fehlte auch dort. */
body.ist-app.randlos #screen-landing .lp-header {
  padding-top: calc(12px + env(safe-area-inset-top, 0px));
}
body.ist-app.randlos #screen-auth,
body.ist-app.randlos #screen-setup,
body.ist-app.randlos #screen-appstart {
  padding-top: max(24px, env(safe-area-inset-top, 0px));
}

/* WAAGERECHT GAB ES GAR KEINEN SICHEREN ABSTAND, im ganzen Projekt nicht:
   `safe-area-inset-left` und `-right` kamen in keiner Datei vor. Mit
   `contentInset: always` trug die Huelle auch sie; mit `never` nicht mehr.

   Im Querformat sind das auf einem iPhone rund 59 Pixel je Seite. Rechts
   sitzen Abmelden und das Zahnrad, hinter dem „Spieldaten loeschen" und
   „Konto endgueltig loeschen" liegen -- die faellen zu einem guten Teil ins
   Sensorgehaeuse, das gar keine Beruehrungen annimmt. Man trifft dann den
   Nachbarn.
   AM BILDSCHIRM, NICHT AN DEN EINZELNEN KAESTEN. Mein erster Anlauf legte
   `max(22px, env(...))` auf die Kopfzeile und `max(12px, ...)` auf
   `.game-wrap`, `.fb-wrap` und `.keypad`. Die Sicherheitspruefung hat
   nachgemessen, was das anrichtet, und es war schlimmer als das Problem:

     - `.keypad` sitzt IN `.game-wrap`. Im Querformat zog beides den Abstand,
       in einer 170 Pixel breiten Spalte: Die Zifferntasten fielen von 51,5
       auf 29 Pixel und damit unter die 44, die das Haus sonst ueberall
       verteidigt.
     - `max(12px, ...)` ist ein BODEN, kein Zuschlag. Wo schon 20 oder 24
       Pixel standen (`.game-wrap` ueber 760px, `.fb-wrap` ab 1000px), machte
       er daraus zwoelf.
     - Und die Fuchsbau-Kopfzeile bekam gar nichts, weil ihre eigene Regel
       eine ID traegt und damit gewinnt -- derselbe Vorrang, der drei
       Absaetze weiter oben schon als Kommentar steht.

   Ein Abstand, der von aussen kommt, gehoert deshalb nach aussen. `.screen`
   ist der aeusserste Behaelter jedes Bildschirms; was drinsteht, rueckt
   mit, und kein abgestimmter Innenabstand wird ueberschrieben. Im
   Hochformat sind die Werte null, dort aendert sich nichts.

   `#screen-landing` braucht seine eigene Zeile: `#screen-landing.active`
   steht auf `padding: 0` und traegt eine ID, schlaegt also die Regel
   darueber. */
body.ist-app.randlos .screen,
body.ist-app.randlos #screen-landing.active {
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
}
/* UND DIE ZWEI MIT EIGENEM `padding: 24px`. Sie tragen eine ID und schlagen
   die Regel darueber -- fuer die senkrechte Achse ist das oben schon
   beruecksichtigt, fuer die waagerechte war es vergessen. Beide sind
   zentrierte Karten mit `max-width: 460px`, im Querformat also ohnehin weit
   vom Rand; die Zeile steht hier trotzdem, damit die Regel vollstaendig ist
   und nicht davon abhaengt, dass ein Kasten zufaellig schmal bleibt.
   Angemerkt in der Sicherheitspruefung zu 1.17.162. */
body.ist-app.randlos #screen-auth,
body.ist-app.randlos #screen-setup {
  padding-left: max(24px, env(safe-area-inset-left, 0px));
  padding-right: max(24px, env(safe-area-inset-right, 0px));
}

/* ---------------- Einstieg der App (nur dort) ----------------
   Ein ruhiger Bildschirm mit genau einer Handlung. Alles, was hier zusaetzlich
   staende, waere eine Huerde vor dem ersten Spiel. */
/* Der Bildschirm selbst muss die Hoehe tragen, sonst klebt der Inhalt oben
   und der Knopf liegt weit ausserhalb des Daumenbereichs. */
#screen-appstart.active {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}
#screen-appstart .as-wrap {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.1rem;
  padding: 2rem 1.5rem calc(2rem + env(safe-area-inset-bottom));
  text-align: center;
}
#screen-appstart .as-logo {
  /* Das ganze Motiv, nicht als Kreis: Der Schriftzug „SUDOKU SAGAX" gehoert
     dazu und traegt die Marke. Mitwachsend, damit auf kleinen Geraeten der
     Knopf nicht aus dem Bild rutscht. */
  width: min(86vw, 420px);
  height: auto;
  aspect-ratio: 1248 / 832;
  object-fit: contain;
  /* Das Motiv hat einen dunklen, rechteckigen Grund. Auf dem hellen Verlauf
     sieht das ohne Rundung und Schatten aufgeklebt aus — so liest es sich als
     gewolltes Element. */
  border-radius: var(--radius-lg, 16px);
  box-shadow: var(--shadow-md);
}
#screen-appstart .as-title {
  margin-top: .3rem;
  font-family: var(--font-display, inherit);
  font-size: clamp(1.9rem, 8vw, 2.6rem);
  letter-spacing: .06em;
  margin: 0;
  text-wrap: balance;
}
/* Der Claim ist nur noch fuer Vorleser da (sr-only im Markup). Den Abstand,
   den er getragen hat, uebernimmt der Knopf. */
#screen-appstart .as-title { margin-bottom: .2rem; }
/* Nonas Zeile. Sie sagt, WOFUER man sich anmeldet -- der Knopf allein sagt
   nur, womit. */
#screen-appstart .as-nona {
  margin: 0;
  max-width: 24rem;
  color: var(--text);
  font-size: 1rem;
  line-height: 1.55;
  text-wrap: balance;
}
/* DIE ANMELDEKNOEPFE, gleichrangig. Sobald Apple dazukommt, verlangt Apple
   genau das: gleiche Groesse, gleiches Gewicht, nicht darunter versteckt.
   Deshalb eine gemeinsame Klasse und kein Sonderfall je Knopf. */
#screen-appstart .as-anmelden {
  margin-top: .2rem;
  font-size: 1.05rem;
  padding-block: .9rem;
  max-width: 22rem;
}
/* Der Weg vorbei. Klein, aber nicht versteckt -- und mit vollem Tippziel:
   Wer nur schauen will, soll ihn finden, ohne zu suchen. */
#screen-appstart .as-gast {
  margin-top: .2rem;
  background: none;
  border: 0;
  color: var(--muted);
  font-family: inherit;
  font-size: .95rem;
  text-decoration: underline;
  text-underline-offset: 3px;
  padding: .8rem 1rem;
  min-height: 44px;
  cursor: pointer;
}
#screen-appstart .as-gast:hover { color: var(--text); }
#screen-appstart .as-sub {
  margin: 0;
  color: var(--muted);
  font-size: .88rem;
  line-height: 1.5;
  /* Breit genug, dass die drei Angaben in eine Zeile passen; auf schmalen
     Geraeten bricht es zwischen den Trennpunkten statt mitten im Wort. */
  max-width: 26rem;
  text-wrap: balance;
}
/* Gleichberechtigt daneben, nicht kleingedruckt: Wer schon ein Konto hat,
   soll es finden, ohne dass es den Erstspieler bedraengt. */
#screen-appstart .as-login {
  margin-top: .5rem;
  background: none;
  border: 0;
  /* Der Akzent als Schrift, nicht als Flaeche: auf Creme sind das 4,87:1
     statt 2,79:1. */
  color: var(--color-primary-text);
  font-weight: 600;
  font-size: 1rem;
  padding: .8rem 1rem;
  /* 44 ist das Minimum der Richtlinie, 48 die Android-Empfehlung — und hier
     ist Platz im Ueberfluss. */
  min-height: 48px;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: .25em;
}

/* ---------------- Was in der App NICHT gilt ----------------
   Die Verweise auf die Inhaltsseiten (Regeln, Raetselarten, Raenge,
   Schwierigkeitsseiten, Binaerraetsel) sind fuer Suchmaschinen gebaut. Sie
   liegen nicht im App-Buendel — ein Tipp darauf fuehrte aus der Huelle heraus
   auf eine Adresse, die es dort nicht gibt. Ein Test prueft, dass im gebauten
   Buendel kein Verweis ins Nichts zeigt (Block 306; bis zum 30.08.2026
   behauptete diese Zeile den Test, ohne dass es ihn gab).

   Die SEO-Ueberschrift verschwindet aus demselben Grund: Sie ist ein Satz fuer
   Suchergebnisse, nicht fuer jemanden, der die App schon installiert hat. */
/* DER APP-HINWEIS GEHOERT NICHT IN DIE APP. Sie laedt dieselbe index.html
   aus ihrem Buendel; ein Verweis auf den App Store waere dort einer auf sich
   selbst. Gilt fuer die Fusszeilen-Links. Die Ticker-Meldung traegt dieselbe
   Klasse, wird aber zusaetzlich in `tickerMeldungen` (js/app.js) aus der
   ROTATION genommen -- nur zu verstecken genuegte dort nicht, sie bekaeme
   ihre zwanzig Sekunden trotzdem und der Kasten stuende leer.
   Geprueft in test/design-02-sprachen-und-inhalte.test.mjs, Block 337. */
body.ist-app .app-hinweis { display: none; }
body.ist-app .lp-nav,
body.ist-app .landing-binary,
/* DER RUMPF SELBST, nicht nur sein Inhalt (1.17.118).

   Korbis Bild vom 30.08.2026: zwischen „Anmelden" und dem Kartenrand rund
   64 px Nichts. `.lp-hero` ist eine Flex-Spalte mit 26 px Abstand, und
   `.lp-hero-copy` blieb als LEERES Kind stehen -- 0 px hoch, aber ein
   voller Abstand teuer.

   Die drei Regeln darunter fuer h1, Untertitel und Chips bleiben trotzdem
   stehen. Sie sind nicht ueberfluessig, sondern die Aussage: Diese Texte
   sind fuer Suchergebnisse geschrieben. Wer den Rumpf spaeter wieder
   einblendet, soll nicht versehentlich drei Saetze mitbringen, die in der
   App nie gemeint waren. Block 310n. */
body.ist-app .lp-hero-copy,
body.ist-app .lp-h1,
body.ist-app .lp-sub,
body.ist-app .lp-vorteile,
body.ist-app .lp-lead,
body.ist-app .lp-more {
  display: none;
}

/* UND DIE SUCHMASCHINEN-TEXTE ALS GANZES.

   Gemessen am 30.08.2026 im iPhone-Viewport, App-Ansicht: Die
   Gast-Startseite war 6.709px hoch, knapp acht Bildschirme -- und 5.795px
   davon waren `.lp-content`: „Was ist", Schwierigkeiten, Raenge, Regeln,
   Mechaniken, FAQ. 86 Prozent der Seite, die in der App niemand liest.

   Die Verweise darin waren laengst ausgeblendet, weil sie ins Nichts
   fuehren; der Text blieb stehen. Wer die App oeffnet, hat sich fuer uns
   schon entschieden -- er muss nicht mehr ueberzeugt werden, er will
   spielen.

   IM BROWSER BLEIBT ALLES, und das ist keine Nebenbemerkung: Dort ist dieser
   Text der Grund, warum die Seite ueberhaupt gefunden wird. Deshalb haengt
   die Regel an `body.ist-app` und nicht an einer Bildschirmbreite. */
body.ist-app .lp-content {
  display: none;
}

/* DIE FOOTER-LEISTE WIRD NICHT ALS GANZE AUSGEBLENDET, sondern nur ihre
   VERWEISE -- die Versionsanzeige darin bleibt.

   Bis zum 30.08.2026 stand hier `body.ist-app .footer-links { display: none }`,
   und damit war in der installierten App nirgends ablesbar, welcher
   Buendelstand laeuft. Der Play-Store-`versionName` hilft dabei nicht, denn
   das Buendel wird unabhaengig davon aktualisiert: Bei einer Fehlermeldung aus
   der App wusste weder der Nutzer noch wir, worueber wir reden. Gefunden von
   der Gaming-Abnahme vom 30.08.2026.

   Warum ausgerechnet dieser Knopf bleiben darf: Die anderen Eintraege sind
   Verweise auf Inhaltsseiten, die nicht im Buendel liegen -- ein Tipp darauf
   fuehrte aus der Huelle ins Nichts. Die Versionsanzeige zeigt nirgendwohin,
   sie oeffnet ein Overlay.

   Die Kinder auszublenden statt des Behaelters ist bewusst: Eine Ausnahme
   INNERHALB eines `display: none`-Elements waere wirkungslos, egal wie
   spezifisch sie ist -- gruen in jedem Test, unsichtbar am Geraet.

   SEIT DEM 08.09.2026 STEHT FEEDBACK DANEBEN, aus genau demselben Grund.
   Korbis Befund: „Man kann in der App kein Feedback senden. Diese Funktion
   gibt es scheinbar nur in Web." Gemessen stimmte das: Alle drei
   Feedback-Knoepfe der Fusszeile standen auf `display: none`, und der
   vierte steckt im Ticker, dessen Nachricht nur zeitweise vorbeirollt.

   Auch dieser Knopf zeigt nirgendwohin, er oeffnet ein Overlay. Und er ist
   der Weg, auf dem uns Befunde aus der App ueberhaupt erreichen -- dort
   sehen wir sonst nichts. Block 364 haelt beide Ausnahmen fest. */
body.ist-app .footer-links > *:not(.version-link):not(.feedback-link) {
  display: none;
}

/* UND DER ARCHIV-VERWEIS NACH DER TAGES-CHALLENGE.

   Er ist ein Sonderfall, weil ihn keine Inventur der statischen Verweise
   findet: Im Markup hat `#dr-archive` gar kein `href`, es wird erst beim
   Anzeigen gesetzt (js/app.js, `/sudoku-des-tages/<datum>`). In der App
   fuehrte er deshalb still ins Menue zurueck -- am Geraet gefunden am
   30.08.2026.

   Verborgen statt umgebogen, auf Korbis Ansage: Das Tagesarchiv ist eine
   Seite fuer Suchmaschinen; in der App braucht sie niemand. Block 306 haelt
   die ganze Klasse fest, also auch den naechsten Verweis dieser Art. */
body.ist-app #dr-archive {
  display: none;
}

/* ---------------- DIE REIHENFOLGE DER GAST-STARTSEITE IN DER APP ----------

   Korbi am Geraet, 30.08.2026, mit zwei Bildern: „ganz ideal ist das Layout
   bei der nicht eingeloggten Startseite auf der App immer noch nicht."

   GEMESSEN im Handy-Viewport (393x852, body.ist-app erzwungen), vorher:

     Brett                  179 px
     „Direkt losspielen."   544 px   <- 365 px UNTER dem Brett
     Stufen-Kacheln         661 px
     „Mehr als Sudoku"      970 px   (793 px hoch)
     Rangliste             1763 px
     Konto-Anreiz          1909 px   <- Bildschirm 3
     Seite gesamt          2511 px = 2,9 Bildschirme

   ZWEI DINGE STIMMTEN DARAN NICHT.

   Erstens kam das Brett nackt. Im Browser stehen darueber eine h1 und ein
   Untertitel -- in der App sind beide ausgeblendet, weil sie fuer
   Suchergebnisse geschrieben sind (Regel weiter oben). Der einzige Satz, der
   sagt, was das hier ist, stand darunter, und die Haelfte davon unter dem
   Falz.

   Zweitens stand der Konto-Anreiz auf dem dritten Bildschirm, hinter 793 px
   Kacheln. Korbis Auftrag vom selben Tag war das Gegenteil: „Da sollten mehr
   Anreize drin sein, dass man ein Konto macht."

   GELOEST UEBER `order`, NICHT UEBER ZWEITE TEXTE. Ein eigener App-Titel
   waere eine zweite Stelle mit derselben Aussage -- und zwei Stellen mit
   derselben Aussage laufen auseinander. Es ist dieselbe Ueberschrift, sie
   steht nur dort, wo sie hingehoert.

   `order` WIRKT NUR UNTER GESCHWISTERN, und das ist der Grund, warum in
   index.html Markup umgezogen ist: `#landing-more` lag IN `#landing-play`.
   Ohne den Umzug saehen diese Regeln richtig aus und taeten nichts.

   Block 310 prueft die Reihenfolge als VERGLEICH, nicht als Zahl -- welche
   Werte hier stehen, ist gleichgueltig, solange die Folge stimmt. */
body.ist-app .landing.glass {
  display: flex;
  flex-direction: column;
}
body.ist-app #landing-progress { order: 1; }
body.ist-app #landing-play     { order: 2; }
body.ist-app #landing-wall     { order: 3; }
body.ist-app #landing-more     { order: 4; }
body.ist-app #landing-board    { order: 5; }
body.ist-app #landing-login    { order: 6; }

/* Und innerhalb des Spiel-Blocks: erst sagen, was es ist, dann das Brett.

   AUF `#landing-play` EINGEGRENZT, nicht auf die Klasse allein: Der
   Anreiz-Block traegt dieselbe `.landing-title`. Eine Regel ohne diesen
   Vorfahren zoege ihn dort mit nach oben, ueber die Zeile, die ihn erklaert
   -- gruen in jedem Test, falsch am Geraet. Block 310h haelt das fest. */
body.ist-app #landing-play {
  display: flex;
  flex-direction: column;
}
body.ist-app #landing-play .landing-title { order: 1; margin-top: 0; }
body.ist-app #landing-play .landing-sub   { order: 2; }
body.ist-app #landing-play #instant-board { order: 3; }
body.ist-app #landing-play .instant-hint  { order: 4; }
/* `align-self: center`, damit aus der Pille kein Balken wird. `.landing-free`
   ist `inline-block`; als Flex-Kind wird es blockifiziert und fuellt mit dem
   voreingestellten `stretch` die ganze Kartenbreite. Von der Nachpruefung am
   30.08.2026 gemessen -- der Preis dafuer, den Behaelter zum Ordnen auf Flex
   umzustellen, und er faellt nur in der App an. Block 310m. */
body.ist-app #landing-play #landing-free  { order: 5; align-self: center; }
body.ist-app #landing-play #landing-tiles { order: 6; }

/* ---------------- Offline-Streifen ----------------
   Schmal, ruhig, ueber allem — aber nicht im Weg: `pointer-events: none`,
   damit kein Tipp auf das Spielfeld daran haengenbleibt. Er unterbricht
   nichts, er erklaert nur. */
#offline-streifen {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  z-index: 60;
  pointer-events: none;
  padding: calc(.35rem + env(safe-area-inset-top)) .9rem .35rem;
  text-align: center;
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--on-accent-deep);
  background: color-mix(in srgb, var(--accent2) 88%, #000);
  box-shadow: var(--shadow-md);
}
#offline-streifen[hidden] { display: none; }
/* Der Streifen ueberlagert sonst die Kopfzeile („Menue", Name, Hilfe) — auf
   dem Geraet gesehen. Er soll erklaeren, nicht die Bedienung wegnehmen, also
   schiebt er den Inhalt nach unten. Die Hoehe entspricht dem Streifen samt
   sicherem Bereich. */
body.hat-offline-streifen {
  padding-top: calc(1.9rem + env(safe-area-inset-top));
}
/* IN DER RANDLOSEN HUELLE OHNE DEN SICHEREN BEREICH, sonst steht er zweimal
   in der Rechnung: Die Zeile darueber schiebt den ganzen Koerper um
   1,9rem + env nach unten, und die Kopfzeile schlaegt seit 1.17.161 noch
   einmal env auf. Zusammen waere die Kopfzeile eine Statusleiste zu tief.
   Im Browser bleibt es bei der Zeile darueber -- dort traegt die Kopfzeile
   nichts. Angemerkt in der Sicherheitspruefung zu 1.17.161. */
body.ist-app.randlos.hat-offline-streifen {
  padding-top: 1.9rem;
}

/* Der Kontoteil des Gewinn-Bildschirms ist nur eine Klammer zum Ausblenden,
   kein Layout-Element. `.pause-card` ist ein Flex-Container mit Luecke — als
   gewoehnliches div haette der Wrapper Text und Knopf aus dem Flexfluss
   genommen und sie waeren aneinandergeklebt.

   `display: contents` macht ihn fuer das Layout unsichtbar; `[hidden]` weiter
   oben traegt trotzdem, weil dort `display: none !important` steht. */
#gw-konto { display: contents; }

/* ===== Rueckweg von der Anmeldung hat nicht geklappt =====
   Bewusst KEIN Overlay mit Sperrschicht: Der Hinweis erklaert, er blockiert
   nicht. Wer weiterspielen will, tippt ihn weg — als Gast, mit Fortschritt.
   Eine Meldung, die den Weg versperrt, waere an dieser Stelle genau die
   zweite Huerde nach der ersten, die schon gescheitert ist.

   Unten statt oben, damit er den Offline-Streifen (z-index 60, oben) nicht
   verdeckt und umgekehrt. Beide koennen gleichzeitig auftreten: im
   eingebauten Browser mit wackligem Mobilfunk ist das eher Regel als
   Ausnahme. */
/* Der Weg zurueck ins Spiel, siehe Kommentar in index.html.

   `z-index: 55` liegt unter `#offline-streifen` (60) und der Rueckweg-Box
   (61): Beide sagen etwas, was dringender ist als ein Angebot. Rechts unten
   und schmal, damit er auf keinem Text sitzt. */
.lp-spielen {
  position: fixed;
  right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom));
  z-index: 55;
  padding: 12px 18px;
  border-radius: 999px;
  font-size: 15px; font-weight: 700; text-decoration: none;
  color: var(--color-bg-base, #1b0f0c);
  background: var(--accent);
  box-shadow: 0 10px 28px -8px rgba(0, 0, 0, .6);
}
.lp-spielen:hover, .lp-spielen:focus-visible { filter: brightness(1.08); }

/* Die Tages-Challenge steht in derselben Form wie das Binaerraetsel, bekommt
   aber den Akzentrahmen: Sie ist das eine taegliche Angebot der Seite.

   SIE BLEIBT AUCH IN DER APP STEHEN, und das ist der Unterschied zum
   Binaerraetsel, dessen Klasse sie fuer die Form mitbenutzt. `body.ist-app`
   blendet `.landing-binary` aus, weil das SEO-Beiwerk ist. Die Challenge ist
   das Gegenteil davon: In der App ist die Landing fuer einen Gast die
   einzige Flaeche, die er hat -- ohne diese Zeile waere die Challenge dort
   fuer ihn gar nicht erreichbar. */
.landing-daily { border-color: var(--accent); }
body.ist-app .landing-daily { display: flex; }

.rw-box {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom));
  z-index: 61;
  max-width: 520px;
  margin: 0 auto;
  padding: 14px 16px 12px;
  border-radius: var(--radius-lg);
  /* DECKEND, und das ist hier keine Geschmacksfrage: `--panel` ist Glas mit
     7 % Deckkraft. Damit schien das Sudoku-Gitter durch den Text, und der
     Hinweis war unlesbar — ausgerechnet der Hinweis, der jemandem erklären
     soll, warum die Anmeldung nicht ging. Auf dem Bildschirmfoto gesehen,
     nicht am Quelltext. `--wine` ist in beiden Themes deckend. */
  background: var(--wine);
  /* Kante aus `--hairline-strong` statt `--panel-brd`: Im hellen Design steht
     der Kasten (#fbf5ea) auf einer Seite in #f3ead9 — das sind 1,10:1, die
     Fläche allein trägt also nichts. Mit dem zarteren Rand hing die Karte
     praktisch nur am Schatten und verschwamm über dem cremefarbenen Gitter.
     Im dunklen Design ändert der Tausch nichts Sichtbares. */
  border: 1px solid var(--hairline-strong);
  box-shadow: var(--shadow-lg);
  color: var(--text);
}
.rw-box[hidden] { display: none; }
.rw-titel { display: block; font-family: var(--font-display); font-size: 16px; margin-bottom: 6px; }
.rw-text { margin: 0 0 10px; font-size: 14px; line-height: 1.5; color: var(--muted); }
/* Die Adresse zum Abtippen bzw. Kopieren — im eingebauten Browser ist das
   manchmal der einzige Weg nach draussen. Umbruch erlaubt, sonst schiebt sie
   auf schmalen Geraeten die Box auseinander. */
.rw-adresse-zeile { margin: 0 0 12px; }
.rw-adresse-zeile code {
  font-family: var(--font-pencil); font-size: 13px; color: var(--color-primary-text);
  word-break: break-all;
}
.rw-knoepfe { display: flex; flex-direction: column; gap: 8px; }
/* Volle Breite und Hoehe ueber der 44px-Marke: Der Kasten erscheint auf
   Handys, oft einhaendig bedient, und nach einem Fehlschlag. Ein knapper
   Treffer waere hier die dritte Huerde. */
.rw-knopf { width: 100%; min-height: 46px; }

/* ===== Anzeigenversprechen ueber dem Falz =====
   Unterzeile und die drei Nutzenpunkte, die bisher nur im Gast-Wall standen
   (also erst nach drei geloesten Raetseln). Ein Besucher aus einer Anzeige
   entscheidet in den ersten Sekunden; was er erst nach dem dritten Raetsel
   sieht, sieht er nie.

   Bewusst kompakt und einzeilig gehalten: Die Punkte duerfen das spielbare
   Gitter nicht nach unten schieben, sonst zerstoert das Einloesen des
   Versprechens genau das, was das Versprechen einloest. */
.lp-sub {
  margin: 14px 0 0;
  font-size: 16px;
  line-height: 1.5;
  color: var(--muted);
  max-width: 620px;
  text-wrap: pretty;
}
.lp-vorteile {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  /* Zentriert wie Überschrift und Unterzeile darüber. `text-align: center`
     vom Elternteil wirkt NICHT auf Flex-Elemente — ohne diese Zeile klebten
     die Chips links, während alles andere in der Mitte stand, und der dritte
     hing allein links unten. Sah nach Absicht aus, war ein Versehen. */
  justify-content: center;
  gap: 6px 8px;
}
.lp-vorteile li {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  background: var(--surface-1);
  border: 1px solid var(--panel-brd);
  border-radius: var(--radius-pill);
  /* 8 px statt 10: Auf 360-px-Geraeten sind es sonst 315 px bei 312 px
     Platz — drei Pixel, die eine zweite Zeile kosten. */
  padding: 4px 8px;
  white-space: nowrap;
}

/* Am Rechner steht der Kopf linksbündig (text-align: left im 1024er-Block),
   also gehören auch die Chips nach links. Diese Regel MUSS hinter der
   Basisregel stehen: Beide haben dieselbe Spezifität, und die spätere
   gewinnt. Sie stand zuerst weiter oben in der Datei und wurde von der weiter
   unten angehängten Basis überschrieben — im gerenderten Bild schwebten die
   Chips dann mittig über linksbündigem Text. */
@media (min-width: 1024px) {
  .lp-vorteile { justify-content: flex-start; }
}

/* Die Zusage direkt ueber dem Anmeldeknopf. Bewusst in Textfarbe, nicht
   gedaempft: Fuer jemanden, der gerade drei Raetsel geloest hat, ist das
   Mitnehmen des Fortschritts das staerkste Argument der Seite. */
.auth-mitnahme {
  margin: 0 0 12px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

/* Die Fluchttuer fuer Unentschlossene hatte 17 bis 19 px Trefferhoehe und lag
   damit unter dem Mindestmass von 24 px, das die Richtlinie verlangt (und weit
   unter den 44 px, die auf dem Handy ueblich sind). Ausgerechnet der Weg
   zurueck ins Gratis-Spielen war die schlechteste Flaeche der Seite. */
#screen-auth .landing-login {
  min-height: 44px;
  padding: 10px 12px;
}

/* Das Motiv war mit 340 px das dominanteste Element und drueckte Text und
   Knopf nach unten. Auf dem Einrichtungsbildschirm steht dasselbe Bild aus
   demselben Grund schon kleiner. */
#screen-auth .brand-logo { max-width: 220px; }

/* ============================================================== Fuchsbau
   Tower Defense auf dem Sudoku-Gitter.

   ZWEI DINGE STEHEN HIER, WEIL SIE IM ENTWURF FEHLTEN und zwei Pruefer sie
   vor dem Bauen gefunden haben:

   1. DAS BRETT KENNT SEINE HOEHE. Im Mockup bemass es sich nur nach Breite --
      auf 1440x900 wurde es 822 Pixel hoch, und die komplette Ziffernleiste lag
      bei y=1027, also unter der Bildkante. Man konnte das Spiel nicht
      bedienen, ohne zu scrollen, und beim Scrollen verliert man das Brett aus
      dem Blick, waehrend die Uhr laeuft. Die Formel unten ist dieselbe, die
      der Sudoku-Bildschirm seit langem benutzt.

   2. DIE FARBEN KOMMEN AUS DEN TOKENS. Im Mockup standen sie fest im SVG
      (#1c0c0f, #f4e8d2 und so weiter). Bei hellem Erscheinungsbild waere
      Fuchsbau ein dunkles Loch in einer hellen App gewesen, und der
      Schussstrahl auf Creme unsichtbar. Kein einziger Farbwert steht hier
      fest; alles laeuft ueber die Variablen, die themes.css umschaltet.

   Die Strassen sind bewusst DUNKLER als der Zellgrund, nicht goldener. Im
   Entwurf unterschieden sie sich von der Turmreichweite um 0,015 Alpha bei
   gleichem Farbton -- das eine ist dauerhaftes Gelaende, das andere ein
   fluechtiger Auswahlzustand, und auf dem Handy waren sie nicht zu trennen.
   Gold bleibt jetzt Zustandsfarbe. */

.fb-wrap { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 8px 12px 16px; }
.fb-main, .fb-seite { display: flex; flex-direction: column; align-items: center;
  gap: 10px; width: 100%; max-width: 720px; }

/* Die Werte ERSETZEN die Uhrzeile, sie kommen nicht dazu. */
.fb-stats { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; justify-content: center; }
/* AUF SCHMALEN GERAETEN NICHT UMBRECHEN.

   Gemessen auf einem 320er Bildschirm: Die drei Pillen passten nicht
   nebeneinander, brachen auf zwei Zeilen um und kosteten damit rund 30 Pixel
   Hoehe -- die letzte Tastenreihe stand 28 Pixel unter der Kante. Das
   Hoehenbudget rechnet mit EINER Zeile, also muss es auch eine bleiben.
   Enger gesetzt passen sie bis hinunter zu 320. */
@media (max-width: 380px) {
  .fb-stats { gap: 6px; flex-wrap: nowrap; }
  .fb-stat { padding: 3px 7px; gap: 4px; }
  .fb-stat-v { font-size: var(--fs-sm); }
}
.fb-stat { display: inline-flex; align-items: baseline; gap: 6px; padding: 4px 10px;
  background: var(--ctrl); border: 1px solid var(--ctrl-brd); border-radius: var(--radius-md); }
.fb-stat-l { font-size: var(--fs-xs); color: var(--gold); font-weight: 700; letter-spacing: .04em; }
.fb-stat-v { font-size: var(--fs-md); color: var(--text); font-variant-numeric: tabular-nums; }

/* DIE HOEHE, NACHGEMESSEN STATT ABGESCHRIEBEN.

   Erst stand hier `min(76vh, 58vw, 640px)` -- die Formel des
   Sudoku-Bildschirms. Am laufenden Browser gemessen (1440x900) ergab das ein
   640er Brett, und die Ziffernleiste endete bei y=946: 46 Pixel UNTER der
   Bildkante. Genau der Befund, den die Design-Pruefung am Mockup hatte, nur
   diesmal in meinem eigenen Code -- weil Fuchsbau unter dem Brett mehr
   traegt als Sudoku: Zeitleiste, Phasenzeile, Wellenvorschau.

   Nachgerechnet, was oben und unten wirklich steht: Kopfzeile 44, Wertezeile
   34, Zeitleiste 4, Phasenzeile 34, Vorschau 26, Ziffernleiste 56, dazu
   Abstaende und Innenabstand rund 74. Macht 272; mit Reserve 300. */
/* DIE GASSE. Ihr Seitenverhältnis ist fest, damit die Türme nicht bei jeder
   Fensterhöhe anders proportioniert sind; die Breite folgt dem Brett, damit
   Gitterdrittel und Turm senkrecht übereinander liegen. Genau darauf beruht
   die Zuordnung „linkes Drittel baut den linken Turm": Wenn sie nicht
   fluchten, muss man sie erklären statt zeigen. */
/* EINE BREITE FÜR ALLES, was untereinander steht.

   Vorher stand dieselbe Formel an fünf Stellen, und die Handy-Regel weiter
   unten überschrieb vier davon. Die fünfte war die Gasse: Sie blieb bei
   `58vw` und war damit 228 Pixel breit, während das Brett darunter 377 hatte.
   Damit standen Gitterdrittel und Turm NICHT übereinander -- und genau darauf
   beruht die ganze Zuordnung „linkes Drittel baut den linken Turm". Eine
   Zuordnung, die man sehen soll, muss fluchten.

   Jetzt steht die Breite an genau einer Stelle. Wer sie ändert, ändert sie
   für alle. */
/* 146, nicht 130. Die 130 stammen aus der Zeit, als die Gasse faelschlich
   auf 337 Pixel eingefroren war und damit rund 70 Pixel zu flach; seit sie
   dem Brett folgt, stimmt das Budget nicht mehr. Gemessen bei 768, 800 und
   900 Pixeln Fensterhoehe: jeweils 11 Pixel zu wenig. */
/* DER SICHERE BEREICH GEHOERT INS BUDGET.

   Die Abzuege unten sind feste Zahlen und kennen `env()` nicht. Auf einem
   iPhone mit Home-Indikator sind das 34 Pixel, die die Seite unten polstert
   -- bei 659 nutzbaren Pixeln blieben nach dem Brett aber nur 10 uebrig, die
   letzte Tastenreihe stand also 24 Pixel unter der Kante. Im kopflosen
   Browser ist der Wert 0, deshalb war davon in keiner Aufnahme etwas zu
   sehen; auf dem Geraet, auf dem gespielt wird, schon.

   Als eigene Groesse statt direkt als `env()`, damit eine Messung sie von
   aussen setzen kann und die Formel nicht nur auf Zuruf stimmt. */
#screen-fuchsbau { --fb-sai: env(safe-area-inset-bottom, 0px);
  --fb-breite: min(58vw, calc((100svh - 146px - var(--fb-sai)) / 1.26), 640px); }
/* HIER STEHT BEWUSST KEIN `100%`, anders als bei `--board-size` (Block 312).

   Am 30.08.2026 war es kurz drin und wurde zurueckgenommen. Der Grund ist
   ein Fund der Sicherheitspruefung: `--fb-breite` wird auf `.fb-boardwrap`
   benutzt, und im QUERFORMAT ist `.fb-main` `display: contents` -- das
   Brett sitzt dann direkt in einer `auto`-Spur des Rasters von `.fb-wrap`.
   Ein Prozentwert hat dort keinen definiten Bezug, ist also zirkulaer, und
   Chrome loest ihn gegen NULL auf: Gitter und Gasse verschwanden ganz.
   Gemessen wurde das im Tutorial mit Ziffernblock quer.

   Und er kaufte nichts. Im Hochformat lag das Brett bei 393 px mit 377,27
   auf einem Behaelter von 377,0 -- 0,27 px, also Rundung, kein Ueberlauf.
   Die Bretter der anderen Raetselarten standen 4 bis 8 px ueber; hier gab
   es nichts zu holen.

   WER HIER SPAETER EINEN DECKEL BRAUCHT, setzt ihn als `max-width` an
   `.fb-boardwrap` in einem Zusammenhang mit definiter Elternbreite -- nicht
   in eine Variable, die auch in einer `auto`-Spur landet. */
.fb-lanewrap { position: relative; width: var(--fb-breite); }
.fb-lane { display: block; width: 100%; aspect-ratio: 100 / 26;
  border-radius: var(--radius-md) var(--radius-md) 0 0; }
/* Klein und am Rand. Man braucht sie erst, wenn die Tuerme stehen, und bis
   dahin soll sie nicht im Weg sein.

   LINKS, nicht rechts. Rechts steht der Bau, und der Knopf lag mitten auf ihm
   -- ausgerechnet auf dem einen Bild, das zeigt, worum gespielt wird. Links
   unterhalb des Weges ist die Gasse leer: Tuerme und Gegner stehen auf dem
   Weg, ihre Koerper liegen darueber. */
.fb-call {
  position: absolute; left: 6px; bottom: 6px;
  padding: 4px 10px; font-size: var(--fs-xs); min-height: 0; line-height: 1.5;
  /* EIGENE FARBEN, kein Thema. Die Gasse ist in beiden Themen dunkel, weil
     sie ein Bild ist und keine Flaeche der Seite. Der Knopf erbte trotzdem
     `--ctrl` und `--text`: Im hellen Thema wurde daraus fast schwarzer Text
     (44, 28, 20) auf einer zu 95 Prozent durchsichtigen Platte ueber dem
     Gassenboden (36, 16, 18), gemessen 1,2 zu 1. Mit diesen Werten sind es
     rund 15 zu 1, in beiden Themen gleich. */
  background: rgba(20, 10, 10, 0.72);
  color: #f0e6d2;
  border: 1px solid rgba(236, 199, 106, 0.45);
}
/* GESPERRT HEISST WEG, nicht blass. Gemessen deckte der Knopf die ersten 87
   von 337 Pixeln der Gasse ab, und dort betreten die Angreifer sie -- eine
   blasse Platte ueber dem Eingang verdeckt genau das, was man sehen soll.
   Gebraucht wird er ohnehin nur in der Atempause, und dann laeuft niemand. */
.fb-call[disabled] { display: none; }
.fb-boardwrap { position: relative; width: var(--fb-breite); aspect-ratio: 1 / 1; }
/* DAS BRETT IST DAS GEMEINSAME BRETT.

   „das layout des spielbretts ... sieht ganz anders aus wie beim rest. das
   sollte schon gleich bleiben."

   Hier standen eigene Regeln fuer Rahmen, Schrift und Zellfarben, dazu
   Blocklinien ueber `nth-child`. Alles davon gibt es in `.board` und `.cell`
   schon, samt Schachbrettmuster der Bloecke, Notizdarstellung und den
   Zustandsfarben. Zwei Fassungen derselben Sache laufen auseinander -- das
   ist in diesem Projekt schon einmal teuer geworden.

   Uebrig bleiben genau die zwei Zustaende, die es NUR im Fuchsbau gibt: der
   volle Block, der einen Turm eine Stufe hebt, und das senkrechte Drittel.

   Der Bau steht nicht mehr im Gitter, sondern rechts in der Gasse; die
   Sonderzelle in der Mitte ist damit entfallen und das Raetsel hat wieder
   alle 81 Felder. */
#fb-board.board {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  box-shadow: none;
}

/* Ein voller Block traegt einen Turm. Ein schmaler Goldrand sagt das, ohne
   die Ziffern zu stoeren -- man liest sie ja weiter. */
/* `:not(.selected)`, und das ist kein Feinschliff: Der ID-Selektor hat hoehere
   Spezifitaet als `.cell.selected`, der Goldrand des vollen Blocks hat also
   den Auswahlrahmen ueberschrieben. Wer eine Zelle in einem vollen Block
   antippte, sah nicht mehr, dass sie gewaehlt ist -- eingebaut von mir beim
   Angleichen ans gemeinsame Brett, gefunden von der Sicherheitspruefung. */
#fb-board .cell.fb-voll:not(.selected) { box-shadow: inset 0 0 0 1px var(--gold-soft); }

/* DAS DRITTEL, DAS ZU EINEM TURM GEHÖRT. Angetippt hebt eine stehende Ziffer
   ihr senkrechtes Drittel hervor -- und damit den Turm darüber. Die
   Verbindung zwischen Gitter und Gasse lässt sich zeigen, nicht erklären. */
#fb-board.fb-drittel .cell { opacity: .42; transition: opacity .18s ease; }
#fb-board.fb-drittel[data-drittel="0"] .cell:nth-child(9n+1),
#fb-board.fb-drittel[data-drittel="0"] .cell:nth-child(9n+2),
#fb-board.fb-drittel[data-drittel="0"] .cell:nth-child(9n+3),
#fb-board.fb-drittel[data-drittel="1"] .cell:nth-child(9n+4),
#fb-board.fb-drittel[data-drittel="1"] .cell:nth-child(9n+5),
#fb-board.fb-drittel[data-drittel="1"] .cell:nth-child(9n+6),
#fb-board.fb-drittel[data-drittel="2"] .cell:nth-child(9n+7),
#fb-board.fb-drittel[data-drittel="2"] .cell:nth-child(9n+8),
#fb-board.fb-drittel[data-drittel="2"] .cell:nth-child(9n+9) { opacity: 1; }
/* DIE STATUSZEILE BRICHT NICHT UM.

   „sobald wellen kommen gehen die button automatisch nach hinten. wenn du
   also nicht so schnell bist ist das einfach nur noch gemein."

   Der Grund war der Umbruch: „Welle 1 in 1 s" passt in eine Zeile, „Welle 1
   läuft. Noch 8 unterwegs" nicht. Die Zeile wurde zweizeilig, alles darunter
   rutschte 22 Pixel nach unten, und die Ziffernleiste verliess das Fenster --
   ausgerechnet in dem Moment, in dem man sie braucht. Ein Layout, das sich
   nach der Laenge einer Statusmeldung richtet, wackelt bei jeder Meldung. */

/* Die Wellenvorschau ist die Stelle, an der gemalte Gegner tragen: Dort sind
   sie 40 Pixel gross und werden bewusst angesehen, waehrend sie auf dem Brett
   18 Pixel haben und jede Zeichnung zu einem Fleck wird. */

.fb-keys { display: grid; grid-template-columns: repeat(9, minmax(0, 1fr)); gap: 6px;
  width: var(--fb-breite); }
/* DIESELBEN TASTEN WIE IM REST DER APP.

   „das layout des spielbretts und der button ... sieht ganz anders aus wie
   beim rest. das sollte schon gleich bleiben." Das Brett ist zurueckgebaut,
   die Tasten waren es nicht: Sie standen in Inter und Gold neben einem Brett
   in Fraunces und Creme, mit 6er statt 13er Radius und ohne Schattenkante.
   Vier Unterschiede fuer dieselbe Handlung.

   Uebernommen wird die Optik von `.key`, nicht die Form: `aspect-ratio: 1`
   ergaebe bei fuenf Spalten quadratische Tasten von 72 Pixeln, und die Hoehe
   ist auf einem Telefon das knappste Gut. */
.fb-key { display: flex; flex-direction: column; align-items: center; gap: 1px;
  min-height: 56px; padding: 6px 2px; cursor: pointer;
  background: var(--ctrl); border: 1px solid var(--ctrl-brd); border-radius: 13px;
  box-shadow: var(--ctrl-sh);
  font-family: var(--font-numeric);
  color: var(--text); }
.fb-key-d { font-size: var(--fs-lg); font-weight: 600; color: var(--text); }
/* Der Turmname muss in seine Taste passen. „Scharfschuetze" ist mit
   dreizehn Zeichen der laengste und lief bei neun Tasten nebeneinander heraus
   -- am Bildschirm gesehen, nicht gerechnet. */
.fb-key-c { font-family: var(--font-body); font-size: 10px; font-weight: 500;
  color: var(--muted); font-variant-numeric: tabular-nums; }
/* NICHT `disabled`, sondern gedaempft: Eine gesperrte Taste wirkt kaputt, eine
   gedaempfte wirkt teuer. Und man muss sehen koennen, was sie kostet. */
.fb-key-arm { opacity: .45; }
.fb-key:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* --------------------------------------------------- Fuchsbau auf dem Handy

   GEMESSEN, NICHT GESCHAETZT. Vor dieser Regel ergab die Desktop-Formel
   `min(58vw, ...)` auf einem 393er Geraet ein Brett von 228 Pixeln und Zellen
   von 24,9 -- das Konzept verlangt mindestens 300 beziehungsweise Tippziele
   von 44. Im Querformat waren es 73 Pixel Brett, also unbenutzbar. `58vw` ist
   eine Desktop-Regel; auf dem Handy bindet sie und macht alles klein.

   DAS HOEHENBUDGET (Kapitel 6.1): Die drei Werte ERSETZEN die Uhrzeile, sie
   kommen nicht dazu. Die Zeitleiste ist ein 4 Pixel hoher Balken ohne eigene
   Textzeile. Nachgerechnet, was oben und unten wirklich steht: Kopfzeile 44,
   Wertezeile 30, Balken 4, Phasenzeile 30, Vorschau 24, Ziffernleiste in zwei
   Reihen 112, Abstaende und sichere Bereiche 56 -- macht 300. Nachgemessen
   waren es auf einem 360x640-Geraet fuenf Pixel zu wenig: Die Tasten endeten
   bei 645. Rechnen ersetzt das Messen nicht, es engt nur ein, wo man misst --
   deshalb 312. Seit die Gasse ueber dem Brett steht, kommen rund 90 Pixel
   dazu: 400.

   DIE ZIFFERNLEISTE WIRD 5+4 wie im Sudoku-Bildschirm; die Begruendung steht
   weiter unten an der Regel selbst. */
@media (max-width: 820px) {
  .fb-wrap { padding: 4px 8px calc(8px + env(safe-area-inset-bottom)); }
  .fb-main { gap: 6px; }

  /* Auch hier nur EINE Zahl. Die Gasse braucht rund 90 Pixel zusaetzlich zum
     Brett, deshalb 400 statt 312 im Hoehenbudget. */
  /* DAS HOEHENBUDGET, NEU GEMESSEN AN EINEM ECHTEN GERAET.

     Bis hierher war es an 393x852 gerechnet -- das ist die Geraetegroesse,
     nicht die Fenstergroesse. Auf einem iPhone mit Adresszeile und unterer
     Leiste bleiben rund 659 Pixel, also 193 weniger. Meine kopflosen Proben
     hatten diese Leisten nicht und haben deshalb nie gesehen, was Korbi
     sofort sah: Die zweite Tastenreihe endete bei 707 in einem 659 hohen
     Fenster.

     `100svh` ist genau richtig dafuer -- es MEINT die kleine Fenstergroesse.
     Falsch war der Abzug, weil ich ihn gegen ein Fenster geeicht habe, das
     auf keinem Telefon vorkommt. */
  /* GETEILT, NICHT ABGEZOGEN. Gasse und Brett stehen untereinander, die Gasse
     ist 0,34 mal so hoch wie das Brett breit -- der Inhalt waechst also mit
     dem 1,34-Fachen jedes Pixels Breite, das Fenster nur mit dem Einfachen.
     Ein fester Abzug kann das nicht auffangen: Er passte gemessen bei 659 und
     bei 852, und fiel bei 740 wieder heraus. Solche Loecher findet man nicht
     durch Ausprobieren an zwei Groessen, sondern nur, indem man die Formel
     aufschreibt. */
  /* NEU GERECHNET, nachdem Zeitleiste und Statuszeile in die Gasse
     gewandert sind. Sie kosteten 58 Pixel Hoehe fuer zwei Textzeilen, und
     das Brett band die Hoehe, nicht die Breite: gemessen 268 Pixel bei 377
     verfuegbaren. Die Gasse ist zugleich flacher (0,26 statt 0,34), der
     Inhalt waechst also nur noch mit dem 1,26-Fachen der Brettbreite. */
  #screen-fuchsbau { --fb-breite: min(96vw, max(186px, calc((100svh - 234px - var(--fb-sai)) / 1.26))); }

  .fb-stats { gap: 6px; }
  .fb-stat { padding: 2px 8px; }
  .fb-stat-l { font-size: var(--fs-xs); }
  .fb-stat-v { font-size: var(--fs-sm); }

  /* FUENF UND VIER, wie im Sudoku-Bildschirm. Neun Tasten nebeneinander
     waeren auf 360 Pixeln je 36 breit; die Projektregel verlangt 44.

     Diese Zahl hat sich zweimal gedreht, und beide Male entschied der Platz
     IN der Taste, nicht das Raster: Solange dort ein Rollenname stand
     („Scharfschütze", dreizehn Zeichen), brauchte es drei Spalten. Seit es
     keine Rollen und keine Preise mehr gibt, steht dort die Ziffer und wie
     oft sie fehlt -- dafuer reichen fuenf Spalten, und das Brett bekommt die
     neunzig Pixel Hoehe zurueck, die die Namen gekostet haben. */
  .fb-keys { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 6px; }
  .fb-key { min-height: 46px; padding: 4px 6px; }
  /* Der Name war hier bis eben mit `display: none` abgeschaltet, weil bei
     fuenf Spalten kein Platz war. Die Regel stand DIREKT HINTER der neuen
     und hat sie geschlagen: Das Raster wurde dreispaltig, der Name blieb
     weg, und mein erster Test dazu war trotzdem gruen -- er hat nur
     nachgesehen, ob irgendwo `display: block` steht. Zwei Regeln fuer
     dasselbe Element im selben Block sind fast immer ein Ueberbleibsel. */
  .fb-key-d { font-size: var(--fs-md); }

}

/* Querformat: Brett links, Bedienung rechts. Ohne diese Regel bleibt vom
   Brett nichts uebrig -- gemessen 73 Pixel auf 852x393. Eine Sperre der
   Ausrichtung gibt es im Projekt nicht und soll es nicht geben. */
/* Querformat: Brett links, Bedienung rechts.

   JEDES ELEMENT BEKOMMT EINE EIGENE ZEILE, und das ist keine Feinheit. Die
   erste Fassung gab allen rechten Elementen dieselbe benannte Flaeche
   (`grid-area: rechts`) plus eine Sammelzuweisung auf alle Kinder. Mehrere
   Grid-Elemente in derselben Flaeche ueberlappen VOLLSTAENDIG -- sie fliessen
   nicht untereinander. Gemessen lagen Wertezeile, Zeitleiste, Statuszeile,
   Vorschau und Ziffernleiste alle auf x=304, y=16: „Welle vorziehen" war von
   Taste 5 verdeckt und nicht klickbar, und der Ziffern-Hinweis erschien unter
   den Tasten, also unsichtbar -- auf genau dem Geraet, fuer das er gebaut ist.

   Meine eigene Messung hat das nicht gesehen, weil sie Brettgroesse und „liegt
   im Sichtfenster" geprueft hat. Ueberdeckung ist fuer beide Masse unsichtbar.
   Gefunden hat es die Sicherheitspruefung; Test 234i haelt es fest. */
@media (max-height: 480px) and (orientation: landscape) {
  /* `.fb-main` und `.fb-seite` verschwinden hier als Kaesten und geben ihre
     Kinder direkt an das Gitter weiter (`display: contents`). Sonst waere die
     Bedienspalte EIN Gitterelement, und die benannten Flaechen unten
     griffen ins Leere -- die Zuweisung `grid-area: tasten` wirkt nur auf ein
     direktes Kind des Gitters. */
  .fb-wrap {
    display: grid;
    grid-template-columns: auto minmax(180px, 1fr);
    grid-template-rows: auto auto auto 1fr;
    /* Die Gasse steht ÜBER dem Brett, auch im Querformat. Sie gehoert
       raeumlich dorthin: Die drei Tuerme muessen ueber ihren Dritteln
       stehen, sonst ist die Zuordnung nicht mehr zu sehen. */
    grid-template-areas:
      "gasse werte"
      "brett tasten"
      "brett dialog";
    align-items: start;
    gap: 6px 10px;
    max-width: none;
    padding: 4px 10px 8px;
  }
  .fb-main, .fb-seite { display: contents; }
  /* Die Kopfzeile wird flach -- im Querformat ist Hoehe das knappste Gut,
     und der Titel darf nicht ein Drittel davon nehmen. */
  #screen-fuchsbau .topbar { padding: 4px 10px; min-height: 0; }
  /* HIER STAND EINE RUECKNAHME FUER DIE RANDLOSE HUELLE, und sie war
     ueberfluessig. Meine Begruendung lautete, `body.ist-app.randlos .topbar`
     sei spezifischer als die Zeile darueber. Ist es nicht: eine ID (1,1,0)
     schlaegt drei Klassen (0,3,1). Die 4px stehen ohnehin.

     Nachgemessen im Querformat 852x393, mit und ohne die Zeile: beide Male
     4px. Gefunden hat es die Sicherheitspruefung zu 1.17.161. */
  /* `.game-title` ist `display: block` -- ohne diese Zeile war die
     Flex-Angabe darunter wirkungslos und der Titel blieb zweizeilig, waehrend
     der Kommentar „wird flach" das Gegenteil behauptete. */
  #screen-fuchsbau .game-title { display: flex; flex-direction: row; gap: 8px;
    align-items: baseline; justify-content: center; }
  #screen-fuchsbau .game-diff { display: inline; }
  #screen-fuchsbau .game-level { font-size: var(--fs-sm); }

  /* Das Brett rechnet gegen die RESTHOEHE, nicht gegen die Fensterhoehe.
     Die erste Fassung nahm `92svh` -- 362 Pixel Brett unter einer Kopfzeile
     passen aber nicht in ein 393 Pixel hohes Fenster, und die unteren Reihen
     lagen ausserhalb. Am Bildschirm gesehen: Das Brett war da, nur nicht
     ganz. Eine Formel, die die Kopfzeile vergisst, misst das falsche Fenster. */
  /* Die Gasse ist 0,34 mal so hoch wie das Brett breit ist. Untereinander
     brauchen beide also das 1,34-Fache der Breite an Hoehe -- wer nur die
     Bretthoehe abzieht, rechnet die Gasse nicht mit und schiebt die unteren
     Reihen aus dem Fenster. Gemessen waren es neun Pixel. */
  /* 64, nicht 40. Gemessen im Querformat 852x393: Kopfzeile, Werte-Zeile und
     die Abstaende brauchen 63 Pixel, die Formel gab 40 her -- das Brett stand
     23 Pixel unter der Kante. */
  #screen-fuchsbau { --fb-breite: min(calc((100svh - 64px - var(--fb-sai)) / 1.26), 46vw); }
  .fb-boardwrap { grid-area: brett; align-self: start; }
  /* Dieselbe Breite wie das Brett, nicht `100%`. Bei `100%` misst sich die
     Gasse an der Gitterspalte, und die Spalte richtet sich wiederum nach dem
     breitesten Kind -- die Gasse hat sich damit selbst breiter gemacht als
     das Brett (gemessen im Handy-Querformat: Brett 280, Gasse 402). Die
     Tuerme standen dann neben ihren Dritteln. */
  .fb-lanewrap { grid-area: gasse; align-self: end; width: var(--fb-breite); }
  .fb-lane { width: 100%; }
  .fb-stats    { grid-area: werte;    width: 100%; justify-content: flex-start; }
  .fb-keys     { grid-area: tasten;   width: 100%; align-self: start;
                 grid-template-columns: repeat(5, minmax(0, 1fr)); }

  /* Der Dialog bekommt eine EIGENE Zeile. Ihn in die Tastenflaeche zu legen
     waere genau der Fehler, den der Kommentar oben beschreibt: Zwei Elemente
     in derselben benannten Flaeche ueberlappen vollstaendig. */
  .fb-dlg { grid-area: dialog; align-self: start; padding: 8px; gap: 8px;
            flex-direction: row; align-items: center; }
  .fb-dlg-bild { width: 56px; height: 56px; }
  .fb-dlg-satz { font-size: var(--fs-xs); line-height: 1.35; }
  /* Im Vorspann steht nur der Dialog, und er darf die ganze Breite haben. */
  #screen-fuchsbau.fb-vorspann .fb-wrap { grid-template-columns: 1fr; min-height: 0; }
  #screen-fuchsbau.fb-vorspann .fb-dlg { grid-area: auto; flex-direction: row;
    text-align: left; padding: 10px; gap: 12px; }
  #screen-fuchsbau.fb-vorspann .fb-dlg-bild { width: 108px; height: 108px; }
  #screen-fuchsbau.fb-vorspann .fb-dlg-text { align-items: stretch; }
  #screen-fuchsbau.fb-vorspann .fb-dlg-satz { font-size: var(--fs-sm); }
}

/* Der Hinweis beim Vorwaehlen einer Ziffer hebt sich ab, sonst liest man ihn
   als Statuszeile und wundert sich, warum dort ploetzlich ein Turmname steht. */

/* Mittig statt oben geklebt -- aber NUR, wo Platz ist. Auf hohen Geraeten ist
   das Brett breitenbegrenzt (96vw), nicht hoehenbegrenzt; gemessen blieben
   unten 450 Pixel leer, waehrend oben alles zusammenrueckte. Auf einem
   360x640-Geraet dagegen kostete dieselbe Zentrierung genau einen Pixel zu
   viel und schob die letzte Tastenreihe aus dem Bild. Eine Annehmlichkeit
   darf nicht die Bedienbarkeit kosten. */
@media (max-width: 820px) and (min-height: 700px) {
  .fb-wrap { min-height: calc(100svh - 60px); align-items: center; }
}

/* Die Fuchsbau-Kachel im Spieltypen-Raster. Ember-Orange liegt zwischen
   Normal und Schwer, ist aber heller und satter -- die Kachel soll
   eigenstaendig wirken, ohne aus der Reihe zu fallen. */
.diff-card-fuchsbau { --accent: #e2652e; }

/* ------------------------------------------------- Der Tutorial-Dialog

   Portraet links, Text rechts, am unteren Rand. Das Konzept wollte
   ausdruecklich KEIN Dialogfenster -- „auf dem Handy die halbe Spielflaeche"
   --, und der Einwand war richtig. Die Antwort darauf ist die Anordnung, nicht
   der Verzicht: feste Hoehe unten, das Brett bleibt darueber sichtbar.

   Die Fuechsin ist in dieser App bisher Dekor. Hier bekommt sie eine Rolle. */
.fb-dlg {
  display: flex; gap: 12px; align-items: center;
  width: 100%; padding: 12px 14px;
  background: var(--surface-3); border: 1px solid var(--gold-soft);
  border-radius: var(--radius-lg);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .28);
}
.fb-dlg-bild {
  width: 96px; height: 96px; flex: 0 0 auto; border-radius: var(--radius-md);
  object-fit: cover; border: 1px solid var(--gold-soft);
}
.fb-dlg-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.fb-dlg-satz { margin: 0; font-size: var(--fs-md); line-height: 1.45; color: var(--text); }
.fb-dlg-fuss { display: flex; gap: 8px; align-items: center; }

/* Abdunkeln und Hervorheben. Ein Dialog ueber dem Brett waere auf dem Handy
   die halbe Flaeche; stattdessen wird alles GEDAEMPFT ausser dem, worum es
   gerade geht. */
/* ABDUNKELN, ABER NICHT AUSLOESCHEN.

   Bei 28 Prozent waren die Ziffern kaum noch zu entziffern, und ausgerechnet
   in den beiden Abschnitten, in denen der Spieler selbst handeln soll, lag
   das Brett im Dunkeln, waehrend die Ziffernleiste darunter voll leuchtete.
   Das Auge ging dorthin, wo nichts zu entscheiden war.

   Und das „leuchtende Feld" leuchtete nicht: ein duenner Goldrahmen ohne
   Fuellung. Jetzt ist es gefuellt, hat einen Schein nach aussen und pulst,
   solange es gemeint ist. */
#fb-board.fb-dim .cell { opacity: .5; transition: opacity .18s ease; }
#fb-board.fb-dim .cell.fb-zeigt {
  opacity: 1;
  background: var(--cell-selected);
  box-shadow: inset 0 0 0 3px var(--gold), 0 0 14px 2px rgba(226, 180, 85, .45);
  animation: fb-puls 1.6s ease-in-out infinite;
}
@keyframes fb-puls {
  0%, 100% { box-shadow: inset 0 0 0 3px var(--gold), 0 0 10px 1px rgba(226, 180, 85, .35); }
  50%      { box-shadow: inset 0 0 0 3px var(--gold), 0 0 20px 5px rgba(226, 180, 85, .60); }
}
@media (prefers-reduced-motion: reduce) {
  #fb-board.fb-dim .cell.fb-zeigt { animation: none; }
}
#fb-board.fb-dim.fb-zeigt-alles .cell { opacity: 1; }

@media (max-width: 820px) {
  .fb-dlg { padding: 10px; gap: 10px; }
  .fb-dlg-bild { width: 72px; height: 72px; }
  .fb-dlg-satz { font-size: var(--fs-sm); }
}

/* ── WAS DAS TUTORIAL EIN- UND AUSBLENDET ───────────────────────────────────

   Das Tutorial zeigt nicht die ganze Oberflaeche und dimmt sie, sondern es
   zeigt nur, was der aktuelle Abschnitt braucht. Zwei Gruende, beide aus dem
   ersten echten Durchgang:

   „Im mobilen kann man das Tutorial zwar durchführen, weil man scrollen kann,
   dafür sind auch die Buttons immer im Hintergrund zu sehen und man versteht
   teilweise nicht, dass man jetzt wieder hochscrollen muss."

   Auf 852 Pixel Hoehe passen Kopfzeile, Werte, Brett, Zeitleiste, Phase,
   Ziffernleiste UND ein Dialog nicht uebereinander. Entweder etwas liegt
   darueber (das war der Fehler) oder man scrollt (das war die Folge). Der
   dritte Weg ist, die Ziffernleiste nur dann zu zeigen, wenn der Abschnitt
   sie braucht -- das sind genau zwei von zehn. */
#screen-fuchsbau.fb-tutorial .fb-keys,
#screen-fuchsbau.fb-tutorial .fb-call,
#screen-fuchsbau.fb-tutorial #fb-pause { display: none; }
#screen-fuchsbau.fb-tutorial.fb-tasten .fb-keys { display: grid; }

/* WENN DIE ZIFFERNLEISTE DAZUKOMMT, WEICHT DAS BRETT.

   Gemessen auf 393x852, im Abschnitt ueber Tinte und Preise: Kopf 81, Brett
   377, Zeitleiste und Phase 31, Ziffernleiste 194, Dialog 153. Der Dialog
   endete bei 885 und damit 33 Pixel unterhalb des Fensters -- „Weiter" und
   „Überspringen" waren nicht erreichbar. Also wieder eine Sackgasse, diesmal
   von mir selbst gebaut, beim Beheben der vorigen.

   Die Leiste ist mit Namen 194 statt 150 Pixel hoch; das ist der Preis fuer
   die Namen und er ist es wert. Das Brett ist das einzige Element hier, das
   sich ohne Verlust verkleinern laesst: Bei 314 Pixeln ist eine Zelle immer
   noch 35 Pixel breit, also gut lesbar und gut treffbar.

   NICHT ueber das Hoehenbudget geloest: Auf 852 Pixeln ist das Brett
   breitenbegrenzt (96vw), nicht hoehenbegrenzt -- eine Formel mit `100svh`
   haette hier gar nichts bewirkt. Das war der erste Anlauf. */
@media (max-width: 820px) {
  /* IM UEBUNGSSCHRITT WEICHT DIE GASSE, NICHT DAS BRETT.

     Vorher schrumpfte hier das Brett, und ohne Untergrenze: Auf 659 Pixeln
     Fensterhoehe ergab `calc(100svh - 520px)` genau 139 Pixel Brett mit
     15-Pixel-Zellen. Korbi: „das Feld ist so klein, dass man es nicht
     richtig antippen kann. Habs dann groß gezogen und man kann es trotzdem
     nicht antippen. Also musste ich Überspringen."

     Das Brett ist in diesem Abschnitt aber der Gegenstand -- man soll genau
     dort eine Ziffer setzen. Die Gasse ist es nicht. Also verschwindet sie
     fuer diese zwei Abschnitte und kommt danach zurueck. Das spart 104 Pixel
     und traegt die Ziffernleiste UND den Dialog. */
  #screen-fuchsbau.fb-tutorial.fb-tasten .fb-lane { display: none; }
  /* Und der Dialog wird schmaler. Im Uebungsschritt stehen Brett,
     Ziffernleiste UND Dialog gleichzeitig da -- das ist der Moment mit dem
     meisten Inhalt, und ein Portraet von 72 Pixeln ist dann der teuerste
     Schmuck auf dem Bildschirm. */
  #screen-fuchsbau.fb-tutorial.fb-tasten .fb-dlg { padding: 8px; gap: 8px; }
  #screen-fuchsbau.fb-tutorial.fb-tasten .fb-dlg-bild { width: 54px; height: 54px; }
  #screen-fuchsbau.fb-tutorial.fb-tasten .fb-dlg-satz { font-size: var(--fs-xs); }
  #screen-fuchsbau.fb-tutorial.fb-tasten { --fb-breite: min(92vw, max(196px, calc(100svh - 330px))); }
}

/* Der Abschnitt ueber die Gasse hebt hervor, worum es geht: die Szene selbst
   und die Turmstufen in der Wertezeile. Ohne das redet der Text ueber etwas,
   das zwar dasteht, aber nicht als das Gemeinte erkennbar ist. */
#screen-fuchsbau.fb-zeigt-gasse .fb-lane,
#screen-fuchsbau.fb-zeigt-gasse .fb-stat-tuerme {
  box-shadow: 0 0 0 2px var(--gold), 0 0 16px 2px rgba(226, 180, 85, .35);
  border-radius: var(--radius-md);
}
#screen-fuchsbau.fb-zeigt-gasse .fb-stat-tuerme .fb-stat-v { color: var(--gold); font-weight: 700; }

/* Der Vorspann laeuft OHNE Brett: „Ich würde die ersten Dialoge auch anzeigen,
   bevor man das Spielbrett sieht, sonst ist man schonmal super abgelenkt." */
#screen-fuchsbau.fb-vorspann .fb-main { display: none; }
/* Mittig im RESTLICHEN Fenster, nicht in 60 Prozent davon. Mit 60svh sass
   die Karte gemessen 130 Pixel unter der Kopfzeile und darunter blieben 400
   Pixel leer -- der Vorspann sah aus, als fehle etwas. */
#screen-fuchsbau.fb-vorspann .fb-wrap {
  justify-content: center; min-height: calc(100svh - 112px);
}
#screen-fuchsbau.fb-vorspann .fb-seite { max-width: 620px; }
#screen-fuchsbau.fb-vorspann .fb-dlg {
  flex-direction: column; align-items: center; text-align: center;
  gap: 18px; padding: 26px 22px;
}
#screen-fuchsbau.fb-vorspann .fb-dlg-bild { width: min(46vw, 200px); height: min(46vw, 200px); }
#screen-fuchsbau.fb-vorspann .fb-dlg-text { align-items: center; }
#screen-fuchsbau.fb-vorspann .fb-dlg-satz { font-size: var(--fs-lg); line-height: 1.5; }

/* ── DIE BREITE ANSICHT ──────────────────────────────────────────────────────

   „Obwohl die Desktop-Version jede Menge Platz hat wirkt es wie in der
   mobilen Version zusammen gepfercht."

   Zu Recht: Bis hierher stand alles in einer 720 Pixel breiten Mittelspalte,
   auf einem 1920er Schirm genauso wie auf einem Handy. Ab 1000 Pixel Breite
   gibt es deshalb zwei Spalten -- Brett links, Bedienung rechts.

   Damit loest sich auch der zweite Teil des Einwands: „Zudem kann man die
   Text in den Buttons nicht lesen." Neben dem Brett war eine Zifferntaste
   640/9 = 71 Pixel breit, und „Scharfschütze" braucht bei dieser Schriftgroesse
   rund 90. In der eigenen Spalte ist jede Taste eine Zeile ueber die volle
   Breite; der Name steht ungekuerzt da. */
@media (min-width: 1000px) {
  .fb-wrap { flex-direction: row; align-items: flex-start; justify-content: center;
             gap: 28px; padding: 14px 24px 24px; }
  .fb-main { width: auto; max-width: none; flex: 0 0 auto; }
  /* LINKS UND MITTIG, wie in jeder anderen Rätselart.

     Gemeldet als „Fuchsbau ist im Vollbild das einzige Spiel mit anderem
     Ziffernblock". Bei 1440x900 nachgemessen: Brett y 297 bis 895, die
     Spalte y 91 bis 366 — die Tasten klebten oben rechts, darunter blieben
     529 Pixel Spalte leer.

     Dass sie rechts stand, war kein Versehen: Sie ist die Antwort auf
     „Zudem kann man die Text in den Buttons nicht lesen", als die Tasten
     noch Turmnamen trugen. Heute steht dort die Restzahl, der längste Text
     ist „Notiz" — der Grund ist entfallen, die Anordnung war geblieben.

     DIE BREITE BLEIBT: In derselben Spalte steht der Tutorial-Dialog, und
     der liegt dort, weil er als schwebende Ebene die Knöpfe verdeckte.
     Schmaler beschädigt diesen Fix. Block 332.

     `order` STATT DOM-UMBAU, und das ist eine Abwägung, keine Bequemlichkeit:
     Damit folgt die Tab-Reihenfolge weiter dem DOM — erst Brett, dann Tasten
     —, während die Optik es umgekehrt zeigt. Die klassischen Rätsel haben
     beides deckungsgleich, weil `.game-left` dort im Markup vor dem Brett
     steht. Es umzubauen hieße hier nur, dieselbe Abweichung ans Handy zu
     verschieben: dort gehört die Spalte UNTER das Brett, also stünde dann
     dort ein `order`. Alles bleibt erreichbar, die Reihenfolge Brett-dann-
     Bedienung ist in sich schlüssig, und der Dialog steht am Ende, wo seine
     Knöpfe hingehören. */
  .fb-seite { width: 340px; max-width: 340px; flex: 0 0 auto; align-items: stretch; gap: 14px;
              order: -1; align-self: center; }
  /* Im Tutorial nicht: Dort steht der Dialog über den Tasten und soll beim
     Ein- und Ausblenden nicht springen. Genau dafür stand hier `align-items:
     flex-start`. Die Klasse setzt js/fuchsbau-view.js beim Start. */
  #screen-fuchsbau.fb-tutorial .fb-seite { align-self: flex-start; }

  /* Auch hier das 1,34-Fache: Gasse plus Brett brauchen zusammen mehr Hoehe
     als das Brett allein. Ohne die Teilung stand das Brettende gemessen bei
     972 Pixeln in einem 900 Pixel hohen Fenster -- die unterste Reihe UND die
     ganze Zeitleiste lagen ausserhalb. Die Zeitleiste ist in diesem Spiel der
     einzige Druck; sie nicht zu sehen heisst, das Spiel nicht zu spielen. */
  /* Dieselbe Korrektur wie in der Grundregel: Seit die Gasse dem Brett folgt,
     statt auf 337 Pixeln festzuhaengen, ist sie rund 70 Pixel hoeher. Gemessen
     bei 768, 800 und 900 Pixeln Fensterhoehe waren es jeweils 11 zu wenig. */
  #screen-fuchsbau { --fb-breite: min(56vw, calc((100svh - 146px - var(--fb-sai)) / 1.26), 700px); }

  /* NEUN ZEILEN UND EIN DIALOG MUESSEN ZUSAMMEN IN DIE HOEHE PASSEN.

     Beim nachgespielten Durchgang gemessen: Bedienspalte 866 Pixel bei einem
     900 Pixel hohen Fenster, davon 544 fuer die Tasten (56 je Zeile) und 308
     fuer den Dialog. Ergebnis: „Überspringen" lag 39 Pixel unterhalb des
     Fensterrands. Kein Ueberdecken diesmal, sondern schlicht ausserhalb --
     eine Verdeckungspruefung allein haette das nicht gesehen, die Messung
     musste auch fragen, ob das Element ueberhaupt im Sichtfenster liegt.

     Jetzt: 40 Pixel je Zeile und ein Portraet von 128. Macht 400 + 276 statt
     544 + 308, und es bleibt Luft fuer die Kopfzeile. */
  /* DREI MAL DREI, wie ein Sudoku-Block -- und wie auf dem Handy dieselbe
     Sache in derselben Form. Bis eben war es hier eine senkrechte Liste aus
     neun breiten Balken: „Zwei Gestaltungen für dieselbe Funktion." Die
     Liste hatte ihren Grund, solange in jeder Taste ein Rollenname stand;
     seit dort nur noch Ziffer und Restzahl stehen, hat sie keinen mehr. */
  .fb-keys { width: 100%; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
  /* Neun Ziffern fuellen genau drei Zeilen, die Notiztaste stand danach als
     zehnte allein in einer vierten Zeile ganz links. Ueber die volle Breite
     liest sie sich als das, was sie ist: ein Werkzeug, keine zehnte Ziffer. */
  .fb-key-werkzeug { grid-column: 1 / -1; }
  .fb-key { padding: 10px 6px; }
  .fb-key-d { font-size: var(--fs-lg); }
  .fb-key-c { font-size: var(--fs-sm); }

  /* Im Dialog steht das Portraet ueber dem Text, nicht daneben: In 340 Pixeln
     blieben neben einem Bild keine lesbaren 45 Zeichen uebrig.

     Und er steht OBEN in der Spalte. Unter den Tasten waere er das Letzte,
     was man liest, obwohl er das Erste ist, worum es geht -- ausserdem
     springt er sonst jedes Mal, wenn die Ziffernleiste ein- oder ausblendet. */
  .fb-dlg { flex-direction: column; align-items: center; gap: 12px; padding: 16px;
            order: -1; }
  .fb-dlg-bild { width: 128px; height: 128px; }
  .fb-dlg-text { width: 100%; }
  #screen-fuchsbau.fb-vorspann .fb-wrap { min-height: calc(100svh - 120px); }
  #screen-fuchsbau.fb-vorspann .fb-dlg-bild { width: 220px; height: 220px; }
}

/* Das Bild auf dem Abschlussbildschirm. Ohne Gesicht ist „Der Bau ist
   gefallen" nur eine Zeile Text. */
.fb-end-bild { width: 120px; height: 120px; border-radius: var(--radius-md);
  object-fit: cover; border: 1px solid var(--gold-soft); margin: 0 auto 12px; display: block;
  /* Sterne poppen, die Punktzahl kommt mit der Karte -- nur Nona stand
     einfach da, obwohl die Animation dafuer schon geschrieben war (unter
     einem Klassennamen, den kein Element traegt). */
  animation: starpop .5s ease backwards; }
/* Das Konfetti faellt HINTER die Karte. Vorher lag es darauf, also auch auf
   Nonas Gesicht: `.confetti` ist absolut positioniert, und die Pausenkarte
   hatte weder `position` noch `z-index`, mit dem sie dagegenhalten konnte. */
#fb-end .pause-card { position: relative; z-index: 2; }
/* Die Karte ist mit Bild, Titel, Sternen, Punktzahl und drei Knoepfen rund
   635 Pixel hoch. Bei 659 nutzbaren Pixeln reicht das knapp -- und bei einem
   kleineren Fenster wurde oben und unten beschnitten, „Zum Menü" war dann
   nicht mehr erreichbar. Die Ebene ist zentriert, hatte aber keinen Ausweg. */
#fb-end { overflow-y: auto; }
@media (max-height: 700px) {
  .fb-end-bild { width: 88px; height: 88px; margin-bottom: 8px; }
}

/* Eine Ziffer, die schon neunmal steht, wird still: Sie ist nicht kaputt,
   sie ist erledigt. */
/* Die eine Ziffer, die das Tutorial gerade meint. Sie pulst, alle anderen
   sind gesperrt: Eine Einfuehrung prueft nicht, sie zeigt. */
.fb-key-gemeint {
  box-shadow: 0 0 0 2px var(--gold), 0 0 16px 2px rgba(226, 180, 85, .5);
  animation: fb-puls 1.6s ease-in-out infinite;
}
.fb-key[disabled] { opacity: .3; }
@media (prefers-reduced-motion: reduce) { .fb-key-gemeint { animation: none; } }
/* WENN EIN TURM WAECHST, leuchtet das Drittel auf, das ihn gebaut hat.
   Das ist die Verbindung, um die es in dieser Raetselart geht, und sie laesst
   sich nur zeigen. */
#fb-board.fb-jubel .cell { transition: background .18s ease; }
#fb-board.fb-jubel[data-jubel="0"] .cell:nth-child(9n+1),
#fb-board.fb-jubel[data-jubel="0"] .cell:nth-child(9n+2),
#fb-board.fb-jubel[data-jubel="0"] .cell:nth-child(9n+3),
#fb-board.fb-jubel[data-jubel="1"] .cell:nth-child(9n+4),
#fb-board.fb-jubel[data-jubel="1"] .cell:nth-child(9n+5),
#fb-board.fb-jubel[data-jubel="1"] .cell:nth-child(9n+6),
#fb-board.fb-jubel[data-jubel="2"] .cell:nth-child(9n+7),
#fb-board.fb-jubel[data-jubel="2"] .cell:nth-child(9n+8),
#fb-board.fb-jubel[data-jubel="2"] .cell:nth-child(9n+9) {
  animation: fb-drittel-jubel 1.4s ease;
}
/* NICHT `background` ANIMIEREN.

   Eine Animation schlaegt normale Deklarationen. Solange sie lief, stand auf
   jeder Zelle des Drittels derselbe Grundton -- das Schachbrett, die Auswahl,
   die gleiche Ziffer und der Goldrand am vollen Block waren 1,4 Sekunden lang
   weg und schnappten danach zurueck. Gefeiert wurde also, indem das Brett
   kurz kaputtging.

   Und die Farbe war `--cell-success`, ein Gruen: das einzige Gruen auf einem
   weinroten Brett. Gemeint war Gold, das stand sogar als Ersatzwert daneben.

   Ueber `box-shadow` bleibt der Hintergrund unberuehrt, und der Anfangs- und
   Endzustand ist genau der Goldrand, den die volle Zelle ohnehin traegt. */
@keyframes fb-drittel-jubel {
  0%   { box-shadow: inset 0 0 0 1px var(--gold-soft); }
  22%  { box-shadow: inset 0 0 0 2px var(--gold), inset 0 0 22px rgba(236, 199, 106, 0.42); }
  100% { box-shadow: inset 0 0 0 1px var(--gold-soft); }
}
/* Und die Zahl in der Kachel macht den Sprung mit. */
.fb-stufe-hoch { animation: fb-stufe-hoch .9s ease; display: inline-block; }
@keyframes fb-stufe-hoch {
  0%   { transform: scale(1);   color: var(--text); }
  30%  { transform: scale(1.5); color: var(--gold); }
  100% { transform: scale(1);   color: var(--text); }
}
@media (prefers-reduced-motion: reduce) {
  #fb-board.fb-jubel .cell, .fb-stufe-hoch { animation: none; }
}

/* Der Notizschalter im zehnten Tastenfeld. Er sieht aus wie eine Taste,
   weil er eine ist -- nur schaltet er um, statt zu setzen. */
.fb-key-werkzeug .fb-key-d { font-size: var(--fs-md); color: var(--muted); }
.fb-key-werkzeug .fb-key-c { font-size: 10px; }
.fb-key-an { box-shadow: inset 0 0 0 2px var(--gold); background: var(--surface-2); }
.fb-key-an .fb-key-d, .fb-key-an .fb-key-c { color: var(--gold); }
/* Im Notizmodus zeigt die Zelle, dass jetzt Bleistift gilt. */
#screen-fuchsbau.fb-notizen #fb-board .cell.selected { box-shadow: inset 0 0 0 2px var(--num-pencil); }

.fb-key-fertig { opacity: .4; }
.fb-key-fertig .fb-key-d { color: var(--muted); }

/* Nonas Gesicht ueber den Sternen. Die anderen Raetselarten haben das nicht,
   und genau darum geht es: „Und das obwohl Nona dabei ist." */
.win-fox {
  display: block; margin: 0 auto 10px;
  width: 96px; height: 96px; border-radius: var(--radius-md);
  object-fit: cover; border: 1px solid var(--gold-soft);
  animation: starpop .5s ease backwards;
}

/* SEHR SCHMALE GERAETE BRAUCHEN MEHR ABZUG, nicht alle.

   Auf einem 320er Bildschirm bricht die Kopfzeile um, und das kostet Hoehe,
   die die 234 im Handy-Budget nicht kennen: Gemessen standen die letzten
   Tasten 16 Pixel unter der Kante. Den Abzug fuer alle zu erhoehen waere der
   falsche Weg -- auf dem Geraet, auf dem wirklich gespielt wird (393 breit,
   659 hoch), wuerde das Brett dadurch kleiner, und genau darum ging die
   letzte Runde. Also nur hier, und weil die Regel nach dem 820er Block
   steht, gewinnt sie ohne hoehere Spezifitaet. */
@media (max-width: 360px) {
  #screen-fuchsbau { --fb-breite: min(96vw, max(186px, calc((100svh - 252px - var(--fb-sai)) / 1.26))); }
}

/* Der Start haelt die Landing zurueck, bis feststeht, wer da ist.
   Regel und Begruendung: js/einstieg.js, Ausfuehrung: js/app.js.
   Der ID-Selektor sticht `.screen.active` ohne !important. */
body.start-wartet #screen-landing { display: none; }

/* Die Uebung in den Einfuehrungen: eine Ziffernleiste zum Antippen.
   Aufgabe darueber, neun Ziffern darunter. 44px, damit ein Finger trifft --
   dieselbe Groesse wie in der echten Ziffernleiste des Spiels. */
.ip-pad { margin: 10px 0 4px; }
.ip-aufgabe {
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--accent);
  margin-bottom: 8px;
}
/* Fuenf und vier, wie die echte Ziffernleiste im Spiel. Als Flexbox brach
   sie 7 + 2 um, und die 8 und die 9 wurden dabei doppelt so breit wie der
   Rest -- eine Leiste, die anders aussieht als die im Spiel, lehrt die
   falsche Bewegung. */
.ip-reihe { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; }
.ip-ziffer {
  min-height: 44px;
  border-radius: 10px;
  border: 1px solid var(--panel-brd);
  background: var(--surface-1);
  color: var(--text);
  font-family: var(--font-display);
  font-size: 17px;
  cursor: pointer;
  transition: background-color 0.15s, border-color 0.15s, color 0.15s;
}
/* Gewaehlt: gefuellt statt nur umrandet. Ein Rahmen allein ist auf einem
   dunklen Grund mit dem Daumen darueber kaum zu sehen. */
.ip-ziffer.an {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--color-text-primary);
}

/* Kein Trennstrich in den Einfuehrungs-Titeln.

   `.profile-title` traegt `hyphens: auto`, und das ist dort richtig: Es
   haelt „Datenschutzerklaerung" davon ab, ueber den Kartenrand zu laufen.
   Die Titel der Einfuehrungen sind dagegen Saetze aus mehreren Woertern --
   dort waehlte der Umbruch „Die Summe ver-rät die Ziffern", obwohl eine
   Leerstelle davor frei gewesen waere. `overflow-wrap` bleibt als Netz fuer
   den Fall, dass doch einmal ein langes Wort kommt; es bricht dann ohne
   Strich statt ueber den Rand zu laufen. */
#intro-title { hyphens: manual; overflow-wrap: anywhere; }

/* Apples Anmeldeknopf. Schwarz mit weisser Schrift und Apfel, wie Apples
   Vorgaben es verlangen -- ein Knopf in unserer Hausfarbe waere dort nicht
   zulaessig. Im hellen Thema bleibt er schwarz: Apple laesst genau zwei
   Fassungen zu, und die weisse braeuchte einen Rahmen, der auf Creme wieder
   unsichtbar waere. */
.btn-apple {
  background: #000;
  color: #fff;
  border: 1px solid #000;
}
.btn-apple:hover { background: #1a1a1a; border-color: #1a1a1a; }

/* Die Karte fuer ein volles, aber falsches Brett. Sie leiht sich Rahmen und
   Kopf von `.profile-overlay`; hier steht nur, was ihr eigen ist. */
.voll-text { margin: 0 0 16px; color: var(--muted); font-size: 14px; line-height: 1.5; }
.voll-weiter { margin-top: 8px; }

/* Der Streifen „Weiterspielen" auf der Landing. Aufbau wie `.win-store`
   darueber: Text nimmt den Platz, Knopf und Kreuz bleiben schmal. Das Kreuz
   ist 44 Pixel, weil es sonst dreimal getroffen werden muss. */
.gast-weiter {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 14px;
  padding: 10px 12px;
  border: 1px solid var(--panel-brd);
  border-radius: 14px;
  background: var(--surface-1);
  text-align: left;
}
.gast-weiter[hidden] { display: none; }
.gw-text { flex: 1 1 auto; font-size: 13px; color: var(--text); }
.gast-weiter .btn { flex: none; white-space: nowrap; }
.gw-zu {
  flex: none; width: 44px; height: 44px; margin: -10px -12px -10px 0;
  padding: 0; border: 0; background: none; color: var(--muted);
  font-size: 20px; line-height: 1; cursor: pointer;
}
.gw-zu:hover { color: var(--text); }
@media (max-width: 560px) {
  /* Gleiche Ueberlegung wie bei `.win-store`: Der Text braucht seine eigene
     Zeile, sonst steht er vierzeilig neben einem Knopf. */
  .gast-weiter { flex-wrap: wrap; gap: 8px 10px; }
  .gw-text { flex: 1 1 100%; }
  .gw-zu { margin-left: auto; }
}

/* „unterbrochen" unter der Serienzahl: Der Ton sagt schon, dass hier etwas
   offen ist, ohne dass der Satz laut werden muss. */
.dash-unterbrochen { color: var(--warning, var(--accent)); }

/* Das Konto-Angebot im Duell-Sieg. Aufbau wie `.win-store` darueber, damit
   der Gewonnen-Bildschirm nicht zwei Macharten fuer dieselbe Sache hat. */
.win-duell-konto {
  display: flex; align-items: center; gap: 10px;
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--panel-brd);
  text-align: left;
}
.win-duell-konto[hidden] { display: none; }
.wdk-text { flex: 1 1 auto; font-family: var(--font-body); font-size: 13px; font-weight: 400; color: var(--muted); }
.win-duell-konto .btn { flex: none; white-space: nowrap; }
@media (max-width: 560px) {
  .win-duell-konto { flex-wrap: wrap; gap: 8px 10px; }
  .wdk-text { flex: 1 1 100%; }
}

/* Das Feld mit dem Duell-Link. `readonly` und nicht `disabled`: Ein
   deaktiviertes Feld laesst sich nicht markieren, und Markieren ist der Weg,
   der immer funktioniert -- auch dort, wo die Zwischenablage abgelehnt wird. */
.win-duell-url {
  flex: 1 1 100%;
  min-width: 0;
  padding: 9px 11px;
  border-radius: 10px;
  border: 1px solid var(--panel-brd);
  background: var(--scrim);
  color: var(--text);
  font-family: inherit;
  /* 16px, damit iOS beim Antippen nicht die ganze Seite vergroessert --
     dieselbe Grenze wie beim Melde-Feld, siehe #feedback-message. */
  font-size: 16px;
}

/* DER FORTSETZEN-KASTEN. Er ersetzt sechs einzelne Streifen (`.resume-banner`,
   bis 1.17.159): eine Ueberschrift, darunter je angefangenem Raetsel eine
   Zeile. Der Rahmen links ist derselbe Akzentstrich, den die Streifen hatten
   -- die Startseite soll nicht aussehen, als haette sie ein neues Bauteil
   bekommen, sondern als waere aufgeraeumt worden. */
.resume-box {
  border-radius: 14px;
  padding: 14px 18px;
  margin-bottom: 22px;
  border-left: 3px solid var(--accent);
}
.resume-box[hidden] { display: none; }
.resume-box-head { margin-bottom: 10px; }
.resume-list { display: flex; flex-direction: column; gap: 8px; }
.resume-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.resume-row-text { flex: 1 1 auto; min-width: 0; font-size: 14px; color: var(--text); }
.resume-row .btn { flex: none; white-space: nowrap; }
/* 44 Pixel, wie ueberall: ein Kreuz, das man dreimal treffen muss, ist
   schlimmer als keins. */
.resume-row-weg {
  flex: none; width: 44px; height: 44px; margin: -8px -10px -8px 0;
  padding: 0; border: 0; background: none; color: var(--muted);
  font-size: 16px; line-height: 1; cursor: pointer;
}
.resume-row-weg:hover { color: var(--text); }
@media (max-width: 560px) {
  /* Am Handy braucht der Text seine eigene Zeile: „Binärrätsel · Level 12 ·
     04:31" neben einem Knopf und einem Kreuz stuende sonst dreizeilig. */
  .resume-row { flex-wrap: wrap; gap: 6px 10px; }
  .resume-row-text { flex: 1 1 100%; }
  .resume-row-weg { margin-left: auto; }
}

/* HIER STANDEN DREI SPALTEN FUER DIE RAETSELARTEN, UND SIE SIND WIEDER WEG.

   Eingebaut am 09.09.2026 nach einer Nachpruefung: Zehn Kacheln zu je 233
   Pixel in zwei Spalten ergeben 1.219 Pixel auf einer Seite von 3.269, das
   war der laengste einzelne Posten. Drei Spalten machten daraus 726.

   Korbi am 11.09.2026, mit einem Bildschirmfoto vom iPhone: schlecht. Die
   Bilder auf den Kacheln sind der Grund, warum jemand eine Art ausprobiert,
   und bei drei Spalten sind sie briefmarkengross. Die Rechnung war richtig
   und die Schlussfolgerung trotzdem falsch -- eine kuerzere Seite ist kein
   Wert an sich, wenn das, was sie kuerzer macht, die Ware ist.

   Wer die Seitenlaenge wieder angehen will: Das Mittel ist nicht, die
   Kacheln zu schrumpfen, sondern weniger davon auf einmal zu zeigen. Diese
   Zeilen kommen nicht zurueck. */

