Rebuilt the Library page to reuse the music library's exact look — no reinvention, just new data: - Same classes: .library-container, .library-artist-card grid, .alphabet- selector, .library-search-input, loading/empty states. Movies/Shows tab pill is the only video-specific bit. - Real posters via a server-side proxy: GET /api/video/poster/<kind>/<id> streams the Plex/Jellyfin artwork (token stays server-side); cards fall back to an emoji on miss. list_movies/list_shows now expose has_poster (no raw server paths leaked). - Client-side search + A-Z letter filter (article-aware) over the loaded set; cards are divs (not clickable yet, per request). Scan button in the header reuses the shared scan controller and reloads on done. 110 tests green.
205 lines
6.9 KiB
CSS
205 lines
6.9 KiB
CSS
/*
|
|
* SoulSync — Video side shell styling.
|
|
*
|
|
* Loaded AFTER style.css so these rules win where specificity ties. Everything
|
|
* keys off body[data-side="music|video"] (set by video-side.js). The music side
|
|
* is the default and is never affected — these rules only do something once the
|
|
* body flips to data-side="video".
|
|
*/
|
|
|
|
/* ── The Music ↔ Video header toggle (animated sliding pill) ────────────── */
|
|
.side-toggle {
|
|
position: relative;
|
|
display: flex;
|
|
margin-top: 12px;
|
|
padding: 4px;
|
|
background: rgba(0, 0, 0, 0.28);
|
|
border: 1px solid rgba(255, 255, 255, 0.07);
|
|
border-radius: 11px;
|
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.35);
|
|
isolation: isolate;
|
|
}
|
|
|
|
/* The thumb that slides under the active side. Positioned over the left half by
|
|
default; slides to the right half when body[data-side="video"]. */
|
|
.side-toggle-thumb {
|
|
position: absolute;
|
|
z-index: 0;
|
|
top: 4px;
|
|
bottom: 4px;
|
|
left: 4px;
|
|
width: calc(50% - 4px);
|
|
border-radius: 8px;
|
|
background: linear-gradient(135deg,
|
|
rgb(var(--accent-rgb, 88 101 242)),
|
|
rgba(var(--accent-rgb, 88 101 242), 0.82));
|
|
box-shadow: 0 3px 10px rgba(var(--accent-rgb, 88 101 242), 0.35);
|
|
transition: transform 0.28s cubic-bezier(0.34, 1.3, 0.5, 1);
|
|
}
|
|
body[data-side="video"] .side-toggle-thumb {
|
|
transform: translateX(100%);
|
|
}
|
|
|
|
.side-toggle-btn {
|
|
position: relative;
|
|
z-index: 1;
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
padding: 7px 6px;
|
|
font-size: 11.5px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.02em;
|
|
color: var(--text-secondary, #9aa0aa);
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: color 0.2s ease;
|
|
}
|
|
.side-toggle-btn .side-toggle-icon {
|
|
width: 14px;
|
|
height: 14px;
|
|
flex: none;
|
|
opacity: 0.85;
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
.side-toggle-btn:not(.active):hover {
|
|
color: var(--text-primary, #e8e8ea);
|
|
}
|
|
.side-toggle-btn.active {
|
|
color: #fff;
|
|
}
|
|
.side-toggle-btn.active .side-toggle-icon {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* ── Nav swap: music nav vs video nav ──────────────────────────────────── */
|
|
/* The video nav reuses .sidebar-nav layout (same class) — only visibility
|
|
differs. Hidden by default (music side); shown when data-side="video". */
|
|
.video-nav {
|
|
display: none;
|
|
}
|
|
body[data-side="video"] nav.sidebar-nav:not(.video-nav) {
|
|
display: none;
|
|
}
|
|
body[data-side="video"] .video-nav {
|
|
display: flex;
|
|
}
|
|
|
|
/* ── Page visibility per side ──────────────────────────────────────────── */
|
|
/* Music pages hidden on the video side; video pages hidden on the music side.
|
|
The music router still controls .page.active among music pages; we just hide
|
|
the whole set while the video side is showing. */
|
|
body[data-side="music"] .page.video-page {
|
|
display: none !important;
|
|
}
|
|
body[data-side="video"] .page:not(.video-page) {
|
|
display: none !important;
|
|
}
|
|
body[data-side="video"] .page.video-page {
|
|
display: block;
|
|
}
|
|
|
|
/* ── Service status: not needed on the video side (for now) ─────────────── */
|
|
body[data-side="video"] .status-section {
|
|
display: none;
|
|
}
|
|
|
|
/* ── Shared pages: show the REAL music page on the video side ───────────── */
|
|
/* For now Settings is shown identically (no hiding of music-only bits yet).
|
|
The id selector + extra attribute outrank the blanket music-page hide rule. */
|
|
body[data-side="video"][data-video-page="video-settings"] #settings-page {
|
|
display: block !important;
|
|
}
|
|
body[data-side="video"][data-video-page="video-settings"] #video-page-host {
|
|
display: none !important;
|
|
}
|
|
/* Video-only additions inside the shared settings page (e.g. the Movies/TV
|
|
library mapping) — hidden on the music side. */
|
|
body[data-side="music"] [data-video-only] {
|
|
display: none !important;
|
|
}
|
|
/* Conversely, the music-library picker is irrelevant on the video side — hide
|
|
it there (the Movies/TV mapping is its replacement). */
|
|
body[data-side="video"] #plex-library-selector-container,
|
|
body[data-side="video"] #jellyfin-library-selector-container,
|
|
body[data-side="video"] #navidrome-folder-selector-container {
|
|
display: none !important;
|
|
}
|
|
/* Music-only settings sections (e.g. the music API services) are hidden on the
|
|
video side; their video counterparts carry data-video-only. */
|
|
body[data-side="video"] [data-music-only] {
|
|
display: none !important;
|
|
}
|
|
|
|
/* ── 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;
|
|
}
|
|
|
|
/* ── 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
|
|
resurrect a hidden page. */
|
|
.video-subpage[hidden],
|
|
.video-placeholder-slot[hidden] {
|
|
display: none !important;
|
|
}
|
|
|
|
/* ── Placeholder content (for pages not built yet) ─────────────────────── */
|
|
.video-placeholder-slot {
|
|
padding: 48px 40px;
|
|
}
|
|
.video-placeholder-slot .header-title {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
}
|
|
.video-placeholder-note {
|
|
margin-top: 10px;
|
|
color: var(--text-secondary, #9aa0aa);
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* Empty-state line inside reused dashboard cards (e.g. Recent Downloads). */
|
|
.video-empty-note {
|
|
margin: 0;
|
|
padding: 8px 2px;
|
|
color: var(--text-secondary, #9aa0aa);
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* ── Library page ──────────────────────────────────────────────────────── */
|
|
/* The grid/cards/search/alphabet all reuse the music library CSS verbatim.
|
|
The only video-specific bit is the Movies/Shows tab pill. */
|
|
.video-library-tabs {
|
|
display: flex;
|
|
gap: 6px;
|
|
padding: 4px;
|
|
margin: 0 auto 4px;
|
|
width: max-content;
|
|
background: rgba(0, 0, 0, 0.25);
|
|
border: 1px solid rgba(255, 255, 255, 0.07);
|
|
border-radius: 12px;
|
|
}
|
|
.video-lib-tab {
|
|
padding: 8px 22px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--text-secondary, #9aa0aa);
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: 9px;
|
|
cursor: pointer;
|
|
transition: background 0.15s ease, color 0.15s ease;
|
|
}
|
|
.video-lib-tab:hover { color: var(--text-primary, #e8e8ea); }
|
|
.video-lib-tab.active {
|
|
color: #fff;
|
|
background: rgb(var(--accent-rgb, 88 101 242));
|
|
}
|