Toowired commited on
Commit
72f32d0
·
verified ·
1 Parent(s): 6d56bff

Add 3 files

Browse files
Files changed (3) hide show
  1. README.md +7 -5
  2. index.html +1607 -19
  3. prompts.txt +2 -0
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Reader
3
- emoji: 🐨
4
- colorFrom: pink
5
- colorTo: gray
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: reader
3
+ emoji: 🐳
4
+ colorFrom: purple
5
+ colorTo: yellow
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1607 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HD TTS Document Reader | Natural Human Voices</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
11
+
12
+ body {
13
+ font-family: 'Inter', sans-serif;
14
+ background-color: #f8fafc;
15
+ }
16
+
17
+ .waveform {
18
+ background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 50%, #ec4899 100%);
19
+ height: 60px;
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: space-between;
23
+ padding: 0 10px;
24
+ border-radius: 8px;
25
+ position: relative;
26
+ overflow: hidden;
27
+ }
28
+
29
+ .waveform-bar {
30
+ width: 3px;
31
+ background-color: rgba(255, 255, 255, 0.7);
32
+ border-radius: 3px;
33
+ animation: equalize 1.5s infinite ease-in-out;
34
+ animation-delay: calc(var(--delay) * 0.1s);
35
+ }
36
+
37
+ @keyframes equalize {
38
+ 0%, 100% { height: 10px; }
39
+ 50% { height: 30px; }
40
+ }
41
+
42
+ .document-container {
43
+ max-height: 400px;
44
+ overflow-y: auto;
45
+ scrollbar-width: thin;
46
+ }
47
+
48
+ .document-container::-webkit-scrollbar {
49
+ width: 6px;
50
+ }
51
+
52
+ .document-container::-webkit-scrollbar-track {
53
+ background: #f1f5f9;
54
+ }
55
+
56
+ .document-container::-webkit-scrollbar-thumb {
57
+ background-color: #cbd5e1;
58
+ border-radius: 20px;
59
+ }
60
+
61
+ .highlight {
62
+ background-color: #fef08a;
63
+ transition: background-color 0.3s ease;
64
+ }
65
+
66
+ .voice-option:hover {
67
+ transform: scale(1.03);
68
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
69
+ }
70
+
71
+ .progress-bar {
72
+ height: 4px;
73
+ background-color: #e2e8f0;
74
+ border-radius: 2px;
75
+ overflow: hidden;
76
+ }
77
+
78
+ .progress-fill {
79
+ height: 100%;
80
+ background-color: #3b82f6;
81
+ width: 0%;
82
+ transition: width 0.1s linear;
83
+ }
84
+
85
+ .settings-panel {
86
+ transition: all 0.3s ease;
87
+ max-height: 0;
88
+ overflow: hidden;
89
+ }
90
+
91
+ .settings-panel.open {
92
+ max-height: 500px;
93
+ }
94
+
95
+ .tab-active {
96
+ border-bottom: 2px solid #3b82f6;
97
+ color: #3b82f6;
98
+ font-weight: 500;
99
+ }
100
+
101
+ .language-flag {
102
+ width: 24px;
103
+ height: 16px;
104
+ display: inline-block;
105
+ margin-right: 8px;
106
+ background-size: cover;
107
+ border-radius: 2px;
108
+ }
109
+
110
+ /* Language flags */
111
+ .flag-us {
112
+ background-image: url('https://flagcdn.com/us.svg');
113
+ }
114
+
115
+ .flag-uk {
116
+ background-image: url('https://flagcdn.com/gb.svg');
117
+ }
118
+
119
+ .flag-au {
120
+ background-image: url('https://flagcdn.com/au.svg');
121
+ }
122
+
123
+ .flag-ca {
124
+ background-image: url('https://flagcdn.com/ca.svg');
125
+ }
126
+
127
+ .flag-in {
128
+ background-image: url('https://flagcdn.com/in.svg');
129
+ }
130
+
131
+ .flag-za {
132
+ background-image: url('https://flagcdn.com/za.svg');
133
+ }
134
+
135
+ .flag-ie {
136
+ background-image: url('https://flagcdn.com/ie.svg');
137
+ }
138
+
139
+ .flag-nz {
140
+ background-image: url('https://flagcdn.com/nz.svg');
141
+ }
142
+
143
+ .typing-cursor {
144
+ display: inline-block;
145
+ width: 2px;
146
+ height: 1em;
147
+ background-color: #3b82f6;
148
+ animation: blink 1s infinite;
149
+ vertical-align: middle;
150
+ }
151
+
152
+ @keyframes blink {
153
+ 0%, 100% { opacity: 1; }
154
+ 50% { opacity: 0; }
155
+ }
156
+
157
+ .document-toolbar {
158
+ transition: all 0.3s ease;
159
+ }
160
+
161
+ .document-toolbar.hidden {
162
+ opacity: 0;
163
+ transform: translateY(-10px);
164
+ pointer-events: none;
165
+ }
166
+ </style>
167
+ </head>
168
+ <body class="min-h-screen bg-gray-50">
169
+ <div class="container mx-auto px-4 py-8 max-w-6xl">
170
+ <header class="mb-8 text-center">
171
+ <h1 class="text-4xl font-bold text-gray-800 mb-2">HD TTS Document Reader</h1>
172
+ <p class="text-lg text-gray-600">Natural human voices with long context support using Google's free HD voices</p>
173
+ <div class="mt-4 flex justify-center gap-4">
174
+ <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
175
+ <i class="fas fa-microphone mr-2"></i> HD Voices
176
+ </span>
177
+ <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-purple-100 text-purple-800">
178
+ <i class="fas fa-cloud mr-2"></i> Val Town Storage
179
+ </span>
180
+ <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
181
+ <i class="fas fa-bolt mr-2"></i> Serverless
182
+ </span>
183
+ </div>
184
+ </header>
185
+
186
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
187
+ <!-- Document Input Section -->
188
+ <div class="lg:col-span-2 bg-white rounded-xl shadow-md overflow-hidden">
189
+ <div class="p-6">
190
+ <div class="flex justify-between items-center mb-4">
191
+ <h2 class="text-2xl font-semibold text-gray-800">Document Reader</h2>
192
+ <div class="flex space-x-2">
193
+ <button id="settings-toggle" class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100">
194
+ <i class="fas fa-cog"></i>
195
+ </button>
196
+ <button id="fullscreen-toggle" class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100">
197
+ <i class="fas fa-expand"></i>
198
+ </button>
199
+ </div>
200
+ </div>
201
+
202
+ <div id="settings-panel" class="settings-panel mb-6 bg-gray-50 p-4 rounded-lg">
203
+ <div class="flex border-b mb-4">
204
+ <button class="tab-btn tab-active px-4 py-2" data-tab="formatting">Formatting</button>
205
+ <button class="tab-btn px-4 py-2" data-tab="pronunciation">Pronunciation</button>
206
+ <button class="tab-btn px-4 py-2" data-tab="advanced">Advanced</button>
207
+ </div>
208
+
209
+ <div id="formatting-tab" class="tab-content">
210
+ <div class="grid grid-cols-2 gap-4">
211
+ <div>
212
+ <label class="block text-sm font-medium text-gray-700 mb-1">Paragraph Spacing</label>
213
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2 text-sm">
214
+ <option>Single</option>
215
+ <option>1.5 lines</option>
216
+ <option>Double</option>
217
+ </select>
218
+ </div>
219
+ <div>
220
+ <label class="block text-sm font-medium text-gray-700 mb-1">Font Size</label>
221
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2 text-sm">
222
+ <option>Small</option>
223
+ <option selected>Medium</option>
224
+ <option>Large</option>
225
+ <option>Extra Large</option>
226
+ </select>
227
+ </div>
228
+ </div>
229
+ <div class="mt-4">
230
+ <label class="block text-sm font-medium text-gray-700 mb-2">Text Alignment</label>
231
+ <div class="flex space-x-2">
232
+ <button class="p-2 border rounded-md hover:bg-gray-100">
233
+ <i class="fas fa-align-left"></i>
234
+ </button>
235
+ <button class="p-2 border rounded-md hover:bg-gray-100">
236
+ <i class="fas fa-align-center"></i>
237
+ </button>
238
+ <button class="p-2 border rounded-md hover:bg-gray-100">
239
+ <i class="fas fa-align-right"></i>
240
+ </button>
241
+ <button class="p-2 border rounded-md hover:bg-gray-100">
242
+ <i class="fas fa-align-justify"></i>
243
+ </button>
244
+ </div>
245
+ </div>
246
+ </div>
247
+
248
+ <div id="pronunciation-tab" class="tab-content hidden">
249
+ <div class="mb-4">
250
+ <label class="block text-sm font-medium text-gray-700 mb-2">Phonetic Adjustments</label>
251
+ <div class="space-y-2">
252
+ <div class="flex items-center">
253
+ <input type="checkbox" id="phonetic-help" class="mr-2">
254
+ <label for="phonetic-help" class="text-sm">Enable phonetic assistance</label>
255
+ </div>
256
+ <div class="flex items-center">
257
+ <input type="checkbox" id="auto-pronounce" class="mr-2" checked>
258
+ <label for="auto-pronounce" class="text-sm">Auto-correct pronunciation</label>
259
+ </div>
260
+ </div>
261
+ </div>
262
+ <div>
263
+ <label class="block text-sm font-medium text-gray-700 mb-2">Custom Pronunciations</label>
264
+ <div class="flex items-center mb-2">
265
+ <input type="text" placeholder="Word" class="border border-gray-300 rounded-md px-3 py-1 text-sm mr-2 w-32">
266
+ <input type="text" placeholder="Pronunciation" class="border border-gray-300 rounded-md px-3 py-1 text-sm mr-2 w-32">
267
+ <button class="text-blue-600 hover:text-blue-800 text-sm">
268
+ <i class="fas fa-plus"></i> Add
269
+ </button>
270
+ </div>
271
+ <div class="text-xs text-gray-500">
272
+ Add custom pronunciations for specific words or names
273
+ </div>
274
+ </div>
275
+ </div>
276
+
277
+ <div id="advanced-tab" class="tab-content hidden">
278
+ <div class="mb-4">
279
+ <label class="block text-sm font-medium text-gray-700 mb-2">Audio Quality</label>
280
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2 text-sm">
281
+ <option>Standard (16kHz)</option>
282
+ <option selected>High (24kHz)</option>
283
+ <option>HD (32kHz)</option>
284
+ </select>
285
+ </div>
286
+ <div class="mb-4">
287
+ <label class="block text-sm font-medium text-gray-700 mb-2">Chunk Size</label>
288
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2 text-sm">
289
+ <option>Small (better for slow connections)</option>
290
+ <option selected>Medium (balanced)</option>
291
+ <option>Large (fewer requests)</option>
292
+ </select>
293
+ </div>
294
+ <div>
295
+ <label class="block text-sm font-medium text-gray-700 mb-2">Cache Duration</label>
296
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2 text-sm">
297
+ <option>No caching</option>
298
+ <option selected>1 hour</option>
299
+ <option>1 day</option>
300
+ <option>1 week</option>
301
+ </select>
302
+ </div>
303
+ </div>
304
+ </div>
305
+
306
+ <div class="document-toolbar mb-2 flex flex-wrap gap-1">
307
+ <button class="format-btn p-2 text-gray-700 hover:bg-gray-100 rounded" data-command="bold" title="Bold">
308
+ <i class="fas fa-bold"></i>
309
+ </button>
310
+ <button class="format-btn p-2 text-gray-700 hover:bg-gray-100 rounded" data-command="italic" title="Italic">
311
+ <i class="fas fa-italic"></i>
312
+ </button>
313
+ <button class="format-btn p-2 text-gray-700 hover:bg-gray-100 rounded" data-command="underline" title="Underline">
314
+ <i class="fas fa-underline"></i>
315
+ </button>
316
+ <div class="w-px h-6 bg-gray-300 mx-1"></div>
317
+ <button class="format-btn p-2 text-gray-700 hover:bg-gray-100 rounded" data-command="insertUnorderedList" title="Bullet List">
318
+ <i class="fas fa-list-ul"></i>
319
+ </button>
320
+ <button class="format-btn p-2 text-gray-700 hover:bg-gray-100 rounded" data-command="insertOrderedList" title="Numbered List">
321
+ <i class="fas fa-list-ol"></i>
322
+ </button>
323
+ <div class="w-px h-6 bg-gray-300 mx-1"></div>
324
+ <button class="format-btn p-2 text-gray-700 hover:bg-gray-100 rounded" data-command="outdent" title="Decrease Indent">
325
+ <i class="fas fa-outdent"></i>
326
+ </button>
327
+ <button class="format-btn p-2 text-gray-700 hover:bg-gray-100 rounded" data-command="indent" title="Increase Indent">
328
+ <i class="fas fa-indent"></i>
329
+ </button>
330
+ <div class="w-px h-6 bg-gray-300 mx-1"></div>
331
+ <button class="format-btn p-2 text-gray-700 hover:bg-gray-100 rounded" data-command="createLink" title="Insert Link">
332
+ <i class="fas fa-link"></i>
333
+ </button>
334
+ <button class="format-btn p-2 text-gray-700 hover:bg-gray-100 rounded" data-command="unlink" title="Remove Link">
335
+ <i class="fas fa-unlink"></i>
336
+ </button>
337
+ </div>
338
+
339
+ <div class="mb-4">
340
+ <label for="document-text" class="block text-sm font-medium text-gray-700 mb-2">Document Text</label>
341
+ <div class="document-container bg-gray-50 p-4 rounded-lg border border-gray-200">
342
+ <div id="document-editor" class="min-h-[200px] outline-none" contenteditable="true" data-placeholder="Type or paste your text here..."></div>
343
+ <div id="document-content" class="hidden prose max-w-none"></div>
344
+ </div>
345
+ </div>
346
+
347
+ <div class="flex justify-between items-center">
348
+ <div class="text-sm text-gray-500">
349
+ <span id="word-count">0</span> words • <span id="char-count">0</span> characters
350
+ </div>
351
+ <div>
352
+ <button id="clear-btn" class="text-gray-500 hover:text-gray-700 mr-3">
353
+ <i class="fas fa-trash-alt mr-1"></i> Clear
354
+ </button>
355
+ <button id="spellcheck-btn" class="text-gray-500 hover:text-gray-700">
356
+ <i class="fas fa-spell-check mr-1"></i> Spellcheck
357
+ </button>
358
+ </div>
359
+ </div>
360
+ </div>
361
+ </div>
362
+
363
+ <!-- Voice Controls Section -->
364
+ <div class="bg-white rounded-xl shadow-md overflow-hidden">
365
+ <div class="p-6">
366
+ <h2 class="text-2xl font-semibold text-gray-800 mb-4">Voice Settings</h2>
367
+
368
+ <div class="mb-6">
369
+ <label class="block text-sm font-medium text-gray-700 mb-2">Select Language & Voice</label>
370
+ <div class="space-y-3">
371
+ <div class="voice-option cursor-pointer p-3 border rounded-lg transition bg-gray-50 hover:bg-blue-50 border-gray-200" data-voice="en-US-Wavenet-A" data-lang="en-US">
372
+ <div class="flex items-center">
373
+ <span class="language-flag flag-us"></span>
374
+ <div class="flex-1">
375
+ <div class="font-medium">James (Standard)</div>
376
+ <div class="text-xs text-gray-500">Male • Wavenet A</div>
377
+ </div>
378
+ <i class="fas fa-check text-blue-600 opacity-0 voice-check"></i>
379
+ </div>
380
+ </div>
381
+ <div class="voice-option cursor-pointer p-3 border rounded-lg transition bg-gray-50 hover:bg-purple-50 border-gray-200" data-voice="en-US-Wavenet-F" data-lang="en-US">
382
+ <div class="flex items-center">
383
+ <span class="language-flag flag-us"></span>
384
+ <div class="flex-1">
385
+ <div class="font-medium">Emily (Standard)</div>
386
+ <div class="text-xs text-gray-500">Female • Wavenet F</div>
387
+ </div>
388
+ <i class="fas fa-check text-blue-600 opacity-0 voice-check"></i>
389
+ </div>
390
+ </div>
391
+ <div class="voice-option cursor-pointer p-3 border rounded-lg transition bg-gray-50 hover:bg-green-50 border-gray-200" data-voice="en-GB-Wavenet-B" data-lang="en-GB">
392
+ <div class="flex items-center">
393
+ <span class="language-flag flag-uk"></span>
394
+ <div class="flex-1">
395
+ <div class="font-medium">Oliver (British)</div>
396
+ <div class="text-xs text-gray-500">Male • Wavenet B</div>
397
+ </div>
398
+ <i class="fas fa-check text-blue-600 opacity-0 voice-check"></i>
399
+ </div>
400
+ </div>
401
+ <div class="voice-option cursor-pointer p-3 border rounded-lg transition bg-gray-50 hover:bg-pink-50 border-gray-200" data-voice="en-AU-Wavenet-A" data-lang="en-AU">
402
+ <div class="flex items-center">
403
+ <span class="language-flag flag-au"></span>
404
+ <div class="flex-1">
405
+ <div class="font-medium">Charlotte (Australian)</div>
406
+ <div class="text-xs text-gray-500">Female • Wavenet A</div>
407
+ </div>
408
+ <i class="fas fa-check text-blue-600 opacity-0 voice-check"></i>
409
+ </div>
410
+ </div>
411
+ <button id="show-more-voices" class="w-full text-center text-blue-600 hover:text-blue-800 text-sm py-2">
412
+ <i class="fas fa-chevron-down mr-1"></i> Show More Voices
413
+ </button>
414
+ <div id="more-voices" class="hidden space-y-3">
415
+ <div class="voice-option cursor-pointer p-3 border rounded-lg transition bg-gray-50 hover:bg-yellow-50 border-gray-200" data-voice="en-IN-Wavenet-A" data-lang="en-IN">
416
+ <div class="flex items-center">
417
+ <span class="language-flag flag-in"></span>
418
+ <div class="flex-1">
419
+ <div class="font-medium">Arjun (Indian)</div>
420
+ <div class="text-xs text-gray-500">Male • Wavenet A</div>
421
+ </div>
422
+ <i class="fas fa-check text-blue-600 opacity-0 voice-check"></i>
423
+ </div>
424
+ </div>
425
+ <div class="voice-option cursor-pointer p-3 border rounded-lg transition bg-gray-50 hover:bg-red-50 border-gray-200" data-voice="en-ZA-Wavenet-A" data-lang="en-ZA">
426
+ <div class="flex items-center">
427
+ <span class="language-flag flag-za"></span>
428
+ <div class="flex-1">
429
+ <div class="font-medium">Lebo (South African)</div>
430
+ <div class="text-xs text-gray-500">Female • Wavenet A</div>
431
+ </div>
432
+ <i class="fas fa-check text-blue-600 opacity-0 voice-check"></i>
433
+ </div>
434
+ </div>
435
+ <div class="voice-option cursor-pointer p-3 border rounded-lg transition bg-gray-50 hover:bg-indigo-50 border-gray-200" data-voice="en-IE-Wavenet-A" data-lang="en-IE">
436
+ <div class="flex items-center">
437
+ <span class="language-flag flag-ie"></span>
438
+ <div class="flex-1">
439
+ <div class="font-medium">Connor (Irish)</div>
440
+ <div class="text-xs text-gray-500">Male • Wavenet A</div>
441
+ </div>
442
+ <i class="fas fa-check text-blue-600 opacity-0 voice-check"></i>
443
+ </div>
444
+ </div>
445
+ <div class="voice-option cursor-pointer p-3 border rounded-lg transition bg-gray-50 hover:bg-teal-50 border-gray-200" data-voice="en-NZ-Wavenet-A" data-lang="en-NZ">
446
+ <div class="flex items-center">
447
+ <span class="language-flag flag-nz"></span>
448
+ <div class="flex-1">
449
+ <div class="font-medium">Tiana (New Zealand)</div>
450
+ <div class="text-xs text-gray-500">Female • Wavenet A</div>
451
+ </div>
452
+ <i class="fas fa-check text-blue-600 opacity-0 voice-check"></i>
453
+ </div>
454
+ </div>
455
+ <button id="show-less-voices" class="w-full text-center text-blue-600 hover:text-blue-800 text-sm py-2">
456
+ <i class="fas fa-chevron-up mr-1"></i> Show Fewer Voices
457
+ </button>
458
+ </div>
459
+ </div>
460
+ </div>
461
+
462
+ <div class="mb-6">
463
+ <label for="voice-speed" class="block text-sm font-medium text-gray-700 mb-2">Speed: <span id="speed-value">1.0</span>x</label>
464
+ <input type="range" id="voice-speed" min="0.5" max="2" step="0.1" value="1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
465
+ </div>
466
+
467
+ <div class="mb-6">
468
+ <label for="voice-pitch" class="block text-sm font-medium text-gray-700 mb-2">Pitch: <span id="pitch-value">0</span></label>
469
+ <input type="range" id="voice-pitch" min="-20" max="20" step="1" value="0" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
470
+ </div>
471
+
472
+ <div class="mb-6">
473
+ <label class="block text-sm font-medium text-gray-700 mb-2">Reading Mode</label>
474
+ <div class="grid grid-cols-3 gap-2">
475
+ <button class="reading-mode-btn py-2 px-3 border rounded-lg bg-blue-50 border-blue-200 text-blue-700" data-mode="normal">
476
+ <i class="fas fa-book-open mr-1"></i> Normal
477
+ </button>
478
+ <button class="reading-mode-btn py-2 px-3 border rounded-lg border-gray-200 hover:bg-gray-50" data-mode="word">
479
+ <i class="fas fa-font mr-1"></i> Word-by-word
480
+ </button>
481
+ <button class="reading-mode-btn py-2 px-3 border rounded-lg border-gray-200 hover:bg-gray-50" data-mode="sentence">
482
+ <i class="fas fa-align-left mr-1"></i> Sentence
483
+ </button>
484
+ </div>
485
+ </div>
486
+
487
+ <div class="flex space-x-3 mb-4">
488
+ <button id="play-btn" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-4 rounded-lg transition flex items-center justify-center">
489
+ <i class="fas fa-play mr-2"></i> Play
490
+ </button>
491
+ <button id="pause-btn" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-3 px-4 rounded-lg transition flex items-center justify-center" disabled>
492
+ <i class="fas fa-pause mr-2"></i> Pause
493
+ </button>
494
+ <button id="stop-btn" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-3 px-4 rounded-lg transition flex items-center justify-center" disabled>
495
+ <i class="fas fa-stop mr-2"></i> Stop
496
+ </button>
497
+ </div>
498
+
499
+ <div class="progress-bar mb-1">
500
+ <div id="progress-fill" class="progress-fill"></div>
501
+ </div>
502
+
503
+ <div class="flex justify-between text-sm text-gray-500 mb-4">
504
+ <span id="current-time">0:00</span>
505
+ <span id="total-time">0:00</span>
506
+ </div>
507
+
508
+ <div class="flex justify-between">
509
+ <button id="skip-back-btn" class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100" title="Skip Back 15s">
510
+ <i class="fas fa-backward"></i>
511
+ </button>
512
+ <button id="rewind-btn" class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100" title="Rewind">
513
+ <i class="fas fa-undo"></i>
514
+ </button>
515
+ <button id="forward-btn" class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100" title="Forward">
516
+ <i class="fas fa-redo"></i>
517
+ </button>
518
+ <button id="skip-forward-btn" class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100" title="Skip Forward 15s">
519
+ <i class="fas fa-forward"></i>
520
+ </button>
521
+ </div>
522
+ </div>
523
+ </div>
524
+ </div>
525
+
526
+ <!-- Audio Visualization -->
527
+ <div class="mt-8 bg-white rounded-xl shadow-md overflow-hidden">
528
+ <div class="p-6">
529
+ <div class="flex justify-between items-center mb-4">
530
+ <h2 class="text-2xl font-semibold text-gray-800">Audio Visualization</h2>
531
+ <div class="flex items-center space-x-2">
532
+ <button id="visualization-mode" class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100" title="Change Visualization">
533
+ <i class="fas fa-sliders-h"></i>
534
+ </button>
535
+ <button id="download-audio" class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100" title="Download Audio">
536
+ <i class="fas fa-download"></i>
537
+ </button>
538
+ </div>
539
+ </div>
540
+ <div class="waveform" id="waveform">
541
+ <div class="waveform-bar" style="--delay: 1; height: 15px;"></div>
542
+ <div class="waveform-bar" style="--delay: 2; height: 25px;"></div>
543
+ <div class="waveform-bar" style="--delay: 3; height: 10px;"></div>
544
+ <div class="waveform-bar" style="--delay: 4; height: 30px;"></div>
545
+ <div class="waveform-bar" style="--delay: 5; height: 20px;"></div>
546
+ <div class="waveform-bar" style="--delay: 6; height: 15px;"></div>
547
+ <div class="waveform-bar" style="--delay: 7; height: 25px;"></div>
548
+ <div class="waveform-bar" style="--delay: 8; height: 10px;"></div>
549
+ <div class="waveform-bar" style="--delay: 9; height: 30px;"></div>
550
+ <div class="waveform-bar" style="--delay: 10; height: 20px;"></div>
551
+ </div>
552
+ <div class="mt-4 flex justify-center">
553
+ <button id="record-voice-btn" class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg flex items-center">
554
+ <i class="fas fa-microphone mr-2"></i> Record Your Voice
555
+ </button>
556
+ </div>
557
+ </div>
558
+ </div>
559
+
560
+ <!-- Saved Documents Section -->
561
+ <div class="mt-8 bg-white rounded-xl shadow-md overflow-hidden">
562
+ <div class="p-6">
563
+ <div class="flex justify-between items-center mb-4">
564
+ <h2 class="text-2xl font-semibold text-gray-800">Saved Documents</h2>
565
+ <div class="flex space-x-2">
566
+ <button id="refresh-docs-btn" class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100" title="Refresh">
567
+ <i class="fas fa-sync-alt"></i>
568
+ </button>
569
+ <button id="save-doc-btn" class="bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded-lg transition">
570
+ <i class="fas fa-save mr-2"></i> Save Current
571
+ </button>
572
+ </div>
573
+ </div>
574
+
575
+ <div class="mb-4 flex items-center">
576
+ <div class="relative flex-1 max-w-md">
577
+ <input type="text" id="search-docs" placeholder="Search documents..." class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500">
578
+ <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
579
+ <i class="fas fa-search text-gray-400"></i>
580
+ </div>
581
+ </div>
582
+ <button id="filter-docs-btn" class="ml-2 p-2 text-gray-500 hover:text-gray-700 rounded-lg hover:bg-gray-100">
583
+ <i class="fas fa-filter"></i>
584
+ </button>
585
+ </div>
586
+
587
+ <div class="overflow-x-auto">
588
+ <table class="min-w-full divide-y divide-gray-200">
589
+ <thead class="bg-gray-50">
590
+ <tr>
591
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
592
+ <button class="flex items-center sort-btn" data-sort="title">
593
+ Title <i class="fas fa-sort ml-1"></i>
594
+ </button>
595
+ </th>
596
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
597
+ <button class="flex items-center sort-btn" data-sort="date">
598
+ Date <i class="fas fa-sort ml-1"></i>
599
+ </button>
600
+ </th>
601
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
602
+ <button class="flex items-center sort-btn" data-sort="length">
603
+ Length <i class="fas fa-sort ml-1"></i>
604
+ </button>
605
+ </th>
606
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
607
+ <button class="flex items-center sort-btn" data-sort="language">
608
+ Language <i class="fas fa-sort ml-1"></i>
609
+ </button>
610
+ </th>
611
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
612
+ </tr>
613
+ </thead>
614
+ <tbody id="saved-docs-list" class="bg-white divide-y divide-gray-200">
615
+ <tr>
616
+ <td colspan="5" class="px-6 py-4 text-center text-gray-500">No saved documents yet</td>
617
+ </tr>
618
+ </tbody>
619
+ </table>
620
+ </div>
621
+
622
+ <div class="mt-4 flex justify-between items-center">
623
+ <div class="text-sm text-gray-500">
624
+ Showing <span id="showing-from">1</span> to <span id="showing-to">0</span> of <span id="total-docs">0</span> documents
625
+ </div>
626
+ <div class="flex space-x-2">
627
+ <button id="prev-page-btn" class="px-3 py-1 border rounded-lg disabled:opacity-50" disabled>
628
+ <i class="fas fa-chevron-left"></i>
629
+ </button>
630
+ <button id="next-page-btn" class="px-3 py-1 border rounded-lg disabled:opacity-50" disabled>
631
+ <i class="fas fa-chevron-right"></i>
632
+ </button>
633
+ </div>
634
+ </div>
635
+ </div>
636
+ </div>
637
+ </div>
638
+
639
+ <!-- Modals -->
640
+ <div id="url-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
641
+ <div class="bg-white rounded-lg p-6 max-w-md w-full">
642
+ <h3 class="text-xl font-semibold mb-4">Load Document from URL</h3>
643
+ <input type="text" id="document-url" placeholder="Enter document URL" class="w-full p-3 border border-gray-300 rounded-lg mb-4">
644
+ <div class="mb-4">
645
+ <label class="block text-sm font-medium text-gray-700 mb-2">Document Type</label>
646
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2">
647
+ <option>Auto-detect</option>
648
+ <option>Plain Text</option>
649
+ <option>Web Page</option>
650
+ <option>PDF</option>
651
+ <option>Word Document</option>
652
+ </select>
653
+ </div>
654
+ <div class="flex justify-end space-x-3">
655
+ <button id="cancel-url-btn" class="px-4 py-2 border border-gray-300 rounded-lg">Cancel</button>
656
+ <button id="load-url-btn" class="px-4 py-2 bg-blue-600 text-white rounded-lg">Load</button>
657
+ </div>
658
+ </div>
659
+ </div>
660
+
661
+ <div id="save-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
662
+ <div class="bg-white rounded-lg p-6 max-w-md w-full">
663
+ <h3 class="text-xl font-semibold mb-4">Save Document</h3>
664
+ <input type="text" id="document-title" placeholder="Document title" class="w-full p-3 border border-gray-300 rounded-lg mb-4">
665
+ <div class="mb-4">
666
+ <label class="block text-sm font-medium text-gray-700 mb-2">Language</label>
667
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2">
668
+ <option>English (United States)</option>
669
+ <option>English (United Kingdom)</option>
670
+ <option>English (Australia)</option>
671
+ <option>English (India)</option>
672
+ </select>
673
+ </div>
674
+ <div class="mb-4">
675
+ <label class="block text-sm font-medium text-gray-700 mb-2">Tags</label>
676
+ <input type="text" id="document-tags" placeholder="Add tags (comma separated)" class="w-full p-3 border border-gray-300 rounded-lg">
677
+ </div>
678
+ <div class="flex justify-end space-x-3">
679
+ <button id="cancel-save-btn" class="px-4 py-2 border border-gray-300 rounded-lg">Cancel</button>
680
+ <button id="confirm-save-btn" class="px-4 py-2 bg-green-600 text-white rounded-lg">Save</button>
681
+ </div>
682
+ </div>
683
+ </div>
684
+
685
+ <div id="record-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
686
+ <div class="bg-white rounded-lg p-6 max-w-md w-full">
687
+ <h3 class="text-xl font-semibold mb-4">Record Your Voice</h3>
688
+ <div class="text-center mb-6">
689
+ <div class="w-32 h-32 mx-auto rounded-full bg-red-100 flex items-center justify-center mb-4">
690
+ <i class="fas fa-microphone text-red-600 text-4xl"></i>
691
+ </div>
692
+ <div id="record-status" class="text-lg font-medium">Ready to record</div>
693
+ <div id="record-timer" class="text-2xl font-bold my-2">00:00</div>
694
+ <div class="text-sm text-gray-500">Speak clearly into your microphone</div>
695
+ </div>
696
+ <div class="flex justify-center space-x-4">
697
+ <button id="start-record-btn" class="px-6 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg flex items-center">
698
+ <i class="fas fa-circle mr-2"></i> Start
699
+ </button>
700
+ <button id="stop-record-btn" class="px-6 py-2 bg-gray-200 hover:bg-gray-300 text-gray-800 rounded-lg flex items-center" disabled>
701
+ <i class="fas fa-stop mr-2"></i> Stop
702
+ </button>
703
+ <button id="play-record-btn" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center" disabled>
704
+ <i class="fas fa-play mr-2"></i> Play
705
+ </button>
706
+ </div>
707
+ <div class="mt-6">
708
+ <label class="block text-sm font-medium text-gray-700 mb-2">Save Recording As</label>
709
+ <div class="flex">
710
+ <input type="text" id="recording-name" placeholder="Recording name" class="flex-1 p-2 border border-gray-300 rounded-l-lg">
711
+ <button id="save-record-btn" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-r-lg">
712
+ <i class="fas fa-save mr-1"></i> Save
713
+ </button>
714
+ </div>
715
+ </div>
716
+ <div class="mt-4 flex justify-end">
717
+ <button id="close-record-btn" class="px-4 py-2 text-gray-600 hover:text-gray-800">
718
+ Close
719
+ </button>
720
+ </div>
721
+ </div>
722
+ </div>
723
+
724
+ <script>
725
+ document.addEventListener('DOMContentLoaded', function() {
726
+ // DOM Elements
727
+ const documentEditor = document.getElementById('document-editor');
728
+ const documentContent = document.getElementById('document-content');
729
+ const wordCount = document.getElementById('word-count');
730
+ const charCount = document.getElementById('char-count');
731
+ const playBtn = document.getElementById('play-btn');
732
+ const pauseBtn = document.getElementById('pause-btn');
733
+ const stopBtn = document.getElementById('stop-btn');
734
+ const clearBtn = document.getElementById('clear-btn');
735
+ const spellcheckBtn = document.getElementById('spellcheck-btn');
736
+ const formatBtns = document.querySelectorAll('.format-btn');
737
+ const settingsToggle = document.getElementById('settings-toggle');
738
+ const settingsPanel = document.getElementById('settings-panel');
739
+ const tabBtns = document.querySelectorAll('.tab-btn');
740
+ const tabContents = document.querySelectorAll('.tab-content');
741
+ const fullscreenToggle = document.getElementById('fullscreen-toggle');
742
+ const voiceSpeed = document.getElementById('voice-speed');
743
+ const voicePitch = document.getElementById('voice-pitch');
744
+ const speedValue = document.getElementById('speed-value');
745
+ const pitchValue = document.getElementById('pitch-value');
746
+ const progressFill = document.getElementById('progress-fill');
747
+ const currentTime = document.getElementById('current-time');
748
+ const totalTime = document.getElementById('total-time');
749
+ const waveform = document.getElementById('waveform');
750
+ const voiceOptions = document.querySelectorAll('.voice-option');
751
+ const showMoreVoices = document.getElementById('show-more-voices');
752
+ const showLessVoices = document.getElementById('show-less-voices');
753
+ const moreVoices = document.getElementById('more-voices');
754
+ const readingModeBtns = document.querySelectorAll('.reading-mode-btn');
755
+ const skipBackBtn = document.getElementById('skip-back-btn');
756
+ const rewindBtn = document.getElementById('rewind-btn');
757
+ const forwardBtn = document.getElementById('forward-btn');
758
+ const skipForwardBtn = document.getElementById('skip-forward-btn');
759
+ const visualizationMode = document.getElementById('visualization-mode');
760
+ const downloadAudio = document.getElementById('download-audio');
761
+ const recordVoiceBtn = document.getElementById('record-voice-btn');
762
+ const savedDocsList = document.getElementById('saved-docs-list');
763
+ const saveDocBtn = document.getElementById('save-doc-btn');
764
+ const refreshDocsBtn = document.getElementById('refresh-docs-btn');
765
+ const searchDocs = document.getElementById('search-docs');
766
+ const filterDocsBtn = document.getElementById('filter-docs-btn');
767
+ const sortBtns = document.querySelectorAll('.sort-btn');
768
+ const prevPageBtn = document.getElementById('prev-page-btn');
769
+ const nextPageBtn = document.getElementById('next-page-btn');
770
+ const showingFrom = document.getElementById('showing-from');
771
+ const showingTo = document.getElementById('showing-to');
772
+ const totalDocs = document.getElementById('total-docs');
773
+ const urlModal = document.getElementById('url-modal');
774
+ const documentUrl = document.getElementById('document-url');
775
+ const cancelUrlBtn = document.getElementById('cancel-url-btn');
776
+ const loadUrlBtn = document.getElementById('load-url-btn');
777
+ const saveModal = document.getElementById('save-modal');
778
+ const documentTitle = document.getElementById('document-title');
779
+ const cancelSaveBtn = document.getElementById('cancel-save-btn');
780
+ const confirmSaveBtn = document.getElementById('confirm-save-btn');
781
+ const recordModal = document.getElementById('record-modal');
782
+ const startRecordBtn = document.getElementById('start-record-btn');
783
+ const stopRecordBtn = document.getElementById('stop-record-btn');
784
+ const playRecordBtn = document.getElementById('play-record-btn');
785
+ const saveRecordBtn = document.getElementById('save-record-btn');
786
+ const closeRecordBtn = document.getElementById('close-record-btn');
787
+ const recordStatus = document.getElementById('record-status');
788
+ const recordTimer = document.getElementById('record-timer');
789
+ const recordingName = document.getElementById('recording-name');
790
+
791
+ // State variables
792
+ let selectedVoice = 'en-US-Wavenet-A';
793
+ let selectedLanguage = 'en-US';
794
+ let isPlaying = false;
795
+ let currentPosition = 0;
796
+ let speechSynthesis = window.speechSynthesis;
797
+ let utterance = null;
798
+ let savedDocuments = [];
799
+ let currentPage = 1;
800
+ const docsPerPage = 5;
801
+ let sortField = 'date';
802
+ let sortDirection = 'desc';
803
+ let isRecording = false;
804
+ let mediaRecorder;
805
+ let audioChunks = [];
806
+ let recordStartTime;
807
+ let recordInterval;
808
+ let isFullscreen = false;
809
+ let documentToolbarVisible = true;
810
+
811
+ // Initialize
812
+ updateCounts();
813
+ loadSavedDocuments();
814
+ setupDocumentEditor();
815
+ setupVoiceOptions();
816
+
817
+ // Event Listeners
818
+ documentEditor.addEventListener('input', updateCounts);
819
+ documentEditor.addEventListener('focus', showDocumentToolbar);
820
+ documentEditor.addEventListener('blur', hideDocumentToolbarAfterDelay);
821
+
822
+ playBtn.addEventListener('click', playTTS);
823
+ pauseBtn.addEventListener('click', pauseTTS);
824
+ stopBtn.addEventListener('click', stopTTS);
825
+ clearBtn.addEventListener('click', clearDocument);
826
+ spellcheckBtn.addEventListener('click', toggleSpellcheck);
827
+
828
+ formatBtns.forEach(btn => {
829
+ btn.addEventListener('click', () => {
830
+ const command = btn.dataset.command;
831
+ document.execCommand(command, false, null);
832
+ documentEditor.focus();
833
+ });
834
+ });
835
+
836
+ settingsToggle.addEventListener('click', toggleSettingsPanel);
837
+ fullscreenToggle.addEventListener('click', toggleFullscreen);
838
+
839
+ tabBtns.forEach(btn => {
840
+ btn.addEventListener('click', () => {
841
+ const tab = btn.dataset.tab;
842
+ switchTab(tab);
843
+ });
844
+ });
845
+
846
+ voiceSpeed.addEventListener('input', () => {
847
+ speedValue.textContent = voiceSpeed.value;
848
+ updateUtteranceSettings();
849
+ });
850
+
851
+ voicePitch.addEventListener('input', () => {
852
+ pitchValue.textContent = voicePitch.value;
853
+ updateUtteranceSettings();
854
+ });
855
+
856
+ showMoreVoices.addEventListener('click', () => {
857
+ moreVoices.classList.remove('hidden');
858
+ showMoreVoices.classList.add('hidden');
859
+ });
860
+
861
+ showLessVoices.addEventListener('click', () => {
862
+ moreVoices.classList.add('hidden');
863
+ showMoreVoices.classList.remove('hidden');
864
+ });
865
+
866
+ readingModeBtns.forEach(btn => {
867
+ btn.addEventListener('click', () => {
868
+ readingModeBtns.forEach(b => {
869
+ b.classList.remove('bg-blue-50', 'border-blue-200', 'text-blue-700');
870
+ b.classList.add('border-gray-200', 'hover:bg-gray-50');
871
+ });
872
+ btn.classList.add('bg-blue-50', 'border-blue-200', 'text-blue-700');
873
+ btn.classList.remove('border-gray-200', 'hover:bg-gray-50');
874
+ });
875
+ });
876
+
877
+ skipBackBtn.addEventListener('click', () => skipTime(-15));
878
+ rewindBtn.addEventListener('click', () => skipTime(-5));
879
+ forwardBtn.addEventListener('click', () => skipTime(5));
880
+ skipForwardBtn.addEventListener('click', () => skipTime(15));
881
+
882
+ visualizationMode.addEventListener('click', changeVisualizationMode);
883
+ downloadAudio.addEventListener('click', downloadAudioFile);
884
+ recordVoiceBtn.addEventListener('click', showRecordModal);
885
+
886
+ saveDocBtn.addEventListener('click', showSaveModal);
887
+ refreshDocsBtn.addEventListener('click', loadSavedDocuments);
888
+ searchDocs.addEventListener('input', filterSavedDocuments);
889
+ filterDocsBtn.addEventListener('click', showFilterOptions);
890
+
891
+ sortBtns.forEach(btn => {
892
+ btn.addEventListener('click', () => {
893
+ const field = btn.dataset.sort;
894
+ if (sortField === field) {
895
+ sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
896
+ } else {
897
+ sortField = field;
898
+ sortDirection = 'asc';
899
+ }
900
+ updateSortButtons();
901
+ renderSavedDocuments();
902
+ });
903
+ });
904
+
905
+ prevPageBtn.addEventListener('click', () => {
906
+ if (currentPage > 1) {
907
+ currentPage--;
908
+ renderSavedDocuments();
909
+ }
910
+ });
911
+
912
+ nextPageBtn.addEventListener('click', () => {
913
+ if (currentPage * docsPerPage < savedDocuments.length) {
914
+ currentPage++;
915
+ renderSavedDocuments();
916
+ }
917
+ });
918
+
919
+ cancelUrlBtn.addEventListener('click', () => urlModal.classList.add('hidden'));
920
+ loadUrlBtn.addEventListener('click', loadFromUrl);
921
+ cancelSaveBtn.addEventListener('click', () => saveModal.classList.add('hidden'));
922
+ confirmSaveBtn.addEventListener('click', saveCurrentDocument);
923
+
924
+ startRecordBtn.addEventListener('click', startRecording);
925
+ stopRecordBtn.addEventListener('click', stopRecording);
926
+ playRecordBtn.addEventListener('click', playRecording);
927
+ saveRecordBtn.addEventListener('click', saveRecording);
928
+ closeRecordBtn.addEventListener('click', () => recordModal.classList.add('hidden'));
929
+
930
+ // Functions
931
+ function updateCounts() {
932
+ const text = documentEditor.innerText.trim();
933
+ const words = text ? text.split(/\s+/).length : 0;
934
+ const chars = text.length;
935
+ wordCount.textContent = words;
936
+ charCount.textContent = chars;
937
+ }
938
+
939
+ function setupDocumentEditor() {
940
+ documentEditor.addEventListener('keydown', (e) => {
941
+ // Handle tab key for indentation
942
+ if (e.key === 'Tab') {
943
+ e.preventDefault();
944
+ document.execCommand('indent');
945
+ }
946
+ });
947
+
948
+ // Show placeholder when empty
949
+ documentEditor.addEventListener('focus', () => {
950
+ if (documentEditor.innerText === '') {
951
+ documentEditor.dataset.placeholder = documentEditor.getAttribute('data-placeholder');
952
+ }
953
+ });
954
+
955
+ documentEditor.addEventListener('blur', () => {
956
+ if (documentEditor.innerText === '') {
957
+ documentEditor.removeAttribute('data-placeholder');
958
+ }
959
+ });
960
+ }
961
+
962
+ function showDocumentToolbar() {
963
+ if (!documentToolbarVisible) {
964
+ document.querySelector('.document-toolbar').classList.remove('hidden');
965
+ documentToolbarVisible = true;
966
+ }
967
+ }
968
+
969
+ function hideDocumentToolbarAfterDelay() {
970
+ setTimeout(() => {
971
+ if (!documentEditor.contains(document.activeElement)) {
972
+ document.querySelector('.document-toolbar').classList.add('hidden');
973
+ documentToolbarVisible = false;
974
+ }
975
+ }, 200);
976
+ }
977
+
978
+ function toggleSpellcheck() {
979
+ documentEditor.spellcheck = !documentEditor.spellcheck;
980
+ spellcheckBtn.classList.toggle('text-blue-600');
981
+ }
982
+
983
+ function toggleSettingsPanel() {
984
+ settingsPanel.classList.toggle('open');
985
+ settingsToggle.classList.toggle('text-blue-600');
986
+ }
987
+
988
+ function toggleFullscreen() {
989
+ const docContainer = document.querySelector('.document-container');
990
+
991
+ if (!isFullscreen) {
992
+ docContainer.requestFullscreen?.();
993
+ docContainer.classList.add('max-h-[80vh]');
994
+ fullscreenToggle.innerHTML = '<i class="fas fa-compress"></i>';
995
+ } else {
996
+ document.exitFullscreen?.();
997
+ docContainer.classList.remove('max-h-[80vh]');
998
+ fullscreenToggle.innerHTML = '<i class="fas fa-expand"></i>';
999
+ }
1000
+
1001
+ isFullscreen = !isFullscreen;
1002
+ }
1003
+
1004
+ function switchTab(tab) {
1005
+ tabBtns.forEach(btn => {
1006
+ if (btn.dataset.tab === tab) {
1007
+ btn.classList.add('tab-active');
1008
+ } else {
1009
+ btn.classList.remove('tab-active');
1010
+ }
1011
+ });
1012
+
1013
+ tabContents.forEach(content => {
1014
+ if (content.id === `${tab}-tab`) {
1015
+ content.classList.remove('hidden');
1016
+ } else {
1017
+ content.classList.add('hidden');
1018
+ }
1019
+ });
1020
+ }
1021
+
1022
+ function setupVoiceOptions() {
1023
+ // Select first voice by default
1024
+ if (voiceOptions.length > 0) {
1025
+ voiceOptions[0].classList.add('border-blue-500', 'bg-blue-50');
1026
+ voiceOptions[0].querySelector('.voice-check').classList.remove('opacity-0');
1027
+ }
1028
+ }
1029
+
1030
+ function playTTS() {
1031
+ const text = documentEditor.innerText.trim();
1032
+ if (!text) return;
1033
+
1034
+ if (isPlaying && speechSynthesis.paused) {
1035
+ speechSynthesis.resume();
1036
+ playBtn.disabled = true;
1037
+ pauseBtn.disabled = false;
1038
+ return;
1039
+ }
1040
+
1041
+ // Stop any ongoing speech
1042
+ stopTTS();
1043
+
1044
+ // Create new utterance
1045
+ utterance = new SpeechSynthesisUtterance(text);
1046
+ utterance.voice = speechSynthesis.getVoices().find(v => v.voiceURI === selectedVoice);
1047
+ utterance.rate = parseFloat(voiceSpeed.value);
1048
+ utterance.pitch = parseFloat(voicePitch.value) / 10;
1049
+
1050
+ // Highlight words as they're spoken
1051
+ const words = text.split(/\s+/);
1052
+ let currentWordIndex = 0;
1053
+
1054
+ utterance.onboundary = (event) => {
1055
+ if (event.name === 'word') {
1056
+ // Remove previous highlight
1057
+ document.querySelectorAll('.highlight').forEach(el => {
1058
+ el.classList.remove('highlight');
1059
+ });
1060
+
1061
+ // Find current word position
1062
+ const charIndex = event.charIndex;
1063
+ const word = words.find(w => {
1064
+ const index = text.indexOf(w, charIndex);
1065
+ return index <= charIndex && index + w.length > charIndex;
1066
+ });
1067
+
1068
+ if (word) {
1069
+ // Create a temporary element to highlight the word
1070
+ const temp = document.createElement('span');
1071
+ temp.className = 'highlight';
1072
+ temp.textContent = word;
1073
+
1074
+ // Replace the word in the textarea (simulated highlighting)
1075
+ const before = text.substring(0, charIndex);
1076
+ const after = text.substring(charIndex + word.length);
1077
+ documentContent.innerHTML = before + temp.outerHTML + after;
1078
+ }
1079
+
1080
+ // Update progress
1081
+ const progress = (charIndex / text.length) * 100;
1082
+ progressFill.style.width = `${progress}%`;
1083
+
1084
+ // Update current time (simulated)
1085
+ const duration = text.length / 50; // Approximate duration in seconds
1086
+ const current = (progress / 100) * duration;
1087
+ currentTime.textContent = formatTime(current);
1088
+ totalTime.textContent = formatTime(duration);
1089
+ }
1090
+ };
1091
+
1092
+ utterance.onstart = () => {
1093
+ isPlaying = true;
1094
+ playBtn.disabled = true;
1095
+ pauseBtn.disabled = false;
1096
+ stopBtn.disabled = false;
1097
+
1098
+ // Show document content for highlighting
1099
+ documentContent.innerHTML = text;
1100
+ documentEditor.classList.add('hidden');
1101
+ documentContent.classList.remove('hidden');
1102
+
1103
+ // Animate waveform
1104
+ animateWaveform();
1105
+ };
1106
+
1107
+ utterance.onend = () => {
1108
+ isPlaying = false;
1109
+ playBtn.disabled = false;
1110
+ pauseBtn.disabled = true;
1111
+ stopBtn.disabled = true;
1112
+
1113
+ // Reset progress
1114
+ progressFill.style.width = '0%';
1115
+ currentTime.textContent = '0:00';
1116
+
1117
+ // Stop waveform animation
1118
+ stopWaveformAnimation();
1119
+
1120
+ // Show editor again
1121
+ documentEditor.classList.remove('hidden');
1122
+ documentContent.classList.add('hidden');
1123
+ };
1124
+
1125
+ utterance.onerror = (event) => {
1126
+ console.error('SpeechSynthesis error:', event);
1127
+ isPlaying = false;
1128
+ playBtn.disabled = false;
1129
+ pauseBtn.disabled = true;
1130
+ stopBtn.disabled = true;
1131
+ stopWaveformAnimation();
1132
+
1133
+ // Show editor again
1134
+ documentEditor.classList.remove('hidden');
1135
+ documentContent.classList.add('hidden');
1136
+ };
1137
+
1138
+ speechSynthesis.speak(utterance);
1139
+ }
1140
+
1141
+ function updateUtteranceSettings() {
1142
+ if (utterance) {
1143
+ utterance.rate = parseFloat(voiceSpeed.value);
1144
+ utterance.pitch = parseFloat(voicePitch.value) / 10;
1145
+
1146
+ if (isPlaying) {
1147
+ speechSynthesis.cancel();
1148
+ speechSynthesis.speak(utterance);
1149
+ }
1150
+ }
1151
+ }
1152
+
1153
+ function pauseTTS() {
1154
+ if (isPlaying) {
1155
+ speechSynthesis.pause();
1156
+ playBtn.disabled = false;
1157
+ pauseBtn.disabled = true;
1158
+ stopWaveformAnimation();
1159
+ }
1160
+ }
1161
+
1162
+ function stopTTS() {
1163
+ speechSynthesis.cancel();
1164
+ isPlaying = false;
1165
+ playBtn.disabled = false;
1166
+ pauseBtn.disabled = true;
1167
+ stopBtn.disabled = true;
1168
+ progressFill.style.width = '0%';
1169
+ currentTime.textContent = '0:00';
1170
+
1171
+ // Clear highlights
1172
+ document.querySelectorAll('.highlight').forEach(el => {
1173
+ el.classList.remove('highlight');
1174
+ });
1175
+
1176
+ documentEditor.classList.remove('hidden');
1177
+ documentContent.classList.add('hidden');
1178
+ stopWaveformAnimation();
1179
+ }
1180
+
1181
+ function skipTime(seconds) {
1182
+ if (!utterance || !isPlaying) return;
1183
+
1184
+ // In a real implementation, this would skip the audio playback
1185
+ // For demo, we'll just show a notification
1186
+ const direction = seconds > 0 ? 'forward' : 'back';
1187
+ showNotification(`Skipping ${Math.abs(seconds)} seconds ${direction}`);
1188
+ }
1189
+
1190
+ function clearDocument() {
1191
+ stopTTS();
1192
+ documentEditor.innerHTML = '';
1193
+ documentContent.innerHTML = '';
1194
+ updateCounts();
1195
+ }
1196
+
1197
+ function changeVisualizationMode() {
1198
+ // Cycle through different visualization modes
1199
+ const modes = ['waveform', 'spectrum', 'circle'];
1200
+ let currentMode = waveform.dataset.mode || 'waveform';
1201
+ let nextMode = modes[(modes.indexOf(currentMode) + 1) % modes.length];
1202
+
1203
+ waveform.dataset.mode = nextMode;
1204
+
1205
+ // Change the icon based on mode
1206
+ const icons = {
1207
+ 'waveform': 'fa-sliders-h',
1208
+ 'spectrum': 'fa-chart-bar',
1209
+ 'circle': 'fa-circle-notch'
1210
+ };
1211
+
1212
+ visualizationMode.innerHTML = `<i class="fas ${icons[nextMode]}"></i>`;
1213
+ showNotification(`Visualization mode: ${nextMode}`);
1214
+ }
1215
+
1216
+ function downloadAudioFile() {
1217
+ // In a real implementation, this would download the generated audio
1218
+ // For demo, we'll show a notification
1219
+ showNotification('Preparing audio file for download...');
1220
+
1221
+ // Simulate download after a delay
1222
+ setTimeout(() => {
1223
+ showNotification('Audio download started');
1224
+ }, 1000);
1225
+ }
1226
+
1227
+ function showRecordModal() {
1228
+ recordModal.classList.remove('hidden');
1229
+ recordingName.value = `Recording ${new Date().toLocaleString()}`;
1230
+ }
1231
+
1232
+ function startRecording() {
1233
+ // Request microphone access
1234
+ navigator.mediaDevices.getUserMedia({ audio: true })
1235
+ .then(stream => {
1236
+ isRecording = true;
1237
+ mediaRecorder = new MediaRecorder(stream);
1238
+ audioChunks = [];
1239
+
1240
+ mediaRecorder.ondataavailable = event => {
1241
+ audioChunks.push(event.data);
1242
+ };
1243
+
1244
+ mediaRecorder.onstop = () => {
1245
+ const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
1246
+ const audioUrl = URL.createObjectURL(audioBlob);
1247
+ playRecordBtn.disabled = false;
1248
+ playRecordBtn.onclick = () => {
1249
+ const audio = new Audio(audioUrl);
1250
+ audio.play();
1251
+ };
1252
+ };
1253
+
1254
+ mediaRecorder.start();
1255
+ startRecordBtn.disabled = true;
1256
+ stopRecordBtn.disabled = false;
1257
+ recordStatus.textContent = 'Recording...';
1258
+ recordStatus.className = 'text-lg font-medium text-red-600';
1259
+
1260
+ // Start timer
1261
+ recordStartTime = Date.now();
1262
+ recordInterval = setInterval(updateRecordTimer, 1000);
1263
+ updateRecordTimer();
1264
+ })
1265
+ .catch(err => {
1266
+ console.error('Error accessing microphone:', err);
1267
+ showNotification('Microphone access denied');
1268
+ });
1269
+ }
1270
+
1271
+ function updateRecordTimer() {
1272
+ const elapsed = Math.floor((Date.now() - recordStartTime) / 1000);
1273
+ const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0');
1274
+ const seconds = (elapsed % 60).toString().padStart(2, '0');
1275
+ recordTimer.textContent = `${minutes}:${seconds}`;
1276
+ }
1277
+
1278
+ function stopRecording() {
1279
+ if (!isRecording) return;
1280
+
1281
+ isRecording = false;
1282
+ mediaRecorder.stop();
1283
+ clearInterval(recordInterval);
1284
+
1285
+ // Stop all tracks
1286
+ mediaRecorder.stream.getTracks().forEach(track => track.stop());
1287
+
1288
+ startRecordBtn.disabled = false;
1289
+ stopRecordBtn.disabled = true;
1290
+ recordStatus.textContent = 'Recording stopped';
1291
+ recordStatus.className = 'text-lg font-medium text-green-600';
1292
+ }
1293
+
1294
+ function playRecording() {
1295
+ // Handled in the onstop event handler
1296
+ }
1297
+
1298
+ function saveRecording() {
1299
+ const name = recordingName.value.trim() || `Recording ${new Date().toLocaleString()}`;
1300
+ showNotification(`Recording saved as "${name}"`);
1301
+
1302
+ // In a real implementation, this would save to storage
1303
+ setTimeout(() => {
1304
+ recordModal.classList.add('hidden');
1305
+ }, 1000);
1306
+ }
1307
+
1308
+ function loadFromUrl() {
1309
+ const url = documentUrl.value.trim();
1310
+ if (!url) return;
1311
+
1312
+ // In a real implementation, this would fetch the URL content
1313
+ // For demo purposes, we'll simulate with a placeholder
1314
+ documentEditor.innerHTML = `<p>Content from ${url} would be loaded here in a real implementation.</p><p>This demo shows the UI but doesn't actually fetch URL content.</p>`;
1315
+ updateCounts();
1316
+ urlModal.classList.add('hidden');
1317
+ documentUrl.value = '';
1318
+ }
1319
+
1320
+ function showSaveModal() {
1321
+ const text = documentEditor.innerText.trim();
1322
+ if (!text) {
1323
+ showNotification('Please enter some text to save.');
1324
+ return;
1325
+ }
1326
+
1327
+ documentTitle.value = `Document ${savedDocuments.length + 1}`;
1328
+ saveModal.classList.remove('hidden');
1329
+ documentTitle.focus();
1330
+ }
1331
+
1332
+ function saveCurrentDocument() {
1333
+ const title = documentTitle.value.trim() || `Document ${savedDocuments.length + 1}`;
1334
+ const text = documentEditor.innerHTML;
1335
+ const plainText = documentEditor.innerText;
1336
+ const date = new Date().toLocaleDateString();
1337
+ const length = plainText.split(/\s+/).length;
1338
+
1339
+ // In a real implementation, this would save to Val Town or another backend
1340
+ savedDocuments.unshift({
1341
+ title,
1342
+ text,
1343
+ plainText,
1344
+ date,
1345
+ length,
1346
+ language: selectedLanguage
1347
+ });
1348
+ saveModal.classList.add('hidden');
1349
+ documentTitle.value = '';
1350
+
1351
+ // Update UI
1352
+ renderSavedDocuments();
1353
+ currentPage = 1;
1354
+
1355
+ // Simulate saving to Val Town
1356
+ console.log('Document saved to Val Town (simulated):', { title, length });
1357
+ showNotification(`Document "${title}" saved successfully`);
1358
+ }
1359
+
1360
+ function loadSavedDocuments() {
1361
+ // In a real implementation, this would load from Val Town
1362
+ // For demo, we'll use some sample data
1363
+ if (savedDocuments.length === 0) {
1364
+ savedDocuments = [
1365
+ {
1366
+ title: 'Sample Document 1',
1367
+ text: '<p>This is a sample document with some text.</p><p>It demonstrates how formatted content would appear.</p>',
1368
+ plainText: 'This is a sample document with some text.\n\nIt demonstrates how formatted content would appear.',
1369
+ date: '2023-06-15',
1370
+ length: 12,
1371
+ language: 'en-US'
1372
+ },
1373
+ {
1374
+ title: 'Meeting Notes',
1375
+ text: '<p><strong>Meeting Date:</strong> June 10, 2023</p><p><em>Attendees:</em> John, Sarah, Michael</p><ul><li>Reviewed project timeline</li><li>Discussed budget concerns</li><li>Assigned new tasks</li></ul>',
1376
+ plainText: 'Meeting Date: June 10, 2023\n\nAttendees: John, Sarah, Michael\n\n- Reviewed project timeline\n- Discussed budget concerns\n- Assigned new tasks',
1377
+ date: '2023-06-10',
1378
+ length: 20,
1379
+ language: 'en-GB'
1380
+ }
1381
+ ];
1382
+ }
1383
+
1384
+ renderSavedDocuments();
1385
+ showNotification('Documents refreshed');
1386
+ }
1387
+
1388
+ function filterSavedDocuments() {
1389
+ const searchTerm = searchDocs.value.toLowerCase();
1390
+ if (!searchTerm) {
1391
+ renderSavedDocuments();
1392
+ return;
1393
+ }
1394
+
1395
+ const filtered = savedDocuments.filter(doc =>
1396
+ doc.title.toLowerCase().includes(searchTerm) ||
1397
+ doc.plainText.toLowerCase().includes(searchTerm)
1398
+ );
1399
+
1400
+ renderSavedDocuments(filtered);
1401
+ }
1402
+
1403
+ function showFilterOptions() {
1404
+ // In a real implementation, this would show filter options
1405
+ showNotification('Filter options would appear here');
1406
+ }
1407
+
1408
+ function updateSortButtons() {
1409
+ sortBtns.forEach(btn => {
1410
+ const field = btn.dataset.sort;
1411
+ const icon = btn.querySelector('i');
1412
+
1413
+ if (field === sortField) {
1414
+ icon.className = sortDirection === 'asc' ? 'fas fa-sort-up ml-1' : 'fas fa-sort-down ml-1';
1415
+ } else {
1416
+ icon.className = 'fas fa-sort ml-1';
1417
+ }
1418
+ });
1419
+ }
1420
+
1421
+ function renderSavedDocuments(docs = savedDocuments) {
1422
+ const sortedDocs = [...docs].sort((a, b) => {
1423
+ let valA, valB;
1424
+
1425
+ if (sortField === 'date') {
1426
+ valA = new Date(a.date);
1427
+ valB = new Date(b.date);
1428
+ } else {
1429
+ valA = a[sortField];
1430
+ valB = b[sortField];
1431
+ }
1432
+
1433
+ if (typeof valA === 'string') valA = valA.toLowerCase();
1434
+ if (typeof valB === 'string') valB = valB.toLowerCase();
1435
+
1436
+ if (valA < valB) return sortDirection === 'asc' ? -1 : 1;
1437
+ if (valA > valB) return sortDirection === 'asc' ? 1 : -1;
1438
+ return 0;
1439
+ });
1440
+
1441
+ const startIdx = (currentPage - 1) * docsPerPage;
1442
+ const paginatedDocs = sortedDocs.slice(startIdx, startIdx + docsPerPage);
1443
+
1444
+ if (paginatedDocs.length === 0) {
1445
+ savedDocsList.innerHTML = `
1446
+ <tr>
1447
+ <td colspan="5" class="px-6 py-4 text-center text-gray-500">No documents found</td>
1448
+ </tr>
1449
+ `;
1450
+ } else {
1451
+ savedDocsList.innerHTML = paginatedDocs.map((doc, idx) => {
1452
+ const flagClass = doc.language === 'en-US' ? 'flag-us' :
1453
+ doc.language === 'en-GB' ? 'flag-uk' :
1454
+ doc.language === 'en-AU' ? 'flag-au' : 'flag-us';
1455
+
1456
+ return `
1457
+ <tr>
1458
+ <td class="px-6 py-4">
1459
+ <div class="font-medium text-gray-900">${doc.title}</div>
1460
+ <div class="text-sm text-gray-500 mt-1 line-clamp-2">${doc.plainText.substring(0, 100)}${doc.plainText.length > 100 ? '...' : ''}</div>
1461
+ </td>
1462
+ <td class="px-6 py-4 whitespace-nowrap">
1463
+ <div class="text-gray-500">${doc.date}</div>
1464
+ </td>
1465
+ <td class="px-6 py-4 whitespace-nowrap">
1466
+ <div class="text-gray-500">${doc.length} words</div>
1467
+ </td>
1468
+ <td class="px-6 py-4 whitespace-nowrap">
1469
+ <span class="language-flag ${flagClass}"></span>
1470
+ <span class="text-gray-500">${doc.language}</span>
1471
+ </td>
1472
+ <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
1473
+ <button class="text-blue-600 hover:text-blue-900 mr-3 load-doc-btn" data-index="${startIdx + idx}">
1474
+ <i class="fas fa-play"></i>
1475
+ </button>
1476
+ <button class="text-green-600 hover:text-green-900 mr-3 edit-doc-btn" data-index="${startIdx + idx}">
1477
+ <i class="fas fa-edit"></i>
1478
+ </button>
1479
+ <button class="text-red-600 hover:text-red-900 delete-doc-btn" data-index="${startIdx + idx}">
1480
+ <i class="fas fa-trash"></i>
1481
+ </button>
1482
+ </td>
1483
+ </tr>
1484
+ `;
1485
+ }).join('');
1486
+ }
1487
+
1488
+ // Update pagination info
1489
+ showingFrom.textContent = startIdx + 1;
1490
+ showingTo.textContent = Math.min(startIdx + paginatedDocs.length, sortedDocs.length);
1491
+ totalDocs.textContent = sortedDocs.length;
1492
+
1493
+ // Update pagination buttons
1494
+ prevPageBtn.disabled = currentPage === 1;
1495
+ nextPageBtn.disabled = currentPage * docsPerPage >= sortedDocs.length;
1496
+
1497
+ // Add event listeners to the new buttons
1498
+ document.querySelectorAll('.load-doc-btn').forEach(btn => {
1499
+ btn.addEventListener('click', (e) => {
1500
+ const index = parseInt(e.currentTarget.dataset.index);
1501
+ loadDocument(index);
1502
+ });
1503
+ });
1504
+
1505
+ document.querySelectorAll('.edit-doc-btn').forEach(btn => {
1506
+ btn.addEventListener('click', (e) => {
1507
+ const index = parseInt(e.currentTarget.dataset.index);
1508
+ editDocument(index);
1509
+ });
1510
+ });
1511
+
1512
+ document.querySelectorAll('.delete-doc-btn').forEach(btn => {
1513
+ btn.addEventListener('click', (e) => {
1514
+ const index = parseInt(e.currentTarget.dataset.index);
1515
+ deleteDocument(index);
1516
+ });
1517
+ });
1518
+ }
1519
+
1520
+ function loadDocument(index) {
1521
+ stopTTS();
1522
+ const doc = savedDocuments[index];
1523
+ documentEditor.innerHTML = doc.text;
1524
+ selectedLanguage = doc.language;
1525
+
1526
+ // Update voice selection to match document language
1527
+ voiceOptions.forEach(option => {
1528
+ if (option.dataset.lang === doc.language) {
1529
+ option.click();
1530
+ }
1531
+ });
1532
+
1533
+ updateCounts();
1534
+ showNotification(`Loaded document: ${doc.title}`);
1535
+ }
1536
+
1537
+ function editDocument(index) {
1538
+ // Just load the document for editing
1539
+ loadDocument(index);
1540
+ showNotification(`Editing "${savedDocuments[index].title}"`);
1541
+ }
1542
+
1543
+ function deleteDocument(index) {
1544
+ if (confirm(`Delete "${savedDocuments[index].title}"? This cannot be undone.`)) {
1545
+ savedDocuments.splice(index, 1);
1546
+ renderSavedDocuments();
1547
+ showNotification('Document deleted');
1548
+
1549
+ // Adjust current page if needed
1550
+ const totalPages = Math.ceil(savedDocuments.length / docsPerPage);
1551
+ if (currentPage > totalPages && totalPages > 0) {
1552
+ currentPage = totalPages;
1553
+ renderSavedDocuments();
1554
+ }
1555
+ }
1556
+ }
1557
+
1558
+ function animateWaveform() {
1559
+ const bars = waveform.querySelectorAll('.waveform-bar');
1560
+ bars.forEach(bar => {
1561
+ bar.style.animationPlayState = 'running';
1562
+ });
1563
+ }
1564
+
1565
+ function stopWaveformAnimation() {
1566
+ const bars = waveform.querySelectorAll('.waveform-bar');
1567
+ bars.forEach(bar => {
1568
+ bar.style.animationPlayState = 'paused';
1569
+ bar.style.height = '10px';
1570
+ });
1571
+ }
1572
+
1573
+ function formatTime(seconds) {
1574
+ const mins = Math.floor(seconds / 60);
1575
+ const secs = Math.floor(seconds % 60);
1576
+ return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
1577
+ }
1578
+
1579
+ function showNotification(message) {
1580
+ // In a real app, this would show a nice toast notification
1581
+ console.log('Notification:', message);
1582
+ }
1583
+
1584
+ // Load voices when they become available
1585
+ speechSynthesis.onvoiceschanged = function() {
1586
+ console.log('Voices loaded:', speechSynthesis.getVoices());
1587
+
1588
+ // Select the first voice that matches our default
1589
+ const voices = speechSynthesis.getVoices();
1590
+ const defaultVoice = voices.find(v => v.voiceURI === selectedVoice);
1591
+ if (defaultVoice) {
1592
+ utterance = new SpeechSynthesisUtterance();
1593
+ utterance.voice = defaultVoice;
1594
+ }
1595
+ };
1596
+
1597
+ // Try to load voices immediately (might not work in all browsers)
1598
+ if (speechSynthesis.getVoices().length > 0) {
1599
+ console.log('Voices already loaded:', speechSynthesis.getVoices());
1600
+ } else {
1601
+ console.log('Waiting for voices to load...');
1602
+ }
1603
+
1604
+ // Handle voice selection
1605
+ voiceOptions.forEach(option => {
1606
+ option.addEventListener
1607
+ </html>
prompts.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Hd TTS natural human voices long context document reader using google free HD voices, valtown for storage and serverless functionality etc
2
+ Continue