New reading surface with paper/sepia/dark themes, slim top and bottom bars, and sheets for navigation, search, settings, audio and per-verse study tools. Plan progress is built into the reader. Page-flip mode removed. ESV added via the server-side proxy (ESV_API_KEY). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9e8420a1ae
commit
9e722a296d
17 files changed
+903
-791
No files matched your search
@@ -0,0 +1,79 @@
|
||||
// The page itself: chapter heading, translation notice, and verses set in a
|
||||
// serif face. Tap a verse to open the verse sheet; select text to highlight.
|
||||
import { formatCrossRef } from '../../utils/wordStudy.js';
|
||||
|
||||
export default function ReaderText({
|
||||
title, translation, notice, verses, loading, error, layout, fontSize, wide,
|
||||
bookmarks, bookKey, chapter, selectedVerse, onSelectVerse, renderVerseText, searchLower,
|
||||
crossRefs, showCrossRefs, onSelectionEnd,
|
||||
}) {
|
||||
const handleTap = (n) => {
|
||||
const sel = window.getSelection();
|
||||
if (sel && !sel.isCollapsed && sel.toString().trim()) return; // user is selecting text, not tapping
|
||||
onSelectVerse(selectedVerse === n ? null : n);
|
||||
};
|
||||
|
||||
const verseProps = (v) => {
|
||||
const bm = bookmarks[`${bookKey}-${chapter}-${v.number}`];
|
||||
const selected = selectedVerse === v.number;
|
||||
return {
|
||||
id: `reader-verse-${v.number}`,
|
||||
'data-verse-num': v.number,
|
||||
onClick: () => handleTap(v.number),
|
||||
className: 'cursor-pointer rounded-sm transition-colors',
|
||||
style: selected
|
||||
? { backgroundColor: 'var(--r-hl)' }
|
||||
: bm ? { backgroundColor: `${bm}66`, boxShadow: `0 0 0 2px ${bm}66` } : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const chips = (v) => showCrossRefs && crossRefs?.[v.number]?.length > 0 && (
|
||||
<span className="ml-1 inline-flex flex-wrap gap-1 align-baseline font-sans">
|
||||
{crossRefs[v.number].slice(0, 4).map((ref, i) => (
|
||||
<span key={i} className="rounded-full border border-[var(--r-line)] px-1.5 py-px text-[10px] text-[var(--r-sub)]">{formatCrossRef(ref)}</span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
|
||||
const num = (n) => <sup className="mr-1 select-none font-sans text-[0.6em] text-[var(--r-sub)]">{n}</sup>;
|
||||
|
||||
return (
|
||||
<article
|
||||
className={`mx-auto px-5 pb-32 pt-6 text-[var(--r-text)] ${wide ? 'max-w-5xl' : 'max-w-2xl'}`}
|
||||
onPointerUp={onSelectionEnd}
|
||||
>
|
||||
<header className="mb-5 text-center">
|
||||
<h1 className="font-serif text-3xl font-normal tracking-tight">{title}</h1>
|
||||
<p className="mt-1 font-sans text-xs text-[var(--r-sub)]">{translation}</p>
|
||||
</header>
|
||||
|
||||
{loading && <p className="text-center font-sans text-sm text-[var(--r-sub)]">Loading…</p>}
|
||||
{error && <p className="text-center font-sans text-sm text-red-600">{error}</p>}
|
||||
|
||||
{!loading && !error && (
|
||||
<div
|
||||
className={`font-serif ${wide ? 'md:columns-2 md:gap-x-12' : ''}`}
|
||||
style={{ fontSize: `${1.125 * fontSize}rem`, lineHeight: 1.85 }}
|
||||
>
|
||||
{layout === 'flow' ? (
|
||||
<p className="text-pretty">
|
||||
{verses.map((v) => (
|
||||
<span key={v.number} {...verseProps(v)}>
|
||||
{num(v.number)}{renderVerseText(v.number, v.text, searchLower)}{chips(v)}{' '}
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
) : (
|
||||
verses.map((v) => (
|
||||
<p key={v.number} className="mb-3 break-inside-avoid">
|
||||
<span {...verseProps(v)}>{num(v.number)}{renderVerseText(v.number, v.text, searchLower)}{chips(v)}</span>
|
||||
</p>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{notice && !loading && <p className="mt-10 border-t border-[var(--r-line)] pt-3 font-sans text-[11px] leading-snug text-[var(--r-sub)]">{notice}</p>}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user