# CardHunt

`CardHunt` is the whole experience in one component. Cards hide in a grid. A button starts a scan that finds them one by one, each with the reveal its tier calls for. Afterwards the panel stays: the finds are listed, each can be looked at again, and the scan can be run again from a blank grid.

> Live preview: see it play at https://bettertyped.github.io/react-cards-opening/docs/components/card-hunt/
>
> Five cards are in there. Press the button.

## Usage

```tsx
import { useOpenedCards } from "@react-cards-opening/core";
import { CardHunt, themeOf } from "@react-cards-opening/ui";

function Hunt({ cards, activity }) {
  const { opened, open } = useOpenedCards("deal:ada:2026");
  return (
    <CardHunt
      cards={cards}
      opened={opened}
      onOpen={open}
      levels={activity} // 0 to 4, one per cell
      theme={themeOf("grape", "dark")}
      describeSpot={(cell) => `day ${cell + 1}`}
      sessionKey="deal:ada:2026:spots"
    />
  );
}
```

## The grid

`levels` is the grid: one number per cell, 0 for unlit up to 4. Cells fill top to bottom, then left to right, seven to a column unless `rows` says otherwise. Cards hide on lit cells. A year of activity is the obvious grid, but it can be any data that makes a shape: a season of matches, a board, a month of orders.

## Props

| Prop | Type | What it does |
| --- | --- | --- |
| `cards` | `readonly C[]` | Every card in the deal, found or not. Each needs a `name`. |
| `opened` | `readonly string[]` | Ids already opened. |
| `onOpen` | `(id: string) => void` | A card was turned face up for the first time. |
| `levels` | `readonly number[]` | The grid: one number per cell, 0 to 4. |
| `lead?` | `number` | Empty slots before the first cell, to line a calendar up with its weekday. |
| `rows?` | `number` | Cells in a column. 7 by default. |
| `theme?` | `CardTheme` | The colours. |
| `rarities?` | `readonly RarityTier[]` | Your tiers. |
| `describeSpot?` | `(cell: number) => string` | How a cell reads on a card and a badge: "Mar 14". |
| `actions?` | `ReactNode` | Extra buttons in the header, next to the scan button. |
| `sessionKey?` | `string \| null` | Where this visit remembers each card's cell, so replays reopen the same tiles. |
| `hero?` | `boolean` | The hunt is the page: bigger type, and it fills the box it is given. |
| `onRunChange?` | `(running: boolean) => void` | A scan started or came to rest. |
| `labels?` | `Partial<CardHuntLabels>` | Every word in the panel. |
| `effects?` | `EffectPlan \| false` | What each tier plays on top of the card's own reveal. The [signature shows](https://bettertyped.github.io/react-cards-opening/docs/effects/plans/) when left out, `false` for none. |
| `landing?` | `(card) => HTMLElement \| null` | Where a found card lands instead of its badge. Answer null and it fades out. |
| `renderFace?` | `(card, info) => ReactNode` | The front of the card. |
| `renderBack?` | `(card) => ReactNode` | The back of the card. |
| `readingOf?` | `(card, info) => readonly string[]` | The text on a face of your own. |
| `className?` | `string` | Added to the panel. |
| `classNames?` | `CardHuntClassNames` | A class of your own for each part: the panel's, the grid's and the reveal's. See [Styling by hand](https://bettertyped.github.io/react-cards-opening/docs/guides/styling/). |
| `style?` | `CSSProperties` | Added to the panel. |

## As the page

With `hero`, the hunt is what the visitor came for. The headline is larger, and the panel fills the height of its container with the grid in the middle. Give the container a height.

> Live preview: see it play at https://bettertyped.github.io/react-cards-opening/docs/components/card-hunt/

## What the buttons do

| Button | When | What it does |
| --- | --- | --- |
| Scan | Cards are still hidden | Finds them one after another. |
| Find all | During a scan with more to come | Skips the show and opens the rest at once. |
| Scan again | Everything is found | Replays from a blank grid. Each card comes out of the same tile. |
| A badge or a lit cell | After a find | Brings that card up again. |

## Your own words

Every line in the panel comes from `labels`. The headline and the subline are functions of where the hunt stands, so they can count. [Labels and translation](https://bettertyped.github.io/react-cards-opening/docs/guides/labels-and-translation/) lists them all.

```tsx
<CardHunt
  labels={{
    headline: ({ pending, running }) => (running ? "Looking..." : `${pending} left to find`),
    scan: () => "Search the board",
    found: (where) => `Was on ${where}`,
  }}
  {...rest}
/>
```

## Related

- [ScanGrid](https://bettertyped.github.io/react-cards-opening/docs/components/scan-grid/)
- [CardOpening](https://bettertyped.github.io/react-cards-opening/docs/components/card-opening/)
- [Labels and translation](https://bettertyped.github.io/react-cards-opening/docs/guides/labels-and-translation/)
- [Hiding places](https://bettertyped.github.io/react-cards-opening/docs/core/spots/)

Source: https://bettertyped.github.io/react-cards-opening/docs/components/card-hunt/
