From a7954426349a533c8a8b89f287c21b6859330d71 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 19 Sep 2026 08:27:43 +0000 Subject: [PATCH] Choose a channel by what it is, not by its name in a list A was also where the command palette read its channel actions, so the list lives in App.ui.channels now and the palette asks for it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QBDkEXP4htyXTCZUwMLphd --- frontend/css/style.css | 251 ++++++++++++++++++++++++ frontend/index.html | 26 ++- frontend/js/enhance.js | 18 +- frontend/js/ui.js | 423 ++++++++++++++++++++++++++++++++-------- tests/smoke_channels.py | 199 +++++++++++++++++++ 5 files changed, 834 insertions(+), 83 deletions(-) create mode 100644 tests/smoke_channels.py diff --git a/frontend/css/style.css b/frontend/css/style.css index 29c0649..0191870 100644 --- a/frontend/css/style.css +++ b/frontend/css/style.css @@ -2704,6 +2704,257 @@ body.theme-light .video-card img:not(.is-loaded) { flex-shrink: 0; } +/* --- Channel picker ---------------------------------------------------- */ +/* The trigger in the menu drawer: what's being read now, favicon and all. */ +.channel-trigger { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + padding: 10px 12px; + background: var(--bg-tertiary); + border: 1px solid var(--border); + border-radius: 10px; + color: var(--text-primary); + font-family: var(--font-body); + font-size: 14px; + text-align: left; + cursor: pointer; + transition: border-color 0.15s ease, background 0.15s ease; +} + +.channel-trigger:hover, +.channel-trigger:focus-visible { + border-color: var(--border-hover); + outline: none; +} + +.channel-trigger-text { + flex: 1; + min-width: 0; +} + +.channel-trigger-name, +.channel-trigger-note { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.channel-trigger-note { + font-size: 12px; + color: var(--text-secondary); + text-transform: none; + letter-spacing: 0; +} + +.channel-trigger-caret { + flex-shrink: 0; + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 6px solid var(--text-secondary); +} + +/* A favicon, with the channel's initial behind it for the ones that never + arrive -- a row is never a name beside an empty square. */ +.channel-mark { + position: relative; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 8px; + background: rgba(201, 165, 103, 0.14); + border: 1px solid var(--border); + overflow: hidden; +} + +.channel-mark::before { + content: attr(data-letter); + font-family: var(--font-display); + font-size: 14px; + line-height: 1; + color: var(--accent); +} + +.channel-favicon { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: contain; + padding: 3px; + background: inherit; +} + +.channel-picker { + position: fixed; + inset: 0; + z-index: 4200; + display: none; + align-items: flex-start; + justify-content: center; + padding: 8vh 12px 12px 12px; + background: rgba(10, 8, 4, 0.55); + backdrop-filter: blur(6px); +} + +.channel-picker.open { display: flex; } + +.channel-picker-box { + display: flex; + flex-direction: column; + width: min(560px, 100%); + max-height: min(74vh, 700px); + /* Solid, not the usual translucent panel: the section headings stick over + the rows as they scroll, and a see-through heading is unreadable. */ + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: 0 30px 70px var(--shadow); + overflow: hidden; + animation: card-rise 0.2s ease both; +} + +.channel-picker-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 16px 18px 10px 18px; +} + +.channel-picker-head h3 { + font-family: var(--font-display); + font-size: 18px; + font-weight: 600; +} + +.channel-search { + margin: 0 18px 12px 18px; + padding: 11px 14px; + background: var(--bg-tertiary); + border: 1px solid var(--border); + border-radius: 10px; + color: var(--text-primary); + font-family: var(--font-body); + font-size: 14px; +} + +.channel-search::placeholder { color: var(--text-secondary); } +.channel-search:focus { outline: none; border-color: var(--border-hover); } + +.channel-picker-list { + flex: 1; + overflow-y: auto; + padding: 0 10px 10px 10px; + overscroll-behavior: contain; +} + +.channel-section { + position: sticky; + top: 0; + z-index: 1; + padding: 10px 8px 6px 8px; + background: var(--bg-primary); + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-secondary); +} + +.channel-row { + display: flex; + align-items: flex-start; + gap: 12px; + width: 100%; + padding: 10px 12px; + border: 1px solid transparent; + border-radius: 10px; + background: transparent; + color: var(--text-primary); + font-family: var(--font-body); + text-align: left; + cursor: pointer; +} + +.channel-row.is-active { background: var(--bg-tertiary); } + +.channel-row.is-current { + border-color: var(--border-hover); + background: rgba(201, 165, 103, 0.10); +} + +.channel-row-text { + flex: 1; + min-width: 0; +} + +.channel-row-name { + display: block; + font-size: 14px; +} + +.channel-row-note { + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + margin-top: 2px; + font-size: 12px; + line-height: 1.4; + color: var(--text-secondary); +} + +.channel-row-tags { + display: flex; + flex-wrap: wrap; + gap: 5px; + margin-top: 6px; +} + +.channel-tag { + padding: 2px 7px; + border: 1px solid var(--border); + border-radius: 999px; + font-size: 10px; + letter-spacing: 0.04em; + color: var(--text-secondary); +} + +.channel-row-flag { + flex-shrink: 0; + align-self: center; + font-size: 10px; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--accent); +} + +.channel-picker-empty { + padding: 8px 20px 20px 20px; + color: var(--text-secondary); + font-size: 13px; +} + +@media (max-width: 720px) { + .channel-picker { padding: 0; } + + .channel-picker-box { + width: 100%; + max-height: 100%; + height: 100%; + border: none; + border-radius: 0; + } +} + /* --- Reels HUD polish: serif title + brass scrubber + mute pulse ------- */ .feed-title { font-family: var(--font-display); } diff --git a/frontend/index.html b/frontend/index.html index ac2e67f..fbfbc4c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -58,8 +58,17 @@
- - + +
@@ -194,6 +203,19 @@ + +