„Scratch disk" wörtlich zu übersetzen ist derselbe Fehler wie „Wachhund"
für Watchdog und „Keks" für Cookie. Und „Marke" ist kein Fachbegriff für
das, was gemeint ist: Jellyfin nennt diese Abschnitte Media Segments, und
das ist auch das Wort, in dem sich darüber reden lässt.
Kratzplatz* → Transcode* (was es ist, steht in FLARO_TRANSCODE)
Marke, Marken → Segment, Segmente
Markenerkennung → Segmenterkennung
Markenart/-suche → Segmentart/-suche
Tonabdruck → Fingerprint (Chromaprint nennt es so)
Tonabgleich → Fingerprintabgleich
Schlüsselbild → Keyframe
Bildabtaster → Frameabtaster
Gewöhnliches Deutsch bleibt gewöhnliches Deutsch: Wartung, Freigabe,
Meldung, Abspann, Vorspann. Das war nie das Problem.
**Was sich nicht ändert, und zwar mit Absicht.** Die drei
Einstellungsschlüssel `wartung.kratzplatz.paket.*` bleiben, wie sie sind —
sie stehen in der Datenbank, und ein neuer Name hiesse, dass die Werte
des Nutzers stillschweigend auf die Vorgabe zurückfallen. Dasselbe für
die Endpunktpfade `/marken`: sie sind nicht sichtbar, und ein Umbenennen
wäre Diff ohne Gewinn.
**Die Migration ist von Hand geschrieben.** `dotnet ef` hat ein Gerüst
erzeugt, das beide Tabellen löscht und neu anlegt — es sieht eine
verschwundene und eine neue Tabelle, nicht eine umbenannte. Damit wäre
jedes erkannte Segment und jeder Vermerk „hier wurde schon gesucht" weg
gewesen. `RenameTable` und `RenameIndex` tun dasselbe, ohne eine Zeile
anzufassen; nachgemessen an einer echten SQLite-Datei: Zeilen vorher 1
und 1, nachher 1 und 1, Inhalt unverändert.
Nachgezogen: in `bilder.ts` und `main.ts` heisst „marke" ein Häkchen und
das Logo — die blieben unberührt. Und „Segment" ist sächlich, was eine
Blindersetzung nicht weiss: „eine Segment" stand danach an vierzehn
Stellen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2383 lines
96 KiB
CSS
2383 lines
96 KiB
CSS
/* 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);
|
||
}
|
||
|
||
/* Die Sprachwahl einer Serie steht zwischen dem Kopf und den Staffeln und
|
||
braucht nach unten denselben Abstand wie jeder andere Abschnitt — ohne
|
||
ihn klebt die Staffelwahl am Untertitelknopf. */
|
||
.serienstimmen { margin-bottom: var(--s-section); }
|
||
.serienstimmen .hinweis:empty { display: none; }
|
||
.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); }
|
||
|
||
/* **Fremde Bewertungen: Kürzel statt Stern.**
|
||
*
|
||
* Der Stern gehört der Wertung von TMDB; ihn für drei weitere Quellen zu
|
||
* wiederholen hiesse, vier gleich aussehende Zahlen nebeneinanderzustellen,
|
||
* von denen keine sagt, woher sie kommt. Das Kürzel sagt es in zwei Zeichen.
|
||
*
|
||
* **Und kein Logo.** Die Segmente von IMDb und Rotten Tomatoes mitzuliefern
|
||
* hiesse, fremde Zeichen auszuliefern — mit allem, was daran hängt. */
|
||
.bewertung.fremd { font-weight: 500; }
|
||
.bewertungsquelle {
|
||
font-size: 11px; font-weight: 700; letter-spacing: .03em;
|
||
color: var(--text-muted);
|
||
border: 1px solid var(--line); border-radius: var(--r-control);
|
||
padding: 1px 5px;
|
||
}
|
||
|
||
.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 Segmentefarbe 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; }
|
||
|
||
/* **Das Bild füllt die Fläche, dann sitzt es auch mittig.**
|
||
*
|
||
* Gemessen an einem 21:9-Film auf einem 1024×768-Fenster: die Hülle war
|
||
* 768 hoch, der Videokasten 459 — und er klebte bei oben 0. Die Breite
|
||
* stimmte, die Höhe nicht, und der schwarze Rand lag ganz unten statt
|
||
* gleichmässig oben und unten. Im Fenster fällt das kaum auf, im Vollbild
|
||
* sofort.
|
||
*
|
||
* **Woran es lag.** Die Hülle ist ein Raster mit `place-items: center`.
|
||
* Damit wird das Videoelement nicht gedehnt, und sein `height: 100%` löst
|
||
* sich gegen eine Zeile auf, die ihre Höhe wiederum aus dem Element nimmt —
|
||
* die Prozentangabe hatte nichts, woran sie sich halten konnte.
|
||
*
|
||
* Absolut über die ganze Hülle nimmt die Frage weg: `object-fit: contain`
|
||
* rechnet dann gegen den vollen Bildschirm und legt den schwarzen Rand von
|
||
* selbst zu gleichen Teilen oben und unten. Die kleine Leiste positioniert
|
||
* dasselbe Element ohnehin absolut und bleibt unberührt — deshalb das
|
||
* `:not(.pl-klein)`. */
|
||
.pl:not(.pl-klein) .pl-video { position: absolute; inset: 0; }
|
||
|
||
.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;
|
||
}
|
||
|
||
/* **Drei Spalten, nicht drei Flex-Kinder.**
|
||
*
|
||
* Die Mitte soll die Mitte des Bildes sein, nicht die Mitte dessen, was
|
||
* links und rechts übrig bleibt. Mit `space-between` wanderte der
|
||
* Abspielknopf, sobald rechts ein Menü dazukam oder links die Lautstärke
|
||
* wegfiel. `1fr auto 1fr` hält ihn fest. */
|
||
/* „Intro überspringen" — rechtsbündig über der Zeitleiste.
|
||
*
|
||
* Als eigene Zeile im Bedienblock, nicht als Überzug über dem Bild: so
|
||
* blendet er mit der Bedienung ein und aus und verdeckt nie die Leiste. */
|
||
/* **Eigene Schicht, nicht Teil der Bedienung.**
|
||
*
|
||
* In der Leiste verschwand er mit ihr — und am Fernseher sieht man die
|
||
* Leiste gar nicht. Hier liegt er über dem Bild, rechts über der Stelle,
|
||
* an der die Leiste erscheinen würde, und bleibt stehen, solange die Marke
|
||
* läuft. */
|
||
.pl-segmente {
|
||
position: absolute;
|
||
right: var(--s-section);
|
||
bottom: calc(var(--s-section) * 3.5);
|
||
z-index: 3;
|
||
display: flex;
|
||
gap: var(--s-snug);
|
||
}
|
||
.pl-segmente .btn { box-shadow: 0 2px 12px rgba(0, 0, 0, .6); }
|
||
|
||
.pl-unten {
|
||
display: grid; grid-template-columns: 1fr auto 1fr;
|
||
align-items: center; gap: var(--s-regular);
|
||
}
|
||
.pl-gruppe { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
||
.pl-mitte { display: flex; align-items: center; gap: 12px; justify-self: center; }
|
||
.pl-unten > .pl-gruppe:last-child { justify-self: end; }
|
||
|
||
/* **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), rechts sind es nur Knöpfe. */
|
||
.pl-unten > .pl-gruppe:first-child { 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;
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
/* **Schmal weicht, was man auch anders bekommt.**
|
||
*
|
||
* Die Lautstärke liegt am Telefon auf einer Taste an der Seite, und die
|
||
* verbleibende Zeit steht auch an der Zeitleiste ablesbar. Was nicht weicht,
|
||
* ist der Abspielknopf in der Mitte — sonst rutscht er aus der Mitte, und
|
||
* genau dort sucht ihn der Daumen. */
|
||
@media (max-width: 560px) {
|
||
.pl-laut, .pl-rest { display: none; }
|
||
}
|
||
|
||
/* 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;
|
||
}
|
||
|
||
/* **Das Protokoll als Liste, nicht als Textblock.**
|
||
*
|
||
* Eine Zeile je Eintrag, neueste oben, Zeit links. So liest es sich wie
|
||
* eine Liste von Ereignissen — und nicht wie eine Datei, in der man die
|
||
* Stelle erst suchen muss. */
|
||
.protokollliste {
|
||
margin-top: var(--s-regular);
|
||
border: 1px solid var(--line);
|
||
border-radius: var(--r-control);
|
||
background: var(--canvas);
|
||
max-height: 60vh;
|
||
overflow: auto;
|
||
}
|
||
.protokollzeile {
|
||
display: flex; gap: var(--s-regular);
|
||
padding: 7px var(--s-regular);
|
||
border-bottom: 1px solid var(--line);
|
||
font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||
color: var(--text-dim);
|
||
}
|
||
.protokollzeile:last-child { border-bottom: 0; }
|
||
.protokollzeit { flex: 0 0 auto; color: var(--text-muted); font-variant-numeric: tabular-nums; }
|
||
.protokolltext { flex: 1; white-space: pre-wrap; word-break: break-word; }
|
||
|
||
/* Fehler und Warnungen tragen die Farbe am Rand, nicht im Text: eine ganze
|
||
Zeile in Rot liest sich schlechter als eine in Grau mit rotem Strich. */
|
||
.protokollzeile.fehler { border-left: 3px solid var(--danger, #e5484d); color: var(--text); }
|
||
.protokollzeile.warnung { border-left: 3px solid #d9a441; }
|
||
.protokollzeile.fortsetzung { padding-left: calc(var(--s-regular) * 2 + 52px); }
|
||
|
||
.protokolltage { margin-top: var(--s-regular); }
|
||
.protokollkacheln { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
|
||
.protokollkachel {
|
||
display: flex; flex-direction: column; gap: 2px;
|
||
padding: 8px 12px;
|
||
border: 1px solid var(--line); border-radius: var(--r-control);
|
||
color: var(--text); text-decoration: none;
|
||
}
|
||
.protokollkachel:hover { background: var(--hover, rgba(255, 255, 255, .06)); }
|
||
|
||
/* 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. */
|
||
/* **Zwei Höhen je Stufe, und das ist der eigentliche Punkt.**
|
||
*
|
||
* Hier stand nur eine, und weiter unten überschrieb die Telefonregel sie
|
||
* mit einem festen Wert. Dadurch tat die Einstellung am Telefon gar nichts:
|
||
* ob „niedrig" oder „sehr hoch", es kamen immer dieselben 52 vh heraus —
|
||
* gemeldet als „auf 100 % ist es immer noch zu niedrig".
|
||
*
|
||
* Am Telefon fällt jede Stufe eine Spur flacher aus als am Schreibtisch:
|
||
* der Bildschirm ist hochkant, und derselbe Anteil nähme der ersten
|
||
* Kachelreihe dort den Platz, den sie am breiten Bildschirm noch hat. */
|
||
:root {
|
||
--hero-hoehe: min(72vh, 640px);
|
||
--hero-hoehe-schmal: min(52vh, 420px);
|
||
}
|
||
/* **Die flachste Stufe.** „Niedrig" war mit 62 vh nicht niedrig, sondern
|
||
mittel mit anderem Namen — die Spanne reichte nur nach oben. Wer den
|
||
Aufmacher als Kopfzeile will und nicht als halbe Seite, braucht das hier. */
|
||
html[data-aufmacher="flach"] {
|
||
--hero-hoehe: min(46vh, 420px);
|
||
--hero-hoehe-schmal: min(34vh, 280px);
|
||
}
|
||
html[data-aufmacher="niedrig"] {
|
||
--hero-hoehe: min(62vh, 560px);
|
||
--hero-hoehe-schmal: min(42vh, 340px);
|
||
}
|
||
html[data-aufmacher="hoch"] {
|
||
--hero-hoehe: min(82vh, 720px);
|
||
--hero-hoehe-schmal: min(62vh, 500px);
|
||
}
|
||
html[data-aufmacher="sehrhoch"] {
|
||
--hero-hoehe: min(92vh, 800px);
|
||
--hero-hoehe-schmal: min(72vh, 580px);
|
||
}
|
||
/* Die fünfte Stufe, weil die vierte am Telefon nicht reichte. Mehr als das
|
||
ginge nicht sinnvoll: darüber steht von der ersten Reihe nichts mehr im
|
||
Bild, und dann ist es kein Aufmacher mehr, sondern die ganze Seite. */
|
||
html[data-aufmacher="voll"] {
|
||
--hero-hoehe: min(100vh, 900px);
|
||
--hero-hoehe-schmal: min(84vh, 700px);
|
||
}
|
||
|
||
.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.
|
||
|
||
**Kurz und steil, nicht lang und flach.** Hier stand 55 % Deckung auf
|
||
halber Höhe und durchsichtig erst bei 85 % — von einem starken Aufmacher
|
||
war dadurch vor allem Grau zu sehen. Der Text sitzt unten links; genau
|
||
dort muss es tragen, und darüber nicht.
|
||
|
||
**Zweite Runde, und zwar nach unten.** Auch kurz und steil war es noch
|
||
zu dunkel: der Verlauf von links legte sich bis zur Mitte des Bildes und
|
||
nahm ihm die Farbe, und der von unten begann sein Grau schon auf
|
||
Zweidritteln. Jetzt trägt der untere das Gewicht — dort steht die
|
||
Schrift —, und der von links ist nur noch ein Hauch über dem linken
|
||
Drittel. Dass Schrift auf einem hellen Bild lesbar bleibt, übernimmt
|
||
ihr eigener Schattenwurf, nicht eine Decke über dem ganzen Aufmacher. */
|
||
.hero-buehne::after {
|
||
content: '';
|
||
position: absolute; inset: 0;
|
||
background:
|
||
linear-gradient(to top, var(--canvas) 0%, rgba(11, 11, 13, .62) 12%,
|
||
rgba(11, 11, 13, .16) 30%, transparent 48%),
|
||
linear-gradient(to right, rgba(11, 11, 13, .55) 0%, rgba(11, 11, 13, .12) 22%,
|
||
transparent 38%);
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
/* **Der Schatten trägt, nicht die Decke.** Seit der Verlauf heller ist,
|
||
kann das Bild hinter der Schrift hell sein. Ein Schattenwurf am Text
|
||
kostet nichts und wirkt genau dort, wo er gebraucht wird — eine dunklere
|
||
Decke über dem ganzen Aufmacher wirkte überall. */
|
||
.hero-titel,
|
||
.hero-meta,
|
||
.hero-text { text-shadow: 0 1px 4px rgba(0, 0, 0, .85), 0 0 18px rgba(0, 0, 0, .55); }
|
||
|
||
.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 {
|
||
/* **Die gewählte Stufe, nur flacher.** Hier stand ein fester Wert, und
|
||
der machte die Einstellung am Telefon wirkungslos. */
|
||
height: var(--hero-hoehe-schmal);
|
||
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); }
|