Spaces:
Running on Zero
Running on Zero
feat(ui): add brutalist mono theme tokens and responsive css
Browse files- tests/test_theme.py +28 -0
- theme.py +110 -0
tests/test_theme.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""L1 theme assertions — palette tokens, CSS presence."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import theme
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def test_palette_tokens_are_brutalist_mono():
|
| 8 |
+
assert theme.BG == "#0A0A0A"
|
| 9 |
+
assert theme.INK == "#E5E5E5"
|
| 10 |
+
assert theme.PRIMARY == "#FFFFFF"
|
| 11 |
+
# No color accent — that's the whole point of Brutalist Mono
|
| 12 |
+
for v in (theme.BG, theme.SURFACE, theme.SURFACE_STRONG, theme.BORDER, theme.BORDER_STRONG, theme.INK, theme.INK_MUTED, theme.PRIMARY):
|
| 13 |
+
assert v.startswith("#")
|
| 14 |
+
assert len(v) == 7 # all hex, no rgba
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def test_css_contains_responsive_breakpoints():
|
| 18 |
+
css = theme.CSS
|
| 19 |
+
assert "@media" in css
|
| 20 |
+
assert "1024px" in css # tablet breakpoint
|
| 21 |
+
assert "640px" in css # mobile breakpoint
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def test_build_theme_returns_gradio_theme():
|
| 25 |
+
import gradio as gr
|
| 26 |
+
t = theme.build_theme()
|
| 27 |
+
# gr.themes.Base is the parent class
|
| 28 |
+
assert isinstance(t, gr.themes.Base)
|
theme.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Brutalist Mono — pure black/white, no color accent.
|
| 2 |
+
|
| 3 |
+
Palette tokens are the source of truth; CSS pulls from them. The audio
|
| 4 |
+
waveform is the only optionally-colored element (rendered white in v1).
|
| 5 |
+
"""
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import gradio as gr
|
| 9 |
+
|
| 10 |
+
# --- Palette ----------------------------------------------------------------
|
| 11 |
+
BG = "#0A0A0A"
|
| 12 |
+
SURFACE = "#141414"
|
| 13 |
+
SURFACE_STRONG = "#000000"
|
| 14 |
+
BORDER = "#1F1F1F"
|
| 15 |
+
BORDER_STRONG = "#2A2A2A"
|
| 16 |
+
INK = "#E5E5E5"
|
| 17 |
+
INK_MUTED = "#6B6B6B"
|
| 18 |
+
PRIMARY = "#FFFFFF"
|
| 19 |
+
ERROR_BG = "#1A1A1A"
|
| 20 |
+
RADIUS = "6px"
|
| 21 |
+
FONT_STACK = (
|
| 22 |
+
'"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif'
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def build_theme() -> gr.themes.Base:
|
| 27 |
+
"""Returns a Gradio theme keyed to Brutalist Mono tokens."""
|
| 28 |
+
return gr.themes.Base(
|
| 29 |
+
primary_hue=gr.themes.colors.gray,
|
| 30 |
+
neutral_hue=gr.themes.colors.gray,
|
| 31 |
+
font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
|
| 32 |
+
).set(
|
| 33 |
+
body_background_fill=BG,
|
| 34 |
+
body_text_color=INK,
|
| 35 |
+
block_background_fill=SURFACE,
|
| 36 |
+
block_border_color=BORDER,
|
| 37 |
+
block_border_width="1px",
|
| 38 |
+
block_radius=RADIUS,
|
| 39 |
+
input_background_fill=SURFACE_STRONG,
|
| 40 |
+
input_border_color=BORDER_STRONG,
|
| 41 |
+
input_border_color_focus=PRIMARY,
|
| 42 |
+
button_primary_background_fill=PRIMARY,
|
| 43 |
+
button_primary_text_color=BG,
|
| 44 |
+
button_primary_background_fill_hover=PRIMARY,
|
| 45 |
+
button_secondary_background_fill=SURFACE_STRONG,
|
| 46 |
+
button_secondary_text_color=INK,
|
| 47 |
+
button_secondary_border_color=BORDER_STRONG,
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
CSS = f"""
|
| 52 |
+
/* --- Sole brand bits --------------------------------------------------- */
|
| 53 |
+
.ams-header {{
|
| 54 |
+
display:flex; justify-content:space-between; align-items:baseline;
|
| 55 |
+
padding:8px 0 4px 0;
|
| 56 |
+
}}
|
| 57 |
+
.ams-brand {{
|
| 58 |
+
font-size:16px; font-weight:600; letter-spacing:-0.01em; color:{INK};
|
| 59 |
+
}}
|
| 60 |
+
.ams-brand-period {{ color:{PRIMARY}; }}
|
| 61 |
+
.ams-status {{ font-size:11px; color:{INK_MUTED}; letter-spacing:0.02em; }}
|
| 62 |
+
|
| 63 |
+
/* --- CTA banner -------------------------------------------------------- */
|
| 64 |
+
.ams-cta {{
|
| 65 |
+
font-size:13px; color:{INK_MUTED}; margin:2px 0 12px 0; padding-bottom:10px;
|
| 66 |
+
border-bottom:1px solid {BORDER};
|
| 67 |
+
}}
|
| 68 |
+
.ams-cta strong {{ color:{INK}; }}
|
| 69 |
+
.ams-cta-heart {{ color:{PRIMARY}; }}
|
| 70 |
+
.ams-cta a {{ color:{INK}; text-decoration:underline; }}
|
| 71 |
+
|
| 72 |
+
/* --- Sidebar nav (desktop >= 1024) ------------------------------------ */
|
| 73 |
+
.ams-sidebar {{ background:{SURFACE_STRONG}; padding:14px 10px; border-radius:{RADIUS}; min-width:170px; }}
|
| 74 |
+
.ams-side-item {{
|
| 75 |
+
display:block; padding:8px 10px; border-radius:4px; margin-bottom:3px;
|
| 76 |
+
font-size:13px; color:{INK_MUTED}; cursor:pointer; text-decoration:none;
|
| 77 |
+
}}
|
| 78 |
+
.ams-side-item.active {{
|
| 79 |
+
background:#1A1A1A; color:{PRIMARY};
|
| 80 |
+
border-left:2px solid {PRIMARY}; padding-left:8px;
|
| 81 |
+
}}
|
| 82 |
+
|
| 83 |
+
/* --- LoRA chip pill --------------------------------------------------- */
|
| 84 |
+
.ams-chip {{
|
| 85 |
+
display:inline-block; padding:5px 10px; border-radius:14px;
|
| 86 |
+
font-size:11px; margin:0 5px 5px 0; background:{SURFACE_STRONG};
|
| 87 |
+
border:1px solid {BORDER_STRONG}; color:{INK_MUTED}; cursor:pointer;
|
| 88 |
+
}}
|
| 89 |
+
.ams-chip.on {{ border-color:{PRIMARY}; color:{PRIMARY}; }}
|
| 90 |
+
.ams-chip.upload {{ border-style:dashed; color:{PRIMARY}; }}
|
| 91 |
+
|
| 92 |
+
/* --- LoRA file drop zone (tighten Gradio default ~400px height) ------ */
|
| 93 |
+
.ams-lora-file .upload-container {{ min-height:56px !important; }}
|
| 94 |
+
|
| 95 |
+
/* --- Hide Gradio footer ----------------------------------------------- */
|
| 96 |
+
footer {{ display:none !important; }}
|
| 97 |
+
|
| 98 |
+
/* --- Responsive: tablet 640-1024 px ----------------------------------- */
|
| 99 |
+
@media (max-width: 1024px) {{
|
| 100 |
+
.ams-sidebar {{ min-width:34px; padding:6px 4px; }}
|
| 101 |
+
.ams-side-item {{ font-size:0; padding:6px; }}
|
| 102 |
+
.ams-side-item::first-letter {{ font-size:16px; }}
|
| 103 |
+
}}
|
| 104 |
+
|
| 105 |
+
/* --- Responsive: mobile < 640 px -------------------------------------- */
|
| 106 |
+
@media (max-width: 640px) {{
|
| 107 |
+
.ams-sidebar {{ display:none; }}
|
| 108 |
+
.ams-cta {{ font-size:11px; }}
|
| 109 |
+
}}
|
| 110 |
+
"""
|