Files
kjvstudy.org/kjvstudy_org/templates/chapter.html
T
kennethreitz 84fb89f806 Revert chapter.html to working state before navigation changes
Restored chapter.html to the version from commit fb4e970 which had the perfect working keyboard navigation. The recent changes to chapter view were not needed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 09:07:33 -05:00

656 lines
21 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ book }} {{ chapter }} - KJV Bible{% endblock %}
{% block description %}Read {{ book }} chapter {{ chapter }} from the King James Version Bible. {{ verses[0].text[:100] if verses and verses|length > 0 else '' }}...{% endblock %}
{% block head %}
<style>
/* Red Letter Edition - Words of Christ */
.words-of-christ {
color: #c41e3a;
}
[data-theme="dark"] .words-of-christ {
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: #111;
}
[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: #ddd;
}
.sidenote,
.marginnote {
max-height: 150px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
/* Highlight sidenote when its checkbox is checked */
.margin-toggle:checked + .sidenote,
.margin-toggle:checked + .marginnote {
background-color: rgba(255, 237, 160, 0.3);
border-left: 3px solid rgba(255, 193, 7, 0.6);
padding-left: 0.5rem;
margin-left: -0.5rem;
}
[data-theme="dark"] .margin-toggle:checked + .sidenote,
[data-theme="dark"] .margin-toggle:checked + .marginnote {
background-color: rgba(255, 193, 7, 0.15);
border-left: 3px solid rgba(255, 193, 7, 0.5);
}
.sidenote.expanded,
.marginnote.expanded {
max-height: none;
overflow: visible;
}
.sidenote.truncated:not(.expanded)::after,
.marginnote.truncated:not(.expanded)::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background: linear-gradient(to bottom, transparent, #fffff8);
pointer-events: none;
}
.marginnote {
margin-top: 0.3rem;
margin-bottom: 1rem;
}
@media (max-width: 760px) {
.sidenote,
.marginnote {
max-height: 120px;
}
}
hr {
border: 0;
height: 0;
text-align: center;
margin: 2em 0;
}
hr::before {
content: "⁂";
font-size: 1.5rem;
letter-spacing: 0.6em;
color: #111;
}
.verse-number-link {
font-family: et-book-roman-old-style;
font-size: 1rem;
vertical-align: super;
text-decoration: none;
color: inherit;
position: relative;
top: -0.5rem;
left: 0.1rem;
margin-right: 0.2rem;
}
.verse-number-link:hover {
color: #111;
text-decoration: underline;
}
/* Keyboard navigation selected verse */
p[id^="verse-"].selected {
background: rgba(74, 124, 89, 0.1);
margin-left: -1rem;
padding-left: 1rem;
border-left: 3px solid #4a7c59;
transition: all 0.15s ease;
}
[data-theme="dark"] p[id^="verse-"].selected {
background: rgba(107, 155, 122, 0.15);
border-left-color: #6b9b7a;
}
/* Keyboard navigation selected action button */
.action-btn.selected {
outline: 2px solid #4a7c59;
outline-offset: 4px;
}
[data-theme="dark"] .action-btn.selected {
outline-color: #6b9b7a;
}
.verse-number-link.has-commentary {
color: #c41e3a;
}
[data-theme="dark"] .verse-number-link.has-commentary {
color: #ff6b6b;
}
/* Prevent sidenote numbers from appearing too close to verse numbers */
.verse-number-link + label.sidenote-number {
margin-left: 0.3rem;
}
.chapter-nav {
max-width: 60%;
margin: 2rem 0;
padding: 1.5rem;
border: 1px solid var(--border-color);
border-radius: 4px;
}
.chapter-nav-controls {
display: flex;
gap: 1rem;
align-items: center;
flex-wrap: wrap;
}
.chapter-nav-btn {
padding: 0.5rem 1rem;
font-size: 0.95rem;
font-weight: 600;
background: var(--bg-color);
color: var(--text-color);
border: 1px solid var(--border-color-darker);
border-radius: 4px;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
display: inline-block;
}
.chapter-nav-btn:hover {
background: var(--code-bg);
border-color: var(--text-secondary);
color: var(--link-hover);
}
.chapter-nav-btn.secondary {
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border-color);
}
.chapter-nav-btn.secondary:hover {
background: var(--code-bg);
border-color: var(--border-color-darker);
color: var(--text-color);
}
.chapter-select {
padding: 0.5rem;
font-size: 0.95rem;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--bg-color);
color: var(--text-color);
font-family: inherit;
cursor: pointer;
}
.chapter-select:focus {
outline: none;
border-color: var(--link-color);
}
.nav-help {
font-size: 0.85rem;
color: var(--text-secondary);
font-style: italic;
margin-top: 0.75rem;
}
.chapter-actions {
margin: 1.5rem 0 1rem;
display: flex;
gap: 0.5rem;
}
.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;
}
.interlinear-btn:hover {
border-color: #4a7c59;
color: #4a7c59;
}
@media print {
.chapter-actions,
.action-btn {
display: none;
}
}
</style>
{% endblock %}
{% block content %}
<h1>{{ book }} {{ chapter }}</h1>
<p class="subtitle"><a href="/books">Authorized King James Version</a></p>
<div class="chapter-actions">
<a href="/book/{{ book }}/chapter/{{ chapter }}/interlinear" class="action-btn interlinear-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="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129" />
</svg>
Interlinear
</a>
{% if pdf_available %}
<a href="/book/{{ book }}/chapter/{{ chapter }}/pdf" 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>
{% for verse in verses %}
{% set commentary = commentaries[verse.verse] if commentaries and verse.verse in commentaries else none %}
<p id="verse-{{ verse.verse }}">
<a href="/book/{{ book }}/chapter/{{ chapter }}/verse/{{ verse.verse }}" class="verse-number-link{% if commentary and commentary['is_enhanced'] == True %} has-commentary{% endif %}">{{ verse.verse }}</a> {{ verse.text | red_letter(book, chapter, verse.verse) | inject_word_markers(commentary.word_studies if commentary else [], verse.verse) | link_names | safe }}
{% if commentary %}
{% if commentary.cross_reference_groups %}
<label for="sn-{{ verse.verse }}-xrefs" class="margin-toggle sidenote-number"></label>
<input type="checkbox" id="sn-{{ verse.verse }}-xrefs" class="margin-toggle"/>
<span class="sidenote">
{% for group in commentary.cross_reference_groups %}
<strong>{{ group.description }}:</strong> {% for ref in group.refs %}<a href="{{ ref.url }}">{{ ref.text }}</a>{% if not loop.last %}; {% endif %}{% endfor %}.{% if not loop.last %}<br>{% endif %}
{% endfor %}
</span>
{% endif %}
{% endif %}
</p>
{% endfor %}
</section>
<nav class="chapter-nav">
<div class="chapter-nav-controls">
{% if chapter > 1 %}
<a href="/book/{{ book }}/chapter/{{ chapter - 1 }}" class="chapter-nav-btn" id="prev-chapter">
← Previous
</a>
{% endif %}
<select class="chapter-select" onchange="if(this.value) window.location.href=this.value">
<option value="">Jump to chapter...</option>
{% for ch in chapters %}
<option value="/book/{{ book }}/chapter/{{ ch }}"{% if ch == chapter %} selected{% endif %}>
Chapter {{ ch }}
</option>
{% endfor %}
</select>
{% if chapter < chapters|length %}
<a href="/book/{{ book }}/chapter/{{ chapter + 1 }}" class="chapter-nav-btn" id="next-chapter">
Next →
</a>
{% endif %}
<a href="/book/{{ book }}" class="chapter-nav-btn secondary">
{{ book }}
</a>
</div>
<div class="nav-help">
Tip: ↑/↓ to select verses • Enter to view • ←/→ chapters • i=interlinear • p=PDF
</div>
</nav>
{% if pdf_available %}
{% endif %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Function to link verse references
function linkVerseReferences(element) {
let html = element.innerHTML;
// Match patterns like "Verse 1", "Verses 1-5", "verse 12", etc.
html = html.replace(/\b(Verse|verse)s?\s+(\d+)(?:-(\d+))?\b/g, function(match, word, start, end) {
if (end) {
return '<a href="#verse-' + start + '-' + end + '">' + match + '</a>';
} else {
return '<a href="#verse-' + start + '">' + match + '</a>';
}
});
element.innerHTML = html;
}
const sidenotes = document.querySelectorAll('.sidenote, .marginnote');
// Make sidenotes and marginnotes expandable and link verse references
sidenotes.forEach(function(sidenote) {
// Check if content is actually truncated
if (sidenote.scrollHeight > sidenote.clientHeight) {
sidenote.classList.add('truncated');
}
sidenote.addEventListener('click', function(e) {
// Don't expand if clicking a link
if (e.target.tagName !== 'A') {
this.classList.toggle('expanded');
}
});
linkVerseReferences(sidenote);
});
// Handle verse ranges in URL hash
function highlightVerses() {
const hash = window.location.hash.substring(1); // Remove the #
if (hash.startsWith('verse-')) {
const versePart = hash.substring(6); // Remove 'verse-'
const rangeParts = versePart.split('-');
if (rangeParts.length === 2) {
// Verse range (e.g., verse-22-23)
const start = parseInt(rangeParts[0]);
const end = parseInt(rangeParts[1]);
for (let i = start; i <= end; i++) {
const verseEl = document.getElementById('verse-' + i);
if (verseEl) {
verseEl.style.backgroundColor = 'rgba(255, 235, 59, 0.2)';
}
}
// Scroll to first verse
const firstVerse = document.getElementById('verse-' + start);
if (firstVerse) {
firstVerse.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
} else {
// Single verse
const verseEl = document.getElementById('verse-' + versePart);
if (verseEl) {
verseEl.style.backgroundColor = 'rgba(255, 235, 59, 0.2)';
verseEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
}
}
highlightVerses();
window.addEventListener('hashchange', highlightVerses);
// Two-zone navigation: action buttons and verses
const actionButtons = Array.from(document.querySelectorAll('.chapter-actions .action-btn'));
const verses = document.querySelectorAll('p[id^="verse-"]');
let selectedVerseIndex = -1;
let selectedActionIndex = -1;
let inActionZone = false;
// Initialize from hash if present (e.g., #verse-24)
function initFromHash() {
const hash = window.location.hash;
if (hash && hash.startsWith('#verse-')) {
const verseNum = parseInt(hash.substring(7));
if (!isNaN(verseNum)) {
// Find the index of this verse
for (let i = 0; i < verses.length; i++) {
const verseId = verses[i].id;
if (verseId === 'verse-' + verseNum) {
selectedVerseIndex = i;
verses[i].classList.add('selected');
break;
}
}
}
}
}
initFromHash();
function clearAllSelections() {
if (selectedVerseIndex >= 0 && selectedVerseIndex < verses.length) {
verses[selectedVerseIndex].classList.remove('selected');
}
actionButtons.forEach(btn => btn.classList.remove('selected'));
}
function selectAction(index) {
clearAllSelections();
inActionZone = true;
selectedActionIndex = Math.max(0, Math.min(index, actionButtons.length - 1));
selectedVerseIndex = -1;
actionButtons[selectedActionIndex].classList.add('selected');
actionButtons[selectedActionIndex].scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
function selectVerse(index) {
clearAllSelections();
inActionZone = false;
selectedActionIndex = -1;
// Update index with bounds checking
selectedVerseIndex = Math.max(0, Math.min(index, verses.length - 1));
// Add selection to new verse
verses[selectedVerseIndex].classList.add('selected');
// Scroll into view
verses[selectedVerseIndex].scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
// Keyboard navigation for chapters and verses
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;
}
// Up arrow or k: Previous item
if (e.key === 'ArrowUp' || e.key === 'k') {
e.preventDefault();
if (inActionZone) {
// Move within action buttons or stay at first
if (selectedActionIndex > 0) {
selectAction(selectedActionIndex - 1);
}
} else if (selectedVerseIndex === 0 && actionButtons.length > 0) {
// At first verse, move to action buttons
selectAction(actionButtons.length - 1);
} else if (selectedVerseIndex < 0) {
// No selection, start at first verse (viewport-aware)
selectVerse(KJVNav.findFirstVisibleIndex(Array.from(verses)));
} else if (KJVNav.isSelectionOffScreen(Array.from(verses), selectedVerseIndex)) {
selectVerse(KJVNav.findFirstVisibleIndex(Array.from(verses)));
} else {
selectVerse(selectedVerseIndex - 1);
}
}
// Down arrow or j: Next item
if (e.key === 'ArrowDown' || e.key === 'j') {
e.preventDefault();
if (inActionZone) {
// Move within action buttons or to verses
if (selectedActionIndex < actionButtons.length - 1) {
selectAction(selectedActionIndex + 1);
} else {
// Move to first verse
selectVerse(0);
}
} else if (selectedVerseIndex < 0) {
// No selection, start at first verse (initial down starts with verses, not actions)
selectVerse(KJVNav.findFirstVisibleIndex(Array.from(verses)));
} else if (KJVNav.isSelectionOffScreen(Array.from(verses), selectedVerseIndex)) {
selectVerse(KJVNav.findFirstVisibleIndex(Array.from(verses)));
} else {
selectVerse(selectedVerseIndex + 1);
}
}
// Enter: Navigate to selected item
if (e.key === 'Enter') {
e.preventDefault();
if (inActionZone && selectedActionIndex >= 0) {
window.location.href = actionButtons[selectedActionIndex].href;
} else if (selectedVerseIndex >= 0) {
const link = verses[selectedVerseIndex].querySelector('.verse-number-link');
if (link) window.location.href = link.href;
}
}
// Left arrow or h: Navigate action buttons or chapters
if (e.key === 'ArrowLeft' || e.key === 'h') {
e.preventDefault();
if (inActionZone) {
// Move between action buttons
if (selectedActionIndex > 0) {
selectAction(selectedActionIndex - 1);
}
} else {
const hash = window.location.hash;
if (hash && hash.startsWith('#verse-')) {
// If we came here via a verse link (yellow highlight), go back
history.back();
} else {
const prevBtn = document.getElementById('prev-chapter');
if (prevBtn) {
window.location.href = prevBtn.href;
} else {
// Go back to book page
window.location.href = '/book/{{ book }}';
}
}
}
}
// Right arrow or l: Navigate action buttons or chapters
if (e.key === 'ArrowRight' || e.key === 'l') {
if (inActionZone) {
// Move between action buttons
e.preventDefault();
if (selectedActionIndex < actionButtons.length - 1) {
selectAction(selectedActionIndex + 1);
}
} else {
const nextBtn = document.getElementById('next-chapter');
if (nextBtn) {
e.preventDefault();
window.location.href = nextBtn.href;
}
}
}
// Escape: Clear selection
if (e.key === 'Escape') {
e.preventDefault();
clearAllSelections();
selectedVerseIndex = -1;
selectedActionIndex = -1;
inActionZone = false;
}
// i: Go to interlinear view
if (e.key === 'i') {
e.preventDefault();
var interlinearBtn = document.querySelector('.interlinear-btn');
if (interlinearBtn) window.location.href = interlinearBtn.href;
}
// p: Download PDF
if (e.key === 'p') {
e.preventDefault();
var pdfBtn = document.querySelector('.pdf-btn');
if (pdfBtn) window.location.href = pdfBtn.href;
}
// Space: Read selected verse aloud
if (e.key === ' ' && selectedVerseIndex >= 0) {
e.preventDefault();
var verseEl = verses[selectedVerseIndex];
// Clone the element to manipulate
var clone = verseEl.cloneNode(true);
// Remove sidenotes and marginnotes from clone
clone.querySelectorAll('.sidenote, .marginnote, .sidenote-number, .margin-toggle').forEach(function(el) {
el.remove();
});
// Get text content, excluding the verse number link
var text = clone.textContent || clone.innerText;
// Remove verse number from the beginning
text = text.replace(/^\s*\d+\s*/, '');
KJVSpeech.toggle(text);
}
});
// Click to select verse
verses.forEach((verse, index) => {
verse.addEventListener('click', function(e) {
if (e.target.tagName !== 'A' && e.target.tagName !== 'LABEL') {
selectVerse(index);
}
});
});
// Only allow one sidenote to be highlighted at a time
document.querySelectorAll('.margin-toggle').forEach(function(checkbox) {
checkbox.addEventListener('change', function() {
if (this.checked) {
// Uncheck all other margin-toggle checkboxes
document.querySelectorAll('.margin-toggle').forEach(function(otherCheckbox) {
if (otherCheckbox !== checkbox) {
otherCheckbox.checked = false;
}
});
}
});
});
});
</script>
{% endblock %}