if ('serviceWorker' in navigator) {
const currentHost = window.location.hostname;
if (currentHost === 'creative-encounters.com' || currentHost === 'www.creative-encounters.com') {
navigator.serviceWorker.addEventListener('message', function(event) {
if (event.data && event.data.type === 'SW_UPDATED') {
window.location.reload();
}
});
navigator.serviceWorker.register('/sw.js');
} else {
navigator.serviceWorker.getRegistrations().then(function(registrations) {
for (let registration of registrations) {
registration.unregister();
}
});
}
}
let modal;
let playerBook = '';
let playerVerse = '';
let ceIsAdmin = false;
let currentNavFn = null;
function loaded() {
console.log('[Creative-Encounters] loaded')
modal = new bootstrap.Modal(document.getElementById('modal'))
checkAuth();
currentNavFn = biLinkPress;
setActiveNav('nav-bible');
bibleInjectTocBadges();
get("/bible", "rightColumn").then(() => { bibleInjectTocBadges(); readInjectTocProgress(); });
historyUpdateNav();
favUpdateNav();
// Audio position tracking (saves every ~5 s)
const audio = document.getElementById('player-bar-audio');
let _posTimer = null;
audio.addEventListener('timeupdate', function() {
if (!playerCurrentArchive || this.currentTime < 5) return;
if (_posTimer) return;
const arch = playerCurrentArchive, time = this.currentTime;
_posTimer = setTimeout(() => { posSave(arch, time); _posTimer = null; }, 5000);
});
window.addEventListener('scroll', () => {
const btn = document.getElementById('bibleBackToTop');
if (!btn) return;
btn.classList.toggle('visible', window.scrollY > 200);
}, { passive: true });
}
function closeNavbar() { /* no-op - collapsible navbar removed */ }
function dualColumn() { /* no-op - layout is now always two-column */ }
function singleColumn() { /* no-op - layout is now always two-column */ }
// --- Auth ---
async function checkAuth() {
try {
const resp = await fetch('/authcheck');
ceIsAdmin = resp.ok;
} catch (_) {
ceIsAdmin = false;
}
}
function setActiveNav(id) {
document.querySelectorAll('.left-nav-item').forEach(el => el.classList.remove('active'));
if (id) {
const el = document.getElementById(id);
if (el) el.classList.add('active');
}
}
function scriptureLink(book, verse) {
document.getElementById('modal-title').innerText = `${book} ${verse}`;
document.getElementById('modal-body').innerHTML = `
`;
document.getElementById('modal-footer').innerHTML = '';
modal.show();
}
function stLinkPress() {
if (!confirmLeaveStudy()) return;
currentNavFn = stLinkPress;
setActiveNav('nav-st');
get("/studies", "rightColumn");
clearLeftColumn();
}
function studiesLinkPress(id) {
get(`/study/${id}`, "rightColumn");
}
function biLinkPress() {
if (!confirmLeaveStudy()) return;
currentNavFn = biLinkPress;
setActiveNav('nav-bible');
get("/bible", "rightColumn").then(() => bibleInjectTocBadges());
clearLeftColumn();
}
function abLinkPress() {
if (!confirmLeaveStudy()) return;
setActiveNav(null);
get("/about", "rightColumn");
clearLeftColumn();
}
function wnLinkPress() {
if (!confirmLeaveStudy()) return;
currentNavFn = wnLinkPress;
setActiveNav('nav-wn');
get("/whatsnew", "rightColumn");
clearLeftColumn();
}
function wwLinkPress() {
if (!confirmLeaveStudy()) return;
currentNavFn = wwLinkPress;
setActiveNav('nav-ww');
get("/wow", "rightColumn");
clearLeftColumn();
}
function rsLinkPress() {
if (!confirmLeaveStudy()) return;
currentNavFn = rsLinkPress;
setActiveNav('nav-rs');
get("/renewal", "rightColumn");
clearLeftColumn();
}
function wnBookLinkPress(testament, book) {
get(`/wnbook/${testament}/${book}`, "rightColumn");
}
function wnTopicLinkPress(section, topic) {
get(`/wntopic/${section}/${topic}`, "rightColumn");
}
function bibleBookLinkPress(book) {
bibleCancelSelection();
get(`/bible/${book.replace(/\s+/g, '')}`, "rightColumn").then(() => {
bibleLoadHighlights(book);
readLoadBook(book);
bibleUpdateHlBtn(book);
});
}
// --- Bible Highlights ---
function bibleHlKey(book) { return `ce-hl-${book}`; }
function bibleHlTxtKey(book) { return `ce-hl-txt-${book}`; }
// --- Verse / Passage Selection ---
let bibleSelectionStart = null; // { book, ch, v }
let bibleSelectionEnd = null; // { book, ch, v }
function bibleVersePress(el) {
const book = el.dataset.book;
const ch = el.dataset.ch;
const v = parseInt(el.dataset.v, 10);
if (!bibleSelectionStart) {
// Nothing selected yet — set start
bibleSelectionStart = { book, ch, v };
el.classList.add('verse-selected');
bibleColorBarShow();
} else if (bibleSelectionStart.book === book && bibleSelectionStart.ch === ch) {
if (bibleSelectionStart.v === v && !bibleSelectionEnd) {
// Tap same verse again — cancel
bibleCancelSelection();
return;
}
// Same chapter — set/update end, show preview
bibleSelectionEnd = { book, ch, v };
const minV = Math.min(bibleSelectionStart.v, v);
const maxV = Math.max(bibleSelectionStart.v, v);
document.querySelectorAll('.verse-preview').forEach(e => e.classList.remove('verse-preview'));
for (let i = minV; i <= maxV; i++) {
const e = document.querySelector(`.bible-verse[data-book="${book}"][data-ch="${ch}"][data-v="${i}"]`);
if (e) e.classList.add('verse-preview');
}
bibleColorBarShow();
} else {
// Different chapter — reset to this verse as new start
bibleCancelSelection();
bibleSelectionStart = { book, ch, v };
el.classList.add('verse-selected');
bibleColorBarShow();
}
}
function bibleColorBarShow() {
const { book, ch, v: sv } = bibleSelectionStart;
const ev = bibleSelectionEnd ? bibleSelectionEnd.v : null;
const minV = ev !== null ? Math.min(sv, ev) : sv;
const maxV = ev !== null ? Math.max(sv, ev) : sv;
const label = minV === maxV ? `${book} ${ch}:${minV}` : `${book} ${ch}:${minV}\u2013${maxV}`;
document.getElementById('bible-color-bar-label').textContent = label;
const hint = document.getElementById('bible-color-bar-hint');
if (hint) hint.style.display = ev !== null ? 'none' : '';
document.getElementById('bible-color-bar').style.display = '';
}
function bibleApplyColor(colorIdx) {
if (!bibleSelectionStart) return;
const { book, ch } = bibleSelectionStart;
const minV = Math.min(bibleSelectionStart.v, bibleSelectionEnd ? bibleSelectionEnd.v : bibleSelectionStart.v);
const maxV = Math.max(bibleSelectionStart.v, bibleSelectionEnd ? bibleSelectionEnd.v : bibleSelectionStart.v);
const arr = bibleGetHighlights(book);
const texts = bibleGetHlTexts(book);
const colors = bibleGetHlColors(book);
for (let i = minV; i <= maxV; i++) {
const key = `${ch}:${i}`;
const verseEl = document.querySelector(`.bible-verse[data-book="${book}"][data-ch="${ch}"][data-v="${i}"]`);
if (colorIdx === -1) {
const idx = arr.indexOf(key);
if (idx !== -1) arr.splice(idx, 1);
delete texts[key];
delete colors[key];
if (verseEl) { verseEl.classList.remove('highlighted'); verseEl.removeAttribute('data-hl'); }
} else {
if (!arr.includes(key)) arr.push(key);
if (verseEl) texts[key] = verseEl.textContent.trim();
colors[key] = colorIdx;
if (verseEl) { verseEl.classList.add('highlighted'); verseEl.dataset.hl = String(colorIdx); }
}
}
if (Object.keys(texts).length) localStorage.setItem(bibleHlTxtKey(book), JSON.stringify(texts));
else localStorage.removeItem(bibleHlTxtKey(book));
if (Object.keys(colors).length) localStorage.setItem(bibleHlColorKey(book), JSON.stringify(colors));
else localStorage.removeItem(bibleHlColorKey(book));
bibleSaveHighlights(book, arr);
bibleUpdateHlBtn(book);
bibleInjectTocBadges();
bibleCancelSelection();
}
function bibleCancelSelection() {
document.querySelectorAll('.verse-selected').forEach(e => e.classList.remove('verse-selected'));
document.querySelectorAll('.verse-preview').forEach(e => e.classList.remove('verse-preview'));
bibleSelectionStart = null;
bibleSelectionEnd = null;
const bar = document.getElementById('bible-color-bar');
if (bar) bar.style.display = 'none';
}
function bibleGetHighlights(book) {
try { return JSON.parse(localStorage.getItem(bibleHlKey(book)) || '[]'); } catch { return []; }
}
function bibleGetHlTexts(book) {
try { return JSON.parse(localStorage.getItem(bibleHlTxtKey(book)) || '{}'); } catch { return {}; }
}
function bibleSaveHighlights(book, arr) {
if (arr.length === 0) { localStorage.removeItem(bibleHlKey(book)); localStorage.removeItem(bibleHlTxtKey(book)); }
else localStorage.setItem(bibleHlKey(book), JSON.stringify(arr));
}
// Count distinct passages (runs of consecutive verses in the same chapter)
function bibleCountPassages(arr) {
// Group verse numbers by chapter
const byChapter = {};
arr.forEach(key => {
const [ch, v] = key.split(':');
(byChapter[ch] = byChapter[ch] || []).push(parseInt(v, 10));
});
let passages = 0;
Object.values(byChapter).forEach(verses => {
verses.sort((a, b) => a - b);
passages++; // first verse always starts a passage
for (let i = 1; i < verses.length; i++) {
if (verses[i] !== verses[i - 1] + 1) passages++; // gap = new passage
}
});
return passages;
}
// Group a verse key array into passage run objects for rendering
function bibleGroupPassagesForBook(arr, book) {
const byChapter = {};
arr.forEach(key => {
const [ch, v] = key.split(':');
(byChapter[ch] = byChapter[ch] || []).push(parseInt(v, 10));
});
const colors = bibleGetHlColors(book);
const texts = bibleGetHlTexts(book);
const runs = [];
Object.keys(byChapter).sort((a, b) => parseInt(a) - parseInt(b)).forEach(ch => {
const verses = byChapter[ch].slice().sort((a, b) => a - b);
let runStart = verses[0], runEnd = verses[0];
for (let i = 1; i <= verses.length; i++) {
if (i < verses.length && verses[i] === runEnd + 1) { runEnd = verses[i]; continue; }
// Emit run
const colorIdx = colors[`${ch}:${runStart}`] || 0;
const allText = [];
for (let v = runStart; v <= runEnd; v++) {
const key = `${ch}:${v}`;
const el = document.querySelector(`.bible-verse[data-book="${book}"][data-ch="${ch}"][data-v="${v}"]`);
const t = (el ? el.textContent.trim() : null) || texts[key] || '';
if (t) allText.push(t);
}
const firstText = allText[0] || '';
const fullText = allText.join(' ');
const isRange = runStart !== runEnd;
const label = isRange ? `${ch}:${runStart}–${runEnd}` : `${ch}:${runStart}`;
const displayText = isRange ? firstText + '…' : firstText;
const note = bibleGetNote(book, ch, runStart);
const noteId = `${book.replace(/\s+/g, '_')}-${ch}-${runStart}`;
runs.push({ ch, startV: runStart, endV: runEnd, colorIdx, displayText, fullText, note, noteId, label, isRange });
if (i < verses.length) { runStart = verses[i]; runEnd = verses[i]; }
}
});
return runs;
}
function bibleHlCopyPassage(book, ch, startV, endV, fullText) {
const ref = startV === endV ? `${book} ${ch}:${startV}` : `${book} ${ch}:${startV}\u2013${endV}`;
navigator.clipboard.writeText(`\u201C${fullText}\u201D \u2014 ${ref}`).then(() => {
const el = document.querySelector(`.bible-verse[data-book="${book}"][data-ch="${ch}"][data-v="${startV}"]`);
if (el) { el.classList.add('bible-verse-copied'); setTimeout(() => el.classList.remove('bible-verse-copied'), 800); }
}).catch(() => {});
}
function bibleUpdateHlBtn(book) {
const btn = document.querySelector('.bible-hl-btn');
if (!btn) return;
const arr = bibleGetHighlights(book);
const count = bibleCountPassages(arr);
btn.style.display = count ? 'inline-block' : 'none';
}
function bibleVerseKey(el) {
return `${el.dataset.ch}:${el.dataset.v}`;
}
function bibleLoadHighlights(book) {
const arr = bibleGetHighlights(book);
if (!arr.length) return;
const set = new Set(arr);
const texts = bibleGetHlTexts(book);
const colors = bibleGetHlColors(book);
let textsDirty = false;
document.querySelectorAll('.bible-verse[data-book]').forEach(el => {
if (el.dataset.book === book && set.has(bibleVerseKey(el))) {
el.classList.add('highlighted');
const key = bibleVerseKey(el);
el.dataset.hl = String(colors[key] || 0);
if (!texts[key]) { texts[key] = el.textContent.trim(); textsDirty = true; }
}
});
if (textsDirty) localStorage.setItem(bibleHlTxtKey(book), JSON.stringify(texts));
bibleLoadNotes(book);
}
function bibleInjectTocBadges() {
// Collect all highlighted book counts from localStorage
const counts = {};
let total = 0;
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (!k.startsWith('ce-hl-') || k.startsWith('ce-hl-txt-') || k.startsWith('ce-hl-color-')) continue;
const book = k.slice(6);
try {
const arr = JSON.parse(localStorage.getItem(k) || '[]');
if (Array.isArray(arr) && arr.length) { const n = bibleCountPassages(arr); counts[book] = n; total += n; }
} catch {}
}
// Inject/update badges on any visible TOC items
document.querySelectorAll('.bible-book-item[id^="bible-book-"]').forEach(el => {
const book = el.id.slice(11);
let badge = el.querySelector('.bible-hl-badge');
if (counts[book]) {
if (!badge) {
badge = document.createElement('span');
badge.className = 'bible-hl-badge';
el.appendChild(badge);
}
badge.textContent = counts[book];
} else if (badge) {
badge.remove();
}
});
// Update global highlights nav item
const globalNav = document.getElementById('nav-hl');
const globalBadge = document.getElementById('global-hl-badge');
if (globalNav) globalNav.style.display = total ? '' : 'none';
if (globalBadge) globalBadge.textContent = total;
readInjectTocProgress();
}
function bibleHlSearch(input) {
const q = input.value.trim().toLowerCase();
// Filter items
document.querySelectorAll('#hl-list .bible-hl-item').forEach(el => {
el.style.display = (!q || el.dataset.text.includes(q)) ? '' : 'none';
});
// Hide groups where all items are hidden
document.querySelectorAll('#hl-list .bible-hl-group').forEach(group => {
const visible = group.querySelectorAll('.bible-hl-item:not([style*="display: none"])');
group.style.display = visible.length ? '' : 'none';
});
// Hide book headings where all following groups are hidden (global modal)
document.querySelectorAll('#hl-list .bible-hl-book-heading').forEach(heading => {
let next = heading.nextElementSibling;
let anyVisible = false;
while (next && !next.classList.contains('bible-hl-book-heading')) {
if (next.style.display !== 'none') { anyVisible = true; break; }
next = next.nextElementSibling;
}
heading.style.display = anyVisible ? '' : 'none';
});
}
function bibleHlPassageRow(book, run, onClickFn, extraClass) {
const { ch, startV, endV, colorIdx, displayText, fullText, note, noteId, label, isRange } = run;
const safeBook = book.replace(/'/g, "\\'");
const safeFullText = fullText.replace(/'/g, "\\'");
let html = `
]/g, '')}"> `;
html += `
`;
html += ``;
html += `
${label} ${displayText}`;
if (note) html += `
📝 ${note}
`;
else html += ``;
html += `
`;
html += `
`;
html += ``;
html += ``;
html += `
`;
html += `
`;
html += ``;
html += ``;
html += `
`;
return html;
}
function bibleViewAllHighlights() {
const titleEl = document.getElementById('modal-title');
const bodyEl = document.getElementById('modal-body');
const footerEl = document.getElementById('modal-footer');
titleEl.textContent = 'All Highlights';
footerEl.innerHTML = '';
// Collect all books with highlights (skip txt/color sub-keys)
const books = {};
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (!k.startsWith('ce-hl-') || k.startsWith('ce-hl-txt-') || k.startsWith('ce-hl-color-')) continue;
const book = k.slice(6);
try {
const arr = JSON.parse(localStorage.getItem(k) || '[]');
if (Array.isArray(arr) && arr.length) books[book] = arr;
} catch {}
}
if (!Object.keys(books).length) {
bodyEl.innerHTML = '
No highlighted verses yet.
';
modal.show();
return;
}
let html = '';
Object.keys(books).sort().forEach(book => {
const runs = bibleGroupPassagesForBook(books[book], book);
if (!runs.length) return;
html += `
${book}
`;
let lastCh = null;
runs.forEach(run => {
if (run.ch !== lastCh) {
if (lastCh !== null) html += '';
html += `
Chapter ${run.ch}
`;
lastCh = run.ch;
}
const safeBook = book.replace(/'/g, "\\'");
html += bibleHlPassageRow(book, run, `bibleGoToVerseFromGlobal('${safeBook}','${run.ch}',${run.startV})`, '');
});
if (lastCh !== null) html += '
';
});
bodyEl.innerHTML = '
' + html + '
';
modal.show();
document.getElementById('hl-search').focus();
}
function bibleGoToVerseFromGlobal(book, ch, v) {
modal.hide();
// If the book is already loaded, jump directly
const existing = document.querySelector(`.bible-verse[data-book="${book}"]`);
if (existing) {
bibleGoToVerse(book, ch, v);
return;
}
// Otherwise load the book first, then jump
get(`/bible/${book.replace(/\s+/g, '')}`, 'rightColumn').then(() => {
bibleLoadHighlights(book);
readLoadBook(book);
bibleUpdateHlBtn(book);
bibleGoToVerse(book, ch, v);
});
}
function bibleViewHighlights(book) {
const arr = bibleGetHighlights(book);
const titleEl = document.getElementById('modal-title');
const bodyEl = document.getElementById('modal-body');
const footerEl = document.getElementById('modal-footer');
titleEl.textContent = `${book} — Highlights`;
footerEl.innerHTML = '';
if (!arr.length) {
bodyEl.innerHTML = '
No highlighted verses yet. Tap any verse to highlight it.
';
} else {
const runs = bibleGroupPassagesForBook(arr, book);
let html = '';
let lastCh = null;
runs.forEach(run => {
if (run.ch !== lastCh) {
if (lastCh !== null) html += '';
html += `
Chapter ${run.ch}
`;
lastCh = run.ch;
}
const safeBook = book.replace(/'/g, "\'");
html += bibleHlPassageRow(book, run, `bibleGoToVerse('${safeBook}','${run.ch}',${run.startV})`, '');
});
if (lastCh !== null) html += '
';
footerEl.innerHTML = ``;
bodyEl.innerHTML = '
' + html + '
';
document.getElementById('hl-search').focus();
}
modal.show();
}
function bibleClearHighlights(book) {
localStorage.removeItem(bibleHlKey(book));
localStorage.removeItem(bibleHlTxtKey(book));
localStorage.removeItem(bibleHlColorKey(book));
document.querySelectorAll('.bible-verse.highlighted').forEach(el => {
el.classList.remove('highlighted');
el.removeAttribute('data-hl');
});
bibleUpdateHlBtn(book);
bibleInjectTocBadges();
modal.hide();
}
function bibleGoToVerse(book, ch, v) {
modal.hide();
const el = document.querySelector(`.bible-verse[data-book="${book}"][data-ch="${ch}"][data-v="${v}"]`);
if (!el) return;
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
el.classList.add('bible-verse-flash');
setTimeout(() => el.classList.remove('bible-verse-flash'), 1500);
}
let currentOpenArchive = null;
async function programLinkPress(title, verse, id, archive) {
const panel = document.getElementById('prog-expand-' + archive);
const chevron = document.getElementById('prog-chevron-' + archive);
// Toggle closed if already open
if (currentOpenArchive === archive) {
panel.innerHTML = '';
panel.classList.remove('open');
if (chevron) chevron.classList.remove('open');
currentOpenArchive = null;
return;
}
// Close previously open panel
if (currentOpenArchive) {
const prevPanel = document.getElementById('prog-expand-' + currentOpenArchive);
const prevChevron = document.getElementById('prog-chevron-' + currentOpenArchive);
if (prevPanel) { prevPanel.innerHTML = ''; prevPanel.classList.remove('open'); }
if (prevChevron) prevChevron.classList.remove('open');
}
currentOpenArchive = archive;
await get(`/program/${id}/${archive}`, 'prog-expand-' + archive);
panel.classList.add('open');
if (chevron) chevron.classList.add('open');
}
function closeProgramList() {
const pl = document.getElementById('programList');
if (pl) {
pl.remove();
lastArchive = undefined;
}
}
async function loadStudy(id) {
await get(`/study/${id}`, "rightColumn");
const spans = document.getElementsByClassName('study-scripture');
for (let i = 0; i < spans.length; i++) {
await getScriptureStudy(spans[i].id, spans[i].dataset.book, spans[i].dataset.verse);
}
}
let studyDirty = false;
function markStudyDirty() {
studyDirty = true;
}
function clearStudyDirty() {
studyDirty = false;
}
async function loadNewStudy() {
if (!confirmLeaveStudy()) return;
await get("/studynew", "rightColumn");
document.getElementById('studyID').value = randomGuid();
clearStudyDirty();
document.getElementById('studyTitle').addEventListener('input', markStudyDirty);
}
async function loadEditStudy(id) {
if (!confirmLeaveStudy()) return;
await get("/studynew", "rightColumn");
clearStudyDirty();
document.getElementById('studyTitle').addEventListener('input', markStudyDirty);
fetch(`/studydata/${id}`)
.then(response => response.json())
.then(data => {
document.getElementById('studyID').value = data.id;
document.getElementById('studyTitle').value = data.title;
data.data.forEach(item => {
if (item.type === 'text') {
addStudyText(item.content);
} else if (item.type === 'scripture') {
addStudyScripture(item.book, item.verse);
}
});
});
}
function confirmLeaveStudy() {
if (studyDirty) {
return confirm('You have unsaved changes. Leave anyway?');
}
return true;
}
function deleteStudy(id) {
if (confirm('Are you sure you want to delete this study? This action cannot be undone.')) {
fetch(getAuth(`/studydelete/${id}`), { method: 'DELETE' })
.then(response => {
if (response.ok) {
stLinkPress();
} else {
alert('Error deleting study.');
}
})
.catch(error => {
console.error('Error:', error);
alert('Error deleting study.');
});
}
}
function saveNewStudy() {
const id = document.getElementById('studyID').value.trim();
const title = document.getElementById('studyTitle').value.trim();
if (!title) {
alert('Please enter a title for the study.');
return;
}
const data = { id: id, title: title, data: [] };
document.querySelectorAll('.study-data').forEach(ta => {
if (ta.tagName.toLowerCase() === 'textarea') {
data.data.push({ type: 'text', content: ta.value.trim() });
} else if (ta.tagName.toLowerCase() === 'span') {
const book = ta.querySelector('input[name="book"]').value.trim();
const verse = ta.querySelector('input[name="verse"]').value.trim();
if (book && verse) {
data.data.push({ type: 'scripture', book: book, verse: verse });
}
}
});
if (data.data.length === 0) {
alert('Please add at least one text or scripture to the study.');
return;
}
fetch('/studysave', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => {
if (response.ok) {
clearStudyDirty();
loadStudy(data.id);
} else {
alert('Error saving study.');
}
})
.catch(error => {
console.error('Error:', error);
alert('Error saving study.');
});
}
function addStudyText(value) {
const container = document.getElementById('extraInputs');
const div = document.createElement('div');
div.className = 'study-block mb-2';
const ta = document.createElement('textarea');
ta.className = 'form-control study-data';
ta.rows = 1;
ta.value = value || '';
ta.placeholder = 'Text...';
ta.addEventListener('input', function() {
markStudyDirty();
this.style.height = 'auto';
this.style.height = this.scrollHeight + 'px';
});
// Set initial height for pre-filled content
setTimeout(() => { ta.style.height = ta.scrollHeight + 'px'; }, 0);
div.appendChild(blockControls(div));
div.appendChild(ta);
container.appendChild(div);
ta.focus();
}
function addStudyScripture(book, verse) {
const container = document.getElementById('extraInputs');
const div = document.createElement('div');
div.className = 'study-block mb-2';
const inner = document.createElement('span');
inner.className = 'd-flex gap-2 align-items-center study-data';
const bookInput = document.createElement('input');
bookInput.type = 'text';
bookInput.className = 'form-control';
bookInput.name = 'book';
bookInput.placeholder = 'Book';
bookInput.setAttribute('list', 'bible-books');
bookInput.style.maxWidth = '200px';
bookInput.value = book || '';
bookInput.addEventListener('input', markStudyDirty);
const verseInput = document.createElement('input');
verseInput.type = 'text';
verseInput.className = 'form-control';
verseInput.name = 'verse';
verseInput.placeholder = 'e.g. 3:16';
verseInput.style.maxWidth = '160px';
verseInput.value = verse || '';
verseInput.addEventListener('input', markStudyDirty);
inner.appendChild(bookInput);
inner.appendChild(verseInput);
div.appendChild(blockControls(div));
div.appendChild(inner);
container.appendChild(div);
bookInput.focus();
}
function blockControls(div) {
const bar = document.createElement('div');
bar.className = 'study-block-controls';
const up = document.createElement('button');
up.type = 'button';
up.className = 'study-block-btn';
up.title = 'Move up';
up.innerHTML = '↑';
up.onclick = () => {
const prev = div.previousElementSibling;
if (prev) div.parentNode.insertBefore(div, prev);
};
const down = document.createElement('button');
down.type = 'button';
down.className = 'study-block-btn';
down.title = 'Move down';
down.innerHTML = '↓';
down.onclick = () => {
const next = div.nextElementSibling;
if (next) div.parentNode.insertBefore(next, div);
};
const remove = document.createElement('button');
remove.type = 'button';
remove.className = 'study-block-btn study-block-btn-remove';
remove.title = 'Remove';
remove.innerHTML = '×';
remove.onclick = () => { div.remove(); markStudyDirty(); };
bar.appendChild(up);
bar.appendChild(down);
bar.appendChild(remove);
return bar;
}
function showPersistentPlayer(title, sub, archiveSrc, hasScripture) {
const audio = document.getElementById('player-bar-audio');
audio.src = `https://s3.amazonaws.com/creative-encounters/${archiveSrc}.mp3`;
document.getElementById('player-bar-title').textContent = title;
document.getElementById('player-bar-sub').textContent = sub || '';
const scriptureBtn = document.getElementById('player-scripture-btn');
hasScripture ? scriptureBtn.classList.remove('d-none') : scriptureBtn.classList.add('d-none');
document.getElementById('persistent-player').classList.remove('d-none');
document.body.classList.add('player-active');
playerCurrentArchive = archiveSrc;
favUpdatePlayerBtn(archiveSrc);
// Restore saved position if available
const savedPos = posGet(archiveSrc);
if (savedPos > 5) {
const handler = function() { audio.currentTime = savedPos; };
audio.addEventListener('canplay', handler, { once: true });
}
audio.play().catch(() => {});
}
function closePlayer() {
const audio = document.getElementById('player-bar-audio');
audio.pause();
audio.src = '';
document.getElementById('persistent-player').classList.add('d-none');
document.body.classList.remove('player-active');
playerCurrentArchive = null;
}
function loadSong(url, title) {
showPersistentPlayer(title, '', url, false);
}
function loadProgram(title, verse, day, archive) {
modal.hide();
playerBook = title;
playerVerse = verse;
historyRecord(title, `${verse} (${day})`, archive);
showPersistentPlayer(title, `${verse} (${day})`, archive, true);
}
function loadTopicProgram(archive, title) {
showPersistentPlayer(title, '', archive, false);
}
// --- Play History ---
const HISTORY_KEY = 'ce-play-history';
function historyGet() {
try { return JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); } catch { return []; }
}
function historyRecord(title, sub, archive) {
const arr = historyGet();
// Remove existing entry for same archive so it bubbles to top
const idx = arr.findIndex(e => e.archive === archive);
if (idx !== -1) arr.splice(idx, 1);
arr.unshift({ title, sub, archive, ts: Date.now() });
// Keep at most 100 entries
if (arr.length > 100) arr.length = 100;
localStorage.setItem(HISTORY_KEY, JSON.stringify(arr));
historyUpdateNav();
}
function historyUpdateNav() {
const arr = historyGet();
const nav = document.getElementById('nav-history');
const badge = document.getElementById('history-badge');
if (nav) nav.style.display = arr.length ? '' : 'none';
if (badge) badge.textContent = arr.length;
}
function showPlayHistory() {
const arr = historyGet();
const titleEl = document.getElementById('modal-title');
const bodyEl = document.getElementById('modal-body');
const footerEl = document.getElementById('modal-footer');
titleEl.textContent = 'Play History';
footerEl.innerHTML = arr.length
? ``
: '';
if (!arr.length) {
bodyEl.innerHTML = '
No programs played yet.
';
modal.show();
return;
}
let html = '
';
arr.forEach(e => {
const date = new Date(e.ts).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
const searchText = `${e.title} ${e.sub}`.toLowerCase();
html += `
`;
html += `
${e.title}
${e.sub}
${date}
`;
html += ``;
html += '
';
});
html += '
';
bodyEl.innerHTML = html;
modal.show();
document.getElementById('history-search').focus();
}
function historySearch(input) {
const q = input.value.trim().toLowerCase();
document.querySelectorAll('#history-list .history-item').forEach(el => {
el.style.display = (!q || el.dataset.text.includes(q)) ? '' : 'none';
});
}
function historyReplay(archive, title, sub) {
modal.hide();
showPersistentPlayer(title, sub, archive, true);
}
function historyClear() {
localStorage.removeItem(HISTORY_KEY);
historyUpdateNav();
modal.hide();
}
function getAuth(path) {
return path; // auth cookie is sent automatically
}
async function get(path, id) {
return fetch(getAuth(path))
.then(function (response) {
return response.text();
})
.then(function (data) {
document.getElementById(id).innerHTML = data;
scrollToTop();
return data;
})
.catch(function (error) {
console.log(error);
});
};
function clearLeftColumn() {
document.getElementById('leftColumn').innerHTML = '';
}
function hideScripture() {
// no-op — scripture is now shown in the modal
}
function getScripture(data, verse, labels) {
let text, stc, stv, enc, env, chps;
const parts = verse.split(" - ");
[stc, stv] = startChapterVerse(parts[0], data);
if (parts.length === 2) {
[enc, env] = endChapterVerse(parts[1], data);
}
if (parts.length === 1) {
if (parts[0].includes(":")) {
if (parts[0].includes("-")) {
[enc, env] = [stc, parts[0].split("-")[1]];
} else {
[enc, env] = [stc, stv];
}
} else {
[enc, env] = [stc, data[stc-1].verses.length];
}
}
text = '';
chps = enc - stc + 1;
if (chps === 1) {
if (labels) {
text += formatChapter(stc);
}
text += formatVerse(stv, env, data[stc-1].verses, labels);
} else {
if (labels) {
text += formatChapter(stc);
}
text += formatVerse(stv, data[stc-1].verses.length, data[stc-1].verses, labels);
if (chps >2) {
for (let i = stc; i < enc-1; i++) {
text += "
";
if (labels) {
text += formatChapter(i+1);
}
text += formatVerse(1, data[i].verses.length, data[i].verses, labels);
}
}
text += "
";
if (labels) {
text += formatChapter(enc);
}
text += formatVerse(1, env, data[enc-1].verses, labels);
}
return text;
}
async function getScriptureStudy(id, book, verse) {
const container = document.getElementById(id);
if (!container) return;
// Render a clickable reference link immediately — no need to prefetch
container.innerHTML = `📖 ${book} ${verse}
`;
}
function getScriptureProgram(book, verse) {
fetch("/scripture/" + book.replace(/\s+/g, ''))
.then(response => response.text())
.then(data => {
data = JSON.parse(data);
document.getElementById('modal-title').innerText = `${book} ${verse}`;
document.getElementById('modal-body').innerHTML = `
${getScripture(data, verse, true)}
`;
document.getElementById('modal-footer').innerHTML = '';
modal.show();
})
.catch(error => console.error(error));
}
function formatVerse(start, end, section, labels) {
let text = '';
for (let i = start; i <= end; i++) {
if (labels) {
text += `${i}`;
}
text += section[i-1];
}
return text;
}
function formatChapter(num) {
return `