Skip to content

feat(website): serve the hero film in two qualities - #839

Merged
cameri merged 1 commit into
mainfrom
feat/hero-dual-quality
Oct 16, 2026
Merged

cameri merged 1 commit into
mainfrom
feat/hero-dual-quality

Conversation

@chappie-daemon

Copy link
Copy Markdown
Collaborator

What

Prepares the site for the upcoming high-res hero render:

  • FilmScreen grows an optional lowSrc. The full-quality source remains the default <source>; an inline pick swaps in the low variant before the browser commits to the full download when the client asks for it — navigator.connection.saveData, a slow effectiveType, or a small viewport on a ≤2× display. The swap preserves playback position and plays state.
  • Hero passes /media/hero-low.mp4. That file is committed now as a copy of the current video, so the attribute never dangles; the GPU render (next step, needs containers/gpu-render/gpu-render-setup.sh --apply on the host) replaces both files with real high/low variants.
  • build-media.mjs encodes both variants (crf 15 full, crf 22 low) from the two hero-animation renders, records both sources in hero.meta.json, and refuses a low variant whose film.meta.json disagrees with the full render on NIP count (both renders must come from the same code state).
  • HERO_LOW_SRC env override mirrors HERO_SRC for the low leg.

Verified

  • pnpm run build in website/site: 14 pages, data-low-src="/media/hero-low.mp4" on the hero video, pick logic inline in the page.
  • build-media.mjs exercised against fixture renders in both shapes: full-only (skips the low leg with a reason) and full+low (encodes both, writes both sources to meta). Fixture outputs discarded; the committed media stays the current real video.

Next

The GPU render session (host side) produces the real high-res film; build-media then ships hero.mp4 (very_high, crf 15) + hero-low.mp4 (low, crf 22), the film guard flips back to hard-fail, and the NIP-77 staleness closes in the same render.

FilmScreen grows an optional lowSrc: the full-quality source stays the
default <source>, and an inline pick swaps in the low variant before
the browser commits to the download for clients that ask for it —
navigator.connection.saveData, a slow effectiveType, or a small
viewport on a non-retina screen. The swap preserves playback position
and plays state. Hero passes /media/hero-low.mp4 (committed alongside
as the current video so the attribute never dangles); build-media.mjs
encodes both variants (crf 15 full, crf 22 low) from the high and low
hero-animation renders and records both sources in hero.meta.json,
refusing a low variant whose film.meta.json disagrees with the full
one on NIP count.
@changeset-bot

changeset-bot Bot commented Oct 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 728427c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
nostream-website Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@greptile-apps

greptile-apps Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] Adds dual-quality video serving to the hero section.

Fix the render-pair check before merging so an old low film cannot pass as a match for a new full film.

Findings

  1. P1 Old renders pass the check ▶
  2. P2 Full video downloads before selection ▶
  3. P2 Browser checks add type errors ▶
  4. P2 Metadata names a rejected source ▶

Summary

Adds an optional low-quality hero video and chooses it for data-saver, slow-connection, and smaller-screen visitors.

  • The home-page hero switches to a smaller film for data-saving visitors.
  • The media build creates full and lower-quality hero films from matching renders.

Acknowledged preparation: chappie-daemon states that the committed low video intentionally copies the current film and that full-only builds intentionally skip the low encode pending the GPU render.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Full render] --> C[Read shared film.meta.json]
  B[Low render] --> C
  C --> D[Compare NIP counts]
  D --> E[Encode full and low videos]
  E --> F[Page exposes full video]
  F --> G{Client prefers low?}
  G -->|Yes| H[Script reloads low video]
  G -->|No| I[Keep full video]
Loading

Reviews (1) · Last reviewed commit: "feat(website): serve the hero film in tw..." · Reviewed by Greptile

Comment on lines +86 to +88
const lowMeta = readFilmMeta(landingLowVideo)
if (lowMeta?.nipCount === filmMeta.nipCount) {
encodeHero(landingLowVideo, path.join(MEDIA, 'hero-low.mp4'), 22)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Old renders pass the check

lowMeta and filmMeta read the same output/film.meta.json for the default full and low renders. render.ts overwrites that file after either render. If an old low render remains when a new full render finishes, this comparison always passes and ships the old film alongside the new one. Write and check metadata tied to each video before accepting the pair.

Comment on lines +16 to 17
<video muted loop playsinline preload="auto" poster={poster} data-autoplay data-low-src={lowSrc} aria-label={title}>
<source src={src} type="video/mp4" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Full video downloads before selection

The full video is already discoverable with preload="auto" before the quality picker runs. Astro processes this ordinary <script> as a module, so it does not block parsing before the browser starts loading the full source. Data-saver and small-screen visitors can download part of the full video and then start another download for the low video. Keep the full URL out of the active source until the choice is made, with a separate fallback for visitors without JavaScript.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +35 to +36
navigator.connection?.saveData === true ||
(navigator.connection?.effectiveType ?? '4g') in { 'slow-2g': 1, '2g': 1, '3g': 1 } ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Browser checks add type errors

navigator.connection is not declared by the standard TypeScript DOM types, and the site has no declaration that adds it. Optional chaining makes unsupported browsers safe, but does not fix the type error. These two accesses add errors to the site's check command. Add a typed optional connection property and use it for both checks.

nipCount: filmMeta.nipCount,
generatedAt: filmMeta.generatedAt,
sourceVideo: path.relative(HERO_DIR, landingVideo),
...(landingLowVideo ? { sourceLowVideo: path.relative(HERO_DIR, landingLowVideo) } : {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Metadata names a rejected source

sourceLowVideo is recorded whenever a candidate exists, even when its metadata disagrees and the script skips encoding it. With a mismatched HERO_LOW_SRC, hero.meta.json therefore names a rejected source rather than the source of the served file. Record this field only after the low video is successfully encoded.

@cameri
cameri merged commit 91a5a7e into main Oct 16, 2026
13 of 14 checks passed
@cameri
cameri deleted the feat/hero-dual-quality branch October 16, 2026 02:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants