diff --git a/src/components/hero/AnimatedBackground.tsx b/src/components/hero/AnimatedBackground.tsx index 1f8d47f..3dc7601 100644 --- a/src/components/hero/AnimatedBackground.tsx +++ b/src/components/hero/AnimatedBackground.tsx @@ -23,22 +23,21 @@ import { useEffect, useRef } from 'react'; interface Props { /** Show the canvas star field. Off on interest pages for a calmer feel. */ particles?: boolean; - /** Tailwind/className passthrough for the root. */ - className?: string; /** - * Opt-in View Transition name. Unset by default. The View Transitions API - * requires this name to be UNIQUE within a page snapshot, so the page must - * opt in on AT MOST ONE field (typically the hero). Applied via the `.vt-sky` - * CSS class rather than React's inline style to avoid an SSR/hydration - * attribute mismatch. + * Tailwind/className passthrough for the root. The CALL SITE (in .astro) is + * responsible for including `vt-sky` here when this element should carry the + * `view-transition-name: sky` morph. We intentionally do NOT compute the + * transition class inside this React component: any conditional className + * interpolation causes a server/client hydration attribute mismatch under + * Astro's client:idle. Passing a static, fully-formed string guarantees the + * server-rendered and hydrated class attributes are byte-identical. */ - transitionName?: 'sky'; + className?: string; } export default function AnimatedBackground({ particles = true, className = '', - transitionName, }: Props) { const canvasRef = useRef(null); @@ -135,11 +134,12 @@ export default function AnimatedBackground({ const cy = 0; const maxR = Math.sqrt(cx * cx + height * height) + 40; - // One star per ~3200 px^2 of the disk, capped for perf (~14% anchors). + // Dense starfield: ~3x the previous density for a richly star-packed sky. + // One star per ~1100 px^2 of the disk, capped for perf (~15% anchors). const diskArea = Math.PI * maxR * maxR; - const count = Math.min(520, Math.floor(diskArea / 3200)); + const count = Math.min(1500, Math.floor(diskArea / 1100)); stars = Array.from({ length: count }, (_, i) => { - const anchor = i % 7 === 0; // ~14% anchors -> dense constellation backbone + const anchor = i % 7 === 0; // ~15% anchors -> dense constellation backbone // Uniform-ish distribution across the disk via sqrt on radius. const rr = maxR * Math.sqrt(Math.random()); const a = Math.random() * Math.PI * 2; @@ -321,11 +321,9 @@ export default function AnimatedBackground({ }; }, [particles]); - const skyClass = transitionName === 'sky' ? 'vt-sky' : ''; - return (