Sync UI from kosmonautical/openhands-index-paul with show_all_labels feature preserved - Add OpenHands-Design typography (Inter + JetBrains Mono fonts) - Update top navigation with script for proper routing - Add show_all_labels toggle to scatter plots (preserved from original) - Apply neutral chart gridlines to visualizations - Various styling and layout improvements

#28
Files changed (8) hide show
  1. about.py +95 -94
  2. alternative_agents_page.py +22 -21
  3. app.py +486 -118
  4. constants.py +5 -2
  5. content.py +789 -227
  6. main_page.py +88 -81
  7. ui_components.py +21 -35
  8. visualizations.py +6 -1
about.py CHANGED
@@ -2,110 +2,111 @@ import gradio as gr
2
 
3
 
4
  def build_page():
5
- with gr.Column(elem_id="about-page-content-wrapper"):
6
- # --- Section 1: About ---
7
- gr.HTML(
8
- """
9
- <h2>About</h2>
10
- <p>
11
- OpenHands Index tracks AI coding agent performance across software engineering benchmarks, providing a unified view of both accuracy and cost efficiency.
12
- </p>
13
- """
14
- )
15
- gr.Markdown("---", elem_classes="divider-line")
 
16
 
17
- # --- Section 2: Benchmark Details ---
18
- gr.HTML(
19
- """
20
- <h2>Benchmark Details</h2>
21
- <p>We evaluate agents across five categories:</p>
22
- <ul class="info-list">
23
- <li><strong>Issue Resolution:</strong> <a href="https://www.swebench.com/" target="_blank">SWE-bench Verified</a> — 500 instances</li>
24
- <li><strong>Frontend:</strong> <a href="https://github.com/OpenHands/SWE-bench-multimodal" target="_blank">SWE-bench Multimodal</a> — 617 instances</li>
25
- <li><strong>Greenfield:</strong> <a href="https://github.com/commit-0/commit0" target="_blank">Commit0</a> — 16 libraries (lite split)</li>
26
- <li><strong>Testing:</strong> <a href="https://github.com/logic-star-ai/swt-bench" target="_blank">SWT-bench Verified</a> — 433 instances</li>
27
- <li><strong>Information Gathering:</strong> <a href="https://huggingface.co/gaia-benchmark" target="_blank">GAIA</a> — 165 questions (validation split)</li>
28
- </ul>
29
- """
30
- )
31
- gr.Markdown("---", elem_classes="divider-line")
32
 
33
- # --- Section 3: Methodology ---
34
- gr.HTML(
35
- """
36
- <h2>Methodology</h2>
37
- <p><strong>Per-benchmark scores:</strong> Each benchmark reports a percentage metric (resolve rate, accuracy, or test pass rate), making scores comparable regardless of dataset size.</p>
38
- <p><strong>Average score:</strong> Macro-average across all five categories with equal weighting.</p>
39
- <p><strong>Cost &amp; Runtime:</strong> Average USD and seconds per task instance.</p>
40
- <p>All evaluations use the <a href="https://github.com/OpenHands/software-agent-sdk" target="_blank">OpenHands Agent SDK</a> with identical configurations per model.</p>
41
- """
42
- )
43
- gr.Markdown("---", elem_classes="divider-line")
44
 
45
- # --- Section 4: API Access ---
46
- gr.HTML(
47
- """
48
- <h2>API Access</h2>
49
- <p>Access leaderboard data programmatically via our REST API:</p>
50
- <ul class="info-list">
51
- <li><a href="https://index.openhands.dev/api/docs" target="_blank">Interactive API Documentation</a> - Swagger UI with all endpoints</li>
52
- <li><a href="https://index.openhands.dev/api/leaderboard" target="_blank">/api/leaderboard</a> - Full leaderboard with scores and metadata</li>
53
- <li><a href="https://index.openhands.dev/api/categories" target="_blank">/api/categories</a> - List of benchmark categories</li>
54
- </ul>
55
- <p style="margin-top: 10px;"><strong>Example:</strong></p>
56
- <pre class="citation-block" style="font-size: 0.9em;">curl "https://index.openhands.dev/api/leaderboard?limit=5"</pre>
57
- """
58
- )
59
- gr.Markdown("---", elem_classes="divider-line")
60
 
61
- # --- Section 5: Resources ---
62
- gr.HTML(
63
- """
64
- <h2>Resources</h2>
65
- <ul class="info-list">
66
- <li><a href="https://github.com/OpenHands/OpenHands" target="_blank">OpenHands</a> - The main OpenHands repository</li>
67
- <li><a href="https://github.com/OpenHands/software-agent-sdk" target="_blank">Software Agent SDK</a> - The agent code used for evaluation</li>
68
- <li><a href="https://github.com/OpenHands/benchmarks" target="_blank">Benchmarks</a> - The benchmarking code</li>
69
- <li><a href="https://github.com/OpenHands/openhands-index-results" target="_blank">Results</a> - Raw evaluation results</li>
70
- </ul>
71
- """
72
- )
73
- gr.Markdown("---", elem_classes="divider-line")
74
 
75
- # --- Section 5: Contact ---
76
- gr.HTML(
77
- """
78
- <h2>Contact</h2>
79
- <p>
80
- Questions or feedback? Join us on <a href="https://dub.sh/openhands" target="_blank">Slack</a>.
81
- </p>
82
- """
83
- )
84
- gr.Markdown("---", elem_classes="divider-line")
85
 
86
- # --- Section 6: Acknowledgements ---
87
- gr.HTML(
88
- """
89
- <h2>Acknowledgements</h2>
90
- <p>
91
- The leaderboard interface is adapted from the
92
- <a href="https://huggingface.co/spaces/allenai/asta-bench-leaderboard" target="_blank">AstaBench Leaderboard</a>
93
- by Allen Institute for AI.
94
- </p>
95
- """
96
- )
97
- gr.Markdown("---", elem_classes="divider-line")
98
 
99
- # --- Section 7: Citation ---
100
- gr.HTML(
101
- """
102
- <h2>Citation</h2>
103
- <pre class="citation-block">
104
  @misc{openhandsindex2025,
105
  title={OpenHands Index: A Comprehensive Leaderboard for AI Coding Agents},
106
  author={OpenHands Team},
107
  year={2025},
108
  howpublished={https://index.openhands.dev}
109
  }</pre>
110
- """
111
- )
 
2
 
3
 
4
  def build_page():
5
+ with gr.Column(elem_id="page-content-wrapper"):
6
+ with gr.Column(elem_id="about-page-content-wrapper"):
7
+ # --- Section 1: About ---
8
+ gr.HTML(
9
+ """
10
+ <h2>About</h2>
11
+ <p>
12
+ OpenHands Index tracks AI coding agent performance across software engineering benchmarks, providing a unified view of both accuracy and cost efficiency.
13
+ </p>
14
+ """
15
+ )
16
+ gr.Markdown("---", elem_classes="divider-line")
17
 
18
+ # --- Section 2: Benchmark Details ---
19
+ gr.HTML(
20
+ """
21
+ <h2>Benchmark Details</h2>
22
+ <p>We evaluate agents across five categories:</p>
23
+ <ul class="info-list">
24
+ <li><strong>Issue Resolution:</strong> <a href="https://www.swebench.com/" target="_blank">SWE-bench Verified</a> — 500 instances</li>
25
+ <li><strong>Frontend:</strong> <a href="https://github.com/OpenHands/SWE-bench-multimodal" target="_blank">SWE-bench Multimodal</a> — 617 instances</li>
26
+ <li><strong>Greenfield:</strong> <a href="https://github.com/commit-0/commit0" target="_blank">Commit0</a> — 16 libraries (lite split)</li>
27
+ <li><strong>Testing:</strong> <a href="https://github.com/logic-star-ai/swt-bench" target="_blank">SWT-bench Verified</a> — 433 instances</li>
28
+ <li><strong>Information Gathering:</strong> <a href="https://huggingface.co/gaia-benchmark" target="_blank">GAIA</a> — 165 questions (validation split)</li>
29
+ </ul>
30
+ """
31
+ )
32
+ gr.Markdown("---", elem_classes="divider-line")
33
 
34
+ # --- Section 3: Methodology ---
35
+ gr.HTML(
36
+ """
37
+ <h2>Methodology</h2>
38
+ <p><strong>Per-benchmark scores:</strong> Each benchmark reports a percentage metric (resolve rate, accuracy, or test pass rate), making scores comparable regardless of dataset size.</p>
39
+ <p><strong>Average score:</strong> Macro-average across all five categories with equal weighting.</p>
40
+ <p><strong>Cost &amp; Runtime:</strong> Average USD and seconds per task instance.</p>
41
+ <p>All evaluations use the <a href="https://github.com/OpenHands/software-agent-sdk" target="_blank">OpenHands Agent SDK</a> with identical configurations per model.</p>
42
+ """
43
+ )
44
+ gr.Markdown("---", elem_classes="divider-line")
45
 
46
+ # --- Section 4: API Access ---
47
+ gr.HTML(
48
+ """
49
+ <h2>API Access</h2>
50
+ <p>Access leaderboard data programmatically via our REST API:</p>
51
+ <ul class="info-list">
52
+ <li><a href="https://index.openhands.dev/api/docs" target="_blank">Interactive API Documentation</a> - Swagger UI with all endpoints</li>
53
+ <li><a href="https://index.openhands.dev/api/leaderboard" target="_blank">/api/leaderboard</a> - Full leaderboard with scores and metadata</li>
54
+ <li><a href="https://index.openhands.dev/api/categories" target="_blank">/api/categories</a> - List of benchmark categories</li>
55
+ </ul>
56
+ <p style="margin-top: 10px;"><strong>Example:</strong></p>
57
+ <pre class="citation-block" style="font-size: 0.9em;">curl "https://index.openhands.dev/api/leaderboard?limit=5"</pre>
58
+ """
59
+ )
60
+ gr.Markdown("---", elem_classes="divider-line")
61
 
62
+ # --- Section 5: Resources ---
63
+ gr.HTML(
64
+ """
65
+ <h2>Resources</h2>
66
+ <ul class="info-list">
67
+ <li><a href="https://github.com/OpenHands/OpenHands" target="_blank">OpenHands</a> - The main OpenHands repository</li>
68
+ <li><a href="https://github.com/OpenHands/software-agent-sdk" target="_blank">Software Agent SDK</a> - The agent code used for evaluation</li>
69
+ <li><a href="https://github.com/OpenHands/benchmarks" target="_blank">Benchmarks</a> - The benchmarking code</li>
70
+ <li><a href="https://github.com/OpenHands/openhands-index-results" target="_blank">Results</a> - Raw evaluation results</li>
71
+ </ul>
72
+ """
73
+ )
74
+ gr.Markdown("---", elem_classes="divider-line")
75
 
76
+ # --- Section 5: Contact ---
77
+ gr.HTML(
78
+ """
79
+ <h2>Contact</h2>
80
+ <p>
81
+ Questions or feedback? Join us on <a href="https://dub.sh/openhands" target="_blank">Slack</a>.
82
+ </p>
83
+ """
84
+ )
85
+ gr.Markdown("---", elem_classes="divider-line")
86
 
87
+ # --- Section 6: Acknowledgements ---
88
+ gr.HTML(
89
+ """
90
+ <h2>Acknowledgements</h2>
91
+ <p>
92
+ The leaderboard interface is adapted from the
93
+ <a href="https://huggingface.co/spaces/allenai/asta-bench-leaderboard" target="_blank">AstaBench Leaderboard</a>
94
+ by Allen Institute for AI.
95
+ </p>
96
+ """
97
+ )
98
+ gr.Markdown("---", elem_classes="divider-line")
99
 
100
+ # --- Section 7: Citation ---
101
+ gr.HTML(
102
+ """
103
+ <h2>Citation</h2>
104
+ <pre class="citation-block">
105
  @misc{openhandsindex2025,
106
  title={OpenHands Index: A Comprehensive Leaderboard for AI Coding Agents},
107
  author={OpenHands Team},
108
  year={2025},
109
  howpublished={https://index.openhands.dev}
110
  }</pre>
111
+ """
112
+ )
alternative_agents_page.py CHANGED
@@ -76,28 +76,29 @@ def _append_openhands_shared_models(
76
 
77
 
78
  def build_page():
79
- gr.HTML(ALTERNATIVE_AGENTS_INTRO)
 
80
 
81
- gr.Markdown("---")
82
 
83
- test_df, test_tag_map = get_full_leaderboard_data(
84
- "test",
85
- agent_filter=SimpleLeaderboardViewer.AGENT_FILTER_ALTERNATIVE,
86
- )
87
-
88
- if test_df.empty:
89
- gr.Markdown(
90
- "No alternative agent submissions yet. New runs land in "
91
- "`alternative_agents/{type}/{model}/` in "
92
- "[openhands-index-results](https://github.com/OpenHands/openhands-index-results)."
93
  )
94
- return
95
-
96
- test_df = _append_openhands_shared_models(test_df, split="test")
97
 
98
- create_leaderboard_display(
99
- full_df=test_df,
100
- tag_map=test_tag_map,
101
- category_name="Overall",
102
- split_name="test",
103
- )
 
 
 
 
 
 
 
 
 
 
 
76
 
77
 
78
  def build_page():
79
+ with gr.Column(elem_id="page-content-wrapper"):
80
+ gr.HTML(ALTERNATIVE_AGENTS_INTRO)
81
 
82
+ gr.Markdown("---")
83
 
84
+ test_df, test_tag_map = get_full_leaderboard_data(
85
+ "test",
86
+ agent_filter=SimpleLeaderboardViewer.AGENT_FILTER_ALTERNATIVE,
 
 
 
 
 
 
 
87
  )
 
 
 
88
 
89
+ if test_df.empty:
90
+ gr.Markdown(
91
+ "No alternative agent submissions yet. New runs land in "
92
+ "`alternative_agents/{type}/{model}/` in "
93
+ "[openhands-index-results](https://github.com/OpenHands/openhands-index-results)."
94
+ )
95
+ return
96
+
97
+ test_df = _append_openhands_shared_models(test_df, split="test")
98
+
99
+ create_leaderboard_display(
100
+ full_df=test_df,
101
+ tag_map=test_tag_map,
102
+ category_name="Overall",
103
+ split_name="test",
104
+ )
app.py CHANGED
@@ -3,7 +3,7 @@ import logging
3
  import sys
4
  import os
5
 
6
- from constants import FONT_FAMILY_SHORT
7
 
8
  logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
9
  logger = logging.getLogger(__name__)
@@ -24,8 +24,9 @@ except Exception as e:
24
  logger.error(f"Error during data setup: {e}", exc_info=True)
25
  logger.warning("Continuing with app startup despite error")
26
 
27
- import gradio as gr
28
  import urllib.parse
 
 
29
  from huggingface_hub import HfApi
30
  from config import LEADERBOARD_PATH, LOCAL_DEBUG
31
  from content import css
@@ -41,10 +42,16 @@ from about import build_page as build_about_page
41
  logger.info(f"All modules imported (LOCAL_DEBUG={LOCAL_DEBUG})")
42
 
43
  api = HfApi()
44
- LOGO_PATH = "assets/logo.svg"
45
 
46
  # PostHog analytics (client-side)
47
  POSTHOG_API_KEY = os.getenv("POSTHOG_API_KEY", "phc_ERBPfEE0gwNgkOBsxbHr1wh9mBsYcsw4zSLtvdA9RFg")
 
 
 
 
 
 
 
48
  posthog_script = f"""
49
  <script>
50
  !function(t,e){{var o,n,p,r;e.__SV||(window.posthog && window.posthog.__loaded)||(window.posthog=e,e._i=[],e.init=function(i,s,a){{function g(t,e){{var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){{t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){{var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e}},u.people.toString=function(){{return u.toString(1)+".people (stub)"}},o="init ss us bi os hs es ns capture Bi calculateEventProperties cs register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSurveysLoaded onSessionId getSurveys getActiveMatchingSurveys renderSurvey displaySurvey cancelPendingSurvey canRenderSurvey canRenderSurveyAsync identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException startExceptionAutocapture stopExceptionAutocapture loadToolbar get_property getSessionProperty ps vs createPersonProfile gs Zr ys opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing get_explicit_consent_status is_capturing clear_opt_in_out_capturing ds debug O fs getPageViewId captureTraceFeedback captureTraceMetric Yr".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])}},e.__SV=1)}}(document,window.posthog||[]);
@@ -77,28 +84,196 @@ redirect_script = """
77
  </script>
78
  """
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  # JavaScript to fix navigation links to use relative paths (avoids domain mismatch when behind proxy)
81
  fix_nav_links_script = """
82
  <script>
83
  (function() {
84
  function fixNavLinks() {
85
- // Find all navigation links in the nav-holder
86
- const navLinks = document.querySelectorAll('.nav-holder nav a');
87
  navLinks.forEach(link => {
88
  const href = link.getAttribute('href');
89
  if (href) {
90
- // Extract the pathname from the href (works with both relative and absolute URLs)
91
  try {
92
  const url = new URL(href, window.location.origin);
93
- // Only update if the pathname starts with /
94
- if (url.pathname.startsWith('/')) {
 
95
  link.setAttribute('href', url.pathname);
96
  }
97
  } catch (e) {
98
- // If URL parsing fails, leave the href as-is
99
  }
100
  }
101
- // Remove target="_blank" from nav links to prevent opening in new tab
102
  link.removeAttribute('target');
103
  });
104
  }
@@ -191,8 +366,8 @@ function updateChartsForDarkMode() {
191
  'paper_bgcolor': isDark ? '#1f1f1f' : 'white',
192
  'plot_bgcolor': isDark ? '#1f1f1f' : 'white',
193
  'font.color': isDark ? '#e0e0e0' : '#333',
194
- 'xaxis.gridcolor': isDark ? '#444' : '#eee',
195
- 'yaxis.gridcolor': isDark ? '#444' : '#eee'
196
  });
197
  }
198
  });
@@ -226,133 +401,317 @@ document.addEventListener('DOMContentLoaded', () => {
226
  </script>
227
  """
228
  # --- Theme Definition ---
229
- # Color scheme aligned with OpenHands brand (from openhands-ui/tokens.css)
230
- # Primary: Yellow (#FFE165), Neutral: Grey scale, Accents: Green (#BCFF8C), Red (#FF684E)
 
 
 
 
231
  theme = gr.themes.Base(
232
- # Primary hue - Yellow (OpenHands brand color)
233
  primary_hue=gr.themes.Color(
234
- c50="#FFFCF0", c100="#FFF3C0", c200="#FFEEAA", c300="#FFEA92", c400="#FFE57B",
235
- c500="#FFE165", c600="#DCC257", c700="#BBA54A", c800="#99873D", c900="#76682F", c950="#534921"
236
  ),
237
- # Secondary hue - Green accent (from OpenHands palette)
238
- secondary_hue=gr.themes.Color(
239
- c50="#F8FFF4", c100="#E4FFD0", c200="#DAFFBF", c300="#CFFFAD", c400="#C6FF9D",
240
- c500="#BCFF8C", c600="#A2DC79", c700="#8ABB67", c800="#719954", c900="#577641", c950="#3D532E"
241
- ),
242
- # Neutral hue - Grey scale (OpenHands dark mode colors)
243
  neutral_hue=gr.themes.Color(
244
- c50="#F7F8FB", c100="#EBEDF3", c200="#D4D8E7", c300="#B1B9D3", c400="#82889B",
245
- c500="#525662", c600="#3A3C45", c700="#2F3137", c800="#222328", c900="#18191C", c950="#0D0D0F"
246
  ),
247
- font=[FONT_FAMILY_SHORT, 'sans-serif'],
248
- font_mono=['monospace'],
249
  ).set(
250
- body_text_color='*neutral_950',
251
- body_text_color_subdued='*neutral_700',
252
- body_text_color_subdued_dark='*neutral_300',
253
- body_text_color_dark='*neutral_50',
254
- background_fill_primary='*neutral_50',
255
- background_fill_primary_dark='*neutral_900',
256
- background_fill_secondary='*neutral_100',
257
- background_fill_secondary_dark='*neutral_800',
258
- border_color_accent='*primary_500',
259
- border_color_accent_subdued='*neutral_300',
260
- border_color_accent_subdued_dark='*neutral_600',
261
- color_accent='*primary_500',
262
- color_accent_soft='*neutral_200',
263
- color_accent_soft_dark='*neutral_800',
264
- link_text_color='*neutral_700',
265
- link_text_color_dark='*neutral_300',
266
- link_text_color_active_dark='*primary_500',
267
- link_text_color_hover_dark='*primary_400',
268
- link_text_color_visited_dark='*neutral_400',
269
- table_even_background_fill='*neutral_100',
270
- table_even_background_fill_dark='*neutral_800',
271
- button_primary_background_fill='*primary_500',
272
- button_primary_background_fill_dark='*primary_500',
273
- button_primary_background_fill_hover='*primary_400',
274
- button_primary_background_fill_hover_dark='*primary_400',
275
- button_secondary_background_fill='*secondary_500',
276
- button_secondary_background_fill_dark='*secondary_600',
277
- button_secondary_text_color='*neutral_900',
278
- button_secondary_text_color_dark='*neutral_900',
279
- block_title_text_color='*neutral_900',
280
- button_primary_text_color='*neutral_900',
281
- block_title_text_color_dark='*neutral_50',
282
- button_primary_text_color_dark='*neutral_900',
283
- block_border_color='*neutral_300',
284
- block_border_color_dark='*neutral_700',
285
- block_background_fill_dark='*neutral_900',
286
- block_background_fill='*neutral_50',
287
- checkbox_label_text_color='*neutral_900',
288
- checkbox_label_background_fill='*neutral_200',
289
- checkbox_label_background_fill_dark='*neutral_700',
290
- checkbox_background_color_selected='*primary_500',
291
- checkbox_background_color_selected_dark='*primary_500',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  )
 
 
 
293
  try:
294
- with open(LOGO_PATH, "r") as f:
295
- svg_content = f.read()
296
- encoded_svg = urllib.parse.quote(svg_content)
297
- home_icon_data_uri = f"data:image/svg+xml,{encoded_svg}"
298
- except FileNotFoundError:
299
- logger.warning(f"Home icon file not found at {LOGO_PATH}")
300
- home_icon_data_uri = "none"
301
-
302
- # Load dark mode logo (PNG)
303
- LOGO_DARK_PATH = "assets/logo-dark.png"
304
  try:
305
- import base64
306
- with open(LOGO_DARK_PATH, "rb") as f:
307
- dark_logo_content = f.read()
308
- encoded_dark_logo = base64.b64encode(dark_logo_content).decode('utf-8')
309
- home_icon_dark_data_uri = f"data:image/png;base64,{encoded_dark_logo}"
310
- except FileNotFoundError:
311
- logger.warning(f"Dark mode logo file not found at {LOGO_DARK_PATH}")
312
- home_icon_dark_data_uri = home_icon_data_uri # Fallback to light logo
313
-
314
- # --- This is the final CSS ---
315
- final_css = css + f"""
316
- /* --- Find the "Home" button and replace its text with an icon --- */
317
- .nav-holder nav a[href$="/"] {{
318
- display: none !important;
319
- }}
320
- .nav-holder nav a[href*="/home"] {{
321
- grid-row: 1 !important;
322
- grid-column: 1 !important;
323
- justify-self: start !important;
324
- display: flex !important;
325
- align-items: center !important;
326
- justify-content: center !important;
327
 
328
- /* 2. Hide the original "Home" text */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  font-size: 0 !important;
 
330
  text-indent: -9999px;
331
-
332
- /* 3. Apply the icon as the background (light mode) */
333
- background-image: url("{home_icon_data_uri}") !important;
 
 
 
 
 
 
 
 
 
 
 
334
  background-size: contain !important;
335
  background-repeat: no-repeat !important;
336
- background-position: center !important;
337
-
338
- width: 240px !important;
339
- height: 50px !important;
340
- padding: 0 !important;
341
  border: none !important;
342
- outline: none !important;
 
 
343
  }}
344
-
345
- /* Dark mode logo override */
346
- .dark .nav-holder nav a[href*="/home"] {{
347
- background-image: url("{home_icon_dark_data_uri}") !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  }}
349
  """
 
350
  # --- Gradio App Definition ---
351
  logger.info("Creating Gradio application")
352
  demo = gr.Blocks(
353
  theme=theme,
354
  css=final_css,
355
- head=posthog_script + scroll_script + redirect_script + fix_nav_links_script + tooltip_script + dark_mode_script,
 
 
 
 
 
 
 
 
356
  title="OpenHands Index",
357
  )
358
 
@@ -424,7 +783,16 @@ if __name__ == "__main__":
424
  # Respect platform port/host if provided (e.g., OpenHands runtime)
425
  port = int(os.environ.get("PORT", os.environ.get("GRADIO_SERVER_PORT", 7860)))
426
  host = os.environ.get("HOST", os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0"))
427
- logger.info(f"Launching app on {host}:{port}")
428
- uvicorn.run(app, host=host, port=port)
 
 
 
 
 
 
 
 
 
429
  logger.info("App launched successfully")
430
 
 
3
  import sys
4
  import os
5
 
6
+ from constants import FONT_MONO_NAME, FONT_FAMILY_SHORT
7
 
8
  logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
9
  logger = logging.getLogger(__name__)
 
24
  logger.error(f"Error during data setup: {e}", exc_info=True)
25
  logger.warning("Continuing with app startup despite error")
26
 
 
27
  import urllib.parse
28
+
29
+ import gradio as gr
30
  from huggingface_hub import HfApi
31
  from config import LEADERBOARD_PATH, LOCAL_DEBUG
32
  from content import css
 
42
  logger.info(f"All modules imported (LOCAL_DEBUG={LOCAL_DEBUG})")
43
 
44
  api = HfApi()
 
45
 
46
  # PostHog analytics (client-side)
47
  POSTHOG_API_KEY = os.getenv("POSTHOG_API_KEY", "phc_ERBPfEE0gwNgkOBsxbHr1wh9mBsYcsw4zSLtvdA9RFg")
48
+ # OpenHands-Design typography (matches OpenHands-Design/index.html)
49
+ DESIGN_FONTS_LINK = """
50
+ <link rel="preconnect" href="https://fonts.googleapis.com">
51
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
52
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
53
+ """
54
+
55
  posthog_script = f"""
56
  <script>
57
  !function(t,e){{var o,n,p,r;e.__SV||(window.posthog && window.posthog.__loaded)||(window.posthog=e,e._i=[],e.init=function(i,s,a){{function g(t,e){{var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){{t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){{var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e}},u.people.toString=function(){{return u.toString(1)+".people (stub)"}},o="init ss us bi os hs es ns capture Bi calculateEventProperties cs register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSurveysLoaded onSessionId getSurveys getActiveMatchingSurveys renderSurvey displaySurvey cancelPendingSurvey canRenderSurvey canRenderSurveyAsync identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException startExceptionAutocapture stopExceptionAutocapture loadToolbar get_property getSessionProperty ps vs createPersonProfile gs Zr ys opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing get_explicit_consent_status is_capturing clear_opt_in_out_capturing ds debug O fs getPageViewId captureTraceFeedback captureTraceMetric Yr".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])}},e.__SV=1)}}(document,window.posthog||[]);
 
84
  </script>
85
  """
86
 
87
+ # Gradio 5.30+ does not use .nav-holder — tag the real multipage links, then style via CSS
88
+ # (see: OpenHands-Design index.html top nav)
89
+ # IMPORTANT: do not strip all classes on every run — that unstyles the bar, changes layout, and
90
+ # makes getBoundingClientRect() unstable (layout thrash / "jumping" nav).
91
+ oh_top_nav_script = """
92
+ <script>
93
+ (function() {
94
+ const ROUTE_PATHS = new Set(['/home', '/issue-resolution', '/greenfield', '/frontend', '/testing', '/information-gathering', '/about', '/alternative-agents']);
95
+ var lastBar = null;
96
+ var lastLinks = null;
97
+ function pathKey(anchor) {
98
+ try {
99
+ var p = new URL(anchor.getAttribute('href') || '', window.location.origin).pathname.replace(/\\/$/, '') || '/';
100
+ if (p === '/' || p === '') p = '/home';
101
+ return p;
102
+ } catch (e) { return null; }
103
+ }
104
+ function routePath(anchor) {
105
+ var p = pathKey(anchor);
106
+ return p && ROUTE_PATHS.has(p) ? p : null;
107
+ }
108
+ function getTopRowRouteLinks() {
109
+ const g = document.querySelector('gradio-app');
110
+ if (!g) return [];
111
+ /* Only links inside the multipage <nav> — not intro/Markdown (avoids byPath picking a
112
+ body <a> first, and drops the #page-content-wrapper filter that could hide the bar). */
113
+ function routeLinksInNav(nav) {
114
+ if (!nav) return [];
115
+ return Array.from(nav.querySelectorAll('a[href]')).filter(function (a) { return routePath(a); });
116
+ }
117
+ /* Prefer the <nav> that contains the Home wordmark (avoids wrong <nav> + works if Svelte class hash changes). */
118
+ var bar = getRouteNav() || g.querySelector('nav.svelte-ti537g');
119
+ var candidates = routeLinksInNav(bar);
120
+ if (candidates.length < 1) {
121
+ const navs = g.querySelectorAll('nav');
122
+ for (var i = 0; i < navs.length; i++) {
123
+ const c = routeLinksInNav(navs[i]);
124
+ if (c.length > 0) { bar = navs[i]; candidates = c; break; }
125
+ }
126
+ }
127
+ if (candidates.length < 1) return [];
128
+ /* Single top-nav link: still tag nav so wordmark/oh-top-nav CSS runs (avoids “missing logo / links stuck left” until 2+ links exist) */
129
+ if (candidates.length === 1) {
130
+ const a = candidates[0];
131
+ const nav = a.closest("nav");
132
+ if (nav && g.contains(nav)) {
133
+ return [a];
134
+ }
135
+ return [];
136
+ }
137
+ const byPath = new Map();
138
+ for (const a of candidates) {
139
+ const p = pathKey(a);
140
+ if (!p || !ROUTE_PATHS.has(p)) continue;
141
+ if (!byPath.has(p)) {
142
+ byPath.set(p, a);
143
+ } else {
144
+ const cur = byPath.get(p);
145
+ const curP = new URL(cur.getAttribute('href') || '', window.location.origin).pathname;
146
+ const newP = new URL(a.getAttribute('href') || '', window.location.origin).pathname;
147
+ if (p === '/home' && (curP === '/' || curP === '') && newP === '/home') {
148
+ byPath.set(p, a);
149
+ }
150
+ }
151
+ }
152
+ const unique = Array.from(byPath.values());
153
+ const tops = unique.map((x) => x.getBoundingClientRect().top);
154
+ const minTop = Math.min.apply(null, tops);
155
+ /* ~1 row; always keep /home (wordmark) even if y differs (e.g. from position:absolute layout) */
156
+ return unique.filter((a) => {
157
+ if (pathKey(a) === '/home') return true;
158
+ return Math.abs(a.getBoundingClientRect().top - minTop) < 22;
159
+ });
160
+ }
161
+ function lca(els) {
162
+ if (els.length < 2) return els[0] && els[0].parentElement;
163
+ const chain = (node) => { const a = []; for (let n = node; n; n = n.parentElement) a.push(n); return a; };
164
+ let c = chain(els[0]);
165
+ for (let i = 1; i < els.length; i++) {
166
+ const d = new Set(chain(els[i]));
167
+ c = c.filter((n) => d.has(n));
168
+ }
169
+ return c[0] || null;
170
+ }
171
+ function sameLinkSet(a, b) {
172
+ if (!a || !b || a.length !== b.length) return false;
173
+ const sb = new Set(b);
174
+ for (var i = 0; i < a.length; i++) { if (!sb.has(a[i])) return false; }
175
+ return true;
176
+ }
177
+ function isDetached(el) { return el && !document.body.contains(el); }
178
+ function clearBar() {
179
+ if (lastBar) { lastBar.classList.remove('oh-top-nav'); }
180
+ if (lastLinks) { lastLinks.forEach(function (a) { a.classList.remove('oh-nav-link'); }); }
181
+ lastBar = null;
182
+ lastLinks = null;
183
+ }
184
+ /**
185
+ * Multipage top bar: must NOT use the first <nav> in the app (there can be others).
186
+ * The wordmark (first /home) lives in the same <nav> as the route row — that is the bar.
187
+ * Svelte class hash can change, so do not rely only on nav.svelte-ti537g.
188
+ */
189
+ function getRouteNav() {
190
+ var g = document.querySelector('gradio-app');
191
+ if (!g) return null;
192
+ var h = g.querySelector('a[href*="/home"]');
193
+ if (h) {
194
+ var n = h.closest('nav');
195
+ if (n && g.contains(n)) { return n; }
196
+ }
197
+ n = g.querySelector('nav.oh-top-nav, nav.svelte-ti537g, .nav-holder nav, nav[role="navigation"]');
198
+ if (n) return n;
199
+ n = g.querySelector('nav');
200
+ return n || null;
201
+ }
202
+ function applyOhNav() {
203
+ if (lastLinks && (lastLinks.some(isDetached) || (lastBar && isDetached(lastBar)))) {
204
+ lastBar = null;
205
+ lastLinks = null;
206
+ }
207
+ const links = getTopRowRouteLinks();
208
+ if (links.length < 1) {
209
+ if (lastBar && lastLinks && !lastLinks.some(isDetached)) {
210
+ return;
211
+ }
212
+ if (lastBar) { clearBar(); }
213
+ return;
214
+ }
215
+ var row = lca(links);
216
+ if (!row || row === document.body || (row.tagName && row.tagName.toLowerCase() === 'gradio-app')) {
217
+ row = links[0].parentElement;
218
+ }
219
+ if (lastBar === row && lastLinks && sameLinkSet(lastLinks, links)) {
220
+ return;
221
+ }
222
+ if (lastBar && lastBar !== row) { lastBar.classList.remove('oh-top-nav'); }
223
+ if (lastLinks) { lastLinks.forEach(function (a) { a.classList.remove('oh-nav-link'); }); }
224
+ if (row) { row.classList.add('oh-top-nav'); }
225
+ links.forEach(function (a) {
226
+ a.classList.add('oh-nav-link');
227
+ if (pathKey(a) === '/home') {
228
+ a.classList.add('oh-nav-wordmark');
229
+ a.setAttribute('aria-label', 'OpenHands Home'); a.setAttribute('title', 'Home');
230
+ }
231
+ });
232
+ lastBar = row;
233
+ lastLinks = links.slice();
234
+ }
235
+ var deb;
236
+ function scheduleApply() {
237
+ if (deb) { clearTimeout(deb); }
238
+ deb = setTimeout(function () { requestAnimationFrame(applyOhNav); }, 50);
239
+ }
240
+ if (document.readyState === 'loading') {
241
+ document.addEventListener('DOMContentLoaded', function () { requestAnimationFrame(applyOhNav); });
242
+ } else { requestAnimationFrame(applyOhNav); }
243
+ requestAnimationFrame(function () { requestAnimationFrame(applyOhNav); });
244
+ window.addEventListener('resize', scheduleApply, { passive: true });
245
+ var obs = new MutationObserver(scheduleApply);
246
+ var ga = document.querySelector('gradio-app');
247
+ if (ga) { obs.observe(ga, { childList: true, subtree: true }); }
248
+ else { document.addEventListener('DOMContentLoaded', function () { var g = document.querySelector('gradio-app'); if (g) obs.observe(g, { childList: true, subtree: true }); }); }
249
+ try { if (typeof queueMicrotask === 'function') { queueMicrotask(applyOhNav); } } catch (e) {}
250
+ setTimeout(applyOhNav, 0);
251
+ setTimeout(applyOhNav, 50);
252
+ setTimeout(applyOhNav, 500);
253
+ setTimeout(applyOhNav, 2000);
254
+ })();
255
+ </script>
256
+ """
257
+
258
  # JavaScript to fix navigation links to use relative paths (avoids domain mismatch when behind proxy)
259
  fix_nav_links_script = """
260
  <script>
261
  (function() {
262
  function fixNavLinks() {
263
+ const navLinks = document.querySelectorAll('gradio-app nav.oh-top-nav a[href], gradio-app nav.svelte-ti537g a[href]');
 
264
  navLinks.forEach(link => {
265
  const href = link.getAttribute('href');
266
  if (href) {
 
267
  try {
268
  const url = new URL(href, window.location.origin);
269
+ if (url.pathname === '/' || url.pathname === '') {
270
+ link.setAttribute('href', '/home');
271
+ } else if (url.pathname.startsWith('/')) {
272
  link.setAttribute('href', url.pathname);
273
  }
274
  } catch (e) {
 
275
  }
276
  }
 
277
  link.removeAttribute('target');
278
  });
279
  }
 
366
  'paper_bgcolor': isDark ? '#1f1f1f' : 'white',
367
  'plot_bgcolor': isDark ? '#1f1f1f' : 'white',
368
  'font.color': isDark ? '#e0e0e0' : '#333',
369
+ 'xaxis.gridcolor': isDark ? '#242424' : '#d4d4d4',
370
+ 'yaxis.gridcolor': isDark ? '#242424' : '#d4d4d4',
371
  });
372
  }
373
  });
 
401
  </script>
402
  """
403
  # --- Theme Definition ---
404
+ # Aligned with OpenHands-Design (see OpenHands-Design/DESIGN.md, index.html)
405
+ # Near-black canvas, white primary CTA, neutral grey scale
406
+ _MUTED_GREEN = gr.themes.Color(
407
+ c50="#f0fdf4", c100="#dcfce7", c200="#bbf7d0", c300="#86efac", c400="#4ade80",
408
+ c500="#22c55e", c600="#16a34a", c700="#15803d", c800="#166534", c900="#14532d", c950="#052e16",
409
+ )
410
  theme = gr.themes.Base(
 
411
  primary_hue=gr.themes.Color(
412
+ c50="#fafafa", c100="#f4f4f5", c200="#e4e4e7", c300="#d4d4d8", c400="#a1a1aa",
413
+ c500="#ffffff", c600="#f4f4f5", c700="#e4e4e7", c800="#d4d4d8", c900="#a1a1aa", c950="#71717a"
414
  ),
415
+ secondary_hue=_MUTED_GREEN,
 
 
 
 
 
416
  neutral_hue=gr.themes.Color(
417
+ c50="#fafafa", c100="#f4f4f4", c200="#e5e5e5", c300="#d4d4d4", c400="#a3a3a3",
418
+ c500="#737373", c600="#525252", c700="#404040", c800="#262626", c900="#171717", c950="#0d0d0d"
419
  ),
420
+ font=[FONT_FAMILY_SHORT, "system-ui", "sans-serif"],
421
+ font_mono=[FONT_MONO_NAME, "ui-monospace", "SFMono-Regular", "Menlo", "monospace"],
422
  ).set(
423
+ body_text_color="*neutral_950",
424
+ body_text_color_subdued="*neutral_600",
425
+ body_text_color_subdued_dark="*neutral_400",
426
+ body_text_color_dark="*neutral_50",
427
+ background_fill_primary="*neutral_50",
428
+ background_fill_primary_dark="*neutral_950",
429
+ background_fill_secondary="*neutral_100",
430
+ background_fill_secondary_dark="*neutral_900",
431
+ # Light: strokes match top nav (#e4e4e7); dark: DESIGN.md #242424 (--border / --input)
432
+ border_color_accent="#e4e4e7",
433
+ border_color_accent_subdued="#e4e4e7",
434
+ border_color_accent_subdued_dark="#242424",
435
+ # Primary border for inputs & dropdown chrome (maps to --border-color-primary in Gradio)
436
+ border_color_primary="#e4e4e7",
437
+ border_color_primary_dark="#242424",
438
+ color_accent="*primary_500",
439
+ color_accent_soft="*neutral_200",
440
+ color_accent_soft_dark="*neutral_800",
441
+ link_text_color="*neutral_700",
442
+ link_text_color_dark="*neutral_300",
443
+ link_text_color_active_dark="*primary_500",
444
+ link_text_color_hover_dark="*neutral_50",
445
+ link_text_color_visited_dark="*neutral_500",
446
+ table_even_background_fill="*neutral_100",
447
+ table_even_background_fill_dark="*neutral_900",
448
+ button_primary_background_fill="*primary_500",
449
+ button_primary_background_fill_dark="*primary_500",
450
+ button_primary_background_fill_hover="*primary_600",
451
+ button_primary_background_fill_hover_dark="*primary_600",
452
+ button_secondary_background_fill="*neutral_200",
453
+ button_secondary_background_fill_dark="*neutral_800",
454
+ button_secondary_text_color="*neutral_900",
455
+ button_secondary_text_color_dark="*neutral_50",
456
+ block_title_text_color="*neutral_950",
457
+ button_primary_text_color="*neutral_950",
458
+ block_title_text_color_dark="*neutral_50",
459
+ button_primary_text_color_dark="*neutral_950",
460
+ block_border_color="#e4e4e7",
461
+ block_border_color_dark="#242424",
462
+ block_background_fill_dark="*neutral_900",
463
+ block_background_fill="*neutral_50",
464
+ checkbox_label_text_color="*neutral_900",
465
+ checkbox_label_background_fill="*neutral_200",
466
+ checkbox_label_background_fill_dark="*neutral_700",
467
+ # Checkmark SVG is white; selected fill must not be white (this theme’s primary_500 = white → invisible)
468
+ checkbox_background_color_selected="*neutral_950",
469
+ checkbox_background_color_selected_dark="*neutral_600",
470
+ # OpenHands-Design §4 Inputs: rounded-md (4px), border-border, bg-muted/40, text-sm, focus ring #ccc + offset
471
+ input_radius="0.25rem",
472
+ input_border_width="1px",
473
+ input_border_color="*border_color_primary",
474
+ input_border_color_dark="#242424",
475
+ input_border_color_hover="*neutral_300",
476
+ input_border_color_hover_dark="#2e2e2e",
477
+ input_border_color_focus="#a1a1aa",
478
+ input_border_color_focus_dark="#525252",
479
+ input_background_fill="rgba(244, 244, 245, 0.75)",
480
+ input_background_fill_dark="rgba(31, 31, 31, 0.45)",
481
+ input_background_fill_hover="rgba(244, 244, 245, 0.9)",
482
+ input_background_fill_hover_dark="rgba(31, 31, 31, 0.55)",
483
+ input_background_fill_focus="rgba(229, 229, 234, 0.95)",
484
+ input_background_fill_focus_dark="rgba(31, 31, 31, 0.65)",
485
+ input_shadow="0 0 0 0 transparent",
486
+ input_shadow_dark="0 0 0 0 transparent",
487
+ input_shadow_focus="0 0 0 2px #fafafa, 0 0 0 3px #cccccc",
488
+ input_shadow_focus_dark="0 0 0 2px #0d0d0d, 0 0 0 3px #cccccc",
489
+ input_placeholder_color="*neutral_500",
490
+ input_placeholder_color_dark="#8c8c8c",
491
+ input_padding="8px 12px",
492
+ input_text_size="*text_sm",
493
+ input_text_weight="400",
494
+ # Form labels (BlockTitle): text-sm font-medium (colors set above)
495
+ block_title_text_size="*text_sm",
496
+ block_title_text_weight="500",
497
+ # Dropdown / popover elevation (§4 shadow-md)
498
+ shadow_drop="0 1px 2px 0 rgba(0, 0, 0, 0.12)",
499
+ shadow_drop_lg="0 4px 6px -1px rgba(0, 0, 0, 0.18), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
500
+ # Checkboxes: align border with inputs
501
+ checkbox_border_color="*neutral_300",
502
+ checkbox_border_color_dark="#242424",
503
+ checkbox_border_color_focus="#a1a1aa",
504
+ checkbox_border_color_focus_dark="#525252",
505
+ form_gap_width="12px",
506
  )
507
+ # Top nav wordmark: on-light = black/dark ink (light bar, far left); on-dark = light ink (dark bar)
508
+ NAV_LOGO_SVG_LIGHT = "assets/openhands-logotype-on-light.svg"
509
+ NAV_LOGO_SVG_DARK = "assets/openhands-logotype-on-dark.svg"
510
  try:
511
+ with open(NAV_LOGO_SVG_LIGHT, "r", encoding="utf-8") as _f:
512
+ _oh_nav_data_uri_light = f"data:image/svg+xml,{urllib.parse.quote(_f.read())}"
513
+ except OSError:
514
+ _oh_nav_data_uri_light = "none"
 
 
 
 
 
 
515
  try:
516
+ with open(NAV_LOGO_SVG_DARK, "r", encoding="utf-8") as _f:
517
+ _oh_nav_data_uri_dark = f"data:image/svg+xml,{urllib.parse.quote(_f.read())}"
518
+ except OSError:
519
+ _oh_nav_data_uri_dark = "none"
520
+
521
+ # Early decode to reduce first-paint logo flicker (data URI, no extra network)
522
+ NAV_LOGO_PRELOAD = (
523
+ f'<span class="oh-nav-logotype-preload" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden;clip:rect(0,0,0,0)">'
524
+ f'<img src="{_oh_nav_data_uri_light}" width="160" height="40" alt="" decoding="async" fetchpriority="high"/>'
525
+ f'<img src="{_oh_nav_data_uri_dark}" width="160" height="40" alt="" decoding="async" fetchpriority="low"/>'
526
+ f"</span>"
527
+ )
 
 
 
 
 
 
 
 
 
 
528
 
529
+ # --- This is the final CSS --- (appended after content.css so it wins the cascade for Home)
530
+ final_css = (
531
+ css
532
+ + f"""
533
+ /* Multipage: trim duplicate /, hide unstyled duplicate Gradio <a> */
534
+ gradio-app nav a[href$="/"] {{ display: none !important; }}
535
+ gradio-app .nav-holder nav a[href="/home"]:not(.oh-nav-link) {{ display: none !important; }}
536
+ /* Wordmark (Gradio /home): pinned top-left; no transition (reduces paint flicker) */
537
+ gradio-app nav.svelte-ti537g a[href*="/home"],
538
+ gradio-app .nav-holder nav a[href*="/home"],
539
+ gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"] {{
540
+ position: absolute !important;
541
+ left: 20px !important;
542
+ top: 0 !important;
543
+ bottom: 0 !important;
544
+ margin: auto 0 !important;
545
+ z-index: 2 !important;
546
+ display: inline-flex !important;
547
+ align-items: center !important;
548
  font-size: 0 !important;
549
+ line-height: 0 !important;
550
  text-indent: -9999px;
551
+ color: transparent !important;
552
+ overflow: hidden !important;
553
+ width: min(133px, 42vw) !important;
554
+ min-width: 80px !important;
555
+ min-height: 22px !important;
556
+ max-width: 133px !important;
557
+ height: 22px !important;
558
+ max-height: 22px !important;
559
+ box-sizing: content-box !important;
560
+ padding: 6px 12px 6px 12px !important;
561
+ flex: 0 0 auto !important;
562
+ flex-shrink: 0 !important;
563
+ background-color: transparent !important;
564
+ background-image: url("{_oh_nav_data_uri_light}") !important;
565
  background-size: contain !important;
566
  background-repeat: no-repeat !important;
567
+ background-position: left center !important;
 
 
 
 
568
  border: none !important;
569
+ box-shadow: none !important;
570
+ border-radius: 6px !important;
571
+ transition: none !important;
572
  }}
573
+ /* Dark top bar: light-colored wordmark */
574
+ html.dark gradio-app nav.svelte-ti537g a[href*="/home"],
575
+ html.dark gradio-app .nav-holder nav a[href*="/home"],
576
+ html.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"],
577
+ body.dark gradio-app nav.svelte-ti537g a[href*="/home"],
578
+ body.dark gradio-app .nav-holder nav a[href*="/home"],
579
+ body.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"],
580
+ html:has([class*="gradio-container-"].dark) gradio-app nav.svelte-ti537g a[href*="/home"],
581
+ html:has([class*="gradio-container-"].dark) gradio-app .nav-holder nav a[href*="/home"],
582
+ html:has([class*="gradio-container-"].dark) gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"],
583
+ .gradio-container.dark gradio-app nav.svelte-ti537g a[href*="/home"],
584
+ .gradio-container.dark gradio-app .nav-holder nav a[href*="/home"],
585
+ .gradio-container.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"],
586
+ [class*="gradio-container-"].dark gradio-app nav.svelte-ti537g a[href*="/home"],
587
+ [class*="gradio-container-"].dark gradio-app .nav-holder nav a[href*="/home"],
588
+ [class*="gradio-container-"].dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"] {{
589
+ background-image: url("{_oh_nav_data_uri_dark}") !important;
590
+ }}
591
+ /* Home wordmark: no grey hover/focus chip */
592
+ gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:hover,
593
+ gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:focus-visible,
594
+ gradio-app nav.svelte-ti537g a[href*="/home"]:hover,
595
+ html.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:hover,
596
+ html.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:focus-visible,
597
+ html.dark gradio-app nav.svelte-ti537g a[href*="/home"]:hover,
598
+ body.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:hover,
599
+ body.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:focus-visible,
600
+ body.dark gradio-app nav.svelte-ti537g a[href*="/home"]:hover,
601
+ html:has([class*="gradio-container-"].dark) gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:hover,
602
+ html:has([class*="gradio-container-"].dark) gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:focus-visible,
603
+ html:has([class*="gradio-container-"].dark) gradio-app nav.svelte-ti537g a[href*="/home"]:hover,
604
+ .gradio-container.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:hover,
605
+ .gradio-container.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:focus-visible,
606
+ .gradio-container.dark gradio-app nav.svelte-ti537g a[href*="/home"]:hover,
607
+ [class*="gradio-container-"].dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:hover,
608
+ [class*="gradio-container-"].dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:focus-visible,
609
+ [class*="gradio-container-"].dark gradio-app nav.svelte-ti537g a[href*="/home"]:hover {{
610
+ background-color: transparent !important;
611
+ }}
612
+ @media (max-width: 768px) {{
613
+ gradio-app nav.svelte-ti537g a[href*="/home"],
614
+ gradio-app .nav-holder nav a[href*="/home"],
615
+ gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"] {{
616
+ left: 20px !important;
617
+ }}
618
+ gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:hover,
619
+ gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"]:focus-visible,
620
+ gradio-app nav.svelte-ti537g a[href*="/home"]:hover {{
621
+ left: 20px !important;
622
+ }}
623
+ }}
624
+ /* Active Home (wordmark — route .active) */
625
+ gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].active,
626
+ gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].svelte-ti537g.active {{
627
+ color: transparent !important;
628
+ left: 20px !important;
629
+ top: 0 !important;
630
+ bottom: 0 !important;
631
+ margin: auto 0 !important;
632
+ background-color: #e4e4e7 !important;
633
+ background-image: url("{_oh_nav_data_uri_light}") !important;
634
+ background-size: contain !important;
635
+ background-repeat: no-repeat !important;
636
+ background-position: left center !important;
637
+ border-color: transparent !important;
638
+ box-shadow: none !important;
639
+ }}
640
+ html.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].active,
641
+ html.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].svelte-ti537g.active,
642
+ body.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].active,
643
+ body.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].svelte-ti537g.active,
644
+ html:has([class*="gradio-container-"].dark) gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].active,
645
+ html:has([class*="gradio-container-"].dark) gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].svelte-ti537g.active,
646
+ .gradio-container.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].active,
647
+ .gradio-container.dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].svelte-ti537g.active,
648
+ [class*="gradio-container-"].dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].active,
649
+ [class*="gradio-container-"].dark gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].svelte-ti537g.active {{
650
+ background-color: hsl(0 0% 12% / 0.95) !important;
651
+ background-image: url("{_oh_nav_data_uri_dark}") !important;
652
+ }}
653
+ @media (max-width: 768px) {{
654
+ gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].active,
655
+ gradio-app nav.oh-top-nav a.oh-nav-link[href*="/home"].svelte-ti537g.active {{
656
+ left: 20px !important;
657
+ }}
658
+ }}
659
+ /* Markdown `---` → <hr>. var(--oh-border) = light #e4e4e7 / dark #242424 (content.py). */
660
+ gradio-app hr, .gradio-container hr, [class*="gradio-container-"] hr {{
661
+ box-sizing: border-box !important;
662
+ border: 0 !important;
663
+ border-top: 1px solid var(--oh-border) !important;
664
+ color: var(--oh-border) !important;
665
+ background: transparent !important;
666
+ background-color: transparent !important;
667
+ height: 0 !important;
668
+ opacity: 1 !important;
669
+ }}
670
+ /* Multipage route chips: Gradio 5.30 index bundle sets
671
+ a.active.svelte-ti537g {{ color: var(--body-text-color); background: var(--block-background-fill) }}.
672
+ This block is last in final_css; typography matches route chip rules in content.py (13px) for every tab including active. */
673
+ gradio-app nav a.svelte-ti537g.active:not([href*="/home"]) {{
674
+ color: #fafafa !important;
675
+ background-color: #18181b !important;
676
+ border-color: transparent !important;
677
+ box-shadow: none !important;
678
+ transition: none !important;
679
+ font-size: 13px !important;
680
+ line-height: 1.4 !important;
681
+ font-weight: 400 !important;
682
+ font-family: var(--oh-font-sans) !important;
683
+ }}
684
+ html.dark gradio-app nav a.svelte-ti537g.active:not([href*="/home"]),
685
+ body.dark gradio-app nav a.svelte-ti537g.active:not([href*="/home"]),
686
+ html:has([class*="gradio-container-"].dark) gradio-app nav a.svelte-ti537g.active:not([href*="/home"]),
687
+ .gradio-container.dark gradio-app nav a.svelte-ti537g.active:not([href*="/home"]),
688
+ [class*="gradio-container-"].dark gradio-app nav a.svelte-ti537g.active:not([href*="/home"]) {{
689
+ color: #ffffff !important;
690
+ background-color: hsl(0 0% 12% / 0.95) !important;
691
+ border-color: transparent !important;
692
+ box-shadow: none !important;
693
+ transition: none !important;
694
+ font-size: 13px !important;
695
+ line-height: 1.4 !important;
696
+ font-weight: 400 !important;
697
+ font-family: var(--oh-font-sans) !important;
698
  }}
699
  """
700
+ )
701
  # --- Gradio App Definition ---
702
  logger.info("Creating Gradio application")
703
  demo = gr.Blocks(
704
  theme=theme,
705
  css=final_css,
706
+ head=DESIGN_FONTS_LINK
707
+ + NAV_LOGO_PRELOAD
708
+ + posthog_script
709
+ + scroll_script
710
+ + redirect_script
711
+ + oh_top_nav_script
712
+ + fix_nav_links_script
713
+ + tooltip_script
714
+ + dark_mode_script,
715
  title="OpenHands Index",
716
  )
717
 
 
783
  # Respect platform port/host if provided (e.g., OpenHands runtime)
784
  port = int(os.environ.get("PORT", os.environ.get("GRADIO_SERVER_PORT", 7860)))
785
  host = os.environ.get("HOST", os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0"))
786
+ # Auto-reload: set RELOAD=1 or UVICORN_RELOAD=1 to restart on .py changes (CSS in content.py, etc.)
787
+ _reload = os.environ.get("UVICORN_RELOAD", os.environ.get("RELOAD", "")).lower() in (
788
+ "1",
789
+ "true",
790
+ "yes",
791
+ )
792
+ logger.info(f"Launching app on {host}:{port}" + (" (auto-reload on .py changes)" if _reload else ""))
793
+ if _reload:
794
+ uvicorn.run("app:app", host=host, port=port, reload=True)
795
+ else:
796
+ uvicorn.run(app, host=host, port=port)
797
  logger.info("App launched successfully")
798
 
constants.py CHANGED
@@ -1,8 +1,11 @@
1
  # Single source of truth for styling constants
 
2
 
3
  # Font settings
4
- FONT_FAMILY = "Arial, sans-serif"
5
- FONT_FAMILY_SHORT = "Arial" # For places that don't accept fallbacks
 
 
6
 
7
  # Marker options for plot icons
8
  MARK_BY_COMPANY = "Company"
 
1
  # Single source of truth for styling constants
2
+ # Aligned with OpenHands-Design (Inter + JetBrains Mono)
3
 
4
  # Font settings
5
+ FONT_FAMILY = "'Inter', system-ui, sans-serif"
6
+ FONT_FAMILY_MONO = "'JetBrains Mono', ui-monospace, monospace"
7
+ FONT_MONO_NAME = "JetBrains Mono" # First font for gr.themes.Base(font_mono=...)
8
+ FONT_FAMILY_SHORT = "Inter" # Gradio theme / primary stack name
9
 
10
  # Marker options for plot icons
11
  MARK_BY_COMPANY = "Company"
content.py CHANGED
@@ -17,16 +17,13 @@ def create_gradio_anchor_id(text: str, validation) -> str:
17
  TITLE = """<h1 align="left" id="space-title">OpenHands Index</h1>"""
18
 
19
  INTRO_PARAGRAPH = """
 
20
  <p>
21
- The <strong>OpenHands Index</strong> is a comprehensive benchmark for evaluating AI coding agents across real-world software engineering tasks. As agents become more capable, we need ways to measure their performance across diverse challenges, from fixing bugs to building applications.
22
  </p>
23
-
24
-
25
  <p>
26
- The OpenHands Index assesses models across five categories: <strong>Issue Resolution</strong> (fixing bugs), <strong>Greenfield</strong> (building new apps), <strong>Frontend</strong> (UI development), <strong>Testing</strong> (test generation), and <strong>Information Gathering</strong>. All models are currently evaluated using the <a href="https://github.com/OpenHands/software-agent-sdk">OpenHands Software Agent SDK</a>. This provides a single view of both <strong>performance</strong> and <strong>cost efficiency</strong>, enabling fair comparisons between agents.
27
  </p>
28
-
29
-
30
  <p>
31
  For methodology details, see the <a href="/about" class="intro-link">About</a> page.
32
  </p>
@@ -235,45 +232,234 @@ def hf_uri_to_web_url(uri: str) -> str:
235
 
236
 
237
  css = """
238
- /* CSS Color Variables aligned with OpenHands brand (openhands-ui/tokens.css) */
239
  :root {
240
- /* Primary - Yellow */
241
- --color-primary-accent: #FFE165;
242
- --color-primary-light: #FFF3C0;
243
- --color-primary-dark: #BBA54A;
244
-
245
- /* Secondary - Green */
246
- --color-secondary-accent: #BCFF8C;
247
- --color-secondary-dark: #577641;
248
-
249
- /* Neutral - Grey scale */
250
- --color-neutral-50: #F7F8FB;
251
- --color-neutral-100: #EBEDF3;
252
- --color-neutral-200: #D4D8E7;
253
- --color-neutral-300: #B1B9D3;
254
- --color-neutral-700: #2F3137;
255
- --color-neutral-800: #222328;
256
- --color-neutral-900: #18191C;
257
- --color-neutral-950: #0D0D0F;
258
-
259
- /* Semantic colors */
260
- --color-primary-link: #2F3137;
261
- --color-primary-link-dark: #B1B9D3;
262
- --color-background-light: #F7F8FB;
263
- --color-background-dark: #18191C;
264
- --color-text-dark: #0D0D0F;
265
- --color-text-light: #F7F8FB;
266
- --color-button-hover: #222328;
267
-
268
- /* Danger/Error - Red */
269
- --color-danger: #FF684E;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  }
271
 
272
- /* This makes space for the huggingface header bar which must shown on HF spaces. */
273
- /* FIXME Media queries don't seem to survive rendering. */
274
- /* @media (min-width: 768px) { ... } */
275
  gradio-app {
276
- padding-top: 65px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  }
278
 
279
  /* Global Styles */
@@ -281,42 +467,117 @@ h2 {
281
  overflow: hidden;
282
  }
283
 
284
- /* Global link color styles */
285
- .dark a {
286
- color: var(--color-primary-link-dark);
287
- }
288
- .dark a:hover {
289
- color: #dddddd;
290
- }
291
- .dark a:visited {
292
- color: #bbbbbb;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  }
294
 
295
  #intro-paragraph {
296
- font-size: 18px;
297
- max-width: 90%;
298
- padding-left: 35px;
299
- margin-top: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  }
301
 
302
  #intro-paragraph p,
303
  #intro-paragraph li {
304
- font-size: 16px;
305
- line-height: 1.8;
 
 
 
 
 
 
 
306
  }
307
 
308
- /* Links in intro paragraph */
309
  .intro-link {
310
- color: var(--color-primary-link);
311
- text-decoration: underline;
 
312
  }
313
- .dark .intro-link {
314
- color: var(--color-primary-link-dark);
315
  }
316
 
317
  #intro-paragraph ul {
318
- margin-top: 20px;
319
- margin-bottom: 20px;
 
 
 
320
  }
321
 
322
  #diagram-image {
@@ -329,40 +590,82 @@ h2 {
329
  object-fit: cover;
330
  }
331
  #intro-category-paragraph {
332
- font-size: 18px;
333
- max-width: 90%;
334
- margin-top: 20px;
 
335
  }
336
 
337
  #intro-category-paragraph p,
338
  #intro-category-paragraph li {
339
- font-size: 16px;
340
- line-height: 1.8;
 
 
 
 
 
 
341
  }
342
 
343
  #intro-category-paragraph ul {
344
- margin-top: 20px;
345
- margin-bottom: 20px;
 
 
 
346
  }
347
 
348
  #about-content {
349
- font-size: 18px;
350
- max-width: 60%;
351
- padding-left: 25px;
 
 
 
 
 
 
 
 
352
  }
353
  #category-intro {
354
- font-size: 18px;
355
- max-width: 60%;
 
 
 
 
 
 
 
 
356
  }
357
  #logo-image {
358
  margin: 0;
359
- margin-bottom: 30px;
360
  justify-content: flex-start;
361
- max-width: 250px;
362
  height: auto;
363
  }
364
- #page-content-wrapper{
365
- padding-left: 25px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
  }
367
  .table-component{
368
  height: auto !important;
@@ -383,7 +686,7 @@ table.svelte-1e98i6s td {
383
  vertical-align: top !important;
384
  }
385
  table.gr-table {
386
- font-size: 15px !important;
387
  }
388
  .html-container {
389
  padding-top: 0 !important;
@@ -392,22 +695,152 @@ table.gr-table {
392
  overflow: visible !important;
393
  }
394
  #pareto-disclaimer {
395
- color: var(--color-primary-accent) !important;
396
  }
397
  thead.svelte-1e98i6s th {
398
- background: white !important;
399
  }
400
  .dark thead.svelte-1e98i6s th {
401
- background: var(--color-background-dark) !important;
402
  }
403
  .cell-wrap.svelte-v1pjjd {
404
- font-family: Arial, sans-serif;
405
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  nav.svelte-ti537g.svelte-ti537g {
407
- justify-content: flex-start;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  }
409
- .nav-holder {
410
- padding-left: 20px !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  }
412
  #legend-markdown span {
413
  margin-right: 15px !important;
@@ -418,7 +851,7 @@ nav.svelte-ti537g.svelte-ti537g {
418
  position: relative !important;
419
  }
420
  .dark #leaderboard-accordion .label-wrap {
421
- color: var(--color-primary-accent) !important;
422
  }
423
  .dark block.svelte-1svsvh2 {
424
  background: var(--color-background-dark) !important;
@@ -430,40 +863,48 @@ nav.svelte-ti537g.svelte-ti537g {
430
  display: flex !important;
431
  flex-wrap: wrap !important;
432
  align-items: center !important;
433
- gap: 10px !important;
434
- }
435
- .dark .primary-link-button {
436
- color: var(--color-primary-link-dark);
437
  }
 
438
  .primary-link-button {
439
  background: none;
440
  border: none;
441
  padding: 0;
442
  margin: 0;
443
  font-family: inherit;
444
- font-size: 16px;
445
- color: var(--color-primary-link);
 
446
  text-decoration: none;
447
  cursor: pointer;
448
  white-space: nowrap;
 
449
  }
450
  .primary-link-button:hover {
451
- text-decoration: underline;
 
 
 
 
 
 
 
 
452
  }
453
  .sub-nav-label {
454
- font-weight: bold;
455
- font-size: 16px;
456
  display: flex;
457
  align-items: center;
458
  }
459
- .wrap-header-df th span{
460
  white-space: normal !important;
461
  word-break: normal !important;
462
  overflow-wrap: break-word !important;
463
  line-height: 1.2 !important;
464
  vertical-align: top !important;
465
  font-size: 12px !important;
466
- font-family: Arial, sans-serif;
467
  }
468
  .wrap-header-df th {
469
  height: auto !important;
@@ -537,110 +978,149 @@ span.wrap[tabindex="0"][role="button"][data-editable="false"] {
537
  /* --- inside table tooltips --- */
538
  .native-tooltip-icon {
539
  cursor: help;
540
- text-decoration: underline dotted 1px;
541
- }
542
- /* Main Nav bar styling */
543
- .nav-holder nav {
544
- display: grid !important;
545
- grid-template-columns: auto auto auto auto auto 1fr auto auto !important;
546
- gap: 10px 20px !important; /* Vertical and horizontal spacing */
547
- width: 100% !important;
548
- align-items: center;
549
- }
550
- .nav-holder nav a[href*="about"] {
551
- grid-row: 1 !important;
552
- grid-column: 7 !important;
553
  }
554
- .nav-holder nav a[href*="submit"] {
555
- grid-row: 1 !important;
556
- grid-column: 8 !important;
 
 
 
 
 
 
 
 
 
 
 
557
  white-space: nowrap !important;
558
- }
559
- /* Hide the Alternative Agents page from the top-level nav for now. */
560
- .nav-holder nav a[href*="alternative-agents"] {
561
- display: none !important;
562
- }
563
-
564
- /* Divider line between header and category nav */
565
- .nav-holder nav::after {
566
- content: ''; /* Required for pseudo-elements to appear */
567
- background-color: #C9C9C3;
568
- height: 1px;
569
- grid-row: 2 !important;
570
- grid-column: 1 / -1 !important;
571
- }
572
-
573
- /* Horizontal scrolling for navigation */
574
- .nav-holder nav {
575
- overflow-x: auto;
576
- scrollbar-width: none;
577
- -ms-overflow-style: none;
578
- }
579
- .nav-holder nav::-webkit-scrollbar {
580
- display: none;
581
- }
582
-
583
- /* Category navigation buttons in row 3 */
584
- .nav-holder nav a[href*="literature-understanding"],
585
- .nav-holder nav a[href*="code-execution"],
586
- .nav-holder nav a[href*="data-analysis"],
587
- .nav-holder nav a[href*="discovery"] {
588
- grid-row: 3 !important;
589
- justify-self: center !important;
590
- width: fit-content !important;
591
- white-space: nowrap;
592
- flex-shrink: 0;
593
- }
594
-
595
- .nav-holder nav a[href*="literature-understanding"] { grid-column: 1 !important; }
596
- .nav-holder nav a[href*="code-execution"] { grid-column: 2 !important; }
597
- .nav-holder nav a[href*="data-analysis"] { grid-column: 3 !important; }
598
- .nav-holder nav a[href*="discovery"] { grid-column: 4 !important; }
599
-
600
- /* Navigation hover styles */
601
- .nav-holder nav a[href*="about"]:hover,
602
- .nav-holder nav a[href*="submit"]:hover,
603
- .nav-holder nav a[href*="literature-understanding"]:hover,
604
- .nav-holder nav a[href*="code-execution"]:hover,
605
- .nav-holder nav a[href*="data-analysis"]:hover,
606
- .nav-holder nav a[href*="discovery"]:hover {
607
- background-color: #FDF9F4;
608
- }
609
-
610
- .dark .nav-holder nav a[href*="about"]:hover,
611
- .dark .nav-holder nav a[href*="submit"]:hover,
612
- .dark .nav-holder nav a[href*="literature-understanding"]:hover,
613
- .dark .nav-holder nav a[href*="code-execution"]:hover,
614
- .dark .nav-holder nav a[href*="data-analysis"]:hover,
615
- .dark .nav-holder nav a[href*="discovery"]:hover {
616
- background-color: #1C3A3C;
617
- }
618
- .benchmark-main-subtitle{
619
- color: var(--color-primary-link);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
  overflow: hidden;
621
  padding-top: 120px;
 
 
 
622
  }
623
- .dark .benchmark-main-subtitle{
624
- color: var(--color-primary-link-dark);
 
 
 
 
 
 
625
  }
626
- .benchmark-title{
627
- color: var(--color-primary-link);
628
- margin-top: 50px;
629
- font-size: 20px;
630
- }
631
- .dark .benchmark-title{
632
- color: var(--color-primary-accent);
633
  }
634
  .benchmark-description {
635
- margin: 20px 0;
636
- max-width: 800px;
 
 
637
  }
638
-
639
- .dark #main-header h2 {
640
- color: var(--color-primary-accent);
 
 
641
  }
 
 
 
642
  #main-header h2 {
643
- color: var(--color-primary-link);
 
 
 
 
 
 
 
 
644
  }
645
 
646
  /* --- New HTML-Based Tooltip Styles --- */
@@ -726,18 +1206,66 @@ span.wrap[tabindex="0"][role="button"][data-editable="false"] {
726
  flex-wrap: wrap;
727
  }
728
 
729
- /* About Page CSS */
730
  #about-page-content-wrapper {
731
  margin-left: auto;
732
  margin-right: auto;
733
- max-width: 800px;
734
- padding: 0 24px;
735
  display: flex;
736
  flex-direction: column;
737
- gap: 40px;
738
- margin-top: 40px;
739
- opacity: 85%;
740
- margin-bottom: 60px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
741
  }
742
  .link-buttons-container {
743
  display: flex;
@@ -804,13 +1332,13 @@ html {
804
  min-height: 572px;
805
  background-color: #fff;
806
  padding: 24px 32px;
807
- border: 1px solid var(--color-neutral-300);
808
  border-radius: 4px;
809
  }
810
 
811
  .dark .plot-legend-container {
812
  background: rgba(247, 248, 251, 0.1);
813
- border-color: var(--color-neutral-700);
814
  }
815
 
816
  #plot-legend-logo {
@@ -872,6 +1400,38 @@ h3 .header-link-icon {
872
  .winners-by-category-container {
873
  margin: 20px 0;
874
  overflow-x: auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
875
  }
876
 
877
  .winners-unified-table {
@@ -879,22 +1439,23 @@ h3 .header-link-icon {
879
  border-collapse: collapse;
880
  font-size: 13px;
881
  background: #fff;
882
- border: 1px solid var(--color-neutral-300);
883
  border-radius: 12px;
884
  overflow: hidden;
885
  }
886
 
887
  .dark .winners-unified-table {
888
  background: rgba(247, 248, 251, 0.05);
889
- border-color: var(--color-neutral-700);
890
  }
891
 
 
892
  .winners-unified-table thead tr {
893
- background: linear-gradient(to right, rgba(255, 225, 101, 0.15), rgba(220, 194, 87, 0.25));
894
  }
895
 
896
  .dark .winners-unified-table thead tr {
897
- background: linear-gradient(to right, rgba(255, 225, 101, 0.2), rgba(220, 194, 87, 0.3));
898
  }
899
 
900
  .winners-unified-table .category-header {
@@ -903,15 +1464,15 @@ h3 .header-link-icon {
903
  font-weight: 700;
904
  font-size: 13px;
905
  color: var(--color-text-dark);
906
- border-bottom: 2px solid var(--color-primary-accent) !important;
907
- border-right: 2px solid #999 !important;
908
  white-space: nowrap;
909
  }
910
 
911
  .dark .winners-unified-table .category-header {
912
  color: #fff;
913
- border-bottom-color: var(--color-primary-accent) !important;
914
- border-right-color: var(--color-neutral-500) !important;
915
  }
916
 
917
  .winners-unified-table .category-header:last-child {
@@ -928,23 +1489,24 @@ h3 .header-link-icon {
928
  .winners-unified-table td {
929
  padding: 8px 6px;
930
  vertical-align: middle;
931
- border-bottom: 1px solid #eee;
932
  }
933
 
934
  .dark .winners-unified-table td {
935
- border-bottom-color: #2a3a3a;
936
  }
937
 
938
  .winners-unified-table tbody tr:last-child td {
939
  border-bottom: none;
940
  }
941
 
 
942
  .winners-unified-table tbody tr:hover {
943
- background: rgba(255, 225, 101, 0.1);
944
  }
945
 
946
  .dark .winners-unified-table tbody tr:hover {
947
- background: rgba(255, 225, 101, 0.15);
948
  }
949
 
950
  .winners-unified-table .score-cell {
@@ -953,12 +1515,12 @@ h3 .header-link-icon {
953
  color: var(--color-primary-dark);
954
  padding-left: 12px;
955
  min-width: 50px;
956
- border-right: 1px solid #eee;
957
  }
958
 
959
  .dark .winners-unified-table .score-cell {
960
  color: var(--color-primary-accent);
961
- border-right-color: var(--color-neutral-700);
962
  }
963
 
964
  .winners-unified-table .model-cell {
@@ -966,12 +1528,12 @@ h3 .header-link-icon {
966
  color: var(--color-text-dark);
967
  font-weight: 500;
968
  padding-right: 12px;
969
- border-right: 2px solid #999;
970
  }
971
 
972
  .dark .winners-unified-table .model-cell {
973
  color: #fff;
974
- border-right-color: var(--color-neutral-500);
975
  }
976
 
977
  .winners-unified-table td:nth-last-child(1) {
@@ -1061,10 +1623,9 @@ h3 .header-link-icon {
1061
  padding-right: 8px !important;
1062
  }
1063
 
1064
- /* Reduce padding on intro paragraph */
1065
  #intro-paragraph {
1066
- padding-left: 8px !important;
1067
- padding-right: 8px !important;
1068
  max-width: 100% !important;
1069
  }
1070
 
@@ -1075,9 +1636,10 @@ h3 .header-link-icon {
1075
  max-width: 100% !important;
1076
  }
1077
 
1078
- /* Reduce navigation holder padding */
1079
- .nav-holder {
1080
- padding-left: 8px !important;
 
1081
  padding-right: 8px !important;
1082
  }
1083
 
@@ -1088,10 +1650,10 @@ h3 .header-link-icon {
1088
  max-width: 100% !important;
1089
  }
1090
 
1091
- /* Reduce about page wrapper padding */
1092
  #about-page-content-wrapper {
1093
- padding-left: 12px !important;
1094
- padding-right: 12px !important;
1095
  }
1096
 
1097
  /* Reduce gradio container padding */
 
17
  TITLE = """<h1 align="left" id="space-title">OpenHands Index</h1>"""
18
 
19
  INTRO_PARAGRAPH = """
20
+ <h1 class="intro-page-title">The OpenHands Index</h1>
21
  <p>
22
+ This is a comprehensive benchmark for evaluating AI coding agents across real-world software engineering tasks. As agents become more capable, we need ways to measure their performance across diverse challenges, from fixing bugs to building applications.
23
  </p>
 
 
24
  <p>
25
+ The index assesses models across five categories: <strong>Issue Resolution</strong> (fixing bugs), <strong>Greenfield</strong> (building new apps), <strong>Frontend</strong> (UI development), <strong>Testing</strong> (test generation), and <strong>Information Gathering</strong>. All models are currently evaluated using the <a href="https://github.com/OpenHands/software-agent-sdk">OpenHands Software Agent SDK</a>. This provides a single view of both <strong>performance</strong> and <strong>cost efficiency</strong>, enabling fair comparisons between agents.
26
  </p>
 
 
27
  <p>
28
  For methodology details, see the <a href="/about" class="intro-link">About</a> page.
29
  </p>
 
232
 
233
 
234
  css = """
235
+ /* OpenHands-Design tokens (OpenHands-Design/index.html, DESIGN.md) */
236
  :root {
237
+ --oh-bg: hsl(0 0% 5%);
238
+ --oh-card: hsl(0 0% 7%);
239
+ --oh-secondary: hsl(0 0% 8%);
240
+ --oh-muted: hsl(0 0% 12%);
241
+ /* Light mode: 1px strokes / dividers — same grey as top nav bar (#e4e4e7 zinc-200) */
242
+ --oh-border: #e4e4e7;
243
+ /* Slightly stronger on interactive hover (zinc-300) */
244
+ --oh-border-hover: #d4d4d8;
245
+ --oh-muted-hover: hsl(0 0% 18%);
246
+ --oh-fg: hsl(0 0% 98%);
247
+ --oh-fg-muted: hsl(0 0% 55%);
248
+ --oh-primary: hsl(0 0% 100%);
249
+ --oh-primary-fg: hsl(0 0% 0%);
250
+ --oh-success: #22c55e;
251
+ --oh-success-fg: #86efac;
252
+ --oh-warning: #f59e0b;
253
+ --oh-info: #3b82f6;
254
+ --oh-destructive: #dc2626;
255
+ --oh-ring: #cccccc;
256
+ --oh-shadow-card: 0 1px 2px 0 hsl(0 0% 0% / 0.3);
257
+ --oh-font-sans: 'Inter', system-ui, sans-serif;
258
+ --oh-font-mono: 'JetBrains Mono', ui-monospace, monospace;
259
+ /* DESIGN.md §5: spacing (4px scale) — index.html / section rhythm */
260
+ --oh-pad-x-page: 24px;
261
+ /* DESIGN.md: container max 1400px — page column + #page-content-wrapper */
262
+ --oh-content-max: 87.5rem;
263
+ /* DESIGN.md §4: dropdown item hover (light / dark) */
264
+ --oh-form-hover-light: #f4f4f5;
265
+ --oh-form-active-dark: hsl(0 0% 12% / 0.6);
266
+ --oh-pad-y-section: 4rem; /* 64px — .section in index.html */
267
+ --oh-gap-prose-paragraph: 1rem; /* 16px — gap-4 */
268
+ --oh-gap-prose-tight: 0.5rem; /* 8px — gap-2 */
269
+ --oh-prose-size: 0.875rem; /* 14px — text-sm, primary body */
270
+ --oh-prose-leading: 1.5rem; /* 24px — leading-6 w/ text-sm */
271
+ /* DESIGN.md: inline links use --info; focus ring = --oh-ring */
272
+ --oh-link-visited: hsl(217 60% 48%);
273
+ --oh-link-hover: hsl(217 85% 62%);
274
+ /* Legacy var names (charts, tables) — mapped to design surfaces */
275
+ --color-primary-accent: #e8e8e5;
276
+ --color-primary-light: #f5f5f4;
277
+ --color-primary-dark: #a1a1aa;
278
+ --color-secondary-accent: #22c55e;
279
+ --color-secondary-dark: #16a34a;
280
+ --color-neutral-50: #fafafa;
281
+ --color-neutral-100: #f4f4f4;
282
+ --color-neutral-200: #e5e5e5;
283
+ --color-neutral-300: #d4d4d4;
284
+ --color-neutral-700: #404040;
285
+ --color-neutral-800: #262626;
286
+ --color-neutral-900: #171717;
287
+ --color-neutral-950: #0d0d0d;
288
+ --color-primary-link: #404040;
289
+ --color-primary-link-dark: #a3a3a3;
290
+ --color-background-light: #fafafa;
291
+ --color-background-dark: #0d0d0d;
292
+ --color-text-dark: #0d0d0d;
293
+ --color-text-light: #fafafa;
294
+ --color-button-hover: #404040;
295
+ --color-danger: #dc2626;
296
+ }
297
+
298
+ /* Dark theme: near-black strokes (DESIGN.md --border / #242424) */
299
+ html.dark,
300
+ body.dark,
301
+ html:has([class*="gradio-container-"].dark),
302
+ .gradio-container.dark,
303
+ [class*="gradio-container-"].dark {
304
+ --oh-border: hsl(0 0% 14%);
305
+ --oh-border-hover: hsl(0 0% 24%);
306
+ }
307
+
308
+ body, .gradio-container {
309
+ font-family: var(--oh-font-sans) !important;
310
+ -webkit-font-smoothing: antialiased;
311
+ }
312
+ code, kbd, pre, .cell-wrap.svelte-v1pjjd {
313
+ font-family: var(--oh-font-mono) !important;
314
+ }
315
+
316
+ /* Markdown `---` → <hr>. Gradio preflight uses color:inherit on hr, which tracks body text, not the border line. */
317
+ /* Section divider: 1px solid var(--oh-border) — light #e4e4e7 (top nav), dark #242424 */
318
+ gradio-app hr,
319
+ .gradio-container hr,
320
+ #about-page-content-wrapper hr,
321
+ #page-content-wrapper hr,
322
+ .prose hr,
323
+ div[class*="markdown"] hr,
324
+ .divider-line hr,
325
+ [class~="divider-line"] hr {
326
+ box-sizing: border-box;
327
+ display: block;
328
+ height: 0 !important;
329
+ margin: 0.75rem 0 1.5rem 0;
330
+ padding: 0 !important;
331
+ border: 0 !important;
332
+ border-top: 1px solid var(--oh-border) !important;
333
+ background: transparent !important;
334
+ background-color: transparent !important;
335
+ /* Some UAs use `color` for the default <hr> border; lock to the border token (not --body-text-color) */
336
+ color: var(--oh-border) !important;
337
+ opacity: 1 !important;
338
  }
339
 
340
+ /* No top padding on gradio-app (control_page_title is a prop on this element) */
 
 
341
  gradio-app {
342
+ padding-top: 0;
343
+ overflow: visible !important;
344
+ display: block;
345
+ }
346
+ /* In-flow strip so the fixed <nav> does not sit on top of the first line of main content */
347
+ gradio-app::before {
348
+ content: "" !important;
349
+ display: block !important;
350
+ width: 100% !important;
351
+ height: 56px !important;
352
+ flex-shrink: 0 !important;
353
+ pointer-events: none !important;
354
+ }
355
+ html {
356
+ scroll-padding-top: 56px;
357
+ min-height: 100%;
358
+ /* Full-viewport canvas; match body/gradio so gutter + Gradio block fills never show a wrong tone (About’s lighter DOM hides this) */
359
+ background-color: var(--color-background-light, #fafafa);
360
+ }
361
+ html.dark,
362
+ html:has([class*="gradio-container-"].dark) {
363
+ background-color: var(--color-background-dark, #0d0d0d);
364
+ }
365
+ /* Main column: cap width to match DESIGN.md; nav stays full-bleed (sibling, not inside .main) */
366
+ gradio-app .main,
367
+ gradio-app .main.fillable {
368
+ max-width: var(--oh-content-max) !important;
369
+ margin-left: auto !important;
370
+ margin-right: auto !important;
371
+ width: 100% !important;
372
+ box-sizing: border-box !important;
373
+ }
374
+ /*
375
+ * OpenHands-Design §4: Dropdown — menu z-index, min-w, border bg-popover, shadow-md, p-1
376
+ * + menu item: text-sm, rounded-md, px-2 py-1.5, data-[highlighted]:bg-muted/60
377
+ * Gradio uses ul.options / li.item; active row uses bg-gray-100 / dark:bg-gray-600 — override to tokens.
378
+ */
379
+ gradio-app ul.options {
380
+ border: 1px solid var(--border-color-primary) !important;
381
+ border-radius: 0.25rem !important;
382
+ padding: 0.25rem !important;
383
+ background: var(--background-fill-primary) !important;
384
+ box-shadow: var(--shadow-drop-lg) !important;
385
+ scrollbar-width: thin;
386
+ scrollbar-color: hsl(0 0% 55% / 0.45) transparent;
387
+ }
388
+ gradio-app ul.options li.item {
389
+ font-size: 0.875rem !important;
390
+ line-height: 1.25rem;
391
+ border-radius: 0.25rem;
392
+ padding: 0.375rem 0.5rem !important;
393
+ margin: 0 0.125rem;
394
+ transition: color 0.2s ease, background-color 0.2s ease;
395
+ }
396
+ /* Light: hover / keyboard highlight — neutral chip */
397
+ gradio-app ul.options li.item:hover,
398
+ gradio-app ul.options li.item.active {
399
+ background-color: var(--oh-form-hover-light) !important;
400
+ }
401
+ /* Dark: canonical hover:bg-muted/60 (DESIGN.md §2) */
402
+ html.dark gradio-app ul.options li.item:hover,
403
+ html.dark gradio-app ul.options li.item.active,
404
+ body.dark gradio-app ul.options li.item:hover,
405
+ body.dark gradio-app ul.options li.item.active,
406
+ html:has([class*="gradio-container-"].dark) gradio-app ul.options li.item:hover,
407
+ html:has([class*="gradio-container-"].dark) gradio-app ul.options li.item.active,
408
+ .gradio-container.dark gradio-app ul.options li.item:hover,
409
+ .gradio-container.dark gradio-app ul.options li.item.active,
410
+ [class*="gradio-container-"].dark gradio-app ul.options li.item:hover,
411
+ [class*="gradio-container-"].dark gradio-app ul.options li.item.active {
412
+ background-color: var(--oh-form-active-dark) !important;
413
+ color: var(--body-text-color) !important;
414
+ }
415
+ /* Touch target: min h-10 (40px) for dropdown / text field wrap (DESIGN.md §8) */
416
+ gradio-app .container > .wrap > .wrap-inner {
417
+ min-height: 2.5rem;
418
+ box-sizing: border-box;
419
+ transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
420
+ }
421
+ /* Leaderboard filter column: form stacks checkboxes with no gap (Gradio form uses --form-gap-width) */
422
+ gradio-app .column.oh-leaderboard-filter-col .form {
423
+ gap: 0 !important;
424
+ }
425
+ /* Gradio root: overflow: hidden; — keep visible; sticky still fails if scroll is on an inner .main, so nav uses position: fixed */
426
+ body > [class*="gradio-container-"],
427
+ body > div.gradio-container,
428
+ body > [class^="gradio-container"] {
429
+ overflow: visible !important;
430
+ }
431
+ /* Multipage top <nav> — fixed + opaque background + single bottom edge (see .nav-holder: border removed) */
432
+ gradio-app nav.svelte-ti537g {
433
+ position: fixed !important;
434
+ top: 0 !important;
435
+ left: 0 !important;
436
+ right: 0 !important;
437
+ width: 100% !important;
438
+ z-index: 200 !important;
439
+ min-height: 56px !important;
440
+ height: 56px !important;
441
+ max-height: 56px !important;
442
+ box-sizing: border-box !important;
443
+ display: flex !important;
444
+ align-items: center !important;
445
+ flex-wrap: nowrap !important;
446
+ /* Default = light top bar (Gradio / :root use dark design tokens, so we don’t use --oh-bg here) */
447
+ background: #fafafa !important;
448
+ background-color: #fafafa !important;
449
+ border-bottom: 1px solid #e4e4e7 !important;
450
+ box-shadow: none !important;
451
+ -webkit-backface-visibility: hidden;
452
+ backface-visibility: hidden;
453
+ }
454
+ /* Dark theme: restore near-black bar (html/body, or any [class*=gradio-container-] with .dark) */
455
+ html.dark gradio-app nav.svelte-ti537g,
456
+ body.dark gradio-app nav.svelte-ti537g,
457
+ html:has([class*="gradio-container-"].dark) gradio-app nav.svelte-ti537g,
458
+ .gradio-container.dark gradio-app nav.svelte-ti537g,
459
+ [class*="gradio-container-"].dark gradio-app nav.svelte-ti537g {
460
+ background: var(--oh-bg) !important;
461
+ background-color: var(--oh-bg) !important;
462
+ border-bottom: 1px solid var(--oh-border) !important;
463
  }
464
 
465
  /* Global Styles */
 
467
  overflow: hidden;
468
  }
469
 
470
+ /* In-content links (DESIGN.md semantic info for links; top nav is outside #page-content-wrapper) */
471
+ #page-content-wrapper a[href],
472
+ #about-page-content-wrapper a[href],
473
+ #about-content a[href],
474
+ #category-intro a[href],
475
+ #intro-paragraph a[href],
476
+ #intro-category-paragraph a[href],
477
+ .prose a[href],
478
+ table.gr-table a[href] {
479
+ color: var(--oh-info) !important;
480
+ text-decoration: none;
481
+ transition: color 0.2s ease;
482
+ }
483
+ #page-content-wrapper a[href]:hover,
484
+ #about-page-content-wrapper a[href]:hover,
485
+ #about-content a[href]:hover,
486
+ #category-intro a[href]:hover,
487
+ #intro-paragraph a[href]:hover,
488
+ #intro-category-paragraph a[href]:hover,
489
+ .prose a[href]:hover,
490
+ table.gr-table a[href]:hover {
491
+ color: var(--oh-link-hover) !important;
492
+ }
493
+ #page-content-wrapper a[href]:visited,
494
+ #about-page-content-wrapper a[href]:visited,
495
+ #about-content a[href]:visited,
496
+ #category-intro a[href]:visited,
497
+ #intro-paragraph a[href]:visited,
498
+ #intro-category-paragraph a[href]:visited,
499
+ .prose a[href]:visited,
500
+ table.gr-table a[href]:visited {
501
+ color: var(--oh-link-visited) !important;
502
+ }
503
+ #page-content-wrapper a[href]:focus-visible,
504
+ #about-page-content-wrapper a[href]:focus-visible,
505
+ #about-content a[href]:focus-visible,
506
+ #category-intro a[href]:focus-visible,
507
+ #intro-paragraph a[href]:focus-visible,
508
+ #intro-category-paragraph a[href]:focus-visible,
509
+ .prose a[href]:focus-visible,
510
+ table.gr-table a[href]:focus-visible {
511
+ outline: 2px solid var(--oh-ring);
512
+ outline-offset: 2px;
513
+ border-radius: 2px;
514
  }
515
 
516
  #intro-paragraph {
517
+ font-size: var(--oh-prose-size);
518
+ max-width: min(100%, 52rem);
519
+ padding-left: 0;
520
+ padding-right: 0;
521
+ margin-top: 0;
522
+ margin-bottom: var(--oh-gap-prose-paragraph);
523
+ }
524
+ /* Theme-aware: Gradio’s --body-text-color (matches light/dark theme); prose overrides need same specificity */
525
+ #intro-paragraph h1.intro-page-title,
526
+ #intro-paragraph .prose h1.intro-page-title {
527
+ font-size: clamp(2rem, 2.5rem + 1.2vw, 2.75rem) !important;
528
+ font-weight: 600 !important;
529
+ line-height: 1.1 !important;
530
+ letter-spacing: -0.03em !important;
531
+ margin: 0 0 1.25rem 0 !important;
532
+ color: var(--body-text-color, var(--color-neutral-950)) !important;
533
+ text-align: left;
534
+ }
535
+ html.dark #intro-paragraph h1.intro-page-title,
536
+ html.dark #intro-paragraph .prose h1.intro-page-title,
537
+ body.dark #intro-paragraph h1.intro-page-title,
538
+ body.dark #intro-paragraph .prose h1.intro-page-title,
539
+ .gradio-container.dark #intro-paragraph h1.intro-page-title,
540
+ .gradio-container.dark #intro-paragraph .prose h1.intro-page-title,
541
+ [class*="gradio-container-"].dark #intro-paragraph h1.intro-page-title,
542
+ [class*="gradio-container-"].dark #intro-paragraph .prose h1.intro-page-title {
543
+ color: var(--body-text-color, var(--oh-fg)) !important;
544
+ }
545
+ #intro-paragraph .html-container,
546
+ #intro-paragraph .prose {
547
+ padding-left: 0 !important;
548
+ padding-right: 0 !important;
549
+ margin-left: 0 !important;
550
  }
551
 
552
  #intro-paragraph p,
553
  #intro-paragraph li {
554
+ font-size: var(--oh-prose-size);
555
+ line-height: var(--oh-prose-leading);
556
+ }
557
+ #intro-paragraph p,
558
+ #intro-paragraph .prose p {
559
+ margin: 0 0 var(--oh-gap-prose-paragraph) 0;
560
+ }
561
+ #intro-paragraph p:last-child {
562
+ margin-bottom: 0;
563
  }
564
 
565
+ /* DESIGN.md: Label pattern same link token, medium weight for explicit intro-link */
566
  .intro-link {
567
+ color: var(--oh-info) !important;
568
+ text-decoration: none;
569
+ font-weight: 500;
570
  }
571
+ .intro-link:hover {
572
+ color: var(--oh-link-hover) !important;
573
  }
574
 
575
  #intro-paragraph ul {
576
+ margin: var(--oh-gap-prose-paragraph) 0;
577
+ padding-left: 1.25rem;
578
+ }
579
+ #intro-paragraph ul li + li {
580
+ margin-top: 0.35em;
581
  }
582
 
583
  #diagram-image {
 
590
  object-fit: cover;
591
  }
592
  #intro-category-paragraph {
593
+ font-size: var(--oh-prose-size);
594
+ max-width: min(100%, 52rem);
595
+ margin-top: 1.5rem;
596
+ margin-bottom: var(--oh-gap-prose-paragraph);
597
  }
598
 
599
  #intro-category-paragraph p,
600
  #intro-category-paragraph li {
601
+ font-size: var(--oh-prose-size);
602
+ line-height: var(--oh-prose-leading);
603
+ }
604
+ #intro-category-paragraph p {
605
+ margin: 0 0 var(--oh-gap-prose-paragraph) 0;
606
+ }
607
+ #intro-category-paragraph p:last-child {
608
+ margin-bottom: 0;
609
  }
610
 
611
  #intro-category-paragraph ul {
612
+ margin: var(--oh-gap-prose-paragraph) 0;
613
+ padding-left: 1.25rem;
614
+ }
615
+ #intro-category-paragraph ul li + li {
616
+ margin-top: 0.35em;
617
  }
618
 
619
  #about-content {
620
+ font-size: var(--oh-prose-size);
621
+ line-height: var(--oh-prose-leading);
622
+ max-width: min(100%, 42rem);
623
+ padding-left: 0;
624
+ margin-bottom: var(--oh-pad-y-section);
625
+ }
626
+ #about-content p {
627
+ margin: 0 0 var(--oh-gap-prose-paragraph) 0;
628
+ }
629
+ #about-content p:last-child {
630
+ margin-bottom: 0;
631
  }
632
  #category-intro {
633
+ font-size: var(--oh-prose-size);
634
+ line-height: var(--oh-prose-leading);
635
+ max-width: min(100%, 42rem);
636
+ margin-bottom: var(--oh-gap-prose-paragraph);
637
+ }
638
+ #category-intro p {
639
+ margin: 0 0 var(--oh-gap-prose-paragraph) 0;
640
+ }
641
+ #category-intro p:last-child {
642
+ margin-bottom: 0;
643
  }
644
  #logo-image {
645
  margin: 0;
646
+ margin-bottom: 2rem;
647
  justify-content: flex-start;
648
+ max-width: 250px;
649
  height: auto;
650
  }
651
+ /* index.html hero/section: comfortable row gap for intro + diagram */
652
+ #intro-row {
653
+ gap: 1.5rem;
654
+ align-items: flex-start;
655
+ width: 100%;
656
+ }
657
+ /* DESIGN.md: container max 1400px, horizontal padding 24px (index .section) */
658
+ #page-content-wrapper {
659
+ box-sizing: border-box;
660
+ width: 100%;
661
+ max-width: min(100%, var(--oh-content-max));
662
+ margin-left: auto;
663
+ margin-right: auto;
664
+ /* Match home: gradio-app::before (56px nav) + intro-paragraph’s former margin-top */
665
+ padding: 1.5rem var(--oh-pad-x-page) 0;
666
+ display: flex;
667
+ flex-direction: column;
668
+ gap: 1.5rem; /* gap-6 between major blocks */
669
  }
670
  .table-component{
671
  height: auto !important;
 
686
  vertical-align: top !important;
687
  }
688
  table.gr-table {
689
+ font-size: 14px !important;
690
  }
691
  .html-container {
692
  padding-top: 0 !important;
 
695
  overflow: visible !important;
696
  }
697
  #pareto-disclaimer {
698
+ color: var(--oh-success) !important;
699
  }
700
  thead.svelte-1e98i6s th {
701
+ background: var(--color-neutral-50) !important;
702
  }
703
  .dark thead.svelte-1e98i6s th {
704
+ background: var(--color-neutral-900) !important;
705
  }
706
  .cell-wrap.svelte-v1pjjd {
707
+ font-family: var(--oh-font-mono) !important;
708
+ }
709
+ /* Gradio 5.30: top route row — right-align; left padding reserves zone for wordmark (app.py) */
710
+ gradio-app .nav-holder > nav.svelte-ti537g,
711
+ gradio-app .nav-holder nav.svelte-ti537g,
712
+ gradio-app nav.svelte-ti537g {
713
+ margin: 0 !important;
714
+ margin-left: 0 !important;
715
+ margin-right: 0 !important;
716
+ width: 100% !important;
717
+ max-width: none !important;
718
+ box-sizing: border-box !important;
719
+ justify-content: flex-end !important;
720
+ padding: 0 20px 0 164px !important;
721
+ }
722
  nav.svelte-ti537g.svelte-ti537g {
723
+ justify-content: flex-end !important;
724
+ }
725
+ /* First paint: default route chip text only — not .active (Gradio gives .active the block fill; we override in app.py last) */
726
+ gradio-app nav.svelte-ti537g > a[href]:not([href*="/home"]):not(.active) {
727
+ font-size: 13px !important;
728
+ font-weight: 400 !important;
729
+ line-height: 1.4 !important;
730
+ color: #71717a !important;
731
+ padding: 6px 12px !important;
732
+ border-radius: 6px !important;
733
+ white-space: nowrap !important;
734
+ }
735
+ html.dark gradio-app nav.svelte-ti537g > a[href]:not([href*="/home"]):not(.active),
736
+ body.dark gradio-app nav.svelte-ti537g > a[href]:not([href*="/home"]):not(.active),
737
+ html:has([class*="gradio-container-"].dark) gradio-app nav.svelte-ti537g > a[href]:not([href*="/home"]):not(.active),
738
+ .gradio-container.dark gradio-app nav.svelte-ti537g > a[href]:not([href*="/home"]):not(.active),
739
+ [class*="gradio-container-"].dark gradio-app nav.svelte-ti537g > a[href]:not([href*="/home"]):not(.active) {
740
+ color: var(--oh-fg-muted) !important;
741
+ }
742
+ /* Dedupe & trim root link before/after .oh-top-nav (mirrors app.py) */
743
+ gradio-app nav.svelte-ti537g a[href$="/"] {
744
+ display: none !important;
745
+ }
746
+ gradio-app .nav-holder nav a[href="/home"]:not(.oh-nav-link) {
747
+ display: none !important;
748
+ }
749
+ /* Gradio: .nav-holder had border-bottom in flow while <nav> is position:fixed — that border scrolled (moving line). No border/background on holder; edge lives on <nav> only. */
750
+ gradio-app .nav-holder,
751
+ .nav-holder.svelte-ti537g.svelte-ti537g {
752
+ padding: 0 !important;
753
+ border: none !important;
754
+ border-bottom: none !important;
755
+ background: transparent !important;
756
+ box-shadow: none !important;
757
+ width: 100% !important;
758
+ max-width: none !important;
759
+ text-align: left !important;
760
+ }
761
+ /* Gradio 5.30: default nav has horizontal margin/padding; keep bar tight */
762
+ nav.oh-top-nav.svelte-ti537g.svelte-ti537g {
763
+ margin: 0 !important;
764
+ }
765
+ /* .oh-top-nav is applied by oh_top_nav_script — Gradio 5.30+ has no .nav-holder */
766
+ .oh-top-nav {
767
+ position: fixed !important;
768
+ top: 0 !important;
769
+ left: 0 !important;
770
+ right: 0 !important;
771
+ z-index: 200 !important;
772
+ flex: 0 0 auto !important;
773
+ display: flex !important;
774
+ flex-direction: row !important;
775
+ flex-flow: row nowrap !important;
776
+ flex-wrap: nowrap !important;
777
+ align-items: center !important;
778
+ align-content: center !important;
779
+ justify-content: flex-end !important;
780
+ gap: 4px !important;
781
+ height: 56px !important;
782
+ min-height: 56px !important;
783
+ max-height: 56px !important;
784
+ width: 100% !important;
785
+ max-width: 100% !important;
786
+ box-sizing: border-box !important;
787
+ padding: 0 20px 0 164px !important;
788
+ margin: 0 !important;
789
+ /* Opaque bar — default light; dark overrides follow */
790
+ background: #fafafa !important;
791
+ background-color: #fafafa !important;
792
+ backdrop-filter: none !important;
793
+ -webkit-backdrop-filter: none !important;
794
+ border-bottom: 1px solid #e4e4e7 !important;
795
+ box-shadow: none !important;
796
+ overflow: visible !important;
797
+ list-style: none !important;
798
+ -webkit-backface-visibility: hidden;
799
+ backface-visibility: hidden;
800
+ }
801
+ /* Gradio .fillable limits max-width at breakpoints; keep route bar edge-to-edge (nav can be .fillable.svelte-ti537g) */
802
+ nav.oh-top-nav.fillable,
803
+ .oh-top-nav.fillable,
804
+ nav.fillable.oh-top-nav,
805
+ nav.oh-top-nav.svelte-ti537g.fillable {
806
+ max-width: none !important;
807
+ width: 100% !important;
808
  }
809
+ /* Dark top bar: document and/or .dark on Gradio container (including :has when only container is .dark) */
810
+ html.dark .oh-top-nav,
811
+ body.dark .oh-top-nav,
812
+ html:has([class*="gradio-container-"].dark) .oh-top-nav,
813
+ .gradio-container.dark .oh-top-nav,
814
+ [class*="gradio-container-"].dark .oh-top-nav {
815
+ background: var(--oh-bg) !important;
816
+ background-color: var(--oh-bg) !important;
817
+ border-bottom: 1px solid var(--oh-border) !important;
818
+ backdrop-filter: none !important;
819
+ -webkit-backdrop-filter: none !important;
820
+ }
821
+ /* Active route chip visuals: see app.py final_css (appended after this file; must beat Gradio a.active.svelte-ti537g) */
822
+ /* Avoid double line: nav border-bottom + first block border-top */
823
+ .oh-top-nav + * {
824
+ border-top: none !important;
825
+ box-shadow: none !important;
826
+ }
827
+ .oh-top-nav::-webkit-scrollbar { display: none !important; }
828
+ /* Gradio 5.30: every route <a> uses .svelte-ti537g; some (e.g. duplicate Home) lack .oh-nav-link — clear underline/visited */
829
+ .oh-top-nav a.svelte-ti537g,
830
+ .oh-top-nav a.svelte-ti537g:hover,
831
+ .oh-top-nav a.svelte-ti537g:focus,
832
+ .oh-top-nav a.svelte-ti537g:active,
833
+ .oh-top-nav a.svelte-ti537g:visited,
834
+ .oh-top-nav a.svelte-ti537g.active {
835
+ text-decoration: none !important;
836
+ text-decoration-line: none !important;
837
+ }
838
+ /* Gradio can set column layout on parents; keep links in one row */
839
+ .oh-top-nav > a.oh-nav-link,
840
+ .oh-top-nav a.oh-nav-link {
841
+ flex: 0 0 auto !important;
842
+ display: inline-flex !important;
843
+ align-items: center !important;
844
  }
845
  #legend-markdown span {
846
  margin-right: 15px !important;
 
851
  position: relative !important;
852
  }
853
  .dark #leaderboard-accordion .label-wrap {
854
+ color: var(--oh-fg) !important;
855
  }
856
  .dark block.svelte-1svsvh2 {
857
  background: var(--color-background-dark) !important;
 
863
  display: flex !important;
864
  flex-wrap: wrap !important;
865
  align-items: center !important;
866
+ gap: 0.5rem !important; /* gap-2 */
 
 
 
867
  }
868
+ .dark .primary-link-button,
869
  .primary-link-button {
870
  background: none;
871
  border: none;
872
  padding: 0;
873
  margin: 0;
874
  font-family: inherit;
875
+ font-size: var(--oh-prose-size);
876
+ font-weight: 500;
877
+ color: var(--oh-info);
878
  text-decoration: none;
879
  cursor: pointer;
880
  white-space: nowrap;
881
+ transition: color 0.2s ease;
882
  }
883
  .primary-link-button:hover {
884
+ color: var(--oh-link-hover);
885
+ text-decoration: none;
886
+ }
887
+ .primary-link-button:focus-visible {
888
+ color: var(--oh-link-hover);
889
+ text-decoration: none;
890
+ outline: 2px solid var(--oh-ring);
891
+ outline-offset: 2px;
892
+ border-radius: 2px;
893
  }
894
  .sub-nav-label {
895
+ font-weight: 600;
896
+ font-size: var(--oh-prose-size);
897
  display: flex;
898
  align-items: center;
899
  }
900
+ .wrap-header-df th span {
901
  white-space: normal !important;
902
  word-break: normal !important;
903
  overflow-wrap: break-word !important;
904
  line-height: 1.2 !important;
905
  vertical-align: top !important;
906
  font-size: 12px !important;
907
+ font-family: var(--oh-font-sans) !important;
908
  }
909
  .wrap-header-df th {
910
  height: auto !important;
 
978
  /* --- inside table tooltips --- */
979
  .native-tooltip-icon {
980
  cursor: help;
981
+ text-decoration: none;
 
 
 
 
 
 
 
 
 
 
 
 
982
  }
983
+ /* Route chips: do not require .oh-nav-link (JS adds it async — without it, .dark a and Gradio nav rules used to break hover/active) */
984
+ gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active),
985
+ gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active),
986
+ .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active) {
987
+ font-family: var(--oh-font-sans) !important;
988
+ font-size: 13px !important;
989
+ font-weight: 400 !important;
990
+ line-height: 1.4 !important;
991
+ color: #71717a !important;
992
+ text-decoration: none !important;
993
+ padding: 6px 12px !important;
994
+ border-radius: 6px !important;
995
+ border: 1px solid transparent !important;
996
+ transition: color 0.2s, background-color 0.2s, border-color 0.2s !important;
997
  white-space: nowrap !important;
998
+ box-shadow: none !important;
999
+ cursor: pointer !important;
1000
+ pointer-events: auto !important;
1001
+ position: relative !important;
1002
+ z-index: 1 !important;
1003
+ }
1004
+ gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active):hover,
1005
+ gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active):focus-visible,
1006
+ gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active):hover,
1007
+ gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active):focus-visible,
1008
+ .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active):hover,
1009
+ .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active):focus-visible {
1010
+ color: #fafafa !important;
1011
+ background-color: #18181b !important;
1012
+ border-color: transparent !important;
1013
+ }
1014
+ /* Dark: muted links on dark bar */
1015
+ html.dark gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active),
1016
+ html.dark gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active),
1017
+ html.dark .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active),
1018
+ body.dark gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active),
1019
+ body.dark gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active),
1020
+ body.dark .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active),
1021
+ html:has([class*="gradio-container-"].dark) gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active),
1022
+ html:has([class*="gradio-container-"].dark) gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active),
1023
+ html:has([class*="gradio-container-"].dark) .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active),
1024
+ .gradio-container.dark gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active),
1025
+ .gradio-container.dark gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active),
1026
+ .gradio-container.dark .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active),
1027
+ [class*="gradio-container-"].dark gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active),
1028
+ [class*="gradio-container-"].dark gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active),
1029
+ [class*="gradio-container-"].dark .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active) {
1030
+ color: var(--oh-fg-muted) !important;
1031
+ }
1032
+ html.dark gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active):hover,
1033
+ html.dark gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active):focus-visible,
1034
+ html.dark gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active):hover,
1035
+ html.dark gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active):focus-visible,
1036
+ html.dark .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active):hover,
1037
+ html.dark .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active):focus-visible,
1038
+ body.dark gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active):hover,
1039
+ body.dark gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active):focus-visible,
1040
+ body.dark gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active):hover,
1041
+ body.dark gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active):focus-visible,
1042
+ body.dark .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active):hover,
1043
+ body.dark .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active):focus-visible,
1044
+ html:has([class*="gradio-container-"].dark) gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active):hover,
1045
+ html:has([class*="gradio-container-"].dark) gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active):focus-visible,
1046
+ html:has([class*="gradio-container-"].dark) gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active):hover,
1047
+ html:has([class*="gradio-container-"].dark) gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active):focus-visible,
1048
+ html:has([class*="gradio-container-"].dark) .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active):hover,
1049
+ html:has([class*="gradio-container-"].dark) .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active):focus-visible,
1050
+ .gradio-container.dark gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active):hover,
1051
+ .gradio-container.dark gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active):focus-visible,
1052
+ .gradio-container.dark gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active):hover,
1053
+ .gradio-container.dark gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active):focus-visible,
1054
+ .gradio-container.dark .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active):hover,
1055
+ .gradio-container.dark .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active):focus-visible,
1056
+ [class*="gradio-container-"].dark gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active):hover,
1057
+ [class*="gradio-container-"].dark gradio-app nav.oh-top-nav a.svelte-ti537g:not([href*="/home"]):not(.active):focus-visible,
1058
+ [class*="gradio-container-"].dark gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active):hover,
1059
+ [class*="gradio-container-"].dark gradio-app nav.svelte-ti537g a.svelte-ti537g:not([href*="/home"]):not(.active):focus-visible,
1060
+ [class*="gradio-container-"].dark .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active):hover,
1061
+ [class*="gradio-container-"].dark .oh-top-nav a.oh-nav-link:not([href*="/home"]):not(.active):focus-visible {
1062
+ color: #ffffff !important;
1063
+ background-color: var(--oh-muted) !important;
1064
+ }
1065
+ /* Dark app shell (OpenHands-Design near-black) */
1066
+ body.dark, .gradio-container.dark {
1067
+ --body-background-fill: #0d0d0d !important;
1068
+ /* Must match body: #121212 vs #0d0d0d on nested Gradio blocks reads as a full-height left band on leaderboard/row layouts; About is unaffected visually */
1069
+ --block-background-fill: #0d0d0d !important;
1070
+ }
1071
+ body.dark, .gradio-container.dark,
1072
+ .gradio-container.dark .contain,
1073
+ .gradio-container.dark .wrap,
1074
+ .gradio-container.dark .main,
1075
+ .gradio-container.dark {
1076
+ background-color: #0d0d0d !important;
1077
+ color: #fafafa !important;
1078
+ }
1079
+ .benchmark-main-subtitle {
1080
+ color: var(--oh-fg-muted);
1081
  overflow: hidden;
1082
  padding-top: 120px;
1083
+ font-size: var(--oh-prose-size);
1084
+ font-weight: 500;
1085
+ line-height: var(--oh-prose-leading);
1086
  }
1087
+ .benchmark-title {
1088
+ color: var(--color-text-dark);
1089
+ margin-top: 2.5rem;
1090
+ /* DESIGN.md: section heading — text-lg / font-semibold */
1091
+ font-size: 1.125rem;
1092
+ font-weight: 600;
1093
+ line-height: 1.3;
1094
+ letter-spacing: -0.02em;
1095
  }
1096
+ .dark .benchmark-title {
1097
+ color: var(--oh-fg);
 
 
 
 
 
1098
  }
1099
  .benchmark-description {
1100
+ margin: var(--oh-gap-prose-paragraph) 0 1.5rem;
1101
+ max-width: 52rem;
1102
+ font-size: var(--oh-prose-size);
1103
+ line-height: var(--oh-prose-leading);
1104
  }
1105
+ .benchmark-description p {
1106
+ margin: 0 0 var(--oh-gap-prose-paragraph) 0;
1107
+ }
1108
+ .benchmark-description p:last-child {
1109
+ margin-bottom: 0;
1110
  }
1111
+
1112
+ /* DESIGN.md: page heading — text-2xl / font-semibold */
1113
+ .dark #main-header h2,
1114
  #main-header h2 {
1115
+ color: #fafafa !important;
1116
+ font-size: 1.5rem;
1117
+ line-height: 1.2;
1118
+ font-weight: 600;
1119
+ letter-spacing: -0.02em;
1120
+ margin: 0 0 var(--oh-gap-prose-paragraph) 0;
1121
+ }
1122
+ .gradio-container:not(.dark) #main-header h2 {
1123
+ color: #0d0d0d !important;
1124
  }
1125
 
1126
  /* --- New HTML-Based Tooltip Styles --- */
 
1206
  flex-wrap: wrap;
1207
  }
1208
 
1209
+ /* About Page CSS — nested in #page-content-wrapper for same horizontal + top padding as Home */
1210
  #about-page-content-wrapper {
1211
  margin-left: auto;
1212
  margin-right: auto;
1213
+ max-width: 50rem; /* 800px — readable line length */
1214
+ padding: 0;
1215
  display: flex;
1216
  flex-direction: column;
1217
+ gap: 2.5rem; /* 40px — matches index .section-title margin rhythm */
1218
+ margin-top: 0;
1219
+ opacity: 0.85;
1220
+ margin-bottom: 3.75rem; /* 60px */
1221
+ box-sizing: border-box;
1222
+ }
1223
+
1224
+ /* Alternative Agents intro — align with Home / category hero width and heading scale */
1225
+ #alternative-agents-intro {
1226
+ font-size: var(--oh-prose-size);
1227
+ line-height: var(--oh-prose-leading);
1228
+ max-width: min(100%, 52rem);
1229
+ }
1230
+ #alternative-agents-intro h2 {
1231
+ font-size: 1.5rem;
1232
+ font-weight: 600;
1233
+ line-height: 1.2;
1234
+ letter-spacing: -0.02em;
1235
+ margin: 0 0 var(--oh-gap-prose-paragraph) 0;
1236
+ color: var(--body-text-color, var(--oh-fg)) !important;
1237
+ }
1238
+ #alternative-agents-intro p {
1239
+ margin: 0 0 var(--oh-gap-prose-paragraph) 0;
1240
+ }
1241
+ #alternative-agents-intro p:last-child {
1242
+ margin-bottom: 0;
1243
+ }
1244
+ #about-page-content-wrapper h2 {
1245
+ font-size: 1.5rem;
1246
+ font-weight: 600;
1247
+ line-height: 1.2;
1248
+ letter-spacing: -0.02em;
1249
+ margin: 0 0 0.75rem 0;
1250
+ color: var(--body-text-color, var(--oh-fg)) !important;
1251
+ }
1252
+ #about-page-content-wrapper p,
1253
+ #about-page-content-wrapper li {
1254
+ font-size: var(--oh-prose-size);
1255
+ line-height: var(--oh-prose-leading);
1256
+ }
1257
+ #about-page-content-wrapper p {
1258
+ margin: 0 0 var(--oh-gap-prose-paragraph) 0;
1259
+ }
1260
+ #about-page-content-wrapper p:last-child {
1261
+ margin-bottom: 0;
1262
+ }
1263
+ #about-page-content-wrapper .info-list {
1264
+ margin: var(--oh-gap-prose-paragraph) 0;
1265
+ padding-left: 1.25rem;
1266
+ }
1267
+ #about-page-content-wrapper .info-list li + li {
1268
+ margin-top: 0.35em;
1269
  }
1270
  .link-buttons-container {
1271
  display: flex;
 
1332
  min-height: 572px;
1333
  background-color: #fff;
1334
  padding: 24px 32px;
1335
+ border: 1px solid var(--oh-border);
1336
  border-radius: 4px;
1337
  }
1338
 
1339
  .dark .plot-legend-container {
1340
  background: rgba(247, 248, 251, 0.1);
1341
+ border-color: var(--oh-border);
1342
  }
1343
 
1344
  #plot-legend-logo {
 
1400
  .winners-by-category-container {
1401
  margin: 20px 0;
1402
  overflow-x: auto;
1403
+ scrollbar-gutter: stable;
1404
+ /* Horizontal scrollbar: thin thumb, no visible track (OpenHands-Design) */
1405
+ scrollbar-width: thin;
1406
+ scrollbar-color: hsl(0 0% 40% / 0.5) transparent;
1407
+ }
1408
+ html.dark .winners-by-category-container,
1409
+ body.dark .winners-by-category-container,
1410
+ .gradio-container.dark .winners-by-category-container {
1411
+ scrollbar-color: hsl(0 0% 55% / 0.45) transparent;
1412
+ }
1413
+ .winners-by-category-container::-webkit-scrollbar {
1414
+ height: 6px;
1415
+ }
1416
+ .winners-by-category-container::-webkit-scrollbar-track {
1417
+ background: transparent;
1418
+ }
1419
+ .winners-by-category-container::-webkit-scrollbar-thumb {
1420
+ background: hsl(0 0% 40% / 0.5);
1421
+ border-radius: 9999px;
1422
+ }
1423
+ .winners-by-category-container::-webkit-scrollbar-thumb:hover {
1424
+ background: hsl(0 0% 35% / 0.7);
1425
+ }
1426
+ html.dark .winners-by-category-container::-webkit-scrollbar-thumb,
1427
+ body.dark .winners-by-category-container::-webkit-scrollbar-thumb,
1428
+ .gradio-container.dark .winners-by-category-container::-webkit-scrollbar-thumb {
1429
+ background: hsl(0 0% 55% / 0.5);
1430
+ }
1431
+ html.dark .winners-by-category-container::-webkit-scrollbar-thumb:hover,
1432
+ body.dark .winners-by-category-container::-webkit-scrollbar-thumb:hover,
1433
+ .gradio-container.dark .winners-by-category-container::-webkit-scrollbar-thumb:hover {
1434
+ background: hsl(0 0% 60% / 0.65);
1435
  }
1436
 
1437
  .winners-unified-table {
 
1439
  border-collapse: collapse;
1440
  font-size: 13px;
1441
  background: #fff;
1442
+ border: 1px solid var(--oh-border);
1443
  border-radius: 12px;
1444
  overflow: hidden;
1445
  }
1446
 
1447
  .dark .winners-unified-table {
1448
  background: rgba(247, 248, 251, 0.05);
1449
+ border-color: var(--oh-border);
1450
  }
1451
 
1452
+ /* Category header bar: flat neutral grey */
1453
  .winners-unified-table thead tr {
1454
+ background: hsl(0 0% 92%);
1455
  }
1456
 
1457
  .dark .winners-unified-table thead tr {
1458
+ background: hsl(0 0% 12%);
1459
  }
1460
 
1461
  .winners-unified-table .category-header {
 
1464
  font-weight: 700;
1465
  font-size: 13px;
1466
  color: var(--color-text-dark);
1467
+ border-bottom: 1px solid var(--oh-border) !important;
1468
+ border-right: 1px solid var(--oh-border) !important;
1469
  white-space: nowrap;
1470
  }
1471
 
1472
  .dark .winners-unified-table .category-header {
1473
  color: #fff;
1474
+ border-bottom-color: var(--oh-border) !important;
1475
+ border-right-color: var(--oh-border) !important;
1476
  }
1477
 
1478
  .winners-unified-table .category-header:last-child {
 
1489
  .winners-unified-table td {
1490
  padding: 8px 6px;
1491
  vertical-align: middle;
1492
+ border-bottom: 1px solid var(--oh-border);
1493
  }
1494
 
1495
  .dark .winners-unified-table td {
1496
+ border-bottom-color: var(--oh-border);
1497
  }
1498
 
1499
  .winners-unified-table tbody tr:last-child td {
1500
  border-bottom: none;
1501
  }
1502
 
1503
+ /* Row hover: muted/60 style (DESIGN.md), not yellow */
1504
  .winners-unified-table tbody tr:hover {
1505
+ background: hsl(0 0% 0% / 0.04);
1506
  }
1507
 
1508
  .dark .winners-unified-table tbody tr:hover {
1509
+ background: hsl(0 0% 12% / 0.5);
1510
  }
1511
 
1512
  .winners-unified-table .score-cell {
 
1515
  color: var(--color-primary-dark);
1516
  padding-left: 12px;
1517
  min-width: 50px;
1518
+ border-right: 1px solid var(--oh-border);
1519
  }
1520
 
1521
  .dark .winners-unified-table .score-cell {
1522
  color: var(--color-primary-accent);
1523
+ border-right-color: var(--oh-border);
1524
  }
1525
 
1526
  .winners-unified-table .model-cell {
 
1528
  color: var(--color-text-dark);
1529
  font-weight: 500;
1530
  padding-right: 12px;
1531
+ border-right: 1px solid var(--oh-border);
1532
  }
1533
 
1534
  .dark .winners-unified-table .model-cell {
1535
  color: #fff;
1536
+ border-right-color: var(--oh-border);
1537
  }
1538
 
1539
  .winners-unified-table td:nth-last-child(1) {
 
1623
  padding-right: 8px !important;
1624
  }
1625
 
 
1626
  #intro-paragraph {
1627
+ padding-left: 0 !important;
1628
+ padding-right: 0 !important;
1629
  max-width: 100% !important;
1630
  }
1631
 
 
1636
  max-width: 100% !important;
1637
  }
1638
 
1639
+ /* Nav: keep left gutter for wordmark; tighten sides on small viewports */
1640
+ .oh-top-nav,
1641
+ gradio-app nav.svelte-ti537g {
1642
+ padding-left: 116px !important;
1643
  padding-right: 8px !important;
1644
  }
1645
 
 
1650
  max-width: 100% !important;
1651
  }
1652
 
1653
+ /* About is inside #page-content-wrapper — outer handles horizontal padding */
1654
  #about-page-content-wrapper {
1655
+ padding-left: 0 !important;
1656
+ padding-right: 0 !important;
1657
  }
1658
 
1659
  /* Reduce gradio container padding */
main_page.py CHANGED
@@ -54,107 +54,114 @@ def filter_complete_entries(df: pd.DataFrame) -> pd.DataFrame:
54
 
55
 
56
  def build_page():
57
- with gr.Row(elem_id="intro-row"):
58
- with gr.Column(scale=1):
59
- gr.HTML(INTRO_PARAGRAPH, elem_id="intro-paragraph")
60
-
61
- # --- Leaderboard Display Section ---
62
- gr.Markdown("---")
63
- CATEGORY_NAME = "Overall"
64
- gr.HTML(f'<h2>OpenHands Index {CATEGORY_NAME} Leaderboard <span style="font-weight: normal; color: inherit;">(Aggregate)</span></h2>', elem_id="main-header")
65
-
66
- test_df, test_tag_map = get_full_leaderboard_data("test")
67
- if not test_df.empty:
68
- show_incomplete_checkbox, show_open_only_checkbox, mark_by_dropdown = create_leaderboard_display(
69
- full_df=test_df,
70
- tag_map=test_tag_map,
71
- category_name=CATEGORY_NAME,
72
- split_name="test"
73
  )
74
 
75
- test_df_complete = filter_complete_entries(test_df)
76
- has_complete_entries = len(test_df_complete) > 0
 
 
 
 
 
 
77
 
78
- if 'Openness' in test_df.columns:
79
- test_df_open = test_df[test_df['Openness'].str.lower() == 'open'].copy()
80
- else:
81
- test_df_open = test_df.copy()
82
- test_df_complete_open = filter_complete_entries(test_df_open)
83
 
84
- initial_df = test_df_complete if has_complete_entries else test_df
 
 
 
 
85
 
86
- # --- Winners by Category Section ---
87
- gr.Markdown("---")
88
- gr.HTML('<h2>Winners by Category</h2>', elem_id="winners-header")
89
- gr.Markdown("Top 5 performing systems in each benchmark category.")
90
 
91
- winners_component = gr.HTML(
92
- create_winners_by_category_html(initial_df, top_n=5),
93
- elem_id="winners-by-category",
94
- )
95
 
96
- # --- New Visualization Sections ---
97
- gr.Markdown("---")
 
 
98
 
99
- # Evolution Over Time Section
100
- gr.HTML('<h2>Evolution Over Time</h2>', elem_id="evolution-header")
101
- gr.Markdown("Track how model performance has improved over time based on release dates.")
102
 
103
- evolution_component = gr.Plot(
104
- value=create_evolution_over_time_chart(initial_df, MARK_BY_DEFAULT),
105
- elem_id="evolution-chart",
106
- )
107
 
108
- gr.Markdown("---")
 
 
 
109
 
110
- # Open Model Accuracy by Size Section (always shows open models only by design)
111
- gr.HTML('<h2>Open Model Accuracy by Size</h2>', elem_id="size-accuracy-header")
112
- gr.Markdown("Compare open-weights model performance against their parameter count.")
113
 
114
- size_component = gr.Plot(
115
- value=create_accuracy_by_size_chart(initial_df, MARK_BY_DEFAULT),
116
- elem_id="size-accuracy-chart",
117
- )
118
 
119
- def update_extra_sections(show_incomplete, show_open_only, mark_by):
120
- include_incomplete = show_incomplete or not has_complete_entries
121
- base_df = test_df if include_incomplete else test_df_complete
122
- base_df_open = test_df_open if include_incomplete else test_df_complete_open
123
- winners_df = base_df_open if show_open_only else base_df
124
 
125
- winners_html = create_winners_by_category_html(winners_df, top_n=5)
126
- evolution_fig = create_evolution_over_time_chart(winners_df, mark_by)
127
- size_fig = create_accuracy_by_size_chart(base_df, mark_by)
 
 
128
 
129
- return winners_html, evolution_fig, size_fig
 
 
130
 
131
- show_incomplete_input = show_incomplete_checkbox if show_incomplete_checkbox is not None else gr.State(value=True)
132
- show_open_only_input = show_open_only_checkbox if show_open_only_checkbox is not None else gr.State(value=False)
133
- extra_section_inputs = [show_incomplete_input, show_open_only_input, mark_by_dropdown]
134
 
135
- if show_incomplete_checkbox is not None:
136
- show_incomplete_checkbox.change(
137
- fn=update_extra_sections,
138
- inputs=extra_section_inputs,
139
- outputs=[winners_component, evolution_component, size_component]
140
  )
141
-
142
- if show_open_only_checkbox is not None:
143
- show_open_only_checkbox.change(
144
- fn=update_extra_sections,
145
- inputs=extra_section_inputs,
146
- outputs=[winners_component, evolution_component, size_component]
147
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
- if mark_by_dropdown is not None:
150
- mark_by_dropdown.change(
151
- fn=update_extra_sections,
152
- inputs=extra_section_inputs,
153
- outputs=[winners_component, evolution_component, size_component]
154
- )
155
-
156
- else:
157
- gr.Markdown("No data available.")
158
 
159
  if __name__ == "__main__":
160
  demo.launch()
 
54
 
55
 
56
  def build_page():
57
+ with gr.Column(elem_id="page-content-wrapper"):
58
+ with gr.Row(elem_id="intro-row"):
59
+ with gr.Column(scale=1):
60
+ gr.HTML(INTRO_PARAGRAPH, elem_id="intro-paragraph")
61
+
62
+ # --- Leaderboard Display Section ---
63
+ CATEGORY_NAME = "Overall"
64
+ gr.HTML(
65
+ f'<h2>OpenHands Index {CATEGORY_NAME} Leaderboard <span style="font-weight: normal; color: inherit;">(Aggregate)</span></h2>',
66
+ elem_id="main-header",
 
 
 
 
 
 
67
  )
68
 
69
+ test_df, test_tag_map = get_full_leaderboard_data("test")
70
+ if not test_df.empty:
71
+ show_incomplete_checkbox, show_open_only_checkbox, mark_by_dropdown = create_leaderboard_display(
72
+ full_df=test_df,
73
+ tag_map=test_tag_map,
74
+ category_name=CATEGORY_NAME,
75
+ split_name="test",
76
+ )
77
 
78
+ test_df_complete = filter_complete_entries(test_df)
79
+ has_complete_entries = len(test_df_complete) > 0
 
 
 
80
 
81
+ if "Openness" in test_df.columns:
82
+ test_df_open = test_df[test_df["Openness"].str.lower() == "open"].copy()
83
+ else:
84
+ test_df_open = test_df.copy()
85
+ test_df_complete_open = filter_complete_entries(test_df_open)
86
 
87
+ initial_df = test_df_complete if has_complete_entries else test_df
 
 
 
88
 
89
+ # --- Winners by Category Section ---
90
+ gr.Markdown("---")
91
+ gr.HTML('<h2>Winners by Category</h2>', elem_id="winners-header")
92
+ gr.Markdown("Top 5 performing systems in each benchmark category.")
93
 
94
+ winners_component = gr.HTML(
95
+ create_winners_by_category_html(initial_df, top_n=5),
96
+ elem_id="winners-by-category",
97
+ )
98
 
99
+ # --- New Visualization Sections ---
100
+ gr.Markdown("---")
 
101
 
102
+ # Evolution Over Time Section
103
+ gr.HTML('<h2>Evolution Over Time</h2>', elem_id="evolution-header")
104
+ gr.Markdown("Track how model performance has improved over time based on release dates.")
 
105
 
106
+ evolution_component = gr.Plot(
107
+ value=create_evolution_over_time_chart(initial_df, MARK_BY_DEFAULT),
108
+ elem_id="evolution-chart",
109
+ )
110
 
111
+ gr.Markdown("---")
 
 
112
 
113
+ # Open Model Accuracy by Size Section (always shows open models only by design)
114
+ gr.HTML('<h2>Open Model Accuracy by Size</h2>', elem_id="size-accuracy-header")
115
+ gr.Markdown("Compare open-weights model performance against their parameter count.")
 
116
 
117
+ size_component = gr.Plot(
118
+ value=create_accuracy_by_size_chart(initial_df, MARK_BY_DEFAULT),
119
+ elem_id="size-accuracy-chart",
120
+ )
 
121
 
122
+ def update_extra_sections(show_incomplete, show_open_only, mark_by):
123
+ include_incomplete = show_incomplete or not has_complete_entries
124
+ base_df = test_df if include_incomplete else test_df_complete
125
+ base_df_open = test_df_open if include_incomplete else test_df_complete_open
126
+ winners_df = base_df_open if show_open_only else base_df
127
 
128
+ winners_html = create_winners_by_category_html(winners_df, top_n=5)
129
+ evolution_fig = create_evolution_over_time_chart(winners_df, mark_by)
130
+ size_fig = create_accuracy_by_size_chart(base_df, mark_by)
131
 
132
+ return winners_html, evolution_fig, size_fig
 
 
133
 
134
+ show_incomplete_input = (
135
+ show_incomplete_checkbox if show_incomplete_checkbox is not None else gr.State(value=True)
 
 
 
136
  )
137
+ show_open_only_input = (
138
+ show_open_only_checkbox if show_open_only_checkbox is not None else gr.State(value=False)
 
 
 
 
139
  )
140
+ extra_section_inputs = [show_incomplete_input, show_open_only_input, mark_by_dropdown]
141
+
142
+ if show_incomplete_checkbox is not None:
143
+ show_incomplete_checkbox.change(
144
+ fn=update_extra_sections,
145
+ inputs=extra_section_inputs,
146
+ outputs=[winners_component, evolution_component, size_component],
147
+ )
148
+
149
+ if show_open_only_checkbox is not None:
150
+ show_open_only_checkbox.change(
151
+ fn=update_extra_sections,
152
+ inputs=extra_section_inputs,
153
+ outputs=[winners_component, evolution_component, size_component],
154
+ )
155
+
156
+ if mark_by_dropdown is not None:
157
+ mark_by_dropdown.change(
158
+ fn=update_extra_sections,
159
+ inputs=extra_section_inputs,
160
+ outputs=[winners_component, evolution_component, size_component],
161
+ )
162
 
163
+ else:
164
+ gr.Markdown("No data available.")
 
 
 
 
 
 
 
165
 
166
  if __name__ == "__main__":
167
  demo.launch()
ui_components.py CHANGED
@@ -705,7 +705,7 @@ def create_leaderboard_display(
705
  primary_runtime_col = f"{category_name} Runtime"
706
 
707
  # Function to create cost/performance scatter plot from data
708
- def create_cost_scatter_plot(df_data, mark_by=MARK_BY_DEFAULT, show_all_labels=False):
709
  return _plot_scatter_plotly(
710
  data=df_data,
711
  x=primary_cost_col if primary_cost_col in df_data.columns else None,
@@ -713,12 +713,11 @@ def create_leaderboard_display(
713
  agent_col="SDK Version",
714
  name=category_name,
715
  plot_type='cost',
716
- mark_by=mark_by,
717
- show_all_labels=show_all_labels
718
  )
719
 
720
  # Function to create runtime/performance scatter plot from data
721
- def create_runtime_scatter_plot(df_data, mark_by=MARK_BY_DEFAULT, show_all_labels=False):
722
  return _plot_scatter_plotly(
723
  data=df_data,
724
  x=primary_runtime_col if primary_runtime_col in df_data.columns else None,
@@ -726,8 +725,7 @@ def create_leaderboard_display(
726
  agent_col="SDK Version",
727
  name=category_name,
728
  plot_type='runtime',
729
- mark_by=mark_by,
730
- show_all_labels=show_all_labels
731
  )
732
 
733
  # Create initial cost scatter plots for all filter combinations
@@ -774,7 +772,7 @@ def create_leaderboard_display(
774
 
775
  # Add toggle checkboxes and dropdown ABOVE the plot
776
  with gr.Row():
777
- with gr.Column(scale=3):
778
  if has_complete_entries:
779
  show_incomplete_checkbox = gr.Checkbox(
780
  label=f"Show incomplete entries ({num_incomplete} entries with fewer than 5 categories)",
@@ -794,7 +792,6 @@ def create_leaderboard_display(
794
  )
795
  else:
796
  show_open_only_checkbox = None
797
-
798
  # Add checkbox for showing all labels on scatter plot
799
  show_all_labels_checkbox = gr.Checkbox(
800
  label="Show all labels on scatter plots",
@@ -853,7 +850,7 @@ def create_leaderboard_display(
853
  df_to_show = df_display_all if show_incomplete else df_display_complete
854
  view_df = df_view_full if show_incomplete else df_view_complete
855
 
856
- # Regenerate plots with current mark_by and show_all_labels settings
857
  cost_plot = create_cost_scatter_plot(view_df, mark_by, show_all_labels)
858
  runtime_plot = create_runtime_scatter_plot(view_df, mark_by, show_all_labels)
859
  return df_to_show, cost_plot, runtime_plot
@@ -884,11 +881,6 @@ def create_leaderboard_display(
884
  inputs=filter_inputs,
885
  outputs=[dataframe_component, cost_plot_component, runtime_plot_component]
886
  )
887
- show_all_labels_checkbox.change(
888
- fn=update_display,
889
- inputs=filter_inputs,
890
- outputs=[dataframe_component, cost_plot_component, runtime_plot_component]
891
- )
892
  else:
893
  dataframe_component = gr.DataFrame(
894
  headers=df_headers,
@@ -933,11 +925,6 @@ def create_leaderboard_display(
933
  inputs=filter_inputs_no_complete,
934
  outputs=[dataframe_component, cost_plot_component, runtime_plot_component]
935
  )
936
- show_all_labels_checkbox.change(
937
- fn=update_display_no_complete,
938
- inputs=filter_inputs_no_complete,
939
- outputs=[dataframe_component, cost_plot_component, runtime_plot_component]
940
- )
941
 
942
  legend_markdown = create_legend_markdown(category_name)
943
  gr.HTML(value=legend_markdown, elem_id="legend-markdown")
@@ -974,16 +961,16 @@ def create_leaderboard_display(
974
  new_df_display_open = prepare_df_for_display(new_df_view_open)
975
  new_df_display_complete_open = prepare_df_for_display(new_df_view_complete_open)
976
 
977
- # Create new scatter plots for all combinations (with current mark_by and show_all_labels)
978
- new_cost_scatter_complete = create_cost_scatter_plot(new_df_view_complete, mark_by, show_all_labels) if len(new_df_display_complete) > 0 else go.Figure()
979
- new_cost_scatter_all = create_cost_scatter_plot(new_df_view_full, mark_by, show_all_labels)
980
- new_cost_scatter_open = create_cost_scatter_plot(new_df_view_open, mark_by, show_all_labels) if len(new_df_view_open) > 0 else go.Figure()
981
- new_cost_scatter_complete_open = create_cost_scatter_plot(new_df_view_complete_open, mark_by, show_all_labels) if len(new_df_view_complete_open) > 0 else go.Figure()
982
 
983
- new_runtime_scatter_complete = create_runtime_scatter_plot(new_df_view_complete, mark_by, show_all_labels) if len(new_df_display_complete) > 0 else go.Figure()
984
- new_runtime_scatter_all = create_runtime_scatter_plot(new_df_view_full, mark_by, show_all_labels)
985
- new_runtime_scatter_open = create_runtime_scatter_plot(new_df_view_open, mark_by, show_all_labels) if len(new_df_view_open) > 0 else go.Figure()
986
- new_runtime_scatter_complete_open = create_runtime_scatter_plot(new_df_view_complete_open, mark_by, show_all_labels) if len(new_df_view_complete_open) > 0 else go.Figure()
987
 
988
  # Return the appropriate data based on checkbox states
989
  if show_open_only:
@@ -1018,7 +1005,6 @@ def create_leaderboard_display(
1018
  if show_open_only_checkbox is not None:
1019
  timer_inputs.append(show_open_only_checkbox)
1020
  timer_inputs.append(mark_by_dropdown) # Always include mark_by
1021
- timer_inputs.append(show_all_labels_checkbox)
1022
  refresh_timer.tick(
1023
  fn=check_and_refresh_data,
1024
  inputs=timer_inputs,
@@ -1039,8 +1025,8 @@ def create_leaderboard_display(
1039
  new_df_view_full = new_df_view_full[new_df_view_full['Openness'].str.lower() == 'open'].copy()
1040
 
1041
  new_df_display_all = prepare_df_for_display(new_df_view_full)
1042
- new_cost_scatter_all = create_cost_scatter_plot(new_df_view_full, mark_by, show_all_labels)
1043
- new_runtime_scatter_all = create_runtime_scatter_plot(new_df_view_full, mark_by, show_all_labels)
1044
  return new_df_display_all, new_cost_scatter_all, new_runtime_scatter_all
1045
 
1046
  if show_open_only:
@@ -1050,15 +1036,15 @@ def create_leaderboard_display(
1050
  if show_open_only_checkbox is not None:
1051
  refresh_timer.tick(
1052
  fn=check_and_refresh_all,
1053
- inputs=[show_open_only_checkbox, mark_by_dropdown, show_all_labels_checkbox],
1054
  outputs=[dataframe_component, cost_plot_component, runtime_plot_component]
1055
  )
1056
  else:
1057
- def check_and_refresh_simple(mark_by=MARK_BY_DEFAULT, show_all_labels=False):
1058
- return check_and_refresh_all(False, mark_by, show_all_labels)
1059
  refresh_timer.tick(
1060
  fn=check_and_refresh_simple,
1061
- inputs=[mark_by_dropdown, show_all_labels_checkbox],
1062
  outputs=[dataframe_component, cost_plot_component, runtime_plot_component]
1063
  )
1064
 
 
705
  primary_runtime_col = f"{category_name} Runtime"
706
 
707
  # Function to create cost/performance scatter plot from data
708
+ def create_cost_scatter_plot(df_data, mark_by=MARK_BY_DEFAULT):
709
  return _plot_scatter_plotly(
710
  data=df_data,
711
  x=primary_cost_col if primary_cost_col in df_data.columns else None,
 
713
  agent_col="SDK Version",
714
  name=category_name,
715
  plot_type='cost',
716
+ mark_by=mark_by
 
717
  )
718
 
719
  # Function to create runtime/performance scatter plot from data
720
+ def create_runtime_scatter_plot(df_data, mark_by=MARK_BY_DEFAULT):
721
  return _plot_scatter_plotly(
722
  data=df_data,
723
  x=primary_runtime_col if primary_runtime_col in df_data.columns else None,
 
725
  agent_col="SDK Version",
726
  name=category_name,
727
  plot_type='runtime',
728
+ mark_by=mark_by
 
729
  )
730
 
731
  # Create initial cost scatter plots for all filter combinations
 
772
 
773
  # Add toggle checkboxes and dropdown ABOVE the plot
774
  with gr.Row():
775
+ with gr.Column(scale=3, elem_classes=["oh-leaderboard-filter-col"]):
776
  if has_complete_entries:
777
  show_incomplete_checkbox = gr.Checkbox(
778
  label=f"Show incomplete entries ({num_incomplete} entries with fewer than 5 categories)",
 
792
  )
793
  else:
794
  show_open_only_checkbox = None
 
795
  # Add checkbox for showing all labels on scatter plot
796
  show_all_labels_checkbox = gr.Checkbox(
797
  label="Show all labels on scatter plots",
 
850
  df_to_show = df_display_all if show_incomplete else df_display_complete
851
  view_df = df_view_full if show_incomplete else df_view_complete
852
 
853
+ # Regenerate plots with current mark_by setting
854
  cost_plot = create_cost_scatter_plot(view_df, mark_by, show_all_labels)
855
  runtime_plot = create_runtime_scatter_plot(view_df, mark_by, show_all_labels)
856
  return df_to_show, cost_plot, runtime_plot
 
881
  inputs=filter_inputs,
882
  outputs=[dataframe_component, cost_plot_component, runtime_plot_component]
883
  )
 
 
 
 
 
884
  else:
885
  dataframe_component = gr.DataFrame(
886
  headers=df_headers,
 
925
  inputs=filter_inputs_no_complete,
926
  outputs=[dataframe_component, cost_plot_component, runtime_plot_component]
927
  )
 
 
 
 
 
928
 
929
  legend_markdown = create_legend_markdown(category_name)
930
  gr.HTML(value=legend_markdown, elem_id="legend-markdown")
 
961
  new_df_display_open = prepare_df_for_display(new_df_view_open)
962
  new_df_display_complete_open = prepare_df_for_display(new_df_view_complete_open)
963
 
964
+ # Create new scatter plots for all combinations (with current mark_by)
965
+ new_cost_scatter_complete = create_cost_scatter_plot(new_df_view_complete, mark_by) if len(new_df_display_complete) > 0 else go.Figure()
966
+ new_cost_scatter_all = create_cost_scatter_plot(new_df_view_full, mark_by)
967
+ new_cost_scatter_open = create_cost_scatter_plot(new_df_view_open, mark_by) if len(new_df_view_open) > 0 else go.Figure()
968
+ new_cost_scatter_complete_open = create_cost_scatter_plot(new_df_view_complete_open, mark_by) if len(new_df_view_complete_open) > 0 else go.Figure()
969
 
970
+ new_runtime_scatter_complete = create_runtime_scatter_plot(new_df_view_complete, mark_by) if len(new_df_display_complete) > 0 else go.Figure()
971
+ new_runtime_scatter_all = create_runtime_scatter_plot(new_df_view_full, mark_by)
972
+ new_runtime_scatter_open = create_runtime_scatter_plot(new_df_view_open, mark_by) if len(new_df_view_open) > 0 else go.Figure()
973
+ new_runtime_scatter_complete_open = create_runtime_scatter_plot(new_df_view_complete_open, mark_by) if len(new_df_view_complete_open) > 0 else go.Figure()
974
 
975
  # Return the appropriate data based on checkbox states
976
  if show_open_only:
 
1005
  if show_open_only_checkbox is not None:
1006
  timer_inputs.append(show_open_only_checkbox)
1007
  timer_inputs.append(mark_by_dropdown) # Always include mark_by
 
1008
  refresh_timer.tick(
1009
  fn=check_and_refresh_data,
1010
  inputs=timer_inputs,
 
1025
  new_df_view_full = new_df_view_full[new_df_view_full['Openness'].str.lower() == 'open'].copy()
1026
 
1027
  new_df_display_all = prepare_df_for_display(new_df_view_full)
1028
+ new_cost_scatter_all = create_cost_scatter_plot(new_df_view_full, mark_by)
1029
+ new_runtime_scatter_all = create_runtime_scatter_plot(new_df_view_full, mark_by)
1030
  return new_df_display_all, new_cost_scatter_all, new_runtime_scatter_all
1031
 
1032
  if show_open_only:
 
1036
  if show_open_only_checkbox is not None:
1037
  refresh_timer.tick(
1038
  fn=check_and_refresh_all,
1039
+ inputs=[show_open_only_checkbox, mark_by_dropdown],
1040
  outputs=[dataframe_component, cost_plot_component, runtime_plot_component]
1041
  )
1042
  else:
1043
+ def check_and_refresh_simple(mark_by=MARK_BY_DEFAULT):
1044
+ return check_and_refresh_all(False, mark_by)
1045
  refresh_timer.tick(
1046
  fn=check_and_refresh_simple,
1047
+ inputs=[mark_by_dropdown],
1048
  outputs=[dataframe_component, cost_plot_component, runtime_plot_component]
1049
  )
1050
 
visualizations.py CHANGED
@@ -9,7 +9,7 @@ import plotly.graph_objects as go
9
  import aliases
10
 
11
  # Import the generic scatter chart function - single source of truth
12
- from leaderboard_transformer import create_scatter_chart, STANDARD_LAYOUT, STANDARD_FONT
13
 
14
 
15
  def _find_column(df: pd.DataFrame, candidates: list, default: str = None) -> str:
@@ -43,6 +43,7 @@ def create_evolution_over_time_chart(df: pd.DataFrame, mark_by: str = None) -> g
43
  font=STANDARD_FONT
44
  )
45
  fig.update_layout(**STANDARD_LAYOUT, title="Model Performance Evolution Over Time")
 
46
  return fig
47
 
48
  # Find score column
@@ -63,6 +64,7 @@ def create_evolution_over_time_chart(df: pd.DataFrame, mark_by: str = None) -> g
63
  font=STANDARD_FONT
64
  )
65
  fig.update_layout(**STANDARD_LAYOUT, title="Model Performance Evolution Over Time")
 
66
  return fig
67
 
68
  # Use the generic scatter chart
@@ -102,6 +104,7 @@ def create_accuracy_by_size_chart(df: pd.DataFrame, mark_by: str = None) -> go.F
102
  font=STANDARD_FONT
103
  )
104
  fig.update_layout(**STANDARD_LAYOUT, title="Open Model Accuracy by Size")
 
105
  return fig
106
 
107
  # Filter to only open-weights models
@@ -124,6 +127,7 @@ def create_accuracy_by_size_chart(df: pd.DataFrame, mark_by: str = None) -> go.F
124
  font=STANDARD_FONT
125
  )
126
  fig.update_layout(**STANDARD_LAYOUT, title="Open Model Accuracy by Size")
 
127
  return fig
128
 
129
  # Find score column
@@ -143,6 +147,7 @@ def create_accuracy_by_size_chart(df: pd.DataFrame, mark_by: str = None) -> go.F
143
  font=STANDARD_FONT
144
  )
145
  fig.update_layout(**STANDARD_LAYOUT, title="Open Model Accuracy by Size")
 
146
  return fig
147
 
148
  # Use the generic scatter chart
 
9
  import aliases
10
 
11
  # Import the generic scatter chart function - single source of truth
12
+ from leaderboard_transformer import apply_neutral_chart_gridlines, create_scatter_chart, STANDARD_LAYOUT, STANDARD_FONT
13
 
14
 
15
  def _find_column(df: pd.DataFrame, candidates: list, default: str = None) -> str:
 
43
  font=STANDARD_FONT
44
  )
45
  fig.update_layout(**STANDARD_LAYOUT, title="Model Performance Evolution Over Time")
46
+ apply_neutral_chart_gridlines(fig)
47
  return fig
48
 
49
  # Find score column
 
64
  font=STANDARD_FONT
65
  )
66
  fig.update_layout(**STANDARD_LAYOUT, title="Model Performance Evolution Over Time")
67
+ apply_neutral_chart_gridlines(fig)
68
  return fig
69
 
70
  # Use the generic scatter chart
 
104
  font=STANDARD_FONT
105
  )
106
  fig.update_layout(**STANDARD_LAYOUT, title="Open Model Accuracy by Size")
107
+ apply_neutral_chart_gridlines(fig)
108
  return fig
109
 
110
  # Filter to only open-weights models
 
127
  font=STANDARD_FONT
128
  )
129
  fig.update_layout(**STANDARD_LAYOUT, title="Open Model Accuracy by Size")
130
+ apply_neutral_chart_gridlines(fig)
131
  return fig
132
 
133
  # Find score column
 
147
  font=STANDARD_FONT
148
  )
149
  fig.update_layout(**STANDARD_LAYOUT, title="Open Model Accuracy by Size")
150
+ apply_neutral_chart_gridlines(fig)
151
  return fig
152
 
153
  # Use the generic scatter chart