굵기 파도 타이틀
큰 한글 제목 위로 가는 획에서 굵은 획까지, 굵기의 너울이 왼쪽에서 오른쪽으로 지나간다.
설정값 미리보기
바꾼 문구·색으로 다시 재생합니다
프롬프트 설정
Claude 가 읽는 제작 지시문입니다. 직접 읽지 않아도 됩니다.
큰 한글 제목 위로 「굵기의 파도」가 지나가는 16:9 타이틀 애니메이션을 HTML 파일 하나로 만들어 줘. 미리보기에서 바로 재생되게. 길게 설명할 테니 숫자는 그대로 지켜 줘.
핵심 아이디어: 글자는 제자리에 가만히 있고 굵기만 움직인다. 너울 하나가 왼쪽에서 오른쪽으로 지나가면, 너울 앞쪽의 획은 머리카락처럼 가늘어지고(물이 빠지는 자리), 물마루가 닿은 획은 아주 굵어졌다가, 지나간 뒤에는 원래 굵기로 돌아온다. 굵기는 음절마다 한 값이 아니라 화면 가로 위치마다 연속으로 변해서, 한 글자 안에서도 가로획이 한쪽 끝은 가늘고 다른 끝은 굵게 이어진다. 화면을 꽉 채운 두 줄 위로 가장 큰 너울이 첫 1초 안에 들이닥치고, 크기가 다른 여진 셋이 뒤따른다. 화면이 멈춰 있는 순간이 없다.
[바꿀 곳]
- 윗줄: 파도를
- 아랫줄: 타는 글자
- 색: 배경 #0B1A26 · 글자 #F2EEE4 · 물마루 #C0FFF6 (물마루 색은 글자 색보다 어둡지 않게 고른다 — 어두운 바탕에서 굵어지는 자리가 어두워지면 굵기 변화가 상쇄된다. 이 두 색은 어떤 비율로 섞어도 상대 휘도 0.856 이상)
- 길이: 8초 (끝나면 처음으로 이어서 반복)
- 화면비: 16:9 (캔버스 1920×1080)
- 글꼴: 'Pretendard', 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', sans-serif · 굵기 400 한 벌만 쓴다
- 문구를 바꿀 때: ㅋ·ㅆ 처럼 두 획이 거의 닿는 글자는 가장 굵을 때 틈이 닫혀 보일 수 있다. 거슬리면 CONFIG.weight.growEm 을 0.018 에서 0.012 로 낮춘다.
[크기] (W = 화면 폭 1920, H = 화면 높이 1080)
- 줄마다 글자 크기 S 를 따로 정한다: 그 줄의 잉크 폭이 0.88W 가 되게(짧은 줄일수록 글자가 커진다 — 「파도를」이 「타는 글자」보다 크다).
- 음절 사이에 0.04em 을 더 띄운다(띄어쓰기 칸 뒤에도 똑같이). 음절은 한 글자씩 따로 놓는다: 각 음절의 x = 앞 음절들의 measureText advance 폭 + 0.04em 의 누적.
- 잉크 폭·높이는 measureText 의 actualBoundingBoxLeft/Right/Ascent/Descent 로 음절마다 재서 합친다(advance 상자가 아니라 실제 획의 상자). 100px 로 한 번 재고 비례로 S 를 구한다.
- 두 줄 잉크 높이의 합 + 줄 사이 0.07H 가 0.84H 를 넘으면 두 줄의 S 를 같은 비율로 줄여 정확히 0.84H 로 맞춘다.
맑은 고딕에서 윗줄 S ≈ 0.51H, 아랫줄 S ≈ 0.35H, 줄이는 비율 ≈ 0.86 이 나온다(Noto Sans KR 은 0.50H · 0.35H · 0.89).
높이 맞춤이 걸리므로 실제 줄 잉크 폭은 0.76~0.78W 다. 이것이 정상이다 — 폭을 0.88W 로 되돌리려고 공식을 고치지 않는다.
- 두 줄 덩어리의 세로 가운데 = 0.5H. 줄마다 잉크 상자의 가로 가운데 = 0.5W.
[굵기 — 픽셀마다 계산한다]
기기마다 가진 굵기 벌이 달라(맑은 고딕은 두세 벌뿐) 글꼴 굵기로는 연속으로 바꿀 수 없다. 그래서 글꼴 굵기는 400 하나로 두고, 글자의 「거리장」을 문턱값으로 잘라 굵기를 연속으로 만든다.
1) 시작할 때 한 번, 줄마다 작업 캔버스(줄 잉크 상자 + 사방 여백 pad = ceil(0.03·S) + 4px)에 흰색·굵기 400 으로 음절들을 fillText 하고 getImageData 로 알파 a(0~1)를 읽는다(getContext 에 willReadFrequently: true).
알파를 먼저 정리한다: a ≤ 1/64 이면 0, a ≥ 63/64 이면 1. 그다음 위아래 두 이웃이 모두 a ≥ 0.99 이거나 좌우 두 이웃이 모두 a ≥ 0.99 인 픽셀은 a = 1 로 메운다.
— 가변 글꼴(Noto Sans KR·Pretendard 등)은 겹친 윤곽을 그대로 두어 글자 안에 1px 반투명 줄이 남는다. 이걸 안 메우면 거리장이 그 줄을 가장자리로 읽어, 가늘어질 때 획 안쪽이 둥글게 파인다(Noto 「를」의 아래 ㄹ). 0.00 과 0.01 같은 알파 흔들림도 가장자리에 1px 눈금 자국을 만든다.
2) 부호 있는 거리장 d(px)를 만든다 — 획 밖은 +(가장 가까운 잉크까지 거리), 획 안은 −(가장 가까운 바깥까지 거리).
정리한 알파로 두 격자를 채운다(INF = 1e20, Infinity 는 쓰지 않는다 — 빼기에서 NaN 이 난다):
a ≥ 1 이면 outer = 0, inner = INF · a ≤ 0 이면 outer = INF, inner = 0 · 그 사이면 outer = max(0, 0.5 − a)², inner = max(0, a − 0.5)².
두 격자에 Felzenszwalb–Huttenlocher 제곱 거리 변환을 세로 한 번, 가로 한 번 돌린 뒤 d = √outer − √inner. Float32Array 로 줄마다 보관한다.
안티에일리어스 알파로 반 픽셀 오프셋을 넣었으므로 가장자리 위치가 1px 보다 정밀하다.
3) 굵기 w 는 −1 ~ +1. 0 = 원래 굵기(400), +1 = 가장 굵게, −1 = 헤어라인. 가장자리를 밀어낼 반지름 r(px):
w ≥ 0 이면 r = w × 0.018 × S, w < 0 이면 r = w × 0.022 × S (음수 = 안으로 깎기). 0.018 은 CONFIG.weight.growEm, 0.022 는 CONFIG.weight.thinEm.
4) 픽셀의 덮임 α = clamp(r − d + 0.5, 0, 1). w = 0 이면 원래 글자와 같은 모양·같은 안티에일리어스가 나온다.
5) 검산(맑은 고딕 브라우저 실측 — 한도를 이 값으로 정한 이유): 굵기 400 세로 줄기 ≈ 0.092em, 가는 가로획 ≈ 0.063em(Noto Sans KR 0.083em · 0.057em).
w = +1 → 세로 줄기 0.092 + 2×0.018 = 0.128em(약 1.4배). w = −1 → 세로 줄기 0.092 − 2×0.022 = 0.048em(약 0.5배), 가는 가로획 0.019em(헤어라인).
그래서 세로 줄기는 가장 가늘 때와 가장 굵을 때가 약 2.7배, 가로획은 약 5배 차이 난다 — 굵기 파도는 주로 가로획에서 읽힌다.
이 문구에서 속이 막히기 시작하는 반지름은 0.024em(Noto 0.026em), 가는 가로획이 끊기기 시작하는 반지름은 0.031em(Noto 0.028em)이다.
growEm 0.018 · thinEm 0.022 는 그 한계에서 윗줄 기준 약 3~5px 씩 남긴 값이다. 움직임을 키우고 싶으면 이 값을 올리지 말고 시간표를 바꾼다.
[파도 — 굵기 지도]
굵기는 줄과 화면 x 만의 함수다(같은 줄의 같은 세로 열은 위아래 모두 같은 w). 프레임마다 줄마다 x 열 1920개의 w 를 먼저 계산해 배열에 담는다.
- 너울 하나의 모양: P(u) = −1.6487 · u · e^(−u²/2). 1.6487 = √e 라서 u = −1 에서 정확히 +1(물마루), u = +1 에서 정확히 −1(골, 헤어라인)이다. |u| > 4.5 이면 사실상 0.
- 너울 k (세기 s, 방향 dir = +1 왼→오 / −1 오→왼, 기준 시각 tc, 속도 v[W/초]):
X = 0.5W + dir·(σ + v·W·(t − tc)) − dir·줄번호·LAG, u = dir·(x − X)/σ, w += s·P(u)
σ = 0.13W(250px), LAG = 0.10W, 줄번호 = 윗줄 0 · 아랫줄 1. 윗줄 물마루가 tc 초에 정확히 화면 한가운데(0.5W)를 지나고, 골은 진행 방향으로 2σ = 0.26W 앞에 있다.
아랫줄은 0.10W 뒤처져 따라온다 — 두 줄을 잇는 파면이 진행 방향으로 기울어 보인다.
- 8초 주기로 감는다: 너울마다 t − 8, t, t + 8 세 시각으로 계산한 값을 모두 더한다. 그래서 0초 직전에 들어오던 너울의 꼬리가 이음매를 건너 그대로 이어지고, 처음과 끝을 비워 둘 필요가 없다(8.00초 화면 = 0.00초 화면이 식으로 정확히 성립한다).
- 너울 넷(감기 포함)을 더한 뒤 w 를 −1 ~ +1 로 자른다(이 시간표에서는 합의 최댓값이 0.99999 라 1 을 넘는 순간이 없다 — 자르기는 안전장치).
- 물마루 색: m = smoothstep(0.70, 1.00, w) × 0.40, 그 열의 글자 색 = mix(글자 색, 물마루 색, m). 가장 큰 너울의 물마루만 살짝 물든다 — 「가장 큰 너울이 여기 있다」는 표시일 뿐, 주인공은 굵기다.
[장면] (시간표 · 보이는 구간 = 글자 위에서 |w| > 0.08, 약 1px 이상 굵기가 바뀌는 구간)
| 너울 | 세기 s | 방향 | 속도 v | 기준 tc | 보이는 구간 |
| ① 큰 너울 | 1.00 | 왼→오 | 0.6W/초 | 1.30 | 7.82–8.00초 → 0.00–2.51초(이음매를 건넌다) |
| ② 잔물결 | 0.45 | 왼→오 | 0.9W/초 | 3.40 | 2.47–4.16초 |
| ③ 중간 | 0.70 | 왼→오 | 0.9W/초 | 5.00 | 4.04–5.79초 |
| ④ 되밀림 | 0.50 | 오→왼 | 0.8W/초 | 6.90 | 5.84–7.76초 |
- 0.00초: 첫 프레임부터 윗줄 왼쪽 끝 「파」가 이미 가늘어지고 있다(w ≈ −0.4). 정지한 제목으로 시작하지 않는다.
- 0.25초: 큰 너울의 골이 왼쪽에서 들어와 「파」가 헤어라인(w = −1, 0.13W)이 된다.
- 0.75초: 물마루가 들어온다 — 윗줄은 0.17W 에서 가장 굵고 0.43W 에서 헤어라인. 첫 1초 안에 가장 가는 획과 가장 굵은 획이 한 줄에 같이 보인다.
- 1.30초 — 시그니처: 큰 너울 ①(가장 느림)의 윗줄 물마루가 화면 한가운데에 선다. 이때 윗줄은 왼쪽부터 원래 굵기 → 0.5W 에서 가장 굵고 물마루 색이 살짝 듦 → 0.76W 에서 헤어라인 → 오른쪽 끝에서 다시 원래 굵기. 아랫줄 물마루는 0.40W 에 있다. 이 물마루가 폭 약 0.27W 인 윗줄 글자 하나를 약 0.45초에 걸쳐 건넌다.
- 2.47–5.79초: 여진 ②(잔물결)와 ③(중간)이 차례로 왼쪽에서 들어와 지나간다. 앞 너울이 빠지기 전에 다음 너울이 들어온다.
- 5.84–7.76초: 되밀림 ④가 오른쪽에서 들어와 왼쪽으로 빠진다(골이 왼쪽 앞, 물마루가 뒤).
- 7.82초부터 다음 바퀴의 큰 너울 ①이 다시 왼쪽에서 밀려온다. 글자 위가 1px 넘게 바뀌지 않는 순간은 5.79–5.84초와 7.76–7.82초, 각 0.05초뿐이다.
[구현]
- HTML 파일 하나로 만든다. 외부 파일·글꼴·라이브러리·네트워크 요청을 쓰지 않는다(미리보기에서 막히거나 느려진다).
- 첫 <script> 맨 앞에 CONFIG 객체를 두고 [바꿀 곳]의 값과 이 글에 나온 숫자(시간표·크기·색·세기)를 이름 붙여 모은다. 나중에 숫자만 바꿔 다듬을 수 있게.
- 그림은 시간 t(초)만으로 계산하는 함수 render(t) 로 그린다. requestAnimationFrame 의 타임스탬프나 performance.now() 로 t 를 구한다. setTimeout·setInterval·CSS 애니메이션·transition 으로 움직이지 않는다(프레임마다 속도가 달라지고, 다시 재생할 때 어긋난다). 시뮬레이션처럼 상태가 쌓이는 작품은 고정 시간 간격으로 상태를 진행하되 그 진행도 타임스탬프로만 정한다.
- 난수는 시드 고정 난수(mulberry32 등)만 쓴다. Math.random 은 쓰지 않는다(매번 다른 화면이 나온다).
- 캔버스 내부 해상도는 화면비에 맞춘 기준 크기로 고정하고, 창 크기에 맞춰 화면비를 지키며 가운데에 꽉 차게 보이게 한다(남는 곳은 배경색). 스크롤이 생기지 않게 한다. devicePixelRatio 는 그리는 선명도에만 반영한다.
- 글자는 시스템 글꼴만 쓴다: 'Pretendard', 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', sans-serif. 붓글씨·손글씨 느낌은 글꼴이 아니라 획을 그려서 낸다.
- 길이가 정해진 작품은 끝나면 처음과 같은 화면으로 돌아가 이음매 없이 반복한다.
- 모든 함수·상수·셰이더 문자열을 먼저 정의하고, 시작(초기화와 첫 requestAnimationFrame)은 스크립트 맨 끝에서 한 번만 호출한다. 정의되기 전에 쓰면 첫 화면에서 멈춘다.
- 이 작품의 그리는 순서:
① 시작할 때: 1920×1080 글자 버퍼 캔버스 + ImageData 하나를 만든다(전부 투명). 거리장에서 굵기가 바뀌어도 덮임이 변하지 않는 픽셀을 미리 가른다 —
d < −(thinEm·S + 1) 인 깊은 안쪽은 한 번 흰색 불투명으로 써 두고, d > growEm·S + 1 인 바깥은 투명으로 둔다.
그 사이 가장자리 띠 픽셀의 버퍼 번호와 d 를 Int32Array·Float32Array 로 줄마다 모아 둔다(줄 상자 픽셀의 약 1/3).
② 프레임마다: 그 띠 픽셀만 돌며 α 를 계산해 Uint32Array 로 흰색 + 알파를 쓴다(리틀 엔디언이라 (round(α·255)<<24) | 0x00FFFFFF) → putImageData.
③ 색: 줄마다 1920×1 색 띠(열마다 그 열의 글자 색, 물마루 섞임 포함, 줄마다 자기 w)를 만들어, 1920×1080 색 캔버스에 그 줄 상자(+pad) 높이로 세로로 늘여 그린다(imageSmoothingEnabled = false, 일반 합성).
그다음 글자 버퍼에 globalCompositeOperation = 'source-in' 으로 색 캔버스 전체를 한 번 그리고 'source-over' 로 되돌린다. 줄마다 따로 source-in 하면 그 그림 밖이 지워져 다른 줄이 사라진다.
④ 화면 캔버스에 배경색을 칠하고 글자 버퍼를 drawImage 로 화면 크기에 맞춰 붙인다(imageSmoothingQuality = 'high').
- 글자 버퍼는 1920×1080 으로 고정한다(녹화 기준 1080p). devicePixelRatio 는 화면 캔버스 크기에만 반영하고, 고해상도 화면에서 가장자리가 약간 부드러워지는 것은 받아들인다 — 버퍼를 DPR 배로 키우면 픽셀이 DPR² 배가 되어 60fps 를 못 지킨다.
- 거리장과 가장자리 띠는 시작할 때 한 번만 만든다.
- CONFIG 에 모을 것: text{top, bottom} · color{bg, ink, crest} · duration 8 · layout{fitW 0.88, blockH 0.84, lineGap 0.07, trackEm 0.04} · weight{growEm 0.018, thinEm 0.022} · wave{sigmaW 0.13, lagW 0.10, crestFrom 0.70, crestMix 0.40} · swells[{s, dir, v, tc}] 넷.
- 디버그: G 키를 누르면 화면 아래쪽에 두 줄의 w(x) 곡선을 가는 선으로 겹쳐 보여 준다(기본은 끔).
[하지 말 것]
- 글자를 위아래로 출렁이게 하거나 크기·기울기를 바꾸지 않는다 — 이 작품의 파도는 굵기 하나다. 자리가 움직이면 눈이 위치를 따라가 굵기 변화가 안 읽힌다.
- font-weight 를 바꿔 다시 그리거나 음절마다 굵기를 한 값으로 주지 않는다 — 기기마다 가진 굵기 벌이 달라 계단처럼 튀고, 한 글자 안을 물결이 지나가는 모습이 사라진다.
- 화면을 세로 띠로 잘라 띠마다 strokeText 로 팽창·침식하지 않는다 — 띠 경계가 안티에일리어스되어 글자 위에 머리카락 같은 밝은 세로줄이 생기고, 프레임마다 글자를 수백 번 다시 그려 느려진다.
- growEm 0.018 · thinEm 0.022 를 넘기지 않는다 — 한계까지 약 3~5px 밖에 안 남아, 더 굵히면 글자 속이 막히고 더 깎으면 가는 가로획이 끊긴다.
- 알파 정리(1/64 · 63/64 · 1px 이음매 메우기)를 빼지 않는다 — 가변 글꼴에서 가늘어질 때 획 안쪽이 둥글게 파인다.
- 물마루 색을 글자 색보다 어둡게 하거나 crestMix 를 0.40 보다 올리지 않는다 — 굵어지는 자리가 어두워져 굵기 변화가 상쇄되고, 썸네일에서 색 덩어리가 먼저 눈에 걸린다.
- 거리장을 프레임마다 다시 만들지 않는다 — 한 번에 수십 ms 가 들어 끊긴다.
- 사인파를 화면 전체에 같은 주기로 끝없이 흘리지 않는다 — 크기가 다른 너울이 한 번씩 지나가야 「가장 큰 너울」이라는 순간이 생긴다.
- 글로우·그림자·그라디언트 배경·물방울·파티클·물결선이나 바다 그림을 더하지 않는다 — 파도는 글자 굵기만으로 보여 주는 작품이고, 장식이 붙으면 굵기 변화가 묻힌다.
- 물마루 색을 w < 0.70 인 곳이나 글자 전체에 칠하지 않는다 — 색이 주인공이 되면 굵기가 안 보인다.
- 처음과 끝을 「고요」로 비워 이음매를 맞추지 않는다 — 첫 1초에 굵기 파도가 안 읽히고, 반복할 때 정지 화면이 1초 넘게 걸린다. 이음매는 8초 주기 감기로 맞춘다.
- 화면에 제목 말고 다른 글자(라벨·자막·진행 막대·시간)를 두지 않는다 — 타이틀 카드로 녹화해 쓰는 작품이라 화면에 남으면 그대로 찍힌다.
사용 방법
필요한 것: claude.ai 계정 — 무료 계정에서도 Claude Opus 5.5 를 고를 수 있습니다
- claude.ai 에서 새 대화를 엽니다.
- 모델 메뉴에서 Claude Opus 5.5 를 고르고, 같은 메뉴의 Effort(답변 깊이)를 High 로 둡니다.
- 복사한 프롬프트를 붙여 넣고 전송합니다. 결과로 나온 HTML 파일을 브라우저로 열면 재생됩니다.
- 권장 모델
- Claude Opus 5.5 · Effort High
- 화면비
- 16:9 (가로)
- 재생 길이
- 8초
- 결과물
- HTML 파일 하나 · 따로 설치할 것 없음
- 영상 파일
- 위 「MP4 저장 기능 포함」을 켜면 결과 화면에 녹화 단추가 생깁니다 1080 기준 · 12 Mbps · 브라우저에 따라 MP4 또는 WebM
추가 요청 예시
결과가 나온 뒤 같은 대화에서 이어서 요청하면 해당 부분만 수정됩니다.
- 색을 따뜻하게 바꿔 줘: 배경 #14110F, 글자 #E9DCC9, 굵어지는 자리의 밝은 색(물마루) #FFE9C7. 이 밝은 색이 글자 색보다 어두워지지 않는 규칙은 그대로 지켜 줘.
- 문구를 윗줄 「오늘도」, 아랫줄 「출렁이는 마음」으로 바꿔 줘. 줄마다 폭과 높이를 맞추는 크기 규칙은 그대로 둬.
- 더 잔잔하게 해 줘: 글자가 굵어지고 가늘어지는 물결 네 개의 세기를 전부 절반으로 줄이고, 굵어지는 자리의 밝은 색(물마루)은 빼서 굵기 변화만 남겨 줘.
한글