Ashkan Taghipour (The University of Western Australia)
UI cleanup: remove progress steps, fix hero header text visibility
ca4b089 | """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); | |
| } | |
| """ | |