Skip to content
All skills
OPERATOR content v1.0.0 · Apache-2.0

Hyperframes Invitation Card

Production pipeline for vertical animated invitation cards (wedding, save-the-date, birthday, event) using HyperFrames. 1080x1920 H.264 MP4 under 4MB that plays inline on WhatsApp, Messenger, Instagram Stories, and email. Covers the 6-scene template (hero, names + circular photo cameo, date, venue/ceremony, reception, closing), music-bed preprocessing with ffmpeg, and the bilingual-fork workflow for shipping the same invitation in multiple languages. Read BEFORE authoring any personal invitation — the pattern is different from the marketing/explainer use case that `hyperframes-video` documents.

Audited
Source
SHA-256
Last reviewed
How we audit →

Install in your agent

Tell your agent: "install the recipes skill, then add hyperframes-invitation-card"
Or via curl: curl -sL https://recipes.wisechef.ai/skill -o ~/.claude/skills/recipes/SKILL.md

Full skill source · SKILL.md

HyperFrames Invitation Card Pipeline

Sister skill of hyperframes-video and hyperframes-composition-rules. Those cover the framework contracts; this one covers the personal-invitation application pattern.

Read The Core Skills First (MANDATORY)

Before writing HTML, load both:

  • skill_view hyperframes-video — general pipeline, rules, transitions, verification
  • skill_view hyperframes-composition-rules — hard rules (clip class, timeline registry, track overlap, no exit animations except final)

Do NOT write an invitation composition from memory. The 5 non-negotiable rules apply to invitations just as much as to marketing reels, and the wedding-invitation smoke test on 2026-04-21 passed lint 0/0 on first try ONLY because both skills were loaded first.

When To Use This Skill

Use for personal/event invitations where:

  • Format is vertical 1080x1920 (phone-native, WhatsApp/Instagram Story friendly)
  • A user-provided photo needs to feature in the composition
  • A music bed (user-uploaded, often from Discord/voice-memo, often mislabeled .ogg) needs to be mixed in
  • Sometimes a second language version is needed (common: Polish + English, Spanish + English, etc.)

Do NOT use this skill for:

  • Marketing reels, carousels-as-video, explainers → use hyperframes-video or domain-specific content engines
  • Landscape 16:9 or square 1:1 → different templates needed, this pattern is portrait-first
  • Print invitations → this is MP4 output; use a PDF/design pipeline instead

The 6-Scene Wedding/Event Template (tested, ship-ready)

Timing budget: 22.5s total. Each scene has entrance animations only; transitions are 0.4s blur-crossfades between adjacent tracks. Alternate data-track-index between 0 and 1 so no overlap occurs on the same track.

# Scene Start Dur Track Background Purpose
1 Hero / "Save the Date" 0.0 3.3 0 Ivory radial Grab attention; eyebrow + script + sub
2 Names + circular photo cameo 3.2 4.2 1 Parchment Couple's names + their selfie in a gold-ringed circle
3 Date 7.3 4.0 0 Night (dark) Huge day number, month, year; cinematic hero moment
4 Ceremony/venue 11.2 4.0 1 Ivory Church name, location, time
5 Reception 15.1 4.0 0 Gold/brass Reception venue + "follows the ceremony" tag
6 Closing 19.0 3.5 1 Deep brown Heartfelt line + names in calligraphy + place-date mark. ONLY scene allowed an exit fade.

Transitions fire at t=3.0, 7.0, 11.0, 14.9, 18.8 — each gsap.to on outgoing scene at that time, paired with gsap.from on incoming scene 0.1-0.2s later. The outgoing scene MUST be fully visible at transition start (no preemptive exits — the transition IS the exit).

The Circular Photo Cameo Pattern

<div class="cameo-frame s2-frame">
  <img class="cameo-photo s2-photo" src="assets/couple.jpg" alt="..." />
  <div class="cameo-ring s2-ring"></div>
</div>
.cameo-frame {
  position: relative;
  width: 720px;
  height: 720px;
}
.cameo-photo {
  width: 640px;
  height: 640px;
  border-radius: 50%;
  object-fit: cover;
  object-position: 50% 40%;  /* TUNE THIS — for selfies, 40% lifts faces into center */
  border: 6px solid #C9A864;
  box-shadow: 0 20px 60px rgba(42, 26, 20, 0.35), inset 0 0 0 14px #F5EBDB;
}
.cameo-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px dashed #C9A864;
  opacity: 0.6;
}

Animation pair (nice entrance):

tl.from('.s2-frame', { scale: 0.75, opacity: 0, duration: 0.9, ease: 'back.out(1.4)' }, 3.40);
tl.from('.s2-ring',  { rotation: -180, opacity: 0, duration: 1.4, ease: 'power3.out',
                       transformOrigin: '50% 50%' }, 3.50);

Pitfall: selfie face-centering. Default object-position: 50% 50% crops heads off for typical selfies (taken from below, faces near top). Use 50% 40% or 50% 35% for selfies. Verify via frame sampling, don't trust the CSS default.

Music Bed Preprocessing (CRITICAL — do this BEFORE the render)

User-provided music is almost always too long and at full volume. HyperFrames' data-volume attribute works but is crude. Pre-process with ffmpeg for clean fades + correct length:

# Trim to composition length, lower volume, add fade-in and fade-out
ffmpeg -y -i assets/music_raw.mp3 \
  -t 22.5 \
  -af "volume=0.35,afade=t=in:st=0:d=0.8,afade=t=out:st=21.5:d=1.0" \
  -codec:a libmp3lame -b:a 192k \
  assets/music_bed.mp3

Then wire into composition:

<audio id="music-bed" data-start="0" data-duration="22.5"
       data-track-index="9" data-volume="1.0"
       src="assets/music_bed.mp3"></audio>

Audio elements do NOT need class="clip". Put them on a high data-track-index (9+) to stay out of the way of visual tracks.

Tuning: 0.35 volume is gentle background bed that doesn't fight vocals or text legibility. Fade-in 0.8s feels natural, fade-out 1.0s at 21.5s catches the final scene's settle.

Discord Voice Memo / Audio File Quirks

Users frequently say "I attached an MP3" but the file in ~/.hermes/cache/audio/ has a .ogg extension (Discord transcoding quirk). Verify with ffprobe:

ffprobe -v error -show_entries stream=codec_name,codec_type \
  -show_entries format=format_name,duration \
  ${HOME}/.hermes/cache/audio/audio_<hash>.ogg

If codec_name=mp3 and format_name=mp3, it's actually an MP3 — just copy to assets/music.mp3 (ffmpeg will handle either way, but name it correctly for humans reading the project later).

Finding the recently-uploaded file: find ${HOME}/.hermes/cache -type f \( -name "*.mp3" -o -name "*.ogg" -o -name "*.m4a" -o -name "*.wav" \) -mmin -60 — look for recent audio drops regardless of extension. ${HOME}/.hermes/cache/audio/ is the canonical drop directory for Discord audio.

Bilingual Fork Workflow

When the user wants the same invitation in two languages (common: PL + EN for Polish weddings, ES + EN for Latin American weddings):

  1. Build and verify the first language first. Do the full lint + render + vision-check cycle. Get user approval before forking.
  2. Duplicate the project — do NOT try to parameterize one HTML with string swapping. HyperFrames is deterministic and static; duplication is cheaper than a template engine.
    cd /home/adam && rm -rf wedding-invitation-pl && \
      npx hyperframes init wedding-invitation-pl --no-install
    mkdir -p wedding-invitation-pl/assets
    cp wedding-invitation/assets/couple.jpg     wedding-invitation-pl/assets/
    cp wedding-invitation/assets/music_bed.mp3  wedding-invitation-pl/assets/
    cp wedding-invitation/index.html            wedding-invitation-pl/index.html
    
  3. Three ID changes in the new project (miss any and render fails silently or produces stale animations):
    • <html lang="pl"> on the root element
    • data-composition-id="wedding-invitation-pl" on the #stage div
    • window.__timelines['wedding-invitation-pl'] = tl; at the end of the GSAP block
    • meta.json{"id": "wedding-invitation-pl", "name": "wedding-invitation-pl", ...}
  4. Translate ONLY visible strings. Do NOT translate class names, IDs, or GSAP selectors. The 11 typical translation targets for a wedding invitation:
Slot EN PL ES
Eyebrow "You are invited" "Zapraszamy" "Estás invitado"
Hero script "Save the Date" "Zapisz datę" "Reserva la fecha"
Names tag "are getting married" "biorą ślub" "se casan"
Date eyebrow "Saturday" "Sobota" "Sábado"
Month "August" "Sierpnia" (genitive!) "Agosto"
Ceremony chip "The Ceremony" "Ceremonia" "La Ceremonia"
Ceremony label "Holy Mass" "Msza Święta" "Santa Misa"
Reception chip "Reception" "Wesele" "Recepción"
Reception label "The celebration" "Przyjęcie weselne" "La celebración"
Follow-up tag "to follow the ceremony" "bezpośrednio po ceremonii" "tras la ceremonia"
Closing line "with joy in our hearts, we invite you to share this day with us" "z radością w sercu, zapraszamy, abyście byli z nami tego dnia" "con alegría en el corazón, los invitamos a compartir este día"

Polish genitive gotcha: Months take genitive after a day number — "15 sierpnia" NOT "15 sierpień". Similarly Russian uses genitive, Czech uses genitive. English and Spanish don't. Get this right on first try by using the native speaker form.

Typography Defaults That Work

These three Google fonts render Polish, Spanish, French, German diacritics cleanly via HyperFrames' font pipeline — verified 2026-04-21 with ą, ę, ś, ł, ż, ń, ó, Ś, Ż all correct:

<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400&family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;1,400&family=Great+Vibes&display=swap" rel="stylesheet" />

Size floors for 1080x1920 portrait:

  • Hero script (Great Vibes): 220px
  • Names (Playfair Display bold): 96px
  • Date day number (Playfair Display bold): 300px
  • Venue name (Playfair Display semibold): 72px
  • Venue time (Playfair Display bold): 180px
  • Body italic (Cormorant Garamond): 48px
  • Eyebrow (Cormorant Garamond italic, letter-spaced): 34px

Going smaller than these for the main focal element of a scene produces frames that feel empty on a phone screen.

Culturally-Flavored Ornaments

Simple SVG ornaments beat stock vector imports. For Kraków/Polish weddings, inline wycinanki-inspired SVGs in crimson (#8E1824) + gold (#C9A864):

<svg class="ornament-top" viewBox="0 0 420 120" xmlns="http://www.w3.org/2000/svg">
  <g fill="#8E1824">
    <circle cx="210" cy="60" r="14"/>
    <path d="M210 46 C 195 20, 170 20, 165 45 C 160 65, 185 70, 210 60 Z"/>
    <path d="M210 46 C 225 20, 250 20, 255 45 C 260 65, 235 70, 210 60 Z"/>
    <circle cx="120" cy="60" r="8"/>
    <circle cx="300" cy="60" r="8"/>
  </g>
</svg>

Replaceable: swap motifs for the culture (papel picado for Mexican weddings, mehndi patterns for South Asian, hand-drawn fern fronds for modern-minimal). Keep them as inline SVG — external files slow the render worker and risk missing-asset warnings.

Frame Sampling — When Early Sampling Lies

The first HyperFrames render sampling pass on the wedding-invitation smoke test returned "year 2026 not visible" and "time 15:30 not visible" as false negatives. Root cause: sampled at t=9.3 and t=13.3, but the year enters at t=9.3 and the time enters at t=13.3 — the frame captured the scene mid-entrance.

Rule: sample at data-start + (scene_duration * 0.75), not at the middle. For a 4.0s scene starting at 7.3s, sample at 10.3s (7.3 + 3.0). This catches the scene fully settled, all entrance animations complete.

Typical sample timestamps for the 6-scene template:

Scene 1 (0.0,   3.3s dur): sample at 1.8
Scene 2 (3.2,   4.2s dur): sample at 5.3
Scene 3 (7.3,   4.0s dur): sample at 10.5    # NOT 9.3
Scene 4 (11.2,  4.0s dur): sample at 14.5    # NOT 13.3
Scene 5 (15.1,  4.0s dur): sample at 18.5    # NOT 17.3
Scene 6 (19.0,  3.5s dur): sample at 21.0    # NOT 20.5

If you sample earlier and something "looks missing," re-sample later before concluding the render is broken.

Verification Before Shipping

cd <project>
npx hyperframes lint --strict  # MUST exit 0
npx hyperframes render --strict
# Verify audio stream present:
ffprobe -v error -show_entries stream=codec_type,codec_name \
  renders/<latest>.mp4  # expect h264 + aac
# Sample and vision-check:
for t in 1.8 5.3 10.5 14.5 18.5 21.0; do
  ffmpeg -y -ss $t -i renders/<latest>.mp4 -frames:v 1 verify/s_${t}.png
done
# Vision-check each frame for its expected scene content

Only ship when:

  1. Lint strict passes (0/0)
  2. File has both h264 + aac streams (if music bed added)
  3. Every sampled frame shows the expected scene content (vision pass)
  4. Diacritics render correctly in the target language

Delivery Format

The rendered MP4 is typically 3-4MB for a 22.5s invitation with music. That fits inside:

  • WhatsApp attachment limit (16MB)
  • Messenger attachment limit (25MB)
  • Instagram Story (must be 9:16 which 1080x1920 is — direct upload works)
  • Email attachment for Gmail/Outlook/Apple Mail (20MB+)

Use MEDIA:<absolute-path> in the assistant response — Discord/Telegram/Messenger bridges auto-upload.

Iteration Levers (common user asks after first draft)

When shipping the first draft, expect these follow-up asks — have the answer ready:

  • Add music → see Music Bed Preprocessing section above
  • Crop photo differently → change object-position percentage on .cameo-photo
  • Longer/shorter → scale all scene data-start and data-duration proportionally; update GSAP offsets to match
  • Different language → see Bilingual Fork Workflow section
  • Change palette → swap CSS custom properties or replace the 6 .bg-* class backgrounds
  • Swap culture motifs → replace inline SVG ornaments
  • Add QR code for RSVP → use qrencode -o assets/rsvp.png -s 10 "https://..." then <img src="assets/rsvp.png"> in the closing scene

Related Skills

  • hyperframes-video — parent skill with render pipeline details, prerequisites, Docker, CLI flags
  • hyperframes-composition-rules — the 5 non-negotiable rules (load this first, always)
  • nano-banana-pro / image-gen skills — for generating venue illustrations if user has no photo
  • local-tts-kokoro — if the user wants voiceover narration (rare for invitations, common for explainers)

Field Lessons (2026-04-21 Adam & Weronika wedding invitation)

First draft: 6-scene template, 22.5s, 1080x1920, rendered in 3 min 11s on QEMU 4-core. Lint 0/0 on first try because hyperframes-video and hyperframes-composition-rules were both loaded before HTML authoring. All six scenes verified via vision-check of mid-scene frames.

First iteration (user-requested): add music bed from Discord-uploaded MP3 (saved as .ogg extension), generate Polish version. Music preprocessing (ffmpeg trim + fade + volume) took 3 seconds; bilingual fork took under 2 minutes for string translation + project duplication; second render added another 3 minutes. Total "music + second language" cycle: ~8 minutes including both re-renders.

Two false-negative vision checks caught on first pass (year + time sampling too early) reinforced the "sample at 75% of scene duration" rule now in this skill.

Polish diacritics (ą, ę, ś, ł, ż, ń, ó + capitals) all rendered correctly via Playfair Display + Cormorant Garamond + Great Vibes from Google Fonts without any extra configuration — the HyperFrames font pipeline caches them correctly.