search for standard sites pub-search.waow.tech
search zig blog atproto
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: publication click → opens publication URL, hover shows tooltip

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

+90 -21
+90 -21
site/atlas.js
··· 777 777 778 778 // --- hover state --- 779 779 var hoveredIndex = -1; 780 + var hoveredPub = -1; // index into pubData 780 781 var mouseX = 0, mouseY = 0; 781 782 783 + function findNearestPub(sx, sy) { 784 + if (!pubData || pubData.length === 0) return -1; 785 + cacheTransform(); 786 + for (var i = 0; i < pubData.length; i++) { 787 + var pub = pubData[i]; 788 + var pr = Math.min(40, Math.sqrt(pub.count) * zoom * 0.5); 789 + if (pr < 4) continue; 790 + var psx = cx + pub.cx * scale, psy = cy + pub.cy * scale; 791 + var dx = sx - psx, dy = sy - psy; 792 + if (dx * dx + dy * dy <= pr * pr) return i; 793 + } 794 + return -1; 795 + } 796 + 797 + function pubUrl(pub) { 798 + if (pub.basePath) return 'https://' + pub.basePath; 799 + return null; 800 + } 801 + 802 + function showPubTooltip(pubIdx, sx, sy) { 803 + var pub = pubData[pubIdx]; 804 + tooltipTitle.textContent = pub.name || pub.basePath; 805 + tooltipMeta.textContent = pub.count + ' documents'; 806 + tooltipPlatform.textContent = pub.platform || 'other'; 807 + var c = frameColors[pub.platform] || frameColors.other; 808 + tooltipPlatform.style.background = c.edge; 809 + tooltipPlatform.style.color = c.core; 810 + tooltip.style.display = 'block'; 811 + var tw = tooltip.offsetWidth, th = tooltip.offsetHeight; 812 + if (isMobile) { 813 + var tx = Math.max(8, Math.min(W - tw - 8, (W - tw) / 2)); 814 + tooltip.style.left = tx + 'px'; 815 + tooltip.style.top = '48px'; 816 + } else { 817 + var tx = sx + 16, ty = sy - th - 8; 818 + if (tx + tw > W - 10) tx = sx - tw - 16; 819 + if (ty < 10) ty = sy + 16; 820 + tooltip.style.left = tx + 'px'; 821 + tooltip.style.top = ty + 'px'; 822 + } 823 + canvas.style.cursor = 'pointer'; 824 + } 825 + 782 826 // --- mobile detection --- 783 827 var isMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0; 784 828 var HIT_RADIUS = isMobile ? 40 : 20; ··· 821 865 return; 822 866 } 823 867 cacheTransform(); 868 + // check publications first (rendered on top of points) 869 + var pi = findNearestPub(mouseX, mouseY); 870 + if (pi >= 0) { 871 + if (hoveredPub !== pi) { 872 + hoveredPub = pi; 873 + hoveredIndex = -1; 874 + markDirty(); 875 + showPubTooltip(pi, mouseX, mouseY); 876 + } 877 + return; 878 + } 879 + hoveredPub = -1; 824 880 var idx = findNearest(mouseX, mouseY, HIT_RADIUS); 825 881 if (idx !== hoveredIndex) { 826 882 hoveredIndex = idx; ··· 832 888 833 889 window.addEventListener('mouseup', function(e) { 834 890 if (dragging) { 835 - if (Math.abs(e.clientX - dragStartX) < 4 && Math.abs(e.clientY - dragStartY) < 4 && hoveredIndex >= 0) { 836 - var p = data.points[hoveredIndex]; 837 - var url = atUriToUrl(p.uri, p.basePath, p.platform, p.path); 838 - if (url) window.open(url, '_blank'); 891 + if (Math.abs(e.clientX - dragStartX) < 4 && Math.abs(e.clientY - dragStartY) < 4) { 892 + // check publication click first 893 + if (hoveredPub >= 0) { 894 + var url = pubUrl(pubData[hoveredPub]); 895 + if (url) window.open(url, '_blank'); 896 + } else if (hoveredIndex >= 0) { 897 + var p = data.points[hoveredIndex]; 898 + var url = atUriToUrl(p.uri, p.basePath, p.platform, p.path); 899 + if (url) window.open(url, '_blank'); 900 + } 839 901 } 840 902 dragging = false; 841 903 } ··· 915 977 Math.abs(endedTouches[0].clientY - dragStartY) < 10)) { 916 978 var tx = endedTouches[0].clientX, ty = endedTouches[0].clientY; 917 979 cacheTransform(); 918 - var idx = findNearest(tx, ty, HIT_RADIUS); 919 - if (idx >= 0) { 920 - if (idx === selectedIndex) { 921 - // second tap on same point — open URL 922 - var p = data.points[idx]; 923 - var url = atUriToUrl(p.uri, p.basePath, p.platform, p.path); 924 - if (url) window.open(url, '_blank'); 980 + // check publications first 981 + var pi = findNearestPub(tx, ty); 982 + if (pi >= 0) { 983 + var url = pubUrl(pubData[pi]); 984 + if (url) window.open(url, '_blank'); 985 + selectedIndex = -1; 986 + hideTooltip(); 987 + } else { 988 + var idx = findNearest(tx, ty, HIT_RADIUS); 989 + if (idx >= 0) { 990 + if (idx === selectedIndex) { 991 + var p = data.points[idx]; 992 + var url = atUriToUrl(p.uri, p.basePath, p.platform, p.path); 993 + if (url) window.open(url, '_blank'); 994 + selectedIndex = -1; 995 + hideTooltip(); 996 + } else { 997 + selectedIndex = idx; 998 + hoveredIndex = idx; 999 + showTooltip(idx, tx, ty); 1000 + markDirty(); 1001 + } 1002 + } else { 925 1003 selectedIndex = -1; 926 1004 hideTooltip(); 927 - } else { 928 - // first tap — show tooltip 929 - selectedIndex = idx; 930 - hoveredIndex = idx; 931 - showTooltip(idx, tx, ty); 932 1005 markDirty(); 933 1006 } 934 - } else { 935 - // tapped empty space — dismiss 936 - selectedIndex = -1; 937 - hideTooltip(); 938 - markDirty(); 939 1007 } 940 1008 } 941 1009 } ··· 975 1043 function hideTooltip() { 976 1044 tooltip.style.display = 'none'; 977 1045 hoveredIndex = -1; 1046 + hoveredPub = -1; 978 1047 canvas.style.cursor = dragging ? 'grabbing' : 'grab'; 979 1048 } 980 1049