Initial commit: WatchStack v0.1.0

- FastAPI + SQLite backend with REST API
- Polymorphic Media model (book + series)
- Cover lookup via Open Library
- Vanilla HTML/CSS/JS frontend (MAL-inspired)
- Dark theme, status tabs, drawer detail, stats modal
- 8 seed demo entries
This commit is contained in:
ki
2026-07-21 23:11:14 +02:00
commit 67f09363f3
16 changed files with 2320 additions and 0 deletions
+227
View File
@@ -0,0 +1,227 @@
<!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</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 AZ</option>
<option value="title_desc">Titel ZA</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">
<!-- Cards per JS -->
</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 (010)</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>
<!-- ===================== Toast ===================== -->
<div id="toast" class="toast" hidden></div>
<script src="/static/js/app.js" defer></script>
</body>
</html>