The Magical Portfolio is a single-page React application deployed as a collection of static files. At runtime it behaves like a traditional SPA — one JavaScript bundle, one CSS file, client-side routing — but at rest on disk (and on the file-server) it presents a separate HTML entry-point for every URL. This hybrid model means the site works on any static host (GitHub Pages, Netlify, Cloudflare Pages, S3 + CloudFront) without server-side routing support, while still delivering full animated page transitions and a cohesive mystical design system.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/magical/llms.txt
Use this file to discover all available pages before exploring further.
Component Hierarchy
The App shell composes four top-level concerns that are always mounted, regardless of which route is active:| Layer | Component | z-index | Role |
|---|---|---|---|
| Background | BackgroundEffects | z-[-1] | Animated star particles, radial gradients, noise texture |
| Cursor | CustomCursor (dot) | z-[100] | 12 × 12 px mystic-mint spring-physics dot |
| Cursor | CustomCursor (glow) | z-[99] | 128 × 128 px mystic-violet/10 blur halo |
| Navigation | Navigation | z-50 | Fixed header; scrolls into a frosted-glass variant after 50 px |
| Content | Route page | auto | Full-height page component, padded inside the motion wrapper |
BackgroundEffects renders 40 randomly positioned floating particles using Framer Motion’s animate prop with infinite repeat, layered over large blurred radial gradients (blur-[120px] / blur-[150px]) and a near-invisible SVG noise texture.
CustomCursor replaces the system cursor on pointer-capable devices (window.matchMedia("(pointer: fine)")). The dot uses spring physics (stiffness: 500, damping: 28, mass: 0.5) so it trails the pointer with a snappy feel. The glow halo uses softer springs (stiffness: 250, damping: 20, mass: 0.8).
Build Pipeline
Vite bundles all React source into three output artefacts consumed by every HTML shim:<link rel="modulepreload"> the pre-split component chunks (Navigation.js, BackgroundEffects.js, CustomCursor.js, Sigil.js, and useScreenInit.js) so they are fetched in parallel with main.js before the browser starts parsing the bundle.
Static Deployment Model
Because the app uses hash-based routing (see Routing), every URL the user navigates to is structurally<origin>/pages/About.html#/about — the #/about fragment is consumed entirely by the browser-side React Router and never reaches the server. The static host only ever needs to serve a plain .html file.
Each route therefore has its own HTML shim in the pages/ directory:
<div id="root">, loads assets/main.js (relative path adjusted for nesting depth), and includes the hash-redirect inline script. The React app boots, reads window.__STATIC_PAGE_ROUTE__ to know which hash fragment to initialise, and React Router takes it from there.
This means no 404 rewrites are needed on the host. A user bookmarking /pages/About.html or a search engine following a direct link will always receive a valid HTML document with the correct <title> tag.
Animation Architecture
Framer Motion drives every motion layer in the app:| Concern | API Used | Example |
|---|---|---|
| Page enter / exit | AnimatePresence + motion.div | opacity: 0→1, filter: blur(10px)→blur(0px), duration: 0.8 s |
| Scroll-linked progress | useScroll + useTransform | Chronicle page timeline bar fills as the user scrolls |
| Spring physics — cursor | motion.div with spring transition | CustomCursor tracks pointer with configurable stiffness / damping |
| Spring physics — skill orbs | motion.div with type: "spring" | Affinity orbs spring into their orbital positions on mount |
| Path drawing — sigils | motion.text / motion.path strokeDasharray | Hero name strokes in letter-by-letter on the Portal page |
| 3-D tilt — cards | useMotionValue + useTransform | Grimoire and Portal cards tilt on mouse move via rotateX / rotateY |
AnimatePresence keyed on useLocation().pathname. Both initial and exit states use the same values (opacity: 0, filter: blur(10px)), giving a symmetric cross-fade between routes:
[0.22, 1, 0.36, 1] is a custom cubic-bezier that accelerates quickly at the start and decelerates at the end, giving the blur-in a cinematic, spell-cast feel.
Routing
Hash-based routing, per-route HTML shims, the nav items array, and how to add a new page.
Theming
The
mystic-* color palette, typography stack, glow utilities, and Tailwind JIT configuration.