Initial commit

This commit is contained in:
scoped
2026-05-15 02:41:52 +00:00
commit e2de5f705a
73 changed files with 9965 additions and 0 deletions

4
dist/sortarr/web/Dockerfile vendored Normal file
View File

@@ -0,0 +1,4 @@
FROM nginx:1.27-alpine
COPY src /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

20
dist/sortarr/web/nginx.conf vendored Normal file
View File

@@ -0,0 +1,20 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location /api/ {
proxy_pass http://backend:8099/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
try_files $uri $uri/ /index.html;
}
}

1006
dist/sortarr/web/src/app.js vendored Normal file

File diff suppressed because it is too large Load Diff

158
dist/sortarr/web/src/index.html vendored Normal file
View File

@@ -0,0 +1,158 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sortarr</title>
<link rel="stylesheet" href="/styles.css">
<link rel="stylesheet" href="/themes.css">
<link rel="stylesheet" href="/api/theme/custom.css">
</head>
<body>
<div class="app-shell">
<aside class="sidebar">
<div class="brand">
<span class="brand-mark">S</span>
<div>
<strong>Sortarr</strong>
</div>
</div>
<nav>
<a href="#/overview" data-route="overview" class="active">Overview</a>
<a href="#/library" data-route="library">Library</a>
<a href="#/downloads" data-route="downloads">Downloads</a>
<a href="#/releases" data-route="releases">Releases</a>
<a href="#/tools" data-route="tools">Tools</a>
<a href="#/settings" data-route="settings">Settings</a>
</nav>
</aside>
<main>
<header class="topbar">
<div>
<h1>Media Dashboard</h1>
<p id="statusLine">Connecting to backend...</p>
</div>
<div class="actions">
<button id="scanButton">Run scan</button>
<button id="refreshButton">Refresh</button>
</div>
</header>
<section id="page-overview" class="page active">
<div class="grid overview-grid">
<article class="panel">
<h2>Storage</h2>
<div id="storageCards" class="storage-list"></div>
</article>
<article class="panel">
<h2>File Types</h2>
<div id="extensionBreakdown" class="bars"></div>
</article>
<article class="panel">
<h2>Activity</h2>
<div id="events" class="event-list"></div>
</article>
</div>
</section>
<section id="page-library" class="page panel">
<div class="section-head">
<div>
<h2>Library Contents</h2>
<p id="libraryStatus" class="muted"></p>
</div>
<div class="actions">
<input id="libraryFilter" placeholder="Filter library">
<button id="libraryScanButton">Scan library</button>
</div>
</div>
<div id="libraryTabs" class="segmented"></div>
<div id="libraryGrid" class="poster-grid"></div>
<div id="libraryPager" class="pager"></div>
</section>
<section id="page-downloads" class="page panel">
<div class="section-head">
<div>
<h2>Downloads</h2>
<p id="downloadsStatus" class="muted"></p>
</div>
<button id="downloadsRefresh">Refresh downloads</button>
</div>
<div class="downloads-layout">
<article>
<h3>Organizer Queue</h3>
<div id="organizerSummary" class="queue-summary"></div>
<div id="organizerRows" class="download-list"></div>
</article>
<article>
<h3>Current /downloads Files</h3>
<div id="downloadRows" class="download-list"></div>
</article>
<article>
<h3>Recently Planned or Moved</h3>
<div id="recentDownloadRows" class="download-list"></div>
</article>
</div>
</section>
<section id="page-releases" class="page panel">
<div class="section-head">
<h2>Missing & Upcoming</h2>
<button id="releaseRefresh">Refresh releases</button>
</div>
<div id="releaseRows" class="release-grid"></div>
</section>
<section id="page-tools" class="page panel">
<div class="section-head">
<h2>Library Tools</h2>
<span class="muted">Uses the cached library index. Run a library scan first if results look stale.</span>
</div>
<div class="tool-grid">
<button id="transcoderPlanButton">Build transcode queue</button>
<button id="transcoderRunButton">Run next transcode</button>
<button id="subtitleAuditButton">Run subtitle audit</button>
<button id="duplicateButton">Duplicate finder</button>
</div>
<div id="toolOutput" class="tool-output"></div>
</section>
<section id="page-settings" class="page panel">
<div class="section-head">
<div>
<h2>Settings</h2>
<p class="muted">Runtime settings are saved in /data/state.json and override TOML/env values for this backend process.</p>
</div>
<div class="actions">
<button id="tmdbTestButton" type="button">TMDb API Test</button>
<button id="settingsSaveButton" type="button">Save settings</button>
</div>
</div>
<div id="settingsNotice" class="settings-notice" role="status" aria-live="polite"></div>
<section class="settings-hero">
<div>
<h3>Dashboard Theme</h3>
<p class="muted">Choose the local dashboard theme here. The default theme below is also configurable and saved on the server.</p>
</div>
<div id="themeOptions" class="theme-options"></div>
</section>
<div id="settingsForm" class="settings-stack"></div>
<details open>
<summary>Raw config</summary>
<pre id="configView"></pre>
</details>
</section>
</main>
</div>
<div id="mediaModal" class="modal">
<div class="modal-backdrop"></div>
<div class="modal-shell">
<button id="closeModal" class="modal-close">&times;</button>
<div id="modalBody" class="modal-content"></div>
</div>
</div>
<div id="toastHost" class="toast-host" aria-live="polite"></div>
<script src="/app.js"></script>
</body>
</html>

822
dist/sortarr/web/src/styles.css vendored Normal file
View File

@@ -0,0 +1,822 @@
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: var(--font);
font-size: calc(15px - (var(--compact, 0) * 1px));
}
.app-shell { display: grid; grid-template-columns: 260px 1fr; min-height: 100vh; }
.sidebar {
border-right: 1px solid var(--border);
background: var(--surface);
padding: calc(20px * var(--density));
position: sticky;
top: 0;
height: 100vh;
}
.brand { display: flex; gap: 12px; align-items: center; margin-bottom: 28px; }
.brand-mark {
display: grid;
place-items: center;
width: 38px;
height: 38px;
border-radius: var(--radius);
background: var(--accent);
color: var(--bg);
font-weight: 800;
}
.brand small, #statusLine, .muted { color: var(--muted); }
nav { display: grid; gap: 6px; }
nav a {
color: var(--muted);
text-decoration: none;
padding: 10px 12px;
border-radius: var(--radius);
}
nav a.active, nav a:hover { background: var(--surface-2); color: var(--text); }
.page { display: none; }
.page.active { display: block; }
select, input, button {
border: 1px solid var(--border);
background: var(--surface-2);
color: var(--text);
border-radius: var(--radius);
padding: 10px 12px;
}
button { cursor: pointer; }
button:hover { border-color: var(--accent); }
button:disabled { cursor: wait; opacity: .62; }
main { padding: 24px; display: grid; gap: 24px; align-content: start; }
.topbar, .section-head { display: flex; justify-content: space-between; gap: 16px; align-items: center; }
h1, h2, h3, p { margin: 0; }
h1 { font-size: 28px; }
h2 { font-size: 17px; }
h3 { font-size: 14px; color: var(--muted); font-weight: 700; }
.actions { display: flex; gap: 10px; }
.grid { display: grid; gap: 16px; }
.overview-grid { grid-template-columns: 1.3fr 1fr 1fr; }
.panel {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: calc(18px * var(--density));
}
.storage-list, .event-list, .download-list, .bars { display: grid; gap: 12px; margin-top: 16px; }
.storage-card { display: grid; gap: 8px; }
.meter { height: 10px; background: var(--surface-2); border-radius: 999px; overflow: hidden; }
.meter span { display: block; height: 100%; background: var(--accent); }
.kv { display: flex; justify-content: space-between; color: var(--muted); font-size: 13px; }
.bar-row { display: grid; grid-template-columns: 72px 1fr 44px; gap: 10px; align-items: center; }
.event { border-left: 3px solid var(--accent); padding-left: 10px; color: var(--muted); }
.event.error { border-color: var(--bad); }
.segmented {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 16px;
}
.segmented button {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
}
.segmented button.active {
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 18%, var(--surface-2));
}
.segmented span {
color: var(--muted);
font-size: 12px;
}
.table-wrap { overflow: auto; margin-top: 16px; max-height: 68vh; }
table { width: 100%; border-collapse: collapse; min-width: 720px; }
th, td { border-bottom: 1px solid var(--border); padding: 10px; text-align: left; }
th { color: var(--muted); font-weight: 600; }
td:first-child {
max-width: 520px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.download, .release {
display: grid;
gap: 8px;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 12px;
background: var(--surface-2);
}
.download.warning { border-color: var(--warn); }
.poster-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 16px;
margin-top: 18px;
}
.poster-card {
display: grid;
gap: 8px;
min-width: 0;
padding: 0;
border: 0;
background: transparent;
color: var(--text);
text-align: left;
}
.poster-card.active .poster,
.poster-card:hover .poster {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.poster {
display: grid;
place-items: center;
aspect-ratio: 2 / 3;
overflow: hidden;
border-radius: var(--radius);
background: var(--surface-2);
border: 1px solid var(--border);
}
.poster img {
width: 100%;
height: 100%;
object-fit: cover;
}
.poster-placeholder {
display: grid;
place-items: center;
width: 100%;
height: 100%;
background: linear-gradient(135deg, var(--surface-2), var(--surface));
color: var(--accent);
font-size: 42px;
font-weight: 800;
}
.poster-card strong,
.poster-card small {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.poster-card small { color: var(--muted); }
.media-detail {
margin-top: 22px;
}
.detail-shell {
display: grid;
grid-template-columns: 190px minmax(0, 1fr);
gap: 20px;
border-top: 1px solid var(--border);
padding-top: 22px;
}
.detail-poster {
align-self: start;
}
.detail-body {
display: grid;
gap: 16px;
min-width: 0;
}
.detail-block,
.season-list {
display: grid;
gap: 12px;
}
.season-list details {
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface-2);
padding: 10px 12px;
}
.season-list summary {
cursor: pointer;
font-weight: 700;
}
.episode-list {
display: grid;
gap: 8px;
margin-top: 12px;
}
.episode {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 12px;
padding: 10px;
border-radius: var(--radius);
background: var(--surface);
border-left: 3px solid var(--good);
}
.episode.missing { border-left-color: var(--bad); }
.episode.upcoming { border-left-color: var(--warn); }
.episode p {
margin-top: 5px;
line-height: 1.35;
}
.episode-actions {
display: flex;
align-items: center;
gap: 8px;
}
.probe-output {
display: grid;
gap: 12px;
}
.stream-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
}
.stream-grid section {
display: grid;
align-content: start;
gap: 8px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface-2);
padding: 12px;
}
.stream-row {
display: grid;
gap: 6px;
padding-top: 8px;
border-top: 1px solid var(--border);
}
.track-actions {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.track-actions button {
padding: 6px 8px;
font-size: 12px;
}
.downloads-layout {
display: grid;
grid-template-columns: minmax(320px, 1fr) minmax(0, 1.2fr) minmax(320px, .8fr);
gap: 18px;
margin-top: 18px;
}
.downloads-layout article { min-width: 0; }
.queue-summary {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(78px, 1fr));
gap: 8px;
margin-top: 12px;
}
.queue-summary span {
display: grid;
gap: 2px;
padding: 9px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
color: var(--muted);
font-size: 12px;
}
.queue-summary strong {
color: var(--text);
font-size: 18px;
}
.download small, .download span {
overflow-wrap: anywhere;
}
.download.bundle {
background: var(--surface);
}
.bundle-head {
display: flex;
justify-content: space-between;
gap: 12px;
align-items: start;
}
.bundle-head div {
display: grid;
gap: 4px;
min-width: 0;
}
.subtitle-chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.subtitle-chips span {
border: 1px solid var(--border);
border-radius: 999px;
background: var(--surface-2);
color: var(--muted);
padding: 4px 8px;
font-size: 12px;
}
.download.loose {
opacity: .82;
}
.organizer-card {
border-left: 3px solid var(--accent);
}
.organizer-card.needs-review,
.organizer-card.dry-run {
border-left-color: var(--warn);
}
.organizer-card.low-confidence,
.organizer-card.skipped {
border-left-color: var(--bad);
}
.organizer-card.moved {
border-left-color: var(--good);
}
.confidence {
border: 1px solid var(--border);
border-radius: 999px;
padding: 4px 8px;
font-size: 12px;
white-space: nowrap;
}
.confidence.good { color: var(--good); }
.confidence.warn { color: var(--warn); }
.confidence.bad { color: var(--bad); }
.plan-paths {
display: grid;
gap: 5px;
}
.plan-paths small {
display: grid;
gap: 2px;
}
.plan-paths b {
color: var(--muted);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0;
}
.subtitle-list {
display: grid;
gap: 4px;
padding: 8px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface-2);
}
.plan-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.plan-actions button {
padding: 7px 10px;
}
.release-grid, .tool-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-top: 16px; }
.release img {
width: 100%;
aspect-ratio: 2 / 3;
object-fit: cover;
border-radius: var(--radius);
}
.release.missing { border-color: var(--bad); }
.release.upcoming { border-color: var(--warn); }
.release a {
color: var(--accent);
text-decoration: none;
}
.pager {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-top: 14px;
}
.tool-output { margin-top: 18px; display: grid; gap: 12px; }
.tool-output h3 { margin: 0; font-size: 15px; }
code {
display: block;
overflow: auto;
padding: 10px;
border-radius: var(--radius);
background: var(--bg);
color: var(--muted);
}
.settings-hero {
display: grid;
grid-template-columns: minmax(220px, .45fr) minmax(0, 1fr);
gap: 18px;
align-items: start;
margin-top: 18px;
padding: 16px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
}
.settings-hero h3 { margin: 0 0 6px; }
.settings-notice {
display: none;
margin-top: 14px;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface-2);
color: var(--muted);
}
.settings-notice:not(:empty) { display: block; }
.settings-stack {
display: grid;
gap: 18px;
margin-top: 18px;
max-width: 1180px;
}
.settings-card {
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
overflow: hidden;
}
.settings-card-head {
display: flex;
justify-content: space-between;
gap: 16px;
align-items: start;
padding: 16px;
border-bottom: 1px solid var(--border);
background: var(--surface-2);
cursor: pointer;
list-style: none;
}
.settings-card-head::-webkit-details-marker {
display: none;
}
.settings-card-head h3 {
margin: 0 0 5px;
}
.settings-card-head p {
margin: 0;
}
.settings-card-head > span {
color: var(--muted);
font-size: 12px;
white-space: nowrap;
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--surface);
}
.settings-grid {
display: grid;
grid-template-columns: 1fr;
gap: 0;
}
.setting-row {
display: grid;
grid-template-columns: minmax(260px, 1fr) minmax(260px, 520px);
gap: 24px;
align-items: start;
border: 0;
border-bottom: 1px solid var(--border);
border-radius: 0;
background: var(--surface);
padding: 16px;
}
.setting-row:last-child { border-bottom: 0; }
.setting-rich {
align-items: start;
min-height: 0;
}
.setting-copy {
display: grid;
gap: 8px;
min-width: 0;
max-width: 620px;
}
.setting-copy > div {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 8px;
}
.setting-copy small,
.setting-rich small {
color: var(--muted);
line-height: 1.35;
}
.setting-path {
font-size: 12px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.setting-control {
display: flex;
justify-content: flex-end;
align-items: center;
min-width: 0;
}
.setting-control.wide {
display: grid;
gap: 6px;
justify-content: stretch;
}
.setting-row input[type="number"], .setting-row select { width: 132px; }
.setting-row input[type="text"],
.setting-row input[type="password"],
.setting-row textarea {
width: 100%;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--bg);
color: var(--text);
padding: 10px;
}
.setting-row textarea {
resize: vertical;
min-height: 70px;
}
.setting-row input[type="checkbox"] {
width: 22px;
height: 22px;
align-self: center;
}
.switch {
justify-self: end;
position: relative;
display: inline-flex;
width: 48px;
height: 28px;
}
.switch input {
position: absolute;
opacity: 0;
}
.switch span {
width: 100%;
border-radius: 999px;
background: var(--surface-2);
border: 1px solid var(--border);
transition: background .15s ease, border-color .15s ease;
}
.switch span::after {
content: "";
position: absolute;
width: 20px;
height: 20px;
top: 4px;
left: 4px;
border-radius: 50%;
background: var(--muted);
transition: transform .15s ease, background .15s ease;
}
.switch input:checked + span {
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 20%, var(--surface-2));
}
.switch input:checked + span::after {
transform: translateX(20px);
background: var(--accent);
}
.range-control {
display: grid;
align-content: center;
gap: 10px;
min-width: 0;
width: 100%;
}
.range-control input[type="range"] {
width: 100%;
accent-color: var(--accent);
}
.range-control span {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
}
.compound-control {
display: grid;
grid-template-columns: repeat(2, minmax(160px, 1fr));
gap: 10px;
width: 100%;
}
.compound-control input,
.compound-control select {
min-width: 0;
}
.compound-control label {
display: grid;
gap: 5px;
}
.compound-control .inline-check {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
color: var(--muted);
}
.compound-control .span-2 {
grid-column: 1 / -1;
}
.theme-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
gap: 10px;
max-width: 980px;
}
.theme-option {
display: grid;
grid-template-columns: 42px 1fr;
align-items: center;
gap: 10px;
text-align: left;
background: var(--surface-2);
}
.theme-option.active {
border-color: var(--accent);
box-shadow: inset 0 0 0 1px var(--accent);
}
.theme-swatch {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
width: 42px;
height: 32px;
overflow: hidden;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--bg);
}
.theme-swatch i,
.theme-swatch b,
.theme-swatch em {
display: block;
min-width: 0;
}
.theme-swatch i { background: var(--surface); }
.theme-swatch b { background: var(--surface-2); }
.theme-swatch em {
grid-column: 1 / -1;
background: var(--accent);
}
pre {
white-space: pre-wrap;
overflow: auto;
background: var(--surface-2);
border-radius: var(--radius);
padding: 14px;
color: var(--muted);
}
.toast-host {
position: fixed;
right: 18px;
bottom: 18px;
z-index: 20;
display: grid;
gap: 10px;
width: min(420px, calc(100vw - 36px));
}
.toast {
transform: translateY(8px);
opacity: 0;
padding: 12px 14px;
border: 1px solid var(--border);
border-left: 4px solid var(--accent);
border-radius: var(--radius);
background: var(--surface);
color: var(--text);
box-shadow: 0 14px 34px rgba(0, 0, 0, .22);
transition: opacity .18s ease, transform .18s ease;
}
.toast.visible {
transform: translateY(0);
opacity: 1;
}
.toast.success { border-left-color: var(--good); }
.toast.error { border-left-color: var(--bad); }
@media (max-width: 900px) {
.app-shell { grid-template-columns: 1fr; }
.sidebar { position: static; height: auto; }
.overview-grid { grid-template-columns: 1fr; }
.downloads-layout { grid-template-columns: 1fr; }
.detail-shell { grid-template-columns: 1fr; }
.detail-poster { max-width: 220px; }
.episode { grid-template-columns: 1fr; }
.topbar, .section-head { align-items: stretch; flex-direction: column; }
.actions, .pager { flex-wrap: wrap; }
.settings-hero { grid-template-columns: 1fr; }
.settings-card-head { flex-direction: column; }
.setting-row { grid-template-columns: 1fr; gap: 14px; }
.range-control { min-width: 0; }
.setting-row input[type="text"],
.setting-row input[type="password"],
.setting-row textarea,
.compound-control {
width: 100%;
}
.compound-control { grid-template-columns: 1fr; }
.bundle-head { flex-direction: column; }
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
overflow: hidden;
}
.modal.active { display: flex; align-items: center; justify-content: center; }
.modal-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.62);
backdrop-filter: blur(4px);
}
.modal-shell {
position: relative;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
width: 90%;
max-width: 900px;
max-height: 90vh;
display: flex;
flex-direction: column;
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.42);
animation: modal-slide 0.24s cubic-bezier(0, 0, 0.2, 1);
}
@keyframes modal-slide {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.modal-close {
position: absolute;
top: 12px;
right: 12px;
background: var(--surface-2);
border: none;
width: 32px;
height: 32px;
border-radius: 50%;
font-size: 20px;
display: grid;
place-items: center;
z-index: 10;
}
.modal-content {
overflow-y: auto;
padding: 24px;
}
/* Badge */
.badge {
display: inline-flex;
align-items: center;
padding: 2px 6px;
border-radius: 4px;
font-size: 11px;
font-weight: 700;
background: var(--surface-2);
color: var(--muted);
border: 1px solid var(--border);
}
.badge.accent { background: var(--accent); color: var(--bg); border: none; }
/* Multi-version Card Indicator */
.poster-card { position: relative; }
.card-badge {
position: absolute;
top: 8px;
right: 8px;
z-index: 5;
box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}
/* Track actions enhanced */
.track-actions { display: flex; gap: 6px; margin-top: 4px; }
.track-actions button {
padding: 4px 8px;
font-size: 11px;
background: var(--surface-2);
}
.track-actions button:hover { border-color: var(--accent); }
.track-actions button.danger:hover { border-color: var(--bad); color: var(--bad); }
.stream-row { padding: 8px 0; border-bottom: 1px solid var(--border); }
.stream-row:last-child { border-bottom: none; }
.circle-badge {
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
font-size: 12px;
line-height: 1;
}
.identify-panel { margin: 16px 0; background: var(--surface-2); }
.identify-results { display: grid; gap: 8px; margin-top: 16px; max-height: 300px; overflow-y: auto; }
.identify-result { display: flex; gap: 12px; align-items: flex-start; }
.mini-poster { width: 60px; border-radius: 4px; flex-shrink: 0; }
.small { font-size: 12px; }

134
dist/sortarr/web/src/themes.css vendored Normal file
View File

@@ -0,0 +1,134 @@
:root,
[data-theme="slate"] {
--bg: #111318;
--surface: #191d24;
--surface-2: #222833;
--text: #eef2f7;
--muted: #96a1af;
--border: #303846;
--accent: #60a5fa;
--good: #34d399;
--warn: #fbbf24;
--bad: #f87171;
--radius: 8px;
--density: 1;
--font: Inter, ui-sans-serif, system-ui, sans-serif;
}
[data-theme="midnight"] {
--bg: #080b12;
--surface: #121826;
--surface-2: #1b2740;
--text: #f8fafc;
--muted: #93a4bd;
--border: #293550;
--accent: #22d3ee;
--good: #4ade80;
--warn: #facc15;
--bad: #fb7185;
}
[data-theme="graphite"] {
--bg: #151515;
--surface: #202020;
--surface-2: #2b2b2b;
--text: #f5f5f5;
--muted: #b2b2b2;
--border: #3a3a3a;
--accent: #a3e635;
--good: #86efac;
--warn: #fde047;
--bad: #fca5a5;
}
[data-theme="nord"] {
--bg: #202632;
--surface: #2c3444;
--surface-2: #374155;
--text: #eceff4;
--muted: #c0c9d8;
--border: #4c566a;
--accent: #88c0d0;
--good: #a3be8c;
--warn: #ebcb8b;
--bad: #bf616a;
}
[data-theme="dracula"] {
--bg: #1d1b26;
--surface: #282a36;
--surface-2: #343746;
--text: #f8f8f2;
--muted: #c7bfdc;
--border: #44475a;
--accent: #bd93f9;
--good: #50fa7b;
--warn: #f1fa8c;
--bad: #ff5555;
}
[data-theme="solar"] {
--bg: #f4f0df;
--surface: #fffaf0;
--surface-2: #eee8d5;
--text: #273238;
--muted: #657b83;
--border: #d5cdb6;
--accent: #268bd2;
--good: #2aa198;
--warn: #b58900;
--bad: #dc322f;
}
[data-theme="forest"] {
--bg: #101812;
--surface: #18251b;
--surface-2: #213326;
--text: #eef7ed;
--muted: #a7b9a6;
--border: #314638;
--accent: #7ddf64;
--good: #22c55e;
--warn: #eab308;
--bad: #ef4444;
}
[data-theme="marine"] {
--bg: #081417;
--surface: #102225;
--surface-2: #183236;
--text: #edfdfd;
--muted: #9fc5c7;
--border: #28494e;
--accent: #2dd4bf;
--good: #5eead4;
--warn: #fcd34d;
--bad: #f97373;
}
[data-theme="ember"] {
--bg: #171111;
--surface: #241818;
--surface-2: #362221;
--text: #fff7ed;
--muted: #d7b6a2;
--border: #513530;
--accent: #fb923c;
--good: #84cc16;
--warn: #facc15;
--bad: #f43f5e;
}
[data-theme="paper"] {
--bg: #f7f8fa;
--surface: #ffffff;
--surface-2: #eef1f5;
--text: #151a22;
--muted: #5f6b7a;
--border: #d6dce5;
--accent: #2563eb;
--good: #16a34a;
--warn: #ca8a04;
--bad: #dc2626;
}