Poster frames for the walkthrough videos

The player was showing a blank white rectangle, which reads as broken. That
is frame zero of each recording — the instant before the page painted. Only
frame zero is white (by 0.5s the real UI is up), but frame zero is exactly
what a browser picks when no poster is set.

Each video now has a poster pulled from ~30% in, where the tool is in a
meaningful state rather than mid-intro. Posters live beside the videos in the
volume and the player derives the URL from the video's own, so adding a new
walkthrough needs no page change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-28 14:43:17 -05:00
parent 1dbee99c49
commit d5385b0512
2 changed files with 11 additions and 5 deletions
+4
View File
@@ -42,6 +42,10 @@
var vid = document.createElement('video'); var vid = document.createElement('video');
vid.controls = true; vid.controls = true;
// Poster lives beside the video (same slug, .jpg). Without it the browser
// shows frame zero, and frame zero is the instant before the page painted
// — a blank white rectangle that reads as a broken player.
vid.poster = SRC.replace(/\.mp4$/, '.jpg');
// Nothing is fetched until the panel is actually opened; open() then bumps // Nothing is fetched until the panel is actually opened; open() then bumps
// this to 'metadata' so an expanded panel shows a real first frame rather // this to 'metadata' so an expanded panel shows a real first frame rather
// than a black rectangle. // than a black rectangle.
+7 -5
View File
@@ -1068,13 +1068,15 @@ async function handleApi(req,res,pathname){
// //
// Range support is not optional here: without it the browser cannot seek, so // Range support is not optional here: without it the browser cannot seek, so
// the scrub bar looks present but does nothing. // the scrub bar looks present but does nothing.
if((req.method==='GET'||req.method==='HEAD')&&/^\/tv\/[a-z0-9-]{1,60}\.mp4$/.test(pathname)){ if((req.method==='GET'||req.method==='HEAD')&&/^\/tv\/[a-z0-9-]{1,60}\.(mp4|jpg)$/.test(pathname)){
const slug=pathname.slice(4).replace(/\.mp4$/,''); const isImg=/\.jpg$/.test(pathname);
const vfile=path.join(DATA_DIR,'tool-videos',slug+'.mp4'); const name=pathname.slice(4);
const vfile=path.join(DATA_DIR,'tool-videos',name);
let st=null; let st=null;
try{ st=fs.statSync(vfile); }catch(err){ return json(res,404,{error:'No walkthrough for that tool yet.'}); } try{ st=fs.statSync(vfile); }catch(err){ return json(res,404,{error:'No walkthrough for that tool yet.'}); }
const range=req.headers.range; const range=isImg?null:req.headers.range;
const base={'Content-Type':'video/mp4','Accept-Ranges':'bytes','Cache-Control':'public, max-age=3600'}; const base={'Content-Type':isImg?'image/jpeg':'video/mp4','Accept-Ranges':isImg?'none':'bytes',
'Cache-Control':'public, max-age=3600'};
if(range){ if(range){
const m=/bytes=(\d*)-(\d*)/.exec(range)||[]; const m=/bytes=(\d*)-(\d*)/.exec(range)||[];
const start=m[1]?parseInt(m[1],10):0; const start=m[1]?parseInt(m[1],10):0;