apolinario commited on
Commit
cdac885
·
1 Parent(s): dcef6f0

Light/dark/auto theme with system detection and toggle

Browse files
Files changed (1) hide show
  1. index.html +108 -18
index.html CHANGED
@@ -7,7 +7,7 @@
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
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;
@@ -24,6 +24,33 @@
24
  --danger: #ef4444;
25
  --success: #22c55e;
26
  --warning: #fbbf24;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
 
29
  * { margin: 0; padding: 0; box-sizing: border-box; }
@@ -73,7 +100,7 @@ body {
73
  justify-content: center;
74
  font-size: 16px;
75
  font-weight: 800;
76
- color: #1c1917;
77
  font-family: 'Inter', sans-serif;
78
  }
79
 
@@ -332,17 +359,17 @@ body {
332
  .btn-generate {
333
  flex: 1; padding: 14px 24px;
334
  background: linear-gradient(135deg, #fbbf24, var(--accent));
335
- color: #1c1917; border: none; border-radius: 10px;
336
  font-size: 14px; font-weight: 600; font-family: 'Inter', sans-serif;
337
  cursor: pointer; transition: all 0.15s;
338
  display: flex; align-items: center; justify-content: center; gap: 8px;
339
- box-shadow: 0 4px 0 #92400e, 0 6px 12px rgba(0,0,0,0.3);
340
  transform: translateY(0);
341
  }
342
- .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); }
343
- .btn-generate:active { transform: translateY(2px); box-shadow: 0 1px 0 #92400e, 0 2px 4px rgba(0,0,0,0.3); }
344
  .btn-generate:disabled { opacity: 0.4; cursor: not-allowed; transform: none; filter: none; box-shadow: none; }
345
- .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; }
346
  .btn-generate.loading .spinner { display: block; }
347
  .btn-generate.loading .btn-text { display: none; }
348
  @keyframes spin { to { transform: rotate(360deg); } }
@@ -367,7 +394,7 @@ body {
367
  }
368
  .status-cta.upgrade { background: #ff9d00; color: #000; }
369
  .status-cta.upgrade:hover { background: #ffb340; }
370
- .status-cta.credits { background: var(--accent); color: #1c1917; }
371
  .status-cta.credits:hover { background: var(--accent-hover); }
372
 
373
  .progress-track { width: 100%; height: 3px; background: var(--surface3); border-radius: 2px; margin-top: 6px; overflow: hidden; display: none; }
@@ -381,7 +408,7 @@ body {
381
 
382
  .play-btn {
383
  width: 40px; height: 40px; border-radius: 50%; background: var(--accent);
384
- border: none; color: #1c1917; cursor: pointer;
385
  display: flex; align-items: center; justify-content: center; flex-shrink: 0; transition: all 0.15s;
386
  }
387
  .play-btn:hover { background: var(--accent-hover); transform: scale(1.05); }
@@ -435,7 +462,7 @@ body {
435
  font-family: 'Inter', sans-serif; cursor: pointer; border: none; transition: all 0.1s;
436
  }
437
 
438
- .voice-tile-btn.use { background: var(--accent); color: #1c1917; font-weight: 600; }
439
  .voice-tile-btn.use:hover { background: var(--accent-hover); }
440
  .voice-tile-btn.del { background: transparent; color: var(--text-muted); border: 1px solid var(--border); }
441
  .voice-tile-btn.del:hover { color: var(--danger); border-color: var(--danger); }
@@ -583,18 +610,30 @@ pre {
583
  display: flex; align-items: center; justify-content: center;
584
  transition: all 0.1s; flex-shrink: 0;
585
  }
586
- .preview-btn:hover { background: var(--accent); color: #1c1917; border-color: var(--accent); }
587
  .preview-btn svg { width: 12px; height: 12px; }
588
 
 
 
 
 
 
 
 
 
 
589
  .footer {
590
  text-align: center; padding: 32px 0 16px; font-size: 12px; color: var(--text-muted);
591
  }
592
  .footer a { color: var(--text-secondary); text-decoration: none; }
593
  .footer a:hover { color: var(--accent); }
594
  .cm { color: var(--text-muted); }
595
- .s { color: #86efac; }
596
- .k { color: #fcd34d; }
597
- .f { color: #93c5fd; }
 
 
 
598
 
599
  /* ── Modal ── */
600
  .modal-overlay {
@@ -633,7 +672,7 @@ pre {
633
  .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; }
634
  .btn-cancel { background: var(--surface2); color: var(--text-secondary); border: 1px solid var(--border); }
635
  .btn-cancel:hover { background: var(--surface3); color: var(--text); }
636
- .btn-save { background: var(--accent); color: #1c1917; font-weight: 600; }
637
  .btn-save:hover { background: var(--accent-hover); }
638
  .btn-save:disabled { opacity: 0.4; cursor: not-allowed; }
639
 
@@ -654,10 +693,10 @@ pre {
654
  padding: 13px 32px; background: #ff9d00; color: #000; border: none; border-radius: 10px;
655
  font-size: 15px; font-weight: 600; font-family: 'Inter', sans-serif; cursor: pointer;
656
  display: inline-flex; align-items: center; gap: 8px; transition: all 0.15s;
657
- box-shadow: 0 4px 0 #b45309, 0 6px 12px rgba(0,0,0,0.3);
658
  }
659
- .btn-hf:hover { background: #ffb340; transform: translateY(-2px); box-shadow: 0 6px 0 #b45309, 0 8px 16px rgba(0,0,0,0.3); }
660
- .btn-hf:active { transform: translateY(2px); box-shadow: 0 1px 0 #b45309, 0 2px 4px rgba(0,0,0,0.3); }
661
  .landing-note { font-size: 12px; color: var(--text-muted); }
662
 
663
  ::-webkit-scrollbar { width: 6px; }
@@ -675,12 +714,60 @@ pre {
675
  </head>
676
  <body>
677
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  <div id="landing">
679
  <div class="logo-icon" style="width:56px;height:56px;font-size:28px;border-radius:14px">0</div>
680
  <h1>ZeroLabs</h1>
681
  <p class="subtitle">Voice cloning powered by open-source models. 100x cheaper than proprietary competitors like ElevenLabs.</p>
682
  <button class="btn-hf" onclick="login()">🤗 Sign in with Hugging Face</button>
683
  <p class="landing-note">Uses your HF ZeroGPU quota &middot; Free tier included</p>
 
 
 
684
  </div>
685
 
686
  <div id="app" class="app-layout" style="display:none">
@@ -691,6 +778,9 @@ pre {
691
  ZeroLabs
692
  </div>
693
  <div class="header-right">
 
 
 
694
  <div class="user-pill" id="user-pill">
695
  <img id="user-avatar" src="" alt="">
696
  <span id="user-name"></span>
 
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
9
  <style>
10
+ :root, [data-theme="dark"] {
11
  --bg: #0f0e0d;
12
  --surface: #1c1917;
13
  --surface2: #292524;
 
24
  --danger: #ef4444;
25
  --success: #22c55e;
26
  --warning: #fbbf24;
27
+ --btn-text: #1c1917;
28
+ --shadow-color: rgba(0,0,0,0.3);
29
+ --btn-shadow: #92400e;
30
+ --btn-shadow-hf: #b45309;
31
+ }
32
+
33
+ [data-theme="light"] {
34
+ --bg: #fafaf9;
35
+ --surface: #ffffff;
36
+ --surface2: #f5f5f4;
37
+ --surface3: #e7e5e4;
38
+ --border: #d6d3d1;
39
+ --border-hover: #a8a29e;
40
+ --text: #1c1917;
41
+ --text-secondary: #57534e;
42
+ --text-muted: #78716c;
43
+ --accent: #d97706;
44
+ --accent-hover: #b45309;
45
+ --accent-subtle: rgba(217,119,6,0.08);
46
+ --accent-glow: rgba(217,119,6,0.1);
47
+ --danger: #dc2626;
48
+ --success: #16a34a;
49
+ --warning: #d97706;
50
+ --btn-text: #ffffff;
51
+ --shadow-color: rgba(0,0,0,0.1);
52
+ --btn-shadow: #92400e;
53
+ --btn-shadow-hf: #7c2d12;
54
  }
55
 
56
  * { margin: 0; padding: 0; box-sizing: border-box; }
 
100
  justify-content: center;
101
  font-size: 16px;
102
  font-weight: 800;
103
+ color: var(--btn-text);
104
  font-family: 'Inter', sans-serif;
105
  }
106
 
 
359
  .btn-generate {
360
  flex: 1; padding: 14px 24px;
361
  background: linear-gradient(135deg, #fbbf24, var(--accent));
362
+ color: var(--btn-text); border: none; border-radius: 10px;
363
  font-size: 14px; font-weight: 600; font-family: 'Inter', sans-serif;
364
  cursor: pointer; transition: all 0.15s;
365
  display: flex; align-items: center; justify-content: center; gap: 8px;
366
+ box-shadow: 0 4px 0 var(--btn-shadow), 0 6px 12px var(--shadow-color);
367
  transform: translateY(0);
368
  }
369
+ .btn-generate:hover { filter: brightness(1.05); transform: translateY(-2px); box-shadow: 0 6px 0 var(--btn-shadow), 0 8px 16px var(--shadow-color); }
370
+ .btn-generate:active { transform: translateY(2px); box-shadow: 0 1px 0 var(--btn-shadow), 0 2px 4px var(--shadow-color); }
371
  .btn-generate:disabled { opacity: 0.4; cursor: not-allowed; transform: none; filter: none; box-shadow: none; }
372
+ .btn-generate .spinner { width: 16px; height: 16px; border: 2px solid rgba(0,0,0,0.2); border-top-color: var(--btn-text); border-radius: 50%; animation: spin 0.6s linear infinite; display: none; }
373
  .btn-generate.loading .spinner { display: block; }
374
  .btn-generate.loading .btn-text { display: none; }
375
  @keyframes spin { to { transform: rotate(360deg); } }
 
394
  }
395
  .status-cta.upgrade { background: #ff9d00; color: #000; }
396
  .status-cta.upgrade:hover { background: #ffb340; }
397
+ .status-cta.credits { background: var(--accent); color: var(--btn-text); }
398
  .status-cta.credits:hover { background: var(--accent-hover); }
399
 
400
  .progress-track { width: 100%; height: 3px; background: var(--surface3); border-radius: 2px; margin-top: 6px; overflow: hidden; display: none; }
 
408
 
409
  .play-btn {
410
  width: 40px; height: 40px; border-radius: 50%; background: var(--accent);
411
+ border: none; color: var(--btn-text); cursor: pointer;
412
  display: flex; align-items: center; justify-content: center; flex-shrink: 0; transition: all 0.15s;
413
  }
414
  .play-btn:hover { background: var(--accent-hover); transform: scale(1.05); }
 
462
  font-family: 'Inter', sans-serif; cursor: pointer; border: none; transition: all 0.1s;
463
  }
464
 
465
+ .voice-tile-btn.use { background: var(--accent); color: var(--btn-text); font-weight: 600; }
466
  .voice-tile-btn.use:hover { background: var(--accent-hover); }
467
  .voice-tile-btn.del { background: transparent; color: var(--text-muted); border: 1px solid var(--border); }
468
  .voice-tile-btn.del:hover { color: var(--danger); border-color: var(--danger); }
 
610
  display: flex; align-items: center; justify-content: center;
611
  transition: all 0.1s; flex-shrink: 0;
612
  }
613
+ .preview-btn:hover { background: var(--accent); color: var(--btn-text); border-color: var(--accent); }
614
  .preview-btn svg { width: 12px; height: 12px; }
615
 
616
+ /* ── Theme toggle ── */
617
+ .theme-btn {
618
+ width: 32px; height: 32px; border-radius: 8px; border: 1px solid var(--border);
619
+ background: var(--surface2); color: var(--text-muted); cursor: pointer;
620
+ display: flex; align-items: center; justify-content: center; transition: all 0.1s;
621
+ }
622
+ .theme-btn:hover { color: var(--text); border-color: var(--border-hover); }
623
+ .theme-btn svg { width: 16px; height: 16px; }
624
+
625
  .footer {
626
  text-align: center; padding: 32px 0 16px; font-size: 12px; color: var(--text-muted);
627
  }
628
  .footer a { color: var(--text-secondary); text-decoration: none; }
629
  .footer a:hover { color: var(--accent); }
630
  .cm { color: var(--text-muted); }
631
+ [data-theme="dark"] .s { color: #86efac; }
632
+ [data-theme="dark"] .k { color: #fcd34d; }
633
+ [data-theme="dark"] .f { color: #93c5fd; }
634
+ [data-theme="light"] .s { color: #166534; }
635
+ [data-theme="light"] .k { color: #92400e; }
636
+ [data-theme="light"] .f { color: #1e40af; }
637
 
638
  /* ── Modal ── */
639
  .modal-overlay {
 
672
  .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; }
673
  .btn-cancel { background: var(--surface2); color: var(--text-secondary); border: 1px solid var(--border); }
674
  .btn-cancel:hover { background: var(--surface3); color: var(--text); }
675
+ .btn-save { background: var(--accent); color: var(--btn-text); font-weight: 600; }
676
  .btn-save:hover { background: var(--accent-hover); }
677
  .btn-save:disabled { opacity: 0.4; cursor: not-allowed; }
678
 
 
693
  padding: 13px 32px; background: #ff9d00; color: #000; border: none; border-radius: 10px;
694
  font-size: 15px; font-weight: 600; font-family: 'Inter', sans-serif; cursor: pointer;
695
  display: inline-flex; align-items: center; gap: 8px; transition: all 0.15s;
696
+ box-shadow: 0 4px 0 var(--btn-shadow-hf), 0 6px 12px var(--shadow-color);
697
  }
698
+ .btn-hf:hover { background: #ffb340; transform: translateY(-2px); box-shadow: 0 6px 0 var(--btn-shadow-hf), 0 8px 16px var(--shadow-color); }
699
+ .btn-hf:active { transform: translateY(2px); box-shadow: 0 1px 0 var(--btn-shadow-hf), 0 2px 4px var(--shadow-color); }
700
  .landing-note { font-size: 12px; color: var(--text-muted); }
701
 
702
  ::-webkit-scrollbar { width: 6px; }
 
714
  </head>
715
  <body>
716
 
717
+ <script>
718
+ // Theme: auto (system), dark, light — runs before paint to avoid flash
719
+ (function() {
720
+ const saved = localStorage.getItem("zl_theme");
721
+ function getEffective(pref) {
722
+ if (pref === "light" || pref === "dark") return pref;
723
+ return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
724
+ }
725
+ const effective = getEffective(saved || "auto");
726
+ document.documentElement.setAttribute("data-theme", effective);
727
+
728
+ window._themePref = saved || "auto"; // auto, dark, light
729
+
730
+ window.cycleTheme = function() {
731
+ const order = ["auto", "light", "dark"];
732
+ const idx = order.indexOf(window._themePref);
733
+ window._themePref = order[(idx + 1) % 3];
734
+ localStorage.setItem("zl_theme", window._themePref);
735
+ const eff = getEffective(window._themePref);
736
+ document.documentElement.setAttribute("data-theme", eff);
737
+ updateThemeIcons(eff);
738
+ };
739
+
740
+ window.updateThemeIcons = function(eff) {
741
+ const sun = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>';
742
+ const moon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
743
+ const auto = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 2a10 10 0 0 1 0 20V2z" fill="currentColor"/></svg>';
744
+ const icon = window._themePref === "auto" ? auto : eff === "dark" ? moon : sun;
745
+ document.querySelectorAll(".theme-btn").forEach(b => b.innerHTML = icon);
746
+ };
747
+
748
+ // Listen for system theme changes when in auto mode
749
+ window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", () => {
750
+ if (window._themePref === "auto") {
751
+ const eff = getEffective("auto");
752
+ document.documentElement.setAttribute("data-theme", eff);
753
+ updateThemeIcons(eff);
754
+ }
755
+ });
756
+
757
+ // Update icons after DOM loads
758
+ document.addEventListener("DOMContentLoaded", () => updateThemeIcons(effective));
759
+ })();
760
+ </script>
761
+
762
  <div id="landing">
763
  <div class="logo-icon" style="width:56px;height:56px;font-size:28px;border-radius:14px">0</div>
764
  <h1>ZeroLabs</h1>
765
  <p class="subtitle">Voice cloning powered by open-source models. 100x cheaper than proprietary competitors like ElevenLabs.</p>
766
  <button class="btn-hf" onclick="login()">🤗 Sign in with Hugging Face</button>
767
  <p class="landing-note">Uses your HF ZeroGPU quota &middot; Free tier included</p>
768
+ <button class="theme-btn" onclick="cycleTheme()" title="Toggle theme" style="position:fixed;top:16px;right:16px">
769
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
770
+ </button>
771
  </div>
772
 
773
  <div id="app" class="app-layout" style="display:none">
 
778
  ZeroLabs
779
  </div>
780
  <div class="header-right">
781
+ <button class="theme-btn" onclick="cycleTheme()" title="Toggle theme" id="theme-btn-app">
782
+ <svg id="theme-icon-app" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
783
+ </button>
784
  <div class="user-pill" id="user-pill">
785
  <img id="user-avatar" src="" alt="">
786
  <span id="user-name"></span>