Spaces:
Running on Zero
fix(ui): full Brutalist Mono retheme — IBM Plex, padding crush, scoped selectors
Browse filesSix issues from the user's manual mobile test:
1. Bluish background tint despite earlier --neutral-* override. Cause:
Gradio applies its CSS in multiple :root/.dark contexts. Solution:
override the neutral scale + body/block/input fill custom properties
under :root, html.dark, .gradio-container, .gradio-container.dark
AND .gradio-container .dark (all five contexts).
2. Huge ~60 px horizontal margins on the 360 px viewport. Cause:
.gradio-container > .app ships with padding: 16px 32px. Override
to 16px 20px on desktop and 8px 10px on mobile so the form actually
uses the available width.
3. Field labels rendering at 14 px (~24 visual px on phone) with
default Inter. Replace Inter with IBM Plex Sans (body) + IBM Plex
Mono (labels, status, metadata) — both via Google Fonts, both
distinctive enough to escape generic AI-slop aesthetics per the
frontend-design discipline. Shrink labels to 10 px desktop / 9 px
mobile, uppercase tracked mono, muted ink.
4. Helper text (gr.Textbox info=...) was getting uppercased + mono
because the parent <label> rule cascaded text-transform via CSS
inheritance. Fix: scope label styling to ONLY the span.has-info
inner element, and add an explicit reset on .info-text (Gradio
6.14's actual class for helper text — found via Playwright DOM
inspect, not [data-testid="info"] as the older versions used).
Force info-text to sentence-case sans italic small muted.
5. Vocal mode radio pills oversized. Add a dedicated rule for
.ams-content .wrap > label (the gr.Radio option) — compact 7 12 px
padding, sans 12 px, border highlight on :checked, custom radio
dot via appearance:none + radial gradient.
6. Output/Metadata blocks were unstyled containers (just a tiny label
+ floating empty-state glyph). Added elem_classes=["ams-out",
"ams-out-audio"|"ams-out-meta"] in ui.py and dedicated CSS so
each component renders as a proper bordered panel with min-height,
uppercase mono label, and muted empty-state glyph.
Also: forced nested .ams-tab-pane rows to flex-direction:column on
< 768 px (Gradio's gr.Row stays as a flex row by default, which
broke the stacked form/output layout on mobile).
Verified at 360 (phone) and 1440 (desktop) via Playwright screenshots
against docs/superpowers/specs/mockups/01_generate_mobile_errors.html.
17/17 L1+L2 tests still pass; ruff clean.
Typography upgrade also documented in theme.py module docstring.
|
@@ -1,20 +1,21 @@
|
|
| 1 |
"""Brutalist Mono — pure black/white, no color accent.
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
| 18 |
"""
|
| 19 |
|
| 20 |
from __future__ import annotations
|
|
@@ -25,50 +26,84 @@ import gradio as gr
|
|
| 25 |
BG = "#0A0A0A"
|
| 26 |
SURFACE = "#141414"
|
| 27 |
SURFACE_STRONG = "#000000"
|
|
|
|
| 28 |
BORDER = "#1F1F1F"
|
| 29 |
BORDER_STRONG = "#2A2A2A"
|
| 30 |
INK = "#E5E5E5"
|
| 31 |
INK_MUTED = "#6B6B6B"
|
|
|
|
| 32 |
PRIMARY = "#FFFFFF"
|
| 33 |
-
ERROR_BG = "#1A1A1A"
|
| 34 |
HOVER_BG = "#1A1A1A"
|
| 35 |
-
RADIUS = "
|
| 36 |
-
|
|
|
|
| 37 |
|
| 38 |
|
| 39 |
def build_theme() -> gr.themes.Base:
|
| 40 |
-
"""Returns a Gradio theme keyed to Brutalist Mono tokens.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
return gr.themes.Base(
|
| 42 |
primary_hue=gr.themes.colors.gray,
|
| 43 |
neutral_hue=gr.themes.colors.gray,
|
| 44 |
-
font=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
).set(
|
| 46 |
body_background_fill=BG,
|
| 47 |
body_text_color=INK,
|
|
|
|
|
|
|
| 48 |
block_background_fill=SURFACE,
|
| 49 |
block_border_color=BORDER,
|
| 50 |
block_border_width="1px",
|
| 51 |
block_radius=RADIUS,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
input_background_fill=SURFACE_STRONG,
|
| 53 |
-
input_border_color=
|
| 54 |
input_border_color_focus=PRIMARY,
|
|
|
|
| 55 |
button_primary_background_fill=PRIMARY,
|
| 56 |
button_primary_text_color=BG,
|
| 57 |
button_primary_background_fill_hover=PRIMARY,
|
| 58 |
button_secondary_background_fill=SURFACE_STRONG,
|
| 59 |
button_secondary_text_color=INK,
|
| 60 |
button_secondary_border_color=BORDER_STRONG,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
)
|
| 62 |
|
| 63 |
|
|
|
|
|
|
|
|
|
|
| 64 |
CSS = f"""
|
| 65 |
-
/* ===
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
--neutral-100: #F5F5F5 !important;
|
| 73 |
--neutral-200: #E5E5E5 !important;
|
| 74 |
--neutral-300: #D4D4D4 !important;
|
|
@@ -79,64 +114,108 @@ CSS = f"""
|
|
| 79 |
--neutral-800: #262626 !important;
|
| 80 |
--neutral-900: #141414 !important;
|
| 81 |
--neutral-950: #0A0A0A !important;
|
| 82 |
-
--body-background-fill:
|
| 83 |
-
--background-fill-primary:
|
| 84 |
-
--background-fill-secondary:
|
| 85 |
-
--block-background-fill:
|
| 86 |
-
--block-label-background-fill:
|
| 87 |
-
--block-title-background-fill:
|
| 88 |
-
--input-background-fill:
|
| 89 |
-
--border-color-primary:
|
| 90 |
-
--border-color-accent:
|
| 91 |
-
--color-accent:
|
| 92 |
-
}
|
| 93 |
-
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
.ams-header {{
|
| 96 |
display:flex; justify-content:space-between; align-items:baseline;
|
| 97 |
-
padding:10px
|
| 98 |
}}
|
| 99 |
.ams-brand {{
|
| 100 |
-
font-
|
|
|
|
|
|
|
| 101 |
}}
|
| 102 |
-
.ams-brand-period {{ color:{PRIMARY}; }}
|
| 103 |
.ams-status {{
|
| 104 |
-
font-
|
| 105 |
-
|
|
|
|
| 106 |
}}
|
| 107 |
|
| 108 |
.ams-cta {{
|
| 109 |
-
font-size:
|
| 110 |
-
margin:2px
|
| 111 |
border-bottom:1px solid {BORDER};
|
|
|
|
| 112 |
}}
|
| 113 |
-
.ams-cta strong {{ color:{INK}; }}
|
| 114 |
.ams-cta-heart {{ color:{PRIMARY}; }}
|
| 115 |
-
.ams-cta a {{ color:{INK}; text-decoration:underline; }}
|
| 116 |
|
| 117 |
-
/* ===
|
|
|
|
|
|
|
| 118 |
.ams-body {{
|
| 119 |
-
gap:
|
| 120 |
align-items:stretch !important;
|
| 121 |
}}
|
| 122 |
|
| 123 |
-
/* ===
|
|
|
|
|
|
|
| 124 |
.ams-sidebar {{
|
| 125 |
background:{SURFACE_STRONG} !important;
|
| 126 |
-
padding:
|
| 127 |
border-radius:{RADIUS} !important;
|
| 128 |
border:1px solid {BORDER} !important;
|
| 129 |
-
min-width:
|
| 130 |
max-width:210px;
|
| 131 |
}}
|
| 132 |
|
| 133 |
-
/* --- Mode radio (re-skin gr.Radio as a vertical sidebar nav) ----------- */
|
| 134 |
.ams-side-radio {{
|
| 135 |
background:transparent !important;
|
| 136 |
border:none !important;
|
| 137 |
padding:0 !important;
|
| 138 |
width:100%;
|
| 139 |
}}
|
|
|
|
| 140 |
.ams-side-radio .wrap {{
|
| 141 |
display:flex !important;
|
| 142 |
flex-direction:column !important;
|
|
@@ -144,19 +223,20 @@ CSS = f"""
|
|
| 144 |
background:transparent !important;
|
| 145 |
border:none !important;
|
| 146 |
}}
|
| 147 |
-
/* Each radio option becomes a sidebar pill */
|
| 148 |
.ams-side-radio label {{
|
| 149 |
display:flex !important;
|
| 150 |
align-items:center !important;
|
| 151 |
-
padding:
|
| 152 |
margin:0 !important;
|
| 153 |
-
border-radius:
|
| 154 |
border:none !important;
|
| 155 |
border-left:2px solid transparent !important;
|
| 156 |
background:transparent !important;
|
| 157 |
color:{INK_MUTED} !important;
|
| 158 |
-
font-
|
|
|
|
| 159 |
font-weight:500 !important;
|
|
|
|
| 160 |
cursor:pointer !important;
|
| 161 |
transition:background 80ms ease, color 80ms ease, border-color 80ms ease;
|
| 162 |
min-height:0 !important;
|
|
@@ -167,11 +247,9 @@ CSS = f"""
|
|
| 167 |
background:{HOVER_BG} !important;
|
| 168 |
color:{INK} !important;
|
| 169 |
}}
|
| 170 |
-
/* Hide the native radio circle */
|
| 171 |
.ams-side-radio label input[type="radio"] {{
|
| 172 |
display:none !important;
|
| 173 |
}}
|
| 174 |
-
/* Active state: white text + white left border + dark bg */
|
| 175 |
.ams-side-radio label.selected,
|
| 176 |
.ams-side-radio label:has(input[type="radio"]:checked) {{
|
| 177 |
background:{HOVER_BG} !important;
|
|
@@ -179,32 +257,35 @@ CSS = f"""
|
|
| 179 |
border-left-color:{PRIMARY} !important;
|
| 180 |
font-weight:600 !important;
|
| 181 |
}}
|
| 182 |
-
/* Hide the (now-empty) form-element-info row that gr.Radio injects */
|
| 183 |
.ams-side-radio + div:empty {{ display:none !important; }}
|
| 184 |
|
| 185 |
-
/*
|
| 186 |
.ams-history {{
|
| 187 |
-
margin-top:
|
| 188 |
-
padding-top:
|
| 189 |
border-top:1px solid {BORDER};
|
| 190 |
}}
|
| 191 |
.ams-history-title {{
|
| 192 |
-
font-
|
| 193 |
-
|
|
|
|
| 194 |
padding:0 4px 6px 4px;
|
| 195 |
}}
|
| 196 |
.ams-history-empty {{
|
| 197 |
-
font-
|
|
|
|
| 198 |
font-style:italic;
|
| 199 |
-
padding:
|
| 200 |
}}
|
| 201 |
|
| 202 |
-
/* ===
|
|
|
|
|
|
|
| 203 |
.ams-content {{
|
| 204 |
background:{SURFACE} !important;
|
| 205 |
border:1px solid {BORDER} !important;
|
| 206 |
border-radius:{RADIUS} !important;
|
| 207 |
-
padding:
|
| 208 |
min-height:540px;
|
| 209 |
}}
|
| 210 |
.ams-tab-pane {{
|
|
@@ -212,172 +293,344 @@ CSS = f"""
|
|
| 212 |
border:none !important;
|
| 213 |
padding:0 !important;
|
| 214 |
}}
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
|
|
|
| 221 |
}}
|
| 222 |
-
.ams-chip.on {{ border-color:{PRIMARY}; color:{PRIMARY}; }}
|
| 223 |
-
.ams-chip.upload {{ border-style:dashed; color:{PRIMARY}; }}
|
| 224 |
|
| 225 |
-
/* ===
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
|
| 231 |
-
/* === Tighten Gradio chrome (narrow scope to avoid breaking output) =====
|
| 232 |
-
Only sharpen the INPUT control surfaces (textareas, number inputs)
|
| 233 |
-
so they read as crisp Brutalist Mono panels. Do NOT touch generic
|
| 234 |
-
``.block`` padding — that collapses gr.Audio / gr.JSON which need
|
| 235 |
-
their own internal spacing. */
|
| 236 |
.ams-content textarea,
|
| 237 |
.ams-content input[type="text"],
|
| 238 |
.ams-content input[type="number"] {{
|
| 239 |
background:{SURFACE_STRONG} !important;
|
| 240 |
border:1px solid {BORDER} !important;
|
| 241 |
-
border-radius:
|
| 242 |
color:{INK} !important;
|
| 243 |
-
|
|
|
|
|
|
|
|
|
|
| 244 |
}}
|
| 245 |
.ams-content textarea:focus,
|
| 246 |
.ams-content input[type="text"]:focus,
|
| 247 |
.ams-content input[type="number"]:focus {{
|
| 248 |
outline:none !important;
|
| 249 |
border-color:{PRIMARY} !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
}}
|
| 251 |
.ams-content input[type="range"] {{
|
| 252 |
accent-color:{PRIMARY} !important;
|
| 253 |
}}
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
.ams-content .
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
font-size:10px !important;
|
| 261 |
letter-spacing:0.08em !important;
|
| 262 |
text-transform:uppercase !important;
|
| 263 |
color:{INK_MUTED} !important;
|
| 264 |
background:transparent !important;
|
| 265 |
-
|
| 266 |
-
padding:0 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
}}
|
| 268 |
|
| 269 |
-
/*
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
.
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
font-size:10px !important;
|
| 279 |
-
font-weight:500 !important;
|
| 280 |
-
letter-spacing:0.06em !important;
|
| 281 |
-
text-transform:uppercase !important;
|
| 282 |
-
border:none !important;
|
| 283 |
-
box-shadow:none !important;
|
| 284 |
-
padding:4px 0 !important;
|
| 285 |
}}
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
}}
|
|
|
|
|
|
|
|
|
|
| 290 |
|
| 291 |
-
/*
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
|
| 299 |
-
/* ===
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
@media (max-width: 640px) {{
|
| 301 |
-
/* Stack body so sidebar (now a tab strip) sits above content */
|
| 302 |
.ams-body {{
|
| 303 |
flex-direction:column !important;
|
| 304 |
gap:8px !important;
|
| 305 |
}}
|
| 306 |
-
|
| 307 |
-
/* Sidebar = horizontal scroll strip. Strip its desktop chrome
|
| 308 |
-
(border, large padding, fixed width) so it reads as a tab bar. */
|
| 309 |
.ams-sidebar {{
|
| 310 |
min-width:100% !important;
|
| 311 |
max-width:100% !important;
|
| 312 |
-
padding:2px !important;
|
| 313 |
border:none !important;
|
| 314 |
background:transparent !important;
|
| 315 |
border-radius:0 !important;
|
| 316 |
}}
|
| 317 |
-
|
| 318 |
-
/* The radio's outer block (gr.Radio with container=False still gets
|
| 319 |
-
padding from Gradio's base styles). Flatten it. */
|
| 320 |
-
.ams-side-radio {{
|
| 321 |
-
padding:0 !important;
|
| 322 |
-
background:transparent !important;
|
| 323 |
-
}}
|
| 324 |
-
|
| 325 |
-
/* Real options live in the second .wrap (Gradio renders an extra
|
| 326 |
-
hidden one first); both flex-row + overflow + nowrap.
|
| 327 |
-
CRITICAL: override the desktop label width:100% — that's what
|
| 328 |
-
makes labels stack vertically inside a flex-row container.
|
| 329 |
-
flex-wrap:nowrap forces a single row + horizontal scroll instead
|
| 330 |
-
of wrapping to 2 rows. */
|
| 331 |
.ams-side-radio .wrap {{
|
| 332 |
flex-direction:row !important;
|
| 333 |
flex-wrap:nowrap !important;
|
| 334 |
overflow-x:auto !important;
|
| 335 |
overflow-y:hidden !important;
|
| 336 |
gap:6px !important;
|
| 337 |
-
padding
|
| 338 |
-
/* Hide scrollbar but keep scrolling */
|
| 339 |
scrollbar-width:none !important;
|
| 340 |
-ms-overflow-style:none !important;
|
| 341 |
}}
|
| 342 |
-
.ams-side-radio .wrap::-webkit-scrollbar {{
|
| 343 |
-
display:none !important;
|
| 344 |
-
}}
|
| 345 |
-
|
| 346 |
.ams-side-radio label {{
|
| 347 |
-
/* Compact pill: just enough room for emoji + label, no flex-grow */
|
| 348 |
width:auto !important;
|
| 349 |
min-width:0 !important;
|
| 350 |
max-width:max-content !important;
|
| 351 |
flex:0 0 auto !important;
|
|
|
|
| 352 |
font-size:11px !important;
|
| 353 |
font-weight:600 !important;
|
| 354 |
white-space:nowrap !important;
|
| 355 |
-
padding:
|
| 356 |
-
/* Bottom border instead of left border for the horizontal context */
|
| 357 |
-
border-left:none !important;
|
| 358 |
-
border-bottom:2px solid transparent !important;
|
| 359 |
-
border-radius:4px !important;
|
| 360 |
-
justify-content:center !important;
|
| 361 |
background:{SURFACE_STRONG} !important;
|
| 362 |
-
border
|
| 363 |
-
border-
|
| 364 |
-
|
| 365 |
}}
|
| 366 |
.ams-side-radio label.selected,
|
| 367 |
.ams-side-radio label:has(input[type="radio"]:checked) {{
|
| 368 |
-
border-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
}}
|
| 372 |
-
|
| 373 |
-
/* History block off-screen on mobile (already display:none on tablet+;
|
| 374 |
-
restate here in case the cascade gets weird) */
|
| 375 |
.ams-history {{ display:none !important; }}
|
| 376 |
|
| 377 |
-
/*
|
| 378 |
-
.ams-header {{ padding:
|
| 379 |
-
.ams-brand {{ font-size:
|
| 380 |
-
.ams-
|
| 381 |
-
.ams-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
}}
|
| 383 |
"""
|
|
|
|
| 1 |
"""Brutalist Mono — pure black/white, no color accent.
|
| 2 |
|
| 3 |
+
The aesthetic identity of ACE Music Studio: precision, density, mono-
|
| 4 |
+
spaced labels, true neutral grays, and a single white accent rationed
|
| 5 |
+
across active states + the primary CTA. Inspired by record-sleeve
|
| 6 |
+
liner notes and a code editor's chrome.
|
| 7 |
+
|
| 8 |
+
Typography (selected per the frontend-design discipline):
|
| 9 |
+
- IBM Plex Sans — body, brand, helper text. Mechanically refined,
|
| 10 |
+
warmer than Inter, free via Google Fonts.
|
| 11 |
+
- IBM Plex Mono — labels, status indicator, brand period, metadata
|
| 12 |
+
JSON, all-caps small text. Distinctive character without going
|
| 13 |
+
novelty.
|
| 14 |
+
|
| 15 |
+
The wireframes at ``docs/superpowers/specs/mockups/`` remain the visual
|
| 16 |
+
source of truth. The mobile breakpoint at 640 px replaces the sidebar
|
| 17 |
+
with a horizontal scroll strip and crushes the outer Gradio padding so
|
| 18 |
+
the full 360 px viewport is actually usable.
|
| 19 |
"""
|
| 20 |
|
| 21 |
from __future__ import annotations
|
|
|
|
| 26 |
BG = "#0A0A0A"
|
| 27 |
SURFACE = "#141414"
|
| 28 |
SURFACE_STRONG = "#000000"
|
| 29 |
+
SURFACE_RAISED = "#1A1A1A"
|
| 30 |
BORDER = "#1F1F1F"
|
| 31 |
BORDER_STRONG = "#2A2A2A"
|
| 32 |
INK = "#E5E5E5"
|
| 33 |
INK_MUTED = "#6B6B6B"
|
| 34 |
+
INK_FAINT = "#3F3F3F"
|
| 35 |
PRIMARY = "#FFFFFF"
|
|
|
|
| 36 |
HOVER_BG = "#1A1A1A"
|
| 37 |
+
RADIUS = "4px"
|
| 38 |
+
FONT_SANS = '"IBM Plex Sans", -apple-system, BlinkMacSystemFont, system-ui, sans-serif'
|
| 39 |
+
FONT_MONO = '"IBM Plex Mono", "JetBrains Mono", ui-monospace, Menlo, monospace'
|
| 40 |
|
| 41 |
|
| 42 |
def build_theme() -> gr.themes.Base:
|
| 43 |
+
"""Returns a Gradio theme keyed to Brutalist Mono tokens.
|
| 44 |
+
|
| 45 |
+
Uses IBM Plex Sans as the body font and IBM Plex Mono as the
|
| 46 |
+
monospace partner. Both are pulled from Google Fonts at runtime.
|
| 47 |
+
"""
|
| 48 |
return gr.themes.Base(
|
| 49 |
primary_hue=gr.themes.colors.gray,
|
| 50 |
neutral_hue=gr.themes.colors.gray,
|
| 51 |
+
font=[
|
| 52 |
+
gr.themes.GoogleFont("IBM Plex Sans"),
|
| 53 |
+
"system-ui",
|
| 54 |
+
"sans-serif",
|
| 55 |
+
],
|
| 56 |
+
font_mono=[
|
| 57 |
+
gr.themes.GoogleFont("IBM Plex Mono"),
|
| 58 |
+
"ui-monospace",
|
| 59 |
+
"monospace",
|
| 60 |
+
],
|
| 61 |
).set(
|
| 62 |
body_background_fill=BG,
|
| 63 |
body_text_color=INK,
|
| 64 |
+
background_fill_primary=BG,
|
| 65 |
+
background_fill_secondary=SURFACE,
|
| 66 |
block_background_fill=SURFACE,
|
| 67 |
block_border_color=BORDER,
|
| 68 |
block_border_width="1px",
|
| 69 |
block_radius=RADIUS,
|
| 70 |
+
block_label_text_color=INK_MUTED,
|
| 71 |
+
block_label_background_fill="transparent",
|
| 72 |
+
block_title_background_fill="transparent",
|
| 73 |
+
block_title_text_color=INK_MUTED,
|
| 74 |
input_background_fill=SURFACE_STRONG,
|
| 75 |
+
input_border_color=BORDER,
|
| 76 |
input_border_color_focus=PRIMARY,
|
| 77 |
+
input_placeholder_color=INK_FAINT,
|
| 78 |
button_primary_background_fill=PRIMARY,
|
| 79 |
button_primary_text_color=BG,
|
| 80 |
button_primary_background_fill_hover=PRIMARY,
|
| 81 |
button_secondary_background_fill=SURFACE_STRONG,
|
| 82 |
button_secondary_text_color=INK,
|
| 83 |
button_secondary_border_color=BORDER_STRONG,
|
| 84 |
+
border_color_primary=BORDER,
|
| 85 |
+
border_color_accent=BORDER_STRONG,
|
| 86 |
+
color_accent=PRIMARY,
|
| 87 |
+
color_accent_soft=SURFACE_RAISED,
|
| 88 |
)
|
| 89 |
|
| 90 |
|
| 91 |
+
# Note: all `!important` below is intentional — Gradio's svelte-hashed
|
| 92 |
+
# classes load AFTER our CSS in some browsers, and the framework's
|
| 93 |
+
# default rules sit at the same specificity as ours without it.
|
| 94 |
CSS = f"""
|
| 95 |
+
/* ============================================================
|
| 96 |
+
* Brutalist Mono — global palette + Gradio variable overrides
|
| 97 |
+
* Gradio's gr.themes.colors.gray maps to Tailwind slate-*, which has
|
| 98 |
+
* a perceptible blue tint on cool-temperature phone displays. Pin
|
| 99 |
+
* every neutral + surface variable to true monochrome hex.
|
| 100 |
+
* ============================================================ */
|
| 101 |
+
:root,
|
| 102 |
+
html.dark,
|
| 103 |
+
.gradio-container,
|
| 104 |
+
.gradio-container.dark,
|
| 105 |
+
.gradio-container .dark {{
|
| 106 |
+
--neutral-50: #FAFAFA !important;
|
| 107 |
--neutral-100: #F5F5F5 !important;
|
| 108 |
--neutral-200: #E5E5E5 !important;
|
| 109 |
--neutral-300: #D4D4D4 !important;
|
|
|
|
| 114 |
--neutral-800: #262626 !important;
|
| 115 |
--neutral-900: #141414 !important;
|
| 116 |
--neutral-950: #0A0A0A !important;
|
| 117 |
+
--body-background-fill: {BG} !important;
|
| 118 |
+
--background-fill-primary: {BG} !important;
|
| 119 |
+
--background-fill-secondary: {SURFACE} !important;
|
| 120 |
+
--block-background-fill: {SURFACE} !important;
|
| 121 |
+
--block-label-background-fill: transparent !important;
|
| 122 |
+
--block-title-background-fill: transparent !important;
|
| 123 |
+
--input-background-fill: {SURFACE_STRONG} !important;
|
| 124 |
+
--border-color-primary: {BORDER} !important;
|
| 125 |
+
--border-color-accent: {BORDER_STRONG} !important;
|
| 126 |
+
--color-accent: {PRIMARY} !important;
|
| 127 |
+
--body-text-color: {INK} !important;
|
| 128 |
+
--body-text-color-subdued: {INK_MUTED} !important;
|
| 129 |
+
--block-label-text-color: {INK_MUTED} !important;
|
| 130 |
+
--block-title-text-color: {INK_MUTED} !important;
|
| 131 |
+
--link-text-color: {INK} !important;
|
| 132 |
+
font-family: {FONT_SANS};
|
| 133 |
+
}}
|
| 134 |
+
body, .gradio-container {{
|
| 135 |
+
background: {BG} !important;
|
| 136 |
+
color: {INK} !important;
|
| 137 |
+
font-family: {FONT_SANS} !important;
|
| 138 |
+
font-feature-settings: "ss01", "ss03", "cv11";
|
| 139 |
+
}}
|
| 140 |
+
|
| 141 |
+
/* ============================================================
|
| 142 |
+
* Crush Gradio's default ``.app`` wrapper padding.
|
| 143 |
+
* Default ``.gradio-container > .app`` ships with 16px 32px which
|
| 144 |
+
* eats 64 px of the 360 px mobile viewport. Replace with a sane
|
| 145 |
+
* scale that respects the breakpoints.
|
| 146 |
+
* ============================================================ */
|
| 147 |
+
.gradio-container > .app,
|
| 148 |
+
.gradio-container .main.fillable {{
|
| 149 |
+
padding: 16px 20px !important;
|
| 150 |
+
max-width: none !important;
|
| 151 |
+
}}
|
| 152 |
+
@media (max-width: 640px) {{
|
| 153 |
+
.gradio-container > .app,
|
| 154 |
+
.gradio-container .main.fillable {{
|
| 155 |
+
padding: 8px 10px !important;
|
| 156 |
+
}}
|
| 157 |
+
}}
|
| 158 |
+
main, .contain {{
|
| 159 |
+
width: 100% !important;
|
| 160 |
+
max-width: none !important;
|
| 161 |
+
}}
|
| 162 |
+
|
| 163 |
+
/* ============================================================
|
| 164 |
+
* Header + CTA banner
|
| 165 |
+
* ============================================================ */
|
| 166 |
.ams-header {{
|
| 167 |
display:flex; justify-content:space-between; align-items:baseline;
|
| 168 |
+
padding:10px 2px 6px 2px;
|
| 169 |
}}
|
| 170 |
.ams-brand {{
|
| 171 |
+
font-family: {FONT_SANS};
|
| 172 |
+
font-size:17px; font-weight:600;
|
| 173 |
+
letter-spacing:-0.01em; color:{INK};
|
| 174 |
}}
|
| 175 |
+
.ams-brand-period {{ color:{PRIMARY}; font-family: {FONT_MONO}; }}
|
| 176 |
.ams-status {{
|
| 177 |
+
font-family: {FONT_MONO};
|
| 178 |
+
font-size:10px; color:{INK_MUTED};
|
| 179 |
+
letter-spacing:0.08em; text-transform:uppercase;
|
| 180 |
}}
|
| 181 |
|
| 182 |
.ams-cta {{
|
| 183 |
+
font-size:12px; color:{INK_MUTED};
|
| 184 |
+
margin:2px 2px 14px 2px; padding-bottom:10px;
|
| 185 |
border-bottom:1px solid {BORDER};
|
| 186 |
+
line-height:1.5;
|
| 187 |
}}
|
| 188 |
+
.ams-cta strong {{ color:{INK}; font-weight:600; }}
|
| 189 |
.ams-cta-heart {{ color:{PRIMARY}; }}
|
| 190 |
+
.ams-cta a {{ color:{INK}; text-decoration:underline; text-decoration-color:{BORDER_STRONG}; }}
|
| 191 |
|
| 192 |
+
/* ============================================================
|
| 193 |
+
* Body row (sidebar + content)
|
| 194 |
+
* ============================================================ */
|
| 195 |
.ams-body {{
|
| 196 |
+
gap:12px !important;
|
| 197 |
align-items:stretch !important;
|
| 198 |
}}
|
| 199 |
|
| 200 |
+
/* ============================================================
|
| 201 |
+
* Sidebar — desktop ≥ 1024
|
| 202 |
+
* ============================================================ */
|
| 203 |
.ams-sidebar {{
|
| 204 |
background:{SURFACE_STRONG} !important;
|
| 205 |
+
padding:12px 6px !important;
|
| 206 |
border-radius:{RADIUS} !important;
|
| 207 |
border:1px solid {BORDER} !important;
|
| 208 |
+
min-width:188px;
|
| 209 |
max-width:210px;
|
| 210 |
}}
|
| 211 |
|
|
|
|
| 212 |
.ams-side-radio {{
|
| 213 |
background:transparent !important;
|
| 214 |
border:none !important;
|
| 215 |
padding:0 !important;
|
| 216 |
width:100%;
|
| 217 |
}}
|
| 218 |
+
.ams-side-radio > div > .wrap,
|
| 219 |
.ams-side-radio .wrap {{
|
| 220 |
display:flex !important;
|
| 221 |
flex-direction:column !important;
|
|
|
|
| 223 |
background:transparent !important;
|
| 224 |
border:none !important;
|
| 225 |
}}
|
|
|
|
| 226 |
.ams-side-radio label {{
|
| 227 |
display:flex !important;
|
| 228 |
align-items:center !important;
|
| 229 |
+
padding:8px 11px !important;
|
| 230 |
margin:0 !important;
|
| 231 |
+
border-radius:3px !important;
|
| 232 |
border:none !important;
|
| 233 |
border-left:2px solid transparent !important;
|
| 234 |
background:transparent !important;
|
| 235 |
color:{INK_MUTED} !important;
|
| 236 |
+
font-family: {FONT_SANS} !important;
|
| 237 |
+
font-size:12px !important;
|
| 238 |
font-weight:500 !important;
|
| 239 |
+
letter-spacing:0.005em !important;
|
| 240 |
cursor:pointer !important;
|
| 241 |
transition:background 80ms ease, color 80ms ease, border-color 80ms ease;
|
| 242 |
min-height:0 !important;
|
|
|
|
| 247 |
background:{HOVER_BG} !important;
|
| 248 |
color:{INK} !important;
|
| 249 |
}}
|
|
|
|
| 250 |
.ams-side-radio label input[type="radio"] {{
|
| 251 |
display:none !important;
|
| 252 |
}}
|
|
|
|
| 253 |
.ams-side-radio label.selected,
|
| 254 |
.ams-side-radio label:has(input[type="radio"]:checked) {{
|
| 255 |
background:{HOVER_BG} !important;
|
|
|
|
| 257 |
border-left-color:{PRIMARY} !important;
|
| 258 |
font-weight:600 !important;
|
| 259 |
}}
|
|
|
|
| 260 |
.ams-side-radio + div:empty {{ display:none !important; }}
|
| 261 |
|
| 262 |
+
/* History block (below the mode radio) */
|
| 263 |
.ams-history {{
|
| 264 |
+
margin-top:12px;
|
| 265 |
+
padding-top:8px;
|
| 266 |
border-top:1px solid {BORDER};
|
| 267 |
}}
|
| 268 |
.ams-history-title {{
|
| 269 |
+
font-family: {FONT_MONO};
|
| 270 |
+
font-size:9px; color:{INK_MUTED};
|
| 271 |
+
letter-spacing:0.12em; text-transform:uppercase;
|
| 272 |
padding:0 4px 6px 4px;
|
| 273 |
}}
|
| 274 |
.ams-history-empty {{
|
| 275 |
+
font-family: {FONT_SANS};
|
| 276 |
+
font-size:11px; color:{INK_FAINT};
|
| 277 |
font-style:italic;
|
| 278 |
+
padding:4px 4px;
|
| 279 |
}}
|
| 280 |
|
| 281 |
+
/* ============================================================
|
| 282 |
+
* Content pane
|
| 283 |
+
* ============================================================ */
|
| 284 |
.ams-content {{
|
| 285 |
background:{SURFACE} !important;
|
| 286 |
border:1px solid {BORDER} !important;
|
| 287 |
border-radius:{RADIUS} !important;
|
| 288 |
+
padding:14px !important;
|
| 289 |
min-height:540px;
|
| 290 |
}}
|
| 291 |
.ams-tab-pane {{
|
|
|
|
| 293 |
border:none !important;
|
| 294 |
padding:0 !important;
|
| 295 |
}}
|
| 296 |
+
/* Force the inner 2-column row inside each pane to actually stack
|
| 297 |
+
on narrow screens — Gradio gr.Row keeps row direction by default. */
|
| 298 |
+
@media (max-width: 768px) {{
|
| 299 |
+
.ams-tab-pane .row,
|
| 300 |
+
.ams-tab-pane [class*="row"][class*="svelte"] {{
|
| 301 |
+
flex-direction:column !important;
|
| 302 |
+
}}
|
| 303 |
}}
|
|
|
|
|
|
|
| 304 |
|
| 305 |
+
/* ============================================================
|
| 306 |
+
* Form field chrome — labels, helper info, inputs
|
| 307 |
+
* Gradio's defaults render labels and helper text at 14-16 px.
|
| 308 |
+
* Brutalist Mono wants labels small + uppercase + mono.
|
| 309 |
+
* ============================================================ */
|
| 310 |
+
/* Scope the small-uppercase-mono treatment to ONLY the label text
|
| 311 |
+
spans, NOT the entire <label> wrapper. Cascading text-transform
|
| 312 |
+
from a label wrapper would otherwise uppercase the helper text
|
| 313 |
+
(.info-text) and the input's own text. */
|
| 314 |
+
.ams-content span.has-info,
|
| 315 |
+
.ams-content span.svelte-jdcl7l,
|
| 316 |
+
.ams-content .block-label > span,
|
| 317 |
+
.ams-content [data-testid="block-label"] > span,
|
| 318 |
+
.ams-content .label-wrap > span:first-child {{
|
| 319 |
+
font-family: {FONT_MONO} !important;
|
| 320 |
+
font-size:10px !important;
|
| 321 |
+
letter-spacing:0.08em !important;
|
| 322 |
+
text-transform:uppercase !important;
|
| 323 |
+
color:{INK_MUTED} !important;
|
| 324 |
+
background:transparent !important;
|
| 325 |
+
border:none !important;
|
| 326 |
+
box-shadow:none !important;
|
| 327 |
+
padding:0 0 4px 0 !important;
|
| 328 |
+
margin:0 !important;
|
| 329 |
+
font-weight:500 !important;
|
| 330 |
+
display:block !important;
|
| 331 |
+
}}
|
| 332 |
+
/* Reset the <label> wrapper itself so it doesn't cascade transforms */
|
| 333 |
+
.ams-content label.container,
|
| 334 |
+
.ams-content label.svelte-1hguek3 {{
|
| 335 |
+
text-transform:none !important;
|
| 336 |
+
font-family:inherit !important;
|
| 337 |
+
font-size:inherit !important;
|
| 338 |
+
letter-spacing:normal !important;
|
| 339 |
+
display:block !important;
|
| 340 |
+
padding:0 !important;
|
| 341 |
+
}}
|
| 342 |
+
/* Helper text — Gradio 6.14 renders it as <div class="info-text …">.
|
| 343 |
+
Force sentence case + sans + small + faint italic. */
|
| 344 |
+
.ams-content .info-text,
|
| 345 |
+
.ams-content [class*="info-text"],
|
| 346 |
+
.ams-content .block .info,
|
| 347 |
+
.ams-content [data-testid="info"] {{
|
| 348 |
+
font-family: {FONT_SANS} !important;
|
| 349 |
+
font-size:10px !important;
|
| 350 |
+
color:{INK_FAINT} !important;
|
| 351 |
+
letter-spacing:0 !important;
|
| 352 |
+
text-transform:none !important;
|
| 353 |
+
font-style:italic !important;
|
| 354 |
+
padding:0 0 6px 0 !important;
|
| 355 |
+
line-height:1.4 !important;
|
| 356 |
+
font-weight:400 !important;
|
| 357 |
+
}}
|
| 358 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
.ams-content textarea,
|
| 360 |
.ams-content input[type="text"],
|
| 361 |
.ams-content input[type="number"] {{
|
| 362 |
background:{SURFACE_STRONG} !important;
|
| 363 |
border:1px solid {BORDER} !important;
|
| 364 |
+
border-radius:3px !important;
|
| 365 |
color:{INK} !important;
|
| 366 |
+
font-family: {FONT_SANS} !important;
|
| 367 |
+
font-size:13px !important;
|
| 368 |
+
padding:10px 12px !important;
|
| 369 |
+
line-height:1.5 !important;
|
| 370 |
}}
|
| 371 |
.ams-content textarea:focus,
|
| 372 |
.ams-content input[type="text"]:focus,
|
| 373 |
.ams-content input[type="number"]:focus {{
|
| 374 |
outline:none !important;
|
| 375 |
border-color:{PRIMARY} !important;
|
| 376 |
+
box-shadow:none !important;
|
| 377 |
+
}}
|
| 378 |
+
.ams-content textarea::placeholder,
|
| 379 |
+
.ams-content input::placeholder {{
|
| 380 |
+
color:{INK_FAINT} !important;
|
| 381 |
}}
|
| 382 |
.ams-content input[type="range"] {{
|
| 383 |
accent-color:{PRIMARY} !important;
|
| 384 |
}}
|
| 385 |
+
|
| 386 |
+
/* ============================================================
|
| 387 |
+
* Form Radio (Vocal mode) — compact pills, NOT the sidebar tabs
|
| 388 |
+
* ============================================================ */
|
| 389 |
+
.ams-content .block:has(input[type="radio"]) .wrap {{
|
| 390 |
+
display:flex !important;
|
| 391 |
+
flex-direction:column !important;
|
| 392 |
+
gap:6px !important;
|
| 393 |
+
}}
|
| 394 |
+
.ams-content .wrap > label {{
|
| 395 |
+
font-family: {FONT_SANS} !important;
|
| 396 |
+
font-size:12px !important;
|
| 397 |
+
text-transform:none !important;
|
| 398 |
+
letter-spacing:0 !important;
|
| 399 |
+
font-weight:500 !important;
|
| 400 |
+
color:{INK} !important;
|
| 401 |
+
background:{SURFACE_STRONG} !important;
|
| 402 |
+
border:1px solid {BORDER} !important;
|
| 403 |
+
border-radius:3px !important;
|
| 404 |
+
padding:7px 12px !important;
|
| 405 |
+
display:flex !important;
|
| 406 |
+
align-items:center !important;
|
| 407 |
+
gap:8px !important;
|
| 408 |
+
cursor:pointer !important;
|
| 409 |
+
}}
|
| 410 |
+
.ams-content .wrap > label:hover {{
|
| 411 |
+
border-color:{BORDER_STRONG} !important;
|
| 412 |
+
background:{HOVER_BG} !important;
|
| 413 |
+
}}
|
| 414 |
+
.ams-content .wrap > label:has(input[type="radio"]:checked) {{
|
| 415 |
+
border-color:{PRIMARY} !important;
|
| 416 |
+
background:{SURFACE_RAISED} !important;
|
| 417 |
+
}}
|
| 418 |
+
/* Custom radio dot */
|
| 419 |
+
.ams-content .wrap > label input[type="radio"] {{
|
| 420 |
+
appearance:none !important;
|
| 421 |
+
-webkit-appearance:none !important;
|
| 422 |
+
width:12px !important; height:12px !important;
|
| 423 |
+
border:1px solid {BORDER_STRONG} !important;
|
| 424 |
+
border-radius:50% !important;
|
| 425 |
+
margin:0 !important;
|
| 426 |
+
flex-shrink:0 !important;
|
| 427 |
+
}}
|
| 428 |
+
.ams-content .wrap > label input[type="radio"]:checked {{
|
| 429 |
+
border-color:{PRIMARY} !important;
|
| 430 |
+
background: radial-gradient({PRIMARY} 0 4px, transparent 5px) !important;
|
| 431 |
+
}}
|
| 432 |
+
|
| 433 |
+
/* ============================================================
|
| 434 |
+
* Primary button (▶ Generate)
|
| 435 |
+
* ============================================================ */
|
| 436 |
+
.ams-content button.primary {{
|
| 437 |
+
background:{PRIMARY} !important;
|
| 438 |
+
color:{BG} !important;
|
| 439 |
+
border:none !important;
|
| 440 |
+
border-radius:3px !important;
|
| 441 |
+
font-family: {FONT_SANS} !important;
|
| 442 |
+
font-size:13px !important;
|
| 443 |
+
font-weight:600 !important;
|
| 444 |
+
letter-spacing:0.005em !important;
|
| 445 |
+
padding:11px 18px !important;
|
| 446 |
+
cursor:pointer !important;
|
| 447 |
+
transition:transform 80ms ease, opacity 80ms ease;
|
| 448 |
+
margin-top:6px !important;
|
| 449 |
+
}}
|
| 450 |
+
.ams-content button.primary:hover {{
|
| 451 |
+
opacity:0.92 !important;
|
| 452 |
+
transform:translateY(-1px);
|
| 453 |
+
}}
|
| 454 |
+
.ams-content button.primary:active {{
|
| 455 |
+
transform:translateY(0);
|
| 456 |
+
}}
|
| 457 |
+
|
| 458 |
+
/* ============================================================
|
| 459 |
+
* Output panel — gr.Audio (.ams-out-audio) and gr.JSON (.ams-out-meta)
|
| 460 |
+
* Targeted via the elem_classes hooks defined in ui.py so we don't
|
| 461 |
+
* have to chase svelte-hashed class names.
|
| 462 |
+
* ============================================================ */
|
| 463 |
+
.ams-content .ams-out {{
|
| 464 |
+
background:{SURFACE_STRONG} !important;
|
| 465 |
+
border:1px solid {BORDER} !important;
|
| 466 |
+
border-radius:3px !important;
|
| 467 |
+
padding:12px !important;
|
| 468 |
+
margin-top:10px !important;
|
| 469 |
+
}}
|
| 470 |
+
.ams-content .ams-out-audio {{
|
| 471 |
+
min-height:90px !important;
|
| 472 |
+
}}
|
| 473 |
+
.ams-content .ams-out-meta {{
|
| 474 |
+
min-height:80px !important;
|
| 475 |
+
font-family: {FONT_MONO} !important;
|
| 476 |
+
font-size:11px !important;
|
| 477 |
+
line-height:1.6 !important;
|
| 478 |
+
}}
|
| 479 |
+
.ams-content .ams-out-meta span {{
|
| 480 |
+
font-family: {FONT_MONO} !important;
|
| 481 |
+
font-size:11px !important;
|
| 482 |
+
}}
|
| 483 |
+
/* The Output/Metadata block labels live as <label class="svelte-19djge9">.
|
| 484 |
+
Force the Brutalist label treatment + uppercase regardless of the
|
| 485 |
+
svelte hash, since they're inside .ams-out. */
|
| 486 |
+
.ams-content .ams-out label,
|
| 487 |
+
.ams-content .ams-out label > span {{
|
| 488 |
+
font-family: {FONT_MONO} !important;
|
| 489 |
font-size:10px !important;
|
| 490 |
letter-spacing:0.08em !important;
|
| 491 |
text-transform:uppercase !important;
|
| 492 |
color:{INK_MUTED} !important;
|
| 493 |
background:transparent !important;
|
| 494 |
+
font-weight:500 !important;
|
| 495 |
+
padding:0 0 6px 0 !important;
|
| 496 |
+
display:block !important;
|
| 497 |
+
}}
|
| 498 |
+
/* Empty-state svg glyphs (music note, JSON braces) — center + muted */
|
| 499 |
+
.ams-content .ams-out svg {{
|
| 500 |
+
color:{INK_FAINT} !important;
|
| 501 |
+
opacity:0.5 !important;
|
| 502 |
}}
|
| 503 |
|
| 504 |
+
/* Add explicit gap between form column and output column when stacked */
|
| 505 |
+
@media (max-width: 768px) {{
|
| 506 |
+
.ams-tab-pane > .row,
|
| 507 |
+
.ams-tab-pane > [class*="row"] {{
|
| 508 |
+
gap:18px !important;
|
| 509 |
+
}}
|
| 510 |
+
.ams-content .block + .block {{
|
| 511 |
+
margin-top:4px !important;
|
| 512 |
+
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 513 |
}}
|
| 514 |
+
|
| 515 |
+
/* ============================================================
|
| 516 |
+
* LoRA chip pill — kept for M2 wiring
|
| 517 |
+
* ============================================================ */
|
| 518 |
+
.ams-chip {{
|
| 519 |
+
display:inline-block; padding:5px 10px; border-radius:14px;
|
| 520 |
+
font-size:11px; margin:0 5px 5px 0; background:{SURFACE_STRONG};
|
| 521 |
+
border:1px solid {BORDER_STRONG}; color:{INK_MUTED}; cursor:pointer;
|
| 522 |
}}
|
| 523 |
+
.ams-chip.on {{ border-color:{PRIMARY}; color:{PRIMARY}; }}
|
| 524 |
+
.ams-chip.upload {{ border-style:dashed; color:{PRIMARY}; }}
|
| 525 |
+
.ams-lora-file .upload-container {{ min-height:56px !important; }}
|
| 526 |
|
| 527 |
+
/* Hide Gradio footer + the floating "Use via API" / settings panel */
|
| 528 |
+
footer {{ display:none !important; }}
|
| 529 |
+
.show-api {{ display:none !important; }}
|
| 530 |
+
.built-with {{ display:none !important; }}
|
| 531 |
+
|
| 532 |
+
/* ============================================================
|
| 533 |
+
* Responsive: tablet 640-1024 px
|
| 534 |
+
* Keep the full sidebar (with labels) — the icon-rail middle state
|
| 535 |
+
* fights Gradio's DOM. Just narrow it slightly.
|
| 536 |
+
* ============================================================ */
|
| 537 |
+
@media (min-width: 641px) and (max-width: 1024px) {{
|
| 538 |
+
.ams-sidebar {{ min-width:160px; max-width:180px; padding:10px 6px !important; }}
|
| 539 |
+
.ams-side-radio label {{ font-size:11px !important; padding:7px 9px !important; }}
|
| 540 |
+
}}
|
| 541 |
|
| 542 |
+
/* ============================================================
|
| 543 |
+
* Responsive: mobile < 640 px
|
| 544 |
+
* Sidebar becomes a horizontal scroll pill strip at the top.
|
| 545 |
+
* Form + Output stack with proper gap.
|
| 546 |
+
* ============================================================ */
|
| 547 |
@media (max-width: 640px) {{
|
|
|
|
| 548 |
.ams-body {{
|
| 549 |
flex-direction:column !important;
|
| 550 |
gap:8px !important;
|
| 551 |
}}
|
|
|
|
|
|
|
|
|
|
| 552 |
.ams-sidebar {{
|
| 553 |
min-width:100% !important;
|
| 554 |
max-width:100% !important;
|
| 555 |
+
padding:2px 0 !important;
|
| 556 |
border:none !important;
|
| 557 |
background:transparent !important;
|
| 558 |
border-radius:0 !important;
|
| 559 |
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 560 |
.ams-side-radio .wrap {{
|
| 561 |
flex-direction:row !important;
|
| 562 |
flex-wrap:nowrap !important;
|
| 563 |
overflow-x:auto !important;
|
| 564 |
overflow-y:hidden !important;
|
| 565 |
gap:6px !important;
|
| 566 |
+
padding:2px 0 !important;
|
|
|
|
| 567 |
scrollbar-width:none !important;
|
| 568 |
-ms-overflow-style:none !important;
|
| 569 |
}}
|
| 570 |
+
.ams-side-radio .wrap::-webkit-scrollbar {{ display:none !important; }}
|
|
|
|
|
|
|
|
|
|
| 571 |
.ams-side-radio label {{
|
|
|
|
| 572 |
width:auto !important;
|
| 573 |
min-width:0 !important;
|
| 574 |
max-width:max-content !important;
|
| 575 |
flex:0 0 auto !important;
|
| 576 |
+
font-family: {FONT_SANS} !important;
|
| 577 |
font-size:11px !important;
|
| 578 |
font-weight:600 !important;
|
| 579 |
white-space:nowrap !important;
|
| 580 |
+
padding:7px 12px !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 581 |
background:{SURFACE_STRONG} !important;
|
| 582 |
+
border:1px solid {BORDER} !important;
|
| 583 |
+
border-radius:3px !important;
|
| 584 |
+
justify-content:center !important;
|
| 585 |
}}
|
| 586 |
.ams-side-radio label.selected,
|
| 587 |
.ams-side-radio label:has(input[type="radio"]:checked) {{
|
| 588 |
+
border-color:{PRIMARY} !important;
|
| 589 |
+
background:{SURFACE_RAISED} !important;
|
| 590 |
+
color:{PRIMARY} !important;
|
| 591 |
}}
|
|
|
|
|
|
|
|
|
|
| 592 |
.ams-history {{ display:none !important; }}
|
| 593 |
|
| 594 |
+
/* Header + CTA tighter */
|
| 595 |
+
.ams-header {{ padding:2px 2px 2px 2px !important; }}
|
| 596 |
+
.ams-brand {{ font-size:14px !important; }}
|
| 597 |
+
.ams-status {{ font-size:9px !important; }}
|
| 598 |
+
.ams-cta {{ font-size:11px !important; margin:0 2px 8px 2px !important; padding-bottom:8px !important; }}
|
| 599 |
+
|
| 600 |
+
/* Content pane tighter */
|
| 601 |
+
.ams-content {{ padding:12px !important; border-radius:3px !important; }}
|
| 602 |
+
|
| 603 |
+
/* Field labels + info shrink further */
|
| 604 |
+
.ams-content label,
|
| 605 |
+
.ams-content .block-label,
|
| 606 |
+
.ams-content [data-testid="block-label"],
|
| 607 |
+
.ams-content span.svelte-jdcl7l,
|
| 608 |
+
.ams-content .label-wrap {{
|
| 609 |
+
font-size:9px !important;
|
| 610 |
+
}}
|
| 611 |
+
.ams-content .block .info,
|
| 612 |
+
.ams-content [data-testid="info"] {{
|
| 613 |
+
font-size:9px !important;
|
| 614 |
+
padding-bottom:4px !important;
|
| 615 |
+
}}
|
| 616 |
+
.ams-content textarea,
|
| 617 |
+
.ams-content input[type="text"],
|
| 618 |
+
.ams-content input[type="number"] {{
|
| 619 |
+
font-size:12px !important;
|
| 620 |
+
padding:8px 10px !important;
|
| 621 |
+
}}
|
| 622 |
+
.ams-content .wrap > label {{
|
| 623 |
+
padding:6px 10px !important;
|
| 624 |
+
font-size:11px !important;
|
| 625 |
+
}}
|
| 626 |
+
.ams-content button.primary {{
|
| 627 |
+
padding:11px 14px !important;
|
| 628 |
+
font-size:12px !important;
|
| 629 |
+
}}
|
| 630 |
+
.ams-content [data-testid="audio"],
|
| 631 |
+
.ams-content .audio-container,
|
| 632 |
+
.ams-content .json-holder {{
|
| 633 |
+
min-height:64px !important;
|
| 634 |
+
}}
|
| 635 |
}}
|
| 636 |
"""
|
|
@@ -59,15 +59,23 @@ def build_generate_tab() -> dict[str, gr.components.Component]:
|
|
| 59 |
)
|
| 60 |
|
| 61 |
# --- OUTPUT column (right, ~40% width) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
with gr.Column(scale=10):
|
| 63 |
components["output_audio"] = gr.Audio(
|
| 64 |
label="Output",
|
| 65 |
type="filepath",
|
| 66 |
interactive=False,
|
|
|
|
| 67 |
)
|
| 68 |
# gr.JSON renders a dict directly as a syntax-highlighted, expandable
|
| 69 |
# tree. gr.Code(language="json") refuses dicts — it requires a
|
| 70 |
# pre-stringified blob — and crashes with "'dict' has no .strip()".
|
| 71 |
-
components["output_meta"] = gr.JSON(
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
return components
|
|
|
|
| 59 |
)
|
| 60 |
|
| 61 |
# --- OUTPUT column (right, ~40% width) ---
|
| 62 |
+
# elem_classes on each output component give CSS hooks for the
|
| 63 |
+
# Brutalist Mono treatment (uppercase mono labels + bordered
|
| 64 |
+
# empty-state panels). Without these we'd need to target
|
| 65 |
+
# svelte-hashed classes which can change across Gradio versions.
|
| 66 |
with gr.Column(scale=10):
|
| 67 |
components["output_audio"] = gr.Audio(
|
| 68 |
label="Output",
|
| 69 |
type="filepath",
|
| 70 |
interactive=False,
|
| 71 |
+
elem_classes=["ams-out", "ams-out-audio"],
|
| 72 |
)
|
| 73 |
# gr.JSON renders a dict directly as a syntax-highlighted, expandable
|
| 74 |
# tree. gr.Code(language="json") refuses dicts — it requires a
|
| 75 |
# pre-stringified blob — and crashes with "'dict' has no .strip()".
|
| 76 |
+
components["output_meta"] = gr.JSON(
|
| 77 |
+
label="Metadata",
|
| 78 |
+
elem_classes=["ams-out", "ams-out-meta"],
|
| 79 |
+
)
|
| 80 |
|
| 81 |
return components
|