# Rarity and drops

A drop is a scene that starts out hidden: an achievement the year earned, or an easter egg handed out by luck. Each has a rarity, common, rare, epic or legendary, and the rarity decides its colour everywhere it shows up. This page covers the data a drop is made of and how one turns into a scene.

> Live preview: try it at https://bettertyped.github.io/react-wrapped/docs/scanner/drops/
>
> The four rarities in the current palette, then the sample year's drops.

## The four rarities

| Rarity | Label | Colour variable |
| --- | --- | --- |
| `common` | Common | `--wr-common` |
| `rare` | Rare | `--wr-rare` |
| `epic` | Epic | `--wr-epic` |
| `legendary` | Legendary | `--wr-legendary` |

`RARITY_LABELS` maps each rarity to its word. The colours are not fixed: every palette brings four that sit well on it, in `palette.rarity`, and the custom palette cuts them from your accent. On a stage, `data-rarity` on an element sets `--wr-rarity` for everything inside it, which is how a chart lights its point in the colour of the scene it is in.

`rarities.tsx`

```tsx
import type { CSSProperties } from "react";

import { PIXEL_ART, RARITY_LABELS, type Rarity, resolvePalette, type WrappedConfig } from "@react-wrapped/core";
import { type HiddenDrop, PixelGlyph } from "@react-wrapped/scan";

const GRID: CSSProperties = { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 10 };
const TILE: CSSProperties = { display: "grid", gap: 12, padding: 16, borderRadius: 18 };
const NAME: CSSProperties = { display: "block", fontWeight: 650, fontSize: 15 };
const SMALL: CSSProperties = { display: "block", fontSize: 13, fontWeight: 600 };

// Easiest to hardest. The colours are the palette's own, so they change with the theme.
const RARITIES: readonly Rarity[] = ["common", "rare", "epic", "legendary"];

export function Rarities({ drops, config }: { drops: readonly HiddenDrop[]; config: WrappedConfig }) {
  const palette = resolvePalette(config);
  const tile = { ...TILE, background: palette.card, color: palette.fg };
  return (
    <div style={GRID}>
      {RARITIES.map((rarity) => (
        <div key={rarity} style={tile}>
          <PixelGlyph art={PIXEL_ART.trophy} colour={palette.rarity[rarity]} cell={8} />
          <span>
            <b style={NAME}>{RARITY_LABELS[rarity]}</b>
            <small style={{ ...SMALL, color: palette.rarity[rarity] }}>{palette.rarity[rarity]}</small>
          </span>
        </div>
      ))}
      {drops.map((drop) => (
        <div key={drop.id} style={tile}>
          <PixelGlyph art={drop.art} colour={palette.rarity[drop.rarity]} cell={8} />
          <span>
            <b style={NAME}>{drop.name}</b>
            <small style={{ ...SMALL, color: palette.rarity[drop.rarity] }}>
              {`${drop.kind}, ${RARITY_LABELS[drop.rarity]}`}
            </small>
          </span>
        </div>
      ))}
    </div>
  );
}
```

## A hidden drop

`HiddenDrop` is what the scan knows about a drop: enough to draw its card. The scene itself is yours and stays out of the video until the drop is found.

| Key | Type | What it is |
| --- | --- | --- |
| `id` | `string` | Stable for this drop. `found` and `onOpen` speak in ids. |
| `name` | `string` | What the card and the badge call it. |
| `rarity` | `"common" \| "rare" \| "epic" \| "legendary"` | How rare it is. Decides the colour and how big a show the card gets. |
| `kind` | `string` | The small word next to the rarity: "Achievement", "Easter egg". |
| `line` | `string` | One line on the back of the card, in this year's own numbers. |
| `art` | `PixelArt` | Nine rows of nine characters: `x` lit, `o` half lit, `.` off. |

## From a drop to a scene

Build your scenes from the list of opened ids. A drop that has been opened gets a scene, placed with `drop` set to its rarity (the timeline colours its segment) and with `locked` (nobody rewrites what was assigned):

```tsx
import { PIXEL_ART, RARITY_LABELS, type SceneInstance } from "@react-wrapped/core";
import type { HiddenDrop } from "@react-wrapped/scan";
import { factScene } from "@react-wrapped/scenes";

const DROPS: readonly HiddenDrop[] = [
  {
    id: "long-haul",
    name: "Long Haul",
    rarity: "epic",
    kind: "Achievement",
    line: "48 days in a row without missing one.",
    art: PIXEL_ART.flame,
  },
];

/** The scenes of every drop opened so far. Add them to the story where they belong. */
export function dropScenes(opened: readonly string[]): SceneInstance[] {
  return DROPS.filter((drop) => opened.includes(drop.id)).map((drop) =>
    factScene(
      {
        stamp: RARITY_LABELS[drop.rarity],
        rarity: drop.rarity,
        title: [drop.name],
        description: drop.line,
        art: drop.art,
        variant: 0,
      },
      { key: `drop-${drop.id}`, label: drop.name, drop: drop.rarity, locked: true },
    ),
  );
}
```

Give a drop's scene a place in the story, not the end of it: put it right after the scene it continues. When the viewer has reordered the cut by hand, `arrangeScenes` still drops a new scene in after the one you put before it.

## Which drops a year gets

`cutDrops` picks the drops a video really gets out of everything a year qualified for. It follows two rules. At most one easter egg: a video padded with eggs is a run of jokes. And every other drop is an achievement that can draw a picture of its own numbers, with no two drawing the same kind of picture.

```ts
import { cutDrops } from "@react-wrapped/scan";

interface Trait {
  id: string;
  kind: "achievement" | "easterEgg";
  /** The chart this achievement would draw for this year, or null when it has none. */
  chart: string | null;
}

export function dropsFor(rolled: readonly Trait[], earned: readonly Trait[]): Trait[] {
  // `drops` is what was rolled: its length is how many slots there are, and its egg is the egg.
  // `earned` is every achievement, hardest first. The hardest ones that can be drawn get the slots.
  return cutDrops({ drops: rolled, earned }, (trait) => trait.chart);
}
```

> Roll once and store the result. If a reload could roll again, people would reload until they like what they got. The sample in the repository works its roll out from the handle, so the same person always gets the same drops.

## Related

- [YearScan](https://bettertyped.github.io/react-wrapped/docs/scanner/year-scan/)
- [Fact scene](https://bettertyped.github.io/react-wrapped/docs/scenes/fact/)
- [Trophies scene](https://bettertyped.github.io/react-wrapped/docs/scenes/trophies/)
- [Theming](https://bettertyped.github.io/react-wrapped/docs/theming/)
- [Scanner](https://bettertyped.github.io/react-wrapped/docs/scanner/)

Source: https://bettertyped.github.io/react-wrapped/docs/scanner/drops/
