z-image-studio / theme.py
techfreakworm's picture
feat(theme): css rules for param info tooltips and custom model selector
1a3b62d unverified
raw
history blame
7.17 kB
"""Onyx Amber theme — palette tokens, gr.themes.Base subclass, and CSS string."""
from __future__ import annotations
import gradio as gr
AMBER: dict[str, str] = {
"body_bg": "#0F0C08",
"panel_bg": "#0F0C08",
"input_bg": "#0F0C08",
"canvas_bg": "#110D08",
"border": "#2A2218",
"text": "#FAF1E3",
"text_dim": "#A89478",
"accent": "#FFB02E",
"accent_text": "#1A1208",
"radius": "8px",
"radius_sm": "6px",
}
class _OnyxAmberBase(gr.themes.Base):
"""gr.themes.Base subclass with helpers for iterating over font lists.
Gradio collapses font lists into a CSS string at __init__; we expose the
original lists via .font_list / .font_mono_list for code that needs to
iterate. The public self.font / self.font_mono attributes are left alone so
that _get_theme_css() emits the correct --font / --font-mono CSS variables.
"""
@property
def font_list(self) -> list:
"""Return the original font list (GoogleFont + str entries)."""
return self._font
@property
def font_mono_list(self) -> list:
"""Return the original monospace font list (GoogleFont + str entries)."""
return self._font_mono
def build_theme() -> gr.themes.Base:
"""Return a Gradio theme matching the Onyx Amber palette."""
return _OnyxAmberBase(
primary_hue=gr.themes.Color(
c50="#FFF8E6",
c100="#FFEFC2",
c200="#FFE08A",
c300="#FFD161",
c400="#FFC042",
c500=AMBER["accent"],
c600="#E69926",
c700="#B37A1F",
c800="#805717",
c900="#4D3510",
c950="#1A1208",
),
neutral_hue=gr.themes.Color(
c50="#FAF1E3",
c100="#E8DCC4",
c200="#D4C2A1",
c300="#A89478",
c400="#867054",
c500="#5C4D38",
c600="#3C3225",
c700="#2A2218",
c800="#1C170F",
c900="#100C08",
c950="#0A0805",
),
font=[gr.themes.GoogleFont("Geist"), "system-ui", "sans-serif"],
font_mono=[gr.themes.GoogleFont("Geist Mono"), "ui-monospace", "monospace"],
radius_size=gr.themes.sizes.radius_md,
).set(
body_background_fill=AMBER["body_bg"],
body_text_color=AMBER["text"],
body_text_color_subdued=AMBER["text_dim"],
background_fill_primary=AMBER["panel_bg"],
background_fill_secondary=AMBER["canvas_bg"],
block_background_fill=AMBER["panel_bg"],
block_border_color=AMBER["border"],
block_border_width="1px",
block_radius=AMBER["radius"],
input_background_fill=AMBER["input_bg"],
input_border_color=AMBER["border"],
button_primary_background_fill=AMBER["accent"],
button_primary_background_fill_hover=AMBER["accent"],
button_primary_text_color=AMBER["accent_text"],
button_primary_border_color=AMBER["accent"],
slider_color=AMBER["accent"],
color_accent=AMBER["accent"],
color_accent_soft="rgba(255,176,46,0.12)",
)
CSS: str = """
/* Onyx Amber — atmospheric layer that Gradio's theme can't express alone */
body, .gradio-container {
background-image: radial-gradient(ellipse 80% 60% at 50% 0%, rgba(255,176,46,0.06), transparent 70%);
}
/* Amber glow on primary button */
.gradio-container button.primary {
box-shadow: 0 0 0 1px rgba(255,176,46,0.4), 0 8px 24px -8px rgba(255,176,46,0.35);
}
/* Slim status line typography */
.zis-status {
font-family: 'Geist Mono', ui-monospace, monospace;
font-size: 11px;
letter-spacing: 0.06em;
color: #A89478;
}
/* LoRA file slot — solid amber border + slim icon when a file is loaded */
.zis-lora.loaded {
border: 1px solid #FFB02E !important;
}
/* ===== Param tooltip — (i) icon next to labels (spec § 4.6) ===== */
.zis-row-label {
display: inline-flex; align-items: center;
font-size: 11px; color: #A89478; font-weight: 500;
margin-bottom: 6px;
}
.zis-info {
display: inline-flex; align-items: center; justify-content: center;
width: 12px; height: 12px;
font: italic 600 8px 'Geist', system-ui, sans-serif;
border: 1px solid #2A2218; border-radius: 50%;
color: #A89478; vertical-align: super;
margin-left: 3px; cursor: help; position: relative;
transition: border-color 0.12s, color 0.12s;
}
.zis-info:hover { border-color: #FFB02E; color: #FFB02E; }
.zis-info::after {
content: attr(data-info);
position: absolute; bottom: 100%; left: 50%;
transform: translateX(-50%) translateY(-4px);
background: #1C170F; color: #FAF1E3;
border: 1px solid #2A2218; border-radius: 6px;
padding: 6px 10px;
font: 400 11px 'Geist', system-ui, sans-serif; line-height: 1.4;
width: 200px; white-space: normal;
opacity: 0; pointer-events: none;
transition: opacity 0.12s; z-index: 50;
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
}
.zis-info:hover::after, .zis-info.shown::after { opacity: 1; }
/* ===== Custom model selector — 2-col phone / 4-col tablet+ (spec § 4.7) ===== */
.zis-models {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 10px;
}
@media (min-width: 768px) {
.zis-models { grid-template-columns: repeat(4, 1fr); }
}
.zis-model {
display: flex; align-items: center; gap: 8px;
padding: 10px 12px;
border: 1px solid #2A2218; border-radius: 8px;
background: transparent; cursor: pointer;
color: #FAF1E3;
font: 500 12px 'Geist', system-ui, sans-serif;
text-decoration: none;
transition: opacity 0.15s, border-color 0.15s, background 0.15s;
}
.zis-model .dot {
width: 10px; height: 10px; border-radius: 50%;
border: 1px solid #2A2218; flex-shrink: 0;
}
.zis-model .name { flex: 1; text-align: left; }
.zis-model.on {
background: #FFB02E; color: #1A1208; border-color: #FFB02E;
}
.zis-model.on .dot { background: #1A1208; border-color: #1A1208; }
.zis-model.soon {
opacity: 0.55;
background: rgba(255,176,46,0.04);
border-style: dashed;
position: relative;
}
.zis-model.soon .name { color: #A89478; }
.zis-model.soon .name .ext {
font-size: 10px; color: #FFB02E;
margin-left: 4px; vertical-align: super;
}
.zis-model.soon .soon-tag {
font-family: 'Geist Mono', ui-monospace, monospace;
font-size: 8.5px; letter-spacing: 0.12em; text-transform: uppercase;
background: rgba(255,176,46,0.18); color: #FFB02E;
padding: 2px 6px; border-radius: 100px;
flex-shrink: 0;
}
.zis-model.soon:hover { opacity: 0.78; border-color: #FFB02E; }
.zis-model.soon::after {
content: "Coming soon — opens GitHub";
position: absolute; bottom: 100%; left: 50%;
transform: translateX(-50%) translateY(-4px);
background: #1C170F; color: #FAF1E3;
border: 1px solid #2A2218; border-radius: 6px;
padding: 6px 10px;
font: 400 11px 'Geist', system-ui, sans-serif;
white-space: nowrap;
opacity: 0; pointer-events: none;
transition: opacity 0.12s; z-index: 50;
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
}
.zis-model.soon:hover::after { opacity: 1; }
""".strip()