/**
 * Anordnung der Seite.
 *
 * `components.css` bringt das Aussehen der einzelnen Bausteine mit — Knöpfe,
 * Karten, Listen. Diese Datei sagt nur, wo sie stehen und wann sie sichtbar
 * sind. Die Trennung hält die Vorlage austauschbar: Wer die Bausteine in
 * einem anderen Projekt braucht, nimmt `components.css` mit und lässt diese
 * Datei liegen.
 */

/* Die Vorlage sperrt das Rollen an `body`, damit der Sternenraum ruhig steht.
   Auf einem Telefon passt der Inhalt aber nicht auf einen Bildschirm, deshalb
   wandert das Rollen in die Hülle: Der Hintergrund bleibt fest, der Inhalt
   darf sich bewegen.

   Die feste Höhe ist dabei der entscheidende Teil. Mit dem `min-height` der
   Vorlage wächst die Hülle einfach mit dem Inhalt — sie rollt dann nie, und
   weil `body` das Überstehende abschneidet, wäre alles unterhalb des
   Bildschirmrands schlicht nicht erreichbar.

   `dvh` statt `vh`, weil `vh` auf dem Telefon die ein- und ausfahrende
   Adressleiste nicht mitzählt und die untersten Knöpfe darunter verschwinden.

   `safe center` hält den Inhalt mittig, solange er passt, und rückt ihn nach
   oben, sobald er größer wird. Ohne `safe` wäre bei zu hohem Inhalt der obere
   Rand nach oben aus dem Rollbereich geschoben und nicht mehr erreichbar. */
.app-shell {
  height: 100dvh;
  align-content: center;
  align-content: safe center;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: max(24px, env(safe-area-inset-top)) 16px max(24px, env(safe-area-inset-bottom));
}

/* Der Ausgang oben links.

   Festgestellt statt mitscrollend: Bei einer Playlist mit zwanzig Zeilen wäre
   er sonst genau dann weg, wenn man ihn braucht.

   44 Bildpunkte im Quadrat sind kein Zufall — darunter wird eine Fläche mit
   dem Finger unzuverlässig zu treffen. Das Symbol darin ist kleiner; der Rest
   ist Trefferfläche.

   Die Abstände gehen über `env(safe-area-inset-…)`, damit er auf einem Gerät
   mit runden Ecken oder Kameraaussparung nicht angeschnitten wird. */
.app-exit {
  position: fixed;
  top: max(16px, env(safe-area-inset-top));
  left: max(16px, env(safe-area-inset-left));
  z-index: 30;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  color: var(--template-text);
  border: 1.5px solid var(--template-line);
  border-radius: var(--template-radius);
  background: var(--template-panel);
  box-shadow: var(--template-shadow);
  backdrop-filter: blur(12px);
  cursor: pointer;
  transition:
    border-color 160ms ease,
    background 160ms ease;
}

.app-exit:hover,
.app-exit:focus-visible {
  border-color: var(--template-purple-hover);
  outline: 0;
  background: rgba(157, 28, 255, 0.16);
}

.app-exit[hidden] {
  display: none;
}

.app-exit svg {
  width: 22px;
  height: 22px;
}

.app-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;

  /* Die Breite der beiden zusammenhängenden Zeilen — Eingabe und Knöpfe.
     Sie stehen direkt untereinander, und schon zwei Bildpunkte Unterschied
     sieht man als schiefe Kante. Deshalb eine Zahl an einer Stelle statt
     zweier, die man beim nächsten Umbau getrennt anpassen müsste.

     Maßgeblich ist die App: Dort ist alles 480 Punkte breit. 478 plus die
     beiden Rahmenlinien ergibt außen genau diese 480. */
  --app-row-content: 478px;

  /* Vorschau, Titelliste und Ladebalken. In der App dieselbe Zahl wie die
     Zeilen; nur das Bild der Vorschau ist schmaler. */
  --app-block-width: 480px;
  --app-preview-width: 420px;

  /* Die Zeile von außen — also mit ihren beiden Rahmenlinien. Wer die Breite
     einer Zeile *treffen* will, braucht diese Zahl und nicht den Inhalt. */
  --app-row-width: calc(var(--app-row-content) + 2 * var(--app-row-border));

  /* Luft zum Bildschirmrand, links und rechts zusammen. Entspricht
     `padding(horizontal = 16.dp)` im Bildschirm der App. Auf einem schmalen
     Telefon ist das die Zahl, die alle Breiten begrenzt — und weil sie für alle
     dieselbe ist, stehen sie dort in einer Flucht statt gestaffelt. */
  --app-side-gap: 32px;

  /* Rahmenbreite der zusammenhängenden Zeilen — ganzzahlig, und das ist kein
     Zufall.

     Die Vorlage schreibt 1,5 px. Der Browser zeichnet daraus je nach
     Bildschirm 1 px oder 2 px, rundet also. Eine Verschiebung von -1,5 px
     rundet er dagegen nicht. Genau daran hing das aufgeklappte Auswahlfeld:
     Es stand mal eine halbe Bildpunktbreite über, mal war es schmaler als die
     Zeile darüber — sichtbar als zweiter Umriss. Mit einer ganzen Zahl sind
     Rahmen und Versatz immer derselbe Wert. */
  --app-row-border: 1px;
}

/* Beide Zeilen genau gleich breit, gleich begrenzt und gleich umrandet. */
.template-form-row,
.app-actions {
  width: var(--app-row-width);
  max-width: calc(100vw - var(--app-side-gap));
  border-width: var(--app-row-border);
}

/* Die Knopfzeile taucht erst auf, wenn es etwas herunterzuladen gibt. Sie
   folgt damit derselben Regel wie Vorschau, Liste und Ladebalken: sichtbar
   über `is-visible`, sonst gar nicht da.

   Gebaut wie die Eingabezeile darüber: ein Rahmen um alles, die Knöpfe darin
   ohne Abstand und nur durch eine Linie getrennt. Das liest sich als ein
   Bedienelement mit mehreren Möglichkeiten statt als lose herumstehende
   Knöpfe — und es nimmt die Form der Zeile über ihr auf.

   `overflow: hidden` erledigt die Rundung: Statt dem ersten Knopf die linke
   und dem letzten die rechte Ecke zuzuweisen, schneidet der Rahmen sie
   einfach ab. Das bleibt richtig, egal ob zwei oder drei Knöpfe darin
   stehen — und der Archiv-Knopf kommt ja nur manchmal dazu.

   Die Breite kommt aus der gemeinsamen Regel weiter oben. */
.app-actions {
  display: none;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  overflow: hidden;
  /* Kurzschreibweise mit der gemeinsamen Breite — ein schlichtes
     `border: 1.5px solid` würde die Vorgabe von weiter oben wieder
     überschreiben und die Zeile anders umranden als die darüber. */
  border: var(--app-row-border) solid var(--template-line);
  border-radius: var(--template-radius);
  background: var(--template-panel);
  box-shadow: var(--template-shadow);
  backdrop-filter: blur(12px);
}

.app-actions.is-visible {
  display: grid;
}

/* Die Knöpfe geben ihren eigenen Rahmen und ihre Rundung ab — beides trägt
   jetzt die Umhüllung. Die Höhe ist dieselbe wie in der Eingabezeile. */
.app-actions > button {
  height: 58px;
  border: 0;
  border-radius: 0;
}

/* Die Trennlinie sitzt am jeweils folgenden Knopf, nicht am vorhergehenden.
   So bekommt der letzte keine Linie nach außen, und es braucht keine
   Sonderregel für ihn. Ein ausgeblendeter Archiv-Knopf stört dabei nicht:
   `+` fragt nach dem Nachbarn im Dokument, und der ist dann eben der
   nächste sichtbare. */
.app-actions > button + button {
  border-left: var(--app-row-border) solid var(--template-line);
}

/* Eingabefeld und Formatauswahl stehen untereinander — auf jeder Größe.

   Die Vorlage teilt die Zeile in drei Spalten (Feld, Auswahl, Knopf). Gesucht
   wird hier aber mit der Eingabetaste, den Knopf gibt es nicht mehr, und ein
   Raster hält seine Spalten frei, ob etwas darin steht oder nicht — übrig
   bliebe also eine leere Spalte. Statt sie zu entfernen, fällt die Zeile ganz
   auf eine Spalte zurück und stapelt.

   Das ist dieselbe Anordnung wie auf dem Telefon und in den beiden Apps. Ein
   Link ist lang; über die volle Breite ist mehr davon zu lesen als in einem
   Feld, das sich den Platz mit der Formatauswahl teilt. */
.template-form-row {
  grid-template-columns: 1fr;
}

/* Gestapelt sitzt das Eingabefeld oben — also gehören ihm die oberen Ecken.

   Die Vorlage rundet links, weil dort die Formatauswahl daneben stand. Sichtbar
   wurde das erst am Fokusring: Der ist ein innenliegender Schatten und folgt der
   Rundung des Feldes. Unten links kurvte er deshalb nach innen, während die
   Zeile dort geradeaus weiterläuft — die Linie schien abgeschnitten. */
.template-form-row .template-input {
  border-radius: calc(var(--template-radius) - 1px) calc(var(--template-radius) - 1px) 0 0;
}

/* Gestapelt ist die Formatauswahl das letzte Element der Zeile — die untere
   Rundung gehört also ihr, nicht die rechte.

   Und die Trennlinie liegt oben statt links. Die Vorlage setzt sie seitlich, weil
   dort das Eingabefeld daneben stand; untereinander liegt so eine seitliche Linie
   genau auf dem Rahmen der Zeile. Nachgemessen war die linke Kante dadurch zwei
   Bildpunkte breit statt einem — die Zeile sah links dicker aus als rechts.

   Ohne die obere Linie gäbe es zwischen Feld und Auswahl gar keine Trennung: Was
   dort wie eine aussah, war der Fokusring des Eingabefeldes und verschwand,
   sobald man daneben tippte. */
.template-form-row .template-select-trigger {
  border-top: var(--app-row-border) solid var(--template-line);
  border-right: 0;
  border-left: 0;
  border-radius: 0 0 calc(var(--template-radius) - 1px) calc(var(--template-radius) - 1px);
}

/* Aufgeklappt sind Auslöser und Liste **ein** Element.

   Zugeklappt endet die Zeile rechts unten rund — das ist ihre Kante. Klappt
   die Liste auf, ist sie das nicht mehr: Die Kante wandert an die Unterseite
   der Liste. Bleibt die Rundung oben stehen, sieht man zwei Kästen, die sich
   berühren, statt eines Feldes, das sich geöffnet hat.

   Die Trennlinie dazwischen bleibt trotzdem — sie sagt, wo der Knopf endet und
   die Auswahl beginnt. Sie kommt aber vom Rahmen der Zeile und **nicht** von
   einem eigenen unteren Rahmen an dieser Stelle: Bei `box-sizing: border-box`
   und fester Höhe schrumpft der Innenraum durch so eine Linie von 57 auf 56
   Punkte, und die Beschriftung rutscht einen halben Punkt nach oben. Man sieht
   es als kurzes Springen von "MP3" beim Aufklappen.

   Gestapelt liegt die Rundung unten statt rechts; aufgeklappt wandert also die
   untere weg, und oben bleibt gar keine. */
.template-select.is-open .template-select-trigger,
.template-select.is-collapsing .template-select-trigger {
  border-radius: 0;
}

.template-select-menu {
  border-radius: 0 0 calc(var(--template-radius) - 1px) calc(var(--template-radius) - 1px);
}

/* Aufgeklappt verliert die Zeile ihre untere Rundung.

   Das war die eigentliche Ursache der Kanten am Übergang, und sie ist von
   außen kaum zu sehen: Eine abgerundete Ecke kurvt schon einige Bildpunkte
   *vor* dem Ende nach innen weg. Der Rahmen der Zeile war deshalb auf den
   letzten drei Bildpunkten gar nicht mehr an der Außenkante — nachgemessen
   war dort reiner Hintergrund —, während die Liste darunter sofort mit einer
   geraden Kante beginnt. Genau dieser Versatz sah aus wie zwei Kästen.

   Beide unteren Ecken: Die Liste hängt unter der ganzen Zeile, nicht nur unter
   einer Spalte. */
.template-form-row:has(.template-select.is-open),
.template-form-row:has(.template-select.is-collapsing) {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

/* Die Liste liegt kantengleich unter der Zeile.

   Ohne das ist sie auf jeder Seite genau eine Rahmenbreite schmaler: Die
   Zeile trägt ihren Rahmen außen, die Liste hängt aber *innerhalb* davon und
   bringt ihren eigenen mit. Nachgemessen waren es 25,0–365,0 gegen 24,0–366,0
   — man sieht dann zwei Umrisse ineinander statt eines durchgehenden.

   Die 1,5 px sind dieselbe Rahmenbreite wie an `.template-form-row`. Werden
   sie dort geändert, gehört es hier mitgeändert. */
.template-form-row .template-select-menu {
  left: calc(-1 * var(--app-row-border));
  width: calc(100% + 2 * var(--app-row-border));
  /* Rundum, obere Linie eingeschlossen — und die ist der eigentliche Punkt.
     
     Die Trennung zwischen Auslöser und Liste muss irgendwo herkommen. Am
     Auslöser darf sie nicht sitzen: Der hat eine feste Höhe, und eine Linie
     kostet dort einen Punkt Innenraum, den man als Springen der Beschriftung
     sieht. Die Liste liegt dagegen absolut — ihre Linien verschieben nichts,
     weder in ihr noch außerhalb. Sie deckt dabei den unteren Rahmen der Zeile
     ab, es bleibt also bei einer Linie. */
  border: var(--app-row-border) solid var(--template-line);
}

/* Ein verstecktes Bild soll auch keinen Rahmen hinterlassen. Ohne diese Regel
   bliebe bei einem Titel ohne Cover ein leerer, umrandeter Kasten stehen. */
.template-preview-image[hidden] {
  display: none;
}

/* In der Vorlage ist die Vorschau der Blickfang und misst 380 Bildpunkte. Hier
   ist sie nur die Bestätigung "richtiger Treffer" und teilt sich den Platz mit
   Trackliste, Ladebalken und Knopfzeile — bei 380 drückt sie alles darunter aus
   dem Bild. Die Größe hängt deshalb an der Fensterhöhe: hohe Bildschirme zeigen
   ein großes Cover, flache ein kleines, und beide behalten den Rest im Blick. */
.template-preview {
  margin-bottom: 0;
}

.template-preview-image.is-square {
  width: clamp(110px, 24vh, 240px);
}

.template-preview-image.is-video {
  width: clamp(190px, 42vh, 340px);
}

/* Prozentzahl und Zustandszeichen teilen sich eine Spalte.

   Die Vorlage hat dafür zwei: eine für die Zahl, eine für Haken oder Kreuz.
   Beide sind aber nie gleichzeitig gefüllt — solange geladen wird, steht dort
   die Zahl, danach das Zeichen. Zwei Spalten hießen also, dass eine davon
   immer leer ist und dem Titel trotzdem ihren Platz und ihren Abstand
   wegnimmt. Deshalb hier die erste auf null und die zweite so breit, dass
   "100 %" hineinpasst.

   `tabular-nums` gibt allen Ziffern dieselbe Breite. Ohne das wackelt die
   Zahl bei jedem Sprung von 8 auf 9 seitlich hin und her. */
.template-track.has-status,
.template-track.is-single-digit.has-status {
  grid-template-columns: 10px 16px minmax(0, 1fr) 0 46px 92px;
}

.template-track-state {
  font-variant-numeric: tabular-nums;
}

/* Die Laufschrift steuert `ui/marquee.js`, nicht das Stylesheet.

   Die Klasse `is-marquee` wird weiterhin gesetzt — sie gibt dem Text seine
   volle Breite, sonst bliebe er auf den Kasten gestaucht. Die Bildfolge der
   Vorlage muss dabei aber abgeschaltet werden, sonst liefen zwei Bewegungen
   gleichzeitig: das Pendel aus dem CSS und der Umlauf aus dem Skript. */
.template-preview-title.is-marquee span,
.template-progress-title.is-marquee span,
.template-track-title.is-marquee span {
  animation: none;
}

/* Am Rechner ist Platz für die ausgeschriebene Stufe. */
.app-quality-short {
  display: none;
}

/* Die Zeile mit Prozent und Zählung besteht aus vielen kleinen Teilen, die
   einzeln ein- und ausgeblendet werden. Ohne das hier bräuchte jedes davon
   eine eigene Regel im Skript. */
#percent [hidden] {
  display: none;
}

/* Die Trennpunkte in "42 % · Titelname" treten zurück.

   In der Vorlage sind sie so weiß wie der Text. Ein Trennzeichen ist aber
   keine Angabe, sondern nur die Fuge dazwischen — in derselben Helligkeit
   liest es sich wie ein drittes Wort. Grau wie der Interpret über der Liste
   (dieselbe Deckkraft, 0.58) rückt es dahin, wo es hingehört. */
.template-percent .template-progress-dot,
.template-percent .template-progress-divider {
  color: rgba(251, 248, 255, 0.58);
}

@media (max-width: 760px) {
  .app-panel {
    gap: 14px;
  }

  /* Die Anordnung der Eingabezeile steht nicht mehr hier: Sie stapelt
     inzwischen auf jeder Größe gleich, also gilt die Regel weiter oben ohne
     Medienabfrage. */

  /* Die Qualitätsstufen alle in eine Zeile.

     Die Vorlage lässt sie umbrechen (`auto-fit` mit 74 Bildpunkten Mindest-
     breite), was bei fünf Stufen auf dem Telefon zwei Zeilen ergibt. Hier
     stattdessen so viele gleich breite Spalten, wie Knöpfe da sind — das
     passt für die fünf Bitraten ebenso wie für die vier Bildhöhen von MP4,
     ohne dass irgendwo eine Zahl steht, die man beim nächsten Format
     nachziehen müsste.

     Damit das aufgeht, zeigen die Knöpfe hier die Kurzform: bei rund
     sechzig Bildpunkten je Knopf passt "320", aber nicht "320 kbit/s". Die
     ausgeschriebene Fassung bleibt im `aria-label`, wird also weiterhin
     vollständig vorgelesen. */
  .template-option-grid {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    width: var(--app-row-width);
    max-width: calc(100vw - var(--app-side-gap));
    gap: 6px;
  }

  .template-option-grid button {
    min-width: 0;
    padding: 0 4px;
  }

  .app-quality-long {
    display: none;
  }

  .app-quality-short {
    display: inline;
  }

  /* Zeilenraster und Pfeilknopf stehen nicht mehr hier: Sie gelten inzwischen
     auf jeder Größe, weil die App es auch so hält. */

  /* Auf dem Telefon stehen die Knöpfe untereinander — nebeneinander werden
     drei davon zu schmal, um sie sicher zu treffen. Der gemeinsame Rahmen
     bleibt, gestapelt wird nur die Richtung: Aus Spalten werden Zeilen, und
     die Trennlinie wandert von links nach oben. */
  .app-actions {
    grid-auto-flow: row;
    grid-auto-columns: auto;
  }

  .app-actions > button + button {
    border-top: var(--app-row-border) solid var(--template-line);
    border-left: 0;
  }
}


/* --- Gleichstand mit der App -----------------------------------------------

   Ab hier steht nichts, was die Website für sich bräuchte. Es sind die Stellen,
   an denen die Vorlage andere Maße mitbringt als die Fassung für Handy und
   Rechner — und maßgeblich ist die App.

   Die Farben mussten dabei nicht angefasst werden: `VoidColors.kt` trägt schon
   dieselben Werte wie die Variablen hier, bis auf die zweite Nachkommastelle
   der Deckkraft. Unterschiedlich waren Höhen, Innenabstände, Schriftschnitte —
   und der Weichzeichner, den es in Compose gar nicht gibt. */

/* Kein Weichzeichner hinter den Flächen — und kein Schlagschatten.
   
   Die Zeile ist mit 86 Prozent Deckkraft in beiden Fassungen gleich
   durchscheinend, aber im Browser lagen die Sterne dahinter zusätzlich unscharf.
   Das war der auffälligste Unterschied überhaupt, obwohl keine einzige Farbe
   davon abwich.

   Der Schatten ist der zweite. `0 18px 44px rgba(5, 4, 9, 0.38)` ist ein weiter,
   sehr dunkler Fleck unter der Zeile — und er erklärt die schwarzen Ecken:

   - An den eigenen Rundungen liegt ein Teil jedes Bildpunkts *außerhalb* des
     Rahmens. Dort malt der Schatten, und die Ecke wird dunkler als die Fläche
     daneben.
   - Die Titelliste darunter ist zu 58 Prozent durchscheinend. Der Schatten der
     Zeile liegt also unter ihr und drückt ihre linke obere Ecke mit.

   Nachgerechnet an der Ecke der Liste: 0,38 × (5,4,9) + 0,62 × (11,6,19) ergibt
   (8,7 | 5,2 | 15,2) — gemessen waren es (9 | 5 | 15). In der App gibt es
   weder Schatten noch dieses Nachdunkeln. */
.template-form-row,
.app-actions,
.template-panel {
  backdrop-filter: none;
  box-shadow: none;
}

/* Der Pfeil als Vektor, nicht als gedrehtes Quadrat.
   
   Die Vorlage baut ihn aus zwei Rahmenlinien eines leeren Quadrats, um 45 Grad
   gekippt. Das ergibt eine andere Geometrie als der Winkel der App: gemessen
   14 mal 8 Punkte gegen 12 mal 7. Jetzt tragen beide dieselben drei Punkte.

   20 mal 20 ist die Größe des Symbols, nicht die des Winkels darin — genau wie
   `chevronDown` in der App, der in einem 24er Feld von (6|9,5) nach (18|9,5)
   läuft und dadurch ringsum Luft behält. Der Abstand zur Beschriftung sind
   deshalb 8 und nicht 12: Die restlichen 4 bringt das Symbol selbst mit. */
.template-select-trigger svg {
  width: 20px;
  height: 20px;
  margin-left: 8px;
  transition: transform 180ms ease;
}

/* Aufgeklappt zeigt er nach oben — den Weg über rechts, wie in der App. */
.template-select.is-open .template-select-trigger svg {
  transform: rotate(-180deg);
}

/* Kein zusätzlicher Ring, wenn das Feld dran ist.
   
   Die Vorlage legt beim Fokus einen innenliegenden Schatten über die ganze
   Kante des Feldes. Zusammen mit dem Rahmen der Zeile waren das zwei Linien
   dicht nebeneinander, und im Gegensatz zur App, deren Textfeld gar keinen
   Ring kennt.

   Dass das Feld dran ist, sagt weiterhin die Schreibmarke — die ist violett und
   blinkt. Für die Bedienung mit der Tastatur reicht das: Es gibt auf der Seite
   genau ein Textfeld. */
.template-form-row .template-input:focus {
  box-shadow: none;
}

/* Beide Felder 56 statt 58 hoch, und der Innenabstand 16 statt 20 — das ist,
   was ein Textfeld in Material mitbringt und was die App daher zeigt.

   Der Schreibstrich ist violett wie in der App (`cursorColor = Purple`). Ohne
   Angabe nimmt der Browser die Textfarbe, und die ist hier Weiß — der Strich
   war also der einzige Teil des Feldes, der nicht zum Void gehörte. */
.template-form-row .template-input {
  height: 56px;
  padding: 0 16px;
  caret-color: var(--template-purple);
}

/* Der Behälter der Auswahl bringt in der Vorlage eigene 58 Punkte mit. Bliebe
   das stehen, wäre die Zeile zwei Punkte höher als in der App — die Auswahl
   steckt in ihm drin. */
.template-form-row .template-select {
  height: 56px;
}

/* Die 56 schließen die Trennlinie oben mit ein: In Compose wird der Rahmen über
   die Fläche gezeichnet, in CSS zählt er bei `border-box` zur Höhe. So kommen
   beide auf dieselben 114 Punkte für die ganze Zeile. */
.template-form-row .template-select-trigger {
  height: 56px;
  /* Keine eigene Fläche. Die Vorlage setzt hier die kräftigere Farbe, weil die
     Auswahl dort als Knopf neben dem Feld stand. Untereinander gehören beide zu
     einer Fläche, und in der App ist es auch eine. */
  background: transparent;
  font-weight: 700;
}

/* Die Liste: höhere Zeilen, kein Schlagschatten, gleiche Rundung.
   
   Der Schatten fiel im Browser nach unten aus der Liste heraus; in Compose gibt
   es ihn nicht. 8 statt 7 Punkte Rundung, weil die App ihren Rahmen über die
   Fläche zeichnet und außen deshalb auf volle 8 kommt. */
.template-select-menu {
  border-radius: 0 0 var(--template-radius) var(--template-radius);
  box-shadow: none;
}

.template-select-menu button {
  height: 48px;
  font-weight: 400;
  color: var(--template-muted);
}

/* Der gewählte Eintrag steht fett — daran erkennt man ihn in der App auch dann,
   wenn die violette Fläche im Sternenraum untergeht. */
.template-select-menu button[aria-selected="true"] {
  font-weight: 700;
}

/* Vorschau, Liste und Balken auf die Maße der App — und mit derselben Luft zum
   Rand wie die Zeilen. Die Vorlage lässt hier 72 Bildpunkte; auf einem Telefon
   sind das gut zwanzig Prozent der Breite, und die Liste stünde sichtbar
   schmaler als die Eingabezeile darüber. */
.template-preview {
  width: min(var(--app-preview-width), calc(100vw - var(--app-side-gap)));
}

.template-track-list,
.template-progress {
  width: min(var(--app-block-width), calc(100vw - var(--app-side-gap)));
}


/* --- Abstände ---------------------------------------------------------------

   14 zwischen den Blöcken, wie `Arrangement.spacedBy(14.dp)` im Bildschirm der
   App. Nachgemessen an einer Aufnahme: Cover endet bei 428, Eingabezeile
   beginnt bei 443. */
.app-panel {
  gap: 14px;
}

/* Und derselbe Rand zum Bildschirm: 16, wie `padding(horizontal = 16.dp)` in
   der App. Die Vorlage nimmt 24 — dann bliebe auf einem schmalen Telefon
   sechzehn Punkte weniger für den Titel in der Liste. */
.app-shell {
  padding: 16px;
}

/* --- Vorschau ---------------------------------------------------------------

   Titel 16 Punkte fett in vollem Weiß, Untertitel 12 Punkte in gedämpftem — das
   sind `titleMedium` und `bodySmall` aus Material, die die App verwendet. Die
   Vorlage hatte 1,06 rem bei 86 Prozent Deckkraft und 0,7 rem im Schnitt 500.

   Die Zeilenhöhen sind nicht Geschmack, sondern Rechnung: Material gibt
   `titleMedium` 24 Punkte Zeilenhöhe auf 16 Punkte Schrift (also 1,5) und
   `bodySmall` 16 auf 12 (also 1,34). Damit sitzen die Abstände von 4 Punkten
   zwischen Titel, Untertitel und Bild an derselben Stelle wie in der App. */
.template-preview-title {
  color: var(--template-text);
  font-size: 16px;
  /* Ohne feste Zeilenhöhe — genau wie `titleMedium` in `VoidTheme.kt`, das
     keine angibt. Compose nimmt dann die natürliche Höhe der Schrift, und das
     tut der Browser bei `normal` auch: bei Noto Sans sind das 1,36 mal die
     Schriftgröße. */
  line-height: normal;
}

.template-preview-subtitle {
  margin-top: 4px;
  color: var(--template-muted);
  font-size: 13px;
  font-weight: 400;
  line-height: 18px;
}

/* Das Cover füllt seinen Rahmen, statt hineingepasst zu werden — `Crop` in der
   App, `cover` hier. Der zusätzliche Lichtring der Vorlage fällt weg; in Compose
   gibt es ihn nicht. */
.template-preview-image {
  margin-top: 4px;
  object-fit: cover;
  box-shadow: none;
}

/* Feste 200 für ein Albumcover, volle Breite bei 16:9 für ein Video — genau die
   beiden Fälle, die `PreviewCard` unterscheidet. Nachgemessen in der App:
   860–1059 waagerecht, 229–428 senkrecht, also 200 mal 200. */
.template-preview-image.is-square {
  width: 200px;
  height: 200px;
  max-width: 200px;
  max-height: 200px;
}

.template-preview-image.is-video {
  width: 100%;
  height: auto;
  max-width: 100%;
  max-height: none;
  aspect-ratio: 16 / 9;
}

/* --- Titelliste -------------------------------------------------------------

   36 statt 32 Punkte Zeilenhöhe. Die Listenhöhe rechnet sich daraus von selbst
   neu: fünf Zeilen, vier Zwischenräume, zweimal Innenabstand. */
.template-track-list {
  --template-row-height: 36px;
  /* Die Vorlage setzt 12 Punkte Abstand nach oben. Zusammen mit dem Abstand
     zwischen den Blöcken wären das 26 statt 14 — die Liste hinge deutlich
     tiefer unter der Eingabezeile als in der App. */
  margin-top: 0;
  /* 7 plus die Rahmenlinie ergeben die 8 der App.
     
     Compose zeichnet den Rahmen über die Fläche, er kostet also keinen Platz;
     in CSS liegt er außerhalb des Innenabstands. Mit vollen 8 wäre die Liste
     zwei Punkte höher und die Zeilen zwei schmaler als in der App. */
  padding: 7px;
}

/* Ein Raster für alle Fälle.
   
   Die Vorlage schaltet die Spalten je nachdem um, ob die Nummer ein- oder
   zweistellig ist und ob schon ein Auftrag läuft. Die App tut das nicht: Dort
   ist die Nummer immer 20 breit und die Statusspalte immer 46, auch wenn nichts
   darin steht. Das ist der Grund, warum in der App nichts springt, sobald ein
   Download beginnt. */
/* Ohne laufenden Auftrag gehört der Platz dem Titel.
   
   Vorher standen die 46 Punkte der Statusspalte immer da, auch leer. Die Folge
   sah man nicht an der Spalte, sondern an der Laufschrift: Ein Titel begann zu
   wandern, sobald er *seine Spalte* überschritt — und die endete 58 Punkte vor
   dem Download-Knopf. Er lief also los, obwohl davor noch Platz war.
   
   `display: none` statt Breite null: Ein Rasterfeld mit der Breite null bekommt
   trotzdem seinen Abstand von 6 Punkten. Ganz ausgeblendet ist es kein Feld
   mehr, und übrig bleibt der eine Abstand zum Knopf. */
.template-track,
.template-track.is-single-digit {
  grid-template-columns: 20px 24px minmax(0, 1fr) 32px;
  font-size: 13px;
  line-height: 18px;
}

.template-track-percent,
.template-track:not(.has-status) .template-track-state {
  display: none;
}

/* Sobald ein Auftrag läuft, macht der Titel Platz. Das kostet ihn einmal 52
   Punkte — dafür steht die Zahl dort, wo sie hingehört, und rückt nicht mit
   jedem Titel woandershin. */
.template-track.has-status,
.template-track.is-single-digit.has-status {
  grid-template-columns: 20px 24px minmax(0, 1fr) 46px 32px;
}

.template-track.has-status .template-track-state {
  display: block;
}

/* 20 in einer 24 breiten Spalte, mittig.
   
   Material zeichnet sein Häkchen kleiner als den Bereich, den es belegt — der
   Rest ist Platz zum Antippen. Ein Browser-Häkchen füllt dagegen genau die
   Größe, die man ihm gibt; mit vollen 24 säße es sichtbar dichter am Titel als
   in der App. Nachgemessen in der App: 20 Punkte breit. */
.template-track input {
  width: 20px;
  height: 20px;
  justify-self: center;
}

.template-track-number {
  color: var(--template-purple);
}

.template-track-title,
.template-track-title span {
  color: var(--template-text);
}

.template-track-percent {
  min-width: 46px;
  color: var(--template-purple);
  font-size: 13px;
}

.template-track-state {
  font-size: 13px;
  text-align: right;
}

.template-track.is-error .template-track-state {
  color: var(--template-danger);
}

/* Der Knopf je Zeile: ein Quadrat mit Pfeil, kein Wort.
   
   Ausgeschrieben braucht "Download" 92 Punkte — fast ein Viertel der Zeile, und
   das ausgerechnet dem Titel weggenommen. Die App zeigt dort 32 mal 26 mit einem
   Pfeil darin. Der Name bleibt im `aria-label`, für Screenreader ändert sich
   also nichts. */
.template-track button {
  width: 32px;
  height: 26px;
  padding: 0;
  font-size: 13px;
  /* Ein Raster mit mittigem Inhalt, sonst klebt das Symbol links.
     
     Ein Knopf zentriert von sich aus nur Inline-Inhalt — das galt für das
     Zeichen "↓" von vorher. Das Symbol ist jetzt ein Kasten, und ein Kasten
     stellt sich an den Zeilenanfang. Nachgemessen: 1 Punkt Luft links, 15
     rechts. */
  display: grid;
  place-items: center;
}

/* 16 in einem 32 mal 26 großen Feld — dieselbe Größe wie in der App. */
.template-track button svg {
  display: block;
  width: 16px;
  height: 16px;
}

/* --- Der große Knopf --------------------------------------------------------

   56 wie in `VoidButton`, nicht 58.

   Und ohne die Umhüllung: Die Vorlage legt einen gemeinsamen Rahmen um mehrere
   Knöpfe, weil dort einmal drei nebeneinander standen. Übrig ist einer, und in
   der App ist er einfach ein violettes Feld über die volle Breite — kein Rahmen,
   keine Fläche dahinter. Ohne diese Regel wäre der violette Teil 478 statt 480
   breit und läge in einer dünnen Linie. */
.app-actions {
  border: 0;
  background: transparent;
  box-shadow: none;
}

.app-actions > button {
  height: 56px;
  border-radius: var(--template-radius);
  /* 14, nicht 16. Ein Knopf in Material trägt `labelLarge`, und das steht in
     `VoidTheme.kt` auf 14 fett. Die Vorlage erbte hier schlicht die Größe des
     Fließtextes. */
  font-size: 14px;
}


/* --- Was nichts anzeigt, nimmt auch keinen Platz ----------------------------

   Balken, Prozentzeile und Meldung standen bisher immer im Fluss, auch leer.
   Gemessen waren das 115 Punkte unter dem Download-Knopf — und weil der ganze
   Inhalt mittig steht, schob dieser unsichtbare Block alles Sichtbare um gut
   die Hälfte davon nach oben. In der App gibt es die drei erst, wenn ein
   Auftrag läuft oder etwas schiefging; `ProgressSection` zeichnet sonst eine
   leere Spalte ohne Höhe. */
.template-progress:not(.is-active),
.template-percent:not(.is-active) {
  display: none;
}

.template-status:empty {
  display: none;
}

/* Die Fehlermeldung wie in der App.
   
   Dort ist es ein `Text` in `ProgressSection` mit `VoidColors.Danger` und
   `bodySmall` — also dieselben 13 auf 18 wie in der Titelliste, über die volle
   Breite mittig gesetzt. Die Vorlage hatte ein helleres Rosa (#ff8bc2 gegen
   #ff4d6d), 14,7 statt 13 Punkte Schrift und einen eigenen Abstand von 6
   Punkten nach oben, der zu den 14 der Spalte noch hinzukam.
   
   Die Mindesthöhe von 22 kann ebenfalls weg: Sie hielt früher Platz frei,
   damit die Zeile beim Erscheinen einer Meldung nichts verschiebt — inzwischen
   ist das Element ganz verschwunden, solange nichts darinsteht. */
.template-status {
  width: min(var(--app-block-width), calc(100vw - var(--app-side-gap)));
  min-height: 0;
  margin: 0;
  font-size: 13px;
  line-height: 18px;
}

.template-status.is-error {
  color: var(--template-danger);
}


/* --- Die Schriftgrößen ------------------------------------------------------

   Die Schriftart musste nicht angefasst werden: `system-ui` im Browser und die
   Vorgabe von Compose landen auf demselben — unter Linux bei Noto Sans, auf
   Android bei Roboto, unter Windows bei Segoe UI. Nachgemessen an "PSYCHO" in
   der Titelliste: 48 mal 9 Punkte in der App, 48,9 mal 9,0 bei Noto Sans in 13
   Punkten im Browser.

   Unterschiedlich waren allein die Größen, und die stehen in `VoidTheme.kt`:

       titleMedium   16 fett     Titel der Vorschau
       bodyLarge     16          Eingabefeld und Formatauswahl
       labelLarge    14 fett     der große Knopf
       bodySmall     13 / 18     Liste, Untertitel, Prozentzahl, Pfeilknopf

   Die 13 sind der auffälligste Punkt: Die Vorlage rechnete in `rem` und kam auf
   13,76 in der Zeile und 12,16 bei der Prozentzahl — zwei verschiedene Größen
   dort, wo die App eine hat. */


/* --- Die Qualitätsknöpfe ----------------------------------------------------

   `QualityRow` in der App ist eine Reihe gleich hoher Felder, die sich nach
   ihrer Beschriftung richten und zusammen mittig stehen. Die Vorlage baute
   dafür ein Raster mit einer Mindestbreite von 74 Punkten je Knopf — bei
   "BEST" blieb dadurch links und rechts Luft, die dort nicht hingehört.

   Die Farben waren die zweite Hälfte: Die Fläche der Vorlage ist mit 90 Prozent
   fast dicht und dazu ein anderes Violett, die der App ist dieselbe
   durchscheinende Fläche wie überall (`--template-panel`, 86 Prozent). Auch der
   gewählte Knopf war mit 72 Prozent durchscheinend, in der App ist er das volle
   Violett. */
.template-option-grid {
  grid-template-columns: none;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  justify-content: center;
  width: min(var(--app-block-width), calc(100vw - var(--app-side-gap)));
  margin-top: 0;
}

.template-option-grid button {
  min-width: 0;
  height: 40px;
  padding: 0 16px;
  color: var(--template-muted);
  font-size: 14px;
  border-color: var(--template-line);
  background: var(--template-panel);
}

.template-option-grid button:hover,
.template-option-grid button:focus-visible,
.template-option-grid button.is-selected {
  color: var(--template-text);
  border-color: var(--template-purple-hover);
  background: var(--template-purple);
}


/* --- Ohne erreichbaren Server ----------------------------------------------

   Dann bleibt nur die Meldung stehen, mittig im Bild. Ein Eingabefeld, dessen
   Inhalt nirgends hingeschickt werden kann, führt in die Irre: Man tippt,
   wartet, und es passiert nichts. Die Formatauswahl wäre ohnehin leer — die
   Liste der Formate kommt vom Server.

   Dieselbe Regel gilt in den Apps; dort entscheidet `DownloaderScreen` danach,
   ob Formate da sind und eine Meldung ansteht. */
.app-panel.is-offline .template-form-row,
.app-panel:not(.is-ready) .template-form-row {
  display: none;
}
