llm-trainer / theme.py
Nekochu's picture
initial release: QLoRA SFT trainer + deslop + Horizon theme
07aa1a9
Raw
History Blame Contribute Delete
30.3 kB
import gradio as gr
from gradio.themes.utils import colors, fonts, sizes
# ── Horizon Palette ──────────────────────────────────────────
# BG #020509 Page void (darkest)
# SURFACE rgba(18,35,56,0.6) Block fills
# SURFACE_RAISED rgba(30,48,70,0.75) Label/badge fills
# SURFACE_INPUT rgba(8,16,32,0.8) Recessed input wells
# SURFACE_PANEL rgba(16,32,58,0.65) Panel fills
# TEXT #e7edf4 Primary text
# TEXT_MUTED #96a6b8 Subdued text
# TEXT_PLACEHOLDER #8a9db3 Input placeholders
# ACCENT_GOLD #d8a84f Primary action, accent bar
# ACCENT_HOVER #efc36d Gold hover state
# ACCENT_TITLE #f2c66d Block titles
# ACCENT_BRONZE #c4a56f Label text, secondary btn text
# WARM_BORDER rgba(180,110,48,*) Block/panel borders
# LINE rgba(50,75,105,0.7) Structural borders
# DANGER #9f4a3d Cancel/stop
class Horizon(gr.themes.Base):
def __init__(self):
super().__init__(
primary_hue=colors.orange,
secondary_hue=colors.stone,
neutral_hue=colors.slate,
font=(
fonts.GoogleFont("Space Grotesk"),
"ui-sans-serif",
"system-ui",
"sans-serif",
),
font_mono=(
fonts.GoogleFont("Space Mono"),
"ui-monospace",
"monospace",
),
radius_size=sizes.radius_sm,
spacing_size=sizes.spacing_sm,
text_size=sizes.text_sm,
)
self.set(
body_background_fill="#020509",
body_background_fill_dark="#020509",
body_text_color="#e7edf4",
body_text_color_dark="#e7edf4",
body_text_color_subdued="#96a6b8",
body_text_color_subdued_dark="#96a6b8",
block_padding="9px 11px",
block_label_padding="2px 6px",
block_label_margin="0px",
block_title_padding="2px 6px",
block_title_border_width="0px",
block_title_border_width_dark="0px",
block_label_background_fill="rgba(20, 34, 58, 0.75)",
block_label_background_fill_dark="rgba(20, 34, 58, 0.75)",
block_label_text_color="#c4a56f",
block_label_text_color_dark="#c4a56f",
block_title_text_color="#f2c66d",
block_title_text_color_dark="#f2c66d",
input_background_fill="rgba(8, 16, 32, 0.8)",
input_background_fill_dark="rgba(8, 16, 32, 0.8)",
input_border_color="rgba(180, 110, 48, 0.34)",
input_border_color_dark="rgba(180, 110, 48, 0.34)",
input_border_width="1px",
input_padding="8px",
input_radius="6px",
input_shadow="rgba(0,0,0,0.12) 0px 1px 3px 0px inset",
input_shadow_dark="rgba(0,0,0,0.12) 0px 1px 3px 0px inset",
input_placeholder_color="#8a9db3",
input_placeholder_color_dark="#8a9db3",
button_primary_background_fill="#d8a84f",
button_primary_background_fill_dark="#d8a84f",
button_primary_background_fill_hover="#efc36d",
button_primary_background_fill_hover_dark="#efc36d",
button_primary_text_color="#020509",
button_primary_text_color_dark="#020509",
button_primary_border_color="#efc36d",
button_primary_border_color_dark="#efc36d",
button_secondary_background_fill="transparent",
button_secondary_background_fill_dark="transparent",
button_secondary_background_fill_hover="rgba(216, 168, 79, 0.08)",
button_secondary_background_fill_hover_dark="rgba(216, 168, 79, 0.08)",
button_secondary_text_color="#c4a56f",
button_secondary_text_color_dark="#c4a56f",
button_cancel_background_fill="#9f4a3d",
button_cancel_background_fill_dark="#9f4a3d",
button_cancel_text_color="#FFFFFF",
button_cancel_text_color_dark="#FFFFFF",
border_color_primary="rgba(50, 75, 105, 0.7)",
border_color_primary_dark="rgba(50, 75, 105, 0.7)",
shadow_drop="rgba(0,0,0,0.08) 0px 1px 2px 0px",
shadow_drop_lg="0 1px 3px 0 rgba(0,0,0,0.12), 0 1px 2px -1px rgba(0,0,0,0.12)",
shadow_inset="rgba(0,0,0,0.08) 0px 2px 4px 0px inset",
button_small_padding="4px 10px",
button_large_padding="6px 14px",
button_small_text_size="*text_sm",
button_large_text_size="*text_md",
button_small_text_weight="500",
button_large_text_weight="500",
form_gap_width="0px",
checkbox_label_gap="4px",
checkbox_border_width="1px",
prose_text_size="*text_sm",
prose_header_text_weight="600",
)
theme = Horizon()
THEME_CSS = """
:root {
--hz-warm: rgba(180, 110, 48, 0.45);
--hz-warm-hover: rgba(200, 130, 60, 0.55);
--hz-text: #e7edf4;
--hz-accent: #d8a84f;
--hz-transition: 0.18s ease;
--hz-radius: 6px;
--hz-scroll: 0%;
}
/* ── Background: transparent so stars show through ── */
gradio-app {
background: transparent !important;
}
.gradio-container {
max-width: 1120px !important;
margin: 0 auto !important;
background: transparent !important;
color: var(--hz-text);
position: relative;
z-index: 1;
}
/* ── Scroll progress: atmosphere glow (the ONE gradient) ── */
.gradio-container::before {
content: "";
position: fixed;
top: 0; left: 0;
z-index: 1000;
width: var(--hz-scroll);
height: 2px;
background: linear-gradient(90deg, #d8a84f 0%, #e8b85a 45%, #5d4a2b 75%, #0a1422 100%);
box-shadow: 2px 0 10px rgba(216, 168, 79, 0.35);
transition: width 120ms linear;
pointer-events: none;
}
::selection {
background: rgba(216, 168, 79, 0.28);
color: var(--hz-text);
}
/* ── Sora for headings, Space Grotesk for body (font loaded via THEME_HEAD <link>) ── */
h1, h2, h3, h4, h5, h6,
.label-wrap,
.panel > span:first-child {
font-family: 'Sora', 'Space Grotesk', sans-serif !important;
}
/* ── Header ── */
.app-header {
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--hz-warm);
}
.app-header h3 {
margin-bottom: 0.35rem;
background: linear-gradient(90deg, #e7edf4, #f2c66d, #e7edf4, #d8a84f, #e7edf4);
background-size: 300% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: shimmer 8s linear infinite;
}
.app-header code {
color: var(--hz-accent);
background: rgba(216, 168, 79, 0.1);
border: 1px solid rgba(216, 168, 79, 0.18);
display: inline-block;
animation: float 6s ease-in-out infinite;
}
/* ── Panels — combined #5 reflex + #6 glassmorphism ── */
.panel {
position: relative;
overflow: visible;
background: rgba(15, 28, 55, 0.55);
backdrop-filter: blur(12px) saturate(1.4);
-webkit-backdrop-filter: blur(12px) saturate(1.4);
border: 1px solid rgba(180, 110, 48, 0.35);
box-shadow:
inset 1px 2px 0px -1px rgba(255, 255, 255, 0.12),
inset -0.3px -1px 4px 0px rgba(0, 0, 0, 0.2),
0px 8px 20px rgba(0, 0, 0, 0.18);
transition: border-color var(--hz-transition), box-shadow var(--hz-transition), backdrop-filter var(--hz-transition);
}
.panel::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 3px;
background: var(--hz-accent);
animation: accent-breathe 5s ease-in-out infinite;
}
.panel:hover {
backdrop-filter: blur(16px) saturate(1.6);
-webkit-backdrop-filter: blur(16px) saturate(1.6);
border-color: rgba(200, 130, 60, 0.5);
box-shadow:
inset 1px 2px 0px -1px rgba(255, 255, 255, 0.18),
inset -0.3px -1px 4px 0px rgba(0, 0, 0, 0.25),
0px 10px 25px rgba(0, 0, 0, 0.22);
}
/* ── Blocks — #5 reflex + #6 glassmorphism (non-dropdown blocks get backdrop-filter) ── */
.block:not(:has(input[aria-expanded])):not(:has(ul.options)) {
backdrop-filter: blur(1px) saturate(1.3) brightness(1.15);
-webkit-backdrop-filter: blur(1px) saturate(1.3) brightness(1.15);
}
.block {
background: rgba(12, 25, 50, 0.45);
border: 1px solid rgba(180, 110, 48, 0.28);
box-shadow:
inset 1px 2px 0px -1px rgba(216, 168, 79, 0.1),
0px 4px 12px rgba(0, 0, 0, 0.14);
transition: border-color var(--hz-transition), box-shadow var(--hz-transition);
}
.block:hover,
.block:focus-within {
border-color: var(--hz-warm-hover);
box-shadow:
inset 1px 2px 0px -1px rgba(255, 255, 255, 0.14),
0px 6px 14px rgba(0, 0, 0, 0.16);
}
/* ── Buttons ── */
button {
white-space: nowrap;
font-size: 13px;
line-height: 1.4;
transition: transform var(--hz-transition), border-color var(--hz-transition), box-shadow var(--hz-transition), background-color var(--hz-transition), filter var(--hz-transition);
}
button.primary, button[class*="primary"] {
position: relative;
overflow: hidden;
}
button.primary::after, button[class*="primary"]::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
transform: translateX(-100%);
transition: transform var(--hz-transition);
pointer-events: none;
border-radius: inherit;
}
button.primary:hover, button[class*="primary"]:hover {
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(216, 168, 79, 0.25);
}
button.primary:hover::after, button[class*="primary"]:hover::after {
transform: translateX(100%);
}
button.primary:active, button[class*="primary"]:active {
transform: scale(0.97);
box-shadow: none;
}
button:active:not(:disabled) {
transform: scale(0.97);
}
/* Secondary: ghost with gold border */
button.secondary, button[class*="secondary"] {
border: 1px solid rgba(216, 168, 79, 0.35);
position: relative;
overflow: hidden;
}
button.secondary:hover, button[class*="secondary"]:hover {
border-color: rgba(216, 168, 79, 0.55);
background: rgba(216, 168, 79, 0.08);
box-shadow: 0 0 12px rgba(216, 168, 79, 0.1);
color: #c4a56f;
}
button.secondary:active, button[class*="secondary"]:active {
background: rgba(216, 168, 79, 0.12);
transform: scale(0.97);
box-shadow: none;
}
button:disabled, button[disabled] {
opacity: 0.45;
cursor: not-allowed;
transform: none !important;
box-shadow: none !important;
filter: none !important;
}
/* ── Inputs ── */
input, textarea, select {
transition: border-color var(--hz-transition), box-shadow var(--hz-transition);
}
input[type="checkbox"],
input[type="radio"],
input[type="range"] {
accent-color: var(--hz-accent);
}
input:focus, textarea:focus, select:focus {
outline: none !important;
border-color: var(--hz-accent) !important;
box-shadow: 0 0 0 2px rgba(216, 168, 79, 0.18) !important;
}
/* Slider thumb */
input[type="range"]::-webkit-slider-thumb {
box-shadow: 0 0 0 3px rgba(216, 168, 79, 0.15), 0 2px 6px rgba(0,0,0,0.2);
}
/* ── Checkbox & radio — old squared style ── */
.checkbox-group label,
[data-testid="checkbox-group"] label,
.radio-group label,
[data-testid="radio"] label {
border: 1px solid rgba(180, 110, 48, 0.34);
border-radius: var(--hz-radius);
padding: 4px 8px;
background: rgba(30, 48, 72, 0.6);
transition: border-color var(--hz-transition), background var(--hz-transition);
cursor: pointer;
}
.checkbox-group label:hover,
[data-testid="checkbox-group"] label:hover,
.radio-group label:hover,
[data-testid="radio"] label:hover {
border-color: rgba(216, 168, 79, 0.5);
background: rgba(38, 56, 82, 0.7);
}
/* Multi-select tags */
[data-testid="dropdown"] .token,
.multiselect .item {
border-radius: var(--hz-radius);
border: 1px solid rgba(180, 110, 48, 0.34);
background: rgba(30, 48, 72, 0.6);
padding: 2px 8px;
}
/* ── Dropdown containers — tight padding ── */
.dropdown-container, .block:has(.dropdown-arrow) { padding: 1px 2px !important; }
.dropdown-container .wrap, .block:has(.dropdown-arrow) .wrap { margin: 0 !important; padding: 0 !important; }
.secondary-wrap { padding: 1px 2px !important; }
.secondary-wrap input { padding: 0 2px !important; }
/* ── File preview — filename gets most of the width ── */
.file-preview .filename { width: auto !important; word-break: break-all !important; white-space: normal !important; }
.file-preview td:not(.filename) { width: auto !important; white-space: nowrap !important; padding: 2px !important; }
/* ── Table ── */
table tbody tr {
transition: background-color var(--hz-transition);
}
table tbody tr:hover {
background: linear-gradient(90deg, transparent 0%, rgba(216,168,79,0.08) 40%, rgba(216,168,79,0.12) 50%, rgba(216,168,79,0.08) 60%, transparent 100%);
background-size: 200% 100%;
animation: hz-row-sweep 1.2s ease-out forwards;
}
/* ── Chatbot message slide-in ── */
.message {
animation: hz-msg-in 0.35s ease-out both;
}
/* ── Gallery/image hover zoom ── */
.image-container img,
[data-testid="gallery"] .thumbnail-item img {
transition: transform var(--hz-transition);
}
.image-container:hover img,
[data-testid="gallery"] .thumbnail-item:hover img {
transform: scale(1.03);
}
/* Daftplug glass on Color Picker, File, Video, accordion content */
[data-testid="color"],
[data-testid="file"],
[data-testid="video"] {
position: relative;
overflow: hidden;
}
[data-testid="color"]::after,
[data-testid="file"]::after,
[data-testid="video"]::after {
content: '';
position: absolute;
z-index: -1;
inset: 0;
border-radius: inherit;
backdrop-filter: blur(0px);
filter: url(#hz-glass-a);
overflow: hidden;
isolation: isolate;
}
[data-testid="color"]::before,
[data-testid="file"]::before,
[data-testid="video"]::before {
content: '';
position: absolute;
inset: 0;
z-index: 0;
border-radius: inherit;
box-shadow:
inset 2px 2px 0px -2px rgba(255, 255, 255, 0.3),
inset 0 0 3px 1px rgba(255, 255, 255, 0.1);
pointer-events: none;
}
/* ── Glass B shine on dropdown popup ── */
ul.options::after, ul[role="listbox"]::after {
content: '';
position: sticky;
inset: 0;
z-index: 0;
border-radius: inherit;
box-shadow:
inset 2px 2px 1px 0 rgba(255, 255, 255, 0.35),
inset -1px -1px 1px 1px rgba(255, 255, 255, 0.2);
pointer-events: none;
}
/* ── Accordion — warm gold + warm content area ── */
.label-wrap {
color: var(--hz-accent);
border-bottom: 1px solid var(--hz-warm);
transition: transform var(--hz-transition);
}
.accordion, [data-testid="accordion"] {
transition: border-color var(--hz-transition), background-color var(--hz-transition), box-shadow var(--hz-transition);
}
.label-wrap .icon {
color: var(--hz-accent);
}
.label-wrap:hover {
transform: translateX(2px);
}
/* Accordion + form containers — warm, not gray */
.form {
background: transparent;
border-color: rgba(180, 110, 48, 0.18);
}
/* ── Dropdown popup — visible + styled (force override Gradio defaults) ── */
ul.options, ul[role="listbox"],
ul[class*="options"] {
background: rgba(12, 25, 50, 0.2) !important;
backdrop-filter: blur(3px) contrast(1.02) brightness(1.12);
-webkit-backdrop-filter: blur(3px) contrast(1.02) brightness(1.12);
border: 1px solid rgba(255, 255, 255, 0.08) !important;
box-shadow:
inset 0 0 14px rgba(255, 255, 255, 0.15),
inset -1px -3px 2px rgba(255, 255, 255, 0.15),
inset 1px 3px 2px rgba(255, 255, 255, 0.15),
0 0 10px rgba(0, 0, 0, 0.25) !important;
border-radius: var(--hz-radius) !important;
max-height: 320px !important;
overflow-y: auto !important;
}
ul.options li, ul[role="listbox"] li,
ul[class*="options"] li {
color: var(--hz-text) !important;
padding: 5px 6px !important;
background: transparent !important;
background-color: transparent !important;
}
ul.options li:hover, ul[role="listbox"] li:hover,
ul[class*="options"] li:hover {
background: rgba(216, 168, 79, 0.12) !important;
}
ul.options li.selected, ul.options li[class*="selected"],
ul.options li[class*="active"],
ul[class*="options"] li.selected {
background: rgba(216, 168, 79, 0.1) !important;
color: var(--hz-accent) !important;
}
/* ── Glass A shine on panels (white edge glow) ── */
.panel::after {
content: '';
position: absolute;
inset: 0;
z-index: 0;
border-radius: inherit;
box-shadow:
inset 2px 2px 0px -2px rgba(255, 255, 255, 0.5),
inset 0 0 3px 1px rgba(255, 255, 255, 0.3);
pointer-events: none;
}
/* ── Daftplug glass on secondary buttons ── */
button.secondary, button[class*="secondary"],
button[class~="sm"] {
position: relative;
overflow: hidden;
}
button.secondary::before, button[class*="secondary"]::before,
button[class~="sm"]::before {
content: '';
position: absolute;
inset: 0;
z-index: 0;
border-radius: inherit;
box-shadow:
inset 2px 2px 0px -2px rgba(255, 255, 255, 0.4),
inset 0 0 3px 1px rgba(255, 255, 255, 0.15);
pointer-events: none;
}
button.secondary::after, button[class*="secondary"]::after,
button[class~="sm"]::after {
content: '';
position: absolute;
z-index: -1;
inset: 0;
border-radius: inherit;
backdrop-filter: blur(0px);
-webkit-backdrop-filter: blur(0px);
filter: url(#hz-glass-a);
-webkit-filter: url(#hz-glass-a);
overflow: hidden;
isolation: isolate;
}
/* ── Scrollbar ── */
* {
scrollbar-width: thin;
scrollbar-color: rgba(180,110,48,0.25) transparent;
}
*::-webkit-scrollbar { width: 5px; height: 5px; }
*::-webkit-scrollbar-thumb { background: rgba(180,110,48,0.25); border-radius: 3px; }
*::-webkit-scrollbar-thumb:hover { background: rgba(216,168,79,0.4); }
/* ── Focus-visible ── */
:where(button, input, textarea, select, [role="button"], [tabindex]):focus-visible {
outline: 2px solid var(--hz-accent) !important;
outline-offset: 2px;
box-shadow: 0 0 0 3px rgba(216, 168, 79, 0.18) !important;
}
/* ── Star field ── */
#hz-stars {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
pointer-events: none;
z-index: -1;
overflow: hidden;
/* Idea 4: ambient warm-bottom / cool-top tinting */
box-shadow:
inset 0 0 120px 28px rgba(0, 0, 0, 0.28),
inset 0 -140px 220px -70px rgba(200, 140, 50, 0.08),
inset 0 140px 220px -70px rgba(40, 80, 140, 0.04);
}
/* Photorealistic planet atmosphere — fixed at page bottom only */
/* Idea 5: slow ambient color temperature drift */
#hz-stars::before {
content: '';
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
animation: hz-amb 45s ease-in-out infinite;
}
@keyframes hz-amb {
0%, 100% { background: rgba(180, 130, 50, 0.03); }
50% { background: rgba(40, 80, 150, 0.02); }
}
.hz-star {
position: absolute;
background: rgba(220, 225, 235, 0.85);
border-radius: 50%;
animation: hz-twinkle var(--dur, 3s) infinite ease-in-out;
animation-delay: var(--delay, 0s);
contain: paint;
}
@keyframes hz-twinkle {
0%, 100% { opacity: 0.3; transform: scale(0.9); }
50% { opacity: 1; transform: scale(1.2); }
}
@keyframes hz-atm-breathe {
0%, 100% { opacity: 0.82; }
50% { opacity: 1; }
}
/* ── Keyframes ── */
@keyframes accent-breathe {
0%, 100% { opacity: 0.5; }
50% { opacity: 0.8; }
}
@keyframes shimmer {
0% { background-position: 200% center; }
100% { background-position: -200% center; }
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-2px); }
}
@keyframes hz-msg-in {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes hz-row-sweep {
from { background-position: -100% 0; }
to { background-position: 200% 0; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
"""
theme.custom_css = THEME_CSS
THEME_HEAD = """
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&display=swap" rel="stylesheet">
<script>
var _hzPoll = setInterval(function() {
var app = document.querySelector("gradio-app");
if (!app) return;
clearInterval(_hzPoll);
if (document.getElementById("hz-stars")) return;
var c = document.createElement("div");
c.id = "hz-stars";
app.prepend(c);
var stars = [];
for (var i = 0; i < 60; i++) {
var s = document.createElement("div");
s.className = "hz-star";
var x = Math.random() * 100;
var y = Math.random() * 100;
var isStatic = Math.random() < 0.25;
var speed = isStatic ? 0 : 0.15 + Math.random() * 0.4;
var size = isStatic ? 1 + Math.random() : 1.5 + Math.random() * 2;
s.style.left = x + "%";
s.style.top = y + "%";
s.style.width = size + "px";
s.style.height = size + "px";
var twinkle = isStatic ? 7 + Math.random() * 5 : 2.5 + (3 - Math.min(size, 3)) * 1.4 + Math.random() * 3;
s.style.setProperty("--dur", twinkle + "s");
s.style.setProperty("--delay", (Math.random() * 5) + "s");
if (Math.random() < 0.15) {
s.style.background = "rgba(216,168,79,0.8)";
s.style.boxShadow = "0 0 4px rgba(216,168,79,0.4)";
} else if (Math.random() < 0.12) {
s.style.background = "rgba(91,164,196,0.7)";
s.style.boxShadow = "0 0 4px rgba(91,164,196,0.3)";
} else {
s.style.boxShadow = "0 0 2px rgba(220,225,235,0.3)";
}
c.appendChild(s);
stars.push({ el: s, initY: y, speed: speed });
}
window.addEventListener("scroll", function() {
var scroll = window.scrollY;
for (var j = 0; j < stars.length; j++) {
if (stars[j].speed === 0) continue;
var pos = (stars[j].initY - scroll * stars[j].speed * 0.03) % 100;
if (pos < 0) pos += 100;
stars[j].el.style.top = pos + "%";
}
}, { passive: true });
// Photorealistic planet atmosphere at page bottom
if (!document.getElementById("hz-atmosphere")) {
var atm = document.createElement("div");
atm.id = "hz-atmosphere";
atm.style.cssText = "position:absolute;bottom:-20px;left:0;width:100%;height:350px;pointer-events:none;z-index:0;overflow:hidden;";
atm.style.animation = "hz-atm-breathe 11s ease-in-out infinite";
atm.innerHTML = '<div style="position:absolute;inset:0;border-radius:60% 60% 0 0 / 50% 50% 0 0;background:radial-gradient(ellipse 120% 80% at 50% 120%,' +
'transparent 0%,' +
'transparent 12%,' +
'rgba(190,90,20,0.45) 20%,' +
'rgba(230,130,40,0.38) 25%,' +
'rgba(240,170,70,0.28) 30%,' +
'rgba(210,200,130,0.18) 35%,' +
'rgba(120,200,180,0.1) 40%,' +
'rgba(60,160,210,0.06) 46%,' +
'rgba(35,100,190,0.03) 54%,' +
'rgba(18,55,140,0.015) 65%,' +
'transparent 80%);"></div>';
var container = document.querySelector(".gradio-container");
if (container) container.appendChild(atm);
}
// Idea 2: Nebula patches — asymmetric warm/cool depth
var nebulae = [
{ x: '18%', y: '22%', w: '35vw', h: '30vh', color: 'rgba(200,140,50,0.09)' },
{ x: '72%', y: '68%', w: '30vw', h: '25vh', color: 'rgba(50,120,160,0.07)' },
{ x: '48%', y: '48%', w: '25vw', h: '20vh', color: 'rgba(160,80,70,0.05)' }
];
nebulae.forEach(function(n) {
var d = document.createElement('div');
d.style.cssText = 'position:fixed;pointer-events:none;border-radius:50%;' +
'left:' + n.x + ';top:' + n.y + ';width:' + n.w + ';height:' + n.h +
';background:radial-gradient(ellipse,'+n.color+',transparent 70%);' +
'transform:translate(-50%,-50%);z-index:0;';
c.appendChild(d);
});
// Inject SVG liquid glass refraction filter
if (!document.getElementById("hz-refraction-svg")) {
var svgNS = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(svgNS, "svg");
svg.id = "hz-refraction-svg";
svg.setAttribute("style", "position:absolute;width:0;height:0;");
svg.innerHTML = '<defs>' +
'<filter id="hz-refract" filterUnits="userSpaceOnUse" x="0" y="0" width="2000" height="2000">' +
'<feTurbulence type="fractalNoise" baseFrequency="0.001" numOctaves="1" result="turbulence"/>' +
'<feDisplacementMap in="SourceGraphic" in2="turbulence" scale="50"/>' +
'</filter>' +
'<filter id="hz-glass-a" x="0%" y="0%" width="100%" height="100%">' +
'<feTurbulence type="fractalNoise" baseFrequency="0.008 0.008" numOctaves="2" seed="92" result="noise"/>' +
'<feGaussianBlur in="noise" stdDeviation="0.02" result="blur"/>' +
'<feDisplacementMap in="SourceGraphic" in2="blur" scale="77" xChannelSelector="R" yChannelSelector="G"/>' +
'</filter>' +
'<filter id="hz-glass-b" x="0%" y="0%" width="100%" height="100%" filterUnits="objectBoundingBox">' +
'<feTurbulence type="fractalNoise" baseFrequency="0.01 0.01" numOctaves="1" seed="5" result="turbulence"/>' +
'<feGaussianBlur in="turbulence" stdDeviation="3" result="softMap"/>' +
'<feSpecularLighting in="softMap" surfaceScale="5" specularConstant="1" specularExponent="100" lighting-color="white" result="specLight"><fePointLight x="-200" y="-200" z="300"/></feSpecularLighting>' +
'<feComposite in="specLight" operator="arithmetic" k1="0" k2="1" k3="1" k4="0" result="litImage"/>' +
'<feDisplacementMap in="SourceGraphic" in2="softMap" scale="150" xChannelSelector="R" yChannelSelector="G"/>' +
'</filter>' +
'</defs>';
app.appendChild(svg);
}
// Force glass on dropdown popups (fixes first-open flash)
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
m.addedNodes.forEach(function(n) {
if (n.classList && (n.classList.contains('options') || n.tagName === 'UL')) {
n.style.setProperty('background', 'rgba(12,25,50,0.2)', 'important');
n.style.setProperty('backdrop-filter', 'blur(3px) contrast(1.02) brightness(1.12)');
}
});
});
});
observer.observe(app, { childList: true, subtree: true });
}, 200);
</script>
"""
THEME_JS = """
() => {
const root = document.documentElement;
// Scroll progress bar -- uses same captured-scroll approach as star parallax
let lastScrollSource = null;
const elementScrollTop = (el) => {
if (!el) return 0;
if (el === window) return window.scrollY || 0;
if (el === document) return window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0;
if (el === document.body || el === document.documentElement || el === document.scrollingElement) {
return window.scrollY || el.scrollTop || 0;
}
return el.scrollTop || 0;
};
const updateScroll = () => {
let best = elementScrollTop(lastScrollSource);
let bestH = 1;
const els = [document.scrollingElement, document.documentElement, document.body,
document.querySelector("gradio-app"), document.querySelector(".gradio-container")];
for (const el of els) {
if (!el) continue;
const t = elementScrollTop(el);
if (t > best) { best = t; bestH = el.scrollHeight - el.clientHeight; }
}
for (const el of document.querySelectorAll("body *")) {
if (el.scrollHeight > el.clientHeight + 1 && (el.scrollTop || 0) > best) {
best = el.scrollTop || 0;
bestH = el.scrollHeight - el.clientHeight;
}
}
const pct = bestH > 0 ? (best / bestH) * 100 : 0;
root.style.setProperty("--hz-scroll", Math.min(100, Math.max(0, pct)) + "%");
};
document.addEventListener("scroll", (e) => {
lastScrollSource = e.target;
updateScroll();
}, { capture: true, passive: true });
window.addEventListener("wheel", updateScroll, { passive: true });
window.addEventListener("touchmove", updateScroll, { passive: true });
window.addEventListener("resize", updateScroll);
updateScroll();
// Ctrl+K focus
document.addEventListener("keydown", (e) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
const inp = document.querySelector("input:not([type='hidden']), textarea");
if (inp) { e.preventDefault(); inp.focus(); inp.select?.(); }
}
});
}
"""