Initial commit
This commit is contained in:
26
README.md
Normal file
26
README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Media Tracker
|
||||||
|
|
||||||
|
A dependency-free web app for tracking movies, TV shows, books, and games.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
Open `index.html` in a browser, or serve this folder with the included static server:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
node server.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open `http://localhost:5173`.
|
||||||
|
|
||||||
|
## Data
|
||||||
|
|
||||||
|
The tracker stores items in browser `localStorage`. Use the Export and Import buttons to move or back up your collection.
|
||||||
|
|
||||||
|
## Search Sources
|
||||||
|
|
||||||
|
- Movies: Wikipedia through the MediaWiki Action API
|
||||||
|
- TV shows: TVmaze API
|
||||||
|
- Books: Open Library Search API
|
||||||
|
- Games: CheapShark API
|
||||||
|
|
||||||
|
If a search source is unavailable, add the item manually.
|
||||||
135
index.html
Normal file
135
index.html
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Media Tracker</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app-shell">
|
||||||
|
<header class="topbar">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Self-hosted collection</p>
|
||||||
|
<h1>Media Tracker</h1>
|
||||||
|
</div>
|
||||||
|
<div class="topbar-actions">
|
||||||
|
<button class="button subtle" id="exportButton" type="button">Export</button>
|
||||||
|
<label class="button subtle file-button" for="importInput">Import</label>
|
||||||
|
<input id="importInput" type="file" accept="application/json">
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="workspace" aria-live="polite">
|
||||||
|
<section class="search-panel" aria-labelledby="searchHeading">
|
||||||
|
<div class="section-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Discover</p>
|
||||||
|
<h2 id="searchHeading">Search media</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="type-tabs" role="tablist" aria-label="Media type">
|
||||||
|
<button class="type-tab active" type="button" data-type="movie">Movies</button>
|
||||||
|
<button class="type-tab" type="button" data-type="tv">TV Shows</button>
|
||||||
|
<button class="type-tab" type="button" data-type="book">Books</button>
|
||||||
|
<button class="type-tab" type="button" data-type="game">Games</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="search-form" id="searchForm">
|
||||||
|
<label for="searchInput">Search</label>
|
||||||
|
<div class="search-row">
|
||||||
|
<input id="searchInput" name="query" type="search" autocomplete="off" placeholder="Blade Runner, Silo, Dune, Hades">
|
||||||
|
<button class="button primary" type="submit">Search</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="manual-add">
|
||||||
|
<button class="text-button" id="toggleManualButton" type="button" aria-expanded="false">Add manually</button>
|
||||||
|
<form class="manual-form hidden" id="manualForm">
|
||||||
|
<div class="field-grid">
|
||||||
|
<label>
|
||||||
|
Title
|
||||||
|
<input id="manualTitle" type="text" required>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Year
|
||||||
|
<input id="manualYear" type="text" inputmode="numeric" maxlength="4">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Creator
|
||||||
|
<input id="manualCreator" type="text" placeholder="Director, author, studio">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Cover URL
|
||||||
|
<input id="manualCover" type="url" placeholder="https://">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label>
|
||||||
|
Notes
|
||||||
|
<textarea id="manualNotes" rows="3"></textarea>
|
||||||
|
</label>
|
||||||
|
<div class="manual-actions">
|
||||||
|
<button class="button primary" type="submit">Add item</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-feedback" id="searchFeedback">Ready</div>
|
||||||
|
<div class="result-grid" id="results"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="tracker-panel" aria-labelledby="trackerHeading">
|
||||||
|
<div class="section-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Collection</p>
|
||||||
|
<h2 id="trackerHeading">Tracker</h2>
|
||||||
|
</div>
|
||||||
|
<div class="summary-strip" id="summary"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filters">
|
||||||
|
<label>
|
||||||
|
Type
|
||||||
|
<select id="typeFilter">
|
||||||
|
<option value="all">All media</option>
|
||||||
|
<option value="movie">Movies</option>
|
||||||
|
<option value="tv">TV Shows</option>
|
||||||
|
<option value="book">Books</option>
|
||||||
|
<option value="game">Games</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Progress
|
||||||
|
<select id="progressFilter">
|
||||||
|
<option value="all">All progress</option>
|
||||||
|
<option value="planned">No dates</option>
|
||||||
|
<option value="active">In progress</option>
|
||||||
|
<option value="complete">Finished</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Find
|
||||||
|
<input id="collectionSearch" type="search" placeholder="Filter collection">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Sort
|
||||||
|
<select id="sortSelect">
|
||||||
|
<option value="recent">Recently added</option>
|
||||||
|
<option value="updated">Recently updated</option>
|
||||||
|
<option value="finished">Recently finished</option>
|
||||||
|
<option value="started">Recently started</option>
|
||||||
|
<option value="title">Title</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="collection-list" id="collection"></div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toast hidden" id="toast" role="status"></div>
|
||||||
|
<script src="app.js" defer></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
50
server.js
Normal file
50
server.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
const http = require("http");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const root = __dirname;
|
||||||
|
const port = Number(process.env.PORT || 5173);
|
||||||
|
|
||||||
|
const contentTypes = {
|
||||||
|
".html": "text/html; charset=utf-8",
|
||||||
|
".css": "text/css; charset=utf-8",
|
||||||
|
".js": "text/javascript; charset=utf-8",
|
||||||
|
".json": "application/json; charset=utf-8",
|
||||||
|
};
|
||||||
|
|
||||||
|
const server = http.createServer((request, response) => {
|
||||||
|
const urlPath = getUrlPath(request.url);
|
||||||
|
const requestedPath = urlPath === "/" ? "/index.html" : urlPath;
|
||||||
|
const filePath = path.resolve(root, `.${requestedPath}`);
|
||||||
|
|
||||||
|
if (!filePath.startsWith(root)) {
|
||||||
|
response.writeHead(403);
|
||||||
|
response.end("Forbidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readFile(filePath, (error, data) => {
|
||||||
|
if (error) {
|
||||||
|
response.writeHead(404);
|
||||||
|
response.end("Not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.writeHead(200, {
|
||||||
|
"Content-Type": contentTypes[path.extname(filePath)] || "application/octet-stream",
|
||||||
|
});
|
||||||
|
response.end(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.log(`Media Tracker running at http://localhost:${port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
function getUrlPath(value) {
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(new URL(value, "http://localhost").pathname);
|
||||||
|
} catch (error) {
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
898
styles.css
Normal file
898
styles.css
Normal file
@@ -0,0 +1,898 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #14181c;
|
||||||
|
--bg-deep: #0b1015;
|
||||||
|
--surface: #1f2832;
|
||||||
|
--surface-raised: #26313d;
|
||||||
|
--surface-muted: #2f3a46;
|
||||||
|
--ink: #d8e0e8;
|
||||||
|
--ink-strong: #ffffff;
|
||||||
|
--muted: #8f9aa6;
|
||||||
|
--line: #34414f;
|
||||||
|
--line-strong: #4a5868;
|
||||||
|
--green: #00c030;
|
||||||
|
--green-soft: rgba(0, 192, 48, 0.16);
|
||||||
|
--orange: #ff8000;
|
||||||
|
--orange-soft: rgba(255, 128, 0, 0.16);
|
||||||
|
--blue: #40bcf4;
|
||||||
|
--blue-soft: rgba(64, 188, 244, 0.16);
|
||||||
|
--red: #ff5c57;
|
||||||
|
--shadow: 0 18px 38px rgba(0, 0, 0, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-width: 320px;
|
||||||
|
color: var(--ink);
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, #1c2630 0, var(--bg) 220px, var(--bg-deep) 100%);
|
||||||
|
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-shell {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 18px;
|
||||||
|
padding: 18px clamp(16px, 4vw, 40px);
|
||||||
|
background: rgba(20, 24, 28, 0.94);
|
||||||
|
border-bottom: 1px solid #2c3440;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar > div:first-child {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar h1,
|
||||||
|
.section-heading h2 {
|
||||||
|
margin: 0;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar h1 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
color: var(--ink-strong);
|
||||||
|
font-size: clamp(1.55rem, 3vw, 2.35rem);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar h1::before {
|
||||||
|
content: "";
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--orange);
|
||||||
|
box-shadow: 16px 0 0 var(--green), 32px 0 0 var(--blue);
|
||||||
|
margin-right: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-actions,
|
||||||
|
.manual-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-button {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#importInput {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(330px, 0.85fr) minmax(500px, 1.5fr);
|
||||||
|
gap: 24px;
|
||||||
|
width: min(1180px, calc(100% - 32px));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 24px 0 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-panel,
|
||||||
|
.tracker-panel {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading h2 {
|
||||||
|
color: var(--ink-strong);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-tabs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-deep);
|
||||||
|
border: 1px solid #26313d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-tab,
|
||||||
|
.button,
|
||||||
|
.text-button {
|
||||||
|
min-height: 38px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-tab {
|
||||||
|
padding: 8px 10px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-tab.active {
|
||||||
|
border-color: rgba(0, 192, 48, 0.5);
|
||||||
|
color: var(--ink-strong);
|
||||||
|
background: var(--green-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 14px;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.primary {
|
||||||
|
border-color: #00a82a;
|
||||||
|
background: var(--green);
|
||||||
|
color: #06250e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.primary:hover {
|
||||||
|
border-color: #20d04c;
|
||||||
|
background: #21d94e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.primary:disabled {
|
||||||
|
cursor: default;
|
||||||
|
border-color: var(--line);
|
||||||
|
background: var(--surface-muted);
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.subtle {
|
||||||
|
border-color: var(--line);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.subtle:hover,
|
||||||
|
.type-tab:hover,
|
||||||
|
.text-button:hover {
|
||||||
|
border-color: var(--line-strong);
|
||||||
|
color: var(--ink-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-button {
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form,
|
||||||
|
.manual-add,
|
||||||
|
.filters,
|
||||||
|
.result-card,
|
||||||
|
.collection-card,
|
||||||
|
.empty-state {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(31, 40, 50, 0.94);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form,
|
||||||
|
.manual-add {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manual-add {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form label,
|
||||||
|
.filters label,
|
||||||
|
.manual-form label {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #111820;
|
||||||
|
color: var(--ink-strong);
|
||||||
|
padding: 10px 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
color: #687684;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus,
|
||||||
|
button:focus-visible {
|
||||||
|
outline: 3px solid rgba(64, 188, 244, 0.22);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manual-form {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-feedback {
|
||||||
|
min-height: 26px;
|
||||||
|
margin: 12px 0 8px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 86px 1fr;
|
||||||
|
gap: 13px;
|
||||||
|
padding: 10px;
|
||||||
|
transition: border-color 160ms ease, transform 160ms ease, background 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card:hover,
|
||||||
|
.collection-card:hover {
|
||||||
|
border-color: var(--line-strong);
|
||||||
|
background: var(--surface-raised);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover,
|
||||||
|
.cover-placeholder {
|
||||||
|
width: 86px;
|
||||||
|
aspect-ratio: 2 / 3;
|
||||||
|
border-radius: 6px;
|
||||||
|
object-fit: cover;
|
||||||
|
background: #121a22;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-placeholder {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 900;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-body {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-title {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--ink-strong);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-meta,
|
||||||
|
.item-description,
|
||||||
|
.date-line,
|
||||||
|
.progress-line {
|
||||||
|
margin: 5px 0 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.84rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-line {
|
||||||
|
color: var(--green);
|
||||||
|
font-weight: 850;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-planned .progress-line {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-active .progress-line {
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-complete .progress-line {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-description {
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 23px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--surface-muted);
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 900;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.movie {
|
||||||
|
background: var(--orange-soft);
|
||||||
|
color: #ffb366;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.tv {
|
||||||
|
background: var(--green-soft);
|
||||||
|
color: #40dc63;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.book {
|
||||||
|
background: var(--blue-soft);
|
||||||
|
color: #79d4ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.game {
|
||||||
|
background: rgba(202, 152, 255, 0.18);
|
||||||
|
color: #c9a6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-actions,
|
||||||
|
.collection-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-action {
|
||||||
|
color: var(--blue);
|
||||||
|
font-size: 0.84rem;
|
||||||
|
font-weight: 800;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-action:hover {
|
||||||
|
color: #8bdaff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-strip {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(62px, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-pill {
|
||||||
|
min-width: 62px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #111820;
|
||||||
|
padding: 8px 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-pill:nth-child(1) {
|
||||||
|
border-top-color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-pill:nth-child(2) {
|
||||||
|
border-top-color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-pill:nth-child(3) {
|
||||||
|
border-top-color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-pill strong {
|
||||||
|
display: block;
|
||||||
|
color: var(--ink-strong);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-pill span {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: clamp(124px, 18vw, 164px) minmax(0, 1fr);
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
transition: border-color 160ms ease, transform 160ms ease, background 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 auto 0 0;
|
||||||
|
width: 4px;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--green);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card.progress-planned::before {
|
||||||
|
background: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card.progress-active::before {
|
||||||
|
background: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card.progress-complete::before {
|
||||||
|
background: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card .cover,
|
||||||
|
.collection-card .cover-placeholder {
|
||||||
|
grid-row: 1 / span 2;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 186px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 6px 0 0 6px;
|
||||||
|
object-fit: cover;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card .item-body {
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card .item-title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card .item-description {
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-panel {
|
||||||
|
display: grid;
|
||||||
|
grid-column: 2;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 0 16px 14px 0;
|
||||||
|
padding: 9px;
|
||||||
|
border: 1px solid rgba(64, 188, 244, 0.16);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(17, 24, 32, 0.78);
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-summary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.74rem;
|
||||||
|
font-weight: 850;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-summary span:last-child {
|
||||||
|
color: var(--blue);
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
max-height: 240px;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(120px, 1fr) repeat(2, minmax(116px, 140px));
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 6px 7px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(38, 49, 61, 0.7);
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-row:hover {
|
||||||
|
border-color: var(--line-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-row.progress-complete {
|
||||||
|
border-color: rgba(0, 192, 48, 0.34);
|
||||||
|
background: var(--green-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-row.progress-active {
|
||||||
|
border-color: rgba(255, 128, 0, 0.32);
|
||||||
|
background: var(--orange-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-title {
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-row input[type="date"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-name {
|
||||||
|
min-width: 0;
|
||||||
|
color: var(--ink-strong);
|
||||||
|
font-weight: 850;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-meta {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-runtime {
|
||||||
|
color: var(--blue);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 850;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-empty {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-load-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(140px, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-grid label,
|
||||||
|
.season-date-field {
|
||||||
|
display: grid;
|
||||||
|
gap: 5px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 850;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-grid input,
|
||||||
|
.season-date-field input {
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 7px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(180px, 1fr) auto auto;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes-input {
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-button {
|
||||||
|
border-color: rgba(255, 92, 87, 0.34);
|
||||||
|
color: #ff938f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-button:hover {
|
||||||
|
border-color: var(--red);
|
||||||
|
color: #ffd6d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
padding: 18px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state h3 {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
color: var(--ink-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
max-width: min(360px, calc(100vw - 32px));
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #e7eef5;
|
||||||
|
color: #101820;
|
||||||
|
padding: 12px 14px;
|
||||||
|
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.35);
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1060px) {
|
||||||
|
.workspace {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.topbar,
|
||||||
|
.section-heading {
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-actions,
|
||||||
|
.manual-actions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-actions .button,
|
||||||
|
.manual-actions .button,
|
||||||
|
.manual-actions select {
|
||||||
|
flex: 1 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-tabs,
|
||||||
|
.filters,
|
||||||
|
.field-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
grid-template-columns: 72px 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card .cover,
|
||||||
|
.result-card .cover-placeholder {
|
||||||
|
width: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card {
|
||||||
|
grid-template-columns: 112px minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card .cover,
|
||||||
|
.collection-card .cover-placeholder {
|
||||||
|
grid-row: 1;
|
||||||
|
min-height: 168px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card .item-body {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-list {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
max-height: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-panel {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
margin: 0 12px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-actions {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.workspace {
|
||||||
|
width: min(100% - 24px, 1180px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-tabs,
|
||||||
|
.filters,
|
||||||
|
.field-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-list {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card {
|
||||||
|
grid-template-columns: 96px minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card .cover,
|
||||||
|
.collection-card .cover-placeholder {
|
||||||
|
min-height: 146px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card .item-title-row {
|
||||||
|
align-items: start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-card .item-description {
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-row {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.season-title {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-actions,
|
||||||
|
.collection-actions {
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button,
|
||||||
|
.date-grid,
|
||||||
|
.notes-input {
|
||||||
|
width: 100%;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user