"""Shared Streamlit UI components — disclaimer banner, KPI cards, tier pill, and a one-shot mobile-responsive CSS injection that every page picks up via ``disclaimer_banner()``. """ from __future__ import annotations import streamlit as st from .theme import TIER_COLORS _DISCLAIMER_TEXT = ( "**Not investment advice.** This is a technical study and analysis " "intended for discussion. See the [Disclaimer](Disclaimer) page." ) # Tightens layout, shrinks heavy chart heights, compacts KPI metrics, and # makes wide DataFrames horizontally scrollable on narrow viewports. Streamlit # columns already stack on narrow viewports — this CSS just polishes the # resulting vertical layout so it doesn't feel like a desktop site mashed # onto a phone. _MOBILE_CSS = """ """ def _inject_mobile_css() -> None: """Inject the responsive CSS once per session run. Streamlit deduplicates identical st.markdown calls implicitly across the same page render, so this is safe to call from every page.""" st.markdown(_MOBILE_CSS, unsafe_allow_html=True) def disclaimer_banner() -> None: _inject_mobile_css() st.info(_DISCLAIMER_TEXT, icon=":material/info:") def page_header(title: str, subtitle: str | None = None, *, eyebrow: str | None = None) -> None: """Heavyweight typographic header — sets a forensic, editorial tone.""" _inject_mobile_css() if eyebrow: st.markdown( f"
{eyebrow}
", unsafe_allow_html=True, ) st.markdown( f"

{title}

", unsafe_allow_html=True, ) if subtitle: st.markdown( f"
{subtitle}
", unsafe_allow_html=True, ) else: st.markdown("
", unsafe_allow_html=True) def kpi_row(metrics: list[tuple[str, str, str | None]]) -> None: """Row of KPI tiles: list of (label, value, delta-or-None).""" cols = st.columns(len(metrics)) for col, (label, value, delta) in zip(cols, metrics): with col: st.metric(label=label, value=value, delta=delta) def tier_pill(tier: str) -> str: color = TIER_COLORS.get(tier, "#3F4868") return ( f"{tier.upper()}" ) def footer() -> None: st.markdown( "
" "
" "© 2026 Palaniappan Chidambaram. Personal research — not investment advice. " "Disclaimer" "
", unsafe_allow_html=True, ) def empty_state(message: str, hint: str | None = None) -> None: st.markdown( f"
" f"
{message}
" f"{f'
{hint}
' if hint else ''}" f"
", unsafe_allow_html=True, )