Während der Player auf Daten wartete, stand im mittleren Knopf das Abspieldreieck und der Ladekreis lag dahinter. Das las sich als „angehalten": man drückte auf Abspielen, und es geschah nichts — es lief ja bereits, es kam nur nichts an. Jetzt sitzt der Kreisel **im** Knopf, dort wo sonst das Symbol steht. Der freistehende Kreisel in der Bildmitte bleibt für den Fall, dass die Bedienelemente ausgeblendet sind. Zwei übereinander gibt es nicht mehr. `togglePlayPause` zählt das Warten jetzt zum Laufen. Vorher führte ein Tipp in diesem Moment zu `play()` auf etwas, das schon spielte. Dazu auf dem Mac, wie im Browser auch: - Ein Klick ins Bild hält an, statt erst die Bedienelemente zu holen. Die kommen dort von allein, sobald sich der Zeiger regt. - Leertaste hält an, Pfeil links und rechts springen. Mit dem Finger bleibt es beim Alten: ohne Zeiger gibt es keine Bewegung ohne Berührung, und ein Tipp, der sofort anhält, nähme den einzigen Weg zu Scrubber, Tonspur und Qualität. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
559 lines
25 KiB
CSS
559 lines
25 KiB
CSS
/* ============================================================================
|
|
Flixy Web — ein Frontend für Jellyfin
|
|
----------------------------------------------------------------------------
|
|
Die Werte stammen aus FlixyDesign, nicht aus dem Augenmaß: dieselben
|
|
Flächen, dieselben Radien, dieselben Abstände wie in der App.
|
|
========================================================================= */
|
|
:root {
|
|
--accent: #C04AC9; /* Orchid — wie auf dem iPad eingestellt */
|
|
--canvas: #0B0B0D;
|
|
--surface: #141417;
|
|
--elevated: #1D1D21;
|
|
--placeholder: #26262B;
|
|
--text: #F5F5F7;
|
|
--text-2: #A0A0A8;
|
|
--text-3: #6E6E76;
|
|
--separator: rgba(255,255,255,.10);
|
|
|
|
--r-card: 8px;
|
|
--r-control: 10px;
|
|
--r-sheet: 16px;
|
|
--r-panel: 22px;
|
|
|
|
--s-tight: 4px; --s-snug: 8px; --s-regular: 12px;
|
|
--s-loose: 16px; --s-section: 24px; --s-margin: 16px;
|
|
|
|
/* Die Kachelbreite. Der Regler in den Einstellungen verstellt genau diese
|
|
eine Zahl — Reihen, Raster und Querformat rechnen sich daraus ab. */
|
|
--card-w: 150px;
|
|
--sidebar-w: 235px;
|
|
|
|
--font: -apple-system, BlinkMacSystemFont, "SF Pro Text",
|
|
"Segoe UI Variable Text", "Segoe UI", system-ui, Roboto, sans-serif;
|
|
}
|
|
|
|
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
|
|
|
|
html, body {
|
|
margin: 0; padding: 0; height: 100%;
|
|
background: var(--canvas); color: var(--text);
|
|
font-family: var(--font); font-size: 15px; line-height: 1.4;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
body { overflow-x: hidden; }
|
|
button { font-family: inherit; cursor: pointer; border: none; }
|
|
img { display: block; }
|
|
|
|
.hidden { display: none !important; }
|
|
|
|
/* ---- Anmeldung ----------------------------------------------------------- */
|
|
#login {
|
|
min-height: 100dvh; display: grid; place-items: center; padding: var(--s-loose);
|
|
}
|
|
.login-card {
|
|
width: min(400px, 100%); display: flex; flex-direction: column; gap: var(--s-regular);
|
|
}
|
|
.login-card h1 {
|
|
font-size: 34px; font-weight: 700; margin: 0 0 var(--s-tight);
|
|
letter-spacing: -.02em; text-align: center;
|
|
}
|
|
.login-card .sub {
|
|
color: var(--text-2); text-align: center; margin: 0 0 var(--s-section);
|
|
}
|
|
.field { display: flex; flex-direction: column; gap: 6px; }
|
|
.field label { font-size: 13px; color: var(--text-2); }
|
|
.field input {
|
|
background: var(--elevated); border: 1px solid var(--separator);
|
|
border-radius: var(--r-control); color: var(--text);
|
|
padding: 11px var(--s-regular); font-size: 15px; font-family: inherit;
|
|
}
|
|
.field input:focus { outline: none; border-color: var(--accent); }
|
|
|
|
.btn {
|
|
border-radius: 999px; padding: 12px var(--s-section); font-size: 15px;
|
|
font-weight: 600; background: var(--elevated); color: var(--text);
|
|
transition: filter .15s ease;
|
|
}
|
|
.btn:hover { filter: brightness(1.25); }
|
|
.btn-primary { background: var(--accent); color: #fff; }
|
|
.btn-row { display: flex; gap: var(--s-regular); flex-wrap: wrap; }
|
|
|
|
.error {
|
|
color: #F08A8D; font-size: 13px; min-height: 18px; text-align: center;
|
|
}
|
|
.note { color: var(--text-3); font-size: 12px; text-align: center; margin-top: var(--s-snug); }
|
|
|
|
/* ---- Die schwebende Navigationsinsel -------------------------------------
|
|
Das Merkmal der App: eine Fläche mit Abstand ringsum, hinter der der
|
|
Inhalt durchläuft. Sie blendet beim Scrollen aus und kommt zurück, sobald
|
|
man wieder oben steht — genau wie in der App. */
|
|
#chrome {
|
|
position: fixed; top: calc(10px + env(safe-area-inset-top)); left: 0; right: 0;
|
|
z-index: 50; display: flex; justify-content: center;
|
|
transition: transform .28s cubic-bezier(.32,.72,0,1), opacity .28s ease;
|
|
pointer-events: none;
|
|
}
|
|
#chrome.away { transform: translateY(-140%); opacity: 0; }
|
|
|
|
.island {
|
|
pointer-events: auto;
|
|
display: flex; align-items: center; gap: var(--s-tight);
|
|
padding: var(--s-snug);
|
|
background: rgba(20,20,23,.72);
|
|
backdrop-filter: saturate(180%) blur(20px);
|
|
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
|
border: 1px solid var(--separator);
|
|
border-radius: var(--r-panel);
|
|
box-shadow: 0 8px 24px rgba(0,0,0,.45);
|
|
max-width: calc(100vw - 24px); overflow-x: auto; scrollbar-width: none;
|
|
}
|
|
.island::-webkit-scrollbar { display: none; }
|
|
|
|
.island button {
|
|
background: none; color: var(--text); font-size: 15px; white-space: nowrap;
|
|
padding: var(--s-snug) var(--s-loose); border-radius: 999px;
|
|
}
|
|
.island button.active { background: var(--elevated); color: var(--accent); }
|
|
.island .sep { width: 1px; height: 22px; background: var(--separator); margin: 0 var(--s-tight); }
|
|
|
|
/* Der Umschalter zwischen oben und seitlich — wie auf dem iPad ein Knopf in
|
|
der Leiste selbst. Ihn in die Einstellungen zu verbannen hieße, für einen
|
|
Handgriff zwei Ebenen tief zu gehen. */
|
|
.island .toggle-nav {
|
|
padding: var(--s-snug); color: var(--text-2); display: flex; align-items: center;
|
|
border-radius: var(--r-control); flex: 0 0 auto;
|
|
}
|
|
.island .toggle-nav:hover { color: var(--text); background: var(--elevated); }
|
|
.island .toggle-nav svg { width: 18px; height: 18px; display: block; }
|
|
#app.side .island .toggle-nav { align-self: flex-end; margin: 0 var(--s-snug) var(--s-snug); }
|
|
|
|
/* ---- Aufmacher -----------------------------------------------------------
|
|
Randlos bis an den oberen Rand, die Insel schwebt darüber. Genau die
|
|
Entscheidung, die in der App getroffen wurde. */
|
|
#hero {
|
|
position: relative; height: var(--hero-h, 82vh); min-height: 380px; overflow: hidden;
|
|
}
|
|
#hero .shot {
|
|
position: absolute; inset: 0; background-size: cover; background-position: center;
|
|
opacity: 0; transition: opacity .55s ease;
|
|
}
|
|
#hero .shot.on { opacity: 1; }
|
|
#hero .veil {
|
|
position: absolute; inset: 0;
|
|
background:
|
|
linear-gradient(to bottom, rgba(0,0,0,.45) 0, rgba(0,0,0,0) 150px),
|
|
linear-gradient(to bottom, rgba(11,11,13,0) 45%, rgba(11,11,13,.75) 78%, var(--canvas) 100%);
|
|
}
|
|
#hero .body {
|
|
position: absolute; left: 0; right: 0; bottom: 0;
|
|
padding: 0 var(--s-margin) var(--s-section);
|
|
display: flex; flex-direction: column; gap: var(--s-snug);
|
|
}
|
|
#hero .logo { max-width: 286px; max-height: 84px; object-fit: contain; } /* 10 % über der App-Größe, auf Wunsch */
|
|
#hero h2 { font-size: 34px; font-weight: 700; margin: 0; letter-spacing: -.02em; }
|
|
#hero .meta { color: var(--text-2); font-size: 13px; }
|
|
#hero .summary {
|
|
color: var(--text-2); font-size: 13px; max-width: 640px;
|
|
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
|
}
|
|
#hero .actions { display: flex; gap: var(--s-regular); margin-top: var(--s-tight); }
|
|
.dots { display: flex; gap: var(--s-snug); justify-content: center; padding: var(--s-regular) 0 var(--s-section); }
|
|
.dots button {
|
|
width: 6px; height: 6px; border-radius: 999px; background: rgba(255,255,255,.35);
|
|
padding: 0; transition: width .25s ease, background .25s ease;
|
|
}
|
|
.dots button:hover { background: rgba(255,255,255,.6); }
|
|
.dots button.on { width: 18px; background: var(--accent); }
|
|
|
|
/* Blättern von Hand. Die Pfeile liegen auf dem Bild und halten sich zurück,
|
|
bis der Zeiger da ist — auf einem Zeigegerät. Wer wischt, braucht sie nicht. */
|
|
.hero-nav {
|
|
position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
|
|
width: 44px; height: 44px; border-radius: 999px; padding: 0;
|
|
background: rgba(20,20,23,.55); color: var(--text); font-size: 20px;
|
|
backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
|
|
border: 1px solid var(--separator); opacity: 0; transition: opacity .2s ease;
|
|
}
|
|
#hero:hover .hero-nav { opacity: 1; }
|
|
.hero-nav.prev { left: var(--s-margin); }
|
|
.hero-nav.next { right: var(--s-margin); }
|
|
@media (hover: none) { .hero-nav { display: none; } }
|
|
|
|
/* ---- Reihen -------------------------------------------------------------- */
|
|
.row { margin-bottom: var(--s-section); }
|
|
.row h3 {
|
|
font-size: 17px; font-weight: 600; margin: 0 0 var(--s-regular);
|
|
padding: 0 var(--s-margin); letter-spacing: -.01em;
|
|
}
|
|
.strip {
|
|
display: flex; gap: var(--s-regular); overflow-x: auto;
|
|
padding: 0 var(--s-margin) var(--s-tight); scroll-snap-type: x proximity;
|
|
scrollbar-width: none;
|
|
}
|
|
.strip::-webkit-scrollbar { display: none; }
|
|
|
|
.card { flex: 0 0 auto; width: var(--card-w); scroll-snap-align: start; background: none; padding: 0; text-align: left; }
|
|
.card.wide { width: calc(var(--card-w) * 1.73); }
|
|
.card .art {
|
|
width: 100%; aspect-ratio: 2/3; border-radius: var(--r-card);
|
|
background: var(--placeholder) center/cover no-repeat;
|
|
position: relative; overflow: hidden;
|
|
}
|
|
.card.wide .art { aspect-ratio: 16/9; }
|
|
.card .title {
|
|
font-size: 13px; color: var(--text); margin-top: 6px;
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
}
|
|
.card .sub { font-size: 12px; color: var(--text-3); }
|
|
.card .bar { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: rgba(255,255,255,.2); }
|
|
.card .bar span { display: block; height: 100%; background: var(--accent); }
|
|
|
|
/* ---- Raster (Mediathek) -------------------------------------------------- */
|
|
.grid {
|
|
display: grid; grid-template-columns: repeat(auto-fill, minmax(calc(var(--card-w) * .75), 1fr));
|
|
gap: var(--s-loose) var(--s-regular); padding: 0 var(--s-margin) var(--s-section);
|
|
}
|
|
.grid .card { width: auto; }
|
|
|
|
/* ---- Detailblatt --------------------------------------------------------- */
|
|
#sheet {
|
|
position: fixed; inset: 0; z-index: 60; background: rgba(0,0,0,.55);
|
|
display: grid; place-items: center; padding: var(--s-loose);
|
|
backdrop-filter: blur(8px);
|
|
}
|
|
#sheet .panel {
|
|
background: var(--elevated); border: 1px solid var(--separator);
|
|
border-radius: var(--r-sheet); width: min(680px, 100%);
|
|
max-height: 86dvh; overflow: auto; padding: var(--s-section);
|
|
}
|
|
#sheet h2 { margin: 0 0 var(--s-snug); font-size: 24px; letter-spacing: -.02em; }
|
|
#sheet .meta { color: var(--text-2); font-size: 13px; margin-bottom: var(--s-regular); }
|
|
#sheet p { color: var(--text-2); font-size: 14px; }
|
|
/* ---- Der Player ----------------------------------------------------------
|
|
Vollbild und schwarz. Ein Film nimmt den Bildschirm ein — alles andere ist
|
|
ein Vorschaufenster. */
|
|
#player {
|
|
position: fixed; inset: 0; z-index: 100; background: #000;
|
|
display: grid; place-items: center; overflow: hidden;
|
|
}
|
|
#player video { width: 100%; height: 100%; object-fit: contain; background: #000; cursor: pointer; }
|
|
|
|
/* **Die Fläche selbst nimmt keine Klicks entgegen, nur ihre Knöpfe.**
|
|
Sonst liegt sie über dem Bild und schluckt jeden Klick — und „ins Bild
|
|
tippen hält an" funktioniert genau dann nicht, wenn die Bedienung zu
|
|
sehen ist, also fast immer. */
|
|
.pl-chrome {
|
|
position: absolute; inset: 0; opacity: 0; pointer-events: none;
|
|
transition: opacity .25s ease;
|
|
display: grid; grid-template-rows: auto 1fr auto;
|
|
}
|
|
.pl-chrome.on { opacity: 1; }
|
|
.pl-chrome.on button,
|
|
.pl-chrome.on .pl-track,
|
|
.pl-chrome.on select { pointer-events: auto; }
|
|
|
|
/* Zwei Verläufe statt einer Fläche: die Mitte bleibt frei, und genau dort
|
|
ist das Bild. */
|
|
/* Eine gleichmäßige Abdunklung über allem, wie in der App: „genug, damit
|
|
weiße Schrift auf hellen Szenen lesbar bleibt, wenig genug, dass das Bild
|
|
nicht verfälscht wirkt." Zwei Verläufe an den Rändern taten das nicht —
|
|
in der Mitte stehen die Transportknöpfe und hatten nichts hinter sich. */
|
|
.pl-chrome { background: rgba(0,0,0,.35); }
|
|
|
|
.pl-top, .pl-bottom { padding: var(--s-loose) var(--s-section); }
|
|
.pl-top {
|
|
display: flex; align-items: center; gap: var(--s-regular);
|
|
padding-top: calc(var(--s-loose) + env(safe-area-inset-top));
|
|
}
|
|
.pl-bottom { padding-bottom: calc(var(--s-loose) + env(safe-area-inset-bottom)); }
|
|
|
|
/* Die Stufe als Text — jetzt ohne Kreis darum, also ohne Widerspruch. */
|
|
.pl-quality { font-size: 14px; font-weight: 600; white-space: nowrap; }
|
|
|
|
.pl-title { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
.pl-title strong { font-size: 17px; font-weight: 600; letter-spacing: -.01em; }
|
|
.pl-title span { font-size: 13px; color: var(--text-2); }
|
|
|
|
/* **Keine Knopfflächen.** In der App sind das blanke weiße Symbole auf dem
|
|
Bild — der einzige Hintergrund ist die Abdunklung über allem. Runde
|
|
Flächen darunter waren meine Erfindung, und sie sind der Grund, warum
|
|
„Original" als Text darin schief aussah: ein Wort passt in keinen Kreis. */
|
|
.pl-icon {
|
|
background: none; border: none; color: #fff; flex: 0 0 auto;
|
|
padding: var(--s-snug); font-size: 15px; border-radius: var(--r-control);
|
|
opacity: .9; transition: opacity .15s ease, background .15s ease;
|
|
}
|
|
.pl-icon:hover { opacity: 1; background: rgba(255,255,255,.12); }
|
|
/* Eine Vorgabegröße für alle Symbole, damit sie zueinander passen; einzelne
|
|
überschreiben sie über `icon(name, size)`. */
|
|
.pl-icon svg, .tiny svg, .hero-nav svg { width: 22px; height: 22px; }
|
|
.btn svg { width: 15px; height: 15px; vertical-align: -2px; margin-right: 2px; }
|
|
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
|
|
.tiny, .pl-icon, .pl-round, .hero-nav { display: inline-flex; align-items: center; justify-content: center; }
|
|
|
|
.pl-center {
|
|
display: flex; align-items: center; justify-content: center;
|
|
gap: calc(var(--s-section) * 2);
|
|
}
|
|
/* Auch hier keine Fläche — nur das Symbol, wie in der App. Lesbar bleibt es
|
|
durch die Abdunklung, die über dem ganzen Bild liegt. */
|
|
.pl-round {
|
|
background: none; border: none; color: #fff; padding: var(--s-snug);
|
|
opacity: .95; transition: opacity .15s ease, transform .15s ease;
|
|
}
|
|
.pl-round:hover { opacity: 1; transform: scale(1.08); }
|
|
|
|
/* Die Sekundenzahl als Text, nicht als Ziffer im Pfeil: übereinander war
|
|
beides unlesbar, und die Zahl ist die eigentliche Auskunft. */
|
|
|
|
|
|
/* Wie in der App: der Regler über die **volle Breite**, die Zeiten darunter.
|
|
Nebeneinander gestellt nimmt er in der Mitte Platz weg und wirkt wie ein
|
|
Formularfeld zwischen zwei Beschriftungen. */
|
|
.pl-times {
|
|
display: flex; justify-content: space-between;
|
|
margin-top: var(--s-tight);
|
|
font-size: 13px; color: rgba(255,255,255,.8);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.pl-track {
|
|
position: relative; width: 100%; height: 22px; cursor: pointer;
|
|
display: flex; align-items: center;
|
|
}
|
|
.pl-track::before {
|
|
content: ''; position: absolute; left: 0; right: 0; height: 4px;
|
|
background: rgba(0,0,0,.45); border-radius: 999px;
|
|
box-shadow: inset 0 0 0 1px rgba(255,255,255,.14);
|
|
}
|
|
.pl-buf, .pl-fill { position: absolute; left: 0; height: 4px; border-radius: 999px; }
|
|
.pl-buf { background: rgba(255,255,255,.3); width: 0; }
|
|
.pl-fill { background: var(--accent); width: 0; }
|
|
.pl-track i {
|
|
position: absolute; width: 13px; height: 13px; border-radius: 999px;
|
|
background: var(--accent); transform: translateX(-50%);
|
|
box-shadow: 0 1px 4px rgba(0,0,0,.6); transition: transform .12s ease;
|
|
}
|
|
.pl-track:hover i { transform: translateX(-50%) scale(1.25); }
|
|
|
|
.pl-spacer { flex: 1; }
|
|
|
|
/* Während geladen wird, treten die Transportknöpfe zurück: sie tun ohnehin
|
|
nichts, und der Kreisel lag vorher genau hinter dem größten von ihnen. */
|
|
/* Während des ersten Ladens haben Vor- und Zurückspulen keine Wirkung — es
|
|
gibt noch keine Zeitachse. Der mittlere Knopf bleibt stehen: er trägt
|
|
jetzt selbst den Kreisel. */
|
|
#player.loading .pl-center > :not(.pl-play) { opacity: 0; pointer-events: none; }
|
|
.pl-center { transition: opacity .2s ease; }
|
|
|
|
.pl-busy {
|
|
position: absolute; display: none; pointer-events: none;
|
|
}
|
|
.pl-busy.on { display: block; }
|
|
.pl-busy i, .pl-wait {
|
|
display: block; width: 44px; height: 44px; border-radius: 999px;
|
|
border: 3px solid rgba(255,255,255,.2); border-top-color: var(--accent);
|
|
animation: spin .8s linear infinite;
|
|
}
|
|
|
|
/* Feste Größe für den mittleren Knopf, damit die beiden daneben stehen
|
|
bleiben: der Kreisel ist schmaler als das Symbol, das er vertritt, und
|
|
ohne diese Zeile rückten Zurück- und Vorspulen bei jedem Wechsel
|
|
zusammen. */
|
|
.pl-round.pl-play { width: 68px; height: 68px; }
|
|
|
|
.pl-error {
|
|
position: absolute; max-width: 460px; padding: var(--s-section);
|
|
background: var(--elevated); border: 1px solid rgba(229,72,77,.35);
|
|
border-radius: var(--r-sheet); display: flex; flex-direction: column; gap: var(--s-regular);
|
|
box-shadow: 0 20px 60px rgba(0,0,0,.6);
|
|
}
|
|
.pl-error strong { color: #F08A8D; font-size: 16px; font-weight: 600; }
|
|
.pl-error span { color: var(--text-2); font-size: 14px; }
|
|
.pl-error .btn { align-self: flex-start; }
|
|
/* Das Menü für Qualität und Spuren — über der Leiste, nicht als Blatt.
|
|
Ein Blatt würde das Bild verdecken, und man stellt hier etwas ein, das man
|
|
am Bild beurteilen will. */
|
|
.pl-menu {
|
|
position: fixed; z-index: 4;
|
|
min-width: 232px; max-width: 320px; max-height: 60vh; overflow-y: auto;
|
|
background: rgba(20,20,23,.92); border: 1px solid var(--separator);
|
|
border-radius: var(--r-sheet); padding: var(--s-snug);
|
|
backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px);
|
|
box-shadow: 0 16px 48px rgba(0,0,0,.6);
|
|
}
|
|
.pl-group {
|
|
font-size: 11px; font-weight: 600; color: var(--text-3);
|
|
text-transform: uppercase; letter-spacing: .06em;
|
|
padding: var(--s-regular) var(--s-regular) var(--s-tight);
|
|
}
|
|
.pl-group:first-child { padding-top: var(--s-snug); }
|
|
.pl-item {
|
|
display: flex; align-items: center; justify-content: space-between; gap: var(--s-regular);
|
|
width: 100%; padding: 9px var(--s-regular); border-radius: var(--r-control);
|
|
background: none; color: var(--text); font-size: 14px; text-align: left;
|
|
}
|
|
.pl-item:hover { background: rgba(255,255,255,.08); }
|
|
.pl-item.on { color: var(--accent); }
|
|
.pl-item svg { width: 16px; height: 16px; flex: 0 0 auto; }
|
|
.pl-item span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
.pl-error code {
|
|
font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11px;
|
|
color: var(--text-3); background: rgba(0,0,0,.4); padding: var(--s-snug);
|
|
border-radius: 6px; word-break: break-all; line-height: 1.5;
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.pl-top, .pl-bottom { padding-left: var(--s-loose); padding-right: var(--s-loose); }
|
|
.pl-round { width: 50px; height: 50px; }
|
|
.pl-round.pl-play { width: 64px; height: 64px; }
|
|
.pl-rate, .pl-tools input[type=range] { display: none; }
|
|
}
|
|
|
|
/* ---- Die Navigation an der Seite -----------------------------------------
|
|
Dieselbe Wahl wie in der App: die Leiste sitzt oben als Insel oder links
|
|
als Spalte. Links bleibt sie stehen, statt beim Scrollen wegzufahren —
|
|
dort nimmt sie niemandem etwas weg. */
|
|
#app.side #chrome {
|
|
top: 0; bottom: 0; left: 0; right: auto; width: calc(var(--sidebar-w) + 24px);
|
|
align-items: stretch; padding: 12px; transform: none !important; opacity: 1 !important;
|
|
}
|
|
#app.side .island {
|
|
flex-direction: column; align-items: stretch; gap: 2px;
|
|
width: 100%; max-width: none; overflow-y: auto; overflow-x: hidden;
|
|
padding: var(--s-regular) var(--s-snug);
|
|
}
|
|
#app.side .island button { text-align: left; border-radius: var(--r-control); }
|
|
#app.side .island .sep { width: auto; height: 1px; margin: var(--s-snug); }
|
|
#app.side #content { margin-left: calc(var(--sidebar-w) + 24px); }
|
|
|
|
/* ---- Der Umzug der Leiste -----------------------------------------------
|
|
Sie **zieht um**, sie verschwindet nicht und taucht woanders wieder auf.
|
|
Das ist der Unterschied zwischen einer Leiste, die man mit den Augen
|
|
verfolgt, und zweien, von denen eine geht und eine kommt — und es ist das,
|
|
was Mac und iPad tun.
|
|
|
|
Gemacht wird das mit FLIP: die alte Lage messen, umbauen, die neue Lage
|
|
messen, und das Element vom alten Zustand in den neuen fahren lassen. Der
|
|
Weg dazwischen ist eine einzige Bewegung, obwohl der Aufbau in der Mitte
|
|
ausgetauscht wurde. Über CSS allein ginge das nicht — `right: auto` und
|
|
`bottom: auto` lassen sich nicht animieren, und genau die wechseln hier.
|
|
|
|
Der Inhalt rückt gleichzeitig nach; ohne das bliebe links ein Loch stehen,
|
|
bis die Leiste angekommen ist. */
|
|
#content { transition: margin-left .42s cubic-bezier(.32,.72,0,1); }
|
|
|
|
/* Wer Bewegung nicht mag, bekommt keine. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
#content, .card .art, .shot, .island { transition: none !important; }
|
|
}
|
|
#app.side .pad-top { padding-top: var(--s-loose); }
|
|
|
|
.island .brand {
|
|
display: none; font-size: 20px; font-weight: 700; letter-spacing: -.02em;
|
|
padding: var(--s-snug) var(--s-loose) var(--s-regular); color: var(--text);
|
|
}
|
|
#app.side .island .brand { display: block; }
|
|
.island .group-label {
|
|
display: none; font-size: 12px; color: var(--text-3);
|
|
padding: var(--s-regular) var(--s-loose) var(--s-tight);
|
|
}
|
|
#app.side .island .group-label { display: block; }
|
|
|
|
/* ---- Einstellungen -------------------------------------------------------- */
|
|
.settings { max-width: 620px; margin: 0 auto; padding: 0 var(--s-margin) var(--s-section); }
|
|
.settings h2 { font-size: 28px; letter-spacing: -.02em; margin: 0 0 var(--s-section); }
|
|
.settings .group {
|
|
font-size: 13px; font-weight: 600; color: var(--text-3);
|
|
text-transform: uppercase; letter-spacing: .06em;
|
|
margin: var(--s-section) 0 var(--s-snug); padding-left: var(--s-tight);
|
|
}
|
|
.settings .group:first-of-type { margin-top: 0; }
|
|
.hexrow { display: flex; gap: var(--s-snug); margin-top: var(--s-regular); }
|
|
.hexrow input {
|
|
flex: 1; background: var(--elevated); border: 1px solid var(--separator);
|
|
border-radius: var(--r-control); color: var(--text); padding: 9px var(--s-regular);
|
|
font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 14px;
|
|
}
|
|
.hexrow input:focus { outline: none; border-color: var(--accent); }
|
|
.hint { font-size: 12px; color: var(--text-3); margin: var(--s-snug) 0 0; min-height: 16px; }
|
|
.rowline {
|
|
display: flex; align-items: center; gap: var(--s-snug);
|
|
padding: var(--s-snug) 0; border-bottom: 1px solid var(--separator);
|
|
}
|
|
.rowline:last-child { border-bottom: none; }
|
|
.rowline.off span { color: var(--text-3); text-decoration: line-through; }
|
|
.tiny {
|
|
width: 30px; height: 30px; border-radius: var(--r-control);
|
|
background: var(--elevated); color: var(--text-2); font-size: 14px; padding: 0;
|
|
}
|
|
.tiny:hover:not(:disabled) { color: var(--text); }
|
|
.tiny:disabled { opacity: .3; cursor: default; }
|
|
.pick {
|
|
background: var(--elevated); border: 1px solid var(--separator);
|
|
border-radius: var(--r-control); color: var(--text);
|
|
padding: 9px var(--s-regular); font-family: inherit; font-size: 14px; width: 100%;
|
|
}
|
|
.pick:focus { outline: none; border-color: var(--accent); }
|
|
.pickrow { display: flex; gap: var(--s-regular); flex-wrap: wrap; }
|
|
.pickrow label { flex: 1; min-width: 160px; font-size: 13px; color: var(--text-2); }
|
|
.pickrow .pick { margin-top: 6px; }
|
|
.setting {
|
|
background: var(--surface); border: 1px solid var(--separator);
|
|
border-radius: var(--r-sheet); padding: var(--s-loose); margin-bottom: var(--s-regular);
|
|
}
|
|
.setting h4 { margin: 0 0 var(--s-snug); font-size: 15px; font-weight: 600; }
|
|
.setting p { margin: 0 0 var(--s-regular); color: var(--text-2); font-size: 13px; }
|
|
.swatches { display: flex; flex-wrap: wrap; gap: var(--s-regular); }
|
|
.swatch {
|
|
width: 34px; height: 34px; border-radius: 999px; padding: 0;
|
|
border: 2px solid transparent; transition: transform .12s ease;
|
|
}
|
|
.swatch:hover { transform: scale(1.12); }
|
|
.swatch.on { border-color: var(--text); }
|
|
.seg { display: flex; gap: var(--s-snug); }
|
|
.seg button { flex: 1; background: var(--elevated); color: var(--text-2); border-radius: var(--r-control); padding: 10px; }
|
|
.seg button.on { background: var(--accent); color: #fff; }
|
|
input[type=range] { width: 100%; accent-color: var(--accent); }
|
|
.range-row { display: flex; align-items: center; gap: var(--s-regular); }
|
|
.range-row output { min-width: 82px; text-align: right; color: var(--text-2); font-size: 13px; }
|
|
.check {
|
|
display: flex; align-items: center; gap: var(--s-snug);
|
|
padding: var(--s-snug) 0; font-size: 14px; color: var(--text-2); cursor: pointer;
|
|
}
|
|
.check input { accent-color: var(--accent); width: 17px; height: 17px; }
|
|
|
|
/* ---- Zustände ------------------------------------------------------------ */
|
|
#content { padding-bottom: var(--s-section); }
|
|
|
|
/* Ein Seitenwechsel soll nicht schlagen. Kurz und flach — was länger dauert,
|
|
fühlt sich nach Warten an statt nach Bewegung. */
|
|
@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
|
|
.row, .grid, .settings, #hero { animation: rise .32s cubic-bezier(.32,.72,0,1) both; }
|
|
/* Die Reihen kommen gestaffelt — die erste zuerst, dann nach unten weg. */
|
|
.row:nth-of-type(2) { animation-delay: .04s; }
|
|
.row:nth-of-type(3) { animation-delay: .08s; }
|
|
.row:nth-of-type(4) { animation-delay: .12s; }
|
|
.row:nth-of-type(n+5) { animation-delay: .16s; }
|
|
|
|
/* Kacheln heben sich beim Überfahren leicht an. */
|
|
.card .art { transition: transform .2s cubic-bezier(.32,.72,0,1), box-shadow .2s ease; }
|
|
.card:hover .art { transform: scale(1.04); box-shadow: 0 10px 24px rgba(0,0,0,.5); }
|
|
.pad-top { padding-top: calc(84px + env(safe-area-inset-top)); }
|
|
.spinner {
|
|
width: 26px; height: 26px; border: 2px solid var(--separator);
|
|
border-top-color: var(--accent); border-radius: 999px;
|
|
animation: spin .8s linear infinite; margin: 60px auto;
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
.empty { text-align: center; color: var(--text-2); padding: 60px var(--s-margin); }
|
|
|
|
@media (max-width: 600px) {
|
|
#hero { height: 70vh; }
|
|
#hero h2 { font-size: 26px; }
|
|
.card { width: 120px; }
|
|
.card.wide { width: 200px; }
|
|
}
|