Add the Glass Stage layout theme with overlay controls, 10 s skips, quick chips and stacked tiles
This commit is contained in:
@@ -364,7 +364,7 @@ const DEFAULT_SETTINGS = {
|
||||
density: 'comfortable', // 'comfortable' | 'compact'
|
||||
perfMode: false, // disable heavy visual effects for speed
|
||||
reduceMotion: false, // disable animations
|
||||
layout: 'classic', // 'classic'|'sanctuary-stage'|'liturgical'|'living-hymnal'|'broadcaster'|'contemplative'|'bento-hub'
|
||||
layout: 'classic', // 'classic'|'glass-stage'|'sanctuary-stage'|'liturgical'|'living-hymnal'|'broadcaster'|'contemplative'|'bento-hub'
|
||||
autoBackupEnabled: false,
|
||||
autoBackupIntervalDays: 7,
|
||||
serviceVideo: false, // show the video panel inside service mode (lyric videos)
|
||||
@@ -1103,6 +1103,8 @@ function syncMuteIcon() {
|
||||
const waves = lvl === 0 ? '<path d="M16 9l5 6M21 9l-5 6"/>' : lvl === 1 ? '<path d="M15.5 9.5a4 4 0 0 1 0 5"/>' : '<path d="M15.5 9.5a4 4 0 0 1 0 5M18.5 6.5a8 8 0 0 1 0 11"/>';
|
||||
b.innerHTML = `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9.5v5h3.5L12 18.5v-13L7.5 9.5z" class="spk"/>${waves}</svg>`;
|
||||
b.classList.toggle('muted', lvl === 0);
|
||||
const pct = $('volPct');
|
||||
if (pct) pct.textContent = Math.round(v * 100) + '%';
|
||||
b.title = lvl === 0 ? 'Unmute' : 'Mute';
|
||||
b.setAttribute('aria-label', b.title);
|
||||
}
|
||||
@@ -2411,6 +2413,10 @@ function wirePlayerEvents() {
|
||||
function updatePlayBtn() {
|
||||
Remote.changed();
|
||||
els.playBtn.innerHTML = Player.master.paused ? ICON_PLAY : ICON_PAUSE;
|
||||
const sp = $('stgPlay');
|
||||
if (sp) sp.innerHTML = Player.master.paused ? ICON_PLAY : ICON_PAUSE;
|
||||
const stage = document.querySelector('.player-stage');
|
||||
if (stage) stage.classList.toggle('is-paused', !!Player.master.paused);
|
||||
$('miniPlayBtn').textContent = Player.master.paused ? '▶' : '⏸';
|
||||
const spBtn = $('servicePlayBtn');
|
||||
if (spBtn) spBtn.textContent = Player.master.paused ? '▶' : '⏸';
|
||||
@@ -2656,6 +2662,11 @@ function openMoreActions() {
|
||||
}
|
||||
|
||||
// Reflect cache state on the now-playing Save button.
|
||||
// Tiles are [icon][label]; only the label text changes with state.
|
||||
function setTile(btn, label) {
|
||||
const tl = btn.querySelector('.tl');
|
||||
if (tl) tl.textContent = label; else btn.textContent = label;
|
||||
}
|
||||
function updateNowPlayingActions() {
|
||||
if (!current || !current.meta) return;
|
||||
const id = current.meta.id;
|
||||
@@ -2674,20 +2685,20 @@ function updateNowPlayingActions() {
|
||||
const btn = els.saveBtn;
|
||||
if (!btn) return;
|
||||
if (removing.has(id)) {
|
||||
btn.textContent = '⏳ Removing…';
|
||||
setTile(btn, 'Removing…');
|
||||
btn.classList.remove('done');
|
||||
btn.disabled = true;
|
||||
} else if (downloading.has(id)) {
|
||||
btn.textContent = '⏳ Saving…';
|
||||
setTile(btn, 'Saving…');
|
||||
btn.classList.remove('done');
|
||||
btn.disabled = true;
|
||||
} else if (cachedIds.has(id)) {
|
||||
btn.textContent = '✓ Saved';
|
||||
setTile(btn, 'Saved');
|
||||
btn.classList.add('done');
|
||||
btn.disabled = false;
|
||||
btn.title = 'Saved for offline — click to remove from cache';
|
||||
} else {
|
||||
btn.textContent = '⬇ Save';
|
||||
setTile(btn, 'Save');
|
||||
btn.classList.remove('done');
|
||||
btn.disabled = false;
|
||||
btn.title = 'Save this video for offline playback';
|
||||
@@ -3889,6 +3900,48 @@ const Notes = (() => {
|
||||
};
|
||||
})();
|
||||
|
||||
// ---- Glass Stage layout: stage overlay, 10 s skips, quick-setting chips ----
|
||||
(function glassStage() {
|
||||
const skip = (d) => { try { Player.seek(Math.max(0, Player.master.currentTime + d)); } catch { /* not ready */ } };
|
||||
const on = (id, fn) => { const el = document.getElementById(id); if (el) el.addEventListener('click', fn); };
|
||||
on('back10Btn', () => skip(-10)); on('fwd10Btn', () => skip(10));
|
||||
on('stgBack', () => skip(-10)); on('stgFwd', () => skip(10));
|
||||
on('stgPlay', () => $('playBtn').click());
|
||||
const setAudio = (v) => { const t = $('audioOnlyToggle'); if (t && t.checked !== v) { t.checked = v; t.dispatchEvent(new Event('change', { bubbles: true })); } };
|
||||
on('stgVideo', () => setAudio(false)); on('stgAudio', () => setAudio(true));
|
||||
const stage = document.querySelector('.player-stage');
|
||||
let hideT = null;
|
||||
if (stage) stage.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.stg-overlay button')) return;
|
||||
stage.classList.add('stg-show');
|
||||
clearTimeout(hideT);
|
||||
hideT = setTimeout(() => stage.classList.remove('stg-show'), 3000);
|
||||
});
|
||||
function chips() {
|
||||
if (document.documentElement.dataset.layout !== 'glass-stage') return;
|
||||
const box = $('deckChips');
|
||||
if (!box) return;
|
||||
const after = (id) => { const t = String(($(id) || {}).title || ''); const m = t.match(/:\s*([^—(]+?)\s*(?:—|\(|$)/); return m ? m[1].trim() : ''; };
|
||||
const list = [['Speed ' + (($('speedSelect') || {}).value || '1') + '×', true]];
|
||||
const eq = after('eqBtn'); if (eq) list.push(['EQ: ' + eq, false]);
|
||||
const tr = after('transitionBtn'); if (tr) list.push([tr === 'Normal' ? 'Gapless: off' : tr, false]);
|
||||
const pip = $('pipBtn'); if (pip && !pip.classList.contains('hidden')) list.push(['PiP ready', false]);
|
||||
const key = list.map((x) => x[0]).join('|');
|
||||
if (box.dataset.key !== key) {
|
||||
box.dataset.key = key;
|
||||
box.innerHTML = list.map(([t, a]) => `<button type="button" class="dchip${a ? ' on' : ''}"></button>`).join('');
|
||||
[...box.children].forEach((b, i) => { b.textContent = list[i][0]; b.onclick = openMoreControls; });
|
||||
}
|
||||
const q = $('qualitySelect'), sq = $('stgQuality');
|
||||
if (sq && q && q.options.length) sq.textContent = q.options[q.selectedIndex] ? q.options[q.selectedIndex].text : 'Auto';
|
||||
const ao = !!data.settings.audioOnly;
|
||||
const v = $('stgVideo'), a = $('stgAudio');
|
||||
if (v) v.classList.toggle('on', !ao); if (a) a.classList.toggle('on', ao);
|
||||
}
|
||||
setInterval(chips, 1500);
|
||||
setTimeout(chips, 400);
|
||||
})();
|
||||
|
||||
// ============================================================================
|
||||
// Phone remote (server/remote.js relays both ends over /ws/remote)
|
||||
// ============================================================================
|
||||
@@ -6931,7 +6984,7 @@ function updateLoopRepeatButtons() {
|
||||
const mb = $('modeBtn');
|
||||
if (mb) {
|
||||
const i = playModeIndex();
|
||||
mb.innerHTML = `<svg viewBox="0 0 24 24" aria-hidden="true">${MODE_ICONS[i]}</svg>`;
|
||||
mb.innerHTML = `<svg viewBox="0 0 24 24" aria-hidden="true">${MODE_ICONS[i]}</svg><span class="mode-name">${PLAY_MODES[i].name}</span>`;
|
||||
mb.classList.toggle('active', i > 0);
|
||||
mb.dataset.badge = i === 3 ? '∞' : '';
|
||||
mb.title = `${PLAY_MODES[i].name} — ${PLAY_MODES[i].sub}. Tap to change.`;
|
||||
@@ -7939,7 +7992,7 @@ async function renderSettings() {
|
||||
</label>
|
||||
<label class="set-row">
|
||||
<span>Layout theme</span>
|
||||
${sel('setLayout', data.settings.layout || 'classic', [['classic', 'Classic (default)'], ['sanctuary-stage', 'Sanctuary Stage'], ['liturgical', 'Liturgical Chronicle'], ['living-hymnal', 'Living Hymnal'], ['broadcaster', 'Sunday Broadcaster'], ['contemplative', 'Contemplative'], ['bento-hub', 'Bento Hub']])}
|
||||
${sel('setLayout', data.settings.layout || 'classic', [['classic', 'Classic (default)'], ['glass-stage', 'Glass Stage'], ['sanctuary-stage', 'Sanctuary Stage'], ['liturgical', 'Liturgical Chronicle'], ['living-hymnal', 'Living Hymnal'], ['broadcaster', 'Sunday Broadcaster'], ['contemplative', 'Contemplative'], ['bento-hub', 'Bento Hub']])}
|
||||
</label>
|
||||
<label class="set-row">
|
||||
<span>Font size</span>
|
||||
|
||||
Reference in New Issue
Block a user