Skip to content

Repository files navigation

Happy Birthday, Daphine 🎀

A single-page birthday website built for Wamala Daphine's 17th birthday — an envelope you open into a hero moment, a letter, a "our story" photo timeline, a flip-card list of reasons, a little quiz, and a confetti finale.

Live structure: HTML / CSS / vanilla JS, no framework. npm run build runs a minify step for deploy (see package.json/netlify.toml), but opening index.html directly or serving the folder as-is works fine for local dev — nothing needs compiling to preview changes.

Features

  • Envelope opener — a wax-seal envelope overlay that opens on tap, plays an opening sound effect and a heart/sparkle burst, then starts the background music (see Adding your own music).
  • Background music — a single track that plays through every section (see Adding your own music).
  • Falling petals on the hero and finale sections.
  • A letter to Daphine, with a small beating-heart animation near the close.
  • Our Story — a timeline of milestones, each with a tilted polaroid photo (or video) slot (see Adding real photos).
  • 5 Reasons — tap-to-flip cards, each with its own icon and a hidden reason on the back, with ambient floating hearts above them.
  • A little quiz about the relationship, with a checkmark-burst animation on correct answers, a shake on wrong ones, and a scored result at the end.
  • Finale — a closing message with a canvas confetti burst plus a radiating hearts animation.
  • Scroll-reveal animations throughout, with a prefers-reduced-motion fallback that disables all motion (including the animations above).
  • Installable + works offline — a web app manifest and service worker let it be added to the home screen (manifest.json) and revisited without a signal (sw.js). See Progressive Web App.

Project structure

index.html     Page markup
style.css      All styling
script.js      GENERATED — `npm run concat` builds this from js/*.js, don't edit directly
js/            The real source: one focused file per feature (envelope, audio, lottie,
               petals, polaroid, reasons, reveals, quiz, pwa), concatenated in that
               order into script.js
manifest.json  Web app manifest (name, icons, colors) for "Add to Home Screen"
sw.js          Service worker: precaches the app shell (including fonts), caches
               photos/audio the first time they're requested so later visits work offline
icon-192.png,
icon-512.png   PWA install icons, derived from apple-touch-icon.png
photos/        Drop real photos/videos here for the "Our Story" polaroids
audio/         Drop your background-music.mp3 and envelope-open.mp3 here
animations/    Hand-built Lottie JSON motion graphics (hearts, checkmark, sparkle burst)
vendor/        Vendored lottie-web player (vendor/lottie.min.js)
fonts/         Self-hosted Playfair Display/Mulish/Caveat woff2 files (downloaded from
               Google Fonts) so the service worker can precache real typography instead
               of falling back to system fonts on the first offline visit
scripts/       update-sw-version.js — auto-generates sw.js's VERSION, run as
               part of `npm run concat`/`build`

Editing behavior? Change the relevant file in js/, then run npm run concat to regenerate script.js (this also happens automatically as the first step of npm run build). The split exists purely for readability — the files share one closure at runtime exactly like the old single script.js did, so cross-file calls (e.g. the envelope starting the music) still just work.

Running locally

No build tools needed. Just serve the folder statically, e.g.:

python3 -m http.server 8000

Then open http://localhost:8000. (Opening index.html directly as a file:// URL also works, though some browsers restrict autoplay/Web Audio until a click — the envelope tap covers that.)

Adding real photos

Each milestone in "Our Story" has a polaroid slot wired to an image file that doesn't exist yet. Drop images with these exact filenames into the photos/ folder and they'll appear automatically — no code changes needed:

File Milestone
photos/story-1-how-we-met.jpg How We Met
photos/story-2-first-meeting.jpg First Physical Meeting
photos/story-3-funny-moments-1.mp4-4.mp4 Funny Moments (4 video slots)
photos/story-4-hard-moments.jpg Hard Moments
photos/story-5-birthday-1.jpg-5.jpg Today: Your Birthday (5-photo carousel, tap a photo to enlarge)

If a file is missing, that slot shows a styled placeholder instead of a broken image, so the page never looks broken while you're still collecting photos.

Adding your own music

Drop a single track in as audio/background-music.mp3 and it plays automatically, looping through every section — no code changes needed. It starts when the envelope is tapped open (browsers require a user gesture before audio can play) and can be muted with the speaker button in the top corner. If the file is missing, the page just plays silently rather than breaking.

Drop a short paper/envelope-opening sound in as audio/envelope-open.mp3 and it plays once, right when the envelope is tapped open — the background music then starts as soon as that sound finishes. If the file is missing, the background music just starts immediately instead.

Progressive Web App

The site registers sw.js on load (js/11-pwa.js), which precaches the app shell (HTML/CSS/JS, icons, Lottie animations, fonts) on first visit, then caches photos and audio the first time they're requested. Once she's opened it once, it keeps working with no signal.

manifest.json makes it installable — on Android, Chrome offers "Add to Home Screen" automatically; on iOS, Safari's share sheet has "Add to Home Screen" (using the existing apple-touch-icon.png). Both use start_url/scope of ".", which resolves relative to wherever the site is hosted, so this works unchanged on both the GitHub Pages /Her/ subpath and a Netlify root domain.

If you change apple-touch-icon.png, regenerate the manifest icons to match:

python3 -c "
from PIL import Image
im = Image.open('apple-touch-icon.png').convert('RGB')
for size, name in [(192, 'icon-192.png'), (512, 'icon-512.png')]:
    im.resize((size, size), Image.LANCZOS).save(name, optimize=True)
"

If you add/rename files in animations/ or the top-level app shell, add them to PRECACHE_URLS in sw.js. VERSION itself is auto-generated by scripts/update-sw-version.js (a hash of PRECACHE_URLS' contents) every time npm run concat/build runs, so it updates on its own whenever a precached file's contents change — don't hand-edit it. Old caches are deleted automatically on activate.

Customizing content

Everything is plain text/data in the source, no CMS:

  • Letter text — index.html, #letter section.
  • Timeline milestones — index.html, #story section.
  • The 5 reasons — the reasons array in js/06-reasons.js.
  • Quiz questions — the quizData array in js/08-quiz.js.
  • Colors and fonts — CSS custom properties at the top of style.css (--blush, --rose-gold, --gold, --mauve, --font-display, --font-body, --font-hand).

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages