Skip to main content

Documentation Index

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

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

The Craft uses no CMS, no database, and no external content API. Every piece of portfolio content — your bio, projects, skills, work history, blog posts, and testimonials — is co-located directly as plain JavaScript objects and arrays inside the page files under assets/. To update any section, open the relevant file, edit the data, and save. Vite’s hot-module replacement will reflect your changes in the browser instantly during development.

Your Name and Bio

File: assets/About.js The About page renders your name as a static heading and populates three bio paragraphs from JSX literals. Locate the following structure and replace the placeholder values with your own:
// The heading that displays your name
<h1 className="font-cinzel text-4xl text-midnight-darker mb-2">
  Know Thy Witch
</h1>
<h2 className="font-cursive text-2xl text-blood mb-8">
  A Brief Familiar History
</h2>

// The three bio paragraphs
<p>I did not choose the path of the developer; the code called to me...</p>
<p>For over a decade, I have wandered the digital brambles...</p>
<p>When I am not weaving spells for the web, you can find me...</p>

// The availability status line
<span className="font-garamond italic text-blood">
  Accepting new pacts & contracts
</span>
Replace the <p> tag content with your actual bio. Replace 'Accepting new pacts & contracts' with your current availability — for example, 'Open to full-time roles' or 'Currently unavailable'.
The 'Know Thy Witch' heading is the page’s section title, not your personal name. If you want your name displayed prominently on this page, add an <h2> or edit the existing <h2> that reads 'A Brief Familiar History' to show your name instead.

Projects

File: assets/Projects.js Projects are stored in the projects array near the top of the file. Each entry renders as a pinned parchment card with a slight rotation. The array shape is:
{
  title: 'Necromancer UI',
  ingredients: ['React', 'Tailwind', 'Framer Motion'],
  incantation: 'A dark-mode dashboard that resurrects dead data streams into actionable insights. Built for the modern data warlock.',
  link: '#',
  rotation: -2  // degrees of CSS rotation (-5 to 5 works well)
}
The four default entries are:
TitleStack
Necromancer UIReact, Tailwind, Framer Motion
Alchemist E-CommerceNext.js, Stripe, PostgreSQL
Scrying Glass AppReact Native, WebRTC, Socket.io
Grimoire CMSVue, Node.js, MongoDB
Replace each entry’s title, ingredients, and incantation with your own project details.
Every entry has link: '#' as a placeholder. Replace this with the actual deployed URL of your project (e.g. link: 'https://my-project.vercel.app'). Until you do, the “Inspect Artifact →” button on each card goes nowhere.
The rotation value applies a subtle CSS tilt to each card. Values between -5 and 5 look natural. Alternating between a negative and positive value across cards (e.g. -2, 3, -1, 4) gives the stacked-papers effect its character.

Skills

File: assets/Skills.js Skills are stored in the skills array and rendered as interactive nodes on a pentagram SVG. Hovering a node displays the skill’s full description in the panel to the left. Each entry shape is:
{
  id: 'divination',
  name: 'Divination',
  subtitle: 'Analytics & Data',
  description: 'Scrying into the data streams to foresee user behavior and system performance. (Google Analytics, Mixpanel, Datadog)',
  x: 100,  // SVG x coordinate (0–200)
  y: 20    // SVG y coordinate (0–200)
}
The five default skills and their pentagram vertex positions are:
IDNameSubtitlexy
divinationDivinationAnalytics & Data10020
transmutationTransmutationState & Refactoring18080
illusionIllusionUI & Animations150170
conjurationConjurationBackend & APIs50170
abjurationAbjurationSecurity & Testing2080
If you only want to rename skills, change name, subtitle, and description but leave x and y untouched. The five coordinates form a regular pentagon — the pentagram lines in the SVG are hard-coded to those same points, so moving node positions without also updating the <path> elements in Skills.js will misalign the star.

Work History

File: assets/Work.js Past roles are stored in the work history array. Each entry maps to a candle in the interactive candle row — clicking a candle reveals that role’s details in an animated glass panel. The entry shape is:
{
  id: 'coven-1',
  year: '2023 - Present',
  company: 'Ethereal Systems',
  role: 'High Sorcerer (Senior Frontend)',
  description: 'Leading a coven of 5 junior developers. Architected the new React-based spellbook that increased casting speed (page load) by 40%. Implemented dark rituals (Redux Saga) for complex state management.',
  candleHeight: 140  // px — taller candle = more prominent role
}
The three default entries use id values 'coven-1', 'coven-2', and 'coven-3'. The first entry is selected by default when the page loads.
Use candleHeight to communicate visual hierarchy. Your most senior or current role should have the tallest candle (the default maximum is 140). Earlier or more junior roles look natural at 100 and 70.
The id field must be unique across all entries — it is used as a React key and as the state selector when a candle is clicked. If you add more entries, use a consistent naming pattern such as 'coven-4', 'coven-5', etc.

Blog Posts

File: assets/Blog.js Blog entries are stored in the posts array near the top of the file. They render as overlapping parchment cards that fan out and lift on hover. The entry shape is:
{
  title: 'Binding State with Zustand',
  date: 'Oct 31, 2023',
  excerpt: 'A treatise on lightweight state management rituals that avoid the heavy toll of Redux boilerplate.',
  rotation: -3  // slight tilt in degrees
}
The three default entries are:
TitleDate
Binding State with ZustandOct 31, 2023
The Necromancy of Legacy CodeSep 15, 2023
Warding Your APIsAug 02, 2023
Blog cards currently display only a title, date, and excerpt — there is no detail page or external link built in. The “Read Entry →” label is decorative. If you want cards to link to real blog posts, add a link field to each entry and wire it up to the anchor tag inside the card’s JSX in Blog.js.

Testimonials

File: assets/Testimonials.js Testimonials are stored in the testimonials array and displayed one at a time inside an animated crystal-ball mirror. Clicking the mirror or using the dot indicators cycles through entries. The entry shape is:
{
  id: 1,
  quote: 'They lifted a curse from our legacy codebase that had plagued us for years. The new system runs smoother than a polished scrying glass.',
  author: 'Lord Malakor',
  title: 'CTO, Ethereal Systems'
}
The three default testimonials use id values 1, 2, and 3. Replace the quote, author, and title fields with real client or colleague endorsements.
The id field drives the dot-indicator key prop. Keep IDs as unique integers. If you remove entries and leave gaps in the sequence (e.g. 1, 3), nothing will break — but keeping them sequential (1, 2, 3, …) is cleaner.

Contact Form

File: assets/Contact.js The contact form collects three fields — True Name (name), Ethereal Address (email), and Your Intent (message) — and currently simulates a successful submission using a setTimeout:
const handleSubmit = (e) => {
  e.preventDefault();
  setIsLoading(true);
  setTimeout(() => {
    setIsLoading(false);
    setIsSuccess(true);
  }, 2000);
};
After the timeout fires, the form is replaced by a success state that reads “Message Cast.” The submitted data goes nowhere.
Without replacing the submit handler with a real integration, all form submissions are silently discarded. No email is ever sent. Wire up a real email service before deploying your portfolio publicly.
To deliver messages to your inbox, replace the setTimeout block with one of the following approaches:
1

Formspree (no backend required)

Sign up at formspree.io, create a form, and replace the onSubmit handler with a fetch call to your form endpoint:
const handleSubmit = async (e) => {
  e.preventDefault();
  setIsLoading(true);
  await fetch('https://formspree.io/f/YOUR_FORM_ID', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(formData),
  });
  setIsLoading(false);
  setIsSuccess(true);
};
2

EmailJS (client-side only)

Use the EmailJS SDK to send email directly from the browser without a server. Install the package and call emailjs.send() inside the handler.
3

Serverless function

Deploy a serverless function (Vercel Functions, Netlify Functions, or AWS Lambda) that accepts a POST request and uses Nodemailer or an email API such as SendGrid or Resend to forward the message.

File: components/MoonPhaseNav.js The navigation data array controls the tooltip label shown on hover for each route, the route path, and the moon phase icon rendered in the navigation rail. The eight-entry array is defined near the top of components/MoonPhaseNav.js and has the following logical structure:
// Logical structure — find this array by searching for path: "/" in MoonPhaseNav.js
[
  { path: '/',             name: 'The Cover',        phase: 'new'             },
  { path: '/about',        name: 'The Practitioner', phase: 'waxing-crescent' },
  { path: '/projects',     name: 'Spellwork',        phase: 'first-quarter'   },
  { path: '/skills',       name: 'Arcane Arts',      phase: 'waxing-gibbous'  },
  { path: '/work',         name: 'Coven Records',    phase: 'full'            },
  { path: '/case-studies', name: 'Tome of Workings', phase: 'waning-gibbous'  },
  { path: '/blog',         name: 'Whispers',         phase: 'last-quarter'    },
  { path: '/contact',      name: 'Summoning',        phase: 'waning-crescent' },
]
MoonPhaseNav.js is a pre-compiled, minified bundle file — the array’s variable name in the source is an internal identifier. To locate the array, search for the string path:"/" inside the file; it appears at the very start of the route data. Edit the name and phase values for any entry directly in-place.
You can rename the name field freely (e.g. change 'Spellwork' to 'Projects') without affecting routing — the path field is what React Router uses for navigation. The phase value determines which lunar icon is drawn next to each entry.
Do not change the path values here without also updating the matching <Route path="..."> entry in assets/main.js. The two must stay in sync or navigation links will break.

Build docs developers (and LLMs) love