- 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
236 lines
9.3 KiB
HTML
236 lines
9.3 KiB
HTML
<!doctype html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>WatchStack – Watchlist für Bücher & Serien</title>
|
||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg">
|
||
<link rel="stylesheet" href="/static/css/style.css">
|
||
</head>
|
||
<body>
|
||
<!-- ===================== Header / Hero ===================== -->
|
||
<header class="hero">
|
||
<div class="hero-bg"></div>
|
||
<div class="hero-inner">
|
||
<div class="brand">
|
||
<div class="logo">W</div>
|
||
<div class="brand-text">
|
||
<h1>WatchStack <span class="version-badge">beta</span></h1>
|
||
<p>Deine Watchlist für <strong>Bücher</strong>, <strong>Serien</strong> & mehr.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="hero-search">
|
||
<input id="search-input" type="search" placeholder="Titel, Autor oder Genre suchen…" autocomplete="off">
|
||
<select id="filter-kind">
|
||
<option value="">Alle Medien</option>
|
||
<option value="book">Nur Bücher</option>
|
||
<option value="series">Nur Serien</option>
|
||
</select>
|
||
<select id="filter-status">
|
||
<option value="">Alle Status</option>
|
||
<option value="plan">Geplant</option>
|
||
<option value="reading">Laufend</option>
|
||
<option value="done">Abgeschlossen</option>
|
||
<option value="hold">Pausiert</option>
|
||
<option value="dropped">Abgebrochen</option>
|
||
</select>
|
||
<select id="filter-sort">
|
||
<option value="updated_desc">Zuletzt geändert</option>
|
||
<option value="title_asc">Titel A–Z</option>
|
||
<option value="title_desc">Titel Z–A</option>
|
||
<option value="rating_desc">Beste Bewertung</option>
|
||
<option value="rating_asc">Schlechteste Bewertung</option>
|
||
<option value="year_desc">Neueste zuerst</option>
|
||
<option value="year_asc">Älteste zuerst</option>
|
||
</select>
|
||
<button id="open-stats" class="btn btn-ghost" title="Statistik anzeigen">📊</button>
|
||
<button id="open-add" class="btn btn-primary">+ Neu</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Status-Tabs (MAL-Style) -->
|
||
<nav class="status-tabs" id="status-tabs">
|
||
<button class="tab active" data-status="">Alle</button>
|
||
<button class="tab" data-status="plan">Geplant <span class="count" id="cnt-plan">0</span></button>
|
||
<button class="tab" data-status="reading">Laufend <span class="count" id="cnt-reading">0</span></button>
|
||
<button class="tab" data-status="done">Abgeschlossen <span class="count" id="cnt-done">0</span></button>
|
||
<button class="tab" data-status="hold">Pausiert <span class="count" id="cnt-hold">0</span></button>
|
||
<button class="tab" data-status="dropped">Abgebrochen <span class="count" id="cnt-dropped">0</span></button>
|
||
</nav>
|
||
</header>
|
||
|
||
<!-- ===================== Main ===================== -->
|
||
<main class="container">
|
||
<!-- Genre/Tag-Chips (Filter) -->
|
||
<div class="chips" id="genre-chips" aria-label="Genres">
|
||
<span class="chip-label">Genres:</span>
|
||
<!-- per JS befüllt -->
|
||
</div>
|
||
<div class="chips" id="tag-chips" aria-label="Tags">
|
||
<span class="chip-label">Tags:</span>
|
||
<!-- per JS befüllt -->
|
||
</div>
|
||
|
||
<!-- Listen-Grid -->
|
||
<section id="grid" class="grid">
|
||
<div class="grid-loading">
|
||
<div class="spinner"></div>
|
||
<span>Lade Watchlist…</span>
|
||
</div>
|
||
</section>
|
||
|
||
<div id="empty-state" class="empty hidden">
|
||
<h2>Noch nichts hier.</h2>
|
||
<p>Klick auf <strong>+ Neu</strong> und leg deinen ersten Eintrag an.</p>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- ===================== Modal: Add / Edit ===================== -->
|
||
<div class="modal" id="modal-add" hidden>
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-card">
|
||
<header class="modal-header">
|
||
<h2 id="modal-title">Neuer Eintrag</h2>
|
||
<button class="icon-btn" data-close aria-label="Schließen">✕</button>
|
||
</header>
|
||
<form id="form-add" class="form">
|
||
<input type="hidden" name="id">
|
||
|
||
<div class="row">
|
||
<label class="field grow">
|
||
<span>Art</span>
|
||
<select name="kind" required>
|
||
<option value="book">📚 Buch</option>
|
||
<option value="series">📺 Serie</option>
|
||
</select>
|
||
</label>
|
||
<label class="field">
|
||
<span>Status</span>
|
||
<select name="status">
|
||
<option value="plan">Geplant</option>
|
||
<option value="reading">Laufend</option>
|
||
<option value="done">Abgeschlossen</option>
|
||
<option value="hold">Pausiert</option>
|
||
<option value="dropped">Abgebrochen</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<label class="field grow">
|
||
<span>Titel*</span>
|
||
<input name="title" required maxlength="200">
|
||
<button type="button" id="btn-lookup" class="btn btn-ghost small" title="Online nachschlagen">🔍 Nachschlagen</button>
|
||
</label>
|
||
<label class="field">
|
||
<span>Jahr</span>
|
||
<input name="release_year" type="number" min="1800" max="2200">
|
||
</label>
|
||
</div>
|
||
|
||
<!-- Buch-Felder -->
|
||
<div class="row" data-when="book">
|
||
<label class="field grow">
|
||
<span>Autor</span>
|
||
<input name="author" maxlength="200">
|
||
</label>
|
||
<label class="field">
|
||
<span>ISBN</span>
|
||
<input name="isbn" maxlength="20" placeholder="z.B. 9783608938284">
|
||
</label>
|
||
</div>
|
||
<div class="row" data-when="book">
|
||
<label class="field"><span>Bände</span><input name="total_volumes" type="number" min="0"></label>
|
||
<label class="field"><span>Kapitel</span><input name="total_chapters" type="number" min="0"></label>
|
||
<label class="field"><span>Seiten</span><input name="total_pages" type="number" min="0"></label>
|
||
</div>
|
||
|
||
<!-- Serien-Felder -->
|
||
<div class="row" data-when="series">
|
||
<label class="field grow">
|
||
<span>Sender / Streaming</span>
|
||
<input name="network" maxlength="120" placeholder="z.B. Netflix, ARD, …">
|
||
</label>
|
||
<label class="field"><span>Staffeln</span><input name="total_seasons" type="number" min="0"></label>
|
||
<label class="field"><span>Episoden</span><input name="total_episodes" type="number" min="0"></label>
|
||
</div>
|
||
<div class="row" data-when="series">
|
||
<label class="field"><span>Start</span><input name="start_date" type="date"></label>
|
||
<label class="field"><span>Ende</span><input name="end_date" type="date"></label>
|
||
<label class="field"><span>Staffel aktuell</span><input name="season_watching" type="number" min="0"></label>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<label class="field grow">
|
||
<span>Cover-URL</span>
|
||
<input name="cover_url" placeholder="https://… (oder per Nachschlagen automatisch)">
|
||
</label>
|
||
<label class="field">
|
||
<span>Bewertung (0–10)</span>
|
||
<input name="rating" type="number" step="0.1" min="0" max="10">
|
||
</label>
|
||
</div>
|
||
|
||
<label class="field">
|
||
<span>Beschreibung</span>
|
||
<textarea name="description" rows="3"></textarea>
|
||
</label>
|
||
|
||
<div class="row">
|
||
<label class="field grow">
|
||
<span>Genres (Komma-getrennt)</span>
|
||
<input name="genres" placeholder="Fantasy, Sci-Fi, Roman">
|
||
</label>
|
||
<label class="field grow">
|
||
<span>Tags (Komma-getrennt)</span>
|
||
<input name="tags" placeholder="long-runner, klassiker">
|
||
</label>
|
||
</div>
|
||
|
||
<label class="field">
|
||
<span>Notizen</span>
|
||
<textarea name="notes" rows="2"></textarea>
|
||
</label>
|
||
|
||
<footer class="modal-footer">
|
||
<button type="button" class="btn btn-ghost" data-close>Abbrechen</button>
|
||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||
</footer>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===================== Drawer: Detail ===================== -->
|
||
<aside class="drawer" id="drawer" hidden>
|
||
<div class="drawer-backdrop" data-close-drawer></div>
|
||
<div class="drawer-card">
|
||
<button class="icon-btn drawer-close" data-close-drawer aria-label="Schließen">✕</button>
|
||
<div id="drawer-content"><!-- per JS --></div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ===================== Modal: Stats ===================== -->
|
||
<div class="modal" id="modal-stats" hidden>
|
||
<div class="modal-backdrop" data-close-stats></div>
|
||
<div class="modal-card stats-card">
|
||
<header class="modal-header">
|
||
<h2>Deine Statistik</h2>
|
||
<button class="icon-btn" data-close-stats aria-label="Schließen">✕</button>
|
||
</header>
|
||
<div id="stats-body"><!-- per JS --></div>
|
||
</div>
|
||
</div>
|
||
|
||
<footer class="footer">
|
||
<span>WatchStack <span id="version">…</span> · Apache 2.0</span>
|
||
<span>Made for readers & binge-watchers</span>
|
||
</footer>
|
||
|
||
<!-- ===================== Toast ===================== -->
|
||
<div id="toast" class="toast" hidden></div>
|
||
|
||
<script src="/static/js/app.js" defer></script>
|
||
</body>
|
||
</html>
|