polyglot-me / theme.py
praxelhq's picture
a11y polish: theme floating component labels, AA owl roles, brighter secondary text
e9a4196 verified
Raw
History Blame Contribute Delete
8.13 kB
"""Praxy Field Guide — shared design system for the Build-Small hackathon apps.
One CSS framework, themed per app via tokens. Injected via gr.HTML('<style>…</style>')
because Gradio 6.0 dropped the css= argument from gr.Blocks(). All colour pairings
are chosen to pass WCAG AA (body text >=4.5:1, large text / UI >=3:1).
A copy of this file lives in each hf_space_* directory (the spaces upload
independently and cannot share a module). Keep them in sync.
"""
from __future__ import annotations
# Per-app design tokens. Each value is a CSS colour; contrast verified vs bg/accent.
THEMES = {
# Nidra — night sky. Dark is thematically right; text is solid (not low-alpha).
"nidra": {
"bg": "#0B1026", "bg2": "#070A1C", "surface": "#161C3F", "surface2": "#1E2550",
"ink": "#F4EFDE", "muted": "#AEB4D6", "faint": "#9CA2C8",
"accent": "#F5C842", "accent_ink": "#1A1304", "accent2": "#9E86FF",
"border": "rgba(245,200,66,.20)", "ring": "rgba(245,200,66,.45)",
"display": "Fraunces", "body": "Plus Jakarta Sans",
},
# Read-it-to-Amma — warm morning parchment. Accessibility app -> high contrast.
"readit": {
"bg": "#FBF3E3", "bg2": "#F6E9CE", "surface": "#FFFCF5", "surface2": "#F5EDE0",
"ink": "#2D1306", "muted": "#6B3D1E", "faint": "#9A7550",
"accent": "#C2410C", "accent_ink": "#FFFFFF", "accent2": "#7B1F2E",
"border": "rgba(120,60,0,.18)", "ring": "rgba(194,65,12,.35)",
"display": "Fraunces", "body": "Plus Jakarta Sans",
},
# Polyglot Me — festival; refined dark with four AA language accents.
"polyglot": {
"bg": "#0E1020", "bg2": "#080A18", "surface": "#181B33", "surface2": "#20243F",
"ink": "#F1EEF8", "muted": "#AFB0CC", "faint": "#9698BE",
"accent": "#8B7FF9", "accent_ink": "#0B0B1A", "accent2": "#5BB8F2",
"border": "rgba(255,255,255,.10)", "ring": "rgba(139,127,249,.45)",
"display": "Fraunces", "body": "Plus Jakarta Sans",
},
# Parliament of Owls — naturalist field guide; parchment + forest + brass.
# Palette WCAG-verified: ink/bg 16.6:1, muted/bg 7.3:1, forest-accent/cream 7+:1.
"owls": {
"bg": "#F0E6CC", "bg2": "#E6D8B6", "surface": "#FAF5EB", "surface2": "#F2E9D2",
"ink": "#221E14", "muted": "#5C4A1E", "faint": "#8A7B5C",
"accent": "#2F5D44", "accent_ink": "#F7F1E1", "accent2": "#8B6914",
"border": "rgba(60,50,30,.22)", "ring": "rgba(47,93,68,.40)",
"display": "Fraunces", "body": "Plus Jakarta Sans",
},
}
_FONTS = ("https://fonts.googleapis.com/css2?"
"family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;0,9..144,700;0,9..144,900;"
"1,9..144,400;1,9..144,600&"
"family=Plus+Jakarta+Sans:wght@400;500;600;700;800&"
"family=Noto+Sans:wght@400;500;600&display=swap")
def build_css(theme: str, extra: str = "") -> str:
"""Return the full themed stylesheet for `theme` (a key in THEMES)."""
t = THEMES[theme]
return f"""
@import url('{_FONTS}');
:root {{
--bg:{t['bg']}; --bg2:{t['bg2']}; --surface:{t['surface']}; --surface2:{t['surface2']};
--ink:{t['ink']}; --muted:{t['muted']}; --faint:{t['faint']};
--accent:{t['accent']}; --accent-ink:{t['accent_ink']}; --accent2:{t['accent2']};
--border:{t['border']}; --ring:{t['ring']};
--display:'{t['display']}',Georgia,serif; --body:'{t['body']}','Noto Sans',sans-serif;
--radius:18px; --gap:18px;
}}
/* ---- fill the whole viewport, widen the column ---- */
html, body, gradio-app, .gradio-container, .app, #root {{
background: var(--bg) !important;
}}
body, .gradio-container {{ font-family: var(--body) !important; color: var(--ink) !important; }}
.gradio-container {{
max-width: 1180px !important; margin: 0 auto !important;
padding-left: 20px !important; padding-right: 20px !important;
}}
.gradio-container, .gradio-container * {{ box-sizing: border-box; }}
/* headings + prose */
h1,h2,h3 {{ font-family: var(--display) !important; color: var(--ink) !important; }}
/* ---- surfaces / cards ---- */
.gr-form, .gr-block, .gr-panel, .block, .contain, .gr-box, .form {{
background: var(--surface) !important;
border: 1px solid var(--border) !important;
border-radius: var(--radius) !important;
}}
.gap, .panel, .wrap {{ gap: var(--gap) !important; }}
/* ---- labels ---- */
label > span, .label-wrap > span, span[data-testid="block-info"] {{
color: var(--muted) !important; font-size: 11px !important;
font-weight: 700 !important; letter-spacing: .09em !important;
text-transform: uppercase !important; font-family: var(--body) !important;
}}
/* floating component labels (Audio / File / Image): Gradio overlays these with a
near-white chip; theme them so they don't read as unstyled white blobs. */
.block-label, span.block-label, div.block-label {{
background: var(--surface2) !important; color: var(--muted) !important;
border: 1px solid var(--border) !important; box-shadow: none !important;
backdrop-filter: none !important;
}}
.block-label span, .block-label p {{ color: var(--muted) !important; }}
.block-label svg, .block-label svg * {{ color: var(--muted) !important; fill: currentColor !important; }}
/* ---- inputs ---- */
input[type=text], input[type=number], textarea, select, .gr-text-input {{
background: var(--surface2) !important;
border: 1.5px solid var(--border) !important;
border-radius: 12px !important;
color: var(--ink) !important; font-size: 15px !important;
font-family: var(--body) !important;
}}
input::placeholder, textarea::placeholder {{ color: var(--faint) !important; opacity: 1; }}
input:focus, textarea:focus, select:focus {{
border-color: var(--accent) !important; outline: none !important;
box-shadow: 0 0 0 3px var(--ring) !important;
}}
input[type=range] {{ accent-color: var(--accent) !important; }}
input[type=checkbox] {{ accent-color: var(--accent) !important; width:18px; height:18px; }}
/* ---- accessible focus ring for keyboard users ---- */
:focus-visible {{ outline: 3px solid var(--ring) !important; outline-offset: 2px !important; }}
/* ---- primary CTA ---- */
button.primary, .gr-button-primary, #cta button {{
background: var(--accent) !important; color: var(--accent-ink) !important;
font-weight: 800 !important; font-size: 16px !important; letter-spacing: .02em !important;
font-family: var(--body) !important; border: none !important;
border-radius: 14px !important; padding: 16px 30px !important; width: 100% !important;
cursor: pointer !important;
box-shadow: 0 6px 22px rgba(0,0,0,.18) !important;
transition: transform .15s ease, box-shadow .25s ease, filter .2s ease !important;
}}
button.primary:hover, .gr-button-primary:hover, #cta button:hover {{
transform: translateY(-2px) !important; filter: brightness(1.05) !important;
box-shadow: 0 10px 34px rgba(0,0,0,.28) !important;
}}
#cta button:active {{ transform: translateY(0) !important; }}
/* secondary buttons */
.gr-button-secondary {{
background: var(--surface2) !important; color: var(--ink) !important;
border: 1.5px solid var(--border) !important; border-radius: 12px !important;
font-family: var(--body) !important; font-weight: 600 !important;
}}
/* audio + image blocks */
.waveform-container, .gr-audio, audio {{ border-radius: 12px !important; }}
/* tidy chrome */
footer {{ display: none !important; }}
.gradio-container .prose a {{ color: var(--accent2) !important; }}
::-webkit-scrollbar {{ width: 8px; height: 8px; }}
::-webkit-scrollbar-track {{ background: var(--bg2); }}
::-webkit-scrollbar-thumb {{ background: var(--accent); border-radius: 6px; opacity:.6; }}
/* shared pill / divider helpers used inside gr.HTML output */
.pf-pill {{
display:inline-block; padding:5px 14px; border-radius:20px; font-size:11px;
font-weight:700; letter-spacing:.06em; font-family:var(--body);
}}
.pf-divider {{ height:1px; background:linear-gradient(90deg,transparent,var(--border),transparent); margin:18px 0; border:none; }}
@media (max-width: 760px) {{
.gradio-container {{ padding-left: 12px !important; padding-right: 12px !important; }}
}}
{extra}
"""