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 ) ── */ 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 = """ """ 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?.(); } } }); } """