mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 23:00:16 +00:00
3336863a4d
- Add KJVNav.initGridNav for standardized 2D grid navigation - Migrate books.html, topics.html, resources.html to use initGridNav - Add sidebarActive check to all templates with custom keyboard handlers - Add [ and ] shortcuts for prev/next chapter on chapter pages - Add [ and ] shortcuts for prev/next book on book pages - Update accessibility page with comprehensive keyboard shortcut docs - Add honest note about keyboard navigation complexity - Fix sidebar nav conflicting with main content selection 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
797 lines
31 KiB
HTML
797 lines
31 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ book }} - KJV Bible{% endblock %}
|
|
|
|
{% block description %}Study the book of {{ book }} from the King James Bible (KJV). {% if book_intro and book_intro.introduction %}{{ book_intro.introduction[:110]|striptags }}...{% elif introduction %}{{ introduction[:110]|striptags }}...{% else %}Complete KJV text with chapters, commentary, and study resources.{% endif %}{% endblock %}
|
|
|
|
{% block head %}
|
|
<style>
|
|
.chapters-section h2 + p a {
|
|
font-size: 1.8rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.chapters-section h2 + p a.selected {
|
|
background: rgba(74, 124, 89, 0.15);
|
|
color: #4a7c59;
|
|
padding: 0.1rem 0.4rem;
|
|
border-radius: 4px;
|
|
outline: 2px solid rgba(74, 124, 89, 0.4);
|
|
}
|
|
|
|
[data-theme="dark"] .chapters-section h2 + p a.selected {
|
|
background: rgba(107, 155, 122, 0.2);
|
|
color: #6b9b7a;
|
|
outline-color: rgba(107, 155, 122, 0.4);
|
|
}
|
|
|
|
/* Selected chapter section (green box) */
|
|
.chapters-section.selected {
|
|
outline: 3px solid #4a7c59;
|
|
outline-offset: 4px;
|
|
background: rgba(74, 124, 89, 0.05);
|
|
padding: 1rem;
|
|
margin-left: -1rem;
|
|
margin-right: -1rem;
|
|
border-radius: 4px;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
[data-theme="dark"] .chapters-section.selected {
|
|
outline-color: #6b9b7a;
|
|
background: rgba(107, 155, 122, 0.1);
|
|
}
|
|
|
|
/* Selected content paragraphs (green box) */
|
|
section:not(.chapters-section) p.selected,
|
|
section:not(.chapters-section) li.selected,
|
|
section:not(.chapters-section) blockquote.selected {
|
|
outline: 3px solid #4a7c59;
|
|
outline-offset: 4px;
|
|
background: rgba(74, 124, 89, 0.05);
|
|
padding: 1rem;
|
|
margin-left: -1rem;
|
|
margin-right: -1rem;
|
|
border-radius: 4px;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
[data-theme="dark"] section:not(.chapters-section) p.selected,
|
|
[data-theme="dark"] section:not(.chapters-section) li.selected,
|
|
[data-theme="dark"] section:not(.chapters-section) blockquote.selected {
|
|
outline-color: #6b9b7a;
|
|
background: rgba(107, 155, 122, 0.1);
|
|
}
|
|
|
|
.popular-chapter {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.nav-hint {
|
|
font-size: 0.85rem;
|
|
color: var(--text-secondary);
|
|
font-style: italic;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.book-meta {
|
|
color: var(--text-secondary, #666);
|
|
font-size: 0.95rem;
|
|
margin-top: -0.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
section blockquote {
|
|
margin: 1rem 0 1.5rem;
|
|
padding-left: 1rem;
|
|
border-left: 3px solid var(--border-color, #ddd);
|
|
}
|
|
|
|
section blockquote p {
|
|
font-style: italic;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
section blockquote footer {
|
|
font-size: 0.9rem;
|
|
color: var(--text-secondary, #666);
|
|
}
|
|
|
|
section blockquote footer em {
|
|
display: block;
|
|
margin-top: 0.25rem;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
section ul li {
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
section ul li strong {
|
|
color: var(--text-color, #111);
|
|
}
|
|
|
|
.book-actions {
|
|
margin: 1.5rem 0 1.5rem;
|
|
}
|
|
|
|
.print-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.5rem 1rem;
|
|
font-size: 0.95rem;
|
|
color: var(--text-secondary, #666);
|
|
background: var(--code-bg, #f8f8f8);
|
|
border: 1px solid var(--border-color, #ddd);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.print-btn:hover {
|
|
background: var(--bg-color, #fff);
|
|
border-color: var(--link-color);
|
|
color: var(--link-color);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.print-btn svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
|
|
@media print {
|
|
.book-actions,
|
|
.print-btn {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const bookName = "{{ book }}";
|
|
|
|
// Known Bible book names and abbreviations for cross-references
|
|
const bibleBooks = [
|
|
'Genesis', 'Exodus', 'Leviticus', 'Numbers', 'Deuteronomy',
|
|
'Joshua', 'Judges', 'Ruth', '1 Samuel', '2 Samuel', '1 Kings', '2 Kings',
|
|
'1 Chronicles', '2 Chronicles', 'Ezra', 'Nehemiah', 'Esther',
|
|
'Job', 'Psalms', 'Psalm', 'Proverbs', 'Ecclesiastes', 'Song of Solomon',
|
|
'Isaiah', 'Jeremiah', 'Lamentations', 'Ezekiel', 'Daniel',
|
|
'Hosea', 'Joel', 'Amos', 'Obadiah', 'Jonah', 'Micah', 'Nahum',
|
|
'Habakkuk', 'Zephaniah', 'Haggai', 'Zechariah', 'Malachi',
|
|
'Matthew', 'Mark', 'Luke', 'John', 'Acts', 'Romans',
|
|
'1 Corinthians', '2 Corinthians', 'Galatians', 'Ephesians', 'Philippians',
|
|
'Colossians', '1 Thessalonians', '2 Thessalonians', '1 Timothy', '2 Timothy',
|
|
'Titus', 'Philemon', 'Hebrews', 'James', '1 Peter', '2 Peter',
|
|
'1 John', '2 John', '3 John', 'Jude', 'Revelation'
|
|
];
|
|
|
|
// Build regex pattern for book names (escaped and sorted by length desc to match longer names first)
|
|
const bookPattern = bibleBooks
|
|
.sort((a, b) => b.length - a.length)
|
|
.map(b => b.replace(/\s+/g, '\\s+'))
|
|
.join('|');
|
|
|
|
// Function to create a link for a verse reference
|
|
// Single verses use /verse/ path (for tooltip support), ranges use #verse- anchor
|
|
function createVerseLink(book, chapter, verseStart, verseEnd, linkText) {
|
|
const normalizedBook = book.replace(/\s+/g, ' ');
|
|
if (verseEnd && verseEnd !== verseStart) {
|
|
return '<a href="/book/' + encodeURIComponent(normalizedBook) + '/chapter/' + chapter + '#verse-' + verseStart + '-' + verseEnd + '">' + linkText + '</a>';
|
|
} else {
|
|
return '<a href="/book/' + encodeURIComponent(normalizedBook) + '/chapter/' + chapter + '/verse/' + verseStart + '">' + linkText + '</a>';
|
|
}
|
|
}
|
|
|
|
// Create a link for cross-chapter ranges (e.g., 2:17-3:5).
|
|
function createCrossChapterRangeLink(book, startChapter, startVerse, endChapter, endVerse, linkText) {
|
|
const normalizedBook = book.replace(/\s+/g, ' ');
|
|
return '<a href="/book/' + encodeURIComponent(normalizedBook) + '/chapter/' + startChapter + '#verse-' + startVerse + '" data-end-chapter="' + endChapter + '" data-end-verse="' + endVerse + '">' + linkText + '</a>';
|
|
}
|
|
|
|
// Function to link verse references in text
|
|
function linkVerseReferences(element) {
|
|
if (!element) return;
|
|
|
|
// Get all text nodes
|
|
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
|
|
const textNodes = [];
|
|
let node;
|
|
while (node = walker.nextNode()) {
|
|
textNodes.push(node);
|
|
}
|
|
|
|
textNodes.forEach(function(textNode) {
|
|
let text = textNode.textContent;
|
|
let changed = false;
|
|
|
|
// Match chapter ranges in parentheses like "(chapters 1-11)", but NOT verse references
|
|
// Only match if there's no colon (which would indicate verses)
|
|
text = text.replace(/\((?:chapters?\s+)?(\d+)(?:-(\d+))?\)(?![^(]*:)/gi, function(match, chapterStart, chapterEnd) {
|
|
// Check if this looks like a verse reference context - skip if so
|
|
if (/\d+:\d+/.test(text.substring(Math.max(0, text.indexOf(match) - 20), text.indexOf(match) + match.length + 20))) {
|
|
return match;
|
|
}
|
|
changed = true;
|
|
if (chapterEnd) {
|
|
const hasChapterWord = /chapters?\s+/i.test(match);
|
|
const prefix = hasChapterWord ? match.match(/chapters?\s+/i)[0] : '';
|
|
return '(<a href="/book/' + bookName + '/chapter/' + chapterStart + '">' + prefix + chapterStart + '-' + chapterEnd + '</a>)';
|
|
} else {
|
|
const hasChapterWord = /chapters?\s+/i.test(match);
|
|
const prefix = hasChapterWord ? match.match(/chapters?\s+/i)[0] : '';
|
|
return '(<a href="/book/' + bookName + '/chapter/' + chapterStart + '">' + prefix + chapterStart + '</a>)';
|
|
}
|
|
});
|
|
|
|
// Build cross-book regex for use in parenthetical processing
|
|
const crossBookRangeRegex = new RegExp('^(' + bookPattern + ')\\s+(\\d+):(\\d+)-(\\d+):(\\d+)$', 'i');
|
|
const crossBookRegex = new RegExp('^(' + bookPattern + ')\\s+(\\d+):(\\d+)(?:-(\\d+))?$', 'i');
|
|
|
|
// Process parenthetical groups containing verse references
|
|
// This handles patterns like "(Romans 5:12-21; 1 Corinthians 15:21-22, 45-49)"
|
|
// and "(27:27-29, 39-40; 48:15-16; 49:1-27)"
|
|
text = text.replace(/\(([^)]+)\)/g, function(match, inner) {
|
|
// Skip if already has anchor tags (already processed)
|
|
if (inner.includes('<a ')) return match;
|
|
|
|
// Check if this contains verse references (has colons with numbers)
|
|
if (!/\d+:\d+/.test(inner)) return match;
|
|
|
|
let currentBook = bookName;
|
|
let currentChapter = null;
|
|
let localChanged = false;
|
|
|
|
// Split by semicolons and commas, preserving delimiters
|
|
const parts = inner.split(/([;,])/);
|
|
let newParts = [];
|
|
|
|
for (let i = 0; i < parts.length; i++) {
|
|
const part = parts[i].trim();
|
|
|
|
// If it's a delimiter, keep it
|
|
if (part === ';' || part === ',') {
|
|
newParts.push(parts[i]); // Keep original spacing
|
|
continue;
|
|
}
|
|
|
|
if (!part) {
|
|
newParts.push(parts[i]);
|
|
continue;
|
|
}
|
|
|
|
// Check for cross-book cross-chapter range like "Malachi 2:17-3:5"
|
|
const crossBookRangeMatch = part.match(crossBookRangeRegex);
|
|
if (crossBookRangeMatch) {
|
|
currentBook = crossBookRangeMatch[1].replace(/\s+/g, ' ');
|
|
currentChapter = crossBookRangeMatch[4];
|
|
localChanged = true;
|
|
newParts.push(createCrossChapterRangeLink(currentBook, crossBookRangeMatch[2], crossBookRangeMatch[3], crossBookRangeMatch[4], crossBookRangeMatch[5], part));
|
|
continue;
|
|
}
|
|
|
|
// Check for cross-book reference like "Romans 5:12-21" or "1 Corinthians 15:21-22"
|
|
const crossBookMatch = part.match(crossBookRegex);
|
|
if (crossBookMatch) {
|
|
currentBook = crossBookMatch[1].replace(/\s+/g, ' ');
|
|
currentChapter = crossBookMatch[2];
|
|
localChanged = true;
|
|
newParts.push(createVerseLink(currentBook, currentChapter, crossBookMatch[3], crossBookMatch[4], part));
|
|
continue;
|
|
}
|
|
|
|
// Check for cross-chapter range within the current book like "2:17-3:5"
|
|
const crossChapterRangeMatch = part.match(/^(\d+):(\d+)-(\d+):(\d+)$/);
|
|
if (crossChapterRangeMatch) {
|
|
currentBook = bookName;
|
|
currentChapter = crossChapterRangeMatch[3];
|
|
localChanged = true;
|
|
newParts.push(createCrossChapterRangeLink(currentBook, crossChapterRangeMatch[1], crossChapterRangeMatch[2], crossChapterRangeMatch[3], crossChapterRangeMatch[4], part));
|
|
continue;
|
|
}
|
|
|
|
// Check for full chapter:verse-verse pattern (current book)
|
|
const fullRangeMatch = part.match(/^(\d+):(\d+)-(\d+)$/);
|
|
if (fullRangeMatch) {
|
|
currentBook = bookName;
|
|
currentChapter = fullRangeMatch[1];
|
|
localChanged = true;
|
|
newParts.push(createVerseLink(currentBook, currentChapter, fullRangeMatch[2], fullRangeMatch[3], part));
|
|
continue;
|
|
}
|
|
|
|
// Check for full chapter:verse pattern (single verse, current book)
|
|
const fullSingleMatch = part.match(/^(\d+):(\d+)$/);
|
|
if (fullSingleMatch) {
|
|
currentBook = bookName;
|
|
currentChapter = fullSingleMatch[1];
|
|
localChanged = true;
|
|
newParts.push(createVerseLink(currentBook, currentChapter, fullSingleMatch[2], null, part));
|
|
continue;
|
|
}
|
|
|
|
// Check for abbreviated verse-verse pattern (inherits book and chapter)
|
|
const abbrevRangeMatch = part.match(/^(\d+)-(\d+)$/);
|
|
if (abbrevRangeMatch && currentChapter) {
|
|
localChanged = true;
|
|
newParts.push(createVerseLink(currentBook, currentChapter, abbrevRangeMatch[1], abbrevRangeMatch[2], part));
|
|
continue;
|
|
}
|
|
|
|
// Check for abbreviated single verse (inherits book and chapter)
|
|
const abbrevSingleMatch = part.match(/^(\d+)$/);
|
|
if (abbrevSingleMatch && currentChapter && parts[i-1] && parts[i-1].trim() === ',') {
|
|
localChanged = true;
|
|
newParts.push(createVerseLink(currentBook, currentChapter, abbrevSingleMatch[1], null, part));
|
|
continue;
|
|
}
|
|
|
|
// Not a verse reference, keep as-is
|
|
newParts.push(parts[i]);
|
|
}
|
|
|
|
if (localChanged) {
|
|
changed = true;
|
|
return '(' + newParts.join('') + ')';
|
|
}
|
|
return match;
|
|
});
|
|
|
|
// Match standalone cross-book cross-chapter ranges outside parentheses
|
|
const standaloneCrossBookRangeRegex = new RegExp('(' + bookPattern + ')\\s+(\\d+):(\\d+)-(\\d+):(\\d+)', 'gi');
|
|
text = text.replace(standaloneCrossBookRangeRegex, function(match, book, startChapter, startVerse, endChapter, endVerse) {
|
|
changed = true;
|
|
return createCrossChapterRangeLink(book, startChapter, startVerse, endChapter, endVerse, match);
|
|
});
|
|
|
|
// Match standalone cross-book verse references outside parentheses (with colon)
|
|
const standaloneCrossBookRegex = new RegExp('(' + bookPattern + ')\\s+(\\d+):(\\d+)(?:-(\\d+))?', 'gi');
|
|
text = text.replace(standaloneCrossBookRegex, function(match, book, chapter, verseStart, verseEnd) {
|
|
changed = true;
|
|
return createVerseLink(book, chapter, verseStart, verseEnd, match);
|
|
});
|
|
|
|
// Match cross-book chapter ranges like "Hebrews 5-7" (no colon = chapters, not verses)
|
|
const crossBookChapterRangeRegex = new RegExp('(' + bookPattern + ')\\s+(\\d+)-(\\d+)(?!:)', 'gi');
|
|
text = text.replace(crossBookChapterRangeRegex, function(match, book, chapterStart, chapterEnd) {
|
|
changed = true;
|
|
const normalizedBook = book.replace(/\s+/g, ' ');
|
|
return '<a href="/book/' + encodeURIComponent(normalizedBook) + '/chapter/' + chapterStart + '">' + match + '</a>';
|
|
});
|
|
|
|
// Match cross-book single chapter like "Hebrews 11" (no colon)
|
|
const crossBookChapterRegex = new RegExp('(' + bookPattern + ')\\s+(\\d+)(?![:\\d-])', 'gi');
|
|
text = text.replace(crossBookChapterRegex, function(match, book, chapter) {
|
|
changed = true;
|
|
const normalizedBook = book.replace(/\s+/g, ' ');
|
|
return '<a href="/book/' + encodeURIComponent(normalizedBook) + '/chapter/' + chapter + '">' + match + '</a>';
|
|
});
|
|
|
|
// Match remaining standalone cross-chapter ranges like "2:17-3:5"
|
|
text = text.replace(/(?<![>\d])(\d+):(\d+)-(\d+):(\d+)(?![<\d])/g, function(match, startChapter, startVerse, endChapter, endVerse) {
|
|
changed = true;
|
|
return createCrossChapterRangeLink(bookName, startChapter, startVerse, endChapter, endVerse, match);
|
|
});
|
|
|
|
// Match remaining standalone verse ranges like "1:1-5" not in parentheses
|
|
text = text.replace(/(?<![>\d])(\d+):(\d+)-(\d+)(?![<\d])/g, function(match, chapter, verseStart, verseEnd) {
|
|
changed = true;
|
|
return createVerseLink(bookName, chapter, verseStart, verseEnd, match);
|
|
});
|
|
|
|
// Match remaining standalone single verses like "1:1" not in parentheses
|
|
text = text.replace(/(?<![>\d])(\d+):(\d+)(?![-<\d])/g, function(match, chapter, verse) {
|
|
changed = true;
|
|
return createVerseLink(bookName, chapter, verse, null, match);
|
|
});
|
|
|
|
if (changed) {
|
|
const span = document.createElement('span');
|
|
span.innerHTML = text;
|
|
textNode.parentNode.replaceChild(span, textNode);
|
|
// Replace the span's children with its contents
|
|
while (span.firstChild) {
|
|
span.parentNode.insertBefore(span.firstChild, span);
|
|
}
|
|
span.parentNode.removeChild(span);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Link verse references in all sections
|
|
document.querySelectorAll('section').forEach(function(section) {
|
|
linkVerseReferences(section);
|
|
});
|
|
|
|
// Navigation with chapter drilldown
|
|
const chapterSection = document.querySelector('.chapters-section');
|
|
const chapterLinks = document.querySelectorAll('.chapters-section a[data-chapter]');
|
|
// Select paragraphs, list items, and blockquotes, but exclude paragraphs inside blockquotes
|
|
const contentParagraphs = document.querySelectorAll('section:not(.chapters-section) > p, section:not(.chapters-section) li, section:not(.chapters-section) blockquote');
|
|
|
|
let selectedChapterIndex = -1;
|
|
let selectedParagraphIndex = -1;
|
|
let chapterSectionSelected = false;
|
|
let inChapterDrilldown = false; // Are we drilling down into individual chapters?
|
|
|
|
// Get current book index for left/right navigation
|
|
const currentBookIndex = bibleBooks.findIndex(b => b.toLowerCase() === bookName.toLowerCase());
|
|
|
|
function clearAllSelections() {
|
|
if (chapterSection) {
|
|
chapterSection.classList.remove('selected');
|
|
}
|
|
if (selectedChapterIndex >= 0 && selectedChapterIndex < chapterLinks.length) {
|
|
chapterLinks[selectedChapterIndex].classList.remove('selected');
|
|
}
|
|
if (selectedParagraphIndex >= 0 && selectedParagraphIndex < contentParagraphs.length) {
|
|
contentParagraphs[selectedParagraphIndex].classList.remove('selected');
|
|
}
|
|
}
|
|
|
|
function selectChapterSection() {
|
|
clearAllSelections();
|
|
chapterSectionSelected = true;
|
|
inChapterDrilldown = false;
|
|
selectedChapterIndex = -1;
|
|
selectedParagraphIndex = -1;
|
|
|
|
if (chapterSection) {
|
|
chapterSection.classList.add('selected');
|
|
chapterSection.scrollIntoView({
|
|
behavior: 'auto',
|
|
block: 'center'
|
|
});
|
|
}
|
|
}
|
|
|
|
function selectChapter(index) {
|
|
clearAllSelections();
|
|
chapterSectionSelected = false;
|
|
inChapterDrilldown = true;
|
|
selectedParagraphIndex = -1;
|
|
|
|
// Update index with bounds checking
|
|
selectedChapterIndex = Math.max(0, Math.min(index, chapterLinks.length - 1));
|
|
|
|
// Add selection to new chapter
|
|
chapterLinks[selectedChapterIndex].classList.add('selected');
|
|
|
|
// Scroll into view if needed
|
|
chapterLinks[selectedChapterIndex].scrollIntoView({
|
|
behavior: 'auto',
|
|
block: 'nearest'
|
|
});
|
|
}
|
|
|
|
function selectParagraph(index) {
|
|
clearAllSelections();
|
|
chapterSectionSelected = false;
|
|
inChapterDrilldown = false;
|
|
selectedChapterIndex = -1;
|
|
|
|
// Update index with bounds checking
|
|
selectedParagraphIndex = Math.max(0, Math.min(index, contentParagraphs.length - 1));
|
|
|
|
// Add selection to new paragraph
|
|
contentParagraphs[selectedParagraphIndex].classList.add('selected');
|
|
|
|
// Scroll into view if needed
|
|
contentParagraphs[selectedParagraphIndex].scrollIntoView({
|
|
behavior: 'auto',
|
|
block: 'center'
|
|
});
|
|
}
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
// Don't trigger if user is typing
|
|
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;
|
|
|
|
// Up arrow or k: Previous item
|
|
if (e.key === 'ArrowUp' || e.key === 'k') {
|
|
e.preventDefault();
|
|
if (inChapterDrilldown) {
|
|
// Navigating within chapter numbers
|
|
if (selectedChapterIndex > 0) {
|
|
selectChapter(selectedChapterIndex - 1);
|
|
} else {
|
|
// Exit drilldown, go back to chapter section
|
|
selectChapterSection();
|
|
}
|
|
} else if (chapterSectionSelected) {
|
|
// At chapter section, can't go up (it's the first item)
|
|
// Stay here
|
|
} else if (selectedParagraphIndex >= 0) {
|
|
// In paragraph zone
|
|
if (selectedParagraphIndex === 0) {
|
|
// Move back to chapter section
|
|
selectChapterSection();
|
|
} else {
|
|
selectParagraph(selectedParagraphIndex - 1);
|
|
}
|
|
} else {
|
|
// No selection - start with first visible item
|
|
if (chapterSection && KJVNav.isInViewport(chapterSection)) {
|
|
selectChapterSection();
|
|
} else if (contentParagraphs.length > 0) {
|
|
const visibleIndex = KJVNav.findFirstVisibleIndex(Array.from(contentParagraphs));
|
|
selectParagraph(visibleIndex);
|
|
} else {
|
|
selectChapterSection();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Down arrow or j: Next item
|
|
if (e.key === 'ArrowDown' || e.key === 'j') {
|
|
e.preventDefault();
|
|
if (inChapterDrilldown) {
|
|
// Navigating within chapter numbers
|
|
if (selectedChapterIndex < chapterLinks.length - 1) {
|
|
selectChapter(selectedChapterIndex + 1);
|
|
} else {
|
|
// Exit drilldown to first paragraph
|
|
if (contentParagraphs.length > 0) {
|
|
selectParagraph(0);
|
|
} else {
|
|
// No paragraphs, exit drilldown back to chapter section
|
|
selectChapterSection();
|
|
}
|
|
}
|
|
} else if (chapterSectionSelected) {
|
|
// At chapter section, move to first paragraph
|
|
if (contentParagraphs.length > 0) {
|
|
selectParagraph(0);
|
|
}
|
|
} else if (selectedParagraphIndex >= 0) {
|
|
// In paragraph zone
|
|
if (selectedParagraphIndex < contentParagraphs.length - 1) {
|
|
selectParagraph(selectedParagraphIndex + 1);
|
|
}
|
|
} else {
|
|
// No selection - start with first visible item
|
|
if (chapterSection && KJVNav.isInViewport(chapterSection)) {
|
|
selectChapterSection();
|
|
} else if (contentParagraphs.length > 0) {
|
|
const visibleIndex = KJVNav.findFirstVisibleIndex(Array.from(contentParagraphs));
|
|
selectParagraph(visibleIndex);
|
|
} else {
|
|
selectChapterSection();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enter: Drill into chapter section or navigate to chapter
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
if (chapterSectionSelected) {
|
|
// Drill down into first chapter
|
|
selectChapter(0);
|
|
} else if (inChapterDrilldown && selectedChapterIndex >= 0) {
|
|
// Navigate to selected chapter
|
|
window.location.href = chapterLinks[selectedChapterIndex].href;
|
|
}
|
|
// Paragraphs and chapter section don't navigate anywhere
|
|
}
|
|
|
|
// Escape: Exit drilldown mode
|
|
if (e.key === 'Escape') {
|
|
e.preventDefault();
|
|
if (inChapterDrilldown) {
|
|
// Exit drilldown back to chapter section
|
|
selectChapterSection();
|
|
} else {
|
|
// Clear all selections
|
|
clearAllSelections();
|
|
chapterSectionSelected = false;
|
|
inChapterDrilldown = false;
|
|
selectedChapterIndex = -1;
|
|
selectedParagraphIndex = -1;
|
|
}
|
|
}
|
|
|
|
// Left arrow: Previous chapter in drilldown, otherwise previous book
|
|
if (e.key === 'ArrowLeft') {
|
|
e.preventDefault();
|
|
if (inChapterDrilldown && selectedChapterIndex > 0) {
|
|
selectChapter(selectedChapterIndex - 1);
|
|
} else if (!inChapterDrilldown && currentBookIndex > 0) {
|
|
window.location.href = '/book/' + encodeURIComponent(bibleBooks[currentBookIndex - 1]);
|
|
}
|
|
}
|
|
|
|
// Right arrow: Next chapter in drilldown, otherwise next book
|
|
if (e.key === 'ArrowRight') {
|
|
e.preventDefault();
|
|
if (inChapterDrilldown && selectedChapterIndex < chapterLinks.length - 1) {
|
|
selectChapter(selectedChapterIndex + 1);
|
|
} else if (!inChapterDrilldown && currentBookIndex < bibleBooks.length - 1) {
|
|
window.location.href = '/book/' + encodeURIComponent(bibleBooks[currentBookIndex + 1]);
|
|
}
|
|
}
|
|
|
|
// p: Download PDF
|
|
if (e.key === 'p') {
|
|
e.preventDefault();
|
|
var pdfBtn = document.querySelector('.print-btn');
|
|
if (pdfBtn) window.location.href = pdfBtn.href;
|
|
}
|
|
|
|
// [ : Previous book
|
|
if (e.key === '[' && currentBookIndex > 0) {
|
|
e.preventDefault();
|
|
window.location.href = '/book/' + encodeURIComponent(bibleBooks[currentBookIndex - 1]);
|
|
}
|
|
|
|
// ] : Next book
|
|
if (e.key === ']' && currentBookIndex < bibleBooks.length - 1) {
|
|
e.preventDefault();
|
|
window.location.href = '/book/' + encodeURIComponent(bibleBooks[currentBookIndex + 1]);
|
|
}
|
|
});
|
|
|
|
// Clicking on chapter links should just navigate normally
|
|
// Keyboard navigation is separate - no click-to-select behavior needed
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<h1>{{ book }}</h1>
|
|
<p class="subtitle"><a href="/books">Authorized King James Version</a></p>
|
|
|
|
{% if book_intro %}
|
|
<p class="book-meta">
|
|
{% if book_intro.author %}<strong>Author:</strong> {{ book_intro.author }}{% endif %}
|
|
{% if book_intro.date_written %} · <strong>Written:</strong> {{ book_intro.date_written }}{% endif %}
|
|
{% if book_intro.category %} · <strong>Category:</strong> {{ book_intro.category }}{% endif %}
|
|
</p>
|
|
{% endif %}
|
|
|
|
{% if pdf_available %}
|
|
<div class="book-actions">
|
|
<a href="/book/{{ book }}/pdf" class="print-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>
|
|
Download Book PDF
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<section class="chapters-section">
|
|
<h2>Chapters</h2>
|
|
<p>
|
|
<label for="mn-popular" class="margin-toggle">⊕</label>
|
|
<input type="checkbox" id="mn-popular" class="margin-toggle"/>
|
|
<span class="marginnote">Chapters in <strong>bold</strong> are among the most frequently read and studied passages.</span>
|
|
{% for chapter in chapters %}
|
|
<a href="/book/{{ book }}/chapter/{{ chapter }}" {% if chapter_popularity[chapter] >= 7 %}class="popular-chapter"{% endif %} data-chapter="{{ chapter }}">{{ chapter }}</a>{% if not loop.last %} · {% endif %}
|
|
{% endfor %}
|
|
</p>
|
|
<p class="nav-hint">Tip: ↑/↓ to navigate • Enter to drill into chapters • Esc to exit • ←/→ for adjacent books</p>
|
|
</section>
|
|
|
|
{% if book_intro and book_intro.introduction %}
|
|
<section>
|
|
<h2>Introduction</h2>
|
|
{{ book_intro.introduction|md|safe }}
|
|
</section>
|
|
{% elif introduction %}
|
|
<section>
|
|
<h2>Introduction</h2>
|
|
{{ introduction|safe }}
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if book_intro and book_intro.outline %}
|
|
<section>
|
|
<h2>Book Outline</h2>
|
|
<ul>
|
|
{% for item in book_intro.outline %}
|
|
<li><strong>{{ item.section }}</strong> ({{ item.chapters }}) — {{ item.description|mdi|safe }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if book_intro and book_intro.key_themes %}
|
|
<section>
|
|
<h2>Key Themes</h2>
|
|
<ul>
|
|
{% for theme in book_intro.key_themes %}
|
|
{% if theme is mapping %}
|
|
<li><strong>{{ theme.theme }}</strong>: {{ theme.description|mdi|safe }}</li>
|
|
{% else %}
|
|
<li>{{ theme }}</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</ul>
|
|
</section>
|
|
{% elif themes %}
|
|
<section>
|
|
<h2>Major Themes</h2>
|
|
{{ themes|safe }}
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if book_intro and book_intro.key_verses %}
|
|
<section>
|
|
<h2>Key Verses</h2>
|
|
{% for verse in book_intro.key_verses %}
|
|
<blockquote>
|
|
<p>{{ verse.text|mdi|safe }}</p>
|
|
<footer>— {{ verse.reference }}{% if verse.significance %} <em>({{ verse.significance|mdi|safe }})</em>{% endif %}</footer>
|
|
</blockquote>
|
|
{% endfor %}
|
|
</section>
|
|
{% elif key_passages %}
|
|
<section>
|
|
<h2>Key Passages</h2>
|
|
<ul>
|
|
{% for passage in key_passages %}
|
|
<li><a href="{{ passage.url }}">{{ passage.reference }}</a> — {{ passage.description }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if book_intro and book_intro.historical_context %}
|
|
<section>
|
|
<h2>Historical Context</h2>
|
|
{{ book_intro.historical_context|md|safe }}
|
|
</section>
|
|
{% elif historical_context %}
|
|
<section>
|
|
<h2>Historical Context</h2>
|
|
{{ historical_context|safe }}
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if book_intro and book_intro.literary_style %}
|
|
<section>
|
|
<h2>Literary Style</h2>
|
|
{{ book_intro.literary_style|md|safe }}
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if book_intro and book_intro.theological_significance %}
|
|
<section>
|
|
<h2>Theological Significance</h2>
|
|
{{ book_intro.theological_significance|md|safe }}
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if book_intro and book_intro.christ_in_book %}
|
|
<section>
|
|
<h2>Christ in {{ book }}</h2>
|
|
{{ book_intro.christ_in_book|md|safe }}
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if book_intro and book_intro.relationship_to_new_testament %}
|
|
<section>
|
|
<h2>Relationship to the New Testament</h2>
|
|
{{ book_intro.relationship_to_new_testament|md|safe }}
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if book_intro and book_intro.practical_application %}
|
|
<section>
|
|
<h2>Practical Application</h2>
|
|
{{ book_intro.practical_application|md|safe }}
|
|
</section>
|
|
{% endif %}
|
|
|
|
<nav>
|
|
<p><a href="/">← All Books</a></p>
|
|
</nav>
|
|
{% endblock %}
|