mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 06:46:13 +00:00
1b07aac579
MiniJinja's finalizer workaround (needed for Jinja2-compatible escaping) added 3x overhead per variable, negating the Rust speed advantage. Back to Jinja2 with the 17x faster single-regex link_names filter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1618 lines
46 KiB
HTML
1618 lines
46 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ book }} {{ chapter }}:{{ verse_num }} - KJV Bible{% endblock %}
|
|
|
|
{% block description %}{{ verse_text[:155] if verse_text else 'Study the Authorized King James Version Bible' }}{% endblock %}
|
|
|
|
{% block og_type %}article{% endblock %}
|
|
{% block og_title %}{{ book }} {{ chapter }}:{{ verse_num }} - KJV Bible{% endblock %}
|
|
{% block og_description %}{{ verse_text[:150] if verse_text else '' }}...{% endblock %}
|
|
{% block og_image %}https://kjvstudy.org/og/verse/{{ book | urlencode }}/{{ chapter }}/{{ verse_num }}.png{% endblock %}
|
|
{% block twitter_image %}https://kjvstudy.org/og/verse/{{ book | urlencode }}/{{ chapter }}/{{ verse_num }}.png{% endblock %}
|
|
|
|
{% block structured_data %}
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "Article",
|
|
"headline": {{ (book ~ " " ~ chapter|string ~ ":" ~ verse_num|string) | tojson }},
|
|
"articleBody": {{ verse_text | tojson }},
|
|
"author": {
|
|
"@type": "Organization",
|
|
"name": "KJV Study"
|
|
},
|
|
"publisher": {
|
|
"@type": "Organization",
|
|
"name": "KJV Study",
|
|
"url": "https://kjvstudy.org"
|
|
},
|
|
"mainEntityOfPage": {
|
|
"@type": "WebPage",
|
|
"@id": "https://kjvstudy.org/book/{{ book }}/chapter/{{ chapter }}/verse/{{ verse_num }}"
|
|
},
|
|
"isPartOf": {
|
|
"@type": "Book",
|
|
"name": {{ book | tojson }},
|
|
"@id": "https://kjvstudy.org/book/{{ book }}"
|
|
}
|
|
}
|
|
</script>
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "BreadcrumbList",
|
|
"itemListElement": [
|
|
{
|
|
"@type": "ListItem",
|
|
"position": 1,
|
|
"name": "Home",
|
|
"item": "https://kjvstudy.org"
|
|
},
|
|
{
|
|
"@type": "ListItem",
|
|
"position": 2,
|
|
"name": "Books",
|
|
"item": "https://kjvstudy.org/books"
|
|
},
|
|
{
|
|
"@type": "ListItem",
|
|
"position": 3,
|
|
"name": {{ book | tojson }},
|
|
"item": "https://kjvstudy.org/book/{{ book | urlencode }}"
|
|
},
|
|
{
|
|
"@type": "ListItem",
|
|
"position": 4,
|
|
"name": "Chapter {{ chapter }}",
|
|
"item": "https://kjvstudy.org/book/{{ book | urlencode }}/chapter/{{ chapter }}"
|
|
},
|
|
{
|
|
"@type": "ListItem",
|
|
"position": 5,
|
|
"name": "Verse {{ verse_num }}"
|
|
}
|
|
]
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|
|
{% block head %}
|
|
<style>
|
|
/* Action buttons */
|
|
.chapter-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.action-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.45rem 0.9rem;
|
|
font-size: 0.9rem;
|
|
color: var(--text-secondary);
|
|
background: var(--code-bg);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.action-btn:hover {
|
|
background: var(--bg-color);
|
|
border-color: var(--link-color);
|
|
color: var(--link-color);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.action-btn svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
|
|
/* Red Letter Edition - Words of Christ */
|
|
.words-of-christ {
|
|
color: #c41e3a;
|
|
}
|
|
|
|
/* Dark mode for .words-of-christ is in base.html */
|
|
|
|
/* Links inside red letter text should also be red */
|
|
.words-of-christ a {
|
|
color: #c41e3a;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
[data-theme="dark"] .words-of-christ a {
|
|
color: #ff6b6b;
|
|
}
|
|
|
|
/* Prevent sidenotes from inheriting red color */
|
|
.words-of-christ .sidenote,
|
|
.words-of-christ .marginnote,
|
|
.words-of-christ .sidenote-number,
|
|
.words-of-christ label.sidenote-number {
|
|
color: var(--text-color);
|
|
}
|
|
|
|
[data-theme="dark"] .words-of-christ .sidenote,
|
|
[data-theme="dark"] .words-of-christ .marginnote,
|
|
[data-theme="dark"] .words-of-christ .sidenote-number,
|
|
[data-theme="dark"] .words-of-christ label.sidenote-number {
|
|
color: var(--text-color);
|
|
}
|
|
|
|
/* Prevent word definitions from inheriting red color */
|
|
.words-of-christ .word-original,
|
|
.words-of-christ .word-english,
|
|
.words-of-christ .word-definition {
|
|
color: #111;
|
|
}
|
|
|
|
.words-of-christ .word-transliteration,
|
|
.words-of-christ .word-parsing {
|
|
color: #666;
|
|
}
|
|
|
|
.words-of-christ .word-strongs {
|
|
color: #0066cc;
|
|
}
|
|
|
|
[data-theme="dark"] .words-of-christ .word-original,
|
|
[data-theme="dark"] .words-of-christ .word-english,
|
|
[data-theme="dark"] .words-of-christ .word-definition {
|
|
color: #ddd;
|
|
}
|
|
|
|
[data-theme="dark"] .words-of-christ .word-transliteration,
|
|
[data-theme="dark"] .words-of-christ .word-parsing {
|
|
color: #999;
|
|
}
|
|
|
|
[data-theme="dark"] .words-of-christ .word-strongs {
|
|
color: #6699ff;
|
|
}
|
|
|
|
.verse-text {
|
|
font-size: 1.8rem;
|
|
line-height: 2.4rem;
|
|
margin: 2rem 0;
|
|
font-style: italic;
|
|
}
|
|
|
|
.verse-reference {
|
|
color: #666;
|
|
font-size: 1.2rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.share-section {
|
|
max-width: 65%;
|
|
margin: 1.5rem 0 2rem 0;
|
|
}
|
|
|
|
.share-section .section-heading {
|
|
font-size: 1.4rem;
|
|
font-weight: 600;
|
|
color: var(--text-color);
|
|
text-transform: none;
|
|
letter-spacing: normal;
|
|
}
|
|
|
|
.share-pills {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4rem;
|
|
padding: 0.75rem 0;
|
|
}
|
|
|
|
.share-pill {
|
|
display: inline-block;
|
|
padding: 0.25rem 0.6rem;
|
|
font-size: 0.85rem;
|
|
font-family: inherit;
|
|
color: var(--text-secondary);
|
|
background: transparent;
|
|
border: 1px solid var(--border-color-darker);
|
|
border-radius: 12px;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.share-pill:hover {
|
|
border-color: var(--link-color);
|
|
color: var(--link-color);
|
|
background: rgba(74, 124, 89, 0.05);
|
|
}
|
|
|
|
.share-pill.copied {
|
|
border-color: var(--link-color);
|
|
color: var(--link-color);
|
|
}
|
|
|
|
[data-theme="dark"] .share-pill {
|
|
border-color: #444;
|
|
}
|
|
|
|
[data-theme="dark"] .share-pill:hover {
|
|
background: rgba(74, 124, 89, 0.15);
|
|
}
|
|
|
|
[data-font-size="large"] .share-pill {
|
|
font-size: 1rem;
|
|
padding: 0.3rem 0.75rem;
|
|
}
|
|
|
|
/* Generic collapsible section toggle */
|
|
.section-toggle {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
width: 100%;
|
|
padding: 0.5rem 0;
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
text-align: left;
|
|
font-family: inherit;
|
|
transition: opacity 0.2s;
|
|
}
|
|
|
|
.section-toggle:hover {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.section-toggle:focus {
|
|
outline: 2px solid var(--link-color);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.section-toggle .toggle-icon {
|
|
font-size: 0.8rem;
|
|
color: var(--text-secondary);
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.section-heading {
|
|
font-size: 0.9rem;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.share-label {
|
|
font-size: 0.9rem;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
margin-bottom: 0.75rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.cross-references-section .section-heading {
|
|
font-size: 1.4rem;
|
|
text-transform: none;
|
|
letter-spacing: normal;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.crossrefs-compact {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4rem;
|
|
padding: 0.75rem 0;
|
|
max-width: 65%;
|
|
}
|
|
|
|
.crossref-pill {
|
|
display: inline-block;
|
|
padding: 0.25rem 0.6rem;
|
|
background: transparent;
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 15px;
|
|
font-size: 0.85rem;
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
transition: all 0.15s;
|
|
white-space: nowrap;
|
|
position: relative;
|
|
}
|
|
|
|
a.crossref-pill:hover {
|
|
border-color: var(--link-color);
|
|
color: var(--link-color);
|
|
background: rgba(74, 124, 89, 0.05);
|
|
}
|
|
|
|
.crossref-pill .cross-ref-tooltip {
|
|
left: 0;
|
|
transform: none;
|
|
min-width: 200px;
|
|
max-width: 350px;
|
|
white-space: normal;
|
|
}
|
|
|
|
[data-theme="dark"] .crossref-pill {
|
|
border-color: #444;
|
|
}
|
|
|
|
[data-theme="dark"] a.crossref-pill:hover {
|
|
background: rgba(74, 124, 89, 0.15);
|
|
}
|
|
|
|
/* Large font size mode */
|
|
[data-font-size="large"] .verse-text {
|
|
font-size: 2.4rem;
|
|
line-height: 3.2rem;
|
|
}
|
|
|
|
[data-font-size="large"] .word-english {
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
[data-font-size="large"] .word-original {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
[data-font-size="large"] .word-original.hebrew {
|
|
font-size: 2.2rem;
|
|
}
|
|
|
|
[data-font-size="large"] .word-original.greek {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
[data-font-size="large"] .word-strongs {
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
[data-font-size="large"] .crossref-pill {
|
|
font-size: 1.1rem;
|
|
padding: 0.35rem 0.8rem;
|
|
}
|
|
|
|
[data-font-size="large"] .crossrefs-compact {
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
/* Verse navigation pills */
|
|
.verse-nav-pills {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4rem;
|
|
margin: 1.5rem 0;
|
|
}
|
|
|
|
.nav-pill {
|
|
display: inline-block;
|
|
padding: 0.3rem 0.7rem;
|
|
background: transparent;
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 20px;
|
|
font-size: 0.85rem;
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
transition: all 0.15s;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.nav-pill:hover {
|
|
border-color: var(--link-color);
|
|
color: var(--link-color);
|
|
background: rgba(74, 124, 89, 0.05);
|
|
}
|
|
|
|
[data-theme="dark"] .nav-pill {
|
|
border-color: #444;
|
|
}
|
|
|
|
[data-theme="dark"] .nav-pill:hover {
|
|
background: rgba(74, 124, 89, 0.15);
|
|
}
|
|
|
|
[data-font-size="large"] .nav-pill {
|
|
font-size: 1.1rem;
|
|
padding: 0.4rem 0.9rem;
|
|
}
|
|
|
|
[data-font-size="large"] .verse-nav-pills {
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.share-buttons {
|
|
display: flex;
|
|
gap: 0.75rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.share-btn {
|
|
padding: 0.5rem 1rem;
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
border: 1px solid var(--border-color-darker);
|
|
border-radius: 4px;
|
|
background: var(--bg-color);
|
|
color: var(--text-color);
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
}
|
|
|
|
.share-btn:hover {
|
|
background: var(--code-bg);
|
|
border-color: var(--link-color);
|
|
color: var(--link-color);
|
|
}
|
|
|
|
.share-btn.copied {
|
|
border-color: var(--link-color);
|
|
color: var(--link-color);
|
|
}
|
|
|
|
/* Cross-reference tooltip styles */
|
|
.cross-ref-link {
|
|
position: relative;
|
|
cursor: help;
|
|
}
|
|
|
|
.cross-ref-tooltip {
|
|
display: none;
|
|
position: absolute;
|
|
bottom: 100%;
|
|
left: 0;
|
|
min-width: 300px;
|
|
max-width: 400px;
|
|
padding: 0.75rem 1rem;
|
|
margin-bottom: 0.5rem;
|
|
background: var(--bg-color);
|
|
border: 1px solid var(--border-color-darker);
|
|
border-radius: 6px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
z-index: 1000;
|
|
font-size: 0.9rem;
|
|
line-height: 1.6;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.cross-ref-link:hover .cross-ref-tooltip {
|
|
display: block;
|
|
}
|
|
|
|
.tooltip-verse-text {
|
|
font-weight: 600;
|
|
font-style: italic;
|
|
color: var(--text-secondary);
|
|
margin-top: 0.5rem;
|
|
padding-top: 0.5rem;
|
|
border-top: 1px solid var(--border-color);
|
|
}
|
|
|
|
[data-theme="dark"] .cross-ref-tooltip {
|
|
background: var(--bg-color);
|
|
border-color: var(--border-color-darker);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
/* Interlinear styles - inline flow design */
|
|
.interlinear-container {
|
|
max-width: 100%;
|
|
margin: 2rem 0;
|
|
}
|
|
|
|
/* Collapsible toggle button */
|
|
.interlinear-toggle {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
width: 100%;
|
|
padding: 0.75rem 0;
|
|
background: none;
|
|
border: none;
|
|
border-bottom: 1px solid var(--border-color);
|
|
cursor: pointer;
|
|
text-align: left;
|
|
font-family: inherit;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.interlinear-toggle:hover {
|
|
background: var(--code-bg);
|
|
}
|
|
|
|
.interlinear-toggle:focus {
|
|
outline: 2px solid var(--link-color);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.toggle-icon {
|
|
font-size: 0.8rem;
|
|
color: var(--text-secondary);
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.interlinear-toggle.expanded .toggle-icon {
|
|
transform: rotate(0deg);
|
|
}
|
|
|
|
.toggle-hint {
|
|
font-size: 0.85rem;
|
|
color: var(--text-tertiary);
|
|
font-weight: normal;
|
|
}
|
|
|
|
.interlinear-heading {
|
|
font-size: 1.4rem;
|
|
font-weight: 600;
|
|
margin: 0;
|
|
color: var(--text-color);
|
|
text-transform: none;
|
|
letter-spacing: normal;
|
|
}
|
|
|
|
.interlinear-flow {
|
|
line-height: 1.4;
|
|
font-size: 1rem;
|
|
margin: 0.75rem 0;
|
|
padding: 0.5rem 0;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem 0.25rem;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.word-unit {
|
|
display: inline-flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
text-align: center;
|
|
gap: 0.1rem;
|
|
cursor: pointer;
|
|
position: relative;
|
|
padding: 0.3rem 0.5rem;
|
|
border-radius: 4px;
|
|
transition: all 0.15s ease;
|
|
border: 1px solid transparent;
|
|
vertical-align: top;
|
|
}
|
|
|
|
.word-unit:hover {
|
|
background: #f8f9fa;
|
|
border-color: #e0e0e0;
|
|
}
|
|
|
|
.word-unit.expanded {
|
|
background: #f0f7f4;
|
|
border-color: #4a7c59;
|
|
z-index: 101;
|
|
}
|
|
|
|
.word-original {
|
|
font-size: 1.6rem;
|
|
font-weight: 400;
|
|
color: #222;
|
|
line-height: 1.3;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
/* Hebrew text (RTL) */
|
|
.word-original.hebrew {
|
|
direction: rtl;
|
|
font-family: "SBL Hebrew", "Ezra SIL", "Times New Roman", "Noto Serif Hebrew", serif;
|
|
font-size: 1.8rem;
|
|
}
|
|
|
|
/* Greek text */
|
|
.word-original.greek {
|
|
font-family: "SBL Greek", "Gentium Plus", "Times New Roman", "Noto Serif", serif;
|
|
font-size: 1.6rem;
|
|
}
|
|
|
|
.word-unit:hover .word-original {
|
|
color: #111;
|
|
}
|
|
|
|
.word-english {
|
|
font-size: 0.7rem;
|
|
color: #4a7c59;
|
|
font-weight: 500;
|
|
line-height: 1.2;
|
|
max-width: 100px;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
.word-strongs {
|
|
font-size: 0.6rem;
|
|
color: #888;
|
|
font-family: monospace;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
|
|
.word-strongs a {
|
|
color: inherit;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.word-strongs a:hover {
|
|
color: #4a7c59;
|
|
}
|
|
|
|
/* Hover tooltip - full definition preview */
|
|
.word-tooltip {
|
|
position: absolute;
|
|
bottom: 100%;
|
|
left: 50%;
|
|
transform: translateX(-50%) translateY(-4px);
|
|
background: #333;
|
|
color: #fff;
|
|
padding: 0.65rem 1rem;
|
|
border-radius: 4px;
|
|
font-size: 0.95rem;
|
|
white-space: normal;
|
|
max-width: 380px;
|
|
min-width: 200px;
|
|
text-align: center;
|
|
line-height: 1.5;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.15s, transform 0.15s;
|
|
z-index: 999999999;
|
|
}
|
|
|
|
.word-tooltip::after {
|
|
content: "";
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
border: 5px solid transparent;
|
|
border-top-color: #333;
|
|
}
|
|
|
|
.word-unit:hover .word-tooltip {
|
|
opacity: 1;
|
|
transform: translateX(-50%) translateY(-8px);
|
|
}
|
|
|
|
.word-unit.expanded .word-tooltip {
|
|
opacity: 0;
|
|
}
|
|
|
|
/* Full detail panel */
|
|
.word-detail {
|
|
display: none;
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 50%;
|
|
transform: translateX(-50%) translateY(8px);
|
|
background: #fff;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
padding: 1rem 1.25rem;
|
|
min-width: 260px;
|
|
max-width: 320px;
|
|
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
|
|
z-index: 100;
|
|
text-align: left;
|
|
line-height: 1.5;
|
|
opacity: 0;
|
|
transition: opacity 0.2s, transform 0.2s;
|
|
}
|
|
|
|
.word-unit.expanded .word-detail {
|
|
display: block;
|
|
opacity: 1;
|
|
transform: translateX(-50%) translateY(12px);
|
|
}
|
|
|
|
.word-detail-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
margin-bottom: 0.75rem;
|
|
padding-bottom: 0.75rem;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.word-detail-original {
|
|
font-size: 2.2rem;
|
|
font-weight: 400;
|
|
color: #222;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.word-detail-original.hebrew {
|
|
direction: rtl;
|
|
font-family: "SBL Hebrew", "Ezra SIL", "Times New Roman", "Noto Serif Hebrew", serif;
|
|
font-size: 2.4rem;
|
|
}
|
|
|
|
.word-detail-original.greek {
|
|
font-family: "SBL Greek", "Gentium Plus", "Times New Roman", "Noto Serif", serif;
|
|
font-size: 2.2rem;
|
|
}
|
|
|
|
.word-detail-english {
|
|
font-size: 1rem;
|
|
color: #4a7c59;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.word-detail-row {
|
|
display: flex;
|
|
margin-bottom: 0.35rem;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.word-detail-row:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.word-detail-label {
|
|
color: #888;
|
|
min-width: 85px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.word-detail-value {
|
|
color: #333;
|
|
}
|
|
|
|
.word-detail-definition {
|
|
margin-top: 0.75rem;
|
|
padding-top: 0.75rem;
|
|
border-top: 1px solid #eee;
|
|
font-size: 0.85rem;
|
|
color: #555;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.interlinear-link {
|
|
display: inline-block;
|
|
margin-top: 0.5rem;
|
|
font-size: 0.9rem;
|
|
color: #4a7c59;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.interlinear-link:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* Dark mode for interlinear */
|
|
[data-theme="dark"] .word-original {
|
|
color: #eee;
|
|
}
|
|
|
|
[data-theme="dark"] .word-unit:hover .word-original {
|
|
color: #fff;
|
|
}
|
|
|
|
[data-theme="dark"] .word-unit:hover {
|
|
background: #2a2a2a;
|
|
border-color: #444;
|
|
}
|
|
|
|
[data-theme="dark"] .word-unit.expanded {
|
|
background: #1a2e1a;
|
|
border-color: #4a7c59;
|
|
}
|
|
|
|
[data-theme="dark"] .word-detail {
|
|
background: #222;
|
|
border-color: #444;
|
|
}
|
|
|
|
[data-theme="dark"] .word-detail-original {
|
|
color: #eee;
|
|
}
|
|
|
|
[data-theme="dark"] .word-detail-value {
|
|
color: #ddd;
|
|
}
|
|
|
|
[data-theme="dark"] .word-detail-definition {
|
|
color: #bbb;
|
|
border-top-color: #444;
|
|
}
|
|
|
|
[data-theme="dark"] .word-detail-header {
|
|
border-bottom-color: #444;
|
|
}
|
|
|
|
[data-theme="dark"] .interlinear-heading {
|
|
color: #ddd;
|
|
border-bottom-color: #444;
|
|
}
|
|
|
|
/* Only apply OS dark mode when explicit theme is not set to light */
|
|
@media (prefers-color-scheme: dark) {
|
|
html:not([data-theme="light"]) .word-original {
|
|
color: #eee;
|
|
}
|
|
|
|
html:not([data-theme="light"]) .word-unit:hover .word-original {
|
|
color: #fff;
|
|
}
|
|
|
|
html:not([data-theme="light"]) .word-unit:hover {
|
|
background: #2a2a2a;
|
|
border-color: #444;
|
|
}
|
|
|
|
html:not([data-theme="light"]) .word-unit.expanded {
|
|
background: #1a2e1a;
|
|
border-color: #4a7c59;
|
|
}
|
|
|
|
html:not([data-theme="light"]) .word-detail {
|
|
background: #222;
|
|
border-color: #444;
|
|
}
|
|
|
|
html:not([data-theme="light"]) .word-detail-original {
|
|
color: #eee;
|
|
}
|
|
|
|
html:not([data-theme="light"]) .word-detail-value {
|
|
color: #ddd;
|
|
}
|
|
|
|
html:not([data-theme="light"]) .word-detail-definition {
|
|
color: #bbb;
|
|
border-top-color: #444;
|
|
}
|
|
|
|
html:not([data-theme="light"]) .word-detail-header {
|
|
border-bottom-color: #444;
|
|
}
|
|
|
|
html:not([data-theme="light"]) .interlinear-heading {
|
|
color: #ddd;
|
|
border-bottom-color: #444;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
p.verse-text {
|
|
font-size: 2.2rem !important;
|
|
line-height: 3rem !important;
|
|
margin: 1.5rem 0;
|
|
}
|
|
|
|
.share-container {
|
|
max-width: 100%;
|
|
padding: 1rem 0;
|
|
}
|
|
|
|
.share-buttons {
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.share-btn {
|
|
padding: 0.75rem 1rem;
|
|
font-size: 0.9rem;
|
|
min-height: 44px;
|
|
flex: 1;
|
|
justify-content: center;
|
|
}
|
|
|
|
.cross-ref-tooltip {
|
|
display: none;
|
|
}
|
|
|
|
.interlinear-flow {
|
|
line-height: 1.4;
|
|
gap: 0.75rem 0.5rem;
|
|
}
|
|
|
|
.word-unit {
|
|
min-width: 60px;
|
|
padding: 0.5rem 0.6rem;
|
|
}
|
|
|
|
.word-original {
|
|
font-size: 1.8rem;
|
|
}
|
|
|
|
.word-original.hebrew {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.word-original.greek {
|
|
font-size: 1.8rem;
|
|
}
|
|
|
|
.word-tooltip {
|
|
display: none;
|
|
}
|
|
|
|
.word-detail {
|
|
position: fixed;
|
|
top: auto;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
transform: none;
|
|
max-width: none;
|
|
border-radius: 16px 16px 0 0;
|
|
padding: 1.5rem;
|
|
box-shadow: 0 -4px 24px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
.word-unit.expanded .word-detail {
|
|
transform: none;
|
|
}
|
|
}
|
|
|
|
@media print {
|
|
.share-container,
|
|
.chapter-actions,
|
|
nav {
|
|
display: none !important;
|
|
}
|
|
|
|
.verse-text {
|
|
max-width: 100% !important;
|
|
}
|
|
|
|
section {
|
|
page-break-inside: avoid;
|
|
}
|
|
|
|
.sidenote,
|
|
.marginnote {
|
|
display: block;
|
|
float: none;
|
|
width: 100%;
|
|
margin: 0.5rem 0;
|
|
font-size: 0.9rem;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.interlinear-container,
|
|
.word-tooltip,
|
|
.word-detail {
|
|
display: none !important;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<h1>{{ book }} {{ chapter }}:{{ verse_num }}</h1>
|
|
<p class="subtitle"><a href="/books">Authorized King James Version</a></p>
|
|
|
|
<div class="chapter-actions">
|
|
<button class="action-btn" id="listen-btn" aria-label="Listen to verse and commentary">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
|
|
</svg>
|
|
Listen
|
|
</button>
|
|
{% if pdf_available and pdf_url %}
|
|
<a href="{{ pdf_url }}" class="action-btn pdf-btn">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
PDF
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<section>
|
|
<p class="verse-text">{{ verse_text | format_lists | red_letter(book, chapter, verse_num) | safe }}</p>
|
|
|
|
{% if interlinear_words %}
|
|
<section class="interlinear-container" id="interlinear">
|
|
<h3 class="interlinear-heading">Original Language Analysis</h3>
|
|
<div class="interlinear-flow">
|
|
{% for word in interlinear_words %}
|
|
<div class="word-unit" onclick="toggleWord(this)">
|
|
<span class="word-original {% if is_old_testament %}hebrew{% else %}greek{% endif %}">{{ word.original }}</span>
|
|
<span class="word-english">{{ word.english }}</span>
|
|
<span class="word-strongs">
|
|
{% if word.strongs %}
|
|
<a href="/strongs/{{ word.strongs }}" onclick="event.stopPropagation()">{{ word.strongs }}</a>
|
|
{% endif %}
|
|
</span>
|
|
<div class="word-detail">
|
|
<div class="word-detail-header">
|
|
<span class="word-detail-original {% if is_old_testament %}hebrew{% else %}greek{% endif %}">{{ word.original }}</span>
|
|
<span class="word-detail-english">{{ word.english }}</span>
|
|
</div>
|
|
{% if word.transliteration %}
|
|
<div class="word-detail-row">
|
|
<span class="word-detail-label">Pronunciation:</span>
|
|
<span class="word-detail-value">{{ word.transliteration }}</span>
|
|
</div>
|
|
{% endif %}
|
|
<div class="word-detail-row">
|
|
<span class="word-detail-label">Strong's:</span>
|
|
<span class="word-detail-value">
|
|
{% if word.strongs %}
|
|
<a href="/strongs/{{ word.strongs }}" style="color: #4a7c59;">{{ word.strongs }}</a>
|
|
{% else %}
|
|
—
|
|
{% endif %}
|
|
</span>
|
|
</div>
|
|
{% if word.parsing %}
|
|
<div class="word-detail-row">
|
|
<span class="word-detail-label">Grammar:</span>
|
|
<span class="word-detail-value">{{ word.parsing }}</span>
|
|
</div>
|
|
{% endif %}
|
|
<div class="word-detail-row">
|
|
<span class="word-detail-label">Word #:</span>
|
|
<span class="word-detail-value">{{ word.position }} of {{ interlinear_words|length }}</span>
|
|
</div>
|
|
{% if word.definition %}
|
|
<div class="word-detail-definition">
|
|
{{ word.definition }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
<script>
|
|
function toggleWord(el) {
|
|
document.querySelectorAll('.word-unit.expanded').forEach(function(word) {
|
|
if (word !== el) word.classList.remove('expanded');
|
|
});
|
|
el.classList.toggle('expanded');
|
|
}
|
|
document.addEventListener('click', function(e) {
|
|
if (!e.target.closest('.word-unit')) {
|
|
document.querySelectorAll('.word-unit.expanded').forEach(function(word) {
|
|
word.classList.remove('expanded');
|
|
});
|
|
}
|
|
});
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
document.querySelectorAll('.word-unit.expanded').forEach(function(word) {
|
|
word.classList.remove('expanded');
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{% endif %}
|
|
|
|
<nav class="verse-nav-pills">
|
|
<a href="/book/{{ book }}" class="nav-pill">← {{ book }}</a>
|
|
<a href="/book/{{ book }}/chapter/{{ chapter }}#verse-{{ verse_num }}" class="nav-pill">Read in Chapter {{ chapter }}</a>
|
|
{% if verse_num > 1 %}<a href="/book/{{ book }}/chapter/{{ chapter }}/verse/{{ verse_num - 1 }}" class="nav-pill">← Verse {{ verse_num - 1 }}</a>{% endif %}
|
|
{% if verse_num < total_verses %}<a href="/book/{{ book }}/chapter/{{ chapter }}/verse/{{ verse_num + 1 }}" class="nav-pill">Verse {{ verse_num + 1 }} →</a>{% endif %}
|
|
</nav>
|
|
|
|
{% if cross_references %}
|
|
<div class="cross-references-section" id="cross-references">
|
|
<h3 class="section-heading">Cross References</h3>
|
|
<div class="crossrefs-compact">
|
|
{% for ref in cross_references %}
|
|
{%- set ref_parts = ref.ref.rsplit(' ', 1) -%}
|
|
{%- if ref_parts|length == 2 -%}
|
|
{%- set book_name = ref_parts[0] -%}
|
|
{%- set chapter_verse = ref_parts[1] -%}
|
|
{%- if ':' in chapter_verse -%}
|
|
{%- set ch = chapter_verse.split(':')[0] -%}
|
|
{%- set v = chapter_verse.split(':')[1] -%}
|
|
<a href="/book/{{ book_name }}/chapter/{{ ch }}/verse/{{ v }}" class="crossref-pill cross-ref-link">{{ ref.ref }}{% if ref.text %}<span class="cross-ref-tooltip">{{ ref.text }}</span>{% endif %}</a>
|
|
{%- else -%}
|
|
<span class="crossref-pill">{{ ref.ref }}</span>
|
|
{%- endif -%}
|
|
{%- else -%}
|
|
<span class="crossref-pill">{{ ref.ref }}</span>
|
|
{%- endif -%}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<script>
|
|
function toggleSection(sectionId) {
|
|
var contentId = sectionId + '-content';
|
|
var content = document.getElementById(contentId);
|
|
var toggle = content.previousElementSibling;
|
|
while (toggle && !toggle.classList.contains('section-toggle')) {
|
|
toggle = toggle.previousElementSibling;
|
|
}
|
|
if (!toggle) return;
|
|
|
|
var icon = toggle.querySelector('.toggle-icon');
|
|
var isExpanded = toggle.getAttribute('aria-expanded') === 'true';
|
|
|
|
if (isExpanded) {
|
|
content.hidden = true;
|
|
toggle.setAttribute('aria-expanded', 'false');
|
|
icon.textContent = '▶';
|
|
toggle.classList.remove('expanded');
|
|
localStorage.setItem(sectionId + '-expanded', 'false');
|
|
} else {
|
|
content.hidden = false;
|
|
toggle.setAttribute('aria-expanded', 'true');
|
|
icon.textContent = '▼';
|
|
toggle.classList.add('expanded');
|
|
localStorage.setItem(sectionId + '-expanded', 'true');
|
|
}
|
|
}
|
|
|
|
</script>
|
|
</section>
|
|
|
|
{% if commentary %}
|
|
<div id="commentary"></div>
|
|
{% if commentary.analysis %}
|
|
<div class="commentary-section" id="analysis">
|
|
<h2>Analysis & Commentary</h2>
|
|
{{ commentary.analysis|split_paragraphs|safe }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if commentary.historical %}
|
|
<div class="commentary-section" id="historical">
|
|
<h2>Historical Context</h2>
|
|
{{ commentary.historical|split_paragraphs|safe }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if commentary.theological %}
|
|
<div class="commentary-section" id="theological">
|
|
<h2>Theological Significance</h2>
|
|
{{ commentary.theological|split_paragraphs|safe }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if commentary.questions %}
|
|
<div class="commentary-section" id="questions">
|
|
<h2>Questions for Reflection</h2>
|
|
<ul>
|
|
{% for question in commentary.questions %}
|
|
<li>{{ question }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
<!-- Keyboard Navigation - Paragraph-based with drill-down -->
|
|
<script>
|
|
(function() {
|
|
// Collect all readable elements: paragraphs, list items, cross-ref items
|
|
function getElements() {
|
|
return Array.from(document.querySelectorAll(
|
|
'section > p.verse-text, ' +
|
|
'.interlinear-container, ' +
|
|
'.commentary-section p, ' +
|
|
'.commentary-section li'
|
|
)).filter(function(el) {
|
|
// Filter out empty or very short elements, and UI elements
|
|
if (el.closest('.word-detail') || el.closest('.share-container') || el.closest('nav')) return false;
|
|
// Skip elements inside collapsed sections
|
|
var hiddenParent = el.closest('[hidden]');
|
|
if (hiddenParent) return false;
|
|
return el.textContent.trim().length > 10 || el.classList.contains('interlinear-container');
|
|
});
|
|
}
|
|
var elements = getElements();
|
|
|
|
var selectedIndex = -1;
|
|
var inWordMode = false;
|
|
var selectedWordIndex = -1;
|
|
|
|
function getWords() {
|
|
var container = document.querySelector('.interlinear-container');
|
|
if (!container) return [];
|
|
return Array.from(container.querySelectorAll('.word-unit'));
|
|
}
|
|
|
|
function clearWordSelection() {
|
|
document.querySelectorAll('.word-unit.keyboard-selected').forEach(function(word) {
|
|
word.classList.remove('keyboard-selected');
|
|
word.style.outline = '';
|
|
word.style.outlineOffset = '';
|
|
});
|
|
document.querySelectorAll('.word-unit.expanded').forEach(function(word) {
|
|
word.classList.remove('expanded');
|
|
});
|
|
selectedWordIndex = -1;
|
|
inWordMode = false;
|
|
}
|
|
|
|
function clearSelection() {
|
|
if (selectedIndex >= 0 && selectedIndex < elements.length) {
|
|
elements[selectedIndex].style.outline = '';
|
|
elements[selectedIndex].style.outlineOffset = '';
|
|
elements[selectedIndex].classList.remove('selected');
|
|
}
|
|
clearWordSelection();
|
|
}
|
|
|
|
function selectElement(index) {
|
|
clearSelection();
|
|
// Refresh elements list in case sections were toggled
|
|
elements = getElements();
|
|
if (elements.length === 0) return;
|
|
selectedIndex = Math.max(0, Math.min(index, elements.length - 1));
|
|
elements[selectedIndex].style.outline = '2px solid #4a7c59';
|
|
elements[selectedIndex].style.outlineOffset = '4px';
|
|
elements[selectedIndex].classList.add('selected');
|
|
elements[selectedIndex].scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
}
|
|
|
|
function selectWord(index) {
|
|
var words = getWords();
|
|
if (words.length === 0) return;
|
|
|
|
// Clear previous word selection
|
|
document.querySelectorAll('.word-unit.keyboard-selected').forEach(function(word) {
|
|
word.classList.remove('keyboard-selected');
|
|
word.style.outline = '';
|
|
word.style.outlineOffset = '';
|
|
});
|
|
|
|
selectedWordIndex = Math.max(0, Math.min(index, words.length - 1));
|
|
var word = words[selectedWordIndex];
|
|
word.classList.add('keyboard-selected');
|
|
word.style.outline = '2px solid #4a7c59';
|
|
word.style.outlineOffset = '2px';
|
|
word.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
}
|
|
|
|
// Grid navigation helpers - find words in same column on different rows
|
|
function getWordRows() {
|
|
var words = getWords();
|
|
if (words.length === 0) return [];
|
|
|
|
var rows = [];
|
|
var currentRow = [];
|
|
var lastTop = null;
|
|
|
|
words.forEach(function(word, i) {
|
|
var rect = word.getBoundingClientRect();
|
|
var top = Math.round(rect.top);
|
|
|
|
if (lastTop === null || Math.abs(top - lastTop) < 10) {
|
|
currentRow.push({ element: word, index: i, left: rect.left, width: rect.width });
|
|
} else {
|
|
if (currentRow.length > 0) rows.push(currentRow);
|
|
currentRow = [{ element: word, index: i, left: rect.left, width: rect.width }];
|
|
}
|
|
lastTop = top;
|
|
});
|
|
if (currentRow.length > 0) rows.push(currentRow);
|
|
|
|
return rows;
|
|
}
|
|
|
|
function findWordInDirection(currentIndex, direction) {
|
|
var words = getWords();
|
|
if (words.length === 0 || currentIndex < 0) return currentIndex;
|
|
|
|
var rows = getWordRows();
|
|
if (rows.length === 0) return currentIndex;
|
|
|
|
// Find which row and position current word is in
|
|
var currentRowIndex = -1;
|
|
var currentColIndex = -1;
|
|
var currentLeft = 0;
|
|
var currentWidth = 0;
|
|
|
|
for (var r = 0; r < rows.length; r++) {
|
|
for (var c = 0; c < rows[r].length; c++) {
|
|
if (rows[r][c].index === currentIndex) {
|
|
currentRowIndex = r;
|
|
currentColIndex = c;
|
|
currentLeft = rows[r][c].left;
|
|
currentWidth = rows[r][c].width;
|
|
break;
|
|
}
|
|
}
|
|
if (currentRowIndex >= 0) break;
|
|
}
|
|
|
|
if (currentRowIndex < 0) return currentIndex;
|
|
|
|
// Find target row
|
|
var targetRowIndex = currentRowIndex + direction;
|
|
if (targetRowIndex < 0 || targetRowIndex >= rows.length) {
|
|
return -1; // Signal to exit word mode
|
|
}
|
|
|
|
// Find closest word in target row by horizontal position
|
|
var targetRow = rows[targetRowIndex];
|
|
var currentCenter = currentLeft + currentWidth / 2;
|
|
var bestMatch = targetRow[0];
|
|
var bestDistance = Infinity;
|
|
|
|
for (var i = 0; i < targetRow.length; i++) {
|
|
var wordCenter = targetRow[i].left + targetRow[i].width / 2;
|
|
var distance = Math.abs(wordCenter - currentCenter);
|
|
if (distance < bestDistance) {
|
|
bestDistance = distance;
|
|
bestMatch = targetRow[i];
|
|
}
|
|
}
|
|
|
|
return bestMatch.index;
|
|
}
|
|
|
|
function isInterlinearSelected() {
|
|
return selectedIndex >= 0 && elements[selectedIndex] &&
|
|
elements[selectedIndex].classList.contains('interlinear-container');
|
|
}
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') {
|
|
return;
|
|
}
|
|
// Don't handle if sidebar navigation is active
|
|
if (KJVNav.sidebarActive) return;
|
|
|
|
// Escape: Exit word mode or clear selection
|
|
if (e.key === 'Escape') {
|
|
e.preventDefault();
|
|
if (inWordMode) {
|
|
clearWordSelection();
|
|
// Re-highlight the interlinear container
|
|
if (isInterlinearSelected()) {
|
|
elements[selectedIndex].style.outline = '2px solid #4a7c59';
|
|
elements[selectedIndex].style.outlineOffset = '4px';
|
|
}
|
|
} else {
|
|
clearSelection();
|
|
selectedIndex = -1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// In word mode - navigate words in grid pattern
|
|
if (inWordMode) {
|
|
var words = getWords();
|
|
if (e.key === 'ArrowRight' || e.key === 'l') {
|
|
e.preventDefault();
|
|
if (selectedWordIndex < words.length - 1) {
|
|
selectWord(selectedWordIndex + 1);
|
|
}
|
|
} else if (e.key === 'ArrowLeft' || e.key === 'h') {
|
|
e.preventDefault();
|
|
if (selectedWordIndex > 0) {
|
|
selectWord(selectedWordIndex - 1);
|
|
}
|
|
} else if (e.key === 'ArrowDown' || e.key === 'j') {
|
|
e.preventDefault();
|
|
// Navigate to word below in grid
|
|
var targetIndex = findWordInDirection(selectedWordIndex, 1);
|
|
if (targetIndex === -1) {
|
|
// No row below, exit word mode and go to next element
|
|
clearWordSelection();
|
|
selectElement(selectedIndex + 1);
|
|
} else {
|
|
selectWord(targetIndex);
|
|
}
|
|
} else if (e.key === 'ArrowUp' || e.key === 'k') {
|
|
e.preventDefault();
|
|
// Navigate to word above in grid
|
|
var targetIndex = findWordInDirection(selectedWordIndex, -1);
|
|
if (targetIndex === -1) {
|
|
// No row above, exit word mode
|
|
clearWordSelection();
|
|
if (isInterlinearSelected()) {
|
|
elements[selectedIndex].style.outline = '2px solid #4a7c59';
|
|
elements[selectedIndex].style.outlineOffset = '4px';
|
|
}
|
|
} else {
|
|
selectWord(targetIndex);
|
|
}
|
|
} else if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
// Toggle word expansion
|
|
if (selectedWordIndex >= 0 && words[selectedWordIndex]) {
|
|
document.querySelectorAll('.word-unit.expanded').forEach(function(word) {
|
|
if (word !== words[selectedWordIndex]) {
|
|
word.classList.remove('expanded');
|
|
}
|
|
});
|
|
words[selectedWordIndex].classList.toggle('expanded');
|
|
}
|
|
} else if (e.key === 'Escape') {
|
|
e.preventDefault();
|
|
clearWordSelection();
|
|
if (isInterlinearSelected()) {
|
|
elements[selectedIndex].style.outline = '2px solid #4a7c59';
|
|
elements[selectedIndex].style.outlineOffset = '4px';
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Normal navigation mode
|
|
if (e.key === 'ArrowDown' || e.key === 'j') {
|
|
e.preventDefault();
|
|
if (KJVNav.isSelectionOffScreen(elements, selectedIndex)) {
|
|
selectElement(KJVNav.findFirstVisibleIndex(elements));
|
|
} else {
|
|
selectElement(selectedIndex < 0 ? 0 : selectedIndex + 1);
|
|
}
|
|
} else if (e.key === 'ArrowUp' || e.key === 'k') {
|
|
e.preventDefault();
|
|
if (KJVNav.isSelectionOffScreen(elements, selectedIndex)) {
|
|
selectElement(KJVNav.findFirstVisibleIndex(elements));
|
|
} else if (selectedIndex <= 0) {
|
|
selectElement(0);
|
|
} else {
|
|
selectElement(selectedIndex - 1);
|
|
}
|
|
} else if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
// If on interlinear, toggle expand or enter word mode
|
|
if (isInterlinearSelected()) {
|
|
var toggle = document.querySelector('.interlinear-toggle');
|
|
var content = document.getElementById('interlinear-content');
|
|
if (content && content.hidden) {
|
|
// First expand the section
|
|
toggleInterlinear();
|
|
} else {
|
|
// Already expanded, enter word mode
|
|
inWordMode = true;
|
|
elements[selectedIndex].style.outline = '';
|
|
selectWord(0);
|
|
}
|
|
} else if (selectedIndex >= 0) {
|
|
// If on a cross-reference, navigate to it
|
|
var link = elements[selectedIndex].querySelector('a');
|
|
if (link) window.location.href = link.href;
|
|
}
|
|
} else if (e.key === 'ArrowLeft' || e.key === 'h') {
|
|
e.preventDefault();
|
|
history.back();
|
|
} else if (e.key === 'ArrowRight' || e.key === 'l') {
|
|
e.preventDefault();
|
|
{% if verse_num < total_verses %}
|
|
window.location.href = '/book/{{ book }}/chapter/{{ chapter }}/verse/{{ verse_num + 1 }}';
|
|
{% endif %}
|
|
} else if (e.key === '[') {
|
|
// Previous verse
|
|
e.preventDefault();
|
|
{% if verse_num > 1 %}
|
|
window.location.href = '/book/{{ book }}/chapter/{{ chapter }}/verse/{{ verse_num - 1 }}';
|
|
{% endif %}
|
|
} else if (e.key === ']') {
|
|
// Next verse
|
|
e.preventDefault();
|
|
{% if verse_num < total_verses %}
|
|
window.location.href = '/book/{{ book }}/chapter/{{ chapter }}/verse/{{ verse_num + 1 }}';
|
|
{% endif %}
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
{% if related_content and (related_content.topics or related_content.people or related_content.resources or related_content.stories) %}
|
|
<div style="border-top: 1px solid var(--border-color); padding-top: 2rem; margin-top: 3rem;">
|
|
<h2>Related Resources</h2>
|
|
<p style="font-size: 0.95rem; color: var(--text-secondary); margin-bottom: 1.5rem;">
|
|
Explore related topics, people, and study resources to deepen your understanding of this passage.
|
|
</p>
|
|
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.5rem;">
|
|
{% if related_content.topics %}
|
|
<div>
|
|
<h3 style="font-size: 1rem; margin-bottom: 0.75rem; color: var(--text-secondary);">Topics</h3>
|
|
<ul style="list-style: none; padding: 0; margin: 0;">
|
|
{% for topic in related_content.topics %}
|
|
<li style="margin-bottom: 0.5rem;">
|
|
<a href="{{ topic.url }}" style="font-size: 0.95rem;">{{ topic.name }}</a>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if related_content.people %}
|
|
<div>
|
|
<h3 style="font-size: 1rem; margin-bottom: 0.75rem; color: var(--text-secondary);">People</h3>
|
|
<ul style="list-style: none; padding: 0; margin: 0;">
|
|
{% for person in related_content.people %}
|
|
<li style="margin-bottom: 0.5rem;">
|
|
<a href="{{ person.url }}" style="font-size: 0.95rem;">{{ person.name }}</a>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if related_content.resources %}
|
|
<div>
|
|
<h3 style="font-size: 1rem; margin-bottom: 0.75rem; color: var(--text-secondary);">Study Resources</h3>
|
|
<ul style="list-style: none; padding: 0; margin: 0;">
|
|
{% for resource in related_content.resources %}
|
|
<li style="margin-bottom: 0.5rem;">
|
|
<a href="{{ resource.url }}" style="font-size: 0.95rem;">{{ resource.name }}</a>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if related_content.stories %}
|
|
<div>
|
|
<h3 style="font-size: 1rem; margin-bottom: 0.75rem; color: var(--text-secondary);">Bible Stories</h3>
|
|
<ul style="list-style: none; padding: 0; margin: 0;">
|
|
{% for story in related_content.stories %}
|
|
<li style="margin-bottom: 0.5rem;">
|
|
<a href="{{ story.url }}" style="font-size: 0.95rem;">{{ story.name }}</a>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<script>
|
|
(function() {
|
|
// Listen button handler with toggle
|
|
var listenBtn = document.getElementById('listen-btn');
|
|
var isListening = false;
|
|
if (listenBtn) {
|
|
listenBtn.addEventListener('click', function() {
|
|
if (isListening) {
|
|
if (window.KJVSpeech) window.KJVSpeech.stop();
|
|
listenBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" /></svg> Listen';
|
|
isListening = false;
|
|
return;
|
|
}
|
|
// Collect verse text and commentary
|
|
var parts = [];
|
|
var verseEl = document.querySelector('.verse-text');
|
|
var verseText = verseEl ? verseEl.textContent.trim() : '';
|
|
if (verseText) parts.push(verseText);
|
|
// Add commentary sections, skipping bold verse text in first paragraph
|
|
var commentaryEls = document.querySelectorAll('.commentary-section p, .commentary-section li');
|
|
var isFirst = true;
|
|
commentaryEls.forEach(function(el) {
|
|
var elText;
|
|
// For first paragraph, skip the bold/strong part (usually contains verse text)
|
|
if (isFirst && el.tagName === 'P') {
|
|
var strong = el.querySelector('strong');
|
|
if (strong && verseText && strong.textContent.trim().substring(0, 40) === verseText.substring(0, 40)) {
|
|
// Clone the element and remove the strong tag to get remaining text
|
|
var clone = el.cloneNode(true);
|
|
var strongInClone = clone.querySelector('strong');
|
|
if (strongInClone) strongInClone.remove();
|
|
elText = clone.textContent.trim();
|
|
} else {
|
|
elText = el.textContent.trim();
|
|
}
|
|
isFirst = false;
|
|
} else {
|
|
elText = el.textContent.trim();
|
|
}
|
|
if (elText) parts.push(elText);
|
|
});
|
|
var text = parts.join(' ');
|
|
if (window.KJVSpeech && text) {
|
|
window.KJVSpeech.speak(text);
|
|
listenBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" /></svg> Stop';
|
|
isListening = true;
|
|
}
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
{% endblock %}
|