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.

Show page skeleton while loading, fill in data progressively

- Always show page structure (info cards, board, etc.)
- Show skeleton placeholder for move count while loading
- Board renders immediately and fills with stones as data arrives
- Reactions panel shows loading state separately
- Add shimmer animation for skeleton text

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

+74 -66
+74 -66
src/routes/game/[id]/+page.svelte
··· 552 552 </div> 553 553 </header> 554 554 555 - {#if loading} 556 - <div class="loading-state"> 557 - <p>Loading game data...</p> 555 + <div class="game-info"> 556 + <div class="info-card cloud-card"> 557 + <h3>Game Info</h3> 558 + <p> 559 + <strong>Status:</strong> <span class="status-{gameStatus}">{gameStatus}</span> 560 + {#if gameStatus === 'waiting' && data.session && data.session.did === gamePlayerOne} 561 + <button class="copy-invite-btn" onclick={copyInviteLink}> 562 + Copy Invite 563 + </button> 564 + {/if} 565 + </p> 566 + <p><strong>Board:</strong> {gameBoardSize}x{gameBoardSize}</p> 567 + <p><strong>Moves:</strong> {#if loadingMoves}<span class="skeleton-text">...</span>{:else}{moves.length}{/if}</p> 568 + <a href={getShareUrl()} target="_blank" rel="noopener noreferrer" class="share-button-small"> 569 + 🦋 Share on Bluesky 570 + </a> 558 571 </div> 559 - {:else} 560 - <div class="game-info"> 561 - <div class="info-card cloud-card"> 562 - <h3>Game Info</h3> 563 - <p> 564 - <strong>Status:</strong> <span class="status-{gameStatus}">{gameStatus}</span> 565 - {#if gameStatus === 'waiting' && data.session && data.session.did === gamePlayerOne} 566 - <button class="copy-invite-btn" onclick={copyInviteLink}> 567 - Copy Invite 568 - </button> 569 - {/if} 570 - </p> 571 - <p><strong>Board:</strong> {gameBoardSize}x{gameBoardSize}</p> 572 - <p><strong>Moves:</strong> {moves.length}</p> 573 - <a href={getShareUrl()} target="_blank" rel="noopener noreferrer" class="share-button-small"> 574 - 🦋 Share on Bluesky 572 + 573 + <div class="info-card cloud-card"> 574 + <h3>Players</h3> 575 + <p> 576 + <span class="player-black">⚫</span> 577 + <a href="https://bsky.app/profile/{gamePlayerOne}" target="_blank" rel="noopener noreferrer" class="player-link"> 578 + {playerOneHandle} 575 579 </a> 576 - </div> 577 - 578 - <div class="info-card cloud-card"> 579 - <h3>Players</h3> 580 + {#if data.session && data.session.did === gamePlayerOne} 581 + <span class="you-label">(you)</span> 582 + {/if} 583 + </p> 584 + {#if gamePlayerTwo} 580 585 <p> 581 - <span class="player-black">⚫</span> 582 - <a href="https://bsky.app/profile/{gamePlayerOne}" target="_blank" rel="noopener noreferrer" class="player-link"> 583 - {playerOneHandle} 586 + <span class="player-white">⚪</span> 587 + <a href="https://bsky.app/profile/{gamePlayerTwo}" target="_blank" rel="noopener noreferrer" class="player-link"> 588 + {playerTwoHandle || gamePlayerTwo} 584 589 </a> 585 - {#if data.session && data.session.did === gamePlayerOne} 590 + {#if data.session && data.session.did === gamePlayerTwo} 586 591 <span class="you-label">(you)</span> 587 592 {/if} 588 593 </p> 589 - {#if gamePlayerTwo} 590 - <p> 591 - <span class="player-white">⚪</span> 592 - <a href="https://bsky.app/profile/{gamePlayerTwo}" target="_blank" rel="noopener noreferrer" class="player-link"> 593 - {playerTwoHandle || gamePlayerTwo} 594 - </a> 595 - {#if data.session && data.session.did === gamePlayerTwo} 596 - <span class="you-label">(you)</span> 597 - {/if} 594 + {:else} 595 + <p class="waiting">Waiting for opponent...</p> 596 + {/if} 597 + </div> 598 + 599 + {#if gameStatus === 'completed' && !loadingMoves} 600 + {#if resigns.length > 0} 601 + {@const resign = resigns[0]} 602 + <div class="info-card cloud-card cancelled-card"> 603 + <h3>Game Ended</h3> 604 + <p class="cancelled-text"> 605 + {resign.color === 'black' ? '⚫' : '⚪'} 606 + {resign.color === 'black' ? playerOneHandle : playerTwoHandle} 607 + {resign.player === data.session?.did ? ' (you)' : ''} 608 + resigned 598 609 </p> 599 - {:else} 600 - <p class="waiting">Waiting for opponent...</p> 601 - {/if} 602 - </div> 603 - 604 - {#if gameStatus === 'completed'} 605 - {#if resigns.length > 0} 606 - {@const resign = resigns[0]} 607 - <div class="info-card cloud-card cancelled-card"> 608 - <h3>Game Ended</h3> 609 - <p class="cancelled-text"> 610 - {resign.color === 'black' ? '⚫' : '⚪'} 611 - {resign.color === 'black' ? playerOneHandle : playerTwoHandle} 612 - {resign.player === data.session?.did ? ' (you)' : ''} 613 - resigned 614 - </p> 615 - </div> 616 - {:else if gameBlackScore !== null && gameWhiteScore !== null} 617 - <div class="info-card cloud-card score-card"> 618 - <h3>Final Scores</h3> 619 - <p><span class="player-black">⚫</span> Black: {gameBlackScore}</p> 620 - <p><span class="player-white">⚪</span> White: {gameWhiteScore}</p> 621 - {#if gameWinner} 622 - <p class="winner-text">🏆 Winner: {gameWinner === gamePlayerOne ? playerOneHandle : playerTwoHandle}</p> 623 - {/if} 624 - </div> 625 - {/if} 610 + </div> 611 + {:else if gameBlackScore !== null && gameWhiteScore !== null} 612 + <div class="info-card cloud-card score-card"> 613 + <h3>Final Scores</h3> 614 + <p><span class="player-black">⚫</span> Black: {gameBlackScore}</p> 615 + <p><span class="player-white">⚪</span> White: {gameWhiteScore}</p> 616 + {#if gameWinner} 617 + <p class="winner-text">🏆 Winner: {gameWinner === gamePlayerOne ? playerOneHandle : playerTwoHandle}</p> 618 + {/if} 619 + </div> 626 620 {/if} 627 - </div> 621 + {/if} 622 + </div> 628 623 629 624 {#if gameStatus === 'active' || gameStatus === 'completed'} 630 625 <div class="game-board-container"> ··· 908 903 </div> 909 904 </div> 910 905 {/if} 911 - {:else} 906 + {:else if gameStatus === 'waiting'} 912 907 <p class="waiting-message">Waiting for another player to join...</p> 913 908 {/if} 914 - {/if} 915 909 </div> 916 910 917 911 <style> ··· 926 920 .container { 927 921 padding: 1rem; 928 922 } 923 + } 924 + 925 + .skeleton-text { 926 + display: inline-block; 927 + background: linear-gradient(90deg, var(--sky-cloud) 25%, var(--sky-blue-pale) 50%, var(--sky-cloud) 75%); 928 + background-size: 200% 100%; 929 + animation: shimmer 1.5s infinite; 930 + border-radius: 0.25rem; 931 + min-width: 2rem; 932 + } 933 + 934 + @keyframes shimmer { 935 + 0% { background-position: 200% 0; } 936 + 100% { background-position: -200% 0; } 929 937 } 930 938 931 939 .loading-state {