Skip to main content

Documentation Index

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

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

The three layout components in Spell Index require no props — each manages its own state and wires itself directly to the router. They are intended to be mounted once at the application root and remain persistent across every route change.
The Navigation component renders a persistent site-wide nav. On lg breakpoints and above it displays as a fixed left sidebar (w-64); on smaller screens it collapses entirely and a hamburger button opens a full-screen animated mobile drawer instead.

Route array

All nine routes are hardcoded inside Navigation.js. Each entry has a path, a thematic label displayed in Cinzel font, and a sub subtitle shown in smaller uppercase tracking:
const routes = [
  { path: "/",             label: "The Summoning",  sub: "Home"         },
  { path: "/about",        label: "Origin Story",   sub: "About"        },
  { path: "/skills",       label: "Dark Arts",      sub: "Skills"       },
  { path: "/projects",     label: "Conjurings",     sub: "Projects"     },
  { path: "/work",         label: "Day Job Spells", sub: "Work"         },
  { path: "/case-studies", label: "Post-Mortems",   sub: "Case Studies" },
  { path: "/articles",     label: "Scrolls",        sub: "Articles"     },
  { path: "/testimonials", label: "Testimony",      sub: "Testimonials" },
  { path: "/contact",      label: "Send a Raven",   sub: "Contact"      },
];

Active state indicator

The current route is detected with React Router’s useLocation. When a route is active, its label renders in amber (text-amber) instead of the default parchment. A 4 × 4 px amber dot (w-1 h-1 rounded-full) appears to the left of the active link and animates between routes using Framer Motion’s layoutId="nav-indicator" — this causes the dot to smoothly slide from the old link to the new one rather than blinking in place.
// Active indicator (rendered only when the route matches)
<motion.div
  layoutId="nav-indicator"
  className="absolute -left-4 top-1/2 -translate-y-1/2 w-1 h-1 rounded-full bg-amber shadow-[0_0_8px_rgba(245,196,90,0.8)]"
/>

Desktop sidebar

<nav className="hidden lg:flex flex-col fixed left-0 top-0 h-screen w-64
                border-r border-ink bg-midnight/80 backdrop-blur-md z-40 p-8">
  {/* Logo + tagline */}
  {/* Route list */}
</nav>
  • backdrop-blur-md keeps the sidebar legible over the particle background.
  • The scrollable route list uses a custom scrollbar class (custom-scrollbar) to preserve the dark aesthetic.

Mobile drawer

On screens smaller than lg, the sidebar is hidden (hidden lg:flex). A floating hamburger button (fixed top-4 right-4 z-50) opens the drawer by setting local isOpen state to true. The drawer itself is conditionally rendered inside Framer Motion’s AnimatePresence and slides in from the right:
<motion.div
  initial={{ opacity: 0, x: "100%" }}
  animate={{ opacity: 1, x: 0 }}
  exit={{ opacity: 0, x: "100%" }}
  transition={{ type: "spring", damping: 25, stiffness: 200 }}
  className="fixed inset-0 z-50 bg-midnight/95 backdrop-blur-xl flex flex-col p-8 lg:hidden"
>
Each route item in the mobile drawer also receives an individual initial/animate stagger so links fade-slide up in sequence. Clicking any link calls setIsOpen(false) to dismiss the drawer.

Usage

import { Navigation } from "./components/witchy/Navigation";

// Mount once at the app root — no props needed
<Navigation />
Navigation relies on React Router’s useLocation hook. It must be rendered inside a <BrowserRouter> (or equivalent) context, or it will throw a hook error.

The Footer component renders a 192 px tall bar (h-48) pinned below the main content area with a top border (border-t border-ink). It has three visual layers:
  1. Dot grid — a CSS background-image radial-gradient pattern at 24 × 24 px spacing, 50 % opacity.
  2. Credit text — centered font-cinzel lines: “Manifested with TypeScript and questionable life choices.” and the current year (new Date().getFullYear()).
  3. Mouse-tracking cat — a small teal SVG cat icon (w-8 h-8) that spring-animates its left position to follow the horizontal cursor position within the footer. The position is tracked via an onMouseMove handler that calculates (clientX - rect.left) / rect.width * 100 as a percentage.
import { Footer } from "./components/witchy/Footer";

// Mount below your main content — no props needed
<Footer />
The cat’s spring animation uses stiffness: 100, damping: 20, giving it a gentle lag. Increase stiffness for a snappier follow or decrease damping for a bouncier effect.

PageTransition

PageTransition is a thin wrapper component that accepts React children and applies a unified enter/exit animation to every page. It uses the current location.pathname as its Framer Motion key so that a new animation fires on every route change.

Transition behaviour

Phaseopacityfiltery offset
Enter0 → 1blur(10px) → blur(0)20px → 0
Exit1 → 0blur(0) → blur(10px)0 → -20px
The cubic-bezier easing [0.22, 1, 0.36, 1] (a fast-in slow-out curve) gives the entrance a premium feel. Duration is 0.8s.

Usage

Wrap each page component with PageTransition inside your route definitions:
import { PageTransition } from "./components/witchy/PageTransition";

function SkillsPage() {
  return (
    <PageTransition>
      <main className="flex-1 lg:pl-64 p-8">
        {/* page content */}
      </main>
    </PageTransition>
  );
}
PageTransition must be a child of Framer Motion’s <AnimatePresence> to enable exit animations. The root App component wraps all routes in <AnimatePresence mode="wait"> to ensure only one page is mounted at a time during transitions.

Build docs developers (and LLMs) love