A prototype of a digital wedding experience platform. One address per couple that moves through four lives:
Invitation → Wedding day → Digital album → Private memory archive
The demo wedding is Mohamed & Sara, 25 October 2026, The Grand Hall, Cairo. All names, photos and data are fictional placeholders.
Built with Next.js (App Router), TypeScript, Tailwind CSS and GSAP. No other runtime dependencies.
Requires Node.js 18.18 or newer.
npm install
npm run devThen open:
| URL | What you see |
|---|---|
http://localhost:3000/w/mohamed-sara |
The experience, in the stage that matches today's date |
…/w/mohamed-sara?stage=before |
Invitation (cinematic intro, story, details, countdown, venue, schedule, RSVP, guestbook) |
…/w/mohamed-sara?stage=during |
Wedding-day companion (live "now" marker on the schedule, guest photo sharing) |
…/w/mohamed-sara?stage=after |
"The day may end" transition into the magazine-style album |
…/w/mohamed-sara/private |
Private area: login, private album, archive, privacy notes |
A small stage switcher pill (bottom of the screen) lets you jump between the three
stages. It only appears because prototype.showStagePreview is true in the data.
Demo login for the private area
- Email:
sara@mohamed-sara.example - Password:
october
This is mock authentication. It is not security.
To test on a phone on the same network: npm run dev -- -H 0.0.0.0, then open
http://<your-computer-ip>:3000/w/mohamed-sara. Chrome DevTools device mode is
fine for layout checks, but please try a real phone for the intro and scrolling.
Production build:
npm run build
npm start
npm run typecheck # optionalapp/
page.tsx Platform home (lists weddings)
w/[slug]/layout.tsx Applies the wedding's theme as CSS variables
w/[slug]/page.tsx Resolves the stage and renders the experience
w/[slug]/private/page.tsx Private area (future: server-side auth check)
components/
ui/ Reusable primitives: MediaImage, CinematicVideo,
Lightbox, VideoModal, SoundToggle, SplitChars, StagePreview
wedding/ Every section of the experience + registry.tsx
data/
wedding.ts ALL couple-specific content
labels.ts Default UI strings (overridable per wedding)
privacy.ts Privacy principles shown in the private area
theme/
theme.ts Design tokens: colour, type, spacing, radius, motion, sections
templates.ts Templates = theme + component variants
lib/
types.ts Data model (Wedding, Couple, Event, Venue, Guests, RSVP, …)
stage.ts before / during / after logic
media.ts Media URL resolution (future: signed URLs)
services/ Mock rsvp, guestbook, uploads, auth, archive
hooks/ Reduced motion, clock, scroll lock, scroll reveals
Components never contain couple-specific text. Names, dates, story lines, the
timeline, events, schedule, photos, videos, guestbook seeds, album chapters and
archive collections all come from data/wedding.ts. To add a wedding, add another
object to the weddings array with a new slug; it is served at /w/<slug>
with no component changes.
lib/stage.ts decides the stage from the wedding's start time (the "day" runs from
6 hours before to 10 hours after). Each stage has its own section order in
wedding.layouts and its own copy in wedding.copy, so the invitation, the
wedding-day page and the album are the same sections rearranged and re-voiced,
not three sites.
A template is not a whole website. theme/theme.ts defines tokens (colours,
typography, spacing, radius, motion style, section appearance) and
theme/templates.ts pairs a theme with a set of variants per section:
hero, story, timeline, gallery, RSVP, album and so on.
components/wedding/registry.tsx maps each section type and variant to a
component. A wedding can take a template and override single choices in
wedding.design (e.g. template A with the gallery style from template C).
Templates A–F are declared (Cinematic Luxury, Editorial Romance, Modern Minimal,
Arabic Heritage, Garden Romance, Dark Contemporary). Only the cinematic
variants are implemented in this prototype; unknown variants fall back to them.
To add a variant: build the component, register it in registry.tsx, and
reference it from a template or a wedding's design.variants.
| Feature | Prototype behaviour | Intended production service |
|---|---|---|
| RSVP | Saved to localStorage, confirmation shown |
Supabase Postgres + Resend confirmation email |
| Guestbook | Seed messages + localStorage |
Supabase, with couple moderation |
| Guest photo upload | Local preview only, nothing leaves the browser | Signed upload to private R2 / Cloudinary |
| Private login | Hard-coded demo credentials | Supabase Auth + membership check per wedding |
| Protected media links | Visual countdown on a simulated signed URL | Short-lived signed URLs generated server-side |
| Download all memories | Downloads a JSON manifest of the archive | Background job that builds a ZIP of originals |
| Music | A soft ambient pad synthesised with Web Audio | Set music.src in the data to a licensed track |
| Videos | Poster images with slow movement; player runs in preview mode | Real files via video.src / HLS |
| Photos | Placeholder images from picsum.photos, graded to a warm monochrome | The couple's own photos |
Every integration point is marked with a FUTURE: comment in the code
(grep -rn "FUTURE:"). .env.example lists the variables they will need.
The code is shaped so original photos are never addressed by a public URL:
components ask lib/media.ts for a variant (thumb, display, full) of a
media reference, and that function is where signed, expiring URLs will be issued
once storage is private. Originals and web-optimised copies are separate objects.
The private area explains this as a plan, not as a finished guarantee: it is a
prototype and makes no security or permanence claims.
The project is ready to push to GitHub and import into Vercel with default settings (framework preset: Next.js). No environment variables are needed for the prototype.
- This prototype was written without being able to run
npm installin the authoring environment, so the firstnpm run buildon your machine is its first real build. If anything fails, the error message is usually enough to fix it quickly. - Placeholder images load from
picsum.photos; they need an internet connection and will differ from the final look of real photography. - Motion respects
prefers-reduced-motion: the intro becomes a simple reveal, scroll-scrubbed effects are replaced by static layouts. - On phones the "Our Moments" gallery uses native horizontal swipe; the scroll-driven horizontal track is desktop-only (≥ 768px) for performance.