Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/dev-haunt/llms.txt

Use this file to discover all available pages before exploring further.

BatSwarm fills the background of every DevHaunt page with a small flock of animated bats. It is a purely decorative, always-on effect: the bats are positioned fixed to the viewport, set to pointer-events: none, and stacked below page content so they never obscure anything interactive. Each bat is an independent Framer Motion <motion.div> with randomised speed, delay, vertical offset, and scale — so the swarm feels organic rather than mechanical.

How it works

On mount, a useEffect call generates an array of bat configuration objects using Array.from({ length: 4 }). Each object captures a random snapshot of:
  • top — vertical start position (5–35% of viewport height)
  • duration — crossing time (15–25 s)
  • delay — stagger before the first loop starts (0–10 s)
  • scale — apparent size (0.5–1.0×)
These values are stored in state and then mapped to individual animated elements.
useEffect(() => {
  const bats = Array.from({ length: 4 }).map((_, i) => ({
    id: i,
    top: Math.random() * 30 + 5,
    duration: Math.random() * 10 + 15,
    delay: Math.random() * 10,
    scale: Math.random() * 0.5 + 0.5,
  }));
  setBats(bats);
}, []);

Animation

Each bat uses two simultaneous Framer Motion animation tracks on its x and y axes:
<motion.div
  className="absolute left-[-100px]"
  style={{ top: `${bat.top}%`, scale: bat.scale }}
  animate={{
    x: ['0vw', '120vw'],
    y: [0, -20, 10, -10, 0, -30, 20, 0],
  }}
  transition={{
    x: {
      duration: bat.duration,
      repeat: Infinity,
      ease: 'linear',
      delay: bat.delay,
    },
    y: {
      duration: bat.duration,
      repeat: Infinity,
      ease: 'easeInOut',
      delay: bat.delay,
    },
  }}
>
  {/* SVG bat */}
</motion.div>
  • x track — linear sweep from -100px off the left edge to 120vw off the right edge, then loops. linear easing keeps the horizontal speed constant.
  • y track — a multi-keyframe sequence that bobs the bat up and down erratically with easeInOut easing, mimicking the fluttering flight path of a real bat.
Because each bat has its own duration and delay, they never cross the screen in lockstep.

SVG bat shape

Each bat is rendered as an inline SVG (40×20 viewBox, fill: #051f25) made up of three paths:
<svg width="40" height="20" viewBox="0 0 40 20" fill="#051f25">
  {/* Body and main wing span */}
  <path d="M20 10C20 10 15 0 0 5C5 10 15 15 20 20C25 15 35 10 40 5C25 0 20 10 20 10Z" />
  {/* Left wing detail */}
  <path d="M20 10C18 8 16 2 10 0C13 5 17 8 20 10Z" />
  {/* Right wing detail */}
  <path d="M20 10C22 8 24 2 30 0C27 5 23 8 20 10Z" />
</svg>
The fill colour #051f25 is slightly darker than the night background (#0b3a44) so the bats are visible as silhouettes without demanding attention.

Positioning and stacking

fixed inset-0   → covers the full viewport, stays in place on scroll
pointer-events-none → never blocks clicks
z-30            → above page content (z-10) but below FogLayer (z-40) and Navigation (z-50)
overflow-hidden → clips bats that haven't fully entered or exited the viewport
Bats start at left: -100px (off-screen left) and animate to 120vw (off-screen right). The container’s overflow: hidden clips them at both edges for a clean entry and exit.

Customisation

PropertyLocationDefault
Number of batsArray.from({ length: 4 })4
Speed rangeMath.random() * 10 + 1515 – 25 s
Vertical rangeMath.random() * 30 + 55 – 35 %
Size rangeMath.random() * 0.5 + 0.50.5 – 1.0 ×
Bat colourfill on <svg>#051f25
Vertical bob pathy keyframe array[0,-20,10,-10,0,-30,20,0]
To add more bats, change the length value in Array.from. Each additional bat is independently randomised, so the swarm scales naturally — though more than ~8 bats may feel crowded on narrow viewports.

Lifecycle

BatSwarm runs continuously for as long as Layout is mounted. Because all animations use repeat: Infinity, there is no idle state — bats are always in flight. The component has no teardown logic beyond the standard React unmount, at which point Framer Motion stops all animations automatically.

Build docs developers (and LLMs) love