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
+12 -23
View File
@@ -22,21 +22,15 @@ import { useEffect, useRef } from 'react';
interface Props {
/** Density multiplier. 1 = default; lower for quieter sections. */
density?: number;
/** Tailwind/className passthrough for the root. */
className?: string;
/**
* Opt-in View Transition name. DEFAULT IS UNSET so the field carries NO
* view-transition-name. The View Transitions API requires the name to be
* UNIQUE within a single page snapshot; assigning "sky" to every instance
* caused "Unexpected duplicate view-transition-name: sky" + transition
* aborts. The page should set this on AT MOST ONE field per page (typically
* the hero), and it must match the corresponding element on the destination
* page for the morph (e.g. constellations -> circuitry) to play.
*
* Set via the `data-sky` attribute + the `.vt-sky` CSS class (see global.css)
* rather than React's inline style, which avoids an SSR/hydration mismatch.
* Tailwind/className passthrough. The .astro call site is responsible for
* including `vt-sky` when this element should carry the view-transition
* morph. We do NOT compute the transition class here: conditional className
* interpolation triggers a server/client hydration attribute mismatch under
* Astro's client:idle. A static, fully-formed string keeps SSR and hydration
* byte-identical.
*/
transitionName?: 'sky';
className?: string;
/** Star color theme. 'gold' leans warm, 'violet' leans mystic. */
tint?: 'gold' | 'violet' | 'teal';
}
@@ -44,7 +38,6 @@ interface Props {
export default function ConstellationField({
density = 1,
className = '',
transitionName,
tint = 'gold',
}: Props) {
const canvasRef = useRef<HTMLCanvasElement>(null);
@@ -100,10 +93,11 @@ export default function ConstellationField({
const cy = 0;
const maxR = Math.sqrt(cx * cx + height * height) + 40;
// Denser field for richer constellations. One star per ~4500 px^2, scaled
// by density; ~15% anchors for a dense constellation backbone.
// ~3x density for a richly star-packed sky. One star per ~1500 px^2,
// scaled by density; ~15% anchors for a dense constellation backbone.
const count = Math.min(
360,
Math.floor(((Math.PI * maxR * maxR) / 4500) * density),
900,
Math.floor(((Math.PI * maxR * maxR) / 1500) * density),
);
stars = Array.from({ length: count }, (_, i) => {
const anchor = i % 7 === 0;
@@ -243,15 +237,10 @@ export default function ConstellationField({
};
}, [density, tint]);
// Apply the view-transition-name via a CSS class (global.css `.vt-sky`) rather
// than React's inline style: avoids an SSR/hydration attribute mismatch and
// keeps the name off by default (only one field per page should opt in).
const skyClass = transitionName === 'sky' ? 'vt-sky' : '';
return (
<canvas
ref={canvasRef}
className={`pointer-events-none absolute inset-0 h-full w-full ${skyClass} ${className}`}
className={`pointer-events-none absolute inset-0 h-full w-full ${className}`}
aria-hidden="true"
/>
);