Download theme.py from jackstev/step-response-explainer: direct link, hf CLI and curl.
- Browser
- Download file 8.44 kB
-
https://huggingface.co/spaces/jackstev/step-response-explainer/resolve/main/theme.py
- Command line
-
hf download hf://spaces/jackstev/step-response-explainer/theme.py
-
curl -L -o theme.py https://huggingface.co/spaces/jackstev/step-response-explainer/resolve/main/theme.py
8.44 kB
| """Shared visual language for the three 24-679 HW3 apps. | |
| Warm light palette: apricot page, aubergine ink, hibiscus actions, marigold data. | |
| The same file ships with every Space so the three apps read as one set. | |
| """ | |
| from html import escape | |
| import gradio as gr | |
| # Named palette tokens reused by the theme, CSS, and HTML renderers | |
| P = { | |
| "page": "#FBEEE2", # apricot tinted page, warm but not cream | |
| "surface": "#FFF9F4", # panels sit a step lighter than the page | |
| "line": "#EFD9C8", # hairlines, input borders, bar tracks | |
| "track": "#F6E4D6", # empty part of score bars | |
| "ink": "#3A2530", # warm aubergine body text | |
| "muted": "#7A6170", # secondary text and captions | |
| "accent": "#B83E62", # hibiscus, buttons and keyboard focus | |
| "accent_soft": "#F7D9DF", # selected chips and soft highlights | |
| "data": "#E9A03F", # marigold, bars and plotted curves | |
| "good": "#4F7F55", # passing checks | |
| "bad": "#A5352B", # failing checks | |
| "warn": "#8A5A12", # cautions that are not failures | |
| } | |
| HEAD_FONT = "'Bricolage Grotesque', 'Figtree', system-ui, sans-serif" | |
| def _same_in_dark(**tokens): | |
| """Duplicate each token into its _dark twin so the palette never flips.""" | |
| out = {} | |
| for key, value in tokens.items(): | |
| out[key] = value | |
| out[f"{key}_dark"] = value # lock light mode even if browser asks for dark | |
| return out | |
| def build_theme(): | |
| """Gradio theme holding shared control defaults for all three apps.""" | |
| base = gr.themes.Base( | |
| primary_hue="rose", | |
| neutral_hue="stone", | |
| font=[gr.themes.GoogleFont("Figtree"), "system-ui", "sans-serif"], | |
| font_mono=["ui-monospace", "monospace"], | |
| ) | |
| paired = _same_in_dark( | |
| body_background_fill=P["page"], | |
| body_text_color=P["ink"], | |
| body_text_color_subdued=P["muted"], | |
| background_fill_primary=P["surface"], | |
| background_fill_secondary=P["page"], | |
| border_color_primary=P["line"], | |
| color_accent_soft=P["accent_soft"], | |
| block_background_fill=P["surface"], | |
| block_border_color=P["line"], | |
| block_label_text_color=P["muted"], | |
| block_label_background_fill=P["surface"], | |
| block_title_text_color=P["ink"], | |
| block_shadow="none", | |
| input_background_fill="#FFFFFF", | |
| input_border_color=P["line"], | |
| input_border_color_focus=P["accent"], | |
| input_shadow="none", | |
| button_primary_background_fill=P["accent"], | |
| button_primary_background_fill_hover="#A03455", | |
| button_primary_text_color="#FFFFFF", | |
| button_primary_border_color=P["accent"], | |
| button_secondary_background_fill=P["surface"], | |
| button_secondary_background_fill_hover=P["accent_soft"], | |
| button_secondary_text_color=P["ink"], | |
| button_secondary_border_color=P["line"], | |
| slider_color=P["accent"], | |
| checkbox_background_color_selected=P["accent"], | |
| checkbox_border_color_selected=P["accent"], | |
| checkbox_label_background_fill=P["surface"], | |
| checkbox_label_background_fill_hover=P["accent_soft"], | |
| checkbox_label_background_fill_selected=P["accent_soft"], | |
| checkbox_label_text_color=P["ink"], | |
| checkbox_label_text_color_selected=P["ink"], | |
| link_text_color=P["accent"], | |
| link_text_color_hover="#A03455", | |
| table_even_background_fill=P["surface"], | |
| table_odd_background_fill="#FFFFFF", | |
| table_border_color=P["line"], | |
| error_background_fill="#F6DEDA", | |
| error_border_color=P["bad"], | |
| error_text_color=P["bad"], | |
| loader_color=P["accent"], | |
| ) | |
| # Tokens without a _dark twin are set once | |
| return base.set(color_accent=P["accent"], block_radius="14px", input_radius="10px", | |
| block_border_width="1px", **paired) | |
| CSS = f""" | |
| @import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,500;12..96,650&family=Figtree:wght@400;500;600&display=swap'); | |
| /* Page frame: one centred column that stops growing at 1120px */ | |
| .gradio-container {{ max-width: 1120px !important; margin: auto !important; }} | |
| .hw3-head, .hw3-foot {{ padding: 0; border: 0; }} | |
| /* Header: the title carries the personality, the lede stays quiet */ | |
| .hw3-head h1 {{ | |
| font-family: {HEAD_FONT}; font-weight: 650; color: {P['ink']}; | |
| font-size: clamp(34px, 5vw, 52px); line-height: 1.02; letter-spacing: -0.02em; | |
| margin: 12px 0 8px; | |
| }} | |
| .hw3-head h2 {{ | |
| font-family: {HEAD_FONT}; font-weight: 650; color: {P['ink']}; | |
| font-size: 28px; line-height: 1.1; letter-spacing: -0.01em; margin: 18px 0 6px; | |
| }} | |
| .hw3-head p {{ max-width: 64ch; color: {P['muted']}; font-size: 16px; line-height: 1.55; margin: 0 0 8px; }} | |
| /* Result card: verdict line set large, evidence underneath */ | |
| .verdict {{ | |
| background: {P['surface']}; border: 1px solid {P['line']}; border-radius: 14px; | |
| padding: 22px 24px; color: {P['ink']}; font-variant-numeric: tabular-nums; | |
| }} | |
| .verdict .v-title {{ | |
| font-family: {HEAD_FONT}; font-weight: 650; font-size: clamp(30px, 4vw, 44px); | |
| line-height: 1.05; letter-spacing: -0.015em; margin: 0; | |
| }} | |
| .verdict.empty .v-title {{ color: {P['muted']}; font-size: 26px; }} | |
| .verdict .v-sub {{ color: {P['muted']}; font-size: 15px; margin: 8px 0 18px; line-height: 1.5; }} | |
| .verdict .v-foot {{ | |
| color: {P['muted']}; font-size: 13px; line-height: 1.55; | |
| border-top: 1px solid {P['line']}; padding-top: 12px; margin-top: 18px; | |
| }} | |
| /* Score bars: width comes from the model output, CSS only styles it */ | |
| .bar-row {{ margin: 0 0 14px; }} | |
| .bar-row .bar-lab {{ display: flex; justify-content: space-between; font-size: 14px; margin-bottom: 6px; }} | |
| .bar-row .track {{ height: 10px; background: {P['track']}; border-radius: 999px; overflow: hidden; }} | |
| .bar-row .fill {{ height: 100%; background: {P['data']}; border-radius: 999px; }} | |
| .bar-row .fill.lead {{ background: {P['accent']}; }} | |
| /* Status badges pair colour with text so meaning never relies on colour */ | |
| .badge {{ display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 13px; font-weight: 600; }} | |
| .badge.good {{ background: #E3EFE1; color: {P['good']}; }} | |
| .badge.bad {{ background: #F6DEDA; color: {P['bad']}; }} | |
| .badge.warn {{ background: #FBEBCB; color: {P['warn']}; }} | |
| /* Keep keyboard focus obvious everywhere */ | |
| .gradio-container *:focus-visible {{ outline: 3px solid {P['accent']} !important; outline-offset: 2px; }} | |
| .hw3-foot p {{ color: {P['muted']}; font-size: 13px; line-height: 1.55; margin: 6px 0; }} | |
| @media (max-width: 640px) {{ | |
| .verdict {{ padding: 18px; }} | |
| .hw3-head p {{ font-size: 15px; }} | |
| }} | |
| """ | |
| def header_html(title, lede): | |
| """Page header: one strong title and a single sentence of purpose.""" | |
| return f'<header class="hw3-head"><h1>{escape(title)}</h1><p>{escape(lede)}</p></header>' | |
| def section_html(title, lede): | |
| """Secondary heading for a major panel within the page.""" | |
| return f'<header class="hw3-head"><h2>{escape(title)}</h2><p>{escape(lede)}</p></header>' | |
| def bars_html(scores, lead=None): | |
| """Horizontal score bars; the leading class gets the accent colour.""" | |
| rows = [] | |
| for label, value in scores.items(): | |
| cls = "fill lead" if label == lead else "fill" # highlight the predicted class | |
| rows.append( | |
| f'<div class="bar-row"><div class="bar-lab"><span>{escape(str(label))}</span>' | |
| f'<strong>{value:.1%}</strong></div><div class="track" aria-hidden="true">' | |
| f'<div class="{cls}" style="width:{100 * value:.2f}%"></div></div></div>' | |
| ) | |
| return "".join(rows) | |
| def verdict_html(title, sub="", body="", foot="", empty=False): | |
| """Result card shell shared by the tabular and image apps.""" | |
| cls = "verdict empty" if empty else "verdict" | |
| sub_html = f'<p class="v-sub">{sub}</p>' if sub else "" | |
| foot_html = f'<p class="v-foot">{foot}</p>' if foot else "" | |
| return (f'<section class="{cls}" aria-live="polite"><p class="v-title">{escape(title)}</p>' | |
| f"{sub_html}{body}{foot_html}</section>") | |
| def badge(text, tone): | |
| """Small pill; tone is good, bad, or warn.""" | |
| return f'<span class="badge {tone}">{escape(text)}</span>' | |
| def footer_html(lines): | |
| """Quiet footer paragraphs for credits and links (callers escape their own HTML).""" | |
| return '<footer class="hw3-foot">' + "".join(f"<p>{line}</p>" for line in lines) + "</footer>" | |