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 SpellBook component renders the skills section of the portfolio as a physical hardcover book. A dark leather spine sits on the left, and the open page area displays one chapter at a time on a cream parchment background. Visitors page through chapters using prev/next chevron buttons below the book. The component takes no props — all data is defined internally.

Data structure

Three chapters are hardcoded as a const array inside SpellBook.js. Each chapter object contains a title, an incantation subtitle, and a skills array where every skill has a name and a level (0–100 integer representing percentage mastery):
const chapters = [
  {
    title: "Front-End Alchemy",
    incantation: "Visually manifest that which is unseen.",
    skills: [
      { name: "React / Next.js", level: 90 },
      { name: "TypeScript",      level: 85 },
      { name: "Tailwind CSS",    level: 95 },
      { name: "Framer Motion",   level: 80 },
    ],
  },
  {
    title: "Back-End Sorcery",
    incantation: "Command the hidden forces of data.",
    skills: [
      { name: "Node.js",    level: 85 },
      { name: "PostgreSQL", level: 75 },
      { name: "GraphQL",    level: 80 },
      { name: "Python",     level: 70 },
    ],
  },
  {
    title: "Mystical Tools",
    incantation: "Artifacts of power for the modern mage.",
    skills: [
      { name: "Git / GitHub", level: 90 },
      { name: "Docker",       level: 70 },
      { name: "AWS",          level: 65 },
      { name: "Figma",        level: 85 },
    ],
  },
];

Visual layout

Book shell

The outer container is relative w-full max-w-4xl mx-auto h-[600px] with perspective-1000 applied to enable child 3D transforms. Inside it, a dark background div (bg-ink) provides the book body, styled with heavy drop shadows to suggest depth:
shadow-[20px_20px_60px_rgba(0,0,0,0.8)]

Spine

A w-12 left strip (bg-void) mimics the book spine with three horizontal amber rule marks to suggest binding stitches.

Parchment page

The right portion is bg-[#e8dcc7] (warm cream). A noise SVG texture is layered on top at 40 % opacity with mix-blend-multiply to simulate aged paper grain:
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200'
  xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E
  %3CfeTurbulence type='fractalNoise' baseFrequency='0.8'
  numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E
  %3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'
  /%3E%3C/svg%3E")`

Page-turn animation

Chapter content is wrapped in Framer Motion’s AnimatePresence mode="wait" so the exiting chapter fully completes its animation before the entering one begins. A custom direction value (stored in state as +1 or -1) is passed as the Framer Motion custom prop to drive directional rotation:
const variants = {
  enter:  (dir) => ({ rotateY: dir > 0 ? -90 : 90,  opacity: 0, z: 100 }),
  center:          ({ rotateY: 0,                     opacity: 1, z: 0   }),
  exit:   (dir) => ({ rotateY: dir > 0 ?  90 : -90,  opacity: 0, z: 100 }),
};
The animated motion.div page uses transformStyle: "preserve-3d" and origin-left so the rotation pivots from the spine edge, matching how a real book page would turn. The transition is a spring with duration: 0.6 and bounce: 0.2.

Skill progress bars

Each skill inside a chapter renders a label row (name left, percentage right) and a progress bar below it. The bar track is bg-ink/10 and the fill is a motion.div that animates its width from 0 to ${level}% on mount:
<motion.div
  className="h-full bg-ink"
  initial={{ width: 0 }}
  animate={{ width: `${skill.level}%` }}
  transition={{ duration: 1, delay: 0.3 + index * 0.1 }}
/>
Each bar is staggered by index * 0.1 s so they fill in sequence from top to bottom.
Two chevron buttons (ChevronLeft, ChevronRight from Lucide React) sit 80 px below the book in a centered flex row. The onClick handler increments or decrements the currentChapter index and stores the direction:
const turnPage = (direction) => {
  const next = currentChapter + direction;
  if (next >= 0 && next < chapters.length) {
    setDirection(direction);
    setCurrentChapter(next);
  }
};
Buttons are disabled when at the first or last chapter and dim to 30 % opacity via disabled:opacity-30.

Usage

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

// No props — drop it straight onto the Skills page
<SpellBook />

Modifying chapter data

To add a new chapter or skill, edit the chapters array at the top of SpellBook.js:
{
  title: "Eldritch DevOps",
  incantation: "Tame the infrastructure demons.",
  skills: [
    { name: "Terraform", level: 60 },
    { name: "Kubernetes", level: 55 },
  ],
},
Adding more than three chapters will work without code changes — the prev/next buttons check index >= 0 && index < chapters.length dynamically. The parchment page area has overflow-hidden so keep skill lists to four items or fewer to avoid clipping on smaller displays.

Build docs developers (and LLMs) love