초대장을 준비하는 중…
D & S
MADE WITH CLAUDE CODE
SCROLL TO OPEN ↓
OUR STORY
우리가 걸어온 길
처음 만난 날
2019 봄
첫 데이트
2019 여름
함께한 여행
2021
그 날의 약속
2025 프러포즈
맞잡은 손
언제나
이제, 우리
2026
YOU ARE INVITED
저희 두 사람,
하나가 됩니다
소중한 분들을
초대합니다
WALK WITH US
이 길의 끝에서
기다릴게요
THE CELEBRATION BEGINS IN

결혼식까지

--
DAYS
--
HOURS
--
MIN
--
SEC

2026년 11월 14일 토요일 오후 2시
저희의 새로운 시작을 함께해 주세요.

SCHEDULE OF EVENTS

예식 안내

13:30
하객 입장 · 포토 타임
14:00
예식 시작
14:40
성혼 선언 · 폐백
15:10
피로연 · 식사

더 채플 청담 · 3F 그랜드홀
서울 강남구 청담동

네이버 지도로 길찾기
WITH LOVE

순간들

A LITTLE TOKEN

마음 전하실 곳

참석이 어려우신 분들을 위해 마음 전하실 곳을 안내드립니다.

신랑 도현○○은행 123-456-789012
신부 서연○○은행 987-654-321098
GUESTBOOK

축하 한마디

두 사람에게 따뜻한 축복을 남겨주세요.

Dohyun & Seoyeon
2026 . 11 . 14 . SATURDAY 2PM
두 사람이 사랑으로 만나
한 길을 함께 걷고자 합니다.
귀한 걸음으로 축복해 주세요.
HOW IT'S MADE · 전부 공개

코드 한 줄 없이,
Claude Code로 만들었어요

아무것도 없는 새 컴퓨터에 Claude Code만 깔린 상태 기준이에요. 아래 키 3개를 처음부터 발급받고, 프롬프트를 순서대로 복붙하면 Claude Code가 영상 생성·합성·배포까지 다 합니다.

STEP 0 · 키 3개 발급 (처음부터)

이 3개만 사람이 직접 받아요 (다 무료)

Cloudflare 계정 (사이트 배포용):dash.cloudflare.com 에서 무료 가입. 끝. 나중에 Claude Code에 "wrangler login" 시키면 브라우저로 로그인돼요.
GCP Vertex 키 (영상·이미지 AI 생성):console.cloud.google.com 가입 → 새 프로젝트 만들기 → 검색창에 "Vertex AI" 쳐서 API 사용설정 → "IAM · 서비스 계정" → 서비스 계정 만들기 → 키 추가(JSON) 다운로드 → 그 파일을 ~/.claude/.gcp-vertex-key.pem 위치에 저장.
Pexels 키 (무료 사진):pexels.com/api 에서 가입 → "Your API Key" 복사. (내 사진 쓰면 이건 생략)
STEP 1 · 환경 준비 (복붙)

도구 설치 + 키 연결

새 컴퓨터에 Claude Code만 있어. 이 작업에 필요한 도구를 설치/준비해줘: ffmpeg, node, wrangler(cloudflare), 헤드리스 QA용 puppeteer-core + chromium, yt-dlp. 그리고 방금 발급받은 키를 연결할게: Pexels 키는 저장소 밖 안전한 곳에, GCP Vertex 키 파일은 ~/.claude/.gcp-vertex-key.pem 로, Cloudflare는 'wrangler login'으로 로그인하게 안내해줘.
STEP 2 · 영상·사진 만들기 (복붙)

AI로 자산부터 생성

버건디 청첩장용 자산을 만들어줘.
1) 봉투: Imagen(imagen-3.0-generate-002, 9:16)으로 "딥버건디 청첩장 봉투+금박 왁스실+말린 장미·유칼립투스, 어두운 벨벳 배경" 이미지 생성.
2) 봉투 개봉 영상: 그 이미지를 Veo 3.1 i2v로 "왁스실 떼지고 → 플랩 열리며 → 카드·황금빛·꽃잎 나오는" 8초 영상으로.
3) 식장: Veo로 "골든아워 야외 결혼식장을 카메라가 꽃길 따라 전진 비행" 8초.
4) 주마등 배경: Veo 가성비(veo-3.0-fast)로 "버건디·골드 보케가 떠다니는" 배경 루프.
5) 스냅: Pexels에서 'couple sunset', 'wedding proposal' 등 세로 사진 6장.
GCP 키는 ~/.claude/.gcp-vertex-key.pem 사용. 다 docs 아래 저장해줘.
STEP 3 · 스크롤 청첩장 페이지 (복붙)

5막을 영화처럼 잇기

위 자산으로 세로 9:16 시네마틱 스크롤 청첩장(버건디)을 만들어줘. 스크롤하면 5막이 심리스하게 이어진다:
①봉투 개봉 ②주마등(스냅이 하나씩 앞으로 지나가고 배경 보케가 움직임) ③식장으로 날아 들어가며 긴 세로팬(버진로드) ④카운트다운·예식일정·오시는길·마음전하실곳 ⑤이름·날짜 피날레.
규칙: GSAP ScrollTrigger + Lenis. ★봉투·식장 스크럽은 'canvas 프레임 시퀀스'로(아래 비결). 핀 높이 100dvh, overflow-x:clip, 막 사이 어두운 veil 크로스페이드. 헤드리스 chromium으로 스크롤 지점별 스크린샷 찍어 검증하고 보여줘. Cloudflare Pages로 배포(파일당 25MB 이하로 압축). 이름·날짜·사진은 우선 더미.
STEP 4 · 방명록 · 배경음악 (복붙)

서버 없는 방명록 + 아이폰에서도 되는 음악

이 두 기능은 프롬프트·코드·함정까지 따로 아주 자세히 정리해뒀어요. → 방명록·배경음악 상세 가이드 보기 (맨 아래)

★ 비결 1 · 모바일에서 안 끊기는 법

영상을 "사진 시퀀스"로 쪼개 canvas에 그린다

스크롤에 맞춰 영상을 재생(video의 currentTime 조작)하면 폰(특히 사파리)에서 끊기거나 아예 안 돌아갑니다. 데스크톱에선 멀쩡해 보여서 속기 쉬워요.

그래서 우리는 영상을 ffmpeg로 사진 80장으로 쪼개고(fps=10), 전부 미리 불러둔 뒤, 스크롤 위치에 맞는 사진 한 장을 canvas에 그립니다. 디코딩·탐색이 없어서 폰에서 버터처럼 부드러워요. 카메라 줌·위빙·세로팬은 그 canvas에 CSS로 얹고요. (자동재생 배경 루프만 video 그대로 둬도 됨)

★ 비결 2 · 이거 모르면 며칠 날립니다

실제로 막혔던 함정들

· overflow-x:hidden 쓰면 position:sticky가 깨져요(핀이 그냥 스크롤돼 화면이 텅 빔) → overflow-x:clip 사용.
· 핀 높이 100vh는 모바일 주소창 때문에 점프100dvh.
· 세로팬이 캔버스 높이보다 크면 영상 아래 검정 노출 → 캔버스 높이에 맞춰 팬 폭 제한.
· Cloudflare Pages는 파일당 25MB 한도 → 영상 압축.
· 추측 금지: 헤드리스 브라우저로 스크롤별 스크린샷을 찍어 눈으로 확인하며 진행.

How it's made
방명록 · 배경음악, 똑같이 만드는 법

이 청첩장의 방명록(서버·DB 없이 작동)배경음악(아이폰에서도 재생)을, Claude Code에 프롬프트만 복붙해서 그대로 만드는 법입니다. 코딩 몰라도 됩니다.

준비물 딱 2개. ① 새 컴퓨터에 Claude Code 설치 ② Cloudflare 계정(무료, dash.cloudflare.com). 배포는 Cloudflare Pages, 방명록 저장은 Cloudflare KV(키-값 저장소)라 별도 DB 서버가 필요 없습니다. 먼저 한 번만 Claude Code에게 "wrangler login으로 Cloudflare 로그인 안내해줘"라고 시키세요.

1방명록서버 없음 · DB 없음

하객이 이름과 축하 메시지를 남기면 실시간으로 목록에 쌓입니다. 핵심은 전통적인 서버·데이터베이스가 전혀 없다는 것.

어떻게 동작하나 (1분 이해)

STEP 1저장소(KV) 만들고 연결

PROMPT · 복붙
내 청첩장 사이트에 방명록을 붙일 거야. 데이터는 Cloudflare KV에 저장해.
1) `GUESTBOOK` 이라는 KV 네임스페이스를 만들어줘 (wrangler로).
2) 프로젝트 wrangler.toml에 그 KV를 binding 이름 `GUESTBOOK` 으로 연결해줘.
3) 이 사이트는 Cloudflare Pages이고 빌드 출력 폴더는 정적 폴더(예: _site)야.
   pages_build_output_dir 도 맞춰줘.
완료되면 wrangler.toml 내용을 보여줘.
결과 예시 · wrangler.toml
name = "내사이트이름"
pages_build_output_dir = "_site"
compatibility_date = "2025-12-01"

[[kv_namespaces]]
binding = "GUESTBOOK"
id = "자동으로-생성된-KV-아이디"

STEP 2방명록 API 함수 만들기

PROMPT · 복붙
Cloudflare Pages Function으로 방명록 API를 만들어줘.
경로는 functions/api/guestbook.js (URL은 /api/guestbook). KV 바인딩 이름 GUESTBOOK.

- GET : 최근 메시지 최대 200개를 최신순 JSON으로. KV.list로 한 번에 가져오되
  내용은 각 키의 metadata에 저장해서 목록을 KV 호출 1번으로 끝내.
  (키 형식 m:<타임스탬프>_<랜덤>)
- POST : { name, message, website } 받아 저장. 스팸 방지 필수:
  1) 허니팟: website(화면 숨김)가 채워지면 봇이니 조용히 무시(성공인 척).
  2) 길이: 이름 1~20자, 메시지 2~200자. 공백 정리.
  3) 링크 차단: http/www/.com/.kr, 텔레그램·카톡아이디 패턴 거부.
  4) IP 레이트리밋: 같은 IP 60초 1번 (KV에 rl:<ip> 60초 TTL).
- 응답은 항상 JSON, no-store. KV 미연결이면 error:"kv_unbound".
결과 예시 · functions/api/guestbook.js (핵심)
export async function onRequestGet({ env }) {
  if (!env.GUESTBOOK) return J({ ok:false, error:"kv_unbound", items:[] });
  const list = await env.GUESTBOOK.list({ prefix:"m:", limit:1000 });
  const items = list.keys.map(k => k.metadata).filter(Boolean)
                  .sort((a,b) => b.t - a.t).slice(0,200);
  return J({ ok:true, items });
}
export async function onRequestPost({ request, env }) {
  const b = await request.json();
  if (clean(b.website,50)) return J({ ok:true });          // 허니팟
  const name = clean(b.name,20), msg = clean(b.message,200);
  if (name.length<1 || msg.length<2) return J({ok:false,error:"too_short"},400);
  if (hasLink(name+" "+msg))         return J({ok:false,error:"no_links"},400);
  const ip = request.headers.get("cf-connecting-ip") || "0";  // 레이트리밋
  if (await env.GUESTBOOK.get("rl:"+ip)) return J({ok:false,error:"rate_limited"},429);
  await env.GUESTBOOK.put("rl:"+ip, "1", { expirationTtl:60 });
  const t = Date.now(), key = `m:${t}_${Math.random().toString(36).slice(2,8)}`;
  await env.GUESTBOOK.put(key, "", { metadata:{ n:name, m:msg, t } });
  return J({ ok:true, item:{ n:name, m:msg, t } });
}
왜 metadata에 저장하나? KV는 목록을 가져올 때 값(value)까지 한 번에 주지 않습니다. 짧은 메시지를 키의 metadata에 넣으면 list() 한 번으로 내용까지 다 받아와서 200개를 KV 호출 1회로 끝낼 수 있어요(무료 한도 절약 + 빠름).

STEP 3화면(폼 + 목록) 붙이기

PROMPT · 복붙
청첩장에 방명록 섹션을 추가해줘 (기존 버건디·골드 톤에 맞춰서).
- 입력: 이름(max 20), 메시지(textarea, max 200), '남기기' 버튼.
- 봇 방지 허니팟 input(website)을 화면 밖(left:-9999px)에 숨겨 같이 전송.
- 열리면 GET /api/guestbook 로 목록을 카드로 렌더(이름·상대시간·메시지).
- 남기면 POST 후 성공 시 방금 글을 목록 맨 위에 바로 끼우고 입력칸 비우기.
  실패 시 에러코드별 한국어 안내(too_short/no_links/rate_limited/kv_unbound).
- 시간은 '방금/N분 전/N시간 전/M.D'.
- XSS 방지: 사용자 입력은 textContent로만, innerHTML 금지.
보안 한 줄: 하객 이름·메시지를 화면에 넣을 때 반드시 textContent(있는 그대로 글자로). innerHTML 쓰면 누가 스크립트를 적었을 때 실행될 수 있어요.

2배경음악아이폰에서도 재생

왜 그냥 autoplay가 안 되나: 모바일(특히 iOS)은 페이지가 멋대로 소리 내는 걸 막습니다. 음악은 사용자가 화면을 한 번 탭한 직후에만 시작 가능. 스크롤은 제스처로 안 쳐줍니다. 그래서 "탭하면 시작"으로 설계.

STEP 1저작권 free 음악 넣기

PROMPT · 복붙
배경음악을 넣을 거야. 저작권free 음원(mp3)을 audio/bgm.mp3 에 둘게.
(또는 무료·상업가능 음원 출처와 받는 법을 추천해줘.)
1MB 넘으면 ffmpeg로 96~128kbps로 압축해 용량 줄여줘.
loop, playsinline, preload="none" 인 <audio id="bgm"> 태그를 추가해줘.

STEP 2탭하면 시작 + 켜고끄기 + 기억

PROMPT · 복붙
배경음악 재생 로직을 만들어줘 (iOS 자동재생 정책에 맞게).
- 우하단 동그란 사운드 토글(🔇/🔊), 처음엔 은은한 펄스 + "탭하여 음악과 함께 ♪" 힌트.
- 첫 탭(버튼이든 화면 아무데든)에서 시작. 단 볼륨 0에서 1.7초에 걸쳐
  0.34까지 페이드인(갑자기 안 터지게).
- 버튼으로 켜고 끄기. 끄면 다음 방문 때 자동으로 안 켜지게
  localStorage('wed_muted')에 음소거 기억.
- iOS: click은 유효 제스처, scroll은 무효 → '첫 클릭 자동시작'은 document click(once)로.
결과 예시 · 재생 핵심
var on=false, muted=localStorage.getItem('wed_muted')==='1';
function enable(){ on=true; localStorage.setItem('wed_muted','0');
  bgm.play().then(()=>fade(bgm,0.34,1700)).catch(()=>{}); setIcon(); }
function disable(){ on=false; localStorage.setItem('wed_muted','1'); bgm.pause(); setIcon(); }
btn.addEventListener('click', e=>{ e.stopPropagation(); on?disable():enable(); });
document.addEventListener('click', ()=>{ if(!on&&!muted) enable(); }, { once:true });

STEP 3"입장"과 음악을 한 몸으로 (추천)

봉투의 왁스 씰을 탭하면 그 탭으로 음악이 같이 시작되게 하면 자연스럽습니다(별도 안내 없이 입장 = 재생).

PROMPT · 복붙
입장 동작과 음악을 연결해줘.
- window.__weddingEnter() 전역 함수: 호출되면 이전에 음소거였어도 무시하고
  음악을 강제 시작(enable).
- 봉투 가운데 '왁스 씰' 탭 핫스팟을 두고, 탭하면 __weddingEnter() 호출.
- 씰 위치는 봉투 이미지가 object-fit:cover로 잘리는 걸 감안해 JS로 화면좌표를
  계산해 정확히 씰 위에 오게(모바일·웹 둘 다). 헤드리스 chromium으로
  모바일·데스크탑 스크린샷을 찍어 씰에 맞는지 확인해서 보여줘(추측 금지).

STEP 4스크롤 효과음 (선택)

PROMPT · 복붙
효과음도 넣어줘 (음악이 켜져 있을 때만).
- 봉투 개봉 구간 진입 시 '종이 스치는 소리' 1회, 피날레 진입 시 '반짝' 1회.
- IntersectionObserver로 섹션이 보일 때 1회만 재생.
- 효과음은 Web Audio API(AudioContext+decodeAudioData)로 미리 디코드해
  지연 없이, 볼륨 0.26 정도로 작게.
흔한 함정:preload="none"이라야 가볍게 뜸. ② 볼륨은 꼭 페이드인. ③ iOS는 playsinline 필수. ④ 음소거는 localStorage로 기억해 재방문 존중. ⑤ 효과음은 <audio> 여러 개보다 Web Audio가 끊김 없이 깔끔.

3배포하고 확인

PROMPT · 복붙
이제 Cloudflare Pages로 배포해줘 (wrangler pages deploy). 배포 끝나면:
1) 사이트 URL 알려줘.
2) /api/guestbook 에 테스트 글을 하나 남겨보고 다시 조회해 저장·조회 확인.
3) KV 바인딩(GUESTBOOK)이 Pages 프로젝트에 실제 연결됐는지 확인
   (안 됐으면 대시보드 Settings에서 연결하는 법 안내).
4) 모바일에서 음악이 탭으로 시작되는지, 방명록이 보이는지 점검 포인트 알려줘.
방명록이 "준비 중입니다"로 뜨면: KV 바인딩이 배포된 Pages 프로젝트에 연결 안 된 경우. Cloudflare 대시보드 → Pages → 프로젝트 → Settings → Functions → KV namespace bindings에서 변수명 GUESTBOOK으로 연결하고 다시 배포하면 됩니다.

막히는 데 있으면 → @aitigermask

코드 한 줄 없이 AI로 만든 청첩장입니다.
만드는 법 → @aitigermask

탭하여 음악과 함께 ♪