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 organises its UI into two distinct tiers: global components that are always mounted and visible regardless of the active route, and page components that are swapped in and out by React Router’s <Outlet />. The two tiers are composited through a z-index stacking system — background effects sit beneath everything, the cursor trail floats above everything, and page content lives in the space in between.

Global Components

These three components are mounted once by the root app shell (xe) and remain in the DOM for the entire session.

BackgroundEffects

z-0 · fixed inset-0 · pointer-events-noneRenders the animated environment that gives every page its dark, atmospheric feel. Composed of three layers stacked inside a single fixed inset-0 container:
  • Sigil pattern — a faint repeating SVG triangle-and-circle motif at opacity-[0.03] with mix-blend-screen.
  • Fog blobs — three large blurred radial gradients in coven-purple-900 and coven-dark that drift via the animate-fog-drift Tailwind keyframe class.
  • Floating particles — 40 procedurally-positioned motion.div dots in coven-green-400 that animate upward on infinite loops with staggered delays and random horizontal drift.
<div className="fixed inset-0 pointer-events-none z-0 overflow-hidden bg-coven-black">
  {/* sigil SVG pattern */}
  {/* fog blob divs */}
  {particles.map(p => <motion.div key={p.id} ... />)}
</div>

CursorTrail

z-[100] · fixed inset-0 · pointer-events-noneTracks mousemove events and renders a two-part cursor replacement:
  • Leader dot — a 16 × 16 px coven-green-400 circle with blur-[2px] and a green box-shadow glow. Follows the cursor with transition: { type: "tween", ease: "backOut", duration: 0.1 }.
  • Trail particles — a rolling buffer of the last 15 cursor positions rendered as 8 × 8 px coven-purple-400 dots. Each fades out and scales to zero over 500 ms via Framer Motion’s initial → animate props.
The trail buffer is capped at 15 entries and pruned every 50 ms by a setInterval cleanup effect, keeping memory use constant during long mouse movements.

Navigation

z-50 · Desktop: fixed left-0 top-0 h-screen w-20Renders two distinct navigation surfaces depending on viewport width:Desktop sidebar (md+): A 80 px wide fixed column on the left edge with backdrop-blur-md and a coven-purple-900/50 right border. Each route renders as a 48 × 48 px rounded icon button. On hover, a tooltip slides in from the left showing the thematic label (e.g. “Grimoire”) in font-mono. The active route gets bg-coven-purple-900/80 text-coven-green-400 box-glow-green.Mobile burger (below md): A 48 × 48 px pill button in the top-right corner with glass-panel and box-glow-green. Tapping it triggers a full-screen overlay at z-[60] animated with backdropFilter: blur(16px). Route links in the overlay use font-creepster at text-2xl and stagger in with transition: { delay: index * 0.1 }.

App Shell (xe)

Root component · Mounts all global layersThe root xe component wires everything together. It reads the current location via useLocation() and passes r.pathname as the key prop on the motion.div wrapper inside <AnimatePresence mode="wait">.
const xe = () => {
  const r = useLocation();
  return (
    <div className="min-h-screen w-full relative text-gray-200">
      <BackgroundEffects />
      <CursorTrail />
      <Navigation />
      <main className="relative z-10 md:ml-20 ...">
        <AnimatePresence mode="wait">
          <motion.div key={r.pathname} ...>
            <Outlet />
          </motion.div>
        </AnimatePresence>
      </main>
    </div>
  );
};

Page Components

Six page components are registered with React Router and rendered through the <Outlet />. Each receives the same entry/exit animation from the parent motion.div.

Home (he)

Route: / · Label: SummonThe landing page hero. Features a typewriter effect that cycles through developer identity strings and a radial navigation layout that echoes the sidebar routes in a larger, more dramatic format. Sets the tone for the occult aesthetic with heavy use of font-creepster headings and coven-green accent colours.

About (fe)

Route: /about · Label: OriginsA biography section structured as a scrollable timeline on the left paired with a sticky sidebar on the right. The timeline entries use motion.div with scroll-triggered initial/animate variants to reveal content as the user scrolls down. The sticky sidebar holds contact links and a brief summary card with glass-panel styling.

Projects (be)

Route: /projects · Label: GrimoireDisplays the project portfolio as a horizontal carousel of project cards. Each card renders with glass-panel, box-glow-green or box-glow-purple borders, tech stack badges in font-mono, and links to live demos or repositories. Cards animate in with staggered motion.div delays on mount.

Skills (ye)

Route: /skills · Label: AffinitiesVisualises technical proficiencies as a radar/spider chart. Skill categories are drawn from a static data array compiled into the bundle. The chart is rendered with animated path drawing on mount, reinforcing the spellcasting metaphor of the “Affinities” label.

Writing (je)

Route: /writing · Label: ScrollsA blog/writing index with a featured post displayed prominently at the top and remaining posts in a vertically stacked list below. Post metadata (title, date, tags) is stored as a static array in the bundle. All post content is client-side only — no CMS or external fetch.

Contact

Route: /contact · Label: RavenA contact form with fields for name, email, and message. Form inputs use the shared glass-panel class for frosted-glass styling consistent with the card components on other pages. The submit button uses box-glow-green to draw attention.

Shared Patterns

All interactive components across both tiers share a common set of Tailwind utilities and Framer Motion conventions defined in assets/main.css and applied uniformly through the bundle.

Framer Motion Conventions

Every interactive element that appears or transitions uses the standard initial → animate → exit trio:
<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{   opacity: 0, y: -20 }}
  transition={{ duration: 0.3, ease: "easeOut" }}
>
  {/* content */}
</motion.div>
Staggered list reveals use transition={{ delay: index * 0.1 }} on each item so sequential content appears in a cascading wave rather than all at once.

Tailwind Utility Classes

ClassEffectUsed On
glass-panelFrosted-glass card with backdrop-blur and semi-transparent coven-purple borderProject cards, contact form, sidebar panels
box-glow-greenNeon coven-green-400 box-shadow haloActive nav item, CTA buttons, featured elements
box-glow-purpleNeon coven-purple-400 box-shadow haloSecondary cards, hover states
text-glow-greenGreen text shadow glowActive mobile nav labels
font-creepsterDisplay typeface — occult / horror aestheticAll page headings (h1, h2)
font-monoMonospace typefaceLabels, badges, code snippets, tooltips
animate-fog-driftCustom keyframe — slow translating driftBackgroundEffects fog layer

Colour Palette Tokens

The compiled stylesheet uses a coven-* namespace of custom colour tokens throughout the codebase. In your local source project, these are defined in tailwind.config.js as extensions to the default palette:
TokenApproximate colourRole
coven-black#0a0a0fPage and background base
coven-dark#12121aCard and panel backgrounds
coven-purple-*Purple scale (400900)Borders, secondary accents, fog
coven-green-*Lime/chartreuse scale (300400)Primary accent, active states, particles

Build docs developers (and LLMs) love