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;