Documentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/dev.void/llms.txt
Use this file to discover all available pages before exploring further.
StarfieldBackground is the ambient backdrop that fills the entire viewport on every page of dev.void. It paints hundreds of tiny white dots on an HTML <canvas>, animates them scrolling upward to simulate drifting through space, and layers a radial gradient over the top to keep the centre of the screen slightly lighter than the edges. The component is positioned behind every other element and never intercepts mouse or touch events.
How it works
The component mounts a<canvas> element via a useRef and wires up all animation logic inside a single useEffect. On mount — and again on every window resize — it recalculates the canvas dimensions to match window.innerWidth × window.innerHeight and repopulates the star array.
Star generation
The number of stars is proportional to the viewport area:| Field | Range | Purpose |
|---|---|---|
x | 0 → canvas.width | Horizontal position |
y | 0 → canvas.height | Vertical position |
size | 0.1 → 1.6 | Radius in pixels |
speed | 0.1 → 0.6 | Upward drift per frame |
opacity | 0 → 1 | Current alpha value |
Animation loop
Each frame the canvas is cleared withclearRect, then every star is drawn as a filled circle using arc. After drawing, two mutations happen:
- Drift —
yis decremented byspeed. When a star drifts off the top (y < 0), it is repositioned to the bottom at a randomx. - Twinkle —
opacityshifts by a random ±0.05 per frame, clamped to[0.1, 1.0], producing a subtle natural shimmer.
requestAnimationFrame and is cancelled on unmount through the useEffect cleanup function.
Because star density is computed from the viewport area, resizing the window triggers a full re-generation of the star field rather than stretching or cropping. This keeps visual density consistent across screen sizes.
Positioning and layering
The outer wrapper isfixed inset-0 z-0 pointer-events-none bg-space-950. Being fixed means the star field stays in place while the user scrolls — pages slide over it rather than scrolling past it. pointer-events-none ensures every click and touch falls through to content layers above.
Inside the wrapper there are two children:
- Gradient overlay — an
absolute inset-0<div>with a radial gradient (from-space-800/50 via-space-950 to-space-950). It softens the centre of the sky so that brighter content like hero text reads cleanly against it. - Canvas —
absolute inset-0 opacity-60, the star-animation surface. Reducing opacity to 60 % lets the deep-space background colour bleed through and prevents the stars from feeling overpowering on bright monitors.
Usage
Drop<StarfieldBackground /> as the first child of your root layout wrapper so that every subsequent sibling naturally layers on top of it:
Customisation
Adjust star density
Change the divisor in the density formula. A smaller value (e.g.
1500) produces a denser field; a larger value (e.g. 6000) gives a sparse, minimalist sky.Change drift speed
Edit the
speed range in the star-generation loop. Math.random() * 0.5 + 0.1 gives 0.1–0.6 px per frame. Multiply the upper bound to make the field feel faster, or reduce it for a near-static night sky.Twinkle intensity
The twinkle effect shifts opacity by
(Math.random() - 0.5) * 0.1 per frame. Increase the multiplier for more dramatic flickering or set it to 0 to disable twinkling entirely.Canvas opacity
The
<canvas> element carries opacity-60 via Tailwind. Increase it toward opacity-100 for a brighter, more dramatic starfield, or decrease it for a subtler backdrop that keeps the focus on content.Accessibility
The component renders no text content and carries no semantic role — it is purely decorative. It already respects theprefers-reduced-motion media query via the global CSS rule in main.css, which sets animation-duration: 0.01ms for users who have requested reduced motion. The requestAnimationFrame loop itself is not a CSS animation, however, so if you need to fully honour reduced-motion preferences you should read window.matchMedia("(prefers-reduced-motion: reduce)").matches before starting the loop and skip the animation if it is true.