""" F1 Predictor 2026 - UI Components All components. Colors: skyblue, darkblue, darkgray, black, red, green. NO WHITE TEXT ANYWHERE. """ import streamlit as st def hero(title: str, subtitle: str, kpis: list[dict] = None) -> None: """Hero section with accent bar and KPI cards.""" kpis = kpis or [] kpi_html = "".join( f"""
{k.get('value','-')}
{k.get('label','')}
""" for k in kpis ) st.markdown( f"""

{title}

{subtitle}

{kpi_html}
""", unsafe_allow_html=True, ) def section_title(text: str) -> None: """Section title with skyblue accent bar.""" st.markdown( f"""
{text}
""", unsafe_allow_html=True, ) def divider() -> None: """Styled horizontal divider.""" st.markdown( '
', unsafe_allow_html=True, ) def card(title: str, content: str, icon: str = "📊") -> None: """Card with icon header.""" st.markdown( f"""
{icon} {title}
{content}
""", unsafe_allow_html=True, ) def metric_card(label: str, value: str, delta: str = "", accent: str = "#3B82F6") -> None: """Individual metric card.""" delta_html = f'
{delta}
' if delta else "" st.markdown( f"""
{value}
{label}
{delta_html}
""", unsafe_allow_html=True, ) def metrics_grid(metrics: list[dict]) -> None: """Grid of metric cards.""" grid = "".join( f"""
{m.get('value','-')}
{m.get('label','')}
""" for m in metrics ) st.markdown( f"""
{grid}
""", unsafe_allow_html=True, ) def data_table(headers: list[str], rows: list[list]) -> None: """Custom styled data table.""" th = "".join(f"{h}" for h in headers) trs = "" for row in rows: cells = "".join(f"{c}" for c in row) trs += f"{cells}" st.markdown( f"""{th}{trs}
""", unsafe_allow_html=True, ) def stat_row(label: str, value: str, highlight: bool = False) -> None: """Label-value row.""" vcolor = "#3B82F6" if highlight else "#0F172A" st.markdown( f"""
{label} {value}
""", unsafe_allow_html=True, ) def progress_bar(value: float, max_val: float = 100.0, label: str = "") -> None: """Progress bar with label.""" pct = min(100.0, max(0.0, (value / max_val) * 100)) lbl = f'
{label}
' if label else "" st.markdown( f"""{lbl}
0%{pct:.0f}%
""", unsafe_allow_html=True, ) def podium(items: list[dict], max_items: int = 3) -> None: """Podium list with gold/silver/bronze styling.""" borders = ["#FBBF24", "#9CA3AF", "#D97706"] bgs = ["rgba(251,191,36,0.1)", "rgba(156,163,175,0.1)", "rgba(217,119,6,0.1)"] medals = ["🥇", "🥈", "🥉"] html = "" for i, item in enumerate(items[:max_items]): border = borders[i] if i < 3 else "#E2E8F0" bg = bgs[i] if i < 3 else "#F1F5F9" medal = medals[i] if i < 3 else f"P{i+1}" html += f"""
{medal}
{item.get('name','Unknown')}
{item.get('team','')}
{item.get('stat','-')}
""" st.markdown(f'
{html}
', unsafe_allow_html=True) def alert(message: str, alert_type: str = "info") -> None: """Alert box: info, success, warning, error.""" colors = { "success": ("#10B981", "rgba(16,185,129,0.1)"), "warning": ("#F59E0B", "rgba(245,158,11,0.1)"), "error": ("#EF4444", "rgba(239,68,68,0.1)"), "info": ("#06B6D4", "rgba(6,182,212,0.1)"), } border, bg = colors.get(alert_type, colors["info"]) st.markdown( f"""
{message}
""", unsafe_allow_html=True, ) def weather(condition: str = "dry") -> None: """Weather widget.""" data = { "dry": {"icon": "☀️", "temp": "24°C", "desc": "Sunny & Clear"}, "mixed": {"icon": "🌥️", "temp": "18°C", "desc": "Partly Cloudy"}, "wet": {"icon": "🌧️", "temp": "14°C", "desc": "Rain Expected"}, }.get(condition, {"icon": "☀️", "temp": "24°C", "desc": "Sunny & Clear"}) st.markdown( f"""
{data['icon']}
{data['temp']}
{data['desc']}
""", unsafe_allow_html=True, ) def team_badge(team_name: str, color: str) -> None: """Team badge with color dot.""" st.markdown( f""" {team_name}""", unsafe_allow_html=True, ) def empty_state(icon: str, title: str, description: str = "") -> None: """Empty state placeholder.""" desc = f'
{description}
' if description else "" st.markdown( f"""
{icon}
{title}
{desc}
""", unsafe_allow_html=True, ) def session_tabs(active: str = "race") -> str: """Friday/Saturday/Sunday session selector. Returns active key.""" sessions = { "practice": {"icon": "🏁", "day": "Friday Practice", "desc": "FP1 · FP2 · FP3"}, "qualifying": {"icon": "⚡", "day": "Saturday Qualifying", "desc": "Q1 · Q2 · Q3"}, "race": {"icon": "🏆", "day": "Sunday Grand Prix", "desc": "Full Race"}, } tabs = "" for key, data in sessions.items(): is_active = key == active bg = "linear-gradient(135deg,#3B82F6,#2563EB)" if is_active else "#F1F5F9" border = "#3B82F6" if is_active else "#E2E8F0" title_color = "#F8FAFC" if is_active else "#0F172A" desc_color = "rgba(248,250,252,0.8)" if is_active else "#94A3B8" shadow = "0 4px 12px rgba(59,130,246,0.25)" if is_active else "none" tabs += f"""
{data['icon']}
{data['day']}
{data['desc']}
""" st.markdown( f"""
{tabs}
""", unsafe_allow_html=True, ) return active def navbar(active_page: str = "dashboard") -> None: """Top navigation bar.""" pages = { "dashboard": "Dashboard", "h2h": "H2H", "constructor": "Constructor", "championship": "Championship", "accuracy": "Accuracy", "download": "Download", } links = "" for key, name in pages.items(): is_active = key == active_page bg = "#1E3A8A" if is_active else "transparent" color = "#F8FAFC" if is_active else "#475569" hover = "#F1F5F9" if not is_active else "#1E3A8A" links += f"""""" st.markdown( f"""
F1 Predictor 2026
{links}
""", unsafe_allow_html=True, )