From 0e9edeea16e5b3dca2a9ddcf510863f4a5fbdc8f Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Mon, 16 Feb 2026 14:07:06 -0600 Subject: [PATCH 01/25] Feat: Add intro chapter button to bible picker Adds a button to select the introduction chapter for a book in the bible chapter picker. This allows users to easily navigate to the book's introduction. --- .../src/components/bible-chapter-picker.tsx | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/bible-chapter-picker.tsx b/packages/ui/src/components/bible-chapter-picker.tsx index 68a07d21..9bfb59dd 100644 --- a/packages/ui/src/components/bible-chapter-picker.tsx +++ b/packages/ui/src/components/bible-chapter-picker.tsx @@ -165,6 +165,25 @@ function Root({ {bookItem.chapters && bookItem.chapters.length > 0 ? (
+ {bookItem.intro?.id && bookItem.intro?.passage_id ? ( + + + + ) : null} {bookItem.chapters.map((chapterRef) => { const chapterId = chapterRef.passage_id.split('.').pop() || ''; return ( @@ -179,11 +198,7 @@ function Root({ setSearchQuery(''); }} > - {!chapterId.toLowerCase().includes('intro') ? ( - chapterId - ) : ( - - )} + {chapterId} ); From c58cd209f612d9c9c9aad09ce402c13f332c6704 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Mon, 16 Feb 2026 14:12:12 -0600 Subject: [PATCH 02/25] Refactor bible reader to conditionally render header Conditionally renders the book and chapter header in the bible reader component. This ensures the header is only displayed when the chapter is a valid numerical value. --- packages/ui/src/components/bible-reader.tsx | 29 ++++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/ui/src/components/bible-reader.tsx b/packages/ui/src/components/bible-reader.tsx index 9b3fa285..78091d6e 100644 --- a/packages/ui/src/components/bible-reader.tsx +++ b/packages/ui/src/components/bible-reader.tsx @@ -194,22 +194,25 @@ function Content() { }, [books?.data, book]); const usfmReference = `${book}.${chapter}`; + const chapterIsNumerical: boolean = !!parseInt(chapter || '', 10) || false; return (
-

- - {bookData?.title || 'Loading...'} - - - {chapter || '-'} - -

+ {chapterIsNumerical ? ( +

+ + {bookData?.title || 'Loading...'} + + + {chapter || '-'} + +

+ ) : null} Date: Mon, 16 Feb 2026 14:16:14 -0600 Subject: [PATCH 03/25] Fix: Conditionally render grid columns in Bible reader toolbar Adjust the `grid-cols` class in the Bible reader toolbar to dynamically render either `auto_1fr_auto` or `1fr_auto` based on the `authEnabled` state from the `yvContext`. --- packages/ui/src/components/bible-reader.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/bible-reader.tsx b/packages/ui/src/components/bible-reader.tsx index 78091d6e..a4f679ae 100644 --- a/packages/ui/src/components/bible-reader.tsx +++ b/packages/ui/src/components/bible-reader.tsx @@ -312,7 +312,14 @@ function Toolbar({ border = 'top' }: { border?: 'top' | 'bottom' }) { border === 'bottom' && 'yv:border-b', )} > -
+
{!!yvContext?.authEnabled && }
From 2443fdf9948aef36c357f7a7225a65b504e5fbca Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Mon, 16 Feb 2026 14:57:50 -0600 Subject: [PATCH 04/25] Fix: Display chapter title or "Intro" in picker --- packages/ui/src/components/bible-chapter-picker.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/bible-chapter-picker.tsx b/packages/ui/src/components/bible-chapter-picker.tsx index 9bfb59dd..bf9a21ae 100644 --- a/packages/ui/src/components/bible-chapter-picker.tsx +++ b/packages/ui/src/components/bible-chapter-picker.tsx @@ -258,13 +258,18 @@ function Trigger({ asChild = true, children, ...props }: TriggerProps) { const theme = background || providerTheme; const currentBook = books?.data?.find((bookItem) => bookItem.id === book); + let currentChapter: string = + currentBook?.chapters?.find((ch) => ch.id === chapter)?.title || chapter; + if (!!currentBook?.intro && chapter === currentBook.intro.id) { + currentChapter = currentBook.intro.title; + } const buttonText = loading ? 'Loading...' - : `${currentBook?.title || 'Select a chapter'}${chapter ? ` ${chapter}` : ''}`; + : `${currentBook?.title || 'Select a chapter'}${currentChapter ? ` ${currentChapter}` : ''}`; const content = typeof children === 'function' - ? children({ book, chapter, currentBook, loading }) + ? children({ book, chapter: currentChapter, currentBook, loading }) : children || ; return ( From b72671c29efd35af8adfe1b8df9fb3ad3a0c881b Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Mon, 16 Feb 2026 15:06:06 -0600 Subject: [PATCH 05/25] Add '.ili1' CSS class to bible reader styles This class handles intended list items, ensuring correct styling for elements like the Acts Intro in certain Bible versions. --- packages/ui/src/styles/bible-reader.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index d198a8ab..d259182c 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -168,6 +168,13 @@ display: list-item; } + /* Intended list item (e.g. see Acts Intro in NIV Bible Version `111`) */ + & .ili1 { + list-style-type: none; + margin-left: 2em; + display: list-item; + } + /* Indented paragraph indent */ & .ipi { text-indent: 1.5em; From 4a3f615a9180264f8a310f6bc0301a1f264f4e31 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Mon, 16 Feb 2026 15:09:34 -0600 Subject: [PATCH 06/25] fix: Correct typo in bible reader styles Corrected "Intended" to "Indented" in a CSS comment for clarity. --- packages/ui/src/styles/bible-reader.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index d259182c..ee5d4db6 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -168,7 +168,7 @@ display: list-item; } - /* Intended list item (e.g. see Acts Intro in NIV Bible Version `111`) */ + /* Indented list item (e.g. see Acts Intro in NIV Bible Version `111`) */ & .ili1 { list-style-type: none; margin-left: 2em; From 7b05d1ee04d52e6c07f7d2fe5884a73ef8dd1867 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Mon, 16 Feb 2026 20:34:55 -0600 Subject: [PATCH 07/25] Update bible reader styles with new CSS classes This commit adds and updates several CSS classes within the bible reader styling to ensure accurate rendering of scripture content. Key changes include: - Applying `display: block` to the root element for better layout control. - Inheriting typography from the root for improved cascade and em-based sizing. - Adding detailed styling for various scripture formatting tags such as titles, headings, paragraphs, poetry, quotes, and special text styles. - Updating comments to better reflect the purpose of each CSS class. --- packages/ui/src/styles/bible-reader.css | 529 +++++++++++++++++++----- 1 file changed, 421 insertions(+), 108 deletions(-) diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index ee5d4db6..d89cd175 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -2,6 +2,7 @@ [data-slot='yv-bible-renderer'] { /* Makes it where cascading styles from the parent app do not affect the bible reader */ all: unset; + display: block; --font-sans: 'Inter', sans-serif; --font-serif: 'Source Serif 4', serif; @@ -9,17 +10,23 @@ --yv-reader-font-size: 20px; --yv-reader-line-height: 1.625; + /* Set base typography on the root so descendants can inherit */ + font-family: var(--yv-reader-font-family); + font-size: var(--yv-reader-font-size); + line-height: var(--yv-reader-line-height); + /* Disable text selection and tap highlights on mobile */ -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; - /* Set default font and line-height for all descendants */ + /* Inherit typography from root instead of forcing a fixed size on every element. + This allows em-based sizing in headings/poetry to cascade properly to children. */ & * { - line-height: var(--yv-reader-line-height); - font-family: var(--yv-reader-font-family); - font-size: var(--yv-reader-font-size); + font-family: inherit; + font-size: inherit; + line-height: inherit; margin: 0; padding: 0; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); @@ -32,17 +39,16 @@ @apply yv:*:w-full yv:text-foreground yv:*:max-w-lg; /* Hide metadata and navigation classes */ - & .id, - & .ide, - & .sts, - & .rem, - & .toc1, - & .toc2, - & .toc3, - & .imte, - & .ie, - & .mte, - & .cl { + & .id, /* \id - File identification */ + & .ide, /* \ide - Encoding specification */ + & .sts, /* \sts - Status */ + & .rem, /* \rem - Remark */ + & .toc1, /* \toc1 - Long table of contents text */ + & .toc2, /* \toc2 - Short table of contents text */ + & .toc3, /* \toc3 - Book abbreviation */ + & .ie, /* \ie - Introduction end marker */ + & .mte, /* \mte - Major title at ending */ + & .cl { /* \cl - Chapter label */ display: none; } @@ -75,7 +81,7 @@ @apply yv:hidden; } - /* Footnote */ + /* \f - Footnote container (YouVersion wrapper) */ & .yv-n { display: none; } @@ -131,12 +137,12 @@ } /* Major titles */ - & .imt, - & .imt1, - & .mt, - & .mt1, - & .ms, - & .ms1 { + & .imt, /* \imt - Introduction major title */ + & .imt1, /* \imt1 - Introduction major title (level 1) */ + & .mt, /* \mt - Main title */ + & .mt1, /* \mt1 - Main title (level 1) */ + & .ms, /* \ms - Major section heading */ + & .ms1 { /* \ms1 - Major section heading (level 1) */ font-size: 1.3em; font-weight: bold; line-height: 1.8em; @@ -144,7 +150,7 @@ display: block; } - /* Other title */ + /* \iot - Introduction outline title (e.g. "Outline of Contents") */ & .iot { font-size: 1.3em; font-weight: 700; @@ -153,40 +159,140 @@ display: block; } - /* Indented ordered list */ - & .io, - & .io1 { + /* \io - Introduction outline entries (displayed as bulleted list items) */ + & .io, /* \io - Introduction outline entry */ + & .io1 { /* \io1 - Introduction outline entry (level 1) */ + list-style-type: disc; + margin-inline-start: 1em; + display: list-item; + } + + & .io2, /* \io2 - Introduction outline entry (level 2) */ + & .io3 { /* \io3 - Introduction outline entry (level 3) */ list-style-type: disc; - margin-left: 1em; + margin-inline-start: 2em; display: list-item; } - & .io2, - & .io3 { + /* \io4 - Introduction outline entry (level 4) */ + & .io4 { list-style-type: disc; - margin-left: 2em; + margin-inline-start: 3em; + display: list-item; + } + + /* \ior - Introduction outline reference range (e.g. "(1.1-13)") */ + & .ior { + font-style: italic; + } + + /* \ili - Introduction list items */ + & .ili, /* \ili - Introduction list item (default) */ + & .ili1 { /* \ili1 - Introduction list item (level 1) */ + list-style-type: none; + margin-inline-start: 2em; display: list-item; } - /* Indented list item (e.g. see Acts Intro in NIV Bible Version `111`) */ - & .ili1 { + /* \ili2 - Introduction list item (level 2) */ + & .ili2 { list-style-type: none; - margin-left: 2em; + margin-inline-start: 4em; display: list-item; } - /* Indented paragraph indent */ + /* \ili3 - Introduction list item (level 3) */ + & .ili3 { + list-style-type: none; + margin-inline-start: 6em; + display: list-item; + } + + /* \ipi - Indented introduction paragraph */ & .ipi { + display: block; text-indent: 1.5em; } + /* \im - Introduction flush left (margin) paragraph */ + & .im { + display: block; + text-indent: 0; + margin: 0; + } + + /* \imi - Indented introduction flush left (margin) paragraph */ + & .imi { + display: block; + margin-inline-start: 1em; + text-indent: 0; + } + + /* \ipq - Introduction quote from scripture text */ + & .ipq { + display: block; + font-style: italic; + text-indent: 1em; + } + + /* \imq - Introduction flush left quote from text */ + & .imq { + display: block; + font-style: italic; + text-indent: 0; + } + + /* \ipr - Introduction right-aligned paragraph (typically a scripture reference) */ + & .ipr { + display: block; + text-align: end; + } + + /* \ib - Introduction blank line (whitespace between intro paragraphs) */ + & .ib { + display: block; + height: 1em; + } + + /* \iqt - Introduction quoted text (scripture quotation in intro) */ + & .iqt { + font-style: italic; + margin-inline-start: 1em; + } + + /* \imte - Introduction major title ending (marks end of introduction) */ + & .imte, /* \imte - Introduction major title ending */ + & .imte1 { /* \imte1 - Introduction major title ending (level 1) */ + font-size: 1.3em; + font-weight: bold; + line-height: 1.8em; + margin: 1em 0 0; + display: block; + } + + /* \imte2 - Introduction major title ending (level 2) */ + & .imte2 { + line-height: 1.8em; + margin: 0.5em 0; + font-weight: bold; + display: block; + } + + /* \iex - Introduction explanatory/bridge text (e.g. attribution at end of epistles) */ + & .iex { + display: block; + font-style: italic; + line-height: 2em; + margin: 0.5em 0; + } + /* Title levels 2-4 */ - & .imt2, - & .imt3, - & .imt4, - & .mt2, - & .mt3, - & .mt4 { + & .imt2, /* \imt2 - Introduction major title (level 2) */ + & .imt3, /* \imt3 - Introduction major title (level 3) */ + & .imt4, /* \imt4 - Introduction major title (level 4) */ + & .mt2, /* \mt2 - Main title (level 2) */ + & .mt3, /* \mt3 - Main title (level 3) */ + & .mt4 { /* \mt4 - Main title (level 4) */ line-height: 1.8em; margin: 0.5em 0; font-weight: bold; @@ -194,12 +300,11 @@ } /* Section headers */ - & .is, - & .s, - & .s1, - & .ms1, - & .is1, - & .is2 { + & .is, /* \is - Introduction section heading */ + & .s, /* \s - Section heading */ + & .s1, /* \s1 - Section heading (level 1) */ + & .is1, /* \is1 - Introduction section heading (level 1) */ + & .is2 { /* \is2 - Introduction section heading (level 2) */ font-size: 1.1em; font-weight: bold; line-height: 1.8em; @@ -214,6 +319,23 @@ display: block; } + /* \mr - Major section reference range (e.g. "(Psalms 1–41)") */ + & .mr { + display: block; + font-style: italic; + font-weight: bold; + line-height: 1.8em; + margin: 0 0 0.5em; + } + + /* \ms3 - Major section heading (level 3) */ + & .ms3 { + line-height: 1.8em; + margin: 0.5em 0; + font-weight: bold; + display: block; + } + /* Notes within headings */ & .s1 .note .heading, & .s .note .heading { @@ -237,29 +359,34 @@ font-size: 0.9em; } - /* Other styles within a footnote */ - & .note .fr { + /* Footnote/note character styles */ + & .note .fr { /* \fr - Footnote reference (verse number) */ padding-right: 0.25em; } - & .note .fq, - & .note .fqa { + & .note .fq, /* \fq - Footnote quotation from text */ + & .note .fqa { /* \fqa - Footnote alternate translation */ font-style: italic; } - & .note .fv { + & .note .ft { /* \ft - Footnote text */ + display: inline; + } + + & .note .fv { /* \fv - Footnote verse number */ vertical-align: super; font-size: 0.75em; } - & .note .fk { + & .note .fk { /* \fk - Footnote keyword */ font-style: italic; font-weight: bold; } /* Paragraphs - text-indent 1em based on iOS CSS */ - & .p, - & .ip { + & .p, /* \p - Normal paragraph */ + & .ip { /* \ip - Introduction paragraph */ + display: block; margin-bottom: 0; text-indent: 1em; } @@ -500,25 +627,87 @@ margin-top: 0; } - /* Words of Jesus */ + /* \wj - Words of Jesus (red letter) */ & .wj { color: var(--yv-red); } - /* Name of Deity (small-caps) */ + /* \nd - Name of Deity (e.g. "LORD"), \sc - Small-cap text */ & .nd, & .sc { font-variant: small-caps; } - /* Line break */ + /* \bd - Bold text */ + & .bd { + font-weight: bold; + } + + /* \em - Emphasis text */ + & .em { + font-style: italic; + } + + /* \bdit - Bold-italic text */ + & .bdit { + font-weight: bold; + font-style: italic; + } + + /* \bk - Book name reference (e.g. "The Gospel according to Mark") */ + & .bk { + font-style: italic; + } + + /* \ord - Ordinal number ending (e.g. the "st" in "1st") */ + & .ord { + font-size: 0.75em; + line-height: 2em; + position: relative; + vertical-align: baseline; + top: -0.5em; + } + + /* \pn - Proper name */ + & .pn { + text-decoration: underline; + } + + /* \k - Keyword/keyterm */ + & .k { + font-weight: normal; + font-style: normal; + } + + /* \w - Wordlist/glossary entry */ + & .w { + display: inline; + } + + /* \rq - Inline cross-reference quotation */ + & .rq { + font-style: italic; + font-size: 0.8em; + } + + /* \x - Cross-reference note */ + & .x { + display: none; + } + + /* \f - Footnote container */ + & .f { + display: inline; + } + + /* \b - Blank line (stanza break in poetry) */ & .b { line-height: 2em; height: 1em; display: block; } - /* No-break paragraph */ + /* \m - Continuation (margin) paragraph, \nb - No-break paragraph */ & .m, & .nb { margin-bottom: calc(var(--yv-reader-font-size) * 0.5); @@ -526,60 +715,60 @@ display: block; } - /* Poetry quotes */ + /* \q - Poetic line */ & .q, & .q1 { display: block; - padding-left: 2em; + padding-inline-start: 2em; text-indent: -2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } & .q2 { display: block; - padding-left: 2em; + padding-inline-start: 2em; text-indent: -1em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } & .q3 { display: block; - padding-left: 3em; + padding-inline-start: 3em; text-indent: -2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } & .q4 { display: block; - padding-left: 4em; + padding-inline-start: 4em; text-indent: -2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } - /* Indented paragraphs */ + /* \pi - Indented paragraph (used for discourse sections) */ & .pi, & .pi1 { display: block; text-indent: 1em; - padding-left: 0; + padding-inline-start: 0; margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } & .pi2 { display: block; text-indent: 2em; - padding-left: 1em; + padding-inline-start: 1em; margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } & .pi3 { display: block; text-indent: 4em; - padding-left: 3em; + padding-inline-start: 3em; margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - /* Margin indented */ + /* \mi - Indented flush left, \pm - Embedded text, \pmo - Embedded opening, \pmc - Embedded closing, \pmr - Embedded refrain */ & .mi, & .pm, & .pmo, @@ -587,109 +776,108 @@ & .pmr { display: block; text-indent: 0; - padding-left: 2em; + padding-inline-start: 2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - /* Right-aligned */ + /* \pr - Right-aligned paragraph, \qr - Right-aligned poetic refrain */ & .pr, & .qr { - text-align: right; + text-align: end; display: block; margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - /* Center-aligned */ + /* \pc - Centered paragraph (inscriptions), \qc - Centered poetic line */ & .pc, & .qc { - font-variant: small-caps; text-align: center; - font-weight: bold; display: block; margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - /* Indented quotes */ + /* \iq - Introduction poetic line */ & .iq, & .iq1 { display: block; - padding-left: 2em; + padding-inline-start: 2em; text-indent: -2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } & .iq2 { display: block; - padding-left: 2em; + padding-inline-start: 2em; text-indent: -1em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } & .iq3 { display: block; - padding-left: 3em; + padding-inline-start: 3em; text-indent: -2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } & .iq4 { display: block; - padding-left: 4em; + padding-inline-start: 4em; text-indent: -2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } - /* Quote margin */ + /* \qm - Embedded text poetic line (quoted poetry within prose) */ & .qm, & .qm1 { display: block; - padding-left: 2em; + padding-inline-start: 2em; text-indent: -2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } & .qm2 { display: block; - padding-left: 2em; + padding-inline-start: 2em; text-indent: -1em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } & .qm3 { display: block; - padding-left: 3em; + padding-inline-start: 3em; text-indent: -2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } & .qm4 { display: block; - padding-left: 4em; + padding-inline-start: 4em; text-indent: -2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } - /* Italic text */ + /* \add - Translator addition, \it - Italic text, \tl - Transliterated word */ & .add, & .it, & .tl { font-style: italic; } + /* \qt - Quoted text (OT quotation in NT, etc.) */ & .qt { font-style: italic; } - /* Selah */ + /* \qs - Selah (commonly in Psalms and Habakkuk) */ & .qs { display: block; - text-align: right; + text-align: end; font-style: italic; margin-top: calc(var(--yv-reader-font-size) * 0.5); margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - /* Section headers level 2 */ + /* \s2 - Section heading (level 2) */ & .s2 { display: block; font-weight: bold; @@ -698,15 +886,15 @@ font-size: 1em; } - & .s3, - & .s4 { + & .s3, /* \s3 - Section heading (level 3) */ + & .s4 { /* \s4 - Section heading (level 4) */ display: block; font-weight: bold; line-height: 1.8em; margin: 0 0 0.5em; } - /* Major section header level 2 */ + /* \ms2 - Major section heading (level 2) */ & .ms2 { line-height: 1.8em; margin: 0.5em 0; @@ -714,58 +902,70 @@ display: block; } - /* Reference/ruby text */ + /* \r - Parallel passage reference (e.g. "(Mark 1.1-8; Luke 3.1-18)") */ & .r { display: block; font-style: italic; - font-weight: bold; line-height: 1.8em; margin: 0 0 0.5em; } - & .sp, + /* \sp - Speaker identification (e.g. Job, Song of Songs) */ + & .sp { + display: block; + font-style: italic; + line-height: 1.8em; + margin: 0 0 0.5em; + } + + /* \sr - Section reference range */ & .sr { display: block; font-style: italic; - font-weight: bold; + font-size: 0.8em; line-height: 1.8em; margin: 0 0 0.5em; } - & .qa { + & .qa { /* \qa - Acrostic heading (e.g. Hebrew letters in Psalm 119) */ display: block; font-weight: bold; line-height: 1.8em; margin: 0 0 0.5em; } - /* Hebrew text heading */ + /* \d - Descriptive title / Hebrew subtitle (e.g. Psalm superscriptions) */ & .d { display: block; font-style: italic; - font-weight: bold; line-height: 1.8em; margin: 0.5em 0; } - /* List items */ + /* \li - List entry */ & .li, & .li1 { display: list-item; list-style-type: disc; - margin-left: 1em; + margin-inline-start: 1em; } & .li2 { display: list-item; list-style-type: disc; - margin-left: 2em; + margin-inline-start: 2em; + } + + & .li3 { + display: list-item; + list-style-type: disc; + margin-inline-start: 3em; } - & .li3, & .li4 { display: list-item; list-style-type: disc; + margin-inline-start: 4em; } /* Line breaks */ @@ -873,14 +1073,127 @@ vertical-align: top; } - /* RTL Support */ - &[dir='rtl'] { - direction: rtl; - text-align: right; + /* \po - Opening of an epistle/letter */ + & .po { + display: block; + text-indent: 1em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - &[dir='rtl'] .p, - &[dir='rtl'] .m { + /* \ph - Indented paragraph with hanging indent (deprecated, use \li) */ + & .ph, + & .ph1 { + display: block; + padding-inline-start: 2em; + text-indent: -2em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.5); + } + + & .ph2 { + display: block; + padding-inline-start: 3em; + text-indent: -2em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.5); + } + + /* \sig - Signature of the author of a letter/epistle */ + & .sig { + font-style: italic; + } + + /* \dc - Deuterocanonical/LXX additions */ + & .dc { + font-style: italic; + } + + /* \qd - Hebrew note at end of poetic section */ + & .qd { + display: block; + font-style: italic; + margin: 0.5em 0; + } + + /* \sd - Semantic division (vertical space between sections) */ + & .sd { + display: block; + height: 1em; + } + + /* \lh - List header, \lf - List footer */ + & .lh, + & .lf { + display: block; + margin: 0.5em 0 0.25em; + } + + /* \lim - Embedded list entry */ + & .lim, + & .lim1 { + display: list-item; + list-style-type: disc; + margin-inline-start: 2em; + } + + & .lim2 { + display: list-item; + list-style-type: disc; + margin-inline-start: 4em; + } + + /* \qac - Acrostic letter within a poetic line */ + & .qac { + font-weight: bold; + } + + /* \lb - Line break (same as \b) */ + & .lb { + display: block; + height: 1em; + } + + /* \cd - Chapter description */ + & .cd { + display: block; + font-style: italic; + margin: 0.5em 0; + } + + /* \fdc - Footnote deuterocanonical content */ + & .fdc { + font-style: italic; + } + + /* \cls - Closure of an epistle/letter (e.g. "May God's grace be with you.") */ + & .cls { + display: block; + text-align: end; + margin: 0.5em 0; + } + + /* \no - Normal text (resets italic/bold within a styled context) */ + & .no { + font-weight: normal; + font-style: normal; + } + + /* \fp - Footnote paragraph (block-level paragraph within a footnote) */ + & .fp { + display: block; + margin-top: 0.5em; + text-indent: 1em; + } + + /* \lit - Liturgical note (e.g. "Glory:" in Psalms) */ + & .lit { + display: block; + text-align: end; + font-style: italic; + } + + /* RTL Support - logical properties handle indent/padding mirroring automatically. + This block only needs to set direction and base text alignment. */ + &[dir='rtl'] { + direction: rtl; text-align: right; } } From e73ae5edd2939c94839cd124028d73a53cf38e71 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Mon, 16 Feb 2026 21:04:42 -0600 Subject: [PATCH 08/25] Add styling for verse labels This commit introduces CSS rules for the `.yv-vlbl` class, ensuring that verse labels are displayed with the correct sans-serif font. This enhances the visual consistency and readability of biblical content within the UI. --- packages/ui/src/styles/bible-reader.css | 52 +++++++++++++++++-------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index d89cd175..8610a914 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -48,7 +48,8 @@ & .toc3, /* \toc3 - Book abbreviation */ & .ie, /* \ie - Introduction end marker */ & .mte, /* \mte - Major title at ending */ - & .cl { /* \cl - Chapter label */ + & .cl { + /* \cl - Chapter label */ display: none; } @@ -81,6 +82,10 @@ @apply yv:hidden; } + & .yv-vlbl { + font-family: var(--font-sans); + } + /* \f - Footnote container (YouVersion wrapper) */ & .yv-n { display: none; @@ -142,7 +147,8 @@ & .mt, /* \mt - Main title */ & .mt1, /* \mt1 - Main title (level 1) */ & .ms, /* \ms - Major section heading */ - & .ms1 { /* \ms1 - Major section heading (level 1) */ + & .ms1 { + /* \ms1 - Major section heading (level 1) */ font-size: 1.3em; font-weight: bold; line-height: 1.8em; @@ -161,14 +167,16 @@ /* \io - Introduction outline entries (displayed as bulleted list items) */ & .io, /* \io - Introduction outline entry */ - & .io1 { /* \io1 - Introduction outline entry (level 1) */ + & .io1 { + /* \io1 - Introduction outline entry (level 1) */ list-style-type: disc; margin-inline-start: 1em; display: list-item; } & .io2, /* \io2 - Introduction outline entry (level 2) */ - & .io3 { /* \io3 - Introduction outline entry (level 3) */ + & .io3 { + /* \io3 - Introduction outline entry (level 3) */ list-style-type: disc; margin-inline-start: 2em; display: list-item; @@ -188,7 +196,8 @@ /* \ili - Introduction list items */ & .ili, /* \ili - Introduction list item (default) */ - & .ili1 { /* \ili1 - Introduction list item (level 1) */ + & .ili1 { + /* \ili1 - Introduction list item (level 1) */ list-style-type: none; margin-inline-start: 2em; display: list-item; @@ -262,7 +271,8 @@ /* \imte - Introduction major title ending (marks end of introduction) */ & .imte, /* \imte - Introduction major title ending */ - & .imte1 { /* \imte1 - Introduction major title ending (level 1) */ + & .imte1 { + /* \imte1 - Introduction major title ending (level 1) */ font-size: 1.3em; font-weight: bold; line-height: 1.8em; @@ -292,7 +302,8 @@ & .imt4, /* \imt4 - Introduction major title (level 4) */ & .mt2, /* \mt2 - Main title (level 2) */ & .mt3, /* \mt3 - Main title (level 3) */ - & .mt4 { /* \mt4 - Main title (level 4) */ + & .mt4 { + /* \mt4 - Main title (level 4) */ line-height: 1.8em; margin: 0.5em 0; font-weight: bold; @@ -304,7 +315,8 @@ & .s, /* \s - Section heading */ & .s1, /* \s1 - Section heading (level 1) */ & .is1, /* \is1 - Introduction section heading (level 1) */ - & .is2 { /* \is2 - Introduction section heading (level 2) */ + & .is2 { + /* \is2 - Introduction section heading (level 2) */ font-size: 1.1em; font-weight: bold; line-height: 1.8em; @@ -360,32 +372,38 @@ } /* Footnote/note character styles */ - & .note .fr { /* \fr - Footnote reference (verse number) */ + & .note .fr { + /* \fr - Footnote reference (verse number) */ padding-right: 0.25em; } & .note .fq, /* \fq - Footnote quotation from text */ - & .note .fqa { /* \fqa - Footnote alternate translation */ + & .note .fqa { + /* \fqa - Footnote alternate translation */ font-style: italic; } - & .note .ft { /* \ft - Footnote text */ + & .note .ft { + /* \ft - Footnote text */ display: inline; } - & .note .fv { /* \fv - Footnote verse number */ + & .note .fv { + /* \fv - Footnote verse number */ vertical-align: super; font-size: 0.75em; } - & .note .fk { /* \fk - Footnote keyword */ + & .note .fk { + /* \fk - Footnote keyword */ font-style: italic; font-weight: bold; } /* Paragraphs - text-indent 1em based on iOS CSS */ & .p, /* \p - Normal paragraph */ - & .ip { /* \ip - Introduction paragraph */ + & .ip { + /* \ip - Introduction paragraph */ display: block; margin-bottom: 0; text-indent: 1em; @@ -887,7 +905,8 @@ } & .s3, /* \s3 - Section heading (level 3) */ - & .s4 { /* \s4 - Section heading (level 4) */ + & .s4 { + /* \s4 - Section heading (level 4) */ display: block; font-weight: bold; line-height: 1.8em; @@ -927,7 +946,8 @@ margin: 0 0 0.5em; } - & .qa { /* \qa - Acrostic heading (e.g. Hebrew letters in Psalm 119) */ + & .qa { + /* \qa - Acrostic heading (e.g. Hebrew letters in Psalm 119) */ display: block; font-weight: bold; line-height: 1.8em; From 8be19f37059c04e0647456faf8f2d931a923001a Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Mon, 16 Feb 2026 21:05:13 -0600 Subject: [PATCH 09/25] Make the font of the h1 Bible book and number be our serif font --- packages/ui/src/components/bible-reader.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/bible-reader.tsx b/packages/ui/src/components/bible-reader.tsx index a4f679ae..adaa088b 100644 --- a/packages/ui/src/components/bible-reader.tsx +++ b/packages/ui/src/components/bible-reader.tsx @@ -199,16 +199,16 @@ function Content() { return (
{chapterIsNumerical ? ( -

+

{bookData?.title || 'Loading...'} - + {chapter || '-'}

From 2ae9aa84625f29790924562e0f5afe756692a216 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Mon, 16 Feb 2026 21:31:33 -0600 Subject: [PATCH 10/25] Add USFM styling for reader component This commit introduces CSS styling for additional USFM tags within the Bible reader component. These additions include: - Hiding metadata and navigation tags like `\usfm`, `\h`, `\toca1`, `\toca2`, `\toca3`. - Correcting the display of scripture quotations in introductions (`\iqt`). - Adjusting the styling for inline elements like footnotes (`\fl`). - Improving the handling of proper names (`\pn`). - Updating styling for footnotes and endnotes (`\f`, `\fe`). - Adding styling for secondary language text (`\sls`). - Refining styles for major section headings (`\ms2`). - Ensuring proper alignment for table cells (`\tcr`, `\tcr1`). - Adding styling for indented list items (`\ph3`, `\lim3`). --- packages/ui/src/styles/bible-reader.css | 50 +++++++++++++++++++------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index 8610a914..269b7b48 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -41,13 +41,18 @@ /* Hide metadata and navigation classes */ & .id, /* \id - File identification */ & .ide, /* \ide - Encoding specification */ + & .usfm, /* \usfm - USFM version specification */ + & .h, /* \h - Running header text */ & .sts, /* \sts - Status */ & .rem, /* \rem - Remark */ & .toc1, /* \toc1 - Long table of contents text */ & .toc2, /* \toc2 - Short table of contents text */ & .toc3, /* \toc3 - Book abbreviation */ + & .toca1, /* \toca1 - Alt language long TOC text */ + & .toca2, /* \toca2 - Alt language short TOC text */ + & .toca3, /* \toca3 - Alt language book abbreviation */ & .ie, /* \ie - Introduction end marker */ - & .mte, /* \mte - Major title at ending */ + & .mte, /* \mte - Major title at ending (print convention) */ & .cl { /* \cl - Chapter label */ display: none; @@ -263,10 +268,9 @@ height: 1em; } - /* \iqt - Introduction quoted text (scripture quotation in intro) */ + /* \iqt - Introduction quoted text (scripture quotation in intro, character style) */ & .iqt { font-style: italic; - margin-inline-start: 1em; } /* \imte - Introduction major title ending (marks end of introduction) */ @@ -335,7 +339,6 @@ & .mr { display: block; font-style: italic; - font-weight: bold; line-height: 1.8em; margin: 0 0 0.5em; } @@ -400,6 +403,11 @@ font-weight: bold; } + & .note .fl { + /* \fl - Footnote label (e.g. "Or", "Heb.", "LXX") */ + font-weight: bold; + } + /* Paragraphs - text-indent 1em based on iOS CSS */ & .p, /* \p - Normal paragraph */ & .ip { @@ -686,9 +694,10 @@ top: -0.5em; } - /* \pn - Proper name */ + /* \pn - Proper name (semantic marker, no special formatting per USFM spec) */ & .pn { - text-decoration: underline; + font-weight: normal; + font-style: normal; } /* \k - Keyword/keyterm */ @@ -713,8 +722,9 @@ display: none; } - /* \f - Footnote container */ - & .f { + /* \f - Footnote container, \fe - Endnote container */ + & .f, + & .fe { display: inline; } @@ -874,10 +884,12 @@ margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } - /* \add - Translator addition, \it - Italic text, \tl - Transliterated word */ + /* \add - Translator addition, \it - Italic text, \tl - Transliterated word, + \sls - Secondary language/alternate source text (e.g. Aramaic sections in Ezra/Daniel) */ & .add, & .it, - & .tl { + & .tl, + & .sls { font-style: italic; } @@ -915,6 +927,7 @@ /* \ms2 - Major section heading (level 2) */ & .ms2 { + font-size: 1.1em; line-height: 1.8em; margin: 0.5em 0; font-weight: bold; @@ -1079,7 +1092,7 @@ & .tcr, & .tcr1 { padding: 0.25em; - text-align: right; + text-align: end; vertical-align: top; } @@ -1089,7 +1102,7 @@ color: var(--yv-primary-foreground); font-weight: 700; padding: 0.25em; - text-align: right; + text-align: end; vertical-align: top; } @@ -1116,6 +1129,13 @@ margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } + & .ph3 { + display: block; + padding-inline-start: 4em; + text-indent: -2em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.5); + } + /* \sig - Signature of the author of a letter/epistle */ & .sig { font-style: italic; @@ -1160,6 +1180,12 @@ margin-inline-start: 4em; } + & .lim3 { + display: list-item; + list-style-type: disc; + margin-inline-start: 6em; + } + /* \qac - Acrostic letter within a poetic line */ & .qac { font-weight: bold; From 92b4bd8634a35d3ba7704dc1f2cea3248d73808a Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Mon, 16 Feb 2026 21:34:51 -0600 Subject: [PATCH 11/25] In Arabic/Hebrew Bible versions, this puts the footnote reference spacing on the wrong side. Every other property in the file was converted to logical properties, but this one slipped through. --- packages/ui/src/styles/bible-reader.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index 269b7b48..b0f40f52 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -377,7 +377,7 @@ /* Footnote/note character styles */ & .note .fr { /* \fr - Footnote reference (verse number) */ - padding-right: 0.25em; + padding-inline-end: 0.25em; } & .note .fq, /* \fq - Footnote quotation from text */ From 0781b1d01ba344ff0a4a8c85955dfb2f5dcbacee Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Mon, 16 Feb 2026 21:57:03 -0600 Subject: [PATCH 12/25] fix(ui): show unavailable message when chapter doesn't exist in selected Bible version --- packages/ui/src/components/bible-reader.tsx | 33 +++++++++++++++------ 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/ui/src/components/bible-reader.tsx b/packages/ui/src/components/bible-reader.tsx index adaa088b..e525b1e6 100644 --- a/packages/ui/src/components/bible-reader.tsx +++ b/packages/ui/src/components/bible-reader.tsx @@ -196,6 +196,13 @@ function Content() { const usfmReference = `${book}.${chapter}`; const chapterIsNumerical: boolean = !!parseInt(chapter || '', 10) || false; + // Check if the current chapter is available in this version + const chapterUnavailable = useMemo(() => { + if (!bookData || !chapter) return false; + if (!chapterIsNumerical && !bookData.intro) return true; + return false; + }, [bookData, chapter, chapterIsNumerical]); + return (
{chapterIsNumerical ? ( @@ -214,15 +221,23 @@ function Content() { ) : null} - + {chapterUnavailable ? ( + // This copy was taken from bible.com (e.g. https://www.bible.com/bible/4253/ACT.INTRO1.AFV) +

+ This chapter is not available in this version. Please choose a different chapter or + version. +

+ ) : ( + + )} {version?.copyright && (
Date: Mon, 16 Feb 2026 22:05:51 -0600 Subject: [PATCH 13/25] fix(ui): improve intro chapter detection and add missing test coverage - Replace parseInt with regex for chapterIsNumerical check - Make chapterUnavailable validate against chapters array and intro ID - Remove redundant || false in toolbar grid conditional - Add JSDoc on chapter render prop clarifying it's a display label - Add IntroChapter Storybook story with play function - Add JHN intro mock data and MSW handler using real API response --- .../src/components/bible-chapter-picker.tsx | 1 + .../src/components/bible-reader.stories.tsx | 45 +++++++++++++++++++ packages/ui/src/components/bible-reader.tsx | 13 +++--- packages/ui/src/test/mock-data/books.ts | 5 +++ packages/ui/src/test/mocks/handlers.ts | 10 +++++ 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/components/bible-chapter-picker.tsx b/packages/ui/src/components/bible-chapter-picker.tsx index bf9a21ae..4f73d67d 100644 --- a/packages/ui/src/components/bible-chapter-picker.tsx +++ b/packages/ui/src/components/bible-chapter-picker.tsx @@ -244,6 +244,7 @@ export type TriggerProps = Omit, 'ch | React.ReactNode | ((props: { book: string; + /** Display label for the current chapter (e.g. "1", "Intro"), not the raw chapter ID. */ chapter: string; currentBook: BibleBook | undefined; loading: boolean; diff --git a/packages/ui/src/components/bible-reader.stories.tsx b/packages/ui/src/components/bible-reader.stories.tsx index 4a581694..495a8782 100644 --- a/packages/ui/src/components/bible-reader.stories.tsx +++ b/packages/ui/src/components/bible-reader.stories.tsx @@ -638,3 +638,48 @@ export const WithoutAuth: Story = { await expect(settingsButton).toBeInTheDocument(); }, }; + +/** + * Tests that the Bible reader renders intro chapter content when navigated to a + * non-numerical chapter (e.g. JHN.INTRO). The h1 header should be hidden and + * the intro passage content should render. + */ +export const IntroChapter: Story = { + tags: ['integration'], + args: { + defaultVersionId: 111, + book: 'JHN', + chapter: 'INTRO', + }, + render: (args) => ( +
+ + + + +
+ ), + play: async ({ canvasElement }) => { + // Intro passage content should render + await waitFor( + async () => { + const verseContainer = canvasElement.querySelector('[data-slot="yv-bible-renderer"]'); + await expect(verseContainer).toBeInTheDocument(); + }, + { timeout: 5000 }, + ); + + // The h1 book/chapter header should not be present for non-numerical chapters + const h1 = canvasElement.querySelector('h1'); + await expect(h1).not.toBeInTheDocument(); + + // The unavailable message should not show since the version has this intro + const unavailableMessage = canvasElement.querySelector('p'); + const hasUnavailableText = unavailableMessage?.textContent?.includes('not available'); + await expect(hasUnavailableText).not.toBeTruthy(); + + // Toolbar trigger should show "Intro" label + const chapterButton = screen.getByRole('button', { name: /change bible book and chapter/i }); + await expect(chapterButton.textContent).toContain('Intro'); + }, +}; diff --git a/packages/ui/src/components/bible-reader.tsx b/packages/ui/src/components/bible-reader.tsx index e525b1e6..bd4f5b3b 100644 --- a/packages/ui/src/components/bible-reader.tsx +++ b/packages/ui/src/components/bible-reader.tsx @@ -194,14 +194,15 @@ function Content() { }, [books?.data, book]); const usfmReference = `${book}.${chapter}`; - const chapterIsNumerical: boolean = !!parseInt(chapter || '', 10) || false; + const chapterIsNumerical = /^\d+$/.test(chapter || ''); // Check if the current chapter is available in this version const chapterUnavailable = useMemo(() => { if (!bookData || !chapter) return false; - if (!chapterIsNumerical && !bookData.intro) return true; - return false; - }, [bookData, chapter, chapterIsNumerical]); + const inChapters = bookData.chapters?.some((ch) => ch.passage_id.split('.').pop() === chapter); + const isIntro = bookData.intro?.id === chapter; + return !inChapters && !isIntro; + }, [bookData, chapter]); return (
@@ -330,9 +331,7 @@ function Toolbar({ border = 'top' }: { border?: 'top' | 'bottom' }) {
{!!yvContext?.authEnabled && } diff --git a/packages/ui/src/test/mock-data/books.ts b/packages/ui/src/test/mock-data/books.ts index e92f50aa..9b2266d1 100644 --- a/packages/ui/src/test/mock-data/books.ts +++ b/packages/ui/src/test/mock-data/books.ts @@ -972,6 +972,11 @@ export const mockBooks = { full_title: 'The Gospel According to John', abbreviation: 'John', canon: 'new_testament', + intro: { + id: 'INTRO', + passage_id: 'JHN.INTRO', + title: 'Intro', + }, chapters: Array.from({ length: 21 }, (_, i) => { const chapterNumber = i + 1; return { diff --git a/packages/ui/src/test/mocks/handlers.ts b/packages/ui/src/test/mocks/handlers.ts index 885f0fb3..6bba6919 100644 --- a/packages/ui/src/test/mocks/handlers.ts +++ b/packages/ui/src/test/mocks/handlers.ts @@ -46,6 +46,16 @@ export const globalHandlers = [ return HttpResponse.json(mockPassages['JHN.1.51']); }), + // John intro passage + http.get('*/v1/bibles/111/passages/JHN.INTRO', () => { + return HttpResponse.json({ + id: 'JHN.INTRO', + content: + '
Intro
John closes his book by revealing his purpose in writing Jesus\' story: These are written that you may believe that Jesus is the Messiah, the Son of God, and that by believing you may have life in his name.
John begins his book by echoing words from the Bible\'s creation story—In the beginning—showing his readers that this is a story of a new creation. Just as the first creation was completed in seven days, John uses the number seven to structure his book. For the Jews the number seven represented completeness and wholeness, a finished work of God revealing his purpose for the world.
The story is told in two main parts. The first describes Jesus\' public ministry and has seven sections. Each section closes with a report on how people respond to Jesus, either in faith or unbelief. The second part is devoted to the Passover weekend, when Jesus gave his life for the world.
John records seven instances in which Jesus revealed his identity by using the phrase I am, the name by which God had revealed himself earlier. Similarly, John records seven miraculous signs that Jesus performed. John\'s narrative mentions twice that the resurrection of Jesus took place on thefirst day of the week. In this way he confirms that the power of a new creation has broken into our world.
', + reference: 'John Intro', + }); + }), + // Isaiah passage for verse of the day http.get('*/v1/bibles/111/passages/ISA.43.19', () => { return HttpResponse.json(mockPassages['ISA.43.19']); From 9dc55a9cc081992fe183fa0e79281405c597fa7d Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Mon, 16 Feb 2026 22:08:41 -0600 Subject: [PATCH 14/25] Fix: Wait for intro label in bible reader story Wait for the "Intro" label to appear in the bible reader toolbar in the story. This ensures that the test correctly accounts for asynchronous data loading before asserting the label's presence. --- packages/ui/src/components/bible-reader.stories.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/bible-reader.stories.tsx b/packages/ui/src/components/bible-reader.stories.tsx index 495a8782..d991b911 100644 --- a/packages/ui/src/components/bible-reader.stories.tsx +++ b/packages/ui/src/components/bible-reader.stories.tsx @@ -678,8 +678,15 @@ export const IntroChapter: Story = { const hasUnavailableText = unavailableMessage?.textContent?.includes('not available'); await expect(hasUnavailableText).not.toBeTruthy(); - // Toolbar trigger should show "Intro" label - const chapterButton = screen.getByRole('button', { name: /change bible book and chapter/i }); - await expect(chapterButton.textContent).toContain('Intro'); + // Toolbar trigger should show "Intro" label once books data loads + await waitFor( + async () => { + const chapterButton = screen.getByRole('button', { + name: /change bible book and chapter/i, + }); + await expect(chapterButton.textContent).toContain('Intro'); + }, + { timeout: 5000 }, + ); }, }; From 23ec1495602104d5d10191a38a7f3d3c398124e1 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Tue, 17 Feb 2026 12:17:32 -0600 Subject: [PATCH 15/25] Update bible reader intro chapter story Refine the bible reader story for intro chapters to accurately reflect real-world content. This includes using the TPT version for Joshua's intro, which contains structured sections, and updating assertions to verify specific formatting and content elements. --- .../src/components/bible-reader.stories.tsx | 50 ++++++++++++++----- packages/ui/src/test/mock-data/bibles.json | 49 ++++++++++++++++++ packages/ui/src/test/mock-data/books.ts | 5 ++ packages/ui/src/test/mocks/handlers.ts | 14 ++++++ 4 files changed, 106 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/components/bible-reader.stories.tsx b/packages/ui/src/components/bible-reader.stories.tsx index d991b911..c36ea8ab 100644 --- a/packages/ui/src/components/bible-reader.stories.tsx +++ b/packages/ui/src/components/bible-reader.stories.tsx @@ -640,15 +640,16 @@ export const WithoutAuth: Story = { }; /** - * Tests that the Bible reader renders intro chapter content when navigated to a - * non-numerical chapter (e.g. JHN.INTRO). The h1 header should be hidden and - * the intro passage content should render. + * Tests that a rich intro chapter (Joshua) renders correctly with real-world content + * including structured sections (At a Glance, Purpose, Major Themes), italic spans, + * bold-italic spans, and special formatting classes (imt1, imt2, is, ili, ip, etc.). + * The h1 header should be hidden and all intro content should render. */ -export const IntroChapter: Story = { +export const JoshuaIntroChapter: Story = { tags: ['integration'], args: { - defaultVersionId: 111, - book: 'JHN', + defaultVersionId: 1849, + book: 'JOS', chapter: 'INTRO', }, render: (args) => ( @@ -660,7 +661,7 @@ export const IntroChapter: Story = {
), play: async ({ canvasElement }) => { - // Intro passage content should render + // Wait for the Bible renderer to mount await waitFor( async () => { const verseContainer = canvasElement.querySelector('[data-slot="yv-bible-renderer"]'); @@ -669,16 +670,41 @@ export const IntroChapter: Story = { { timeout: 5000 }, ); - // The h1 book/chapter header should not be present for non-numerical chapters + const verseContainer = canvasElement.querySelector('[data-slot="yv-bible-renderer"]')!; + + // The h1 header should not be present for intro chapters const h1 = canvasElement.querySelector('h1'); await expect(h1).not.toBeInTheDocument(); - // The unavailable message should not show since the version has this intro - const unavailableMessage = canvasElement.querySelector('p'); - const hasUnavailableText = unavailableMessage?.textContent?.includes('not available'); + // The unavailable message should not appear + const hasUnavailableText = verseContainer.textContent?.includes('not available'); await expect(hasUnavailableText).not.toBeTruthy(); - // Toolbar trigger should show "Intro" label once books data loads + // Verify key intro content rendered — title and structured sections + await expect(verseContainer.textContent).toContain('Joshua'); + await expect(verseContainer.textContent).toContain('Introduction'); + await expect(verseContainer.textContent).toContain('At a Glance'); + await expect(verseContainer.textContent).toContain('Traditionally Joshua'); + await expect(verseContainer.textContent).toContain('Purpose'); + await expect(verseContainer.textContent).toContain('Major Themes'); + + // Verify structured list items rendered (ili class content) + await expect(verseContainer.textContent).toContain('Entering the Land'); + await expect(verseContainer.textContent).toContain('Conquering the Land'); + await expect(verseContainer.textContent).toContain('Dividing the Land'); + + // Verify paragraph content rendered (ip class) + await expect(verseContainer.textContent).toContain( + 'wilderness wanderers to courageous conquerors', + ); + + // Verify special formatting rendered (bdit = bold-italic, nd = divine name) + await expect(verseContainer.textContent).toContain('The Land of Promise'); + await expect(verseContainer.textContent).toContain('Covenant and Obedience'); + await expect(verseContainer.textContent).toContain('The Typology of Jesus'); + await expect(verseContainer.textContent).toContain('Yahweh'); + + // Toolbar trigger should show "Intro" label for Joshua await waitFor( async () => { const chapterButton = screen.getByRole('button', { diff --git a/packages/ui/src/test/mock-data/bibles.json b/packages/ui/src/test/mock-data/bibles.json index 9abbddd2..0fd6914c 100644 --- a/packages/ui/src/test/mock-data/bibles.json +++ b/packages/ui/src/test/mock-data/bibles.json @@ -97,6 +97,55 @@ "youversion_deep_link": "https://www.bible.com/versions/111", "organization_id": "05a9aa40-37b6-4e34-b9f1-a443fa4b1fff" }, + "1849": { + "id": 1849, + "abbreviation": "TPT", + "copyright": "The Passion Translation®\nCopyright © 2017, 2018, 2020 by Passion & Fire Ministries, Inc.", + "language_tag": "en", + "localized_abbreviation": "TPT", + "localized_title": "The Passion Translation", + "title": "The Passion Translation", + "books": [ + "GEN", + "JOS", + "JDG", + "RUT", + "PSA", + "PRO", + "SNG", + "ISA", + "LAM", + "MAT", + "MRK", + "LUK", + "JHN", + "ACT", + "ROM", + "1CO", + "2CO", + "GAL", + "EPH", + "PHP", + "COL", + "1TH", + "2TH", + "1TI", + "2TI", + "TIT", + "PHM", + "HEB", + "JAS", + "1PE", + "2PE", + "1JN", + "2JN", + "3JN", + "JUD", + "REV" + ], + "youversion_deep_link": "https://www.bible.com/versions/1849", + "organization_id": "some-org-id" + }, "1588": { "id": 1588, "abbreviation": "AMP", diff --git a/packages/ui/src/test/mock-data/books.ts b/packages/ui/src/test/mock-data/books.ts index 9b2266d1..83be2fe0 100644 --- a/packages/ui/src/test/mock-data/books.ts +++ b/packages/ui/src/test/mock-data/books.ts @@ -121,6 +121,11 @@ export const mockBooks = { full_title: 'The Book of Joshua', abbreviation: 'Josh', canon: 'old_testament', + intro: { + id: 'INTRO', + passage_id: 'JOS.INTRO', + title: 'Intro', + }, chapters: Array.from({ length: 24 }, (_, i) => { const chapterNumber = i + 1; return { diff --git a/packages/ui/src/test/mocks/handlers.ts b/packages/ui/src/test/mocks/handlers.ts index 6bba6919..65ed85b1 100644 --- a/packages/ui/src/test/mocks/handlers.ts +++ b/packages/ui/src/test/mocks/handlers.ts @@ -56,6 +56,16 @@ export const globalHandlers = [ }); }), + // Joshua intro passage (real TPT data for rich intro content testing) + http.get('*/v1/bibles/1849/passages/JOS.INTRO', () => { + return HttpResponse.json({ + id: 'JOS.INTRO', + content: + '
Joshua
Introduction
At a Glance
Author: Traditionally Joshua
Audience: Originally Israel, but this theological history speaks to everyone
Date: 1451–1426 BC
Type of Literature: Theological history
Major Themes: The land of promise; covenant and obedience; the typology of Jesus; conquest and God’s character
Outline:
I. Entering the Land — 1:1–5:12
II. Conquering the Land — 5:13–12:24
III. Dividing the Land — 13:1–22:34
IV. Farewell and Burial in the Land — 23:1–24:33
About the Book of Joshua
A new beginning stretches out before us! When we read the book of Joshua, we learn the ways of God: how he moves us forward, how we triumph over our enemies, and how we do the impossible. Joshua, a former slave in Egypt, became the leader of God’s people after the death of Moses. A generational transfer took place as a younger generation rose with fresh vision, a bold faith, and renewed passion to possess all that God had given them. All this and more is contained in the sacred book you have in your hand, the book of Joshua.
Joshua is the hinge of Israel’s history. The wilderness wandering was now over as the promised land was before them. The manna ceased, the Jordan was behind them, a new leader rose, and a new beginning opened up for the people of God. Walled cities and fierce enemies are no match for the living God. But it would still require a faith-filled people to move in and possess what God had given to them.
As the sixth book of the Bible, Joshua begins the section of Israel’s history. Bundled together, Joshua through Esther make up the biblical, inspired history of the Jewish people. Our Jewish friends call this section of the Bible (Joshua, Judges, Samuel, Kings) the “Former Prophets.” Since this section of the Bible can be considered prophecy, Joshua prophesies to the church today (see 1 Cor. 10:11), giving us instruction for how to live before God. The truths of Joshua are as much for today as the teachings of Paul or Peter. These Former Prophets lay out in front of us the secrets of victory.
The title Former Prophets demonstrates the prophetic nature of God’s dealing with his people and also the nations. It is the story of God’s redemption by the power of his mighty hand and empowering Spirit, giving revelations, performing signs and wonders, and testifying to Yahweh’s faithfulness in all things.
The book of Joshua shows us that we can go into the promised land of what God wants us to be. We can become the delight of God. The book of Joshua is the Ephesians of the Old Testament. Joshua was blessed with every earthly blessing in the land of Canaan. We are blessed with every heavenly blessing in Christ (see Eph. 1:3). Joshua lays out a road map to victory for us so that we can advance into our destiny; and our true destiny is for the better Joshua, Jesus, to lead us into his kingdom (see Eph. 1:13–14).
Purpose
The book of Joshua contains an important and fascinating part of Israel’s history. It describes the transition of God’s chosen people from wilderness wanderers to courageous conquerors. Joshua is written as more than history. It is a “sermon” meant to activate believers today. We have an inheritance that we must fight for in faith. We have every blessing heaven contains (see Eph. 1:3), but we must claim and implement those blessings.
The church today needs the courage to conquer. Many modern believers act more like prisoners of war instead of passionate conquerors. Followers of Jesus must see themselves as soldiers in a disciplined army prepared to fight spiritual battles. The book of Joshua is a book of conquest, emboldening the church to move from passivity to passion. Like Joshua, our battles are spiritual battles, for we fight not against flesh and blood but against forces of darkness (see footnote on Josh. 24:11).
Author and Audience
Joshua was one of the twelve spies who first went into the land of Canaan. Along with Caleb, Joshua was the only one to give a good report. Indeed, Israel recognized Joshua as their prophet and something similar to a “king,” See Rashi, Yoma 73b; Rambam, Hil. Melachim 1:3, 3:8; Hil. Sanhedrin 18:6.although they had not yet come into possession of their kingdom. According to the Jewish historian Josephus, Joshua succeeded Moses when he was eighty-five. He was a military commander who conquered seven nations (kingdoms) in seven years. He died at the age of one hundred and ten and was buried in Timnath Serah.
Joshua’s original name was Hoshea, but Moses changed it to Yehoshua (see Num. 13:8, 16), which can be translated as “Yahweh is salvation” or “Yahweh save.” In fact, the name Yehoshua is nearly the Hebrew equivalent of the name Jesus (Yeshua). The Greek word for Jesus is Iesous and means the same as the name Joshua. One could almost say there is a book in the Bible named Jesus. That’s one book I would want to read, wouldn’t you?
Although certain portions were added after Joshua’s death, translators believe the author was Joshua himself for several reasons: Certain episodes of the book bear the mark of an eyewitness, such as where the author states “we” passed through the waters on dry ground in chapter three; Joshua’s description of Canaanite wickedness parallels the well-known Ras Shamra tablets, written in Joshua’s time; Joshua’s list of boundaries for the twelve tribes (see Josh. 13–19) accurately reflect the known situation of Canaan prior to the Jewish monarchy; descriptions of certain cities, such as Jerusalem still being a Jebusite city (see 15:63) and Gezer still being a Canaanite city (see 16:10), imply the author was living in the time of Joshua; and the author seems to write about things that happened in his lifetime, not about anything happening previously.
For the people of Israel, the book of Joshua was an important “hinge book” between the Torah and Prophecy. This conquering military hero who ushered in the salvation of Yahweh wrote this book to a people wrestling with establishing the nation in the land Yahweh provided, understanding God’s divine revelation-word of promise, and waiting for the fullness of his provision and promises to be realized. Similarly, we, too, wait for Yahweh’s promises to be fully realized, awaiting the day of his promised-land rest!
Major Themes
The Land of Promise. The book of Joshua is the book of the land. It is this long-ago-promised gift of a specific land by Yahweh that is the central animating theme. The verses offered just after Israel takes possession of the land could be a fair summary of the entire book: “So Yahweh gave Israel all the land he had promised their ancestors. They took possession of the land and settled there. Yahweh kept his promise and gave them peace in the land. . . . Not one of their enemies could stand against them. Yahweh didn’t break a single promise that he made to the people of Israel” (21:43–45).
This central theological theme in Joshua is intimately connected with Israel’s national and ethnic identities and to Yahweh’s fulfillment of his promises to the patriarchs Abraham, Isaac, and Jacob to gift their generations such a land of promise. We find six aspects of this gift throughout the book of Joshua: (1) God promised the land-gift to the forefathers; (2) God gave the gift to Israel; (3) Joshua divided it as an inheritance for the people; (4) this gift was closely connected to the land on the east side of the Jordan River; (5) it would not be difficult to take from those still living there because God had caused its inhabitants to tremble with fear; and (6) the land-gift was filled with other gods who tempted Israel.
The promised land wasn’t just land; it was a gift. A gift a good Father (Yahweh) gave to his beloved children (Israel). God promised this gift from the day he called out the man (Abraham) who would birth the nation, and he ultimately fulfilled that promise in this wonderful book. But God also extends this gift to committed hearts and obedient hands today. The book illustrates the tragedy of neglecting this gift, offering a foreshadowing of Israel’s wandering ways that would ultimately lead to exile from the land and separation from the gift of Yahweh.
Covenant and Obedience. The promised land given to the children of Israel as a promised gift is at the heart of a covenant Yahweh made with them generations ago. Genesis 17:7–9 outlines the terms of this covenant:
“I will be your children’s God, just as I am your God.
I will give to you and your seed
the land to which you have migrated.
The entire land of Canaan will be yours and your descendants’
as an everlasting possession.
And I will be their God forever!”
God explained to Abraham, “Your part of the covenant is to obey its terms, you and your descendants throughout the ages.”
Throughout the book of Joshua, the ark of the covenant went before the people as a constant reminder of this relationship, symbolizing Yahweh’s mercy, power, and holiness. At every juncture of Israel’s journey in this book—from the floodwaters of the Jordan to the gates of Jericho and to the covenant’s renewal at Mount Ebal—they were to march in a new manner, with their eyes on the ark and their hearts set on Yahweh.
Obedience was at the core of this covenantal relationship, realized and renewed in Joshua. The people who still lived in the land, with their pagan gods and pagan ways, constantly challenged the obedience of God’s people. Israel was to worship and obey Yahweh alone, practices which Joshua outlined in several ways: they were to meditate on the Torah day and night, learn the commands of Yahweh, practice circumcision, keep the Passover, worship at the place of Yahweh’s choosing, and obey the written laws of Yahweh.
Perhaps the climax of the covenant in the book comes at the end, just before Joshua’s death was reported. He led the people in renewing their relationship with Yahweh at Shechem and put into the starkest terms possible Israel’s need to fully embrace their covenantal relationship with Yahweh and obey him completely: “Make your decision today which gods you will worship—the gods which your ancestors worshiped in Mesopotamia or the gods which the Amorites worship in the land where you are now living—but I and my family, we will give our lives to worship and serve Yahweh” (Josh. 24:15).
The ark not only beautifully illustrates God’s covenant with Israel, it is also a wonderful picture of Jesus Christ, who “is the catalyst of a better covenant which contains far more wonderful promises” (Heb. 8:6). The power of Christ within us enables us to pass over into our full inheritance. Jesus, our forerunner, leads us in, and we are to join with the same obedient voices as those of Israel, declaring “We, too, will worship and serve Yahweh, for he alone is our God” (Josh. 24:18).
The Typology of Jesus. To read the book of Joshua and not see Jesus would be unfortunate. Joshua is a clear type of Jesus, for he took the Israelites into a realm that the law (Moses) was unable to experience. The church father Eusebius offers this connection between the name Joshua and the name Jesus:
Moses was inspired by the divine Spirit to foresee clearly the name of Jesus; and he deemed this name of special honor. Till it was made known to Moses, it had never been on man’s lips before. He bestowed the name of Jesus on him first of all, and only on him, who he knew would succeed, in type and symbol, after his death. His successor had not previously been called Jesus, but his parents had called him Hoshea.See Hist. Eccles. I 3.
For the believer, the typology of Joshua is apparent. First, the name Joshua is wonderfully similar to Jesus (Yeshua). Secondly, the promised land for the follower of Christ becomes a picture of the untold blessings that are ours in Christ (see Eph. 1:3). Canaan was a fertile land. It symbolizes the abundant life of the victorious believer. Canaan had to be conquered, and so our promises and blessings must be claimed by faith. The law of Moses did not attain Canaan but only the grace of God did. Heathen hordes inhabited Canaan, and God’s people had to purge the land of the powers of darkness and idolatry, just as we must purge our hearts (see Eph. 6:12; Heb. 9:23).
The miracle-crossing of the Jordan River typifies crossing over into a life of abundance and union with Jesus Christ. Our Savior was the One who descended into judgment for our sins. The Jordan was miraculously rolled back all the way to a town called Adam; Jesus rolled back the waters of judgment all the way back to the sin of Adam. The rending of the Jordan corresponds to the rending of the veil in the Holy of Holies when Jesus was crucified (see 2 Cor. 3:1–18; Heb. 10:20). We are now those who cross over into union and intimacy with God.
Additionally, the pushing back of the waters (as the dividing of the Red Sea previously) displayed the power of Israel’s God over all other gods, including those of the seas and rivers (that were believed to be untamable in the ancient world). Yahweh showed his power to destroy every other power and authority in carrying out his victory of life for a people he had made his own. This is what caused fear to enter the hearts of the inhabitants of the land who had previously heard of the strong and mighty outstretched arm of Yahweh that split the Red Sea in two and now divided the river. No god can stand before this God! This is even more significant when gods are associated with particular territories or spheres of influence. This God travels and has power over all territories and powers.
Aside from the clear name association, we can see Jesus in the book of Joshua in a number of ways:
The Heavenly Joshua (Heb. 4:8)
The Pioneer of Our Salvation (Heb. 2:10)
The Crimson Rope (Josh. 2:18)
The Ark of the Covenant of the Lord of All the Earth (Josh. 3:11)
The Memorial of Twelve Stones (Josh. 4:19–24)
The Passover Lamb (Josh. 5:10–12; 1 Cor. 5:7–8)
The Altar (Josh. 8:30–35; Heb. 13:10)
The Commander of Yahweh’s Armies (Josh. 5:13–15; Eph. 6:12–18)
The Heavenly Refuge (Josh. 20:1–9; Heb. 6:19–20)
Conquest and God’s Character. The book of Joshua is a road map, a manual for conquest. It contains the secrets of conquest with its amazing, jaw-dropping victories and sadly disappointing defeats. And yet, many point to the book of Joshua as an example of God behaving badly because of this theme of conquest, which seems to touch a raw nerve. Some view these battles as morally dubious, filled with nationalistic violence and terror. However, note several things about the context of the time in which this history of Israel’s conquests unfolds.
First, war was a normal fact of life; it still is. The Bible reminds us there is a time for war (see Eccl. 3:8) and a justifiable war (see Gen. 14), which the book of Joshua illustrates. However, the modern military cannot claim the right to destructive warfare based on Israelite wars, for they served a divine purpose in unfolding Yahweh’s plans. Also the sort of holy war we find in Joshua was not invented by Israel nor limited to the nation. Further, the Canaanites were by no means innocent, for their sexual perversion, child sacrifice, and pagan idolatry could not go unpunished by a righteous God.
Finally, the theme of conquest reveals the character of God. In the book of Joshua, we see God’s mercy unfold, such as in his sparing of Rahab and her family. Also, consider the fact that Yahweh mercifully waited hundreds of years before commencing the conquest, giving the inhabitants of the land ample time to repent (see 2 Pet. 3:9). God waited until their cup of iniquity was filled. No doubt this aspect of Joshua can be troubling. However, in this way the conquest shows us God’s anger, justice, and wrath—important elements of his character.
God’s character is further unfolded through the many conflicts that arise. We discover, along with Israel, that God not only initiates covenant relationships, but he also punishes disobedient people. He also gives his people victory over their enemies on their way toward possessing the land and nationhood. This God is a God who fights for his people, standing with them in the midst of their oppression and suffering, raising them up to the plateau of victory, and assuring them they have no reason to “yield to fear nor be discouraged, for I am Yahweh your God, and I will be with you wherever you go!” (Josh. 1:9).
Joshua
A New Beginning
', + reference: 'Joshua ', + }); + }), + // Isaiah passage for verse of the day http.get('*/v1/bibles/111/passages/ISA.43.19', () => { return HttpResponse.json(mockPassages['ISA.43.19']); @@ -115,6 +125,10 @@ export const globalHandlers = [ return HttpResponse.json(mockBooks); }), + http.get('*/v1/bibles/1849/books', () => { + return HttpResponse.json(mockBooks); + }), + // Chapters for all books using mock data http.get('*/v1/bibles/111/books/GEN/chapters', () => { return HttpResponse.json(mockChapters); From 63e5a274f86ff1a37c707468409a19798be48a89 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Tue, 17 Feb 2026 12:30:02 -0600 Subject: [PATCH 16/25] fix(ui): add top margin to section headings and intro chapter story - Add top margin to .is/.s/.heading section headings for spacing - Add IntroChapter story for JHN.INTRO rendering - Fix JoshuaIntroChapter story to wait for content before assertions --- .../src/components/bible-reader.stories.tsx | 56 ++++++++++++++++++- packages/ui/src/styles/bible-reader.css | 4 +- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/bible-reader.stories.tsx b/packages/ui/src/components/bible-reader.stories.tsx index c36ea8ab..76d2188a 100644 --- a/packages/ui/src/components/bible-reader.stories.tsx +++ b/packages/ui/src/components/bible-reader.stories.tsx @@ -639,6 +639,58 @@ export const WithoutAuth: Story = { }, }; +/** + * Tests that the Bible reader renders intro chapter content when navigated to a + * non-numerical chapter (e.g. JHN.INTRO). The h1 header should be hidden and + * the intro passage content should render. + */ +export const IntroChapter: Story = { + tags: ['integration'], + args: { + defaultVersionId: 111, + book: 'JHN', + chapter: 'INTRO', + }, + render: (args) => ( +
+ + + + +
+ ), + play: async ({ canvasElement }) => { + // Intro passage content should render + await waitFor( + async () => { + const verseContainer = canvasElement.querySelector('[data-slot="yv-bible-renderer"]'); + await expect(verseContainer).toBeInTheDocument(); + }, + { timeout: 5000 }, + ); + + // The h1 book/chapter header should not be present for non-numerical chapters + const h1 = canvasElement.querySelector('h1'); + await expect(h1).not.toBeInTheDocument(); + + // The unavailable message should not show since the version has this intro + const unavailableMessage = canvasElement.querySelector('p'); + const hasUnavailableText = unavailableMessage?.textContent?.includes('not available'); + await expect(hasUnavailableText).not.toBeTruthy(); + + // Toolbar trigger should show "Intro" label once books data loads + await waitFor( + async () => { + const chapterButton = screen.getByRole('button', { + name: /change bible book and chapter/i, + }); + await expect(chapterButton.textContent).toContain('Intro'); + }, + { timeout: 5000 }, + ); + }, +}; + /** * Tests that a rich intro chapter (Joshua) renders correctly with real-world content * including structured sections (At a Glance, Purpose, Major Themes), italic spans, @@ -661,11 +713,12 @@ export const JoshuaIntroChapter: Story = {
), play: async ({ canvasElement }) => { - // Wait for the Bible renderer to mount + // Wait for the intro content to fully load (not just the renderer element) await waitFor( async () => { const verseContainer = canvasElement.querySelector('[data-slot="yv-bible-renderer"]'); await expect(verseContainer).toBeInTheDocument(); + await expect(verseContainer?.textContent).toContain('Joshua'); }, { timeout: 5000 }, ); @@ -681,7 +734,6 @@ export const JoshuaIntroChapter: Story = { await expect(hasUnavailableText).not.toBeTruthy(); // Verify key intro content rendered — title and structured sections - await expect(verseContainer.textContent).toContain('Joshua'); await expect(verseContainer.textContent).toContain('Introduction'); await expect(verseContainer.textContent).toContain('At a Glance'); await expect(verseContainer.textContent).toContain('Traditionally Joshua'); diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index b0f40f52..da1a1422 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -324,14 +324,14 @@ font-size: 1.1em; font-weight: bold; line-height: 1.8em; - margin: 0 0 0.5em; + margin: 0.5em 0 0.5em 0; display: block; } & .heading { font-weight: bold; line-height: 1.8em; - margin: 0 0 0.5em 0; + margin: 0.5em 0 0.5em 0; display: block; } From 1cd3e71845e99fd330bf820a7ff1c6d197feeda7 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Tue, 17 Feb 2026 12:39:38 -0600 Subject: [PATCH 17/25] Refactor bible picker to use chapter label This commit refactors the `BibleChapterPicker` component to correctly display the chapter label in the toolbar trigger. Previously, it was using the raw chapter ID, which could lead to incorrect display for introductory chapters. The `TriggerProps` type has been updated to include `chapterLabel` and the component logic now correctly derives and uses this label for rendering. Unit tests have also been updated to reflect this change and ensure accurate display. --- packages/ui/src/components/bible-chapter-picker.tsx | 12 +++++++----- packages/ui/src/components/bible-reader.stories.tsx | 6 ++++-- packages/ui/src/components/bible-reader.tsx | 6 ++++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/ui/src/components/bible-chapter-picker.tsx b/packages/ui/src/components/bible-chapter-picker.tsx index 4f73d67d..cd0895ad 100644 --- a/packages/ui/src/components/bible-chapter-picker.tsx +++ b/packages/ui/src/components/bible-chapter-picker.tsx @@ -244,8 +244,10 @@ export type TriggerProps = Omit, 'ch | React.ReactNode | ((props: { book: string; - /** Display label for the current chapter (e.g. "1", "Intro"), not the raw chapter ID. */ + /** Raw chapter ID as passed to the Root component (e.g. "GEN.1", "GEN.INTRO"). */ chapter: string; + /** Display label for the current chapter (e.g. "1", "Intro"). */ + chapterLabel: string; currentBook: BibleBook | undefined; loading: boolean; }) => React.ReactNode); @@ -259,18 +261,18 @@ function Trigger({ asChild = true, children, ...props }: TriggerProps) { const theme = background || providerTheme; const currentBook = books?.data?.find((bookItem) => bookItem.id === book); - let currentChapter: string = + let chapterLabel: string = currentBook?.chapters?.find((ch) => ch.id === chapter)?.title || chapter; if (!!currentBook?.intro && chapter === currentBook.intro.id) { - currentChapter = currentBook.intro.title; + chapterLabel = currentBook.intro.title; } const buttonText = loading ? 'Loading...' - : `${currentBook?.title || 'Select a chapter'}${currentChapter ? ` ${currentChapter}` : ''}`; + : `${currentBook?.title || 'Select a chapter'}${chapterLabel ? ` ${chapterLabel}` : ''}`; const content = typeof children === 'function' - ? children({ book, chapter: currentChapter, currentBook, loading }) + ? children({ book, chapter, chapterLabel, currentBook, loading }) : children || ; return ( diff --git a/packages/ui/src/components/bible-reader.stories.tsx b/packages/ui/src/components/bible-reader.stories.tsx index 76d2188a..cdfba00a 100644 --- a/packages/ui/src/components/bible-reader.stories.tsx +++ b/packages/ui/src/components/bible-reader.stories.tsx @@ -678,13 +678,14 @@ export const IntroChapter: Story = { const hasUnavailableText = unavailableMessage?.textContent?.includes('not available'); await expect(hasUnavailableText).not.toBeTruthy(); - // Toolbar trigger should show "Intro" label once books data loads + // Toolbar trigger should show "Intro" (title-case label), NOT "INTRO" (raw chapter ID) await waitFor( async () => { const chapterButton = screen.getByRole('button', { name: /change bible book and chapter/i, }); await expect(chapterButton.textContent).toContain('Intro'); + await expect(chapterButton.textContent).not.toContain('INTRO'); }, { timeout: 5000 }, ); @@ -756,13 +757,14 @@ export const JoshuaIntroChapter: Story = { await expect(verseContainer.textContent).toContain('The Typology of Jesus'); await expect(verseContainer.textContent).toContain('Yahweh'); - // Toolbar trigger should show "Intro" label for Joshua + // Toolbar trigger should show "Intro" (title-case label), NOT "INTRO" (raw chapter ID) await waitFor( async () => { const chapterButton = screen.getByRole('button', { name: /change bible book and chapter/i, }); await expect(chapterButton.textContent).toContain('Intro'); + await expect(chapterButton.textContent).not.toContain('INTRO'); }, { timeout: 5000 }, ); diff --git a/packages/ui/src/components/bible-reader.tsx b/packages/ui/src/components/bible-reader.tsx index bd4f5b3b..51a538ae 100644 --- a/packages/ui/src/components/bible-reader.tsx +++ b/packages/ui/src/components/bible-reader.tsx @@ -346,13 +346,15 @@ function Toolbar({ border = 'top' }: { border?: 'top' | 'bottom' }) { background={background} > - {({ chapter, currentBook, loading }) => ( + {({ chapterLabel, currentBook, loading }) => ( )} From e19bcab8a074bb77e4d3fe089aa57b78dbd540c9 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Tue, 17 Feb 2026 12:48:49 -0600 Subject: [PATCH 18/25] rm unnecessary story test --- .../src/components/bible-reader.stories.tsx | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/packages/ui/src/components/bible-reader.stories.tsx b/packages/ui/src/components/bible-reader.stories.tsx index cdfba00a..36bde8f9 100644 --- a/packages/ui/src/components/bible-reader.stories.tsx +++ b/packages/ui/src/components/bible-reader.stories.tsx @@ -639,59 +639,6 @@ export const WithoutAuth: Story = { }, }; -/** - * Tests that the Bible reader renders intro chapter content when navigated to a - * non-numerical chapter (e.g. JHN.INTRO). The h1 header should be hidden and - * the intro passage content should render. - */ -export const IntroChapter: Story = { - tags: ['integration'], - args: { - defaultVersionId: 111, - book: 'JHN', - chapter: 'INTRO', - }, - render: (args) => ( -
- - - - -
- ), - play: async ({ canvasElement }) => { - // Intro passage content should render - await waitFor( - async () => { - const verseContainer = canvasElement.querySelector('[data-slot="yv-bible-renderer"]'); - await expect(verseContainer).toBeInTheDocument(); - }, - { timeout: 5000 }, - ); - - // The h1 book/chapter header should not be present for non-numerical chapters - const h1 = canvasElement.querySelector('h1'); - await expect(h1).not.toBeInTheDocument(); - - // The unavailable message should not show since the version has this intro - const unavailableMessage = canvasElement.querySelector('p'); - const hasUnavailableText = unavailableMessage?.textContent?.includes('not available'); - await expect(hasUnavailableText).not.toBeTruthy(); - - // Toolbar trigger should show "Intro" (title-case label), NOT "INTRO" (raw chapter ID) - await waitFor( - async () => { - const chapterButton = screen.getByRole('button', { - name: /change bible book and chapter/i, - }); - await expect(chapterButton.textContent).toContain('Intro'); - await expect(chapterButton.textContent).not.toContain('INTRO'); - }, - { timeout: 5000 }, - ); - }, -}; - /** * Tests that a rich intro chapter (Joshua) renders correctly with real-world content * including structured sections (At a Glance, Purpose, Major Themes), italic spans, From d28b6683fb35841a65e84cd9874b6098e3296ce8 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Tue, 17 Feb 2026 13:25:00 -0600 Subject: [PATCH 19/25] Refactor bible reader CSS for clarity and consistency Organize the CSS rules for the bible reader component into logical sections. This improves readability and maintainability. New sections include major titles, section headings, introduction outlines, paragraphs, character styles, and notes. Additionally, styles for specific USFM tags have been refined for better accuracy and consistency. --- packages/ui/src/styles/bible-reader.css | 1004 ++++++++++++++--------- 1 file changed, 621 insertions(+), 383 deletions(-) diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index da1a1422..10b3c189 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -74,12 +74,12 @@ & .fv, & .yv-vlbl { display: inline; - font-size: 0.5em; + font-size: 0.65em; color: var(--yv-muted-foreground); line-height: 1em; vertical-align: baseline; position: relative; - top: -0.6em; + top: -0.3em; white-space: nowrap; } @@ -146,211 +146,488 @@ -webkit-box-decoration-break: clone; } - /* Major titles */ - & .imt, /* \imt - Introduction major title */ - & .imt1, /* \imt1 - Introduction major title (level 1) */ - & .mt, /* \mt - Main title */ - & .mt1, /* \mt1 - Main title (level 1) */ - & .ms, /* \ms - Major section heading */ - & .ms1 { - /* \ms1 - Major section heading (level 1) */ + /* ============================================================ + TITLES — Major Book Titles + ============================================================ */ + + /* \mt, \mt1 - Main title (level 1) — FontSize 20, Bold, Center */ + & .mt, + & .mt1 { + font-size: 1.6em; + font-weight: bold; + line-height: 1.4em; + text-align: center; + margin: 1em 0 0.25em; + display: block; + } + + /* \mt2 - Main title (level 2) — FontSize 16, Italic, Center */ + & .mt2 { + font-size: 1.3em; + font-style: italic; + line-height: 1.4em; + text-align: center; + margin: 0.5em 0 0.15em; + display: block; + } + + /* \mt3 - Main title (level 3) — FontSize 16, Bold, Center */ + & .mt3 { font-size: 1.3em; font-weight: bold; + line-height: 1.4em; + text-align: center; + margin: 0.15em 0; + display: block; + } + + /* \mt4 - Main title (level 4) — FontSize 12, Center */ + & .mt4 { line-height: 1.8em; - margin: 1em 0 0; + text-align: center; + margin: 0.15em 0; display: block; } - /* \iot - Introduction outline title (e.g. "Outline of Contents") */ - & .iot { + /* ============================================================ + TITLES — Introduction Major Titles + ============================================================ */ + + /* \imt, \imt1 - Introduction major title (level 1) — FontSize 14, Bold, Center */ + & .imt, + & .imt1 { + font-size: 1.17em; + font-weight: bold; + line-height: 1.8em; + text-align: center; + margin: 1em 0 0.25em; + display: block; + } + + /* \imt2 - Introduction major title (level 2) — FontSize 13, Italic, Center */ + & .imt2 { + font-size: 1.08em; + font-style: italic; + line-height: 1.8em; + text-align: center; + margin: 0.5em 0 0.25em; + display: block; + } + + /* \imt3 - Introduction major title (level 3) — FontSize 12, Bold, Center */ + & .imt3 { + font-weight: bold; + line-height: 1.8em; + text-align: center; + margin: 0.15em 0; + display: block; + } + + /* \imt4 - Introduction major title (level 4) — FontSize 12, Italic, Center */ + & .imt4 { + font-style: italic; + line-height: 1.8em; + text-align: center; + margin: 0.15em 0; + display: block; + } + + /* ============================================================ + TITLES — Introduction Major Title Ending + ============================================================ */ + + /* \imte, \imte1 - Introduction major title ending (level 1) — FontSize 20, Bold, Center */ + & .imte, + & .imte1 { + font-size: 1.6em; + font-weight: bold; + line-height: 1.4em; + text-align: center; + margin: 1em 0 0.25em; + display: block; + } + + /* \imte2 - Introduction major title ending (level 2) — FontSize 16, Italic, Center */ + & .imte2 { font-size: 1.3em; - font-weight: 700; + font-style: italic; + line-height: 1.4em; + text-align: center; + margin: 0.5em 0 0.15em; + display: block; + } + + /* ============================================================ + HEADINGS — Major Section Headings + ============================================================ */ + + /* \ms, \ms1 - Major section heading (level 1) — FontSize 14, Bold, Center */ + & .ms, + & .ms1 { + font-size: 1.17em; + font-weight: bold; line-height: 1.8em; + text-align: center; + margin: 1em 0 0.25em; + display: block; + } + + /* \ms2 - Major section heading (level 2) — FontSize 14, Bold, Center */ + & .ms2 { + font-size: 1.17em; + font-weight: bold; + line-height: 1.8em; + text-align: center; margin: 0.5em 0; display: block; } - /* \io - Introduction outline entries (displayed as bulleted list items) */ - & .io, /* \io - Introduction outline entry */ + /* \ms3 - Major section heading (level 3) — FontSize 14, Italic, Center */ + & .ms3 { + font-size: 1.17em; + font-style: italic; + line-height: 1.8em; + text-align: center; + margin: 0.5em 0; + display: block; + } + + /* \mr - Major section reference range — Italic, Center */ + & .mr { + display: block; + font-style: italic; + line-height: 1.8em; + text-align: center; + margin: 0 0 0.5em; + } + + /* ============================================================ + HEADINGS — Section Headings + ============================================================ */ + + /* \s, \s1 - Section heading (level 1) — Bold, Center */ + & .s, + & .s1 { + font-weight: bold; + line-height: 1.8em; + text-align: center; + margin: 0.5em 0 0.5em 0; + display: block; + } + + /* \s2 - Section heading (level 2) — Italic, Center */ + & .s2 { + display: block; + font-style: italic; + line-height: 1.8em; + text-align: center; + margin: 0.5em 0; + } + + /* \s3 - Section heading (level 3) — Italic, Left */ + & .s3 { + display: block; + font-style: italic; + line-height: 1.8em; + margin: 0.5em 0 0.25em; + } + + /* \s4 - Section heading (level 4) — Italic, Left */ + & .s4 { + display: block; + font-style: italic; + line-height: 1.8em; + margin: 0.5em 0 0.25em; + } + + /* \sr - Section reference range — Bold, Center */ + & .sr { + display: block; + font-weight: bold; + line-height: 1.8em; + text-align: center; + margin: 0 0 0.5em; + } + + /* \r - Parallel passage reference — Italic, Center */ + & .r { + display: block; + font-style: italic; + line-height: 1.8em; + text-align: center; + margin: 0 0 0.5em; + } + + /* \sp - Speaker identification — Italic, Left */ + & .sp { + display: block; + font-style: italic; + line-height: 1.8em; + margin: 0 0 0.5em; + } + + /* \d - Descriptive title / Hebrew subtitle — Italic, Center */ + & .d { + display: block; + font-style: italic; + line-height: 1.8em; + text-align: center; + margin: 0.5em 0; + } + + /* \qa - Acrostic heading — Italic */ + & .qa { + display: block; + font-style: italic; + line-height: 1.8em; + margin: 0 0 0.5em; + } + + & .heading { + font-weight: bold; + line-height: 1.8em; + margin: 0.5em 0 0.5em 0; + display: block; + } + + /* ============================================================ + HEADINGS — Introduction Section Headings + ============================================================ */ + + /* \is, \is1 - Introduction section heading (level 1) — FontSize 14, Bold, Center */ + & .is, + & .is1 { + font-size: 1.17em; + font-weight: bold; + line-height: 1.8em; + text-align: center; + margin: 0.5em 0 0.5em 0; + display: block; + } + + /* \is2 - Introduction section heading (level 2) — FontSize 12, Bold, Center */ + & .is2 { + font-weight: bold; + line-height: 1.8em; + text-align: center; + margin: 0.5em 0 0.5em 0; + display: block; + } + + /* \yv-h - YouVersion heading (custom, styled like \is) */ + & .yv-h { + font-size: 1.17em; + font-weight: bold; + line-height: 1.8em; + text-align: start; + margin: 0.5em 0 0.5em 0; + display: block; + } + + & .yv-h.r { + font-size: 1em; + font-weight: normal; + line-height: 1.8em; + font-style: italic; + text-align: start; + margin: 0 0 0.5em 0; + display: block; + } + + /* ============================================================ + INTRODUCTION — Outline + ============================================================ */ + + /* \iot - Introduction outline title — Bold, Center */ + & .iot { + font-weight: bold; + line-height: 1.8em; + text-align: center; + margin: 0.5em 0; + display: block; + } + + /* \io, \io1 - Introduction outline entry (level 1) — LeftMargin .5in */ + & .io, & .io1 { - /* \io1 - Introduction outline entry (level 1) */ - list-style-type: disc; - margin-inline-start: 1em; - display: list-item; + display: block; + margin-inline-start: 2em; + } + + /* \io2 - Introduction outline entry (level 2) — LeftMargin .75in */ + & .io2 { + display: block; + margin-inline-start: 3em; } - & .io2, /* \io2 - Introduction outline entry (level 2) */ + /* \io3 - Introduction outline entry (level 3) — LeftMargin 1in */ & .io3 { - /* \io3 - Introduction outline entry (level 3) */ - list-style-type: disc; - margin-inline-start: 2em; - display: list-item; + display: block; + margin-inline-start: 4em; } - /* \io4 - Introduction outline entry (level 4) */ + /* \io4 - Introduction outline entry (level 4) — LeftMargin 1.25in */ & .io4 { - list-style-type: disc; - margin-inline-start: 3em; - display: list-item; + display: block; + margin-inline-start: 5em; } - /* \ior - Introduction outline reference range (e.g. "(1.1-13)") */ + /* \ior - Introduction outline reference range — no special style per spec */ & .ior { - font-style: italic; + display: inline; } - /* \ili - Introduction list items */ - & .ili, /* \ili - Introduction list item (default) */ + /* ============================================================ + INTRODUCTION — List Items + ============================================================ */ + + /* \ili, \ili1 - Introduction list item (level 1) — hanging indent */ + & .ili, & .ili1 { - /* \ili1 - Introduction list item (level 1) */ - list-style-type: none; + display: block; margin-inline-start: 2em; - display: list-item; + text-indent: -1.5em; } /* \ili2 - Introduction list item (level 2) */ & .ili2 { - list-style-type: none; - margin-inline-start: 4em; - display: list-item; + display: block; + margin-inline-start: 3em; + text-indent: -1.5em; } /* \ili3 - Introduction list item (level 3) */ & .ili3 { - list-style-type: none; - margin-inline-start: 6em; - display: list-item; + display: block; + margin-inline-start: 4em; + text-indent: -1.5em; } - /* \ipi - Indented introduction paragraph */ - & .ipi { + /* ============================================================ + INTRODUCTION — Paragraphs + ============================================================ */ + + /* \ip - Introduction paragraph — first line indent */ + & .ip { display: block; - text-indent: 1.5em; + margin-bottom: 0; + text-indent: 1em; } - /* \im - Introduction flush left (margin) paragraph */ + /* \im - Introduction flush left (margin) paragraph — no indent */ & .im { display: block; text-indent: 0; margin: 0; } - /* \imi - Indented introduction flush left (margin) paragraph */ + /* \ipi - Indented introduction paragraph — first line indent + margins */ + & .ipi { + display: block; + text-indent: 1em; + margin-inline-start: 1em; + margin-inline-end: 1em; + } + + /* \imi - Indented introduction flush left paragraph — margins, no indent */ & .imi { display: block; margin-inline-start: 1em; + margin-inline-end: 1em; text-indent: 0; } - /* \ipq - Introduction quote from scripture text */ + /* \ipq - Introduction quote from scripture text — Italic, margins, indent */ & .ipq { display: block; font-style: italic; text-indent: 1em; + margin-inline-start: 1em; + margin-inline-end: 1em; } - /* \imq - Introduction flush left quote from text */ + /* \imq - Introduction flush left quote from text — Italic, margins */ & .imq { display: block; font-style: italic; text-indent: 0; + margin-inline-start: 1em; + margin-inline-end: 1em; } - /* \ipr - Introduction right-aligned paragraph (typically a scripture reference) */ + /* \ipr - Introduction right-aligned paragraph — Italic, Right, margins */ & .ipr { display: block; + font-style: italic; text-align: end; + margin-inline-start: 1em; + margin-inline-end: 1em; } - /* \ib - Introduction blank line (whitespace between intro paragraphs) */ + /* \ib - Introduction blank line */ & .ib { display: block; height: 1em; } - /* \iqt - Introduction quoted text (scripture quotation in intro, character style) */ - & .iqt { - font-style: italic; - } - - /* \imte - Introduction major title ending (marks end of introduction) */ - & .imte, /* \imte - Introduction major title ending */ - & .imte1 { - /* \imte1 - Introduction major title ending (level 1) */ - font-size: 1.3em; - font-weight: bold; - line-height: 1.8em; - margin: 1em 0 0; - display: block; - } - - /* \imte2 - Introduction major title ending (level 2) */ - & .imte2 { - line-height: 1.8em; - margin: 0.5em 0; - font-weight: bold; - display: block; - } - - /* \iex - Introduction explanatory/bridge text (e.g. attribution at end of epistles) */ + /* \iex - Introduction explanatory/bridge text — first line indent */ & .iex { display: block; - font-style: italic; + text-indent: 1em; line-height: 2em; margin: 0.5em 0; } - /* Title levels 2-4 */ - & .imt2, /* \imt2 - Introduction major title (level 2) */ - & .imt3, /* \imt3 - Introduction major title (level 3) */ - & .imt4, /* \imt4 - Introduction major title (level 4) */ - & .mt2, /* \mt2 - Main title (level 2) */ - & .mt3, /* \mt3 - Main title (level 3) */ - & .mt4 { - /* \mt4 - Main title (level 4) */ - line-height: 1.8em; - margin: 0.5em 0; - font-weight: bold; - display: block; + /* \iqt - Introduction quoted text (scripture quotation, character style) — Italic */ + & .iqt { + font-style: italic; } - /* Section headers */ - & .is, /* \is - Introduction section heading */ - & .s, /* \s - Section heading */ - & .s1, /* \s1 - Section heading (level 1) */ - & .is1, /* \is1 - Introduction section heading (level 1) */ - & .is2 { - /* \is2 - Introduction section heading (level 2) */ - font-size: 1.1em; - font-weight: bold; - line-height: 1.8em; - margin: 0.5em 0 0.5em 0; + /* ============================================================ + INTRODUCTION — Poetry + ============================================================ */ + + /* \iq, \iq1 - Introduction poetry (level 1) — Italic, hanging indent */ + & .iq, + & .iq1 { display: block; + font-style: italic; + padding-inline-start: 2em; + text-indent: -2em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } - & .heading { - font-weight: bold; - line-height: 1.8em; - margin: 0.5em 0 0.5em 0; + /* \iq2 - Introduction poetry (level 2) — Italic */ + & .iq2 { display: block; + font-style: italic; + padding-inline-start: 2em; + text-indent: -1em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } - /* \mr - Major section reference range (e.g. "(Psalms 1–41)") */ - & .mr { + /* \iq3 - Introduction poetry (level 3) — Italic */ + & .iq3 { display: block; font-style: italic; - line-height: 1.8em; - margin: 0 0 0.5em; + padding-inline-start: 3em; + text-indent: -2em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } - /* \ms3 - Major section heading (level 3) */ - & .ms3 { - line-height: 1.8em; - margin: 0.5em 0; - font-weight: bold; + /* \iq4 - Introduction poetry (level 4) — Italic */ + & .iq4 { display: block; + font-style: italic; + padding-inline-start: 4em; + text-indent: -2em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } + /* ============================================================ + NOTES — Footnote/Note Character Styles + ============================================================ */ + /* Notes within headings */ & .s1 .note .heading, & .s .note .heading { @@ -374,15 +651,15 @@ font-size: 0.9em; } - /* Footnote/note character styles */ & .note .fr { - /* \fr - Footnote reference (verse number) */ + /* \fr - Footnote reference (verse number) — Bold */ padding-inline-end: 0.25em; + font-weight: bold; } - & .note .fq, /* \fq - Footnote quotation from text */ + & .note .fq, /* \fq - Footnote quotation from text — Italic */ & .note .fqa { - /* \fqa - Footnote alternate translation */ + /* \fqa - Footnote alternate translation — Italic */ font-style: italic; } @@ -392,26 +669,29 @@ } & .note .fv { - /* \fv - Footnote verse number */ + /* \fv - Footnote verse number — Superscript */ vertical-align: super; font-size: 0.75em; } & .note .fk { - /* \fk - Footnote keyword */ + /* \fk - Footnote keyword — Bold + Italic */ font-style: italic; font-weight: bold; } & .note .fl { - /* \fl - Footnote label (e.g. "Or", "Heb.", "LXX") */ + /* \fl - Footnote label — Italic + Bold */ font-weight: bold; + font-style: italic; } - /* Paragraphs - text-indent 1em based on iOS CSS */ - & .p, /* \p - Normal paragraph */ - & .ip { - /* \ip - Introduction paragraph */ + /* ============================================================ + PARAGRAPHS + ============================================================ */ + + /* \p - Normal paragraph — first line indent */ + & .p { display: block; margin-bottom: 0; text-indent: 1em; @@ -653,13 +933,21 @@ margin-top: 0; } + /* ============================================================ + CHARACTER STYLES + ============================================================ */ + /* \wj - Words of Jesus (red letter) */ & .wj { color: var(--yv-red); } - /* \nd - Name of Deity (e.g. "LORD"), \sc - Small-cap text */ - & .nd, + /* \nd - Name of Deity (e.g. "LORD") — Smallcaps */ + & .nd { + font-variant: small-caps; + } + + /* \sc - Small-cap text — Smallcaps */ & .sc { font-variant: small-caps; } @@ -669,7 +957,7 @@ font-weight: bold; } - /* \em - Emphasis text */ + /* \em - Emphasis text — Italic */ & .em { font-style: italic; } @@ -680,12 +968,12 @@ font-style: italic; } - /* \bk - Book name reference (e.g. "The Gospel according to Mark") */ + /* \bk - Book name reference — Italic */ & .bk { font-style: italic; } - /* \ord - Ordinal number ending (e.g. the "st" in "1st") */ + /* \ord - Ordinal number ending — Superscript */ & .ord { font-size: 0.75em; line-height: 2em; @@ -694,16 +982,16 @@ top: -0.5em; } - /* \pn - Proper name (semantic marker, no special formatting per USFM spec) */ + /* \pn - Proper name (semantic marker, no special formatting in this reader) */ & .pn { font-weight: normal; font-style: normal; } - /* \k - Keyword/keyterm */ + /* \k - Keyword/keyterm — Italic + Bold */ & .k { - font-weight: normal; - font-style: normal; + font-style: italic; + font-weight: bold; } /* \w - Wordlist/glossary entry */ @@ -711,7 +999,7 @@ display: inline; } - /* \rq - Inline cross-reference quotation */ + /* \rq - Inline cross-reference quotation — Italic, smaller */ & .rq { font-style: italic; font-size: 0.8em; @@ -728,6 +1016,50 @@ display: inline; } + /* \add - Translator addition — Bold + Italic */ + & .add { + font-weight: bold; + font-style: italic; + } + + /* \it - Italic text, \tl - Transliterated word, + \sls - Secondary language/alternate source text — Italic */ + & .it, + & .tl, + & .sls { + font-style: italic; + } + + /* \qt - Quoted text (OT quotation in NT, etc.) — Italic */ + & .qt { + font-style: italic; + } + + /* \sig - Signature of the author of a letter/epistle — Italic */ + & .sig { + font-style: italic; + } + + /* \dc - Deuterocanonical/LXX additions — Italic */ + & .dc { + font-style: italic; + } + + /* \no - Normal text (resets italic/bold within a styled context) */ + & .no { + font-weight: normal; + font-style: normal; + } + + /* \fdc - Footnote deuterocanonical content */ + & .fdc { + font-style: italic; + } + + /* ============================================================ + POETRY + ============================================================ */ + /* \b - Blank line (stanza break in poetry) */ & .b { line-height: 2em; @@ -735,45 +1067,115 @@ display: block; } - /* \m - Continuation (margin) paragraph, \nb - No-break paragraph */ - & .m, - & .nb { + /* \q, \q1 - Poetic line (level 1) */ + & .q, + & .q1 { + display: block; + padding-inline-start: 2em; + text-indent: -2em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.3); + } + + & .q2 { + display: block; + padding-inline-start: 2em; + text-indent: -1em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.3); + } + + & .q3 { + display: block; + padding-inline-start: 3em; + text-indent: -2em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.3); + } + + & .q4 { + display: block; + padding-inline-start: 4em; + text-indent: -2em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.3); + } + + /* \qc - Centered poetic line */ + & .qc { + text-align: center; + display: block; margin-bottom: calc(var(--yv-reader-font-size) * 0.5); - text-indent: 0; + } + + /* \qr - Right-aligned poetic refrain */ + & .qr { + text-align: end; display: block; + margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - /* \q - Poetic line */ - & .q, - & .q1 { + /* \qs - Selah (commonly in Psalms and Habakkuk) — Italic */ + & .qs { + display: block; + text-align: end; + font-style: italic; + margin-top: calc(var(--yv-reader-font-size) * 0.5); + margin-bottom: calc(var(--yv-reader-font-size) * 0.5); + } + + /* \qac - Acrostic letter within a poetic line — Italic */ + & .qac { + font-style: italic; + } + + /* \qd - Hebrew note at end of poetic section — Italic */ + & .qd { + display: block; + font-style: italic; + margin: 0.5em 0; + margin-inline-start: 1em; + } + + /* \qm, \qm1 - Embedded text poetic line (level 1) */ + & .qm, + & .qm1 { display: block; padding-inline-start: 2em; text-indent: -2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } - & .q2 { + & .qm2 { display: block; padding-inline-start: 2em; text-indent: -1em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } - & .q3 { + & .qm3 { display: block; padding-inline-start: 3em; text-indent: -2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } - & .q4 { + & .qm4 { display: block; padding-inline-start: 4em; text-indent: -2em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } - /* \pi - Indented paragraph (used for discourse sections) */ + /* ============================================================ + PARAGRAPHS — Continued + ============================================================ */ + + /* \m - Continuation (margin) paragraph, \nb - No-break paragraph */ + & .m, + & .nb { + margin-bottom: calc(var(--yv-reader-font-size) * 0.5); + text-indent: 0; + display: block; + } + + /* \pi, \pi1 - Indented paragraph (level 1) — first line indent + left margin */ & .pi, & .pi1 { display: block; @@ -796,7 +1198,8 @@ margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - /* \mi - Indented flush left, \pm - Embedded text, \pmo - Embedded opening, \pmc - Embedded closing, \pmr - Embedded refrain */ + /* \mi - Indented flush left, \pm - Embedded text, \pmo - Embedded opening, + \pmc - Embedded closing, \pmr - Embedded refrain */ & .mi, & .pm, & .pmo, @@ -808,174 +1211,69 @@ margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - /* \pr - Right-aligned paragraph, \qr - Right-aligned poetic refrain */ - & .pr, - & .qr { + /* \pr - Right-aligned paragraph */ + & .pr { text-align: end; display: block; margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - /* \pc - Centered paragraph (inscriptions), \qc - Centered poetic line */ - & .pc, - & .qc { + /* \pc - Centered paragraph (inscriptions) */ + & .pc { text-align: center; display: block; margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - /* \iq - Introduction poetic line */ - & .iq, - & .iq1 { - display: block; - padding-inline-start: 2em; - text-indent: -2em; - margin-bottom: calc(var(--yv-reader-font-size) * 0.3); - } - - & .iq2 { - display: block; - padding-inline-start: 2em; - text-indent: -1em; - margin-bottom: calc(var(--yv-reader-font-size) * 0.3); - } - - & .iq3 { - display: block; - padding-inline-start: 3em; - text-indent: -2em; - margin-bottom: calc(var(--yv-reader-font-size) * 0.3); - } - - & .iq4 { + /* \po - Opening of an epistle/letter */ + & .po { display: block; - padding-inline-start: 4em; - text-indent: -2em; - margin-bottom: calc(var(--yv-reader-font-size) * 0.3); + text-indent: 1em; + margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - /* \qm - Embedded text poetic line (quoted poetry within prose) */ - & .qm, - & .qm1 { + /* \ph - Indented paragraph with hanging indent (deprecated, use \li) */ + & .ph, + & .ph1 { display: block; padding-inline-start: 2em; text-indent: -2em; - margin-bottom: calc(var(--yv-reader-font-size) * 0.3); - } - - & .qm2 { - display: block; - padding-inline-start: 2em; - text-indent: -1em; - margin-bottom: calc(var(--yv-reader-font-size) * 0.3); + margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - & .qm3 { + & .ph2 { display: block; padding-inline-start: 3em; text-indent: -2em; - margin-bottom: calc(var(--yv-reader-font-size) * 0.3); + margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - & .qm4 { + & .ph3 { display: block; padding-inline-start: 4em; text-indent: -2em; - margin-bottom: calc(var(--yv-reader-font-size) * 0.3); - } - - /* \add - Translator addition, \it - Italic text, \tl - Transliterated word, - \sls - Secondary language/alternate source text (e.g. Aramaic sections in Ezra/Daniel) */ - & .add, - & .it, - & .tl, - & .sls { - font-style: italic; - } - - /* \qt - Quoted text (OT quotation in NT, etc.) */ - & .qt { - font-style: italic; - } - - /* \qs - Selah (commonly in Psalms and Habakkuk) */ - & .qs { - display: block; - text-align: end; - font-style: italic; - margin-top: calc(var(--yv-reader-font-size) * 0.5); margin-bottom: calc(var(--yv-reader-font-size) * 0.5); } - /* \s2 - Section heading (level 2) */ - & .s2 { - display: block; - font-weight: bold; - line-height: 1.8em; - margin: 0 0 0.5em; - font-size: 1em; - } - - & .s3, /* \s3 - Section heading (level 3) */ - & .s4 { - /* \s4 - Section heading (level 4) */ + /* \cls - Closure of an epistle/letter — Right-aligned */ + & .cls { display: block; - font-weight: bold; - line-height: 1.8em; - margin: 0 0 0.5em; - } - - /* \ms2 - Major section heading (level 2) */ - & .ms2 { - font-size: 1.1em; - line-height: 1.8em; + text-align: end; margin: 0.5em 0; - font-weight: bold; - display: block; - } - - /* \r - Parallel passage reference (e.g. "(Mark 1.1-8; Luke 3.1-18)") */ - & .r { - display: block; - font-style: italic; - line-height: 1.8em; - margin: 0 0 0.5em; - } - - /* \sp - Speaker identification (e.g. Job, Song of Songs) */ - & .sp { - display: block; - font-style: italic; - line-height: 1.8em; - margin: 0 0 0.5em; - } - - /* \sr - Section reference range */ - & .sr { - display: block; - font-style: italic; - font-size: 0.8em; - line-height: 1.8em; - margin: 0 0 0.5em; } - & .qa { - /* \qa - Acrostic heading (e.g. Hebrew letters in Psalm 119) */ - display: block; - font-weight: bold; - line-height: 1.8em; - margin: 0 0 0.5em; - } + /* ============================================================ + LISTS + ============================================================ */ - /* \d - Descriptive title / Hebrew subtitle (e.g. Psalm superscriptions) */ - & .d { + /* \lh - List header, \lf - List footer */ + & .lh, + & .lf { display: block; - font-style: italic; - line-height: 1.8em; - margin: 0.5em 0; + margin: 0.5em 0 0.25em; } - /* \li - List entry */ + /* \li, \li1 - List entry (level 1) */ & .li, & .li1 { display: list-item; @@ -1001,17 +1299,42 @@ margin-inline-start: 4em; } - /* Line breaks */ + /* \lim, \lim1 - Embedded list entry (level 1) */ + & .lim, + & .lim1 { + display: list-item; + list-style-type: disc; + margin-inline-start: 2em; + } + + & .lim2 { + display: list-item; + list-style-type: disc; + margin-inline-start: 4em; + } + + & .lim3 { + display: list-item; + list-style-type: disc; + margin-inline-start: 6em; + } + + /* ============================================================ + LINE BREAKS & CONTENT + ============================================================ */ + & br { line-height: inherit; } - /* Content classes */ & .content { display: inline; } - /* Footnotes and cross-references */ + /* ============================================================ + FOOTNOTES & CROSS-REFERENCES (display elements) + ============================================================ */ + & .footnote, & .crossref { vertical-align: super; @@ -1020,7 +1343,10 @@ white-space: nowrap; } - /* Subscript and superscript */ + /* ============================================================ + SUBSCRIPT & SUPERSCRIPT + ============================================================ */ + & .sub, & .sup { font-size: 75%; @@ -1037,7 +1363,10 @@ bottom: -0.1em; } - /* Tables */ + /* ============================================================ + TABLES + ============================================================ */ + & table { width: 100%; background: var(--yv-background); @@ -1106,52 +1435,9 @@ vertical-align: top; } - /* \po - Opening of an epistle/letter */ - & .po { - display: block; - text-indent: 1em; - margin-bottom: calc(var(--yv-reader-font-size) * 0.5); - } - - /* \ph - Indented paragraph with hanging indent (deprecated, use \li) */ - & .ph, - & .ph1 { - display: block; - padding-inline-start: 2em; - text-indent: -2em; - margin-bottom: calc(var(--yv-reader-font-size) * 0.5); - } - - & .ph2 { - display: block; - padding-inline-start: 3em; - text-indent: -2em; - margin-bottom: calc(var(--yv-reader-font-size) * 0.5); - } - - & .ph3 { - display: block; - padding-inline-start: 4em; - text-indent: -2em; - margin-bottom: calc(var(--yv-reader-font-size) * 0.5); - } - - /* \sig - Signature of the author of a letter/epistle */ - & .sig { - font-style: italic; - } - - /* \dc - Deuterocanonical/LXX additions */ - & .dc { - font-style: italic; - } - - /* \qd - Hebrew note at end of poetic section */ - & .qd { - display: block; - font-style: italic; - margin: 0.5em 0; - } + /* ============================================================ + SPECIAL / MISC + ============================================================ */ /* \sd - Semantic division (vertical space between sections) */ & .sd { @@ -1159,69 +1445,19 @@ height: 1em; } - /* \lh - List header, \lf - List footer */ - & .lh, - & .lf { - display: block; - margin: 0.5em 0 0.25em; - } - - /* \lim - Embedded list entry */ - & .lim, - & .lim1 { - display: list-item; - list-style-type: disc; - margin-inline-start: 2em; - } - - & .lim2 { - display: list-item; - list-style-type: disc; - margin-inline-start: 4em; - } - - & .lim3 { - display: list-item; - list-style-type: disc; - margin-inline-start: 6em; - } - - /* \qac - Acrostic letter within a poetic line */ - & .qac { - font-weight: bold; - } - /* \lb - Line break (same as \b) */ & .lb { display: block; height: 1em; } - /* \cd - Chapter description */ + /* \cd - Chapter description — Italic */ & .cd { display: block; font-style: italic; margin: 0.5em 0; } - /* \fdc - Footnote deuterocanonical content */ - & .fdc { - font-style: italic; - } - - /* \cls - Closure of an epistle/letter (e.g. "May God's grace be with you.") */ - & .cls { - display: block; - text-align: end; - margin: 0.5em 0; - } - - /* \no - Normal text (resets italic/bold within a styled context) */ - & .no { - font-weight: normal; - font-style: normal; - } - /* \fp - Footnote paragraph (block-level paragraph within a footnote) */ & .fp { display: block; @@ -1229,15 +1465,17 @@ text-indent: 1em; } - /* \lit - Liturgical note (e.g. "Glory:" in Psalms) */ + /* \lit - Liturgical note — Right-aligned, Bold */ & .lit { display: block; text-align: end; - font-style: italic; + font-weight: bold; } - /* RTL Support - logical properties handle indent/padding mirroring automatically. - This block only needs to set direction and base text alignment. */ + /* ============================================================ + RTL SUPPORT + ============================================================ */ + &[dir='rtl'] { direction: rtl; text-align: right; From 570ae826f7913ed1527094ca74a7271886963e72 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Tue, 17 Feb 2026 13:28:24 -0600 Subject: [PATCH 20/25] Align bible reader text to start --- packages/ui/src/styles/bible-reader.css | 44 ++++++++++++------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index 10b3c189..c6242a9a 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -156,7 +156,7 @@ font-size: 1.6em; font-weight: bold; line-height: 1.4em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 1em 0 0.25em; display: block; } @@ -166,7 +166,7 @@ font-size: 1.3em; font-style: italic; line-height: 1.4em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.5em 0 0.15em; display: block; } @@ -176,7 +176,7 @@ font-size: 1.3em; font-weight: bold; line-height: 1.4em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.15em 0; display: block; } @@ -184,7 +184,7 @@ /* \mt4 - Main title (level 4) — FontSize 12, Center */ & .mt4 { line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.15em 0; display: block; } @@ -199,7 +199,7 @@ font-size: 1.17em; font-weight: bold; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 1em 0 0.25em; display: block; } @@ -209,7 +209,7 @@ font-size: 1.08em; font-style: italic; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.5em 0 0.25em; display: block; } @@ -218,7 +218,7 @@ & .imt3 { font-weight: bold; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.15em 0; display: block; } @@ -227,7 +227,7 @@ & .imt4 { font-style: italic; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.15em 0; display: block; } @@ -242,7 +242,7 @@ font-size: 1.6em; font-weight: bold; line-height: 1.4em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 1em 0 0.25em; display: block; } @@ -252,7 +252,7 @@ font-size: 1.3em; font-style: italic; line-height: 1.4em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.5em 0 0.15em; display: block; } @@ -267,7 +267,7 @@ font-size: 1.17em; font-weight: bold; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 1em 0 0.25em; display: block; } @@ -277,7 +277,7 @@ font-size: 1.17em; font-weight: bold; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.5em 0; display: block; } @@ -287,7 +287,7 @@ font-size: 1.17em; font-style: italic; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.5em 0; display: block; } @@ -297,7 +297,7 @@ display: block; font-style: italic; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0 0 0.5em; } @@ -310,7 +310,7 @@ & .s1 { font-weight: bold; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.5em 0 0.5em 0; display: block; } @@ -320,7 +320,7 @@ display: block; font-style: italic; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.5em 0; } @@ -345,7 +345,7 @@ display: block; font-weight: bold; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0 0 0.5em; } @@ -354,7 +354,7 @@ display: block; font-style: italic; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0 0 0.5em; } @@ -371,7 +371,7 @@ display: block; font-style: italic; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.5em 0; } @@ -400,7 +400,7 @@ font-size: 1.17em; font-weight: bold; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.5em 0 0.5em 0; display: block; } @@ -409,7 +409,7 @@ & .is2 { font-weight: bold; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.5em 0 0.5em 0; display: block; } @@ -442,7 +442,7 @@ & .iot { font-weight: bold; line-height: 1.8em; - text-align: center; + text-align: start; /* Intentionally set as left-aligned per YV standards */ margin: 0.5em 0; display: block; } From 3b905f49ef27141510f5792a1cefcc7514677677 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Tue, 17 Feb 2026 13:41:33 -0600 Subject: [PATCH 21/25] Refactor bible reader CSS margins and indents Adjust CSS for bible reader component to use shorthand margin properties and more consistent text indentation for blockquotes. --- packages/ui/src/styles/bible-reader.css | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index c6242a9a..57cc9286 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -551,8 +551,7 @@ display: block; font-style: italic; text-indent: 0; - margin-inline-start: 1em; - margin-inline-end: 1em; + margin: 1em; } /* \ipr - Introduction right-aligned paragraph — Italic, Right, margins */ @@ -593,7 +592,7 @@ display: block; font-style: italic; padding-inline-start: 2em; - text-indent: -2em; + text-indent: -1em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } @@ -601,8 +600,8 @@ & .iq2 { display: block; font-style: italic; - padding-inline-start: 2em; - text-indent: -1em; + padding-inline-start: 3em; + text-indent: -1.5em; margin-bottom: calc(var(--yv-reader-font-size) * 0.3); } From c6db4e795a74e4c19b6ee1fac1b1d6a032e692a7 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Tue, 17 Feb 2026 13:48:15 -0600 Subject: [PATCH 22/25] Refactor: Adjust bible reader list indentations Update text-indent for list items within the bible reader styles to improve readability and visual consistency. --- packages/ui/src/styles/bible-reader.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index 57cc9286..9ab53249 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -486,21 +486,21 @@ & .ili1 { display: block; margin-inline-start: 2em; - text-indent: -1.5em; + text-indent: -1em; } /* \ili2 - Introduction list item (level 2) */ & .ili2 { display: block; margin-inline-start: 3em; - text-indent: -1.5em; + text-indent: -1em; } /* \ili3 - Introduction list item (level 3) */ & .ili3 { display: block; margin-inline-start: 4em; - text-indent: -1.5em; + text-indent: -1em; } /* ============================================================ From aa953302f008db25d3eed9d4ce14c4f190a282e3 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Tue, 17 Feb 2026 13:56:07 -0600 Subject: [PATCH 23/25] Add spacing to introduction list items --- packages/ui/src/styles/bible-reader.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index 9ab53249..460cb545 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -489,6 +489,16 @@ text-indent: -1em; } + /* This adds top margin to the introduction list items */ + & .ili:not(.ili + .ili) { + margin-block-start: 1em; + } + + /* This adds bottommargin to the introduction list items */ + & .ili:has(+ :not(.ili)) { + margin-block-end: 1em; + } + /* \ili2 - Introduction list item (level 2) */ & .ili2 { display: block; From ea5748b54927a96e473dc57dad821299ad1a8561 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Wed, 18 Feb 2026 10:49:41 -0600 Subject: [PATCH 24/25] Add spacing to bible reader introduction entries --- packages/ui/src/styles/bible-reader.css | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/styles/bible-reader.css b/packages/ui/src/styles/bible-reader.css index 460cb545..d305592f 100644 --- a/packages/ui/src/styles/bible-reader.css +++ b/packages/ui/src/styles/bible-reader.css @@ -454,6 +454,18 @@ margin-inline-start: 2em; } + /* This adds top margin to the introduction outline entries */ + & .io:not(.io + .io), + & .io1:not(.io1 + .io1) { + margin-block-start: 1em; + } + + /* This adds bottom margin to the introduction outline entries */ + & .io:has(+ :not(.io)), + & .io1:has(+ :not(.io1)) { + margin-block-end: 1em; + } + /* \io2 - Introduction outline entry (level 2) — LeftMargin .75in */ & .io2 { display: block; @@ -494,7 +506,7 @@ margin-block-start: 1em; } - /* This adds bottommargin to the introduction list items */ + /* This adds bottom margin to the introduction list items */ & .ili:has(+ :not(.ili)) { margin-block-end: 1em; } From e19a9607c0d2326833a0a44fa14b929505a068a1 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Wed, 18 Feb 2026 11:30:49 -0600 Subject: [PATCH 25/25] Refactor bible chapter picker logic Extract chapter selection logic into a reusable handler function to improve code clarity and maintainability. --- .../src/components/bible-chapter-picker.tsx | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/ui/src/components/bible-chapter-picker.tsx b/packages/ui/src/components/bible-chapter-picker.tsx index cd0895ad..f011092b 100644 --- a/packages/ui/src/components/bible-chapter-picker.tsx +++ b/packages/ui/src/components/bible-chapter-picker.tsx @@ -122,6 +122,15 @@ function Root({ }; }, [expandedBook]); + const handleChapterButtonClick = (bookId: string, passageId: string) => { + const chapterId = passageId.split('.').pop() || ''; + if (chapterId && bookId) { + setBook(bookId); + setChapter(chapterId); + setSearchQuery(''); + } + }; + return ( { - const chapterId = bookItem.intro?.passage_id.split('.').pop(); - if (chapterId) { - setBook(bookItem.id); - setChapter(chapterId); - setSearchQuery(''); - } - }} + onClick={() => + handleChapterButtonClick( + bookItem.id, + bookItem.intro?.passage_id || '', + ) + } > @@ -192,11 +199,9 @@ function Root({ variant="secondary" size="icon" className="yv:aspect-square yv:w-full yv:h-full yv:flex yv:items-center yv:justify-center yv:rounded-[4px]" - onClick={() => { - setBook(bookItem.id); - setChapter(chapterId); - setSearchQuery(''); - }} + onClick={() => + handleChapterButtonClick(bookItem.id, chapterRef.passage_id) + } > {chapterId}