From b7f93fe45423d2f89756bb237ecdd9f8f9234ea6 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Wed, 19 Aug 2026 18:53:44 -0400 Subject: [PATCH] Name and color join the preferences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Your wizard's name pre-fills the lobby (and is remembered whenever you create or join), and a preferred color — six swatches in the slip, tap again for none — is claimed quietly in any lobby where it is free and you have not chosen. First-come still wins a contested color; the preference never bumps anyone. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc --- packages/web/src/App.svelte | 40 +++++++++++++++++++++++++++++--- packages/web/src/prefs.svelte.ts | 8 ++++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index 6d01efb..d62c1ff 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -18,7 +18,7 @@ net.connect(); net.startGamePolling(); - let name = $state(""); + let name = $state(prefs.wizardName); let joinCode = $state(""); let claimPhrase = $state(""); let showHelp = $state(false); @@ -953,6 +953,15 @@ /** The preferences slip (client-side taste, saved on this device). */ let prefsOpen = $state(false); + + // In a lobby before the deal: claim the preferred color when it is free + // and this seat has not chosen. One quiet attempt per availability change. + $effect(() => { + if (view || !net.roomId || !net.you || prefs.color == null) return; + if (net.roomColors[net.you] !== undefined) return; + const takenBy = Object.entries(net.roomColors).find(([, v]) => v === prefs.color)?.[0]; + if (takenBy === undefined) net.pickColor(prefs.color); + }); function setPref(k: K, v: (typeof prefs)[K]) { prefs[k] = v; savePrefs(); @@ -1220,6 +1229,21 @@ onchange={(e) => setPref("flourishes", e.currentTarget.checked)} /> Spell flourishes (the animated effects) + +
+ Preferred color + {#each [0, 1, 2, 3, 4, 5] as c (c)} + + {/each} +
Saved on this device. Rules are never affected.
@@ -1450,12 +1474,12 @@
- or join one -
@@ -2452,6 +2476,16 @@ color: #43331f; } .pref-note { font-size: 0.72rem; color: #8a7a5e; margin-top: 0.6rem; } + .pref-name { max-width: 11rem; } + .pref-swatch { + width: 1.3rem; + height: 1.3rem; + border-radius: 50%; + border: 2px solid #43331f; + cursor: pointer; + opacity: 0.55; + } + .pref-swatch.current { opacity: 1; outline: 2px solid #d8b23a; outline-offset: 1px; } .slip.council { background: #ece4f1; border-color: #7a6f9a; } .council-head { font-weight: 700; diff --git a/packages/web/src/prefs.svelte.ts b/packages/web/src/prefs.svelte.ts index df37988..400c8e1 100644 --- a/packages/web/src/prefs.svelte.ts +++ b/packages/web/src/prefs.svelte.ts @@ -10,12 +10,16 @@ export interface Prefs { autoGrab: boolean; /** Spell flourishes (the animated effects layer). */ flourishes: boolean; + /** Pre-filled wizard name for creating and joining games. */ + wizardName: string; + /** Preferred wizard color (0-5), claimed in lobbies when free. */ + color: number | null; } const KEY = "wizwar-prefs"; function load(): Prefs { - const fallback: Prefs = { art: "photo", autoGrab: false, flourishes: true }; + const fallback: Prefs = { art: "photo", autoGrab: false, flourishes: true, wizardName: "", color: null }; try { const raw = localStorage.getItem(KEY); if (!raw) return fallback; @@ -24,6 +28,8 @@ function load(): Prefs { art: p.art === "drawn" ? "drawn" : "photo", autoGrab: p.autoGrab === true, flourishes: p.flourishes !== false, + wizardName: typeof p.wizardName === "string" ? p.wizardName.slice(0, 20) : "", + color: typeof p.color === "number" && p.color >= 0 && p.color <= 5 ? p.color : null, }; } catch { return fallback;