diff --git a/src/components/ShortcutsHelp.jsx b/src/components/ShortcutsHelp.jsx
index 60bb09d..f315b2b 100644
--- a/src/components/ShortcutsHelp.jsx
+++ b/src/components/ShortcutsHelp.jsx
@@ -14,6 +14,7 @@ const BY_PAGE = {
['/', 'Search this chapter or the whole Bible'],
['G', 'Pick a book and chapter'],
['B', 'Bookmark the selected verse'],
+ ['C', 'Compare translations'],
[',', 'Reading settings'],
['F', 'Hide or show the controls'],
],
diff --git a/src/components/reader/CompareSheet.jsx b/src/components/reader/CompareSheet.jsx
new file mode 100644
index 0000000..f8dc8d9
--- /dev/null
+++ b/src/components/reader/CompareSheet.jsx
@@ -0,0 +1,35 @@
+// Compare translations: pick a second Bible to read beside the one you're reading, and whether the
+// words that differ are highlighted. The chapter shows how different the two are.
+import Sheet from './Sheet.jsx';
+import { READER_TRANSLATIONS } from '../../utils/bibleText.js';
+
+export default function CompareSheet({ open, onClose, translation, parallel, onParallel, showDiff, onShowDiff }) {
+ const chip = (active) => `rounded-full border px-3 py-1.5 text-sm font-medium ${active ? 'border-[var(--r-text)] bg-[var(--r-text)] text-[var(--r-bg)]' : 'border-[var(--r-line)] text-[var(--r-sub)] hover:bg-black/5'}`;
+ const options = READER_TRANSLATIONS.filter((t) => t.id !== translation);
+ return (
+
+
+ Read another Bible beside the {translation}. You’ll see how different its wording is for the whole chapter and for each verse.
+
+ Compare {translation} with
+
+ {options.map((t) => (
+ { onParallel(parallel === t.id ? '' : t.id); if (parallel !== t.id) onClose(); }} className={chip(parallel === t.id)}>{t.id}
+ ))}
+ onParallel('')} className={chip(!parallel)}>Off
+
+ {parallel && (
+ <>
+ Highlight the words that differ
+
+ {[[true, 'Show'], [false, 'Hide']].map(([id, text]) => (
+ onShowDiff(id)}
+ className={`flex-1 px-3 py-2 text-sm ${showDiff === id ? 'bg-[var(--r-text)] text-[var(--r-bg)]' : 'text-[var(--r-sub)] hover:bg-black/5'}`}>{text}
+ ))}
+
+ >
+ )}
+ The wording is compared word by word, ignoring capitals and punctuation. It shows how the wording differs, not which translation is more accurate. Tap any verse and open Compare to see all six translations of it.
+
+ );
+}
diff --git a/src/components/reader/ReaderBars.jsx b/src/components/reader/ReaderBars.jsx
index 575365d..9e64073 100644
--- a/src/components/reader/ReaderBars.jsx
+++ b/src/components/reader/ReaderBars.jsx
@@ -1,11 +1,11 @@
// Top and bottom chrome for the reader. Everything stays one tap away; focus
// mode hides both.
-import { ChevronLeft, ChevronRight, ChevronDown, SearchIcon, LinkIcon, PenIcon, HeadphonesIcon, FocusIcon, CheckIcon } from './icons.jsx';
+import { ChevronLeft, ChevronRight, ChevronDown, SearchIcon, LinkIcon, PenIcon, HeadphonesIcon, FocusIcon, CheckIcon, CompareIcon } from './icons.jsx';
import { BookmarkIcon } from '../../utils/bibleUtils.jsx';
const iconBtn = 'flex h-9 w-9 items-center justify-center rounded-lg text-[var(--r-sub)] transition hover:bg-black/5 disabled:opacity-30 disabled:hover:bg-transparent';
-export function TopBar({ title, onBack, onOpenNavigator, onSearch, onBookmarks, bookmarkCount, onSettings, onDoneDrawing }) {
+export function TopBar({ title, onBack, onOpenNavigator, onSearch, onBookmarks, bookmarkCount, onCompare, compareActive, onSettings, onDoneDrawing }) {
return (
@@ -25,6 +25,7 @@ export function TopBar({ title, onBack, onOpenNavigator, onSearch, onBookmarks,
0} />
+
Aa
>
)}
diff --git a/src/components/reader/SettingsSheet.jsx b/src/components/reader/SettingsSheet.jsx
index 15d86f5..0c3a5f9 100644
--- a/src/components/reader/SettingsSheet.jsx
+++ b/src/components/reader/SettingsSheet.jsx
@@ -2,7 +2,6 @@
// to read beside the first.
import Sheet from './Sheet.jsx';
import { READER_THEMES } from './readerThemes.js';
-import { READER_TRANSLATIONS } from '../../utils/bibleText.js';
const SIZES = [['S', 0.875], ['M', 1], ['L', 1.125], ['XL', 1.25], ['XXL', 1.5]];
@@ -22,7 +21,6 @@ function Segmented({ options, value, onChange, label }) {
export default function SettingsSheet({
open, onClose, fontSize, onFontSize, theme, onTheme, layout, onLayout, wide, onWide,
font, onFont, spacing, onSpacing, verseNumbers, onVerseNumbers,
- translation, parallel, onParallel, showDiff, onShowDiff,
}) {
const row = 'mb-4';
const label = 'mb-1.5 block text-xs text-[var(--r-sub)]';
@@ -56,24 +54,6 @@ export default function SettingsSheet({
Width
-
-
Read beside another Bible
-
- {[{ id: '', name: 'Off' }, ...READER_TRANSLATIONS.filter((t) => t.id !== translation)].map((t) => (
- onParallel(t.id)} aria-pressed={parallel === t.id} title={t.name}
- className={`rounded-full border px-3 py-1 text-xs font-medium ${parallel === t.id ? 'border-[var(--r-text)] bg-[var(--r-text)] text-[var(--r-bg)]' : 'border-[var(--r-line)] text-[var(--r-sub)] hover:bg-black/5'}`}>
- {t.id || 'Off'}
-
- ))}
-
-
Shows both side by side on wider screens, and stacked on a phone.
-
- {parallel && (
-
- Highlight differences from {translation}
-
-
- )}
);
}
diff --git a/src/components/reader/icons.jsx b/src/components/reader/icons.jsx
index 5d1c265..59523f5 100644
--- a/src/components/reader/icons.jsx
+++ b/src/components/reader/icons.jsx
@@ -11,3 +11,4 @@ export const PenIcon = () =>
;
export const FocusIcon = () =>
;
export const CheckIcon = () =>
;
+export const CompareIcon = () =>
;
diff --git a/src/pages/ReaderPage.jsx b/src/pages/ReaderPage.jsx
index 3fa14a6..0921d10 100644
--- a/src/pages/ReaderPage.jsx
+++ b/src/pages/ReaderPage.jsx
@@ -15,6 +15,7 @@ import ReaderText from '../components/reader/ReaderText.jsx';
import PlanStrip from '../components/reader/PlanStrip.jsx';
import NavigatorSheet from '../components/reader/NavigatorSheet.jsx';
import SettingsSheet from '../components/reader/SettingsSheet.jsx';
+import CompareSheet from '../components/reader/CompareSheet.jsx';
import SearchSheet from '../components/reader/SearchSheet.jsx';
import MarksSheet from '../components/reader/MarksSheet.jsx';
import { useToast } from '../components/Toasts.jsx';
@@ -272,6 +273,7 @@ export default function ReaderPage() {
if (e.key === '/') { e.preventDefault(); openSheet('search'); }
else if (e.key === 'g' || e.key === 'G') { e.preventDefault(); openSheet('nav'); }
else if (e.key === ',') { e.preventDefault(); openSheet('settings'); }
+ else if (e.key === 'c' || e.key === 'C') { e.preventDefault(); openSheet('compare'); }
else if (e.key === 'f' || e.key === 'F') setChromeVisible((v) => !v);
else if ((e.key === 'b' || e.key === 'B') && selectedKey) {
readerBookmarks[selectedKey] ? removeBookmark(selectedKey, readerBookmarks[selectedKey]) : toggleReaderBookmark(selectedKey);
@@ -405,6 +407,7 @@ export default function ReaderPage() {
onSearch={() => openSheet('search')}
onBookmarks={() => openSheet('bookmarks')}
bookmarkCount={bookmarkEntries.length + highlightEntries.length}
+ onCompare={() => openSheet('compare')} compareActive={!!parallelId}
onSettings={() => openSheet('settings')}
onDoneDrawing={readerDrawMode ? () => setReaderDrawMode(false) : undefined}
/>
@@ -558,6 +561,9 @@ export default function ReaderPage() {
font={readerFont} onFont={setReaderFont}
spacing={readerSpacing} onSpacing={setReaderSpacing}
verseNumbers={readerVerseNumbers} onVerseNumbers={setReaderVerseNumbers}
+ />
+
diff --git a/src/pages/ReaderPage.test.jsx b/src/pages/ReaderPage.test.jsx
index 596093c..1702c9b 100644
--- a/src/pages/ReaderPage.test.jsx
+++ b/src/pages/ReaderPage.test.jsx
@@ -47,6 +47,17 @@ test('renders the chapter, chrome and opens the tool sheets', () => {
expect(screen.getByPlaceholderText(/Search John 3/)).toBeTruthy();
});
+test('the compare button next to Aa opens the comparison sheet, and choosing a Bible turns it on', () => {
+ ctx.setReaderParallel = vi.fn();
+ render(
);
+ const compare = screen.getByLabelText('Compare translations');
+ expect(compare.nextElementSibling).toBe(screen.getByLabelText('Reading settings')); // sits right beside Aa
+ fireEvent.click(compare);
+ expect(screen.getByText(/how different its wording is/)).toBeTruthy();
+ fireEvent.click(screen.getByRole('button', { name: 'NET' }));
+ expect(ctx.setReaderParallel).toHaveBeenCalledWith('NET');
+});
+
test('tapping a verse selects it', () => {
render(
);
fireEvent.click(screen.getByText(/This man came to Jesus/));