From 953ceb4ff497e3b6381f7a4f5ea97e3d047879cf Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 3 Dec 2025 14:38:31 -0500 Subject: [PATCH] Add speech button to sticky breadcrumb actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Click 🔊 to read page content aloud - Button changes to ⏹ while speaking - Click again to stop - Strips out navigation, buttons, and other non-content elements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- kjvstudy_org/static/base.js | 51 ++++++++++++++++++++++++++++++++ kjvstudy_org/templates/base.html | 10 +++++++ 2 files changed, 61 insertions(+) diff --git a/kjvstudy_org/static/base.js b/kjvstudy_org/static/base.js index 043267e..4da7b97 100644 --- a/kjvstudy_org/static/base.js +++ b/kjvstudy_org/static/base.js @@ -37,6 +37,57 @@ function changeFontSize(direction) { } } +// Page speech toggle (for breadcrumb button) +function togglePageSpeech() { + var btn = document.getElementById('speech-toggle-btn'); + if (!btn || !('speechSynthesis' in window)) return; + + // If speaking, stop + if (speechSynthesis.speaking || speechSynthesis.pending) { + speechSynthesis.cancel(); + btn.classList.remove('speaking'); + return; + } + + // Get page content to read + var article = document.querySelector('article'); + if (!article) return; + + var clone = article.cloneNode(true); + // Remove elements we don't want to read + clone.querySelectorAll('.breadcrumb, .sidenote, .marginnote, .toc, script, style, nav, .chapter-nav, .verse-nav, button, .breadcrumb-actions').forEach(function(el) { + el.remove(); + }); + + var text = (clone.textContent || clone.innerText || '').trim(); + if (!text) return; + + var utterance = new SpeechSynthesisUtterance(text); + utterance.rate = 0.9; + + // Try to use a good English voice + var voices = speechSynthesis.getVoices(); + var englishVoice = voices.find(function(v) { + return v.lang && v.lang.startsWith('en') && v.name.includes('Daniel'); + }) || voices.find(function(v) { + return v.lang && v.lang.startsWith('en-GB'); + }) || voices.find(function(v) { + return v.lang && v.lang.startsWith('en'); + }); + if (englishVoice) utterance.voice = englishVoice; + + btn.classList.add('speaking'); + + utterance.onend = function() { + btn.classList.remove('speaking'); + }; + utterance.onerror = function() { + btn.classList.remove('speaking'); + }; + + speechSynthesis.speak(utterance); +} + // Red letter toggle functionality (function() { // Check for saved red letter preference or default to enabled diff --git a/kjvstudy_org/templates/base.html b/kjvstudy_org/templates/base.html index f996773..334a4aa 100644 --- a/kjvstudy_org/templates/base.html +++ b/kjvstudy_org/templates/base.html @@ -420,6 +420,15 @@ content: '☾'; } + .breadcrumb-action-btn.speech-toggle::before { + content: '🔊'; + font-size: 0.75rem; + } + + .breadcrumb-action-btn.speech-toggle.speaking::before { + content: '⏹'; + } + /* Font size scale classes */ [data-font-size="small"] article { font-size: 0.9rem; @@ -1446,6 +1455,7 @@ + {% for crumb in breadcrumbs %}