Clips unfurl: share cards, faststart video, a share button

Each clip page already carried its own title, blurb, poster and
og:video — but the assets behind the tags let the unfurl down. The mp4s
were tail-indexed, so previewers in iMessage, Discord, Slack and
Facebook gave up before finding a player; the tags carried no video
dimensions, without which those same sites refuse to embed one; and the
poster was a 736x578 screenshot of the workshop chrome, cropped to
nothing by any share sheet.

deploy/clips-prep.mjs fixes the set before clips-publish.sh ships it:
a lossless faststart remux of every take, a 1200x630 card per clip cut
from the poster's first-person viewport with the title set over it (an
HTML card screenshot through the harness's chromium — ffmpeg here has
no drawtext), and the fpv frame size stamped into clips.json. The page
emits the card with its dimensions, og:video:width/height, and a Share
button that opens the phone's share sheet or copies the link.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
Eric Wagoner
2026-09-02 11:26:27 -04:00
co-authored by Claude Fable 5.1
parent bea8a2e1e1
commit 8488e9a8d1
4 changed files with 165 additions and 8 deletions
+6 -2
View File
@@ -227,6 +227,10 @@ export interface ClipMeta {
title: string;
blurb: string;
seconds: number;
/** Frame size of the first-person take, stamped by deploy/clips-prep.mjs;
* unfurlers won't embed a player without it. Absent on an unprepped set. */
width?: number;
height?: number;
}
const clipsDir = () => join(DATA_DIR, "..", "clips");
@@ -244,9 +248,9 @@ export function readClips(): ClipMeta[] {
/** Resolve a clip asset request to its path — or null for any name that
* is not exactly a published clip file shape. The gate IS the security:
* nothing outside <slug>(-fpv|-board).mp4 / <slug>.jpg can be named. */
* nothing outside <slug>(-fpv|-board).mp4 / <slug>(-card)?.jpg can be named. */
export function clipAssetPath(file: string): string | null {
if (!/^[a-z0-9-]{1,60}(-fpv|-board)\.mp4$/.test(file) && !/^[a-z0-9-]{1,60}\.jpg$/.test(file)) {
if (!/^[a-z0-9-]{1,60}(-fpv|-board)\.mp4$/.test(file) && !/^[a-z0-9-]{1,60}(-card)?\.jpg$/.test(file)) {
return null;
}
const path = join(clipsDir(), file);