From 5f6b8b76c6ac9cef3f902ea2eeaf4ad0a0c02b45 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 25 Jul 2026 11:14:35 +0700 Subject: [PATCH] feat: rewrite components in Svelte 5 runes Port all six React components. State becomes $state, memos become $derived, and the gameRef mirror that existed only to dodge a stale closure is gone -- runes read current values at call time, so one $state holds the game. Initialization moves to onMount rather than $effect: the page is prerendered, so loadChampions and localStorage must be browser-only, and onMount encodes that structurally instead of relying on the effect body happening to read no reactive state. champion-search becomes a real combobox/listbox: input carries the combobox role with aria-expanded and aria-activedescendant, options are list items wrapping buttons, and Escape closes the dropdown. Visual output is unchanged. next/image becomes plain img -- every usage already passed unoptimized. --- web/src/lib/components/champion-search.svelte | 122 +++++++++++++++ web/src/lib/components/game-board.svelte | 147 ++++++++++++++++++ web/src/lib/components/game-over.svelte | 46 ++++++ web/src/lib/components/guess-grid.svelte | 28 ++++ web/src/lib/components/guess-row.svelte | 35 +++++ web/src/lib/components/stats-display.svelte | 29 ++++ web/src/routes/+page.svelte | 7 +- 7 files changed, 412 insertions(+), 2 deletions(-) create mode 100644 web/src/lib/components/champion-search.svelte create mode 100644 web/src/lib/components/game-board.svelte create mode 100644 web/src/lib/components/game-over.svelte create mode 100644 web/src/lib/components/guess-grid.svelte create mode 100644 web/src/lib/components/guess-row.svelte create mode 100644 web/src/lib/components/stats-display.svelte diff --git a/web/src/lib/components/champion-search.svelte b/web/src/lib/components/champion-search.svelte new file mode 100644 index 0000000..59a69ba --- /dev/null +++ b/web/src/lib/components/champion-search.svelte @@ -0,0 +1,122 @@ + + +
+ = 0 + ? `champion-option-${activeIndex}` + : undefined} + bind:value={query} + oninput={() => { + activeIndex = -1; + isOpen = true; + }} + onkeydown={handleKeyDown} + {disabled} + placeholder={disabled ? "Game over" : "Type a champion name..."} + autocomplete="off" + class="w-full px-4 py-3 rounded-lg bg-[var(--color-input-bg)] border-2 border-[var(--color-input-border)] text-[var(--color-text)] text-base outline-none transition-colors focus:border-[var(--color-accent)] disabled:opacity-50 disabled:cursor-not-allowed placeholder:text-[var(--color-text-muted)]" + /> + + {#if isVisible} + + {/if} +
diff --git a/web/src/lib/components/game-board.svelte b/web/src/lib/components/game-board.svelte new file mode 100644 index 0000000..c5989cd --- /dev/null +++ b/web/src/lib/components/game-board.svelte @@ -0,0 +1,147 @@ + + +{#if loading} +

+ Loading champions... +

+{:else if error} +

+ Failed to load champion data. Please refresh. +

+{:else if game} + +
+ + +
+ + {#if stats} + + {/if} + + + +

+ {game.maxGuesses > 0 + ? `${game.guesses.length} / ${game.maxGuesses} guesses` + : `${game.guesses.length} guess${game.guesses.length !== 1 ? "es" : ""}`} +

+ + + + {#if game.isOver} + + {/if} +{/if} diff --git a/web/src/lib/components/game-over.svelte b/web/src/lib/components/game-over.svelte new file mode 100644 index 0000000..031bfdc --- /dev/null +++ b/web/src/lib/components/game-over.svelte @@ -0,0 +1,46 @@ + + +
+

+ {isWon ? "You got it!" : "Game Over"} +

+ + {#if isWon} +

+ You found {target.name} in + {guessCount} + guess{guessCount > 1 ? "es" : ""}! +

+ {:else} +

+ The champion was {target.name} +

+ {target.name} + {/if} + + {#if onNewGame} + + {/if} +
diff --git a/web/src/lib/components/guess-grid.svelte b/web/src/lib/components/guess-grid.svelte new file mode 100644 index 0000000..bd5a112 --- /dev/null +++ b/web/src/lib/components/guess-grid.svelte @@ -0,0 +1,28 @@ + + +
+ +
+
Champion
+ {#each CLASSIC_ATTRIBUTES as attr (attr.key)} +
{attr.label}
+ {/each} +
+ + + {#each rows as row (row.champion.id)} + + {/each} +
diff --git a/web/src/lib/components/guess-row.svelte b/web/src/lib/components/guess-row.svelte new file mode 100644 index 0000000..7e7912c --- /dev/null +++ b/web/src/lib/components/guess-row.svelte @@ -0,0 +1,35 @@ + + +
+ +
+ {champion.name} + {champion.name} +
+ + + {#each results as r, i (r.key)} +
+ {r.guessValue} + {#if r.direction} + + {r.direction === "up" ? " ↑" : " ↓"} + + {/if} +
+ {/each} +
diff --git a/web/src/lib/components/stats-display.svelte b/web/src/lib/components/stats-display.svelte new file mode 100644 index 0000000..51ff7c4 --- /dev/null +++ b/web/src/lib/components/stats-display.svelte @@ -0,0 +1,29 @@ + + +{#if stats && stats.gamesPlayed > 0} +
+ {#each items as { value, label } (label)} +
+ {value} + {label} +
+ {/each} +
+{/if} diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index bbf213e..28dbcda 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -1,9 +1,12 @@ - + +

LoLdle

+