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 @@
diff --git a/frontend/js/enhance.js b/frontend/js/enhance.js
index a7efb8d..06a0148 100644
--- a/frontend/js/enhance.js
+++ b/frontend/js/enhance.js
@@ -196,11 +196,19 @@ App.enhance = App.enhance || {};
out.push({ label: opt.textContent, hint: 'Source', run: () => { sourceSelect.value = opt.value; fireChange(sourceSelect); } });
});
}
- const channelSelect = document.getElementById('channel-select');
- if (channelSelect) {
- Array.from(channelSelect.options).forEach((opt) => {
- if (opt.value === channelSelect.value) return;
- out.push({ label: opt.textContent, hint: 'Channel', run: () => { channelSelect.value = opt.value; fireChange(channelSelect); } });
+ // The channel list is the picker's (App.ui.channels), not a
+ //