File size: 8,443 Bytes
f915d94 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | """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>"
|