Spaces:
Running
Running
| """Theme and CSS tokens for SupraDashboard.""" | |
| from __future__ import annotations | |
| import gradio as gr | |
| # --------------------------------------------------------------------------- # | |
| # Design tokens — Molecular macrocycle palette # | |
| # Bone-white ground, copper + teal paired accents, Newsreader / Hanken Grotesk # | |
| # / Space Mono type stack. All rule hues pass WCAG AA on #F4EFE6 (bone). # | |
| # --------------------------------------------------------------------------- # | |
| INK = "#2A2622" | |
| MUTED = "#6B6258" | |
| LINE = "#2A26222E" | |
| HAIRLINE = "#2A262214" | |
| SURFACE = "#FBF8F1" | |
| APP_BG = "#F4EFE6" | |
| PRIMARY = "#1E7A74" # teal | |
| PRIMARY_DARK = "#155954" # deep teal | |
| COPPER = "#B5642E" | |
| COPPER_DARK = "#8F4A1E" | |
| OK = "#1E7A4E" | |
| WARN = "#9A6F12" | |
| ERR = "#B42318" | |
| # Rule-index → hue (idx mod len). Tuned for WCAG AA on bone-white #F4EFE6 / #FBF8F1. | |
| # Color is redundant with bold weight + left-border + the literal N. number. | |
| RULE_COLORS = ["#1F5FA8", "#7B4FA0", "#1E7A4E", "#B23A7E", COPPER_DARK, "#1E7A74"] | |
| SERIF = '"Newsreader","Georgia","Times New Roman",serif' | |
| MONO = '"Space Mono","IBM Plex Mono","SF Mono",ui-monospace,"Menlo",monospace' | |
| BODY = '"Hanken Grotesk","ui-sans-serif","system-ui",sans-serif' | |
| # --------------------------------------------------------------------------- # | |
| # Theme + CSS — Molecular macrocycle # | |
| # --------------------------------------------------------------------------- # | |
| def _theme() -> gr.Theme: | |
| return gr.themes.Base( | |
| primary_hue=gr.themes.colors.teal, | |
| secondary_hue=gr.themes.colors.orange, | |
| neutral_hue=gr.themes.colors.stone, | |
| radius_size=gr.themes.sizes.radius_lg, | |
| font=( | |
| gr.themes.GoogleFont("Hanken Grotesk"), | |
| "ui-sans-serif", | |
| "system-ui", | |
| "sans-serif", | |
| ), | |
| font_mono=( | |
| gr.themes.GoogleFont("Space Mono"), | |
| "ui-monospace", | |
| "monospace", | |
| ), | |
| ).set( | |
| # Pin all background/surface hues to bone-white so Gradio's dark-mode | |
| # heuristics don't invert the palette in a default-light deployment. | |
| body_background_fill=APP_BG, | |
| body_background_fill_dark=APP_BG, | |
| background_fill_primary=SURFACE, | |
| background_fill_primary_dark=SURFACE, | |
| background_fill_secondary=APP_BG, | |
| background_fill_secondary_dark=APP_BG, | |
| # Borders | |
| block_border_color="#2A262220", | |
| block_border_color_dark="#2A262220", | |
| block_border_width="1px", | |
| border_color_primary="#2A262222", | |
| border_color_primary_dark="#2A262222", | |
| # Input surfaces | |
| input_background_fill=APP_BG, | |
| input_background_fill_dark=APP_BG, | |
| # Body text | |
| body_text_color=INK, | |
| body_text_color_dark=INK, | |
| body_text_color_subdued=MUTED, | |
| body_text_color_subdued_dark=MUTED, | |
| block_label_text_color=INK, | |
| block_label_text_color_dark=INK, | |
| block_title_text_color=INK, | |
| block_title_text_color_dark=INK, | |
| # Primary button: teal | |
| button_primary_background_fill=PRIMARY, | |
| button_primary_background_fill_dark=PRIMARY, | |
| button_primary_background_fill_hover=PRIMARY_DARK, | |
| button_primary_background_fill_hover_dark=PRIMARY_DARK, | |
| button_primary_text_color="#FBF8F1", | |
| button_primary_text_color_dark="#FBF8F1", | |
| # Secondary button: copper-tinted | |
| button_secondary_background_fill=SURFACE, | |
| button_secondary_background_fill_dark=SURFACE, | |
| button_secondary_background_fill_hover="#F4EFE6", | |
| button_secondary_border_color=COPPER, | |
| button_secondary_text_color=COPPER, | |
| ) | |
| _FONT_HEAD = ( | |
| "<link rel='preconnect' href='https://fonts.googleapis.com'/>" | |
| "<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin/>" | |
| "<link href='https://fonts.googleapis.com/css2?" | |
| "family=Newsreader:ital,opsz,wght@0,6..72,300..600;1,6..72,400..500" | |
| "&family=Hanken+Grotesk:wght@400;500;600;700" | |
| "&family=Space+Mono:wght@400;700&display=swap' rel='stylesheet'/>" | |
| ) | |
| _CSS = f""" | |
| /* ------------------------------------------------------------------ */ | |
| /* Global reset + bone-white ground */ | |
| /* ------------------------------------------------------------------ */ | |
| .gradio-container {{ | |
| max-width: 1280px !important; | |
| margin: 0 auto !important; | |
| background: transparent !important; | |
| font-family: "Hanken Grotesk", ui-sans-serif, system-ui, sans-serif !important; | |
| color: {INK} !important; | |
| }} | |
| body, html {{ | |
| background: {APP_BG} !important; | |
| color: {INK} !important; | |
| }} | |
| /* ------------------------------------------------------------------ */ | |
| /* Rotating heptagon watermark — fixed behind everything */ | |
| /* pointer-events:none so it never blocks interaction */ | |
| /* ------------------------------------------------------------------ */ | |
| body::before {{ | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| background-image: | |
| radial-gradient(900px 600px at 50% 0%, #1E7A741F 0%, transparent 60%), | |
| radial-gradient(700px 560px at 12% 6%, #1E7A7418 0%, transparent 65%), | |
| radial-gradient(620px 580px at 94% 18%, #B5642E20 0%, transparent 62%), | |
| radial-gradient(780px 640px at 68% 96%, #1E7A740E 0%, transparent 65%), | |
| radial-gradient(560px 480px at 28% 92%, #B5642E0A 0%, transparent 62%); | |
| z-index: 0; | |
| pointer-events: none; | |
| }} | |
| /* Heptagon SVG watermark injected as a pseudo-element via a data URI */ | |
| body::after {{ | |
| content: ''; | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| width: 560px; | |
| height: 560px; | |
| transform: translate(-50%, -50%); | |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 700 700' fill='none'%3E%3Cpolygon points='350,28 602,149 664,422 490,640 210,640 36,422 98,149' stroke='%231E7A74' stroke-width='1.5' opacity='0.14'/%3E%3Cpolygon points='350,85 557,185 608,409 465,589 235,589 92,409 143,185' stroke='%23B5642E' stroke-width='1.5' opacity='0.11'/%3E%3Cpolygon points='350,153 504,227 542,394 436,528 264,528 158,394 196,227' stroke='%231E7A74' stroke-width='1.5' opacity='0.09'/%3E%3Ccircle cx='350' cy='350' r='80' stroke='%23B5642E' stroke-width='1.5' opacity='0.09'/%3E%3C/svg%3E"); | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| background-position: center; | |
| opacity: 1; | |
| pointer-events: none; | |
| z-index: 0; | |
| }} | |
| @keyframes hept-spin {{ | |
| from {{ transform: translate(-50%, -50%) rotate(0deg); }} | |
| to {{ transform: translate(-50%, -50%) rotate(360deg); }} | |
| }} | |
| /* Ensure all Gradio content sits above the watermark layer */ | |
| .gradio-container, footer {{ | |
| position: relative; | |
| z-index: 1; | |
| }} | |
| /* ------------------------------------------------------------------ */ | |
| /* Page-load stagger — fills forwards so screenshot captures final */ | |
| /* ------------------------------------------------------------------ */ | |
| @keyframes mol-rise {{ | |
| from {{ opacity: 0; transform: translateY(10px); }} | |
| to {{ opacity: 1; transform: none; }} | |
| }} | |
| @keyframes spin {{ | |
| to {{ transform: rotate(360deg); }} | |
| }} | |
| .spin {{ | |
| display: inline-block; | |
| animation: spin 1s linear infinite; | |
| }} | |
| @media (prefers-reduced-motion: no-preference) {{ | |
| body::after {{ animation: hept-spin 90s linear infinite; }} | |
| .app-head {{ animation: mol-rise .55s cubic-bezier(.2,.7,.2,1) .04s both; }} | |
| .gradio-row {{ animation: mol-rise .55s cubic-bezier(.2,.7,.2,1) .14s both; }} | |
| }} | |
| /* ------------------------------------------------------------------ */ | |
| /* Header / brand */ | |
| /* ------------------------------------------------------------------ */ | |
| .app-head {{ | |
| border: 1px solid #2A262233; | |
| border-radius: 18px; | |
| background: {SURFACE}; | |
| padding: 22px 26px; | |
| margin-bottom: 10px; | |
| box-shadow: 0 1px 0 #fff inset, 0 12px 40px -16px #2A262233; | |
| background-image: linear-gradient(90deg, #1E7A7414, transparent 40%, #B5642E14); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: baseline; | |
| gap: 18px; | |
| }} | |
| .app-brand {{ | |
| display: flex; | |
| align-items: center; | |
| gap: 14px; | |
| }} | |
| .app-hept {{ | |
| width: 44px; | |
| height: 44px; | |
| flex-shrink: 0; | |
| }} | |
| .app-name {{ | |
| font-family: "Newsreader", Georgia, serif; | |
| font-weight: 500; | |
| font-size: 32px; | |
| letter-spacing: -.02em; | |
| color: {INK}; | |
| line-height: 1; | |
| }} | |
| .app-name span {{ | |
| color: {COPPER}; | |
| font-weight: 600; | |
| }} | |
| .app-sub {{ | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif; | |
| font-size: 11px; | |
| letter-spacing: .05em; | |
| color: {INK}; | |
| opacity: .55; | |
| margin-top: 4px; | |
| text-transform: uppercase; | |
| }} | |
| /* ------------------------------------------------------------------ */ | |
| /* Health strip dots */ | |
| /* ------------------------------------------------------------------ */ | |
| .health {{ | |
| display: flex; | |
| gap: 20px; | |
| flex-wrap: wrap; | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif; | |
| font-size: 11px; | |
| color: {MUTED}; | |
| align-items: baseline; | |
| }} | |
| .health .dot {{ | |
| display: inline-block; | |
| width: 9px; | |
| height: 9px; | |
| border-radius: 50%; | |
| margin-right: 7px; | |
| vertical-align: middle; | |
| box-shadow: 0 0 0 2px {SURFACE}, 0 0 0 3px #0001; | |
| }} | |
| /* ------------------------------------------------------------------ */ | |
| /* Run status line */ | |
| /* ------------------------------------------------------------------ */ | |
| .runstatus {{ | |
| border: 1px solid; | |
| border-radius: 12px; | |
| padding: 8px 16px; | |
| margin: 4px 0 6px; | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif; | |
| font-size: 13.5px; | |
| font-weight: 600; | |
| background: {SURFACE}; | |
| letter-spacing: .01em; | |
| }} | |
| .runstatus .g {{ | |
| font-size: 15px; | |
| margin-right: 6px; | |
| }} | |
| /* ------------------------------------------------------------------ */ | |
| /* Prediction card */ | |
| /* ------------------------------------------------------------------ */ | |
| .pred-card {{ | |
| border: 1px solid {LINE}; | |
| border-radius: 16px; | |
| background: {SURFACE}; | |
| padding: 6px 6px 8px; | |
| box-shadow: 0 1px 0 #fff inset, 0 2px 10px -4px #2A26222E, 0 8px 28px -14px #2A262226; | |
| }} | |
| .pred-title {{ | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif; | |
| font-size: 10px; | |
| letter-spacing: .16em; | |
| text-transform: uppercase; | |
| color: {MUTED}; | |
| font-weight: 700; | |
| padding: 8px 14px 6px; | |
| }} | |
| .pred-card .row {{ | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 8px 14px; | |
| border-radius: 10px; | |
| }} | |
| .pred-card .row .lbl {{ | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif; | |
| font-size: 14px; | |
| color: {MUTED}; | |
| }} | |
| .pred-card .row .val {{ | |
| font-family: {MONO}; | |
| font-size: 18px; | |
| letter-spacing: -.01em; | |
| font-weight: 500; | |
| color: {INK}; | |
| font-variant-numeric: tabular-nums; | |
| }} | |
| /* Combined row: copper warm tint */ | |
| .pred-card .row.primary {{ | |
| background: linear-gradient(90deg, #B5642E14, transparent); | |
| border-radius: 10px; | |
| }} | |
| .pred-card .row.primary .lbl {{ | |
| font-weight: 700; | |
| color: {COPPER_DARK}; | |
| font-size: 14.5px; | |
| }} | |
| .pred-card .row.primary .val {{ | |
| font-weight: 700; | |
| font-size: 24px; | |
| color: {COPPER}; | |
| }} | |
| .dots {{ color: {MUTED}; letter-spacing: .1em; }} | |
| /* ------------------------------------------------------------------ */ | |
| /* TL;DR cards */ | |
| /* ------------------------------------------------------------------ */ | |
| .tldr {{ | |
| border: 1px solid {LINE}; | |
| border-radius: 16px; | |
| background: {SURFACE}; | |
| padding: 13px 15px; | |
| box-shadow: 0 1px 0 #fff inset, 0 2px 10px -4px #2A26222E, 0 8px 28px -14px #2A262226; | |
| }} | |
| .tldr {{ | |
| overflow: hidden; | |
| word-break: break-word; | |
| }} | |
| .tldr-tag {{ | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif; | |
| font-size: 9.5px; | |
| letter-spacing: .14em; | |
| text-transform: uppercase; | |
| font-weight: 700; | |
| margin-bottom: 6px; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| }} | |
| .tldr-body {{ | |
| font-family: {SERIF}; | |
| font-size: 14.5px; | |
| line-height: 1.58; | |
| color: {INK}; | |
| }} | |
| /* ------------------------------------------------------------------ */ | |
| /* Reasoning hero panel */ | |
| /* ------------------------------------------------------------------ */ | |
| .reason-head {{ | |
| font-family: "Newsreader", Georgia, serif; | |
| font-style: italic; | |
| font-size: 15px; | |
| color: {MUTED}; | |
| margin: 4px 0 8px; | |
| letter-spacing: .01em; | |
| }} | |
| .reason-wrap {{ | |
| border: 1px solid {LINE}; | |
| border-radius: 18px; | |
| background: {SURFACE}; | |
| padding: 24px 28px; | |
| box-shadow: 0 1px 0 #fff inset, 0 12px 40px -16px #2A262233; | |
| min-height: 440px; | |
| border-top: 2px solid #B5642E33; | |
| background-image: | |
| radial-gradient(420px 320px at 50% 0%, #1E7A740E, transparent 70%), | |
| linear-gradient(160deg, #1E7A7408, transparent 40%, #B5642E06); | |
| }} | |
| .reason-body {{ | |
| font-family: {SERIF}; | |
| font-size: 16.5px; | |
| line-height: 1.72; | |
| color: {INK}; | |
| white-space: normal; | |
| }} | |
| .reason-body .prose {{ | |
| margin: .4em 0; | |
| font-family: {SERIF}; | |
| font-size: 16.5px; | |
| line-height: 1.72; | |
| }} | |
| /* Rule rows: circular node + colored title + ink explanation */ | |
| .reason-body .rule {{ | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 0; | |
| padding: 10px 0 10px 0; | |
| border-bottom: 1px solid {HAIRLINE}; | |
| /* border-left is set inline per rule — required by smoke test */ | |
| }} | |
| .reason-body .rule:last-of-type {{ border-bottom: none; }} | |
| .rule-node {{ | |
| /* Circular numbered badge — color/bg set inline per rule */ | |
| line-height: 1 !important; | |
| margin-top: 2px; | |
| }} | |
| .reason-empty {{ | |
| border-radius: 16px; | |
| padding: 56px 20px; | |
| text-align: center; | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif; | |
| font-size: 14px; | |
| background: {SURFACE}; | |
| color: {MUTED}; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 320px; | |
| line-height: 1.6; | |
| }} | |
| .reason-guide {{ | |
| margin-top: 12px; | |
| font-size: 13px; | |
| color: {INK}; | |
| }} | |
| .reason-guide span {{ | |
| color: {MUTED}; | |
| }} | |
| /* ------------------------------------------------------------------ */ | |
| /* Expert review band */ | |
| /* ------------------------------------------------------------------ */ | |
| .review-band {{ | |
| border: 1px solid {LINE} !important; | |
| border-top: 3px solid {COPPER} !important; | |
| border-radius: 16px !important; | |
| background: {SURFACE} !important; | |
| padding: 20px 22px 22px !important; | |
| margin-top: 18px !important; | |
| box-shadow: 0 1px 0 #fff inset, 0 10px 32px -18px #2A262233 !important; | |
| gap: 6px !important; | |
| }} | |
| .review-q {{ | |
| font-family: "Newsreader", Georgia, serif; | |
| font-size: 18px; | |
| font-weight: 500; | |
| color: {INK}; | |
| margin: 8px 0 4px; | |
| letter-spacing: -.005em; | |
| }} | |
| .ack {{ | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif; | |
| font-size: 13.5px; | |
| padding: 9px 14px; | |
| border-radius: 12px; | |
| display: inline-block; | |
| }} | |
| .ack.ok {{ color: {OK}; background: #1E7A4E1F; }} | |
| .ack.warn {{ color: {WARN}; background: #9A6F1212; }} | |
| /* ------------------------------------------------------------------ */ | |
| /* Radio (rating scale) — horizontal pill chips */ | |
| /* ------------------------------------------------------------------ */ | |
| .rating-radio .wrap {{ gap: 7px; flex-wrap: wrap; }} | |
| .rating-radio label, | |
| .pill-toggle label {{ | |
| border: 1px solid #2A262240 !important; | |
| padding: 6px 12px !important; | |
| border-radius: 999px !important; | |
| background: {SURFACE} !important; | |
| color: {INK} !important; | |
| transition: background .12s ease, color .12s ease, border-color .12s ease !important; | |
| }} | |
| .rating-radio label:hover:not(:has(input:checked)), | |
| .pill-toggle label:hover:not(:has(input:checked)) {{ | |
| border-color: {COPPER} !important; | |
| background: #B5642E0A !important; | |
| }} | |
| .rating-radio label:has(input:checked), | |
| .pill-toggle label:has(input:checked) {{ | |
| background: {PRIMARY} !important; | |
| color: #ffffff !important; | |
| border-color: {PRIMARY} !important; | |
| }} | |
| /* Selected pill: force strong white text on the highlight fill. Gradio renders | |
| the label text inside a child <span>, which otherwise keeps the dark INK | |
| colour and reads poorly on the green/copper fill. */ | |
| .rating-radio label:has(input:checked) span, | |
| .pill-toggle label:has(input:checked) span {{ | |
| color: #ffffff !important; | |
| font-weight: 600 !important; | |
| }} | |
| .rating-radio label span {{ | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif !important; | |
| font-size: 13px !important; | |
| }} | |
| .prompt-modes .wrap {{ | |
| flex-direction: column !important; | |
| gap: 6px !important; | |
| align-items: stretch !important; | |
| }} | |
| .prompt-modes label {{ | |
| display: block !important; | |
| width: 100% !important; | |
| padding: 8px 14px !important; | |
| border: 1.5px solid {LINE} !important; | |
| border-radius: 8px !important; | |
| cursor: pointer !important; | |
| transition: background 0.15s, border-color 0.15s !important; | |
| }} | |
| .prompt-modes label:has(input:checked) {{ | |
| background: {PRIMARY} !important; | |
| border-color: {PRIMARY} !important; | |
| color: white !important; | |
| font-weight: 600 !important; | |
| }} | |
| /* Run control: a REAL Bootstrap-style split button — main "Run" pill + caret | |
| toggle fused via a flex btn-group (shared border, caret has no left radius). | |
| gr.Button renders <button> with no wrapper, so the buttons are direct flex | |
| children of the .run-group row and join seamlessly. */ | |
| .run-col {{ position: relative !important; gap: 6px !important; justify-content: flex-end !important; padding-top: 22px !important; }} | |
| /* The group is the pill; the two buttons fuse inside it. Selectors MUST be more | |
| specific than `button.primary` (which forces border-radius/box-shadow with | |
| !important), so we qualify with `.run-group button.run-*`. */ | |
| .run-group {{ | |
| display: flex !important; flex-wrap: nowrap !important; gap: 0 !important; column-gap: 0 !important; | |
| width: 100% !important; align-items: stretch !important; | |
| border-radius: 12px !important; box-shadow: 0 6px 16px -8px #1E7A7488 !important; | |
| }} | |
| .run-group button.run-main, .run-group button.run-caret {{ | |
| margin: 0 !important; box-shadow: none !important; transform: none !important; | |
| }} | |
| .run-group button.run-main {{ | |
| flex: 1 1 auto !important; | |
| border-radius: 12px 0 0 12px !important; | |
| border-right: 1px solid rgba(255,255,255,.4) !important; | |
| }} | |
| .run-group button.run-caret {{ | |
| flex: 0 0 38px !important; width: 38px !important; min-width: 38px !important; max-width: 38px !important; | |
| padding: 0 !important; border-radius: 0 12px 12px 0 !important; font-size: 13px !important; line-height: 1 !important; | |
| }} | |
| /* Dropdown menu under the group (Use cache / Force re-run) */ | |
| .run-menu {{ | |
| position: absolute !important; top: calc(100% + 4px) !important; left: 0 !important; right: 0 !important; | |
| z-index: 60 !important; background: {SURFACE} !important; border: 1px solid {LINE} !important; | |
| border-radius: 10px !important; box-shadow: 0 14px 32px -12px rgba(42,38,34,.3) !important; | |
| padding: 5px !important; gap: 2px !important; | |
| }} | |
| /* run-menu-item is the class ON the <button> (gr.Button), so target | |
| `button.run-menu-item` — NOT `.run-menu-item button` (matches nothing, which | |
| left the default light text on the cream menu = unreadable). */ | |
| .run-menu button.run-menu-item, button.run-menu-item {{ | |
| width: 100% !important; background: transparent !important; color: {INK} !important; | |
| border: none !important; box-shadow: none !important; text-align: left !important; | |
| justify-content: flex-start !important; padding: 8px 12px !important; border-radius: 7px !important; | |
| font-weight: 500 !important; font-size: 13px !important; | |
| }} | |
| .run-menu button.run-menu-item:hover, button.run-menu-item:hover {{ | |
| background: {APP_BG} !important; color: {COPPER_DARK} !important; | |
| }} | |
| /* ------------------------------------------------------------------ */ | |
| /* Gradio component overrides — keep chrome consistent */ | |
| /* ------------------------------------------------------------------ */ | |
| /* Labels — sentence case, quiet; values outrank their labels */ | |
| .gradio-container label span, | |
| .gradio-container .label-wrap span {{ | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif !important; | |
| font-size: 12px !important; | |
| letter-spacing: .01em !important; | |
| text-transform: none !important; | |
| color: {MUTED} !important; | |
| font-weight: 600 !important; | |
| }} | |
| /* Dropdowns + inputs */ | |
| .gradio-container select, | |
| .gradio-container input, | |
| .gradio-container textarea {{ | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif !important; | |
| color: {INK} !important; | |
| background: {APP_BG} !important; | |
| border-color: #2A262222 !important; | |
| border-radius: 12px !important; | |
| }} | |
| /* Guest structure image container — heptagon-framed border */ | |
| .gradio-container .image-container, | |
| .gradio-container .image-frame {{ | |
| border: 1.5px solid #B5642E44 !important; | |
| border-radius: 16px !important; | |
| background: {SURFACE} !important; | |
| box-shadow: 0 0 0 4px #1E7A7408, 0 2px 10px -4px #2A26222E, 0 8px 28px -14px #2A262226 !important; | |
| overflow: hidden !important; | |
| }} | |
| /* Block cards */ | |
| .gradio-container .block {{ | |
| background: {SURFACE} !important; | |
| border-color: {LINE} !important; | |
| border-radius: 18px !important; | |
| margin-top: 8px !important; | |
| margin-bottom: 8px !important; | |
| }} | |
| /* Primary button: teal with copper hover-shimmer */ | |
| .gradio-container .btn-primary, button.primary {{ | |
| background: linear-gradient(180deg, {PRIMARY}, {PRIMARY_DARK}) !important; | |
| color: #FBF8F1 !important; | |
| border: none !important; | |
| border-radius: 12px !important; | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif !important; | |
| font-weight: 700 !important; | |
| font-size: 14px !important; | |
| letter-spacing: .02em !important; | |
| box-shadow: 0 6px 18px -8px #1E7A7488, 0 1px 0 #ffffff28 inset !important; | |
| transition: transform .12s ease, box-shadow .12s ease !important; | |
| }} | |
| .gradio-container .btn-primary:hover, button.primary:hover {{ | |
| transform: translateY(-1px) !important; | |
| box-shadow: 0 10px 24px -8px #1E7A74AA, 0 1px 0 #ffffff28 inset !important; | |
| }} | |
| /* Tab styling */ | |
| .gradio-container .tabs .tab-nav button {{ | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif !important; | |
| font-weight: 600 !important; | |
| font-size: 13.5px !important; | |
| letter-spacing: .02em !important; | |
| text-transform: none !important; | |
| color: {MUTED} !important; | |
| border-radius: 10px 10px 0 0 !important; | |
| }} | |
| .gradio-container .tabs .tab-nav button.selected {{ | |
| color: {PRIMARY} !important; | |
| border-bottom-color: {PRIMARY} !important; | |
| }} | |
| .gradio-container .tabs .tab-nav button:hover {{ | |
| color: {INK} !important; | |
| }} | |
| /* Submit review — the page's primary deliverable action: filled copper CTA */ | |
| .copper-btn, button.copper-btn {{ | |
| background: linear-gradient(180deg, {COPPER}, {COPPER_DARK}) !important; | |
| color: #FBF8F1 !important; | |
| border: none !important; | |
| border-radius: 12px !important; | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif !important; | |
| font-weight: 700 !important; | |
| font-size: 14px !important; | |
| letter-spacing: .02em !important; | |
| width: auto !important; | |
| min-width: 190px !important; | |
| align-self: flex-end !important; | |
| box-shadow: 0 6px 18px -8px #B5642E77, 0 1px 0 #ffffff30 inset !important; | |
| transition: transform .12s ease, box-shadow .12s ease !important; | |
| }} | |
| .copper-btn:hover, button.copper-btn:hover {{ | |
| transform: translateY(-1px) !important; | |
| background: linear-gradient(180deg, {COPPER}, {COPPER_DARK}) !important; | |
| box-shadow: 0 10px 24px -8px #B5642EAA, 0 1px 0 #ffffff30 inset !important; | |
| }} | |
| /* ── Structure viewer (Host|Guest × 2D|3D, inline source, legend) ── */ | |
| .viz-controls {{ align-items: center !important; gap: 10px !important; flex-wrap: wrap !important; }} | |
| /* Discovery Board: one consolidated control panel (filters · feature filter · | |
| sort · view) — a single bordered card, replacing three loose stacked rows. */ | |
| .board-controls {{ | |
| border: 1px solid {LINE} !important; | |
| border-radius: 14px !important; | |
| background: {SURFACE} !important; | |
| padding: 12px 14px !important; | |
| margin: 6px 0 12px !important; | |
| box-shadow: 0 1px 0 #fff inset, 0 6px 22px -14px #2A262233 !important; | |
| display: flex !important; flex-direction: column !important; gap: 9px !important; | |
| }} | |
| .board-controls > .viz-controls {{ margin: 0 !important; gap: 12px !important; align-items: flex-end !important; }} | |
| /* the inner control blocks shouldn't each draw their own card border */ | |
| .board-controls .block {{ border: none !important; background: transparent !important; box-shadow: none !important; margin: 0 !important; }} | |
| /* compact number fields (feature min/max AND the slider's value box). Center the | |
| digits and give them room — the Min-logKa slider box was clipping its number. */ | |
| .board-controls input[type=number] {{ | |
| height: 38px !important; border-radius: 10px !important; | |
| text-align: center !important; padding: 0 8px !important; min-width: 58px !important; | |
| }} | |
| /* Gradio renders the slider's editable value as a small number box in the slider | |
| header; widen + center it so the value isn't cut off or overlapped. */ | |
| .board-controls .gr-slider input[type=number], | |
| .board-controls input[aria-label*="logKa"], | |
| .board-controls .head_container input {{ | |
| text-align: center !important; min-width: 56px !important; width: 56px !important; | |
| }} | |
| /* The radios are <fieldset class="block pill-toggle"> with an inline | |
| min-width:min(160px,100%); override it so Host|Guest and 2D|3D shrink to | |
| content and both stay visible instead of overflowing/clipping. */ | |
| .viz-controls .pill-toggle {{ flex: 0 0 auto !important; min-width: 0 !important; width: auto !important; }} | |
| /* 2D card: white mat, quiet frame, rounded — now an HTML card (build_2d_html) | |
| that carries the SAME in-view caption bar as the 3D viewer. */ | |
| .struct-2d {{ border: 1px solid {HAIRLINE} !important; border-radius: 16px !important; background: #ffffff !important; box-shadow: 0 1px 3px {LINE}, 0 8px 24px -12px #2A262222 !important; overflow: hidden !important; }} | |
| .struct-2d img {{ display: block !important; max-width: 100% !important; max-height: 320px !important; width: auto !important; height: auto !important; margin: 0 auto !important; object-fit: contain !important; image-rendering: -webkit-optimize-contrast; }} | |
| /* 3D frame: round the iframe itself (no clipping parent that eats controls) */ | |
| .struct-3d {{ border: 1px solid {HAIRLINE} !important; border-radius: 16px !important; background: #ffffff !important; overflow: visible !important; box-shadow: 0 1px 3px {LINE}, 0 8px 24px -12px #2A262222 !important; }} | |
| .struct-3d iframe {{ width: 100% !important; border: none !important; border-radius: 16px !important; display: block !important; }} | |
| /* ── Candidates grid (discovery board): explicit light styling — without it | |
| the Gradio Dataframe falls back to its dark default — unreadable on the light theme ── */ | |
| /* Auto-fit: columns size to content, each cell one line; the .table-wrap scrolls | |
| horizontally when the table is wider than the panel. (gr.Dataframe also exposes | |
| drag-resizable column headers.) | |
| IMPORTANT: do NOT use `width: max-content` here. Gradio's table lives inside a | |
| `.table` flex child; `max-content` made it balloon to ~1e6px (every cell went | |
| invisible). `width: auto; min-width: 100%` lets columns size to content, fill the | |
| panel when narrow, and overflow into the scrolling wrap when wide. */ | |
| /* Column auto-fit ("wrapping"): columns size to content, each cell one line, and | |
| the table overflows into a horizontal scroll when wider than the panel. | |
| NOTE: this does NOT control vertical paging. Gradio's Dataframe renders exactly | |
| the rows that fit `max_height` and does not scroll past them (confirmed with a | |
| minimal standalone repro — virtual scroll is a no-op in our gradio<5.50). So the | |
| Discovery Board sizes `max_height` to the selected row count and a "Rows" | |
| selector caps how many top-ranked records are shown. */ | |
| /* Styling only — let Gradio own the table layout + its single .table-wrap | |
| horizontal scroll. Earlier width/table-layout/overflow overrides fought that and | |
| caused either a duplicate scrollbar or blank right-hand columns. */ | |
| #candidates-grid table {{ font-family: "Hanken Grotesk", sans-serif; font-size: 13.5px; color: {INK}; }} | |
| /* ── Pager (below Discovery Board grid) ── */ | |
| .pager-row {{ | |
| align-items: center !important; | |
| justify-content: center !important; | |
| gap: 10px !important; | |
| flex-wrap: nowrap !important; | |
| padding: 12px 0 4px !important; | |
| margin-top: 0 !important; | |
| border-top: 1px solid {HAIRLINE} !important; | |
| }} | |
| /* Gradio sizes each child to width:100% (the Markdown block was 1216px wide, | |
| shoving the buttons to the row edges). Force every child to content width so | |
| justify-content:center clusters them in the middle. */ | |
| .pager-row > * {{ flex: 0 0 auto !important; width: auto !important; min-width: 0 !important; }} | |
| /* Prev / Next pill ghost buttons */ | |
| .pager-row button {{ | |
| height: 42px !important; | |
| min-width: 0 !important; | |
| width: auto !important; | |
| padding: 0 18px !important; | |
| border-radius: 999px !important; | |
| border: 1px solid #B5642E59 !important; | |
| background: {SURFACE} !important; | |
| color: {COPPER} !important; | |
| font-family: {BODY} !important; | |
| font-size: 13px !important; | |
| font-weight: 600 !important; | |
| letter-spacing: .02em !important; | |
| box-shadow: none !important; | |
| transition: background .13s ease, color .13s ease, border-color .13s ease, transform .1s ease !important; | |
| line-height: 1 !important; | |
| }} | |
| .pager-row button:hover {{ | |
| background: linear-gradient(180deg, {COPPER}, {COPPER_DARK}) !important; | |
| border-color: {COPPER_DARK} !important; | |
| color: {SURFACE} !important; | |
| transform: translateY(-1px) !important; | |
| }} | |
| .pager-row button:disabled {{ | |
| opacity: 0.38 !important; | |
| cursor: not-allowed !important; | |
| transform: none !important; | |
| }} | |
| /* Page number input: compact mono digit field, no label overhead */ | |
| #pager-input {{ | |
| background: transparent !important; | |
| border: none !important; | |
| box-shadow: none !important; | |
| padding: 0 !important; | |
| margin: 0 !important; | |
| min-width: 0 !important; | |
| width: 64px !important; | |
| flex: 0 0 64px !important; | |
| }} | |
| #pager-input .label-wrap {{ display: none !important; }} | |
| #pager-input input[type=number] {{ | |
| width: 64px !important; | |
| height: 42px !important; | |
| padding: 0 8px !important; | |
| border-radius: 8px !important; | |
| border: 1px solid #B5642E59 !important; | |
| background: {APP_BG} !important; | |
| color: {INK} !important; | |
| font-family: {MONO} !important; | |
| font-size: 16px !important; | |
| font-weight: 400 !important; | |
| font-variant-numeric: tabular-nums !important; | |
| text-align: center !important; | |
| -moz-appearance: textfield !important; | |
| }} | |
| #pager-input input[type=number]::-webkit-inner-spin-button, | |
| #pager-input input[type=number]::-webkit-outer-spin-button {{ | |
| -webkit-appearance: none !important; margin: 0 !important; | |
| }} | |
| #pager-input input[type=number]:focus {{ | |
| outline: none !important; | |
| border-color: {COPPER} !important; | |
| box-shadow: 0 0 0 3px #B5642E1A !important; | |
| }} | |
| /* "of N · M records" label: mono numbers, muted prose. NOTE: keep the <p> as | |
| inline/normal flow (NOT flex) — flex on the paragraph collapses the spaces | |
| between "of", the bold count and "records". */ | |
| .pager-row .pager-total {{ | |
| display: flex !important; | |
| align-items: center !important; | |
| margin: 0 6px !important; | |
| }} | |
| .pager-row .pager-total p {{ | |
| color: {MUTED} !important; | |
| font-family: {BODY} !important; | |
| font-size: 15px !important; | |
| white-space: nowrap !important; | |
| margin: 0 !important; | |
| line-height: 1 !important; | |
| }} | |
| .pager-row .pager-total strong {{ | |
| font-family: {MONO} !important; | |
| font-size: 15px !important; | |
| font-weight: 600 !important; | |
| color: {INK} !important; | |
| font-variant-numeric: tabular-nums !important; | |
| }} | |
| #candidates-grid td, #candidates-grid th {{ padding: 9px 12px !important; border-bottom: 1px solid {HAIRLINE} !important; border-right: none !important; white-space: nowrap !important; }} | |
| #candidates-grid td .cell-wrap, #candidates-grid td .text {{ white-space: nowrap !important; overflow: visible !important; }} | |
| #candidates-grid thead th {{ background: {SURFACE} !important; border-bottom: 1px solid {LINE} !important; }} | |
| #candidates-grid thead th .header-button, | |
| #candidates-grid thead th .header-button span, | |
| #candidates-grid thead th .header-content span {{ | |
| color: {MUTED} !important; background: transparent !important; | |
| font-family: {MONO} !important; font-size: 10.5px !important; font-weight: 600 !important; | |
| letter-spacing: .02em !important; text-transform: none !important; cursor: default !important; | |
| }} | |
| #candidates-grid thead th .header-button {{ background: transparent !important; border: none !important; box-shadow: none !important; }} | |
| /* Per-column "â‹®" menu button (sort / filter flyout). Kept visible but quiet: | |
| transparent square, muted glyph, soft copper hover. */ | |
| #candidates-grid .cell-menu-button {{ | |
| background: transparent !important; color: {MUTED} !important; | |
| border: none !important; box-shadow: none !important; outline: none !important; | |
| border-radius: 6px !important; min-width: 0 !important; width: 20px !important; | |
| padding: 0 !important; | |
| }} | |
| #candidates-grid .cell-menu-button:hover {{ background: {HAIRLINE} !important; color: {COPPER_DARK} !important; }} | |
| /* Numeric cols: # (1), Predicted logKa (4), True logKa (5) — mono, right-aligned | |
| (a Host column was inserted at col 3, shifting the numeric columns right by one). */ | |
| #candidates-grid td:nth-child(1), #candidates-grid td:nth-child(4), #candidates-grid td:nth-child(5) {{ text-align: right !important; }} | |
| #candidates-grid td:nth-child(1) .text, #candidates-grid td:nth-child(4) .text, #candidates-grid td:nth-child(5) .text {{ | |
| font-family: {MONO} !important; font-variant-numeric: tabular-nums; | |
| }} | |
| #candidates-grid td:nth-child(6) .text {{ font-family: {MONO} !important; font-size: 11px !important; }} /* SMILES */ | |
| #candidates-grid tbody tr td {{ background: {SURFACE} !important; transition: background-color .12s ease; }} | |
| #candidates-grid tbody tr:nth-child(even) td {{ background: #F8F3EA !important; }} | |
| #candidates-grid tbody tr {{ cursor: pointer; }} | |
| #candidates-grid tbody tr:hover td {{ background: #F1E9DB !important; }} | |
| /* Only remove the vertical cap (the old 62vh re-added a vertical scrollbar and | |
| fought the pager). Leave horizontal scroll to Gradio's native .table-wrap/table | |
| pair — it syncs them on drag; overriding their overflow desynced them and blanked | |
| the right-hand columns. Pagination keeps each page short, so no vertical scroll. */ | |
| #candidates-grid .table-wrap {{ max-height: none !important; }} | |
| /* Quiet, compact Refresh button (was a full-width dark default 'secondary') */ | |
| .refresh-btn, button.refresh-btn {{ | |
| width: auto !important; min-width: 110px !important; max-width: 150px !important; | |
| align-self: flex-start !important; flex: 0 0 auto !important; | |
| background: {SURFACE} !important; color: {INK} !important; | |
| border: 1px solid {LINE} !important; box-shadow: none !important; | |
| border-radius: 10px !important; font-weight: 600 !important; font-size: 13px !important; | |
| }} | |
| .refresh-btn:hover, button.refresh-btn:hover {{ background: {APP_BG} !important; border-color: {COPPER} !important; color: {COPPER_DARK} !important; }} | |
| /* ── Column chooser (Label-Studio-style multiselect over the discovery board) ── */ | |
| .col-chooser {{ margin-top: 4px !important; }} | |
| .col-chooser .wrap-inner, .col-chooser input {{ background: {APP_BG} !important; }} | |
| /* Selected-column chips: copper-tinted tokens so the active view reads at a glance */ | |
| .col-chooser .token, .col-chooser .secondary-wrap .token {{ | |
| background: #B5642E16 !important; color: {COPPER_DARK} !important; | |
| border: 1px solid #B5642E3A !important; border-radius: 999px !important; | |
| font-family: "Hanken Grotesk", ui-sans-serif, sans-serif !important; | |
| font-size: 12px !important; font-weight: 600 !important; | |
| }} | |
| .col-chooser .token .token-remove {{ color: {COPPER_DARK} !important; }} | |
| /* ── Prompt preview ── */ | |
| .ph {{ font-family: {MONO}; color: {COPPER}; background: #B5642E12; padding: 0 4px; border-radius: 4px; font-size: 12.5px; }} | |
| .user-prompt-view {{ font-size: 13px; color: {INK}; line-height: 1.6; }} | |
| .user-prompt-view table {{ width: 100%; border-collapse: collapse; margin: 8px 0; }} | |
| .user-prompt-view td {{ padding: 4px 8px; border-bottom: 1px solid {HAIRLINE}; }} | |
| .user-prompt-view td:first-child {{ color: {INK}; }} | |
| .user-prompt-view td:last-child {{ font-family: {MONO}; }} | |
| /* Footer */ | |
| footer {{ display: none !important; }} | |
| """ | |