/* ── 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); } /* ── 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-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; } /* 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); } /* ── 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 */ }