# Mopi Reaction Master Set

The single source of truth for every Mopi reaction export — GIPHY, Kakao
emoticons, Discord/Telegram/WhatsApp/LINE packs, and anything after those.
Regenerate with:

```sh
node scripts/run-python.mjs scripts/build_reaction_masters.py
```

Do not hand-edit files in here, and do not draw a new Mopi per platform. Every
platform pack crops or converts from this set so the character stays identical
everywhere it lands (`docs/FREE_CONTENT.md`).

## Formats, and which platform each one feeds

| File | Format | Goes to |
| --- | --- | --- |
| `mopi-<morph>-<reaction>.webp` | transparent animated WebP, 256×256 | Browser previews and high-quality archive master |
| `mopi-<morph>-<reaction>-sticker.gif` | transparent animated GIF | GIPHY **stickers** — the sticker search inside Instagram, TikTok, Snap |
| `mopi-<morph>-<reaction>.gif` | opaque animated GIF on the cream plate | GIPHY **GIFs** — Facebook, X, WhatsApp |

The ordinary GIF variant is deliberately not transparent. GIF's 1-bit alpha
cannot hold an antialiased silhouette edge, so a transparent GIF fringes on
every surface that composites it; flattening onto the brand cream (`#f7f0e0`)
avoids that. The separate `-sticker.gif` exists solely because GIPHY's Sticker
upload requires GIF transparency.

Frame timings come from each sprite manifest and the desktop app's
`statePlaybackSpeed` (`src/config/petConfig.ts`), so an exported reaction plays
at the speed the character actually moves.

## First GIPHY upload

[`giphy-first-upload.json`](giphy-first-upload.json) is a five-piece public
sticker upload batch with per-asset, relevant tags and the source URL already
chosen. Upload the named `-sticker.gif` files individually so their different
reaction tags stay attached; do not bulk-apply one tag set.

`check_reaction_masters.py` verifies GIPHY's mechanical upload constraints:
transparent animated GIF, at least two frames, forever loop, 20% first-frame
transparency, 15 seconds, and 100 MB. It does **not** claim approval. GIPHY
also reviews motion, tags, and visual fit; its current sticker guidance includes
a visible-pixel contrast rule that a warm Cream Mopi may need to clear manually.
See [GIPHY Sticker Best Practices](https://support.giphy.com/hc/en-us/articles/360019683472-Sticker-Best-Practices)
and [Requirements for Sticker Approval](https://support.giphy.com/hc/en-us/articles/360029960931-Requirements-for-Sticker-Approval).

### Brand-channel handoff

The five files above satisfy the minimum public-originals count. Before applying,
do these owner-account steps in this order:

1. Connect a real Mopi custom domain to the site. Replace the manifest's
   `source_url` with that domain; `mopi-4yy.pages.dev` is not acceptable for a
   Brand application.
2. Create the official channel under a plain Mopi username, use a matching
   domain email, link the public Mopi social account, and upload the ready
   [`giphy-avatar-250.png`](giphy-avatar-250.png) avatar.
3. Upload the five named files as public stickers. Keep each item's existing,
   reaction-specific tags; use 5–10 relevant tags, no `#`, no irrelevant search
   terms, and no promotional text or download call-to-action inside a GIF.
4. Apply once as a **Brand** (not Creator) channel, then wait for review. Do
   not promise search availability before approval.

These are GIPHY's current application requirements and tagging guidance:
[Tips for Submitting an Application](https://support.giphy.com/hc/en-us/articles/360020433711-Tips-For-Submitting-An-Application),
[Apply for a Brand Channel](https://support.giphy.com/hc/en-us/articles/360019977992-Apply-For-A-Brand-Channel),
and [GIPHY Community Guidelines](https://support.giphy.com/hc/en-us/articles/360020286811-GIPHY-Community-Guidelines).

## Coverage

11 of the 20 reactions named in `docs/FREE_CONTENT.md` are built. The other 9
have no source animation and need new art before they can ship:

```text
confused    curled-up   dangling    falling    nodding
shy         tiny-wave   tired       touched
```

`falling` is desktop drop physics and `touched`/`dangling` are the iOS mesh
deforming live under a finger — neither is a frame sequence, so both need to be
authored rather than extracted. The rest have no sprite row at all.

Nothing was mapped onto a near-enough state to pad the count. These get
uploaded to a search index where the label is how people find them, so a
mislabeled reaction makes the whole set less findable, not more complete.

### Cream has complete shipping-state coverage

| Morph | Reactions covered |
| --- | --- |
| cream, patch, dusty, blush, speckle, tip, fluffy, odd | all 11 |

Cream's runtime is intentionally assembled from its legacy idle/sleep/wake/walk
frames plus the approved reaction and concept-action atlases. The export script
uses those same three sources, so the public master cannot drift away from what
the default Mopi actually does in the app.

See `reaction-masters.json` for the full generated inventory.

For a reaction without an animated source, `reaction-masters.json` distinguishes
between **no source state — needs new art** and **reviewed static candidate
exists — needs animated source**. A static candidate is a design reference only;
it is never mislabeled or exported as a one-frame reaction master.
