A player asked for cards down the side instead of along the bottom. With the new pref on (wide screens only), the hand claims a sticky, scrolling column at the left while the hint strips and action buttons stay beneath the board — the table-edge dissolves into the grid so its children place themselves. The strip moves inside the .game grid to make that possible; the default bottom layout spans the full row as before, and phones keep their one-column flow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
67 lines
2.4 KiB
TypeScript
67 lines
2.4 KiB
TypeScript
// Player preferences: purely client-side taste, saved on this device.
|
|
// Nothing here touches the rules — a preference may change what you SEE
|
|
// or what routine commands the client sends on your behalf, never what
|
|
// is legal.
|
|
|
|
export interface Prefs {
|
|
/** Token art: the photographed cardboard, or the hand-drawn vectors. */
|
|
art: "photo" | "drawn";
|
|
/** Standing on lone grabbable gold at end of turn: pick it up first. */
|
|
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;
|
|
/** "Are you sure?" prompts before easy-to-regret plays. */
|
|
cautions: boolean;
|
|
/** Default skill for automatons seated from the lobby. */
|
|
botTier: "apprentice" | "adept" | "archmage";
|
|
/** Instant replay: notable chronicle lines offer a first-person reel. */
|
|
instantReplay: boolean;
|
|
/** The live first-person pane above the board during play. */
|
|
liveFp: boolean;
|
|
/** The hand of cards stands in a column at the board's left instead of
|
|
* lying along the bottom (wide screens only). */
|
|
handLeft: boolean;
|
|
}
|
|
|
|
const KEY = "wizwar-prefs";
|
|
|
|
function load(): Prefs {
|
|
const fallback: Prefs = {
|
|
art: "photo", autoGrab: false, flourishes: true, wizardName: "", color: null,
|
|
cautions: true, botTier: "adept", instantReplay: true, liveFp: false, handLeft: false,
|
|
};
|
|
try {
|
|
const raw = localStorage.getItem(KEY);
|
|
if (!raw) return fallback;
|
|
const p = JSON.parse(raw) as Partial<Prefs>;
|
|
return {
|
|
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,
|
|
cautions: p.cautions !== false,
|
|
botTier: p.botTier === "apprentice" || p.botTier === "archmage" ? p.botTier : "adept",
|
|
instantReplay: p.instantReplay !== false,
|
|
liveFp: p.liveFp === true,
|
|
handLeft: p.handLeft === true,
|
|
};
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
export const prefs = $state<Prefs>(load());
|
|
|
|
export function savePrefs(): void {
|
|
try {
|
|
localStorage.setItem(KEY, JSON.stringify(prefs));
|
|
} catch {
|
|
// A full or blocked localStorage loses persistence, not the session.
|
|
}
|
|
}
|