Add universal text-to-speech for keyboard-selected content

- Added KJVSpeech global helper using Web Speech API
- Space key reads aloud any element with green box selection
- Works on all pages with keyboard navigation (resources, parables, etc.)
- Chapter page has specialized handler for verse text
- Prefers English voices, cleans up text (removes verse numbers)
- Added Space key to keyboard shortcuts help modal

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-30 00:50:20 -05:00
parent 092361426c
commit df2272188f
2 changed files with 117 additions and 0 deletions
+106
View File
@@ -1633,6 +1633,103 @@
}
};
// Text-to-speech for any selected content
window.KJVSpeech = {
utterance: null,
speaking: false,
speak: function(text) {
if (!('speechSynthesis' in window)) {
console.log('Speech synthesis not supported');
return;
}
// Stop any current speech
this.stop();
// Clean up the text - remove verse numbers at start, collapse whitespace
text = text.replace(/^\s*\d+\s*/, '').replace(/\s+/g, ' ').trim();
if (!text) return;
this.utterance = new SpeechSynthesisUtterance(text);
this.utterance.rate = 0.9;
this.utterance.pitch = 1;
// Try to use a good English voice
var voices = speechSynthesis.getVoices();
var englishVoice = voices.find(function(v) {
return v.lang.startsWith('en') && v.name.includes('Daniel');
}) || voices.find(function(v) {
return v.lang.startsWith('en-GB');
}) || voices.find(function(v) {
return v.lang.startsWith('en');
});
if (englishVoice) {
this.utterance.voice = englishVoice;
}
this.speaking = true;
this.utterance.onend = function() {
KJVSpeech.speaking = false;
};
this.utterance.onerror = function() {
KJVSpeech.speaking = false;
};
speechSynthesis.speak(this.utterance);
},
stop: function() {
if ('speechSynthesis' in window) {
speechSynthesis.cancel();
}
this.speaking = false;
},
toggle: function(text) {
if (this.speaking) {
this.stop();
} else {
this.speak(text);
}
},
// Get text from the currently selected element (green box)
getSelectedText: function() {
// Find element with our green selection outline
var selected = document.querySelector('[style*="outline: 2px solid"]') ||
document.querySelector('[style*="outline:2px solid"]');
if (selected) {
return selected.textContent || selected.innerText;
}
// Fallback: Try specific verse selectors
var verseEl = document.querySelector('.verse-text-content') ||
document.querySelector('.verse-display .text') ||
document.querySelector('.verse-text') ||
document.querySelector('[data-verse-text]');
if (verseEl) {
return verseEl.textContent || verseEl.innerText;
}
return null;
}
};
// Load voices (they may not be available immediately)
if ('speechSynthesis' in window) {
speechSynthesis.getVoices();
speechSynthesis.onvoiceschanged = function() {
speechSynthesis.getVoices();
};
}
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
// Don't trigger if user is typing in an input field
@@ -1728,6 +1825,14 @@
case '?':
showKeyboardHelp();
break;
case ' ':
// Space: Read aloud selected text (green box selection or verse text)
var selectedText = KJVSpeech.getSelectedText();
if (selectedText) {
e.preventDefault();
KJVSpeech.toggle(selectedText);
}
break;
}
}
});
@@ -1807,6 +1912,7 @@
'</div>' +
'<div class="keyboard-help-section">' +
'<h3>Other</h3>' +
'<div class="shortcut"><kbd>Space</kbd><span>Read aloud</span></div>' +
'<div class="shortcut"><kbd>`</kbd><span>Toggle sidebar</span></div>' +
'<div class="shortcut"><kbd>⌘</kbd>+<kbd>D</kbd><span>Toggle dark mode</span></div>' +
'<div class="shortcut"><kbd>/</kbd><span>Search</span></div>' +
+11
View File
@@ -537,6 +537,17 @@ document.addEventListener('DOMContentLoaded', function() {
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];
// Get text content, excluding the verse number link
var text = verseEl.textContent || verseEl.innerText;
// Remove verse number from the beginning
text = text.replace(/^\s*\d+\s*/, '');
KJVSpeech.toggle(text);
}
});
// Click to select verse