/* ============================================================
   Layout & Komponenten - Neo-Brutalismus
   ------------------------------------------------------------
   #app ist der Layout-Container. Alle Kinder liegen flach darin,
   die Bildschirm-Gruppierung laeuft ueber data-*-Attribute.

   Bewusst KEIN overflow:hidden auf #app - die harten Schatten und
   die leicht gekippten Flaechen ragen ueber ihre Box hinaus und
   wuerden sonst abgeschnitten.

   Die Grundregeln (Kante, Schatten, Volltonfarbe, Druckversatz)
   stehen in base.css. Hier stehen nur die Bauteile.
   ============================================================ */

#app {
  position: relative;
  max-width: 500px;
  margin: 0 auto;
  min-height: 100dvh;
  /* Oben derselbe Ausgleich wie unten. Das viewport-fit=cover in der
     index.html laesst die Seite bis unter Notch und Statusleiste
     laufen; ohne den Inset klebt die Topbar auf einem iPhone dort
     drunter. Auf Android und am Rechner ist env() gleich 0, die
     Zeile aendert dort also nichts. */
  padding:
    calc(16px + env(safe-area-inset-top))
    16px
    calc(40px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 16px;
}

#app > [hidden] { display: none !important; }

/* Ebenen explizit: .bg liegt auf 0, alle Inhalte auf 1.
   Ohne das uebermalt die fixierte .bg jeden statischen Inhalt
   (Ueberschriften verschwinden) - genau das ist hier schon passiert. */
#app > * { position: relative; z-index: 1; }
/* Spezifitaet muss die ID-Regel darueber schlagen, sonst wird das
   position:fixed zu relative und die Flaeche kollabiert auf Hoehe 0.
   Die Geometrie steht hier absichtlich noch einmal vollstaendig und
   identisch zu base.css: ein "inset: 0" an dieser Stelle wuerde die
   feste Hoehe von dort wieder aushebeln, und damit waere das
   iOS-Nachruckeln zurueck. Warum 100lvh: siehe base.css. */
#app > .bg {
  position: fixed;
  top: 0; left: 0; right: auto; bottom: auto;
  width: 100%;
  height: 100vh;
  height: 100lvh;
  z-index: 0;
}

/* Jede Flaeche des Systems teilt dieselbe Kante und denselben harten
   Schatten. Wer eine neue Flaeche baut, nimmt diese Liste auf. */
.topbar,
.panel,
.tile,
.flip__face {
  border: var(--bw) solid var(--ink);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  background: var(--paper);
}

/* ---------------- Topbar ----------------
   Klebt oben: der Zurueck-Button muss auf langen Bildschirmen
   (Namensliste, Auflösung mit 20 Spielern) erreichbar bleiben.
   Deshalb deckend schwarz - darunter scrollt Inhalt durch. */
#app > .topbar { position: sticky; top: calc(10px + env(safe-area-inset-top)); z-index: 5; }
.topbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  min-height: 56px;
  background: var(--ink);
  color: var(--paper);
  box-shadow: var(--sh) var(--sh) 0 var(--c4);
}
/* Feste Plaetze links und rechts, damit der Titel mittig bleibt,
   egal wie viele der beiden Knoepfe gerade sichtbar sind. */
.topbar__slot { width: 40px; flex: 0 0 40px; display: flex; }
.topbar__slot--right { justify-content: flex-end; }
.topbar__btn {
  width: 40px; height: 40px;
  border: var(--bw) solid var(--ink);
  border-radius: var(--r);
  background: var(--c1);
  color: var(--ink);
  font: inherit; font-family: var(--font-display);
  font-size: 1.3rem; font-weight: 900; line-height: 1;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translate3d(0, -8px, 0) scale(0.9);
  transition:
    transform var(--motion-fast) var(--ease),
    opacity var(--motion-fast) var(--ease),
    visibility 0s linear var(--motion-fast);
}
.topbar__btn:active { transform: translate(2px, 2px); }
.topbar__btn.is-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translate3d(0, 0, 0) scale(1);
  transition-delay: 0s;
}
.topbar__btn.is-visible:active { transform: translate(2px, 2px); }
.topbar__btn--rules { background: var(--c3); font-size: 1.15rem; }
.topbar__title {
  font-family: var(--font-display);
  font-size: 1.05rem; font-weight: 900;
  text-transform: uppercase; letter-spacing: 0.04em;
  margin: 0; flex: 1; text-align: center;
  color: var(--paper);
}

/* ---------------- Regel-Fenster ----------------
   ACHTUNG: display gehoert an dialog[open], nicht an .modal. Ein
   <dialog> ist geschlossen display:none - ein eigenes display:flex
   auf der Klasse macht es dauerhaft sichtbar. Dieselbe Falle wie bei
   [hidden] (siehe base.css). */
.modal {
  width: min(94vw, 460px);
  max-height: 84dvh;
  padding: 0;
  border: var(--bw) solid var(--ink);
  border-radius: var(--r);
  background: var(--paper);
  color: var(--ink);
  box-shadow: 8px 8px 0 var(--ink);
  overflow: hidden;
}
.modal[open] {
  display: flex;
  flex-direction: column;
  animation: motion-modal-in 0.22s var(--ease) both;
}
/* Der Hintergrund ist die einzige Flaeche im System mit Transparenz -
   ohne sie sieht man nicht mehr, dass die Seite dahinter weiterlebt. */
.modal::backdrop { background: rgba(22, 17, 13, 0.6); }
.modal[open]::backdrop { animation: motion-backdrop-in 0.18s ease-out both; }

@keyframes motion-modal-in {
  from { opacity: 0; transform: translate3d(0, 16px, 0) scale(0.96); }
  to { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }
}

@keyframes motion-backdrop-in {
  from { background: rgba(22, 17, 13, 0); }
  to { background: rgba(22, 17, 13, 0.6); }
}

.modal__head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px;
  background: var(--ink);
  border-bottom: var(--bw) solid var(--ink);
}
.modal__title {
  flex: 1; margin: 0;
  font-family: var(--font-display);
  font-size: 1.05rem; font-weight: 900;
  text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--paper);
}
.modal__close {
  width: 34px; height: 34px; flex: 0 0 34px;
  border: var(--bw) solid var(--ink);
  border-radius: var(--r);
  background: var(--c2);
  color: var(--ink);
  font: inherit; font-family: var(--font-display);
  font-size: 0.95rem; font-weight: 900; line-height: 1;
  cursor: pointer;
  transition: transform var(--dur) var(--ease);
}
.modal__close:active { transform: translate(2px, 2px); }
.modal__body { padding: 16px; overflow-y: auto; }

/* Regeltext aus Spielregeln.md */
.rules p { margin: 0 0 12px; font-size: 0.94rem; font-weight: 500; line-height: 1.5; }
.rules p:last-child { margin-bottom: 0; }
.rules strong { font-weight: 900; }
.rules code {
  font-family: var(--font-mono); font-size: 0.86rem;
  background: var(--muted); border: 2px solid var(--ink); padding: 0 4px;
}
.rules__sub {
  font-family: var(--font-display);
  font-size: 0.95rem; font-weight: 900;
  text-transform: uppercase; letter-spacing: 0.04em;
  margin: 18px 0 10px;
  background: var(--c3);
  border: var(--bw) solid var(--ink);
  box-shadow: 4px 4px 0 var(--ink);
  padding: 5px 10px;
}
.rules__list { margin: 0 0 12px; padding-left: 20px; list-style: none; }
.rules__list li {
  position: relative;
  font-size: 0.92rem; font-weight: 500; line-height: 1.45;
  margin-bottom: 8px;
}
/* Eckiger Aufzaehlungspunkt - runde Punkte gibt es hier nicht. */
.rules__list li::before {
  content: "";
  position: absolute; left: -18px; top: 0.45em;
  width: 8px; height: 8px;
  background: var(--c2);
  border: 2px solid var(--ink);
}

/* ---------------- Text ---------------- */
.headline {
  font-family: var(--font-display);
  font-size: 1.7rem; font-weight: 900;
  text-transform: uppercase; letter-spacing: -0.01em;
  line-height: 1.05;
  margin: 6px 0 0; text-align: center;
  /* Versetzter Farbschatten statt Weichzeichnung - dieselbe Idee
     wie bei den Flaechen, nur in Schrift. */
  text-shadow: 3px 3px 0 var(--c2);
}
.sub {
  text-align: center;
  color: var(--ink);
  font-size: 0.92rem; font-weight: 600; line-height: 1.45;
  margin: 0;
}
/* Fehler brauchen keinen Farbverlauf, sondern einen Kasten. */
.error {
  color: var(--paper);
  font-size: 0.84rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.04em;
  text-align: center;
  margin: 0;
  min-height: 0;
}
.error:not(:empty) {
  background: var(--danger);
  border: var(--bw) solid var(--ink);
  box-shadow: 4px 4px 0 var(--ink);
  padding: 10px 12px;
  animation: motion-notice-in 0.2s var(--ease) both;
}

@keyframes motion-notice-in {
  from { opacity: 0; transform: translate3d(0, -8px, 0); }
  to { opacity: 1; transform: translate3d(0, 0, 0); }
}

/* ---------------- Panels ---------------- */
.panel { padding: 18px; display: flex; flex-direction: column; gap: 16px; }
.panel--tight { padding: 0; gap: 0; }

/* ---------------- Hub ---------------- */
.hero { text-align: center; padding: 14px 4px 0; }
.hero__title {
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 15vw, 3.6rem);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: -0.03em;
  line-height: 0.92;
  margin: 0 0 12px;
  color: var(--paper);
  /* Gestapelte harte Schatten: Papier auf Cyan auf Tinte. */
  text-shadow:
     3px  3px 0 var(--ink),
     8px  8px 0 var(--c1),
    11px 11px 0 var(--ink);
  transform: rotate(-2deg);
}

.hero__intro { font-size: 0.82rem; }
.hub-guides { display: flex; flex-direction: column; gap: 4px; }
.hub-guides__title {
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 900;
  text-transform: uppercase;
}
.hub-guides a {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 8px 4px;
  color: var(--ink);
  font-size: 0.9rem;
  text-underline-offset: 4px;
}

.tile {
  display: flex; align-items: center; gap: 14px;
  padding: 16px; width: 100%; text-align: left;
  font: inherit; color: var(--ink);
  cursor: pointer;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.tile:active {
  transform: translate(var(--sh), var(--sh));
  box-shadow: 0 0 0 var(--ink);
}
/* Kacheln laufen die Palette durch. Reihum, damit der Hub bunt ist
   und trotzdem geordnet bleibt. */
.tile:nth-of-type(4n+1) { background: var(--c1); }
.tile:nth-of-type(4n+2) { background: var(--c2); }
.tile:nth-of-type(4n+3) { background: var(--c3); }
.tile:nth-of-type(4n+4) { background: var(--c5); }

.tile__icon {
  width: 54px; height: 54px; flex: 0 0 54px;
  border: var(--bw) solid var(--ink);
  border-radius: var(--r);
  display: grid; place-items: center;
  font-size: 1.7rem;
  background: var(--paper);
  transform: rotate(-4deg);
}
.tile__body { flex: 1; min-width: 0; display: block; }
.tile__name {
  font-family: var(--font-display);
  font-size: 1.1rem; font-weight: 900;
  text-transform: uppercase; letter-spacing: 0.01em;
  display: block; margin-bottom: 3px;
}
.tile__desc { font-size: 0.85rem; font-weight: 600; display: block; line-height: 1.35; }
.tile__meta {
  font-size: 0.7rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.07em;
  display: block; margin-top: 7px;
}
.tile__chevron { font-family: var(--font-display); font-size: 1.5rem; font-weight: 900; }

/* Zwei Klassen, damit die nth-of-type-Regel oben nicht gewinnt. */
/* Im Raum, aber nicht Host: die Spielwahl liegt beim Host. Sichtbar
   bleiben die Kacheln trotzdem - man will ja sehen, was es gibt. */
.tile:disabled { background: var(--muted); color: var(--text-dim); cursor: default; }
.tile:disabled .tile__icon { background: var(--paper); }
.tile:disabled:active { transform: none; box-shadow: var(--shadow); }

.tile.tile--soon {
  background: transparent;
  border-style: dashed;
  box-shadow: none;
  cursor: default;
}
.tile--soon:active { transform: none; box-shadow: none; }
.tile--soon .tile__icon { background: var(--muted); box-shadow: none; font-size: 1.4rem; }

/* ---------------- Stepper ---------------- */
.stepper { display: flex; align-items: center; gap: 10px; }
.stepper__btn {
  width: 48px; height: 48px; flex: 0 0 48px;
  border: var(--bw) solid var(--ink);
  border-radius: var(--r);
  background: var(--c5);
  color: var(--ink);
  font: inherit; font-family: var(--font-display);
  font-size: 1.4rem; font-weight: 900; line-height: 1;
  cursor: pointer;
  box-shadow: 3px 3px 0 var(--ink);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.stepper__btn:active { transform: translate(3px, 3px); box-shadow: none; }
.stepper .input {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 1.3rem; font-weight: 700;
  -moz-appearance: textfield;
}
.stepper .input::-webkit-outer-spin-button,
.stepper .input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Steht anstelle des Zahlenfeldes, wenn die Anzahl der Imposter dem
   Zufall ueberlassen ist. Sieht aus wie das Feld, ist aber keins -
   eine Zahl gibt es hier ja bewusst nicht zu sehen. */
.stepper__random {
  display: flex; align-items: center; justify-content: center;
  background: var(--c3);
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 0.95rem; font-weight: 900;
  text-transform: uppercase; letter-spacing: 0.06em;
  user-select: none;
}
.stepper__hint {
  font-size: 0.74rem; font-weight: 700;
  line-height: 1.35;
  color: var(--text-dim);
}

.checkline {
  display: flex; align-items: center; gap: 12px;
  font-size: 0.92rem; font-weight: 700;
  cursor: pointer;
}
/* Das Kaestchen wird selbst gezeichnet, nicht nur eingefaerbt.
   Grund ist Issue #19: iOS malt eine Checkbox als abgerundetes
   Quadrat mit eigenem Haken, Android als eckiges - mit accent-color
   stimmt also die Farbe ueberein und die Form nicht, ausgerechnet in
   einer Designsprache, deren erste Regel "harte Kante, kein Radius"
   heisst. Selbst gezeichnet sieht es ueberall gleich aus und traegt
   dieselbe Kante, denselben Schatten und denselben Druckversatz wie
   jede andere Flaeche. */
.checkline input {
  appearance: none;
  -webkit-appearance: none;
  width: 24px; height: 24px; flex: 0 0 24px;
  margin: 0;
  border: var(--bw) solid var(--ink);
  border-radius: var(--r);
  background: var(--paper);
  box-shadow: 3px 3px 0 var(--ink);
  display: grid; place-items: center;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), background var(--dur) var(--ease);
}
.checkline input:checked { background: var(--c4); }
/* Der Haken: zwei Kanten eines Kastens, um 45 Grad gedreht. Kein
   Zeichen aus einer Schrift - das saehe auf jedem System anders aus
   und waere genau der Fehler, den diese Regel behebt. */
.checkline input:checked::after {
  content: "";
  width: 6px; height: 11px;
  margin-top: -3px;
  border: solid var(--paper);
  border-width: 0 3px 3px 0;
  transform: rotate(45deg);
}
/* Gedrueckt heisst verschoben - Regel 4, hier wie ueberall. */
.checkline input:active { transform: translate(3px, 3px); box-shadow: none; }

.chip-row { display: flex; flex-wrap: wrap; gap: 8px; }

/* ---------- Wortbombe ---------- */
.wb-custom-field { margin-top: 18px; }

/* Die lokale Namensliste besteht aus normalen .field/.input-Bausteinen.
   Ohne eigenen Zeilenabstand stossen Beschriftung und harter Schatten des
   vorherigen Felds direkt aneinander. Der Abstand gehoert nur hierhin, damit
   kompakte Formulare an anderer Stelle kompakt bleiben. */
#wb-names {
  gap: 18px;
  padding-top: 2px;
}
#wb-names .label {
  padding-left: 3px;
  font-size: 0.82rem;
  line-height: 1.15;
}
#wb-names .input {
  min-height: 56px;
  padding: 15px 16px;
}

.wordbomb-stage {
  min-height: 220px;
  display: grid;
  place-items: center;
  overflow: hidden;
}

.wordbomb-bomb {
  width: 170px;
  height: 170px;
  display: grid;
  place-items: center;
  border: var(--bw) solid var(--ink);
  border-radius: 50%;
  background: var(--c5);
  box-shadow: 8px 8px 0 var(--ink);
  transform: rotate(-4deg);
  animation: wordbomb-float 1.7s steps(2, end) infinite;
}

.wordbomb-bomb span {
  font-size: 5.6rem;
  line-height: 1;
  filter: grayscale(1) contrast(1.4);
}

.wordbomb-bomb.is-beat {
  animation: wordbomb-beat 140ms steps(2, end) both;
  background: var(--c2);
}

.wordbomb-stage.is-exploded .wordbomb-bomb {
  width: 190px;
  height: 190px;
  background: var(--c2);
  animation: wordbomb-boom 520ms steps(5, end) both;
}

.wordbomb-theme { text-align: center; }
.wordbomb-theme strong {
  display: block;
  margin-top: 8px;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(1.9rem, 10vw, 3.2rem);
  line-height: 1;
  overflow-wrap: anywhere;
}

.wordbomb-result { text-align: center; font-family: var(--font-display); font-size: 1.25rem; }
.wordbomb-result p { margin: 0; font-weight: 900; }
.wordbomb-hearts { color: var(--ink); letter-spacing: 2px; white-space: nowrap; }

/* panel--tight nimmt normalerweise jede Innenkante weg. Bei der Frage nach
   dem Getroffenen braucht die Ueberschrift bewusst Luft und eine eigene
   Flaeche, damit sie nicht links oben am Rahmen klebt. */
#wb-loser-panel > .label {
  padding: 17px 18px 14px;
  border-bottom: var(--bw) solid var(--ink);
  background: var(--c1);
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 5vw, 1.35rem);
  line-height: 1.08;
  letter-spacing: 0.04em;
}
#wb-loser-list .result-row--button {
  min-height: 62px;
  padding: 15px 18px;
  font-size: 1.05rem;
}

.result-row--button {
  font: inherit;
  width: 100%;
  border: 0;
  border-bottom: var(--bw) solid var(--ink);
  border-radius: 0;
  background: var(--paper);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
.result-row--button:last-child { border-bottom: 0; }
.result-row--button:active { background: var(--c3); transform: translate(2px, 2px); }

@keyframes wordbomb-float {
  0%, 100% { transform: translate(-4px, 0) rotate(-4deg); }
  50% { transform: translate(5px, -7px) rotate(4deg); }
}

@keyframes wordbomb-beat {
  0% { transform: scale(1) rotate(-4deg); box-shadow: 8px 8px 0 var(--ink); }
  100% { transform: scale(1.12) rotate(5deg); box-shadow: 3px 3px 0 var(--ink); }
}

@keyframes wordbomb-boom {
  0% { transform: scale(.35) rotate(-18deg); }
  70% { transform: scale(1.18) rotate(8deg); }
  100% { transform: scale(1) rotate(0); }
}

@media (prefers-reduced-motion: reduce) {
  .wordbomb-bomb,
  .wordbomb-bomb.is-beat,
  .wordbomb-stage.is-exploded .wordbomb-bomb { animation: none; }
}

/* ---------------- Moduswahl ----------------
   Zwei Bloecke untereinander statt nebeneinander: die Erklaerung ist
   der eigentliche Inhalt, und nebeneinander bliebe davon auf einem
   Handy je Spalte eine Silbe pro Zeile uebrig.

   Ausgewaehlt heisst hier GEDRUECKT - dieselbe Sprache wie bei jedem
   anderen Bedienelement: die aktive Flaeche sitzt auf ihrem Schatten. */
.seg { display: flex; flex-direction: column; gap: 10px; }
.seg__btn {
  display: block; width: 100%; text-align: left;
  padding: 12px 14px;
  border: var(--bw) solid var(--ink);
  border-radius: var(--r);
  background: var(--paper);
  box-shadow: 4px 4px 0 var(--ink);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), background var(--dur) var(--ease);
}
.seg__btn.is-active {
  background: var(--c1);
  transform: translate(4px, 4px);
  box-shadow: none;
}
/* Im Raum sucht nur der Host den Modus aus. Fuer alle anderen sind die
   beiden Knoepfe taub - aber sichtbar, damit man sieht, was gespielt
   wird. Dieselbe Entscheidung wie bei .tile:disabled im Hub.
   Der gewaehlte Modus behaelt dabei seine Farbe und bleibt damit auch
   ohne Bedienbarkeit als Auswahl lesbar; die Spezifitaet der Regel
   darunter schlaegt die daneben. */
.seg__btn:disabled { background: var(--muted); color: var(--text-dim); cursor: default; }
.seg__btn.is-active:disabled { background: var(--c1); color: var(--ink); }
.seg__btn:disabled:active { transform: none; box-shadow: 4px 4px 0 var(--ink); }
.seg__btn.is-active:disabled:active { transform: translate(4px, 4px); box-shadow: none; }
.seg__name {
  display: block;
  font-family: var(--font-display);
  font-size: 0.95rem; font-weight: 900;
  text-transform: uppercase; letter-spacing: 0.03em;
  margin-bottom: 2px;
}
.seg__name::before { content: "○ "; }
.seg__btn.is-active .seg__name::before { content: "● "; }
.seg__desc { display: block; font-size: 0.8rem; font-weight: 600; line-height: 1.3; }

/* ---------------- Namensliste ---------------- */
.names { display: flex; flex-direction: column; }
.name-row {
  display: flex; align-items: center; gap: 12px;
  padding: 0 12px;
  border-bottom: var(--bw) solid var(--ink);
}
.names .name-row:last-child { border-bottom: none; }
.name-row__idx {
  font-family: var(--font-mono);
  font-size: 0.9rem; font-weight: 700;
  min-width: 24px;
}
.name-row input {
  appearance: none;
  -webkit-appearance: none;
  flex: 1; background: transparent; border: none; color: var(--ink);
  border-radius: var(--r);
  min-width: 0;
  padding: 15px 0; font: inherit; font-family: var(--font-mono);
  font-size: 1rem; font-weight: 700;
}
.name-row input::placeholder { color: var(--text-dim); font-weight: 600; opacity: 1; }
.name-row input:focus { outline: none; }
/* Der Fokus muss trotzdem sichtbar sein - die Zeile faerbt sich. */
.name-row:focus-within { background: var(--c3); }

/* ---------------- Flip-Karte ----------------
   Die Rueckseite ist der lauteste Teil der Seite: Vollton, harte
   Streifen, dicker Rahmen. Aufgedeckt wird sie ruhig (Papier), damit
   das Wort selbst steht und nichts davon ablenkt.
   ------------------------------------------------------------ */
.flip-wrap { display: flex; justify-content: center; padding: 6px 0 10px; }

.flip {
  width: 100%; max-width: 300px;
  aspect-ratio: 3 / 4;
  perspective: 1400px;
  cursor: pointer; background: none; border: none; padding: 0;
}
.flip__inner {
  position: relative; width: 100%; height: 100%;
  transform-style: preserve-3d;
  transition: transform var(--flip-dur) cubic-bezier(0.4, 1.4, 0.4, 1);
}
.flip.is-flipped .flip__inner { transform: rotateY(180deg); }

.flip__face {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 14px; padding: 22px; text-align: center;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  overflow: hidden;
  box-shadow: 8px 8px 0 var(--ink);
}
.flip__face--back {
  background:
    repeating-linear-gradient(
      -45deg,
      var(--c4) 0 22px,
      var(--c2) 22px 44px
    );
  color: var(--paper);
}
/* Innerer Rahmen: die Karte soll aussehen wie gedruckt, nicht wie
   eine Flaeche mit Muster. */
.flip__face--back::after {
  content: ""; position: absolute; inset: 12px;
  border: var(--bw) dashed var(--paper);
  pointer-events: none;
}
.flip__mark {
  font-size: 2.6rem;
  background: var(--paper);
  border: var(--bw) solid var(--ink);
  padding: 6px 10px;
  transform: rotate(-5deg);
}
.flip__name {
  font-family: var(--font-display);
  font-size: 1.5rem; font-weight: 900;
  text-transform: uppercase; letter-spacing: 0.01em;
  margin: 0; word-break: break-word;
  background: var(--ink);
  color: var(--paper);
  padding: 6px 12px;
}
.flip__cta {
  font-size: 0.78rem; font-weight: 900;
  text-transform: uppercase; letter-spacing: 0.08em;
  margin: 0;
  background: var(--paper); color: var(--ink);
  border: 2px solid var(--ink);
  padding: 4px 10px;
}

.flip__face--front {
  transform: rotateY(180deg);
  background: var(--paper);
}
.flip__label {
  font-size: 0.72rem; font-weight: 900;
  text-transform: uppercase; letter-spacing: 0.14em;
  margin: 0;
  background: var(--c1);
  border: 2px solid var(--ink);
  padding: 4px 10px;
}
/* Nur im Normal-Modus: die Karte sagt der Person, dass sie Imposter ist.
   Dieselbe Farbe wie das Imposter-Badge in der Aufloesung, damit beides
   erkennbar dasselbe meint. */
.flip__label.is-imposter { background: var(--c2); }
/* Schriftgroesse nach Wortlaenge gestaffelt (Klasse setzt imposter.js).
   Ohne das bricht "Bewerbungsgespraech" mitten im Wort um - haesslich und
   auf einer Karte, die man nur kurz sieht, auch schlecht lesbar.
   break-word statt anywhere: bricht nur, wenn es wirklich nicht passt. */
.flip__word {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 8vw, 2.15rem); font-weight: 900;
  text-transform: uppercase;
  line-height: 1.1; margin: 0;
  /* -webkit-hyphens zuerst: aeltere Safari-Versionen (und damit
     aeltere iPads) kennen nur die praefixierte Form. Ohne sie trennt
     iOS gar nicht, waehrend Android trennt - und dasselbe lange Wort
     bricht auf beiden Geraeten an einer anderen Stelle um. */
  overflow-wrap: break-word;
  -webkit-hyphens: auto;
  hyphens: auto;
}
.flip__word.is-long  { font-size: clamp(1.25rem, 6.2vw, 1.72rem); }
.flip__word.is-xlong { font-size: clamp(1.02rem, 5.0vw, 1.38rem); }

.reveal__progress {
  font-family: var(--font-mono);
  font-size: 0.85rem; font-weight: 700;
  text-align: center; margin: 4px 0 0;
  color: var(--ink);
}
.reveal__hint {
  font-size: 0.84rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.05em;
  text-align: center; margin: 0; min-height: 1.3em;
  color: var(--ink);
}

/* ---------------- Timer ---------------- */
.timer { align-items: center; }
.timer__starter {
  margin: 0;
  text-align: center;
  font-size: 0.95rem;
  font-weight: 800;
}
.timer__display {
  font-family: var(--font-mono);
  font-size: 3rem; font-weight: 700;
  font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
  background: var(--c1);
  border: var(--bw) solid var(--ink);
  box-shadow: 5px 5px 0 var(--ink);
  padding: 8px 22px;
}
.timer__display.is-done { background: var(--danger); color: var(--paper); }

/* ---------------- Auflösung ----------------
   Beschriftung und Wort stehen untereinander, nicht in einer Zeile:
   das Wort sitzt in einem Kasten, und zwei Kaesten in einer Zeile
   stuenden je nach Wortlaenge mal mittig, mal am Rand. */
.words-out {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  text-align: center; margin: 0;
  font-size: 0.82rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.07em;
}
.words-out strong {
  font-family: var(--font-display);
  font-size: 1.25rem;
  background: var(--c3);
  border: var(--bw) solid var(--ink);
  box-shadow: 4px 4px 0 var(--ink);
  padding: 4px 12px;
  margin-bottom: 12px;
  letter-spacing: 0;
  max-width: 100%;
  overflow-wrap: break-word;
}
.words-out strong:last-child { margin-bottom: 0; }
/* Jeder Kasten bekommt seinen eigenen Vollton, reihum durch --c1..--c5.
   Bei EINEM Kasten sieht man davon nichts - erst wo mehrere
   untereinander stehen (das Wortpaar bei Imposter, mehrere Liker bei
   Who Liked?) werden daraus Flaechen, die man auseinanderhaelt, ohne
   die Beschriftung zu lesen. Volltoene, keine Verlaeufe, und alle hell
   genug fuer --ink darauf - dieselbe Regel wie ueberall sonst. */
.words-out strong:nth-of-type(5n + 1) { background: var(--c3); }
.words-out strong:nth-of-type(5n + 2) { background: var(--c1); }
.words-out strong:nth-of-type(5n + 3) { background: var(--c2); }
.words-out strong:nth-of-type(5n + 4) { background: var(--c5); }
.words-out strong:nth-of-type(5n + 5) { background: var(--c4); }

.result-list { display: flex; flex-direction: column; }
.result-row {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  padding: 13px 14px;
  border-bottom: var(--bw) solid var(--ink);
}
.result-list .result-row:last-child { border-bottom: none; }
/* Jede zweite Zeile abgesetzt: Zebrastreifen statt Trennlinie-Grau. */
.result-list .result-row:nth-child(even) { background: var(--muted); }
.imposter-summary { background: var(--c2); }
.imposter-summary__names {
  margin: 8px 0 0;
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 6vw, 2rem);
  font-weight: 900;
  overflow-wrap: anywhere;
}
.result-list .result-row--imposter { background: var(--c2); }
/* min-width, damit ein kurzer Name nicht mitten im Wort umbricht,
   sobald rechts daneben drei Marken stehen ("Ann/a"). break-word
   greift dann erst, wenn der Name wirklich zu lang ist. */
.result-row__name {
  font-family: var(--font-display);
  font-weight: 900;
  word-break: break-word;
  flex: 1 1 auto;
  min-width: 4.5em;
}

/* ---------------- Spielen im Raum ----------------
   Alle Flaechen hier sind dieselben wie ohne Raum (.panel,
   .result-list, .flip). Neu ist nur, was es ohne Raum nicht gibt:
   Raumcode, Wahlzeilen und Anwesenheit. */

/* Der Code wird vorgelesen und abgetippt - deshalb gross, in fester
   Zeichenbreite und mit weitem Abstand. */
.input--code {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: 0.35em;
  text-indent: 0.35em; /* gleicht den Abstand hinter dem letzten Zeichen aus */
  text-transform: uppercase;
}

.roomcode { align-items: center; gap: 10px; text-align: center; }
.roomcode__value {
  font-family: var(--font-mono);
  font-size: 2.6rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  line-height: 1.1;
  background: var(--c3);
  border: var(--bw) solid var(--ink);
  box-shadow: 5px 5px 0 var(--ink);
  padding: 8px 18px;
}

.sub--warn {
  background: var(--c5);
  border: var(--bw) solid var(--ink);
  padding: 10px 12px;
  font-size: 0.82rem; font-weight: 700;
  text-align: left;
}

/* Wer nicht mehr pollt, gilt als weg - erkennbar, ohne die Zeile
   verschwinden zu lassen. */
.result-row__name.is-away { color: var(--text-faint); text-decoration: line-through; }
.result-row__name.is-away::after {
  content: " offline";
  font-size: 0.7rem; letter-spacing: 0.06em;
  text-decoration: none;
}

.result-row__right { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }

/* Wahlzeile: sieht aus wie eine Ergebniszeile, ist aber ein Button. */
.result-row--pick {
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: var(--bw) solid var(--ink);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.result-row--pick:active { transform: translate(2px, 2px); }
/* Beide Regeln muessen die Zebra-Regel oben schlagen (gleiche Zahl an
   Klassen, deshalb hier ebenfalls mit .result-list davor). */
.result-list .result-row--pick:disabled { background: var(--muted); color: var(--text-faint); cursor: default; }
.result-list .result-row--pick.is-picked { background: var(--c1); }
.result-row--pick.is-picked .result-row__name::after {
  content: " ✓";
  font-weight: 900;
}

/* ---------------- Rauswerfen ----------------
   Der Knopf, mit dem der Host jemanden aus der Party nimmt. Er sitzt
   am rechten Rand einer Zeile, neben den Badges - also klein, aber mit
   derselben harten Kante und demselben harten Schatten wie jede andere
   Flaeche, die man druecken kann (Regel 4: gedrueckt heisst
   verschoben). Die 2 px sind die Staerke der Badges daneben; mit 3 px
   wirkte der Knopf schwerer als die ganze Zeile.

   .is-armed ist der Zwischenzustand nach dem ersten Tipp: aus dem "x"
   wird eine Frage, und sie ist rot. Erst der zweite Tipp wirft. */
.kick {
  min-width: 34px; height: 34px;
  flex: 0 0 auto;
  padding: 0 8px;
  border: 2px solid var(--ink);
  border-radius: var(--r);
  background: var(--paper);
  color: var(--ink);
  font: inherit; font-family: var(--font-display);
  font-size: 1.05rem; font-weight: 900; line-height: 1;
  cursor: pointer;
  box-shadow: 3px 3px 0 var(--ink);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.kick:active { transform: translate(3px, 3px); box-shadow: none; }
.kick.is-armed {
  background: var(--c2);
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
}

/* ---------------- Party ----------------
   Die Flaeche der Party haengt direkt unter der Topbar und ist auf
   jedem Bildschirm da (js/party.js). Auf der Startseite ist sie die
   volle Flaeche mit Code, Liste und Verlassen-Knopf; in einem Spiel
   schrumpft sie auf einen Streifen.

   Dieselben vier Regeln wie ueberall: harte Kante, harter Schatten,
   Volltonfarbe, gedrueckt heisst verschoben. */

.party { display: flex; flex-direction: column; gap: 12px; }
.party:empty { display: none; }

/* ZWEI Knoepfe, nicht einer mit zwei Haelften. Das war vorher ein
   Kasten mit einem Trennstrich in der Mitte - und damit unmoeglich zu
   bedienen: Rahmen und Schatten lagen am Container, also konnte immer
   nur der ganze Kasten auf seinen Schatten fahren. Beim Tippen sackten
   beide Seiten gemeinsam ab, obwohl nur eine gemeint war.

   Jeder Knopf traegt seinen Rahmen und seinen Schatten jetzt selbst.
   Erst dadurch kann einer unten liegen, waehrend der andere oben
   steht - und genau das ist die Anzeige, welches Formular offen ist.
   Der Abstand dazwischen muss groesser sein als der Schattenversatz,
   sonst schiebt sich der linke Schatten unter den rechten Knopf. */
.party-split { display: flex; gap: 10px; }
.party-split__half {
  flex: 1 1 0;
  min-width: 0;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 14px 10px;
  border: var(--bw) solid var(--ink);
  box-shadow: var(--shadow);
  background: var(--c1);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              background var(--dur) var(--ease);
}
.party-split__half + .party-split__half { background: var(--c5); }

/* Regel 4 der Designsprache: gedrueckt heisst verschoben. Hier gilt sie
   zweimal - einmal fluechtig unter dem Finger, einmal als Zustand:
   der Knopf, dessen Formular offen ist, BLEIBT unten liegen. */
.party-split__half:active,
.party-split__half.is-active {
  transform: translate(var(--sh), var(--sh));
  box-shadow: 0 0 0 var(--ink);
  background: var(--c3);
}
.party-split__icon {
  font-family: var(--font-display);
  font-size: 1.5rem; font-weight: 900; line-height: 1;
}
.party-split__name {
  font-family: var(--font-display);
  font-size: 0.92rem; font-weight: 900;
  letter-spacing: 0.01em;
  text-align: center;
}

/* Das Formular klappt unter dem Knopf hervor. Zwei Dinge macht das
   zugleich: es wischt hart von oben nach unten herein (overflow des
   Wrappers), und es schiebt den Inhalt darunter weich auf, statt ihn
   um die volle Panelhoehe springen zu lassen. Das Springen war das
   Unangenehme, nicht das Erscheinen.

   Warum 0fr -> 1fr statt height: auf "auto" laesst sich nicht
   animieren, auf eine feste Hoehe darf es nicht - das Panel ist mit
   Raumcode-Feld hoeher als ohne.

   Das Panel traegt seinen Schatten AUSSERHALB seiner Kante. Der
   Wrapper klippt, also bekommt er rechts und unten Platz dafuer und
   holt ihn per negativem Rand wieder herein - sonst saesse das Panel
   um diesen Platz eingerueckt unter dem Knopf, statt buendig. Die
   16px Innenabstand von #app fangen den Ueberhang auf. */
.party-form {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  padding: 0 10px 10px 0;
  margin: 0 -10px -10px 0;
  animation: aufklappen 0.18s var(--ease) forwards;
}
.party-form > .panel { min-height: 0; }

@keyframes aufklappen {
  from { grid-template-rows: 0fr; }
  to   { grid-template-rows: 1fr; }
}

/* Rueckwaerts, und etwas schneller: hingehen darf Zeit kosten,
   weggehen soll nicht warten lassen. js/party.js haengt das Entfernen
   aus dem DOM an das animationend dieser Animation. */
.party-form.is-zu { animation: zuklappen 0.14s var(--ease) forwards; }

@keyframes zuklappen {
  from { grid-template-rows: 1fr; }
  to   { grid-template-rows: 0fr; }
}

.party__hint {
  margin: 0;
  font-size: 0.82rem; font-weight: 600; line-height: 1.35;
  color: var(--text-dim);
  text-align: center;
}

/* Einladungen gehoeren zur Party, auch im schmalen Spiel-Streifen. */
.roomcode__line { display: flex; align-items: center; justify-content: center; gap: 14px; flex-wrap: wrap; }
.roomcode__line .roomcode__value { font-size: clamp(1.6rem, 7vw, 2.2rem); letter-spacing: 0.14em; text-indent: 0.14em; padding: 8px 10px; }
.invite-actions { display: flex; gap: 7px; flex: 0 0 auto; }
.invite-action {
  appearance: none;
  display: grid; place-items: center;
  width: 44px; height: 44px; padding: 8px;
  border: var(--bw) solid var(--ink); border-radius: var(--r);
  background: var(--c3); color: var(--ink);
  box-shadow: 3px 3px 0 var(--ink); cursor: pointer;
}
.invite-action + .invite-action { background: var(--c5); }
.invite-action:active { transform: translate(3px, 3px); box-shadow: none; }
.invite-action svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 2; }
.party-strip .invite-action { width: 36px; height: 36px; padding: 6px; }
.party-strip:has(.party-strip__leave.is-armed) .invite-actions { display: none; }
.invite-status { margin: 0; text-align: center; }
.invite-status:empty { display: none; }
.invite-modal__body { display: flex; flex-direction: column; align-items: stretch; gap: 14px; }
.invite-modal__body > .sub { margin: 0; text-align: center; }
.invite-qr {
  display: block; width: min(100%, 300px); height: auto; flex: 0 0 auto; align-self: center;
  background: #fff; fill: #000; shape-rendering: crispEdges;
}
.invite-link { font-family: var(--font-mono); font-size: 0.85rem; }
.invite-join__title { margin: 0; font-family: var(--font-display); font-size: 1.5rem; }

/* Im Spiel: ein schmaler Streifen, der sagt, dass der Raum steht -
   und rechts der Weg hinaus. */
.party-strip {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 12px;
  border: var(--bw) solid var(--ink);
  box-shadow: 4px 4px 0 var(--ink);
  background: var(--c1);
}
.party-strip__code {
  font-family: var(--font-mono);
  font-size: 1.05rem; font-weight: 700;
  letter-spacing: 0.18em; text-indent: 0.18em;
}
/* Die Spielerzahl ist das, was im Streifen ausweichen darf: Raumcode
   und Verlassen-Knopf sind fest, sie nimmt den Rest. Scharf wird der
   Knopf breit ("Raum verlassen?"), und auf einem schmalen Handy bleibt
   dann wenig uebrig - abgeschnitten ist das besser als umgebrochen,
   denn ein zweizeiliger Streifen laesst den halben Bildschirm
   springen, waehrend jemand tippt. */
.party-strip__who {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  font-size: 0.78rem; font-weight: 800; letter-spacing: 0.04em;
  text-align: right;
}

/* Der Weg aus dem Raum, mitten im Spiel. Er traegt .kick mit, weil er
   genau dasselbe Bauteil ist: kleiner Kasten, zwei Tipps, der zweite
   handelt (js/party.js).

   Versteckt ist er nur so weit, wie er es sein darf: kleiner als der
   Rest des Streifens, ohne Beschriftung und am aeussersten Rand - aber
   mit eigener Kante und eigenem Schatten, denn Regel 4 der
   Designsprache gilt auch fuer ihn. Wer ihn nicht sucht, tippt ihn
   nicht an; wer ihn braucht, findet ihn.

   Scharf wird er breit ("Raum verlassen?"). Deshalb darf .party-strip
   nicht auf feste Breiten bauen: die Zahl der Spieler weicht aus, der
   Raumcode bleibt stehen. */
.party-strip__leave {
  min-width: 30px; height: 30px;
  margin: -2px 0;
}
.party-strip__leave.is-armed { padding: 0 10px; }

/* ---------------- Who Liked?: Insta ----------------
   Drei neue Bauteile, alle nach denselben vier Regeln: harte Kante,
   harter Schatten, Vollton, gedrueckt heisst verschoben. */

/* Der Knopf, hinter dem die Dateiauswahl steckt: ein <label> mit .btn
   und dem <input type=file> darin. Ein Label oeffnet die Dateiauswahl
   von selbst, ohne eine Zeile JavaScript.

   Das Feld wird NICHT ueber [hidden] weggeraeumt, obwohl das kuerzer
   waere. display:none nimmt ein Formularfeld aus dem Tab-Fokus, und
   fuer Screenreader ist der Knopf dann ein Label ohne Feld. Es steht
   deshalb da, wo es hingehoert - nur 1x1 Pixel gross und
   durchsichtig. Das ist die einzige Bedienung dieses Spiels, an der
   wirklich alles haengt: geht sie nicht auf, kann niemand mitspielen. */
.upload { cursor: pointer; overflow: hidden; }
.upload__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  border: 0;
  padding: 0;
}

/* Fortschritt in zehn harten Bloecken statt als wachsender Balken.
   Grund ist nicht Geschmack: eine Breite liesse sich nur ueber ein
   style-Attribut setzen, und das ist unter der CSP dieser Seite tot
   (siehe _headers). Bloecke brauchen nur eine Klasse - und passen
   ohnehin besser, weil der Fortschritt schubweise kommt. */
.meter { display: flex; gap: 4px; }
.meter i {
  flex: 1 1 0;
  min-width: 0;
  height: 30px;
  border: var(--bw) solid var(--ink);
  border-radius: var(--r);
  background: var(--paper);
  transition: background var(--dur) var(--ease);
}
.meter i.is-on {
  background: var(--c3);
  animation: motion-meter-fill 0.2s var(--ease) both;
}

@keyframes motion-meter-fill {
  0% { transform: scaleY(0.35); background: var(--paper); }
  100% { transform: scaleY(1); background: var(--c3); }
}

/* Der Beitrag selbst. Die einzige Flaeche der Seite, in der fremder
   Inhalt steht - deshalb bekommt sie denselben Rahmen wie alles
   andere und keinerlei Innenabstand: der Rahmen ist unser, der Inhalt
   nicht. */
.panel--post { padding: 0; gap: 0; overflow: hidden; }
.post__toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  border-bottom: var(--bw) solid var(--ink);
  background: var(--c1);
}
.post__label {
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 900;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.post__expand { flex: 0 0 auto; white-space: nowrap; }
.post__frame { background: var(--paper); }
.post-slot,
.post-content { display: flex; flex-direction: column; gap: var(--gap, 16px); }
.post__embed {
  display: block;
  width: 100%;
  height: min(60vh, 640px);
  border: 0;
  background: var(--paper);
}
.post-viewer--expanded .post__embed { height: min(84vh, 920px); }
.post__status {
  margin: 0;
  padding: 10px 12px;
  border-top: var(--bw) solid var(--ink);
  background: var(--muted);
  color: var(--ink);
  font-size: 0.78rem;
  font-weight: 700;
  line-height: 1.35;
  text-align: center;
}
.post__status.is-loading { background: var(--muted); }
.post__status.is-ready { background: var(--c3); }
.post__status.is-error { background: var(--c5); }
.post__link { margin-top: -4px; }
.post__link--external {
  margin-top: 0;
  padding: 14px;
  border: var(--bw) solid var(--ink);
  background: var(--c4);
  color: var(--paper);
  box-shadow: var(--shadow);
  text-align: center;
  text-decoration: none;
}

/* Der Sieger im Endstand. Muss die Zebra-Regel weiter oben schlagen -
   gleiche Spezifitaet, deshalb steht die Regel hier unten. */
.result-list .result-row.is-first { background: var(--c3); }
.result-row.is-first .result-row__name::before { content: "🏆 "; }
