|
189 | 189 | tr{transition:background 0.1s} |
190 | 190 | .stat-cell{transition:background 0.15s,transform 0.1s} |
191 | 191 | .stat-cell:active{transform:scale(0.98)} |
| 192 | + |
| 193 | +/* Filter row (search + affiliation) */ |
| 194 | +.filter-row{display:grid;grid-template-columns:1fr 240px;gap:0.75rem;margin-bottom:1rem} |
| 195 | +@media(max-width:640px){.filter-row{grid-template-columns:1fr}} |
| 196 | +.search-wrap{position:relative;margin-bottom:0} |
| 197 | +.affil-wrap{position:relative} |
| 198 | +.affil-select{width:100%;padding:0.6rem 2.25rem 0.6rem 2.5rem;background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:0.85rem;font-family:'Inter',sans-serif;outline:none;transition:border-color 0.15s;appearance:none;cursor:pointer;text-overflow:ellipsis;white-space:nowrap;overflow:hidden} |
| 199 | +.affil-select:focus{border-color:var(--accent)} |
| 200 | +.affil-select option{background:var(--bg-elevated);color:var(--text)} |
| 201 | +.affil-icon{position:absolute;left:0.85rem;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:0.7rem;pointer-events:none;font-weight:700;letter-spacing:0.06em} |
| 202 | +.affil-caret{position:absolute;right:0.85rem;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:0.65rem;pointer-events:none} |
| 203 | + |
| 204 | +/* Clickable rows */ |
| 205 | +tbody tr{cursor:pointer} |
| 206 | +tbody tr.is-active td{background:var(--bg-hover)} |
| 207 | + |
| 208 | +/* Detail drawer */ |
| 209 | +.drawer-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.55);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);opacity:0;pointer-events:none;transition:opacity 0.2s ease;z-index:50} |
| 210 | +.drawer-backdrop.is-open{opacity:1;pointer-events:auto} |
| 211 | +.drawer{position:fixed;top:0;right:0;height:100vh;height:100dvh;width:min(440px,100vw);background:var(--bg-elevated);border-left:1px solid var(--border);box-shadow:-12px 0 40px rgba(0,0,0,0.4);transform:translateX(100%);transition:transform 0.25s cubic-bezier(0.32,0.72,0,1);z-index:60;display:flex;flex-direction:column;overflow:hidden} |
| 212 | +.drawer.is-open{transform:translateX(0)} |
| 213 | +.drawer-head{padding:1.25rem 1.5rem;border-bottom:1px solid var(--border);display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;flex-shrink:0} |
| 214 | +.drawer-head-meta{font-size:0.7rem;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;color:var(--text-muted);margin-bottom:0.5rem;font-family:'JetBrains Mono',monospace} |
| 215 | +.drawer-name{font-size:1.25rem;font-weight:700;letter-spacing:-0.02em;line-height:1.25;margin-bottom:0.35rem} |
| 216 | +.drawer-affil{font-size:0.85rem;color:var(--text-secondary);line-height:1.5} |
| 217 | +.drawer-role{font-size:0.75rem;color:var(--text-muted);margin-top:0.2rem} |
| 218 | +.drawer-close{flex-shrink:0;width:32px;height:32px;border-radius:var(--radius-xs);background:transparent;border:1px solid var(--border);color:var(--text-secondary);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:1.1rem;line-height:1;transition:all 0.15s;font-family:'Inter',sans-serif} |
| 219 | +.drawer-close:hover{background:var(--bg-hover);color:var(--text);border-color:var(--border-light)} |
| 220 | +.drawer-body{padding:1.5rem;overflow-y:auto;flex:1;-webkit-overflow-scrolling:touch} |
| 221 | +.drawer-row{display:flex;align-items:center;gap:0.75rem;margin-bottom:1.5rem;flex-wrap:wrap} |
| 222 | +.drawer-row .badge{width:2.5rem;height:2.5rem;font-size:1rem} |
| 223 | +.drawer-row-label{font-size:0.85rem;font-weight:600} |
| 224 | +.drawer-row-meta{font-size:0.75rem;color:var(--text-muted);font-family:'JetBrains Mono',monospace} |
| 225 | +.drawer-section{margin-bottom:1.5rem} |
| 226 | +.drawer-section:last-child{margin-bottom:0} |
| 227 | +.drawer-section-label{font-size:0.65rem;font-weight:700;letter-spacing:0.1em;text-transform:uppercase;color:var(--text-muted);margin-bottom:0.5rem} |
| 228 | +.drawer-summary{font-size:0.9rem;line-height:1.7;color:var(--text)} |
| 229 | +.drawer-summary.is-empty{color:var(--text-muted);font-style:italic} |
| 230 | +.drawer-key-source{display:inline-flex;align-items:center;gap:0.4rem;padding:0.45rem 0.75rem;background:rgba(247,147,26,0.08);border:1px solid rgba(247,147,26,0.2);border-radius:var(--radius-xs);color:var(--accent);font-size:0.8rem;font-weight:500;text-decoration:none;transition:all 0.15s;word-break:break-all} |
| 231 | +.drawer-key-source:hover{background:rgba(247,147,26,0.14);border-color:rgba(247,147,26,0.35)} |
| 232 | +.drawer-sources{display:flex;flex-direction:column;gap:0.4rem} |
| 233 | +.drawer-source{display:flex;align-items:flex-start;gap:0.5rem;padding:0.5rem 0.65rem;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-xs);color:var(--text-secondary);font-size:0.78rem;text-decoration:none;line-height:1.5;word-break:break-all;transition:all 0.15s} |
| 234 | +.drawer-source:hover{border-color:var(--border-light);color:var(--text);background:var(--bg-hover)} |
| 235 | +.drawer-source-num{flex-shrink:0;font-family:'JetBrains Mono',monospace;color:var(--text-muted);font-size:0.7rem;padding-top:0.05rem;min-width:1.25rem} |
| 236 | +.drawer-empty{font-size:0.8rem;color:var(--text-muted);font-style:italic} |
192 | 237 | </style> |
193 | 238 | </head> |
194 | 239 | <body> |
@@ -275,10 +320,19 @@ <h2 class="section-title">Scorecard</h2> |
275 | 320 | </div> |
276 | 321 | <div class="filters" id="filters"></div> |
277 | 322 | </div> |
| 323 | +<div class="filter-row"> |
278 | 324 | <div class="search-wrap"> |
279 | 325 | <span class="search-icon">⌕</span> |
280 | 326 | <input type="text" class="search-input" id="search" placeholder="Search by name, affiliation, or summary..."> |
281 | 327 | </div> |
| 328 | +<div class="affil-wrap"> |
| 329 | +<span class="affil-icon">ORG</span> |
| 330 | +<select class="affil-select" id="affil-select" aria-label="Filter by affiliation"> |
| 331 | +<option value="all">All affiliations</option> |
| 332 | +</select> |
| 333 | +<span class="affil-caret">▾</span> |
| 334 | +</div> |
| 335 | +</div> |
282 | 336 | <div class="table-wrap"> |
283 | 337 | <div class="table-scroll"> |
284 | 338 | <table> |
@@ -370,6 +424,35 @@ <h4>Ecosystem</h4> |
370 | 424 |
|
371 | 425 | </div> |
372 | 426 |
|
| 427 | +<!-- Detail drawer --> |
| 428 | +<div class="drawer-backdrop" id="drawer-backdrop" aria-hidden="true"></div> |
| 429 | +<aside class="drawer" id="drawer" role="dialog" aria-modal="true" aria-labelledby="drawer-name" tabindex="-1"> |
| 430 | +<div class="drawer-head"> |
| 431 | +<div> |
| 432 | +<div class="drawer-head-meta" id="drawer-meta">Developer</div> |
| 433 | +<div class="drawer-name" id="drawer-name"></div> |
| 434 | +<div class="drawer-affil" id="drawer-affil"></div> |
| 435 | +<div class="drawer-role" id="drawer-role"></div> |
| 436 | +</div> |
| 437 | +<button type="button" class="drawer-close" id="drawer-close" aria-label="Close detail panel">×</button> |
| 438 | +</div> |
| 439 | +<div class="drawer-body"> |
| 440 | +<div class="drawer-row" id="drawer-score-row"></div> |
| 441 | +<div class="drawer-section"> |
| 442 | +<div class="drawer-section-label">Position Summary</div> |
| 443 | +<div class="drawer-summary" id="drawer-summary"></div> |
| 444 | +</div> |
| 445 | +<div class="drawer-section"> |
| 446 | +<div class="drawer-section-label">Key Source</div> |
| 447 | +<div id="drawer-key-source-wrap"></div> |
| 448 | +</div> |
| 449 | +<div class="drawer-section"> |
| 450 | +<div class="drawer-section-label">All Sources</div> |
| 451 | +<div class="drawer-sources" id="drawer-sources"></div> |
| 452 | +</div> |
| 453 | +</div> |
| 454 | +</aside> |
| 455 | + |
373 | 456 | <script> |
374 | 457 | fetch('/data.json').then(r=>r.json()).then(data=>{ |
375 | 458 | const devs=data.developers; |
@@ -448,35 +531,55 @@ <h4>Ecosystem</h4> |
448 | 531 |
|
449 | 532 | // State |
450 | 533 | let activeScore='all'; |
| 534 | +let activeAffiliation='all'; |
451 | 535 | let searchQuery=''; |
| 536 | +let activeDevName=null; |
| 537 | + |
| 538 | +// Affiliation dropdown — populate from data |
| 539 | +const affilSelect=document.getElementById('affil-select'); |
| 540 | +const affilCounts={}; |
| 541 | +devs.forEach(d=>{const a=(d.affiliation||'').trim();if(a)affilCounts[a]=(affilCounts[a]||0)+1;}); |
| 542 | +Object.keys(affilCounts).sort((a,b)=>a.localeCompare(b)).forEach(a=>{ |
| 543 | +const opt=document.createElement('option'); |
| 544 | +opt.value=a; |
| 545 | +opt.textContent=`${a} (${affilCounts[a]})`; |
| 546 | +affilSelect.appendChild(opt); |
| 547 | +}); |
| 548 | + |
| 549 | +function escapeHtml(s){return String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]);} |
452 | 550 |
|
453 | 551 | function render(){ |
454 | 552 | const tbody=document.getElementById('table-body'); |
455 | 553 | tbody.innerHTML=''; |
| 554 | +const q=searchQuery.toLowerCase(); |
456 | 555 | const filtered=devs.filter(dev=>{ |
457 | 556 | const scoreMatch=activeScore==='all'||dev.quantum_urgency_score===parseInt(activeScore); |
458 | | -const q=searchQuery.toLowerCase(); |
459 | | -const textMatch=!q||dev.name.toLowerCase().includes(q)||dev.affiliation.toLowerCase().includes(q)||(dev.summary&&dev.summary.toLowerCase().includes(q)); |
460 | | -return scoreMatch&&textMatch; |
| 557 | +const affilMatch=activeAffiliation==='all'||dev.affiliation===activeAffiliation; |
| 558 | +const textMatch=!q||dev.name.toLowerCase().includes(q)||(dev.affiliation||'').toLowerCase().includes(q)||(dev.summary&&dev.summary.toLowerCase().includes(q)); |
| 559 | +return scoreMatch&&affilMatch&&textMatch; |
461 | 560 | }); |
462 | 561 | document.getElementById('showing-count').textContent=`Showing ${filtered.length} of ${total}`; |
463 | 562 | if(!filtered.length){ |
464 | 563 | tbody.innerHTML='<tr><td colspan="6" class="empty">No developers match your filters.</td></tr>'; |
465 | 564 | return; |
466 | 565 | } |
| 566 | +const frag=document.createDocumentFragment(); |
467 | 567 | filtered.forEach(dev=>{ |
468 | | -const src=dev.key_source?`<a href="${dev.key_source}" class="source-link" target="_blank" rel="noopener">View</a>`:'<span style="color:var(--text-muted)">--</span>'; |
| 568 | +const src=dev.key_source?`<a href="${escapeHtml(dev.key_source)}" class="source-link" target="_blank" rel="noopener" onclick="event.stopPropagation()">View</a>`:'<span style="color:var(--text-muted)">--</span>'; |
469 | 569 | const notable=dev.notable?'<span class="notable-tag">notable</span>':''; |
470 | 570 | const rank=dev.notable?'+':dev.rank; |
471 | | -tbody.innerHTML+=`<tr> |
472 | | -<td class="td-rank">${rank}</td> |
473 | | -<td class="td-name">${dev.name}${notable}</td> |
474 | | -<td class="td-affil">${dev.affiliation}</td> |
| 571 | +const tr=document.createElement('tr'); |
| 572 | +if(activeDevName===dev.name)tr.classList.add('is-active'); |
| 573 | +tr.innerHTML=`<td class="td-rank">${rank}</td> |
| 574 | +<td class="td-name">${escapeHtml(dev.name)}${notable}</td> |
| 575 | +<td class="td-affil">${escapeHtml(dev.affiliation)}</td> |
475 | 576 | <td><span class="badge badge-${dev.quantum_urgency_score}">${dev.quantum_urgency_score}</span></td> |
476 | | -<td class="td-summary">${dev.summary||'No public quantum position identified.'}</td> |
477 | | -<td>${src}</td> |
478 | | -</tr>`; |
| 577 | +<td class="td-summary">${escapeHtml(dev.summary||'No public quantum position identified.')}</td> |
| 578 | +<td>${src}</td>`; |
| 579 | +tr.addEventListener('click',()=>openDrawer(dev)); |
| 580 | +frag.appendChild(tr); |
479 | 581 | }); |
| 582 | +tbody.appendChild(frag); |
480 | 583 | } |
481 | 584 |
|
482 | 585 | // Filter clicks (pills) |
@@ -511,6 +614,75 @@ <h4>Ecosystem</h4> |
511 | 614 | render(); |
512 | 615 | }); |
513 | 616 |
|
| 617 | +// Affiliation filter |
| 618 | +affilSelect.addEventListener('change',e=>{ |
| 619 | +activeAffiliation=e.target.value; |
| 620 | +render(); |
| 621 | +}); |
| 622 | + |
| 623 | +// === DETAIL DRAWER === |
| 624 | +const drawer=document.getElementById('drawer'); |
| 625 | +const drawerBackdrop=document.getElementById('drawer-backdrop'); |
| 626 | +const drawerCloseBtn=document.getElementById('drawer-close'); |
| 627 | +let lastFocused=null; |
| 628 | + |
| 629 | +function openDrawer(dev){ |
| 630 | +activeDevName=dev.name; |
| 631 | +const scoreLabelMap={5:'Urgent',4:'Proactive',3:'Cautious',2:'Dismissive',1:'No View'}; |
| 632 | +document.getElementById('drawer-meta').textContent=dev.notable?'Notable Contributor':`Influence Rank #${dev.rank}`; |
| 633 | +document.getElementById('drawer-name').textContent=dev.name; |
| 634 | +document.getElementById('drawer-affil').textContent=dev.affiliation||'Independent'; |
| 635 | +const roleEl=document.getElementById('drawer-role'); |
| 636 | +if(dev.role){roleEl.textContent=dev.role;roleEl.style.display='block';}else{roleEl.style.display='none';} |
| 637 | +const vol=dev.pq_work_volume||0; |
| 638 | +const volLabel=vol===0?'no PQ work':vol===1?'1 PQ artifact':`${vol} PQ artifacts`; |
| 639 | +document.getElementById('drawer-score-row').innerHTML= |
| 640 | +`<span class="badge badge-${dev.quantum_urgency_score}">${dev.quantum_urgency_score}</span>`+ |
| 641 | +`<div><div class="drawer-row-label" style="color:${({5:'var(--red)',4:'var(--orange)',3:'var(--yellow)',2:'var(--blue)',1:'var(--gray)'})[dev.quantum_urgency_score]}">${scoreLabelMap[dev.quantum_urgency_score]}</div>`+ |
| 642 | +`<div class="drawer-row-meta">${volLabel}${dev.notable?' · notable':''}</div></div>`; |
| 643 | +const summaryEl=document.getElementById('drawer-summary'); |
| 644 | +if(dev.summary){summaryEl.textContent=dev.summary;summaryEl.classList.remove('is-empty');} |
| 645 | +else{summaryEl.textContent='No public quantum position identified.';summaryEl.classList.add('is-empty');} |
| 646 | +const ksWrap=document.getElementById('drawer-key-source-wrap'); |
| 647 | +ksWrap.innerHTML=dev.key_source |
| 648 | +?`<a class="drawer-key-source" href="${escapeHtml(dev.key_source)}" target="_blank" rel="noopener">↗ ${escapeHtml(dev.key_source)}</a>` |
| 649 | +:'<div class="drawer-empty">No key source on file.</div>'; |
| 650 | +const sourcesEl=document.getElementById('drawer-sources'); |
| 651 | +const sources=Array.isArray(dev.sources)?dev.sources.filter(Boolean):[]; |
| 652 | +if(sources.length){ |
| 653 | +sourcesEl.innerHTML=sources.map((s,i)=>`<a class="drawer-source" href="${escapeHtml(s)}" target="_blank" rel="noopener"><span class="drawer-source-num">${String(i+1).padStart(2,'0')}</span><span>${escapeHtml(s)}</span></a>`).join(''); |
| 654 | +}else{ |
| 655 | +sourcesEl.innerHTML='<div class="drawer-empty">No additional sources listed.</div>'; |
| 656 | +} |
| 657 | +lastFocused=document.activeElement; |
| 658 | +drawer.classList.add('is-open'); |
| 659 | +drawerBackdrop.classList.add('is-open'); |
| 660 | +drawer.setAttribute('aria-hidden','false'); |
| 661 | +drawerBackdrop.setAttribute('aria-hidden','false'); |
| 662 | +document.body.style.overflow='hidden'; |
| 663 | +setTimeout(()=>drawerCloseBtn.focus(),60); |
| 664 | +// re-render to highlight the active row |
| 665 | +render(); |
| 666 | +} |
| 667 | + |
| 668 | +function closeDrawer(){ |
| 669 | +activeDevName=null; |
| 670 | +drawer.classList.remove('is-open'); |
| 671 | +drawerBackdrop.classList.remove('is-open'); |
| 672 | +drawer.setAttribute('aria-hidden','true'); |
| 673 | +drawerBackdrop.setAttribute('aria-hidden','true'); |
| 674 | +document.body.style.overflow=''; |
| 675 | +if(lastFocused&&typeof lastFocused.focus==='function')lastFocused.focus(); |
| 676 | +render(); |
| 677 | +} |
| 678 | + |
| 679 | +drawerCloseBtn.addEventListener('click',closeDrawer); |
| 680 | +drawerBackdrop.addEventListener('click',closeDrawer); |
| 681 | +document.addEventListener('keydown',e=>{if(e.key==='Escape'&&drawer.classList.contains('is-open'))closeDrawer();}); |
| 682 | + |
| 683 | +drawer.setAttribute('aria-hidden','true'); |
| 684 | +drawerBackdrop.setAttribute('aria-hidden','true'); |
| 685 | + |
514 | 686 | render(); |
515 | 687 |
|
516 | 688 | // === BUBBLE CHART === |
@@ -661,13 +833,8 @@ <h4>Ecosystem</h4> |
661 | 833 | const my = (e.clientY - rect.top) * (canvas.height / dpr) / rect.height; |
662 | 834 | const dev = getDevAtPos(mx, my); |
663 | 835 | if (dev) { |
664 | | - searchQuery = dev.name; |
665 | | - activeScore = 'all'; |
666 | | - document.getElementById('search').value = dev.name; |
667 | | - document.querySelectorAll('.pill').forEach(b => { b.classList.remove('active'); if (b.dataset.score === 'all') b.classList.add('active'); }); |
668 | | - document.querySelectorAll('.stat-cell').forEach(c => c.classList.remove('active')); |
669 | | - render(); |
670 | | - document.querySelector('.table-section')?.scrollIntoView({ behavior: 'smooth' }); |
| 836 | + tooltip.style.display = 'none'; |
| 837 | + openDrawer(dev); |
671 | 838 | } |
672 | 839 | }); |
673 | 840 |
|
|
0 commit comments