# CardOpening

`CardOpening` is the reveal itself. It renders nothing until a card is opened, then takes the screen: the card leaves an element you name, flies to the middle face down, hums, turns over with the show its tier asked for, and shrinks into the element where it belongs.

> Live preview: see it play at https://bettertyped.github.io/react-cards-opening/docs/components/card-opening/
>
> Each card leaves its own place and lands back on it.

## Usage

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

function Shop({ cards }) {
  const { opened, open } = useOpenedCards("shop");
  const hunt = useCardOpening({ cards, opened, onOpen: open });
  const places = useRef(new Map<string, HTMLElement>());

  return (
    <>
      {cards.map((card) => (
        <button
          key={card.id}
          ref={(element) => element && places.current.set(card.id, element)}
          onClick={() => hunt.start([card.id])}
        >
          Open
        </button>
      ))}
      <CardOpening
        opening={hunt.opening}
        theme={themeOf("grape", "dark")}
        launch={(card) => places.current.get(card.id) ?? null}
        landing={(card) => places.current.get(card.id) ?? null}
      />
    </>
  );
}
```

## Props

| Prop | Type | What it does |
| --- | --- | --- |
| `opening` | `Opening` | The rules, from `useCardOpening`. |
| `theme?` | `CardTheme` | The colours. `moss` in the dark by default. |
| `rarities?` | `readonly RarityTier[]` | Your tiers. The six defaults otherwise. |
| `launch?` | `(card, show) => HTMLElement \| CardLaunch \| null` | Where the card comes out of. Asked once per card. With nothing, the card grows out of the middle of the screen. |
| `landing?` | `(card) => HTMLElement \| null` | Where the card goes. Asked for a few frames running, since the element may not be rendered yet. With nowhere, the card fades out. |
| `renderFace?` | `(card, info) => ReactNode` | The front. Rows with the class `rco-line` rise in as it turns. |
| `renderBack?` | `(card) => ReactNode` | The back, instead of the question glyph. |
| `noteOf?` | `(card) => string \| undefined` | A small last line on the ready-made face. |
| `readingOf?` | `(card, info) => readonly string[]` | The text on a face of your own, so the card stays up long enough to read. |
| `labels?` | `Partial<CardOpeningLabels>` | What a screen reader and a tooltip say. |
| `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. |
| `classNames?` | `CardOpeningClassNames` | A class of your own for each part. See [Styling by hand](https://bettertyped.github.io/react-cards-opening/docs/guides/styling/). |
| `width?` | `number` | The card's width in pixels. 300 by default, 78% of a narrow screen at most. |
| `ratio?` | `number` | Height over width. 4 / 3 by default. |
| `container?` | `Element \| null` | Where the overlay mounts. The document's body by default. |

## Something to play first

`launch` can return more than an element. Hand back a GSAP timeline as `lead` and the card waits for it: a lid lifting, a pack tearing, a scan closing in. `release` is called when the card has landed or the run was stopped, to put things back.

```tsx
<CardOpening
  opening={hunt.opening}
  launch={(card, { tone }) => {
    const box = boxRef.current;
    if (!box) return null;
    const lead = gsap.timeline();
    lead.to(box, { rotate: -6, duration: 0.08, yoyo: true, repeat: 5 });
    lead.to(box, { boxShadow: `0 0 40px 6px ${tone}`, duration: 0.3 });
    return { element: box, lead, release: () => gsap.set(box, { clearProps: "all" }) };
  }}
/>
```

The second argument says what is about to happen: `tone` is the tier's colour on the theme, `plan` is its [show plan](https://bettertyped.github.io/react-cards-opening/docs/core/show-plan/), and `reduced` is whether motion is reduced.

## Presses

The card is a button and the dimmed page behind it is the backdrop. What each press does is decided by [the rules](https://bettertyped.github.io/react-cards-opening/docs/core/use-card-opening/): face down, a press turns the card; face up, it sends it home; a press beside it stops the run.

## Looking at a card again

```tsx
<button onClick={(event) => hunt.peek(card.id, event.currentTarget)}>{card.name}</button>
```

A peek brings an opened card up out of what was pressed, face up, with no turn. It stays until a press on it, a press beside it or Escape sends it back, and focus returns to where it came from.

### Can two openings be on one page?

Yes. Each `useCardOpening` has its own rules and each `CardOpening` its own overlay. Nothing is kept on `window`.

### Why does the card not fly to my landing element?

`landing` returned null for thirty frames running, or motion is reduced. Check that the element is rendered by the time the card turns over: it is asked for after `onOpen` fires.

### Does it lock scrolling?

No. The backdrop takes presses while a card is up, and positions are measured when the card leaves and again when it lands, so a page that scrolled in between is still right.

## Related

- [Origin and landing](https://bettertyped.github.io/react-cards-opening/docs/guides/origin-and-landing/)
- [Custom card face](https://bettertyped.github.io/react-cards-opening/docs/guides/custom-card-face/)
- [useCardOpening](https://bettertyped.github.io/react-cards-opening/docs/core/use-card-opening/)
- [Effects](https://bettertyped.github.io/react-cards-opening/docs/effects/)

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