Skip to main content

Documentation Index

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

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

Postcard renders one blog article as a tactile, vintage-styled postcard complete with a stamp placeholder, a handwritten-style excerpt, and a textured paper background. Multiple Postcard components are stacked on top of each other on the Blog page — clicking a card fans it out and lifts it above the stack to reveal its content.

Props

article
object
required
The blog article data object to render on this postcard.
article.id
number
required
Unique identifier for the article. Used by the Blog page to track which card is currently selected via useState.
article.title
string
required
Article title displayed in the spooky font at the top-left of the card face.
article.date
string
required
Publication date rendered in small-caps monospace at the bottom-right of the card (e.g. "Oct 31, 2023").
article.excerpt
string
required
Preview text shown in a handwritten-style font (Caveat Brush) in the body of the card. Always rendered on the card face; lifting a card above the stack brings it fully into view.
article.color
string
required
Hex background colour for this card (e.g. "#f4f1ea"). Giving each article a slightly different tint creates visual variety in the stack and helps readers distinguish between cards.
index
number
required
Zero-based position of this card in the rendered stack. Controls both the rotation offset (even indices tilt right, odd tilt left) and the base z-index so earlier articles sit closer to the top of the pile.
isSelected
boolean
required
When true, the card springs upward, flattens its rotation, and scales to 1.1×. Only one card should be selected at a time — managed by the Blog page’s useState.
onClick
function
required
Callback fired when the card is clicked. The Blog page uses this to toggle selection: clicking an already-selected card passes null to collapse it; clicking a different card passes its id to select it.

Blog Articles Data

The four built-in articles are defined in assets/main.js and passed to each <Postcard> by the Blog page:
const articles = [
  {
    id: 1,
    title: 'Tales from the DOM',
    date: 'Oct 31, 2023',
    excerpt: 'Dearest reader, I encountered a terrifying bug today. The elements were shifting without cause. It was... a race condition.',
    color: '#f4f1ea',
  },
  {
    id: 2,
    title: 'The Haunted Hook',
    date: 'Oct 15, 2023',
    excerpt: 'useEffect ran infinitely. The browser screamed. Memory spiked. I had forgotten the dependency array. May the code gods have mercy.',
    color: '#e2d5c4',
  },
  {
    id: 3,
    title: 'CSS Grid Graveyard',
    date: 'Sep 28, 2023',
    excerpt: 'Buried my floats today. Grid is the future. The layout is finally at peace, resting perfectly centered in its container.',
    color: '#d7ccc8',
  },
  {
    id: 4,
    title: 'Vampire Variables',
    date: 'Sep 10, 2023',
    excerpt: 'Global variables are draining the life from this application. We must isolate them before they infect the entire state.',
    color: '#cfd8dc',
  },
];

Stack Layout & Animation

Cards are positioned absolute inside a fixed-height container so they overlap into a physical pile. Framer Motion spring physics handle all transitions:
StaterotateyxscalezIndex
Stacked±(index × 2 + 2)°index × 4px±10px (even +, odd −)110 + index
Hovered–20pxunchanged140
Selected–150px01.150
The alternating tilt direction is calculated from the index parity: even-indexed cards lean right and shift right (x: 10); odd-indexed cards lean left and shift left (x: -10). On selection both offsets reset to zero.

Usage

Rendering the full stack

The Blog page manages selection state and maps the articles array to individual <Postcard> components:
import { useState } from 'react';
import { Postcard } from './components/Postcard';
import { articles } from './assets/main';

export default function BlogPage() {
  const [selectedId, setSelectedId] = useState(null);

  return (
    <div className="relative w-full max-w-md h-[300px]">
      {articles.map((article, index) => (
        <Postcard
          key={article.id}
          article={article}
          index={index}
          isSelected={selectedId === article.id}
          onClick={() => setSelectedId(selectedId === article.id ? null : article.id)}
        />
      ))}
    </div>
  );
}
The stack container needs position: relative and an explicit height (the Blog page uses h-[300px]) so the absolutely positioned cards have a reference frame to stack inside.

Single card (preview / testing)

<Postcard
  article={{
    id: 99,
    title: 'Midnight Deploy',
    date: 'Oct 31, 2024',
    excerpt: 'Never push to production on Halloween...',
    color: '#f4f1ea',
  }}
  index={0}
  isSelected={false}
  onClick={() => {}}
/>

Card Visual Design

  • Paper texture — an inline SVG feTurbulence filter at 8 % opacity overlays the card’s background-color to simulate aged paper.
  • Stamp area — a dashed border box in the top-right corner reads “Place Stamp Here”, keeping the postcard aesthetic faithful.
  • Dividing line — a hairline w-px bg-black/10 runs vertically down the centre of the card, mimicking the traditional postcard address/message split.
  • Excerpt fontCaveat Brush, cursive at 1.2rem gives the body text a handwritten feel.

Customisation

To add, remove, or edit articles, update the articles array in assets/main.js. Each entry only needs the five fields listed above — the stacking positions and rotations are calculated automatically from the array index.
The color field determines the full card background. Keep colours in a similar warm, desaturated range (creams, tans, greys) to maintain the vintage-paper aesthetic. Very saturated colours will clash with the spooky site palette.

Build docs developers (and LLMs) love