"""Brutalist Mono — pure black/white, no color accent. Palette tokens are the source of truth; CSS pulls from them. The audio waveform is the only optionally-colored element (rendered white in v1). UI architecture (locked): - Sidebar layout (NOT ``gr.Tabs``) per wireframes at ``docs/superpowers/specs/mockups/``. - ``.ams-sidebar`` is a flex column at desktop, fixed-width 170-190 px. - ``.ams-side-radio`` is the mode-nav: a ``gr.Radio`` re-skinned via CSS so each option renders as a full-width sidebar pill. The native ``:checked`` pseudo-class supplies the "active" highlight. - ``.ams-content`` is the right column containing 5 ``.ams-tab-pane`` groups; one is visible at a time. - Media queries: at ``<= 1024 px`` the sidebar shrinks to an icon rail. At ``<= 640 px`` the sidebar is replaced by a horizontal scroll strip at the top. """ from __future__ import annotations import gradio as gr # --- Palette ---------------------------------------------------------------- BG = "#0A0A0A" SURFACE = "#141414" SURFACE_STRONG = "#000000" BORDER = "#1F1F1F" BORDER_STRONG = "#2A2A2A" INK = "#E5E5E5" INK_MUTED = "#6B6B6B" PRIMARY = "#FFFFFF" ERROR_BG = "#1A1A1A" HOVER_BG = "#1A1A1A" RADIUS = "6px" FONT_STACK = '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif' def build_theme() -> gr.themes.Base: """Returns a Gradio theme keyed to Brutalist Mono tokens.""" return gr.themes.Base( primary_hue=gr.themes.colors.gray, neutral_hue=gr.themes.colors.gray, font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"], ).set( body_background_fill=BG, body_text_color=INK, block_background_fill=SURFACE, block_border_color=BORDER, block_border_width="1px", block_radius=RADIUS, input_background_fill=SURFACE_STRONG, input_border_color=BORDER_STRONG, input_border_color_focus=PRIMARY, button_primary_background_fill=PRIMARY, button_primary_text_color=BG, button_primary_background_fill_hover=PRIMARY, button_secondary_background_fill=SURFACE_STRONG, button_secondary_text_color=INK, button_secondary_border_color=BORDER_STRONG, ) CSS = f""" /* === Body chrome ======================================================= */ .ams-header {{ display:flex; justify-content:space-between; align-items:baseline; padding:10px 4px 6px 4px; }} .ams-brand {{ font-size:18px; font-weight:600; letter-spacing:-0.01em; color:{INK}; }} .ams-brand-period {{ color:{PRIMARY}; }} .ams-status {{ font-size:11px; color:{INK_MUTED}; letter-spacing:0.06em; text-transform:uppercase; }} .ams-cta {{ font-size:13px; color:{INK_MUTED}; margin:2px 4px 14px 4px; padding-bottom:12px; border-bottom:1px solid {BORDER}; }} .ams-cta strong {{ color:{INK}; }} .ams-cta-heart {{ color:{PRIMARY}; }} .ams-cta a {{ color:{INK}; text-decoration:underline; }} /* === Body row (sidebar + content) ====================================== */ .ams-body {{ gap:16px !important; align-items:stretch !important; }} /* === Sidebar (desktop >= 1024px) ======================================= */ .ams-sidebar {{ background:{SURFACE_STRONG} !important; padding:14px 8px !important; border-radius:{RADIUS} !important; border:1px solid {BORDER} !important; min-width:190px; max-width:210px; }} /* --- Mode radio (re-skin gr.Radio as a vertical sidebar nav) ----------- */ .ams-side-radio {{ background:transparent !important; border:none !important; padding:0 !important; width:100%; }} .ams-side-radio .wrap {{ display:flex !important; flex-direction:column !important; gap:2px !important; background:transparent !important; border:none !important; }} /* Each radio option becomes a sidebar pill */ .ams-side-radio label {{ display:flex !important; align-items:center !important; padding:9px 12px !important; margin:0 !important; border-radius:4px !important; border:none !important; border-left:2px solid transparent !important; background:transparent !important; color:{INK_MUTED} !important; font-size:13px !important; font-weight:500 !important; cursor:pointer !important; transition:background 80ms ease, color 80ms ease, border-color 80ms ease; min-height:0 !important; width:100%; box-sizing:border-box; }} .ams-side-radio label:hover {{ background:{HOVER_BG} !important; color:{INK} !important; }} /* Hide the native radio circle */ .ams-side-radio label input[type="radio"] {{ display:none !important; }} /* Active state: white text + white left border + dark bg */ .ams-side-radio label.selected, .ams-side-radio label:has(input[type="radio"]:checked) {{ background:{HOVER_BG} !important; color:{PRIMARY} !important; border-left-color:{PRIMARY} !important; font-weight:600 !important; }} /* Hide the (now-empty) form-element-info row that gr.Radio injects */ .ams-side-radio + div:empty {{ display:none !important; }} /* --- Session history block (below the mode radio) --------------------- */ .ams-history {{ margin-top:14px; padding-top:10px; border-top:1px solid {BORDER}; }} .ams-history-title {{ font-size:10px; color:{INK_MUTED}; letter-spacing:0.1em; text-transform:uppercase; padding:0 4px 6px 4px; }} .ams-history-empty {{ font-size:11px; color:#3F3F3F; font-style:italic; padding:6px 4px; }} /* === Content area ====================================================== */ .ams-content {{ background:{SURFACE} !important; border:1px solid {BORDER} !important; border-radius:{RADIUS} !important; padding:16px !important; min-height:540px; }} .ams-tab-pane {{ background:transparent !important; border:none !important; padding:0 !important; }} /* === LoRA chip pill (used in M2+) ====================================== */ .ams-chip {{ display:inline-block; padding:5px 10px; border-radius:14px; font-size:11px; margin:0 5px 5px 0; background:{SURFACE_STRONG}; border:1px solid {BORDER_STRONG}; color:{INK_MUTED}; cursor:pointer; }} .ams-chip.on {{ border-color:{PRIMARY}; color:{PRIMARY}; }} .ams-chip.upload {{ border-style:dashed; color:{PRIMARY}; }} /* === LoRA file drop zone (tighten Gradio default ~400px height) ======== */ .ams-lora-file .upload-container {{ min-height:56px !important; }} /* === Hide Gradio footer ================================================ */ footer {{ display:none !important; }} /* === Responsive: tablet 640-1024 px ==================================== */ @media (max-width: 1024px) {{ .ams-sidebar {{ min-width:48px !important; max-width:48px !important; padding:8px 4px !important; }} /* Hide labels, keep only the leading emoji */ .ams-side-radio label {{ font-size:0 !important; padding:8px 0 !important; justify-content:center !important; }} .ams-side-radio label::first-letter {{ font-size:16px !important; }} /* Hide history in tablet rail mode */ .ams-history {{ display:none !important; }} }} /* === Responsive: mobile < 640 px ======================================= */ @media (max-width: 640px) {{ .ams-body {{ flex-direction:column !important; }} .ams-sidebar {{ min-width:100% !important; max-width:100% !important; padding:6px !important; }} /* Mobile: switch sidebar to horizontal scroll strip */ .ams-side-radio .wrap {{ flex-direction:row !important; overflow-x:auto !important; gap:4px !important; }} .ams-side-radio label {{ font-size:11px !important; white-space:nowrap !important; border-left:none !important; border-bottom:2px solid transparent !important; padding:8px 10px !important; justify-content:flex-start !important; }} .ams-side-radio label::first-letter {{ font-size:13px !important; }} .ams-side-radio label:has(input[type="radio"]:checked) {{ border-left-color:transparent !important; border-bottom-color:{PRIMARY} !important; }} .ams-history {{ display:none !important; }} .ams-cta {{ font-size:11px; }} }} """