"""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 = ( "" "" "" ) _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 , 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 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 (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 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 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; }} """
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; }} """