Files
zeekay a582b4c1b3 Put Blue on screen, and dissolve between the feelings
Blue has never appeared. Every clip in public/bg_video was a 132-byte Git LFS
pointer, so the door has been a plain dark rectangle since it went live — and
nothing anywhere said so: the file existed, the request answered 200, the
content-type was video/mp4, and the page rendered. Only its SIZE was wrong.
The real files were in LFS on the remote the whole time.

Two things then kept them off screen, and both are the same shape as the first.

A CDN caches by URL, and `/bg_video/emotion/Happiness.mp4` was the same URL
before and after the bytes behind it changed — so once the real clips reached
the origin, the edge went on serving the pointers it already had, for a day at
the origin's own max-age. Each clip's URL now carries a hash of its own
contents, computed before the build, so new bytes cannot inherit an old URL and
a clip that did not change is not re-fetched. Purging is the other answer and
it is worse: it needs a credential at deploy time, it is a step someone has to
remember, and it throws away a whole zone to fix one file.

And a single video element cannot change what it plays without going blank —
set src, call load(), and it holds nothing until the first frame of the next
clip arrives. That is a black flash in the middle of a sentence, at exactly the
moment the page is asking to be watched. There are two elements now, stacked:
the one you can see keeps playing while the other loads the next clip in
silence, and they trade opacity only once that one says it can play. A clip
that fails to load never gets its turn, so a dead connection leaves the current
one running — a beluga at rest beats a black rectangle.

Proven end to end against the live site: the clip plays at 1920x1080, the free
lane answers with real science, and asking why beluga calves are born grey
dissolves the resting loop into Interest.
2026-09-05 15:09:43 -07:00

39 lines
1.7 KiB
JavaScript

// Stamp every clip's URL with a hash of its own bytes, before the build.
//
// A CDN caches by URL. `/bg_video/emotion/Happiness.mp4` is the same URL after
// the file behind it changes, so a visitor keeps whatever they were served the
// first time — for a day, by the origin's own max-age. That is not theoretical
// here: these files shipped for weeks as 132-byte Git LFS pointers, and when
// the real 6.5MB clips finally reached the origin the edge went on serving the
// pointers. Blue was a black screen and every layer said it was fine.
//
// Purging is the other answer and it is worse: it needs a credential at deploy
// time, it is a step someone has to remember, and it throws away the whole
// zone's cache to fix one file. A URL that names its own contents needs
// nothing, cannot be forgotten, and re-downloads exactly the clips that
// actually changed.
//
// Written to a JSON file the app imports, so the hash is computed ONCE per
// build rather than by every page, and a page with JavaScript off still has
// the right URL in its markup.
import { createHash } from 'node:crypto'
import { readdirSync, readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
const PUBLIC = 'public'
const HERE = ['bg_video/emotion', 'bg_video/static']
const stamp = (file) => createHash('sha256').update(readFileSync(file)).digest('hex').slice(0, 8)
const clips = {}
for (const dir of HERE) {
for (const name of readdirSync(join(PUBLIC, dir)).sort()) {
if (!name.endsWith('.mp4')) continue
clips[`/${dir}/${name}`] = `/${dir}/${name}?v=${stamp(join(PUBLIC, dir, name))}`
}
}
writeFileSync('lib/clips.json', JSON.stringify(clips, null, 2) + '\n')
console.log(`clips.json — ${Object.keys(clips).length} clips stamped`)