Files
watchlist/quivio/static/css/style.css
T
ki 9a4909f083
CI / test (push) Has been cancelled
chore(rebrand): rename WatchStack -> Quivio
Quivio (Kofferwort aus Quire + Video) ersetzt den Arbeitstitel WatchStack.
Es ist origineller, frei auf PyPI/npm, und passt perfekt zur Doppelnatur
der App: Buecher (Quire) + Serien (Video).

Geaendert:
- Python-Paket app/ -> quivio/ (+ alle Imports angepasst)
- DB-File watchstack.db -> quivio.db
- Docker-Image: quivio:0.5.1-beta, Container-Name quivio
- Compose-Services: quivio, quivio-local
- ENV-Vars: QUIVIO_VERSION, QUIVIO_PORT, Volume quivio-data
- Frontend-Logo "W" -> "Q", Titel "Quivio", Footer
- Favicon "Q" (Georgia serif fuer klassischen Look)
- Logger-Name: watchstack -> quivio
- README, NOTICE, dist/README, data/README: Quivio
- CI-Workflow: testet quivio/ Pfade
- build-and-push.sh: lokaler Tag "quivio:VERSION"
- service-Feld in /api/health: quivio
- OpenAPI title: Quivio

Bugfix (gefunden beim Renaming):
- seed.py hatte versteckten "from app.database import DB_PATH" — gefixt

Verifiziert end-to-end:
- Python-Import OK
- Backend: service=quivio, version=0.5.1-beta, 8 unique items
- Docker-Build OK, Push in Gitea-Registry OK (neuer sha256-Digest)
- Container: Quivio 0.5.1-beta ready (logger), HTML ohne WatchStack-Rest
- OpenAPI title=Quivio
- 8 unique items nach cold start (Race-Fix haelt)
2026-07-22 00:42:09 +02:00

589 lines
16 KiB
CSS

/* ========================================================================
Quivio — Theme
- Dark default
- Eine Akzentfarbe (cyan-violett), warme Status-Farben
======================================================================== */
:root {
--bg: #0f1216;
--bg-elev: #161b22;
--bg-elev-2: #1d242d;
--border: #2a313c;
--border-strong: #3a424f;
--text: #e8ecf2;
--text-dim: #9aa3b1;
--text-muted: #6e7684;
--accent: #7c5cff;
--accent-2: #22d3ee;
--accent-grad: linear-gradient(135deg, #7c5cff 0%, #22d3ee 100%);
--danger: #ef4444;
--warn: #f59e0b;
--success: #22c55e;
--plan: #94a3b8;
--reading: #38bdf8;
--done: #22c55e;
--hold: #f59e0b;
--dropped: #ef4444;
--radius: 12px;
--radius-sm: 8px;
--shadow-lg: 0 20px 60px rgba(0,0,0,.45);
--shadow-md: 0 8px 24px rgba(0,0,0,.35);
--transition: 180ms cubic-bezier(.2,.7,.2,1);
--container: 1400px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
button { font: inherit; color: inherit; cursor: pointer; }
input, select, textarea, button { font: inherit; }
a { color: var(--accent-2); text-decoration: none; }
.hidden { display: none !important; }
/* ===================== Hero ===================== */
.hero {
position: relative;
background: linear-gradient(180deg, #1a1f2b 0%, #0f1216 100%);
border-bottom: 1px solid var(--border);
overflow: hidden;
}
.hero-bg {
position: absolute; inset: 0;
background:
radial-gradient(800px 400px at 20% 0%, rgba(124,92,255,.18), transparent 60%),
radial-gradient(700px 350px at 80% 0%, rgba(34,211,238,.15), transparent 60%);
pointer-events: none;
}
.hero-inner {
position: relative;
max-width: var(--container);
margin: 0 auto;
padding: 28px 24px 16px;
display: flex;
flex-direction: column;
gap: 18px;
}
.brand {
display: flex;
align-items: center;
gap: 14px;
}
.logo {
width: 52px; height: 52px;
border-radius: 14px;
background: var(--accent-grad);
display: grid; place-items: center;
font-weight: 800; font-size: 26px;
color: white;
box-shadow: 0 8px 20px rgba(124,92,255,.35);
}
.brand-text h1 {
margin: 0;
font-size: 28px;
font-weight: 800;
letter-spacing: -.01em;
background: var(--accent-grad);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.brand-text p { margin: 2px 0 0; color: var(--text-dim); font-size: 14px; }
.version-badge {
display: inline-block;
margin-left: 8px;
vertical-align: middle;
font-size: 10px;
font-weight: 800;
letter-spacing: .1em;
text-transform: uppercase;
padding: 3px 7px;
border-radius: 999px;
background: linear-gradient(135deg, rgba(245,158,11,.25), rgba(239,68,68,.25));
color: #fbbf24;
border: 1px solid rgba(245,158,11,.4);
-webkit-text-fill-color: #fbbf24;
}
.hero-search {
display: flex; gap: 10px; flex-wrap: wrap;
align-items: center;
}
.hero-search input[type="search"] {
flex: 1 1 320px;
background: var(--bg-elev);
border: 1px solid var(--border);
color: var(--text);
padding: 11px 14px;
border-radius: var(--radius);
outline: none;
transition: border-color var(--transition), box-shadow var(--transition);
}
.hero-search input[type="search"]:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(124,92,255,.18);
}
.hero-search select {
background: var(--bg-elev);
border: 1px solid var(--border);
color: var(--text);
padding: 10px 12px;
border-radius: var(--radius);
}
.btn {
display: inline-flex; align-items: center; justify-content: center;
gap: 6px;
padding: 10px 16px;
border-radius: var(--radius);
border: 1px solid var(--border-strong);
background: var(--bg-elev-2);
color: var(--text);
font-weight: 600;
transition: transform var(--transition), background var(--transition), border-color var(--transition);
}
.btn:hover { background: #252d39; transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn-primary {
background: var(--accent-grad);
border-color: transparent;
color: white;
box-shadow: 0 6px 16px rgba(124,92,255,.35);
}
.btn-primary:hover { filter: brightness(1.08); background: var(--accent-grad); }
.btn-ghost { background: transparent; }
.btn.small { padding: 6px 10px; font-size: 13px; }
.btn-danger { background: rgba(239,68,68,.15); border-color: rgba(239,68,68,.4); color: #fda4a4; }
.btn-danger:hover { background: rgba(239,68,68,.25); }
/* Status-Tabs */
.status-tabs {
position: relative;
max-width: var(--container);
margin: 0 auto;
padding: 4px 18px 0;
display: flex; gap: 4px; flex-wrap: wrap;
}
.tab {
background: transparent;
border: none;
color: var(--text-dim);
padding: 12px 14px;
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
border-bottom: 2px solid transparent;
font-weight: 600;
display: inline-flex; gap: 6px; align-items: center;
transition: color var(--transition), border-color var(--transition), background var(--transition);
}
.tab:hover { color: var(--text); }
.tab.active {
color: var(--text);
border-bottom-color: var(--accent);
background: rgba(124,92,255,.06);
}
.tab .count {
background: var(--bg-elev-2);
color: var(--text-dim);
font-size: 11px;
padding: 2px 7px;
border-radius: 999px;
border: 1px solid var(--border);
}
/* ===================== Container ===================== */
.container {
max-width: var(--container);
margin: 0 auto;
padding: 20px 24px 80px;
}
/* Chips (Genres/Tags) */
.chips {
display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
margin-bottom: 10px;
}
.chip-label {
color: var(--text-muted);
font-size: 12px;
margin-right: 4px;
text-transform: uppercase;
letter-spacing: .08em;
}
.chip {
background: var(--bg-elev);
border: 1px solid var(--border);
color: var(--text-dim);
font-size: 12px;
padding: 5px 10px;
border-radius: 999px;
cursor: pointer;
transition: all var(--transition);
}
.chip:hover { color: var(--text); border-color: var(--border-strong); }
.chip.active {
background: var(--accent-grad);
color: white;
border-color: transparent;
}
/* ===================== Grid / Cards ===================== */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 18px;
margin-top: 14px;
}
.grid-loading {
grid-column: 1 / -1;
display: flex; align-items: center; justify-content: center;
gap: 12px;
padding: 60px 20px;
color: var(--text-dim);
}
.spinner {
width: 22px; height: 22px;
border: 2px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.card {
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
display: flex; flex-direction: column;
cursor: pointer;
transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition);
}
.card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
border-color: var(--border-strong);
}
.cover {
aspect-ratio: 2/3;
background: linear-gradient(135deg, #1f2630, #2a323e);
position: relative;
overflow: hidden;
}
.cover img {
width: 100%; height: 100%; object-fit: cover;
display: block;
transition: transform var(--transition);
}
.card:hover .cover img { transform: scale(1.04); }
.cover-fallback {
position: absolute; inset: 0;
display: grid; place-items: center;
color: var(--text-muted);
font-size: 32px;
text-align: center;
padding: 12px;
font-weight: 600;
}
.card-body {
padding: 12px;
display: flex; flex-direction: column; gap: 6px;
flex: 1;
}
.card-title {
font-size: 14px;
font-weight: 700;
line-height: 1.25;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
min-height: 2.5em;
}
.card-sub {
font-size: 12px; color: var(--text-muted);
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-meta {
margin-top: auto;
display: flex; align-items: center; justify-content: space-between;
gap: 8px;
}
.status-badge {
font-size: 11px;
padding: 3px 8px;
border-radius: 999px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .04em;
}
.status-plan { background: rgba(148,163,184,.15); color: var(--plan); }
.status-reading { background: rgba(56,189,248,.15); color: var(--reading); }
.status-done { background: rgba(34,197,94,.15); color: var(--done); }
.status-hold { background: rgba(245,158,11,.15); color: var(--hold); }
.status-dropped { background: rgba(239,68,68,.15); color: var(--dropped); }
.rating { font-size: 12px; color: #facc15; font-weight: 700; }
.kind-badge {
position: absolute;
top: 8px; left: 8px;
background: rgba(0,0,0,.65);
backdrop-filter: blur(6px);
color: white;
font-size: 11px;
font-weight: 700;
padding: 3px 8px;
border-radius: 999px;
}
.progress-bar {
height: 4px;
background: var(--bg-elev-2);
overflow: hidden;
}
.progress-bar > div {
height: 100%;
background: var(--accent-grad);
transition: width var(--transition);
}
/* Empty state */
.empty {
text-align: center;
padding: 80px 20px;
color: var(--text-dim);
}
.empty h2 { color: var(--text); margin-bottom: 6px; }
/* ===================== Modal ===================== */
.modal { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center; }
.modal-backdrop {
position: absolute; inset: 0;
background: rgba(0,0,0,.55);
backdrop-filter: blur(4px);
}
.modal-card {
position: relative;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius);
width: min(680px, 92vw);
max-height: 92vh;
overflow: auto;
box-shadow: var(--shadow-lg);
}
.modal-header {
display: flex; align-items: center; justify-content: space-between;
padding: 18px 20px;
border-bottom: 1px solid var(--border);
position: sticky; top: 0; background: var(--bg-elev); z-index: 1;
}
.modal-header h2 { margin: 0; font-size: 18px; }
.icon-btn {
background: transparent;
border: none;
color: var(--text-dim);
font-size: 18px;
padding: 6px 10px;
border-radius: 8px;
}
.icon-btn:hover { background: var(--bg-elev-2); color: var(--text); }
.form { padding: 18px 20px; display: flex; flex-direction: column; gap: 14px; }
.row { display: flex; gap: 12px; flex-wrap: wrap; }
.field { display: flex; flex-direction: column; gap: 6px; min-width: 140px; flex: 1; }
.field.grow { flex: 1 1 220px; }
.field span {
font-size: 12px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: .06em;
font-weight: 600;
}
.field input, .field select, .field textarea {
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
padding: 10px 12px;
border-radius: var(--radius-sm);
outline: none;
transition: border-color var(--transition), box-shadow var(--transition);
}
.field input:focus, .field select:focus, .field textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(124,92,255,.18);
}
.field textarea { resize: vertical; }
.modal-footer {
display: flex; justify-content: flex-end; gap: 10px;
border-top: 1px solid var(--border);
padding: 14px 20px;
position: sticky; bottom: 0;
background: var(--bg-elev);
}
/* Conditional field rows by media kind */
.row[data-when] { display: none; }
body.kind-book .row[data-when="book"] { display: flex; }
body.kind-series .row[data-when="series"] { display: flex; }
/* ===================== Drawer (Detail) ===================== */
.drawer { position: fixed; inset: 0; z-index: 60; }
.drawer-backdrop {
position: absolute; inset: 0;
background: rgba(0,0,0,.55);
backdrop-filter: blur(4px);
}
.drawer-card {
position: absolute; right: 0; top: 0; bottom: 0;
width: min(680px, 95vw);
background: var(--bg-elev);
border-left: 1px solid var(--border);
overflow-y: auto;
box-shadow: var(--shadow-lg);
animation: slide-in .25s ease;
}
@keyframes slide-in {
from { transform: translateX(40px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.drawer-close {
position: absolute; top: 14px; right: 14px; z-index: 2;
background: rgba(0,0,0,.5);
border-radius: 999px;
}
.detail-hero {
position: relative;
padding: 28px 24px 20px;
background: linear-gradient(135deg, rgba(124,92,255,.2), rgba(34,211,238,.15));
border-bottom: 1px solid var(--border);
}
.detail-hero .row-top { display: flex; gap: 18px; align-items: flex-start; }
.detail-hero .cover-mini {
width: 110px; aspect-ratio: 2/3;
background: var(--bg-elev-2);
border-radius: var(--radius-sm);
overflow: hidden;
flex-shrink: 0;
box-shadow: var(--shadow-md);
}
.detail-hero .cover-mini img { width: 100%; height: 100%; object-fit: cover; }
.detail-hero .meta-title h2 {
margin: 0 0 4px; font-size: 24px; line-height: 1.15;
}
.detail-hero .meta-title .sub { color: var(--text-dim); font-size: 14px; }
.detail-hero .badges { margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.detail-section { padding: 18px 24px; border-bottom: 1px solid var(--border); }
.detail-section h3 { margin: 0 0 10px; font-size: 13px; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); }
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px,1fr)); gap: 12px; }
.stat-grid .stat {
background: var(--bg-elev-2);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 12px;
}
.stat .label { font-size: 11px; text-transform: uppercase; color: var(--text-muted); letter-spacing: .06em; }
.stat .value { font-size: 18px; font-weight: 700; margin-top: 2px; }
.progress-controls {
display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: 10px;
}
.progress-controls input[type="number"] {
width: 80px;
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
padding: 8px 10px;
border-radius: var(--radius-sm);
}
.actions-row {
display: flex; gap: 8px; flex-wrap: wrap;
padding: 18px 24px;
}
/* Tags in Detail */
.tag-list { display: flex; gap: 6px; flex-wrap: wrap; }
.tag-list .chip { cursor: default; }
/* Rating slider */
.rating-display {
display: inline-flex; align-items: center; gap: 4px;
font-weight: 700;
color: #facc15;
}
.rating-display .star { font-size: 18px; }
/* ===================== Stats modal ===================== */
.stats-card { width: min(560px, 92vw); }
.stat-block { padding: 16px 20px; border-bottom: 1px solid var(--border); }
.stat-block:last-child { border-bottom: none; }
.stat-block h3 { margin: 0 0 10px; font-size: 13px; text-transform: uppercase; color: var(--text-muted); letter-spacing: .08em; }
.bar-row { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.bar-row .label { width: 110px; color: var(--text-dim); font-size: 13px; }
.bar-row .bar {
flex: 1; height: 14px; background: var(--bg); border-radius: 8px; overflow: hidden;
border: 1px solid var(--border);
}
.bar-row .bar > div {
height: 100%;
background: var(--accent-grad);
transition: width var(--transition);
}
.bar-row .count { font-weight: 700; min-width: 30px; text-align: right; }
.big-number {
font-size: 32px; font-weight: 800;
background: var(--accent-grad);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
/* ===================== Toast ===================== */
.toast {
position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
background: var(--bg-elev-2);
border: 1px solid var(--border-strong);
color: var(--text);
padding: 12px 18px;
border-radius: var(--radius);
box-shadow: var(--shadow-md);
z-index: 100;
animation: toast-in .25s ease;
}
@keyframes toast-in {
from { opacity: 0; transform: translate(-50%, 10px); }
to { opacity: 1; transform: translate(-50%, 0); }
}
/* ===================== Responsive ===================== */
@media (max-width: 640px) {
.hero-inner { padding: 18px 16px 10px; }
.container { padding: 14px 16px 60px; }
.grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
.brand-text h1 { font-size: 22px; }
.status-tabs { padding: 4px 12px 0; }
.tab { padding: 10px 10px; font-size: 13px; }
}
/* ===================== Footer ===================== */
.footer {
max-width: var(--container);
margin: 0 auto;
padding: 24px 24px 40px;
display: flex; justify-content: space-between; align-items: center;
color: var(--text-muted);
font-size: 12px;
border-top: 1px solid var(--border);
margin-top: 40px;
}
.footer span:last-child { color: var(--text-dim); }