Name and color join the preferences

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc
This commit is contained in:
Eric Wagoner
2026-08-19 18:53:44 -04:00
co-authored by Claude Fable 5
parent 62c4680d1f
commit b7f93fe454
2 changed files with 44 additions and 4 deletions
+37 -3
View File
@@ -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 extends keyof typeof prefs>(k: K, v: (typeof prefs)[K]) {
prefs[k] = v;
savePrefs();
@@ -1220,6 +1229,21 @@
onchange={(e) => setPref("flourishes", e.currentTarget.checked)} />
<span>Spell flourishes (the animated effects)</span>
</label>
<label class="pref-row">
<span>Wizard name</span>
<input class="setup-input pref-name" maxlength="20" value={prefs.wizardName}
placeholder="e.g. Mordecai"
onchange={(e) => { setPref("wizardName", e.currentTarget.value.trim()); name = prefs.wizardName; }} />
</label>
<div class="pref-row">
<span>Preferred color</span>
{#each [0, 1, 2, 3, 4, 5] as c (c)}
<button class="pref-swatch" class:current={prefs.color === c}
style:background={PLAYER_COLORS[c]}
aria-label={`prefer wizard color ${c + 1}`}
onclick={() => setPref("color", prefs.color === c ? null : c)}></button>
{/each}
</div>
<div class="pref-note">Saved on this device. Rules are never affected.</div>
<div class="attack-actions">
<button class="hint-cancel" onclick={() => (prefsOpen = false)}>done</button>
@@ -1450,12 +1474,12 @@
<input bind:value={name} maxlength="20" placeholder="e.g. Mordecai" />
</label>
<div class="boxlid-actions">
<button class="stamp" disabled={!name.trim()} onclick={() => net.create(name)}>
<button class="stamp" disabled={!name.trim()} onclick={() => { setPref("wizardName", name.trim()); net.create(name); }}>
Create a game
</button>
<span class="or">or join one</span>
<input class="code" bind:value={joinCode} maxlength="4" placeholder="CODE" />
<button class="stamp" disabled={!name.trim() || !joinCode.trim()} onclick={() => net.join(joinCode, name)}>
<button class="stamp" disabled={!name.trim() || !joinCode.trim()} onclick={() => { setPref("wizardName", name.trim()); net.join(joinCode, name); }}>
Join
</button>
</div>
@@ -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;
+7 -1
View File
@@ -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;