"""Custom Gradio theme and CSS for the Pigeon Pea Pangenome Atlas. Provides a premium botanical/academic visual identity with forest-green primary color, gold accents, and clean card-based layouts. Exports ------- get_theme() – returns the configured ``gr.themes.Base`` instance. build_theme() – alias kept for backward compatibility. CUSTOM_CSS – CSS string passed to ``demo.launch(css=...)`` (Gradio 6.x). """ import gradio as gr # ===================================================================== # Gradio theme # ===================================================================== def get_theme() -> gr.themes.Base: """Return a Gradio theme object with the Atlas visual identity. * Primary hue : forest green (#2E7D32) * Secondary hue: gold (#D4A017) * Neutral hue : warm gray * Border radius: 12px containers, 8px buttons * Font : system sans-serif stack """ theme = gr.themes.Base( primary_hue=gr.themes.colors.green, secondary_hue=gr.themes.colors.amber, neutral_hue=gr.themes.Color( name="warmgray", c50="#FAFAF5", c100="#F5F5F0", c200="#E8E8E0", c300="#D4D4CC", c400="#A8A8A0", c500="#787870", c600="#5C5C55", c700="#45453F", c800="#2E2E28", c900="#1A1A15", c950="#0D0D0A", ), font=[ "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial", "sans-serif", ], font_mono=[ "SF Mono", "SFMono-Regular", "ui-monospace", "Menlo", "monospace", ], ).set( # Page body_background_fill="#FAFAF5", body_text_color="#1A1A1A", # Containers / blocks block_background_fill="#FFFFFF", block_border_width="0px", block_border_color="transparent", block_radius="12px", block_shadow="0 2px 8px rgba(0,0,0,0.06)", # Buttons button_primary_background_fill="linear-gradient(135deg, #2E7D32, #43A047)", button_primary_background_fill_hover="linear-gradient(135deg, #256b29, #388E3C)", button_primary_text_color="white", button_primary_border_color="transparent", button_secondary_background_fill="transparent", button_secondary_border_color="#2E7D32", button_secondary_text_color="#2E7D32", button_large_radius="8px", button_small_radius="8px", # Inputs input_radius="8px", input_border_color="#E0E0E0", input_background_fill="#FFFFFF", # Spacing layout_gap="16px", # Shadows shadow_spread="0px", ) return theme # Backward-compatible alias so existing ``from ui.theme import build_theme`` # continues to work without changes. build_theme = get_theme # ===================================================================== # Custom CSS # ===================================================================== CUSTOM_CSS = """ /* ================================================================= Pigeon Pea Pangenome Atlas — Custom CSS ================================================================= Class reference (keep in sync with Python HTML builders): Page : body overrides Cards : .metric-card, .gene-card-panel Hero : .hero-header, .hero-stat, .hero-subtitle Badges : .gene-badge-core, .gene-badge-shell, .gene-badge-cloud Backpack : .backpack-chip, .backpack-chip-core/shell/cloud Stepper : .progress-stepper, .step-complete, .step-current, .step-future Buttons : .btn-primary, .btn-secondary Legacy : .quest-badge, .badge-core/shell/cloud, .gene-card, .presence-barcode, .stat-card, .achievement-badge ================================================================= */ /* ---- Page-level overrides ---- */ body, .gradio-container { background: #FAFAF5 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; } /* Remove default Gradio container borders */ .gradio-container .gr-box, .gradio-container .gr-panel { border: none !important; } /* ---- Card styling ---- */ .gr-block, .gr-panel, .gr-group { background: #FFFFFF; border-radius: 16px !important; box-shadow: 0 2px 8px rgba(0,0,0,0.06); } /* ---- Tabs — underline style ---- */ .tabs > .tab-nav { border-bottom: 2px solid #E0E0E0 !important; background: transparent !important; } .tabs > .tab-nav > button { border: none !important; border-radius: 0 !important; background: transparent !important; padding: 10px 20px !important; font-size: 14px; font-weight: 500; color: #757575; transition: color 0.2s, border-color 0.2s; position: relative; } .tabs > .tab-nav > button.selected { color: #2E7D32 !important; font-weight: 600; border-bottom: 3px solid #2E7D32 !important; } .tabs > .tab-nav > button:hover { color: #2E7D32; } /* ---- Hero header ---- */ .hero-header { background: #1a2332; color: #FFFFFF !important; padding: 40px 48px; border-radius: 16px; margin-bottom: 24px; } .hero-header h1 { margin: 0 0 8px 0; font-size: 28px; font-weight: 700; letter-spacing: -0.5px; color: #FFFFFF !important; } .hero-subtitle { font-size: 16px; color: #94a3b8 !important; margin-bottom: 28px; line-height: 1.5; } .hero-stat { display: inline-block; text-align: center; margin-right: 48px; vertical-align: top; } .hero-stat .stat-number { display: block; font-size: 48px; font-weight: 700; color: #FFFFFF !important; line-height: 1.1; } .hero-stat .stat-label { display: block; font-size: 12px; font-weight: 400; color: #94a3b8 !important; text-transform: uppercase; letter-spacing: 1px; margin-top: 6px; } /* ---- Metric cards ---- */ .metric-card { background: #FFFFFF; border-radius: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); padding: 24px; border-top: 3px solid #2E7D32; text-align: center; transition: box-shadow 0.2s; } .metric-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.10); } .metric-card.amber { border-top-color: #F9A825; } .metric-card.red { border-top-color: #C62828; } .metric-card.blue { border-top-color: #1565C0; } .metric-value { font-size: 36px; font-weight: 700; color: #1A1A1A; line-height: 1.1; margin-bottom: 4px; } .metric-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 1.2px; color: #757575; } /* ---- Gene card side panel ---- */ .gene-card-panel { background: #FFFFFF; border-radius: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); padding: 20px; border-left: 3px solid #2E7D32; } /* Gene classification badges */ .gene-badge-core { display: inline-block; padding: 4px 14px; border-radius: 20px; font-size: 12px; font-weight: 600; background: #2E7D32; color: #FFFFFF; } .gene-badge-shell { display: inline-block; padding: 4px 14px; border-radius: 20px; font-size: 12px; font-weight: 600; background: #F9A825; color: #333333; } .gene-badge-cloud { display: inline-block; padding: 4px 14px; border-radius: 20px; font-size: 12px; font-weight: 600; background: #C62828; color: #FFFFFF; } /* ---- Backpack chips ---- */ .backpack-chip { display: inline-block; padding: 4px 12px; border-radius: 16px; font-size: 12px; font-weight: 600; margin: 2px 4px; vertical-align: middle; } .backpack-chip-core { background: #E8F5E9; color: #2E7D32; border: 1px solid #C8E6C9; } .backpack-chip-shell { background: #FFF8E1; color: #F9A825; border: 1px solid #FFECB3; } .backpack-chip-cloud { background: #FFEBEE; color: #C62828; border: 1px solid #FFCDD2; } /* ---- Progress stepper ---- */ .progress-stepper { display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 0; padding: 16px 0; } .progress-stepper .step { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #757575; padding: 0 16px; position: relative; } .progress-stepper .step::after { content: ""; position: absolute; right: -2px; width: 24px; height: 2px; background: #E0E0E0; } .progress-stepper .step:last-child::after { display: none; } .progress-stepper .step .dot { width: 24px; height: 24px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; flex-shrink: 0; } /* Completed step: filled green circle with checkmark */ .step-complete .dot { background: #2E7D32; color: #FFFFFF; } .step-complete { color: #2E7D32; font-weight: 500; } /* Current step: green ring, bold label */ .step-current .dot { background: transparent; border: 2.5px solid #2E7D32; color: #2E7D32; } .step-current { color: #2E7D32; font-weight: 700; } /* Future step: gray dimmed circle */ .step-future .dot { background: #E0E0E0; color: #9E9E9E; } .step-future { color: #9E9E9E; } /* ---- Buttons ---- */ .btn-primary { display: inline-block; padding: 10px 24px; background: linear-gradient(135deg, #2E7D32, #43A047); color: #FFFFFF !important; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: opacity 0.2s, box-shadow 0.2s; text-decoration: none; } .btn-primary:hover { opacity: 0.92; box-shadow: 0 4px 12px rgba(46,125,50,0.25); } .btn-secondary { display: inline-block; padding: 10px 24px; background: transparent; color: #2E7D32 !important; border: 2px solid #2E7D32; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: background 0.2s; text-decoration: none; } .btn-secondary:hover { background: #E8F5E9; } /* ---- Hide Gradio footer / reduce noise ---- */ footer { display: none !important; } .gradio-container .gr-padded { padding: 12px !important; } /* Clean accordion styling */ .gr-accordion { border: 1px solid #E0E0E0 !important; border-radius: 12px !important; box-shadow: none !important; } .gr-accordion > .label-wrap { padding: 12px 16px !important; } /* ---- Legacy classes (backward compat) ---- */ .quest-badge { display: inline-block; padding: 4px 12px; border-radius: 16px; font-size: 0.85em; font-weight: 600; margin: 2px 4px; } .badge-core { background: #2E7D32; color: white; } .badge-shell { background: #F9A825; color: #333; } .badge-cloud { background: #C62828; color: white; } .gene-card { border: 2px solid #2E7D32; border-radius: 12px; padding: 16px; background: #FFFFFF; box-shadow: 0 2px 8px rgba(0,0,0,0.06); } .presence-barcode span { display: inline-block; width: 3px; height: 20px; margin: 0; } .presence-barcode .present { background: #2E7D32; } .presence-barcode .absent { background: #E0E0E0; } .stat-card { text-align: center; padding: 20px; border-radius: 12px; background: #FFFFFF; border-top: 3px solid #2E7D32; box-shadow: 0 2px 8px rgba(0,0,0,0.06); } .stat-card .stat-value { font-size: 1.8em; font-weight: 700; color: #2E7D32; } .stat-card .stat-label { font-size: 0.85em; color: #757575; } .achievement-badge { display: inline-block; padding: 6px 14px; border-radius: 20px; background: linear-gradient(135deg, #D4A017, #F9A825); color: #333; font-weight: 600; margin: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } """