mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-15 04:27:20 +03:00
fix(site): serve AV1/HEVC card videos and load card media after the page (#62971)
This commit is contained in:
@@ -115,7 +115,7 @@ Motion decisions follow Emil Kowalski's skills ([emilkowalski/skills](https://gi
|
||||
- Show real product footage and screenshots only.
|
||||
- Ship no placeholders; hide a media slot until its real content exists, as on the changelog. → [Timeline](/dev/components#changelog-timeline)
|
||||
- Put heavy media (video, audio, zip, glb, wasm, anything over 256KB) on the CDN, not in git; SVGs, favicons, Open Graph images and small posters stay in git.
|
||||
- Give every video a poster; card videos rest on a still and play on hover. → [Product carousel](/dev/components#carousel)
|
||||
- Give every video a poster; card videos rest on a short loop, started from its still once the page has loaded, and play the demo on hover. → [Product carousel](/dev/components#carousel)
|
||||
- Caption videos that have speech.
|
||||
|
||||
## Accessibility
|
||||
|
||||
@@ -9,7 +9,7 @@ The site's heavy media (product videos, arcade sounds, 3D models and download zi
|
||||
- The build (`script/build.ts`) drops every `mediaFile` from the Worker's assets, so a local copy in `public/` never ships.
|
||||
- In development, `vite dev` proxies `/files` to `https://dev.opencode.ai`, so pages play the dev bucket's copies.
|
||||
- Images (`png`, `jpg`, `webp`, `avif`, `svg`), posters, fonts and the 3D decoders (`arcade3d/draco`, `arcade3d/basis`) stay in git and in the static assets.
|
||||
- Only the H.264 originals are served. The AV1 and HEVC copies `media-manifest.json` once described were never uploaded, so `videoSources()` offers the original alone.
|
||||
- `videoSources()` offers a video's AV1 and HEVC copies (`media/<base>.<width>.<codec>.<hash>.mp4`, listed in `src/component/site/media-manifest.json`) before the H.264 original. The manifest lists only copies that are in the bucket; a video without copies plays its original.
|
||||
|
||||
| Folder | Files |
|
||||
| --- | --- |
|
||||
|
||||
@@ -102,6 +102,8 @@ b3b147cdb22ec2117d539c18490c7103adaa088a13600852b5a5363f667ee051 186628 /brand
|
||||
8998172a4ff22a6b292da18c87a21998728bc86f94b3828e536298ca6b00da54 149085 /brand/badges/opencode-works-with.zip
|
||||
140f2c17fa7235f2199a3db22f1eda3eccbfb8ce98da39ee7b1e3e9eb9387846 6801928 /font/film/opencode-font-film-v3-1080p.mp4
|
||||
c0d7932b3c2f5d27368e2039e580c169ac94802b8317260e5e679551c41e8461 6300093 /font/film/opencode-font-film-v3-4x5.mp4
|
||||
a8d0f5bc1395a756affd9cff1cd0db46e97fba0c1d72508d0e665c62a8a92c2d 146992 /media/card-loops/mobile-orb-75.720.av1.a8d0f5bc.mp4
|
||||
a82f1012a18962731b12ee4b07da137a8a89165b3aef838705c43d75d186ef90 336142 /media/card-loops/mobile-orb-75.720.hevc.a82f1012.mp4
|
||||
927d3f29a862b2af9853d2ba951f29e4ff381e0c5f1b08c7d8b34cd62b7128e2 2818872 /opencode-brand-assets.zip
|
||||
0f1d6dfc747f2d88650b2073bb04cd41380241adfa4de3e2f070bdc4bb367579 272426 /press/anomaly/anomaly-logo.zip
|
||||
9cba0d1d2108c8af4e22cc54cafcd8532101d6bbd2809ac18dd26ff1aaf615b3 5999115 /press-media/zips/opencode-desktop-media.zip
|
||||
|
||||
@@ -66,23 +66,23 @@ console.log(
|
||||
const budgets: Record<string, number> = {
|
||||
"[...404].tsx": 104.1,
|
||||
"brand/index.tsx": 104.5,
|
||||
"changelog/[tag].tsx": 97.8,
|
||||
"changelog/index.tsx": 97,
|
||||
"desktop.tsx": 126.5,
|
||||
"changelog/[tag].tsx": 99.2,
|
||||
"changelog/index.tsx": 98.4,
|
||||
"desktop.tsx": 127.9,
|
||||
"devices.tsx": 56.8,
|
||||
"product/browse.tsx": 102.8,
|
||||
"font/index.tsx": 118.7,
|
||||
"ide.tsx": 126.5,
|
||||
"index.tsx": 113.4,
|
||||
"product/browse.tsx": 104.2,
|
||||
"font/index.tsx": 120.1,
|
||||
"ide.tsx": 127.9,
|
||||
"index.tsx": 114.9,
|
||||
"legal/index.tsx": 148.3,
|
||||
"legal/privacy-policy/index.tsx": 137.8,
|
||||
"legal/terms-of-service/index.tsx": 133.2,
|
||||
"mini.tsx": 126.5,
|
||||
"mobile.tsx": 126.5,
|
||||
"press/index.tsx": 111.8,
|
||||
"product/index.tsx": 111.3,
|
||||
"sdk.tsx": 126.5,
|
||||
"terminal.tsx": 126.5,
|
||||
"mini.tsx": 127.9,
|
||||
"mobile.tsx": 127.9,
|
||||
"press/index.tsx": 113.2,
|
||||
"product/index.tsx": 112.7,
|
||||
"sdk.tsx": 127.9,
|
||||
"terminal.tsx": 127.9,
|
||||
"video-editor/index.tsx": 55.2,
|
||||
}
|
||||
type Chunk = { file: string; imports?: string[] }
|
||||
|
||||
@@ -4,30 +4,6 @@
|
||||
[1920, "av1", "f4aac9d0", 8],
|
||||
[1920, "hevc", "9a8fdd1e", 120]
|
||||
],
|
||||
"/product-videos/card-hover/angled-continue/desktop.mp4": [
|
||||
[1080, "av1", "3d944be5", 8, "hi"],
|
||||
[720, "av1", "8c2f11ae", 4],
|
||||
[1080, "hevc", "06dafc49", 120, "hi"],
|
||||
[720, "hevc", "dd4ef775", 93]
|
||||
],
|
||||
"/product-videos/card-hover/angled-continue/mobile.mp4": [
|
||||
[1080, "av1", "1d02d18f", 8, "hi"],
|
||||
[720, "av1", "fc122802", 4],
|
||||
[1080, "hevc", "029f5c19", 120, "hi"],
|
||||
[720, "hevc", "c94ebb22", 93]
|
||||
],
|
||||
"/product-videos/card-hover/angled-continue/sdk.mp4": [
|
||||
[1080, "av1", "2eef5975", 8, "hi"],
|
||||
[720, "av1", "35b2a488", 4],
|
||||
[1080, "hevc", "d7a76c34", 120, "hi"],
|
||||
[720, "hevc", "270b3d5f", 93]
|
||||
],
|
||||
"/product-videos/card-hover/angled-continue/terminal.mp4": [
|
||||
[1080, "av1", "de1a465e", 8, "hi"],
|
||||
[720, "av1", "32caf68d", 4],
|
||||
[1080, "hevc", "1fb02df6", 120, "hi"],
|
||||
[720, "hevc", "383908d2", 93]
|
||||
],
|
||||
"/product-videos/card-hover/angled/desktop.mp4": [
|
||||
[1080, "av1", "000848bb", 8, "hi"],
|
||||
[720, "av1", "7a8ced27", 4],
|
||||
@@ -52,54 +28,6 @@
|
||||
[1080, "hevc", "5802fcd0", 120, "hi"],
|
||||
[720, "hevc", "33b9daa2", 93]
|
||||
],
|
||||
"/product-videos/card-hover/cinematic-continue/desktop.mp4": [
|
||||
[1080, "av1", "79e8094e", 8, "hi"],
|
||||
[720, "av1", "50aaddc3", 4],
|
||||
[1080, "hevc", "efc2e19a", 120, "hi"],
|
||||
[720, "hevc", "7b98ff3e", 93]
|
||||
],
|
||||
"/product-videos/card-hover/cinematic-continue/mobile.mp4": [
|
||||
[1080, "av1", "735cceb4", 8, "hi"],
|
||||
[720, "av1", "97fa967a", 4],
|
||||
[1080, "hevc", "d0d339af", 120, "hi"],
|
||||
[720, "hevc", "ed5ead76", 93]
|
||||
],
|
||||
"/product-videos/card-hover/cinematic-continue/sdk.mp4": [
|
||||
[1080, "av1", "5b45cb59", 8, "hi"],
|
||||
[720, "av1", "e2cc8efd", 4],
|
||||
[1080, "hevc", "fb1bc058", 120, "hi"],
|
||||
[720, "hevc", "e0752306", 93]
|
||||
],
|
||||
"/product-videos/card-hover/cinematic-continue/terminal.mp4": [
|
||||
[1080, "av1", "c6c59bed", 8, "hi"],
|
||||
[720, "av1", "2f9535de", 4],
|
||||
[1080, "hevc", "6995f361", 120, "hi"],
|
||||
[720, "hevc", "80276e87", 93]
|
||||
],
|
||||
"/product-videos/card-hover/cinematic/desktop.mp4": [
|
||||
[1080, "av1", "829c3da8", 8, "hi"],
|
||||
[720, "av1", "598a8997", 4],
|
||||
[1080, "hevc", "176438ec", 120, "hi"],
|
||||
[720, "hevc", "70baa9dc", 93]
|
||||
],
|
||||
"/product-videos/card-hover/cinematic/mobile.mp4": [
|
||||
[1080, "av1", "0c67eb98", 8, "hi"],
|
||||
[720, "av1", "b5fa7ce2", 4],
|
||||
[1080, "hevc", "5abf1f1e", 120, "hi"],
|
||||
[720, "hevc", "d9b47452", 93]
|
||||
],
|
||||
"/product-videos/card-hover/cinematic/sdk.mp4": [
|
||||
[1080, "av1", "0de9145c", 8, "hi"],
|
||||
[720, "av1", "847247e6", 4],
|
||||
[1080, "hevc", "1c34707c", 120, "hi"],
|
||||
[720, "hevc", "0a03196c", 93]
|
||||
],
|
||||
"/product-videos/card-hover/cinematic/terminal.mp4": [
|
||||
[1080, "av1", "9776cf70", 8, "hi"],
|
||||
[720, "av1", "76a5c8b0", 4],
|
||||
[1080, "hevc", "fa24ac2a", 120, "hi"],
|
||||
[720, "hevc", "24c9487e", 93]
|
||||
],
|
||||
"/product-videos/card-loops/desktop.ja.mp4": [
|
||||
[1064, "av1", "8d53f4c4", 8, "hi"],
|
||||
[720, "av1", "03051f4d", 4],
|
||||
@@ -117,11 +45,9 @@
|
||||
[1064, "hevc", "de5e01b6", 120, "hi"],
|
||||
[720, "hevc", "2adfbc82", 93]
|
||||
],
|
||||
"/product-videos/card-loops/mobile.mp4": [
|
||||
[1064, "av1", "969038f3", 8, "hi"],
|
||||
[720, "av1", "0ad54196", 4],
|
||||
[1064, "hevc", "b522b660", 120, "hi"],
|
||||
[720, "hevc", "13e6540a", 93]
|
||||
"/product-videos/card-loops/mobile-orb-75.mp4": [
|
||||
[720, "av1", "a8d0f5bc", 4],
|
||||
[720, "hevc", "a82f1012", 93]
|
||||
],
|
||||
"/product-videos/card-loops/sdk.mp4": [
|
||||
[1064, "av1", "57dfbb5d", 8, "hi"],
|
||||
@@ -135,30 +61,6 @@
|
||||
[1064, "hevc", "cb91d991", 120, "hi"],
|
||||
[720, "hevc", "e8322dc4", 93]
|
||||
],
|
||||
"/product-videos/features/desktop-1.mp4": [
|
||||
[1200, "av1", "ff4a4260", 5, "hi"],
|
||||
[800, "av1", "ef555136", 4],
|
||||
[1200, "hevc", "9cdc5d28", 93, "hi"],
|
||||
[800, "hevc", "d0e2cfef", 90]
|
||||
],
|
||||
"/product-videos/features/desktop-2.mp4": [
|
||||
[1200, "av1", "b92cab97", 5, "hi"],
|
||||
[800, "av1", "34d0517c", 4],
|
||||
[1200, "hevc", "d598ea33", 93, "hi"],
|
||||
[800, "hevc", "96b8b510", 90]
|
||||
],
|
||||
"/product-videos/features/desktop-3.mp4": [
|
||||
[1200, "av1", "10f94e71", 5, "hi"],
|
||||
[800, "av1", "47a5a8a9", 4],
|
||||
[1200, "hevc", "aa172509", 93, "hi"],
|
||||
[800, "hevc", "ba0c50ee", 90]
|
||||
],
|
||||
"/product-videos/features/desktop-4.mp4": [
|
||||
[1200, "av1", "6cfaeefa", 5, "hi"],
|
||||
[800, "av1", "0316f39c", 4],
|
||||
[1200, "hevc", "5719cc56", 93, "hi"],
|
||||
[800, "hevc", "1fcebe5c", 90]
|
||||
],
|
||||
"/product-videos/features/focused/desktop-1.mp4": [
|
||||
[1200, "av1", "a4464782", 5, "hi"],
|
||||
[800, "av1", "ff6b7fc8", 4],
|
||||
@@ -231,70 +133,6 @@
|
||||
[1200, "hevc", "ddc881b8", 93, "hi"],
|
||||
[800, "hevc", "1bfc3877", 90]
|
||||
],
|
||||
"/product-videos/features/mobile-1.mp4": [
|
||||
[1200, "av1", "dc15a609", 5, "hi"],
|
||||
[800, "av1", "95c72454", 4],
|
||||
[1200, "hevc", "8013fea3", 93, "hi"],
|
||||
[800, "hevc", "be07c96c", 90]
|
||||
],
|
||||
"/product-videos/features/mobile-2.mp4": [
|
||||
[1200, "av1", "3a868173", 5, "hi"],
|
||||
[800, "av1", "b333f8f4", 4],
|
||||
[1200, "hevc", "d1cc1d25", 93, "hi"],
|
||||
[800, "hevc", "27840268", 90]
|
||||
],
|
||||
"/product-videos/features/mobile-3.mp4": [
|
||||
[1200, "av1", "0924e4e2", 5, "hi"],
|
||||
[800, "av1", "a5a118d2", 4],
|
||||
[1200, "hevc", "596b8211", 93, "hi"],
|
||||
[800, "hevc", "b819f9f5", 90]
|
||||
],
|
||||
"/product-videos/features/mobile-4.mp4": [
|
||||
[1200, "av1", "3eb473b1", 5, "hi"],
|
||||
[800, "av1", "dea200a1", 4],
|
||||
[1200, "hevc", "b066c55e", 93, "hi"],
|
||||
[800, "hevc", "10402b50", 90]
|
||||
],
|
||||
"/product-videos/features/sdk-1.mp4": [
|
||||
[1200, "av1", "eead75dc", 5, "hi"],
|
||||
[800, "av1", "8d1247ee", 4],
|
||||
[1200, "hevc", "11e6218a", 93, "hi"],
|
||||
[800, "hevc", "c62e3038", 90]
|
||||
],
|
||||
"/product-videos/features/sdk-2.mp4": [
|
||||
[1200, "av1", "178c6b50", 5, "hi"],
|
||||
[800, "av1", "d52a969f", 4],
|
||||
[1200, "hevc", "810895c4", 93, "hi"],
|
||||
[800, "hevc", "2d8678dc", 90]
|
||||
],
|
||||
"/product-videos/features/sdk-3.mp4": [
|
||||
[1200, "av1", "fe427d04", 5, "hi"],
|
||||
[800, "av1", "22015ab8", 4],
|
||||
[1200, "hevc", "fa20dfe7", 93, "hi"],
|
||||
[800, "hevc", "3f1c498c", 90]
|
||||
],
|
||||
"/product-videos/features/terminal-1.mp4": [
|
||||
[1200, "av1", "8e233258", 5, "hi"],
|
||||
[800, "av1", "4fa5af14", 4],
|
||||
[1200, "hevc", "17334020", 93, "hi"],
|
||||
[800, "hevc", "d614132e", 90]
|
||||
],
|
||||
"/product-videos/features/terminal-2.mp4": [
|
||||
[1200, "av1", "65d38182", 5, "hi"],
|
||||
[800, "av1", "f0cbdd1a", 4],
|
||||
[1200, "hevc", "561a6203", 93, "hi"],
|
||||
[800, "hevc", "a617561a", 90]
|
||||
],
|
||||
"/product-videos/features/terminal-3.mp4": [
|
||||
[1200, "av1", "9e447bd5", 5, "hi"],
|
||||
[800, "av1", "0a311ac9", 4],
|
||||
[1200, "hevc", "8615f916", 93, "hi"],
|
||||
[800, "hevc", "f43c3475", 90]
|
||||
],
|
||||
"/product-videos/features/terminal-4.mp4": [
|
||||
[800, "av1", "114cdf52", 4],
|
||||
[800, "hevc", "4123e308", 90]
|
||||
],
|
||||
"/product-videos/heroes/desktop-landscape-dark.mp4": [
|
||||
[1280, "av1", "87488462", 5, "phone"],
|
||||
[1920, "av1", "476dd57a", 8],
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
"/product-videos/card-loops/mobile-orb-100.webp": "ea6453e6b91748ba",
|
||||
"/product-videos/card-loops/mobile-orb-25.webp": "41f7dc05c0f80eea",
|
||||
"/product-videos/card-loops/mobile-orb-50.webp": "20859953b02bfb65",
|
||||
"/product-videos/card-loops/mobile-orb-75.mp4": "41351d6aaa74c958",
|
||||
"/product-videos/card-loops/mobile-orb-75.webp": "7facb79fd8e4b423",
|
||||
"/product-videos/card-loops/mobile.mp4": "8c1efd91fe0f25ca",
|
||||
"/product-videos/card-loops/mobile.webp": "0854c8b479cb9fb6",
|
||||
|
||||
@@ -7,6 +7,10 @@ import { mediaUrl } from "~/lib/media"
|
||||
* for where they are drawn and content-hashed so they cache for a year. media-manifest.json lists only
|
||||
* copies that met the quality bar (VMAF ≥ 95 and SSIM ≥ 0.98 for video, SSIMULACRA2 ≥ 85 for
|
||||
* stills) against the original, which stays in place as the last fallback. Bundled at build time.
|
||||
* The Mobile orb loop's copies are the exception: every dot of its grid changes brightness in every
|
||||
* frame, so no copy much smaller than the original scores VMAF 95. Its 720-wide AV1 (147 kB) and HEVC
|
||||
* (336 kB) copies, against the 578 kB original, score SSIM 0.99 and were matched by eye at the
|
||||
* card's size.
|
||||
*/
|
||||
// Copy names follow one pattern, so the manifest holds only what varies (media-opt/manifest.py):
|
||||
// /media/<base>.<width>.<codec>.<hash>.mp4 and /media/<base>.<width>.<hash>.<avif|webp>,
|
||||
@@ -19,26 +23,39 @@ type Manifest = {
|
||||
}
|
||||
type MediaKey = "hi" | "phone"
|
||||
|
||||
// Only the stills' half: the pages offer the original videos alone (videoSources), so the video
|
||||
// copies' rows stay out of the bundle.
|
||||
const images: Manifest["image"] =
|
||||
Object.values(import.meta.glob<Manifest["image"]>("./media-manifest.json", { eager: true, import: "image" }))[0] ?? {}
|
||||
const manifest: Manifest = Object.values(
|
||||
import.meta.glob<Manifest>("./media-manifest.json", { eager: true, import: "default" }),
|
||||
)[0] ?? { video: {}, image: {} }
|
||||
|
||||
// The 1080-wide card and 1200-wide clip files go to screens of 2.5 device pixels per CSS pixel or
|
||||
// more (most phones); the 1280-wide hero files to phone-width windows.
|
||||
const queries: Record<MediaKey, string> = {
|
||||
hi: "(min-resolution: 2.5dppx)",
|
||||
phone: "(max-width: 40rem)",
|
||||
}
|
||||
|
||||
const base = (src: string) => `/media/${src.replace(/^\/(product-videos\/)?/, "").replace(/\.\w+$/, "")}`
|
||||
|
||||
export type VideoSource = { src: string; type?: string; media?: string }
|
||||
|
||||
/**
|
||||
* A video's source: the original H.264 file in the files bucket. The manifest's AV1 and HEVC copies
|
||||
* were never uploaded, so they are not offered.
|
||||
* A video's sources in the order the browser should try them, all in the files bucket: the AV1
|
||||
* copies, the HEVC ones (Safari without an AV1 decoder), then the original H.264 file.
|
||||
*/
|
||||
export function videoSources(src: string): VideoSource[] {
|
||||
return [{ src: mediaUrl(src), type: "video/mp4" }]
|
||||
return [
|
||||
...(manifest.video[src] ?? []).map(([width, codec, hash, level, key]) => ({
|
||||
src: mediaUrl(`${base(src)}.${width}.${codec}.${hash}.mp4`),
|
||||
type: `video/mp4; codecs="${codec === "av1" ? `av01.0.${String(level).padStart(2, "0")}M.08` : `hvc1.1.6.L${level}.B0`}"`,
|
||||
media: key ? queries[key] : undefined,
|
||||
})),
|
||||
{ src: mediaUrl(src), type: "video/mp4" },
|
||||
]
|
||||
}
|
||||
|
||||
/** A still's AVIF and WebP srcsets (if it has copies), and the file for `src`: the smallest WebP. */
|
||||
export function imageSources(src: string) {
|
||||
const entry = images[src]
|
||||
const entry = manifest.image[src]
|
||||
const file = (format: "avif" | "webp", [width, hash]: [number, string]) => `${base(src)}.${width}.${hash}.${format}`
|
||||
const set = (format: "avif" | "webp") =>
|
||||
entry?.[format]?.map((item) => `${file(format, item)} ${item[0]}w`).join(", ")
|
||||
|
||||
@@ -582,9 +582,9 @@ export function carouselCss() {
|
||||
* ("hover · reverse") or the still fades back over it ("hover · fade"). "autoplay" plays all the
|
||||
* time. Reduced motion keeps the still.
|
||||
*
|
||||
* With `loop`, the card rests on that short idle loop instead of a still: it plays while the card
|
||||
* is on screen, crossfades to the product video on hover and back on leave. Reduced motion shows
|
||||
* its first frame (`still`).
|
||||
* With `loop`, the card rests on that short idle loop instead of a still: it shows its first frame
|
||||
* (`still`) until the page has loaded, then plays while the card is on screen, crossfades to the
|
||||
* product video on hover and back on leave. Reduced motion and Save-Data keep the first frame.
|
||||
*/
|
||||
export function CardVideo(props: {
|
||||
src: string
|
||||
@@ -652,10 +652,17 @@ export function CardVideo(props: {
|
||||
}
|
||||
createEffect(speed)
|
||||
|
||||
// The idle loop plays only while the card is on screen and resting.
|
||||
// Reduced motion and Save-Data keep a card on its still: no resting loop and no sizzle.
|
||||
const cover = () =>
|
||||
prefersReducedMotion() ||
|
||||
(navigator as typeof window.navigator & { connection?: { saveData?: boolean } }).connection?.saveData === true
|
||||
|
||||
// The idle loop plays only while the card is on screen and resting, and only once the page has
|
||||
// loaded (ready), so it never competes with the stills: it starts from the frame the still shows.
|
||||
let ready = false
|
||||
const rest = () => {
|
||||
if (!idle) return
|
||||
if (visible && !active && mode() !== "autoplay" && !prefersReducedMotion()) {
|
||||
if (ready && visible && !active && mode() !== "autoplay" && !cover()) {
|
||||
void idle.play().catch(() => {})
|
||||
return
|
||||
}
|
||||
@@ -774,8 +781,6 @@ export function CardVideo(props: {
|
||||
// Leaving pauses the sizzle where it is and coming back resumes it, unless the card was away
|
||||
// for over 30s: then it starts again from the cover. Reduced motion and Save-Data keep the cover.
|
||||
setSizzle(window.matchMedia("(hover: none), (pointer: coarse)").matches || isMobile(detectPlatform()))
|
||||
const connection = (navigator as typeof window.navigator & { connection?: { saveData?: boolean } }).connection
|
||||
const cover = () => prefersReducedMotion() || connection?.saveData === true
|
||||
let wait: ReturnType<typeof setTimeout> | undefined
|
||||
let away = 0
|
||||
// Back to the cover with the sizzle rewound, without a fade (the card is off screen).
|
||||
@@ -821,7 +826,13 @@ export function CardVideo(props: {
|
||||
{ rootMargin: "100% 50%" },
|
||||
)
|
||||
if (media) ahead.observe(media)
|
||||
const start = () => {
|
||||
ready = true
|
||||
rest()
|
||||
}
|
||||
afterLoad(start)
|
||||
onCleanup(() => {
|
||||
window.removeEventListener("load", start)
|
||||
view.disconnect()
|
||||
ahead.disconnect()
|
||||
clearTimeout(wait)
|
||||
@@ -839,13 +850,16 @@ export function CardVideo(props: {
|
||||
<video
|
||||
ref={video}
|
||||
aria-hidden="true"
|
||||
// The demo's poster is its first product frame: show it only once the loop covers it, so the
|
||||
// card never flashes the demo while it loads.
|
||||
// The demo's poster is its first product frame, and the loop covers the demo until a frame
|
||||
// of it is on screen (enter waits for one), so only autoplay, which takes the loop away
|
||||
// once mounted, ever shows it.
|
||||
// On phones the cover stays over it until a frame of it is playing, so it needs no poster.
|
||||
// A clip whose still is its own poster (the feature clips) has the image over it until it
|
||||
// plays (except with autoplay), so the attribute would only download it again as WebP.
|
||||
poster={
|
||||
(props.demo && !loaded()) || sizzle() || (!props.loop && !props.still && mode() !== "autoplay")
|
||||
(props.demo && (still() || mode() !== "autoplay")) ||
|
||||
sizzle() ||
|
||||
(!props.loop && !props.still && mode() !== "autoplay")
|
||||
? undefined
|
||||
: posterFile(props.poster)
|
||||
}
|
||||
@@ -915,8 +929,8 @@ export function CardVideo(props: {
|
||||
muted
|
||||
loop
|
||||
playsinline
|
||||
// The first screen's loops buffer with the page; the others once they play on screen.
|
||||
preload={props.priority ? "auto" : "metadata"}
|
||||
// Nothing loads until the loop first plays (rest), after the page has loaded.
|
||||
preload="none"
|
||||
>
|
||||
<VideoSources src={loop()} video={() => idle} />
|
||||
</video>
|
||||
|
||||
@@ -1796,6 +1796,7 @@
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
background: currentColor;
|
||||
mask-image: var(--logo);
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
@@ -1806,11 +1807,18 @@
|
||||
height: calc(var(--height) * var(--logo-size));
|
||||
translate: 0 calc(var(--shift) * var(--logo-size));
|
||||
background: currentColor;
|
||||
mask-image: var(--logo);
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
}
|
||||
|
||||
/* Until the grid is near the screen (data-near, ToolGrid), its logos stay empty masks, so their
|
||||
SVGs don't load with the page. */
|
||||
&:not([data-near]) :is([data-slot="icon"], [data-slot="wordmark"]) {
|
||||
mask-image: linear-gradient(transparent 0 0);
|
||||
}
|
||||
|
||||
/* The typed name stays as the tile's accessible text. */
|
||||
[data-slot="name"] {
|
||||
position: absolute;
|
||||
|
||||
@@ -1351,6 +1351,20 @@ function ToolGrid(props: {
|
||||
return wash.armed() && style ? { animation: style, vars: revealMotion(style, siteSettings.logoTuning) } : undefined
|
||||
}
|
||||
createEffect(on([animation, () => siteSettings.toolsStagger], wash.replay, { defer: true }))
|
||||
// The tiles' logos (SVG masks, index.css) load once the grid is within a screen of the window, so
|
||||
// a page doesn't fetch them with its first screen.
|
||||
onMount(() => {
|
||||
const near = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (!entries.some((entry) => entry.isIntersecting)) return
|
||||
grid?.setAttribute("data-near", "")
|
||||
near.disconnect()
|
||||
},
|
||||
{ rootMargin: "100% 0px" },
|
||||
)
|
||||
if (grid) near.observe(grid)
|
||||
onCleanup(() => near.disconnect())
|
||||
})
|
||||
|
||||
return (
|
||||
<ul
|
||||
@@ -1394,7 +1408,7 @@ function ToolGrid(props: {
|
||||
role={props.iconTiles ? "img" : undefined}
|
||||
aria-label={props.iconTiles ? tool.name : undefined}
|
||||
aria-hidden={props.iconTiles ? undefined : "true"}
|
||||
style={{ "mask-image": `url(${tool.logo})`, "-webkit-mask-image": `url(${tool.logo})` }}
|
||||
style={{ "--logo": `url(${tool.logo})` }}
|
||||
/>
|
||||
{props.iconTiles ? null : tool.name}
|
||||
</>
|
||||
@@ -1404,8 +1418,7 @@ function ToolGrid(props: {
|
||||
data-slot="wordmark"
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
"mask-image": `url(${tool.wordmark})`,
|
||||
"-webkit-mask-image": `url(${tool.wordmark})`,
|
||||
"--logo": `url(${tool.wordmark})`,
|
||||
...(tool.metrics ? balanceLogo(tool.metrics) : {}),
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user