diff --git a/webui/index.html b/webui/index.html
index ee3b7304..1d875e8a 100644
--- a/webui/index.html
+++ b/webui/index.html
@@ -1051,6 +1051,17 @@
Hide owned
+
+ 🌐
+
+
+
+
+
+
+
+
+
diff --git a/webui/static/video/video-discover.js b/webui/static/video/video-discover.js
index e7239b86..c574d013 100644
--- a/webui/static/video/video-discover.js
+++ b/webui/static/video/video-discover.js
@@ -333,6 +333,14 @@
.catch(function () { /* best-effort */ });
}
+ // Rebuild the whole rail stack (genre/curated shelves + the prepended personalized rows).
+ function reloadRails() {
+ renderShelves();
+ loadMoreLike();
+ loadGaps();
+ loadForYou();
+ }
+
// ── shelves (lazy rails) ──────────────────────────────────────────────────
function renderShelves() {
var host = $('[data-vdsc-shelves]'); if (!host) return;
@@ -546,12 +554,30 @@
hide.addEventListener('change', function () {
page.classList.toggle('vdsc-hide-owned', hide.checked);
try { localStorage.setItem('vdsc_hideowned', hide.checked ? '1' : '0'); } catch (e) { /* ignore */ }
- // The rails are now server-filtered for owned (and page deeper), so rebuild
- // them with the new state instead of just CSS-hiding cards in place.
- renderShelves();
- loadMoreLike();
- loadGaps();
- loadForYou();
+ reloadRails(); // rails are server-filtered now (owned + paged deeper) — rebuild
+ });
+ }
+ // Rail language preference — multi-select chips, persisted server-side; rebuilds the rails.
+ var langWrap = $('[data-vdsc-langs]');
+ if (langWrap) {
+ fetch('/api/video/discover/languages', { headers: { Accept: 'application/json' } })
+ .then(function (r) { return r.ok ? r.json() : null; })
+ .then(function (d) {
+ var set = {}; ((d && d.languages) || ['en']).forEach(function (c) { set[c] = 1; });
+ langWrap.querySelectorAll('.vdsc-lang').forEach(function (b) {
+ b.classList.toggle('vdsc-lang--on', !!set[b.getAttribute('data-lang')]);
+ });
+ }).catch(function () { /* default chip state stands */ });
+ langWrap.addEventListener('click', function (e) {
+ var btn = e.target.closest('.vdsc-lang'); if (!btn) return;
+ btn.classList.toggle('vdsc-lang--on');
+ var langs = Array.prototype.map.call(langWrap.querySelectorAll('.vdsc-lang--on'),
+ function (b) { return b.getAttribute('data-lang'); });
+ if (!langs.length) { btn.classList.add('vdsc-lang--on'); return; } // never allow empty
+ fetch('/api/video/discover/languages', {
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ languages: langs }),
+ }).then(function () { reloadRails(); }).catch(function () { /* ignore */ });
});
}
// Infinite scroll: a sentinel near the grid bottom pulls the next page.
diff --git a/webui/static/video/video-side.css b/webui/static/video/video-side.css
index 7b86701d..1830d196 100644
--- a/webui/static/video/video-side.css
+++ b/webui/static/video/video-side.css
@@ -2354,6 +2354,16 @@ body[data-side="video"] #soulsync-toggle { display: none; }
.vdsc-toggle input { accent-color: rgb(var(--accent-rgb, 88 101 242)); width: 16px; height: 16px; cursor: pointer; }
.vdsc-hide-owned .vsr-card--owned { display: none; }
+/* rail-language preference chips — which original languages show in the general rails */
+.vdsc-langs { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; margin-left: 14px; }
+.vdsc-langs-label { font-size: 13px; opacity: 0.6; }
+.vdsc-lang { font-size: 11px; font-weight: 700; letter-spacing: 0.3px; padding: 3px 8px; border-radius: 999px;
+ border: 1px solid rgba(255, 255, 255, 0.12); background: rgba(255, 255, 255, 0.04);
+ color: rgba(255, 255, 255, 0.5); cursor: pointer;
+ transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease; }
+.vdsc-lang:hover { color: rgba(255, 255, 255, 0.85); }
+.vdsc-lang--on { color: #fff; background: rgba(var(--accent-rgb, 88 101 242), 0.85); border-color: transparent; }
+
/* rails: a gentle fade-in when filled + per-title hue on hover */
.vdsc-shelf--in .vdsc-rail { animation: vdscFade 0.45s ease both; }
@keyframes vdscFade { from { opacity: 0.35; } to { opacity: 1; } }