Study panel: follow the reader theme colours
Build and Push Docker Image / build-and-push (push) Successful in 4m54s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-09-29 09:12:17 -04:00
1 parent 9e722a296d
commit e50a73d351
1 file changed
+28 -28
+28 -28
View File
@@ -20,44 +20,44 @@ export function CommentaryTab({ book, chapter, verse }) {
return (
<div>
<label className="text-xs text-slate-600">
<label className="text-xs text-[var(--r-sub,#64748b)]">
Commentary{' '}
<select value={source.id} onChange={(e) => changeSource(e.target.value)}
className="rounded-lg border border-slate-300 bg-white px-2 py-1 text-xs">
className="rounded-lg border border-[var(--r-line,#cbd5e1)] bg-[var(--r-bg,#ffffff)] px-2 py-1 text-xs">
{COMMENTARIES.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
</select>
</label>
<div className="mt-2">
{status === 'loading' && <p className="text-xs text-slate-500">Loading…</p>}
{status === 'empty' && <p className="text-xs text-slate-500">{source.name} has no notes for this chapter.</p>}
{status === 'ready' && !entry && <p className="text-xs text-slate-500">No notes at or before this verse in {source.name}.</p>}
{status === 'loading' && <p className="text-xs text-[var(--r-sub,#64748b)]">Loading…</p>}
{status === 'empty' && <p className="text-xs text-[var(--r-sub,#64748b)]">{source.name} has no notes for this chapter.</p>}
{status === 'ready' && !entry && <p className="text-xs text-[var(--r-sub,#64748b)]">No notes at or before this verse in {source.name}.</p>}
{status === 'ready' && entry && (
<>
{entry.number !== verse && (
<p className="mb-1 text-[11px] text-slate-500">Notes on the passage starting at verse {entry.number}.</p>
<p className="mb-1 text-[11px] text-[var(--r-sub,#64748b)]">Notes on the passage starting at verse {entry.number}.</p>
)}
<div className="max-h-96 overflow-y-auto whitespace-pre-line rounded-xl bg-white p-3 text-[13px] leading-relaxed text-slate-700">
<div className="max-h-96 overflow-y-auto whitespace-pre-line rounded-xl bg-[var(--r-bg,#ffffff)] p-3 text-[13px] leading-relaxed text-[var(--r-text,#334155)]">
{entry.text}
</div>
</>
)}
</div>
<p className="mt-2 text-[10px] text-slate-400">{source.credit}</p>
<p className="mt-2 text-[10px] text-[var(--r-sub,#64748b)]">{source.credit}</p>
</div>
);
}
function PersonDetails({ id }) {
const { status, data } = useAsync(() => loadPerson(id), [id]);
if (status === 'loading') return <p className="mt-1 text-xs text-slate-500">Loading…</p>;
if (status === 'empty') return <p className="mt-1 text-xs text-slate-500">No details available.</p>;
if (status === 'loading') return <p className="mt-1 text-xs text-[var(--r-sub,#64748b)]">Loading…</p>;
if (status === 'empty') return <p className="mt-1 text-xs text-[var(--r-sub,#64748b)]">No details available.</p>;
const era = (y) => `${Math.abs(y)}${y < 0 ? ' BC' : ' AD'}`;
return (
<div className="mt-1 space-y-1 text-xs leading-relaxed text-slate-600">
<div className="mt-1 space-y-1 text-xs leading-relaxed text-[var(--r-sub,#64748b)]">
{(data.description ?? []).map((d, i) => <p key={i}>{d}</p>)}
{!data.description?.length && <p>No description available.</p>}
{data.minYear != null && (
<p className="text-slate-400">
<p className="text-[var(--r-sub,#64748b)]">
Lived approx. {era(data.minYear)}{data.maxYear != null && data.maxYear !== data.minYear ? ` – ${era(data.maxYear)}` : ''}
</p>
)}
@@ -68,9 +68,9 @@ function PersonDetails({ id }) {
function PersonCard({ person }) {
const [open, setOpen] = useState(false);
return (
<li className="rounded-xl bg-white px-3 py-2">
<button type="button" onClick={() => setOpen((o) => !o)} className="w-full text-left text-sm font-semibold text-slate-800">
{person.name} <span className="text-xs font-normal text-slate-400">{open ? '▾' : '▸'}</span>
<li className="rounded-xl bg-[var(--r-bg,#ffffff)] px-3 py-2">
<button type="button" onClick={() => setOpen((o) => !o)} className="w-full text-left text-sm font-semibold text-[var(--r-text,#334155)]">
{person.name} <span className="text-xs font-normal text-[var(--r-sub,#64748b)]">{open ? '▾' : '▸'}</span>
</button>
{open && <PersonDetails id={person.id} />}
</li>
@@ -79,31 +79,31 @@ function PersonCard({ person }) {
export function PeoplePlacesTab({ book, chapter, verse }) {
const { status, data } = useAsync(() => loadTheographic(book, chapter), [book, chapter]);
if (status === 'loading') return <p className="text-xs text-slate-500">Loading…</p>;
if (status === 'empty') return <p className="text-xs text-slate-500">No people or places data for this chapter.</p>;
if (status === 'loading') return <p className="text-xs text-[var(--r-sub,#64748b)]">Loading…</p>;
if (status === 'empty') return <p className="text-xs text-[var(--r-sub,#64748b)]">No people or places data for this chapter.</p>;
const here = (list) => list.filter((x) => x.verses?.includes(verse));
// Theographic lists some places twice (variant spellings) — collapse by name.
const places = [...new Map(here(data.places).map((p) => [p.name, p])).values()];
const people = here(data.people);
const events = here(data.events);
if (!people.length && !places.length && !events.length) {
return <p className="text-xs text-slate-500">Nothing tagged on verse {verse}.</p>;
return <p className="text-xs text-[var(--r-sub,#64748b)]">Nothing tagged on verse {verse}.</p>;
}
return (
<div className="space-y-3">
{people.length > 0 && (
<div>
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-slate-500">People</p>
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-[var(--r-sub,#64748b)]">People</p>
<ul className="space-y-1">{people.map((p) => <PersonCard key={p.id} person={p} />)}</ul>
</div>
)}
{places.length > 0 && (
<div>
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-slate-500">Places</p>
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-[var(--r-sub,#64748b)]">Places</p>
<ul className="space-y-1">
{places.map((p) => (
<li key={p.id} className="flex items-center justify-between gap-2 rounded-xl bg-white px-3 py-2 text-sm text-slate-800">
<span>{p.name}{p.featureType && <span className="ml-1 text-xs text-slate-400">{p.featureType}</span>}</span>
<li key={p.id} className="flex items-center justify-between gap-2 rounded-xl bg-[var(--r-bg,#ffffff)] px-3 py-2 text-sm text-[var(--r-text,#334155)]">
<span>{p.name}{p.featureType && <span className="ml-1 text-xs text-[var(--r-sub,#64748b)]">{p.featureType}</span>}</span>
{p.latitude != null && p.longitude != null && (
<a href={`https://www.openstreetmap.org/?mlat=${p.latitude}&mlon=${p.longitude}#map=8/${p.latitude}/${p.longitude}`}
target="_blank" rel="noopener noreferrer" className="text-xs font-semibold text-sky-600 hover:underline">
@@ -117,17 +117,17 @@ export function PeoplePlacesTab({ book, chapter, verse }) {
)}
{events.length > 0 && (
<div>
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-slate-500">Events</p>
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-[var(--r-sub,#64748b)]">Events</p>
<ul className="space-y-1">
{events.map((e) => (
<li key={e.id} className="rounded-xl bg-white px-3 py-2 text-sm text-slate-800">
{e.name}{e.startDate && <span className="ml-1 text-xs text-slate-400">approx. AD {e.startDate}</span>}
<li key={e.id} className="rounded-xl bg-[var(--r-bg,#ffffff)] px-3 py-2 text-sm text-[var(--r-text,#334155)]">
{e.name}{e.startDate && <span className="ml-1 text-xs text-[var(--r-sub,#64748b)]">approx. AD {e.startDate}</span>}
</li>
))}
</ul>
</div>
)}
<p className="text-[10px] text-slate-400">
<p className="text-[10px] text-[var(--r-sub,#64748b)]">
People, places and events: Theographic Bible Metadata, licensed CC BY-SA 4.0.
</p>
</div>
@@ -138,7 +138,7 @@ export default function StudyPanel({ book, chapter, verse, onClose }) {
const [tab, setTab] = useState('commentary');
const tabBtn = (id, label) => (
<button type="button" onClick={() => setTab(id)}
className={`rounded-lg px-3 py-1 text-xs font-semibold ${tab === id ? 'bg-slate-900 text-white' : 'bg-white text-slate-600 hover:bg-slate-100'}`}>
className={`rounded-lg px-3 py-1 text-xs font-semibold ${tab === id ? 'bg-slate-900 text-white' : 'bg-[var(--r-bg,#ffffff)] text-[var(--r-sub,#64748b)] hover:bg-slate-100'}`}>
{label}
</button>
);
@@ -149,7 +149,7 @@ export default function StudyPanel({ book, chapter, verse, onClose }) {
{tabBtn('commentary', 'Commentary')}
{tabBtn('people', 'People & Places')}
</div>
<button type="button" onClick={onClose} className="rounded p-1 text-xs text-slate-500 hover:bg-white" title="Close">✕</button>
<button type="button" onClick={onClose} className="rounded p-1 text-xs text-[var(--r-sub,#64748b)] hover:bg-[var(--r-bg,#ffffff)]" title="Close">✕</button>
</div>
{tab === 'commentary'
? <CommentaryTab book={book} chapter={chapter} verse={verse} />