Skip to content

Commit f04f03d

Browse files
feat: add L3 speech read-aloud feature with paragraph highlighting
- Add speech.css: floating control panel UI (dark theme, responsive) - Add speech.js: TTS core logic with Web Speech API - Add speech references to all HTML pages
1 parent 04b5a13 commit f04f03d

26 files changed

Lines changed: 776 additions & 7 deletions

about.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,6 @@ <h2>技術資訊</h2>
5454
</div>
5555
<footer>這是 Orange Pi 感測器影像調適繁中教學站 · 由 OpenCode 建置<br>
5656
<span class="footer-license">本站教學內容(繁體中文解說)為本站原創,採 CC-BY-4.0;register 與指令引用自 <a href="https://linux-sunxi.org/" rel="noopener">linux-sunxi</a><a href="https://www.kernel.org/doc/html/latest/media/" rel="noopener">Linux V4L2 文件</a> 與公開感測器資料。</span></footer>
57-
</div></body></html>
57+
</div><link rel="stylesheet" href="assets/speech.css">
58+
<script src="assets/speech.js" defer></script>
59+
</body></html>

assets/speech.css

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
/* speech.css — 語音朗讀浮動控制面板樣式 */
2+
3+
/* ---------- 浮動面板 ---------- */
4+
.speech-panel {
5+
position: fixed;
6+
bottom: 1.5rem;
7+
right: 1.5rem;
8+
z-index: 2147483647;
9+
background: rgba(23, 23, 31, 0.96);
10+
backdrop-filter: blur(12px);
11+
-webkit-backdrop-filter: blur(12px);
12+
border: 1px solid rgba(255, 255, 255, 0.12);
13+
border-radius: 16px;
14+
padding: 0.8rem 1rem;
15+
font-family: "Noto Sans TC", "PingFang TC", sans-serif;
16+
font-size: 0.78rem;
17+
color: #eceaf4;
18+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(255, 255, 255, 0.06);
19+
transition: opacity 0.2s, transform 0.2s;
20+
min-width: 260px;
21+
}
22+
23+
.speech-panel.speech-hidden {
24+
opacity: 0;
25+
pointer-events: none;
26+
transform: translateY(12px);
27+
}
28+
29+
/* ---------- 頂列:播放控制 ---------- */
30+
.speech-row {
31+
display: flex;
32+
align-items: center;
33+
gap: 0.5rem;
34+
margin-bottom: 0.5rem;
35+
}
36+
37+
.speech-btn {
38+
display: inline-flex;
39+
align-items: center;
40+
justify-content: center;
41+
width: 36px;
42+
height: 36px;
43+
border-radius: 10px;
44+
border: 1px solid rgba(255, 255, 255, 0.12);
45+
background: rgba(255, 255, 255, 0.05);
46+
color: #eceaf4;
47+
font-size: 1rem;
48+
cursor: pointer;
49+
transition: background 0.15s, border-color 0.15s, transform 0.1s;
50+
flex-shrink: 0;
51+
}
52+
53+
.speech-btn:hover {
54+
background: rgba(34, 211, 238, 0.15);
55+
border-color: rgba(34, 211, 238, 0.4);
56+
}
57+
58+
.speech-btn:active {
59+
transform: scale(0.94);
60+
}
61+
62+
.speech-btn.active {
63+
background: rgba(34, 211, 238, 0.2);
64+
border-color: rgba(34, 211, 238, 0.6);
65+
color: #22d3ee;
66+
}
67+
68+
.speech-btn.stop {
69+
color: #f87171;
70+
}
71+
72+
.speech-btn.stop:hover {
73+
background: rgba(248, 113, 113, 0.15);
74+
border-color: rgba(248, 113, 113, 0.4);
75+
}
76+
77+
/* ---------- 語速滑桿 ---------- */
78+
.speech-speed-wrap {
79+
display: flex;
80+
align-items: center;
81+
gap: 0.4rem;
82+
flex: 1;
83+
}
84+
85+
.speech-speed-wrap label {
86+
font-size: 0.68rem;
87+
color: #a3a1b6;
88+
white-space: nowrap;
89+
}
90+
91+
.speech-speed-wrap input[type="range"] {
92+
flex: 1;
93+
height: 4px;
94+
-webkit-appearance: none;
95+
appearance: none;
96+
background: rgba(255, 255, 255, 0.1);
97+
border-radius: 2px;
98+
outline: none;
99+
}
100+
101+
.speech-speed-wrap input[type="range"]::-webkit-slider-thumb {
102+
-webkit-appearance: none;
103+
width: 14px;
104+
height: 14px;
105+
border-radius: 50%;
106+
background: #22d3ee;
107+
cursor: pointer;
108+
box-shadow: 0 0 6px rgba(34, 211, 238, 0.5);
109+
}
110+
111+
.speech-speed-wrap input[type="range"]::-moz-range-thumb {
112+
width: 14px;
113+
height: 14px;
114+
border-radius: 50%;
115+
background: #22d3ee;
116+
cursor: pointer;
117+
border: none;
118+
}
119+
120+
.speech-speed-val {
121+
font-size: 0.68rem;
122+
color: #22d3ee;
123+
min-width: 2.2em;
124+
text-align: right;
125+
}
126+
127+
/* ---------- 語音 / 語言選擇 ---------- */
128+
.speech-row2 {
129+
display: flex;
130+
align-items: center;
131+
gap: 0.5rem;
132+
margin-bottom: 0.4rem;
133+
}
134+
135+
.speech-select {
136+
flex: 1;
137+
background: rgba(255, 255, 255, 0.05);
138+
border: 1px solid rgba(255, 255, 255, 0.12);
139+
border-radius: 8px;
140+
color: #eceaf4;
141+
font-size: 0.72rem;
142+
padding: 0.3rem 0.5rem;
143+
outline: none;
144+
cursor: pointer;
145+
}
146+
147+
.speech-select:focus {
148+
border-color: rgba(34, 211, 238, 0.5);
149+
}
150+
151+
.speech-select option {
152+
background: #17171f;
153+
color: #eceaf4;
154+
}
155+
156+
/* ---------- 語言模式切換 ---------- */
157+
.speech-lang-modes {
158+
display: flex;
159+
gap: 0.3rem;
160+
}
161+
162+
.speech-lang-btn {
163+
font-size: 0.64rem;
164+
padding: 0.2rem 0.5rem;
165+
border-radius: 6px;
166+
border: 1px solid rgba(255, 255, 255, 0.1);
167+
background: rgba(255, 255, 255, 0.04);
168+
color: #a3a1b6;
169+
cursor: pointer;
170+
transition: all 0.15s;
171+
}
172+
173+
.speech-lang-btn:hover {
174+
color: #eceaf4;
175+
border-color: rgba(255, 255, 255, 0.2);
176+
}
177+
178+
.speech-lang-btn.active {
179+
background: rgba(34, 211, 238, 0.15);
180+
border-color: rgba(34, 211, 238, 0.5);
181+
color: #22d3ee;
182+
}
183+
184+
/* ---------- 狀態文字 ---------- */
185+
.speech-status {
186+
font-size: 0.64rem;
187+
color: #6e6c80;
188+
margin-top: 0.3rem;
189+
text-align: center;
190+
min-height: 1em;
191+
}
192+
193+
/* ---------- 段落高亮 ---------- */
194+
.speech-highlight {
195+
background: rgba(251, 191, 36, 0.12) !important;
196+
border-left: 3px solid #fbbf24 !important;
197+
padding-left: 0.6rem !important;
198+
border-radius: 0 6px 6px 0 !important;
199+
transition: background 0.25s, border-color 0.25s;
200+
}
201+
202+
/* ---------- 展開/收合 ---------- */
203+
.speech-toggle {
204+
position: fixed;
205+
bottom: 1.5rem;
206+
right: 1.5rem;
207+
z-index: 2147483646;
208+
width: 44px;
209+
height: 44px;
210+
border-radius: 50%;
211+
border: 1px solid rgba(255, 255, 255, 0.12);
212+
background: rgba(23, 23, 31, 0.92);
213+
backdrop-filter: blur(12px);
214+
-webkit-backdrop-filter: blur(12px);
215+
color: #22d3ee;
216+
font-size: 1.2rem;
217+
cursor: pointer;
218+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
219+
display: flex;
220+
align-items: center;
221+
justify-content: center;
222+
transition: transform 0.2s, background 0.15s;
223+
}
224+
225+
.speech-toggle:hover {
226+
transform: scale(1.08);
227+
background: rgba(34, 211, 238, 0.15);
228+
}
229+
230+
.speech-toggle.panel-open {
231+
display: none;
232+
}
233+
234+
/* ---------- 鍵盤快捷鍵提示 ---------- */
235+
.speech-hotkeys {
236+
display: flex;
237+
justify-content: center;
238+
gap: 0.6rem;
239+
margin-top: 0.35rem;
240+
font-size: 0.58rem;
241+
color: #6e6c80;
242+
}
243+
244+
.speech-hotkeys kbd {
245+
display: inline-block;
246+
background: rgba(255, 255, 255, 0.06);
247+
border: 1px solid rgba(255, 255, 255, 0.1);
248+
border-radius: 3px;
249+
padding: 0 0.3rem;
250+
font-family: inherit;
251+
font-size: 0.58rem;
252+
}
253+
254+
/* ---------- 不支援時的提示 ---------- */
255+
.speech-unsupported {
256+
position: fixed;
257+
bottom: 1.5rem;
258+
right: 1.5rem;
259+
z-index: 9999;
260+
background: rgba(23, 23, 31, 0.96);
261+
border: 1px solid rgba(248, 113, 113, 0.3);
262+
border-radius: 12px;
263+
padding: 0.6rem 0.9rem;
264+
font-size: 0.72rem;
265+
color: #f87171;
266+
}
267+
268+
/* ---------- 手機版 ---------- */
269+
@media (max-width: 600px) {
270+
.speech-panel {
271+
bottom: 0.8rem;
272+
right: 0.8rem;
273+
left: 0.8rem;
274+
min-width: unset;
275+
padding: 0.6rem 0.8rem;
276+
}
277+
278+
.speech-toggle {
279+
bottom: 0.8rem;
280+
right: 0.8rem;
281+
}
282+
283+
.speech-btn {
284+
width: 32px;
285+
height: 32px;
286+
font-size: 0.9rem;
287+
}
288+
}

0 commit comments

Comments
 (0)