Michael Rabinovich Cursor commited on
Commit
a636039
·
1 Parent(s): 00f76c2

leaderboard: dedicated mobile card layout for the gallery

Browse files

A horizontal-scroll table is unusable on phones (one render at a time).
On narrow screens hide the table and render one card per model instead
(plus a ground-truth card), each showing the four renders in a labelled
2x2 grid, so the whole comparison reads top-to-bottom with a single
vertical scroll.

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

Files changed (1) hide show
  1. gallery.py +90 -30
gallery.py CHANGED
@@ -397,39 +397,51 @@ a.sub-name:hover { color: var(--accent); text-decoration: underline; }
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 / narrow screens ------------------------------------------------
401
- The full 4-sample matrix is far wider than a phone, so on narrow viewports
402
- the table scrolls horizontally (swipe) instead of being clipped, the rank
403
- column is dropped, and the Submission column is frozen to the left so you can
404
- tell which model each render belongs to while swiping. The header + GT row
405
- stay frozen to the top as before, so the GT thumbnails are always the
406
- reference while you scroll either way. */
407
  @media (max-width: 760px) {
408
- .wrap { padding: 0 8px; }
409
- .section-label { margin: 2px 0 6px; font-size: 12px; }
410
- .gallery { overflow-x: auto; -webkit-overflow-scrolling: touch; }
411
- .grid-head, .grow {
412
- grid-template-columns: 128px 78px repeat(var(--ncol, 4), 118px);
 
 
 
413
  }
414
- /* Drop the rank column (cells + header) to save width. !important beats the
415
- base `.grow.gt-row .rank` rule, which would otherwise re-show it on the GT
416
- row only and wrap that row (7 items into a 6-column grid). */
417
- .rank, .grid-head .h-rank { display: none !important; }
418
- /* Freeze the identity / Submission column while swiping horizontally. */
419
- .ident, .grid-head .h-sub {
420
- position: sticky; left: 0; z-index: 8;
421
- box-shadow: 6px 0 10px -8px rgba(20,22,28,.25);
 
 
 
 
 
 
 
 
 
 
 
422
  }
423
- .grid-head .h-sub { background: #fbfbfd; }
424
- .grow.sub-row .ident { background: var(--panel); }
425
- .grow.sub-row:hover .ident { background: #fafbff; }
426
- .grow.gt-row .ident { background: var(--gt-soft); }
427
- .ident .sub-name { font-size: 13.5px; }
428
- .score-cell { padding: 12px 10px; }
429
- .score-cell .agg { font-size: 19px; }
430
- .score-cell .score-breakdown { display: none; } /* keep the score column narrow */
431
- .thumb-cell { padding: 5px; }
432
- .scroll-cue { right: 1px; }
433
  }
434
  """
435
 
@@ -450,6 +462,7 @@ _BODY = """
450
  </div>
451
  <div class="scroll-cue" id="scrollCue" hidden><span>&#9662; scroll for more models</span></div>
452
  </div>
 
453
  </div>
454
  <div class="modal-back" id="modalBack">
455
  <div class="modal">
@@ -604,6 +617,52 @@ function wireGallery() {
604
  });
605
  }
606
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
  function openModal(fxId, sub) {
608
  const f = fixtureMeta(fxId);
609
  const title = f
@@ -700,6 +759,7 @@ function fitIframe() {
700
  }
701
 
702
  buildGallery();
 
703
  sizeGalleryBox();
704
  fitIframe();
705
  (function () {
 
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
  </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
  });
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
 
759
  }
760
 
761
  buildGallery();
762
+ buildMobile();
763
  sizeGalleryBox();
764
  fitIframe();
765
  (function () {