무한 줌 타이틀
글자의 구멍 속에 다음 낱말이 있고, 그 속으로 끝없이 빨려 들어간다.
설정값 미리보기
바꾼 문구·색으로 다시 재생합니다
프롬프트 설정
Claude 가 읽는 제작 지시문입니다. 직접 읽지 않아도 됩니다.
무한 줌 타이틀 영상을 HTML 파일 하나로 만들어 줘. 미리보기에서 바로 재생되게. 길게 설명할 테니 숫자는 그대로 지켜 줘.
핵심 아이디어: 화면을 꽉 채운 한글 낱말의 속공간(입 의 ㅇ 안쪽 구멍, 명 의 ㅁ 안쪽 네모)이 창이다. 그 창 안에 다음 낱말이 작게 들어 있고, 카메라가 그 구멍으로 빨려 들어가면 다음 낱말이 화면을 꽉 채우며 내려앉는다. 그 낱말의 속공간에는 또 다음 낱말이 있다. 네 낱말을 지나면 처음 낱말로 돌아오고, 그 화면은 처음 화면과 픽셀까지 같다 — 끝없이 들어가는 것처럼 보인다. 구멍의 자리와 크기는 글자를 실제로 그린 픽셀에서 재므로 어느 글꼴에서도 창이 구멍에 맞는다.
[바꿀 곳]
- 낱말(순서대로, 3~5개, 낱말마다 1~3글자): 몰입 · 영감 · 공명 · 여운
낱말마다 닫힌 속공간이 있는 글자(ㅇ ㅁ ㅂ ㅍ ㅎ 이 든 글자)가 하나 이상 있어야 한다. ㅇ·ㅁ 이 세로 모음(ㅣ ㅕ 등)과 만난 글자의 구멍이 가장 크다.
- 단 색(바탕 / 글자) — 낱말 순서대로 돌고, 이웃한 두 단(마지막과 처음 포함)의 바탕색은 서로 달라야 한다:
1단 #111113 / #F2EDE3 · 2단 #F2EDE3 / #111113 · 3단 #FF4B2B / #111113 · 4단 #F2EDE3 / #FF4B2B
(1·2단은 반전, 3·4단은 강조색이 바탕과 글자를 번갈아 맡는다. 낱말이 3개나 5개면 이 목록을 그 수만큼 고친다.)
- 한 단계 길이: 2.0초 (전체 길이 = 2.0초 × 낱말 수 = 8.0초, 끝나면 처음으로 이어서 반복)
- 화면비: 9:16 (캔버스 1080×1920)
- 글꼴 굵기: 800 (기기에 그 굵기가 없으면 가장 가까운 굵은 굵기로)
[크기] — 화면 높이 H, 폭 W 기준. 모든 낱말이 같은 규칙으로 「자기 판」(W×H 크기의 배치)에 놓인다
- 글자를 한 글자씩 세로로 쌓는다(1글자 낱말은 한 글자). 글자 잉크 사이 세로 간격 = 0.06·F (F = 글꼴 크기).
- 글꼴 크기 F: 쌓은 잉크 덩어리 높이가 0.84H, 가장 넓은 글자의 잉크 폭이 0.86W 를 넘지 않는 범위에서 가장 크게.
글꼴 크기 100 에서 measureText 의 actualBoundingBox(Ascent·Descent·Left·Right)로 글자마다 잉크 높이·폭을 재고 비례로 역산한다.
두 글자 낱말이면 높이가 먼저 걸려서 F ≈ 0.44H(맑은 고딕 기준 F ≈ 850px), 잉크 폭 ≈ 0.69W 가 된다.
- 글자마다 잉크 가로 가운데를 판 가운데(x = W/2)에, 잉크 덩어리 전체의 세로 가운데를 판 가운데(y = H/2)에 맞춘다.
advance 원점이 아니라 actualBoundingBox 로 잰 잉크 상자를 기준으로 맞춘다.
measureText 의 상자는 브라우저가 거칠게(글꼴 크기의 1/32 단위) 줄 때가 있다. 그래서 [구멍 재기] 1) 의 픽셀 판에서 잉크 상자를 다시 재서
가로·세로 가운데 어긋남(보통 5px 안팎)만큼 배치를 옮기고, 옮긴 배치로 픽셀 판을 다시 그린 뒤 구멍을 잰다. 글꼴 크기 F 는 바꾸지 않는다.
- 테 두께 g = 0.02·F. 창(속공간) 안쪽 가장자리를 그 단의 바탕색으로 g 만큼 두른다. 이 테가 있어야 1단의 크림색 획과 2단의 크림색 바탕이 붙지 않는다.
[구멍 재기 — 시작할 때 낱말마다 한 번, 픽셀로]
1) 화면 밖 캔버스(W×H)에 그 낱말을 [크기] 배치 그대로 불투명하게 fillText 하고 getImageData 로 읽는다. 알파 ≥ 128 = 잉크, 나머지 = 빈칸.
2) 빈칸을 4-이웃 연결 성분으로 나눈다. 캔버스 가장자리에 닿는 성분은 바깥이다. 나머지가 속공간이고, 넓이가 W·H 의 0.0005배(약 1,040px) 미만인 것은 버린다.
3) 속공간마다 「다음 판이 들어가는 최대 배율」 σ 를 잰다. 그 성분의 마스크로 누적합 표(integral image)를 만들고,
중심 후보 = 성분 무게중심 + 성분 상자 폭·높이의 −0.2~+0.2 를 7×7 로 나눈 격자 49곳. 후보마다 이분 탐색(22번)으로
「가로 σ·W/2 + g, 세로 σ·H/2 + g 의 반폭을 가진 직사각형 안의 픽셀이 전부 그 속공간」인 가장 큰 σ 를 찾는다.
직사각형이 9:16 이어야 다음 판 전체가 들어가고, + g 가 있어야 다음 판이 테에 가리지 않는다.
4) σ 가 가장 큰 속공간을 그 낱말의 창으로 고른다. 창 배율 s = 0.94·σ, 창 중심 c = 그 후보 중심.
σ 가 가장 큰 후보가 여럿이면(최댓값의 0.5% 안) 그 후보들 중심의 평균을 창 중심으로 쓴다. 평균 자리에서 σ 를 다시 재서 줄지 않으면 쓰고,
줄면 성분 무게중심에 가장 가까운 후보를 쓴다. 명 의 ㅁ 처럼 속공간이 다음 판보다 넓으면 높이가 σ 를 정해 가로로 늘어선 후보가 모두 동점인데,
격자를 처음부터 돌며 「더 클 때만」 바꾸면 가장 왼쪽 후보가 남아 판이 한쪽에 붙고(맑은 고딕 왼쪽 22px · 오른쪽 70px), 남은 빈칸이 확대될수록 커져 구도가 기운다.
(0.94 는 여유다. 다음 낱말이 화면을 채운 순간 창 테두리와 테가 화면 밖에 확실히 머문다.)
맑은 고딕 기준: 몰입 s ≈ 0.079(입 의 ㅇ) · 영감 0.081(영 의 첫 ㅇ) · 공명 0.079(명 의 ㅁ) · 여운 0.113(여 의 ㅇ).
한 단계에 8~13배, 한 바퀴에 약 1.8만 배 들어간다. 글꼴이 바뀌면 이 값도 바뀌고, 코드는 늘 재서 쓴다.
5) 고른 속공간의 마스크를 W×H 캔버스(속공간 픽셀만 불투명)로 따로 만들어 둔다. 창을 뚫을 때 쓴다.
6) 속공간이 하나도 없는 낱말은 console.warn 하고 목록에서 뺀다. 콘솔에 낱말별 [F, 속공간 수, s, 한 단계 배율 1/s, c] 표를 출력한다.
[카메라 — 로그 줌]
판 좌표 x 를 화면 좌표로 옮기는 변환은 배율 a 와 이동 b 하나: 화면 = a·x + b. 회전은 없다.
- 단계 k (k = floor(t / 2.0) mod N, u = (t mod 2.0) / 2.0): 그 단계의 바깥 층은 낱말 k.
e(u) = (1 − cos(πu)) / 2. Z = (1/s_k)^e(u). 고정점 p_k = (c_k − s_k·C) / (1 − s_k), C = (W/2, H/2).
낱말 k 층: a = Z, b = p_k·(1 − Z). 즉 고정점 p_k 를 중심으로 확대한다.
- 안쪽 층(자식): a_자식 = a·s_j, b_자식 = b + a·(c_j − s_j·C) (j = 부모 낱말). 낱말 k+1, k+2, … 를 이 규칙으로 차례로 쌓는다.
- 검산: u = 1 이면 Z = 1/s_k 이고 낱말 k+1 층은 a = 1, b = 0 — 다음 단계의 첫 프레임과 정확히 같다. 낱말 N 다음은 낱말 1 이므로 8.0초 = 0초.
- e(u) 는 로그 배율을 0 → 1 로 옮긴다. 기울기 e'(u) = (π/2)·sin(πu) 라서 단계 경계에서 속도가 0 이 되지만 곧바로 선형으로 다시 일어난다 —
멈춰 서는 게 아니라 낱말을 「툭」 딛고 다음 구멍으로 다시 빨려 든다. 가운데(u = 0.5)에서 평균의 약 1.57배로 가장 빠르다.
경계 ±0.3초 동안 배율 변화는 15% 남짓이다(u = 0.15 에서 e = 0.055, 12.7배 단계면 1.15배).
경계 속도가 0 이어야 하는 이유: 단계마다 고정점이 바뀐다(맑은 고딕 몰입 p ≈ (398, 1213) → 영감 p ≈ (398, 326)). 경계 속도가 0 이 아니면 화면 가운데 점의 세로 속도가 한 프레임에 뒤집힌다.
- 고정점 p_k 는 창 중심 c_k 근처(화면 안)에 있다. 구멍이 제자리에서 커지다가 마지막에 화면 가운데로 옮겨 와 판을 채운다.
[장면] (맑은 고딕 기준 1단계 숫자. 다른 단계도 같은 박자다)
1. 0.00초 — 「몰입」이 화면 높이 0.84 를 채운다(검은 바탕, 크림 글자). 입 의 ㅇ 안에 검은 테가 둘리고, 그 안 크림 창에 검은 「영감」이 잉크 높이 약 0.066H 로 서 있다. 「영감」의 ㅇ 안에는 붉은 점 같은 3단이 보인다.
2. 0.00–0.15 짧은 딛기 — 배율 1.00 → 1.04. 0.50초에 1.45배, 이미 빨려 들기 시작한다.
3. 0.15–1.85 빨려 들어감. 1.00초(u = 0.5): 배율 3.57, 입 의 ㅇ 이 화면 폭을 넘고, 창 안 「영감」의 잉크 높이 0.24H.
1.30초(시그니처): 배율 6.35, 「영감」 잉크 높이 0.42H — 검은 테가 화면 가장자리 쪽으로 밀려나는 고리로 보이고, 그 안에 「영감」의 ㅇ 과 그 속 붉은 창이 화면 가운데로 다가온다.
1.50초: 배율 8.76, 「영감」 0.58H. 1.70초: 배율 11.07, 「영감」 0.73H.
4. 1.85–2.00 딛기 — 「영감」이 0.84H 에 내려앉는다(크림 바탕, 검은 글자). 2.00초 화면 = 「영감」을 바깥 층으로 그린 2단계 첫 화면.
서지 않고 곧바로 「영감」의 구멍으로 다시 들어간다. 각 낱말은 높이 0.5H 이상·잉크 전체가 화면 안인 상태로 약 1초 머물러 읽힌다.
5. 2.00–4.00 「영감」 → 「공명」(영 의 ㅇ 으로), 4.00–6.00 「공명」 → 「여운」(명 의 ㅁ 으로), 6.00–8.00 「여운」 → 「몰입」(여 의 ㅇ 으로).
6. 8.00초 = 0.00초 화면. 이음매가 없다.
합격 기준(60fps 로 한 바퀴 진행해 잰다):
- 직전 프레임 대비 평균 화소 차가 한 바퀴 평균의 15% 밑인 「거의 정지」 구간이 단계 경계마다 0.2초 이하, 한 바퀴 합 0.6초 이하.
- 8.00초 프레임과 0.00초 프레임의 차 0. 단계 경계 직전(t = 2k − 0.0001)과 경계 프레임(t = 2k)의 차 0.
- 창 직사각형에서 잉크까지 남는 폭이 좌우끼리, 위아래끼리 3px 안으로 같다(네 낱말 모두).
- GPU 없는 환경(CPU 래스터)에서도 rAF 간격 중앙값 16.7ms(60fps).
[구현]
- 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)은 스크립트 맨 끝에서 한 번만 호출한다. 정의되기 전에 쓰면 첫 화면에서 멈춘다.
이 작품에만 해당하는 구현:
- 기준 크기 1080×1920. 실제 픽셀은 보이는 크기 × devicePixelRatio 로 하되 1080×1920 을 넘기지 않는다(층마다 화면 크기 합성을 하므로).
그리기는 전부 기준 좌표로 하고 setTransform(k·a, 0, 0, k·a, k·b.x, k·b.y) 로 옮긴다(k = 실제 픽셀 폭 / 1080).
- 상태가 쌓이지 않는다. render(t) 는 t mod 8.0 만으로 단계·u·Z 를 다시 계산한다. 배치·구멍 재기는 시작할 때 한 번만 한다(시스템 글꼴이라 document.fonts.ready 뒤에 재도 된다).
- 그릴 층 목록: 낱말 k(바깥)부터 자식 규칙으로 내려가며, 그 층의 판 높이 a·H 가 2px 이상인 동안, 최대 4층.
- 그리는 순서는 안쪽에서 바깥으로:
① 가장 안쪽 층: 본 캔버스 전체를 그 단 바탕색으로 칠하고 낱말을 글자색으로 fillText(창은 뚫지 않는다).
② 바깥 층마다(안쪽에서 두 번째부터 낱말 k 까지): 화면 크기 작업 캔버스 하나를 재사용한다 —
지우고 → 그 단 바탕색으로 전체 칠 → globalCompositeOperation='destination-out' 으로 그 낱말의 창 마스크를 같은 변환으로 drawImage(창이 뚫린다)
창 마스크를 drawImage 할 때는 imageSmoothingEnabled = false 로 둔다(imageSmoothingQuality 를 쓰지 않는다).
→ 'source-over' 로 낱말을 그 단 바탕색 strokeText(lineWidth 2·g, lineJoin round) — 이것이 창 안쪽의 테다
→ 같은 낱말을 그 단 글자색 fillText → 작업 캔버스를 본 캔버스에 drawImage.
창 마스크는 비트맵이라 키우면 가장자리가 판 좌표 1px 의 계단이 되지만, 그 계단은 벡터로 그린 테(폭 g ≈ 17px) 아래에 숨는다 — 스무딩을 꺼도 화면은 같다.
- 글자는 매 프레임 배치 크기 F 의 fillText 를 변환 배율로 키워 벡터로 그린다. 배율이 12배를 넘어도 획이 선명해야 한다.
- CONFIG: words · levels(색 쌍) · stageSec 2.0 · fontWeight 800 · blockH 0.84 · maxInkW 0.86 · lineGap 0.06 · ringK 0.02 · fitMargin 0.94 · minCounter 0.0005 · searchGrid 7 · searchSpan 0.2 · bisect 22 · maxLevels 4 · minLevelPx 2.
- D 키: 디버그 — 바깥 층 낱말의 고른 속공간 윤곽(초록)과 다음 판이 들어갈 직사각형(하늘색)을 겹쳐 보여 준다.
[하지 말 것]
- 줌 중심을 화면 가운데로 두거나 구멍 자리·크기를 숫자로 박지 않는다 — 구멍은 화면 가운데에 있지 않아서, 가운데로 확대하면 구멍이 화면 밖으로 밀려 나간다. 글꼴마다 ㅇ·ㅁ 의 자리와 크기가 달라 박아 둔 숫자는 창이 획을 덮거나 획 사이로 바탕이 샌다. 고정점 p_k 와 s_k 는 언제나 픽셀에서 잰 값으로 계산한다.
- 배율을 시간에 비례하게(Z = 1 + 속도·t) 키우지 않는다 — 처음엔 기어가다가 끝에서 폭주한다. 로그 공간에서 옮겨야 매 순간 같은 비율로 빨려 든다.
- e(u) = u − sin(2πu)/(2π) 처럼 경계 근처가 평평한 곡선(기울기가 u² 로 일어나는 곡선)을 쓰지 않는다 — 단계마다 0.3초 넘게 서서, 한 바퀴의 18% 를 5% 안쪽 배율로 보낸다. 「끝없이 빨려 든다」가 「들어가고, 내려앉고」의 네 장면으로 끊긴다.
- 창 마스크를 부드럽게 키우지 않는다(imageSmoothingEnabled = true, imageSmoothingQuality = 'high' 금지) — 1080×1920 마스크를 15배 키워 고르게 샘플링하면 GPU 없는 기기에서 층마다 25ms 넘게 들어 20fps 로 떨어진다. 계단은 테 아래에 숨으므로 얻는 것이 없다.
- 배율을 프레임마다 곱해 쌓지 않는다(Z *= 1.04) — 프레임 속도마다 빠르기가 달라지고, 반복할 때 8.0초 화면이 0초 화면과 어긋난다.
- 단계 경계에서 컷·페이드·크로스페이드를 넣지 않는다 — 다음 낱말은 이미 창 안에 있던 그 층이 그대로 커진 것이어야 한다. 경계 프레임에 새로 그려 넣으면 위치와 크기가 한 프레임에 튀어 「끝없이 들어간다」가 「장면이 바뀐다」로 읽힌다.
- 안쪽 낱말을 따로 움직이거나 따로 키우지 않는다 — 안쪽 층은 부모의 변환을 그대로 물려받아야 착지 순간 a = 1, b = 0 이 정확히 맞는다.
- 낱말을 미리 비트맵으로 그려 drawImage 로 키우지 않는다 — 한 단계에 12배 넘게 커지므로 획이 계단처럼 뭉개진다. 비트맵은 창 마스크 하나뿐이고, 그 가장자리는 테가 덮는다.
- 테 없이 창을 뚫지 않는다 — 1단의 크림색 획과 2단의 크림색 바탕이 맞붙어 ㅇ 이 꽉 찬 원 덩어리로 보인다. 테가 있어야 획·테·창 세 겹이 갈린다.
- ㅇ 자리에 원을 그리거나 호환 자모 글자(ㅇ, ㅁ)를 따로 그려 창을 만들지 않는다 — 완성 글자 안의 ㅇ 은 따로 쓴 ㅇ 과 모양·크기가 달라, 확대할수록 창이 진짜 구멍과 어긋나 획이 창을 가르거나 틈이 생긴다. 구멍은 그린 글자의 픽셀에서 잰다.
- 화면 회전·나선 줌을 넣지 않는다 — 창 맞춤은 기울지 않은 9:16 직사각형으로 쟀다. 돌리면 다음 판의 모서리가 속공간 밖으로 나가 획에 잘린다.
- 낱말을 가로 한 줄로 놓거나 화면 가운데 작게 두지 않는다 — 세로 화면의 위아래가 비어 슬라이드처럼 보이고, 구멍도 작아져 한 단계 배율이 약 2배(맑은 고딕 「몰입」 12.7배 → 24배)로 커진다. 세로로 쌓아 높이 0.84H 를 채운다.
- 줌 블러·방사형 흐림·모션 블러를 넣지 않는다 — 층마다 화면 크기 합성을 하므로 여러 장을 평균하면 프레임이 떨어지고, 착지 순간 읽혀야 할 낱말을 흐린다.
- 장식 입자·빛 번쩍임·글로우·그림자·비네트를 넣지 않는다 — 볼거리는 평평한 색면이 끝없이 겹쳐 드는 것이다. 장식은 창 안의 작은 낱말과 섞여 구분이 안 되고, 비네트는 화면 가장자리 색을 바꿔 반복 이음매를 흐린다.
- 모서리 라벨·진행 막대·배율 숫자를 띄우지 않는다 — 화면에 있는 것은 낱말뿐이어야 타이틀로 쓸 수 있다.
- 단 색을 두 가지 넘게 섞거나 그라디언트로 칠하지 않는다 — 한 단은 바탕 하나, 글자 하나다. 이웃한 단의 바탕만 다르면 창이 보인다.
사용 방법
필요한 것: claude.ai 계정 — 무료 계정에서도 Claude Opus 5.5 를 고를 수 있습니다
- claude.ai 에서 새 대화를 엽니다.
- 모델 메뉴에서 Claude Opus 5.5 를 고르고, 같은 메뉴의 Effort(답변 깊이)를 High 로 둡니다.
- 복사한 프롬프트를 붙여 넣고 전송합니다. 결과로 나온 HTML 파일을 브라우저로 열면 재생됩니다.
- 권장 모델
- Claude Opus 5.5 · Effort High
- 화면비
- 9:16 (세로)
- 재생 길이
- 8초
- 결과물
- HTML 파일 하나 · 따로 설치할 것 없음
- 영상 파일
- 위 「MP4 저장 기능 포함」을 켜면 결과 화면에 녹화 단추가 생깁니다 1080 기준 · 12 Mbps · 브라우저에 따라 MP4 또는 WebM
추가 요청 예시
결과가 나온 뒤 같은 대화에서 이어서 요청하면 해당 부분만 수정됩니다.
- 낱말을 「성장 · 마음 · 호흡 · 열망」 네 개로 바꿔 줘. 글자 안쪽 빈 공간을 찾는 방식은 그대로 두고.
- 색을 남색 #14213D 와 연노랑 #FCE9B8 두 가지만 써서, 낱말마다 바탕색과 글자색을 서로 맞바꾸게 해 줘.
- 낱말 하나가 지나가는 시간을 2초에서 3초로 늘려서 낱말이 더 오래 읽히게 해 줘. 전체는 12초.
인트로