/* ==========================================================================
   Taktgeber - Gaesteansicht (WP7). Die Hauptansicht des Produkts.

   MOBIL-FIRST, nicht "auch mobil": alles hier ist fuer einen Daumen auf einem
   Handy gebaut - Bottom-Nav, Karten statt Tabellen, Bottom-Sheet mit
   Wisch-Schliessen, FAB. Auf grossen Schirmen wird die Spalte breiter und
   mittig gestellt, mehr nicht.

   Regel wie ueberall: keine Farb-, Abstands-, Radius-, Schatten- oder
   Dauerwerte in dieser Datei. Alles kommt aus tokens.css. Fehlt ein Wert,
   gehoert er dorthin - nicht hierher.
   ========================================================================== */

/* --------------------------------------------------------------- Geruest */

/*
  `hidden` muss `hidden` heissen - auch bei Flex.

  Die Regel des Browsers fuer `[hidden]` ist `display: none` mit derselben
  Gewichtung wie eine Klasse. Jede eigene Klasse mit `display: flex` schlaegt
  sie deshalb, sobald sie spaeter im Stylesheet steht - und genau das ist hier
  ueberall der Fall (Ansichten, Listen, Sheet, Nav, FAB). Das Bottom-Sheet lag
  dann beim ersten Laden offen ueber der Seite, obwohl im HTML `hidden` stand.
  Die ganze Oberflaeche steuert ihre Zustaende ueber `hidden` - also gilt es
  hier einmal fuer alles, statt an dreissig Stellen einzeln.
*/
[hidden] {
  display: none !important;
}

.ga {
  /* Platz fuer Bottom-Nav, FAB und den Bereich unter der Nase des Geraets.
     Ohne das liegt der letzte Titel der Liste unter der Navigationsleiste -
     und der FAB deckt ihn zusaetzlich zur Haelfte ab. */
  padding-bottom: calc(var(--tg-bottom-nav-h) + env(safe-area-inset-bottom) + var(--tg-space-9));
  overflow-x: hidden; /* kein Seiten-Overflow, auch nicht bei langen Titeln */
}

/*
  Das Version-Badge aus shared/base.css sitzt unten rechts - dort liegen hier
  aber Bottom-Nav und FAB. Es wandert deshalb auf dieser Seite nach links und
  ueber die Navigationsleiste. Umgestellt wird nur die POSITION; Aussehen und
  Verhalten bleiben, wie sie ueberall sind (shared/ wird nicht angefasst).
*/
.ga .tg-version {
  left: var(--tg-space-4);
  right: auto;
  bottom: calc(var(--tg-bottom-nav-h) + env(safe-area-inset-bottom) + var(--tg-space-2));
}

.ga .tg-version-history {
  left: var(--tg-space-4);
  right: auto;
  bottom: calc(var(--tg-bottom-nav-h) + env(safe-area-inset-bottom) + var(--tg-space-8));
}

/* Solange das Sheet offen ist, tritt das Badge ab: es liegt auf der Ebene
   `toast` und damit ueber dem Sheet - und stand ausgerechnet auf dem Knopf
   „Wunsch abgeben". Ein Diagnose-Badge darf keine Aktion verdecken. */
.ga[data-sheet='offen'] .tg-version,
.ga[data-sheet='offen'] .tg-version-history {
  display: none;
}

.ga-main {
  width: 100%;
  max-width: var(--tg-container-narrow);
  margin: 0 auto;
  padding: var(--tg-space-4);
  display: flex;
  flex-direction: column;
  gap: var(--tg-space-5);
  min-width: 0;
}

.ga-ansicht {
  display: flex;
  flex-direction: column;
  gap: var(--tg-space-4);
  min-width: 0;
  animation: ga-hinein var(--tg-dur-base) var(--tg-ease-out) both;
}

@keyframes ga-hinein {
  from {
    opacity: 0;
    transform: translateY(var(--tg-space-3));
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.ga-ansicht__titel {
  font-size: var(--tg-fs-xl);
  margin: 0;
}

.ga-abschnitt {
  display: flex;
  flex-direction: column;
  gap: var(--tg-space-3);
  min-width: 0;
}

.ga-abschnitt__titel {
  font-size: var(--tg-fs-lg);
  margin: 0;
}

.ga-abschnitt__fuss {
  margin: 0;
}

/* ------------------------------------------------------------------ Kopf */

.ga-kopf {
  position: sticky;
  top: 0;
  z-index: var(--tg-z-sticky);
  padding-top: env(safe-area-inset-top);
  background: color-mix(in srgb, var(--tg-bg-base) 86%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--tg-line-soft);
}

.ga-kopf__inner {
  display: flex;
  align-items: center;
  gap: var(--tg-space-3);
  width: 100%;
  max-width: var(--tg-container-narrow);
  margin: 0 auto;
  min-height: var(--tg-header-h);
  padding: var(--tg-space-2) var(--tg-space-4);
  min-width: 0;
}

.ga-kopf__text {
  flex: 1 1 auto;
  min-width: 0;
}

.ga-kopf__feier {
  margin: 0;
  font-weight: var(--tg-fw-bold);
  font-size: var(--tg-fs-md);
  line-height: var(--tg-lh-snug);
  overflow-wrap: anywhere;
}

.ga-kopf__phase {
  margin: 0;
  font-size: var(--tg-fs-xs);
  overflow-wrap: anywhere;
}

/*
  Das Emblem: vier Taktbalken, die im Takt atmen. Kein ALMAS-Baer.

  ZWEI MASSE, ZWEI REGELN. Der ABSTAND zwischen den Balken kommt aus dem
  4-px-Raster (DESIGN.md 2) wie jeder andere Abstand auch - hier stand einmal
  `gap: 2px`, ein frei erfundener Zwischenwert. Die STRICHSTAERKE der Balken
  (3px) ist kein Abstand, sondern eine gezeichnete Linie, in derselben Klasse
  wie die 1-px-Raender im ganzen Projekt; das Raster regelt Layout, nicht
  Strichstaerken. Damit das eine Entscheidung bleibt und keine Sammlung von
  Einzelfaellen, haengen alle Striche dieser Seite an einem Wert.
*/
:root {
  /* Strichstaerke gezeichneter Linien (Taktbalken, Nav-Wellen, Nav-Marke). */
  --ga-strich: 3px;
}

/* --------------------------------------------------------------- Banner */

.ga-banner {
  width: 100%;
  max-width: var(--tg-container-narrow);
  margin: var(--tg-space-4) auto 0;
  padding: var(--tg-space-3) var(--tg-space-4);
  border: 1px solid var(--tg-warn);
  border-radius: var(--tg-radius-md);
  background: var(--tg-warn-soft);
  box-shadow: var(--tg-shadow-1), var(--tg-shadow-inset);
  font-size: var(--tg-fs-sm);
  animation: ga-hinein var(--tg-dur-base) var(--tg-ease-out) both;
}

.ga-banner[data-art='fehler'] {
  border-color: var(--tg-danger);
  background: var(--tg-danger-soft);
}

.ga-banner[data-art='info'] {
  border-color: var(--tg-info);
  background: var(--tg-info-soft);
}

.ga-banner__titel {
  margin: 0 0 var(--tg-space-1);
  font-weight: var(--tg-fw-bold);
}

.ga-banner__text {
  margin: 0;
  color: var(--tg-text-muted);
}

/* ------------------------------------------------------------- Beitritt */

.ga-willkommen {
  display: flex;
  flex-direction: column;
  gap: var(--tg-space-2);
  padding-top: var(--tg-space-5);
}

.ga-willkommen__titel {
  font-size: var(--tg-fs-2xl);
  margin: 0;
}

.ga-beitritt {
  display: flex;
  flex-direction: column;
  gap: var(--tg-space-4);
}

.ga-codefeld {
  font-family: var(--tg-font-mono);
  font-size: var(--tg-fs-xl);
  letter-spacing: var(--tg-tracking-wide);
  text-align: center;
  text-transform: uppercase;
}

/*
  Der Platzhalter steht mittig, gross und in Mono - genau wie ein echter
  Eintrag; er las sich als "da steht schon ein Code". Der Ton wird dafuer NICHT
  gedimmt: der Kontrast der Seite liegt gepruefte 5,84:1 aufwaerts, und ein
  Platzhalter ist Text wie jeder andere. Stattdessen sagt er, was er ist ("z. B.
  AB2CD3", siehe index.html) und traegt die normale Laufweite - der gesperrte
  Satz gehoert dem eingetippten Code.
*/
.ga-codefeld::placeholder {
  color: var(--tg-text-faint);
  letter-spacing: normal;
  text-transform: none;
}

.ga-btn--breit {
  width: 100%;
  justify-content: center;
}

.ga-feldfehler {
  margin: 0;
  color: var(--tg-danger);
  font-size: var(--tg-fs-sm);
}

/* ----------------------------------------------------------- Läuft gerade */

.ga-live {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: var(--tg-space-3);
  background: var(--tg-grad-night);
  box-shadow: var(--tg-shadow-3), var(--tg-glow-live), var(--tg-shadow-inset);
}

/*
   Ein wandernder Schleier - das Zeichen dafuer, dass hier gerade etwas
   passiert. Haengt an einem Dauer-Token, also bei reduzierter Bewegung aus.

   `inset: 0 -15%` STATT `inset: 0`: die Flaeche wanderte mit `translateX(12%)`
   in einem `overflow: hidden`-Kasten hin und her - ihre eigene Kante lief
   dabei als sichtbarer senkrechter Streifen quer durchs Bild. Der Ueberstand
   ist genau so gross, dass die Kante nie in den Ausschnitt kommt (12% Weg +
   Reserve).
*/
.ga-live::after {
  content: '';
  position: absolute;
  inset: 0 -15%;
  background: var(--tg-grad-cool);
  opacity: 0.06;
  animation: ga-schleier var(--tg-dur-ambient) var(--tg-ease-in-out) infinite;
  pointer-events: none;
}

/* Pause: der Schleier bedeutet "hier passiert gerade etwas". Wenn nichts
   passiert, darf er nicht weiterlaufen. */
.ga-live[data-pause='ja']::after {
  animation-play-state: paused;
  opacity: 0.02;
}

@keyframes ga-schleier {
  0%,
  100% {
    transform: translateX(-12%);
  }
  50% {
    transform: translateX(12%);
  }
}

.ga-live__kopf {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tg-space-2);
  min-width: 0;
}

.ga-live__titel {
  margin: 0;
  font-size: var(--tg-fs-2xl);
  line-height: var(--tg-lh-tight);
  overflow-wrap: anywhere;
}

.ga-live__kuenstler {
  margin: 0;
  color: var(--tg-text-muted);
  font-size: var(--tg-fs-lg);
  overflow-wrap: anywhere;
}

/* Equalizer. Bei reduzierter Bewegung bleibt ein ruhiger Endzustand stehen -
   nicht nichts (siehe docs/DESIGN.md, Abschnitt 6). */
.ga-eq {
  display: flex;
  align-items: flex-end;
  gap: var(--tg-space-1);
  height: var(--tg-space-6);
}

.ga-eq__bar {
  width: var(--tg-space-2);
  height: 100%;
  border-radius: var(--tg-radius-pill);
  background: linear-gradient(180deg, var(--tg-accent-3), var(--tg-accent));
  transform-origin: bottom;
  animation: ga-eq var(--tg-dur-slower) var(--tg-ease-in-out) infinite alternate;
}

.ga-eq__bar:nth-child(2) {
  animation-delay: calc(var(--tg-dur-slower) / -5);
}
.ga-eq__bar:nth-child(3) {
  animation-delay: calc(var(--tg-dur-slower) / -2.5);
}
.ga-eq__bar:nth-child(4) {
  animation-delay: calc(var(--tg-dur-slower) / -1.6);
}
.ga-eq__bar:nth-child(5) {
  animation-delay: calc(var(--tg-dur-slower) / -1.2);
}

@keyframes ga-eq {
  from {
    transform: scaleY(0.25);
  }
  to {
    transform: scaleY(1);
  }
}

/* Pausiert: der Equalizer steht. Ein stehender Balken sagt mehr als kein
   Balken - "es ist still", nicht "hier fehlt etwas". */
.ga-live[data-pause='ja'] .ga-eq__bar {
  animation: none;
  transform: scaleY(0.3);
  opacity: 0.5;
}

@media (prefers-reduced-motion: reduce) {
  .ga-eq__bar {
    animation: none;
    transform: scaleY(0.7);
  }
  .ga-live::after {
    animation: none;
  }
}

.ga-fortschritt {
  height: var(--tg-space-1);
  border-radius: var(--tg-radius-pill);
  background: var(--tg-bg-sunken);
  overflow: hidden;
}

/*
  Der Balken waechst ueber `transform: scaleX()`, nicht ueber `width`.

  `width` ist Layout: jede Sekunde neu rechnen, neu zeichnen - auf einem Handy
  neben Sheet, Listen und Equalizer die teuerste Art, einen Strich laenger zu
  machen. `transform` laeuft im Compositor und kostet nichts. Die volle Breite
  steht, skaliert wird von links.
*/
.ga-fortschritt__balken {
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
  border-radius: var(--tg-radius-pill);
  background: var(--tg-grad-brand);
  transition: transform var(--tg-dur-slow) var(--tg-ease-out);
}

/*
  Laufzeit ueberschritten: der Server meldet den Titel weiter als `running`,
  obwohl seine Dauer laengst um ist (auf einer echten Feier der Dauerzustand,
  wenn der Player nicht nachfuehrt). Ein voller Balken unter "laeuft gerade"
  behauptet dann eine Messung, die es nicht mehr gibt. Der Balken tritt
  optisch zurueck; die Aussage traegt die Marke `#live-ueber` im Kopf der
  Karte - nie nur Farbe (DESIGN.md 1.5).
*/
/*
  S2: DERSELBE ZUSTAND, DIESELBE FARBE.

  Hier stand --tg-neutral bei opacity .5 - ein voller Balken, der aussah, als
  sei er LEER. Regiepult (dashboard.css) und Leinwand (tv.css) faerben denselben
  Zustand seit jeher amber (--tg-warn). Zwei von drei Flaechen sagen amber; die
  Hauptansicht zieht nach. Der Balken tritt weiterhin zurueck - ueber die
  Deckkraft der FLAECHE, nicht ueber eine dritte Farbe -, und die Aussage traegt
  ohnehin die Marke `#live-ueber` im Kopf der Karte (nie nur Farbe, 1.5).
*/
.ga-fortschritt[data-ueber='ja'] .ga-fortschritt__balken {
  background: var(--tg-warn);
  box-shadow: none;
}

/* ---------------------------------------------------------------- Listen */

.ga-liste {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--tg-space-3);
  min-width: 0;
}

.ga-liste:empty {
  display: none;
}

.ga-karte {
  display: flex;
  align-items: center;
  gap: var(--tg-space-3);
  width: 100%;
  padding: var(--tg-space-3) var(--tg-space-4);
  min-height: var(--tg-tap-target);
  border: 1px solid var(--tg-line);
  border-radius: var(--tg-radius-lg);
  background: var(--tg-bg-raised);
  box-shadow: var(--tg-shadow-2), var(--tg-shadow-inset);
  color: inherit;
  text-align: left;
  min-width: 0;
  transition:
    transform var(--tg-dur-fast) var(--tg-ease-spring),
    border-color var(--tg-dur-fast) var(--tg-ease-out),
    box-shadow var(--tg-dur-fast) var(--tg-ease-out);
}

button.ga-karte {
  cursor: pointer;
}

button.ga-karte:hover {
  border-color: var(--tg-line-strong);
  box-shadow: var(--tg-shadow-3), var(--tg-shadow-inset);
}

button.ga-karte:active {
  transform: scale(0.985);
}

/* Der eigene Wunsch hebt sich ab - das ist der Grund, warum jemand die Liste
   ueberhaupt aufmacht. */
.ga-karte[data-meins='ja'] {
  border-color: var(--tg-accent-line);
  background: linear-gradient(180deg, var(--tg-accent-soft), var(--tg-bg-raised));
}

.ga-karte__zeit {
  flex: none;
  min-width: 3.5rem;
  font-family: var(--tg-font-mono);
  font-size: var(--tg-fs-sm);
  color: var(--tg-text-faint);
  text-align: right;
}

.ga-karte__text {
  flex: 1 1 auto;
  min-width: 0;
}

.ga-karte__titel {
  margin: 0;
  font-weight: var(--tg-fw-medium);
  overflow-wrap: anywhere;
}

.ga-karte__kuenstler {
  margin: 0;
  color: var(--tg-text-faint);
  font-size: var(--tg-fs-sm);
  overflow-wrap: anywhere;
}

.ga-karte__marken {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tg-space-1);
  margin-top: var(--tg-space-1);
}

.ga-karte__pfeil {
  flex: none;
  color: var(--tg-text-faint);
  font-size: var(--tg-fs-lg);
}

/* ------------------------------------------------------------ Leerzustand */

.ga-leer {
  display: flex;
  flex-direction: column;
  gap: var(--tg-space-3);
  align-items: flex-start;
}

.ga-leer--warn {
  border-color: var(--tg-warn);
  background: linear-gradient(180deg, var(--tg-warn-soft), var(--tg-bg-raised));
}

.ga-zustand {
  display: flex;
  flex-direction: column;
  gap: var(--tg-space-2);
  align-items: center;
  text-align: center;
  padding-block: var(--tg-space-6);
}

.ga-zustand__zeichen {
  margin: 0;
  font-size: var(--tg-fs-3xl);
  line-height: 1;
}

/* --------------------------------------------------------------- Skelett */

.ga-skelett {
  display: flex;
  flex-direction: column;
  gap: var(--tg-space-3);
}

.ga-skelett__block {
  height: var(--tg-space-8);
  border-radius: var(--tg-radius-lg);
  background: linear-gradient(
    90deg,
    var(--tg-bg-raised) 0%,
    var(--tg-bg-hover) 50%,
    var(--tg-bg-raised) 100%
  );
  background-size: 200% 100%;
  animation: tg-schimmer var(--tg-dur-slower) var(--tg-ease-in-out) infinite;
}

.ga-skelett__block--gross {
  height: var(--tg-space-9);
}

/* ----------------------------------------------------------------- Suche */

.ga-suche__zeile {
  display: flex;
  gap: var(--tg-space-2);
  min-width: 0;
}

.ga-suche__zeile > .tg-input {
  flex: 1 1 auto;
  min-width: 0;
}

.ga-suche__knopf {
  flex: none;
}

.ga-link__kopf {
  cursor: pointer;
  font-weight: var(--tg-fw-medium);
  min-height: var(--tg-tap-target);
  display: flex;
  align-items: center;
  gap: var(--tg-space-3);
}

/* Der eigene Marker (S8). `display: flex` auf dem <summary> loescht den des
   Browsers - ohne Ersatz sah die Karte aus wie ein leerer, kaputter Kasten. */
.ga-link__kopf::-webkit-details-marker {
  display: none;
}

.ga-link__kopf::marker {
  content: '';
}

.ga-link__text {
  min-width: 0;
}

.ga-link__pfeil {
  margin-inline-start: auto;
  flex: none;
  width: 0.6rem;
  height: 0.6rem;
  border-right: 2px solid var(--tg-accent-strong);
  border-bottom: 2px solid var(--tg-accent-strong);
  transform: rotate(45deg) translate(-0.15rem, -0.15rem);
  transition: transform var(--tg-dur-fast) var(--tg-ease-out);
}

.ga-link[open] .ga-link__pfeil {
  transform: rotate(-135deg) translate(-0.15rem, -0.15rem);
}


.ga-link__feld {
  margin-top: var(--tg-space-3);
}

/* -------------------------------------------------------- Der Clou: ETA */

/*
  Die Antwort auf "wann laeuft mein Titel" bekommt hier den ganzen Schirm.
  Genau daran kranken die Abstimmungs-Jukeboxen: sie sagen "Platz 7" und
  meinen "irgendwann". Diese Karte sagt eine Uhrzeit und eine Phase.
*/
.ga-eta {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--tg-space-3);
  padding: var(--tg-space-6) var(--tg-space-4);
  border: 1px solid var(--tg-accent-line);
  border-radius: var(--tg-radius-xl);
  background:
    radial-gradient(30rem 20rem at 50% -20%, var(--tg-accent-soft) 0%, transparent 70%),
    var(--tg-grad-night);
  box-shadow: var(--tg-shadow-3), var(--tg-glow-accent), var(--tg-shadow-inset);
  text-align: center;
  overflow: hidden;
  min-width: 0;
}

.ga-eta__ring {
  position: relative;
  width: 11rem;
  height: 11rem;
  max-width: 100%;
}

.ga-eta__ring svg {
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

.ga-eta__spur {
  fill: none;
  stroke: var(--tg-line);
  stroke-width: 6;
}

.ga-eta__bogen {
  fill: none;
  stroke: var(--tg-accent-strong);
  stroke-width: 6;
  stroke-linecap: round;
  /* Umfang bei r=52: 2*pi*52 = 326.7. Der Rest kommt aus dem Skript. */
  stroke-dasharray: 326.7;
  stroke-dashoffset: 326.7;
  filter: drop-shadow(0 0 6px var(--tg-accent-soft));
  transition: stroke-dashoffset var(--tg-dur-slow) var(--tg-ease-out);
}

.ga-eta__uhr {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--tg-font-mono);
  font-size: var(--tg-fs-2xl);
  font-weight: var(--tg-fw-bold);
  letter-spacing: var(--tg-tracking-tight);
}

.ga-eta__satz {
  margin: 0;
  font-size: var(--tg-fs-lg);
  font-weight: var(--tg-fw-medium);
  line-height: var(--tg-lh-snug);
  text-wrap: balance;
}

.ga-eta__rest {
  margin: 0;
  font-size: var(--tg-fs-sm);
}

.ga-eta__track {
  width: 100%;
  padding-top: var(--tg-space-3);
  border-top: 1px solid var(--tg-line-soft);
  min-width: 0;
}

.ga-eta__titel {
  margin: 0;
  font-weight: var(--tg-fw-bold);
  overflow-wrap: anywhere;
}

.ga-eta__kuenstler {
  margin: 0;
  font-size: var(--tg-fs-sm);
  overflow-wrap: anywhere;
}

/* Wartet noch auf einen Platz: derselbe Aufbau, ruhigerer Ton. Ein Wunsch
   ohne Uhrzeit ist kein Fehler - er ist noch nicht dran. */
.ga-eta[data-art='wartet'] {
  border-color: var(--tg-line-strong);
  box-shadow: var(--tg-shadow-2), var(--tg-shadow-inset);
}

.ga-eta[data-art='wartet'] .ga-eta__bogen {
  stroke: var(--tg-neutral);
}

/* Abgesagt: freundlich, nicht rot. Eine Absage nennt nie einen Grund. */
.ga-eta[data-art='abgesagt'] {
  border-color: var(--tg-line);
  background: var(--tg-grad-night);
  box-shadow: var(--tg-shadow-2), var(--tg-shadow-inset);
}

.ga-eta[data-art='abgesagt'] .ga-eta__bogen {
  stroke: var(--tg-neutral);
  filter: none;
}

/*
  Ueberfaellig: die geplante Uhrzeit ist vorbei, der Titel lief nicht.

  Kein Warnton. Fuer den Gast ist das kein Fehler, sondern ein Abend, der sich
  verschoben hat - er soll nur nicht auf eine Uhrzeit warten, die es nicht mehr
  gibt. Optisch also die ruhige Fassung der geplanten Karte: derselbe Aufbau,
  neutraler Bogen, kein Leuchten.
*/
.ga-eta[data-art='ueberfaellig'] {
  border-color: var(--tg-line-strong);
  box-shadow: var(--tg-shadow-2), var(--tg-shadow-inset);
}

.ga-eta[data-art='ueberfaellig'] .ga-eta__bogen {
  stroke: var(--tg-neutral);
  filter: none;
}

.ga-eta[data-art='gespielt'] {
  border-color: var(--tg-ok);
}

.ga-eta[data-art='gespielt'] .ga-eta__bogen {
  stroke: var(--tg-ok);
}

/*
  Code der Feier + der Weg hinaus.

  Zwei Luecken in einer Zeile: der Gaestecode war nach dem Beitritt nirgends
  mehr ablesbar (wer ihn weitergeben wollte, musste den Aushang suchen), und
  `vergissBeitritt()` hatte nur den Fehlerpfad als Aufrufer - ein Geraet, das
  einmal auf einer Feier war, kam aus ihr nicht mehr heraus. Die Zeile steht
  ganz unten unter "Meine Wuensche": erreichbar, aber nichts, worueber man
  stolpert.
*/
.ga-codezeile {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--tg-space-2);
  padding-top: var(--tg-space-3);
  border-top: 1px solid var(--tg-line-soft);
  min-width: 0;
}

.ga-codezeile__text {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.ga-codezeile__code {
  font-family: var(--tg-font-mono);
  letter-spacing: var(--tg-tracking-wide);
  color: var(--tg-text);
}

/* ------------------------------------------------------------------- FAB */

.ga-fab {
  position: fixed;
  right: var(--tg-space-4);
  bottom: calc(var(--tg-bottom-nav-h) + env(safe-area-inset-bottom) + var(--tg-space-4));
  z-index: var(--tg-z-fab);
  display: inline-flex;
  align-items: center;
  gap: var(--tg-space-2);
  min-height: var(--tg-tap-target);
  padding: var(--tg-space-3) var(--tg-space-5);
  border: none;
  border-radius: var(--tg-radius-pill);
  background: var(--tg-grad-brand);
  color: var(--tg-text-on-accent);
  font-weight: var(--tg-fw-bold);
  cursor: pointer;
  box-shadow: var(--tg-shadow-3), var(--tg-glow-accent);
  animation: ga-fab-auf var(--tg-dur-slow) var(--tg-ease-spring) both;
  transition: transform var(--tg-dur-fast) var(--tg-ease-spring);
}

.ga-fab:active {
  transform: scale(0.94);
}

.ga-fab__zeichen {
  font-size: var(--tg-fs-lg);
  line-height: 1;
}

@keyframes ga-fab-auf {
  from {
    opacity: 0;
    transform: translateY(var(--tg-space-5)) scale(0.8);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* ------------------------------------------------------------ Bottom-Nav */

.ga-nav {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: var(--tg-z-nav);
  display: flex;
  padding-bottom: env(safe-area-inset-bottom);
  background: color-mix(in srgb, var(--tg-bg-overlay) 92%, transparent);
  backdrop-filter: blur(14px);
  border-top: 1px solid var(--tg-line);
  box-shadow: var(--tg-shadow-3);
}

.ga-nav__eintrag {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--tg-space-1);
  min-height: var(--tg-bottom-nav-h);
  padding: var(--tg-space-2);
  color: var(--tg-text-faint);
  text-decoration: none;
  font-size: var(--tg-fs-xs);
  transition: color var(--tg-dur-fast) var(--tg-ease-out);
}

.ga-nav__eintrag:hover {
  color: var(--tg-text-muted);
}

/* Der aktive Eintrag haengt an aria-current - nicht an einer eigenen Klasse.
   So koennen Aussehen und Semantik nicht auseinanderlaufen. */
.ga-nav__eintrag[aria-current='page'] {
  color: var(--tg-text);
}

.ga-nav__eintrag[aria-current='page']::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  width: var(--tg-space-6);
  height: var(--ga-strich);
  border-radius: var(--tg-radius-pill);
  background: var(--tg-grad-brand);
  transform: translateX(-50%);
  animation: ga-nav-marke var(--tg-dur-base) var(--tg-ease-spring) both;
}

@keyframes ga-nav-marke {
  from {
    transform: translateX(-50%) scaleX(0.2);
    opacity: 0;
  }
  to {
    transform: translateX(-50%) scaleX(1);
    opacity: 1;
  }
}

.ga-nav__zeichen {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: var(--tg-space-1);
  height: var(--tg-space-4);
  font-size: var(--tg-fs-lg);
  line-height: 1;
}

.ga-nav__welle {
  width: var(--ga-strich);
  border-radius: var(--tg-radius-pill);
  background: currentcolor;
  transform-origin: bottom;
}

.ga-nav__welle:nth-child(1) {
  height: 55%;
}
.ga-nav__welle:nth-child(2) {
  height: 100%;
}
.ga-nav__welle:nth-child(3) {
  height: 70%;
}

.ga-nav__eintrag[aria-current='page'] .ga-nav__welle {
  animation: tg-takt var(--tg-dur-slower) var(--tg-ease-in-out) infinite;
}

.ga-nav__eintrag[aria-current='page'] .ga-nav__welle:nth-child(2) {
  animation-delay: calc(var(--tg-dur-slower) / -3);
}
.ga-nav__eintrag[aria-current='page'] .ga-nav__welle:nth-child(3) {
  animation-delay: calc(var(--tg-dur-slower) / -1.5);
}

.ga-nav__text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* Punkt an "Meine": es gibt etwas Neues zu sehen. Nie NUR der Punkt - der
   Text daneben aendert sich mit (aria-label im Skript). */
.ga-nav__punkt {
  position: absolute;
  top: var(--tg-space-3);
  right: 28%;
  width: var(--tg-space-2);
  height: var(--tg-space-2);
  border-radius: var(--tg-radius-pill);
  background: var(--tg-accent-3);
  box-shadow: var(--tg-glow-live);
}

/* ---------------------------------------------------------- Bottom-Sheet */

.ga-sheet {
  position: fixed;
  inset: 0;
  z-index: var(--tg-z-sheet);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.ga-sheet__schleier {
  position: absolute;
  inset: 0;
  background: var(--tg-scrim);
  backdrop-filter: blur(3px);
  animation: ga-schleier-auf var(--tg-dur-base) var(--tg-ease-out) both;
}

@keyframes ga-schleier-auf {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.ga-sheet__karte {
  position: relative;
  width: 100%;
  max-width: var(--tg-container-narrow);
  max-height: 88dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--tg-space-3) var(--tg-space-4)
    calc(env(safe-area-inset-bottom) + var(--tg-space-5));
  border-radius: var(--tg-radius-xl) var(--tg-radius-xl) 0 0;
  background: var(--tg-bg-overlay);
  box-shadow: var(--tg-shadow-3), var(--tg-shadow-inset);
  animation: ga-sheet-auf var(--tg-dur-slow) var(--tg-ease-spring) both;
}

/* Waehrend des Wischens fuehrt das Skript die Karte selbst - ohne Uebergang,
   sonst laeuft sie dem Finger hinterher. */
.ga-sheet__karte[data-wischt='ja'] {
  animation: none;
  transition: none;
}

@keyframes ga-sheet-auf {
  from {
    transform: translateY(100%);
  }
  to {
    transform: translateY(0);
  }
}

.ga-sheet__griff {
  width: var(--tg-space-8);
  height: var(--tg-space-1);
  margin: 0 auto var(--tg-space-4);
  border-radius: var(--tg-radius-pill);
  background: var(--tg-line-strong);
}

.ga-sheet__schritt {
  display: flex;
  flex-direction: column;
  gap: var(--tg-space-4);
  min-width: 0;
}

.ga-sheet__titel {
  margin: 0;
  font-size: var(--tg-fs-xl);
  overflow-wrap: anywhere;
}

.ga-sheet__kuenstler {
  margin: 0;
  overflow-wrap: anywhere;
}

.ga-sheet__knoepfe {
  display: flex;
  flex-direction: column;
  gap: var(--tg-space-2);
}

/*
  Absage-Zweig: die Reihenfolge dreht sich mit der Gewichtung.

  "Alles klar" fuehrt nach einer Absage ins Leere - der Gast hat gerade KEINEN
  Wunsch im Abend. Der Weg, der ihm etwas bringt, ist "Noch einen wuenschen".
  Der Knopf bekommt deshalb nicht nur das Gewicht (Primaer statt Ghost, im
  Skript), sondern auch den Platz: im Stapel trifft der Daumen den oberen.
*/
.ga-sheet__knoepfe[data-ausgang='abgesagt'] #antwort-noch-einer {
  order: -1;
}

/*
  Die Knopfleiste klebt am unteren Rand des Sheets.

  Bei offener Tastatur (gemessen 390x430) lag "Wunsch abgeben" bei y=444, also
  135 px unter dem sichtbaren Rand: die einzige Pflichtaktion des Formulars
  stand hinter zwei OPTIONALEN Feldern und einem Scrollweg. Sie bleibt jetzt
  im Bild, egal wie hoch die Tastatur ist.
*/
#sheet-formular .ga-sheet__knoepfe {
  position: sticky;
  bottom: 0;
  padding-top: var(--tg-space-5);
  padding-bottom: var(--tg-space-1);
  /* Weicher Uebergang statt harter Kante, wenn Inhalt darunter durchscrollt.
     Die Blende ist bewusst so lang wie ein ganzer Abstand (24 px): auf einer
     kurzen Strecke sieht durchscheinender Text nach Zeichenfehler aus, auf
     einer langen nach Verlauf. Farbe wie das Sheet selbst. */
  background: linear-gradient(
    to bottom,
    transparent 0,
    var(--tg-bg-overlay) var(--tg-space-5),
    var(--tg-bg-overlay) 100%
  );
}

/* ------------------------------------------------------- Antwort im Sheet */

.ga-antwort {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--tg-space-2);
  text-align: center;
  padding: var(--tg-space-5) var(--tg-space-2);
}

.ga-antwort__zeichen {
  margin: 0;
  font-size: var(--tg-fs-3xl);
  line-height: 1;
  animation: ga-puls var(--tg-dur-slower) var(--tg-ease-spring) both;
}

@keyframes ga-puls {
  0% {
    transform: scale(0.4);
    opacity: 0;
  }
  60% {
    transform: scale(1.15);
    opacity: 1;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.ga-antwort__uhr {
  margin: 0;
  font-family: var(--tg-font-mono);
  font-size: var(--tg-fs-3xl);
  font-weight: var(--tg-fw-bold);
  letter-spacing: var(--tg-tracking-tight);
  background: var(--tg-grad-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.ga-antwort__satz {
  margin: 0;
  font-size: var(--tg-fs-lg);
  font-weight: var(--tg-fw-medium);
  text-wrap: balance;
}

.ga-antwort__text {
  margin: 0;
  font-size: var(--tg-fs-md);
  text-wrap: pretty;
}

/* ------------------------------------------------------------- Spinner */

/* Ein Knopf, der laedt, zeigt den Spinner STATT des Texts - so springt die
   Breite nicht und der Zustand ist eindeutig. */
.tg-btn[data-laedt='ja'] .ga-btn__text {
  visibility: hidden;
}

.tg-btn[data-laedt='ja'] {
  position: relative;
}

.tg-btn[data-laedt='ja'] .tg-spinner {
  position: absolute;
  left: 50%;
  top: 50%;
  /* Haelfte von --tg-spinner-size (1.25rem), damit der Kreisel mittig sitzt. */
  margin: -0.625rem 0 0 -0.625rem;
}

@media (prefers-reduced-motion: reduce) {
  .ga-skelett__block {
    animation: none;
  }
  .ga-nav__eintrag[aria-current='page'] .ga-nav__welle {
    animation: none;
  }
}

/* --------------------------------------------------------------- Breiter */

/* Ab Tablet wird die Spalte breiter und die Navigation waechst mit - es
   bleibt dieselbe Ansicht, sie hat nur mehr Luft. Es gibt bewusst KEINE
   eigene Desktop-Fassung: die Gaesteansicht IST die mobile App. */
@media (min-width: 48rem) {
  .ga-main {
    padding: var(--tg-space-6) var(--tg-space-5);
    gap: var(--tg-space-6);
  }

  .ga-sheet__karte {
    margin-bottom: var(--tg-space-6);
    border-radius: var(--tg-radius-xl);
    max-height: 80dvh;
  }

  .ga-sheet {
    align-items: center;
  }

  .ga-sheet__karte {
    animation-name: ga-hinein;
  }

  .ga-eta__ring {
    width: 13rem;
    height: 13rem;
  }
}
