extremely claude-assisted go game based on atproto! working on cleaning up and giving a more unique design, still has a bit of a slop vibe to it.
0
fork

Configure Feed

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

Polish reaction overlay: sizing, positioning, click-to-review

- Set emoji size to 28px, fine-tune position offset
- Show only most recent emoji per stone, skip reactions without emoji
- Add hover shrink animation (0.3s ease to 80%) for interactivity hint
- Click an emoji to rewind board history to that move

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

+38 -16
+18 -6
src/lib/components/Board.svelte
··· 31 31 markingDeadStones?: boolean; 32 32 onToggleDeadStone?: (x: number, y: number, color: 'black' | 'white') => void; 33 33 reactionOverlay?: ReactionOverlayItem[]; 34 + onReactionClick?: (x: number, y: number) => void; 34 35 } 35 36 36 37 let { ··· 45 46 deadStones = [], 46 47 markingDeadStones = false, 47 48 onToggleDeadStone = () => {}, 48 - reactionOverlay = [] 49 + reactionOverlay = [], 50 + onReactionClick = () => {} 49 51 }: Props = $props(); 50 52 51 53 let boardElement: HTMLDivElement; ··· 707 709 {@const boardOffset = padding + margin + stoneRadius - 1} 708 710 <div class="reaction-overlay"> 709 711 {#each reactionOverlay as item} 710 - {@const emojiSize = Math.max(12, gridSize * 0.45)} 711 - <div 712 + {@const emojiSize = 28} 713 + <button 712 714 class="reaction-emoji-badge" 713 715 style=" 714 716 left: {boardOffset + item.x * gridSize}px; ··· 716 718 font-size: {emojiSize}px; 717 719 " 718 720 title={item.emojis.join(' ')} 721 + onclick={() => onReactionClick(item.x, item.y)} 719 722 > 720 723 {#if item.emojis.length === 1} 721 724 {item.emojis[0]} ··· 728 731 {:else} 729 732 {item.emojis[0]}<span class="emoji-count">+{item.emojis.length - 1}</span> 730 733 {/if} 731 - </div> 734 + </button> 732 735 {/each} 733 736 </div> 734 737 {/if} ··· 956 959 957 960 .reaction-emoji-badge { 958 961 position: absolute; 959 - transform: translate(10%, -90%); 962 + transform: translate(-28%, -33%); 960 963 line-height: 1; 961 - pointer-events: none; 964 + pointer-events: auto; 965 + cursor: pointer; 962 966 filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5)); 963 967 z-index: 10; 968 + background: none; 969 + border: none; 970 + padding: 0; 971 + transition: transform 0.3s ease; 972 + } 973 + 974 + .reaction-emoji-badge:hover { 975 + transform: translate(-28%, -33%) scale(0.8); 964 976 } 965 977 966 978 .emoji-stack {
+20 -10
src/routes/game/[id]/+page.svelte
··· 311 311 return emojis.slice(0, 3); // Max 3 emojis for badge display 312 312 } 313 313 314 - // Build reaction overlay data for the board (maps reaction emojis to stone coordinates) 314 + // Build reaction overlay data for the board — show most recent emoji per stone 315 315 const reactionOverlayData = $derived(() => { 316 316 if (!showAllReactions || reactions.size === 0 || moves.length === 0) return []; 317 317 318 - const coordMap = new Map<string, string[]>(); // "x,y" -> emojis 318 + const coordMap = new Map<string, string>(); // "x,y" -> most recent emoji 319 319 320 320 for (const [moveUri, reacts] of reactions) { 321 321 const move = moves.find(m => getMoveUri(m) === moveUri); 322 322 if (!move) continue; 323 323 324 + // Find the most recent reaction that has an emoji 325 + const sorted = [...reacts].sort((a, b) => 326 + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() 327 + ); 328 + const latest = sorted.find(r => r.emoji); 329 + if (!latest) continue; 330 + 324 331 const key = `${move.x},${move.y}`; 325 - const emojis = coordMap.get(key) || []; 326 - for (const r of reacts) { 327 - if (r.emoji && !emojis.includes(r.emoji)) { 328 - emojis.push(r.emoji); 329 - } 332 + // Only keep the first one we find per coordinate (most recent wins) 333 + if (!coordMap.has(key)) { 334 + coordMap.set(key, latest.emoji!); 330 335 } 331 - coordMap.set(key, emojis); 332 336 } 333 337 334 - return Array.from(coordMap.entries()).map(([key, emojis]) => { 338 + return Array.from(coordMap.entries()).map(([key, emoji]) => { 335 339 const [x, y] = key.split(',').map(Number); 336 - return { x, y, emojis: emojis.slice(0, 3) }; 340 + return { x, y, emojis: [emoji] }; 337 341 }); 338 342 }); 339 343 ··· 1329 1333 markingDeadStones={markingDeadStones} 1330 1334 onToggleDeadStone={handleToggleDeadStone} 1331 1335 reactionOverlay={reactionOverlayData()} 1336 + onReactionClick={(x, y) => { 1337 + const moveIndex = moves.findIndex(m => m.x === x && m.y === y); 1338 + if (moveIndex !== -1) { 1339 + reviewMove(moveIndex); 1340 + } 1341 + }} 1332 1342 /> 1333 1343 {/if} 1334 1344