diff --git a/.changeset/hero-dual-quality.md b/.changeset/hero-dual-quality.md new file mode 100644 index 000000000..5e32ff328 --- /dev/null +++ b/.changeset/hero-dual-quality.md @@ -0,0 +1,5 @@ +--- +"nostream-website": minor +--- + +Ship the hero film in two qualities: the page serves a high-res render by default and swaps to a low variant for data-saver clients and small screens (navigator.connection.saveData / effectiveType / viewport + DPR pick). build-media.mjs now emits both variants and records both sources in hero.meta.json. diff --git a/website/site/public/media/hero-low.mp4 b/website/site/public/media/hero-low.mp4 new file mode 100644 index 000000000..4253b25d1 Binary files /dev/null and b/website/site/public/media/hero-low.mp4 differ diff --git a/website/site/scripts/build-media.mjs b/website/site/scripts/build-media.mjs index 0c7507db1..de7710391 100644 --- a/website/site/scripts/build-media.mjs +++ b/website/site/scripts/build-media.mjs @@ -45,31 +45,57 @@ const landingVideo = [ path.join(LANDING, 'nostream-hero-low.mp4'), ].find((p) => p && existsSync(p)) -if (landingVideo) { - const filmMeta = readFilmMeta(landingVideo) - if (filmMeta?.nipCount == null) { - if (existsSync(HERO_META)) unlinkSync(HERO_META) - console.error( - `build-media: ${landingVideo} has no film.meta.json beside it — re-render in website/hero-animation (pnpm run render) or pick a source that includes meta.`, - ) - process.exit(1) - } +// The low variant ships beside the full one for mobile / Save-Data. It is the +// previous site video when a fresh high-res render replaces hero.mp4, or the +// low render when that is all hero-animation has produced. +const landingLowVideo = [ + process.env.HERO_LOW_SRC, + path.join(LANDING, 'nostream-hero-low.mp4'), + landingVideo === path.join(LANDING, 'nostream-hero-low.mp4') ? null : landingVideo, +].find((p) => p && existsSync(p) && p !== landingVideo) +const encodeHero = (source, outPath, crf) => execFileSync( 'ffmpeg', [ '-y', '-loglevel', 'error', - '-i', landingVideo, + '-i', source, '-vf', 'unsharp=5:5:0.85:5:5:0.0', '-an', '-c:v', 'libx264', '-preset', 'slow', '-pix_fmt', 'yuv420p', '-movflags', '+faststart', '-tune', 'animation', - '-crf', '15', - path.join(MEDIA, 'hero.mp4'), + '-crf', String(crf), + outPath, ], { stdio: 'inherit' }, ) + +if (landingVideo) { + const filmMeta = readFilmMeta(landingVideo) + if (filmMeta?.nipCount == null) { + if (existsSync(HERO_META)) unlinkSync(HERO_META) + console.error( + `build-media: ${landingVideo} has no film.meta.json beside it — re-render in website/hero-animation (pnpm run render) or pick a source that includes meta.`, + ) + process.exit(1) + } + + encodeHero(landingVideo, path.join(MEDIA, 'hero.mp4'), 15) console.log('hero.mp4 ← landing film (film.ts)') + if (landingLowVideo) { + const lowMeta = readFilmMeta(landingLowVideo) + if (lowMeta?.nipCount === filmMeta.nipCount) { + encodeHero(landingLowVideo, path.join(MEDIA, 'hero-low.mp4'), 22) + console.log('hero-low.mp4 ← landing film (low variant)') + } else { + console.warn( + `skip hero-low.mp4: low source meta (${lowMeta?.nipCount}) disagrees with full source (${filmMeta.nipCount}) — regenerate both renders together`, + ) + } + } else { + console.warn('skip hero-low.mp4: no second source (run `pnpm run render:small` in website/hero-animation)') + } + writeFileSync( HERO_META, `${JSON.stringify( @@ -77,6 +103,7 @@ if (landingVideo) { nipCount: filmMeta.nipCount, generatedAt: filmMeta.generatedAt, sourceVideo: path.relative(HERO_DIR, landingVideo), + ...(landingLowVideo ? { sourceLowVideo: path.relative(HERO_DIR, landingLowVideo) } : {}), }, null, 2, diff --git a/website/site/src/components/FilmFrame.astro b/website/site/src/components/FilmFrame.astro index 4aa3080f3..125cb10e6 100644 --- a/website/site/src/components/FilmFrame.astro +++ b/website/site/src/components/FilmFrame.astro @@ -3,15 +3,16 @@ import FilmScreen from './FilmScreen.astro' interface Props { src: string + lowSrc?: string poster: string title: string class?: string } -const { src, poster, title, class: className } = Astro.props +const { src, lowSrc, poster, title, class: className } = Astro.props ---
- +
+ + diff --git a/website/site/src/components/home/Hero.astro b/website/site/src/components/home/Hero.astro index 0d647ed2e..d9ba89654 100644 --- a/website/site/src/components/home/Hero.astro +++ b/website/site/src/components/home/Hero.astro @@ -15,6 +15,7 @@ import { VERSION } from '../../data/site'