Spaces:
Running
Running
Commit ·
cdac885
1
Parent(s): dcef6f0
Light/dark/auto theme with system detection and toggle
Browse files- 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:
|
| 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:
|
| 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
|
| 340 |
transform: translateY(0);
|
| 341 |
}
|
| 342 |
-
.btn-generate:hover { filter: brightness(1.05); transform: translateY(-2px); box-shadow: 0 6px 0
|
| 343 |
-
.btn-generate:active { transform: translateY(2px); box-shadow: 0 1px 0
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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
|
| 658 |
}
|
| 659 |
-
.btn-hf:hover { background: #ffb340; transform: translateY(-2px); box-shadow: 0 6px 0
|
| 660 |
-
.btn-hf:active { transform: translateY(2px); box-shadow: 0 1px 0
|
| 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 · 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 · 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>
|