/* 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. */ :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 `` 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; } /* ---------- 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