Spaces:
Running
Running
edit when reading highlight only the word pronounced - Follow Up Deployment
Browse files- index.html +79 -80
index.html
CHANGED
|
@@ -119,6 +119,12 @@
|
|
| 119 |
margin-bottom: 1.5rem;
|
| 120 |
text-align: justify;
|
| 121 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
.book-content img {
|
| 124 |
max-width: 100%;
|
|
@@ -382,24 +388,6 @@
|
|
| 382 |
justify-content: center;
|
| 383 |
}
|
| 384 |
|
| 385 |
-
.word-highlight {
|
| 386 |
-
background-color: #dbeafe;
|
| 387 |
-
border-radius: 4px;
|
| 388 |
-
padding: 0 2px;
|
| 389 |
-
transition: background-color 0.2s ease;
|
| 390 |
-
}
|
| 391 |
-
|
| 392 |
-
.word-highlight.current {
|
| 393 |
-
background-color: #3b82f6;
|
| 394 |
-
color: white;
|
| 395 |
-
animation: pulse 0.5s infinite;
|
| 396 |
-
}
|
| 397 |
-
|
| 398 |
-
@keyframes pulse {
|
| 399 |
-
0%, 100% { opacity: 1; }
|
| 400 |
-
50% { opacity: 0.8; }
|
| 401 |
-
}
|
| 402 |
-
|
| 403 |
@keyframes pulse {
|
| 404 |
0%, 100% { opacity: 1; }
|
| 405 |
50% { opacity: 0.7; }
|
|
@@ -1087,8 +1075,9 @@
|
|
| 1087 |
synthesis.cancel();
|
| 1088 |
}
|
| 1089 |
|
| 1090 |
-
//
|
| 1091 |
-
|
|
|
|
| 1092 |
|
| 1093 |
currentUtterance = new SpeechSynthesisUtterance(text);
|
| 1094 |
currentUtterance.rate = audioSpeed;
|
|
@@ -1102,43 +1091,81 @@
|
|
| 1102 |
currentUtterance.addEventListener('end', () => {
|
| 1103 |
isPlaying = false;
|
| 1104 |
document.getElementById('playBtn').innerHTML = '<i class="fas fa-play"></i>';
|
| 1105 |
-
removeHighlights();
|
| 1106 |
});
|
| 1107 |
|
| 1108 |
currentUtterance.addEventListener('boundary', (event) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1109 |
if (event.name === 'word') {
|
| 1110 |
-
const
|
| 1111 |
-
const
|
| 1112 |
-
|
| 1113 |
-
|
| 1114 |
-
let wordIndex = 0;
|
| 1115 |
-
let charCount = 0;
|
| 1116 |
|
| 1117 |
-
|
| 1118 |
-
|
| 1119 |
-
|
| 1120 |
-
|
| 1121 |
-
|
| 1122 |
-
|
| 1123 |
-
|
| 1124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1125 |
|
| 1126 |
-
|
| 1127 |
-
|
| 1128 |
-
|
| 1129 |
-
}
|
| 1130 |
|
| 1131 |
-
//
|
| 1132 |
-
|
| 1133 |
-
wordElements[wordIndex].scrollIntoView({
|
| 1134 |
behavior: 'smooth',
|
| 1135 |
block: 'center'
|
| 1136 |
});
|
| 1137 |
-
currentBook.lastHighlightedWord = wordIndex;
|
| 1138 |
}
|
| 1139 |
-
|
|
|
|
| 1140 |
|
| 1141 |
-
|
| 1142 |
}
|
| 1143 |
}
|
| 1144 |
|
|
@@ -1155,16 +1182,14 @@
|
|
| 1155 |
synthesis.cancel();
|
| 1156 |
isPlaying = false;
|
| 1157 |
document.getElementById('playBtn').innerHTML = '<i class="fas fa-play"></i>';
|
| 1158 |
-
removeHighlights();
|
| 1159 |
|
| 1160 |
-
//
|
| 1161 |
-
|
| 1162 |
-
|
| 1163 |
-
|
| 1164 |
-
|
| 1165 |
-
|
| 1166 |
-
|
| 1167 |
-
}
|
| 1168 |
}
|
| 1169 |
}
|
| 1170 |
|
|
@@ -1207,32 +1232,6 @@
|
|
| 1207 |
voicePitch = parseFloat(pitch);
|
| 1208 |
}
|
| 1209 |
|
| 1210 |
-
function highlightReadingText() {
|
| 1211 |
-
const bookContent = document.getElementById('bookContent');
|
| 1212 |
-
// Wrap each word in a span for highlighting
|
| 1213 |
-
bookContent.innerHTML = bookContent.textContent.replace(/\S+/g, word =>
|
| 1214 |
-
`<span class="word-highlight">${word}</span>`
|
| 1215 |
-
);
|
| 1216 |
-
}
|
| 1217 |
-
|
| 1218 |
-
function removeHighlights() {
|
| 1219 |
-
const bookContent = document.getElementById('bookContent');
|
| 1220 |
-
const wordElements = bookContent.querySelectorAll('.word-highlight');
|
| 1221 |
-
|
| 1222 |
-
// Clear any active highlighting interval
|
| 1223 |
-
if (currentBook && currentBook.highlightInterval) {
|
| 1224 |
-
clearInterval(currentBook.highlightInterval);
|
| 1225 |
-
}
|
| 1226 |
-
|
| 1227 |
-
// Remove all word highlights
|
| 1228 |
-
wordElements.forEach(el => el.classList.remove('current'));
|
| 1229 |
-
|
| 1230 |
-
// Restore original text (remove span wrappers)
|
| 1231 |
-
if (wordElements.length > 0) {
|
| 1232 |
-
bookContent.innerHTML = bookContent.textContent;
|
| 1233 |
-
}
|
| 1234 |
-
}
|
| 1235 |
-
|
| 1236 |
function estimateReadingTime(text) {
|
| 1237 |
const wordsPerMinute = 200;
|
| 1238 |
const words = text.split(' ').length;
|
|
|
|
| 119 |
margin-bottom: 1.5rem;
|
| 120 |
text-align: justify;
|
| 121 |
}
|
| 122 |
+
|
| 123 |
+
.highlight-word {
|
| 124 |
+
background-color: rgba(59, 130, 246, 0.3);
|
| 125 |
+
border-radius: 2px;
|
| 126 |
+
transition: background-color 0.2s ease;
|
| 127 |
+
}
|
| 128 |
|
| 129 |
.book-content img {
|
| 130 |
max-width: 100%;
|
|
|
|
| 388 |
justify-content: center;
|
| 389 |
}
|
| 390 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 391 |
@keyframes pulse {
|
| 392 |
0%, 100% { opacity: 1; }
|
| 393 |
50% { opacity: 0.7; }
|
|
|
|
| 1075 |
synthesis.cancel();
|
| 1076 |
}
|
| 1077 |
|
| 1078 |
+
// Clear previous highlights
|
| 1079 |
+
const highlighted = document.querySelectorAll('.highlight-word');
|
| 1080 |
+
highlighted.forEach(el => el.classList.remove('highlight-word'));
|
| 1081 |
|
| 1082 |
currentUtterance = new SpeechSynthesisUtterance(text);
|
| 1083 |
currentUtterance.rate = audioSpeed;
|
|
|
|
| 1091 |
currentUtterance.addEventListener('end', () => {
|
| 1092 |
isPlaying = false;
|
| 1093 |
document.getElementById('playBtn').innerHTML = '<i class="fas fa-play"></i>';
|
|
|
|
| 1094 |
});
|
| 1095 |
|
| 1096 |
currentUtterance.addEventListener('boundary', (event) => {
|
| 1097 |
+
// Clear previous highlights
|
| 1098 |
+
const highlighted = document.querySelectorAll('.highlight-word');
|
| 1099 |
+
highlighted.forEach(el => el.classList.remove('highlight-word'));
|
| 1100 |
+
|
| 1101 |
+
// Highlight current word
|
| 1102 |
if (event.name === 'word') {
|
| 1103 |
+
const textNode = event.utterance.text.substring(0, event.charIndex);
|
| 1104 |
+
const wordStart = textNode.lastIndexOf(' ') + 1;
|
| 1105 |
+
const wordEnd = event.utterance.text.indexOf(' ', event.charIndex);
|
| 1106 |
+
const word = event.utterance.text.substring(wordStart, wordEnd === -1 ? event.utterance.text.length : wordEnd);
|
|
|
|
|
|
|
| 1107 |
|
| 1108 |
+
// Find and highlight the word in the DOM
|
| 1109 |
+
highlightWordInContent(word, event.charIndex);
|
| 1110 |
+
}
|
| 1111 |
+
});
|
| 1112 |
+
|
| 1113 |
+
synthesis.speak(currentUtterance);
|
| 1114 |
+
}
|
| 1115 |
+
}
|
| 1116 |
+
|
| 1117 |
+
function highlightWordInContent(word, charIndex) {
|
| 1118 |
+
const bookContent = document.getElementById('bookContent');
|
| 1119 |
+
const textNodes = [];
|
| 1120 |
+
|
| 1121 |
+
// Find all text nodes in the content
|
| 1122 |
+
const walker = document.createTreeWalker(
|
| 1123 |
+
bookContent,
|
| 1124 |
+
NodeFilter.SHOW_TEXT,
|
| 1125 |
+
null,
|
| 1126 |
+
false
|
| 1127 |
+
);
|
| 1128 |
+
|
| 1129 |
+
let node;
|
| 1130 |
+
while (node = walker.nextNode()) {
|
| 1131 |
+
textNodes.push(node);
|
| 1132 |
+
}
|
| 1133 |
+
|
| 1134 |
+
// Find the node containing the word
|
| 1135 |
+
let cumulativeLength = 0;
|
| 1136 |
+
for (const node of textNodes) {
|
| 1137 |
+
const nodeText = node.nodeValue;
|
| 1138 |
+
const nodeLength = nodeText.length;
|
| 1139 |
+
|
| 1140 |
+
if (charIndex >= cumulativeLength && charIndex < cumulativeLength + nodeLength) {
|
| 1141 |
+
const offset = charIndex - cumulativeLength;
|
| 1142 |
+
const wordStartInNode = nodeText.lastIndexOf(' ', offset) + 1;
|
| 1143 |
+
const wordEndInNode = nodeText.indexOf(' ', offset);
|
| 1144 |
+
const wordInNode = nodeText.substring(
|
| 1145 |
+
wordStartInNode,
|
| 1146 |
+
wordEndInNode === -1 ? nodeText.length : wordEndInNode
|
| 1147 |
+
);
|
| 1148 |
+
|
| 1149 |
+
if (wordInNode === word) {
|
| 1150 |
+
// Create range and wrap the word in a span
|
| 1151 |
+
const range = document.createRange();
|
| 1152 |
+
range.setStart(node, wordStartInNode);
|
| 1153 |
+
range.setEnd(node, wordEndInNode === -1 ? nodeText.length : wordEndInNode);
|
| 1154 |
|
| 1155 |
+
const span = document.createElement('span');
|
| 1156 |
+
span.className = 'highlight-word';
|
| 1157 |
+
range.surroundContents(span);
|
|
|
|
| 1158 |
|
| 1159 |
+
// Scroll to the word
|
| 1160 |
+
span.scrollIntoView({
|
|
|
|
| 1161 |
behavior: 'smooth',
|
| 1162 |
block: 'center'
|
| 1163 |
});
|
|
|
|
| 1164 |
}
|
| 1165 |
+
break;
|
| 1166 |
+
}
|
| 1167 |
|
| 1168 |
+
cumulativeLength += nodeLength;
|
| 1169 |
}
|
| 1170 |
}
|
| 1171 |
|
|
|
|
| 1182 |
synthesis.cancel();
|
| 1183 |
isPlaying = false;
|
| 1184 |
document.getElementById('playBtn').innerHTML = '<i class="fas fa-play"></i>';
|
|
|
|
| 1185 |
|
| 1186 |
+
// Clear all highlights
|
| 1187 |
+
const highlighted = document.querySelectorAll('.highlight-word');
|
| 1188 |
+
highlighted.forEach(el => {
|
| 1189 |
+
const parent = el.parentNode;
|
| 1190 |
+
parent.replaceChild(document.createTextNode(el.textContent), el);
|
| 1191 |
+
parent.normalize();
|
| 1192 |
+
});
|
|
|
|
| 1193 |
}
|
| 1194 |
}
|
| 1195 |
|
|
|
|
| 1232 |
voicePitch = parseFloat(pitch);
|
| 1233 |
}
|
| 1234 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1235 |
function estimateReadingTime(text) {
|
| 1236 |
const wordsPerMinute = 200;
|
| 1237 |
const words = text.split(' ').length;
|