- Bump version to 0.5.0-beta - Add Apache-2.0 LICENSE (official text) + NOTICE - Add Docker support: multi-stage Dockerfile + docker-compose * non-root user, tini PID 1, gunicorn + uvicorn workers * healthcheck, OCI labels, persistent /data volume - Add GitHub Actions CI (lint/import-check + Docker smoke-test) - Backend polish: * modern lifespan handler (replaces deprecated on_event) * explicit sort validation (400 instead of 500) * title min_length=1 schema validation * configurable data dir + http timeout via env * avg_rating helper extracted (DRY) - Frontend polish: * beta badge in title, footer with live version * loading spinner on initial fetch * updated README, badges, config table, roadmap
589 lines
17 KiB
CSS
589 lines
17 KiB
CSS
/* ========================================================================
|
|
WatchStack — Theme
|
|
- Dark default (wie MAL's moderner Look)
|
|
- 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); }
|