"""Custom Gradio theme for Portfolio Intelligence Platform. Professional financial application theme with: - Trust-inspiring blue/grey colour palette - Clean typography with Inter font - Subtle shadows and borders - Optimised for data-heavy interfaces """ import gradio as gr from gradio.themes.base import Base from gradio.themes.utils import colors, fonts, sizes class FinancialTheme(Base): """Professional financial application theme for Gradio.""" def __init__(self, **kwargs): """Initialise financial theme with professional colours and typography.""" super().__init__( # Trust & stability colours primary_hue=colors.blue, # Professional blue secondary_hue=colors.slate, # Neutral greys neutral_hue=colors.gray, # Professional sizing spacing_size=sizes.spacing_md, radius_size=sizes.radius_sm, # Subtle rounded corners text_size=sizes.text_lg, # Readable text # Professional fonts font=fonts.GoogleFont("Inter"), # Clean sans-serif font_mono=fonts.GoogleFont("IBM Plex Mono") ) # Custom colour overrides super().set( # Background body_background_fill="#f8f9fa", body_background_fill_dark="#1a1a1a", # Primary buttons button_primary_background_fill="#288cfa", button_primary_background_fill_hover="#1a7de8", button_primary_text_color="white", button_primary_shadow="0 2px 4px rgba(40, 140, 250, 0.2)", button_primary_border_color="#288cfa", # Secondary buttons button_secondary_background_fill="white", button_secondary_background_fill_hover="#f8f9fa", button_secondary_text_color="#288cfa", button_secondary_border_color="#dee2e6", # Card/block styling block_background_fill="white", block_border_width="1px", block_border_color="#e5e7eb", block_shadow="0 1px 3px 0 rgba(0, 0, 0, 0.1)", block_title_text_weight="600", block_title_text_color="#242c34", block_label_text_weight="500", block_label_text_color="#6b7280", # Input styling input_background_fill="white", input_border_color="#d1d5db", input_shadow="0 1px 2px 0 rgba(0, 0, 0, 0.05)", input_border_width="1px", # Checkbox/radio checkbox_border_color="#d1d5db", checkbox_background_color="white", # Status colours color_accent_soft="#10b981", # Green for positive stat_background_fill="#fef3c7", # Subtle yellow for highlights # Progress bar loader_color="#288cfa", slider_color="#288cfa", # Table styling table_border_color="#e5e7eb", table_odd_background_fill="#f9fafb", table_even_background_fill="white", # Panel styling panel_background_fill="white", panel_border_color="#e5e7eb", panel_border_width="1px", ) # Custom CSS for additional styling (theme-adaptive with dark mode improvements) FINANCIAL_CSS = """ /* Global container styling */ .gradio-container { font-family: 'Inter', 'Arial', sans-serif; max-width: 1400px; margin: auto; } /* Adaptive container background - follows HuggingFace Spaces theme */ .gradio-container { background: var(--body-background-fill); } /* Clean input styling - theme adaptive */ input, textarea { background-color: var(--input-background-fill); border: 1px solid var(--input-border-color); border-radius: 8px; } input:focus-visible, textarea:focus-visible { outline: 3px solid var(--button-primary-background-fill); outline-offset: 2px; border-radius: 2px; opacity: 1 !important; } input:focus, textarea:focus { opacity: 1 !important; border-color: var(--button-primary-background-fill); box-shadow: 0 0 0 3px var(--button-primary-background-fill-hover); } /* Group/card styling with subtle shadows - theme adaptive */ .gr-group, .gr-box { background-color: var(--block-background-fill); border: 1px solid var(--block-border-color); border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } /* Button styling - theme adaptive */ .gr-button { border-radius: 8px; } .gr-button-primary { background: var(--button-primary-background-fill); border: none; color: var(--button-primary-text-color); font-weight: 600; } .gr-button-primary:hover { background: var(--button-primary-background-fill-hover); } /* Example portfolio cards - theme adaptive */ .gr-examples { background-color: var(--block-background-fill); border: 1px solid var(--block-border-color); border-radius: 8px; } .gr-examples button { background-color: var(--button-secondary-background-fill); border: 1px solid var(--button-secondary-border-color); } .gr-examples button:hover { background-color: var(--button-secondary-background-fill-hover); border-color: var(--button-primary-background-fill); } /* Header styling - theme adaptive */ .header h1 { color: var(--body-text-color); font-weight: 700; letter-spacing: -0.025em; margin-bottom: 0.5rem; } .header h3 { color: var(--body-text-color); opacity: 0.7; font-weight: 400; margin-top: 0; } .markdown-text h1 { font-weight: 600; border-bottom: 2px solid var(--color-accent); padding-bottom: 0.5rem; margin-bottom: 1rem; } .markdown-text h2 { font-weight: 600; margin-top: 1.5rem; margin-bottom: 0.75rem; } .markdown-text h3 { font-weight: 500; margin-top: 1rem; margin-bottom: 0.5rem; opacity: 0.8; } /* Metric cards */ .metrics-row { gap: 1rem; } .metric-card { padding: 1.5rem; border-radius: 8px; } .metric-card .label { font-size: 0.875rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; opacity: 0.7; } .metric-card .value { font-size: 2rem; font-weight: 600; } /* Status colours */ .positive { color: #10b981; font-weight: 600; } .negative { color: #ef4444; font-weight: 600; } .neutral { font-weight: 500; opacity: 0.7; } /* Data tables */ .dataframe { border-radius: 6px; overflow: visible; } .dataframe th { font-weight: 600; text-transform: uppercase; font-size: 0.75rem; letter-spacing: 0.05em; } /* Button enhancements */ .primary.lg { padding: 0.75rem 2rem; font-weight: 600; font-size: 1.125rem; } .secondary.lg { padding: 0.75rem 2rem; font-weight: 600; font-size: 1.125rem; } /* Progress bar */ .progress-bar { background: linear-gradient(90deg, var(--color-accent), var(--color-accent-soft)); } /* Accordion styling for ChatMessage metadata */ .message-metadata { border-left: 3px solid var(--color-accent); padding-left: 1rem; margin: 0.5rem 0; border-radius: 4px; padding: 0.75rem 1rem; } .message-metadata .title { font-weight: 600; margin-bottom: 0.25rem; } .message-metadata .log { font-size: 0.875rem; opacity: 0.7; } /* Tool call status indicators */ .status-pending { color: #f59e0b; } .status-done { color: #10b981; } .status-error { color: #ef4444; } /* Loading spinner */ .loading { border: 3px solid var(--border-color-primary); border-top-color: var(--color-accent); animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Responsive design */ @media (max-width: 768px) { .gradio-container { padding: 1rem; } .metric-card .value { font-size: 1.5rem; } } /* Chart containers */ .plotly-chart { border-radius: 8px; padding: 1rem; } /* Tab styling */ .tab-nav button.selected { border-bottom: 2px solid var(--color-accent); color: var(--color-accent); font-weight: 600; } /* Error messages - theme adaptive */ .error-message { background: var(--error-background-fill, #fee2e2); border-left: 4px solid var(--error-border-color, #ef4444); padding: 1rem; border-radius: 4px; color: var(--error-text-color, #991b1b); } .dark .error-message { background: var(--error-background-fill-dark, #450a0a); color: var(--error-text-color-dark, #fca5a5); } /* Success messages - theme adaptive */ .success-message { background: var(--color-accent-soft-background, #d1fae5); border-left: 4px solid var(--color-accent-soft, #10b981); padding: 1rem; border-radius: 4px; color: var(--color-accent-soft-text, #065f46); } .dark .success-message { background: var(--color-accent-soft-background-dark, #064e3b); color: var(--color-accent-soft-text-dark, #6ee7b7); } /* Info messages - theme adaptive */ .info-message { background: var(--info-background-fill, #dbeafe); border-left: 4px solid var(--button-primary-background-fill, #3b82f6); padding: 1rem; border-radius: 4px; color: var(--info-text-color, #1e40af); } .dark .info-message { background: var(--info-background-fill-dark, #1e3a8a); color: var(--info-text-color-dark, #93c5fd); } """ def get_financial_theme() -> gr.Theme: """Get the financial theme instance. Returns: Configured FinancialTheme instance """ return FinancialTheme()