video dashboard: header matches music (sweep hidden, video meta + quick-nav)

The video dashboard header now mirrors music's: icon + shimmer title,
subtitle, the Watchlist/Wishlist quick-nav (top-right), and the action-button
row. Differences, all isolated:
- Sweep band kept in markup but hidden on the video side (no animation for
  now; meta-source-driven equivalent may return later).
- Quick-nav reuses .watchlist-button/.wishlist-button styling but carries NO
  music IDs (no duplicate IDs, no music-JS binding) — navigates to the video
  Watchlist/Wishlist pages via data-video-goto.
- header-actions holds disabled TMDB/TVDB/Trakt/OMDb placeholder chips
  (.video-meta-button) standing in for music's enrichment buttons until the
  video meta sources are wired.
No inline onclick; 75 tests green.
This commit is contained in:
BoulderBadgeDad 2026-06-13 19:57:22 -07:00
parent c84231dd4f
commit a57951c038
3 changed files with 82 additions and 2 deletions

View file

@ -93,6 +93,32 @@ def test_video_dashboard_subpage_present_with_expected_cards():
assert "onclick" not in block
def test_video_dashboard_header_matches_music_shape():
block = _block(
_INDEX, r'<section class="video-subpage" data-video-subpage="video-dashboard"', "</section>")
header = _block(block, r'<div class="dashboard-header">', "<div class=\"dash-grid\">")
# Same shell as music: sweep band + icon title + subtitle.
assert "dashboard-header-sweep" in header
assert "page-header-icon" in header and "header-title" in header
assert "header-subtitle" in header
# Watchlist/Wishlist quick-nav present, navigating to the video pages (no
# music IDs — would duplicate + bind music JS — just classes + data-goto).
assert "header-quick-nav" in header
assert 'data-video-goto="video-watchlist"' in header
assert 'data-video-goto="video-wishlist"' in header
assert 'id="watchlist-button"' not in header and 'id="wishlist-button"' not in header
# Meta-source placeholder buttons stand in for music's enrichment row.
assert "header-actions" in header
for src in ("tmdb", "tvdb", "trakt", "omdb"):
assert f'data-video-meta="{src}"' in header
assert "onclick" not in header
def test_video_dashboard_sweep_hidden_on_video_side():
css = _CSS_PATH.read_text(encoding="utf-8")
assert 'body[data-side="video"] .dashboard-header-sweep' in css
def test_video_dashboard_has_placeholder_slot_for_unbuilt_pages():
assert 'id="video-placeholder-slot"' in _INDEX

View file

@ -425,11 +425,36 @@
<div class="page video-page" id="video-page-host">
<section class="video-subpage" data-video-subpage="video-dashboard">
<div class="page-shell dashboard-container">
<div class="dashboard-header">
<div class="dashboard-header"><div class="dashboard-header-sweep" aria-hidden="true"><span></span></div>
<div class="header-text">
<h2 class="header-title"><span>Video Dashboard</span></h2>
<h2 class="header-title"><img src="/static/dashboard.png" class="page-header-icon" alt=""><span>Video Dashboard</span></h2>
<p class="header-subtitle">Monitor your video library, downloads and services</p>
</div>
<!-- Video meta-source buttons (TMDB/TVDB/Trakt/OMDb) — disabled
placeholders; the music side's enrichment-button equivalents,
wired live once the video meta sources exist. -->
<div class="header-actions">
<button class="video-meta-button" type="button" data-video-meta="tmdb" disabled title="TMDB — coming soon">TMDB</button>
<button class="video-meta-button" type="button" data-video-meta="tvdb" disabled title="TVDB — coming soon">TVDB</button>
<button class="video-meta-button" type="button" data-video-meta="trakt" disabled title="Trakt — coming soon">Trakt</button>
<button class="video-meta-button" type="button" data-video-meta="omdb" disabled title="OMDb — coming soon">OMDb</button>
</div>
<!-- Watchlist / Wishlist quick-nav (top-right corner) — same as
music; navigates to the video pages via data-video-goto. -->
<div class="header-quick-nav">
<button class="header-button watchlist-button" type="button" data-video-goto="video-watchlist">
<span class="hero-btn-icon">&#128065;</span>
<span class="hero-btn-label">Watchlist</span>
<span class="hero-btn-badge" data-video-badge="watchlist">0</span>
<span class="hero-btn-shimmer"></span>
</button>
<button class="header-button wishlist-button" type="button" data-video-goto="video-wishlist">
<span class="hero-btn-icon">&#11088;</span>
<span class="hero-btn-label">Wishlist</span>
<span class="hero-btn-badge" data-video-badge="wishlist">0</span>
<span class="hero-btn-shimmer"></span>
</button>
</div>
</div>
<div class="dash-grid">

View file

@ -108,6 +108,35 @@ body[data-side="video"] .status-section {
display: none;
}
/* ── Dashboard header: match music, minus the sweep animation ──────────── */
/* The header reuses music's .dashboard-header markup/CSS for an identical look;
we only mute the ambient sweep on the video side (equiv may return later,
driven by meta sources). Scoped to data-side="video" so music is untouched. */
body[data-side="video"] .dashboard-header-sweep {
display: none;
}
/* Video meta-source buttons the placeholder equivalents of music's round
enrichment buttons. Disabled pills until the video meta sources are wired. */
.video-meta-button {
height: 40px;
padding: 0 18px;
border-radius: 20px;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--text-secondary, #9aa0aa);
background: rgba(18, 18, 22, 0.85);
border: 1px solid rgba(255, 255, 255, 0.10);
backdrop-filter: blur(8px) saturate(1.2);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.06);
cursor: not-allowed;
opacity: 0.55;
}
.video-meta-button:disabled {
cursor: not-allowed;
}
/* ── Subpages: one built video page shown at a time ────────────────────── */
/* Each real page is a .video-subpage inside #video-page-host; the controller
toggles the [hidden] attribute. Be explicit so no reused .dash-* rule can