header pass

This commit is contained in:
Greg Kilmer
2026-07-04 16:40:02 -04:00
parent db6ff9f213
commit 1e8f5f1f01
13 changed files with 250 additions and 141 deletions
+13 -15
View File
@@ -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<HTMLCanvasElement>(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 (
<div
className={`pointer-events-none absolute inset-0 -z-10 overflow-hidden ${skyClass} ${className}`}
className={`pointer-events-none absolute inset-0 -z-10 overflow-hidden ${className}`}
aria-hidden="true"
>
{/* Layer 1: night-sky gradient — deep blue, darker at top, lighter toward