VIDRAFT Claude Opus 5 commited on
Commit
ca1ba69
ยท
1 Parent(s): a9448a0

Keep the table inside the viewport, and make the structure copyable

Browse files

Printing whole SMILES pushed the table past the screen - fifty characters in a column
sharing space with six score columns. Truncating alone would have made the structure
useless, since the point of showing it is that someone can take it, so the cell now shows
a prefix with a copy button that puts the full string on the clipboard. The button stops
the click reaching the row, which would otherwise toggle the overlay underneath.

The page still scrolled sideways after that, for an unrelated reason: a grid item defaults
to min-width:auto, so the leaderboard column would not shrink below the table's min-width
and shoved the submission card off-screen.

Withheld structures get no copy button - there is nothing to copy and offering one would
imply otherwise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Files changed (1) hide show
  1. index.html +32 -7
index.html CHANGED
@@ -182,6 +182,9 @@
182
  .legend{display:flex;gap:14px;flex-wrap:wrap;margin-top:12px;font-size:11px;color:var(--muted)}
183
  .legend i{display:inline-block;width:10px;height:10px;border-radius:3px;margin-right:5px;vertical-align:-1px}
184
  .grid{display:grid;grid-template-columns:1fr 348px;gap:20px;align-items:start;margin-top:20px}
 
 
 
185
  @media(max-width:960px){.grid{grid-template-columns:1fr}#langbar{position:static;margin-bottom:14px;width:max-content}#langnote{position:static;margin-bottom:8px}}
186
  .card{background:var(--panel);border:1px solid var(--line);border-radius:16px;
187
  padding:18px 20px;box-shadow:var(--shadow)}
@@ -208,9 +211,15 @@
208
  .rk.top{color:var(--accent);font-size:15px}
209
  .cid{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12.5px;font-weight:600}
210
  .skel{color:#93a1b5;font-size:11px;font-family:ui-monospace,monospace}
211
- .smi2{display:block;color:#1c2a3d;font-size:12px;font-weight:600;
212
- font-family:ui-monospace,Menlo,Consolas,monospace;max-width:290px;
 
213
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
 
 
 
 
 
214
  .lock{color:#93a1b5;font-size:10.5px;margin-top:1px;display:block}
215
  #ov .osmi{margin-top:10px;padding:9px 10px;background:#f6f8fc;border:1px solid var(--line);
216
  border-radius:8px;font-family:ui-monospace,Menlo,monospace;font-size:10.5px;
@@ -429,7 +438,7 @@ const T = {
429
  synthesis:d=>`ํ•ฉ์„ฑ๋‚œ์ด๋„ SA ${d.sa_score ?? "โ€“"} (1 ์‰ฌ์›€ ~ 10 ์–ด๋ ค์›€)`,
430
  },
431
  ov_pred:"์˜ˆ์ธก์น˜", ov_pf:"๋ง๋ผ๋ฆฌ์•„ ํšจ์†Œ", ov_hs:"์‚ฌ๋žŒ ํšจ์†Œ", ov_sel:"์„ ํƒ๋„",
432
- private_note:"์ œ์ถœ์ž๊ฐ€ ๋น„๊ณต๊ฐœ๋ฅผ ์„ ํƒํ•œ ๊ตฌ์กฐ",
433
  private_full:"์ œ์ถœ์ž๊ฐ€ ๋น„๊ณต๊ฐœ๋ฅผ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ํ›„๋ณด ID์™€ ๊ณจ๊ฒฉ ํ•ด์‹œ๋กœ๋งŒ ์‹๋ณ„๋ฉ๋‹ˆ๋‹ค.",
434
  reasons:{
435
  parse: r=>"๊ตฌ์กฐ๋ฅผ ํ•ด์„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค (SMILES ๋˜๋Š” InChI๋กœ ์ œ์ถœํ•˜์„ธ์š”)",
@@ -523,7 +532,7 @@ const T = {
523
  synthesis:d=>`SA score ${d.sa_score ?? "โ€“"} (1 easy - 10 hard)`,
524
  },
525
  ov_pred:"Predicted", ov_pf:"parasite enzyme", ov_hs:"human enzyme", ov_sel:"selectivity",
526
- private_note:"structure withheld by the entrant",
527
  private_full:"The entrant chose to keep this structure private. It is identified only by its candidate ID and skeleton hash.",
528
  reasons:{
529
  parse: r=>"Structure could not be parsed (submit SMILES or InChI)",
@@ -687,11 +696,16 @@ function render(){
687
  + '</span>';
688
  const _m = e.is_anchor ? (e.note || d.ref) : (e.model_name || "\u2013");
689
  const who = `<span class="who" title="${_m}">${_m}</span>`;
690
- // the structure is the name now; a withheld one shows its ends, middle blanked
691
- const shown = (e.visibility === "public" && e.smiles) ? e.smiles : e.smiles_masked;
 
 
 
692
  const id = e.is_anchor ? `<b>${e.candidate_id}</b>`
693
  : (shown
694
- ? `<span class="smi2" title="${shown}">${shown}</span>`
 
 
695
  : `<span class="cid">${e.candidate_id}</span>`);
696
  const tag = e.is_anchor ? `<span class="tag a">${d.anchor}</span>`
697
  : (e.tier===2 ? `<span class="tag r">${d.relegated}</span>` : "");
@@ -704,6 +718,17 @@ function render(){
704
  <span class="tot">${f1(e.total)}</span></div></td></tr>`;
705
  }).join("") : `<tr><td colspan="11" class="who">${d.empty}</td></tr>`;
706
 
 
 
 
 
 
 
 
 
 
 
 
707
  // rows carry their entry so the overlay never has to look anything up again
708
  [...document.querySelectorAll("#rows tr")].forEach((tr, i) => {
709
  const e = rows[i];
 
182
  .legend{display:flex;gap:14px;flex-wrap:wrap;margin-top:12px;font-size:11px;color:var(--muted)}
183
  .legend i{display:inline-block;width:10px;height:10px;border-radius:3px;margin-right:5px;vertical-align:-1px}
184
  .grid{display:grid;grid-template-columns:1fr 348px;gap:20px;align-items:start;margin-top:20px}
185
+ /* A grid item defaults to min-width:auto, so the 1fr column refused to shrink below
186
+ the table's min-width and pushed the submission card off-screen. */
187
+ .grid > *{min-width:0}
188
  @media(max-width:960px){.grid{grid-template-columns:1fr}#langbar{position:static;margin-bottom:14px;width:max-content}#langnote{position:static;margin-bottom:8px}}
189
  .card{background:var(--panel);border:1px solid var(--line);border-radius:16px;
190
  padding:18px 20px;box-shadow:var(--shadow)}
 
211
  .rk.top{color:var(--accent);font-size:15px}
212
  .cid{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12.5px;font-weight:600}
213
  .skel{color:#93a1b5;font-size:11px;font-family:ui-monospace,monospace}
214
+ .smiwrap{display:flex;align-items:center;gap:6px;max-width:250px}
215
+ .smi2{color:#1c2a3d;font-size:12px;font-weight:600;
216
+ font-family:ui-monospace,Menlo,Consolas,monospace;
217
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
218
+ .cp{flex:none;border:1px solid var(--line);background:#fff;border-radius:6px;
219
+ width:22px;height:22px;line-height:1;cursor:pointer;color:var(--muted);
220
+ font-size:11px;padding:0}
221
+ .cp:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
222
+ .cp.done{border-color:var(--accent);color:#fff;background:var(--accent)}
223
  .lock{color:#93a1b5;font-size:10.5px;margin-top:1px;display:block}
224
  #ov .osmi{margin-top:10px;padding:9px 10px;background:#f6f8fc;border:1px solid var(--line);
225
  border-radius:8px;font-family:ui-monospace,Menlo,monospace;font-size:10.5px;
 
438
  synthesis:d=>`ํ•ฉ์„ฑ๋‚œ์ด๋„ SA ${d.sa_score ?? "โ€“"} (1 ์‰ฌ์›€ ~ 10 ์–ด๋ ค์›€)`,
439
  },
440
  ov_pred:"์˜ˆ์ธก์น˜", ov_pf:"๋ง๋ผ๋ฆฌ์•„ ํšจ์†Œ", ov_hs:"์‚ฌ๋žŒ ํšจ์†Œ", ov_sel:"์„ ํƒ๋„",
441
+ private_note:"์ œ์ถœ์ž๊ฐ€ ๋น„๊ณต๊ฐœ๋ฅผ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค", copy_smiles:"๋ถ„์ž์‹ ๋ณต์‚ฌ",
442
  private_full:"์ œ์ถœ์ž๊ฐ€ ๋น„๊ณต๊ฐœ๋ฅผ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ํ›„๋ณด ID์™€ ๊ณจ๊ฒฉ ํ•ด์‹œ๋กœ๋งŒ ์‹๋ณ„๋ฉ๋‹ˆ๋‹ค.",
443
  reasons:{
444
  parse: r=>"๊ตฌ์กฐ๋ฅผ ํ•ด์„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค (SMILES ๋˜๋Š” InChI๋กœ ์ œ์ถœํ•˜์„ธ์š”)",
 
532
  synthesis:d=>`SA score ${d.sa_score ?? "โ€“"} (1 easy - 10 hard)`,
533
  },
534
  ov_pred:"Predicted", ov_pf:"parasite enzyme", ov_hs:"human enzyme", ov_sel:"selectivity",
535
+ private_note:"structure withheld by the entrant", copy_smiles:"Copy SMILES",
536
  private_full:"The entrant chose to keep this structure private. It is identified only by its candidate ID and skeleton hash.",
537
  reasons:{
538
  parse: r=>"Structure could not be parsed (submit SMILES or InChI)",
 
696
  + '</span>';
697
  const _m = e.is_anchor ? (e.note || d.ref) : (e.model_name || "\u2013");
698
  const who = `<span class="who" title="${_m}">${_m}</span>`;
699
+ // the structure is the name now; a withheld one shows its ends, middle blanked.
700
+ // Only a published structure gets a copy button - there is nothing to copy otherwise.
701
+ const isPub = e.visibility === "public" && !!e.smiles;
702
+ const shown = isPub ? e.smiles : e.smiles_masked;
703
+ const clip = shown ? (shown.length > 26 ? shown.slice(0, 26) + "\u2026" : shown) : "";
704
  const id = e.is_anchor ? `<b>${e.candidate_id}</b>`
705
  : (shown
706
+ ? `<span class="smiwrap"><span class="smi2" title="${shown}">${clip}</span>`
707
+ + (isPub ? `<button class="cp" data-smi="${e.smiles}" title="${d.copy_smiles}">\u29c9</button>` : "")
708
+ + `</span>`
709
  : `<span class="cid">${e.candidate_id}</span>`);
710
  const tag = e.is_anchor ? `<span class="tag a">${d.anchor}</span>`
711
  : (e.tier===2 ? `<span class="tag r">${d.relegated}</span>` : "");
 
718
  <span class="tot">${f1(e.total)}</span></div></td></tr>`;
719
  }).join("") : `<tr><td colspan="11" class="who">${d.empty}</td></tr>`;
720
 
721
+ $("#rows").querySelectorAll(".cp").forEach(b => b.onclick = async (ev) => {
722
+ // without this the click also reaches the row and toggles the overlay
723
+ ev.stopPropagation();
724
+ const v = b.dataset.smi;
725
+ try{ await navigator.clipboard.writeText(v); }
726
+ catch(_){ const ta=document.createElement("textarea"); ta.value=v;
727
+ document.body.appendChild(ta); ta.select(); document.execCommand("copy"); ta.remove(); }
728
+ b.classList.add("done"); b.textContent = "\u2713";
729
+ setTimeout(()=>{ b.classList.remove("done"); b.textContent = "\u29c9"; }, 1200);
730
+ });
731
+
732
  // rows carry their entry so the overlay never has to look anything up again
733
  [...document.querySelectorAll("#rows tr")].forEach((tr, i) => {
734
  const e = rows[i];