Extract CSS/JS to static files, wrap header pills, add version footer
All checks were successful
Deploy / deploy (push) Successful in 23s
All checks were successful
Deploy / deploy (push) Successful in 23s
The palette lived in a :root block duplicated across all three templates, so every reshade meant three edits — and drifted twice in practice (the favicon hardcoded its own hex; stale danger-border rgba literals survived two palette changes). Move tokens to a single static/css/base.css, derive every dim/border tint from the four source hues with color-mix, and point the favicon at one static/favicon.svg. Also splits the 1558-line dashboard.html into template + static assets: 662 lines of CSS and 396 of JS were re-sent uninlined on every load with no browser caching, which costs real time on the remote/cellular access this app is built for. Templates drop to 501/42/107 lines. Server-rendered config (timezone, script_root) now arrives via <body data-*>; wiring URL_BASE to request.script_root also fixes /api/* fetches for a future subpath mount, which the hardcoded "" would break. Header service pills now wrap instead of scrolling — they were clipped at narrow widths, and a hidden pill is a service whose status you cannot see. Verified 0 clipped and no page overflow at 320/375/700/1280. Footer gains a version and date so the running build is identifiable without trusting the Unraid Docker tab's cached container IDs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
faf25a811a
commit
18c7719cd7
12 changed files with 1396 additions and 1413 deletions
74
static/css/base.css
Normal file
74
static/css/base.css
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/* ═══════════════════════════════════════════════════════
|
||||
Media Centre — shared design tokens + base reset.
|
||||
|
||||
Single source of truth for the palette. Loaded by
|
||||
dashboard.html, login.html and settings.html; page-specific
|
||||
rules live in dashboard.css / login.css / settings.css.
|
||||
|
||||
To reshade the whole app, change the four hues below —
|
||||
every dim/border variant is derived from them via color-mix.
|
||||
═══════════════════════════════════════════════════════ */
|
||||
:root {
|
||||
/* ── Surfaces ── */
|
||||
--bg: #141416;
|
||||
--surface: #1a1a1d;
|
||||
--surface-2: #202024;
|
||||
--border: #2c2c31;
|
||||
--border-lt: #38383f;
|
||||
|
||||
/* ── Text (all ≥ 4.5:1 on every surface above) ── */
|
||||
--text: #edecf1;
|
||||
--text-2: #a8a7b0;
|
||||
--text-3: #7c7b84;
|
||||
|
||||
/* ── Accent + semantics (the four source hues) ── */
|
||||
--accent: #7c86ff;
|
||||
--ok: #68b98a;
|
||||
--warn: #d0a75a;
|
||||
--danger: #dc5b52;
|
||||
|
||||
/* ── Derived tints — never hardcode these as literals ── */
|
||||
--accent-dim: color-mix(in srgb, var(--accent) 14%, transparent);
|
||||
--ok-dim: color-mix(in srgb, var(--ok) 14%, transparent);
|
||||
--warn-dim: color-mix(in srgb, var(--warn) 14%, transparent);
|
||||
--danger-dim: color-mix(in srgb, var(--danger) 14%, transparent);
|
||||
--danger-border: color-mix(in srgb, var(--danger) 35%, transparent);
|
||||
|
||||
/* ── Type scale (6 steps, no one-offs) ── */
|
||||
--fs-xs: 0.75rem; /* 12px — smallest permitted */
|
||||
--fs-sm: 0.8125rem; /* 13px */
|
||||
--fs-base: 0.9375rem; /* 15px */
|
||||
--fs-lg: 1.125rem; /* 18px */
|
||||
--fs-xl: 1.5rem; /* 24px */
|
||||
--fs-2xl: 2rem; /* 32px */
|
||||
|
||||
/* ── Spacing / shape ── */
|
||||
--gap: 1rem;
|
||||
--gap-lg: 1.5rem;
|
||||
--radius: 10px;
|
||||
--radius-sm: 7px;
|
||||
--tap: 44px; /* minimum touch target */
|
||||
|
||||
--font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: var(--font);
|
||||
font-size: var(--fs-base);
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
a { color: var(--accent); }
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
604
static/css/dashboard.css
Normal file
604
static/css/dashboard.css
Normal file
|
|
@ -0,0 +1,604 @@
|
|||
|
||||
/* ── Header ─────────────────────────────────────────── */
|
||||
header {
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 0.75rem 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--gap);
|
||||
flex-wrap: wrap;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
min-width: 0;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.brand svg { flex-shrink: 0; display: block; }
|
||||
.brand h1 {
|
||||
margin: 0;
|
||||
font-size: var(--fs-lg);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Service status pills — wrap onto as many rows as needed so every
|
||||
service stays visible at any window width. Never scroll or clip:
|
||||
a hidden pill is a service you can't see the status of. */
|
||||
.svc-strip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
flex: 1 1 auto;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
}
|
||||
.svc-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0.35rem 0.7rem;
|
||||
min-height: 34px;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 100px;
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-2);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
transition: border-color 0.2s, color 0.2s;
|
||||
}
|
||||
.svc-pill:hover { border-color: var(--border-lt); color: var(--text); }
|
||||
.svc-pill.down { color: var(--danger); border-color: var(--danger-border); }
|
||||
.svc-name { font-weight: 600; }
|
||||
.svc-stat { color: var(--text-3); }
|
||||
.svc-pill.down .svc-stat { color: var(--danger); }
|
||||
|
||||
.status-light {
|
||||
width: 7px; height: 7px; border-radius: 50%;
|
||||
flex-shrink: 0; background: var(--text-3);
|
||||
}
|
||||
.status-light.up { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-dim); }
|
||||
.status-light.down { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-dim); }
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-left: auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.header-clock {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: var(--fs-sm);
|
||||
color: var(--text-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.icon-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.4rem;
|
||||
min-width: var(--tap);
|
||||
min-height: var(--tap);
|
||||
padding: 0 0.85rem;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-2);
|
||||
font-size: var(--fs-sm);
|
||||
font-family: inherit;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s, border-color 0.2s, background 0.2s;
|
||||
}
|
||||
.icon-btn:hover { color: var(--text); border-color: var(--border-lt); background: var(--surface); }
|
||||
|
||||
/* ── Layout ─────────────────────────────────────────── */
|
||||
main {
|
||||
padding: var(--gap-lg);
|
||||
max-width: 1360px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.section > h2 {
|
||||
margin: 0 0 0.85rem;
|
||||
font-size: var(--fs-xs);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.09em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-3);
|
||||
}
|
||||
.row {
|
||||
display: grid;
|
||||
gap: var(--gap);
|
||||
}
|
||||
.row.cols-3 { grid-template-columns: repeat(3, 1fr); }
|
||||
.row.cols-2 { grid-template-columns: repeat(2, 1fr); }
|
||||
|
||||
/* ── Card ───────────────────────────────────────────── */
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.85rem 1.1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.card-header h3 {
|
||||
margin: 0;
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
.card-header h3 a { color: inherit; text-decoration: none; }
|
||||
.card-header h3 a:hover { color: var(--accent); }
|
||||
.card-meta {
|
||||
margin-left: auto;
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-3);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.card-body { padding: 1.1rem; flex: 1; }
|
||||
.card-body > :first-child { margin-top: 0; }
|
||||
#prowlarr-body { display: flex; flex-direction: column; justify-content: center; }
|
||||
|
||||
/* ── "Right now" hero strip ─────────────────────────── */
|
||||
.hero .card { border-color: var(--border-lt); }
|
||||
.hero-figure {
|
||||
font-size: var(--fs-2xl);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.hero-sub {
|
||||
font-size: var(--fs-sm);
|
||||
color: var(--text-3);
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
|
||||
/* Now-playing list */
|
||||
.stream {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
padding: 0.55rem 0;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.stream:first-child { border-top: none; padding-top: 0; }
|
||||
.stream-icon { font-size: var(--fs-base); flex-shrink: 0; }
|
||||
.stream-info { min-width: 0; flex: 1; }
|
||||
.stream-title {
|
||||
font-size: var(--fs-sm); font-weight: 600;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.stream-user { font-size: var(--fs-xs); color: var(--text-3); }
|
||||
.live-dot {
|
||||
width: 7px; height: 7px; border-radius: 50%;
|
||||
background: var(--ok); flex-shrink: 0;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.live-dot { animation: none; }
|
||||
* { transition-duration: 0.01ms !important; }
|
||||
}
|
||||
|
||||
/* ── Meter (storage / progress) ─────────────────────── */
|
||||
.meter {
|
||||
height: 7px;
|
||||
background: var(--surface-2);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-top: 0.65rem;
|
||||
}
|
||||
.meter-fill {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
background: var(--ok);
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
.meter-fill.warn { background: var(--warn); }
|
||||
.meter-fill.danger { background: var(--danger); }
|
||||
.meter-legend {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-3);
|
||||
margin-top: 0.45rem;
|
||||
}
|
||||
|
||||
/* ── Stats (semantic definition list) ───────────────── */
|
||||
dl.stats {
|
||||
display: grid;
|
||||
gap: 0.6rem;
|
||||
margin: 0;
|
||||
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
||||
}
|
||||
dl.stats > div {
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.65rem 0.75rem;
|
||||
min-width: 0;
|
||||
}
|
||||
dl.stats dt {
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-3);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
dl.stats dd {
|
||||
margin: 0.2rem 0 0;
|
||||
font-size: var(--fs-lg);
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
dd.is-warn { color: var(--warn); }
|
||||
dd.is-danger { color: var(--danger); }
|
||||
dd.is-ok { color: var(--ok); }
|
||||
dd.is-accent { color: var(--accent); }
|
||||
|
||||
/* ── Buttons ────────────────────────────────────────── */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: var(--tap);
|
||||
padding: 0 1rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--accent);
|
||||
color: var(--bg);
|
||||
font-family: inherit;
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: opacity 0.15s, background 0.15s;
|
||||
}
|
||||
.btn:hover:not(:disabled) { opacity: 0.88; }
|
||||
.btn:disabled { opacity: 0.5; cursor: default; }
|
||||
.btn-quiet {
|
||||
background: var(--surface-2);
|
||||
border-color: var(--border);
|
||||
color: var(--text-2);
|
||||
}
|
||||
.btn-quiet:hover:not(:disabled) { color: var(--text); border-color: var(--border-lt); }
|
||||
|
||||
/* Compact add button — still meets 44px on touch (see media query) */
|
||||
.btn-add {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 32px;
|
||||
padding: 0 0.7rem;
|
||||
border: 1px solid var(--border-lt);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface-2);
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
font-size: var(--fs-xs);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
||||
}
|
||||
.btn-add:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
|
||||
/* Armed = awaiting the confirming second tap */
|
||||
.btn-add.armed {
|
||||
background: var(--warn);
|
||||
border-color: var(--warn);
|
||||
color: var(--bg);
|
||||
}
|
||||
.btn-add.done {
|
||||
background: var(--ok-dim);
|
||||
border-color: transparent;
|
||||
color: var(--ok);
|
||||
cursor: default;
|
||||
}
|
||||
.btn-add:disabled { cursor: default; }
|
||||
|
||||
/* ── Search ─────────────────────────────────────────── */
|
||||
.search-bar { display: flex; gap: 0.5rem; margin-bottom: 0.85rem; }
|
||||
.search-bar input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: var(--tap);
|
||||
padding: 0 0.85rem;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
.search-bar input::placeholder { color: var(--text-3); }
|
||||
.search-bar input:focus { border-color: var(--accent); outline: none; }
|
||||
|
||||
.search-result {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.7rem 0.85rem;
|
||||
margin-bottom: 0.85rem;
|
||||
background: var(--accent-dim);
|
||||
border: 1px solid rgba(122,162,247,0.3);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.search-result.visible { display: flex; }
|
||||
.search-result-info { min-width: 0; flex: 1; }
|
||||
.search-result-title {
|
||||
font-size: var(--fs-sm); font-weight: 600;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.search-result-meta { font-size: var(--fs-xs); color: var(--text-3); }
|
||||
.search-msg {
|
||||
font-size: var(--fs-sm);
|
||||
color: var(--text-2);
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
.search-msg.error { color: var(--danger); }
|
||||
|
||||
/* ── Discover (RT) list ─────────────────────────────── */
|
||||
.rt-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.rt-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.7rem;
|
||||
padding: 0.6rem;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: border-color 0.2s, opacity 0.3s, transform 0.3s;
|
||||
}
|
||||
.rt-item:hover { border-color: var(--border-lt); }
|
||||
.rt-item.removing { opacity: 0; transform: scale(0.97); pointer-events: none; }
|
||||
.rt-item.is-hidden { display: none; }
|
||||
.rt-rank {
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-3);
|
||||
font-variant-numeric: tabular-nums;
|
||||
width: 1.2rem;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.rt-poster {
|
||||
width: 38px; height: 56px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
background: var(--surface);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.rt-info { min-width: 0; flex: 1; }
|
||||
.rt-title {
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 600;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.rt-year { color: var(--text-3); font-weight: 400; }
|
||||
.rt-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
font-size: var(--fs-xs);
|
||||
margin-top: 0.15rem;
|
||||
}
|
||||
.score.certified { color: var(--ok); }
|
||||
.score.critics { color: var(--warn); }
|
||||
.score.audience { color: var(--text-3); }
|
||||
/* Description is inline + clamped — readable on touch, no hover required */
|
||||
.rt-desc {
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-3);
|
||||
margin-top: 0.25rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.rt-desc:empty { display: none; }
|
||||
.show-more {
|
||||
margin-top: 0.6rem;
|
||||
width: 100%;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
/* ── Indexers ───────────────────────────────────────── */
|
||||
.indexer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
||||
gap: 0.65rem;
|
||||
}
|
||||
.indexer-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
min-height: var(--tap);
|
||||
padding: 0.75rem 0.9rem;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--fs-base);
|
||||
min-width: 0;
|
||||
}
|
||||
.indexer-item.down { border-color: var(--danger-border); }
|
||||
.indexer-item .status-light { width: 9px; height: 9px; }
|
||||
.indexer-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-2); }
|
||||
|
||||
/* ── Requests (Ombi) ────────────────────────────────── */
|
||||
.req-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.req-item.is-hidden { display: none; }
|
||||
.req-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.7rem;
|
||||
padding: 0.6rem 0.7rem;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.badge {
|
||||
font-size: var(--fs-xs);
|
||||
font-weight: 600;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 100px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.badge.movie { background: var(--accent-dim); color: var(--accent); }
|
||||
.badge.tv { background: var(--ok-dim); color: var(--ok); }
|
||||
.badge.pending { background: var(--warn-dim); color: var(--warn); }
|
||||
.badge.available, .badge.approved { background: var(--ok-dim); color: var(--ok); }
|
||||
.badge.denied { background: var(--danger-dim); color: var(--danger); }
|
||||
.badge.idle { background: var(--surface); color: var(--text-3); }
|
||||
.badge.downloading { background: var(--accent-dim); color: var(--accent); }
|
||||
.badge.paused { background: var(--warn-dim); color: var(--warn); }
|
||||
.req-info { min-width: 0; flex: 1; }
|
||||
.req-title {
|
||||
font-size: var(--fs-sm); font-weight: 600;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.req-sub { font-size: var(--fs-xs); color: var(--text-3); }
|
||||
|
||||
/* ── Insights top lists ─────────────────────────────── */
|
||||
.top-list { display: flex; flex-direction: column; gap: 0.4rem; }
|
||||
.top-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
min-height: 3.25rem;
|
||||
padding: 0.5rem 0.65rem;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.top-rank {
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-3);
|
||||
font-variant-numeric: tabular-nums;
|
||||
width: 1.1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.top-info { min-width: 0; flex: 1; }
|
||||
.top-name {
|
||||
font-size: var(--fs-sm);
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.top-sub { font-size: var(--fs-xs); color: var(--text-3); }
|
||||
.top-plays {
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-2);
|
||||
font-variant-numeric: tabular-nums;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.empty { color: var(--text-3); font-size: var(--fs-sm); }
|
||||
|
||||
/* ── Errors ─────────────────────────────────────────── */
|
||||
.error {
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--danger);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.8rem 0.9rem;
|
||||
color: var(--text-2);
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
.error-detail { margin-top: 0.5rem; }
|
||||
.error-detail summary {
|
||||
cursor: pointer;
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-3);
|
||||
list-style: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.3rem 0;
|
||||
}
|
||||
.error-detail summary::-webkit-details-marker { display: none; }
|
||||
.error-detail summary::before { content: "▸"; }
|
||||
.error-detail[open] summary::before { content: "▾"; }
|
||||
.error-detail summary:hover { color: var(--text-2); }
|
||||
.error-detail pre {
|
||||
margin: 0.4rem 0 0;
|
||||
padding: 0.6rem 0.7rem;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 6px;
|
||||
font-family: var(--mono);
|
||||
font-size: var(--fs-xs);
|
||||
line-height: 1.5;
|
||||
color: var(--text-3);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-3);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
/* Build identity — dim, so it reads as metadata rather than content. */
|
||||
footer .version {
|
||||
display: block;
|
||||
margin-top: 0.3rem;
|
||||
font-family: var(--mono);
|
||||
font-variant-numeric: tabular-nums;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute; width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px; overflow: hidden;
|
||||
clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
||||
}
|
||||
|
||||
/* ── Responsive ─────────────────────────────────────── */
|
||||
@media (max-width: 1100px) {
|
||||
.row.cols-3 { grid-template-columns: 1fr 1fr; }
|
||||
.row.cols-3 > :first-child { grid-column: 1 / -1; }
|
||||
}
|
||||
@media (max-width: 820px) {
|
||||
.row.cols-2, .row.cols-3 { grid-template-columns: 1fr; }
|
||||
.row.cols-3 > :first-child { grid-column: auto; }
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
header { padding: 0.7rem 1rem; gap: 0.6rem; }
|
||||
.brand { order: 1; flex: 1 1 auto; min-width: 0; }
|
||||
.brand h1 { font-size: var(--fs-base); }
|
||||
.header-right { order: 2; }
|
||||
.header-clock { display: none; }
|
||||
.svc-strip { order: 3; width: 100%; flex: 1 0 100%; justify-content: flex-start; }
|
||||
main { padding: 1rem; }
|
||||
.card-body { padding: 0.9rem; }
|
||||
.card-header { padding: 0.75rem 0.9rem; }
|
||||
.search-bar { flex-direction: column; }
|
||||
.search-bar .btn { width: 100%; }
|
||||
/* Full touch targets once there's no mouse */
|
||||
.btn-add { min-height: var(--tap); padding: 0 0.9rem; }
|
||||
.search-bar input { font-size: 16px; } /* prevent iOS zoom on focus */
|
||||
}
|
||||
83
static/css/login.css
Normal file
83
static/css/login.css
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/* Login page — tokens and base reset come from base.css */
|
||||
|
||||
/* Centre the card in the viewport (overrides base.css body). */
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.wrap { width: 100%; max-width: 360px; }
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.6rem;
|
||||
margin-bottom: 1.75rem;
|
||||
}
|
||||
.brand h1 {
|
||||
margin: 0;
|
||||
font-size: var(--fs-lg);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: var(--fs-xs);
|
||||
font-weight: 600;
|
||||
color: var(--text-2);
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
min-height: var(--tap);
|
||||
padding: 0 0.85rem;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
font-size: 16px; /* prevents iOS zoom on focus */
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
input:focus { border-color: var(--accent); }
|
||||
input::placeholder { color: var(--text-3); }
|
||||
|
||||
.error-msg {
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--danger);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.7rem 0.85rem;
|
||||
color: var(--text-2);
|
||||
font-size: var(--fs-sm);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
min-height: var(--tap);
|
||||
margin-top: 1rem;
|
||||
background: var(--accent);
|
||||
color: var(--bg);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
font-family: inherit;
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
button:hover { opacity: 0.88; }
|
||||
138
static/css/settings.css
Normal file
138
static/css/settings.css
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/* Settings page — tokens and base reset come from base.css */
|
||||
|
||||
header {
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 0.75rem 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
header h1 {
|
||||
margin: 0;
|
||||
font-size: var(--fs-lg);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.back-btn {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: var(--tap);
|
||||
padding: 0 0.9rem;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-2);
|
||||
font-size: var(--fs-sm);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
}
|
||||
.back-btn:hover { color: var(--text); border-color: var(--border-lt); }
|
||||
|
||||
main { padding: 1.5rem; max-width: 900px; margin: 0 auto; }
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: var(--gap);
|
||||
}
|
||||
.full { grid-column: 1 / -1; }
|
||||
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.card-header {
|
||||
padding: 0.85rem 1.1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.card-header h2 {
|
||||
margin: 0;
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 600;
|
||||
}
|
||||
.card-body { padding: 1.1rem; }
|
||||
|
||||
.field { margin-bottom: 0.9rem; }
|
||||
label {
|
||||
display: block;
|
||||
font-size: var(--fs-xs);
|
||||
font-weight: 600;
|
||||
color: var(--text-2);
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
input, select {
|
||||
width: 100%;
|
||||
min-height: var(--tap);
|
||||
padding: 0 0.8rem;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
font-size: 16px; /* prevents iOS zoom on focus */
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
input:focus, select:focus { border-color: var(--accent); }
|
||||
input::placeholder { color: var(--text-3); }
|
||||
|
||||
.hint {
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-3);
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
|
||||
button.save {
|
||||
width: 100%;
|
||||
min-height: var(--tap);
|
||||
background: var(--accent);
|
||||
color: var(--bg);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
font-family: inherit;
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
button.save:hover:not(:disabled) { opacity: 0.88; }
|
||||
button.save:disabled { background: var(--border); color: var(--text-3); cursor: default; }
|
||||
|
||||
.error-inline {
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--danger);
|
||||
display: none;
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 1.5rem;
|
||||
right: 1.5rem;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--ok);
|
||||
color: var(--ok);
|
||||
padding: 0.7rem 1.1rem;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 500;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
pointer-events: none;
|
||||
max-width: calc(100vw - 3rem);
|
||||
}
|
||||
.toast.error { border-color: var(--danger); color: var(--danger); }
|
||||
.toast.show { opacity: 1; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
header { padding: 0.7rem 1rem; }
|
||||
main { padding: 1rem; }
|
||||
.toast { left: 1rem; right: 1rem; bottom: 1rem; }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue