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.

Reorganize archive section into paginated grid

- Convert archive games from list to responsive grid layout
- Add pagination with 6 games per page
- Create compact archive cards with game info
- Add archive count header and page navigation
- Style improvements for grid and pagination controls

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

+451 -126
+1 -1
TODOS.md
··· 3 3 - [x] sharing a game using bsky post intents : The web URL endpoint is https://bsky.app/intent/compose, with the HTTP query parameter text. Remember to use URL-escaping on the query parameter value, and that the post length limit on Bluesky is 300 characters (more precisely, 300 Unicode Grapheme Clusters). 4 4 - [x] tag the players involved in the game and use a hashtag composed of the game ID 5 5 - [ ] Opengraph image preview of the game 6 - - [ ] reorganize the completed game section into a grid to save space, and add pagination 6 + - [x] reorganize the completed game section into a grid to save space, and add pagination
+450 -125
src/routes/+page.svelte
··· 14 14 let handles = $state<Record<string, string>>({}); 15 15 let sessionHandle = $state<string | null>(null); 16 16 let showMyGamesOnly = $state(false); 17 + let archivePage = $state(1); 18 + const ARCHIVE_PAGE_SIZE = 6; 17 19 18 20 // Split games by status 19 21 const currentGames = $derived( ··· 33 35 (data.games || []) 34 36 .filter((g) => g.status === 'completed') 35 37 .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()) 38 + ); 39 + 40 + const archiveTotalPages = $derived(Math.ceil(archivedGames.length / ARCHIVE_PAGE_SIZE)); 41 + 42 + const paginatedArchivedGames = $derived( 43 + archivedGames.slice((archivePage - 1) * ARCHIVE_PAGE_SIZE, archivePage * ARCHIVE_PAGE_SIZE) 36 44 ); 37 45 38 46 // Helper to extract resigned info from last_action_type (format: "resigned:black" or "resigned:white") ··· 147 155 </script> 148 156 149 157 <svelte:head> 150 - <title>atprotogo</title> 158 + <title>Cloud Go</title> 151 159 </svelte:head> 152 160 153 161 <div class="container"> 154 - <header> 155 - <h1>AtProto Go!!</h1> 156 - <p class="subtitle">Play Go using the AT Protocol</p> 157 - <p>All your games are contained safely in your personal repository, yours forever.</p> 162 + <header class="header-card"> 163 + <h1>☁️ Cloud Go ☁️</h1> 164 + <p class="subtitle">An atmospheric go-playing experience</p> 165 + <p class="tagline">All your games are contained safely in your personal repository, yours forever.</p> 158 166 </header> 159 167 160 168 {#if !data.session} ··· 241 249 <!-- Archive --> 242 250 {#if archivedGames.length > 0} 243 251 <div class="card archive-section"> 244 - <h2>Archive</h2> 245 - <div class="games-list"> 246 - {#each archivedGames as game} 252 + <div class="archive-header"> 253 + <h2>Archive</h2> 254 + <span class="archive-count">{archivedGames.length} games</span> 255 + </div> 256 + <div class="archive-grid"> 257 + {#each paginatedArchivedGames as game} 247 258 {@const resignedBy = getResignedBy(game)} 248 - <div class="game-item"> 249 - <div class="game-info"> 259 + <a href="/game/{game.rkey}" class="archive-card"> 260 + <div class="archive-card-header"> 250 261 <div class="game-title">{game.title}</div> 251 - <div class="game-badges"> 252 - {#if resignedBy} 253 - <span class="game-status game-status-cancelled"> 254 - {resignedBy === 'black' ? '⚫' : '⚪'} resigned 255 - </span> 256 - {:else} 257 - <span class="game-status game-status-completed">completed</span> 258 - {/if} 259 - </div> 260 - <div> 261 - <strong>{game.board_size}x{game.board_size}</strong> board 262 - <span class="move-count">{moveCounts[game.id] != null ? `${moveCounts[game.id]} moves` : '...'}</span> 263 - </div> 264 - <div class="game-players"> 265 - Player 1: <a href="https://bsky.app/profile/{game.player_one}" target="_blank" rel="noopener noreferrer" class="player-link">{handles[game.player_one] || game.player_one}</a> 266 - {#if game.player_two} 267 - <br />Player 2: <a href="https://bsky.app/profile/{game.player_two}" target="_blank" rel="noopener noreferrer" class="player-link">{handles[game.player_two] || game.player_two}</a> 268 - {/if} 269 - </div> 262 + {#if resignedBy} 263 + <span class="game-status game-status-cancelled"> 264 + {resignedBy === 'black' ? '⚫' : '⚪'} resigned 265 + </span> 266 + {:else} 267 + <span class="game-status game-status-completed">completed</span> 268 + {/if} 270 269 </div> 271 - <a href="/game/{game.rkey}" class="button button-secondary button-sm"> 272 - View 273 - </a> 274 - </div> 270 + <div class="archive-card-meta"> 271 + <strong>{game.board_size}x{game.board_size}</strong> 272 + <span class="move-count">{moveCounts[game.id] != null ? `${moveCounts[game.id]} moves` : '...'}</span> 273 + </div> 274 + <div class="archive-card-players"> 275 + <span>{handles[game.player_one] || game.player_one.slice(0, 15)}</span> 276 + {#if game.player_two} 277 + <span class="vs">vs</span> 278 + <span>{handles[game.player_two] || game.player_two.slice(0, 15)}</span> 279 + {/if} 280 + </div> 281 + </a> 275 282 {/each} 276 283 </div> 284 + {#if archiveTotalPages > 1} 285 + <div class="pagination"> 286 + <button 287 + class="pagination-btn" 288 + disabled={archivePage <= 1} 289 + onclick={() => archivePage = archivePage - 1} 290 + > 291 + Previous 292 + </button> 293 + <span class="pagination-info"> 294 + Page {archivePage} of {archiveTotalPages} 295 + </span> 296 + <button 297 + class="pagination-btn" 298 + disabled={archivePage >= archiveTotalPages} 299 + onclick={() => archivePage = archivePage + 1} 300 + > 301 + Next 302 + </button> 303 + </div> 304 + {/if} 277 305 </div> 278 306 {/if} 279 307 {/if} ··· 379 407 <!-- Archive --> 380 408 {#if archivedGames.length > 0} 381 409 <div class="card archive-section"> 382 - <h2>Archive</h2> 383 - <div class="games-list"> 384 - {#each archivedGames as game} 410 + <div class="archive-header"> 411 + <h2>Archive</h2> 412 + <span class="archive-count">{archivedGames.length} games</span> 413 + </div> 414 + <div class="archive-grid"> 415 + {#each paginatedArchivedGames as game} 385 416 {@const resignedBy = getResignedBy(game)} 386 - <div class="game-item"> 387 - <div class="game-info"> 417 + <a href="/game/{game.rkey}" class="archive-card"> 418 + <div class="archive-card-header"> 388 419 <div class="game-title">{game.title}</div> 389 - <div class="game-badges"> 390 - {#if resignedBy} 391 - <span class="game-status game-status-cancelled"> 392 - {resignedBy === 'black' ? '⚫' : '⚪'} resigned 393 - </span> 394 - {:else} 395 - <span class="game-status game-status-completed">completed</span> 396 - {/if} 397 - </div> 398 - <div> 399 - <strong>{game.board_size}x{game.board_size}</strong> board 400 - <span class="move-count">{moveCounts[game.id] != null ? `${moveCounts[game.id]} moves` : '...'}</span> 401 - </div> 402 - <div class="game-players"> 403 - Player 1: <a href="https://bsky.app/profile/{game.player_one}" target="_blank" rel="noopener noreferrer" class="player-link">{handles[game.player_one] || game.player_one}</a> 404 - {#if game.player_two} 405 - <br />Player 2: <a href="https://bsky.app/profile/{game.player_two}" target="_blank" rel="noopener noreferrer" class="player-link">{handles[game.player_two] || game.player_two}</a> 406 - {/if} 407 - </div> 420 + {#if resignedBy} 421 + <span class="game-status game-status-cancelled"> 422 + {resignedBy === 'black' ? '⚫' : '⚪'} resigned 423 + </span> 424 + {:else} 425 + <span class="game-status game-status-completed">completed</span> 426 + {/if} 427 + </div> 428 + <div class="archive-card-meta"> 429 + <strong>{game.board_size}x{game.board_size}</strong> 430 + <span class="move-count">{moveCounts[game.id] != null ? `${moveCounts[game.id]} moves` : '...'}</span> 408 431 </div> 409 - <a href="/game/{game.rkey}" class="button button-secondary button-sm"> 410 - View 411 - </a> 412 - </div> 432 + <div class="archive-card-players"> 433 + <span>{handles[game.player_one] || game.player_one.slice(0, 15)}</span> 434 + {#if game.player_two} 435 + <span class="vs">vs</span> 436 + <span>{handles[game.player_two] || game.player_two.slice(0, 15)}</span> 437 + {/if} 438 + </div> 439 + </a> 413 440 {/each} 414 441 </div> 442 + {#if archiveTotalPages > 1} 443 + <div class="pagination"> 444 + <button 445 + class="pagination-btn" 446 + disabled={archivePage <= 1} 447 + onclick={() => archivePage = archivePage - 1} 448 + > 449 + Previous 450 + </button> 451 + <span class="pagination-info"> 452 + Page {archivePage} of {archiveTotalPages} 453 + </span> 454 + <button 455 + class="pagination-btn" 456 + disabled={archivePage >= archiveTotalPages} 457 + onclick={() => archivePage = archivePage + 1} 458 + > 459 + Next 460 + </button> 461 + </div> 462 + {/if} 415 463 </div> 416 464 {/if} 417 465 {/if} ··· 426 474 427 475 header { 428 476 text-align: center; 429 - margin-bottom: 3rem; 477 + margin-bottom: 2rem; 478 + } 479 + 480 + .header-card { 481 + background: linear-gradient( 482 + 135deg, 483 + rgba(255, 255, 255, 0.95) 0%, 484 + rgba(245, 248, 250, 0.9) 50%, 485 + rgba(232, 239, 244, 0.85) 100% 486 + ); 487 + border: none; 488 + border-radius: 2rem 2.5rem 2rem 2.2rem; 489 + padding: 2rem 2.5rem; 490 + box-shadow: 491 + 0 0 20px rgba(255, 255, 255, 0.8), 492 + 0 0 40px rgba(255, 255, 255, 0.4), 493 + 0 8px 32px rgba(90, 122, 144, 0.12), 494 + inset 0 1px 1px rgba(255, 255, 255, 0.9); 495 + backdrop-filter: blur(8px); 496 + position: relative; 497 + } 498 + 499 + .header-card::before { 500 + content: ''; 501 + position: absolute; 502 + inset: -2px; 503 + border-radius: inherit; 504 + background: linear-gradient( 505 + 135deg, 506 + rgba(255, 255, 255, 0.6) 0%, 507 + rgba(212, 229, 239, 0.3) 50%, 508 + rgba(255, 255, 255, 0.4) 100% 509 + ); 510 + filter: blur(4px); 511 + z-index: -1; 430 512 } 431 513 432 514 h1 { 433 - font-size: 2.5rem; 515 + font-size: 2.75rem; 434 516 margin: 0; 435 - color: #1a202c; 517 + color: var(--sky-slate-dark); 518 + font-weight: 700; 519 + letter-spacing: -0.02em; 436 520 } 437 521 438 522 .subtitle { 439 - color: #718096; 523 + color: var(--sky-slate); 440 524 font-size: 1.125rem; 441 525 margin-top: 0.5rem; 526 + font-weight: 500; 527 + } 528 + 529 + .tagline { 530 + color: var(--sky-gray); 531 + font-size: 0.95rem; 532 + margin-top: 0.5rem; 442 533 } 443 534 444 535 .login-card { 445 - background: white; 446 - border-radius: 0.5rem; 447 - padding: 2rem; 448 - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 449 - max-width: 400px; 536 + background: linear-gradient( 537 + 135deg, 538 + rgba(255, 255, 255, 0.95) 0%, 539 + rgba(245, 248, 250, 0.9) 50%, 540 + rgba(232, 239, 244, 0.85) 100% 541 + ); 542 + border: none; 543 + border-radius: 2.2rem 2rem 2.4rem 2rem; 544 + padding: 2.5rem; 545 + box-shadow: 546 + 0 0 20px rgba(255, 255, 255, 0.8), 547 + 0 0 40px rgba(255, 255, 255, 0.4), 548 + 0 8px 32px rgba(90, 122, 144, 0.12), 549 + inset 0 1px 1px rgba(255, 255, 255, 0.9); 550 + backdrop-filter: blur(8px); 551 + max-width: 420px; 450 552 margin: 0 auto; 553 + position: relative; 554 + } 555 + 556 + .login-card::before { 557 + content: ''; 558 + position: absolute; 559 + inset: -2px; 560 + border-radius: inherit; 561 + background: linear-gradient( 562 + 135deg, 563 + rgba(255, 255, 255, 0.6) 0%, 564 + rgba(212, 229, 239, 0.3) 50%, 565 + rgba(255, 255, 255, 0.4) 100% 566 + ); 567 + filter: blur(4px); 568 + z-index: -1; 451 569 } 452 570 453 571 .login-card h2 { 454 572 margin-top: 0; 455 - color: #2d3748; 573 + color: var(--sky-slate-dark); 574 + font-size: 1.5rem; 575 + font-weight: 600; 456 576 } 457 577 458 578 .card { 459 - background: white; 460 - border-radius: 0.5rem; 579 + background: linear-gradient( 580 + 135deg, 581 + rgba(255, 255, 255, 0.95) 0%, 582 + rgba(245, 248, 250, 0.9) 50%, 583 + rgba(232, 239, 244, 0.85) 100% 584 + ); 585 + border: none; 586 + border-radius: 1.8rem 2rem 1.6rem 2.1rem; 461 587 padding: 1.5rem; 462 - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 588 + box-shadow: 589 + 0 0 15px rgba(255, 255, 255, 0.7), 590 + 0 0 30px rgba(255, 255, 255, 0.3), 591 + 0 6px 24px rgba(90, 122, 144, 0.1), 592 + inset 0 1px 1px rgba(255, 255, 255, 0.9); 593 + backdrop-filter: blur(8px); 463 594 margin-bottom: 1.5rem; 595 + position: relative; 596 + } 597 + 598 + .card::before { 599 + content: ''; 600 + position: absolute; 601 + inset: -2px; 602 + border-radius: inherit; 603 + background: linear-gradient( 604 + 135deg, 605 + rgba(255, 255, 255, 0.5) 0%, 606 + rgba(212, 229, 239, 0.2) 50%, 607 + rgba(255, 255, 255, 0.3) 100% 608 + ); 609 + filter: blur(3px); 610 + z-index: -1; 464 611 } 465 612 466 613 .card h2 { 467 614 margin-top: 0; 468 - color: #2d3748; 615 + color: var(--sky-slate-dark); 616 + font-weight: 600; 469 617 } 470 618 471 619 .user-section { 472 620 display: flex; 473 621 justify-content: space-between; 474 622 align-items: center; 475 - padding: 1rem; 476 - background: #f7fafc; 477 - border-radius: 0.5rem; 623 + padding: 1rem 1.5rem; 624 + background: linear-gradient(135deg, var(--sky-apricot-light) 0%, var(--sky-rose-light) 100%); 625 + border-radius: 1rem; 478 626 margin-bottom: 2rem; 627 + border: 1px solid var(--sky-apricot); 628 + } 629 + 630 + .user-section p { 631 + color: var(--sky-slate-dark); 479 632 } 480 633 481 634 .create-game-form { ··· 486 639 487 640 .input, .select { 488 641 width: 100%; 489 - padding: 0.75rem; 490 - border: 1px solid #cbd5e0; 491 - border-radius: 0.375rem; 642 + padding: 0.875rem 1rem; 643 + border: 2px solid var(--sky-blue-pale); 644 + border-radius: 0.5rem; 492 645 font-size: 1rem; 646 + background: var(--sky-white); 647 + color: var(--sky-slate-dark); 648 + transition: all 0.2s; 493 649 } 494 650 495 651 .input:focus, .select:focus { 496 652 outline: none; 497 - border-color: #4299e1; 653 + border-color: var(--sky-apricot); 654 + box-shadow: 0 0 0 3px var(--sky-apricot-light); 655 + } 656 + 657 + .input::placeholder { 658 + color: var(--sky-gray-light); 498 659 } 499 660 500 661 .button { 501 - padding: 0.75rem 1.5rem; 662 + padding: 0.875rem 1.75rem; 502 663 border: none; 503 - border-radius: 0.375rem; 664 + border-radius: 0.5rem; 504 665 font-size: 1rem; 666 + font-weight: 600; 505 667 cursor: pointer; 506 668 transition: all 0.2s; 507 669 } ··· 512 674 } 513 675 514 676 .button-primary { 515 - background: #4299e1; 677 + background: linear-gradient(135deg, var(--sky-apricot-dark) 0%, var(--sky-apricot) 100%); 516 678 color: white; 679 + box-shadow: 0 2px 8px rgba(229, 168, 120, 0.3); 517 680 } 518 681 519 682 .button-primary:hover:not(:disabled) { 520 - background: #3182ce; 683 + background: linear-gradient(135deg, var(--sky-apricot) 0%, var(--sky-apricot-dark) 100%); 684 + transform: translateY(-1px); 685 + box-shadow: 0 4px 12px rgba(229, 168, 120, 0.4); 521 686 } 522 687 523 688 .button-secondary { 524 - background: #e2e8f0; 525 - color: #2d3748; 689 + background: var(--sky-cloud); 690 + color: var(--sky-slate); 691 + border: 1px solid var(--sky-blue-pale); 526 692 } 527 693 528 694 .button-secondary:hover:not(:disabled) { 529 - background: #cbd5e0; 695 + background: var(--sky-blue-pale); 696 + color: var(--sky-slate-dark); 530 697 } 531 698 532 699 .button-sm { ··· 537 704 .games-list { 538 705 display: flex; 539 706 flex-direction: column; 540 - gap: 1rem; 707 + gap: 0.75rem; 541 708 } 542 709 543 710 .game-item { 544 711 display: flex; 545 712 justify-content: space-between; 546 713 align-items: center; 547 - padding: 1rem; 548 - border: 1px solid #e2e8f0; 549 - border-radius: 0.375rem; 714 + padding: 1rem 1.25rem; 715 + border: 1px solid var(--sky-blue-pale); 716 + border-radius: 0.75rem; 717 + background: linear-gradient(135deg, var(--sky-white) 0%, var(--sky-cloud) 100%); 718 + transition: all 0.2s; 719 + } 720 + 721 + .game-item:hover { 722 + border-color: var(--sky-apricot); 723 + box-shadow: 0 4px 12px rgba(90, 122, 144, 0.1); 550 724 } 551 725 552 726 .game-info { 553 727 display: flex; 554 728 flex-direction: column; 555 - gap: 0.5rem; 729 + gap: 0.375rem; 556 730 } 557 731 558 732 .game-status { 559 733 display: inline-block; 560 734 padding: 0.25rem 0.75rem; 561 735 border-radius: 9999px; 562 - font-size: 0.75rem; 563 - font-weight: 600; 736 + font-size: 0.7rem; 737 + font-weight: 700; 564 738 text-transform: uppercase; 739 + letter-spacing: 0.05em; 565 740 } 566 741 567 742 .game-status-waiting { 568 - background: #fefcbf; 569 - color: #744210; 743 + background: var(--sky-apricot-light); 744 + color: var(--sky-apricot-dark); 570 745 } 571 746 572 747 .game-status-active { 573 - background: #c6f6d5; 574 - color: #22543d; 748 + background: #d1fae5; 749 + color: #047857; 575 750 } 576 751 577 752 .game-status-completed { 578 - background: #e2e8f0; 579 - color: #4a5568; 753 + background: var(--sky-blue-pale); 754 + color: var(--sky-slate); 580 755 } 581 756 582 757 .game-status-cancelled { 583 - background: #fed7d7; 584 - color: #c53030; 758 + background: var(--sky-rose-light); 759 + color: var(--sky-rose-dark); 585 760 } 586 761 587 762 .game-badges { ··· 592 767 593 768 .game-players { 594 769 font-size: 0.875rem; 595 - color: #718096; 770 + color: var(--sky-gray); 596 771 } 597 772 598 773 .profile-link { 599 - color: #4299e1; 774 + color: var(--sky-slate); 600 775 text-decoration: none; 601 776 font-weight: 600; 777 + transition: color 0.2s; 602 778 } 603 779 604 780 .profile-link:hover { 605 - text-decoration: underline; 781 + color: var(--sky-apricot-dark); 606 782 } 607 783 608 784 .player-link { 609 - color: #4299e1; 785 + color: var(--sky-slate); 610 786 text-decoration: none; 787 + transition: color 0.2s; 611 788 } 612 789 613 790 .player-link:hover { 614 - text-decoration: underline; 791 + color: var(--sky-apricot-dark); 615 792 } 616 793 617 794 .empty-state { 618 795 text-align: center; 619 - color: #a0aec0; 796 + color: var(--sky-gray); 620 797 padding: 2rem; 798 + font-style: italic; 621 799 } 622 800 623 801 form { ··· 628 806 629 807 .spectate-divider { 630 808 text-align: center; 631 - color: #a0aec0; 632 - margin: 1rem 0 0.5rem; 809 + color: var(--sky-gray-light); 810 + margin: 1.25rem 0 0.75rem; 633 811 font-size: 0.875rem; 812 + position: relative; 813 + } 814 + 815 + .spectate-divider::before, 816 + .spectate-divider::after { 817 + content: ''; 818 + position: absolute; 819 + top: 50%; 820 + width: 35%; 821 + height: 1px; 822 + background: var(--sky-blue-pale); 823 + } 824 + 825 + .spectate-divider::before { 826 + left: 0; 827 + } 828 + 829 + .spectate-divider::after { 830 + right: 0; 634 831 } 635 832 636 833 .spectate-button { ··· 645 842 justify-content: flex-end; 646 843 margin-bottom: 1rem; 647 844 font-size: 0.875rem; 648 - color: #718096; 845 + color: var(--sky-gray); 649 846 } 650 847 651 848 .login-banner-link { 652 849 background: none; 653 850 border: none; 654 - color: #4299e1; 851 + color: var(--sky-slate); 655 852 cursor: pointer; 656 853 font-size: 0.875rem; 657 854 padding: 0; 658 855 text-decoration: underline; 856 + font-weight: 500; 857 + transition: color 0.2s; 659 858 } 660 859 661 860 .login-banner-link:hover { 662 - color: #3182ce; 861 + color: var(--sky-apricot-dark); 663 862 } 664 863 665 864 .game-title { 666 865 font-weight: 700; 667 866 font-size: 1.05rem; 668 - color: #1a202c; 867 + color: var(--sky-slate-dark); 669 868 } 670 869 671 870 .move-count { 672 - color: #718096; 871 + color: var(--sky-gray); 673 872 font-size: 0.875rem; 674 873 margin-left: 0.5rem; 675 874 } ··· 708 907 align-items: center; 709 908 gap: 0.5rem; 710 909 font-size: 0.875rem; 711 - color: #718096; 910 + color: var(--sky-gray); 712 911 cursor: pointer; 912 + transition: color 0.2s; 913 + } 914 + 915 + .toggle-label:hover { 916 + color: var(--sky-slate); 713 917 } 714 918 715 919 .toggle-label input { 716 920 cursor: pointer; 921 + accent-color: var(--sky-apricot-dark); 717 922 } 718 923 719 924 .waiting-games-grid { ··· 727 932 flex-wrap: wrap; 728 933 align-items: center; 729 934 gap: 0.5rem; 730 - padding: 0.75rem; 731 - border: 1px solid #e2e8f0; 732 - border-radius: 0.375rem; 935 + padding: 0.875rem 1rem; 936 + border: 1px solid var(--sky-blue-pale); 937 + border-radius: 0.75rem; 938 + background: linear-gradient(135deg, var(--sky-white) 0%, var(--sky-cloud) 100%); 939 + transition: all 0.2s; 940 + } 941 + 942 + .game-item-compact:hover { 943 + border-color: var(--sky-apricot); 944 + box-shadow: 0 4px 12px rgba(90, 122, 144, 0.1); 733 945 } 734 946 735 947 .game-item-compact .game-title { ··· 742 954 flex-direction: column; 743 955 gap: 0.25rem; 744 956 font-size: 0.875rem; 745 - color: #718096; 957 + color: var(--sky-gray); 746 958 flex: 1; 747 959 min-width: 100px; 748 960 } 749 961 750 962 .player-link-small { 751 963 font-size: 0.75rem; 752 - color: #a0aec0; 964 + color: var(--sky-gray-light); 753 965 overflow: hidden; 754 966 text-overflow: ellipsis; 755 967 white-space: nowrap; ··· 757 969 758 970 .archive-section { 759 971 margin-top: 2rem; 760 - border-top: 2px solid #e2e8f0; 972 + border-top: 2px solid var(--sky-blue-pale); 761 973 padding-top: 1rem; 762 974 } 763 975 764 976 .archive-section h2 { 765 - color: #718096; 977 + color: var(--sky-gray); 978 + margin: 0; 979 + } 980 + 981 + .archive-header { 982 + display: flex; 983 + justify-content: space-between; 984 + align-items: center; 985 + margin-bottom: 1rem; 986 + } 987 + 988 + .archive-count { 989 + font-size: 0.875rem; 990 + color: var(--sky-gray-light); 991 + } 992 + 993 + .archive-grid { 994 + display: grid; 995 + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); 996 + gap: 1rem; 997 + } 998 + 999 + .archive-card { 1000 + display: flex; 1001 + flex-direction: column; 1002 + gap: 0.5rem; 1003 + padding: 1rem 1.25rem; 1004 + border: 1px solid var(--sky-blue-pale); 1005 + border-radius: 0.75rem; 1006 + background: linear-gradient(135deg, var(--sky-white) 0%, var(--sky-cloud) 100%); 1007 + transition: all 0.2s; 1008 + text-decoration: none; 1009 + color: inherit; 1010 + opacity: 0.85; 1011 + } 1012 + 1013 + .archive-card:hover { 1014 + opacity: 1; 1015 + border-color: var(--sky-apricot); 1016 + box-shadow: 0 4px 12px rgba(90, 122, 144, 0.1); 1017 + transform: translateY(-2px); 1018 + } 1019 + 1020 + .archive-card-header { 1021 + display: flex; 1022 + justify-content: space-between; 1023 + align-items: flex-start; 1024 + gap: 0.5rem; 1025 + } 1026 + 1027 + .archive-card-header .game-title { 1028 + flex: 1; 1029 + font-size: 1rem; 1030 + } 1031 + 1032 + .archive-card-meta { 1033 + font-size: 0.875rem; 1034 + color: var(--sky-slate); 1035 + } 1036 + 1037 + .archive-card-meta .move-count { 1038 + margin-left: 0.5rem; 1039 + } 1040 + 1041 + .archive-card-players { 1042 + display: flex; 1043 + flex-wrap: wrap; 1044 + align-items: center; 1045 + gap: 0.25rem; 1046 + font-size: 0.8rem; 1047 + color: var(--sky-gray); 1048 + overflow: hidden; 1049 + } 1050 + 1051 + .archive-card-players .vs { 1052 + color: var(--sky-gray-light); 1053 + font-style: italic; 1054 + } 1055 + 1056 + .pagination { 1057 + display: flex; 1058 + justify-content: center; 1059 + align-items: center; 1060 + gap: 1rem; 1061 + margin-top: 1.5rem; 1062 + padding-top: 1rem; 1063 + border-top: 1px solid var(--sky-blue-pale); 1064 + } 1065 + 1066 + .pagination-btn { 1067 + padding: 0.5rem 1rem; 1068 + border: 1px solid var(--sky-blue-pale); 1069 + border-radius: 0.5rem; 1070 + background: var(--sky-white); 1071 + color: var(--sky-slate); 1072 + font-size: 0.875rem; 1073 + font-weight: 500; 1074 + cursor: pointer; 1075 + transition: all 0.2s; 1076 + } 1077 + 1078 + .pagination-btn:hover:not(:disabled) { 1079 + background: var(--sky-cloud); 1080 + border-color: var(--sky-apricot); 1081 + } 1082 + 1083 + .pagination-btn:disabled { 1084 + opacity: 0.4; 1085 + cursor: not-allowed; 1086 + } 1087 + 1088 + .pagination-info { 1089 + font-size: 0.875rem; 1090 + color: var(--sky-gray); 766 1091 } 767 1092 </style>