Michael Rabinovich Cursor commited on
Commit
0c0ddd8
·
1 Parent(s): a636039

leaderboard: mobile uses the same aligned table as desktop

Browse files

Revert the mobile card layout (it broke the GT-vs-output comparison by
putting GT in its own card). On phones now show the same table: sticky
sample-column headers + pinned ground-truth row, with each model's four
renders aligned in the same columns directly under the GT. To fit phone
width the per-model name+score becomes a slim bar above that model's
render row (no room for a left identity column plus four renders). The
box still caps + scrolls internally so the GT row stays pinned.

Co-authored-by: Cursor <cursoragent@cursor.com>

Files changed (1) hide show
  1. gallery.py +67 -106
gallery.py CHANGED
@@ -394,54 +394,45 @@ a.sub-name:hover { color: var(--accent); text-decoration: underline; }
394
  .modal-compare .mthumb.failed span { font-family: var(--mono); font-size: 10px; font-weight: 700; color: var(--bad); text-transform: uppercase; letter-spacing: .04em; text-align: center; }
395
  .modal-note { margin-top: 18px; font-size: 12.5px; color: var(--ink-soft); background: var(--accent-soft); padding: 12px 14px; border-radius: 10px; }
396
  .modal-note a { color: var(--accent); font-weight: 600; }
 
 
 
 
 
397
  .modal-close { margin-top: 20px; width: 100%; padding: 11px; border: 1px solid var(--line-strong); background: #fafbfc; border-radius: 10px; font-family: inherit; font-weight: 600; cursor: pointer; font-size: 14px; }
398
  .modal-close:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
399
 
400
- /* --- Mobile card layout (built by buildMobile) -----------------------------
401
- The wide 4-sample matrix is unusable on a phone (one render at a time behind
402
- a horizontal scroll). On narrow screens we hide the table entirely and show
403
- one card per model instead, each with its four renders in a 2x2 grid, so the
404
- whole comparison is readable with a single vertical scroll. */
405
- .m-cards { display: none; }
 
406
  @media (max-width: 760px) {
407
- .wrap { padding: 0 10px; }
408
  .section-label { margin: 2px 0 4px; font-size: 12px; }
409
- .gallery-shell { display: none; } /* hide the desktop table */
410
- .m-cards { display: block; }
411
- .m-card {
412
- background: var(--panel); border: 1px solid var(--line);
413
- border-radius: var(--radius); box-shadow: var(--shadow);
414
- padding: 13px 13px 14px; margin-bottom: 11px;
415
- }
416
- .m-card.m-gt { background: var(--gt-soft); border: 1px solid var(--gt); }
417
- .m-head { display: flex; align-items: baseline; gap: 9px; margin-bottom: 11px; }
418
- .m-rank { font-family: var(--mono); font-weight: 700; font-size: 13px; color: var(--ink-faint); }
419
- .m-rank.medal-1 { color: #b8860b; } .m-rank.medal-2 { color: #6b7280; } .m-rank.medal-3 { color: #a0522d; }
420
- .m-id { flex: 1; min-width: 0; }
421
- .m-name { font-weight: 600; font-size: 14.5px; line-height: 1.25; color: var(--ink); text-decoration: none; display: block; }
422
- a.m-name:hover { color: var(--accent); }
423
- .m-who { font-size: 11.5px; color: var(--ink-faint); font-family: var(--mono); }
424
- .m-score { font-size: 21px; font-weight: 800; letter-spacing: -.01em; }
425
- .m-val { font-size: 10.5px; font-family: var(--mono); font-weight: 700; color: var(--good); }
426
- .m-val.imperfect { color: #b45309; }
427
- .m-gt .m-name, .m-gt .m-score { color: var(--gt); }
428
- .m-gt-sub { font-size: 11.5px; color: var(--gt); opacity: .8; }
429
- .m-dl { font-size: 11.5px; font-weight: 600; color: var(--accent); text-decoration: none; }
430
- .m-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }
431
- .m-cell { margin: 0; }
432
- .m-thumb {
433
- aspect-ratio: 16/10; border-radius: 8px; background: var(--thumb-bg);
434
- border: 1px solid var(--line); overflow: hidden;
435
- }
436
- .m-thumb img { width: 100%; height: 100%; object-fit: contain; display: block; }
437
- .m-thumb.failed { background: var(--bad-soft); border: 1px dashed #e9b3ae; display: flex; align-items: center; justify-content: center; }
438
- .m-thumb.failed span { font-family: var(--mono); font-size: 9.5px; font-weight: 700; color: var(--bad); text-transform: uppercase; text-align: center; line-height: 1.4; }
439
- .m-cell figcaption {
440
- font-size: 9.5px; text-transform: uppercase; letter-spacing: .04em;
441
- color: var(--ink-faint); font-weight: 700; margin: 5px 0 1px; text-align: center;
442
- }
443
- .m-cell figcaption .mc-diff { color: var(--bad); }
444
- .m-cell figcaption .mc-diff.mc-medium { color: #b45309; }
445
  }
446
  """
447
 
@@ -462,7 +453,6 @@ _BODY = """
462
  </div>
463
  <div class="scroll-cue" id="scrollCue" hidden><span>&#9662; scroll for more models</span></div>
464
  </div>
465
- <div class="m-cards" id="mGallery"></div>
466
  </div>
467
  <div class="modal-back" id="modalBack">
468
  <div class="modal">
@@ -617,76 +607,50 @@ function wireGallery() {
617
  });
618
  }
619
 
620
- // --- Mobile card layout ---------------------------------------------------
621
- // One card per model (plus a ground-truth card), each showing the four renders
622
- // in a 2x2 grid with labels. Built once; CSS shows it only on narrow screens.
623
- function mCaption(f) {
624
- const diff = f.difficulty
625
- ? ' <span class="mc-diff mc-' + esc((f.difficulty || '').toLowerCase()) + '">' + esc(f.difficulty) + '</span>'
626
- : '';
627
- return esc(groupLabel(f.task)) + diff;
628
- }
629
- function mThumb(url, f) {
630
- const inner = url
631
- ? '<img loading="lazy" decoding="async" src="' + url + '" alt="" onerror="mImgFail(this)">'
632
- : '<span>invalid<br>generation</span>';
633
- return '<figure class="m-cell"><div class="m-thumb' + (url ? '' : ' failed') + '">' + inner
634
- + '</div><figcaption>' + mCaption(f) + '</figcaption></figure>';
635
- }
636
- function mImgFail(img) {
637
- const t = img.closest('.m-thumb');
638
- if (t) { t.className = 'm-thumb failed'; t.innerHTML = '<span>invalid<br>generation</span>'; }
639
- }
640
- function buildMobile() {
641
- const root = document.getElementById('mGallery');
642
- if (!root) return;
643
- if (!DATA.subs.length) { root.innerHTML = ''; return; }
644
- let html = '<div class="m-card m-gt">'
645
- + '<div class="m-head"><div class="m-id"><span class="m-name">Ground truth</span>'
646
- + '<span class="m-gt-sub">reference geometry</span></div><span class="m-score">1.000</span></div>'
647
- + '<div class="m-grid">' + FIXTURES.map(f => mThumb(gtRenderFor(f.id), f)).join('') + '</div></div>';
648
- DATA.subs.forEach((s, i) => {
649
- const medal = i < 3 ? 'medal-' + (i + 1) : '';
650
- const imperfect = (s.validity !== null && s.validity < 1) ? 'imperfect' : '';
651
- const name = s.reportUrl
652
- ? '<a class="m-name" href="' + esc(s.reportUrl) + '" target="_blank" rel="noopener">' + esc(s.name) + '</a>'
653
- : '<span class="m-name">' + esc(s.name) + '</span>';
654
- html += '<div class="m-card"><div class="m-head">'
655
- + '<span class="m-rank ' + medal + '">' + (i + 1) + '</span>'
656
- + '<div class="m-id">' + name + '<span class="m-who">' + esc(s.who) + '</span></div>'
657
- + '<div style="text-align:right"><div class="m-score">' + fmt(s.score, 3) + '</div>'
658
- + '<span class="m-val ' + imperfect + '">' + pct(s.validity) + ' valid</span></div></div>'
659
- + '<div class="m-grid">' + FIXTURES.map(f => mThumb(gridRenderFor(s, f.id), f)).join('') + '</div>'
660
- + (s.blobUrl ? '<div style="margin-top:9px"><a class="m-dl" href="' + esc(s.blobUrl) + '" target="_blank" rel="noopener">&#8675; Download ZIP</a></div>' : '')
661
- + '</div>';
662
- });
663
- root.innerHTML = html;
664
- }
665
 
666
  function openModal(fxId, sub) {
667
  const f = fixtureMeta(fxId);
 
668
  const title = f
669
  ? groupLabel(f.task) + (f.difficulty ? ' \\u00b7 ' + f.difficulty : '') + ' (#' + fxId + ')'
670
  : fxId;
671
  document.getElementById('modalTitle').textContent = title;
672
  document.getElementById('modalSub').textContent = sub.name;
673
  const gt = gtRenderFor(fxId);
674
- const out = renderFor(sub, fxId);
 
 
 
 
675
  const cell = cellOf(sub, fxId);
676
  document.getElementById('modalGt').innerHTML = gt
677
  ? '<img src="' + gt + '" alt="ground truth">' : '<span>no GT render</span>';
 
 
678
  const outEl = document.getElementById('modalOut');
679
  if (out) {
680
  outEl.className = 'mthumb';
681
- outEl.innerHTML = '<img src="' + out + '" alt="output">';
682
  } else {
683
  outEl.className = 'mthumb failed';
684
  outEl.innerHTML = '<span>invalid<br>generation</span>';
685
  }
686
  const cad = (cell.cad === null || cell.cad === undefined) ? '-' : Number(cell.cad).toFixed(3);
 
 
 
 
 
 
 
687
  document.getElementById('modalNote').innerHTML =
688
- 'CAD score for this sample: <b>' + cad + '</b>. The full per-sample report '
689
- + '(shape similarity, interface, topology + 3D view) opens from the report viewer.';
 
 
 
 
 
690
  document.getElementById('modalBack').classList.add('show');
691
  }
692
  function closeModal() {
@@ -729,19 +693,17 @@ function updateScrollCue() {
729
  var CHROME_RESERVE = 450;
730
  function sizeGalleryBox() {
731
  try {
732
- // Phones: drop the vertical cap entirely. Mobile browser + HF chrome eat
733
- // most of the short viewport, so a capped box would be tiny AND still
734
- // double-scroll. Instead let the gallery take its natural height and the
735
- // page scroll once vertically; fixtures are reached by horizontal swipe
736
- // (overflow-x). Width (unlike height) is not inflated by the nesting, so
737
- // innerWidth is a reliable mobile check.
738
- if ((window.innerWidth || 1000) < 760) {
739
- document.documentElement.style.setProperty('--gallery-max', 'none');
740
- updateScrollCue();
741
- return;
742
- }
743
  const avail = (window.screen && window.screen.availHeight) || 900;
744
- const h = Math.max(320, Math.min(1200, Math.round(avail - CHROME_RESERVE)));
 
 
 
 
 
 
 
 
 
745
  document.documentElement.style.setProperty('--gallery-max', h + 'px');
746
  updateScrollCue();
747
  } catch (e) { /* keep CSS fallback */ }
@@ -759,7 +721,6 @@ function fitIframe() {
759
  }
760
 
761
  buildGallery();
762
- buildMobile();
763
  sizeGalleryBox();
764
  fitIframe();
765
  (function () {
 
394
  .modal-compare .mthumb.failed span { font-family: var(--mono); font-size: 10px; font-weight: 700; color: var(--bad); text-transform: uppercase; letter-spacing: .04em; text-align: center; }
395
  .modal-note { margin-top: 18px; font-size: 12.5px; color: var(--ink-soft); background: var(--accent-soft); padding: 12px 14px; border-radius: 10px; }
396
  .modal-note a { color: var(--accent); font-weight: 600; }
397
+ /* Edit-diff color key (editing fixtures only), mirrors the report legend. */
398
+ .modal-legend { margin-top: 9px; font-size: 11.5px; color: var(--ink-soft); line-height: 1.7; }
399
+ .modal-legend .mlc { display: inline-block; width: 11px; height: 11px; border-radius: 3px;
400
+ vertical-align: middle; margin: 0 5px 0 14px; border: 1px solid rgba(0,0,0,0.18); }
401
+ .modal-legend .mlc:first-child { margin-left: 0; }
402
  .modal-close { margin-top: 20px; width: 100%; padding: 11px; border: 1px solid var(--line-strong); background: #fafbfc; border-radius: 10px; font-family: inherit; font-weight: 600; cursor: pointer; font-size: 14px; }
403
  .modal-close:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
404
 
405
+ /* --- Mobile / narrow screens ------------------------------------------------
406
+ Same comparison as desktop -- the four sample columns with the ground-truth
407
+ row pinned on top so each model's render sits directly under the GT it should
408
+ match -- but adapted to phone width: there is no room for a left identity
409
+ column AND four renders, so the model name + score become a slim bar above
410
+ that model's four renders. The four columns stay full-width and aligned
411
+ across the GT row and every model, and the header + GT row stay pinned. */
412
  @media (max-width: 760px) {
413
+ .wrap { padding: 0 7px; }
414
  .section-label { margin: 2px 0 4px; font-size: 12px; }
415
+ .grid-head, .grow { grid-template-columns: repeat(var(--ncol, 4), 1fr); }
416
+
417
+ /* Header: keep only the four sample-column labels, aligned over the renders. */
418
+ .grid-head .h-rank, .grid-head .h-sub, .grid-head .h-score { display: none !important; }
419
+ .grid-head .fix-h { padding: 8px 4px; }
420
+ .grid-head .fix-h .fname { display: none; } /* drop the #id, keep task+difficulty */
421
+ .grid-head .fix-h .ftop { flex-direction: column; align-items: flex-start; gap: 3px; }
422
+ .grid-head .fix-h .ftask { font-size: 10px; }
423
+ .grid-head .fix-h .fdiff { font-size: 8px; padding: 1px 5px; }
424
+
425
+ /* Rows: name (3 cols) + score (last col) form a bar above the renders. */
426
+ .rank { display: none !important; }
427
+ .ident { grid-column: 1 / span 3; padding: 9px 8px 5px; }
428
+ .ident .sub-name { font-size: 13.5px; }
429
+ .ident .ident-foot { margin-top: 3px; }
430
+ .score-cell { grid-column: 4 / span 1; padding: 9px 8px 5px; gap: 1px; align-items: flex-end; }
431
+ .score-cell .agg { font-size: 17px; }
432
+ .score-cell .score-breakdown { display: none; }
433
+ .score-cell .validity { font-size: 10px; }
434
+ .grow.gt-row .score-cell { align-items: flex-end; }
435
+ .thumb-cell { padding: 4px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  }
437
  """
438
 
 
453
  </div>
454
  <div class="scroll-cue" id="scrollCue" hidden><span>&#9662; scroll for more models</span></div>
455
  </div>
 
456
  </div>
457
  <div class="modal-back" id="modalBack">
458
  <div class="modal">
 
607
  });
608
  }
609
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
610
 
611
  function openModal(fxId, sub) {
612
  const f = fixtureMeta(fxId);
613
+ const isEditing = !!(f && f.task === 'editing');
614
  const title = f
615
  ? groupLabel(f.task) + (f.difficulty ? ' \\u00b7 ' + f.difficulty : '') + ' (#' + fxId + ')'
616
  : fxId;
617
  document.getElementById('modalTitle').textContent = title;
618
  document.getElementById('modalSub').textContent = sub.name;
619
  const gt = gtRenderFor(fxId);
620
+ // Editing fixtures: the meaningful output is the edit-diff turntable (the
621
+ // material that actually changed vs GT), mirroring the per-submission report
622
+ // -- the plain aligned candidate looks identical to GT for a small edit.
623
+ // Generation shows the plain aligned candidate.
624
+ const out = isEditing ? gridRenderFor(sub, fxId) : renderFor(sub, fxId);
625
  const cell = cellOf(sub, fxId);
626
  document.getElementById('modalGt').innerHTML = gt
627
  ? '<img src="' + gt + '" alt="ground truth">' : '<span>no GT render</span>';
628
+ document.getElementById('modalOutCap').textContent =
629
+ isEditing ? 'Output vs ground truth (edit diff)' : 'Output (aligned)';
630
  const outEl = document.getElementById('modalOut');
631
  if (out) {
632
  outEl.className = 'mthumb';
633
+ outEl.innerHTML = '<img src="' + out + '" alt="' + (isEditing ? 'edit diff' : 'output') + '">';
634
  } else {
635
  outEl.className = 'mthumb failed';
636
  outEl.innerHTML = '<span>invalid<br>generation</span>';
637
  }
638
  const cad = (cell.cad === null || cell.cad === undefined) ? '-' : Number(cell.cad).toFixed(3);
639
+ const legend = isEditing
640
+ ? '<div class="modal-legend">'
641
+ + '<span class="mlc" style="background:#bdc4d1"></span>your output'
642
+ + '<span class="mlc" style="background:#e62929"></span>extra material (too much)'
643
+ + '<span class="mlc" style="background:#f5991a"></span>missing material (too little)'
644
+ + '</div>'
645
+ : '';
646
  document.getElementById('modalNote').innerHTML =
647
+ 'CAD score for this sample: <b>' + cad + '</b>. '
648
+ + (isEditing
649
+ ? 'The right view is the edit diff: red is material your output added that the '
650
+ + 'GT lacks, amber is GT material your output is missing. '
651
+ : '')
652
+ + 'The full per-sample report (shape similarity, interface, topology + 3D view) '
653
+ + 'opens from the report viewer.' + legend;
654
  document.getElementById('modalBack').classList.add('show');
655
  }
656
  function closeModal() {
 
693
  var CHROME_RESERVE = 450;
694
  function sizeGalleryBox() {
695
  try {
 
 
 
 
 
 
 
 
 
 
 
696
  const avail = (window.screen && window.screen.availHeight) || 900;
697
+ // Phones have much taller browser + HF chrome, so they need a bigger
698
+ // reserve (smaller box). Width is not inflated by the iframe nesting, so
699
+ // innerWidth is a reliable narrow-screen check. The box still caps + scrolls
700
+ // internally on mobile so the GT row + sample headers stay pinned and each
701
+ // model's renders sit under the matching GT render (same as desktop).
702
+ const narrow = (window.innerWidth || 1000) < 760;
703
+ const reserve = narrow ? 360 : CHROME_RESERVE;
704
+ const maxH = narrow ? 560 : 1200;
705
+ const minH = narrow ? 280 : 320;
706
+ const h = Math.max(minH, Math.min(maxH, Math.round(avail - reserve)));
707
  document.documentElement.style.setProperty('--gallery-max', h + 'px');
708
  updateScrollCue();
709
  } catch (e) { /* keep CSS fallback */ }
 
721
  }
722
 
723
  buildGallery();
 
724
  sizeGalleryBox();
725
  fitIframe();
726
  (function () {