finese_data_2 / theme.py
Jack-ki1's picture
Upload 61 files
0a7076f verified
Raw
History Blame Contribute Delete
11 kB
"""
Premium modern theme system for Streamlit.
Goals:
- Strong visual identity (gradient + subtle glow + glass effects)
- Light/dark palettes via CSS variables
- Backward compatible with existing app CSS class constants:
SECTION_HEADER_CLASS, SECTION_SUBHEADER_CLASS, CARD_CLASS, CARD_HEADER_CLASS, CARD_BODY_CLASS
- Best-effort styling of common Streamlit widgets and containers
"""
from __future__ import annotations
from typing import Dict, Literal
# ----------------------------
# Backward-compatible constants
# ----------------------------
SECTION_HEADER_CLASS = "section-h"
SECTION_SUBHEADER_CLASS = "section-sub"
CARD_CLASS = "card"
CARD_HEADER_CLASS = "card-header"
CARD_BODY_CLASS = "card-body"
ThemeMode = Literal["light", "dark"]
LIGHT_THEME: Dict[str, str] = {
"bg0": "#F7FBFF",
"bg1": "#EEF6FF",
"panel": "#FFFFFF",
"panel2": "#F1F5F9",
"text": "#0B1220",
"muted": "#475569",
"muted2": "#64748B",
"border": "rgba(148, 163, 184, 0.35)",
"border2": "rgba(148, 163, 184, 0.22)",
"shadow": "0 18px 60px rgba(2, 6, 23, 0.08)",
"shadow2": "0 8px 28px rgba(2, 6, 23, 0.06)",
"brand": "#0ea5e9",
"brand2": "#3b82f6",
"brandSoft": "rgba(14,165,233,0.20)",
"ring": "rgba(59,130,246,0.35)",
"cardBorder": "rgba(14,165,233,0.18)",
"danger": "#ef4444",
"success": "#22c55e",
"warning": "#f59e0b",
}
DARK_THEME: Dict[str, str] = {
"bg0": "#070B14",
"bg1": "#0B1630",
"panel": "rgba(16, 24, 40, 0.70)",
"panel2": "rgba(17, 30, 58, 0.65)",
"text": "#E7EEF8",
"muted": "#93A3B8",
"muted2": "#7788A5",
"border": "rgba(148, 163, 184, 0.22)",
"border2": "rgba(148, 163, 184, 0.16)",
"shadow": "0 24px 80px rgba(0, 0, 0, 0.45)",
"shadow2": "0 14px 40px rgba(0, 0, 0, 0.30)",
"brand": "#38BDF8",
"brand2": "#60A5FA",
"brandSoft": "rgba(56,189,248,0.20)",
"ring": "rgba(96,165,250,0.35)",
"cardBorder": "rgba(56,189,248,0.16)",
"danger": "#fb7185",
"success": "#34d399",
"warning": "#fbbf24",
}
def _get_mode(default: ThemeMode = "light") -> ThemeMode:
try:
import streamlit as st
mode = getattr(st.session_state, "theme", None)
if mode in ("light", "dark"):
return mode
except Exception:
pass
return default
def inject_css() -> None:
"""Inject premium CSS. Called once per page run from app.py."""
import streamlit as st
mode = _get_mode("light")
t = DARK_THEME if mode == "dark" else LIGHT_THEME
# Avoid CSS features that may not work everywhere (e.g., color-mix).
st.markdown(
f"""
<style>
:root {{
--bg0: {t["bg0"]};
--bg1: {t["bg1"]};
--panel: {t["panel"]};
--panel2: {t["panel2"]};
--text: {t["text"]};
--muted: {t["muted"]};
--muted2: {t["muted2"]};
--border: {t["border"]};
--border2: {t["border2"]};
--shadow: {t["shadow"]};
--shadow2: {t["shadow2"]};
--brand: {t["brand"]};
--brand2: {t["brand2"]};
--brandSoft: {t["brandSoft"]};
--ring: {t["ring"]};
--cardBorder: {t["cardBorder"]};
--danger: {t["danger"]};
--success: {t["success"]};
--warning: {t["warning"]};
}}
/* Base canvas */
html, body {{
background: radial-gradient(1200px 900px at 10% 0%, rgba(59,130,246,0.18), transparent 55%),
radial-gradient(900px 700px at 90% 20%, rgba(14,165,233,0.16), transparent 50%),
linear-gradient(180deg, var(--bg0), var(--bg1)) !important;
color: var(--text) !important;
}}
#root {{
color: var(--text);
}}
footer {{
background: transparent !important;
}}
/* Subtle “noise” overlay using gradients (no images) */
.bb-noise {{
position: fixed;
inset: 0;
pointer-events: none;
opacity: 0.05;
background-image:
radial-gradient(circle at 20% 30%, rgba(255,255,255,0.35) 0 1px, transparent 2px),
radial-gradient(circle at 80% 60%, rgba(255,255,255,0.28) 0 1px, transparent 2px);
background-size: 180px 180px, 220px 220px;
mix-blend-mode: overlay;
}}
/* Typography consistency */
body {{
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji" !important;
}}
/* ---- Existing header primitives ---- */
.{SECTION_HEADER_CLASS} {{
font-size: 1.35rem;
font-weight: 900;
margin: 12px 0 8px 0;
letter-spacing: -0.02em;
color: var(--text);
position: relative;
}}
.{SECTION_HEADER_CLASS}::after {{
content: "";
position: absolute;
left: 0;
bottom: -8px;
width: 92px;
height: 3px;
border-radius: 999px;
background: linear-gradient(90deg, var(--brand), var(--brand2));
box-shadow: 0 0 18px rgba(59,130,246,0.25);
}}
.{SECTION_SUBHEADER_CLASS} {{
font-size: 0.98rem;
font-weight: 750;
margin: 0 0 12px 0;
color: var(--muted);
letter-spacing: -0.01em;
}}
/* ---- Card primitives (existing class names) ---- */
.{CARD_CLASS} {{
background: linear-gradient(180deg, rgba(255,255,255,0.65), rgba(255,255,255,0.40)) !important;
border: 1px solid var(--cardBorder) !important;
border-radius: 18px !important;
box-shadow: var(--shadow2) !important;
overflow: hidden;
transform: translateZ(0);
}}
/* Dark mode glass tweak */
.stApp .{CARD_CLASS} {{
{"background: linear-gradient(180deg, rgba(16,24,40,0.65), rgba(16,24,40,0.35)) !important;" if mode == "dark" else ""}
transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease;
}}
.{CARD_CLASS}:hover {{
border-color: rgba(59,130,246,0.35) !important;
box-shadow: var(--shadow) !important;
transform: translateY(-1px);
}}
.{CARD_HEADER_CLASS} {{
padding: 12px 16px;
background: linear-gradient(135deg, rgba(14,165,233,0.18), rgba(59,130,246,0.10)) !important;
background: linear-gradient(135deg, var(--brandSoft), rgba(59,130,246,0.08)) !important;
border-bottom: 1px solid var(--border) !important;
font-weight: 900;
color: var(--text);
}}
.{CARD_BODY_CLASS} {{
padding: 14px 16px;
}}
/* Make tabs content breathe */
section [data-testid="stTabsContent"] {{
padding-top: 10px;
padding-bottom: 14px;
}}
/* ---- Widget styling (best-effort) ---- */
/* Inputs */
input, textarea, select {{
border-radius: 12px !important;
border: 1px solid var(--border) !important;
background: rgba(255,255,255,0.65);
color: var(--text) !important;
}}
/* Dark mode override for input bg */
.stApp input, .stApp textarea, .stApp select {{
{"background: rgba(15,26,46,0.55) !important;" if mode == "dark" else ""}
}}
input:focus, textarea:focus, select:focus {{
outline: none !important;
box-shadow: 0 0 0 3px var(--ring) !important;
border-color: rgba(59,130,246,0.65) !important;
}}
/* Buttons: primary / secondary patterns */
button {{
border-radius: 12px !important;
}}
/* Streamlit base button inner */
div[data-baseweb="button"] {{
border-radius: 12px !important;
}}
/* Hover micro-interaction */
button:hover {{
box-shadow: 0 10px 24px rgba(59,130,246,0.18) !important;
}}
/* Expander headers */
details > summary {{
border-radius: 12px;
font-weight: 800;
}}
/* Sidebar */
section[data-testid="stSidebar"] {{
border-right: 1px solid var(--border) !important;
}}
section[data-testid="stSidebar"] {{
background: rgba(255,255,255,0.03) !important;
}}
.stApp section[data-testid="stSidebar"] {{
{"background: rgba(10,16,34,0.45) !important;" if mode == "dark" else ""}
}}
section[data-testid="stSidebar"] * {{
color: var(--text) !important;
}}
/* Metric labels/values */
div[data-testid="stMetricLabel"] {{
color: var(--muted) !important;
font-weight: 800 !important;
}}
div[data-testid="stMetricValue"] {{
color: var(--text) !important;
font-weight: 950 !important;
letter-spacing: -0.02em;
}}
/* HR */
hr {{
border-top-color: var(--border) !important;
}}
/* Tables in app */
table {{
color: var(--text) !important;
}}
/* Scrollbar (best-effort) */
::-webkit-scrollbar {{
width: 10px;
height: 10px;
}}
::-webkit-scrollbar-track {{
background: rgba(148,163,184,0.10);
border-radius: 999px;
}}
::-webkit-scrollbar-thumb {{
background: rgba(59,130,246,0.35);
border-radius: 999px;
border: 2px solid rgba(0,0,0,0);
background-clip: padding-box;
}}
::-webkit-scrollbar-thumb:hover {{
background: rgba(59,130,246,0.55);
border: 2px solid rgba(0,0,0,0);
background-clip: padding-box;
}}
/* Plotly polish */
.js-plotly-plot .plotly {{
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji" !important;
}}
.js-plotly-plot .xtick text,
.js-plotly-plot .ytick text {{
fill: var(--muted) !important;
}}
/* Radio / checkbox accent (best-effort) */
.stRadio label {{
color: var(--text) !important;
}}
/* Reduce layout cramping */
.stTabs {{
padding-top: 2px;
}}
</style>
<div class="bb-noise"></div>
""",
unsafe_allow_html=True,
)