Skip to main content

Documentation Index

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

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

witch-dev is a single-page application built with React 18 and Vite, deployed as a fully static site to GitHub Pages. Rather than relying on a server to rewrite URLs, each route is backed by its own static HTML shell that bootstraps the same compiled React bundle. All navigation, animation, and rendering logic lives client-side — no backend, no API calls, no build-time data fetching.

Component Tree

The root component (xe) acts as the persistent app shell. It mounts three global layers that are always present regardless of the active route, then delegates page content to a React Router <Outlet /> wrapped in a Framer Motion <AnimatePresence> block.
App (xe)
├── BackgroundEffects          ← z-0  | fixed, pointer-events-none
├── CursorTrail                ← z-[100] | fixed, pointer-events-none
├── Navigation                 ← z-50 | fixed sidebar / mobile overlay
└── <main>  (md:ml-20)
    └── AnimatePresence (mode="wait")
        └── motion.div  (keyed on pathname)
            └── <Outlet />     ← active page component
The sidebar pushes <main> content right by md:ml-20 on medium screens and above, while remaining fixed so it never scrolls with page content.

Z-Index Layer Table

Each visual layer occupies a dedicated stacking slot so animated elements never bleed into each other.
LayerComponentz-indexPositionNotes
BackgroundBackgroundEffectsz-0fixed inset-0Particles, fog blobs, sigil pattern
Content<main>z-10relativeAll page components render here
NavigationNavigationz-50fixed left-0Sidebar on desktop
Mobile MenuMobile overlayz-[60]fixed inset-0Animated full-screen nav
CursorCursorTrailz-[100]fixed inset-0Always on top of everything

Static Shell + SPA Hybrid

GitHub Pages serves files exactly as they exist on disk — there is no URL rewriting layer. witch-dev works around this by shipping a dedicated .html file for every route under the pages/ directory. Each shell does two things:
  1. Sets window.__STATIC_PAGE_ROUTE__ to the logical route path (e.g. "/about").
  2. Runs an IIFE that redirects any bare URL to its hash equivalent (e.g. /pages/About.html/pages/About.html#/about).
Once the redirect fires, the browser loads the same assets/main.js bundle for every shell. React Router reads the hash fragment and renders the appropriate page component. The __STATIC_PAGE_ROUTE__ value is consumed by useScreenInit.js to synchronise the initial router state without a flicker.
User visits /pages/About.html
  → shell sets __STATIC_PAGE_ROUTE__ = "/about"
  → IIFE redirects to /pages/About.html#/about
  → main.js boots React + Router
  → Router reads hash "#/about" → renders <About />

Data Flow

witch-dev contains no external API calls. All page content — project cards, skills data, writing posts — is declared as static JavaScript arrays compiled directly into assets/main.js at build time by Vite. This keeps the bundle self-contained and the site fully functional without a network connection after the initial load.
Vite build
  └── assets/main.js
        ├── projects[]     ← hardcoded project objects
        ├── skills[]       ← skill categories + proficiency values
        ├── writing[]      ← blog post metadata + content
        └── Page components (he, fe, be, ye, je, Contact)

Page Transition System

Every route change triggers a choreographed blur-and-scale animation powered by Framer Motion’s <AnimatePresence> with mode="wait". The outgoing page fully exits before the incoming page begins its entrance, preventing two pages from occupying the content area simultaneously.
<AnimatePresence mode="wait">
  <motion.div
    key={r.pathname}          // new key on every route change
    initial={{ opacity: 0, filter: "blur(10px)", scale: 0.95 }}
    animate={{ opacity: 1, filter: "blur(0px)", scale: 1 }}
    exit={{   opacity: 0, filter: "blur(10px)", scale: 1.05 }}
    transition={{ duration: 0.5, ease: "easeInOut" }}
    className="w-full h-full max-w-7xl mx-auto"
  >
    <Outlet />
  </motion.div>
</AnimatePresence>
The key prop is set to r.pathname (from useLocation()), so React treats each route as a distinct component instance. The asymmetric scale values (0.95 entering, 1.05 exiting) give the transition a subtle push-through depth effect that reinforces the occult aesthetic.

Build docs developers (and LLMs) love