Skip to main content

Documentation Index

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

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

TarotCard is the signature animated card component powering both the Tonight’s Reading spread on the home page and the Spells Cast project showcase. Each card features a 3D flip/reveal effect driven by Framer Motion — the front face displays a decorative diamond sigil and the card’s type label; hovering the card or clicking it rotates it 180° on the Y axis to reveal the title, subtitle, description, ingredient tags, and optional project link on the back face. Cards entrance-animate from below (y: 50 → 0, opacity: 0 → 1) with a configurable stagger delay, so a row of cards materialises one by one like a tarot spread being laid on the table.

Props

type
"past" | "present" | "future" | "project"
required
Controls the subtitle accent colour on the back face and the intended semantic role of the card. Each value maps to a distinct text colour token applied to the subtitle:
ValueSubtitle colourIntended use
"past"Bone/white text-boneBackstory, history, origin
"present"Amber text-amberCurrent work, present state
"future"Violet text-violetAspirations, upcoming plans
"project"Amber text-amberProject showcase cards
The front face always uses border-violet/40 and the back face always uses border-amber/40, regardless of type. Only the subtitle text colour on the back face changes with the type.
title
string
required
The main heading displayed on the back face of the card. Rendered in the serif display font. Example: "The Architect" or "Grimoire.js".
subtitle
string
required
A secondary category label rendered beneath the title on the back face in the card’s type-based accent colour. Example: "Current Workings" or "Documentation Generator".
description
string
required
The body copy shown on the back face of the card. Supports multiple sentences and scrolls within the card if long. Example: "Weaving complex state machines and responsive interfaces...".
ingredients
string[]
An array of tech-stack tag strings rendered as pill badges on the back face under an Ingredients heading. Example: ["React", "AST Parsing", "Tailwind"]. Renders whenever the prop is provided — not exclusive to the "project" type.
A URL rendered as a “Speak Incantation →” anchor button at the bottom of the back face. Opens in a new tab. Example: "https://github.com/your-repo". Renders whenever the prop is provided — not exclusive to the "project" type.
delay
number
default:"0"
Framer Motion entrance animation delay in seconds. Used to stagger cards when rendering a row. Example: 0.2, 0.4, 0.6.

Card Types

Subtitle colour: Bone/white (text-bone)The "past" type is used for history and origin cards — things that have already been woven into the practitioner’s story. On the home page it represents The Foundation card, evoking legacy experience and the old ways of the web.
<TarotCard
  type="past"
  title="The Foundation"
  subtitle="Where I Began"
  description="Forged in the fires of legacy codebases and monolithic architectures..."
  delay={0.2}
/>
The front face label reads “past” in text-violet/60. The subtitle on the back face is rendered in bone/white (text-bone).

Home Page Usage

The home page renders three TarotCard components inside a flex row forming Tonight’s Reading — a classic three-card past/present/future tarot spread.
import { TarotCard } from './components/TarotCard';

// Inside the "Tonight's Reading" section
<div className="flex flex-col md:flex-row items-center justify-center gap-8 md:gap-12">
  <TarotCard
    type="past"
    title="The Foundation"
    subtitle="Where I Began"
    description="Forged in the fires of legacy codebases and monolithic architectures. I learned
      the old ways before the modern frameworks took hold, giving me a deep understanding
      of the raw elements of the web."
    delay={0.2}
  />
  <TarotCard
    type="present"
    title="The Architect"
    subtitle="Current Workings"
    description="Weaving complex state machines and responsive interfaces. My current practice
      involves React, TypeScript, and crafting performant, accessible experiences that
      feel like magic to the end user."
    delay={0.4}
  />
  <TarotCard
    type="future"
    title="The Visionary"
    subtitle="What I'm Building"
    description="Looking toward the ethereal planes of WebGL, spatial computing, and AI-driven
      interfaces. The next evolution of my craft involves bridging the gap between
      human intent and machine execution."
    delay={0.6}
  />
</div>
The section heading is wrapped in its own motion.div with whileInView so it fades in as the user scrolls down, slightly before the cards themselves animate in.

Projects Page Usage

The Projects page (/projects) renders all four project cards by mapping over a data array. Each card is wrapped in a motion.div that applies a spring entrance animation, then passes type="project" to TarotCard.
import { TarotCard } from './components/TarotCard';
import { motion } from 'framer-motion';

const projects = [
  {
    title: "Grimoire.js",
    subtitle: "Documentation Generator",
    description: "A tool that parses your chaotic codebase and generates beautiful, readable "
      + "documentation that looks like ancient manuscripts. Includes automated dependency graphing.",
    ingredients: ["React", "AST Parsing", "Tailwind"],
    link: "#",
  },
  {
    title: "Soul Catcher",
    subtitle: "Analytics Dashboard",
    description: "Privacy-first analytics that tracks user intent rather than identity. "
      + "Visualizes traffic as flowing ethereal energy streams across a dark map.",
    ingredients: ["Next.js", "D3.js", "PostgreSQL"],
    link: "#",
  },
  {
    title: "Alchemist's Forge",
    subtitle: "Component Library",
    description: "A highly accessible, headless component library for building complex interfaces. "
      + "Focuses on keyboard navigation and screen reader support.",
    ingredients: ["TypeScript", "Radix UI", "Framer Motion"],
    link: "#",
  },
  {
    title: "Divination API",
    subtitle: "Predictive Search",
    description: "A fast, edge-hosted search API that predicts what users are looking for before "
      + "they finish typing, using lightweight ML models.",
    ingredients: ["Rust", "Redis", "Cloudflare Workers"],
    link: "#",
  },
];

export function ProjectsPage() {
  return (
    <div className="flex flex-wrap justify-center gap-8 md:gap-12 max-w-6xl mx-auto">
      {projects.map((project, index) => (
        <motion.div
          key={project.title}
          initial={{ opacity: 0, scale: 0.8, rotate: -10 }}
          animate={{ opacity: 1, scale: 1, rotate: 0 }}
          transition={{ delay: index * 0.2, type: "spring", stiffness: 100 }}
          className="hover:z-10"
        >
          <TarotCard
            type="project"
            title={project.title}
            subtitle={project.subtitle}
            description={project.description}
            ingredients={project.ingredients}
            link={project.link}
          />
        </motion.div>
      ))}
    </div>
  );
}
Notice that delay is not passed to TarotCard on the projects page — the stagger is handled by the outer motion.div wrapper using index * 0.2. Passing delay here as well would cause a double-delay and make cards appear sluggish.

Adding New Cards

Follow these steps to add a fifth project card to the showcase:
1

Add a new entry to the projects array

Append your project object to the projects data array. Include all required fields — title, subtitle, description — plus optional ingredients and link.
{
  title: "Phantom Router",
  subtitle: "Edge Middleware",
  description: "An ultra-fast routing layer that runs at the CDN edge, "
    + "intercepting requests before they reach the origin server.",
  ingredients: ["Cloudflare Workers", "Hono", "TypeScript"],
  link: "https://github.com/your-repo/phantom-router",
},
2

Verify the map renders it automatically

Because the page uses projects.map(...), no JSX changes are needed. The new object will automatically be picked up and rendered with the correct index-based stagger delay.
// The existing map handles everything:
{projects.map((project, index) => (
  <motion.div
    key={project.title}
    initial={{ opacity: 0, scale: 0.8, rotate: -10 }}
    animate={{ opacity: 1, scale: 1, rotate: 0 }}
    transition={{ delay: index * 0.2, type: "spring", stiffness: 100 }}
    className="hover:z-10"
  >
    <TarotCard type="project" {...project} />
  </motion.div>
))}
3

Check the layout wraps correctly

The grid uses flex flex-wrap justify-center. With five cards you’ll have a row of four and a single centred card below. If you prefer a symmetrical 2+3 or 3+2 layout, switch to a CSS grid:
<div className="grid grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl mx-auto justify-items-center">
  {projects.map((project, index) => ( /* ... */ ))}
</div>
4

Confirm flip behaviour

Click or hover the new card in the browser to verify the 3D flip reveals the description, ingredients pills render correctly, and the “Speak Incantation →” link opens your URL in a new tab.

Entrance Animation

The delay prop controls the stagger offset for the Framer Motion entrance animation baked into every TarotCard. Internally, the component uses:
initial: { opacity: 0, y: 50 }
animate: { opacity: 1, y: 0 }
transition: { delay: delay, duration: 0.6, ease: "easeOut" }
By assigning incrementally larger values — 0.2, 0.4, 0.6 — to a row of cards, each one rises into view 200 ms after the previous, mimicking the gesture of a reader placing cards one at a time.
// Standard 3-card stagger pattern
<TarotCard type="past"    title="..." delay={0.2} ... />
<TarotCard type="present" title="..." delay={0.4} ... />
<TarotCard type="future"  title="..." delay={0.6} ... />
// Two cards: 200 ms apart
<TarotCard delay={0.2} ... />
<TarotCard delay={0.4} ... />
On the Projects page, the outer motion.div wrapper uses spring physics rather than an easing curve for its entrance:
initial: { opacity: 0, scale: 0.8, rotate: -10 }
animate: { opacity: 1, scale: 1, rotate: 0 }
transition: { delay: index * 0.2, type: "spring", stiffness: 100 }
This creates a “flipping into position” effect — each card overshoots slightly on the rotate and scale axes before settling, as though the cards are being physically dealt onto the table. The stiffness: 100 value keeps the spring tight enough to feel snappy without excessive bounce.

Build docs developers (and LLMs) love