Skip to content
Personal

Interactive Greeting Card

Pick an occasion, write a message, add a doodle, and reveal it with a themed confetti burst — a themed, shareable digital greeting card.

Role
Design & development, solo
Year
2026
Stack
HTML5, CSS3, vanilla JS
Status
Complete

A personalized digital greeting card: pick an occasion, write a message, add an optional hand-drawn doodle, and reveal it from an animated envelope with a matching confetti burst. Cards auto-save locally and restore on reload, and a "Copy Share Link" button encodes the whole card into a URL — open it anywhere and see that exact card, no account or backend required.

What makes it work

  • Four occasions, four real themes. Birthday, Holiday, Thank You, and Congratulations each carry their own accent color and confetti shapes — balloons, snowflakes, hearts, stars — a genuinely different visual moment per occasion, not one template with a recolored button.
  • An explicit share always beats a quiet background save. If a visitor opens a shared card on a device that already has its own saved card, the shared link wins every time — it's an intentional action and shouldn't be silently overridden. A corrupted share link shows a friendly error; a corrupted local save just fails quietly, since only one of those is something the user actually asked for.

Design at a glance

  • ink · #2b2b33
  • surface · #ffffff
  • birthday · #ff6f91
  • holiday · #2f9e6f
  • thank you · #d69e2e
  • congrats · #6b5bd6

System font stack throughout — the color shift per occasion carries the personality, not the typography.

  • Envelope reveal — pending capture

Built to last

  • Native browser APIs only — Canvas, Web Audio, localStorage, URLSearchParams — zero dependencies, zero build step
  • Envelope-reveal animation and confetti both respect prefers-reduced-motion
  • Doodle/signature deliberately excluded from shared links — it only ever exists on the device that drew it

Want an experience with this much craft behind a single interaction?

See it live Start a project
For developers — the full technical breakdown

01. A shared link always wins over a local save

Problem
If a visitor opens a shared card link on a device that already has its own saved card in localStorage, it's ambiguous which one should display.
Choice
The shared link always takes priority — it's an explicit action by the visitor and shouldn't be silently overridden by a background auto-save they may not even know is happening.
Result
Predictable behavior in the one scenario most likely to confuse someone: opening a card that isn't theirs.

02. Persistence lives in share.js, not card.js

Problem
Both the shareable-link feature and localStorage persistence solve the same underlying problem — turning a card into a portable string and back.
Choice
Reused encodeCardData()/decodeCardData() from one module (share.js) instead of duplicating that logic across two files.
Result
card.js stays focused purely on rendering and validating card content, not storage mechanics — and if persistence later needs more complexity, there's already a clean seam to split it out.

Accessibility & performance

  • :focus-visible states and aria-live status regions throughout
  • prefers-reduced-motion skips or shortens the envelope animation and confetti
  • Invalid or corrupted share links show a friendly error and fall back to the blank composer, never a blank or broken page
  • Zero dependencies, zero build step — every effect (confetti, doodle canvas, chime) is a native browser API

What I’d do differently

The doodle/signature is deliberately left out of shared links — a raster image would make the URL impractically long. A v2 with a small hosted backend could store the doodle server-side behind a short share ID instead, without changing the local-only fallback for people who never share.

Stack

  • HTML5 / CSS3
  • Vanilla JavaScript
  • Canvas API
  • Web Audio API
  • URLSearchParams
  • localStorage