sammeeer's picture
genai inclusion
90bfc25
"""
theme.py β€” SchemeImpactNet shared design system
Editorial / policy-brief aesthetic.
Fonts: Fraunces (display) + Source Serif 4 (body) + DM Mono (data/labels)
Palette: warm off-white #FAF9F7, deep stone #1C1917, saffron accent #FB923C
"""
THEME_CSS = """
<style>
@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,600;0,9..144,700;1,9..144,300&family=Source+Serif+4:ital,opsz,wght@0,8..60,300;0,8..60,400;0,8..60,600&family=DM+Mono:wght@400;500&display=swap');
html, body, [class*="css"] {
font-family: 'Source Serif 4', Georgia, serif !important;
}
.stApp {
background-color: #FAF9F7 !important;
}
#MainMenu, footer, header { visibility: hidden; }
.block-container {
padding: 2rem 2.5rem 3rem !important;
max-width: 1320px !important;
}
/* ── Sidebar ── */
[data-testid="stSidebar"] {
background: #1C1917 !important;
border-right: none !important;
}
[data-testid="stSidebarContent"] {
background: #1C1917 !important;
}
/* Nav links generated by st.navigation */
[data-testid="stSidebarNavLink"] {
border-radius: 5px !important;
padding: 0.5rem 1rem !important;
font-family: 'DM Mono', monospace !important;
font-size: 0.7rem !important;
letter-spacing: 0.5px !important;
color: #A8A29E !important;
text-decoration: none !important;
transition: all 0.15s ease !important;
border-left: 2px solid transparent !important;
}
[data-testid="stSidebarNavLink"]:hover {
background: rgba(251,146,60,0.1) !important;
color: #FB923C !important;
border-left-color: rgba(251,146,60,0.4) !important;
}
[data-testid="stSidebarNavLink"][aria-current="page"] {
background: rgba(251,146,60,0.15) !important;
color: #FB923C !important;
border-left-color: #FB923C !important;
}
/* ── Typography ── */
h1, h2, h3 {
font-family: 'Fraunces', serif !important;
color: #1C1917 !important;
}
h1 { font-size: 2.2rem !important; font-weight: 600 !important; line-height: 1.15 !important; }
h2 { font-size: 1.5rem !important; font-weight: 600 !important; }
h3 { font-size: 1.1rem !important; font-weight: 600 !important; }
p { font-family: 'Source Serif 4', serif !important; color: #292524 !important; }
/* ── Metric cards ── */
[data-testid="stMetric"] {
background: #FFFFFF !important;
border: 1px solid #E7E5E4 !important;
border-radius: 8px !important;
padding: 1rem 1.2rem !important;
}
[data-testid="stMetricLabel"] p {
font-family: 'DM Mono', monospace !important;
font-size: 0.62rem !important;
letter-spacing: 2px !important;
text-transform: uppercase !important;
color: #78716C !important;
}
[data-testid="stMetricValue"] {
font-family: 'Fraunces', serif !important;
font-size: 1.85rem !important;
font-weight: 600 !important;
color: #1C1917 !important;
line-height: 1.2 !important;
}
[data-testid="stMetricDelta"] {
font-family: 'DM Mono', monospace !important;
font-size: 0.7rem !important;
}
/* ── Inputs ── */
[data-testid="stSelectbox"] label p,
[data-testid="stSlider"] label p,
[data-testid="stTextInput"] label p,
[data-testid="stMultiSelect"] label p {
font-family: 'DM Mono', monospace !important;
font-size: 0.65rem !important;
letter-spacing: 1.5px !important;
text-transform: uppercase !important;
color: #78716C !important;
}
/* ── Buttons ── */
.stButton > button {
font-family: 'DM Mono', monospace !important;
font-size: 0.7rem !important;
letter-spacing: 1px !important;
text-transform: uppercase !important;
background: #1C1917 !important;
color: #FAF9F7 !important;
border: none !important;
border-radius: 6px !important;
padding: 0.5rem 1.2rem !important;
}
.stButton > button:hover {
background: #FB923C !important;
color: #1C1917 !important;
}
/* ── Dataframes ── */
[data-testid="stDataFrame"] {
border: 1px solid #E7E5E4 !important;
border-radius: 8px !important;
overflow: hidden !important;
}
[data-testid="stDataFrame"] th {
font-family: 'DM Mono', monospace !important;
font-size: 0.65rem !important;
letter-spacing: 1px !important;
text-transform: uppercase !important;
background: #F5F5F4 !important;
color: #57534E !important;
}
/* ── Expander ── */
[data-testid="stExpander"] {
border: 1px solid #E7E5E4 !important;
border-radius: 8px !important;
background: #FFFFFF !important;
}
details summary p {
font-family: 'DM Mono', monospace !important;
font-size: 0.72rem !important;
letter-spacing: 0.5px !important;
color: #57534E !important;
}
/* ── Alerts ── */
[data-testid="stAlert"] {
border-radius: 8px !important;
}
/* ── Caption ── */
[data-testid="stCaptionContainer"] p {
font-family: 'DM Mono', monospace !important;
font-size: 0.63rem !important;
color: #A8A29E !important;
letter-spacing: 0.3px !important;
}
/* ── Divider ── */
hr {
border: none !important;
border-top: 1px solid #E7E5E4 !important;
margin: 1.5rem 0 !important;
}
/* ── Tab strip ── */
[data-testid="stTabs"] [role="tab"] {
font-family: 'DM Mono', monospace !important;
font-size: 0.68rem !important;
letter-spacing: 1px !important;
text-transform: uppercase !important;
}
</style>
"""
# ── Plotly shared layout (light, editorial) ───────────────────────────────────
PLOTLY_LAYOUT = dict(
paper_bgcolor="#FFFFFF",
plot_bgcolor="#FAFAF9",
font=dict(family="DM Mono, monospace", color="#292524", size=10.5),
margin=dict(l=0, r=0, t=44, b=0),
legend=dict(
bgcolor="rgba(255,255,255,0.92)",
bordercolor="#E7E5E4", borderwidth=1,
font=dict(size=10),
),
xaxis=dict(
gridcolor="#F5F5F4", linecolor="#E7E5E4",
tickfont=dict(color="#78716C", size=10),
title_font=dict(color="#57534E", size=11),
zerolinecolor="#E7E5E4",
),
yaxis=dict(
gridcolor="#F5F5F4", linecolor="#E7E5E4",
tickfont=dict(color="#78716C", size=10),
title_font=dict(color="#57534E", size=11),
zerolinecolor="#E7E5E4",
),
)
# ── Colour tokens ─────────────────────────────────────────────────────────────
SAFFRON = "#FB923C" # primary accent
SAFFRON_D = "#EA580C" # darker saffron
SLATE = "#1C1917" # near-black
STONE = "#78716C" # muted label
BORDER = "#E7E5E4"
BG = "#FAF9F7"
WHITE = "#FFFFFF"
GREEN = "#16A34A"
RED = "#DC2626"
AMBER = "#D97706"
BLUE = "#2563EB"
# ── Saffron scale for choropleth / sequential maps ───────────────────────────
SAFFRON_SCALE = [
[0.0, "#FFF7ED"],
[0.25, "#FED7AA"],
[0.5, "#FB923C"],
[0.75, "#EA580C"],
[1.0, "#7C2D12"],
]
# ── Helpers ───────────────────────────────────────────────────────────────────
def inject_theme():
import streamlit as st
st.markdown(THEME_CSS, unsafe_allow_html=True)
def page_header(eyebrow: str, title: str, subtitle: str = ""):
import streamlit as st
sub_html = (
f'<p style="font-family:\'Source Serif 4\',serif; font-size:0.92rem; '
f'color:#78716C; margin:6px 0 0 0; line-height:1.5;">{subtitle}</p>'
if subtitle else ""
)
st.markdown(f"""
<div style="margin-bottom:1.75rem; padding-bottom:1.25rem; border-bottom:2px solid #E7E5E4;">
<p style="font-family:'DM Mono',monospace; font-size:0.58rem; letter-spacing:3.5px;
text-transform:uppercase; color:#FB923C; margin:0 0 7px 0;">{eyebrow}</p>
<h1 style="font-family:'Fraunces',serif; font-size:2.1rem; font-weight:600;
color:#1C1917; margin:0; line-height:1.15;">{title}</h1>
{sub_html}
</div>""", unsafe_allow_html=True)
def section_label(text: str):
import streamlit as st
st.markdown(
f'<p style="font-family:\'DM Mono\',monospace; font-size:0.58rem; '
f'letter-spacing:3px; text-transform:uppercase; color:#A8A29E; '
f'margin:0 0 10px 0; padding-bottom:8px; border-bottom:1px solid #F5F5F4;">'
f'{text}</p>',
unsafe_allow_html=True,
)
def kpi_html(value: str, label: str, color: str = "#1C1917", note: str = "") -> str:
note_html = (
f'<p style="font-family:\'DM Mono\',monospace; font-size:0.62rem; '
f'color:#A8A29E; margin:3px 0 0 0;">{note}</p>'
if note else ""
)
return f"""
<div style="background:#FFFFFF; border:1px solid #E7E5E4; border-radius:8px; padding:1rem 1.25rem;">
<p style="font-family:'DM Mono',monospace; font-size:0.58rem; letter-spacing:2.5px;
text-transform:uppercase; color:#A8A29E; margin:0 0 5px 0;">{label}</p>
<p style="font-family:'Fraunces',serif; font-size:1.9rem; font-weight:600;
color:{color}; line-height:1; margin:0;">{value}</p>
{note_html}
</div>"""
def signal_card_html(value: str, title: str, body: str, accent: str = "#FB923C") -> str:
return f"""
<div style="background:#FFFFFF; border:1px solid #E7E5E4; border-left:3px solid {accent};
border-radius:8px; padding:0.85rem 1rem; margin-bottom:7px;
display:flex; align-items:center; gap:0.9rem;">
<span style="font-family:'Fraunces',serif; font-size:1.55rem; font-weight:600;
color:{accent}; min-width:56px; text-align:right; flex-shrink:0;">{value}</span>
<div>
<p style="font-family:'DM Mono',monospace; font-size:0.6rem; letter-spacing:1.2px;
text-transform:uppercase; color:#57534E; margin:0 0 2px 0;">{title}</p>
<p style="font-family:'Source Serif 4',serif; font-size:0.78rem;
color:#A8A29E; margin:0; line-height:1.4;">{body}</p>
</div>
</div>"""
# NOTE: inject_theme() is now a no-op for page files.
# All CSS is injected once in app.py before st.navigation() runs,
# which means it persists across every page automatically.
def inject_theme():
pass # CSS already injected globally by app.py