Eine Formensprache statt vier Radien und sechs Schriftgrössen
Die Oberfläche wirkte zusammengesetzt, nicht entworfen. Vier Gründe, alle am Quelltext belegbar. Die Farben waren von der App abgedriftet: elevated, placeholder, text, text-dim, text-muted und die Trennlinie wichen in der letzten Stelle ab, und der Standardakzent war in der App Lilac, im Browser Orchid. Einzeln unsichtbar, zusammen genau der Grund, warum App und Browser nicht wie dasselbe Programm aussahen. Alles auf FlaroDesign zurückgeführt. Vier konkurrierende Radien — 6 auf Plakaten, 8 auf Karten, 999 auf jedem Knopf, zweimal nacktes 3 — sind jetzt drei, und die ersten beiden stimmen wieder mit FlaroDesign überein. Pillen bleiben, wo sie hingehören: Suche und Filter. Sechs Überschriftengrössen ohne Verhältnis zueinander sind eine Skala. Der Kacheltitel war mit 13 px/500 leiser als die Metazeile daneben; das erzeugt keine Hierarchie, sondern Rauschen. Und der Akzent sass auf neun Dingen gleichzeitig. Wenn alles Akzent ist, markiert er nichts. Der Primärknopf ist jetzt hell mit dunkler Schrift — genau das macht Netflix, dessen Rot nur im Schriftzug vorkommt. Dazu drei Dinge, die vorher ganz fehlten: ein sichtbarer Fokusring auf allem Bedienbaren, ohne den die Oberfläche mit der Tastatur praktisch unbedienbar war; Rücksicht auf "Bewegung reduzieren", das im Projekt nirgends vorkam; und ein Hover, der mehr tut als zwei Bildpunkte zu heben — mitsamt einem Abspielknopf, der nicht länger das halbe Plakat verdeckt. Tools/tokens-pruefen.py vergleicht beide Quellen und läuft im Veröffentlichungsskript mit. Kein Generator: erzeugter Swift liest sich schlechter als geschriebener, und die Prüfung fängt dasselbe ab. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0cd88acf70
commit
6bd9cdb12d
+151
-37
@@ -9,27 +9,81 @@
|
||||
* Vorbild ist die Anordnung, nicht das Aussehen. */
|
||||
|
||||
:root {
|
||||
--accent: #C04AC9;
|
||||
--accent-dim: #8E3595;
|
||||
--accent-soft: rgba(192, 74, 201, .16);
|
||||
/* --- 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: #1D1D22;
|
||||
--raised: #26262C;
|
||||
--placeholder: #202026;
|
||||
--line: rgba(255, 255, 255, .08);
|
||||
--elevated: #1D1D21;
|
||||
--raised: #26262B;
|
||||
--placeholder: #26262B;
|
||||
--line: rgba(255, 255, 255, .10);
|
||||
|
||||
--text: #F2F2F5;
|
||||
--text-dim: #B4B4BE;
|
||||
--text-muted: #8A8A95;
|
||||
--text-faint: #65656F;
|
||||
/* 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;
|
||||
|
||||
--r-control: 8px; --r-card: 6px; --r-pill: 999px;
|
||||
/* --- 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;
|
||||
@@ -45,7 +99,7 @@ body {
|
||||
margin: 0;
|
||||
background: var(--canvas);
|
||||
color: var(--text);
|
||||
font: 400 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
font: 400 14px/1.55 Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
@@ -65,11 +119,11 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
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: #0E0E11;
|
||||
background: var(--chrome);
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
#topbar .marke {
|
||||
font-size: 20px; font-weight: 700; letter-spacing: -.02em;
|
||||
font-size: var(--t-abschnitt); font-weight: 700; letter-spacing: -.02em;
|
||||
color: var(--accent); user-select: none;
|
||||
}
|
||||
#topbar .suchfeld {
|
||||
@@ -101,7 +155,7 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
#chrome {
|
||||
position: fixed; top: var(--topbar-h); bottom: 0; left: 0;
|
||||
width: var(--sidebar-w); overflow-y: auto;
|
||||
background: #0E0E11;
|
||||
background: var(--chrome);
|
||||
border-right: 1px solid var(--line);
|
||||
padding: var(--s-loose) 0;
|
||||
display: flex; flex-direction: column; gap: 2px;
|
||||
@@ -160,7 +214,7 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
background: var(--surface); border: 1px solid var(--line);
|
||||
border-radius: var(--r-control); padding: var(--s-section);
|
||||
}
|
||||
.login-card h1 { margin: 0; font-size: 26px; letter-spacing: -.02em; color: var(--accent); }
|
||||
.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;
|
||||
@@ -174,7 +228,7 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
margin-bottom: var(--s-section);
|
||||
}
|
||||
.seitenkopf .wer { min-width: 0; }
|
||||
.seitenkopf h1 { margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -.02em; }
|
||||
.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);
|
||||
}
|
||||
@@ -257,8 +311,8 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
.plakat {
|
||||
position: relative; width: 100%; aspect-ratio: 2 / 3;
|
||||
background: var(--placeholder);
|
||||
border-radius: var(--r-card); overflow: hidden;
|
||||
transition: transform .15s ease, box-shadow .15s ease;
|
||||
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;
|
||||
@@ -266,19 +320,31 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
}
|
||||
.plakat.geladen img { opacity: 1; }
|
||||
|
||||
.kachel:hover .plakat {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, .55);
|
||||
/* **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 erscheint beim Überfahren, mittig. */
|
||||
/* **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; inset: 0; display: grid; place-items: center;
|
||||
background: rgba(0, 0, 0, .45);
|
||||
opacity: 0; transition: opacity .15s ease;
|
||||
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: 44px; height: 44px; color: #fff; filter: drop-shadow(0 2px 8px rgba(0,0,0,.6)); }
|
||||
.kachel:hover .schweber { opacity: 1; }
|
||||
.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? */
|
||||
@@ -302,7 +368,10 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
|
||||
.kacheltext { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
|
||||
.kacheltext strong {
|
||||
font-weight: 500; font-size: 13px;
|
||||
/* **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 {
|
||||
@@ -319,7 +388,7 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
display: flex; align-items: center; gap: var(--s-regular);
|
||||
margin-bottom: var(--s-regular);
|
||||
}
|
||||
.reihe-kopf h2 { margin: 0; font-size: 19px; font-weight: 700; letter-spacing: -.01em; }
|
||||
.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);
|
||||
}
|
||||
@@ -371,7 +440,7 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
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: 34px; font-weight: 700; letter-spacing: -.03em; }
|
||||
.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 {
|
||||
@@ -393,13 +462,21 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; gap: var(--s-snug);
|
||||
background: var(--elevated); border: 0; border-radius: var(--r-pill);
|
||||
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; }
|
||||
.btn-primary { background: var(--accent); color: #fff; font-weight: 600; }
|
||||
/* **Der Primärknopf ist hell, nicht farbig.**
|
||||
*
|
||||
* Genau das macht Netflix: der Abspielknopf ist weiss mit schwarzer Schrift,
|
||||
* die Markenfarbe erscheint nur im Schriftzug. Der Akzent sass hier auf neun
|
||||
* Dingen gleichzeitig — Marke, Navigation, Knopf, Fortschritt, Plakette,
|
||||
* Stern, Fokus, Filter, Kontokreis. Wenn alles Akzent ist, markiert er
|
||||
* nichts. */
|
||||
.btn-primary { background: var(--text); color: var(--canvas); font-weight: 600; }
|
||||
.btn-primary:hover { background: #fff; }
|
||||
.btn-primary:hover { background: var(--accent-dim); }
|
||||
|
||||
/* Runde Knöpfe für alles ohne Beschriftung — wie Plex' Merkliste und Haken. */
|
||||
@@ -442,8 +519,8 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
|
||||
/* ---------- Kleinteile ---------- */
|
||||
|
||||
h2 { font-size: 19px; font-weight: 700; letter-spacing: -.01em; margin: 0 0 var(--s-regular); }
|
||||
.seitentitel { margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -.02em; }
|
||||
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 {
|
||||
@@ -541,7 +618,7 @@ h2 { font-size: 19px; font-weight: 700; letter-spacing: -.01em; margin: 0 0 var(
|
||||
}
|
||||
|
||||
.btn-gefahr { background: var(--destructive); color: #fff; font-weight: 600; }
|
||||
.btn-gefahr:hover { background: #C53338; }
|
||||
.btn-gefahr:hover { background: var(--destructive-dim); }
|
||||
|
||||
/* Der Spieler über allem. */
|
||||
.pl-fill {
|
||||
@@ -656,3 +733,40 @@ h2 { font-size: 19px; font-weight: 700; letter-spacing: -.01em; margin: 0 0 var(
|
||||
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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user