amplegest / dashboard /theme.py
Viney's picture
feat: multi-provider LLM support, prominent chat, design pass, and new analytics
7880373
Raw
History Blame Contribute Delete
16.5 kB
"""Centralized design tokens and CSS injection for Amplegest."""
from __future__ import annotations
import streamlit as st
# ── Brand ─────────────────────────────────────────────────────────────────────
BRAND_GREEN = "#10b981"
BRAND_GREEN_DARK = "#059669"
# ── Surfaces ──────────────────────────────────────────────────────────────────
BG = "#ffffff"
BG_MUTED = "#fafaf9"
BORDER = "#e5e7eb"
BORDER_STRONG = "#d4d4d4"
# ── Text ──────────────────────────────────────────────────────────────────────
TEXT = "#0a0a0a"
TEXT_MUTED = "#6b7280"
TEXT_FAINT = "#9ca3af"
# ── Semantic palette (light pastel tints) ─────────────────────────────────────
GREEN = "#10b981"
BULL = "#10b981"
BULL_BG = "#ecfdf5"
BULL_BORDER = "#a7f3d0"
RED = "#ef4444"
BEAR = "#ef4444"
BEAR_BG = "#fef2f2"
BEAR_BORDER = "#fecaca"
AMBER = "#f59e0b"
WARN = "#f59e0b"
WARN_BG = "#fffbeb"
WARN_BORDER = "#fde68a"
BLUE = "#3b82f6"
INFO = "#3b82f6"
INFO_BG = "#eff6ff"
INFO_BORDER = "#bfdbfe"
GRAY = "#6b7280"
PURPLE = "#8b5cf6"
AI_BG = "#faf5ff"
AI_BORDER = "#ddd6fe"
AI_COLOR = "#7c3aed"
AI_BADGE_BG = "#ede9fe"
IMPACT_HIGH_BG = "#0f172a" # slate-900
IMPACT_HIGH_TEXT = "#ffffff"
IMPACT_MED_BG = "#64748b" # slate-500
IMPACT_MED_TEXT = "#ffffff"
IMPACT_LOW_BG = "transparent"
IMPACT_LOW_TEXT = "#94a3b8" # slate-400
IMPACT_LOW_BORDER = "#cbd5e1" # slate-300
# ── Plotly rgba aliases ───────────────────────────────────────────────────────
GREEN_A53 = "rgba(16,185,129,0.53)"
GREEN_A40 = "rgba(16,185,129,0.40)"
RED_A40 = "rgba(239,68,68,0.40)"
PURPLE_A53 = "rgba(139,92,246,0.53)"
BLUE_A53 = "rgba(96,165,250,0.53)"
# ── Radius ────────────────────────────────────────────────────────────────────
RADIUS_CARD = "12px"
RADIUS_BTN = "8px"
RADIUS_CHIP = "6px"
# ── Typographic scale ─────────────────────────────────────────────────────────
FS_PAGE = "1.55rem" # ~25px page/screen title
FS_SECTION = "1.05rem" # ~17px section sub-header
FS_CARD = "0.95rem" # ~15px card headline (semibold)
FS_BODY = "0.9rem" # ~14.5px body text
FS_META = "0.75rem" # ~12px meta/attribution line
FS_EYEBROW = "0.6rem" # uppercase micro-label (was scattered 0.56–0.72rem)
FS_SECTION_LABEL = "0.78rem"
# ── Spacing scale ─────────────────────────────────────────────────────────────
SPACE_1 = "4px"
SPACE_2 = "8px"
SPACE_3 = "12px"
SPACE_4 = "16px"
SPACE_5 = "20px"
SPACE_6 = "28px" # intentionally 8px jump β€” used for section-break vertical rhythm
# NAV_GROUPS now lives in dashboard.nav β€” imported from there in app.py.
# Kept as a re-export for any legacy callers.
def _lazy_nav_groups(): # noqa: F401 β€” accessed via dashboard.nav.NAV_GROUPS
from dashboard.nav import NAV_GROUPS # noqa: F401
return NAV_GROUPS
# ── Logo SVG β€” geometric P in emerald rounded square ─────────────────────────
LOGO_SVG = """<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="9" fill="#10b981"/>
<path d="M12 26V11H19.5C20.9 11 22.1 11.5 23.1 12.5C24.1 13.5 24.6 14.7 24.6 16.2C24.6 17.7 24.1 18.9 23.1 19.9C22.1 20.9 20.9 21.4 19.5 21.4H15.5V26H12Z" fill="white"/>
<path d="M15.5 18.2H19C19.8 18.2 20.4 17.9 20.9 17.4C21.4 16.9 21.6 16.2 21.6 15.4C21.6 14.6 21.4 14 20.9 13.5C20.4 13 19.8 12.7 19 12.7H15.5V18.2Z" fill="#10b981"/>
</svg>"""
def inject_global_css() -> None:
"""Inject Inter font + global Amplegest styles. Call once from app.py."""
st.html(
"""
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
/* ── Typography ─────────────────────────────────────────────────────── */
html, body, .stApp, .main, [data-testid="stAppViewContainer"],
[data-testid="stSidebar"], button, input, select, textarea {
font-family: Inter, ui-sans-serif, -apple-system, "Segoe UI", sans-serif !important;
}
html, body, .stApp, .main, [data-testid="stAppViewContainer"],
[data-testid="stSidebar"] {
line-height: 1.6 !important;
}
/* ── Hide default Streamlit chrome ──────────────────────────────────── */
#MainMenu { visibility: hidden; }
header[data-testid="stHeader"] { background: transparent !important; }
footer { visibility: hidden; }
/* ── Canvas background ──────────────────────────────────────────────────── */
.stApp, [data-testid="stAppViewContainer"] {
background: #fafaf9 !important;
}
/* ── Sidebar ────────────────────────────────────────────────────────── */
[data-testid="stSidebar"] {
background-color: #ffffff !important;
border-right: 1px solid #e5e7eb !important;
}
/* ── Logo row (custom div) ──────────────────────────────────────────── */
.primer-logo-row {
display: flex;
align-items: center;
gap: 10px;
padding: 20px 16px 16px;
border-bottom: 1px solid #e5e7eb;
margin-bottom: 12px;
}
.primer-logo-wordmark {
font-size: 1rem;
font-weight: 700;
color: #0a0a0a;
letter-spacing: -0.02em;
line-height: 1.2;
}
.primer-logo-tagline {
font-size: 0.65rem;
color: #9ca3af;
font-weight: 400;
margin-top: 1px;
}
/* ── Text inputs ────────────────────────────────────────────────────── */
[data-testid="stTextInput"] input {
border-radius: 8px !important;
border: 1px solid #e5e7eb !important;
font-size: 0.9rem !important;
background: #ffffff !important;
color: #0a0a0a !important;
}
[data-testid="stTextInput"] input:focus {
border-color: #10b981 !important;
box-shadow: 0 0 0 2px rgba(16,185,129,0.12) !important;
}
/* ── Primary button β€” dark, not green ──────────────────────────────── */
[data-testid="stBaseButton-primary"] {
background: #0a0a0a !important;
color: #ffffff !important;
border-radius: 8px !important;
border: none !important;
font-weight: 500 !important;
font-size: 0.875rem !important;
letter-spacing: 0.01em !important;
}
[data-testid="stBaseButton-primary"]:hover {
background: #1a1a1a !important;
border: none !important;
}
/* ── Secondary / default buttons ────────────────────────────────────── */
[data-testid="stBaseButton-secondary"] {
border-radius: 8px !important;
border: 1px solid #e5e7eb !important;
font-weight: 500 !important;
color: #0a0a0a !important;
}
/* ── Download button ────────────────────────────────────────────────── */
[data-testid="stDownloadButton"] button {
border-radius: 8px !important;
border: 1px solid #e5e7eb !important;
font-weight: 500 !important;
color: #0a0a0a !important;
}
/* ── Bordered containers ────────────────────────────────────────────── */
[data-testid="stVerticalBlockBorderWrapper"] {
border-radius: 12px !important;
border: 1px solid #e5e7eb !important;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04) !important;
background: #ffffff !important;
padding: 20px 24px !important;
}
/* ── Custom card shadow class ───────────────────────────────────────── */
.primer-card {
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
}
/* ── Section cards (HTML-rendered sections) ─────────────────────────────── */
.primer-section-card {
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 22px 26px;
margin: 0 0 18px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
}
/* ── Dividers ───────────────────────────────────────────────────────── */
hr { border-color: #e5e7eb !important; }
/* ── Status box ─────────────────────────────────────────────────────── */
[data-testid="stStatus"] {
border-radius: 8px !important;
border: 1px solid #e5e7eb !important;
}
/* ── Expanders ──────────────────────────────────────────────────────── */
[data-testid="stExpander"] {
border: 1px solid #e5e7eb !important;
border-radius: 8px !important;
box-shadow: none !important;
}
/* ── Inner tabs (e.g. Numbers chart tabs) ───────────────────────────── */
[data-testid="stTabs"] [role="tab"] {
font-size: 0.82rem !important;
font-weight: 500 !important;
color: #6b7280 !important;
}
[data-testid="stTabs"] [role="tab"][aria-selected="true"] {
color: #0a0a0a !important;
}
[data-testid="stTabs"] [role="tablist"] {
border-bottom: 1px solid #e5e7eb !important;
}
/* ── Captions ───────────────────────────────────────────────────────── */
.stCaption { color: #9ca3af !important; font-size: 0.78rem !important; }
/* ── Alerts / info boxes ────────────────────────────────────────────── */
[data-testid="stAlert"] {
border-radius: 8px !important;
}
/* ── Sidebar nav group expanders (deep-dive accordion) ──────────────── */
[data-testid="stSidebar"] [data-testid="stExpander"] {
border: none !important;
border-top: 1px solid #f0f0ef !important;
border-radius: 0 !important;
box-shadow: none !important;
background: transparent !important;
margin-top: 2px !important;
}
[data-testid="stSidebar"] [data-testid="stExpander"] summary {
font-size: 0.6rem !important;
font-weight: 700 !important;
text-transform: uppercase !important;
letter-spacing: 0.1em !important;
color: #9ca3af !important;
padding: 6px 2px !important;
}
[data-testid="stSidebar"] [data-testid="stExpander"] summary:hover {
color: #6b7280 !important;
}
[data-testid="stSidebar"] [data-testid="stExpander"] summary svg {
width: 12px !important;
height: 12px !important;
color: #9ca3af !important;
}
/* ── Content max-width ──────────────────────────────────────────────── */
.main .block-container {
max-width: 1280px !important;
padding-top: 24px !important;
}
/* ── Prose width (text blocks) ─────────────────────────────────────────── */
.primer-prose {
max-width: 700px;
}
/* ── Sticky summary bar ─────────────────────────────────────────────────── */
.primer-sticky-bar {
position: sticky;
top: 0;
z-index: 50;
background: rgba(250, 250, 249, 0.92);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
border-bottom: 1px solid #e5e7eb;
padding: 10px 0;
margin: 0 0 16px;
}
/* ── Chat messages ──────────────────────────────────────────────────── */
[data-testid="stChatMessage"] {
border-radius: 10px !important;
border: 1px solid #e5e7eb !important;
background: #ffffff !important;
padding: 12px 14px !important;
margin-bottom: 8px !important;
box-shadow: 0 1px 2px rgba(0,0,0,0.04) !important;
}
[data-testid="stChatMessage"][data-message-author-role="user"] {
background: #ffffff !important;
border-color: #e5e7eb !important;
}
[data-testid="stChatMessage"][data-message-author-role="assistant"] {
background: #faf5ff !important;
border-color: #ddd6fe !important;
}
/* ── Chat input ─────────────────────────────────────────────────────── */
[data-testid="stChatInput"] {
border-radius: 10px !important;
border: 1px solid #e5e7eb !important;
box-shadow: 0 1px 3px rgba(0,0,0,0.06) !important;
}
[data-testid="stChatInput"]:focus-within {
border-color: #10b981 !important;
box-shadow: 0 0 0 2px rgba(16,185,129,0.12) !important;
}
[data-testid="stChatInput"] textarea {
font-family: Inter, ui-sans-serif, -apple-system, "Segoe UI", sans-serif !important;
font-size: 0.9rem !important;
color: #0a0a0a !important;
}
</style>
""",
)