scramble-keypad
v0.1.0
Published
A spy-game React keypad whose digits scatter, drift, and reshuffle so position carries no information. Diegetic security gate, not authentication.
Maintainers
Readme
scramble-keypad
A React keypad whose digits sit in shifting, non-standard positions — connected into a living constellation, endlessly drifting, reshuffling on demand. A diegetic security gate for a spy game: entering a code should feel like defeating a system, not filling in a form.
Because position carries no information, an observer watching the player's hand learns nothing.
Security note: this is a game prop, not authentication. The code lives in client memory and is visible in DevTools within seconds. See
scramble-keypad-spec.md§9.
Install
npm install scramble-keypadreact and react-dom (18 or 19) are peer dependencies; framer-motion and clsx come with the package.
Usage
import { ScrambleKeypad, themes } from 'scramble-keypad';
import 'scramble-keypad/styles.css';
<ScrambleKeypad
code="4815"
label="SUBLEVEL 4 — AUTHORISATION REQUIRED"
scrambleMode="onInterval"
scrambleIntervalMs={3000}
layout="scatter"
maxAttempts={3}
theme={themes.oscorp}
onSuccess={() => openDoor('sublevel-4')}
onFailure={(left) => playSound(left === 1 ? 'alarm-warn' : 'reject')}
onLockout={() => triggerAlarm('sublevel-4')}
/>styles.css is a compiled, self-contained stylesheet (~18 kB, no preflight) — consumers do not need Tailwind. Projects that already run Tailwind can import it all the same; its utilities are identical definitions in a scoped layer.
For the full HUD look, load the fonts (or substitute your own via theme.fontFamily):
<link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@300;400;500&family=Share+Tech+Mono&display=swap" rel="stylesheet" />Props
| Prop | Type | Default | |
|---|---|---|---|
| code | string | — | Required. Digits only, e.g. "4815". |
| codeLength | number | code.length | |
| onSuccess | () => void | — | Required. Fires once after the success animation. |
| onFailure | (attemptsRemaining: number) => void | — | |
| onLockout | () => void | — | |
| maxAttempts | number | 3 | Infinity disables lockout. |
| scrambleMode | 'onMount' \| 'onKeyPress' \| 'onInterval' \| 'onEntry' | 'onKeyPress' | |
| scrambleIntervalMs | number | 2000 | Only for 'onInterval'. |
| layout | 'grid' \| 'scatter' \| 'ring' | 'scatter' | |
| drift | boolean | true | Continuous idle wander between scrambles. |
| constellation | boolean | true | Lines linking each digit to its nearest neighbours. |
| label | string | — | Header text and the group's aria-label. |
| showEntry / maskEntry | boolean | true / true | Pip row / pips vs. digits. |
| theme | Partial<KeypadTheme> | oscorp tokens | See below. |
| className | string | — | |
| disabled / autoFocus | boolean | false / true | |
Theming
Two presets ship: themes.oscorp (cyan on near-black, clinical corporate-lab) and themes.redroom (amber on charcoal, mono, no bloom). Pass any Partial<KeypadTheme> — values become CSS custom properties at runtime:
<ScrambleKeypad theme={{ ...themes.redroom, danger: '#FF2D55' }} ... />Accessibility
Digit buttons keep a stable DOM order 0–9 regardless of visual position, so tab order and screen-reader order are predictable. Physical keyboard digits map directly (the scramble defends against shoulder-surfing, not the keyboard). Entry progress announces politely; failure and lockout assertively. Under prefers-reduced-motion all drift, tweening, and the scanline are dropped without changing difficulty.
Development
npm run dev # demo app (code is 4815)
npm test # 15 tests: the spec's 12 cases + constellation coverage
npm run build:lib # dist/: ESM + CJS + .d.ts + styles.css
npm pack # tarball for local install testingBuilt from scramble-keypad-spec.md.
