From 4ae5aee52816268440909f9d27d387d768ce8cfc Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Wed, 27 May 2026 22:04:28 -0700 Subject: [PATCH] Sync page: collapse tabs to brand-logo chips with active label pill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sync-tabs row had 14 sources jostling for horizontal space — labels wrapped to 2 lines, the active pill ate disproportionate room, the whole strip felt cramped and would only get worse as more sources get added. Restyled the strip as circular brand-logo chips. Inactive tabs are 40px discs that show only the source's icon; the currently- active tab swells into a pill that reveals its label inline. Hover surfaces the source name as a native tooltip via the title attr. Each chip carries its source's brand color as a hover ring + active fill (Spotify green, Tidal orange, Qobuz blue, Deezer purple, iTunes coral, YouTube red, Beatport green, LB orange, Last.fm red, SSD teal). Three sources share a logo with another source (Spotify Link / Spotify, Deezer Link / Deezer, iTunes Link / no native iTunes but same logo family). Each "Link" variant carries a small chain-link badge bottom-right so the chip disambiguates without forcing the label to always be visible. CSS-only swap — same JS handlers, same .active class, same data-tab routing. HTML edit wraps each tab's label in a ```` and adds ``data-link="true"`` to the Link variants so the CSS can target them. Responsive: chips collapse to 36px on laptop / tablet and 32px on mobile; the divider hides on mobile and gap tightens. --- webui/index.html | 60 ++++++------- webui/static/helper.js | 1 + webui/static/style.css | 200 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 30 deletions(-) diff --git a/webui/index.html b/webui/index.html index 8d70b9e7..845a8a03 100644 --- a/webui/index.html +++ b/webui/index.html @@ -996,51 +996,51 @@
-
- - - - - - - - - - - - - -
diff --git a/webui/static/helper.js b/webui/static/helper.js index 946b581e..dcf6a421 100644 --- a/webui/static/helper.js +++ b/webui/static/helper.js @@ -3415,6 +3415,7 @@ function closeHelperSearch() { const WHATS_NEW = { '2.6.3': [ { unreleased: true }, + { title: 'Sync page: collapse tabs to brand-logo chips, active swells into a label pill', desc: 'with 14 sync sources now (Spotify, Spotify Link, iTunes Link, Tidal, Qobuz, Deezer, Deezer Link, YouTube, Beatport, ListenBrainz, Last.fm, SoulSync Discovery, Import, Mirrored, plus Server Playlists), the old "row of equal-width labeled pills" tab strip ran out of horizontal room — labels wrapped to 2 lines, the active pill ate disproportionate space, the whole row felt cramped. Restyled the tab strip as a row of circular brand-logo chips (40px on desktop, smaller on tablet/mobile); the currently-active tab swells into a pill with its label inline. Hover tooltip surfaces the source name via the title attribute. Each chip carries its source\'s brand color as a hover ring + active fill (Spotify green, Tidal orange, Qobuz blue, Deezer purple, iTunes coral, YouTube red, Beatport green, LB orange, Last.fm red, SSD teal). To disambiguate the three duplicate-logo sources (Spotify Link / Deezer Link / iTunes Link share a logo with their native-source siblings), each "Link" variant carries a small chain-link badge bottom-right. CSS-only swap — zero JS / behavior changes, same tab click handler, same data-tab routing. The active tab\'s label still shows so context is never lost; non-active tabs trade their label for ~70% horizontal savings.', page: 'sync' }, { title: 'Fix: Soulseek album downloads stuck on "failed" after slskd finished the release (#715)', desc: 'when an album was downloaded via the Soulseek album-bundle path (the release-first flow added in 2.6), the batch would frequently mark itself as failed even though slskd had successfully downloaded every track of the album. Worst on users running slskd with the common ``directories.downloads.username = true`` config — files landed at ``//`` instead of the three hard-coded candidate paths the bundle resolver probed, so every file looked locally missing and the poll spun until the ~30 minute deadline expired. Per-track Soulseek downloads were unaffected because they already routed through a recursive walk-by-basename helper. Lifted that helper into ``core/downloads/file_finder.py`` and rewired the bundle path to use it — same logic the per-track flow has used since 2.5.9. Also: when the bundle poll detects that slskd reports every transfer Completed but no local file resolves within a 45-second grace window, it now exits with a clear log line pointing at the likely ``soulseek.download_path`` mismatch instead of silently spinning until the master deadline. Misleading "(0 tracks, quality=)" log on the preflight-reuse path also fixed — was reading attrs off a None object. 17 new unit tests pin every slskd layout shape (flat, username-prefixed, full-tree-preserved, deep nested, dedup-suffix, quarantine-skip, YouTube/Tidal encoded names, transfer-dir fallback, fuzzy punctuation variants). Closes #715.', page: 'downloads' }, { title: 'Auto-Sync manager: full visual overhaul to match the dashboard vibe', desc: 'the Auto-Sync manager modal had been carrying its original visual treatment forward unchanged while the rest of the app moved toward the glassy / accent-radial / gradient-border aesthetic the dashboard now sets. Restyled every surface inside the modal to match — selector-based override layer at the end of style.css so functionality is untouched (zero JS or HTML changes). Touched surfaces: modal shell (glass shell with thin accent border + corner radial wash + inner top-edge highlight), header (gradient text title + accent-tinted hairline separator + spinning-X close), KPI summary tiles (dashboard-style gradient tiles with accent top-edge glow on hover + gradient stat numbers), live monitor strip (accent-tinted glass card with status-colored borders), refresh / intro buttons (pill primary with accent fill + glow on hover), tabs (underline-style with accent fill + soft radial glow on the active tab), sidebar (glass panel with accent-tinted source-group cards, accent border on scheduled playlists, accent ring on the filter input focus), board area (subtle accent radial spotlight + column cards with gradient headers + drag-over accent glow), drop zones (animated dashed pull with accent radial wash + accent-tinted text on drag-over), scheduled cards (accent left-edge stripe, gradient pill timing badges, pill "Run now" primary + rotating ghost X), run history rows (dashboard recent-activity aesthetic with accent hover lift + pill status badges), bulk-schedule popover, weekly editor (full glass modal matching the version-modal vibe, day-toggle pills, accent-pill save button), and empty / monitor-empty states. Soft accent-tinted scrollbars throughout. Delete the v2 block at the bottom of style.css to revert.', page: 'automations' }, { title: 'Auto-Sync: weekly board cards now match the hourly board', desc: 'when the Weekly Board shipped, its scheduled-card visual diverged from the hourly board — weekly cards only showed the playlist name + weekly label + timezone, while hourly cards carry the full action row (Run now button, unschedule X, next-run countdown, health badge). Standardised the weekly card on the hourly shape so dropping a playlist onto a day column produces a card with the exact same affordances as dropping it onto an hourly bucket: health indicator dot when recent runs failed, source + track count meta line, weekly label + timezone + "next in Nh" pills, Run-now button (disabled while pipeline is running, same gating as hourly), and an unschedule X. Click anywhere outside the buttons still opens the weekly editor for changing days / time / tz. Weekly cards also became draggable between day columns now — drop on a new column appends that day to the schedule (matches the existing multi-day editor flow).', page: 'automations' }, diff --git a/webui/static/style.css b/webui/static/style.css index 297f1ce0..fbadf333 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -63180,6 +63180,206 @@ body.reduce-effects .dash-card::after { } +/* ===================================================================== + * Sync page tabs — icon-chip v2 layout + * + * 14+ tabs no longer fit in a single horizontal pill row. Collapse + * each tab to a circular brand-logo chip; the active tab swells + * into a pill that shows its label inline. Hover surfaces the + * tab title attribute as a native tooltip. ``[data-link="true"]`` + * tabs (Spotify Link, Deezer Link, iTunes Link) carry a small + * chain-link badge bottom-right so they disambiguate from their + * native-source siblings (same brand logo, different source mode). + * + * Behavior untouched — same JS event handlers, same .active class, + * same data-tab routing. CSS-only swap. + * ===================================================================== */ + +.sync-tabs { + flex-wrap: wrap; + gap: 6px; + padding: 8px 10px; + background: + radial-gradient(ellipse at top, + color-mix(in srgb, rgb(var(--accent-rgb)) 4%, transparent) 0%, + rgba(0, 0, 0, 0.22) 70%); + border: 1px solid rgba(255, 255, 255, 0.04); + border-radius: 14px; + align-items: center; +} + +.sync-tab-button { + /* Reset: drop flex:1 stretch + heavy padding, become a chip. */ + flex: 0 0 auto; + width: 40px; + height: 40px; + padding: 0; + gap: 0; + border-radius: 999px; + background: + linear-gradient(160deg, + rgba(255, 255, 255, 0.04) 0%, + rgba(0, 0, 0, 0.18) 100%); + border: 1px solid rgba(255, 255, 255, 0.06); + color: rgba(255, 255, 255, 0.78); + font-size: 12px; + overflow: hidden; + position: relative; + transition: + width 0.32s cubic-bezier(0.34, 1.2, 0.5, 1), + padding 0.32s cubic-bezier(0.34, 1.2, 0.5, 1), + background 0.25s ease, + border-color 0.25s ease, + box-shadow 0.25s ease, + transform 0.18s ease; +} + +.sync-tab-button:hover:not(.active) { + background: + linear-gradient(160deg, + color-mix(in srgb, var(--sync-tab-accent, rgb(var(--accent-rgb))) 14%, transparent) 0%, + rgba(0, 0, 0, 0.18) 100%); + border-color: color-mix(in srgb, var(--sync-tab-accent, rgb(var(--accent-rgb))) 35%, rgba(255, 255, 255, 0.1)); + color: #fff; + transform: translateY(-1px); + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25); +} + +/* Active state — swell into a pill that shows the label inline. */ +.sync-tab-button.active { + width: auto; + padding: 0 14px 0 10px; + gap: 8px; + background: linear-gradient(135deg, + color-mix(in srgb, var(--sync-tab-accent, rgb(var(--accent-rgb))) 92%, white 8%), + color-mix(in srgb, var(--sync-tab-accent, rgb(var(--accent-rgb))) 78%, black 22%)); + border-color: color-mix(in srgb, var(--sync-tab-accent, rgb(var(--accent-rgb))) 55%, transparent); + color: #fff; + box-shadow: + 0 4px 18px color-mix(in srgb, var(--sync-tab-accent, rgb(var(--accent-rgb))) 28%, transparent), + inset 0 1px 0 rgba(255, 255, 255, 0.2); +} + +.sync-tab-server.active { + /* The Server Playlists tab kept its bespoke gradient — preserve it. */ + background: linear-gradient(135deg, + rgba(var(--accent-rgb), 0.95), + rgba(var(--accent-rgb), 0.75)) !important; + color: #000 !important; +} + +/* Per-tab accent. Used by both the active fill and the hover ring, + * so each chip lights up in its brand color. */ +.sync-tab-button[data-tab="spotify"], +.sync-tab-button[data-tab="spotify-public"] { --sync-tab-accent: #1DB954; } +.sync-tab-button[data-tab="tidal"] { --sync-tab-accent: #ff6600; } +.sync-tab-button[data-tab="qobuz"] { --sync-tab-accent: #5b8dee; } +.sync-tab-button[data-tab="deezer"], +.sync-tab-button[data-tab="deezer-link"] { --sync-tab-accent: #a238ff; } +.sync-tab-button[data-tab="itunes-link"] { --sync-tab-accent: #fa586a; } +.sync-tab-button[data-tab="youtube"] { --sync-tab-accent: #ff0000; } +.sync-tab-button[data-tab="beatport"] { --sync-tab-accent: #01ff95; } +.sync-tab-button[data-tab="listenbrainz-sync"] { --sync-tab-accent: #eb743b; } +.sync-tab-button[data-tab="lastfm-sync"] { --sync-tab-accent: #d51007; } +.sync-tab-button[data-tab="soulsync-discovery-sync"] { --sync-tab-accent: #14b8a6; } + +/* Active tabs use white-on-color, so the per-tab icon overrides + * shipped in the original CSS apply automatically. Nothing to do + * for the active-icon swap — handled by existing rules above. */ + +/* Icon scaling — chip is 40px, icon was 16px; size up to 20px so + * the logo fills the chip naturally. */ +.sync-tab-button .tab-icon { + width: 20px; + height: 20px; + flex-shrink: 0; +} + +/* Label collapses to width 0 when not active, so the chip stays + * circular. Reveals on .active via the gap + padding shift above. */ +.sync-tab-label { + max-width: 0; + opacity: 0; + white-space: nowrap; + overflow: hidden; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.02em; + transition: max-width 0.32s cubic-bezier(0.34, 1.2, 0.5, 1), + opacity 0.22s ease; +} + +.sync-tab-button.active .sync-tab-label { + max-width: 200px; + opacity: 1; +} + +/* Chain-link badge for the ``Link`` variants (Spotify Link, Deezer + * Link, iTunes Link). Bottom-right corner of the chip, accent-tinted + * disc with a tiny link glyph. Disambiguates same-brand chips at + * a glance without forcing the label to always be visible. */ +.sync-tab-button[data-link="true"]::after { + content: ''; + position: absolute; + right: 1px; + bottom: 1px; + width: 14px; + height: 14px; + border-radius: 50%; + background: + color-mix(in srgb, var(--sync-tab-accent, rgb(var(--accent-rgb))) 85%, black 15%) + center / 70% no-repeat + url("data:image/svg+xml;charset=utf-8,"); + border: 1.5px solid #1a1d28; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + z-index: 2; +} + +/* When the link-variant tab is active, the badge inherits the + * darker outline of the now-bright pill — bump the border to keep + * contrast against the brand-color fill. */ +.sync-tab-button[data-link="true"].active::after { + border-color: rgba(0, 0, 0, 0.45); +} + +/* Divider between Server Playlists and the rest — slim it down so + * it doesn't dominate the compacted row. */ +.sync-tab-divider { + height: 24px; + margin: 0 4px; + background: rgba(255, 255, 255, 0.08); +} + +/* Responsive — collapse chip size on smaller widths to fit the row. */ +@media (max-width: 1099px) { + .sync-tab-button { + width: 36px; + height: 36px; + } + .sync-tab-button .tab-icon { width: 18px; height: 18px; } + .sync-tab-button.active { padding: 0 12px 0 8px; } + .sync-tab-label { font-size: 11px; } +} + +@media (max-width: 720px) { + .sync-tabs { + gap: 4px; + padding: 6px; + } + .sync-tab-button { + width: 32px; + height: 32px; + } + .sync-tab-button .tab-icon { width: 16px; height: 16px; } + .sync-tab-button[data-link="true"]::after { + width: 12px; + height: 12px; + border-width: 1px; + } + .sync-tab-divider { display: none; } +} + + /* ===================================================================== * Auto-Sync Manager — v2 visual overhaul *