Files
Flaro/web/app/src/style.css
T
ScarriffleandClaude Opus 5 1b4784f1db Zwei Fassungen dürfen gewollt sein, und die Startseite darf nicht warten
**Mehrfachfassungen lassen sich abhaken.** Zwei Dateien an einem Film sind
meist zwei Fassungen und völlig in Ordnung. In der Liste standen sie
trotzdem für immer: sie rechnet jedes Mal frisch, und es gab keinen Zustand.
Wer absichtlich zwei Fassungen hält, konnte die Gruppe nicht loswerden — und
unter den gewollten ging das Ungewollte unter.

Abgehakt wird eine Dateizahl, kein Zeitpunkt. Ein Zeitstempel müsste gegen
`MediaFile.ModifiedAt` verglichen werden, und eine alte Datei, frisch
hineinkopiert, behält ihre alte Zeit und käme nie wieder hoch. Die Anzahl
ist genau die Frage: kommt eine dritte Fassung dazu, meldet sich die Gruppe
von selbst zurück. Gerechnet wird sie auf dem Server — ein Client mit
veralteter Liste hakte sonst eine Gruppe mit zwei Dateien ab, die inzwischen
drei hat.

„Ignorierte zeigen" führt zurück, wie bei den erledigten Meldungen; ohne das
wäre das Abhaken unumkehrbar und niemand wüsste, was er weggeklickt hat.

**Die Startseite wartete dreimal nacheinander.** Erst die Serverauskunft,
dann Konto und Mediatheken, dann erst die Startseite — drei Rundreisen,
bevor das erste Bild in Auftrag ging. Liegt eine Sitzung im Speicher, laufen
die ersten beiden jetzt zugleich los. Nur dann: ohne Sitzung liefen
`/auth/me` und `/libraries` vor der Antwort auf `needsSetup` in ein 401, und
die Sitzungsprüfung kann daraufhin abmelden.

Gemessen am laufenden Server: `users/me`, `system/info` und `libraries`
starten alle bei 37 ms, die Startseitenabfragen folgen bei 73 ms. Vorher
lagen drei Runden dazwischen.

**Sieben Hintergrundbilder auf einmal waren sechs zu viel.** Jede Folie des
Aufmachers bekam sofort ihr `w1280`-Bild; sichtbar ist immer nur eine. Jetzt
lädt die sichtbare sofort und der Rest in der Leerlaufzeit — und wer
weiterschaltet, bevor das lief, bekommt sein Bild dann.

**Die Schrift kam von Google.** `display=swap` rettete den Text, nicht den
ersten Aufbau: ein Stylesheet blockiert das Zeichnen, und ein Heimserver
ohne Internet oder mit träger Namensauflösung wartete davor. Inter liegt
jetzt als zwei Dateien von zusammen sechzig Kilobyte im Projekt, variabel
für alle Schnitte, mit `unicode-range` getrennt nach latin und latin-ext.
Gemessen: null Anfragen an fonts.googleapis.com. Lizenztext liegt dabei.

**Und mein Untertitelvorrat bremste mit.** Er startete sofort beim
Hochfahren und las die ganze Mediathek — von derselben Platte, von der die
Startseite ihre Bilder holt. Jetzt beginnt er eine Minute später und stellt
zurück, solange eine Wiedergabe läuft. Die Datei bleibt dabei vorn in der
Reihe; verworfen wird nichts.

678 Tests grün, davon zwei neue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 17:56:41 +02:00

2202 lines
88 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Flaro — Weboberfläche.
*
* Der Aufbau folgt Plex: Kopfleiste oben, Seitenleiste links mit Icons,
* waagerechte Reihen auf der Startseite, Plakate im Verhältnis 2:3 mit Haken
* und Fortschrittsbalken, eine Detailseite mit unscharfem Hintergrund und
* dem Schriftzug des Films.
*
* **Die Farbe nicht.** Plex ist bernsteingelb, Flaro ist Orchidee. Das
* Vorbild ist die Anordnung, nicht das Aussehen. */
/* ---------- Die Schrift, und zwar von hier ----------
*
* **Warum sie mitgeliefert wird.** Vorher kam sie über ein Stylesheet von
* `fonts.googleapis.com`. Ein Stylesheet blockiert das Zeichnen; ein
* Heimserver ohne Internet oder mit träger Namensauflösung wartete davor,
* bevor das erste Pixel stand. Zwei Dateien, zusammen sechzig Kilobyte,
* nehmen diese Abhängigkeit ganz weg.
*
* **Eine Datei für alle Schnitte.** Inter ist eine variable Schrift: 400
* bis 700 stecken in derselben Datei. Vier getrennte Schnitte wären vier
* Abrufe für dasselbe.
*
* `latin-ext` ist eigen aufgeführt und nicht eingerechnet: die
* `unicode-range` sorgt dafür, dass sie nur lädt, wenn wirklich ein Zeichen
* daraus vorkommt — bei deutschen Titeln selten, bei polnischen oder
* tschechischen eben doch.
*
* Inter steht unter der SIL Open Font License; der Text liegt bei den
* Dateien unter `public/schriften/LIZENZ.txt`. */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400 700;
/* Der Text steht sofort in der Systemschrift und wird ersetzt, sobald
Inter da ist — lieber ein Umspringen als ein leeres Bild. */
font-display: swap;
src: url('/schriften/inter-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400 700;
font-display: swap;
src: url('/schriften/inter-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
:root {
/* --- Akzent ------------------------------------------------------------
*
* **Lilac, nicht Orchidee.** Die Apps starten ab Werk mit `#9B8AD4`
* (`AccentOption.swift`), der Web-Client stand auf `#C04AC9`. Dieselbe
* Software in zwei Farben ist keine Gestaltung, sondern Drift.
*
* Die beiden abgeleiteten Werte werden **gerechnet**, nicht gepflegt —
* sonst müsste man sie bei jedem Farbwechsel von Hand mitziehen und
* vergisst es beim zweiten Mal. */
--accent: #9B8AD4;
--accent-dim: color-mix(in srgb, var(--accent) 72%, black);
--accent-soft: color-mix(in srgb, var(--accent) 16%, transparent);
/* --- Flächen -----------------------------------------------------------
*
* Auf FlaroDesign zurückgeführt: `elevated`, `placeholder`, `text`,
* `text-dim`, `text-muted` und die Trennlinie wichen in der letzten
* Stelle ab — unsichtbar einzeln, zusammen aber genau der Grund, warum
* App und Browser nicht wie dasselbe Programm aussahen. */
--canvas: #0B0B0D;
--surface: #141417;
--elevated: #1D1D21;
--raised: #26262B;
--placeholder: #26262B;
--line: rgba(255, 255, 255, .10);
/* Kopf- und Seitenleiste. **Eigenes Token, weil es vorher als `#0E0E11`
* hart im Regelwerk stand** — und sich damit jedem Theme entzog. */
--chrome: #0E0E11;
--text: #F5F5F7;
--text-dim: #A0A0A8;
--text-muted: #6E6E76;
--text-faint: #55555C;
--destructive: #E5484D;
--destructive-dim: color-mix(in srgb, var(--destructive) 78%, black);
/* Schatten als Token, nicht fünfmal fest verdrahtet. */
--schatten-karte: 0 8px 24px rgba(0, 0, 0, .55);
--schatten-plakat: 0 12px 40px rgba(0, 0, 0, .6);
--schatten-dialog: 0 24px 64px rgba(0, 0, 0, .7);
--s-tight: 4px; --s-snug: 8px; --s-regular: 12px;
--s-loose: 16px; --s-section: 24px; --s-page: 32px;
/* --- Formen ------------------------------------------------------------
*
* **Drei Radien statt vier, und keiner mehr zufällig.** Vorher: 6 px auf
* Plakaten, 8 px auf Karten und Feldern, 999 px auf *jedem* Knopf, dazu
* zweimal nacktes 3 px. Vollrunde Primärknöpfe neben 8-px-Karten ergeben
* keine Formensprache — und sie sind der zweitstärkste Hinweis darauf,
* dass eine Oberfläche nicht entworfen, sondern zusammengesetzt wurde.
*
* 8 und 10 sind dieselben Werte wie in FlaroDesign. */
--r-karte: 8px;
--r-flaeche: 10px;
--r-knopf: 6px;
--r-pill: 999px;
/* Rückwärtsnamen, solange noch Regeln daran hängen. */
--r-card: var(--r-karte);
--r-control: var(--r-flaeche);
/* --- Schrift -----------------------------------------------------------
*
* **Eine Skala statt sechs Zufallswerten.** Die Überschriften standen auf
* 34 / 26 / 22 / 20 / 19 / 17 px — Werte ohne Verhältnis zueinander, alle
* in 700, mit fast gleichem negativem Spacing. Das erzeugt keine
* Hierarchie, sondern Rauschen. */
--t-hero: 32px;
--t-titel: 24px;
--t-abschnitt: 19px;
--t-gross: 15px;
--t-normal: 13px;
--t-klein: 11px;
--sidebar-w: 240px;
--topbar-h: 56px;
color-scheme: dark;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
background: var(--canvas);
color: var(--text);
font: 400 14px/1.55 Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
-webkit-font-smoothing: antialiased;
}
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
.hidden { display: none !important; }
/* **Jedes SVG bekommt eine Grösse, nicht nur die in `.icon`.** Ein rohes
`<svg>` ohne Breite füllt sonst seinen Container — und die Lupe in der
Suchleiste war dreihundert Bildpunkte gross und lag über der Seite. */
svg { width: 20px; height: 20px; display: block; flex: none; }
.icon { width: 20px; height: 20px; display: block; flex: none; }
/* ---------- Kopfleiste ---------- */
#topbar {
position: fixed; inset: 0 0 auto 0; height: var(--topbar-h); z-index: 30;
display: flex; align-items: center; gap: var(--s-loose);
padding: 0 var(--s-loose);
background: var(--chrome);
border-bottom: 1px solid var(--line);
}
#topbar .marke {
font-size: var(--t-abschnitt); font-weight: 700; letter-spacing: -.02em;
color: var(--accent); user-select: none;
}
#topbar .suchfeld {
flex: 1; max-width: 420px;
display: flex; align-items: center; gap: var(--s-snug);
background: var(--elevated); border: 1px solid transparent;
border-radius: var(--r-pill); padding: 0 var(--s-regular); height: 34px;
color: var(--text-muted);
}
#topbar .suchfeld:focus-within { border-color: var(--accent); color: var(--text); }
#topbar .suchfeld input {
flex: 1; min-width: 0; background: none; border: 0; outline: none; color: var(--text);
}
#topbar .rechts { margin-left: auto; display: flex; align-items: center; gap: var(--s-regular); }
#topbar .konto {
display: flex; align-items: center; gap: var(--s-snug);
background: none; border: 0; color: var(--text-dim); padding: 0;
}
#topbar .konto .kreis {
width: 28px; height: 28px; border-radius: 50%;
background: var(--accent); color: #fff;
display: grid; place-items: center; font-size: 13px; font-weight: 600;
}
/* Mit eigenem Bild dieselbe Scheibe, nur gefüllt. */
#topbar .konto img.kreis { object-fit: cover; background: var(--elevated); }
#topbar .konto:hover .kreis { box-shadow: 0 0 0 2px var(--accent-soft); }
/* ---------- Grundgerüst ---------- */
#app { display: flex; min-height: 100%; padding-top: var(--topbar-h); }
#chrome {
position: fixed; top: var(--topbar-h); bottom: 0; left: 0;
width: var(--sidebar-w); overflow-y: auto;
background: var(--chrome);
border-right: 1px solid var(--line);
padding: var(--s-loose) 0;
display: flex; flex-direction: column; gap: 2px;
}
.nav-gruppe {
font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
color: var(--text-faint); padding: var(--s-loose) var(--s-loose) var(--s-snug);
display: flex; align-items: center; justify-content: space-between; gap: var(--s-snug);
}
/* Der Stift erscheint beim Überfahren der Seitenleiste — ständig sichtbar
* wäre er ein Knopf, den man einmal im Jahr braucht, an einer Stelle, an der
* jeden Tag etwas anderes gesucht wird. Bei Tastaturfokus immer. */
.nav-bearbeiten {
background: none; border: 0; padding: 2px; border-radius: var(--r-knopf);
color: var(--text-faint); opacity: 0; transition: opacity .12s ease;
}
#chrome:hover .nav-bearbeiten,
.nav-bearbeiten:focus-visible,
.nav-bearbeiten.an { opacity: 1; }
.nav-bearbeiten:hover { color: var(--text); }
.nav-bearbeiten.an { color: var(--accent); }
.nav-bearbeiten svg { width: 15px; height: 15px; display: block; }
/* ---------- Umsortieren durch Ziehen ----------
*
* Der Behälter braucht keine eigene Form; er existiert, damit beim Ziehen
* gerechnet werden kann, die wievielte Zeile unter dem Zeiger liegt.
*/
.nav-liste { display: flex; flex-direction: column; }
#chrome button.ziehbar {
cursor: grab;
/* Ohne das übernimmt der Browser die senkrechte Geste und scrollt, statt
* die Zeile zu bewegen — auf dem Telefon wäre Ziehen damit unmöglich. */
touch-action: none;
user-select: none;
position: relative;
}
#chrome button.ziehbar:active { cursor: grabbing; }
.nav-griff {
display: inline-flex; color: var(--text-faint); flex: none; margin-right: -2px;
}
.nav-griff svg { width: 14px; height: 14px; }
#chrome button.ziehbar:hover .nav-griff { color: var(--text-muted); }
/* Die gezogene Zeile liegt über den anderen und wirft einen Schatten —
* sonst sieht man nicht, dass sie abgehoben ist. */
#chrome button.wird-gezogen {
z-index: 3; cursor: grabbing;
background: var(--elevated); box-shadow: var(--schatten-dialog);
opacity: .95;
}
/* Die weichenden Zeilen gleiten, die gezogene folgt dem Zeiger ohne
* Verzögerung — eine Übergangszeit auf ihr machte sie zäh. */
#chrome .nav-liste.zieht button:not(.wird-gezogen) {
transition: transform .14s ease;
}
@media (prefers-reduced-motion: reduce) {
#chrome .nav-liste.zieht button:not(.wird-gezogen) { transition: none; }
}
#chrome button {
display: flex; align-items: center; gap: var(--s-regular);
background: none; border: 0; text-align: left;
padding: 9px var(--s-loose); color: var(--text-dim);
border-left: 3px solid transparent;
}
#chrome button > span.name {
flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#chrome button .icon { flex: none; color: var(--text-muted); }
#chrome button:hover { color: var(--text); background: rgba(255, 255, 255, .04); }
#chrome button:hover .icon { color: var(--text-dim); }
/* **Der Balken links, nicht ein gefüllter Knopf.** Bei Plex markiert ein
schmaler farbiger Strich die aktive Zeile; das liest sich in einer langen
Liste ruhiger als ein leuchtender Block. */
#chrome button.check {
color: var(--accent); border-left-color: var(--accent);
background: linear-gradient(90deg, var(--accent-soft), transparent 60%);
}
#chrome button.check .icon { color: var(--accent); }
#chrome button .zahl {
flex: none; font-size: 11px; font-variant-numeric: tabular-nums;
color: var(--text-faint);
}
#chrome button.check .zahl { color: var(--accent); }
.nav-fuss { margin-top: auto; padding-top: var(--s-loose); }
#content {
flex: 1; min-width: 0;
margin-left: var(--sidebar-w);
padding: var(--s-section) var(--s-page) var(--s-page);
}
/* ---------- Anmeldung ---------- */
#login {
min-height: 100vh; display: grid; place-items: center; padding: var(--s-section);
background: var(--canvas);
}
.login-card {
width: 100%; max-width: 360px;
display: flex; flex-direction: column; gap: var(--s-regular);
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--r-control); padding: var(--s-section);
}
.login-card h1 { margin: 0; font-size: var(--t-titel); letter-spacing: -.02em; color: var(--accent); }
.bleiben {
display: flex; align-items: center; gap: var(--s-snug);
color: var(--text-dim); font-size: 13px; cursor: pointer; user-select: none;
}
.bleiben input { accent-color: var(--accent); width: 15px; height: 15px; margin: 0; }
/* ---------- Kopf einer Mediathek ---------- */
.seitenkopf {
display: flex; align-items: flex-start; gap: var(--s-section);
margin-bottom: var(--s-section);
}
.seitenkopf .wer { min-width: 0; }
.seitenkopf h1 { margin: 0; font-size: var(--t-titel); font-weight: 700; letter-spacing: -.02em; }
.seitenkopf .quelle {
font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint);
}
/* Reiter in der Mitte, aktiver unterstrichen — wie bei Plex. */
.reiter { display: flex; gap: var(--s-section); margin: 0 auto; }
.reiter button {
background: none; border: 0; padding: 4px 0 8px;
color: var(--text-muted); font-weight: 500;
border-bottom: 2px solid transparent;
}
.reiter button:hover { color: var(--text); }
.reiter button.check { color: var(--accent); border-bottom-color: var(--accent); }
/* Filterzeile darunter — Pillen, keine Formularfelder.
*
* **Bei Plex sieht ein Filter nicht nach Eingabe aus.** Dort stehen Wörter
* mit einem kleinen Pfeil daneben; ein umrandetes Auswahlfeld sagt „hier
* fehlt noch etwas", und das stimmt nicht — es steht ja schon etwas drin. */
.filterzeile {
display: flex; align-items: center; gap: var(--s-snug);
margin-bottom: var(--s-section); flex-wrap: wrap;
}
.filterzeile .anzahl {
background: var(--elevated); border-radius: var(--r-pill);
padding: 3px 11px; font-size: 13px; color: var(--text-dim);
font-variant-numeric: tabular-nums;
}
.pille {
position: relative; display: inline-flex; align-items: center;
}
.pille .auswahl {
background: none; border: 0; color: var(--text-dim); font-weight: 500;
padding: 6px 11px; border-radius: var(--r-pill);
}
.pille .auswahl:hover { background: var(--elevated); color: var(--text); }
/* Gesetzt heisst farbig — so sieht man ohne Lesen, dass gefiltert wird. */
.pille.gesetzt .auswahl { color: var(--accent); }
/* ---------- Auswahlfeld ----------
*
* Ersetzt <select>. Dessen Liste zeichnet das Betriebssystem: mitten in der
* dunklen Oberfläche erschien ein helles Systemmenü, das sich weder färben
* noch runden lässt. Die Liste hier ist dieselbe Fläche wie das Kontextmenü
* — gleicher Rahmen, gleiche Radien, gleicher Schatten.
*/
.auswahl {
display: inline-flex; align-items: center; gap: 6px;
background: var(--elevated); border: 1px solid var(--line);
border-radius: var(--r-knopf); cursor: pointer;
color: var(--text-dim); font-size: var(--t-normal); font-weight: 500;
padding: 7px 10px; text-align: left;
}
.auswahl:hover { color: var(--text); }
.auswahl > span:first-child {
flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.auswahl-pfeil { display: inline-flex; color: var(--text-faint); }
.auswahl-pfeil svg { width: 15px; height: 15px; }
.auswahl[aria-expanded='true'] .auswahl-pfeil { transform: rotate(180deg); }
.auswahl-liste {
position: fixed; z-index: 120;
max-height: min(50vh, 360px); overflow-y: auto;
padding: var(--s-tight);
background: var(--elevated);
border: 1px solid var(--line); border-radius: var(--r-flaeche);
box-shadow: var(--schatten-dialog);
display: flex; flex-direction: column;
}
.auswahl-punkt {
background: none; border: 0; border-radius: var(--r-knopf);
padding: 8px var(--s-regular); text-align: left; white-space: nowrap;
color: var(--text-dim); font-size: var(--t-normal);
}
.auswahl-punkt:hover { background: var(--raised); color: var(--text); }
/* Das Gewählte trägt die Akzentfarbe — ohne Haken, der die Breite verschiebt. */
.auswahl-punkt.gewaehlt { color: var(--accent); font-weight: 600; }
.filterzeile .zuruecksetzen {
background: none; border: 0; padding: 6px 4px;
color: var(--text-muted); font-size: 13px;
}
.filterzeile .zuruecksetzen:hover { color: var(--text); text-decoration: underline; }
.filterzeile .suchhuelle {
margin-left: auto; display: flex; align-items: center; gap: var(--s-snug);
background: var(--elevated); border: 1px solid transparent;
border-radius: var(--r-pill); padding: 0 var(--s-regular); height: 32px;
color: var(--text-muted); min-width: 220px;
}
.filterzeile .suchhuelle:focus-within { border-color: var(--accent); color: var(--text); }
.filterzeile .suchhuelle input {
flex: 1; min-width: 0; background: none; border: 0; outline: none; color: var(--text);
}
/* ---------- Kacheln ---------- */
.raster {
display: grid; gap: var(--s-loose);
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.kachel {
display: flex; flex-direction: column; gap: var(--s-snug);
background: none; border: 0; padding: 0; text-align: left; min-width: 0;
}
.plakat {
position: relative; width: 100%; aspect-ratio: 2 / 3;
background: var(--placeholder);
border-radius: var(--r-karte); overflow: hidden;
transition: transform .18s ease-out, box-shadow .18s ease-out;
}
.plakat img {
width: 100%; height: 100%; object-fit: cover;
opacity: 0; transition: opacity .2s;
}
.plakat.geladen img { opacity: 1; }
/* **Hover mit Inhalt.** Zwei Bildpunkte Anheben waren kaum zu sehen; ein
leichtes Hochskalieren hebt die Kachel aus der Reihe heraus, so wie bei
Netflix. Nur `transform` und `box-shadow`, damit nichts umbricht. */
.kachel:hover .plakat,
.kachel:focus-visible .plakat {
transform: scale(1.04);
box-shadow: var(--schatten-karte);
z-index: 3;
}
/* **Der Abspielknopf als Kreis unten links, nicht als Vollflächen-Overlay.**
Das Overlay legte sich über das ganze Plakat und verdeckte genau das Bild,
nach dem man gerade sucht. */
.schweber {
position: absolute; left: var(--s-snug); bottom: var(--s-snug);
display: grid; place-items: center;
width: 38px; height: 38px; border-radius: 50%;
background: rgba(10, 10, 12, .78);
backdrop-filter: blur(6px);
opacity: 0; transform: translateY(6px);
transition: opacity .18s ease-out, transform .18s ease-out;
}
.schweber svg { width: 20px; height: 20px; color: #fff; }
.kachel:hover .schweber,
.kachel:focus-visible .schweber { opacity: 1; transform: none; }
/* **Haken und Zahl sitzen in derselben Ecke**, weil sie dieselbe Frage
beantworten: ist hier noch etwas offen? */
.ecke {
position: absolute; top: 0; right: 0; z-index: 2;
min-width: 26px; height: 26px; padding: 0 6px;
display: grid; place-items: center;
font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums;
border-bottom-left-radius: var(--r-card);
}
.ecke.haken { background: rgba(0, 0, 0, .72); color: #fff; }
.ecke.haken svg { width: 15px; height: 15px; }
.ecke.zahl { background: var(--accent); color: #fff; }
/* Ab der ersten Sekunde sichtbar — darum die Mindestbreite. */
.fortschritt {
position: absolute; left: 0; right: 0; bottom: 0; height: 3px; z-index: 2;
background: rgba(255, 255, 255, .22);
}
.fortschritt > div { height: 100%; min-width: 4px; background: var(--accent); }
.kacheltext { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.kacheltext strong {
/* **Lauter als die Zeile darunter.** Mit 13 px/500 war der Titel leiser
als manche Metazeile daneben — die Typografie erzeugte damit keine
Hierarchie, sondern Rauschen. */
font-weight: 600; font-size: 14px; color: var(--text);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kacheltext span {
font-size: 12px; color: var(--text-muted);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kacheltext .dritte { color: var(--text-faint); }
/* ---------- Waagerechte Reihen ---------- */
.reihe { margin-bottom: var(--s-page); }
.reihe-kopf {
display: flex; align-items: center; gap: var(--s-regular);
margin-bottom: var(--s-regular);
}
.reihe-kopf h2 { margin: 0; font-size: var(--t-abschnitt); font-weight: 700; letter-spacing: -.015em; }
.reihe-kopf .mehr {
background: none; border: 0; padding: 0; font-size: 13px; color: var(--accent);
}
.reihe-kopf .mehr:hover { text-decoration: underline; }
.reihe-pfeile { margin-left: auto; display: flex; gap: var(--s-tight); }
.pfeil {
width: 28px; height: 28px; display: grid; place-items: center;
background: var(--elevated); border: 0; border-radius: 50%; color: var(--text-dim);
}
.pfeil:hover { background: var(--raised); color: var(--text); }
.pfeil svg { width: 16px; height: 16px; }
.strip {
display: grid; grid-auto-flow: column; grid-auto-columns: 150px;
gap: var(--s-loose);
overflow-x: auto; scroll-snap-type: x proximity;
scrollbar-width: none; padding-bottom: var(--s-tight);
}
.strip::-webkit-scrollbar { display: none; }
.strip > * { scroll-snap-align: start; }
/* **Ein Text, der beim Überfahren einen anderen zeigt.**
*
* Für die Auflösung: in Ruhe steht „Full HD" da, beim Überfahren „1080p ·
* 1920 × 800". Ein Tooltip täte dasselbe, aber verzögert, über der
* Nachbarzeile und am Finger gar nicht.
*
* **Beide liegen übereinander, nicht nacheinander.** Das Feld ist so breit
* wie der breitere der beiden — sonst rückte beim Überfahren die halbe
* Tabelle. Gerastert statt absolut positioniert, damit die Zeilenhöhe von
* selbst stimmt. */
.wechseltext { display: inline-grid; }
.wechseltext > span { grid-area: 1 / 1; transition: opacity .15s ease; }
.wechseltext > .genau { opacity: 0; color: var(--text-dim); }
@media (hover: hover) {
.wechseltext:hover > span { opacity: 0; }
.wechseltext:hover > .genau { opacity: 1; }
}
/* Ohne Zeiger lässt sich nichts überfahren — dort steht das Genaue
dauerhaft daneben, statt unerreichbar zu sein. */
@media (hover: none) {
.wechseltext { display: inline; }
.wechseltext > .genau { opacity: 1; }
.wechseltext > .genau::before { content: ' · '; }
}
/* ---------- Detailseite ---------- */
.detail { position: relative; }
/* **Der Hintergrund gibt der Seite die Farbe des Films** — wie bei Plex,
* aber nur über dem Inhalt, nicht über dem ganzen Fenster.
*
* Er hat beide Ränder schon gehabt. Zuerst begann er bei `left: 240px`, war
* dabei mit `opacity: .28` und `blur(2px)` aber weder Bild noch Fläche: ein
* grau verwischter Rest, der nichts tat. Die Antwort darauf war `inset: 0`
* — über alles hinweg. Damit lag ein Viertel des Bildes hinter Seiten- und
* Kopfleiste, beide undurchsichtig; bei 1280×800 gemessen 18,8 % der Breite
* und 7 % der Höhe, zusammen 24,4 % der Fläche. Weichgezeichnet fiel das
* nicht auf. Scharf beginnt der Inhalt dann mitten im Motiv, mit einem
* angeschnittenen Kopf am linken Rand.
*
* Jetzt sitzt es in dem Feld, in dem es auch zu sehen ist, und wird darin
* ganz zugeschnitten. Dass es kräftig genug dasteht, macht die Deckung —
* siehe `.detail-bild` —, nicht die Fläche. */
.detail-hintergrund {
position: fixed;
top: var(--topbar-h); right: 0; bottom: 0; left: var(--sidebar-w);
z-index: -1;
pointer-events: none;
}
/* **Scharf, solange nichts darüberliegt.** Die Weichzeichnung steht nicht
* mehr fest hier, sondern wächst mit dem Bildlauf — gerechnet in
* `weichNachBildlauf()`. Ohne Skript bleibt sie bei 0; dann sieht man ein
* scharfes Bild, und das ist kein Fehler, sondern genau der Zustand, in dem
* die Seite aufgeht. */
.detail-bild {
position: absolute; inset: 0;
background-size: cover; background-position: center top;
/* **Scharf etwas blasser als weich, und das ist gemessen.** Eine
* Weichzeichnung verrührt die hellen Stellen mit den dunklen; ein scharfes
* Bild hat die weisse Stelle genau dort, wo die Zeile steht. Bei „Dark"
* kam der kleinste Abstand der Eckdaten zum Hintergrund auf 4,07∶1, solange
* weichgezeichnet wurde — scharf bei derselben Deckung nur noch auf 2,44∶1.
* Bei 0,28 sind es wieder 4,02∶1: so lesbar wie vorher, nur eben scharf. */
opacity: var(--det-deckung, .28);
filter: blur(var(--det-weich, 0px)) saturate(1.1);
/* Weiter hochgezogen als das Bild selbst: die Unschärfe würde sonst am
Rand als harte Kante sichtbar. */
transform: scale(1.1);
mask-image: linear-gradient(180deg, #000 0%, rgba(0,0,0,.55) 55%, transparent 92%);
}
.detail-kopf {
display: flex; gap: var(--s-page); align-items: flex-start;
margin-bottom: var(--s-page);
}
.detail-kopf .plakat {
flex: none; width: 240px;
box-shadow: 0 12px 40px rgba(0, 0, 0, .6);
}
.detail-text { min-width: 0; flex: 1; padding-top: var(--s-snug); }
/* Der Schriftzug des Films statt der Systemschrift — wenn es einen gibt. */
.detail-text .schriftzug {
max-width: 420px; max-height: 110px; margin-bottom: var(--s-regular);
object-fit: contain; object-position: left bottom; display: block;
}
.detail-text h1 { margin: 0 0 var(--s-snug); font-size: var(--t-hero); font-weight: 700; letter-spacing: -.03em; }
.detail-text .unter { margin: 0 0 var(--s-regular); font-size: 17px; color: var(--text-dim); }
.eckdaten {
display: flex; align-items: center; gap: var(--s-regular); flex-wrap: wrap;
color: var(--text-dim); font-size: 13px; margin-bottom: var(--s-loose);
}
.freigabe {
border: 1px solid var(--text-faint); border-radius: 3px;
padding: 0 6px; font-size: 11px; font-weight: 700; letter-spacing: .04em;
color: var(--text-dim);
}
.bewertung { display: flex; align-items: center; gap: 5px; color: var(--text); font-weight: 600; }
.bewertung svg { width: 15px; height: 15px; color: var(--accent); }
.werbezeile { color: var(--text-muted); font-style: italic; margin: 0 0 var(--s-loose); }
.beschreibung { max-width: 70ch; margin: 0 0 var(--s-section); color: var(--text-dim); }
.aktionen { display: flex; align-items: center; gap: var(--s-regular); margin-bottom: var(--s-section); flex-wrap: wrap; }
.btn {
display: inline-flex; align-items: center; gap: var(--s-snug);
background: var(--elevated); border: 0; border-radius: var(--r-knopf);
padding: 9px var(--s-loose); color: var(--text);
}
.btn:hover { background: var(--raised); }
.btn:disabled { opacity: .5; cursor: default; }
.btn svg { width: 18px; height: 18px; }
/* **Der Primärknopf ist hell, nicht farbig.**
*
* Genau das macht Netflix: der Abspielknopf ist weiss mit schwarzer Schrift,
* die Markenfarbe erscheint nur im Schriftzug. Der Akzent sass hier auf neun
* Dingen gleichzeitig — Marke, Navigation, Knopf, Fortschritt, Plakette,
* Stern, Fokus, Filter, Kontokreis. Wenn alles Akzent ist, markiert er
* nichts. */
.btn-primary { background: var(--text); color: var(--canvas); font-weight: 600; }
.btn-primary:hover { background: #fff; }
.btn-primary:hover { background: var(--accent-dim); }
/* Runde Knöpfe für alles ohne Beschriftung — wie Plex' Merkliste und Haken. */
.rund {
width: 38px; height: 38px; padding: 0; border-radius: 50%;
display: grid; place-items: center;
background: none; border: 1px solid var(--line); color: var(--text-dim);
}
.rund:hover { border-color: var(--text-muted); color: var(--text); }
.rund.an { background: var(--accent); border-color: var(--accent); color: #fff; }
.rund svg { width: 19px; height: 19px; }
/* Die Medienangaben als Tabelle — Plex' „Video / Ton / Untertitel". */
.medientabelle { display: grid; grid-template-columns: auto 1fr; gap: var(--s-snug) var(--s-section); margin-bottom: var(--s-section); }
.medientabelle dt { color: var(--text-muted); font-size: 13px; }
.medientabelle dd { margin: 0; font-size: 13px; }
/* Besetzung: runde Porträts, wie bei Plex. */
.besetzung { display: grid; grid-auto-flow: column; grid-auto-columns: 110px; gap: var(--s-loose); overflow-x: auto; scrollbar-width: none; }
.besetzung::-webkit-scrollbar { display: none; }
.kopf-kachel { display: flex; flex-direction: column; align-items: center; gap: var(--s-snug); text-align: center; }
.kopf-kachel .portrait {
width: 92px; height: 92px; border-radius: 50%; overflow: hidden;
background: var(--placeholder); display: grid; place-items: center;
color: var(--text-faint); font-size: 26px; font-weight: 600;
}
.kopf-kachel .portrait img { width: 100%; height: 100%; object-fit: cover; }
.kopf-kachel strong { font-size: 12px; font-weight: 500; }
.kopf-kachel span { font-size: 11px; color: var(--text-muted); }
/* Genre-Kacheln, Plex' „Kategorien". */
.kategorien { display: grid; gap: var(--s-loose); grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
.kategorie {
aspect-ratio: 16 / 9; border: 0; border-radius: var(--r-control);
display: grid; place-items: center; color: #fff;
font-size: 19px; font-weight: 700; letter-spacing: -.01em;
position: relative; overflow: hidden;
}
.kategorie:hover { filter: brightness(1.15); }
/* ---------- Kleinteile ---------- */
h2 { font-size: var(--t-abschnitt); font-weight: 700; letter-spacing: -.015em; margin: 0 0 var(--s-regular); }
.seitentitel { margin: 0; font-size: var(--t-titel); font-weight: 700; letter-spacing: -.02em; }
.einstellungen { display: flex; flex-direction: column; gap: var(--s-regular); margin-bottom: var(--s-page); }
.einstellung {
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--r-control); padding: var(--s-loose);
display: flex; flex-direction: column; gap: var(--s-regular);
}
.einstellung.waehlbar { text-align: left; width: 100%; }
.einstellung.waehlbar:hover { border-color: var(--accent); }
.zeile { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.zeile strong { font-weight: 600; }
.winzig { font-size: 12px; color: var(--text-muted); }
.hinweis { color: var(--text-dim); font-size: 13px; }
.feld {
background: var(--elevated); border: 1px solid transparent;
border-radius: var(--r-control); padding: 9px var(--s-regular);
color: var(--text); outline: none; min-width: 0;
}
.feld:focus { border-color: var(--accent); }
/* Ein mehrzeiliges Feld erbt alles vom einzeiligen — ausser dem, was es nur
dort gibt. Ohne `font: inherit` nähme der Browser eine Schreibmaschine, und
der Dialog hätte plötzlich zwei Schriften. Gezogen wird nur senkrecht:
waagerecht hiesse, den Dialog aufzusprengen. */
textarea.feld {
font: inherit; line-height: 1.45;
resize: vertical; min-height: 84px;
}
.knopfreihe { display: flex; gap: var(--s-snug); flex-wrap: wrap; align-items: center; }
.fehler {
background: rgba(229, 72, 77, .12); border: 1px solid rgba(229, 72, 77, .35);
border-radius: var(--r-control); padding: var(--s-regular); color: #FF9A9D; font-size: 13px;
}
.leer { color: var(--text-muted); padding: var(--s-section) 0; }
.spinner {
width: 22px; height: 22px; margin: var(--s-section) auto;
border: 2px solid var(--line); border-top-color: var(--accent);
border-radius: 50%; animation: dreh .7s linear infinite;
}
@keyframes dreh { to { transform: rotate(360deg); } }
/* Derselbe Kreis, nur neben Text statt über einer leeren Fläche. */
.spinner.klein { width: 14px; height: 14px; margin: 0; flex: none; }
.attribution {
display: flex; align-items: center; gap: var(--s-snug);
margin-top: var(--s-page); padding-top: var(--s-loose);
border-top: 1px solid var(--line);
color: var(--text-faint); font-size: 12px;
}
.attribution img { height: 14px; opacity: .7; }
/* Eine laufende Wiedergabe im Dashboard. */
.einstellung.laeuft { gap: var(--s-snug); }
.laeuft-bild {
flex: none; width: 44px; aspect-ratio: 2 / 3;
border-radius: 3px; overflow: hidden; background: var(--placeholder);
}
.laeuft-bild img { width: 100%; height: 100%; object-fit: cover; }
.fortschritt.breit { position: static; height: 3px; border-radius: 2px; overflow: hidden; }
/* ---------- Dialoge ---------- */
/* Das `<dialog>` des Browsers: Escape schliesst, der Hintergrund ist
unbedienbar, der Fokus bleibt gefangen. Hier bleibt nur das Aussehen. */
.dlg {
width: min(520px, calc(100vw - 2 * var(--s-section)));
padding: 0; border: 1px solid var(--line);
border-radius: var(--r-control);
background: var(--surface); color: var(--text);
box-shadow: 0 24px 64px rgba(0, 0, 0, .7);
}
.dlg::backdrop { background: rgba(0, 0, 0, .62); backdrop-filter: blur(2px); }
.dlg-kopf {
display: flex; align-items: center; gap: var(--s-regular);
padding: var(--s-loose) var(--s-section);
border-bottom: 1px solid var(--line);
}
.dlg-kopf h2 { margin: 0; flex: 1; font-size: 17px; }
.dlg-zu {
width: 30px; height: 30px; display: grid; place-items: center;
background: none; border: 0; border-radius: 50%; color: var(--text-muted);
}
.dlg-zu:hover { background: var(--elevated); color: var(--text); }
.dlg-koerper {
padding: var(--s-section);
display: flex; flex-direction: column; gap: var(--s-loose);
max-height: 60vh; overflow-y: auto;
}
/* Im Bilddialog ist die Höhe fest: so springt die Fläche beim Reiterwechsel
* nicht, und der Reiter bleibt an derselben Stelle. */
.dlg-breit .dlg-koerper { height: 62vh; max-height: none; }
.dlg-feld { display: flex; flex-direction: column; gap: var(--s-snug); }
.dlg-feld.waagerecht {
flex-direction: row; align-items: center; gap: var(--s-snug); cursor: pointer;
}
.dlg-feld.waagerecht input { accent-color: var(--accent); width: 15px; height: 15px; margin: 0; }
.dlg-beschriftung { font-size: 13px; color: var(--text-dim); }
.dlg-fuss {
display: flex; justify-content: flex-end; gap: var(--s-snug);
padding: var(--s-loose) var(--s-section);
border-top: 1px solid var(--line);
}
.btn-gefahr { background: var(--destructive); color: #fff; font-weight: 600; }
.btn-gefahr:hover { background: var(--destructive-dim); }
/* ---------- Der Spieler ----------
*
* **Eigene Steuerung statt der eingebauten.** Die Leiste des Browsers kennt
* weder Tonspuren noch Qualitätsstufen — und die Tonspurwahl ist der Grund,
* aus dem es diesen Server gibt. Ausserdem sah sie, wie der Benutzer sagte,
* „gefühlt von 1930" aus. */
.pl {
position: fixed; inset: 0; z-index: 150; background: #000;
display: grid; place-items: center; outline: none;
}
.pl-video { width: 100%; height: 100%; object-fit: contain; background: #000; }
.pl-dreher { position: absolute; margin: 0; }
/* Kopf und Leiste blenden sich weg, während etwas läuft. */
.pl-kopf, .pl-leiste {
position: absolute; left: 0; right: 0; z-index: 2;
display: flex; align-items: center; gap: var(--s-regular);
/* **Seitlich mindestens 18 Pixel, auf einem Gerät mit Ecken mehr.** Ohne
`env()` liegt der äusserste Knopf auf einem iPhone quer unter der
abgerundeten Ecke. */
padding: 14px max(18px, env(safe-area-inset-right)) 14px max(18px, env(safe-area-inset-left));
transition: opacity .2s ease-out, transform .2s ease-out;
}
.pl-kopf {
top: 0; justify-content: space-between;
background: linear-gradient(to bottom, rgba(0, 0, 0, .78), transparent);
padding-top: max(14px, env(safe-area-inset-top));
}
.pl-leiste {
bottom: 0; flex-direction: column; align-items: stretch; gap: 8px;
background: linear-gradient(to top, rgba(0, 0, 0, .86), transparent);
padding-bottom: max(14px, env(safe-area-inset-bottom));
}
.pl-still .pl-kopf { opacity: 0; transform: translateY(-8px); pointer-events: none; }
.pl-still .pl-leiste { opacity: 0; transform: translateY(8px); pointer-events: none; }
.pl-still .pl-video { cursor: none; }
.pl-titel {
font-size: 15px; font-weight: 600; color: #fff;
text-shadow: 0 1px 3px rgba(0, 0, 0, .7);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pl-unten { display: flex; align-items: center; justify-content: space-between; gap: var(--s-regular); }
.pl-gruppe { display: flex; align-items: center; gap: 6px; min-width: 0; }
/* **Die rechte Gruppe gibt nicht nach.**
*
* Beide Gruppen durften schrumpfen, und ihre Knöpfe sind `flex: 0 0 auto` —
* wird es eng, schrumpft also die Gruppe und ihre Knöpfe stehen heraus. Dann
* liegt der äusserste halb hinter dem Fensterrand. Links gibt es etwas
* nachzugeben (die Lautstärke, die Zeitangabe), rechts sind es nur Knöpfe. */
.pl-unten > .pl-gruppe:last-child { flex: 0 0 auto; }
.pl-unten > .pl-gruppe:first-child { flex: 0 1 auto; overflow: hidden; }
/* **44 Bildpunkte, nicht 32.** Darunter trifft ein Finger daneben, und ein
Fehlgriff auf der Zeitleiste wirft einen aus dem Film. */
.pl-rund {
display: grid; place-items: center;
width: 44px; height: 44px; flex: 0 0 auto;
border: 0; border-radius: 50%; background: transparent; color: #fff;
cursor: pointer; transition: background .15s;
}
.pl-rund:hover, .pl-rund:focus-visible { background: rgba(255, 255, 255, .16); }
.pl-rund svg { width: 22px; height: 22px; }
.pl-stand, .pl-rest {
font-size: 13px; color: rgba(255, 255, 255, .88);
font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Zeit- und Lautstärkeregler: dieselbe Form, verschiedene Breite. */
.pl-zeit, .pl-laut {
-webkit-appearance: none; appearance: none;
height: 20px; background: transparent; cursor: pointer; margin: 0;
}
.pl-zeit { width: 100%; }
.pl-laut { width: 84px; flex: 0 0 auto; }
.pl-zeit::-webkit-slider-runnable-track, .pl-laut::-webkit-slider-runnable-track {
height: 4px; border-radius: 2px;
background: linear-gradient(to right, var(--accent) var(--anteil, 0%),
rgba(255, 255, 255, .28) var(--anteil, 0%));
}
.pl-laut::-webkit-slider-runnable-track { background: rgba(255, 255, 255, .28); }
.pl-zeit::-moz-range-track, .pl-laut::-moz-range-track {
height: 4px; border-radius: 2px; background: rgba(255, 255, 255, .28);
}
.pl-zeit::-moz-range-progress { height: 4px; border-radius: 2px; background: var(--accent); }
.pl-zeit::-webkit-slider-thumb, .pl-laut::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 14px; height: 14px; margin-top: -5px;
border-radius: 50%; background: #fff; border: 0;
box-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
.pl-zeit::-moz-range-thumb, .pl-laut::-moz-range-thumb {
width: 14px; height: 14px; border-radius: 50%; background: #fff; border: 0;
}
.pl-zeit:focus-visible, .pl-laut:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
/* Die Menüs gehen nach oben auf — nach unten wäre ausserhalb des Bildes. */
.pl-menue { position: relative; }
.pl-liste {
position: absolute; bottom: calc(100% + 8px); right: 0;
display: none; flex-direction: column; min-width: 180px;
padding: 6px; background: rgba(20, 20, 23, .96);
border: 1px solid var(--line); border-radius: var(--r-karte);
box-shadow: 0 12px 36px rgba(0, 0, 0, .6);
max-height: 50vh; overflow-y: auto;
}
.pl-menue.auf .pl-liste { display: flex; }
/* **Der Fuss ist sichtbar etwas anderes als die Liste darüber.**
Eine Einstellung zwischen Inhalten sieht aus wie ein Inhalt — der
Tonversatz stand als Eintrag zwischen den Sprachen und las sich wie eine.
Ein Strich, eine Überschrift, ein Regler: drei Mittel, und jedes einzelne
würde es schon sagen. */
.pl-fuss {
margin-top: 6px; padding: 8px 8px 6px;
border-top: 1px solid var(--line);
display: flex; flex-direction: column; gap: 6px;
}
.pl-fusstitel {
display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-snug);
font-size: 11px; font-weight: 600; letter-spacing: .04em;
text-transform: uppercase; color: var(--text-muted);
}
/* Der eingestellte Wert, nicht die Überschrift — deshalb hell und ohne Versalien. */
.pl-fusswert {
text-transform: none; letter-spacing: 0; font-weight: 500;
color: var(--text); white-space: nowrap;
}
/* Breit genug für „Ton 3 s früher" neben der Überschrift. */
.pl-menue .pl-liste { min-width: 230px; }
/* Breiter als der Lautstärkeregler — hier wird genau gezielt, nicht gewischt. */
.pl-versatz { width: 100%; }
/* Die Mitte markieren: ohne sie trifft man die Null nur durch Zufall. */
.pl-versatz::-webkit-slider-runnable-track {
background:
linear-gradient(rgba(255, 255, 255, .55), rgba(255, 255, 255, .55)) 50% / 2px 10px no-repeat,
rgba(255, 255, 255, .28);
}
/* ---------- Das Aussehen der Untertitel ---------- */
.pl-groesse { width: 100%; }
/* Beschriftung links, die Felder rechts — zwei Reihen untereinander, damit
„Schrift" und „Hintergrund" auf derselben Kante stehen. */
.pl-farbreihe {
display: flex; align-items: center; justify-content: space-between;
gap: var(--s-snug);
}
.pl-farben { display: flex; align-items: center; }
.pl-farbname { font-size: 12px; color: var(--text-muted); }
/* Die Felder selbst. Rand in beide Richtungen: ein weisses Feld braucht
einen dunklen Umriss, ein schwarzes einen hellen — ein halbdurchsichtiger
Rand leistet beides auf jedem Untergrund. */
.pl-farbe {
width: 20px; height: 20px; padding: 0;
border-radius: 50%; cursor: pointer;
background: var(--farbe);
border: 1px solid rgba(255, 255, 255, .45);
box-shadow: 0 0 0 1px rgba(0, 0, 0, .5);
}
.pl-farbe + .pl-farbe { margin-left: 6px; }
.pl-farbe:hover { transform: scale(1.12); }
.pl-farbe:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
/* Das gewählte Feld: ein Ring aussen herum, nicht ein Haken darin — ein
Haken wäre auf Weiss unsichtbar und auf Schwarz erst recht. */
.pl-farbe.an {
box-shadow: 0 0 0 1px rgba(0, 0, 0, .5), 0 0 0 3px var(--text);
}
/* „Ohne Hintergrund" ist keine Farbe. Das Karomuster sagt das; ein leeres
Feld sähe nach Weiss aus und wäre damit eine Lüge. */
.pl-farbe.kariert {
background-image:
linear-gradient(45deg, rgba(255, 255, 255, .55) 25%, transparent 25%),
linear-gradient(-45deg, rgba(255, 255, 255, .55) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, .55) 75%),
linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, .55) 75%);
background-size: 8px 8px;
background-position: 0 0, 0 4px, 4px -4px, -4px 0;
}
@media (prefers-reduced-motion: reduce) {
.pl-farbe:hover { transform: none; }
}
.pl-punkt {
display: flex; align-items: center; gap: 8px;
background: none; border: 0; color: var(--text);
padding: 9px 12px; border-radius: var(--r-control);
font-size: 14px; text-align: left; cursor: pointer; white-space: nowrap;
}
.pl-punkt:hover, .pl-punkt:focus-visible { background: var(--raised); }
.pl-punkt .icon { width: 16px; height: 16px; flex: 0 0 auto; }
.pl-punkt .icon svg { width: 16px; height: 16px; }
/* Ohne Haken bleibt der Platz stehen — sonst springt die Beschriftung. */
.pl-punkt:not(.an) { padding-left: 36px; }
.pl-meldung {
position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
z-index: 3; max-width: min(520px, calc(100vw - 48px));
padding: var(--s-regular) var(--s-section);
background: rgba(20, 20, 23, .96); color: var(--text);
border: 1px solid rgba(229, 72, 77, .45); border-radius: var(--r-karte);
text-align: center; line-height: 1.5; font-size: 15px;
}
/* Hinter dem Spieler soll nichts scrollen — aber nur im ganzen Bild. */
body.pl-offen { overflow: hidden; }
/* ---------- Die kleine Fassung ----------
*
* **Wie bei Plex und YouTube: der Film rutscht nach unten, statt zu enden.**
* Wer nebenbei Plakate geraderückt oder etwas sucht, soll ihn nicht verlieren.
*
* Es ist dieselbe Hülle und dasselbe Videoelement — nur anders eingerahmt.
* Ein zweiter Spieler unten hiesse, das Video aus dem Dokument zu nehmen und
* woanders wieder einzuhängen; dabei hört es auf zu spielen und puffert neu.
*/
.pl.pl-klein {
inset: auto 0 0 0;
height: 72px;
/* **Die Leiste fängt hinter der Seitenleiste an.**
*
* Gemessen: `.pl` liegt auf Stufe 150, `#chrome` auf gar keiner. Die
* Leiste spannte deshalb über die ganze Breite und legte sich über die
* unteren 72 Bildpunkte der Seitenleiste — genau dort, wo „Einstellungen"
* klebt. Und weil der Spieler bei jedem Wechsel der Adresse minimiert,
* stand sie zuverlässig im Weg, sobald man dorthin wollte.
*
* **Nicht über die Stufen gelöst, und das ist der Punkt.** `#chrome`
* über 150 zu heben hiesse, die Seitenleiste läge auch über dem grossen
* Spieler — es ist dieselbe Hülle. Dass diese Leiste Platz wegnimmt,
* statt sich darüberzulegen, steht weiter unten ohnehin schon für den
* Inhalt, die Hinweise und die Auswahlleiste; die Seitenleiste war die
* vergessene vierte. */
left: var(--sidebar-w);
/* **`place-items` muss ausdrücklich zurück.** Im ganzen Bild zentriert die
Hülle ihre Kinder; in der Leiste zog das die Beschriftung in die Mitte,
weit weg vom Vorschaubild, an dem sie hängt. */
display: block;
place-items: normal;
background: var(--chrome);
border-top: 1px solid var(--line);
box-shadow: 0 -8px 28px rgba(0, 0, 0, .45);
padding-bottom: env(safe-area-inset-bottom);
}
/* **Unter dem Fortschrittsstrich, nicht darüber.**
*
* Das Bild lag bündig am oberen Rand und schob sich über den Strich — auf der
* rechten Seite, wo noch nichts gefüllt war, verschwand er ganz darunter.
* Zwei Bildpunkte Abstand und eine Ebene darunter, dann läuft er durch. */
.pl.pl-klein .pl-video {
position: absolute; left: 0; top: 2px; z-index: 0;
width: 128px; height: 70px;
object-fit: cover; cursor: pointer;
}
.pl.pl-klein .pl-kopf,
.pl.pl-klein .pl-leiste,
.pl.pl-klein .pl-dreher,
.pl.pl-klein .pl-meldung { display: none; }
.pl-kleinleiste { display: none; }
.pl.pl-klein .pl-kleinleiste {
display: flex; align-items: center; gap: var(--s-snug);
width: 100%; height: 100%;
padding: 0 max(var(--s-regular), env(safe-area-inset-right)) 0 140px;
cursor: pointer;
}
/* Wie oben: der Text gibt nach, die Knöpfe nicht. */
.pl.pl-klein .pl-kleinleiste > .pl-rund { flex: 0 0 auto; }
.pl-kleintext { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pl-kleintitel {
font-size: 14px; font-weight: 600; color: var(--text);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pl-kleinstand {
font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums;
}
/* Der Fortschritt als dünner Strich ganz oben auf der Leiste. */
.pl-kleinbalken {
position: absolute; left: 0; right: 0; top: 0; height: 2px; z-index: 2;
background: rgba(255, 255, 255, .14);
}
.pl-kleinbalken div { height: 100%; background: var(--accent); transition: width .25s linear; }
/* **Platz für die Leiste.** Ohne das läge die letzte Kachelreihe darunter,
und niemand käme an den letzten Film. */
body.pl-leiste-da #content { padding-bottom: 88px; }
body:has(.pl.pl-klein) .hinweise { bottom: 92px; }
body:has(.pl.pl-klein) .auswahlleiste { bottom: 92px; }
@media (max-width: 640px) {
.pl.pl-klein .pl-video { width: 96px; height: 71px; }
.pl.pl-klein .pl-kleinleiste { padding-left: 106px; }
.pl-kleinstand { display: none; }
}
@media (prefers-reduced-motion: reduce) {
.pl-kleinbalken div { transition: none; }
}
@media (max-width: 640px) {
.pl-laut { display: none; }
.pl-rest { display: none; }
}
@media (prefers-reduced-motion: reduce) {
.pl-kopf, .pl-leiste { transition: none; }
}
/* ---------- Schmale Bildschirme ---------- */
@media (max-width: 860px) {
#app { flex-direction: column; }
#chrome {
position: static; width: auto; flex-direction: row; overflow-x: auto;
border-right: 0; border-bottom: 1px solid var(--line); padding: var(--s-snug);
}
#chrome button { border-left: 0; border-bottom: 3px solid transparent; white-space: nowrap; }
#chrome button.check { border-left-color: transparent; border-bottom-color: var(--accent); background: none; }
/* Hier ist die Seitenleiste eine Zeile oben; unten ist sie nicht im Weg,
und die Leiste darf die volle Breite nehmen. */
.pl.pl-klein { left: 0; }
.nav-gruppe { display: none; }
.nav-fuss { margin: 0; padding: 0; display: flex; }
#content { margin-left: 0; padding: var(--s-loose); }
/* Am Telefon gibt es keine Seitenleiste; die Navigation liegt als Zeile
im Fluss und nimmt oben einen Streifen weg, sobald man ganz oben steht.
Nach dem ersten Bildlauf ist sie weg und das Feld stimmt wieder. */
.detail-hintergrund { left: 0; }
.detail-kopf { flex-direction: column; gap: var(--s-loose); }
.detail-kopf .plakat { width: 160px; }
.detail-text h1 { font-size: 26px; }
#topbar .suchfeld { max-width: none; }
}
/* --- Ordnerauswahl ------------------------------------------------------
*
* Klappt im Dialog auf, statt einen zweiten darüberzulegen. Die Höhe ist
* begrenzt und scrollt: ein Verzeichnis mit zweihundert Unterordnern würde
* den Dialog sonst über den Bildschirmrand schieben. */
.ordnerfeld { display: flex; flex-direction: column; gap: var(--s-snug); }
.ordnerzeile { display: flex; gap: var(--s-snug); }
.ordnerzeile .feld { flex: 1; min-width: 0; }
.ordnerzeile .btn { flex: none; gap: 6px; }
.ordnerbaum {
border: 1px solid var(--line); border-radius: var(--r-control);
background: var(--canvas); overflow: hidden;
}
.ordnerkopf {
display: flex; align-items: center; gap: var(--s-snug);
padding: var(--s-snug); border-bottom: 1px solid var(--line);
}
.ordnerkopf .rund { width: 30px; height: 30px; flex: none; }
.ordnerkopf .rund svg { width: 16px; height: 16px; }
.ordnerkopf .rund:disabled { opacity: .35; }
/* Der Pfad scrollt waagerecht statt umzubrechen — ein tiefer Pfad soll die
Kopfzeile nicht dreizeilig machen. */
.ordnerpfad {
display: flex; align-items: center; gap: 2px;
overflow-x: auto; scrollbar-width: none; min-width: 0;
}
.ordnerpfad::-webkit-scrollbar { display: none; }
.ordnerpfad .pfadstueck {
background: none; border: 0; padding: 2px 5px; border-radius: 4px;
color: var(--text-dim); font-size: 12px; white-space: nowrap;
}
.ordnerpfad .pfadstueck:hover { background: var(--elevated); color: var(--text); }
.ordnerpfad .pfadstueck:not(:last-child)::after { content: '/'; margin-left: 5px; color: var(--text-faint); }
.ordnerpfad .pfadstueck.wurzel::after { content: none; }
.ordnerliste {
max-height: 220px; overflow-y: auto;
display: flex; flex-direction: column; padding: var(--s-tight);
}
.ordnerliste .winzig { padding: var(--s-regular); }
.ordnereintrag {
display: flex; align-items: center; gap: var(--s-snug);
background: none; border: 0; border-radius: 5px;
padding: 7px var(--s-snug); text-align: left;
color: var(--text-dim); font-size: 13px;
}
.ordnereintrag svg { width: 17px; height: 17px; color: var(--text-faint); }
.ordnereintrag:hover { background: var(--elevated); color: var(--text); }
.ordnereintrag:hover svg { color: var(--accent); }
.ordnerfuss {
padding: var(--s-snug); border-top: 1px solid var(--line);
display: flex; justify-content: flex-end;
}
/* Der Knopf trägt den ganzen Pfad — bei einem langen muss er kürzen dürfen,
sonst wächst der Dialog mit. */
.ordnerfuss .btn {
max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
display: block;
}
/* Protokollanzeige — Festbreitenschrift, begrenzte Höhe, eigener Bildlauf. */
.protokoll {
margin: var(--s-regular) 0 0;
padding: var(--s-regular);
background: var(--canvas);
border: 1px solid var(--line);
border-radius: var(--r-control);
font: 12px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
color: var(--text-dim);
max-height: 420px;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
}
/* Der Originaltitel unter dem übersetzten — leiser, aber lesbar. */
.originaltitel {
margin-top: 2px;
font-size: 15px;
color: var(--text-muted);
}
/* --- Fokus und Bewegung --------------------------------------------------
*
* **Ein sichtbarer Fokusring auf allem, was man bedienen kann.** Vorher
* färbte sich allenfalls ein Feldrahmen; mit der Tastatur war die Oberfläche
* praktisch unbedienbar, weil man nicht sah, wo man gerade ist. */
:where(button, a, input, select, textarea, [tabindex]):focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--r-knopf);
}
.kachel:focus-visible { outline: none; }
.kachel:focus-visible .plakat {
outline: 2px solid var(--accent);
outline-offset: 3px;
}
/* **Wer „Bewegung reduzieren" eingestellt hat, meint es.**
*
* Das gilt im ganzen Projekt bisher nirgends. Animationen ganz abzuschalten
* wäre falsch — ein Zustandswechsel ohne jede Rückmeldung wirkt wie ein
* Aussetzer. Auf einen Hauch zu kürzen behält die Rückmeldung und nimmt die
* Bewegung. */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .01ms !important;
animation-iteration-count: 1 !important;
transition-duration: .01ms !important;
scroll-behavior: auto !important;
}
.kachel:hover .plakat,
.kachel:focus-visible .plakat { transform: none; }
}
/* --- Hero ----------------------------------------------------------------
*
* Die Startseite begann bei einer Reihe aus 150 px breiten Plakaten; das
* grösste Element über dem Falz war 225 px hoch. Ein Bild über die volle
* Breite ist der grösste Einzelunterschied im Eindruck — und ein Layout-,
* kein Farbproblem. */
/* **Vier Stufen, serverweit eingestellt.**
*
* Eine Pixelzahl einzustellen hiesse, sie für jeden Bildschirm neu
* einzustellen — der Aufmacher misst sich an der Fensterhöhe. Vier Wörter
* sind das, was man dazu zu sagen hat, und `main.ts` setzt sie als Marke am
* `<html>`, sobald die Systemauskunft da ist.
*
* Die Mitte ist, was vorher fest verdrahtet war. Oben ist Schluss, bevor die
* erste Plakatreihe ganz unter den Falz rutscht: sie soll zeigen, dass unten
* noch etwas kommt. */
:root { --hero-hoehe: min(72vh, 640px); }
html[data-aufmacher="niedrig"] { --hero-hoehe: min(62vh, 560px); }
html[data-aufmacher="hoch"] { --hero-hoehe: min(82vh, 720px); }
html[data-aufmacher="sehrhoch"] { --hero-hoehe: min(92vh, 800px); }
.hero {
position: relative;
height: var(--hero-hoehe);
/* Randlos: der Inhaltsbereich hat seitlich Polster, das Bild soll es
nicht haben. Negative Ränder statt eines eigenen Containers. */
margin: calc(var(--s-page) * -1) calc(var(--s-page) * -1) var(--s-section);
overflow: hidden;
display: flex;
align-items: flex-end;
}
.hero-buehne { position: absolute; inset: 0; }
.hero-folie {
position: absolute; inset: 0;
background-size: cover;
background-position: center 20%;
opacity: 0;
transition: opacity .9s ease-in-out;
}
.hero-folie.an { opacity: 1; }
/* **Zwei Verläufe, nicht einer.** Der von unten lässt die erste Reihe sich
darüberschieben; der von links sorgt dafür, dass der Text trägt, auch wenn
das Bild dort hell ist. */
.hero-buehne::after {
content: '';
position: absolute; inset: 0;
background:
linear-gradient(to top, var(--canvas) 2%, rgba(11, 11, 13, .55) 45%, transparent 85%),
linear-gradient(to right, rgba(11, 11, 13, .88) 0%, rgba(11, 11, 13, .35) 45%, transparent 75%);
}
.hero-inhalt {
position: relative;
padding: var(--s-page);
max-width: 620px;
/* **Sonst drückt der Inhalt den Aufmacher auseinander.** Ein Flexkind
schrumpft von sich aus nicht unter seinen Inhalt; die Knopfzeile war
damit auf dem Telefon breiter als der Bildschirm, und `overflow: hidden`
schnitt die rechte Hälfte des Textes ab. */
min-width: 0;
/* **Volle Breite, nicht die des längsten Satzes.** Als blosses Flexkind war
der Block so breit wie sein Inhalt — bei einer kurzen Inhaltsangabe
schmaler als bei einer langen. Auf dem Telefon rückte „Mehr" dadurch beim
Umblättern um fünfzig Pixel. */
flex: 1 1 auto;
display: flex; flex-direction: column; gap: var(--s-regular);
}
.hero-schriftzug {
max-width: 380px; max-height: 120px;
object-fit: contain; object-position: left bottom;
}
.hero-titel {
margin: 0;
font-size: var(--t-hero); font-weight: 700; letter-spacing: -.03em;
}
.hero-meta { font-size: var(--t-normal); color: var(--text-dim); }
/* Drei Zeilen, dann Schluss — eine volle Inhaltsangabe gehört auf die
Detailseite, nicht in den Aufmacher. */
.hero-text {
margin: 0;
color: var(--text-dim);
display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
overflow: hidden;
}
.hero-knoepfe { display: flex; gap: var(--s-snug); margin-top: var(--s-tight); min-width: 0; }
.hero-knoepfe .btn { height: 40px; padding: 0 var(--s-section); gap: 6px; }
/* **Fester Platz für das, was vorne steht.**
„Abspielen", „Fortsetzen ab 1:23:45" und der Termin sind verschieden breit.
Ohne feste Breite wanderte „Mehr" bei jedem Umblättern mit — und das
Karussell blättert von allein. */
.hero-haupt { flex: 0 0 258px; display: flex; }
.hero-haupt > * { flex: 1; }
/* Einzeilig und knopfhoch: zwei Zeilen hoben die ganze Reihe an. */
.hero-bald {
height: 40px; padding: 0 var(--s-regular); gap: var(--s-snug);
font-size: 13px; white-space: nowrap; overflow: hidden; min-width: 0;
}
.hero-bald .icon { flex: none; }
.hero-bald span:not(.icon) { overflow: hidden; text-overflow: ellipsis; }
.hero-bald .icon svg { width: 18px; height: 18px; }
/* **Die Inhaltsangabe klappt auf, wenn man auf dem Titel stehenbleibt.**
*
* In Ruhe steht da, *was* läuft und *was man tun kann*: Schriftzug oder Name,
* Eckdaten — und die Knöpfe, immer. Worum es geht, steht nicht da und nimmt
* auch keinen Platz. Wer zwei Sekunden auf dem Titel stehenbleibt, bekommt
* es; wer vorbeifährt, nicht.
*
* **Die zwei Sekunden stehen nicht hier, sondern in `hero.ts`.** Eine
* `transition-delay` käme auch dann zum Zug, wenn der Zeiger nur quer über den
* Titel streicht — die Verzögerung soll aber eine Absicht abwarten und nicht
* eine Bewegung aussitzen. Sie überlebt dadurch auch „Bewegung reduzieren":
* die globale Regel oben flacht die Überblendung ab, nicht die Geduld.
*
* **Die Höhe kommt gemessen aus `hero.ts`.** Nach `auto` lässt sich nicht
* überblenden, und eine feste Obergrenze wäre bei einer einzeiligen Angabe zu
* drei Vierteln Leerlauf: die Bewegung stünde nach 100 ms und liefe weitere
* 240 ms unsichtbar weiter. Gemessen wird bei jedem Folienwechsel und bei
* jeder Fensteränderung — drei Zeilen sind bei 620 px Breite andere als bei
* 380.
*
* **Der Rasterabstand wandert mit.** `.hero-inhalt` setzt 12 px zwischen alle
* Kinder; eine Zeile ohne Höhe hätte trotzdem zwei davon um sich, und in Ruhe
* stünden 24 px zwischen Eckdaten und Knopfzeile statt 12. Der negative
* `margin-top` zieht sie ab und gibt sie im selben Takt zurück. Mit `padding`
* ginge das nicht — Polster lässt sich nicht wegkürzen.
*
* `hover: hover` wie bei den Pfeilen: wo es kein Überfahren gibt, gibt es auch
* kein Aufklappen — dort steht die Inhaltsangabe wie eh und je da. */
@media (hover: hover) {
.hero-text {
height: 0;
margin-top: calc(var(--s-regular) * -1);
transition: height .22s ease, margin-top .22s ease;
}
.hero-inhalt.aufgeklappt .hero-text {
height: var(--hero-texthoehe, 0px);
margin-top: 0;
transition: height .34s ease, margin-top .34s ease;
}
}
/* **Pfeile am Bildrand.** Senkrecht mittig, damit sie nicht über dem Text
liegen, und über allem anderen — sonst fängt der Textblock den Klick ab. */
.hero-pfeil {
position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
width: 44px; height: 44px; display: grid; place-items: center;
border: 0; border-radius: 50%;
background: rgba(11, 11, 13, .55); color: var(--text);
backdrop-filter: blur(6px);
opacity: .72; transition: opacity .22s ease, background .22s ease;
}
.hero-pfeil:hover { opacity: 1; background: rgba(11, 11, 13, .8); }
.hero-pfeil svg { width: 24px; height: 24px; }
.hero-pfeil-links { left: var(--s-regular); }
.hero-pfeil-rechts { right: var(--s-regular); }
/* **Erst beim Überfahren, und nur wo es ein Überfahren gibt.**
*
* Der Pfeil liegt über dem Bild und damit über allem, was dort steht. Bei
* einem einzeiligen Titel geht das gut; bei „Pirates of the Caribbean —
* Salazars Rache" bricht die Zeile um, die zweite rutscht nach unten, und
* der linke Pfeil sitzt auf dem ersten Buchstaben. Ständig sichtbar ist er
* dort ein Fleck, auf Zuruf ist er ein Knopf.
*
* `hover: hover` ist dabei die ganze Vorsicht: auf einem Tablett gibt es
* kein Überfahren, und ein Knopf, der nur beim Überfahren erscheint, wäre
* dort schlicht nicht erreichbar. Dort bleibt alles, wie es war.
*
* Mit der Tastatur erscheint er ebenfalls — `pointer-events: none` hält nur
* den Zeiger ab, nicht den Fokus. */
@media (hover: hover) {
.hero-pfeil { opacity: 0; pointer-events: none; }
/* Langsamer herein als hinaus: das Erscheinen soll man sehen, das
Verschwinden nicht abwarten müssen. */
.hero:hover .hero-pfeil,
.hero:focus-within .hero-pfeil {
opacity: .72; pointer-events: auto;
transition: opacity .34s ease, background .22s ease;
}
.hero:hover .hero-pfeil:hover { opacity: 1; background: rgba(11, 11, 13, .8); }
.hero-pfeil:focus-visible { opacity: 1; pointer-events: auto; }
}
.hero-punkte {
position: absolute; right: var(--s-page); bottom: calc(var(--s-page) - 12px);
display: flex; gap: 6px;
}
/* **Der Strich ist drei Pixel hoch, die Fläche darf es nicht sein.**
Gemessen: von drei Klicks auf denselben Punkt traf nur der genau auf der
Mittellinie; fünf Pixel darüber oder darunter geschah nichts. Der Knopf
trägt deshalb jetzt 27 Pixel Höhe, von denen man 3 sieht. */
.hero-punkt {
width: 26px; height: 27px; padding: 0; border: 0;
background: none;
display: flex; align-items: center;
}
.hero-punkt::before {
content: ''; flex: 1; height: 3px; border-radius: 2px;
background: rgba(255, 255, 255, .28);
transition: background .2s ease;
}
.hero-punkt.an::before { background: var(--text); }
.hero-punkt:hover::before { background: rgba(255, 255, 255, .6); }
@media (max-width: 860px) {
.hero {
/* Am Telefon eine Stufe tiefer als gewählt: dort ist der Aufmacher im
Verhältnis zum Bildschirm ohnehin höher, und über 60 vh bleibt von der
ersten Reihe nichts mehr übrig. */
height: min(52vh, 420px);
margin: calc(var(--s-loose) * -1) calc(var(--s-loose) * -1) var(--s-section);
}
.hero-inhalt { padding: var(--s-loose); max-width: none; }
.hero-punkte { right: var(--s-loose); bottom: calc(var(--s-loose) - 12px); }
/* Ein Knopf über dem Bild nähme hier ein Drittel der Breite. Gewischt
wird stattdessen — siehe `wischenEinrichten` in hero.ts. */
.hero-pfeil { display: none; }
/* **Hier füllt der vordere Platz die Zeile.** Eine feste Breite plus „Mehr"
wäre breiter als ein Telefon; so steht „Mehr" am rechten Rand und damit
ebenso fest wie am Schreibtisch. */
.hero-haupt { flex: 1 1 auto; min-width: 0; }
.hero-bald { font-size: 12px; padding: 0 var(--s-snug); }
}
/* --- Profil --------------------------------------------------------------
*
* **Die eigene Seite.** Ein Klick auf das eigene Bild meldete ab — ohne
* Rückfrage und ohne Weg zurück. Hier steht stattdessen, wem das Konto
* gehört, und Abmelden ist ein Knopf unter allem anderen. */
.profil { max-width: 720px; }
.profil-kopf {
display: flex; align-items: center; gap: var(--s-section);
margin-bottom: var(--s-section);
}
.profil-bild {
flex: none; width: 96px; height: 96px; border-radius: 50%;
background: var(--accent); color: #fff; overflow: hidden;
display: grid; place-items: center; font-size: 36px; font-weight: 600;
}
.profil-bild img { width: 100%; height: 100%; object-fit: cover; }
.profil-text { display: flex; flex-direction: column; gap: var(--s-snug); min-width: 0; }
.profil-text h1 { margin: 0; font-size: 26px; font-weight: 700; letter-spacing: -.02em; }
.profil h2 {
font-size: 15px; font-weight: 600; color: var(--text-dim);
margin: 0 0 var(--s-regular);
}
/* Abgesetzt, weil es die einzige Handlung hier ist, die etwas beendet. */
.profil-abmelden {
margin-top: var(--s-section); padding-top: var(--s-section);
border-top: 1px solid var(--line);
}
@media (max-width: 860px) {
.profil-kopf { flex-direction: column; align-items: flex-start; gap: var(--s-loose); }
}
/* Zahlen im Profil. Drei Kacheln, eine Wochenkurve, zwei Listen — alles von
Hand gezeichnet. Für zwölf Balken eine Diagrammbibliothek zu laden hiesse,
dreihundert Kilobyte für etwas mitzuschleppen, das aus Rechtecken besteht. */
.profil-zahlen { margin-bottom: var(--s-section); }
.profil h3 { font-size: 13px; font-weight: 600; color: var(--text-dim); margin: 0 0 var(--s-snug); }
.zahlenreihe {
display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: var(--s-regular); margin-bottom: var(--s-section);
}
.zahlkachel {
display: flex; flex-direction: column; gap: 2px;
padding: var(--s-loose); border-radius: var(--r-karte); background: var(--elevated);
}
.zahlkachel strong { font-size: 22px; font-weight: 700; letter-spacing: -.02em; }
.zahlkachel > span { font-size: var(--t-normal); color: var(--text-dim); }
.zahlkachel .winzig { color: var(--text-faint); }
.wochen {
display: flex; align-items: flex-end; gap: 6px;
margin-bottom: var(--s-section);
}
/* Hundert Pixel für den höchsten Balken, zwanzig für die Beschriftung — die
Höhe steht in `profil.ts` und kommt in Pixeln, nicht in Prozent. */
.woche { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; }
.woche-balken {
flex: none; border-radius: 3px 3px 0 0;
background: var(--accent); min-height: 2px;
}
/* Eine Woche ohne Sehzeit bleibt sichtbar — sonst sähe die Kurve aus, als
fehlten dort Daten, statt dass nichts lief. */
.woche-balken.woche-leer { background: var(--line); }
.woche-marke {
flex: none; font-size: 10px; color: var(--text-faint); text-align: center;
padding-top: 4px; font-variant-numeric: tabular-nums;
}
.genres { display: flex; flex-direction: column; gap: var(--s-snug); margin-bottom: var(--s-section); }
.genre { display: flex; align-items: center; gap: var(--s-regular); font-size: var(--t-normal); }
.genre-name { flex: 0 0 110px; color: var(--text-dim); }
.genre-balken { flex: 1; height: 8px; border-radius: 4px; background: var(--elevated); overflow: hidden; }
.genre-balken i { display: block; height: 100%; background: var(--accent); border-radius: 4px; }
.genre-wert { flex: none; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.zuletzt { display: flex; flex-direction: column; gap: 2px; }
.zuletzt-zeile {
display: flex; align-items: center; gap: var(--s-regular);
background: none; border: 0; text-align: left; color: var(--text);
padding: var(--s-snug); border-radius: var(--r-control);
}
.zuletzt-zeile:hover { background: var(--elevated); }
.zuletzt-zeile img, .zuletzt-zeile .platzhalter {
flex: none; width: 32px; height: 48px; border-radius: 4px;
object-fit: cover; background: var(--placeholder);
}
.zuletzt-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.zuletzt-text strong { font-size: var(--t-normal); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* --- Verwaltung ----------------------------------------------------------
*
* **Eine zweite Leiste, keine zweite Seite.** Die Verwaltung war elf
* Abschnitte in einer Spalte; wer das Protokoll sehen wollte, scrollte an
* allem anderen vorbei. Die Leiste sieht aus wie die Hauptleiste daneben —
* derselbe Balken links, dieselbe Farbe —, damit sie als Navigation gelesen
* wird und nicht als Knopfreihe. */
.verwaltung { display: flex; align-items: flex-start; gap: var(--s-section); }
.verw-leiste {
position: sticky; top: calc(var(--topbar-h) + var(--s-section));
flex: none; width: 196px;
display: flex; flex-direction: column; gap: 2px;
}
/* Die Beschriftung ist hier die halbe Aussage: „Verwaltung" über den unteren
Einträgen sagt ohne weiteren Satz, dass diese dem Server gehören und nicht
einem selbst. */
.verw-gruppe {
font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
color: var(--text-faint); padding: var(--s-loose) var(--s-regular) var(--s-snug);
}
.verw-leiste > .verw-gruppe:first-child { padding-top: 0; }
.verw-eintrag {
display: flex; align-items: center; gap: var(--s-regular);
background: none; border: 0; text-align: left;
padding: 9px var(--s-regular); color: var(--text-dim);
border-left: 3px solid transparent; border-radius: 0 var(--r-control) var(--r-control) 0;
font-size: var(--t-normal);
}
/* Dieselbe Zahl wie in der Hauptleiste, nur in der anderen Leiste: rechts
aussen, damit sie nicht am Text klebt, und tabellarisch, damit sie beim
Wechsel von 9 auf 10 nicht springt. */
.verw-eintrag .zahl {
margin-left: auto; flex: none;
font-size: 11px; font-variant-numeric: tabular-nums; color: var(--text-faint);
}
.verw-eintrag.check .zahl { color: var(--accent); }
.verw-eintrag .icon { flex: none; color: var(--text-muted); }
.verw-eintrag:hover { color: var(--text); background: rgba(255, 255, 255, .04); }
.verw-eintrag:hover .icon { color: var(--text-dim); }
.verw-eintrag.check {
color: var(--accent); border-left-color: var(--accent);
background: linear-gradient(90deg, var(--accent-soft), transparent 70%);
}
.verw-eintrag.check .icon { color: var(--accent); }
.verw-buehne { flex: 1; min-width: 0; }
@media (max-width: 860px) {
/* Wie die Hauptleiste: aus der Spalte wird ein Streifen zum Schieben. */
.verwaltung { flex-direction: column; gap: var(--s-loose); }
.verw-leiste {
position: static; width: auto;
flex-direction: row; overflow-x: auto; gap: var(--s-tight);
scrollbar-width: none; padding-bottom: var(--s-tight);
}
.verw-leiste::-webkit-scrollbar { display: none; }
.verw-eintrag {
flex: none; white-space: nowrap;
border-left: 0; border-bottom: 2px solid transparent; border-radius: var(--r-control);
}
.verw-eintrag.check { border-bottom-color: var(--accent); background: var(--accent-soft); }
.verw-buehne { width: 100%; }
/* Im Streifen sind die Überschriften Trennzeichen, keine Zeilen. */
.verw-gruppe { padding: 0 var(--s-snug); align-self: center; white-space: nowrap; }
}
/* --- Bildzuschnitt --------------------------------------------------------
*
* **Weil ein Profilbild rund ist.** Was hochgeladen wird, ist selten
* quadratisch. Den Ausschnitt dem Browser zu überlassen hiesse: mittig, und
* das trifft bei einem Porträt den Hals. */
.zuschnitt-dlg .dlg-koerper { display: flex; flex-direction: column; gap: var(--s-loose); }
.zuschnitt-buehne { display: flex; justify-content: center; }
.zuschnitt-feld {
max-width: 100%;
border-radius: var(--r-karte); background: var(--placeholder);
cursor: grab; touch-action: none;
box-shadow: 0 0 0 1px var(--line);
}
/* Rund nur, wo es rund dasteht — beim Profilbild. */
.zuschnitt-feld.zuschnitt-rund { border-radius: 50%; }
.zuschnitt-feld:active { cursor: grabbing; }
/* --- Kontextmenü ---------------------------------------------------------
*
* Gesehen, Favorit und Weglegen gab es nur auf der Detailseite. In einer
* Übersicht mit zwanzig Titeln hiess „die hab ich alle gesehen" vierzig
* Seitenwechsel. */
.kontextmenue {
position: fixed; z-index: 100;
min-width: 220px; padding: var(--s-tight);
background: var(--elevated);
border: 1px solid var(--line); border-radius: var(--r-flaeche);
box-shadow: var(--schatten-dialog);
display: flex; flex-direction: column;
}
.kontextpunkt {
display: flex; align-items: center; gap: var(--s-snug);
background: none; border: 0; border-radius: var(--r-knopf);
padding: 8px var(--s-regular); text-align: left;
color: var(--text-dim); font-size: var(--t-normal);
}
.kontextpunkt:hover { background: var(--raised); color: var(--text); }
.kontextpunkt svg { width: 17px; height: 17px; }
.kontextpunkt.gefahr { color: var(--destructive); }
/* --- Bildauswahl ---------------------------------------------------------- */
.dlg-breit { width: min(860px, 100vw - 48px); }
/* ---------- Ein Vorschlag im Zuordnungsdialog ----------
*
* Mit Plakat: „Dark" von 2017 und „Dark Shadows" von 1966 sind an einer
* Textzeile kaum zu unterscheiden, am Bild sofort.
*/
.vorschlag {
display: flex; gap: var(--s-regular); align-items: flex-start; width: 100%;
background: var(--raised); border: 1px solid transparent;
border-radius: var(--r-karte); padding: var(--s-regular); text-align: left;
}
.vorschlag:hover { background: var(--elevated); border-color: var(--line); }
.vorschlag:disabled { opacity: .4; }
/* Die laufende Zeile bleibt hell, während die anderen zurücktreten — sonst
* sähe genau die, auf die man geklickt hat, am totesten aus. */
.vorschlag.laeuft { opacity: 1; border-color: var(--accent); background: var(--elevated); }
.vorschlag-bild {
width: 54px; aspect-ratio: 2 / 3; flex: none;
border-radius: var(--r-knopf); overflow: hidden; background: var(--placeholder);
}
.vorschlag-bild img { width: 100%; height: 100%; object-fit: cover; opacity: 0; }
.vorschlag-bild.geladen img { opacity: 1; transition: opacity .18s ease; }
.vorschlag-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.vorschlag-text .hinweis { margin: 0; }
.vorschlag-lauf {
display: flex; align-items: center; gap: var(--s-snug); flex: none;
color: var(--accent); font-size: var(--t-klein); font-weight: 600;
align-self: center;
}
/* Die Herkunft im Zuordnungsdialog — der Pfad bricht um statt abzuschneiden,
* weil ausgerechnet das Ende die Antwort trägt. */
.quelle {
display: flex; flex-direction: column; gap: 2px;
padding: var(--s-snug) var(--s-regular);
background: var(--raised); border-radius: var(--r-knopf);
border-left: 2px solid var(--accent);
}
.quelle-pfad {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: var(--t-klein); color: var(--text); word-break: break-all;
}
/* Ein aufklappbarer Block je Grund — zugeklappt sieht man die Verteilung,
* aufgeklappt die Pfade. Dreitausend Zeilen am Stück liest niemand. */
.grundblock { margin-top: var(--s-snug); }
.grundblock summary {
cursor: pointer; padding: 6px 0; color: var(--text-dim); font-size: var(--t-normal);
}
.grundblock summary:hover { color: var(--text); }
.grundblock summary strong { color: var(--accent); margin-right: 6px; }
.grundblock .protokoll { max-height: 220px; margin-top: var(--s-snug); }
/* Der Dateiname über einer Aktionsreihe — nur wenn es mehrere gibt. */
.dateiname.fassung {
font-family: inherit; color: var(--text-dim); font-weight: 600;
}
.dateiname {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
color: var(--text-faint); margin-top: var(--s-snug);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Der Fühler, der das Nachladen auslöst — unsichtbar, aber nicht null hoch:
* ein Element ohne Höhe kreuzt die Sichtgrenze nie zuverlässig. */
.nachlader { height: 1px; }
/* Der dritte Knopf im Dialogfuss steht links aussen — er gehört weder zu
* Abbrechen noch zu Bestätigen. */
.dlg-weiterer { margin-right: auto; }
/* Das Band an einer Kachel, die noch nicht freigegeben ist. Unten links,
* damit es weder dem Haken oben rechts noch dem Fortschrittsbalken am
* unteren Rand in die Quere kommt. */
.wartet {
position: absolute; left: 0; bottom: 0;
padding: 3px 8px; border-top-right-radius: var(--r-knopf);
background: color-mix(in srgb, var(--accent) 88%, black);
color: #fff; font-size: 11px; font-weight: 700; letter-spacing: .02em;
}
.bildblock { display: flex; flex-direction: column; gap: var(--s-snug); }
.bildblock h3 { margin: 0; font-size: var(--t-gross); font-weight: 600; flex: 1; }
/* ---------- Bildwahl: ein Reiter je Typ ----------
*
* Vorher standen Plakat, Hintergrund und Schriftzug untereinander: der
* Dialogkörper scrollte, und jedes Raster darin scrollte noch einmal. Wo das
* Rad landet, hing davon ab, über welcher Fläche der Zeiger gerade stand.
* Jetzt ist ein Typ sichtbar, und genau eine Fläche scrollt.
*/
.bildreiter {
display: flex; gap: var(--s-tight);
border-bottom: 1px solid var(--line);
margin: calc(var(--s-loose) * -1) 0 0;
position: sticky; top: 0; z-index: 2; background: var(--surface);
}
.bildreiter-knopf {
display: inline-flex; align-items: baseline; gap: 6px;
background: none; border: 0; border-bottom: 2px solid transparent;
padding: 10px var(--s-regular); margin-bottom: -1px;
color: var(--text-muted); font-size: var(--t-normal); font-weight: 600;
}
.bildreiter-knopf:hover { color: var(--text); }
.bildreiter-knopf.an { color: var(--text); border-bottom-color: var(--accent); }
.bildreiter-knopf .winzig { color: var(--text-faint); }
/* **Die eine scrollende Fläche.** Der Dialogkörper selbst wächst nicht mehr
* mit, sonst wären es wieder zwei. */
.bildbuehne { display: flex; flex-direction: column; min-height: 220px; }
.bildgitter {
display: grid; gap: var(--s-snug);
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
padding: 2px;
}
.bildwahl {
position: relative; padding: 0; border: 2px solid transparent;
border-radius: var(--r-karte); overflow: hidden;
background: var(--placeholder);
aspect-ratio: var(--verhaeltnis, 2 / 3);
}
.bildwahl img { width: 100%; height: 100%; object-fit: contain; display: block; }
.bildwahl:hover { border-color: var(--accent); }
/* Das gesetzte Bild ist markiert und nicht anklickbar — es ist schon gewählt. */
.bildwahl.an { border-color: var(--accent); opacity: 1; }
.bildwahl:disabled { cursor: default; }
.bildwahl-marke {
position: absolute; right: 4px; top: 4px;
display: grid; place-items: center;
width: 22px; height: 22px; border-radius: 50%;
background: var(--accent); color: #fff;
}
.bildwahl-marke svg { width: 14px; height: 14px; }
/* Prüfliste — mit Miniplakat, damit erkennbar ist, worum es geht. */
.pruefzeile .pruef-bild {
width: 40px; height: 60px; flex: none;
border-radius: 4px; overflow: hidden;
background: var(--placeholder);
}
.pruefzeile .pruef-bild img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pruefzeile .knopfreihe { align-items: center; }
/* Der Zeitpunktwähler — der Browser bringt Kalender und Uhr mit, aber nicht
die Farben. Ohne das eigene Farbschema wäre das Symbol unsichtbar dunkel. */
input[type="datetime-local"].feld {
color-scheme: dark;
min-height: 38px;
}
input[type="datetime-local"].feld::-webkit-calendar-picker-indicator {
filter: invert(1) opacity(.55);
cursor: pointer;
}
input[type="datetime-local"].feld::-webkit-calendar-picker-indicator:hover {
filter: invert(1) opacity(.9);
}
/* Die Mehrfachauswahl in der Prüfliste. */
.pruefzeile input[type="checkbox"] {
width: 17px; height: 17px; flex: none;
accent-color: var(--accent);
}
.reihe-kopf .knopfreihe .bleiben {
display: flex; align-items: center; gap: 6px;
color: var(--text-dim); font-size: var(--t-normal);
}
.reihe-kopf .knopfreihe .bleiben input { accent-color: var(--accent); width: 16px; height: 16px; }
/* Der sichtbare Griff zum Kontextmenü auf einer Kachel.
*
* Rechtsklick findet nur, wer ihn vermutet, und auf dem Telefon gibt es ihn
* gar nicht. Beim Überfahren und bei Tastaturfokus erscheinen drei Punkte. */
/* **Unten rechts, nicht oben rechts.**
*
* Oben rechts sitzt der Gesehen-Haken, und beide übereinander waren genau das,
* was auf dem Plakat wie ein Fehler aussah. Jetzt hat jede Ecke eine Aufgabe:
* oben links die Auswahl, oben rechts der Stand, unten links Abspielen, unten
* rechts das Menü. */
.kachel-mehr {
position: absolute; right: 6px; bottom: 6px; z-index: 4;
display: grid; place-items: center;
width: 28px; height: 28px; border-radius: 50%;
background: rgba(10, 10, 12, .78);
backdrop-filter: blur(6px);
color: var(--text);
opacity: 0; transition: opacity .15s ease;
cursor: pointer;
}
.kachel-mehr svg { width: 16px; height: 16px; }
.kachel:hover .kachel-mehr,
.kachel:focus-within .kachel-mehr,
.kachel-mehr:focus-visible { opacity: 1; }
/* Auf Geräten ohne Mauszeiger steht er dauerhaft da — sonst käme man nie
heran. */
@media (hover: none) {
.kachel-mehr { opacity: .85; }
}
/* ---------- Kurze Meldungen am unteren Rand ----------
*
* **Für alles, was keinen Ort auf der Seite hat.** Eine Aktion aus einem
* Kontextmenü wirkt, nachdem das Menü schon zu ist — scheitert sie, gäbe es
* sonst nirgends Platz dafür. Vorher verschwand so ein Fehler spurlos.
*
* Über allem ausser den Dialogen (`z-index: 200`): eine Meldung, die hinter
* einem Dialog aufgeht, hat niemand gesehen. Dialoge liegen in der obersten
* Ebene des Browsers und bleiben dennoch darüber — deshalb erscheinen
* Meldungen aus einem Dialog weiterhin in seiner eigenen Fehlerzeile. */
.hinweise {
position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
z-index: 200; display: flex; flex-direction: column; gap: 8px;
align-items: center; pointer-events: none; max-width: min(520px, calc(100vw - 32px));
}
.hinweiszeile {
background: var(--raised); color: var(--text);
border: 1px solid var(--line); border-radius: var(--r-control);
padding: 10px 16px; font-size: 14px; line-height: 1.4;
box-shadow: 0 8px 24px rgba(0, 0, 0, .45);
animation: hinweis-auf .18s ease-out;
}
.hinweiszeile.fehler {
background: #3A1B1E; border-color: rgba(229, 72, 77, .45); color: #FFB3B5;
}
.hinweiszeile.geht { opacity: 0; transform: translateY(6px); transition: all .2s ease-in; }
@keyframes hinweis-auf { from { opacity: 0; transform: translateY(8px); } }
@media (prefers-reduced-motion: reduce) {
.hinweiszeile { animation: none; }
.hinweiszeile.geht { transition: none; }
}
/* ---------- Mehrfachauswahl in der Mediathek ----------
*
* **Oben links, gegenüber vom Gesehen-Haken.** Jede Ecke des Plakats hat eine
* Aufgabe: oben links die Auswahl, oben rechts der Stand, unten links
* Abspielen, unten rechts das Menü. Zwei Dinge in derselben Ecke sahen auf
* dem Plakat wie ein Fehler aus.
*
* **Kein Modus, den man erst einschaltet.** Das Kästchen erscheint beim
* Überfahren — so wie die drei Punkte. Wer nichts damit zu tun hat, sieht es
* nie; wer etwas auswählen will, findet es dort, wo er ohnehin hinsieht. */
.wahlmarke {
position: absolute; top: 8px; left: 8px; z-index: 5;
width: 26px; height: 26px; border-radius: 50%;
display: grid; place-items: center;
background: rgba(10, 10, 12, .62);
border: 2px solid rgba(255, 255, 255, .72);
backdrop-filter: blur(4px);
cursor: pointer;
opacity: 0;
transition: opacity .15s ease, background .15s ease-out, border-color .15s ease-out;
}
.wahlmarke svg { width: 15px; height: 15px; color: #fff; opacity: 0; transition: opacity .15s; }
/* Beim Überfahren, bei Tastaturfokus — und durchgehend, solange überhaupt
etwas gewählt ist. Sonst müsste man für jede weitere Kachel erst wieder
das richtige Eck treffen. */
.kachel:hover .wahlmarke,
.kachel:focus-within .wahlmarke,
.raster.waehlt .wahlmarke,
.kachel.gewaehlt .wahlmarke { opacity: 1; }
.wahlmarke:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.kachel.gewaehlt .wahlmarke {
background: var(--accent); border-color: var(--accent);
}
.kachel.gewaehlt .wahlmarke svg { opacity: 1; }
/* **Solange etwas gewählt ist, führt kein Klick mehr woandershin.** Der
Abspielknopf und der Griff blieben sonst stehen und täten etwas anderes als
das, was der Klick daneben tut. */
.raster.waehlt .schweber,
.raster.waehlt .kachel-mehr { display: none; }
.raster.waehlt .kachel { cursor: pointer; }
.raster.waehlt .kachel:hover .plakat { transform: scale(1.02); }
/* Das Gewählte deutlich abheben — Rahmen und leichtes Abdunkeln, damit es
auch ohne Farbwahrnehmung zu erkennen ist. */
.kachel.gewaehlt .plakat {
outline: 3px solid var(--accent); outline-offset: -3px;
}
.kachel.gewaehlt .plakat img { filter: brightness(.72); }
/* **Die Leiste klebt unten**, über dem Inhalt, damit sie beim Scrollen durch
vierhundert Kacheln nicht verschwindet. */
.auswahlleiste {
position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%);
z-index: 90; display: flex; align-items: center; gap: var(--s-regular);
flex-wrap: wrap; justify-content: center;
max-width: min(760px, calc(100vw - 24px));
padding: 10px 16px;
background: var(--elevated); border: 1px solid var(--line);
border-radius: var(--r-karte);
box-shadow: 0 10px 32px rgba(0, 0, 0, .55);
}
.auswahlzahl { font-size: 14px; font-weight: 600; white-space: nowrap; }
/* Das Kreuz steht abgesetzt — es tut nichts mit den Titeln, es räumt auf. */
.auswahl-zu {
display: grid; place-items: center; flex: 0 0 auto;
width: 34px; height: 34px; border-radius: 50%;
border: 0; background: transparent; color: var(--text-dim);
cursor: pointer; transition: background .15s, color .15s;
margin-left: 4px;
}
.auswahl-zu:hover, .auswahl-zu:focus-visible { background: var(--raised); color: var(--text); }
.auswahl-zu svg { width: 17px; height: 17px; }
/* **Während des Streichens markiert der Browser nichts.** Sonst zieht er die
Beschriftungen blau an, als wolle man Text kopieren. */
body.keine-auswahl { user-select: none; -webkit-user-select: none; }
.raster.zieht .kachel { cursor: crosshair; }
/* Kein Ziehgut aus Plakaten — sonst hängt eines am Mauszeiger. */
.plakat img { -webkit-user-drag: none; user-drag: none; }
@media (max-width: 640px) {
.auswahlleiste { left: 12px; right: 12px; transform: none; max-width: none; }
.auswahlleiste .knopfreihe { width: 100%; }
.auswahlleiste .btn { flex: 1 1 auto; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
.wahlmarke, .wahlmarke svg { transition: none; }
}
/* **Die Meldung weicht der Auswahlleiste aus.** Beide wollen den unteren
Rand; die Meldung lag zwar davor, verdeckte aber genau die Knöpfe, die man
als Nächstes drückt. */
body:has(.auswahlleiste:not(.hidden)) .hinweise { bottom: 104px; }
@media (max-width: 640px) {
body:has(.auswahlleiste:not(.hidden)) .hinweise { bottom: 148px; }
}
/* Die Leiste kommt mit dem ersten Haken — ohne Sprung. */
.auswahlleiste:not(.hidden) { animation: leiste-auf .18s ease-out; }
@keyframes leiste-auf { from { opacity: 0; transform: translate(-50%, 8px); } }
@media (max-width: 640px) {
@keyframes leiste-auf { from { opacity: 0; transform: translateY(8px); } }
}
@media (prefers-reduced-motion: reduce) {
.auswahlleiste:not(.hidden) { animation: none; }
}
/* ---------- Bearbeiten: alles zu einem Titel an einer Stelle ---------- */
.bearb-kopf {
display: flex; gap: var(--s-regular); align-items: center;
padding-bottom: var(--s-regular); border-bottom: 1px solid var(--line);
margin-bottom: var(--s-section);
}
.bearb-plakat {
width: 72px; aspect-ratio: 2 / 3; flex: 0 0 auto;
border-radius: var(--r-control); overflow: hidden; background: var(--placeholder);
}
.bearb-plakat img { width: 100%; height: 100%; object-fit: cover; }
.bearb-kopftext { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.bearb-kopftext strong { font-size: 17px; }
.bearb-abschnitt { margin-bottom: var(--s-section); }
.bearb-abschnitt:last-child { margin-bottom: 0; }
.bearb-abschnitt h3 {
font-size: 14px; font-weight: 600; margin: 0 0 4px;
text-transform: uppercase; letter-spacing: .04em; color: var(--text-dim);
}
.bearb-abschnitt .hinweis { margin: 0 0 var(--s-snug); }
/* Das Eingabefeld nimmt den Platz, die Knöpfe nur, was sie brauchen. */
.bearb-abschnitt .knopfreihe .feld { flex: 1 1 220px; min-width: 0; }
/* **Sagt, dass hier jemand von Hand eingegriffen hat.** Ohne das sieht ein
gesetzter Wert aus wie ein geholter, und niemand weiss, warum das
Auffrischen ihn in Ruhe lässt. */
.bearb-merker {
display: inline-flex; align-items: center; gap: 5px;
margin-top: var(--s-snug);
font-size: 12px; color: var(--accent);
}
.bearb-merker svg { width: 14px; height: 14px; }
@media (max-width: 560px) {
.bearb-abschnitt .knopfreihe .feld { flex: 1 1 100%; }
}
/* Die Bühne hinter den Reitern im Bearbeiten-Dialog. */
.bearb-buehne { margin-top: var(--s-regular); }
/* Die Reiterleiste sitzt hier enger als über einer ganzen Seite. */
.dlg .reiter { margin: 0; border-bottom: 1px solid var(--line); }
/* ---------- Ohne Zuordnung: eine Liste zum Durchgehen ---------- */
.ohnezuordnung {
display: flex; flex-direction: column; gap: 2px;
margin-top: var(--s-snug);
max-height: 320px; overflow-y: auto;
}
.ohnezuordnung-zeile {
display: flex; align-items: baseline; gap: var(--s-regular);
width: 100%; text-align: left; cursor: pointer;
background: none; border: 0; color: var(--text);
padding: 7px var(--s-snug); border-radius: var(--r-control);
}
.ohnezuordnung-zeile:hover,
.ohnezuordnung-zeile:focus-visible { background: var(--raised); }
.oz-titel { font-size: 14px; flex: 0 0 auto; max-width: 45%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Der Dateiname steht daneben — daran erkennt man, was gemeint ist. */
.oz-dazu {
font-size: 12px; color: var(--text-muted); flex: 1 1 auto;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Jede Datei eines mehrfach belegten Titels einzeln, mit einem Weg heraus. */
.fassungstitel { padding: 4px 0; }
.fassungsdatei {
display: flex; align-items: center; gap: var(--s-snug);
padding: 3px var(--s-snug) 3px 24px;
}
.fassungsdatei .oz-dazu { flex: 1 1 auto; }
.btn.klein { padding: 4px 10px; font-size: 12px; flex: 0 0 auto; }
/* Der Ordner in den Medienangaben — schmaler gesetzt, er ist ein Pfad. */
.medientabelle dd.pfad {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px; color: var(--text-dim);
overflow-wrap: anywhere;
}
/* Die Überschrift der zweiten Gruppe in der Prüfliste. */
.pruef-gruppe {
display: flex; align-items: center; gap: var(--s-regular);
flex-wrap: wrap;
padding: var(--s-regular) var(--s-snug) var(--s-snug);
border-top: 1px solid var(--line); margin-top: var(--s-snug);
}
.pruef-gruppe strong { font-size: 14px; }
.pruef-gruppe .winzig { flex: 1 1 240px; }
/* Steht dort, wo sonst Abspielen stünde — kein Knopf, der nichts tut. */
.verfuegbar-ab {
display: inline-flex; align-items: center; gap: var(--s-regular);
padding: 10px 18px; border-radius: var(--r-control);
background: var(--raised); border: 1px solid var(--line);
}
.verfuegbar-ab .icon svg { width: 22px; height: 22px; color: var(--accent); }
.verfuegbar-text { display: flex; flex-direction: column; line-height: 1.3; }
.verfuegbar-text strong { font-size: 14px; }
.verfuegbar-text span { font-size: 13px; color: var(--text-dim); }