Calendar: view switcher with a Compact (art-hidden) view

Adds a Cards/Compact toggle next to the week filter. Compact drops the 16:9
episode art (.vcal-art) — the big space eater — and tightens the cells so a lot
more episodes fit on screen at once; ownership (which the art's ✓ badge used to
show) becomes a green left accent stripe on owned cells. Just toggles a class on
the stable grid wrapper (no refetch), and the choice persists in localStorage.

Calendar-only; music side untouched.
This commit is contained in:
BoulderBadgeDad 2026-06-17 00:00:28 -07:00
parent 7c1644384d
commit 987cee7cb3
3 changed files with 52 additions and 2 deletions

View file

@ -962,6 +962,16 @@
<button class="vcal-nav-today" type="button" data-video-cal-today>Today</button>
<button class="vcal-nav-btn" type="button" data-video-cal-next aria-label="Next week">&#8250;</button>
</div>
<div class="vcal-views" role="group" aria-label="View">
<button class="vcal-view-btn vcal-view-btn--on" type="button" data-video-cal-view="cards" title="Card view with episode art">
<svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><rect x="1" y="1" width="6" height="6" rx="1.6"/><rect x="9" y="1" width="6" height="6" rx="1.6"/><rect x="1" y="9" width="6" height="6" rx="1.6"/><rect x="9" y="9" width="6" height="6" rx="1.6"/></svg>
<span>Cards</span>
</button>
<button class="vcal-view-btn" type="button" data-video-cal-view="compact" title="Compact view — hide art to fit more episodes">
<svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><rect x="1" y="2.4" width="14" height="2.3" rx="1.1"/><rect x="1" y="6.85" width="14" height="2.3" rx="1.1"/><rect x="1" y="11.3" width="14" height="2.3" rx="1.1"/></svg>
<span>Compact</span>
</button>
</div>
<div class="vcal-filter" role="group" aria-label="Filter">
<button class="vcal-filter-btn vcal-filter-btn--on" type="button" data-video-cal-filter="all">All</button>
<button class="vcal-filter-btn" type="button" data-video-cal-filter="owned">In library</button>

View file

@ -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 @@
: '<span class="vcal-time vcal-time--none">Anytime</span>';
var flag = ep.has_file ? '<span class="vcal-flag" title="In your library">✓</span>' : '';
var meta = '<span class="vcal-se">' + se + '</span>' + (epTitle ? '<span class="vcal-ep">' + esc(epTitle) + '</span>' : '');
return '<a class="vcal-cell" style="--vcal-h:' + hue + ';--i:' + (idx % 24) + '" ' +
return '<a class="vcal-cell' + (ep.has_file ? ' vcal-cell--owned' : '') + '" style="--vcal-h:' + hue + ';--i:' + (idx % 24) + '" ' +
'href="/video-detail/library/show/' + ep.show_id + '" ' +
'data-cal-ep="' + ep.id + '" ' +
'title="' + esc(ep.show_title) + (epTitle ? ' — ' + esc(epTitle) : '') + (tl ? ' · ' + tl : '') + '">' +
@ -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);
}

View file

@ -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;