From 3dc9dc1ae5c3f1e72e2cce78d6681f61269cdab6 Mon Sep 17 00:00:00 2001 From: R8s6 <44877866+R8s6@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:51:16 -0700 Subject: [PATCH] Add a responsive Shows page to the web UI Add a Shows grid with artwork and compact feed metadata. Reuse the existing episode cards when browsing a show. --- CLAUDE.md | 12 ++ README.md | 12 ++ html/index.html | 539 ++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 458 insertions(+), 105 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c6f4cf5a..be6dad66 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -299,6 +299,18 @@ debug = false - `/debug/vars` - Runtime metrics (if `debug_endpoints = true`) - `/robots.txt` - Search engine blocking (if `no_index = true`) +### Web UI Navigation + +- `html/index.html` loads `podsync.opml` and fetches each unique RSS feed once per page load +- Latest Episodes is the default landing page; `#shows` opens a responsive artwork grid +- Show cards use channel-level iTunes artwork or RSS image metadata, with a placeholder for missing or broken images +- Shows are sorted by their newest available episode, followed by title when dates match +- Compact metadata uses `2h ago · 42 new`: time is the newest episode's publication date, and count is the number of available RSS items, not playback history +- Clicking a show opens `#show={encoded_feed_url}` and calls the same `displayEpisodesInUI` renderer as Latest Episodes +- Native hash links support browser Back, direct show links, and reverse-proxy path prefixes without extra server routes +- Empty feeds and unavailable feeds remain visible in Shows; feed failures do not prevent other shows from loading +- The UI follows the browser's light or dark appearance + ## Storage Behavior ### Local Storage diff --git a/README.md b/README.md index 4942aa13..913504e8 100644 --- a/README.md +++ b/README.md @@ -195,3 +195,15 @@ This fork adds the following features while maintaining compatibility with upstr - Configurable `filename_template` for downloaded media and RSS enclosure paths - Optional one-time filename migration CLI (`--migrate-filenames`, `--migrate-filenames-dry-run`) +- Responsive web UI with Latest Episodes and Shows navigation + +### Web UI + +Enable `web_ui = true` in `[server]` when using local storage. Latest Episodes remains the landing page. +The Shows tab displays artwork, names, and compact metadata such as `2h ago · 42 new` for feeds included +in `podsync.opml`. The time comes from the newest episode's publication date, and the count is the number +of episodes currently available in that RSS feed. Playback history is not tracked. + +Click a show's artwork to browse only its episodes, using the same episode cards, playback, and download +controls as Latest Episodes. Shows are sorted by their newest episode, and browser Back and bookmarked +show links work with the existing web UI, including under a reverse-proxy path prefix. diff --git a/html/index.html b/html/index.html index e7846dda..f1b69e5c 100644 --- a/html/index.html +++ b/html/index.html @@ -284,36 +284,211 @@ color: #D70015; border-radius: 12px; } + + /* Library navigation and show artwork grid */ + .library-header { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 12px; + width: 100%; + } + .library-actions { + display: flex; + align-items: center; + gap: 16px; + margin-left: auto; + } + .library-nav { + display: flex; + gap: 28px; + width: 100%; + margin-top: 8px; + } + .library-nav-link { + display: inline-flex; + align-items: center; + min-height: 44px; + padding: 8px 0; + border-bottom: 3px solid transparent; + color: #65656E; + font-weight: 500; + text-decoration: none; + } + .library-nav-link:hover, + .library-nav-link[aria-current="page"] { + color: #9D4EDD; + } + .library-nav-link[aria-current="page"] { + border-bottom-color: #AF52DE; + } + .container.shows-layout { + max-width: 1120px; + } + .shows-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 28px 16px; + padding-bottom: 32px; + } + .show-link { + display: block; + min-width: 0; + color: #1D1D1F; + text-decoration: none; + border-radius: 12px; + } + .show-link:focus-visible { + outline: 3px solid #AF52DE; + outline-offset: 6px; + } + .show-artwork { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + aspect-ratio: 1; + overflow: hidden; + border-radius: 12px; + background: #E5E5EA; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + } + .show-artwork img { + width: 100%; + height: 100%; + object-fit: contain; + } + .show-artwork-placeholder { + color: #8E8E93; + font-size: 3rem; + } + .show-name { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + overflow-wrap: anywhere; + margin: 12px 0 4px; + font-size: 1rem; + font-weight: 600; + line-height: 1.35; + } + .show-link:hover .show-name { + color: #9D4EDD; + } + .show-meta { + margin: 0; + color: #65656E; + font-size: 0.8rem; + } + .show-navigation { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + } + .show-navigation a { + display: inline-flex; + align-items: center; + min-height: 44px; + color: #9D4EDD; + text-decoration: none; + } + #page-title { + overflow-wrap: anywhere; + } + @media (min-width: 600px) { + .shows-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 32px 24px; + } + } + @media (min-width: 992px) { + .shows-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + } + @media (max-width: 575px) { + .library-actions { + gap: 10px; + } + .navbar-text { + font-size: 0.8rem; + } + } + @media (prefers-color-scheme: dark) { + body { + background-color: #141418; + color: #F5F5F7; + } + .navbar { + background: #202024 !important; + } + .navbar-brand { + color: #F5F5F7 !important; + } + h2, .episode-title, .show-link { + color: #F5F5F7; + } + .episode-card { + background: #202024; + } + .description-text { + color: #D7D7DD; + } + .duration-badge, .episode-image-placeholder, .show-artwork { + background: #2C2C31; + } + .library-nav-link, .show-meta, #empty-message { + color: #ABABB3; + } + .library-nav-link:hover, + .library-nav-link[aria-current="page"], + .show-link:hover .show-name, + .show-navigation a { + color: #C583F0; + } + }
-