feat(offline): hotseat creation roster + host-participate flow
NewGame offline 'with friends' now builds a local pass-and-play game: - lib/roster.ts: keep-last-valid name + roster->seats (unit-tested). - NewGame.svelte: master host-PIN gate (rows disabled until set), 'are you playing too?' prompt seating the host at row 0, 2-4 player rows with inline name validation + optional per-seat PIN, add/remove (remove behind the master PIN), variant + multiple-words, Start. - PinPad: verification via a verify(pin) callback (UI-held lock at creation, source-held in-game) instead of a lock prop. - i18n: hotseat.* (en + ru); the offline mode selector is now shown offline.
This commit is contained in:
@@ -2,15 +2,17 @@
|
||||
// Apple-lock-screen-style 4-digit PIN pad for offline pass-and-play (hotseat) games.
|
||||
// Modes:
|
||||
// set — enter a PIN, then repeat it to confirm; emits { kind: 'set', lock }.
|
||||
// verify — enter a PIN; auto-checks against `lock` on the 4th digit; emits
|
||||
// { kind: 'verified' } or shakes + clears on a wrong PIN.
|
||||
// change — verify the current PIN (against `lock`), then offer "set a new PIN"
|
||||
// (the set flow) or, when allowRemove, "remove PIN" ({ kind: 'removed' }).
|
||||
// The pad never shows an OK button: entering the 4th digit is the action. PIN storage
|
||||
// is a social lock, not cryptography — see lib/pin.ts.
|
||||
// verify — enter a PIN; runs `verify(pin)` on the 4th digit; emits { kind: 'verified' }
|
||||
// or shakes + clears on a wrong PIN.
|
||||
// change — run `verify` on the current PIN, then offer "set a new PIN" (the set flow)
|
||||
// or, when allowRemove, "remove PIN" ({ kind: 'removed' }).
|
||||
// Verification is delegated to the caller's `verify` callback: at creation time it checks a
|
||||
// UI-held lock (lib/pin verifyPin); in-game it delegates to the local source (which holds the
|
||||
// stored lock). The pad never shows an OK button: entering the 4th digit is the action. PIN
|
||||
// storage is a social lock, not cryptography — see lib/pin.ts.
|
||||
import Modal from './Modal.svelte';
|
||||
import { t } from '../lib/i18n/index.svelte';
|
||||
import { newLock, verifyPin, type PinLock } from '../lib/pin';
|
||||
import { newLock, type PinLock } from '../lib/pin';
|
||||
|
||||
/** PinResult is the outcome handed to onresult when the pad completes. */
|
||||
export type PinResult =
|
||||
@@ -21,14 +23,15 @@
|
||||
let {
|
||||
mode,
|
||||
title = '',
|
||||
lock,
|
||||
verify,
|
||||
allowRemove = false,
|
||||
onclose,
|
||||
onresult,
|
||||
}: {
|
||||
mode: 'set' | 'verify' | 'change';
|
||||
title?: string;
|
||||
lock?: PinLock;
|
||||
/** Verifies an entered PIN (required for 'verify' and 'change'). */
|
||||
verify?: (pin: string) => Promise<boolean>;
|
||||
allowRemove?: boolean;
|
||||
onclose?: () => void;
|
||||
onresult: (r: PinResult) => void;
|
||||
@@ -70,13 +73,13 @@
|
||||
busy = true;
|
||||
try {
|
||||
if (phase === 'old') {
|
||||
if (lock && (await verifyPin(buffer, lock))) {
|
||||
if (verify && (await verify(buffer))) {
|
||||
buffer = '';
|
||||
error = false;
|
||||
phase = 'menu';
|
||||
} else fail();
|
||||
} else if (phase === 'enter' && mode === 'verify') {
|
||||
if (lock && (await verifyPin(buffer, lock))) onresult({ kind: 'verified' });
|
||||
if (verify && (await verify(buffer))) onresult({ kind: 'verified' });
|
||||
else fail();
|
||||
} else if (phase === 'enter') {
|
||||
first = buffer;
|
||||
|
||||
Reference in New Issue
Block a user