ProxyPlayground / theme.py
niftym's picture
Make playground gallery thumbnails a uniform square size
4ed466e verified
Raw History Blame Contribute Delete
22.8 kB
"""
UI styling — kept separate from app.py so you can change the LOOK without
touching any app logic. Edit THEME and CSS below; nothing here affects how the
tool works, only how it looks.
HOW TO ITERATE (see changes live, no redeploy, no tokens):
1. Open a terminal in this project folder.
2. Run: .venv\\Scripts\\gradio app.py (Windows)
.venv/bin/gradio app.py (mac/Linux)
3. Open http://localhost:7860 in your browser.
4. Edit THIS file, save -> it auto-reloads -> refresh the browser.
(Use `gradio app.py`, not `python app.py`, so it hot-reloads on save.)
"""
import gradio as gr
# Keep dark-mode tokens identical to light so OS "dark preference" cannot
# flip the UI back to charcoal (this app is designed as a light surface).
_CANVAS = "#F0F1F3"
_SURFACE = "#F7F8FA"
_PANEL = "#FFFFFF"
_BORDER = "#D5DAE2"
# Darker text for readable contrast on light gray / white
_TEXT = "#1A2332"
_MUTED = "#3D4A5C"
# Darker blue-gray so WHITE label/button text meets contrast (~AA)
_BTN = "#4A6580"
_BTN_HOVER = "#3D5570"
_BTN_SECONDARY = "#D5DEE8"
_BTN_SECONDARY_HOVER = "#C5D0DC"
# Professional / sleek: light gray canvas, EB Garamond, cool blue-gray accents.
THEME = gr.themes.Soft(
primary_hue="slate",
secondary_hue="slate",
neutral_hue="slate",
spacing_size="lg",
text_size="lg",
radius_size="sm",
font=[
gr.themes.GoogleFont("EB Garamond", weights=(400, 500, 600, 700)),
"Georgia",
"Times New Roman",
"serif",
],
font_mono=[
gr.themes.GoogleFont("IBM Plex Mono", weights=(400, 500)),
"ui-monospace",
"Consolas",
"monospace",
],
).set(
# Canvas — light gray (light + dark tokens matched)
body_background_fill=_CANVAS,
body_background_fill_dark=_CANVAS,
background_fill_primary=_SURFACE,
background_fill_primary_dark=_SURFACE,
background_fill_secondary=_CANVAS,
background_fill_secondary_dark=_CANVAS,
block_background_fill=_PANEL,
block_background_fill_dark=_PANEL,
block_border_color=_BORDER,
block_border_color_dark=_BORDER,
border_color_primary=_BORDER,
border_color_primary_dark=_BORDER,
# Primary buttons — darker blue-gray + white text (readable)
button_primary_background_fill=_BTN,
button_primary_background_fill_hover=_BTN_HOVER,
button_primary_background_fill_dark=_BTN,
button_primary_background_fill_hover_dark=_BTN_HOVER,
button_primary_text_color="#FFFFFF",
button_primary_text_color_dark="#FFFFFF",
button_primary_text_color_hover="#FFFFFF",
button_primary_text_color_hover_dark="#FFFFFF",
button_primary_border_color=_BTN,
button_primary_border_color_dark=_BTN,
button_primary_border_color_hover=_BTN_HOVER,
button_primary_border_color_hover_dark=_BTN_HOVER,
# Secondary / default buttons
button_secondary_background_fill=_BTN_SECONDARY,
button_secondary_background_fill_hover=_BTN_SECONDARY_HOVER,
button_secondary_background_fill_dark=_BTN_SECONDARY,
button_secondary_background_fill_hover_dark=_BTN_SECONDARY_HOVER,
button_secondary_text_color=_TEXT,
button_secondary_text_color_dark=_TEXT,
button_secondary_border_color=_BTN_SECONDARY,
button_secondary_border_color_dark=_BTN_SECONDARY,
button_secondary_border_color_hover=_BTN_SECONDARY_HOVER,
button_secondary_border_color_hover_dark=_BTN_SECONDARY_HOVER,
# Text
body_text_color=_TEXT,
body_text_color_dark=_TEXT,
body_text_color_subdued=_MUTED,
body_text_color_subdued_dark=_MUTED,
block_info_text_color=_MUTED,
block_info_text_color_dark=_MUTED,
# Labels match primary button: dark blue-gray + white
block_label_background_fill=_BTN,
block_label_background_fill_dark=_BTN,
block_label_border_color=_BTN,
block_label_border_color_dark=_BTN,
block_label_text_color="#FFFFFF",
block_label_text_color_dark="#FFFFFF",
block_title_text_color=_TEXT,
block_title_text_color_dark=_TEXT,
# Checkbox / radio rows — dark text on light fill; white on selected dark chip
checkbox_label_text_color=_TEXT,
checkbox_label_text_color_dark=_TEXT,
checkbox_label_text_color_selected="#FFFFFF",
checkbox_label_text_color_selected_dark="#FFFFFF",
checkbox_label_background_fill=_PANEL,
checkbox_label_background_fill_dark=_PANEL,
checkbox_label_background_fill_hover=_SURFACE,
checkbox_label_background_fill_hover_dark=_SURFACE,
checkbox_label_background_fill_selected=_BTN,
checkbox_label_background_fill_selected_dark=_BTN,
checkbox_label_border_color=_BORDER,
checkbox_label_border_color_dark=_BORDER,
checkbox_label_border_color_selected=_BTN,
checkbox_label_border_color_selected_dark=_BTN,
checkbox_background_color_selected=_BTN,
checkbox_background_color_selected_dark=_BTN,
# Inputs
input_background_fill=_PANEL,
input_background_fill_dark=_PANEL,
input_border_color=_BORDER,
input_border_color_dark=_BORDER,
input_border_color_focus=_BTN,
input_border_color_focus_dark=_BTN,
input_placeholder_color=_MUTED,
input_placeholder_color_dark=_MUTED,
# Dataframe / table cells — light, never dark navy
table_even_background_fill=_PANEL,
table_even_background_fill_dark=_PANEL,
table_odd_background_fill=_SURFACE,
table_odd_background_fill_dark=_SURFACE,
table_border_color=_BORDER,
table_border_color_dark=_BORDER,
table_text_color=_TEXT,
table_text_color_dark=_TEXT,
table_row_focus=_BTN_SECONDARY,
table_row_focus_dark=_BTN_SECONDARY,
)
# Prefer light UI; strip a sticky dark preference if present.
JS = """
() => {
try {
document.documentElement.classList.remove("dark");
document.body?.classList?.remove("dark");
localStorage.setItem("theme", "light");
localStorage.setItem("colorScheme", "light");
} catch (e) {}
}
"""
CSS = f"""
gradio-app,
.gradio-container,
.gradio-container .main,
.gradio-container .wrap,
.gradio-container .fillable,
.gradio-container .contain {{
background-color: {_CANVAS} !important;
background: {_CANVAS} !important;
color: {_TEXT} !important;
}}
.gradio-container {{
max-width: 1280px !important;
margin: 0 auto !important;
padding: 1.75rem 1.5rem 3rem !important;
font-family: "EB Garamond", Georgia, "Times New Roman", serif !important;
}}
/* Centered title frame */
.hero-frame {{
max-width: 52rem !important;
margin: 0.5rem auto 2rem !important;
padding: 2rem 2.5rem 1.85rem !important;
border: 1px solid {_BORDER} !important;
background: {_PANEL} !important;
text-align: center !important;
box-shadow: none !important;
}}
.hero-frame .prose,
.hero-frame span.md.prose,
.hero-frame .markdown {{
text-align: center !important;
max-width: 40rem;
margin-left: auto !important;
margin-right: auto !important;
}}
.hero-frame h1 {{
text-align: center !important;
margin-left: auto !important;
margin-right: auto !important;
}}
.hero-frame p {{
text-align: center !important;
color: {_MUTED} !important;
font-size: 1.08rem !important;
line-height: 1.55 !important;
margin: 0.85rem auto 0 !important;
}}
/* Crisp, flat primary actions — no heavy shadow */
button.primary,
.primary {{
box-shadow: none !important;
letter-spacing: 0.02em;
min-height: 2.75rem !important;
padding: 0.65rem 1.35rem !important;
font-size: 1.05rem !important;
background: {_BTN} !important;
color: #FFFFFF !important;
border-color: {_BTN} !important;
}}
button.primary:hover,
.primary:hover {{
background: {_BTN_HOVER} !important;
border-color: {_BTN_HOVER} !important;
color: #FFFFFF !important;
}}
/* Tabs — dark text on light canvas */
.gradio-container button.selected {{
color: {_TEXT} !important;
font-weight: 600 !important;
opacity: 1 !important;
border-bottom-color: {_BTN} !important;
}}
.tab-nav button,
.tab-nav button * {{
font-family: "EB Garamond", Georgia, serif !important;
font-size: 1.15rem !important;
padding: 0.85rem 1.1rem !important;
color: {_TEXT} !important;
opacity: 1 !important;
}}
.tab-nav button.selected,
.tab-nav button.selected * {{
color: {_TEXT} !important;
border-bottom-color: {_BTN} !important;
font-weight: 600 !important;
}}
/* Field labels — force white on blue chips (Soft theme sometimes uses body text color) */
.block .container > span,
.block .label-wrap,
.block .label-wrap span,
.block label.float,
.block label.float span,
.block .has-info,
.block span.has-info {{
background: {_BTN} !important;
color: #FFFFFF !important;
border-color: {_BTN} !important;
}}
/* Empty dataframe title is div.label > p (not a <label>) — was white-on-light */
.block div.label > p,
.block .label > p,
.block .header-row .label p {{
color: {_TEXT} !important;
background: transparent !important;
opacity: 1 !important;
}}
/* Result panels under Manifest: light boxes, not deep blue */
.result-panel,
.result-panel .table-container,
.result-panel .wrap,
.result-panel table,
.result-panel .empty,
.result-panel [class*="empty"] {{
background: {_PANEL} !important;
color: {_TEXT} !important;
}}
.result-panel .container > span,
.result-panel .label-wrap,
.result-panel .label-wrap span,
.result-panel .label,
.result-panel .label > p,
.result-panel label,
.result-panel label span,
.result-panel label.float,
.result-panel label.float span {{
background: {_BTN_SECONDARY} !important;
color: {_TEXT} !important;
border-color: {_BORDER} !important;
}}
/* Dataframe empty stub / column bars — force light gray/white */
.table-container,
.table-wrap,
.dataframe-container,
table,
table thead,
table tbody,
table tr,
table th,
table td {{
background-color: {_PANEL} !important;
background: {_PANEL} !important;
color: {_TEXT} !important;
border-color: {_BORDER} !important;
}}
table thead th,
table th {{
background: {_SURFACE} !important;
color: {_TEXT} !important;
}}
/* Gradio empty dataframe placeholder strips */
.block .scroll-hide,
.block [class*="placeholder"],
.block .svelte-2balj6 {{
background: {_PANEL} !important;
color: {_MUTED} !important;
}}
/* Helper / info under inputs */
.info-text,
.block .info,
[class*="info-text"] {{
color: {_MUTED} !important;
}}
/* Inputs & checkboxes stay dark-on-light */
input, textarea, select,
.wrap input, .wrap textarea {{
color: {_TEXT} !important;
background: {_PANEL} !important;
}}
.checkbox-container,
.checkbox-container * {{
color: {_TEXT} !important;
}}
/* Selected radio/checkbox option — white text on dark blue-gray */
.checkbox-container.selected,
.checkbox-container.selected *,
label.selected,
label.selected *,
[class*="checkbox"].selected,
[class*="checkbox"].selected * {{
color: #FFFFFF !important;
}}
/* Embedding source label chip (and similar) — always white on dark fill */
.block .container > span,
.block span.has-info {{
color: #FFFFFF !important;
}}
/* Title — clear presence, not shouting */
.prose h1, .markdown h1, .prose > h1,
span.md.prose h1,
.hero-frame h1 {{
font-weight: 600 !important;
font-size: 2.35rem !important;
letter-spacing: -0.02em;
line-height: 1.2 !important;
color: {_TEXT} !important;
margin: 0 0 0.35rem !important;
}}
.prose h3, .markdown h3,
.gradio-container .chart-caption h3 {{
font-family: "EB Garamond", Georgia, "Times New Roman", serif !important;
font-size: 1.35rem !important;
font-weight: 600 !important;
line-height: 1.3 !important;
margin-top: 1.5rem !important;
margin-bottom: 0.45rem !important;
color: {_TEXT} !important;
}}
.gradio-container .chart-caption h3 {{
margin-top: 0.85rem !important;
}}
.prose, .markdown, span.md.prose,
.prose p, .markdown p, span.md.prose p,
.prose strong, .markdown strong {{
color: {_TEXT} !important;
}}
.prose p, .markdown p {{
color: {_MUTED} !important;
}}
/* Inline code (repo ids, etc.) — light chip + dark text, never charcoal-on-charcoal */
.prose code, .markdown code, span.md.prose code,
.prose pre, .markdown pre, span.md.prose pre,
code, pre, kbd, samp {{
background: {_SURFACE} !important;
background-color: {_SURFACE} !important;
color: {_TEXT} !important;
-webkit-text-fill-color: {_TEXT} !important;
border: 1px solid {_BORDER} !important;
box-shadow: none !important;
}}
.prose code, .markdown code, span.md.prose code,
code, kbd, samp {{
padding: 0.12em 0.4em !important;
border-radius: 4px !important;
font-family: "IBM Plex Mono", ui-monospace, Consolas, monospace !important;
font-size: 0.92em !important;
}}
.prose pre, .markdown pre, span.md.prose pre,
pre {{
padding: 0.75rem 1rem !important;
border-radius: 6px !important;
}}
.hero-frame h1 {{
color: {_TEXT} !important;
}}
/* Unselected radio options: dark text on light fill */
.gradio-container #embedding-source .checkbox-container:not(.selected),
.gradio-container #embedding-source .checkbox-container:not(.selected) * {{
color: {_TEXT} !important;
-webkit-text-fill-color: {_TEXT} !important;
}}
/* Selected radio options: white text on dark fill */
.gradio-container #embedding-source .checkbox-container.selected,
.gradio-container #embedding-source .checkbox-container.selected * {{
color: #FFFFFF !important;
-webkit-text-fill-color: #FFFFFF !important;
}}
/* Hide radio circles on chip-style pickers; keep clickable text labels */
.gradio-container #prompt-category input[type="radio"],
.gradio-container #embedding-source input[type="radio"] {{
appearance: none !important;
-webkit-appearance: none !important;
width: 0 !important;
height: 0 !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
outline: none !important;
position: absolute !important;
opacity: 0 !important;
pointer-events: none !important;
}}
.gradio-container #prompt-category label,
.gradio-container #embedding-source label {{
display: inline-flex !important;
align-items: center !important;
gap: 0 !important;
cursor: pointer !important;
}}
/* Local vs Hugging Face picker: center the "or" between columns */
.gradio-container .viz-or {{
display: flex !important;
align-items: center !important;
justify-content: center !important;
height: 100% !important;
min-height: 6rem !important;
text-align: center !important;
color: {_MUTED} !important;
}}
.gradio-container .viz-or p {{
margin: 0 !important;
color: {_MUTED} !important;
}}
/* Source panels: idle recedes, selected is framed + badged */
.gradio-container .viz-source {{
position: relative !important;
padding: 1rem 1.15rem 1.2rem !important;
border: 1px solid {_BORDER} !important;
border-radius: 8px !important;
background: {_SURFACE} !important;
opacity: 0.58 !important;
box-shadow: none !important;
overflow: visible !important;
cursor: pointer;
}}
.gradio-container .viz-source.selected {{
opacity: 1 !important;
border-color: {_BTN} !important;
background: {_PANEL} !important;
box-shadow: inset 4px 0 0 0 {_BTN} !important;
}}
.gradio-container .viz-source-head {{
align-items: center !important;
margin-bottom: 0.35rem !important;
}}
.gradio-container .viz-source-head .prose,
.gradio-container .viz-source-head .markdown,
.gradio-container .viz-source-head p {{
margin: 0 !important;
}}
.gradio-container .source-badge {{
text-align: right !important;
margin: 0 !important;
}}
.gradio-container .source-badge p,
.gradio-container .source-badge span,
.gradio-container .source-badge .prose,
.gradio-container .source-badge .markdown {{
display: inline-block !important;
margin: 0 !important;
padding: 0.18rem 0.55rem !important;
background: {_BTN} !important;
color: #FFFFFF !important;
-webkit-text-fill-color: #FFFFFF !important;
font-family: "IBM Plex Mono", ui-monospace, Consolas, monospace !important;
font-size: 0.72rem !important;
font-weight: 500 !important;
letter-spacing: 0.07em !important;
text-transform: uppercase !important;
border-radius: 3px !important;
border: none !important;
}}
.gradio-container .source-badge code {{
background: transparent !important;
border: none !important;
color: #FFFFFF !important;
}}
/* Invisible overlay so clicking anywhere on a source card selects it.
Gradio puts elem_classes on the <button> itself (no wrapper). */
.gradio-container .viz-source button.source-overlay {{
position: absolute !important;
inset: 0 !important;
z-index: 3 !important;
width: auto !important;
height: auto !important;
min-width: 0 !important;
min-height: 0 !important;
max-width: none !important;
max-height: none !important;
margin: 0 !important;
padding: 0 !important;
opacity: 0 !important;
cursor: pointer !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
overflow: hidden !important;
}}
.gradio-container .viz-source [role="combobox"],
.gradio-container .viz-source input,
.gradio-container .viz-source textarea,
.gradio-container .viz-source button:not(.source-overlay) {{
position: relative !important;
z-index: 4 !important;
}}
/* Chart captions sit above the figure — never as a chip on top of it */
.gradio-container .chart-caption {{
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
}}
.gradio-container .chart-caption .prose,
.gradio-container .chart-caption .markdown,
.gradio-container .chart-caption p {{
margin: 0 !important;
background: transparent !important;
}}
.gradio-container .chart-card {{
position: relative;
margin: 0 0 1.75rem 0;
}}
.gradio-container .chart-svg-wrap svg {{
width: 100% !important;
height: auto !important;
display: block;
background: {_PANEL};
}}
.gradio-container .chart-dl {{
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
opacity: 0 !important;
z-index: 6 !important;
pointer-events: none !important;
min-width: 8rem !important;
}}
.gradio-container .chart-card:hover .chart-dl {{
opacity: 1 !important;
pointer-events: auto !important;
}}
.gradio-container .chart-card:hover .chart-svg-wrap {{
opacity: 0.78;
}}
/* Dataframe labels (TVD table) above the table, not over cells */
.gradio-container .chart-table label.float,
.gradio-container .chart-table .label-wrap {{
position: static !important;
transform: none !important;
display: inline-block !important;
margin: 0 0 0.5rem 0 !important;
float: none !important;
}}
/* Playground: 10 labeled image cards */
.gradio-container .pg-card {{
background: {_PANEL} !important;
border: 1px solid {_BORDER} !important;
border-radius: 8px !important;
padding: 0.55rem 0.55rem 0.7rem !important;
margin-bottom: 0.6rem !important;
min-width: 0 !important;
}}
.gradio-container .pg-card .pg-thumb,
.gradio-container .pg-card .pg-thumb .image-container,
.gradio-container .pg-card .pg-thumb .image-frame {{
width: 100% !important;
aspect-ratio: 1 / 1 !important;
height: auto !important;
max-height: none !important;
overflow: hidden !important;
background: {_BTN_SECONDARY} !important;
border-radius: 4px !important;
}}
.gradio-container .pg-card img {{
width: 100% !important;
height: 100% !important;
max-height: none !important;
object-fit: cover !important;
object-position: center center !important;
border-radius: 4px !important;
}}
.gradio-container .pg-card .label-wrap,
.gradio-container .pg-card label.float,
.gradio-container .pg-card .container > span {{
background: {_BTN_SECONDARY} !important;
color: {_TEXT} !important;
border-color: {_BORDER} !important;
}}
.gradio-container .skin-chip {{
display: flex !important;
align-items: center !important;
gap: 0.45rem !important;
margin-top: 0.35rem !important;
font-family: "IBM Plex Mono", ui-monospace, Consolas, monospace !important;
font-size: 0.78rem !important;
color: {_MUTED} !important;
}}
.gradio-container .skin-chip .swatch {{
width: 1.05rem !important;
height: 1.05rem !important;
border-radius: 3px !important;
border: 1px solid {_BORDER} !important;
flex-shrink: 0 !important;
}}
.gradio-container .skin-chip.muted {{
font-style: italic !important;
}}
.gradio-container .pg-disclaimer {{
border: 1px solid {_BORDER} !important;
background: {_SURFACE} !important;
padding: 0.75rem 1rem !important;
border-radius: 6px !important;
margin: 0.5rem 0 1.25rem !important;
}}
.gradio-container .pg-disclaimer p {{
margin: 0 !important;
color: {_MUTED} !important;
font-size: 0.98rem !important;
}}
.gradio-container .pg-tvd p {{
color: {_TEXT} !important;
}}
.gradio-container .pg-loader-wrap {{
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
}}
.gradio-container .pg-loader {{
background: {_PANEL} !important;
border: 1px solid {_BORDER} !important;
border-radius: 8px !important;
padding: 1rem 1.15rem 1.1rem !important;
margin: 0.35rem 0 0.85rem 0 !important;
}}
.gradio-container .pg-loader-row {{
display: flex !important;
align-items: center !important;
gap: 0.85rem !important;
}}
.gradio-container .pg-spinner {{
width: 1.35rem !important;
height: 1.35rem !important;
border: 2px solid {_BORDER} !important;
border-top-color: {_BTN} !important;
border-radius: 50% !important;
animation: pg-spin 0.8s linear infinite !important;
flex-shrink: 0 !important;
}}
@keyframes pg-spin {{
to {{ transform: rotate(360deg); }}
}}
.gradio-container .pg-loader-title {{
font-size: 1.12rem !important;
color: {_TEXT} !important;
line-height: 1.3 !important;
}}
.gradio-container .pg-loader-eta {{
margin-top: 0.15rem !important;
color: {_MUTED} !important;
font-size: 0.95rem !important;
}}
.gradio-container .pg-loader-track {{
margin-top: 0.75rem !important;
height: 0.45rem !important;
background: {_BTN_SECONDARY} !important;
border-radius: 999px !important;
overflow: hidden !important;
}}
.gradio-container .pg-loader-fill {{
height: 100% !important;
background: {_BTN} !important;
border-radius: 999px !important;
transition: width 0.35s ease !important;
}}
"""