Backdrop.tsx
1 import { useGpuTier } from "@nous-research/ui/hooks/use-gpu-tier"; 2 3 /** 4 * Replicates the visual layer stack of `<Overlays dark />` from 5 * `@nous-research/ui` without pulling in its leva / gsap / three peer deps. 6 * 7 * See `design-language/src/ui/components/overlays/index.tsx` for the source of 8 * truth. Defaults match LENS_0 (the Hermes teal dark preset); the deep canvas 9 * and the warm vignette both read theme-switchable CSS custom properties so 10 * `ThemeProvider` can repaint the stack without remounting. 11 * 12 * z-1 bg = `var(--background-base)`, mix-blend-mode: difference 13 * z-2 filler-bg jpeg, inverted, opacity 0.033, difference 14 * z-99 warm top-left vignette (`var(--warm-glow)`), opacity 0.22, lighten 15 * z-101 noise grain (SVG, ~55% opacity × `--noise-opacity-mul`, 16 * color-dodge) — gated on GPU tier 17 * 18 * `useGpuTier` returns 0 when WebGL is unavailable, the renderer is a 19 * software rasterizer (SwiftShader/llvmpipe), or the user has 20 * `prefers-reduced-motion: reduce` set. We skip the animated noise layer 21 * in that case so low-power / accessibility-conscious sessions stay crisp, 22 * mirroring the DS `<Noise />` component's own opt-out. 23 */ 24 export function Backdrop() { 25 const gpuTier = useGpuTier(); 26 27 return ( 28 <> 29 <div 30 aria-hidden 31 className="pointer-events-none fixed inset-0 z-[1]" 32 style={{ 33 backgroundColor: "var(--background-base)", 34 mixBlendMode: "difference", 35 }} 36 /> 37 38 <div 39 aria-hidden 40 className="pointer-events-none fixed inset-0 z-[2]" 41 style={ 42 { 43 // Themes can override the filler background by setting 44 // `assets.bg` — the <img> hides itself when a CSS bg is set 45 // so the two don't double-darken. CSS var fallbacks keep the 46 // default behaviour unchanged when no theme customises these. 47 mixBlendMode: 48 "var(--component-backdrop-filler-blend-mode, difference)", 49 opacity: "var(--component-backdrop-filler-opacity, 0.033)", 50 backgroundImage: "var(--theme-asset-bg)", 51 backgroundSize: "var(--component-backdrop-background-size, cover)", 52 backgroundPosition: 53 "var(--component-backdrop-background-position, center)", 54 } as unknown as React.CSSProperties 55 } 56 > 57 <img 58 alt="" 59 className="h-[150dvh] w-auto min-w-[100dvw] object-cover object-top-left invert theme-default-filler" 60 fetchPriority="low" 61 src="/ds-assets/filler-bg0.jpg" 62 /> 63 </div> 64 65 <div 66 aria-hidden 67 className="pointer-events-none fixed inset-0 z-[99]" 68 style={{ 69 background: 70 "radial-gradient(ellipse at 0% 0%, transparent 60%, var(--warm-glow) 100%)", 71 mixBlendMode: "lighten", 72 opacity: 0.22, 73 }} 74 /> 75 76 {gpuTier > 0 && ( 77 <div 78 aria-hidden 79 className="pointer-events-none fixed inset-0 z-[101]" 80 style={{ 81 backgroundImage: 82 "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' fill='%23eaeaea' filter='url(%23n)' opacity='0.6'/%3E%3C/svg%3E\")", 83 backgroundSize: "512px 512px", 84 mixBlendMode: "color-dodge", 85 opacity: "calc(0.55 * var(--noise-opacity-mul, 1))", 86 }} 87 /> 88 )} 89 </> 90 ); 91 }