This stage will show the live passage-by-passage extraction view, prompt editor, and generated sheets once you start a cast.
'
+ '
'
+ ''
+ ''
+ ''
+ '
';
chars.className = '';
}
}
if (lib) {
lib.hidden = (view !== 'library');
if (view === 'library') {
lib.classList.remove('card'); // Remove card styling so it takes full width/height naturally
lib.style.boxShadow = 'none';
lib.style.border = 'none';
lib.style.background = 'transparent';
readerRenderLibrary(); // Re-fetch on open
}
}
if (typeof refreshWorkflowCrumbs === 'function') {
refreshWorkflowCrumbs(view === 'cast' ? 'cast' : view === 'chars' ? 'chars' : 'source');
}
};
window.navReaderView = function (view) {
window.showReaderView(view);
};
window.readerJumpToPage = function (pageNum) {
const pg = parseInt(pageNum, 10);
if (!pg) return Promise.resolve(false);
if (typeof navTo === 'function') navTo('s-reader');
window.showReaderView('main');
let tries = 0;
const waitFrame = () => new Promise(resolve => requestAnimationFrame(resolve));
const jump = async () => {
await waitFrame();
const pageState = readerState.pages?.[pg - 1];
const pageDiv = pageState?.pageDiv;
if (pageDiv && !pageDiv.hidden) {
await readerRenderPage(pg - 1);
pageDiv.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' });
readerRenderVisible();
return true;
}
if (tries++ < 20) {
await new Promise(resolve => setTimeout(resolve, 100));
return jump();
}
if (typeof toast === 'function') toast('Source page is not loaded in Reader yet', 'info');
return false;
};
return jump();
};
window.readerOnShow = async function () {
const sel = $('reader-backend-select');
if (sel && (!sel.value || sel.options.length <= 1)) {
if (typeof availableTtsBackends === 'function' && !availableTtsBackends().length &&
typeof refreshTtsBackendAvailability === 'function') {
try { await refreshTtsBackendAvailability(); } catch (_) {}
}
if (typeof availableTtsBackends === 'function' && availableTtsBackends().length) {
sel.innerHTML = ttsBackendOptions(sel.value);
sel.disabled = false;
}
}
readerUpdateBackendHint();
if (window.VoicePicker) VoicePicker.upgrade('reader-voice-select');
readerRenderLibrary();
if (window._readerStartView) {
window.showReaderView(window._readerStartView);
window._readerStartView = null;
}
_readerEnsureImportVisible();
};
// #reader-config-card ("Voice & synthesis settings") also holds the only
// paste/drag-and-drop import controls (#reader-import-area) — collapsed by
// default (data-collapse-default="closed"), which is fine once a document is
// loaded, but leaves a first-time or post-delete user staring at an empty
// page with no visible way to import anything (confirmed live: deleting the
// current book landed on an empty box with the import controls hidden inside
// the collapsed card above it). data-collapse-default is only read once at
// initCollapsibleCards() init time — this mirrors its `apply(true)` directly
// rather than trying to re-trigger that logic, and never persists to
// localStorage, so a user's own collapse choice made once a document IS
// loaded is left alone.
function _readerEnsureImportVisible() {
if (readerState.sentences.length) return;
const card = document.getElementById('reader-config-card');
const body = card ? card.querySelector('.card-col-body') : null;
if (body && body.hidden) {
body.hidden = false;
card.classList.remove('card-col-closed');
}
}