# og:video pilot: mokcove.com/coloring/

Goal: when someone shares **https://mokcove.com/coloring/**, apps that support it show a short looping clip
instead of a still card. Everything here is a hand-off piece. Nothing has been uploaded, deployed, pushed or posted.
The local crew builds and ships it with Claude, the usual way.

## What's in this folder

| File | What it is |
|---|---|
| `og-video.mp4` | The clip. 6.0 s, 1200x630, 24 fps, H.264 Main 3.1, yuv420p, faststart (moov before mdat), **no audio track**, about 0.5 MB, seamless loop. |
| `og-video-3covers.mp4` | Alternate cut that leaves out Fishcamp (see caveat 1). Same specs, about 0.43 MB. |
| `og-image.jpg` | 1200x630 poster/fallback, the fully colored frame at 2.35 s. |
| `TAGS.html` | The exact `<head>` block to paste in. |
| `check/contact-sheet.png` | 4 frames from the clip (0.25 s line art, 1.3 s wipe, 2.4 s colored, 4.4 s cover flip). |
| `check/live-tags-2026-10-22.txt` | The og:/twitter: tags live on the page today (2026-10-22), for before/after comparison. |
| `src/make_og_video.py` | Rebuilds everything from the shipped PDFs: `python3 src/make_og_video.py` (add `--three` for the alternate cut). |
| `SHA256SUMS` | Checksums so you can confirm you uploaded the right files. |

**Real art only.** Nothing was generated. The line-art page is Little Mokie Christmas p4, "Jolly Mokie", rendered from
`products/mokie-christmas-coloring/mokie-christmas-coloring-book-little.pdf` with pdftoppm at 150 dpi. The colored page is the
existing `coloring-ship/listing-art/christmas/christmas-colored-sample.png`, which lines up pixel for pixel with p4. The covers are
page 1 of each Little Mokie PDF (Christmas, Halloween, Thanksgiving, Fishcamp). The ground is the brand navy, #0c1424 with a #22355c glow.

**The clip, beat by beat:** line art with the Christmas cover beside it (0–0.5 s). The page fills in with color in a soft left-to-right
wipe (0.5–2.1 s) and holds. Then the cover card-flips through Halloween, Thanksgiving and Fishcamp and lands back on Christmas
(2.6–5.1 s). The color fades back to line art (5.15–5.85 s). The last frame matches the first, so the loop has no visible seam.
The only text added is what's already in the art: the page's own "COLORED-IN SAMPLE" pill (all caps, no "!"). There are no prices, urgency lines or deal copy.

## 1. Upload (same domain, HTTPS)

Copy into the jkca-kids repo next to the current OG image:

```
og-video.mp4  -> ~/Documents/GitHub/jkca-kids/site/assets/coloring/og-video.mp4   => https://mokcove.com/assets/coloring/og-video.mp4
og-image.jpg  -> ~/Documents/GitHub/jkca-kids/site/assets/coloring/og-image.jpg   => https://mokcove.com/assets/coloring/og-image.jpg
```

Then replace the og:/twitter: block in `site/coloring/index.html` with `TAGS.html`. Edit the repo file directly: the live page has drifted
from `coloring-ship/make_page.py` (the meta description reads "$5 a book" live and "$5 each" in the script). If the crew regenerates with
make_page.py, port the block into that script too, or it will drop out. Ship the usual way (REPUSH.md: a clean worktree of main, then ship-worker).

Rules:
- **HTTPS, same domain** (mokcove.com). No CDN on another host, no redirects to the file, no auth, and nothing that needs cookies.
- **MIME type:** `og-video.mp4` must be served as `Content-Type: video/mp4`, and `og-image.jpg` as `image/jpeg`. The current host already
  serves `.mp4` as `video/mp4` (checked on `/assets/puff-loop-v2-night-780.mp4`).
- **Byte ranges (check this):** iPhone Safari and the iMessage previewer expect `Range` requests to come back as `206 Partial Content`.
  Today, `/assets/puff-loop-v2-night-780.mp4` returns a plain `200` with the whole file even when a `Range` header is sent. A 0.5 MB
  file often plays anyway, but if the clip doesn't play on iPhone, this is the first thing to fix (serve it from Workers static assets / R2
  with range support, or handle Range in the worker).
- Keep `og-holiday-1200x630.jpg` online. Emails and pins link to it.
- CSP: the page's `Content-Security-Policy` does not affect crawlers fetching og:video, so no CSP change is needed.

Check after deploy, before anyone shares the link:

```
curl -sI https://mokcove.com/assets/coloring/og-video.mp4 | grep -i -E 'HTTP|content-type|content-length'
curl -s -H 'Range: bytes=0-99' -D - -o /dev/null https://mokcove.com/assets/coloring/og-video.mp4 | grep -i -E 'HTTP|content-range'
curl -sI https://mokcove.com/assets/coloring/og-image.jpg | grep -i -E 'HTTP|content-type'
curl -sL -A 'facebookexternalhit/1.1' https://mokcove.com/coloring/ | grep -o '<meta[^>]*og:video[^>]*>'
```
Expected: 200 + video/mp4, a 206 + content-range (or note that it isn't there), 200 + image/jpeg, and the five og:video tags.

## 2. Cache-bust (previews are cached for days)

- **Facebook / Messenger / Instagram DMs:** https://developers.facebook.com/tools/debug/ → paste `https://mokcove.com/coloring/` → **Scrape Again**
  (twice if the image is still the old one). Check that og:video and og:image show the new URLs.
- **LinkedIn:** https://www.linkedin.com/post-inspector/ → paste the URL → Inspect. This re-scrapes it and refreshes LinkedIn's cache (about 7 days otherwise).
- **iMessage:** there's no debugger. Text the link to yourself, from a phone that hasn't previewed it before if you can. iMessage caches by URL on
  each device, so if you still see the old card, send `https://mokcove.com/coloring/?v=og1` to yourself (a new URL means a fresh fetch). If you
  change the files later, bump `?v=1` to `?v=2` in TAGS.html.
- **Discord / Slack:** both cache unfurls. Test with `https://mokcove.com/coloring/?v=og1` in a private DM or a test channel. In Slack, "Remove
  attachment" and repost won't re-fetch; a new query string will.
- **X:** no public validator anymore. Post-composer preview, or just trust the image (X won't play og:video anyway).

## 3. Test-first checklist (all of these before anyone shares the link)

- [ ] `curl` checks above pass (200, video/mp4, image/jpeg, tags present in raw HTML without JS).
- [ ] Open `https://mokcove.com/assets/coloring/og-video.mp4` directly in **iPhone Safari**. It plays, it's sharp, and the colors look right.
- [ ] **iPhone iMessage:** text `https://mokcove.com/coloring/?v=og1` to yourself. The preview autoplays muted and loops with no jump at the seam,
      fills the card with no letterbox bars, and shows the title "Mokie Holiday Coloring Books". Tap it: Safari opens the page.
- [ ] **macOS Messages:** same check.
- [ ] **iPhone Safari:** the sales page itself still loads normally (nothing in `<body>` changed).
- [ ] **Desktop Discord:** paste into a DM to yourself. Note whether it shows a player or the image (both are OK). Check the title/description still show.
- [ ] **Desktop Slack:** paste into a DM to yourself. You should get the image card with the title.
- [ ] **Facebook Sharing Debugger:** no warnings about og:video/og:image; the preview shows the new poster.
- [ ] **LinkedIn Post Inspector:** shows the new poster image and title.
- [ ] Watch the loop 3 times on a phone: calm, no flashing, nothing reads as a deal or a countdown.
- [ ] Decide on Fishcamp (caveat 1) before the first share.
- [ ] Rollback is simple: delete the five `og:video*` lines (and point og:image back to og-holiday-1200x630.jpg if wanted), redeploy, and re-scrape.

## 4. Honest notes: who plays it and who shows the image

| App | What to expect |
|---|---|
| **iMessage / Messages (iOS, iPadOS, macOS)** | **Plays.** Apple's TN3156 and the "Ensuring Beautiful Rich Links" tech talk: a direct, downloadable MP4 in og:video autoplays inline, muted and looping. Icon + image + video must total under 10 MB (we're at about 0.6 MB). This is the main payoff. |
| **Discord** | **Sometimes.** Discord can turn a direct-MP4 og:video into a click-to-play player. It doesn't autoplay, and it may drop the image or description. Results vary by client version; test it. |
| **Slack** | **Mostly the image.** Slack only plays inline video for providers it supports (oEmbed / twitter:player from approved hosts). Expect the still card. |
| **Facebook** | **Image.** Facebook stopped playing third-party og:video in link previews years ago. It shows the og:image card. |
| **LinkedIn** | **Image.** LinkedIn ignores og:video in link previews. |
| **X / Twitter** | **Image** (summary_large_image). Video needs a `twitter:player` card with an HTML player and X approval. Not worth it here. |
| **WhatsApp, Telegram, Signal, Pinterest, Google** | Image (Telegram occasionally shows video; untested). |

So in practice this is an **iMessage upgrade** with a nicer poster everywhere else. That's why og:image is the colored frame: it's what most people will see.

## Caveats

1. **Fishcamp isn't on the live sales page.** mokcove.com/coloring/ sells Halloween, Thanksgiving and Christmas; Fishcamp has a listing in
   `products/mokie-fishcamp-coloring/` but no section on the page, and `/coloring/fishcamp/` is a 404. The brief asked for 4 covers, so
   `og-video.mp4` includes it, but a preview that shows a book the link doesn't sell can confuse people. Use `og-video-3covers.mp4`
   (rename it to og-video.mp4 on upload) until Fishcamp is on the page, or add Fishcamp to the page first.
2. **Range requests** (see Upload). The current host returns 200, not 206, for ranged mp4 requests. Test on a real iPhone before sharing.
3. The poster is a new file name (`og-image.jpg?v=1`), so every scraper re-fetches it. The old image stays online.
4. The live og:description includes prices ("$5 a book, $8 for both."). That's existing copy and it's unchanged. The clip itself carries no price or deal text.
5. No `og:type` change: it stays `website`, which is right for a sales page. `video.other` might push Discord toward a player, but it
   changes how Facebook files the page. Only try it as a separate test.
