Skip to content
Velocity X Docs
← All components

Maze Growth

Free · MIT

Generative maze that grows outward from a seed cell, drawing itself as a living background. Canvas-based, self-contained, pauses when off screen and stops entirely under prefers-reduced-motion.

Category
Backgrounds
Framework
astro
Version
1.0.1
Peer deps
none

Install

bash
velocityx add maze-growth

Free and MIT licensed — no account needed. The file lands under src/components/velocity/maze-growth/ by default (override with --dest), and it's yours to edit from the moment it's written.

Demo

View the live demo on the showcase site ↗

Source

Pulled live from the registry — this is the exact file velocityx add maze-growth writes into your project right now.

components/velocity/maze-growth/MazeGrowth.astro

astro
---
//    VELOCITY COMPONENT · theming
// 
//    Colours here are Tailwind `primary-*` / `accent-*`, which resolve through
//    the Velocity preset. Out of the box they are a neutral indigo — NOT the
//    Aidxn brand — so this drops into your project without dragging our look in.
// 
//    ⚡ NOT USING VELOCITY? Run this once, in your project root:
// 
//          velocityx theme
// 
//    It writes velocity-preset.cjs + velocity-theme.css. Add the preset to your
//    tailwind.config, import the css, then set your whole brand in ONE place:
// 
//          :root {
//            --vx-brand: custom;
//            --vx-primary-600: 220 38 38;   <- your action colour
//            --vx-primary-700: 185 28 28;
//          }
// 
//    ⚠️  Raw RGB channels, space separated — NOT hex. `#DC2626` here silently
//        breaks every /opacity variant in the library (bg-primary-600/20 etc).
// 
//    Prefer not to install anything? Find-and-replace `primary-` with your own
//    scale name. Nothing else in this file depends on Velocity.
/**
 * MazeGrowth — a fast "growth" that draws itself through a geometric maze,
 * rendered as pure SVG so it's trivially swappable.
 *
 * The default art is a generated truchet maze (quarter-arc tiles → flowing
 * corridors). The reveal is a stroke-dashoffset draw with `pathLength="1"`,
 * so it works on ANY SVG path regardless of length — pass your own `paths`
 * (array of `d` strings) to swap the maze for your own SVG and it'll grow the
 * same way. Rows are staggered so growth spreads through the maze in lots.
 *
 * Props:
 *   paths?    — custom SVG path `d` strings (defaults to a generated maze)
 *   viewBox?  — SVG viewBox (match your custom art)
 *   duration? — seconds per draw cycle (lower = faster). Default 2.4.
 *   stagger?  — seconds between each path's start. Default 0.16.
 */
interface Props {
  paths?: string[];
  viewBox?: string;
  duration?: number;
  stagger?: number;
  class?: string;
}

const COLS = 12;
const ROWS = 8;
const S = 40;

function genMazeRows(): string[] {
  const rows: string[] = [];
  for (let r = 0; r < ROWS; r++) {
    let d = '';
    for (let c = 0; c < COLS; c++) {
      const x = c * S;
      const y = r * S;
      const h = S / 2;
      // deterministic truchet tile choice → connected maze corridors
      const flip = ((r * 7 + c * 13 + r * c) % 5) < 3;
      if (flip) {
        d += `M${x + h} ${y}A${h} ${h} 0 0 1 ${x + S} ${y + h}`;
        d += `M${x} ${y + h}A${h} ${h} 0 0 1 ${x + h} ${y + S}`;
      } else {
        d += `M${x + h} ${y}A${h} ${h} 0 0 0 ${x} ${y + h}`;
        d += `M${x + S} ${y + h}A${h} ${h} 0 0 0 ${x + h} ${y + S}`;
      }
    }
    rows.push(d);
  }
  return rows;
}

const {
  paths,
  viewBox = `0 0 ${COLS * S} ${ROWS * S}`,
  duration = 2.4,
  stagger = 0.16,
  class: className = '',
} = Astro.props;

const finalPaths = paths && paths.length ? paths : genMazeRows();
---

<div class:list={['mz-wrap', className]}>
  <svg viewBox={viewBox} preserveAspectRatio="xMidYMid slice" class="mz-svg" aria-hidden="true">
    {
      finalPaths.map((d, i) => (
        <g style={`animation-delay:${(i * stagger).toFixed(2)}s`}>
          <path d={d} pathLength="1" class="mz-glow" style={`animation-delay:${(i * stagger).toFixed(2)}s`} />
          <path d={d} pathLength="1" class="mz-line" style={`animation-delay:${(i * stagger).toFixed(2)}s`} />
        </g>
      ))
    }
  </svg>
</div>

<style define:vars={{ mzDur: `${duration}s` }}>
  .mz-wrap { width: 100%; height: 100%; }
  .mz-svg { display: block; width: 100%; height: 100%; }
  .mz-line,
  .mz-glow {
    fill: none;
    stroke-linecap: round;
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: mz-draw var(--mzDur) linear infinite;
  }
  .mz-line { stroke: var(--primary-500); stroke-width: 2.4; }
  .mz-glow { stroke: var(--primary-400); stroke-width: 8; opacity: 0.45; filter: blur(3px); }
  /* draw in (0→50%), then keep flowing out the tail (→100%) for a continuous run */
  @keyframes mz-draw {
    0%   { stroke-dashoffset: 1; }
    50%  { stroke-dashoffset: 0; }
    100% { stroke-dashoffset: -1; }
  }
  @media (prefers-reduced-motion: reduce) {
    .mz-line, .mz-glow { animation: none; stroke-dashoffset: 0; }
  }
</style>

Theming

Uses the shared primary-* / accent-* scale — see Theming if colours look unstyled after install.