Parity went back into the GraphQL query, but for its temperature only — it still gets no table row, since it has no filesystem to measure. The temp rides in the card header next to the array summary, which was the one genuinely useful thing the old parity row carried: a parity drive cooking away is worth seeing. Multiple parity drives each get a badge, and .meta-parity keeps the label and badge from wrapping apart. The settings cog was a speck. The button was always a full 44px --tap target, but the glyph inside it was set at --fs-sm (13px), so the affordance read as far smaller than the hit area actually was. Now --fs-xl (24px) with line-height 1 — roughly 55% of the button, which is the usual proportion for an icon button. .icon-btn has exactly one user (the cog), so nothing else shifts. Verified at 1400px and 375px: no page overflow, the cog still measures 44x44, and the parity badge stays visible and intact on both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
775 lines
25 KiB
CSS
775 lines
25 KiB
CSS
|
|
/* ── Header ─────────────────────────────────────────── */
|
|
/* Identity on the left, clock + settings on the right, one row at every
|
|
width. The service pills used to occupy a second full-width row here;
|
|
they now live in the Status section at the top of <main>, which is
|
|
where someone looks for them. 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);
|
|
}
|
|
.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 — the Status section's only content, left-aligned
|
|
with every other section's content. All six share the row equally and
|
|
shrink together, so the strip is always exactly one row: never two,
|
|
never a scroller. Every service stays visible at every width; what
|
|
gives way as space tightens is detail *within* a pill, in this order:
|
|
the stat drops below 1024px, then the name shortens below 560px. The
|
|
dot and the pill itself never go, so no service is ever hidden. */
|
|
.svc-strip {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
flex-wrap: nowrap;
|
|
min-width: 0;
|
|
}
|
|
/* Full --tap height now they're content rather than header furniture. */
|
|
.svc-pill {
|
|
flex: 1 1 0; /* equal share of the row, all shrink together */
|
|
min-width: 0; /* ...which flex items won't do without this */
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.45rem;
|
|
padding: 0.35rem 0.9rem;
|
|
min-height: var(--tap);
|
|
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;
|
|
overflow: hidden;
|
|
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;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.svc-stat {
|
|
color: var(--text-3);
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.svc-pill.down .svc-stat { color: var(--danger); }
|
|
@media (max-width: 1024px) {
|
|
/* Six stats stop fitting here. The dot already carries up/down, so the
|
|
stat is the first thing that can go without losing any status. */
|
|
.svc-stat { display: none; }
|
|
}
|
|
@media (max-width: 620px) {
|
|
/* Tighten the furniture so the names keep their characters longer. */
|
|
.svc-strip { gap: 0.3rem; }
|
|
.svc-pill { padding: 0.35rem 0.5rem; gap: 0.3rem; }
|
|
}
|
|
@media (max-width: 560px) {
|
|
/* Six full names in one row leaves ~23px each — two characters and an
|
|
ellipsis, which identifies nothing. Swap in the short label instead.
|
|
The full name stays in the DOM (font-size: 0 — invisible but still
|
|
announced) and on the pill's title attribute. */
|
|
.svc-strip { gap: 0.25rem; }
|
|
.svc-pill { padding: 0.35rem 0.35rem; gap: 0.25rem; }
|
|
.svc-name { font-size: 0; }
|
|
.svc-name::after {
|
|
content: attr(data-short);
|
|
font-size: var(--fs-xs);
|
|
}
|
|
}
|
|
|
|
.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 {
|
|
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);
|
|
/* The button was always a full --tap target, but the cog glyph inside it
|
|
was set at --fs-sm (13px) and read as a speck in a 44px box. The glyph
|
|
is the affordance, so size it to the button. */
|
|
font-size: var(--fs-xl);
|
|
line-height: 1;
|
|
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 table ──────────────────────────── */
|
|
/* Storage owns a full-width row of its own, so the per-disk breakdown is a
|
|
real table rather than a stack of cards: columns line up, digits are
|
|
tabular, and a new metric is a new column instead of a redesign. Stacking
|
|
these as rows in a third-of-a-page column is what hollowed out the two
|
|
cards beside it — see the dead-space rule in CLAUDE.md. */
|
|
/* Storage's header meta is a sentence, not a badge. .card-meta is nowrap so
|
|
short status words never break; this one has to wrap or it pushes the whole
|
|
page sideways on a phone. */
|
|
.card-meta.card-meta-wrap { white-space: normal; text-align: right; }
|
|
.array-meter.meter { margin-top: 0; margin-bottom: 1.1rem; }
|
|
.table-scroll { overflow-x: auto; }
|
|
.disk-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.disk-table th, .disk-table td { padding: 0.45rem 0.6rem; text-align: left; }
|
|
.disk-table th:first-child, .disk-table td:first-child { padding-left: 0; }
|
|
.disk-table th:last-child, .disk-table td:last-child { padding-right: 0; }
|
|
.disk-table thead th {
|
|
font-size: var(--fs-xs);
|
|
font-weight: 500;
|
|
color: var(--text-3);
|
|
white-space: nowrap;
|
|
padding-bottom: 0.5rem;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.disk-table tbody th {
|
|
font-size: var(--fs-sm);
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
white-space: nowrap;
|
|
}
|
|
.disk-table tbody td {
|
|
font-size: var(--fs-sm);
|
|
color: var(--text-2);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.disk-table tbody th { border-bottom: 1px solid var(--border); }
|
|
.disk-table tbody tr:last-child th,
|
|
.disk-table tbody tr:last-child td { border-bottom: none; }
|
|
.disk-table .num { text-align: right; white-space: nowrap; }
|
|
/* The bar and its percentage share one cell, so the bar flexes with the
|
|
table while the number stays a fixed, right-aligned column of digits. */
|
|
.disk-table .usage { width: 32%; min-width: 140px; }
|
|
.usage-cell { display: flex; align-items: center; gap: 0.6rem; }
|
|
.usage-cell .meter { margin-top: 0; flex: 1; height: 5px; }
|
|
.usage-pct {
|
|
font-size: var(--fs-xs);
|
|
color: var(--text-3);
|
|
width: 2.6em;
|
|
text-align: right;
|
|
flex-shrink: 0;
|
|
}
|
|
/* Still used by the disk_state() macro when a disk reports no temperature. */
|
|
.disk-null { color: var(--text-3); }
|
|
/* Parity temp in the Storage header — keep the label and its badge together
|
|
so they never wrap apart from each other. */
|
|
.meta-parity {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
white-space: nowrap;
|
|
}
|
|
@media (max-width: 720px) {
|
|
/* Below this the six columns stop fitting; the table scrolls in its own
|
|
container rather than squeezing the sizes into unreadable wraps. */
|
|
.table-scroll .disk-table { min-width: 540px; }
|
|
}
|
|
.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) {
|
|
header { padding: 0.7rem 1rem; gap: 0.6rem; }
|
|
.brand { flex: 1 1 auto; }
|
|
.brand h1 { font-size: var(--fs-base); }
|
|
.header-clock { display: none; }
|
|
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 */
|
|
}
|