Postcards from the Grave 🪦

Inspiration

The idea struck during a late-night coding session—what if we could send messages from beyond the veil? We wanted to create something that blended the nostalgic charm of physical postcards with the eerie atmosphere of Victorian-era spiritualism. The concept of a "spiritual medium" that transforms ordinary messages into ghostly prose felt like the perfect intersection of AI capabilities and creative storytelling.

We were inspired by:

  • Victorian séance culture — The mysterious rituals of communicating with the dead
  • Gothic horror aesthetics — Dark oil paintings, weathered tombstones, and fog-shrouded graveyards
  • The lost art of postcards — A tangible, personal form of communication in our digital age

The question became: What if AI could channel the spirits and help you send a message from the grave?

What it does

Postcards from the Grave is an immersive web application that transforms ordinary messages into haunting postcards delivered from beyond the veil.

The experience flow:

  1. Enter your message — Type a heartfelt (or mischievous) message to someone
  2. AI Spiritual Medium — Google Gemini transforms your words into cryptic, sorrowful prose
  3. Gothic Artwork Selection — A random piece from our curated gallery of 33 dark oil paintings is chosen
  4. PDF Generation — Your ghostly message and artwork are embedded into a vintage postcard template
  5. Email Delivery — The completed postcard arrives in your recipient's inbox with a spooky subject line

The UI itself is an experience—a carved stone tombstone interface floating in a fog-shrouded graveyard, complete with:

  • Animated ghost sprites drifting across the screen
  • A will-o'-the-wisp cursor that follows your mouse
  • Atmospheric fog layers and grain textures
  • Engraved typography that feels carved into stone

How we built it

Tech Stack

$$ \text{Next.js 14} + \text{React 18} + \text{Google Gemini API} + \text{pdf-lib} + \text{Nodemailer} $$

Architecture: The Three-Layer System

We implemented a strict separation of concerns using three visual layers:

z-index: 0-9    → Background Layer (fog, ghosts, textures)
z-index: 10-99  → Interface Layer (form, buttons, inputs)
z-index: 100+   → Overlay Layer (cursor, particles)

This architecture allowed us to iterate on atmospheric effects without breaking functionality.

Spec-Driven Development with Kiro

We created four comprehensive specs that guided implementation:

Spec Purpose Requirements
component-layer-structure Three-layer UI architecture 4 requirements, 2 properties
animated-ghosts Floating ghost animations 4 requirements, 5 properties
ai-spiritual-medium Full postcard pipeline 11 requirements, 11 properties
pdf-postcard-generator PDF template manipulation 6 requirements

Each spec followed the pattern:

  • requirements.md — User stories with WHEN/THEN/SHALL acceptance criteria
  • design.md — Mermaid diagrams, TypeScript interfaces, correctness properties
  • tasks.md — Granular implementation checklist with requirement traceability

The AI Pipeline

The postcard generation follows a sequential pipeline:

$$ \text{Original Message} \xrightarrow{\text{AI Medium}} \text{Ghostly Message} \xrightarrow{\text{AI Artist}} \text{Gothic Artwork} \xrightarrow{\text{PDF Generator}} \text{Postcard} \xrightarrow{\text{Email Service}} \text{Recipient} $$

Steering Documents

We created 5 steering documents that provided persistent context:

  • project-overview.md — Design philosophy and color palette
  • css-styling-rules.md — Carved stone visual patterns
  • animation-rules.md — Performance guidelines and timing
  • component-architecture.md — Layer structure rules
  • nextjs-conventions.md — File and API conventions

Challenges we ran into

1. The Fog Performance Problem

Our initial fog implementation used JavaScript intervals to animate multiple layers. This caused frame drops on mobile devices. The solution:

/* GPU-accelerated CSS animation instead of JS */
@keyframes fog-drift {
  0% { transform: translateX(0); }
  100% { transform: translateX(-33.33%); }
}

Lesson: Animate only transform and opacity — they're GPU-accelerated.

2. Ghost Memory Leaks

Ghosts that drifted off-screen weren't being cleaned up, causing memory accumulation. We implemented a lifecycle manager:

// Ghost cleanup on animation end
useEffect(() => {
  const timer = setTimeout(() => removeGhost(ghost.id), ghost.speed * 1000);
  return () => clearTimeout(timer);
}, [ghost.id, ghost.speed]);

3. PDF Coordinate Calibration

The pdf-lib library uses a coordinate system where (0, 0) is the bottom-left corner, not top-left. This led to hours of trial-and-error positioning:

$$ y_{\text{pdf}} = \text{pageHeight} - y_{\text{design}} - \text{elementHeight} $$

4. Gmail SMTP Authentication

Gmail's security requirements meant we needed App Passwords with 2FA enabled. The error messages were cryptic until we discovered the specific configuration:

const transporter = nodemailer.createTransport({
  service: 'gmail',
  auth: {
    user: process.env.GMAIL_USER,
    pass: process.env.GMAIL_APP_PASSWORD // 16-char app password, not account password
  }
});

5. Maintaining Aesthetic Consistency

With multiple developers and AI-assisted coding, keeping the "carved stone" aesthetic consistent was challenging. The steering documents solved this by establishing patterns like:

/* The "carved input" pattern - documented in steering */
box-shadow: inset 0px 2px 5px rgba(0, 0, 0, 0.7);
border: 1px solid rgba(0, 0, 0, 0.7);
background-color: rgba(10, 10, 10, 0.5);

Accomplishments that we're proud of

🎨 The Immersive Atmosphere

Every pixel contributes to the spooky vibe. The combination of:

  • Fog drifting at 45-60 second cycles
  • Ghosts with randomized opacity (0.3-0.8) and size (0.5x-1.5x)
  • Grain texture overlay
  • Vignette darkening at edges
  • Will-o'-the-wisp cursor with gentle pulsing

Creates an experience that feels like stepping into a Victorian cemetery.

📜 The Complete Pipeline

Building an end-to-end system that:

  1. Transforms text with AI
  2. Selects matching artwork
  3. Generates a PDF
  4. Sends an email with attachment

...all in a single API call, with proper error handling and fallbacks.

🧪 Property-Based Testing

We defined 11 formal correctness properties that serve as testable contracts:

### Property 1: Ghostly message length constraint
*For any* non-empty original message, the transformed ghostly message 
SHALL have a length under 200 characters.

These properties caught edge cases that example-based tests would have missed.

🏗️ Spec-Driven Architecture

The upfront investment in requirements and design documents paid dividends:

  • Clear contracts between services
  • Fewer integration bugs
  • Easier onboarding for new features
  • Documentation that stays in sync with code

What we learned

1. Steering Documents Are Powerful

Establishing project identity early (color palette, design philosophy, naming conventions) meant every piece of generated code maintained thematic consistency. The project-overview.md steering doc was referenced in nearly every Kiro interaction.

2. Specs Excel for Complex Integrations

The AI postcard pipeline involved 4 services with 11 requirements. Without formal specs, this would have been chaotic. The WHEN/THEN/SHALL format forced us to think through edge cases upfront.

3. CSS Animations > JavaScript Animations

For atmospheric effects, CSS keyframe animations are:

  • More performant (GPU-accelerated)
  • Easier to maintain
  • Less prone to memory leaks

Reserve JavaScript for dynamic values (random positions, spawn timing).

4. Z-Index Systems Need Documentation

Our three-layer system with explicit ranges (0-9, 10-99, 100+) prevented z-index wars and made debugging visual stacking trivial.

5. The "Vibe Coding" vs "Spec-Driven" Balance

Approach Best For
Vibe Coding Visual polish, single components, quick iterations
Spec-Driven Multi-service features, complex integrations, team projects

Both have their place. We used specs for the pipeline, vibe coding for the fog timing.

What's next for Postcards From The Grave

🎭 Custom Ghost Personas

Let users choose their spiritual medium's personality:

  • The Melancholic Poet — Sorrowful, romantic prose
  • The Cryptic Oracle — Mysterious riddles and prophecies
  • The Vengeful Spirit — Dark, ominous warnings

🖼️ AI-Generated Artwork

Replace the static gallery with real-time image generation using Gemini's multimodal capabilities, creating artwork that matches the specific message content.

📱 Mobile App

A native app with:

  • Push notifications when postcards arrive
  • AR mode to "see" ghosts in your environment
  • Offline postcard creation with sync

🌐 Postcard Gallery

A public gallery where users can share their favorite ghostly creations (with permission), building a community around the spooky aesthetic.

🎵 Ambient Soundscape

Optional audio layer with:

  • Distant thunder
  • Creaking gates
  • Whispered voices
  • Howling wind

📊 Analytics Dashboard

For power users:

  • Track how many postcards you've sent
  • See which messages resonated most
  • Unlock achievements ("Sent 13 postcards on Friday the 13th")

"The veil between worlds grows thin. Your message awaits transformation..." 👻

Built With

  • google-gemini-api
  • kiro
  • next.js
  • pdf-lib
  • react
Share this project:

Updates