Skip to content
react-cards-opening

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

hunt.tsx
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.

tsx
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;
  }}
/>

Last updated . Read this page as Markdown.