diff --git a/webui/index.html b/webui/index.html
index d34431f1..3327f422 100644
--- a/webui/index.html
+++ b/webui/index.html
@@ -962,6 +962,16 @@
+
diff --git a/webui/static/video/video-calendar.js b/webui/static/video/video-calendar.js
index 4248a61c..a4a03d59 100644
--- a/webui/static/video/video-calendar.js
+++ b/webui/static/video/video-calendar.js
@@ -18,7 +18,7 @@
var MO = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var MO_FULL = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
'September', 'October', 'November', 'December'];
- var state = { loaded: false, eps: {}, data: null, offset: 0, filter: 'all' };
+ var state = { loaded: false, eps: {}, data: null, offset: 0, filter: 'all', view: 'cards' };
function $(s) { return document.querySelector(s); }
function esc(s) {
@@ -88,7 +88,7 @@
: '
Anytime';
var flag = ep.has_file ? '
✓' : '';
var meta = '
' + se + '' + (epTitle ? '
' + esc(epTitle) + '' : '');
- return '
' +
@@ -448,6 +448,10 @@
for (var i = 0; i < fbs.length; i++) (function (b) {
b.addEventListener('click', function () { state.filter = b.getAttribute('data-video-cal-filter'); render(); });
})(fbs[i]);
+ var vbs = document.querySelectorAll('[data-video-cal-view]');
+ for (var k = 0; k < vbs.length; k++) (function (b) {
+ b.addEventListener('click', function () { setView(b.getAttribute('data-video-cal-view')); });
+ })(vbs[k]);
var addBtn = $('[data-video-cal-addmissing]');
if (addBtn) addBtn.addEventListener('click', addMissing);
@@ -587,6 +591,21 @@
.catch(function () { /* modal already shows payload data */ });
}
+ // View switcher (cards ↔ compact). Just toggles a class on the stable grid
+ // wrapper — no refetch/re-render needed — and remembers the choice.
+ function setView(v) {
+ state.view = v;
+ try { localStorage.setItem('vcalView', v); } catch (e) { /* private mode */ }
+ applyView();
+ }
+ function applyView() {
+ var grid = $('[data-video-cal-grid]');
+ if (grid) grid.classList.toggle('vcal-view--compact', state.view === 'compact');
+ var vbs = document.querySelectorAll('[data-video-cal-view]');
+ for (var i = 0; i < vbs.length; i++)
+ vbs[i].classList.toggle('vcal-view-btn--on', vbs[i].getAttribute('data-video-cal-view') === state.view);
+ }
+
function onPageShown(e) {
if (!e || e.detail !== PAGE_ID) return;
if (!state.loaded) { state.scrollToNow = true; load(); }
@@ -595,6 +614,8 @@
function init() {
wire();
+ try { var sv = localStorage.getItem('vcalView'); if (sv) state.view = sv; } catch (e) { /* private mode */ }
+ applyView();
document.addEventListener('soulsync:video-page-shown', onPageShown);
}
diff --git a/webui/static/video/video-side.css b/webui/static/video/video-side.css
index 05ff07f5..f829da88 100644
--- a/webui/static/video/video-side.css
+++ b/webui/static/video/video-side.css
@@ -1809,6 +1809,25 @@ body[data-side="video"] #soulsync-toggle { display: none; }
padding: 7px 14px; border-radius: 8px; cursor: pointer; transition: all 0.15s ease; }
.vcal-filter-btn:hover { color: #fff; background: rgba(255, 255, 255, 0.08); }
.vcal-filter-btn--on { color: #fff; background: rgba(var(--vcal-accent), 0.9); box-shadow: 0 4px 14px rgba(var(--vcal-accent), 0.32); }
+/* view switcher (cards ↔ compact) — mirrors the filter pill group, with icons */
+.vcal-views { display: inline-flex; gap: 3px; padding: 4px; border-radius: 12px;
+ background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.08); }
+.vcal-view-btn { display: inline-flex; align-items: center; gap: 6px; border: 0; background: transparent;
+ color: rgba(255, 255, 255, 0.7); font-size: 12.5px; font-weight: 700; padding: 7px 13px; border-radius: 8px;
+ cursor: pointer; transition: all 0.15s ease; }
+.vcal-view-btn svg { opacity: 0.85; }
+.vcal-view-btn:hover { color: #fff; background: rgba(255, 255, 255, 0.08); }
+.vcal-view-btn--on { color: #fff; background: rgba(var(--vcal-accent), 0.9); box-shadow: 0 4px 14px rgba(var(--vcal-accent), 0.32); }
+
+/* Compact view — drop the episode art to fit a lot more on screen */
+.vcal-grid.vcal-view--compact .vcal-art { display: none; }
+.vcal-grid.vcal-view--compact .vcal-slot { gap: 8px; padding: 13px 10px; }
+.vcal-grid.vcal-view--compact .vcal-card { border-radius: 10px; }
+.vcal-grid.vcal-view--compact .vcal-info { padding: 10px 12px; }
+.vcal-grid.vcal-view--compact .vcal-show { margin-top: 4px; }
+/* the art carried the library ✓ — surface ownership as a left accent stripe instead */
+.vcal-grid.vcal-view--compact .vcal-cell--owned .vcal-card { box-shadow: inset 3px 0 0 #6cd391; }
+.vcal-grid.vcal-view--compact .vcal-cell--owned .vcal-info { padding-left: 15px; }
/* catch-up button: queue already-aired missing episodes to the wishlist */
.vcal-addmissing { display: inline-flex; align-items: center; gap: 7px; padding: 9px 16px; border-radius: 11px;
border: 1px solid rgba(var(--accent-rgb, 88 101 242), 0.4); cursor: pointer; font-size: 12.5px; font-weight: 800;