Add Listen button across site for text-to-speech

- Chapter pages: Listen to full chapter text
- Bible stories (adult & kids): Listen button at bottom with PDF
- Resource index pages (Ten Commandments, Beatitudes, etc.)
- Resource detail pages (individual commandments, etc.)
- Twelve Apostles, Biblical Prophets, Names of God
- Parables, Women of the Bible, Fruits of the Spirit

All pages use consistent .action-btn styling and KJVSpeech integration.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-06 14:50:16 -05:00
parent a8b64d6b26
commit 12cbc15b2d
11 changed files with 470 additions and 143 deletions
+37 -9
View File
@@ -42,10 +42,12 @@
}
.prophets-actions {
display: flex;
gap: 0.75rem;
margin: 1rem 0 1.5rem;
}
.prophet-download-btn {
.action-btn {
display: inline-flex;
align-items: center;
gap: 0.35rem;
@@ -56,20 +58,27 @@
border: 1px solid var(--border-color, #ddd);
border-radius: 4px;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
}
.prophet-download-btn:hover {
.action-btn:hover {
background: var(--bg-color, #fff);
border-color: var(--link-color);
color: var(--link-color);
}
.prophet-download-btn svg {
.action-btn svg {
width: 14px;
height: 14px;
}
.action-btn.playing {
background: var(--link-color);
color: white;
border-color: var(--link-color);
}
.prophet-description {
max-width: 60%;
font-size: 1.2rem;
@@ -159,7 +168,6 @@
@media print {
.prophets-actions,
.prophet-download-btn,
.toc {
display: none !important;
}
@@ -195,16 +203,22 @@ document.body.dataset.resourceReader = 'true';
<h1>Biblical Prophets</h1>
<p class="subtitle">Messengers of the Most High</p>
{% if resource_pdf_available %}
<div class="prophets-actions">
<a class="prophet-download-btn" href="/biblical-prophets/pdf">
<button class="action-btn" id="listen-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="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 resource_pdf_available %}
<a class="action-btn" href="/biblical-prophets/pdf">
<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 All Prophets (PDF)
Download PDF
</a>
{% endif %}
</div>
{% endif %}
<nav class="toc" id="toc">
<h2>Contents</h2>
@@ -279,6 +293,20 @@ document.body.dataset.resourceReader = 'true';
<script>
document.addEventListener('DOMContentLoaded', function() {
// Listen button handler
var listenBtn = document.getElementById('listen-btn');
if (listenBtn) {
listenBtn.addEventListener('click', function() {
var paragraphs = document.querySelectorAll('.intro-text, .prophet-description p, .verse-text');
var text = Array.from(paragraphs).map(function(p) {
return p.textContent.trim();
}).join(' ');
if (window.KJVSpeech && text) {
window.KJVSpeech.speak(text);
}
});
}
// Generate TOC from h2 and h3 headings
const tocList = document.getElementById('toc-list');
const headings = document.querySelectorAll('section h2, section h3, section article h3');
@@ -297,7 +325,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
// Simple keyboard navigation
KJVNav.initSimpleNav('.prophet-download-btn, .intro-text, .prophet-name, .prophet-description > p, .verse-item');
KJVNav.initSimpleNav('.action-btn, .intro-text, .prophet-name, .prophet-description > p, .verse-item');
});
</script>
{% endblock %}
+33
View File
@@ -538,6 +538,12 @@ document.body.dataset.resourceReader = 'false';
PDF
</a>
{% endif %}
<button class="action-btn listen-btn" id="chapter-listen-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="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>
</div>
<section{% if is_poetry %} class="poetry-chapter"{% endif %}>
@@ -619,6 +625,33 @@ document.body.dataset.resourceReader = 'false';
<script>
document.addEventListener('DOMContentLoaded', function() {
// Listen button - read chapter aloud
var listenBtn = document.getElementById('chapter-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;
}
// Gather all verse text
var verses = document.querySelectorAll('p[id^="verse-"]');
var text = Array.from(verses).map(function(v) {
return v.textContent.replace(/^\d+\s*/, '').trim();
}).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;
}
});
}
// Function to link verse references
function linkVerseReferences(element) {
let html = element.innerHTML;
+31 -9
View File
@@ -81,10 +81,12 @@
}
.fruits-actions {
display: flex;
gap: 0.75rem;
margin: 1rem 0 1.5rem;
}
.fruits-download-btn {
.action-btn {
display: inline-flex;
align-items: center;
gap: 0.35rem;
@@ -95,16 +97,17 @@
border: 1px solid var(--border-color, #ddd);
border-radius: 4px;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
}
.fruits-download-btn:hover {
.action-btn:hover {
background: var(--bg-color, #fff);
border-color: var(--link-color);
color: var(--link-color);
}
.fruits-download-btn svg {
.action-btn svg {
width: 14px;
height: 14px;
}
@@ -159,7 +162,6 @@
@media print {
.fruits-actions,
.fruits-download-btn,
.toc {
display: none !important;
}
@@ -195,16 +197,22 @@ document.body.dataset.resourceReader = 'true';
<h1>Fruits of the Spirit</h1>
<p class="subtitle">The Nine Graces of Galatians 5:22-23</p>
{% if pdf_available %}
<div class="fruits-actions">
<a class="fruits-download-btn" href="/fruits-of-the-spirit/pdf">
<button class="action-btn" id="listen-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="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 %}
<a class="action-btn" href="/fruits-of-the-spirit/pdf">
<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 All Studies (PDF)
Download PDF
</a>
{% endif %}
</div>
{% endif %}
<nav class="toc" id="toc">
<h2>Contents</h2>
@@ -285,6 +293,20 @@ document.body.dataset.resourceReader = 'true';
<script>
document.addEventListener('DOMContentLoaded', function() {
// Listen button handler
var listenBtn = document.getElementById('listen-btn');
if (listenBtn) {
listenBtn.addEventListener('click', function() {
var paragraphs = document.querySelectorAll('.intro-text, .fruit-description p, .verse-text');
var text = Array.from(paragraphs).map(function(p) {
return p.textContent.trim();
}).join(' ');
if (window.KJVSpeech && text) {
window.KJVSpeech.speak(text);
}
});
}
// Generate TOC from h2 and h3 headings
const tocList = document.getElementById('toc-list');
const headings = document.querySelectorAll('section h2, section h3, section article h3');
@@ -303,7 +325,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
// Simple keyboard navigation
KJVNav.initSimpleNav('.fruits-download-btn, .intro-text, .fruit-name, .fruit-description > p, .verse-item');
KJVNav.initSimpleNav('.action-btn, .intro-text, .fruit-name, .fruit-description > p, .verse-item');
});
</script>
{% endblock %}
+37 -9
View File
@@ -81,10 +81,12 @@
}
.names-actions {
display: flex;
gap: 0.75rem;
margin: 1rem 0 1.5rem;
}
.names-download-btn {
.action-btn {
display: inline-flex;
align-items: center;
gap: 0.35rem;
@@ -95,20 +97,27 @@
border: 1px solid var(--border-color, #ddd);
border-radius: 4px;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
}
.names-download-btn:hover {
.action-btn:hover {
background: var(--bg-color, #fff);
border-color: var(--link-color);
color: var(--link-color);
}
.names-download-btn svg {
.action-btn svg {
width: 14px;
height: 14px;
}
.action-btn.playing {
background: var(--link-color);
color: white;
border-color: var(--link-color);
}
.intro-text {
max-width: 60%;
font-size: 1.2rem;
@@ -159,7 +168,6 @@
@media print {
.names-actions,
.names-download-btn,
.toc {
display: none !important;
}
@@ -191,16 +199,22 @@
<h1>Names of God</h1>
<p class="subtitle">The Divine Names Revealed in Holy Scripture</p>
{% if pdf_available %}
<div class="names-actions">
<a class="names-download-btn" href="/names-of-god/pdf">
<button class="action-btn" id="listen-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="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 %}
<a class="action-btn" href="/names-of-god/pdf">
<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 All Names (PDF)
Download PDF
</a>
{% endif %}
</div>
{% endif %}
<nav class="toc" id="toc">
<h2>Contents</h2>
@@ -275,6 +289,20 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
// Listen button handler
var listenBtn = document.getElementById('listen-btn');
if (listenBtn) {
listenBtn.addEventListener('click', function() {
var paragraphs = document.querySelectorAll('.intro-text, .name-description p, .verse-text');
var text = Array.from(paragraphs).map(function(p) {
return p.textContent.trim();
}).join(' ');
if (window.KJVSpeech && text) {
window.KJVSpeech.speak(text);
}
});
}
// Generate TOC from h2 and h3 headings
const tocList = document.getElementById('toc-list');
const headings = document.querySelectorAll('section h2, section h3, section article h3');
@@ -293,7 +321,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
// Simple keyboard navigation
KJVNav.initSimpleNav('.names-download-btn, .intro-text, .name-heading, .name-description > p, .verse-item');
KJVNav.initSimpleNav('.action-btn, .intro-text, .name-heading, .name-description > p, .verse-item');
});
</script>
{% endblock %}
+37 -9
View File
@@ -53,10 +53,12 @@
}
.parables-actions {
display: flex;
gap: 0.75rem;
margin: 1rem 0 1.5rem;
}
.parable-download-btn {
.action-btn {
display: inline-flex;
align-items: center;
gap: 0.35rem;
@@ -67,20 +69,27 @@
border: 1px solid var(--border-color, #ddd);
border-radius: 4px;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
}
.parable-download-btn:hover {
.action-btn:hover {
background: var(--bg-color, #fff);
border-color: var(--link-color);
color: var(--link-color);
}
.parable-download-btn svg {
.action-btn svg {
width: 14px;
height: 14px;
}
.action-btn.playing {
background: var(--link-color);
color: white;
border-color: var(--link-color);
}
.verse-list {
margin: 1.5rem 0 0 0;
}
@@ -164,7 +173,6 @@
@media print {
.parables-actions,
.parable-actions,
.parable-download-btn,
.toc {
display: none !important;
}
@@ -200,16 +208,22 @@ document.body.dataset.resourceReader = 'true';
<h1>Parables of Jesus</h1>
<p class="subtitle">Teaching in Earthly Stories with Heavenly Meanings</p>
{% if resource_pdf_available %}
<div class="parables-actions">
<a class="parable-download-btn" href="/parables/pdf">
<button class="action-btn" id="listen-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="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 resource_pdf_available %}
<a class="action-btn" href="/parables/pdf">
<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 All Parables (PDF)
Download PDF
</a>
{% endif %}
</div>
{% endif %}
<nav class="toc" id="toc">
<h2>Contents</h2>
@@ -293,6 +307,20 @@ document.body.dataset.resourceReader = 'true';
<script>
document.addEventListener('DOMContentLoaded', function() {
// Listen button handler
var listenBtn = document.getElementById('listen-btn');
if (listenBtn) {
listenBtn.addEventListener('click', function() {
var paragraphs = document.querySelectorAll('.intro-text, .parable-description p, .verse-text');
var text = Array.from(paragraphs).map(function(p) {
return p.textContent.trim();
}).join(' ');
if (window.KJVSpeech && text) {
window.KJVSpeech.speak(text);
}
});
}
// Generate TOC from h2 and h3 headings
const tocList = document.getElementById('toc-list');
const headings = document.querySelectorAll('section h2, section h3, section article h3');
@@ -311,7 +339,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
// Simple keyboard navigation
KJVNav.initSimpleNav('.parable-download-btn, .intro-text, .parable-name, .parable-description > p, .verse-item');
KJVNav.initSimpleNav('.action-btn, .intro-text, .parable-name, .parable-description > p, .verse-item');
});
</script>
{% endblock %}
+31 -11
View File
@@ -73,11 +73,12 @@ article {
}
.resource-actions {
display: flex;
gap: 0.75rem;
margin: 1.5rem 0;
}
.print-btn {
.action-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
@@ -92,21 +93,20 @@ article {
text-decoration: none;
}
.print-btn:hover {
.action-btn:hover {
background: var(--bg-color);
border-color: var(--link-color);
color: var(--link-color);
text-decoration: none;
}
.print-btn svg {
.action-btn svg {
width: 16px;
height: 16px;
}
@media print {
.resource-actions,
.print-btn {
.resource-actions {
display: none;
}
}
@@ -129,16 +129,22 @@ article {
<h1>{{ item_name }}</h1>
<p class="resource-title">{{ item.title }}</p>
{% if pdf_available and pdf_url %}
<div class="resource-actions">
<a href="{{ pdf_url }}" class="print-btn" aria-label="Download {{ item_name }} as PDF">
<button class="action-btn" id="listen-btn" aria-label="Listen to {{ item_name }}">
<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" aria-label="Download {{ item_name }} as PDF">
<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="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 PDF
</a>
{% endif %}
</div>
{% endif %}
<section id="description" aria-labelledby="description-heading">
<h2 id="description-heading">Description</h2>
@@ -190,8 +196,22 @@ article {
document.body.dataset.resourceReader = 'true';
(function() {
// Include description paragraphs, verse items, and PDF button
const elements = Array.from(document.querySelectorAll('.resource-description p, .verse-item, .print-btn, .resource-nav-link'));
// Listen button handler
var listenBtn = document.getElementById('listen-btn');
if (listenBtn) {
listenBtn.addEventListener('click', function() {
var paragraphs = document.querySelectorAll('.resource-description p, .verse-text');
var text = Array.from(paragraphs).map(function(p) {
return p.textContent.trim();
}).join(' ');
if (window.KJVSpeech && text) {
window.KJVSpeech.speak(text);
}
});
}
// Include description paragraphs, verse items, and action buttons
const elements = Array.from(document.querySelectorAll('.resource-description p, .verse-item, .action-btn, .resource-nav-link'));
if (elements.length === 0) return;
let selectedIndex = -1;
+53 -6
View File
@@ -78,9 +78,37 @@
}
.resource-index-actions {
display: flex;
gap: 0.75rem;
margin: 1rem 0 1.5rem;
}
.action-btn {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.35rem 0.75rem;
font-size: 0.85rem;
color: var(--text-secondary);
background: var(--code-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
}
.action-btn:hover {
background: var(--bg-color);
border-color: var(--link-color);
color: var(--link-color);
}
.action-btn svg {
width: 14px;
height: 14px;
}
.verse-list {
margin: 1.5rem 0 0 0;
}
@@ -173,7 +201,6 @@
@media print {
.resource-index-actions,
.resource-entry-actions,
.resource-download-btn,
.toc {
display: none !important;
}
@@ -208,16 +235,22 @@ document.body.dataset.resourceReader = 'true';
<h1>{{ page_title }}</h1>
<p class="subtitle">{{ page_subtitle }}</p>
{% if pdf_available %}
<div class="resource-index-actions">
<a class="resource-download-btn" href="{{ base_url }}/pdf" aria-label="Download all {{ page_title }} as PDF">
<button class="action-btn" id="listen-btn" aria-label="Listen to {{ page_title }}">
<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 %}
<a class="action-btn" href="{{ base_url }}/pdf" aria-label="Download all {{ page_title }} as PDF">
<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="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 All (PDF)
Download PDF
</a>
{% endif %}
</div>
{% endif %}
{% if page_description %}
<section aria-label="Introduction">
@@ -283,6 +316,20 @@ document.body.dataset.resourceReader = 'true';
<script>
document.addEventListener('DOMContentLoaded', function() {
// Listen button handler
var listenBtn = document.getElementById('listen-btn');
if (listenBtn) {
listenBtn.addEventListener('click', function() {
var paragraphs = document.querySelectorAll('.intro-text, .resource-item-description p, .verse-text');
var text = Array.from(paragraphs).map(function(p) {
return p.textContent.trim();
}).join(' ');
if (window.KJVSpeech && text) {
window.KJVSpeech.speak(text);
}
});
}
// Generate TOC from h2 and h3 headings
const tocList = document.getElementById('toc-list');
const headings = document.querySelectorAll('section h2, section h3, section article h3');
@@ -301,7 +348,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
// Simple keyboard navigation
KJVNav.initSimpleNav('.resource-download-btn, .intro-text, .resource-name, .resource-item-description > p, .verse-item, .resource-card');
KJVNav.initSimpleNav('.action-btn, .intro-text, .resource-name, .resource-item-description > p, .verse-item, .resource-card');
});
</script>
{% endblock %}
+79 -37
View File
@@ -60,6 +60,40 @@
line-height: 1.6;
max-width: 55ch;
}
.story-actions {
display: flex;
gap: 0.75rem;
margin-top: 2rem;
}
.action-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
font-size: 0.9rem;
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;
}
.action-btn:hover {
background: var(--bg-color, #fff);
border-color: var(--link-color);
color: var(--link-color);
text-decoration: none;
}
.action-btn svg {
width: 16px;
height: 16px;
}
.action-btn.playing {
background: var(--link-color);
color: white;
border-color: var(--link-color);
}
/* Story metadata grid */
.story-meta {
@@ -197,39 +231,12 @@ hr.story-divider::before {
color: var(--text-secondary, #ccc);
}
/* PDF download button */
.print-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
font-size: 0.9rem;
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;
margin-top: 1rem;
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;
}
/* Print styles */
@media print {
.story-meta,
.kids-callout,
.story-nav,
.print-btn {
.story-actions {
display: none;
}
@@ -273,7 +280,7 @@ hr.story-divider::before {
[data-font-size="large"] .kids-callout p {
font-size: 1.15rem;
}
[data-font-size="large"] .print-btn {
[data-font-size="large"] .action-btn {
font-size: 1.1rem;
padding: 0.6rem 1.2rem;
}
@@ -355,12 +362,20 @@ hr.story-divider::before {
<p>This story is also available in a kid-friendly version: <a href="/stories/{{ story.slug }}/kids">{{ story.kids_title }}</a> — written with simpler language and engaging narration perfect for young readers or family devotions.</p>
</aside>
<a href="/stories/{{ story.slug }}/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 PDF
</a>
<div class="story-actions">
<button class="action-btn listen-btn" id="story-listen-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="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>
<a href="/stories/{{ story.slug }}/pdf" class="action-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 PDF
</a>
</div>
</article>
<nav class="story-nav">
@@ -389,8 +404,35 @@ hr.story-divider::before {
<script>
(function() {
// Collect paragraphs, kids callout, and PDF button for up/down navigation (skip story meta so speech focuses on narrative)
var elements = Array.from(document.querySelectorAll('article section p, .kids-callout, .print-btn'));
// Listen button - read story aloud
var listenBtn = document.getElementById('story-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;
}
// Gather story text from paragraphs
var paragraphs = document.querySelectorAll('article section p');
var text = Array.from(paragraphs).map(function(p) {
return p.textContent.trim();
}).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;
}
});
}
// Collect paragraphs, kids callout, and action buttons for up/down navigation (skip story meta so speech focuses on narrative)
var elements = Array.from(document.querySelectorAll('article section p, .kids-callout, .story-actions .action-btn'));
// Find the index of the first paragraph (to start selection there)
var firstParagraphIndex = elements.findIndex(function(el) { return el.tagName === 'P'; });
var selectedIndex = -1;
+64 -35
View File
@@ -39,6 +39,39 @@
margin: 0;
}
.story-actions {
display: flex;
justify-content: center;
gap: 0.75rem;
margin-top: 1rem;
}
.action-btn {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 1rem;
font-size: 0.85rem;
border: 2px solid #c4b5fd;
border-radius: 20px;
background: rgba(255,255,255,0.8);
color: #7c3aed;
cursor: pointer;
transition: all 0.2s;
}
.action-btn:hover {
background: #f5f0ff;
border-color: #8b5cf6;
}
.action-btn svg {
width: 16px;
height: 16px;
}
.action-btn.playing {
background: #8b5cf6;
color: white;
border-color: #8b5cf6;
}
.kids-meta {
display: flex;
flex-wrap: wrap;
@@ -203,33 +236,6 @@ hr.story-divider::before {
color: #d1d5db;
}
/* PDF download button */
.print-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.6rem 1.2rem;
font-size: 0.9rem;
color: #7c3aed;
background: #fff;
border: 2px solid #c4b5fd;
border-radius: 20px;
cursor: pointer;
transition: all 0.2s;
margin: 1.5rem auto;
text-decoration: none;
}
.print-btn:hover {
background: #f5f0ff;
border-color: #8b5cf6;
text-decoration: none;
}
.print-btn svg {
width: 18px;
height: 18px;
}
/* Print styles */
@media print {
.kids-header {
@@ -243,6 +249,7 @@ hr.story-divider::before {
.characters-row,
.themes-section,
.adult-callout,
.story-actions,
.kids-nav {
display: none;
}
@@ -337,12 +344,20 @@ hr.story-divider::before {
Check out the <a href="/stories/{{ story.slug }}">grown-up version</a> of this story!</p>
</aside>
<a href="/stories/{{ story.slug }}/kids/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 PDF
</a>
<div class="story-actions">
<button class="action-btn listen-btn" id="story-listen-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="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>
<a href="/stories/{{ story.slug }}/kids/pdf" class="action-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 PDF
</a>
</div>
</article>
<nav class="kids-nav">
@@ -371,8 +386,22 @@ hr.story-divider::before {
<script>
(function() {
// Collect paragraphs, adult callout, and PDF button for up/down navigation
var elements = Array.from(document.querySelectorAll('article section p, .adult-callout, .print-btn'));
// Listen button handler
var listenBtn = document.getElementById('story-listen-btn');
if (listenBtn) {
listenBtn.addEventListener('click', function() {
var paragraphs = document.querySelectorAll('article section p');
var text = Array.from(paragraphs).map(function(p) {
return p.textContent.trim();
}).join(' ');
if (window.KJVSpeech && text) {
window.KJVSpeech.speak(text);
}
});
}
// Collect paragraphs, adult callout, and action buttons for up/down navigation
var elements = Array.from(document.querySelectorAll('article section p, .adult-callout, .story-actions .action-btn'));
// Find the index of the first paragraph (to start selection there)
var firstParagraphIndex = elements.findIndex(function(el) { return el.tagName === 'P'; });
var selectedIndex = -1;
+37 -9
View File
@@ -19,10 +19,12 @@
}
.apostles-actions {
display: flex;
gap: 0.75rem;
margin: 1rem 0 1.5rem;
}
.apostle-download-btn {
.action-btn {
display: inline-flex;
align-items: center;
gap: 0.35rem;
@@ -33,20 +35,27 @@
border: 1px solid var(--border-color, #ddd);
border-radius: 4px;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
}
.apostle-download-btn:hover {
.action-btn:hover {
background: var(--bg-color, #fff);
border-color: var(--link-color);
color: var(--link-color);
}
.apostle-download-btn svg {
.action-btn svg {
width: 14px;
height: 14px;
}
.action-btn.playing {
background: var(--link-color);
color: white;
border-color: var(--link-color);
}
.apostle-name {
font-size: 1.8rem;
font-weight: 400;
@@ -159,7 +168,6 @@
@media print {
.apostles-actions,
.apostle-download-btn,
.toc {
display: none !important;
}
@@ -195,16 +203,22 @@
<h1>The Twelve Apostles</h1>
<p class="subtitle">Those Whom He Chose to Be With Him</p>
{% if pdf_available %}
<div class="apostles-actions">
<a class="apostle-download-btn" href="/the-twelve-apostles/pdf">
<button class="action-btn" id="listen-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="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 %}
<a class="action-btn" href="/the-twelve-apostles/pdf">
<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 All Apostles (PDF)
Download PDF
</a>
{% endif %}
</div>
{% endif %}
<nav class="toc" id="toc">
<h2>Contents</h2>
@@ -281,6 +295,20 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
// Listen button handler
var listenBtn = document.getElementById('listen-btn');
if (listenBtn) {
listenBtn.addEventListener('click', function() {
var paragraphs = document.querySelectorAll('.intro-text, .apostle-description p, .verse-text');
var text = Array.from(paragraphs).map(function(p) {
return p.textContent.trim();
}).join(' ');
if (window.KJVSpeech && text) {
window.KJVSpeech.speak(text);
}
});
}
// Generate TOC from h2 and h3 headings
const tocList = document.getElementById('toc-list');
const headings = document.querySelectorAll('section h2, section h3, section article h3');
@@ -299,7 +327,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
// Simple keyboard navigation
KJVNav.initSimpleNav('.apostle-download-btn, .intro-text, .apostle-name, .apostle-description > p, .verse-item');
KJVNav.initSimpleNav('.action-btn, .intro-text, .apostle-name, .apostle-description > p, .verse-item');
});
</script>
{% endblock %}
+31 -9
View File
@@ -129,10 +129,12 @@
}
.women-actions {
display: flex;
gap: 0.75rem;
margin: 1rem 0 1.5rem;
}
.women-download-btn {
.action-btn {
display: inline-flex;
align-items: center;
gap: 0.35rem;
@@ -143,23 +145,23 @@
border: 1px solid var(--border-color, #ddd);
border-radius: 4px;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
}
.women-download-btn:hover {
.action-btn:hover {
background: var(--bg-color, #fff);
border-color: var(--link-color);
color: var(--link-color);
}
.women-download-btn svg {
.action-btn svg {
width: 14px;
height: 14px;
}
@media print {
.women-actions,
.women-download-btn,
.toc {
display: none !important;
}
@@ -195,16 +197,22 @@ document.body.dataset.resourceReader = 'true';
<h1>Women of the Bible</h1>
<p class="subtitle">Faithful Witnesses Throughout Redemptive History</p>
{% if pdf_available %}
<div class="women-actions">
<a class="women-download-btn" href="/women-of-the-bible/pdf">
<button class="action-btn" id="listen-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="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 %}
<a class="action-btn" href="/women-of-the-bible/pdf">
<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 All Women (PDF)
Download PDF
</a>
{% endif %}
</div>
{% endif %}
<nav class="toc" id="toc">
<h2>Contents</h2>
@@ -281,6 +289,20 @@ document.body.dataset.resourceReader = 'true';
<script>
document.addEventListener('DOMContentLoaded', function() {
// Listen button handler
var listenBtn = document.getElementById('listen-btn');
if (listenBtn) {
listenBtn.addEventListener('click', function() {
var paragraphs = document.querySelectorAll('.intro-text, .woman-description p, .verse-text');
var text = Array.from(paragraphs).map(function(p) {
return p.textContent.trim();
}).join(' ');
if (window.KJVSpeech && text) {
window.KJVSpeech.speak(text);
}
});
}
// Generate TOC from h2 and h3 headings
const tocList = document.getElementById('toc-list');
const headings = document.querySelectorAll('section h2, section h3, section article h3');
@@ -299,7 +321,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
// Simple keyboard navigation
KJVNav.initSimpleNav('.women-download-btn, .intro-text, .woman-name, .woman-description > p, .verse-item');
KJVNav.initSimpleNav('.action-btn, .intro-text, .woman-name, .woman-description > p, .verse-item');
});
</script>
{% endblock %}