From ac23be3d21803dc240c60bc3968b5e448acd6067 Mon Sep 17 00:00:00 2001 From: matiasngf Date: Tue, 30 Jun 2026 04:35:42 +0000 Subject: [PATCH 01/15] Add themed static fallback for eve logo shader --- .../components/eve-logo-shader/index.tsx | 163 ++++++++++++++++-- apps/docs/next.config.ts | 1 + apps/docs/public/eve-5/fallback-dark.webp | Bin 0 -> 10226 bytes apps/docs/public/eve-5/fallback-light.webp | Bin 0 -> 5848 bytes apps/docs/scripts/render-eve-5.ts | 48 +++++- 5 files changed, 198 insertions(+), 14 deletions(-) create mode 100644 apps/docs/public/eve-5/fallback-dark.webp create mode 100644 apps/docs/public/eve-5/fallback-light.webp diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx index 3a8e409e0..9ca73f586 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx @@ -1,7 +1,8 @@ "use client"; import { App, Device, type VGPUAdapter } from "@vgpu/core"; -import { useEffect, useRef, useState } from "react"; +import { getImageProps } from "next/image"; +import { useEffect, useRef, useState, type ComponentProps } from "react"; import { decodeGltfMesh, meshAspect } from "./mesh"; import { BLOOM_RADIUS, createEve5Renderer, type RenderControls } from "./render"; @@ -14,6 +15,11 @@ class BrowserAdapter implements VGPUAdapter { } const MODEL_URL = "/eve-5/eve-logo.gltf"; +const FALLBACK_DARK_IMAGE_URL = "/eve-5/fallback-dark.webp"; +const FALLBACK_LIGHT_IMAGE_URL = "/eve-5/fallback-light.webp"; +const FALLBACK_IMAGE_WIDTH = 1111; +const FALLBACK_IMAGE_HEIGHT = 364; +const FALLBACK_IMAGE_SIZES = "(min-width: 768px) 1111px, 100vw"; const DEFAULT_LOGO_ASPECT = 78 / 25; const DEFAULT_CONTROLS: RenderControls = { yaw: 0, @@ -31,6 +37,7 @@ const LOGO_RENDER_HEIGHT = 500; const MAX_DEVICE_PIXEL_RATIO = 2; const MAX_ENV_YAW = 0.45; const ENV_YAW_LERP_SPEED = 3; +const CANVAS_FADE_FALLBACK_MS = 800; function getCurrentTheme(): "light" | "dark" { if (typeof document === "undefined") return "light"; @@ -64,18 +71,43 @@ function useResolvedTheme() { return theme; } +function usePrefersReducedMotion() { + const [prefersReducedMotion, setPrefersReducedMotion] = useState(null); + + useEffect(() => { + const media = window.matchMedia("(prefers-reduced-motion: reduce)"); + const syncPreference = () => setPrefersReducedMotion(media.matches); + + syncPreference(); + media.addEventListener("change", syncPreference); + + return () => media.removeEventListener("change", syncPreference); + }, []); + + return prefersReducedMotion; +} + export function EveLogoShader() { const theme = useResolvedTheme(); + const prefersReducedMotion = usePrefersReducedMotion(); const canvasRef = useRef(null); const controlsRef = useRef({ ...DEFAULT_CONTROLS }); const [logoAspect, setLogoAspect] = useState(DEFAULT_LOGO_ASPECT); + const [revealed, setRevealed] = useState(false); useEffect(() => { let cancelled = false; let animationFrame = 0; + let cleanup: (() => void) | undefined; let targetEnvYaw = controlsRef.current.envYaw; let previousFrameTime = performance.now(); + const canvas = canvasRef.current; + resetCanvasVisibility(canvas); + setRevealed(false); + + if (prefersReducedMotion !== false) return; + const updateEnvYaw = (clientX: number) => { const viewportWidth = Math.max(1, window.innerWidth || 1); const normalizedX = Math.max(-1, Math.min(1, (clientX / viewportWidth) * 2 - 1)); @@ -108,8 +140,33 @@ export function EveLogoShader() { const renderer = createEve5Renderer(app.device, format, mesh, { theme: renderTheme }); previousFrameTime = performance.now(); + let disposed = false; + let firstFrameShown = false; + let finishCanvasFade: (() => void) | undefined; + const dispose = () => { + if (disposed) return; + disposed = true; + cancelAnimationFrame(animationFrame); + finishCanvasFade?.(); + resetCanvasVisibility(canvas); + if (!cancelled) setRevealed(false); + renderer.dispose(); + app.device.destroy(); + }; + + app.device.gpu.lost + .then(() => { + if (cancelled) return; + setRevealed(false); + cancelled = true; + dispose(); + }) + .catch(() => { + // The landing page must degrade silently when the GPU process is unavailable. + }); + const draw = (frameTime = performance.now()) => { - if (cancelled) return; + if (cancelled || disposed) return; const deltaSeconds = Math.max(0, (frameTime - previousFrameTime) / 1000); previousFrameTime = frameTime; @@ -122,19 +179,32 @@ export function EveLogoShader() { // shader's @builtin(position) sample different pixels from the back-side targets on DPR > 1. const logicalWidth = Math.max(1, canvas.width - BLOOM_RADIUS * 2); const logicalHeight = Math.max(1, canvas.height - BLOOM_RADIUS * 2); - renderer.render(context.getCurrentTexture().createView(), controlsRef.current, logicalWidth, logicalHeight); + + try { + renderer.render(context.getCurrentTexture().createView(), controlsRef.current, logicalWidth, logicalHeight); + } catch { + cancelled = true; + setRevealed(false); + dispose(); + return; + } + + if (!firstFrameShown) { + firstFrameShown = true; + canvas.style.opacity = "1"; + finishCanvasFade = onCanvasFullyOpaque(canvas, () => { + if (!cancelled) setRevealed(true); + }); + } + animationFrame = requestAnimationFrame(draw); }; draw(); - return () => { - renderer.dispose(); - app.device.destroy(); - }; + return dispose; } - let cleanup: (() => void) | undefined; start() .then((dispose) => { cleanup = dispose; @@ -148,12 +218,29 @@ export function EveLogoShader() { cancelAnimationFrame(animationFrame); window.removeEventListener("pointermove", onPointerMove); cleanup?.(); + resetCanvasVisibility(canvasRef.current); }; - }, [theme]); + }, [theme, prefersReducedMotion]); const logicalSize = getLogicalRenderSize(logoAspect); const paddedWidth = logicalSize.width + BLOOM_RADIUS * 2; const paddedHeight = logicalSize.height + BLOOM_RADIUS * 2; + const fallbackImageOptions = { + alt: "", + width: FALLBACK_IMAGE_WIDTH, + height: FALLBACK_IMAGE_HEIGHT, + sizes: FALLBACK_IMAGE_SIZES, + priority: true, + quality: 95, + } as const; + const { props: fallbackLightImageProps } = getImageProps({ + ...fallbackImageOptions, + src: FALLBACK_LIGHT_IMAGE_URL, + }); + const { props: fallbackDarkImageProps } = getImageProps({ + ...fallbackImageOptions, + src: FALLBACK_DARK_IMAGE_URL, + }); return (
- + + +
@@ -171,6 +268,28 @@ export function EveLogoShader() { ); } +function FallbackImage({ + imageProps, + revealed, + className, +}: { + imageProps: ComponentProps<"img">; + revealed: boolean; + className: string; +}) { + return ( + + ); +} + async function loadMesh() { const response = await fetch(MODEL_URL, { cache: "no-store" }); if (!response.ok) throw new Error(`Failed to load ${MODEL_URL}: ${response.status}`); @@ -211,6 +330,30 @@ function safeLerp(from: number, to: number, amount: number) { return from + (to - from) * safeAmount; } +function resetCanvasVisibility(canvas: HTMLCanvasElement | null) { + if (!canvas) return; + canvas.style.opacity = ""; +} + +function onCanvasFullyOpaque(canvas: HTMLCanvasElement, callback: () => void) { + let done = false; + let timeout = 0; + const finish = () => { + if (done) return; + done = true; + canvas.removeEventListener("transitionend", onTransitionEnd); + window.clearTimeout(timeout); + if (canvas.isConnected) callback(); + }; + const onTransitionEnd = (event: TransitionEvent) => { + if (event.propertyName === "opacity") finish(); + }; + + canvas.addEventListener("transitionend", onTransitionEnd); + timeout = window.setTimeout(finish, CANVAS_FADE_FALLBACK_MS); + return finish; +} + function nextFrame() { return new Promise((resolve) => requestAnimationFrame(() => resolve())); } diff --git a/apps/docs/next.config.ts b/apps/docs/next.config.ts index 6ef14a0ed..c76f94f69 100644 --- a/apps/docs/next.config.ts +++ b/apps/docs/next.config.ts @@ -34,6 +34,7 @@ const config: NextConfig = { images: { formats: ["image/avif", "image/webp"], + qualities: [75, 95], remotePatterns: [ { protocol: "https", diff --git a/apps/docs/public/eve-5/fallback-dark.webp b/apps/docs/public/eve-5/fallback-dark.webp new file mode 100644 index 0000000000000000000000000000000000000000..4a8e645aafeb38ba938e00d9e51b8cf4e1627ca3 GIT binary patch literal 10226 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 literal 0 HcmV?d00001 diff --git a/apps/docs/public/eve-5/fallback-light.webp b/apps/docs/public/eve-5/fallback-light.webp new file mode 100644 index 0000000000000000000000000000000000000000..bacdd69371bc191d273cba06891f6fcd4648276a GIT binary patch literal 5848 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 Wbh4TVq8*^<&|VFxqxc$ literal 0 HcmV?d00001 diff --git a/apps/docs/scripts/render-eve-5.ts b/apps/docs/scripts/render-eve-5.ts index 5be85a7c4..3cdc2ead4 100644 --- a/apps/docs/scripts/render-eve-5.ts +++ b/apps/docs/scripts/render-eve-5.ts @@ -1,3 +1,24 @@ +// This script uses @vgpu/adapter-node, which requires native Vulkan support and a +// new-enough GLIBC. Run it inside the software-Vulkan Docker image when baking +// the static Eve logo fallback: +// +// docker run --rm \ +// -v /home/user/eve-worktrees/eve-migrate-shader:/work \ +// -w /work/apps/docs \ +// -e VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.json \ +// -e LIBGL_ALWAYS_SOFTWARE=1 \ +// -e EVE_LOGO_RENDER_THEME=dark \ +// -e EVE_LOGO_RENDER_WIDTH=1111 \ +// -e EVE_LOGO_RENDER_HEIGHT=364 \ +// browser-webgpu-lab:native-vgpu-node \ +// bash -lc 'xvfb-run -a bash -lc "NODE_OPTIONS=--loader=./scripts/wgsl-node-loader.mjs ./node_modules/.bin/tsx scripts/render-eve-5.ts"' +// +// The Docker image provides GLIBC 2.41, lavapipe/llvmpipe, libvulkan, and xvfb. +// Convert the generated tmp/eve-5-renders//output.png to the desired +// public/eve-5/fallback-.webp with ImageMagick from the same container. +// Set EVE_LOGO_RENDER_THEME=light|dark and EVE_LOGO_RENDER_WIDTH/HEIGHT when +// baking production fallbacks. + import { createHash } from "node:crypto"; import { readFile, mkdir, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; @@ -17,11 +38,14 @@ import { const RUN_ID = new Date().toISOString().replaceAll(":", "-").replace(".", "-"); const OUT_DIR = resolve(process.cwd(), "tmp/eve-5-renders", RUN_ID); const FORMAT: GPUTextureFormat = "rgba8unorm"; -const LOGICAL_WIDTH = 960; -const LOGICAL_HEIGHT = Math.round(LOGICAL_WIDTH / (78 / 25)); +const OUTPUT_WIDTH = readPositiveIntegerEnv("EVE_LOGO_RENDER_WIDTH", 1111); +const OUTPUT_HEIGHT = readPositiveIntegerEnv("EVE_LOGO_RENDER_HEIGHT", 364); +const LOGICAL_WIDTH = Math.max(1, OUTPUT_WIDTH - BLOOM_RADIUS * 2); +const LOGICAL_HEIGHT = Math.max(1, OUTPUT_HEIGHT - BLOOM_RADIUS * 2); const PADDED_SIZE = getPaddedRenderSize(LOGICAL_WIDTH, LOGICAL_HEIGHT); const WIDTH = PADDED_SIZE.width; const HEIGHT = PADDED_SIZE.height; +const THEME = readThemeEnv(); const MODEL_PATH = resolve(process.cwd(), "public/eve-5/eve-logo.gltf"); const DEFAULT_CONTROLS: RenderControls = { yaw: 0, @@ -43,7 +67,7 @@ async function main() { let renderer: ReturnType | undefined; try { - renderer = createEve5Renderer(app.device, FORMAT, mesh); + renderer = createEve5Renderer(app.device, FORMAT, mesh, { theme: THEME }); const output = await renderView(renderer, app.device, DEFAULT_CONTROLS, "output.png"); await renderView(renderer, app.device, { ...DEFAULT_CONTROLS, yaw: -0.49, pitch: 0.31 }, "rotated.png"); await renderView(renderer, app.device, { ...DEFAULT_CONTROLS, wireframe: true }, "wireframe.png"); @@ -51,7 +75,7 @@ async function main() { const log = { runId: RUN_ID, outDir: OUT_DIR, - dimensions: { width: WIDTH, height: HEIGHT, format: FORMAT }, + dimensions: { width: WIDTH, height: HEIGHT, format: FORMAT, theme: THEME }, bloom: { radius: BLOOM_RADIUS, strength: BLOOM_STRENGTH, @@ -118,6 +142,22 @@ async function loadMeshFromDisk(path: string) { }); } +function readPositiveIntegerEnv(name: string, fallback: number) { + const value = process.env[name]; + if (!value) return fallback; + const parsed = Number.parseInt(value, 10); + if (!Number.isFinite(parsed) || parsed <= BLOOM_RADIUS * 2) { + throw new Error(`${name} must be an integer greater than ${BLOOM_RADIUS * 2}.`); + } + return parsed; +} + +function readThemeEnv(): "light" | "dark" { + const value = process.env.EVE_LOGO_RENDER_THEME ?? "dark"; + if (value === "light" || value === "dark") return value; + throw new Error('EVE_LOGO_RENDER_THEME must be "light" or "dark".'); +} + function exactArrayBuffer(buffer: Buffer) { return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); } From b9d02d3041f5381d74f38c1157e92028f5ef811e Mon Sep 17 00:00:00 2001 From: matiasngf Date: Tue, 30 Jun 2026 04:39:58 +0000 Subject: [PATCH 02/15] Match eve fallback bake to desktop DPR --- apps/docs/public/eve-5/fallback-dark.webp | Bin 10226 -> 9326 bytes apps/docs/public/eve-5/fallback-light.webp | Bin 5848 -> 9392 bytes apps/docs/scripts/render-eve-5.ts | 8 +++++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/docs/public/eve-5/fallback-dark.webp b/apps/docs/public/eve-5/fallback-dark.webp index 4a8e645aafeb38ba938e00d9e51b8cf4e1627ca3..55214981edfcb6f06860c27c585e24d943e41594 100644 GIT binary patch literal 9326 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 diff --git a/apps/docs/public/eve-5/fallback-light.webp b/apps/docs/public/eve-5/fallback-light.webp index bacdd69371bc191d273cba06891f6fcd4648276a..d29b0c99a824390735d31adaa001ae2394b0dd2c 100644 GIT binary patch literal 9392 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 T$k{Q!s{h+%>-S0C|9SokI&BPr literal 5848 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 Wbh4TVq8*^<&|VFxqxc$ diff --git a/apps/docs/scripts/render-eve-5.ts b/apps/docs/scripts/render-eve-5.ts index 3cdc2ead4..e52cce7a4 100644 --- a/apps/docs/scripts/render-eve-5.ts +++ b/apps/docs/scripts/render-eve-5.ts @@ -8,8 +8,8 @@ // -e VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.json \ // -e LIBGL_ALWAYS_SOFTWARE=1 \ // -e EVE_LOGO_RENDER_THEME=dark \ -// -e EVE_LOGO_RENDER_WIDTH=1111 \ -// -e EVE_LOGO_RENDER_HEIGHT=364 \ +// -e EVE_LOGO_RENDER_WIDTH=2222 \ +// -e EVE_LOGO_RENDER_HEIGHT=728 \ // browser-webgpu-lab:native-vgpu-node \ // bash -lc 'xvfb-run -a bash -lc "NODE_OPTIONS=--loader=./scripts/wgsl-node-loader.mjs ./node_modules/.bin/tsx scripts/render-eve-5.ts"' // @@ -17,7 +17,9 @@ // Convert the generated tmp/eve-5-renders//output.png to the desired // public/eve-5/fallback-.webp with ImageMagick from the same container. // Set EVE_LOGO_RENDER_THEME=light|dark and EVE_LOGO_RENDER_WIDTH/HEIGHT when -// baking production fallbacks. +// baking production fallbacks. For a 1111x364 CSS canvas on DPR 2 screens, +// render at 2222x728 and downsample the WebP to 1111x364 so the fixed 16px +// bloom padding matches the live canvas in CSS pixels. import { createHash } from "node:crypto"; import { readFile, mkdir, writeFile } from "node:fs/promises"; From f64744c7ec44ccbe72c4b5b9445806d6b16fa769 Mon Sep 17 00:00:00 2001 From: matiasngf Date: Tue, 30 Jun 2026 04:46:10 +0000 Subject: [PATCH 03/15] Match eve fallback image fill mode --- .../docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx index 9ca73f586..1b483c795 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx @@ -283,7 +283,7 @@ function FallbackImage({ aria-hidden="true" role="presentation" decoding="async" - className={`${className} absolute inset-0 size-full object-cover transition-opacity duration-700 ease-linear ${ + className={`${className} absolute inset-0 size-full object-fill transition-opacity duration-700 ease-linear ${ revealed ? "opacity-0" : "opacity-100" }`} /> From e42aef9a9df3fc1bc05b75d002177787499e91a9 Mon Sep 17 00:00:00 2001 From: matiasngf Date: Tue, 30 Jun 2026 04:51:18 +0000 Subject: [PATCH 04/15] Size eve fallback image from canvas height --- .../docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx index 1b483c795..665165bf2 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx @@ -283,7 +283,7 @@ function FallbackImage({ aria-hidden="true" role="presentation" decoding="async" - className={`${className} absolute inset-0 size-full object-fill transition-opacity duration-700 ease-linear ${ + className={`${className} absolute left-1/2 top-0 h-full w-auto max-w-none -translate-x-1/2 transition-opacity duration-700 ease-linear ${ revealed ? "opacity-0" : "opacity-100" }`} /> From 3435a64000e26423087050bf2db4afdb5727f79a Mon Sep 17 00:00:00 2001 From: matiasngf Date: Tue, 30 Jun 2026 04:58:19 +0000 Subject: [PATCH 05/15] Hardcode eve fallback image aspect ratio --- .../docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx index 665165bf2..87faea02b 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx @@ -19,6 +19,7 @@ const FALLBACK_DARK_IMAGE_URL = "/eve-5/fallback-dark.webp"; const FALLBACK_LIGHT_IMAGE_URL = "/eve-5/fallback-light.webp"; const FALLBACK_IMAGE_WIDTH = 1111; const FALLBACK_IMAGE_HEIGHT = 364; +const FALLBACK_IMAGE_ASPECT_RATIO = `${FALLBACK_IMAGE_WIDTH} / ${FALLBACK_IMAGE_HEIGHT}`; const FALLBACK_IMAGE_SIZES = "(min-width: 768px) 1111px, 100vw"; const DEFAULT_LOGO_ASPECT = 78 / 25; const DEFAULT_CONTROLS: RenderControls = { @@ -286,6 +287,7 @@ function FallbackImage({ className={`${className} absolute left-1/2 top-0 h-full w-auto max-w-none -translate-x-1/2 transition-opacity duration-700 ease-linear ${ revealed ? "opacity-0" : "opacity-100" }`} + style={{ aspectRatio: FALLBACK_IMAGE_ASPECT_RATIO }} /> ); } From fea31cc068465e48f57e968c8fd0dacd62aeacb1 Mon Sep 17 00:00:00 2001 From: matiasngf Date: Tue, 30 Jun 2026 05:07:33 +0000 Subject: [PATCH 06/15] Bake eve fallback without bloom padding --- .../components/eve-logo-shader/index.tsx | 30 +++++---- .../components/eve-logo-shader/render.ts | 25 +++++--- apps/docs/public/eve-5/fallback-dark.webp | Bin 9326 -> 8900 bytes apps/docs/public/eve-5/fallback-light.webp | Bin 9392 -> 6244 bytes apps/docs/scripts/render-eve-5.ts | 57 +++++++++++++----- 5 files changed, 74 insertions(+), 38 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx index 87faea02b..d587b4dba 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx @@ -17,10 +17,10 @@ class BrowserAdapter implements VGPUAdapter { const MODEL_URL = "/eve-5/eve-logo.gltf"; const FALLBACK_DARK_IMAGE_URL = "/eve-5/fallback-dark.webp"; const FALLBACK_LIGHT_IMAGE_URL = "/eve-5/fallback-light.webp"; -const FALLBACK_IMAGE_WIDTH = 1111; -const FALLBACK_IMAGE_HEIGHT = 364; +const FALLBACK_IMAGE_WIDTH = 1095; +const FALLBACK_IMAGE_HEIGHT = 348; const FALLBACK_IMAGE_ASPECT_RATIO = `${FALLBACK_IMAGE_WIDTH} / ${FALLBACK_IMAGE_HEIGHT}`; -const FALLBACK_IMAGE_SIZES = "(min-width: 768px) 1111px, 100vw"; +const FALLBACK_IMAGE_SIZES = "(min-width: 768px) 1095px, calc(100vw - 16px)"; const DEFAULT_LOGO_ASPECT = 78 / 25; const DEFAULT_CONTROLS: RenderControls = { yaw: 0, @@ -36,6 +36,7 @@ const DEFAULT_CONTROLS: RenderControls = { }; const LOGO_RENDER_HEIGHT = 500; const MAX_DEVICE_PIXEL_RATIO = 2; +const FALLBACK_IMAGE_PADDING = BLOOM_RADIUS / MAX_DEVICE_PIXEL_RATIO; const MAX_ENV_YAW = 0.45; const ENV_YAW_LERP_SPEED = 3; const CANVAS_FADE_FALLBACK_MS = 800; @@ -279,16 +280,19 @@ function FallbackImage({ className: string; }) { return ( - +
+ +
); } diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/render.ts b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/render.ts index 677c2fce6..d04bcb886 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/render.ts +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/render.ts @@ -91,13 +91,15 @@ export function createEve5Renderer( device: Device, format: GPUTextureFormat, mesh: MeshData, - options: { thicknessScale?: number; theme?: "light" | "dark" } = {}, + options: { thicknessScale?: number; theme?: "light" | "dark"; paddingRadius?: number; bloom?: boolean } = {}, ) { const studioCubemap = createStudioCubemap(device); renderStudioCubemap(device, studioCubemap); const orbitTarget = meshOrbitTarget(mesh); const thicknessScale = options.thicknessScale ?? meshThicknessScale(mesh.bounds); const isLight = options.theme === "light"; + const paddingRadius = options.paddingRadius ?? BLOOM_RADIUS; + const bloomEnabled = options.bloom ?? true; const glassBackShader = device.createShader(compile(glassBackWgsl)); const glassFrontShader = device.createShader(compile(glassFrontWgsl)); @@ -261,7 +263,7 @@ export function createEve5Renderer( size: 16, usage: ["uniform", "copy_dst"], }); - compositeParamsBuffer.write(new Float32Array([BLOOM_STRENGTH, 0, 0, 0])); + compositeParamsBuffer.write(new Float32Array([bloomEnabled ? BLOOM_STRENGTH : 0, 0, 0, 0])); const blurSampler = device.gpu.createSampler({ label: "eve-5-bloom-sampler", magFilter: "linear", @@ -284,7 +286,7 @@ export function createEve5Renderer( let bloomTargets: BloomTargets | undefined; const writeParams = (target: { buffer: Buffer }, controls: RenderControls, logicalWidth: number, logicalHeight: number, passKind: number) => { - const padded = getPaddedRenderSize(logicalWidth, logicalHeight); + const padded = getPaddedRenderSize(logicalWidth, logicalHeight, paddingRadius); const fovRad = degreesToRadians(controls.fov); const verticalScale = padded.height / logicalHeight; const fovEff = 2 * Math.atan(verticalScale * Math.tan(fovRad * 0.5)); @@ -318,7 +320,7 @@ export function createEve5Renderer( }; const ensureBloomTargets = (logicalWidth: number, logicalHeight: number) => { - const padded = getPaddedRenderSize(logicalWidth, logicalHeight); + const padded = getPaddedRenderSize(logicalWidth, logicalHeight, paddingRadius); if (bloomTargets?.width === padded.width && bloomTargets.height === padded.height) return bloomTargets; bloomTargets?.scene.destroy(); bloomTargets?.backMaterial.destroy(); @@ -520,13 +522,13 @@ export function createEve5Renderer( pass.end(); }; - const renderComposite = (view: GPUTextureView, targets: BloomTargets) => { + const renderComposite = (view: GPUTextureView, targets: BloomTargets, bloomTexture: GPUTexture = targets.vertical) => { const bindGroup = device.gpu.createBindGroup({ label: "eve-5-bloom-composite-bind-group", layout: compositePipeline.getBindGroupLayout(0), entries: [ { binding: 0, resource: targets.scene.createView() }, - { binding: 1, resource: targets.vertical.createView() }, + { binding: 1, resource: bloomTexture.createView() }, { binding: 2, resource: blurSampler }, { binding: 3, resource: { buffer: compositeParamsBuffer.gpu } }, ], @@ -581,6 +583,10 @@ export function createEve5Renderer( renderLightComposite(target, targets); return; } + if (!bloomEnabled) { + renderComposite(target, targets, targets.scene); + return; + } renderBlur(targets.scene, targets.horizontal, [1, 0], true); renderBlur(targets.horizontal, targets.vertical, [0, 1], false); renderComposite(target, targets); @@ -627,10 +633,11 @@ type BloomTargets = { vertical: GPUTexture; }; -export function getPaddedRenderSize(width: number, height: number) { +export function getPaddedRenderSize(width: number, height: number, paddingRadius = BLOOM_RADIUS) { + const padding = Math.max(0, Math.round(paddingRadius)) * 2; return { - width: Math.max(1, Math.round(width)) + BLOOM_RADIUS * 2, - height: Math.max(1, Math.round(height)) + BLOOM_RADIUS * 2, + width: Math.max(1, Math.round(width)) + padding, + height: Math.max(1, Math.round(height)) + padding, }; } diff --git a/apps/docs/public/eve-5/fallback-dark.webp b/apps/docs/public/eve-5/fallback-dark.webp index 55214981edfcb6f06860c27c585e24d943e41594..d3cd2edd0a748bf1b2018a13277316beb6e6d4b0 100644 GIT binary patch literal 8900 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 literal 9326 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 T$k{Q!s{h+%>-S0C|9SokI&BPr diff --git a/apps/docs/scripts/render-eve-5.ts b/apps/docs/scripts/render-eve-5.ts index e52cce7a4..9d218766b 100644 --- a/apps/docs/scripts/render-eve-5.ts +++ b/apps/docs/scripts/render-eve-5.ts @@ -8,8 +8,10 @@ // -e VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.json \ // -e LIBGL_ALWAYS_SOFTWARE=1 \ // -e EVE_LOGO_RENDER_THEME=dark \ -// -e EVE_LOGO_RENDER_WIDTH=2222 \ -// -e EVE_LOGO_RENDER_HEIGHT=728 \ +// -e EVE_LOGO_RENDER_WIDTH=1095 \ +// -e EVE_LOGO_RENDER_HEIGHT=348 \ +// -e EVE_LOGO_RENDER_PADDING=0 \ +// -e EVE_LOGO_RENDER_BLOOM=0 \ // browser-webgpu-lab:native-vgpu-node \ // bash -lc 'xvfb-run -a bash -lc "NODE_OPTIONS=--loader=./scripts/wgsl-node-loader.mjs ./node_modules/.bin/tsx scripts/render-eve-5.ts"' // @@ -17,9 +19,9 @@ // Convert the generated tmp/eve-5-renders//output.png to the desired // public/eve-5/fallback-.webp with ImageMagick from the same container. // Set EVE_LOGO_RENDER_THEME=light|dark and EVE_LOGO_RENDER_WIDTH/HEIGHT when -// baking production fallbacks. For a 1111x364 CSS canvas on DPR 2 screens, -// render at 2222x728 and downsample the WebP to 1111x364 so the fixed 16px -// bloom padding matches the live canvas in CSS pixels. +// baking production fallbacks. Fallback images are content-only: render without +// bloom or padding, then place them inside the padded canvas box in CSS so the +// animated shader appears to "turn on" around the same logo geometry. import { createHash } from "node:crypto"; import { readFile, mkdir, writeFile } from "node:fs/promises"; @@ -40,11 +42,13 @@ import { const RUN_ID = new Date().toISOString().replaceAll(":", "-").replace(".", "-"); const OUT_DIR = resolve(process.cwd(), "tmp/eve-5-renders", RUN_ID); const FORMAT: GPUTextureFormat = "rgba8unorm"; -const OUTPUT_WIDTH = readPositiveIntegerEnv("EVE_LOGO_RENDER_WIDTH", 1111); -const OUTPUT_HEIGHT = readPositiveIntegerEnv("EVE_LOGO_RENDER_HEIGHT", 364); -const LOGICAL_WIDTH = Math.max(1, OUTPUT_WIDTH - BLOOM_RADIUS * 2); -const LOGICAL_HEIGHT = Math.max(1, OUTPUT_HEIGHT - BLOOM_RADIUS * 2); -const PADDED_SIZE = getPaddedRenderSize(LOGICAL_WIDTH, LOGICAL_HEIGHT); +const PADDING_RADIUS = readNonNegativeIntegerEnv("EVE_LOGO_RENDER_PADDING", 0); +const BLOOM_ENABLED = readBooleanEnv("EVE_LOGO_RENDER_BLOOM", false); +const OUTPUT_WIDTH = readPositiveIntegerEnv("EVE_LOGO_RENDER_WIDTH", 1095, PADDING_RADIUS * 2); +const OUTPUT_HEIGHT = readPositiveIntegerEnv("EVE_LOGO_RENDER_HEIGHT", 348, PADDING_RADIUS * 2); +const LOGICAL_WIDTH = Math.max(1, OUTPUT_WIDTH - PADDING_RADIUS * 2); +const LOGICAL_HEIGHT = Math.max(1, OUTPUT_HEIGHT - PADDING_RADIUS * 2); +const PADDED_SIZE = getPaddedRenderSize(LOGICAL_WIDTH, LOGICAL_HEIGHT, PADDING_RADIUS); const WIDTH = PADDED_SIZE.width; const HEIGHT = PADDED_SIZE.height; const THEME = readThemeEnv(); @@ -69,7 +73,7 @@ async function main() { let renderer: ReturnType | undefined; try { - renderer = createEve5Renderer(app.device, FORMAT, mesh, { theme: THEME }); + renderer = createEve5Renderer(app.device, FORMAT, mesh, { theme: THEME, paddingRadius: PADDING_RADIUS, bloom: BLOOM_ENABLED }); const output = await renderView(renderer, app.device, DEFAULT_CONTROLS, "output.png"); await renderView(renderer, app.device, { ...DEFAULT_CONTROLS, yaw: -0.49, pitch: 0.31 }, "rotated.png"); await renderView(renderer, app.device, { ...DEFAULT_CONTROLS, wireframe: true }, "wireframe.png"); @@ -79,8 +83,10 @@ async function main() { outDir: OUT_DIR, dimensions: { width: WIDTH, height: HEIGHT, format: FORMAT, theme: THEME }, bloom: { - radius: BLOOM_RADIUS, - strength: BLOOM_STRENGTH, + enabled: BLOOM_ENABLED, + runtimeRadius: BLOOM_RADIUS, + radius: PADDING_RADIUS, + strength: BLOOM_ENABLED ? BLOOM_STRENGTH : 0, threshold: BLOOM_THRESHOLD, logical: { width: LOGICAL_WIDTH, height: LOGICAL_HEIGHT }, padded: { width: WIDTH, height: HEIGHT }, @@ -144,16 +150,35 @@ async function loadMeshFromDisk(path: string) { }); } -function readPositiveIntegerEnv(name: string, fallback: number) { +function readPositiveIntegerEnv(name: string, fallback: number, minimumExclusive = 0) { const value = process.env[name]; if (!value) return fallback; const parsed = Number.parseInt(value, 10); - if (!Number.isFinite(parsed) || parsed <= BLOOM_RADIUS * 2) { - throw new Error(`${name} must be an integer greater than ${BLOOM_RADIUS * 2}.`); + if (!Number.isFinite(parsed) || parsed <= minimumExclusive) { + throw new Error(`${name} must be an integer greater than ${minimumExclusive}.`); } return parsed; } +function readNonNegativeIntegerEnv(name: string, fallback: number) { + const value = process.env[name]; + if (!value) return fallback; + const parsed = Number.parseInt(value, 10); + if (!Number.isFinite(parsed) || parsed < 0) { + throw new Error(`${name} must be a non-negative integer.`); + } + return parsed; +} + +function readBooleanEnv(name: string, fallback: boolean) { + const value = process.env[name]; + if (!value) return fallback; + const normalized = value.toLowerCase(); + if (["1", "true", "yes", "on"].includes(normalized)) return true; + if (["0", "false", "no", "off"].includes(normalized)) return false; + throw new Error(`${name} must be a boolean value.`); +} + function readThemeEnv(): "light" | "dark" { const value = process.env.EVE_LOGO_RENDER_THEME ?? "dark"; if (value === "light" || value === "dark") return value; From 88585afc03ef711cd91531bb0ef954ae8ec849d9 Mon Sep 17 00:00:00 2001 From: matiasngf Date: Tue, 30 Jun 2026 05:10:45 +0000 Subject: [PATCH 07/15] Use cache-busted eve content fallbacks --- .../(home)/components/eve-logo-shader/index.tsx | 4 ++-- .../docs/public/eve-5/fallback-dark-content.webp | Bin 0 -> 8900 bytes .../public/eve-5/fallback-light-content.webp | Bin 0 -> 6244 bytes 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 apps/docs/public/eve-5/fallback-dark-content.webp create mode 100644 apps/docs/public/eve-5/fallback-light-content.webp diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx index d587b4dba..d434a3bfd 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx @@ -15,8 +15,8 @@ class BrowserAdapter implements VGPUAdapter { } const MODEL_URL = "/eve-5/eve-logo.gltf"; -const FALLBACK_DARK_IMAGE_URL = "/eve-5/fallback-dark.webp"; -const FALLBACK_LIGHT_IMAGE_URL = "/eve-5/fallback-light.webp"; +const FALLBACK_DARK_IMAGE_URL = "/eve-5/fallback-dark-content.webp"; +const FALLBACK_LIGHT_IMAGE_URL = "/eve-5/fallback-light-content.webp"; const FALLBACK_IMAGE_WIDTH = 1095; const FALLBACK_IMAGE_HEIGHT = 348; const FALLBACK_IMAGE_ASPECT_RATIO = `${FALLBACK_IMAGE_WIDTH} / ${FALLBACK_IMAGE_HEIGHT}`; diff --git a/apps/docs/public/eve-5/fallback-dark-content.webp b/apps/docs/public/eve-5/fallback-dark-content.webp new file mode 100644 index 0000000000000000000000000000000000000000..d3cd2edd0a748bf1b2018a13277316beb6e6d4b0 GIT binary patch literal 8900 zcmaKubC4y#vZqhmwrx(^w(ag|d)l@&ZQHhO+nAoVrg7Su_St*yyZ61_*p1ElCnF=P zDkJi%U!|I?lvFP{0H7@?rlO_7L)!QczYzm04~$+K(f~{d4>4S{2$D~m1rlJw#I*O> z(I@;VeTRSgweb;FC2w&t$y_?27wt7G@;G)0I`=6`2Rc49=9hg2d@dX&#Gp6~SP4@G z;xat8f$m8*2kb%gf-N9q5QhKEKEg}EUBk`ZhVTy%XMjf;_gD<%2k@Qc)$yvJZEr8o zO6aBhCK2WybTI%F<`HxiAnHE_G5msf`TNxH9(d5V2kHT-ewqE{|3m?S;y}`YM%5E1KlFcw z97xd~e^*q;qs!qV>Yge!t6BW)eJRND?K$A~(qsS|cDSvMrIcw&AbWNHPjWeHN-hSjn4lJG3^R zk0rhe_kl^_!bfW<1zBw5yspyT7I@mAa4XYY2v&}Vq%;iTuwsQezoWvf=%(4t^TRsz z3FY-u7Th#*mL8Dz-rABj`D6&GNShBt2K;#vE>*4=f#A6Q>6Ff)8rCJnQc$Y6?Nr5* zVa3ucNM~jW#)5XQ94_1|vMG{Ghpzc@jD*iXf21+Md#jIw4!AU`pek=OwjCx7_pQGu zW`5ZjZOx2stn}5{AZ?ve+krN*JyFtrbNVwqxGkqLieUwoDGFL-xj1V!0(Q1 zds8_dvA)&NH1zCHL2yf?*q~A(*{8D)>;nC)B4SEwD)Ml<;buJ3hiM z=vjOXb0K4&nY*QwTRMnR(VaJjLxc^->V@{o8Lm4}Fqwma3o3 z$K|-L$w@b_Rgt}mxbHvX7{Afk0sePhQ_niL&%Z)wgKrvnW~&&5gTQh9JkszjuDV2w zW^fM?rAfF=Fn&@RbI8k}y#Ed@BPat^j?HUCJrr{?tdwvXr{z5XbpJn)p*-%wM9z0` zZj8*#;%CN_v9m>b87^z>MgDVgsoS0%FhDMbr(*YfkK2G_4%NZm1AbHOz7licnrs5| zD^*CC6zKWnS{DEBqP2*!+ZAhdCiK&5)QATyy(TmoWMrwK1WkH!GYeKoAPO>Fy!oE{~5qd_zluFhFhVi`GCYk*!nW8~6+{ z`QiAtg@Opn>%X3)Wg=2EkLB`TI!b#>*?jww)o(@nt;xuJ#QAwjAC@;baA#+r@e`P*@VcB^|wfo*{*~ z{<2!(qHQhfbtfMgo*U>RjPArnxBH;z0csD*%k&K;r^i6>IU$DMi$IuIvU^4<)xK>Q z2854dAxEO!e>9y74Vw{Kk;?#V*Fe~2;LO^KCL{7AIBIx!YCw-F_P>$&4@E{$&iaCAVP;`J(w(}iH;lW21bIuvBmYh1 ze-rkf^ncvHdd{HV?I>0=%+{MPw6seBb9L3y_8X=v-Yx$3Ed}4GTX?i+}zy$EBq?K$f$U2VHeo{3I#kt(|o5AYr1w;|6(*W)x zL+^~HJ-a670gjBo<0z&UgyXCvSkX1-4+|@E$n(-zcw&f)mZ?hJ!#^)FHG;p_=K1pO zsGpO}Hh~>E&*L1r#_()EsnpdO%0@S633K~!%v?PJ>9aUJ3XMpgmxcKIOj^R`)a#>D z)OG>2?O9&Fyn!jpHyfa)Cg|_B^_dHfz6B4vVmiT^k3z=C;lQw8aRM+|rlF0CN4+R1 z+!b!r#A&k`YqNzTKSEJCANB}5$2t{lu!#Tp>k(gMJ=2?lS~qOFx1EU1`^5)%=tu3^ z8vU*?Pz0BsZ^vxe+~P{bm2$ob&k237Tn8~4t+QrytyJf^MeP8Qn(F~G%9YB&5%I~_ zw7<@D{<%)|iih!u=;y;99KYQLF9x6IVhyKn?f~(7b5sGPOCvXD(q!K){?Q875Htue zcmdhiuvVSfZ}OL6Eyh*%nx#m6$A(MQnCfim@FB%P|8F7uA8kws0(^mfRs#U#sfcOd zmC&h-`SSt5i^29H&%HoDZIBzbP$Ov4KXGGzo+LO-HF~RV|4{^tV~}y#S}>oIs_!&h z5V{hQi*X4WwY0FLzigdifqEgQ>^(Hy5Kh=KySEAZ$Pmj1)z-TiYvFIM1ri0me&^t6 zMqPFjjKj5*UXVNa)kIrD#FWfB@4aA9haO`IId8JgmYY0mpum=9SSw;t%--&z`OOJq zU}oE1w?FWLwb@t4%$RwKKJHtC5gHV!IHH1sHzx&Bd{V^to~W0!Jd9+o)=61GF^jrv z%qDq3!OeQaeGV&CoJUv*13@pCJ-`OnI)o*2N_~EOoy?UQsFOfj0)i{#JU5ED)XPs)F9h(=JLW%);M#& zC!RU()oiY{P*dDnpV^xr@%`nZ&+_~^WmrfN5>2zLoPH|#->pD___GfpRNZ>+FiC2_ zs9!C#_$bf&Q01tfYTsgjy|$Y#YM^8t9%=$g+Hs3@o20IV)>c|OjtRvQ+(*IE-zU~G ztFRegDWIa;9U{AMmKzP=2ZL@cLicv%phQbi<{u#-Fs@Xym#;Yt%CXWy{hEyOsI02@wQ7EuLMO+C*?NNkM9w z(%K0>F_W`H6)oe}1S|()nqP8X2vJF@61iv$%rm^()G%(cPOUkt1bGF2Cx|Sulxi!% zF1_*uq;-zeDE;Q^tMjSE`^mekY3u#u-j?WPfw#ZL!M^O2G{nZhPdA_fGyF35XXKz8 z;7LXoI|B%86wUuhORaFTN7$Pzd6AcWo|V^ z8O{rL0(e^S`0a48q2@=)Q`|DQF}jeB>_Py*AKu;K{4FUHmyV8 z;W7Z%a11>NY_tneibBqWuZ^5}s}ENB(+-dVAQlD1H#s+7V8&vQ0&u>`E@Id$L$~mN zW7(H=4u3woJIh_~vkhtsWq#IKS*^sOoB39wMg8_fIu-Yt@()hz!iYC&9Tub~l(Y>v zNB3kOAR70cAa(p|x`%Mx38hbw4SYIiEL9tS(2@SKjS*EN(k~2khuO6pEY7cuxerv#PN7Glx`XVuF48j=2g_!V^A&APk zvpf`eUjco^_f7@a4`1>O-W?bWV{u)6WG6r$+JsQNV|F9BR8crn^RViBbLlTkA$-2g z_3TW_uZ-u;HSgcEywG}8-oZfT=URr&7U+CS&iHc6dKfb>98)-bv$TNe@!DJ!))!0U zQ&!y-Y`oXUj*<6OG%p!FB zD~;dt{hY~qG-Oz?qovh!%-AA%Jj~))Y@I`h!n@ViV@W8q%~`x7F>Lw~MwM7p`=g#8 zF*G7J3V;#Mokv_GG^xeSpC4&TJdl}$p9FS{`c%RLvm&nETAODxynVTIRJq&CiH<37 zY&pQ6kAx>@%2l(&XGrX*k7(NJ#f;@&iA(sAz>fwk?S_ihU7c!`=5Z)nUbf~-lbn&+ zWTN&BB5NqIOVqjMhvv;;(6qoatf#rg=ob;61ZNKQDVTf#k^D{EI^PcMx&XcjwnlYt zxR~lXlPG{!-IvRp3cTtrgOnb@M_qWF*4GEzV5T39{?P?kur)1^`8L1~Id}g& zyT3d1CDrUwK1jlN&J#18u=s;pH%DJ}nzVucL~~I!>C@Ow78av3KO~n!bE8X1XF%1; zyKw3Dacexo7)hgJeL{E9O|U z(ltp@^Byns_r)}@6wNZoCiK@9FCU?;c`|_$PhwI7-`z9=Yai5s(F_Jy*TsA+fKQj| zcKL96>b?!g75%b|AYS1=CsibD`m7Q`q{c-lZ|6V*ZJ;Vl=w#+lam^kUeo*5r)TM89D=C<3 zm-VGofk?_jj2mVVS`Il6v@pLv=%|ukKt30~O5Dzt<2wHAjx*o7Ex z^E$ws*nt>M`z#K(=65khwOWc?$R7mY2-0Jz;gDv+0u9YP40aQ?Y+CkD_@Lm3I($NY zfjQyY7ek@H8)^J)25ALwZZ$$^Oyg%+(ns8N`8CuFb9=Q=HUI2`m#P|PF?4_=_wi@^%-ZZ-&ZE=;X;bJfG= z-vu+ixKg6_*bI|H2u6AP&RJIHcoL`Z9miY$Jn6)t#H_PO8D#K3N3^9#@K9!ppckyL zAH3hHFqm!}zMs^!g{01$LrXy(%>NI{t_O=jb zl@>q)$?iOwS`Hmk`yTsgI`pe-`vH4Hz0)%lZk4 zw#(d+1i`Q%Mwe*OFdDYvzb3-qgJo<9$#3iTYZz_n7jb zqdvUUxnT_EjM}MP(MjuAEMjShuN2E+n#^=~ ze(yr$jHMPDJqHz-_m>zKZNHVu4&3GRNC(rhB&J7wvwh6-xL@`HxS{WucqE$;%Y zrJ1-}7|Y_#G9*n~eP1vhrOoe2uH0&HfBA`np;@E<+#8qsBY5ck&}8XGWCCcD9#M3h&X!CMFw0#Z>D+Dag%BTs1I(!Y30~O}Tl&YZv`@yA;8B{aEj2!D4#^GfU!@ODiFP5_aa`!i0-fomysm;$rq};K zFf)3Ut(^Ap%ql63VgI_BN96ganmf-aYm=;LVJQX}a#daplTAS*KDo;0e`2G*WZ=|ChQ|l6SvRIEq{^&=QI zweZGpkeD-7Iz#`0cHp|s6ntC(I6rbq+V_Z|$fvMMjBMzFz0fx?2-<|yx}@)xH^xW2 zlI|k6E2MT+5>2!PYMtGQ)`*oYKDNVi3G%lNPG6K7I8nY_Ngy6r8-1-D_#>2|lxrp7-r*E{5>3xNT0N9^Z7k|e zC>`}*r21jyzhQrTa)Vf$dsUBe%7q21!s39kP*_(VSHy%a9w)vep-VKsS<{pR^nD#S34Y6iVVicqdF*I)o|ZY|uFfwsB^kIU z$2S`9@@Z$gQr)!;;!l=K zk*5yPVO{;3@8%$8(VuFh#twqU+XgEH*Mm4DX_;lgM{5L-Lv0$8aMDJ-Ex$Nc zDZa2MEc9o$ft9DjUrauqRk3xyQV9A(3EeMRMT!kiQA>(&>QwXNHH4mpy*=leOP9@( z3dgSx>1UWT4dok9(^6X?fkha3RzD~B0dL&4OexUVqrBZT+agr3`W+sqU4O-0&>nL} z@H?{vWOsxtBQ7Jdl9w{=)x-650!Evxr;8O(R$pt-(Ly81o3W58`pbAf;@kry2^duH z#p$Mvk&LAgU#1F*Iomz^#l^<2O2gHM;Lr*WKH-S_;-bNsx%l?oP*{ToN7>`SA^sGq zAw?ZvW_d+LMp?F4Sb4Q92l7`@`3Q$~Yb1(U#32eJRt<`dZs>)3{QMlEK3OcbNK`EC zl`Ru>oktdNs?NJWL}ttU?&crBo-+6ckEQkSkN_zs^{aYwF%7PRYg51%?RAh~Q2RwN9s?|!{jBm+=6=c5 zP{cA83)VDJs?m~K(pFb;qv;xsV4zi1yrAkmr)?p!^KsXR##!)LYL%>b7>Yv>>C>C& zp~Fc$X2$*8w6Q=HEPJuuL-+Q?(*7`H4vEi$JpAT^%sl1ob?j5%{oPaIjZm3Tk+jHTi)u)ZHx)cEkjU=x#$S}Apk^WL9^U+M!=k?()z<5`>k zB2OD;;iu+od)j0_53lwwK!f`*)lmQ^ABujj)~nrNaYLV!nxu^f_qHZkTf43-tL}}- z==>;K?alQMKg}pnh=C`Cd7%G2ZX*ntVp6j%6ifm$bo*msjCk&AqPar{3L^{iH@eaR z3oc)0Y^t=sw#gg6-o<+jWD>V(5L)+ihd&bV_2!A${45BqH6467@Rakq2Ku!Hae;M% z1#Ls1;6-W_MDU_14ayg>(U4dqLFRt>j!Eu9ca_`-5h98)ddEge)@LJA{5iwfo}AXt zR0iw!%{x6arjv_%Dl|=pi7~?9`5~69U2Fxqv-`XG|LrHS3#CP3|! z*Pq``PC%2AT9Qs=A>%2nJg{i%WKF32Yii}#)pE>Oe-wp&APbBBh%Yyz^slN<##U6& zK#_a|)3xp|0vg5*km3j}+WTcM#UIsHVm_d&8HzP}PaT}4{>U$ZyLfsKggDJJ4I6WN zV!V+$bbUi=^eXG}wo}-~)kGjrjqlJhE>CUQU;6+fR>I&JLJ0NIvul!mbBH_NRF6L9 zlXtE7Pgl);lgis&!AMqal3MI~-2~BYPD~ovh7?cUUjZA=npv3XQa;CJr;)s})BONb z>ABfNtt}5*$f#yq>k(pD*g91=csfctM+M4}60@%%I-6U@1cPN_AEG zKwV6@z%4)LMh#7xjytv$mHcJVFdHVcdN~ z>>icyk%otQCG_0&v%_wLko8c}*jccaxI`LhPwtxU>)2Vf&uEN%h2JE80kGf}zdv)o zuTu)*2zzAbk+mpyVkFTu;(BV6F}Y!e9g?hNMBVqa+L2yzM5hGynA@&-dK!9Sda&8l zq(U+=&8TVAAp^uiw0Wu5$s_gxR)vj1c_7RNvcrsqA9EY}97IAKOB#%6vk>{w_(kYn)e}Mi zgasA>&AN`n?TXr^Z?jioU+&CWwI$A@M14Q z^84J$M>2Jx_m;RB-W)x4L=R)Y&aBaw2}H7X-AUF?>rXi@{jq!-I;Ot5WpLanu5ZoS z!f_Ju&K-Z!i>8ZlaD-A-ik0AgiY*Xa?4GLAvRhgVEE&%MZFB8v!%gS5$SO?ym&j~5|Hty7$Kiwp{6y)W1*pdCpWT)6{FZcx+%`+7^ei*^0)*$Qm*>|D z#goo^{Up)W3oL;X6#SIhpBZ_7{Z<_HW!$h>olYM5@V0X1X*hz8bbkWmi#{%!RP%{d zM`Q6k=15~!U3RC?W_1kc+0PE`6P1*g(!A}}-I|AeB3eZZ3Un!om~naH<2F;p5bAub zq|LXPP2E+-^7LY_dw#8jB5?QiRYx#qO%h3Kd*2G#At^y?7T&} zH806MYlv`iU!y=gbfK?4(+wvACe)hmZHWzlOy%);hi5F{s;G<|$e+`06)FExjuU=4 zgEeSnbP2h7%7k}Zu_v=hwqqxC%b)0zmSg(&Q(uovTvc|f5Xj|%&KxwVWQ0+Ly4vwV zoo-(C$sH+r=B(t?SVYAZs9Bf7C<@_89Ge73G28V)Z{pKxJOlMZG7ZbQ<^qIh^1@eX vhlZkuHBbvq8NY*0zsegZAk-Z@m}>7xEc}Og&3dim!(i__zWrk+{4e`o+#zYz literal 0 HcmV?d00001 diff --git a/apps/docs/public/eve-5/fallback-light-content.webp b/apps/docs/public/eve-5/fallback-light-content.webp new file mode 100644 index 0000000000000000000000000000000000000000..c9a987a9e507a1f7dc30dbe94008874d8b3c16ae GIT binary patch literal 6244 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 Date: Tue, 30 Jun 2026 05:13:23 +0000 Subject: [PATCH 08/15] Import eve fallback images --- .../[lang]/(home)/components/eve-logo-shader/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx index d434a3bfd..9c4b3fca4 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx @@ -2,6 +2,8 @@ import { App, Device, type VGPUAdapter } from "@vgpu/core"; import { getImageProps } from "next/image"; +import fallbackDarkImage from "../../../../../public/eve-5/fallback-dark-content.webp"; +import fallbackLightImage from "../../../../../public/eve-5/fallback-light-content.webp"; import { useEffect, useRef, useState, type ComponentProps } from "react"; import { decodeGltfMesh, meshAspect } from "./mesh"; import { BLOOM_RADIUS, createEve5Renderer, type RenderControls } from "./render"; @@ -15,8 +17,6 @@ class BrowserAdapter implements VGPUAdapter { } const MODEL_URL = "/eve-5/eve-logo.gltf"; -const FALLBACK_DARK_IMAGE_URL = "/eve-5/fallback-dark-content.webp"; -const FALLBACK_LIGHT_IMAGE_URL = "/eve-5/fallback-light-content.webp"; const FALLBACK_IMAGE_WIDTH = 1095; const FALLBACK_IMAGE_HEIGHT = 348; const FALLBACK_IMAGE_ASPECT_RATIO = `${FALLBACK_IMAGE_WIDTH} / ${FALLBACK_IMAGE_HEIGHT}`; @@ -237,11 +237,11 @@ export function EveLogoShader() { } as const; const { props: fallbackLightImageProps } = getImageProps({ ...fallbackImageOptions, - src: FALLBACK_LIGHT_IMAGE_URL, + src: fallbackLightImage, }); const { props: fallbackDarkImageProps } = getImageProps({ ...fallbackImageOptions, - src: FALLBACK_DARK_IMAGE_URL, + src: fallbackDarkImage, }); return ( From 75931470f8e7905435da4124d3cd23032b41d45f Mon Sep 17 00:00:00 2001 From: matiasngf Date: Tue, 30 Jun 2026 05:19:39 +0000 Subject: [PATCH 09/15] Center eve fallback within content inset --- .../app/[lang]/(home)/components/eve-logo-shader/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx index 9c4b3fca4..e957e4d38 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx @@ -281,15 +281,15 @@ function FallbackImage({ }) { return (
From a508816b6a3567c7a81c0573d7ca99c4e4e63b74 Mon Sep 17 00:00:00 2001 From: matiasngf Date: Tue, 30 Jun 2026 05:25:48 +0000 Subject: [PATCH 10/15] Gate light eve fallback on WebGPU failure --- .../components/eve-logo-shader/index.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx index e957e4d38..c82479a7a 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx @@ -96,6 +96,7 @@ export function EveLogoShader() { const controlsRef = useRef({ ...DEFAULT_CONTROLS }); const [logoAspect, setLogoAspect] = useState(DEFAULT_LOGO_ASPECT); const [revealed, setRevealed] = useState(false); + const [showLightFallback, setShowLightFallback] = useState(false); useEffect(() => { let cancelled = false; @@ -107,6 +108,7 @@ export function EveLogoShader() { const canvas = canvasRef.current; resetCanvasVisibility(canvas); setRevealed(false); + setShowLightFallback(false); if (prefersReducedMotion !== false) return; @@ -122,7 +124,10 @@ export function EveLogoShader() { const renderTheme = theme; const canvas = canvasRef.current; const context = canvas?.getContext("webgpu"); - if (!canvas || !context || !navigator.gpu) return; + if (!canvas || !context || !navigator.gpu) { + setShowLightFallback(true); + return; + } const mesh = await loadMesh(); if (cancelled) return; @@ -160,6 +165,7 @@ export function EveLogoShader() { .then(() => { if (cancelled) return; setRevealed(false); + setShowLightFallback(true); cancelled = true; dispose(); }) @@ -187,6 +193,7 @@ export function EveLogoShader() { } catch { cancelled = true; setRevealed(false); + setShowLightFallback(true); dispose(); return; } @@ -212,6 +219,7 @@ export function EveLogoShader() { cleanup = dispose; }) .catch(() => { + setShowLightFallback(true); // The landing page must degrade silently when WebGPU or the GPU process is unavailable. }); @@ -254,12 +262,12 @@ export function EveLogoShader() { > @@ -272,16 +280,16 @@ export function EveLogoShader() { function FallbackImage({ imageProps, - revealed, + visible, className, }: { imageProps: ComponentProps<"img">; - revealed: boolean; + visible: boolean; className: string; }) { return (
Date: Tue, 30 Jun 2026 01:32:43 -0400 Subject: [PATCH 11/15] Update apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- .../(home)/components/eve-logo-shader/index.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx index c82479a7a..7f97060af 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx @@ -108,9 +108,16 @@ export function EveLogoShader() { const canvas = canvasRef.current; resetCanvasVisibility(canvas); setRevealed(false); - setShowLightFallback(false); - if (prefersReducedMotion !== false) return; + if (prefersReducedMotion !== false) { + // No WebGPU animation will run (reduced motion or the preference hasn't + // resolved yet), so the canvas stays hidden. Show the static light + // fallback to mirror the dark fallback, which is always visible until + // the animation reveals. Otherwise light-theme users would see a blank hero. + setShowLightFallback(true); + return; + } + setShowLightFallback(false); const updateEnvYaw = (clientX: number) => { const viewportWidth = Math.max(1, window.innerWidth || 1); From 3bcfc50e61407b8a92f4015e99de59145020f04b Mon Sep 17 00:00:00 2001 From: matiasngf Date: Tue, 30 Jun 2026 05:36:50 +0000 Subject: [PATCH 12/15] Warm up eve canvas before reveal --- .../app/[lang]/(home)/components/eve-logo-shader/index.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx index 7f97060af..366e78195 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx @@ -40,6 +40,7 @@ const FALLBACK_IMAGE_PADDING = BLOOM_RADIUS / MAX_DEVICE_PIXEL_RATIO; const MAX_ENV_YAW = 0.45; const ENV_YAW_LERP_SPEED = 3; const CANVAS_FADE_FALLBACK_MS = 800; +const CANVAS_REVEAL_RENDER_COUNT = 3; function getCurrentTheme(): "light" | "dark" { if (typeof document === "undefined") return "light"; @@ -155,7 +156,7 @@ export function EveLogoShader() { previousFrameTime = performance.now(); let disposed = false; - let firstFrameShown = false; + let successfulRenderCount = 0; let finishCanvasFade: (() => void) | undefined; const dispose = () => { if (disposed) return; @@ -205,8 +206,8 @@ export function EveLogoShader() { return; } - if (!firstFrameShown) { - firstFrameShown = true; + successfulRenderCount += 1; + if (successfulRenderCount === CANVAS_REVEAL_RENDER_COUNT) { canvas.style.opacity = "1"; finishCanvasFade = onCanvasFullyOpaque(canvas, () => { if (!cancelled) setRevealed(true); From f17834fd57ba35ae08a85ccd91dad372406e1089 Mon Sep 17 00:00:00 2001 From: matiasngf Date: Tue, 30 Jun 2026 05:44:18 +0000 Subject: [PATCH 13/15] Adjust eve shader mobile layout --- .../(home)/components/eve-logo-shader/index.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx index 366e78195..20f988e59 100644 --- a/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx +++ b/apps/docs/app/[lang]/(home)/components/eve-logo-shader/index.tsx @@ -4,7 +4,7 @@ import { App, Device, type VGPUAdapter } from "@vgpu/core"; import { getImageProps } from "next/image"; import fallbackDarkImage from "../../../../../public/eve-5/fallback-dark-content.webp"; import fallbackLightImage from "../../../../../public/eve-5/fallback-light-content.webp"; -import { useEffect, useRef, useState, type ComponentProps } from "react"; +import { useEffect, useRef, useState, type ComponentProps, type CSSProperties } from "react"; import { decodeGltfMesh, meshAspect } from "./mesh"; import { BLOOM_RADIUS, createEve5Renderer, type RenderControls } from "./render"; @@ -20,6 +20,7 @@ const MODEL_URL = "/eve-5/eve-logo.gltf"; const FALLBACK_IMAGE_WIDTH = 1095; const FALLBACK_IMAGE_HEIGHT = 348; const FALLBACK_IMAGE_ASPECT_RATIO = `${FALLBACK_IMAGE_WIDTH} / ${FALLBACK_IMAGE_HEIGHT}`; +const FALLBACK_CONTAINER_ASPECT_RATIO = `${FALLBACK_IMAGE_WIDTH + BLOOM_RADIUS} / ${FALLBACK_IMAGE_HEIGHT + BLOOM_RADIUS}`; const FALLBACK_IMAGE_SIZES = "(min-width: 768px) 1095px, calc(100vw - 16px)"; const DEFAULT_LOGO_ASPECT = 78 / 25; const DEFAULT_CONTROLS: RenderControls = { @@ -263,10 +264,13 @@ export function EveLogoShader() { return (