Skip to content

← Writing

Each wedding template is its own landing page

How I architected 17 distinct wedding invitation templates as 17 mini-landing-pages — what gets shared, what doesn't, and why constraints sharpen creativity.

When you build a SaaS for the Indonesian wedding market, you hit a strange constraint: customers buy your product because of how the output looks, not how your interface looks.

Marryio’s interface — the builder, the dashboard, the checkout — exists in service of a completely different surface: the wedding invitation itself. That invitation has to feel like the user’s wedding, not like a SaaS template. And every wedding is different. So I built 17 templates. And I treat each one like its own landing page.

Why “template” isn’t the right word

Most SaaS templates are themes. Palette swap, font swap, header crop. Same engine underneath. Pick “Modern”, pick “Classic”, pick “Romantic” — you get the same component tree with three colorways.

Marryio templates aren’t themes. They’re complete one-page sites with their own design system, layout architecture, scene model, motion choreography, and mobile UX. minangkabau-01 (Rumah Gadang) and ikoku-journal-02 are not “the same template with different colors.” They’re fundamentally different products that happen to share a content schema.

What gets shared, what doesn’t

Shared across all 17:

  • Content schema (InvitationContent JSONB — names, dates, venues, RSVP, gallery, music)
  • Section order rule (Hero → Couple → Story → Akad → Resepsi → Countdown → … → RSVP → Footer)
  • Behaviors (RSVP, music auto-start, QR check-in flow, guestbook polling)
  • Helper utilities (fmtDate, focal-point computation, calendar URL)

Not shared at all:

  • Layout architecture
  • Scene system
  • Palette
  • Fonts
  • Animations
  • Decorative motifs
  • Mobile gate behavior
  • Section transitions

Each template lives in its own folder: index.tsx (orchestrator), assets.ts (paths to images/svgs), demo-data.ts, metadata.ts, plus optionally lib/palette.ts, lib/animations.ts, lib/helpers.ts, and a components/ subfolder for sections.

The layout architectures

Seven distinct layout types across 17 templates:

  • Split-screen (10) — left panel with fixed cover image, right panel scrollable content. The default canvas for most templates.
  • Scene-carousel (1)noir-editorial-05. Scenes swap in place via closure system, no scrolling.
  • Pinned-scroll with dynamic scenes (1)ikoku-journal-02. GSAP pins the section, scenes change with scroll, palette updates per scene via Zustand.
  • Full-width (2)cinematic-space-07, video-drama-08. The latter has a backgroundVideoUrl slot.
  • Sticky-card-stack (1)wedding-stack-15. Cards stack with scroll, custom rAF + subscribe pattern. No GSAP, no Framer for cards — pure DOM manipulation faster.
  • Arc-slider hero (1)editorial-arc-16. Single fullscreen position: fixed inset-0, no scroll. Cards on a cosine curve with infinite loop, custom useArcSlider hook handles drag/wheel/touch/keyboard at scale 1.3 center → 0.35 edge.
  • Interactive map (1)minangkabau-explorer-17. POI-based explorable cultural map.

A “split-screen” template and a “sticky-card-stack” template share zero implementation. They’re as different as two bespoke client landing pages.

Scene system — the one shared idea

The most memorable templates implement a scene system. Each section has its own palette, transitions, layout treatment. The card, panel, and text colors change per scene to give the template visual rhythm.

Example: ikoku-journal-02 has SCENE_COLORS per scene id in lib/palette.ts. A useSceneStore (Zustand) tracks the active scene. As GSAP ScrollTrigger detects which section is active, the store updates → CSS variables update → all template surfaces respond.

Without a scene system, a template feels like one static design with text variations. With it, each section feels intentional — the “couple” scene reads warm, the “events” scene reads cool, the “closing” scene reads dark and final.

The discipline this requires

Seventeen templates means seventeen codebases with overlapping but not identical patterns. The discipline:

Extract once, use everywhere. Helpers like fmtDate, fp (focal-point), getGoogleCalendarUrl live in template-helpers.ts. Never duplicate them inside a template folder. PR review catches new duplicates and moves them up.

Register GSAP plugins centrally. lib/gsap.ts does gsap.registerPlugin(ScrollTrigger, MotionPathPlugin, useGSAP) once. Templates import from there. Never call gsap.registerPlugin() inside a component — duplicate registration creates subtle bugs.

Animation system separation. Framer Motion handles UI (toasts, dialogs, builder primitives). GSAP handles template scroll choreography. Never animate the same CSS property from both systems on the same element — flicker. Pick one system per element.

Performance comes from per-template discipline, not platform rules. wedding-stack-15 doesn’t use GSAP at all because custom rAF + subscribe is faster for its specific stacking pattern. ikoku-journal-02 requires GSAP pin + Framer useMotionValueEvent together, because Framer v12’s useScroll({ container }) silently fails on hydration. Each template solves its own performance problem.

What it taught me

Constraints sharpen creativity, but only if the constraint is “must share content schema and behavior” — not “must look the same.” The first kind of constraint frees you to design distinctively per template. The second kind forces visual sameness and creates the “every SaaS landing page looks the same” problem we all hate.

Every “template” is essentially a small product. Treat it like one. The orchestrator (index.tsx) is the App.tsx. The sections are pages. The palette is the design system. The mobile gate is the splash screen.

Seventeen templates is not seventeen times more work than one template. With architecture discipline, it’s maybe six to eight times. The institutional knowledge of how to build them — Indonesian payment integration, multi-tenant content storage, scene systems, mobile gate UX — is the actual moat.

The next platform I build, I’ll start with the same constraint: a tight content schema, loose visual freedom. That’s the recipe for a SaaS that doesn’t bore its users into churning.


Drafted 2026-04-25. Marryio is live at marryio.id.