This commit is contained in:
Felix Klement committed 2026-09-18 19:25:48 +02:00
1 parent 0c2b4e7df2
commit e652b54319
11 files changed
+202 -12

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Turns a generated clip into a seamless looping hero background:
# the clip plays forward, then backward, so the loop point cannot be seen.
# Usage: encode-hero-video.sh <source.mp4> <out-basename> [width]
set -euo pipefail
FFMPEG="${FFMPEG:-ffmpeg}"
SRC="$1"; OUT="$2"; W="${3:-1920}"
FILTER="[0:v]scale=${W}:-2:flags=lanczos,fps=25,setpts=PTS-STARTPTS,split[a][b];[b]reverse,trim=start_frame=1,setpts=PTS-STARTPTS[r];[a][r]concat=n=2:v=1[v]"
"$FFMPEG" -y -i "$SRC" -filter_complex "$FILTER" -map "[v]" -an \
-c:v libx264 -profile:v high -crf 30 -preset slow -pix_fmt yuv420p -movflags +faststart "${OUT}.mp4"
"$FFMPEG" -y -i "$SRC" -filter_complex "$FILTER" -map "[v]" -an \
-c:v libvpx-vp9 -crf 40 -b:v 0 -row-mt 1 -deadline good -cpu-used 2 "${OUT}.webm"
ls -lh "${OUT}.mp4" "${OUT}.webm"
+39
View File
@@ -0,0 +1,39 @@
import { chromium } from 'playwright';
/* Checks the hero video: does it load and play, is it gated off on phones and under reduced
motion, and does the hero fill the viewport. */
const [,, url, prefix] = process.argv;
const browser = await chromium.launch();
async function probe(label, opts, shots = []) {
const ctx = await browser.newContext(opts);
const page = await ctx.newPage();
const requested = [];
page.on('request', (r) => { if (/\.(mp4|webm)$/.test(r.url())) requested.push(r.url().split('/').pop()); });
await page.goto(url, { waitUntil: 'networkidle' });
await page.waitForTimeout(2500);
const info = await page.evaluate(() => {
const v = document.querySelector('video');
const hero = document.querySelector('.hero');
return {
hasVideo: !!v,
ready: v?.hasAttribute('data-ready') ?? null,
paused: v?.paused ?? null,
currentTime: v ? Math.round(v.currentTime * 10) / 10 : null,
videoSize: v ? `${v.videoWidth}x${v.videoHeight}` : null,
heroHeight: Math.round(hero.getBoundingClientRect().height),
viewport: window.innerHeight,
headerH: Math.round(document.querySelector('header').getBoundingClientRect().height),
};
});
for (const t of shots) {
await page.waitForTimeout(t);
await page.screenshot({ path: `${prefix}-${label}-${t}.png` });
}
console.log(label, JSON.stringify({ ...info, requested }));
await ctx.close();
}
await probe('desktop', { viewport: { width: 1440, height: 900 } }, [0, 2500]);
await probe('mobile', { viewport: { width: 390, height: 844 } });
await probe('reduced', { viewport: { width: 1440, height: 900 }, reducedMotion: 'reduce' });
await browser.close();
+31
View File
@@ -0,0 +1,31 @@
import { chromium } from 'playwright';
/* For every grid object: does the drawn geometry fit inside its viewBox, and is the drawing
centred in the space its card gives it. */
const [,, url, selector, width = '1440'] = process.argv;
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: Number(width), height: 1000 } });
await page.goto(url, { waitUntil: 'networkidle' });
const rows = await page.evaluate((sel) => {
return Array.from(document.querySelectorAll(sel)).map((svg, i) => {
const vb = svg.viewBox.baseVal;
const b = svg.querySelector('g').getBBox();
const overflow = {
left: +(vb.x - b.x).toFixed(1),
right: +((b.x + b.width) - (vb.x + vb.width)).toFixed(1),
top: +(vb.y - b.y).toFixed(1),
bottom: +((b.y + b.height) - (vb.y + vb.height)).toFixed(1),
};
const clipped = Object.values(overflow).some((v) => v > 0.5);
/* Vertical centring: the gap above the drawing vs the gap below it, inside the card. */
const card = svg.closest('li');
const title = card.querySelector('h2, h3');
const body = card.querySelector('p');
const r = svg.getBoundingClientRect();
const above = +(r.top - title.getBoundingClientRect().bottom).toFixed(1);
const below = +(body.getBoundingClientRect().top - r.bottom).toFixed(1);
return { i, viewBox: `${vb.x} ${vb.y} ${vb.width} ${vb.height}`, clipped, overflow, above, below, offBy: +(above - below).toFixed(1) };
});
}, selector);
console.table(rows);
console.log('any clipped:', rows.some((r) => r.clipped));
await browser.close();