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