Skip to main content

Documentation Index

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

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

TomeCard is the display component for individual writing post entries on the Writing Archive page. Each card is styled like a page torn from a spell book — a neon-purple left border, a font-display title that glows on hover, and a ghost-button link that drifts rightward with a arrow when the card is focused. Cards scroll into view with a staggered Framer Motion fade-and-rise animation based on their position in the list.

Rendered Layout

The card is an <article> element built on a bg-surface-elevated border-l-4 border-neon-purple base. On hover the entire card lifts by -translate-y-1 and a vertical magenta-to-transparent gradient bleeds through the background at 30% opacity. Inside the card, the structure is top-to-bottom:
  1. Header row — a writing sigil icon (neon-purple, opacity-70opacity-100 on hover) alongside the channel name in font-mono text-xs text-neon-purple tracking-widest uppercase. A <time> element on the right shows the date value in the same mono style.
  2. Titlefont-display text-2xl text-text-primary, transitioning to text-glow-purple on hover.
  3. Excerptfont-sans text-text-secondary leading-relaxed, filling the remaining vertical space via flex-grow.
  4. Action link — a GlyphButton in ghost / purple variant with the label "Invoke Text" and an animated that translates right on hover. The url field in the data shape is intended for the live post link; wiring it to the button requires passing an onClick (e.g. window.open(article.url, "_blank")) in the page that renders TomeCard.

Props

PropTypeRequiredDescription
articleobjectYesA writing entry from data/writing.js
indexnumberYesPosition in the list; drives animation stagger delay (index * 0.1s)

Data Shape

Each writing entry in data/writing.js follows this schema:
{
  id: string,       // Unique identifier
  title: string,    // Post title, displayed in font-display
  excerpt: string,  // One-sentence teaser shown below the title
  date: string,     // ISO 8601 date string (YYYY-MM-DD)
  channel: string,  // Category label; see Writing Channels below
  url: string,      // Link to the full post (external URL or "#")
}

Writing Channels

The five channel categories established in data/writing.js act as the tagging system for the archive. Each maps to a distinct type of content:

Technical

Deep dives into specific technologies, APIs, or front-end patterns. Example: Banishing Memory Leaks in React Applications.

Reflections

Personal essays on career path, process, and perspective. Example: From Retail to React: A Non-Linear Path.

Bug Reports

Post-mortems on specific bugs with root-cause analysis and resolution. Example: The Great Z-Index War of 2025.

Project Writeups

Build logs and architectural decision records for personal projects. Example: Building the Void Tracker.

General Audience

Accessible explainers on developer concepts for non-specialists. Example: Demystifying the Event Loop.

Full Writing Data

All five entries from data/writing.js:
// data/writing.js
const writing = [
  {
    id: "1",
    title: "Banishing Memory Leaks in React Applications",
    excerpt:
      "A deep dive into common useEffect pitfalls that lead to memory leaks, and how to properly cleanse your component lifecycle.",
    date: "2025-10-31",
    channel: "Technical",
    url: "#",
  },
  {
    id: "2",
    title: "From Retail to React: A Non-Linear Path",
    excerpt:
      "How dealing with unpredictable customers prepared me for dealing with unpredictable state mutations.",
    date: "2025-09-15",
    channel: "Reflections",
    url: "#",
  },
  {
    id: "3",
    title: "The Great Z-Index War of 2025",
    excerpt:
      "A post-mortem on a bug where a modal was trapped beneath a seemingly innocent sticky header. Lessons learned in stacking contexts.",
    date: "2025-08-22",
    channel: "Bug Reports",
    url: "#",
  },
  {
    id: "4",
    title: "Building the Void Tracker",
    excerpt:
      "Architectural decisions and trade-offs made while building a local-first task management system using Zustand and IndexedDB.",
    date: "2025-07-10",
    channel: "Project Writeups",
    url: "#",
  },
  {
    id: "5",
    title: "Demystifying the Event Loop",
    excerpt:
      "An explanation of the JavaScript event loop designed for those who find the official documentation too arcane.",
    date: "2025-06-05",
    channel: "General Audience",
    url: "#",
  },
];

JSX Usage

import { TomeCard } from "@/components/writing/TomeCard";
import { writing } from "@/data/writing";

export default function WritingPage() {
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
      {writing.map((article, index) => (
        <TomeCard key={article.id} article={article} index={index} />
      ))}
    </div>
  );
}

Adding a New Writing Entry

1

Open the data file

Edit data/writing.js and append a new object to the array.
2

Fill in all required fields

Provide id, title, excerpt, date (ISO 8601), channel (one of the five categories above), and url (the live URL of the post, or "#" for drafts).
3

Choose the correct channel

Use one of the five established channel strings exactly as written. The channel string is rendered verbatim in the card header as a tracking-widest uppercase label.
4

Verify the stagger animation

The index prop is passed automatically when you map over the array, so the new card will receive the correct stagger delay without any extra work.
Keep excerpts to a single sentence or two. The card layout uses flex-grow on the excerpt paragraph so all cards in a row match height — a long excerpt in one card will stretch the entire row.

Build docs developers (and LLMs) love