Card hunt example
This is the panel from Hype Stack's Wrapped, rebuilt from the two packages and nothing else. Five cards hide in a grid of 210 cells. The scan finds them one at a time, each with its tier's reveal, and what was opened is remembered on this device.
5 cards are hiding in here.
Run the scan. Each card opens where the beam finds it.
- Still hidden
- Still hidden
- Still hidden
- Still hidden
- Still hidden
Scan, then press a badge or a lit cell to see a card again.
The code
import { useOpenedCards } from "@react-cards-opening/core";
import { CardHunt, themeOf } from "@react-cards-opening/ui";
import "@react-cards-opening/ui/styles.css";
import { cards } from "./cards";
/** Thirty weeks of activity, 0 (nothing) to 4 (a lot). Use your own data here. */
const levels = Array.from({ length: 30 * 7 }, (_unused, day) => [0, 1, 2, 0, 3, 2, 4, 1, 0, 2][(day * 7) % 10]);
const describeSpot = (cell: number) => `week ${Math.floor(cell / 7) + 1}, day ${(cell % 7) + 1}`;
export function Hunt() {
// Keyed by the deal, so a new deal starts unopened.
const { opened, open, ready } = useOpenedCards("example-deal-1");
if (!ready) return null;
return (
<CardHunt
cards={cards}
opened={opened}
onOpen={open}
levels={levels}
theme={themeOf("grape", "dark")}
describeSpot={describeSpot}
sessionKey="example-deal-1:spots"
/>
);
}What to change first
- `levels`: your real data. The shape of the grid is what makes the hunt feel like it belongs to the person.
- `describeSpot`: what a cell is called. A date, a match, a square of a board.
- The key: who the deal is for and when it was made.
- `labels`: the words. See Labels and translation.
- `landing`: send found cards somewhere other than their badge. See Origin and landing.
Take it apart
CardHunt is a layout, and you may want another. The same hunt from its parts is ScanGrid for the grid, CardOpening for the reveal and useCardOpening between them, with scan.seek() as the reveal's launch.
const scan = useRef<ScanGridHandle>(null);
const hunt = useCardOpening({ cards, opened, onOpen: open });
const [spots, setSpots] = useCardSpots("example-deal-1:spots");
<ScanGrid handle={scan} levels={levels} idle={!hunt.running} />
<button onClick={() => hunt.start()}>Scan</button>
<CardOpening
opening={hunt.opening}
launch={(card, show) => {
const cell = spots[card.id] ?? pickSpot(levels, Object.values(spots));
return scan.current?.seek(cell, show, { onLock: () => setSpots((now) => ({ ...now, [card.id]: cell })) }) ?? null;
}}
/>