Spaces:
Running
Running
| 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?.(); } | |
| } | |
| }); | |
| } | |
| """ | |