Three inline style="..." attributes were pure styling with no per-request data behind them (a hero-block spacing tweak, a trailing note's margin, a gap between two stacked rows) — moved each into dashboard.css as a scoped structural selector so future adjustments happen in one place instead of being split between the template and the stylesheet. Also bumped .stream-title/.stream-user/.stream-icon in the Now Playing card one step up the existing --fs-* scale (13px->15px, 12px->13px, 15px->18px) rather than introducing a one-off size. Documented the "styles belong in CSS, not inline" rule in CLAUDE.md, with the actual exception: per-request computed values (a meter's width, a threshold colour) and JS-toggled initial visibility still can't live in a static stylesheet and are fine to stay inline. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
693 lines
21 KiB
CSS
693 lines
21 KiB
CSS
|
|
/* ── Header ─────────────────────────────────────────── */
|
|
/* Two deliberate rows at every width: identity + controls on top,
|
|
service pills on their own full-width row beneath. Letting the pills
|
|
share row one made the header wrap into ragged rows and orphan the
|
|
clock as soon as the six of them stopped fitting. Scrolls away with
|
|
the page — a status bar isn't worth permanent screen real estate. */
|
|
header {
|
|
background: var(--surface);
|
|
border-bottom: 1px solid var(--border);
|
|
padding: 0.75rem 1.5rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem var(--gap);
|
|
flex-wrap: wrap;
|
|
}
|
|
.brand {
|
|
order: 1;
|
|
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 — own row, wrapping among themselves so every
|
|
service stays visible at any width. Never scroll or clip: a hidden
|
|
pill is a service whose status you can't see. */
|
|
.svc-strip {
|
|
order: 3;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
flex: 0 0 100%;
|
|
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); }
|
|
.status-light.warn { background: var(--warn); box-shadow: 0 0 0 3px var(--warn-dim); }
|
|
|
|
.header-right {
|
|
order: 2;
|
|
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); }
|
|
/* A section with two stacked rows (Watch insights) needs the same gap
|
|
between them as the grid uses between columns. */
|
|
.row + .row { margin-top: var(--gap); }
|
|
|
|
/* ── 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; }
|
|
/* Requests can grow tall when expanded; the Indexers card beside it should
|
|
keep its natural height and sit at the top rather than stretching into a
|
|
mostly-empty box with its content floating in the middle. */
|
|
.row.align-start { align-items: start; }
|
|
|
|
/* ── "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-lg); flex-shrink: 0; }
|
|
.stream-info { min-width: 0; flex: 1; }
|
|
.stream-title {
|
|
font-size: var(--fs-base); font-weight: 600;
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
}
|
|
.stream-user { font-size: var(--fs-sm); 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));
|
|
}
|
|
/* Only the Downloads card stacks stats below the hero figure/sub block —
|
|
give that pairing breathing room without affecting stats blocks that
|
|
open a card (Sonarr/Radarr put dl.stats right after the search bar). */
|
|
.card-body > [aria-live="polite"] + dl.stats { margin-top: 0.9rem; }
|
|
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;
|
|
}
|
|
/* Sits below the request pills, which use --row-h — match it so the
|
|
button reads as one more row rather than a shorter tacked-on control. */
|
|
.show-more.show-more-row { min-height: var(--row-h); }
|
|
|
|
/* ── Indexers ───────────────────────────────────────── */
|
|
/* One indexer per line, built to the same rhythm as the Ombi request rows
|
|
beside it — same padding, gap, radius and two-line body, so the two cards
|
|
read as one pair rather than two unrelated layouts. */
|
|
.indexer-grid { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
.indexer-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.7rem;
|
|
min-height: var(--row-h);
|
|
padding: 0.6rem 0.7rem;
|
|
background: var(--surface-2);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
min-width: 0;
|
|
}
|
|
.indexer-item.down { border-color: var(--danger-border); }
|
|
.indexer-item .status-light { width: 9px; height: 9px; flex-shrink: 0; }
|
|
.indexer-info { min-width: 0; flex: 1; }
|
|
.indexer-name {
|
|
font-size: var(--fs-sm); font-weight: 600;
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
}
|
|
.indexer-sub { font-size: var(--fs-xs); color: var(--text-3); }
|
|
.indexer-sub .proto { text-transform: capitalize; }
|
|
.indexer-sub:empty::before { content: "Indexer"; }
|
|
/* Share of grabs, scaled to the busiest indexer (see dashboard.html).
|
|
The margin is trimmed so a bar-carrying row still fits inside --row-h
|
|
and stays flush with the request rows beside it. */
|
|
.share-bar {
|
|
margin-top: 0.3rem;
|
|
height: 4px;
|
|
border-radius: 100px;
|
|
background: var(--surface);
|
|
overflow: hidden;
|
|
}
|
|
.share-bar span {
|
|
display: block;
|
|
height: 100%;
|
|
border-radius: 100px;
|
|
background: var(--accent);
|
|
}
|
|
.indexer-item.down .share-bar span { background: var(--text-3); }
|
|
.share-pct {
|
|
font-size: var(--fs-sm);
|
|
font-weight: 600;
|
|
font-variant-numeric: tabular-nums;
|
|
color: var(--text-2);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* ── Requests (Ombi) ────────────────────────────────── */
|
|
/* Request rows are the same object as an indexer row — same geometry and
|
|
the same dot/title/sub/badge anatomy — so the two cards read across as
|
|
one pair. --row-h keeps them aligned line-for-line; the indexer rows
|
|
reach it naturally once their share bar is in. */
|
|
.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;
|
|
min-height: var(--row-h);
|
|
padding: 0.6rem 0.7rem;
|
|
background: var(--surface-2);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.req-item .status-light { width: 9px; height: 9px; flex-shrink: 0; }
|
|
.req-type { text-transform: capitalize; }
|
|
.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.online { background: var(--ok-dim); color: var(--ok); }
|
|
.badge.offline { background: var(--danger-dim); color: var(--danger); }
|
|
.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); }
|
|
|
|
/* ── Storage — per-disk breakdown ──────────────────────── */
|
|
/* Same row anatomy as indexer/request rows: name/sub on the left, a status
|
|
badge on the right. Data + cache disks get a usage meter; parity disks
|
|
(no filesystem) are rendered compact — name and temp only. */
|
|
.disk-list { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.85rem; }
|
|
.disk-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.7rem;
|
|
min-height: var(--row-h);
|
|
padding: 0.6rem 0.7rem;
|
|
background: var(--surface-2);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
min-width: 0;
|
|
}
|
|
.disk-info { min-width: 0; flex: 1; }
|
|
.disk-name {
|
|
font-size: var(--fs-sm); font-weight: 600;
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
}
|
|
.disk-sub { font-size: var(--fs-xs); color: var(--text-3); }
|
|
.disk-meter.meter { margin-top: 0.4rem; height: 4px; }
|
|
.disk-list-compact { margin-top: 0.5rem; }
|
|
.disk-item-compact { justify-content: space-between; padding: 0.5rem 0.7rem; min-height: auto; }
|
|
.disk-item-compact .disk-name { flex: 1; }
|
|
.badge.temp-ok { background: var(--ok-dim); color: var(--ok); }
|
|
.badge.temp-warn { background: var(--warn-dim); color: var(--warn); }
|
|
.badge.temp-danger { background: var(--danger-dim); color: var(--danger); }
|
|
|
|
/* ── 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); }
|
|
/* Storage card's usage note trails the meter with no room to spare below it. */
|
|
.meter-legend + .empty { margin-bottom: 0; }
|
|
|
|
/* ── 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) {
|
|
/* Row order and the full-width pill row are set in the base rules. */
|
|
header { padding: 0.7rem 1rem; gap: 0.6rem; }
|
|
.brand { flex: 1 1 auto; }
|
|
.brand h1 { font-size: var(--fs-base); }
|
|
.header-clock { display: none; }
|
|
.svc-strip { 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 */
|
|
}
|