Skip to main content

Documentation Index

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

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

The Projects page is the primary portfolio surface. It renders a row of flag-style filter buttons above a two-column Framer Motion grid — when a filter is selected, React re-renders only the matching cards with a staggered entry animation. Cards share the custom ascii-border CSS class, which appends +----+ ruled lines above and below each card on md+ viewports, reinforcing the terminal-printout aesthetic.

Data structure

All project data lives in a const projects array defined above the component in main.js:
const projects = [
  {
    id: "01",
    title: "NEURAL_WEAVE",
    description: "A real-time data visualization dashboard for complex network topologies.",
    tags: ["--frontend", "--webgl"],
    link: "#"
  },
  {
    id: "02",
    title: "VOID_PROTOCOL",
    description: "Decentralized identity management system with zero-knowledge proofs.",
    tags: ["--backend", "--crypto"],
    link: "#"
  },
  {
    id: "03",
    title: "GRIMOIRE.UI",
    description: "A React component library focused on brutalist and occult aesthetics.",
    tags: ["--frontend", "--design"],
    link: "#"
  },
  {
    id: "04",
    title: "ECHO_CHAMBER",
    description: "Generative audio-visual installation controlled via terminal commands.",
    tags: ["--creative", "--hardware"],
    link: "#"
  }
];
IDTitleTagsDescription
01NEURAL_WEAVE--frontend, --webglReal-time data visualization for complex network topologies
02VOID_PROTOCOL--backend, --cryptoDecentralized identity management with zero-knowledge proofs
03GRIMOIRE.UI--frontend, --designReact component library with brutalist/occult aesthetics
04ECHO_CHAMBER--creative, --hardwareGenerative audio-visual installation via terminal commands

Filter bar

A separate const filters array defines the available filter buttons:
const filters = ["--all", "--frontend", "--backend", "--design", "--creative"];
Active state is tracked with useState("--all"). Each button conditionally applies one of two class strings:
className={`px-3 py-1 transition-colors ${
  activeFilter === filter
    ? "bg-acid text-black"
    : "text-lilac hover:text-bone border border-graphite"
}`}
The active button fills with acid green and inverts to black text; inactive buttons are ghosted with a graphite border and lilac text that lifts to bone on hover. The filtered array passed to the grid is computed inline at render time:
const filtered = activeFilter === "--all"
  ? projects
  : projects.filter(p => p.tags.includes(activeFilter));
The filter values in the filters array must exactly match the strings in each project’s tags array — they are compared with Array.includes(). The --all filter bypasses this check entirely.

Card anatomy

Each card is a motion.div inside a grid grid-cols-1 md:grid-cols-2 gap-8 container:
<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ delay: index * 0.1 }}
  className="ascii-border p-6 bg-black group relative overflow-hidden flex flex-col h-full"
>
Cards animate in sequentially — the first card has delay: 0, the second delay: 0.1, and so on. Re-filtering resets the animation because the component key changes with the filtered array.

Ghost ID number

An absolutely-positioned overlay sits at z-0 behind the card content. It is invisible (opacity-0) by default and fades to opacity-100 on group-hover, revealing the large ghost project ID:
<div className="absolute inset-0 bg-graphite opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center z-0">
  <span className="font-mono text-acid/20 text-6xl font-bold rotate-12 select-none">
    {project.id}
  </span>
</div>
The rotate-12 and text-acid/20 (20% opacity acid green) give it a watermark quality. select-none prevents text selection on hover.

Card header

The title row uses flex justify-between items-start:
  • Left: <h3> in font-mono text-xl font-bold text-bone, turns text-acid on group-hover
  • Right: [01] bracket notation in font-mono text-xs text-graphite

Description

A <p> in font-sans text-lilac text-sm with flex-1 fill — grows to push the footer to the card bottom in the flex column layout. A border-t border-graphite/50 row at the card bottom using flex items-center justify-between:
  • Left: Tag pills — each tag rendered as <span className="font-mono text-xs text-graphite">
  • Right: [ run.exe ] link in font-mono text-sm text-acid, with a > prefix that is opacity-0 until group/btn:hover targets it to opacity-100

Customization

Add a new project: Append an object to the projects array with the required fields:
{
  id: "05",
  title: "YOUR_PROJECT_NAME",
  description: "One sentence describing what it does.",
  tags: ["--frontend"],   // must match a value in the filters array
  link: "https://your-live-url.com"
}
Add a new filter category: Add the new tag string to both the filters array and the tags arrays of any project it should match. For example, adding "--mobile" to filters and ["--frontend", "--mobile"] to a project makes that project appear under both filters. Link to live projects: Replace the "#" value in each project’s link field with the actual URL. The link renders as an <a href={project.link}> — for external URLs, you may also want to add target="_blank" rel="noopener noreferrer" to the anchor element in the card JSX.
The ascii-border CSS class (defined in main.css) uses ::before and ::after pseudo-elements to draw +---+ ruled lines above and below the card. This effect only activates at the md breakpoint (768px+) to avoid clipping on mobile.

Build docs developers (and LLMs) love