Gospel page audio: choose-your-path narration (case, then Guilty or Not guilty tracks), branching player, three admin audio fields, generator --sample/--only; v2.41.0
Publish Container / build (push) Successful in 16m4s
Publish Container / build (push) Successful in 16m4s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ad1c3e35b1
commit
ae0016cfee
9 files changed
+228
-55
No files matched your search
@@ -643,6 +643,22 @@
|
||||
#audio-seek { flex: 1; min-width: 0; accent-color: var(--gold); height: 1.6rem; cursor: pointer; }
|
||||
.audio-time { font-family: 'Cinzel', serif; font-size: 0.8rem; min-width: 4.6rem; text-align: right; color: var(--parchment-dark); white-space: nowrap; }
|
||||
@media (max-width: 600px) { .audio-controls { flex-wrap: wrap; } #audio-seek { flex-basis: 100%; order: 5; } .audio-time { margin-left: auto; } }
|
||||
|
||||
.audio-actions { margin-top: 1rem; padding-top: 1rem; border-top: 1px dashed rgba(184,134,11,0.5); }
|
||||
.audio-actions[hidden] { display: none; }
|
||||
.audio-q { font-family: 'Cinzel', serif; font-weight: 900; font-size: 1.25rem; letter-spacing: 0.06em; text-transform: uppercase; color: #fff; margin-bottom: 0.7rem; text-align: center; }
|
||||
.audio-choices { display: grid; grid-template-columns: 1fr 1fr; gap: 0.8rem; }
|
||||
.audio-choice { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; padding: 1rem 0.8rem; font-family: 'Cinzel', serif; font-weight: 900; font-size: 1.1rem; letter-spacing: 0.08em; text-transform: uppercase; color: #1a0f05; background: linear-gradient(135deg, #f0c24a, var(--gold) 55%, #9a6f0a); border: 2px solid #f6d77a; border-radius: 3px; cursor: pointer; box-shadow: 0 0 22px rgba(240,194,74,0.3); transition: transform .15s, filter .15s; }
|
||||
.audio-choice small { font-family: 'EB Garamond', serif; font-weight: 500; font-size: 0.95rem; letter-spacing: 0; text-transform: none; }
|
||||
.audio-choice:hover { transform: translateY(-2px); filter: brightness(1.08); }
|
||||
.audio-choice--alt { background: transparent; color: var(--parchment); border-color: var(--gold); box-shadow: none; }
|
||||
.audio-choice--alt:hover { background: rgba(184,134,11,0.18); filter: none; }
|
||||
.audio-actions-row { display: flex; flex-wrap: wrap; gap: 0.7rem; justify-content: center; }
|
||||
.audio-actions-row .audio-choice { flex: 1 1 14rem; }
|
||||
.audio-skip { margin-top: 0.8rem; text-align: center; }
|
||||
.audio-skip[hidden] { display: none; }
|
||||
.audio-link { background: none; border: 0; color: var(--parchment-dark); font-family: 'EB Garamond', serif; font-size: 1rem; font-style: italic; text-decoration: underline; cursor: pointer; }
|
||||
@media (max-width: 600px) { .audio-choices { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -687,8 +703,8 @@
|
||||
<div class="audio-top">
|
||||
<span class="audio-icon" aria-hidden="true">🎧</span>
|
||||
<div>
|
||||
<p class="audio-title">Listen to the case</p>
|
||||
<p class="audio-sub">The full case read aloud, about 10 minutes.</p>
|
||||
<p class="audio-title" id="audio-title">Listen to the case</p>
|
||||
<p class="audio-sub" id="audio-sub">The case read aloud, about 10 minutes. At the end you choose your plea.</p>
|
||||
</div>
|
||||
</div>
|
||||
<audio id="audio-el" preload="none"></audio>
|
||||
@@ -699,6 +715,8 @@
|
||||
<span class="audio-time" id="audio-time">0:00</span>
|
||||
<button type="button" class="audio-btn audio-speed" id="audio-speed" aria-label="Playback speed">1×</button>
|
||||
</div>
|
||||
<div class="audio-actions" id="audio-actions" hidden></div>
|
||||
<p class="audio-skip" id="audio-skip-wrap"><button type="button" class="audio-link" id="audio-skip">Skip to the plea</button></p>
|
||||
</div>
|
||||
|
||||
<section class="gospel-message" aria-labelledby="good-news">
|
||||
@@ -1650,8 +1668,9 @@ buildTimeline();
|
||||
});
|
||||
|
||||
|
||||
// ── Audio narration (shown only when a URL is set in Admin → Gospel Page) ──
|
||||
fetch('/api/salvation-audio').then(function (r) { return r.ok ? r.json() : { url: '' }; }).then(function (d) {
|
||||
// ── Audio narration (shown only when URLs are set in Admin → Gospel Page) ──
|
||||
// The listener hears the case, then chooses Guilty or Not guilty and hears that branch.
|
||||
fetch('/api/salvation-audio').then(function (r) { return r.ok ? r.json() : {}; }).then(function (d) {
|
||||
if (!d || !d.url) return;
|
||||
const card = document.getElementById('audio-card');
|
||||
const el = document.getElementById('audio-el');
|
||||
@@ -1659,6 +1678,12 @@ buildTimeline();
|
||||
const seek = document.getElementById('audio-seek');
|
||||
const timeEl = document.getElementById('audio-time');
|
||||
const speedBtn = document.getElementById('audio-speed');
|
||||
const titleEl = document.getElementById('audio-title');
|
||||
const subEl = document.getElementById('audio-sub');
|
||||
const actions = document.getElementById('audio-actions');
|
||||
const skipWrap = document.getElementById('audio-skip-wrap');
|
||||
let part = 'case';
|
||||
|
||||
el.src = d.url;
|
||||
card.hidden = false;
|
||||
const fmt = function (t) { if (!isFinite(t)) return '0:00'; const m = Math.floor(t / 60); const s = Math.floor(t % 60); return m + ':' + (s < 10 ? '0' : '') + s; };
|
||||
@@ -1666,14 +1691,75 @@ buildTimeline();
|
||||
seek.value = el.duration ? (el.currentTime / el.duration) * 100 : 0;
|
||||
timeEl.textContent = fmt(el.currentTime) + (el.duration ? ' / ' + fmt(el.duration) : '');
|
||||
};
|
||||
const setPlayIcon = function (playing) { playBtn.textContent = playing ? '❚❚' : '▶'; playBtn.setAttribute('aria-label', playing ? 'Pause' : 'Play'); };
|
||||
const button = function (cls, html, onClick) {
|
||||
const b = document.createElement('button');
|
||||
b.type = 'button'; b.className = cls; b.innerHTML = html; b.addEventListener('click', onClick);
|
||||
return b;
|
||||
};
|
||||
const show = function (nodes) { actions.innerHTML = ''; nodes.forEach(function (n) { actions.appendChild(n); }); actions.hidden = false; };
|
||||
const hideActions = function () { actions.hidden = true; actions.innerHTML = ''; };
|
||||
const reveal = function () { card.scrollIntoView({ behavior: 'smooth', block: 'center' }); };
|
||||
|
||||
function showChoice() {
|
||||
skipWrap.hidden = true;
|
||||
const wrap = document.createElement('div');
|
||||
const q = document.createElement('p'); q.className = 'audio-q'; q.textContent = 'How do you plead?';
|
||||
const row = document.createElement('div'); row.className = 'audio-choices';
|
||||
row.appendChild(button('audio-choice', 'Guilty<small>I accept the pardon</small>', function () { plead('guilty'); }));
|
||||
row.appendChild(button('audio-choice audio-choice--alt', 'Not guilty<small>I stand on my own record</small>', function () { plead('not-guilty'); }));
|
||||
wrap.appendChild(q); wrap.appendChild(row);
|
||||
show([wrap]);
|
||||
}
|
||||
|
||||
function playPart(name, url, title, sub) {
|
||||
part = name;
|
||||
hideActions();
|
||||
titleEl.textContent = title; subEl.textContent = sub;
|
||||
el.src = url; el.playbackRate = el.playbackRate || 1;
|
||||
el.play().catch(function () {});
|
||||
}
|
||||
|
||||
function plead(value) {
|
||||
skipWrap.hidden = true;
|
||||
// Keep the page in step with the audio: select the plea and open its panel without scrolling away.
|
||||
choose(value, { scroll: false, setSelect: true });
|
||||
const url = value === 'guilty' ? d.guiltyUrl : d.notGuiltyUrl;
|
||||
if (!url) { choose(value, { scroll: true, setSelect: true }); return; }
|
||||
playPart(value === 'guilty' ? 'guilty' : 'not-guilty', url,
|
||||
value === 'guilty' ? 'You pleaded guilty' : 'You pleaded not guilty',
|
||||
value === 'guilty' ? 'Here is what happens next.' : 'Please be sure you understand what this means.');
|
||||
reveal();
|
||||
}
|
||||
|
||||
function onEnded() {
|
||||
setPlayIcon(false);
|
||||
if (part === 'case') { showChoice(); reveal(); return; }
|
||||
const row = document.createElement('div'); row.className = 'audio-actions-row';
|
||||
if (part === 'guilty') {
|
||||
row.appendChild(button('audio-choice', 'Let us know', function () {
|
||||
choose('guilty', { scroll: false, setSelect: true });
|
||||
const pray = document.getElementById('btn-pray'); if (pray && pray.style.display !== 'none') pray.click();
|
||||
document.getElementById('plea-guilty').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}));
|
||||
row.appendChild(button('audio-choice audio-choice--alt', 'Read the record', function () { location.hash = '#record'; }));
|
||||
} else {
|
||||
row.appendChild(button('audio-choice', 'Change my plea to guilty', function () { plead('guilty'); }));
|
||||
row.appendChild(button('audio-choice audio-choice--alt', 'Read the record', function () { location.hash = '#record'; }));
|
||||
row.appendChild(button('audio-choice audio-choice--alt', 'Ask a question', function () { location.href = '/contact?source=salvation'; }));
|
||||
}
|
||||
show([row]); reveal();
|
||||
}
|
||||
|
||||
playBtn.addEventListener('click', function () { if (el.paused) el.play(); else el.pause(); });
|
||||
el.addEventListener('play', function () { playBtn.textContent = '❚❚'; playBtn.setAttribute('aria-label', 'Pause'); });
|
||||
el.addEventListener('pause', function () { playBtn.textContent = '▶'; playBtn.setAttribute('aria-label', 'Play'); });
|
||||
el.addEventListener('ended', function () { playBtn.textContent = '▶'; playBtn.setAttribute('aria-label', 'Play'); });
|
||||
el.addEventListener('play', function () { setPlayIcon(true); });
|
||||
el.addEventListener('pause', function () { setPlayIcon(false); });
|
||||
el.addEventListener('ended', onEnded);
|
||||
el.addEventListener('timeupdate', paint);
|
||||
el.addEventListener('loadedmetadata', paint);
|
||||
seek.addEventListener('input', function () { if (el.duration) el.currentTime = (seek.value / 100) * el.duration; });
|
||||
document.getElementById('audio-back').addEventListener('click', function () { el.currentTime = Math.max(0, el.currentTime - 15); });
|
||||
document.getElementById('audio-skip').addEventListener('click', function () { el.pause(); showChoice(); });
|
||||
const speeds = [1, 1.25, 1.5, 0.85];
|
||||
let si = 0;
|
||||
speedBtn.addEventListener('click', function () { si = (si + 1) % speeds.length; el.playbackRate = speeds[si]; speedBtn.textContent = speeds[si] + '×'; });
|
||||
@@ -1685,7 +1771,9 @@ buildTimeline();
|
||||
const notGuilty = document.getElementById('plea-notguilty');
|
||||
const cardG = document.getElementById('card-guilty');
|
||||
const cardN = document.getElementById('card-notguilty');
|
||||
function choose(value) {
|
||||
function choose(value, opts) {
|
||||
const o = opts || {};
|
||||
if (o.setSelect) select.value = value;
|
||||
guilty.hidden = value !== 'guilty';
|
||||
notGuilty.hidden = value !== 'not-guilty';
|
||||
cardG.classList.toggle('is-selected', value === 'guilty');
|
||||
@@ -1693,7 +1781,7 @@ buildTimeline();
|
||||
cardG.classList.toggle('is-dim', value === 'not-guilty');
|
||||
cardN.classList.toggle('is-dim', value === 'guilty');
|
||||
const panel = value === 'guilty' ? guilty : value === 'not-guilty' ? notGuilty : null;
|
||||
if (panel) panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
if (panel && o.scroll !== false) panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
select.addEventListener('change', function () { choose(select.value); });
|
||||
document.getElementById('btn-change-plea').addEventListener('click', function () { select.value = 'guilty'; choose('guilty'); });
|
||||
|
||||
Reference in new issue
Block a user