Spaces:
Runtime error
Runtime error
| """ | |
| styles.py β Global WCO colour palette, CSS injection, and shared UI helpers | |
| """ | |
| import streamlit as st | |
| # ββ WCO Colour Palette ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| WCO_NAVY = "#003087" | |
| WCO_BLUE = "#0066CC" | |
| WCO_GOLD = "#C8A951" | |
| WCO_GREEN = "#00843D" | |
| WCO_RED = "#C8102E" | |
| WCO_YELLOW = "#F5A800" | |
| WCO_GREY_BG = "#0B1220" | |
| WCO_CARD_BG = "#0F1C35" | |
| WCO_BORDER = "#1E3A6E" | |
| WCO_TEXT = "#D0DCF0" | |
| WCO_MUTED = "#6B85AA" | |
| CHANNEL_COLORS = { | |
| "RED": WCO_RED, | |
| "YELLOW": WCO_YELLOW, | |
| "GREEN": WCO_GREEN, | |
| } | |
| RISK_COLORS = { | |
| "Drugs & Narcotics": "#C8102E", | |
| "Environmental/Plastic Waste": "#00843D", | |
| "Revenue Leakage": "#F5A800", | |
| "IPR Enforcement": "#9B59B6", | |
| "Wildlife Smuggling": "#E67E22", | |
| } | |
| def inject_global_css(): | |
| st.markdown(f""" | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&family=IBM+Plex+Sans:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap'); | |
| /* ββ Root reset ββ */ | |
| html, body, [data-testid="stAppViewContainer"] {{ | |
| background-color: {WCO_GREY_BG} !important; | |
| color: {WCO_TEXT} !important; | |
| font-family: 'IBM Plex Sans', sans-serif; | |
| }} | |
| [data-testid="stSidebar"] {{ | |
| background-color: #070E1C !important; | |
| border-right: 1px solid {WCO_BORDER}; | |
| }} | |
| [data-testid="stSidebar"] * {{ color: {WCO_TEXT} !important; }} | |
| /* ββ Header bar ββ */ | |
| .wco-header {{ | |
| background: linear-gradient(135deg, {WCO_NAVY} 0%, #001550 100%); | |
| border-bottom: 3px solid {WCO_GOLD}; | |
| padding: 18px 28px; | |
| border-radius: 12px; | |
| margin-bottom: 20px; | |
| display: flex; | |
| align-items: center; | |
| gap: 16px; | |
| }} | |
| .wco-header h1 {{ | |
| font-family: 'Playfair Display', serif; | |
| color: {WCO_GOLD}; | |
| font-size: 26px; | |
| margin: 0; | |
| line-height: 1.2; | |
| }} | |
| .wco-header p {{ | |
| color: #8BAAD4; | |
| font-size: 12px; | |
| margin: 4px 0 0; | |
| font-family: 'IBM Plex Mono', monospace; | |
| letter-spacing: 0.06em; | |
| }} | |
| /* ββ Cards ββ */ | |
| .wco-card {{ | |
| background: {WCO_CARD_BG}; | |
| border: 1px solid {WCO_BORDER}; | |
| border-radius: 12px; | |
| padding: 22px 24px; | |
| margin-bottom: 16px; | |
| }} | |
| .wco-card-gold {{ | |
| background: {WCO_CARD_BG}; | |
| border: 1px solid {WCO_GOLD}; | |
| border-radius: 12px; | |
| padding: 22px 24px; | |
| margin-bottom: 16px; | |
| }} | |
| .wco-card h3 {{ | |
| font-family: 'Playfair Display', serif; | |
| color: {WCO_GOLD}; | |
| font-size: 16px; | |
| margin: 0 0 14px; | |
| padding-bottom: 10px; | |
| border-bottom: 1px solid {WCO_BORDER}; | |
| }} | |
| /* ββ Section title ββ */ | |
| .section-title {{ | |
| font-family: 'Playfair Display', serif; | |
| color: {WCO_GOLD}; | |
| font-size: 20px; | |
| font-weight: 700; | |
| margin: 8px 0 16px; | |
| letter-spacing: 0.01em; | |
| }} | |
| /* ββ KPI tiles ββ */ | |
| .kpi-row {{ display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 18px; }} | |
| .kpi-tile {{ | |
| flex: 1; | |
| min-width: 140px; | |
| background: {WCO_CARD_BG}; | |
| border: 1px solid {WCO_BORDER}; | |
| border-radius: 10px; | |
| padding: 16px 18px; | |
| text-align: center; | |
| }} | |
| .kpi-tile .val {{ | |
| font-family: 'Playfair Display', serif; | |
| font-size: 32px; | |
| font-weight: 700; | |
| line-height: 1; | |
| }} | |
| .kpi-tile .lbl {{ | |
| font-size: 11px; | |
| color: {WCO_MUTED}; | |
| margin-top: 6px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| }} | |
| /* ββ Channel badges ββ */ | |
| .badge-red {{ background:#3A0010; color:#FF4466; border:1px solid #C8102E; border-radius:6px; padding:3px 10px; font-size:12px; font-weight:600; }} | |
| .badge-yellow {{ background:#2A1900; color:#FFD700; border:1px solid #F5A800; border-radius:6px; padding:3px 10px; font-size:12px; font-weight:600; }} | |
| .badge-green {{ background:#001A09; color:#44CC88; border:1px solid #00843D; border-radius:6px; padding:3px 10px; font-size:12px; font-weight:600; }} | |
| .badge-gold {{ background:#1C1400; color:{WCO_GOLD}; border:1px solid {WCO_GOLD}; border-radius:6px; padding:3px 10px; font-size:12px; font-weight:600; }} | |
| /* ββ Tables ββ */ | |
| .wco-table {{ width:100%; border-collapse:collapse; font-size:13px; }} | |
| .wco-table th {{ | |
| background:{WCO_NAVY}; | |
| color:{WCO_GOLD}; | |
| font-family:'Playfair Display',serif; | |
| padding:10px 14px; | |
| text-align:left; | |
| border-bottom:2px solid {WCO_GOLD}; | |
| font-size:12px; | |
| letter-spacing:0.05em; | |
| }} | |
| .wco-table td {{ | |
| padding:9px 14px; | |
| border-bottom:1px solid {WCO_BORDER}; | |
| color:{WCO_TEXT}; | |
| vertical-align:top; | |
| }} | |
| .wco-table tr:hover td {{ background:rgba(0,48,135,0.25); }} | |
| /* ββ Progress bars ββ */ | |
| .prog-bar-wrap {{ background:#111D30; border-radius:6px; height:10px; margin:4px 0; overflow:hidden; }} | |
| .prog-bar-fill {{ height:100%; border-radius:6px; transition:width 0.6s ease; }} | |
| /* ββ Alerts ββ */ | |
| .alert-gold {{ | |
| background:rgba(200,169,81,0.1); | |
| border-left:4px solid {WCO_GOLD}; | |
| border-radius:0 8px 8px 0; | |
| padding:12px 16px; | |
| color:#D4B96A; | |
| font-size:13px; | |
| margin:10px 0; | |
| }} | |
| .alert-blue {{ | |
| background:rgba(0,102,204,0.1); | |
| border-left:4px solid {WCO_BLUE}; | |
| border-radius:0 8px 8px 0; | |
| padding:12px 16px; | |
| color:#66AAFF; | |
| font-size:13px; | |
| margin:10px 0; | |
| }} | |
| /* ββ Streamlit widget tweaks ββ */ | |
| .stSlider > div > div > div {{ background:{WCO_BORDER} !important; }} | |
| .stSlider [data-baseweb="slider"] [role="slider"] {{ background:{WCO_GOLD} !important; border-color:{WCO_GOLD} !important; }} | |
| .stButton > button {{ | |
| background:linear-gradient(135deg,{WCO_NAVY},{WCO_BLUE}); | |
| color:{WCO_GOLD}; | |
| border:1px solid {WCO_GOLD}; | |
| border-radius:8px; | |
| font-family:'IBM Plex Sans',sans-serif; | |
| font-weight:600; | |
| letter-spacing:0.04em; | |
| padding:8px 24px; | |
| transition:all 0.2s; | |
| }} | |
| .stButton > button:hover {{ | |
| background:linear-gradient(135deg,{WCO_BLUE},{WCO_NAVY}); | |
| border-color:#FFD700; | |
| color:#FFD700; | |
| transform:translateY(-1px); | |
| box-shadow:0 4px 16px rgba(200,169,81,0.25); | |
| }} | |
| div[data-testid="stMetricValue"] {{ | |
| font-family:'Playfair Display',serif !important; | |
| color:{WCO_GOLD} !important; | |
| }} | |
| [data-testid="stMetricLabel"] {{ color:{WCO_MUTED} !important; font-size:11px !important; text-transform:uppercase !important; letter-spacing:0.07em !important; }} | |
| [data-testid="stExpander"] {{ | |
| background:{WCO_CARD_BG} !important; | |
| border:1px solid {WCO_BORDER} !important; | |
| border-radius:10px !important; | |
| }} | |
| div[data-testid="stDataFrame"] {{ border-radius:10px; overflow:hidden; }} | |
| .stTabs [data-baseweb="tab-list"] {{ background:{WCO_CARD_BG}; border-radius:10px 10px 0 0; }} | |
| .stTabs [data-baseweb="tab"] {{ color:{WCO_MUTED}; font-family:'IBM Plex Sans',sans-serif; }} | |
| .stTabs [aria-selected="true"] {{ color:{WCO_GOLD} !important; border-bottom:2px solid {WCO_GOLD} !important; }} | |
| hr {{ border-color:{WCO_BORDER} !important; }} | |
| </style> | |
| """, unsafe_allow_html=True) | |
| def page_header(icon: str, title: str, subtitle: str): | |
| st.markdown(f""" | |
| <div class="wco-header"> | |
| <div style="font-size:40px">{icon}</div> | |
| <div> | |
| <h1>{title}</h1> | |
| <p>{subtitle}</p> | |
| </div> | |
| <div style="margin-left:auto;text-align:right;"> | |
| <div style="color:#C8A951;font-family:'IBM Plex Mono',monospace;font-size:11px;">π WCO ACCREDITED</div> | |
| <div style="color:#6B85AA;font-size:10px;margin-top:2px;">No-CelH Self-Learning RMS v2.0</div> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| def metric_row(metrics: list): | |
| """metrics = [(value, label, color), ...]""" | |
| cols_html = "" | |
| for val, lbl, col in metrics: | |
| cols_html += f""" | |
| <div class="kpi-tile"> | |
| <div class="val" style="color:{col}">{val}</div> | |
| <div class="lbl">{lbl}</div> | |
| </div>""" | |
| st.markdown(f'<div class="kpi-row">{cols_html}</div>', unsafe_allow_html=True) | |