Skip to main content

Documentation Index

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

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

The Craft implements client-side navigation entirely through React Router v6. All routes are declared in assets/main.js, nested under a single parent layout route, and backed by React.lazy imports so that each page’s JavaScript chunk is only fetched when that route is first visited. A custom 404 component catches any path that does not match the defined routes and keeps the grimoire aesthetic intact.

Route Map

Every navigable path in the application is listed below. The parent route (/) renders the Layout shell, and all child routes render their page component into the <Outlet /> inside that shell.
PathComponentNotes
/HomeIndex route — renders at the root path
/aboutAboutPractitioner bio page
/projectsProjectsPortfolio work and side projects
/skillsSkillsArcane arts / technology skills
/workWorkCoven records — employment history
/case-studiesCaseStudiesTome of Workings deep-dives
/blogBlogWhispers — blog posts
/contactContactSummoning — contact form
/testimonialsTestimonialsClient and colleague testimonials
/*NotFoundCustom 404 — “This page was hexed out of existence.”

Lazy Loading Pattern

Every page component is wrapped in React.lazy with a dynamic import(). This keeps the initial bundle small: the JavaScript for /projects, for example, is only downloaded the first time a visitor navigates to that path.
const Home = React.lazy(() =>
  import("./Home.js").then((t) => ({ default: t.Home }))
);

const Projects = React.lazy(() =>
  import("./Projects.js").then((t) => ({ default: t.Projects }))
);
While a lazy chunk is loading, React.Suspense shows a full-screen fallback component — an animated sigil icon with the text “Consulting the spirits…” rendered in the font-cursive (Pinyon Script) typeface.
<React.Suspense fallback={<SigilFallback />}>
  <Routes>
    {/* ... */}
  </Routes>
</React.Suspense>
The Suspense fallback renders against bg-midnight with the sigil pulsing via the .animate-pulse-glow utility class. In practice the fallback is rarely visible because Vite’s module preload injection pre-fetches linked chunks while the browser is idle.

Layout Nesting

The full router tree demonstrates how the Layout shell wraps every page:
<BrowserRouter>
  <React.Suspense fallback={<SigilFallback />}>
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route path="about" element={<About />} />
        <Route path="projects" element={<Projects />} />
        <Route path="skills" element={<Skills />} />
        <Route path="work" element={<Work />} />
        <Route path="case-studies" element={<CaseStudies />} />
        <Route path="blog" element={<Blog />} />
        <Route path="contact" element={<Contact />} />
        <Route path="testimonials" element={<Testimonials />} />
        <Route path="*" element={<NotFound />} />
      </Route>
    </Routes>
  </React.Suspense>
</BrowserRouter>
The Layout component renders MoonPhaseNav and CursorTrail once — they are persistent across all navigation events. Only the <Outlet /> content changes as the user moves between routes.

MoonPhaseNav Route Array

components/MoonPhaseNav.js maintains its own routes array that drives both the navigation links and the moon phase SVG rendered beside each link. Each entry has three fields:
FieldTypeDescription
pathstringThe URL path this nav item links to
namestringThe grimoire-flavoured display label
phasestringMoon phase key that selects the correct SVG illustration
The full array as defined in the source:
const routes = [
  { path: "/",            name: "The Cover",      phase: "new" },
  { path: "/about",       name: "The Practitioner", phase: "waxing-crescent" },
  { path: "/projects",    name: "Spellwork",       phase: "first-quarter" },
  { path: "/skills",      name: "Arcane Arts",     phase: "waxing-gibbous" },
  { path: "/work",        name: "Coven Records",   phase: "full" },
  { path: "/case-studies",name: "Tome of Workings",phase: "waning-gibbous" },
  { path: "/blog",        name: "Whispers",        phase: "last-quarter" },
  { path: "/contact",     name: "Summoning",       phase: "waning-crescent" },
];
The testimonials route does not appear in MoonPhaseNav — it is accessible by URL but intentionally excluded from the primary navigation. To surface it in the nav, add an entry to this array (see below).

Adding a New Route

1

Create the page file

Add a new file in assets/, for example assets/Rituals.js. Export a named component that matches the filename:
export function Rituals() {
  return (
    <div className="p-12 font-garamond text-parchment">
      <h1 className="font-cinzel text-4xl text-spell text-glow">Rituals</h1>
    </div>
  );
}
2

Add the lazy import to main.js

Near the other lazy imports at the top of assets/main.js, add:
const Rituals = React.lazy(() =>
  import("./Rituals.js").then((t) => ({ default: t.Rituals }))
);
3

Add the Route element

Inside the <Route path="/" element={<Layout />}> block in assets/main.js, add a child route:
<Route path="rituals" element={<Rituals />} />
4

Add a nav entry to MoonPhaseNav

Open components/MoonPhaseNav.js and append an entry to the routes array. Choose whichever moon phase fits the mood — all eight phases are available:
{ path: "/rituals", name: "The Rituals", phase: "full" },
The navigation item and its moon phase SVG will appear automatically.

Build docs developers (and LLMs) love