agharsallah
style: lift Fishbowl UI contrast + readability (same CRT vibe)
137b0ed
Raw
History Blame Contribute Delete
4.96 kB
"""Fishbowl theme + CSS/head assets for the Gradio shell.
This module is the styling surface of the Fishbowl UI. It is import-safe β€”
constructing :class:`FishbowlTheme` or reading the assets never launches Gradio
or touches the engine. The app shell (Unit 9) wires it up with::
from src.ui.fishbowl.theme import FISHBOWL_HEAD, FishbowlTheme, load_css
demo.launch(css=load_css(), head=FISHBOWL_HEAD, theme=FishbowlTheme())
The phosphor / aquatic-CRT look is ported from the ``ui/raw`` prototype:
* ``assets/styles.css`` β€” the full stylesheet, scoped under a ``.fishbowl``
root so it survives Gradio's own cascade, with the CRT overlay layers left
full-bleed.
* ``assets/head.html`` β€” the Google Fonts links (Martian Mono + IBM Plex Mono).
:class:`FishbowlTheme` maps the same dark phosphor palette onto Gradio's theme
variables so the built-in widgets (sliders, dropdowns, buttons rendered by
Gradio itself) sit comfortably inside the theater.
"""
from __future__ import annotations
from pathlib import Path
import gradio as gr
__all__ = ["FISHBOWL_HEAD", "FishbowlTheme", "load_css"]
_ASSETS = Path(__file__).parent / "assets"
# ---- palette (mirrors the CSS custom properties in assets/styles.css) ----
# The ink ramp is calibrated up the lightness axis for readable contrast on the
# dark tank β€” same teal phosphor hue family, just legible (see styles.css :root).
_BG_0 = "#05121a" # deepest tank
_BG_1 = "#07181f"
_BG_2 = "#0a222b"
_PANEL_SOLID = "#0c2730"
_INK = "#d2f7f0" # primary copy
_INK_MID = "#9fdcd2" # secondary copy / block titles
_INK_DIM = "#7cb6ad" # labels / subdued (was #4d8983 β€” too dim to read)
_CYAN = "#4fe6d2"
_TEAL = "#2bc4b4"
def load_css() -> str:
"""Return the Fishbowl stylesheet as a string for ``gr.Blocks(css=...)``.
Reads ``assets/styles.css`` relative to this file, so it works regardless
of the process working directory.
"""
return (_ASSETS / "styles.css").read_text(encoding="utf-8")
def _load_head() -> str:
"""Read ``assets/head.html`` for ``demo.launch(head=...)``."""
return (_ASSETS / "head.html").read_text(encoding="utf-8")
#: The ``<head>`` markup (font links + meta) passed to ``demo.launch(head=...)``.
FISHBOWL_HEAD: str = _load_head()
class FishbowlTheme(gr.themes.Base):
"""A dark phosphor / aquatic-CRT Gradio theme for the Fishbowl app.
Subclasses :class:`gr.themes.Base` and overrides the colour, font, and
radius variables so Gradio's own widgets blend into the theater. The
bespoke theater markup is styled by :func:`load_css`; this theme covers the
surrounding Gradio chrome.
"""
def __init__(self) -> None:
super().__init__(
primary_hue=gr.themes.colors.teal,
secondary_hue=gr.themes.colors.cyan,
neutral_hue=gr.themes.colors.slate,
font=gr.themes.GoogleFont("IBM Plex Mono"),
font_mono=gr.themes.GoogleFont("Martian Mono"),
)
# Map the aquatic phosphor palette onto Gradio's theme variables so the
# built-in components match the ported .fishbowl markup.
super().set(
# backgrounds
body_background_fill=_BG_0,
body_background_fill_dark=_BG_0,
background_fill_primary=_BG_1,
background_fill_primary_dark=_BG_1,
background_fill_secondary=_BG_2,
background_fill_secondary_dark=_BG_2,
block_background_fill=_PANEL_SOLID,
block_background_fill_dark=_PANEL_SOLID,
panel_background_fill=_PANEL_SOLID,
panel_background_fill_dark=_PANEL_SOLID,
# text β€” form labels read at the "mid" tier (the old "dim" sank them
# below readable); body copy stays at full ink.
body_text_color=_INK,
body_text_color_dark=_INK,
body_text_color_subdued=_INK_MID,
body_text_color_subdued_dark=_INK_MID,
block_label_text_color=_INK_MID,
block_label_text_color_dark=_INK_MID,
block_title_text_color=_INK_MID,
block_title_text_color_dark=_INK_MID,
input_background_fill="rgba(5, 18, 26, 0.6)",
input_background_fill_dark="rgba(5, 18, 26, 0.6)",
# borders
border_color_primary="*neutral_700",
block_border_color="*neutral_700",
# accents β€” cyan/teal phosphor
color_accent=_CYAN,
color_accent_soft=_TEAL,
button_primary_background_fill=_TEAL,
button_primary_background_fill_dark=_TEAL,
button_primary_text_color=_BG_0,
button_primary_text_color_dark=_BG_0,
slider_color=_CYAN,
slider_color_dark=_CYAN,
# radii to match --r / --r-lg
block_radius="8px",
button_large_radius="4px",
button_small_radius="4px",
input_radius="4px",
)