Six components provide the ambient atmosphere and interactive focal points that make Spell Index feel alive. Four of them take no props and are mounted globally or per-page;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.
ConstellationText accepts a single text prop; and SummoningForm manages its own form state internally. Each is documented in full below.
BackgroundParticles
BackgroundParticles renders a position: fixed full-viewport layer (z-index: 0) of 40 floating particles. It sits beneath all page content and provides a continuous ambient motion effect throughout the entire app.
How it works
On mount,useMemo generates a stable array of 40 particle descriptors so that layout never triggers re-generation:
motion.div that animates upward by 20 vh and drifts ±5 vw horizontally over its duration, fading from opacity: 0 through 0.4 and back to 0 on an infinite loop. Amber particles glow with shadow-[0_0_8px_rgba(245,196,90,0.6)]; teal particles use shadow-[0_0_8px_rgba(61,214,196,0.4)].
A noise texture SVG and a radial-gradient background (from-ink/40 via-midnight to-void) are layered behind the particles to deepen the starfield appearance.
Usage
CursorTrail
CursorTrail replaces the default OS cursor with a custom animated orb and a trailing particle cloud. It is mounted globally at the app root level and covers the entire viewport via position: fixed, z-index: 100.
Cursor orb
Amotion.div spring-animates to the current mouse position (clientX - 8, clientY - 8) on every mousemove event. When the pointer moves over an interactive element (a, button, input, textarea, [role="button"]), the orb scales to 1.5× and transitions from teal to amber to signal clickability:
Trail particles
Up to 12 historical mouse positions are stored in state. AsetInterval trims the oldest position every 50 ms. Each trail point is rendered via AnimatePresence as a motion.div that immediately begins animating to opacity: 0, scale: 0 and is removed on exit — creating the fading comet-tail effect.
Usage
MoonPhases
MoonPhases renders a 256 × 256 px moon SVG whose illuminated area changes in real time as the user scrolls the page. It is placed on the home page as a decorative element that responds to scroll progress.
How it works
Framer Motion’suseScroll tracks the page’s vertical scroll as a MotionValue. A derived useTransform maps scrollY from the range [0, 1000] to [0, 100] (a 0–100 scroll percentage). The component re-renders on each onChange event (clamped 0–100).
The moon shape is drawn as a single SVG <path> using a dynamic M/A/A/Z arc formula that morphs between new moon, crescent, half, gibbous, and full moon shapes based on the scrollPercent value:
0.5 → 1.2 → 0.5 via a second useTransform) for extra depth.
Usage
MoonPhases uses Framer Motion’s useScroll which measures the document’s root scroll, not a container scroll. It should be placed on a page that has enough content to scroll — it will stay static on pages shorter than the viewport.CauldronCanvas
CauldronCanvas renders an HTML5 <canvas> animation (400 px tall, full container width) where circular tech-skill bubbles rise from the bottom, wobble side to side, and fade out near the top. A hand-drawn SVG cauldron floats below the canvas in an infinite gentle bob animation.
Canvas bubble system
Ten technology names are hardcoded as the bubble content pool:| Property | Description |
|---|---|
x | Horizontal spawn position (near canvas centre ± 50 px) |
y | Current vertical position (decrements each frame by speed) |
radius | 20–40 px |
speed | 0.5–1.5 px per frame |
text | Random tech label from the pool |
wobble | Accumulating angle (radians) for sin drift |
wobbleSpeed | 0.02–0.07 radians per frame |
opacity | Fades in from 0 near the bottom, fades out near the top |
y < -50) or fades to zero, it is reset as a fresh bubble from the cauldron mouth.
The canvas is sized to its parent container’s clientWidth on mount and on window resize. A requestAnimationFrame loop drives rendering; the loop is cancelled on component unmount via the effect cleanup function.
Usage
ConstellationText
ConstellationText takes a single text string, splits it into individual characters, and renders each as a large Cinzel font letter with an animated amber star dot above it. A dashed SVG polyline connects the star dots in a constellation pattern that draws itself in over 3 seconds.
Props
The string to render as a constellation. Each character becomes an individual animated element. Spaces render as blank gaps. Example:
"ALEXANDER".Animation sequence
- The outer
motion.divusesvariantswithstaggerChildren: 0.2anddelayChildren: 0.5so each character enters in sequence. - Each character
motion.divstarts at{ opacity: 0, y: 20, filter: "blur(10px)" }and animates to fully visible and unblurred over1 s. - The amber star dot above each character pulses
scale: 1 → 1.5 → 1andopacity: 0.5 → 1 → 0.5in an infinite loop, staggered per character bydelay: index * 0.2. - The connecting SVG
motion.pathanimatespathLength: 0 → 1over3 swith a 1 s delay, drawing the constellation line after the letters have appeared.
Usage
SummoningForm
SummoningForm is the contact form component. It renders three fields (name, email, message) on top of an SVG summoning circle that progressively illuminates as the user fills in each field. On successful submission a full-overlay success animation plays.
Summoning circle progress
The SVG behind the form contains three progressive elements that become visible as fields are completed:| Completed fields | Element revealed |
|---|---|
| 1 | First triangle (upward-pointing) |
| 2 | Second triangle (downward-pointing) |
| 3 | Inner amber circle — full pentagram formed |
strokeDashoffset animated circle tracks exact completion percentage: strokeDashoffset: 565 - (565 * completedFields / 3 * 100 / 100).
Form fields
| Label | Input type | Required |
|---|---|---|
| True Name | text | Yes |
| Astral Address | email | Yes |
| Incantation | textarea (4 rows) | Yes |
disabled until all three fields have content and while the form is in its 2-second simulated submission state (“Summoning…”). After submission, all fields reset and a success overlay appears for 5 seconds before auto-dismissing.
Usage
SummoningForm currently uses a client-side setTimeout to simulate form submission. To wire it to a real backend, replace the setTimeout block inside the handleSubmit function with your fetch / API call and resolve the loading/success state accordingly.