apolinario commited on
Commit
3a2a2e5
·
1 Parent(s): d2356ec

Citrus theme, 3D buttons, voice picker dropdown, settings accordion

Browse files
Files changed (1) hide show
  1. index.html +135 -50
index.html CHANGED
@@ -8,22 +8,22 @@
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
9
  <style>
10
  :root {
11
- --bg: #09090b;
12
- --surface: #111113;
13
- --surface2: #18181b;
14
- --surface3: #1f1f23;
15
- --border: #27272a;
16
- --border-hover: #3f3f46;
17
- --text: #fafafa;
18
- --text-secondary: #a1a1aa;
19
- --text-muted: #71717a;
20
- --accent: #8b5cf6;
21
- --accent-hover: #a78bfa;
22
- --accent-subtle: rgba(139,92,246,0.1);
23
- --accent-glow: rgba(139,92,246,0.15);
24
  --danger: #ef4444;
25
  --success: #22c55e;
26
- --warning: #f59e0b;
27
  }
28
 
29
  * { margin: 0; padding: 0; box-sizing: border-box; }
@@ -66,7 +66,7 @@ body {
66
  .logo-icon {
67
  width: 28px;
68
  height: 28px;
69
- background: linear-gradient(135deg, var(--accent), #ec4899);
70
  border-radius: 7px;
71
  display: flex;
72
  align-items: center;
@@ -281,29 +281,65 @@ body {
281
  }
282
  .lang-select:focus { outline: none; border-color: var(--accent); }
283
 
284
- /* Settings */
285
- .settings-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
  .setting-group { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; }
287
  .setting-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); margin-bottom: 8px; }
288
  .setting-value { display: flex; align-items: center; gap: 10px; }
289
  .setting-value input[type="range"] { flex: 1; accent-color: var(--accent); height: 4px; }
290
  .setting-num { font-size: 13px; color: var(--text-secondary); min-width: 30px; text-align: right; font-variant-numeric: tabular-nums; }
291
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  /* Generate */
293
  .generate-bar { display: flex; gap: 12px; align-items: center; }
294
 
295
  .btn-generate {
296
  flex: 1; padding: 14px 24px;
297
- background: linear-gradient(135deg, var(--accent), #7c3aed);
298
- color: white; border: none; border-radius: 10px;
299
  font-size: 14px; font-weight: 600; font-family: 'Inter', sans-serif;
300
- cursor: pointer; transition: all 0.2s;
301
  display: flex; align-items: center; justify-content: center; gap: 8px;
 
 
302
  }
303
- .btn-generate:hover { filter: brightness(1.1); transform: translateY(-1px); }
304
- .btn-generate:active { transform: translateY(0); }
305
- .btn-generate:disabled { opacity: 0.4; cursor: not-allowed; transform: none; filter: none; }
306
- .btn-generate .spinner { width: 16px; height: 16px; border: 2px solid rgba(255,255,255,0.3); border-top-color: white; border-radius: 50%; animation: spin 0.6s linear infinite; display: none; }
307
  .btn-generate.loading .spinner { display: block; }
308
  .btn-generate.loading .btn-text { display: none; }
309
  @keyframes spin { to { transform: rotate(360deg); } }
@@ -314,7 +350,7 @@ body {
314
  font-size: 13px; display: none; gap: 8px; flex-direction: column;
315
  }
316
  .status-bar.visible { display: flex; }
317
- .status-bar.info { background: rgba(139,92,246,0.08); color: var(--accent-hover); border: 1px solid rgba(139,92,246,0.15); }
318
  .status-bar.success { background: rgba(34,197,94,0.08); color: var(--success); border: 1px solid rgba(34,197,94,0.15); }
319
  .status-bar.error { background: rgba(239,68,68,0.08); color: var(--danger); border: 1px solid rgba(239,68,68,0.15); }
320
 
@@ -328,12 +364,12 @@ body {
328
  }
329
  .status-cta.upgrade { background: #ff9d00; color: #000; }
330
  .status-cta.upgrade:hover { background: #ffb340; }
331
- .status-cta.credits { background: var(--accent); color: white; }
332
  .status-cta.credits:hover { background: var(--accent-hover); }
333
 
334
  .progress-track { width: 100%; height: 3px; background: var(--surface3); border-radius: 2px; margin-top: 6px; overflow: hidden; display: none; }
335
  .progress-track.visible { display: block; }
336
- .progress-fill { height: 100%; background: linear-gradient(90deg, var(--accent), #ec4899); border-radius: 2px; transition: width 0.3s; width: 0%; }
337
 
338
  /* Player */
339
  .player-card { margin-top: 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px 20px; display: none; }
@@ -342,7 +378,7 @@ body {
342
 
343
  .play-btn {
344
  width: 40px; height: 40px; border-radius: 50%; background: var(--accent);
345
- border: none; color: white; cursor: pointer;
346
  display: flex; align-items: center; justify-content: center; flex-shrink: 0; transition: all 0.15s;
347
  }
348
  .play-btn:hover { background: var(--accent-hover); transform: scale(1.05); }
@@ -396,7 +432,7 @@ body {
396
  font-family: 'Inter', sans-serif; cursor: pointer; border: none; transition: all 0.1s;
397
  }
398
 
399
- .voice-tile-btn.use { background: var(--accent); color: white; }
400
  .voice-tile-btn.use:hover { background: var(--accent-hover); }
401
  .voice-tile-btn.del { background: transparent; color: var(--text-muted); border: 1px solid var(--border); }
402
  .voice-tile-btn.del:hover { color: var(--danger); border-color: var(--danger); }
@@ -477,7 +513,7 @@ pre {
477
  }
478
  .cm { color: var(--text-muted); }
479
  .s { color: #86efac; }
480
- .k { color: #c4b5fd; }
481
  .f { color: #93c5fd; }
482
 
483
  /* ── Modal ── */
@@ -517,7 +553,7 @@ pre {
517
  .btn-modal { padding: 9px 20px; border-radius: 8px; font-size: 13px; font-weight: 500; font-family: 'Inter', sans-serif; cursor: pointer; transition: all 0.1s; border: none; }
518
  .btn-cancel { background: var(--surface2); color: var(--text-secondary); border: 1px solid var(--border); }
519
  .btn-cancel:hover { background: var(--surface3); color: var(--text); }
520
- .btn-save { background: var(--accent); color: white; }
521
  .btn-save:hover { background: var(--accent-hover); }
522
  .btn-save:disabled { opacity: 0.4; cursor: not-allowed; }
523
 
@@ -537,9 +573,11 @@ pre {
537
  .btn-hf {
538
  padding: 13px 32px; background: #ff9d00; color: #000; border: none; border-radius: 10px;
539
  font-size: 15px; font-weight: 600; font-family: 'Inter', sans-serif; cursor: pointer;
540
- display: inline-flex; align-items: center; gap: 8px; transition: all 0.2s;
 
541
  }
542
- .btn-hf:hover { background: #ffb340; transform: translateY(-1px); }
 
543
  .landing-note { font-size: 12px; color: var(--text-muted); }
544
 
545
  ::-webkit-scrollbar { width: 6px; }
@@ -627,13 +665,16 @@ pre {
627
  <div class="page-desc">Select a voice and type your text to generate speech.</div>
628
  </div>
629
 
630
- <div class="voice-selector" id="voice-selector" onclick="openAddVoice()">
631
  <div class="voice-avatar" id="sel-voice-avatar" style="background:var(--surface3)">🎤</div>
632
  <div class="voice-selector-info">
633
  <div class="voice-selector-name" id="sel-voice-name">No voice selected</div>
634
- <div class="voice-selector-hint" id="sel-voice-hint">Click to add a voice</div>
 
 
 
635
  </div>
636
- <div class="voice-selector-change">Change</div>
637
  </div>
638
 
639
  <div class="editor">
@@ -660,19 +701,27 @@ pre {
660
  </div>
661
  </div>
662
 
663
- <div class="settings-row">
664
- <div class="setting-group">
665
- <div class="setting-label">Inference Steps</div>
666
- <div class="setting-value">
667
- <input type="range" id="setting-steps" min="4" max="64" value="32">
668
- <span class="setting-num" id="steps-val">32</span>
669
- </div>
670
- </div>
671
- <div class="setting-group">
672
- <div class="setting-label">Speed</div>
673
- <div class="setting-value">
674
- <input type="range" id="setting-speed" min="50" max="150" value="100">
675
- <span class="setting-num" id="speed-val">1.0x</span>
 
 
 
 
 
 
 
 
676
  </div>
677
  </div>
678
  </div>
@@ -974,10 +1023,46 @@ function updateVoiceSelector() {
974
  avatar.textContent = "🎤";
975
  avatar.style.background = "var(--surface3)";
976
  name.textContent = "No voice selected";
977
- hint.textContent = "Click to add a voice";
978
  }
979
  }
980
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
981
  // ── Usage Tracker ──
982
 
983
  function updateUsageTracker(addSeconds) {
 
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
9
  <style>
10
  :root {
11
+ --bg: #0f0e0d;
12
+ --surface: #1c1917;
13
+ --surface2: #292524;
14
+ --surface3: #44403c;
15
+ --border: #44403c;
16
+ --border-hover: #57534e;
17
+ --text: #fafaf9;
18
+ --text-secondary: #a8a29e;
19
+ --text-muted: #78716c;
20
+ --accent: #f59e0b;
21
+ --accent-hover: #fbbf24;
22
+ --accent-subtle: rgba(245,158,11,0.1);
23
+ --accent-glow: rgba(245,158,11,0.12);
24
  --danger: #ef4444;
25
  --success: #22c55e;
26
+ --warning: #fbbf24;
27
  }
28
 
29
  * { margin: 0; padding: 0; box-sizing: border-box; }
 
66
  .logo-icon {
67
  width: 28px;
68
  height: 28px;
69
+ background: linear-gradient(135deg, var(--accent), #d97706);
70
  border-radius: 7px;
71
  display: flex;
72
  align-items: center;
 
281
  }
282
  .lang-select:focus { outline: none; border-color: var(--accent); }
283
 
284
+ /* Settings accordion */
285
+ .settings-accordion {
286
+ margin-bottom: 16px;
287
+ }
288
+ .settings-toggle {
289
+ display: flex; align-items: center; gap: 6px;
290
+ padding: 8px 0; font-size: 12px; font-weight: 500;
291
+ color: var(--text-muted); cursor: pointer; border: none; background: none;
292
+ font-family: 'Inter', sans-serif; transition: color 0.1s;
293
+ }
294
+ .settings-toggle:hover { color: var(--text-secondary); }
295
+ .settings-toggle svg { width: 14px; height: 14px; transition: transform 0.2s; }
296
+ .settings-toggle.open svg { transform: rotate(90deg); }
297
+ .settings-body { display: none; padding-top: 8px; }
298
+ .settings-body.open { display: block; }
299
+ .settings-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
300
  .setting-group { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; }
301
  .setting-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); margin-bottom: 8px; }
302
  .setting-value { display: flex; align-items: center; gap: 10px; }
303
  .setting-value input[type="range"] { flex: 1; accent-color: var(--accent); height: 4px; }
304
  .setting-num { font-size: 13px; color: var(--text-secondary); min-width: 30px; text-align: right; font-variant-numeric: tabular-nums; }
305
 
306
+ /* Voice picker dropdown */
307
+ .voice-picker-dropdown {
308
+ position: absolute; top: 100%; left: 0; right: 0;
309
+ background: var(--surface); border: 1px solid var(--border);
310
+ border-radius: 10px; margin-top: 4px; z-index: 20;
311
+ box-shadow: 0 8px 24px rgba(0,0,0,0.4);
312
+ max-height: 240px; overflow-y: auto; display: none;
313
+ }
314
+ .voice-picker-dropdown.visible { display: block; }
315
+ .voice-picker-item {
316
+ display: flex; align-items: center; gap: 10px;
317
+ padding: 10px 14px; cursor: pointer; transition: background 0.1s;
318
+ border: none; background: none; width: 100%; text-align: left;
319
+ color: var(--text); font-family: 'Inter', sans-serif; font-size: 13px;
320
+ }
321
+ .voice-picker-item:hover { background: var(--surface2); }
322
+ .voice-picker-item .voice-avatar { width: 30px; height: 30px; font-size: 13px; }
323
+ .voice-picker-item:last-child { border-top: 1px solid var(--border); color: var(--accent); }
324
+ .voice-selector { position: relative; }
325
+
326
  /* Generate */
327
  .generate-bar { display: flex; gap: 12px; align-items: center; }
328
 
329
  .btn-generate {
330
  flex: 1; padding: 14px 24px;
331
+ background: linear-gradient(135deg, #fbbf24, var(--accent));
332
+ color: #1c1917; border: none; border-radius: 10px;
333
  font-size: 14px; font-weight: 600; font-family: 'Inter', sans-serif;
334
+ cursor: pointer; transition: all 0.15s;
335
  display: flex; align-items: center; justify-content: center; gap: 8px;
336
+ box-shadow: 0 4px 0 #92400e, 0 6px 12px rgba(0,0,0,0.3);
337
+ transform: translateY(0);
338
  }
339
+ .btn-generate:hover { filter: brightness(1.05); transform: translateY(-2px); box-shadow: 0 6px 0 #92400e, 0 8px 16px rgba(0,0,0,0.3); }
340
+ .btn-generate:active { transform: translateY(2px); box-shadow: 0 1px 0 #92400e, 0 2px 4px rgba(0,0,0,0.3); }
341
+ .btn-generate:disabled { opacity: 0.4; cursor: not-allowed; transform: none; filter: none; box-shadow: none; }
342
+ .btn-generate .spinner { width: 16px; height: 16px; border: 2px solid rgba(0,0,0,0.2); border-top-color: #1c1917; border-radius: 50%; animation: spin 0.6s linear infinite; display: none; }
343
  .btn-generate.loading .spinner { display: block; }
344
  .btn-generate.loading .btn-text { display: none; }
345
  @keyframes spin { to { transform: rotate(360deg); } }
 
350
  font-size: 13px; display: none; gap: 8px; flex-direction: column;
351
  }
352
  .status-bar.visible { display: flex; }
353
+ .status-bar.info { background: rgba(245,158,11,0.08); color: var(--accent-hover); border: 1px solid rgba(245,158,11,0.15); }
354
  .status-bar.success { background: rgba(34,197,94,0.08); color: var(--success); border: 1px solid rgba(34,197,94,0.15); }
355
  .status-bar.error { background: rgba(239,68,68,0.08); color: var(--danger); border: 1px solid rgba(239,68,68,0.15); }
356
 
 
364
  }
365
  .status-cta.upgrade { background: #ff9d00; color: #000; }
366
  .status-cta.upgrade:hover { background: #ffb340; }
367
+ .status-cta.credits { background: var(--accent); color: #1c1917; }
368
  .status-cta.credits:hover { background: var(--accent-hover); }
369
 
370
  .progress-track { width: 100%; height: 3px; background: var(--surface3); border-radius: 2px; margin-top: 6px; overflow: hidden; display: none; }
371
  .progress-track.visible { display: block; }
372
+ .progress-fill { height: 100%; background: linear-gradient(90deg, var(--accent), #fcd34d); border-radius: 2px; transition: width 0.3s; width: 0%; }
373
 
374
  /* Player */
375
  .player-card { margin-top: 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px 20px; display: none; }
 
378
 
379
  .play-btn {
380
  width: 40px; height: 40px; border-radius: 50%; background: var(--accent);
381
+ border: none; color: #1c1917; cursor: pointer;
382
  display: flex; align-items: center; justify-content: center; flex-shrink: 0; transition: all 0.15s;
383
  }
384
  .play-btn:hover { background: var(--accent-hover); transform: scale(1.05); }
 
432
  font-family: 'Inter', sans-serif; cursor: pointer; border: none; transition: all 0.1s;
433
  }
434
 
435
+ .voice-tile-btn.use { background: var(--accent); color: #1c1917; font-weight: 600; }
436
  .voice-tile-btn.use:hover { background: var(--accent-hover); }
437
  .voice-tile-btn.del { background: transparent; color: var(--text-muted); border: 1px solid var(--border); }
438
  .voice-tile-btn.del:hover { color: var(--danger); border-color: var(--danger); }
 
513
  }
514
  .cm { color: var(--text-muted); }
515
  .s { color: #86efac; }
516
+ .k { color: #fcd34d; }
517
  .f { color: #93c5fd; }
518
 
519
  /* ── Modal ── */
 
553
  .btn-modal { padding: 9px 20px; border-radius: 8px; font-size: 13px; font-weight: 500; font-family: 'Inter', sans-serif; cursor: pointer; transition: all 0.1s; border: none; }
554
  .btn-cancel { background: var(--surface2); color: var(--text-secondary); border: 1px solid var(--border); }
555
  .btn-cancel:hover { background: var(--surface3); color: var(--text); }
556
+ .btn-save { background: var(--accent); color: #1c1917; font-weight: 600; }
557
  .btn-save:hover { background: var(--accent-hover); }
558
  .btn-save:disabled { opacity: 0.4; cursor: not-allowed; }
559
 
 
573
  .btn-hf {
574
  padding: 13px 32px; background: #ff9d00; color: #000; border: none; border-radius: 10px;
575
  font-size: 15px; font-weight: 600; font-family: 'Inter', sans-serif; cursor: pointer;
576
+ display: inline-flex; align-items: center; gap: 8px; transition: all 0.15s;
577
+ box-shadow: 0 4px 0 #b45309, 0 6px 12px rgba(0,0,0,0.3);
578
  }
579
+ .btn-hf:hover { background: #ffb340; transform: translateY(-2px); box-shadow: 0 6px 0 #b45309, 0 8px 16px rgba(0,0,0,0.3); }
580
+ .btn-hf:active { transform: translateY(2px); box-shadow: 0 1px 0 #b45309, 0 2px 4px rgba(0,0,0,0.3); }
581
  .landing-note { font-size: 12px; color: var(--text-muted); }
582
 
583
  ::-webkit-scrollbar { width: 6px; }
 
665
  <div class="page-desc">Select a voice and type your text to generate speech.</div>
666
  </div>
667
 
668
+ <div class="voice-selector" id="voice-selector" onclick="toggleVoicePicker(event)">
669
  <div class="voice-avatar" id="sel-voice-avatar" style="background:var(--surface3)">🎤</div>
670
  <div class="voice-selector-info">
671
  <div class="voice-selector-name" id="sel-voice-name">No voice selected</div>
672
+ <div class="voice-selector-hint" id="sel-voice-hint">Click to select or add a voice</div>
673
+ </div>
674
+ <div class="voice-selector-change">
675
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
676
  </div>
677
+ <div class="voice-picker-dropdown" id="voice-picker"></div>
678
  </div>
679
 
680
  <div class="editor">
 
701
  </div>
702
  </div>
703
 
704
+ <div class="settings-accordion">
705
+ <button class="settings-toggle" onclick="this.classList.toggle('open');this.nextElementSibling.classList.toggle('open')">
706
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
707
+ Advanced Settings
708
+ </button>
709
+ <div class="settings-body">
710
+ <div class="settings-row">
711
+ <div class="setting-group">
712
+ <div class="setting-label">Inference Steps</div>
713
+ <div class="setting-value">
714
+ <input type="range" id="setting-steps" min="4" max="64" value="32">
715
+ <span class="setting-num" id="steps-val">32</span>
716
+ </div>
717
+ </div>
718
+ <div class="setting-group">
719
+ <div class="setting-label">Speed</div>
720
+ <div class="setting-value">
721
+ <input type="range" id="setting-speed" min="50" max="150" value="100">
722
+ <span class="setting-num" id="speed-val">1.0x</span>
723
+ </div>
724
+ </div>
725
  </div>
726
  </div>
727
  </div>
 
1023
  avatar.textContent = "🎤";
1024
  avatar.style.background = "var(--surface3)";
1025
  name.textContent = "No voice selected";
1026
+ hint.textContent = "Click to select or add a voice";
1027
  }
1028
  }
1029
 
1030
+ // ── Voice Picker Dropdown ──
1031
+
1032
+ window.toggleVoicePicker = async function(e) {
1033
+ e.stopPropagation();
1034
+ const picker = document.getElementById("voice-picker");
1035
+ if (picker.classList.contains("visible")) {
1036
+ picker.classList.remove("visible");
1037
+ return;
1038
+ }
1039
+ const voices = await getVoices();
1040
+ picker.innerHTML = voices.map(v => `
1041
+ <button class="voice-picker-item" onclick="event.stopPropagation();pickVoice('${v.id}')">
1042
+ <div class="voice-avatar" style="background:${v.color}">${v.name[0].toUpperCase()}</div>
1043
+ <span>${v.name}</span>
1044
+ </button>
1045
+ `).join("") + `
1046
+ <button class="voice-picker-item" onclick="event.stopPropagation();closePickerAndAdd()">
1047
+ + Add new voice
1048
+ </button>`;
1049
+ picker.classList.add("visible");
1050
+ };
1051
+
1052
+ window.pickVoice = async function(id) {
1053
+ document.getElementById("voice-picker").classList.remove("visible");
1054
+ await window.selectVoice(id);
1055
+ };
1056
+
1057
+ window.closePickerAndAdd = function() {
1058
+ document.getElementById("voice-picker").classList.remove("visible");
1059
+ openAddVoice();
1060
+ };
1061
+
1062
+ document.addEventListener("click", () => {
1063
+ document.getElementById("voice-picker")?.classList.remove("visible");
1064
+ });
1065
+
1066
  // ── Usage Tracker ──
1067
 
1068
  function updateUsageTracker(addSeconds) {