슬롯머신 디코드
비취 에나멜 판의 크롬 창 속에서 한글 음절 릴이 돌다가 왼쪽부터 「탁」 멈추고, 문장이 맞는 순간 빛줄기가 화면을 쓸고 간다.
설정값 미리보기
바꾼 문구·색으로 다시 재생합니다
프롬프트 설정
Claude 가 읽는 제작 지시문입니다. 직접 읽지 않아도 됩니다.
한글 문장이 슬롯머신 릴에서 풀려 나오는 세로 모션그래픽을 HTML 파일 하나로 만들어 줘. 미리보기에서 바로 재생되게. 길게 설명할 테니 숫자는 그대로 지켜 줘.
핵심 아이디어: 화면 전체가 녹청빛 비취 에나멜 판이고(1950년대 기계 앞판처럼 차분하고 광택만 있는 면), 판에 뚫린 여덟 개의 창마다 크롬 테두리가 박혀 있다. 창 뒤에서 상아색 원통(릴)이 돈다. 음절 하나가 릴 하나다. 첫 프레임부터 여덟 개의 릴이 전부 최고 속도로 돌며 무작위 한글 음절을 쏟아 낸다. 그러다 읽는 순서대로 왼쪽 글자부터 한 칸씩 「탁」 멈춘다. 멈출 때마다 띠가 정답 칸을 살짝 지나쳤다가 되돌아오고, 창이 짧게 덜컥이고, 그 창의 크롬 테두리가 하얗게 한 번 반짝인다. 한 줄이 다 맞으면 그 줄 창에 불이 들어온다(띠가 밝아지고 테두리가 크롬에서 금으로). 마지막 칸만 혼자 천천히 기어 와서 멈추고, 그 순간 사선 빛줄기 하나가 왼쪽 위에서 오른쪽 아래로 화면 전체를 쓸고 지나간다 — 에나멜과 금속이 빛을 받는 한 방. 그다음 기계(띠·테두리)가 왼쪽부터 벗겨져 에나멜 판 위에 문장만 남는다. 기계가 무작위 음절을 단 채 불 꺼진 크롬으로 돌아와 레버를 당기듯 다시 돌기 시작하고, 처음 화면(전부 도는 릴)으로 이어진다. 정답 음절은 띠 위의 실제 칸 하나에만 들어 있어서 굴러 들어와 멈춘다 — 마지막에 바꿔 끼우는 것이 아니고, 첫 화면에는 정답이 없다.
[바꿀 곳]
- 문장: 줄마다 한 단어, 음절 하나 = 릴 하나. 1줄 「오늘의」 / 2줄 「운세는」 / 3줄 「맑음」 (한 줄 4음절·4줄까지 아래 크기 규칙이 자동으로 맞춘다)
- 색:
에나멜 판 위끝 #3A6158 · 가운데(55%) #355850 · 아래끝 #26403A — 판은 차분한 면이다: 세 스톱 모두 OKLCH 채도 C 0.05 이하(지금 0.047 · 0.043 · 0.034). 화면의 40% 이상을 덮는 색은 C 0.08 이하로 두고, 강한 채도는 금 링·경계선·빛줄기처럼 화면의 15% 아래를 차지하는 강조에만 쓴다. 판 색을 바꿀 때도 이 상한을 지킨다.
꺼진 띠 #E4DAC4 · 켜진 띠 #FFF9EA · 띠 위 글자 #1C1416 · 맨 문장 글자 #FBF3E1
크롬 테두리 = 아래 [판·테두리·빛]의 크롬 스톱 · 금 테두리 = 금 스톱 · 경계선 #FFC94A · 빛줄기 #FFF6E8
- 길이: 8초 (끝나면 처음으로 이어서 반복)
- 화면비: 9:16 (캔버스 1080×1920)
- 글꼴: 'Pretendard', 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', sans-serif · 굵기 800(없으면 그 글꼴의 가장 굵은 것)
- 시드: 7
[크기]
W = 화면 폭 1080, H = 화면 높이 1920. 크기는 전부 이 둘의 비율로 쓴다.
- L = 줄 수, n = 가장 긴 줄의 음절 수. 지금 L = 3, n = 3, 릴 M = 8개.
- 창(릴이 보이는 구멍) 폭 cw = (0.90W − (n−1)·0.03W) ÷ n. 지금 0.28W(302px). 같은 줄의 창 사이 간격 0.03W.
- 창 높이 rh = min(0.19H, (0.64H − (L−1)·0.035H) ÷ L). 지금 0.19H(365px). 줄 사이 간격 0.035H.
- 줄 덩어리 전체(창 위끝~아래끝) 높이 = L·rh + (L−1)·0.035H, 그 중심 y = 0.47H. 지금 0.15H~0.79H 를 차지한다. 각 줄은 가로 가운데 정렬(3줄 「맑음」은 창 2개가 가운데).
- 글자 크기 fs = min(0.86·cw, 0.70·rh). 지금 255px = 0.133H. 창 덩어리는 화면 폭의 90%, 글자 잉크는 약 82%(x 0.09W~0.91W)를 덮는다.
- 창 모서리 반지름 0.03·cw.
- 크롬 테두리(링): 창 바깥으로 두께 b = 0.010W(약 11px). 바깥 모서리 반지름 0.03·cw + b. 그래서 같은 줄 이웃 링 사이에 에나멜이 약 11px, 줄 사이에 약 46px 보인다. 링까지 친 덩어리는 x 0.04W~0.96W, y 0.144H~0.796H.
- 아래 0.80H~1.00H 는 비워 둔다(쇼츠·릴스 화면 아래쪽 글자·버튼이 덮는 자리). 에나멜 판만 있다.
[릴 — 띠, 원통, 흐림]
- 띠의 칸 간격(한 음절에서 다음 음절까지) P = 1.30·fs(지금 332px). 원통 반지름 Rc = 0.75·rh(지금 274px).
- 릴 k 의 위치 s_k(t) 는 칸 단위 실수다. s 가 늘면 띠가 아래로 내려간다. 띠 위 n번 칸(정수, 음수도 있다)의 각도 θ = (s − n)·P ÷ Rc, 화면 y = 창 중심 y + Rc·sinθ, 세로 배율 cosθ, 가로 배율 0.90 + 0.10·cosθ(원통 가장자리로 돌아갈수록 글자가 조금 좁아진다 — 정면 칸은 1.00 그대로). 창 밖은 창 모양(둥근 사각형)으로 잘라 낸다.
- 그리는 칸은 잉크가 창에 걸리는 칸뿐이다: |θ| < π/2 이고 Rc·|sinθ| − 0.50·fs·cosθ < rh/2 인 칸만 그린다(지금 |s − n| < 0.894 — 한 샘플에 많아야 2칸). 창 밖에서 잘려 나갈 칸까지 그리면 최고 속도에서 글자 그리기가 3분의 1 늘어 약한 기기에서 프레임이 빠진다.
확인용: 멈춰 있을 때 이웃 칸은 θ = 1.213(69.5°)이라 잉크 윗끝이 창 중심에서 216px — 창 반높이 182px 밖이라 안 보인다. 돌 때는 창 안에 약 1.2칸이 보이고, 창 가장자리 글자는 세로 0.75배·가로 0.97배로 눌린다.
- 글자는 칸 중심에 그 음절의 잉크 중심이 오게 그린다: 음절마다 measureText(음절) 의 (actualBoundingBoxAscent − actualBoundingBoxDescent) ÷ 2 를 세로 보정값으로 구해 textBaseline 'alphabetic' 에 더한다. 보정값은 glyph(k, n) 과 함께 캐시한다. 「가」 하나로 모든 음절을 보정하거나 textBaseline 'middle' 에 맡기지 않는다 — 받침도 아래로 뻗는 획도 없는 「오」 같은 음절이 7px 위로 뜬다. 확인용: 멈춘 정답 8자의 잉크 중심이 모두 창 중심 ±2px 안.
- 띠 위 글자 glyph(k, n): n = N_k(아래) 한 칸만 그 릴의 정답 음절이다. 나머지 모든 칸(n = 0 과 음수 포함)은 시드 (seed·1000003 + k·10007 + n·131) >>> 0 으로 만든 mulberry32 의 첫 세 값 r1·r2·r3 로 고른 음절이다.
초성 = [0,2,3,5,6,7,9,11,12,14,15,16,17,18][floor(r1·14)] (ㄱㄴㄷㄹㅁㅂㅅㅇㅈㅊㅋㅌㅍㅎ)
중성 = [0,1,2,4,5,6,8,12,13,18,20][floor(r2·11)] (ㅏㅐㅑㅓㅔㅕㅗㅛㅜㅡㅣ)
받침 = r3 < 0.35 이면 [1,4,8,16,21][floor(r3 ÷ 0.35 · 5)] (ㄱㄴㄹㅁㅇ), 아니면 0
음절 = String.fromCharCode(0xAC00 + (초성·21 + 중성)·28 + 받침). 정답 음절과 같으면 중성을 목록의 다음 것으로 바꾼다. 같은 (k, n) 은 언제나 같은 글자다(Map 에 캐시).
- 원통 음영(빛은 서 있고 띠만 돈다): 띠를 다 그린 위에 창 크기의 음영 판을 덮는다. 창 높이 방향 선형 그라디언트 — 위끝 rgba(40,8,12,0.55) → 26% 지점 0 → 74% 지점 0 → 아래끝 0.55(검정이 아니라 따뜻한 먹갈색이라 상아 원통이 회색으로 죽지 않는다). 그 위에 하이라이트 띠 — 위에서 14% 지점 rgba(255,252,240,0) → 24% 지점 0.28 → 34% 지점 0. 마지막으로 창 가장자리 안쪽에 0.002W 선 rgba(42,10,16,0.9)(판이 창을 파고든 홈). 이 음영 판은 띠의 위치와 무관하게 창에 고정이다.
- 회전 흐림(세로로만, 반 프레임 셔터): 창마다 작은 화면 밖 캔버스 두 장(샘플용·누적용, 창 크기)을 쓴다. 반 프레임(1/120초) 동안 움직인 거리 d = |s_k(t) − s_k(t − 1/120)|·P (px). 샘플 수 K = min(12, max(1, ceil(d ÷ 4))).
j = 0..K−1 마다 시각 t_j = t − j ÷ (120·K) 의 띠를 샘플 캔버스에 그린다(먼저 그 시각의 띠 색으로 전부 칠하고 글자를 그린다). 그 j번째 장을 누적 캔버스에 globalAlpha = 1 ÷ (j+1) 로 덮어 그리면 정확한 평균이 된다. 누적 캔버스를 창 자리에 그린다.
확인용: 최고 속도 18칸/초에서 d ≈ 50px → K = 12(샘플 간격 약 4px), 6칸/초에서 d ≈ 17px → K = 5, 2칸/초에서 d ≈ 5.5px → K = 2, 멈추면 K = 1 이라 글자가 칼같이 선명하다. 최고 속도에서도 음절의 꼴이 반쯤 읽혀야 한다(「글자가 돌고 있다」가 보이는 것이 슬롯이다).
[릴의 움직임 — 전부 닫힌 식]
- 릴 번호 k = 0..M−1 은 읽는 순서(1줄 왼쪽부터, 그다음 줄). r(k) = 그 릴의 줄 번호(0부터).
- 멈춤 시각 hit_k = 1.50 + 0.28·k + 0.12·r(k), 마지막 릴만 여기에 0.50 을 더한다.
지금: 1.50 · 1.78 · 2.06 · 2.46 · 2.74 · 3.02 · 3.42 · 4.20(마지막 「음」).
문장이 길어 마지막 멈춤이 4.20 을 넘으면 모든 hit_k 를 1.50 + (hit_k − 1.50)·2.70 ÷ (hit_마지막 − 1.50) 로 줄인다.
- 상수: 예비 당김 A0 = 0.10칸 · 예비 시간 Ta = 0.20초 · 릴 사이 출발 시차 0.025초 · 가속 시간 Tup = 0.45초 · 기준 최고 속도 V0 = 18칸/초 · 멈출 때 속도 vh = 6칸/초(마지막 릴만 vhLast = 2.0칸/초) · 감속 시간 Td = 0.75초(마지막 릴만 1.30초) · 반동 ω = 28, 감쇠 c = 12. 아래 식의 v 는 그 릴의 멈출 때 속도(vh 또는 vhLast)다.
- 시간 u: 릴 식은 「다음 바퀴 기준 시간」 u 로 쓴다. t < 6.55 이면 u = t, t ≥ 6.55(기계 복귀 시작)이면 u = t − 8(8 = 한 바퀴 길이). 그래서 한 바퀴 끝의 레버 당김·가속이 다음 바퀴 0초의 회전으로 그대로 이어진다.
- 릴 k 의 구간(u 기준): a = −0.85 + 0.025·k, tB = a + Ta, tC = tB + Tup, tD = hit_k − Td, Tc = tD − tC.
1) u < a: s = 0 (무작위 음절 n = 0 이 창 가운데에 멈춰 있다 — 기계가 돌아온 직후의 모습)
2) a ≤ u < tB, x = (u − a)/Ta: s = −A0·(1 − cos(πx))/2 — 띠가 위로 0.10칸(33px) 당겨졌다가 멈춘다(레버를 당기는 예비 동작).
3) tB ≤ u < tC, x = (u − tB)/Tup: s = −A0 + V·Tup·x²/2 — 속도가 0 에서 V 까지 곧게 오른다.
4) tC ≤ u < tD: s = −A0 + V·Tup/2 + V·(u − tC) — 등속 회전. 모든 릴의 tC 가 0 보다 앞이라(릴 7 도 −0.025) 0초에는 8릴이 전부 최고 속도로 돌고 있다.
5) tD ≤ u < hit_k, x = (u − tD)/Td: s = −A0 + V·Tup/2 + V·Tc + Td·( v·x + (V − v)·(1 − (1−x)³)/3 ) — 속도가 v + (V − v)·(1−x)² 꼴로 처음엔 빠르게, 끝에선 천천히 줄어 정확히 v 로 정답 칸에 닿는다.
6) u ≥ hit_k, τ = u − hit_k: s = N_k + (v/ω)·e^(−cτ)·sin(ωτ) — 「탁」. τ=0 에서 위치도 속도(v)도 이어진다.
- N_k 와 V(릴마다 다르다): 먼저 V0 로 5)의 끝 위치 S = −A0 + V0·(Tup/2 + Tc + Td/3) + v·Td·2/3 을 계산해 N_k = round(S). 그다음 V = (N_k + A0 − v·Td·2/3) ÷ (Tup/2 + Tc + Td/3). 이러면 hit_k 에 s 가 정확히 정수 N_k 가 된다.
지금 값: N = 29 · 33 · 38 · 44 · 49 · 54 · 60 · 66, V = 18.32 · 17.92 · 18.14 · 17.79 · 17.97 · 18.12 · 17.87 · 17.96 (V0 와 2% 이내).
- 반동 확인용(보통 릴): 멈춘 뒤 τ ≈ 0.042초에 정답 칸을 0.12칸(약 40px) 아래로 지나쳤다가, τ ≈ 0.154초에 약 10px 위로 되돌아가고, 0.31초 뒤 1px 안에 든다. 마지막 릴은 v 가 2.0 이라 지나침이 약 13px, 0.21초 뒤 1px 안.
- 덜컥임: 릴 k 의 창 전체(링·띠·글자·음영)를 τ ≥ 0 동안 아래로 J·e^(−18τ)·sin(45τ) 만큼 옮긴다. 보통 릴 J = 0.006H(최대 약 6.6px), 마지막 릴 J = 0.012H(최대 약 13.3px) — 반동이 작아진 만큼 덜컥임으로 마지막 「탁」을 세운다. 최대는 τ ≈ 0.026초. 판은 움직이지 않는다.
- 마지막 릴은 감속 1.30초 · 도착 속도 2.0칸/초라 마지막 1초 동안 약 5.2칸, 마지막 0.5초 동안 약 1.4칸만 지나간다. 정답 「음」이 창 위끝에 나타나 멈추기까지 약 0.36초(22프레임) 동안 천천히 내려온다(보통 릴은 0.14초). 다른 일곱 칸이 다 멈춘 화면에서 혼자 기어 오는 것이 이 작품의 긴장이다.
[판·테두리·빛]
- 에나멜 판: 화면 전체에 세로 선형 그라디언트 #3A6158(0) → #355850(0.55) → #26403A(1). 그 위에 천장 조명 반사 하나 — 방사형 그라디언트 중심 (0.5W, −0.05H), 반지름 0.55H, rgba(255,255,255,0.12) → 0. 판은 움직이지 않는다(돌고 멈추는 것은 기계 안의 릴뿐이라는 대비).
- 크롬 링: 링 모양(바깥 둥근 사각형에서 창 둥근 사각형을 evenodd 로 뺀 것)을 링 높이 방향 선형 그라디언트로 칠한다. 크롬 스톱 0 #F7F4EE · 0.12 #D9D4CD · 0.48 #9A949C · 0.52 #5E5862 · 0.88 #BDB7B1 · 1 #EFEAE2 — 가운데의 어두운 수평선이 금속으로 읽히게 하는 핵심이다. 링 바깥 가장자리에 0.0015W 선 #1A2B27(판에 박힌 자리 — 판 아래끝보다 어두운 같은 색상이라 테가 따로 뜨지 않는다).
- 금 링: 같은 모양, 금 스톱 0 #FFF2C8 · 0.12 #F6D27A · 0.48 #C8952A · 0.52 #8A5C12 · 0.88 #E6B552 · 1 #FFE7A6.
- 「탁」 반짝임: 릴 k 가 멈춘 뒤(τ = u − hit_k, 0 ≤ τ < 0.35) 그 링 위에 흰 링(#FFFFFF, 같은 모양)을 불투명도 F·e^(−16τ) 로 덮는다. 보통 릴 F = 0.55, 마지막 릴 F = 0.85. 확인용: τ = 0.1초에 보통 0.11 — 0.3초 안에 사라진다. 링에만, 창 안 띠에는 덮지 않는다.
- 줄 점등: 줄 r 의 점등 시각 T_r = 그 줄 마지막 릴의 hit(지금 2.06 · 3.02 · 4.20). lit_r(t) = t ≥ 6.55 이면 0, 아니면 x = clamp((t − T_r) ÷ 0.15, 0, 1) 의 smoothstep x²(3 − 2x). 그 줄 세 창의 띠 색 = 꺼진 띠와 켜진 띠의 lit_r 보간, 금 링을 크롬 링 위에 globalAlpha = lit_r 로 덮고, 원통 음영 판은 globalAlpha = 1 − 0.25·lit_r 로 그린다(불이 들어오면 원통 그늘이 옅어진다). 0초 화면과 복귀한 기계는 lit = 0(불 꺼진 크롬)이다.
- 마지막 빛줄기(보상 한 방): 4.24~4.84초에만. 방향 n = (cos20°, sin20°) = (0.940, 0.342). D(t) = −1.00W + 2.00W·(t − 4.24) ÷ 0.60(등속, 초당 약 3,600px = 60fps 에서 프레임당 약 60px). 띠의 중심점 P = (0.5W, 0.47H) + D·n. createLinearGradient(P − 0.20W·n, P + 0.20W·n) 에 색 #FFF6E8 불투명도 스톱 0 → 0 · 0.25 → 0.06 · 0.42 → 0.24 · 0.50 → 0.40 · 0.58 → 0.24 · 0.75 → 0.06 · 1 → 0 을 주고 화면 전체를 fillRect(합성은 기본 source-over) 한 번. 줄기는 오른쪽 위에서 왼쪽 아래로 기운 「/」 모양으로, 왼쪽 위 판에서 시작해 오른쪽 아래로 빠져나간다. 확인용: 밝은 심(불투명도 0.24 이상)이 약 0.064W(69px) 폭이라 폰 폭에서도 한 줄 빛으로 읽힌다. 「오」 창을 약 4.41초, 「음」 창을 약 4.63초에 지나 — 문장을 읽는 순서대로 빛이 훑는다. 4.84초에는 화면 밖이고, 그때 벗겨짐 경계는 아직 x ≈ 0.03W 라 둘이 겹치지 않는다.
- 비용 상한: 판(화면 크기 1장)·크롬 링·금 링·흰 링·원통 음영 판(창 크기 4장)은 시작할 때(그리고 캔버스 크기가 바뀔 때) 화면 밖 캔버스에 한 번 그려 두고 매 프레임 drawImage 만 한다 — 창은 모두 같은 크기라 한 장씩이면 된다. 매 프레임 새로 만드는 그라디언트는 빛줄기 1개(0.60초 동안만)뿐이다.
[장면]
시간표(초). 모든 시각은 한 바퀴 8초 안의 t 다.
- 0.00초: 비취 에나멜 판 위 크롬 창 8개가 전부 최고 속도로 돌며 무작위 음절이 세로로 쏟아진다. 정답은 아직 어디에도 없다. 첫 프레임부터 화면이 재질과 움직임으로 꽉 차 있다.
- 0.75초부터 릴 0 이 감속하며 무작위 음절이 하나씩 읽히기 시작한다(감속은 멈춤 0.75초 전부터).
- 1.50~4.20초 시그니처: 왼쪽 위 「오」(1.50)부터 0.28초 간격으로 「탁·탁·탁」 — 멈출 때마다 덜컥임과 링 반짝임. 줄이 바뀔 때 0.12초 더 쉰다. 3.42초 「맑」이 멈춘 뒤 「음」 혼자 0.78초 더 돌며 점점 느려지다 4.20초에 마지막 「탁」. 이 구간 내내 창은 화면 폭의 90%, 높이 0.15H~0.79H 를 덮고 있다.
- 줄 점등: 2.06초 1줄, 3.02초 2줄, 4.20초 3줄 — 그 줄 세 창의 띠가 밝아지고 링이 금으로 0.15초에 바뀐다. 6.55초까지 유지.
- 4.24~4.84초 빛줄기: 사선 빛 한 줄이 화면 전체를 0.60초에 쓸고 지나간다. 녹청 에나멜과 금 링 위에서 가장 또렷하고, 상아 원통 위에서는 옅다.
- 4.70~5.70초 벗겨짐: 세로 경계선 x_w = −0.02W + 1.04W·easeInOutSine(u), easeInOutSine(u) = (1 − cos πu)/2, 가 왼쪽에서 오른쪽으로 1.00초 동안 화면을 지나간다. 경계 왼쪽은 「맨 문장」(에나멜 판 위에 정답 음절만 띠 글자 자리 그대로 #FBF3E1 로, 띠·음영·링 없음), 오른쪽은 기계 그대로. 경계에 #FFC94A 세로선(폭 0.004W, 줄 덩어리 위끝 −0.02H 부터 아래끝 +0.008H 까지 — 0.80H 를 넘지 않게) 하나. 경계가 글자를 지나가는 동안 글자 반쪽은 판 위의 밝은 글자, 반쪽은 띠 위 어두운 글자로 보인다 — 확인용: 경계가 글자 하나를 가르는 시간은 열마다 7~10프레임(약 0.12~0.17초).
- 5.70~6.55초 맨 문장 머묾: 녹청 에나멜 위 상아색 문장 세 줄.
- 6.55~7.15초 기계 복귀: 같은 경계선이 다시 왼쪽에서 오른쪽으로 0.60초 easeInOutCubic 으로 지나가며 이번엔 경계 왼쪽이 기계, 오른쪽이 맨 문장. 복귀한 기계는 정답이 아니라 무작위 음절(n = 0)을 창 가운데에 단 채 멈춰 있고, 불 꺼진 크롬·꺼진 띠다. 문장이 무작위 음절로 「섞이며」 덮인다.
- 7.15~7.525초 예비 동작: 릴 0 부터 0.025초 간격으로 차례로 위로 0.10칸 당겨졌다가 아래로 돌기 시작한다(왼쪽에서 오른쪽으로 번지는 물결).
- 7.35~7.975초 가속(릴마다 0.45초), 그 뒤 최고 속도. 8.00초 화면 = 0.00초 화면이다(u 가 같은 식을 이어 쓰므로 위치도 속도도 끊기지 않는다).
[합격 기준 — 시작할 때 콘솔 대조표]
시작할 때 아래 표를 console.table 로 한 번 출력하고, 줄마다 PASS/FAIL 을 적는다. 기본 문장·기본 색에서는 전부 PASS 가 정상이다. FAIL 줄이 하나라도 있으면 그 줄을 console.warn 으로 다시 알린다(그때는 식이나 색을 다시 본다).
- 릴 8줄: k · hit_k · N_k · V · s(hit_k) — |s(hit_k) − N_k| < 1e−6 이면 PASS. 기대 N·V 는 위 「지금 값」.
- 첫 화면: t = 0 에서 정답 칸이 창에 걸린 릴 수(|s_k(0) − N_k| < 0.894 인 k 의 수) — 0 이면 PASS.
- 이음매: 8릴 중 최대 |s_k(8 − 0.0001) − s_k(0.0001)| — 0.01 미만이면 PASS(기대 약 0.004칸).
- 판 채도: 판 세 스톱의 OKLCH C(sRGB → 선형 → OKLab 표준 변환, C = √(a² + b²)) — 모두 0.08 이하면 PASS(기대 0.047 · 0.043 · 0.034).
- 글자 대비(WCAG 상대 휘도 공식으로 CONFIG 의 hex 에서 계산, 본문 글자 대비 4.5:1 이상이면 PASS):
띠 글자 #1C1416 / 꺼진 띠 #E4DAC4 → 기대 13.0
띠 글자 / 켜진 띠 #FFF9EA → 기대 17.2
하이라이트 띠 최고점(글자·띠 둘 다 #FFFCF0 을 28% 섞은 색) → 기대 5.8
맨 문장 #FBF3E1 / 판의 가장 밝은 색(위끝 #3A6158 에 흰색 12% 를 섞은 #52746C, 보수적으로 잡은 값) → 기대 4.7
빛줄기는 한 글자 위를 0.1초 남짓 지나가는 순간의 빛이라 이 표에서 뺀다(빛줄기가 지나갈 때는 세 줄 모두 켜져 있어, 최고점에서도 켜진 띠 위 약 4.7:1).
[구현]
- 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)은 스크립트 맨 끝에서 한 번만 호출한다. 정의되기 전에 쓰면 첫 화면에서 멈춘다.
- 이 작품 전용: s_k(t) 는 위 u 변환과 1)~6) 식 그대로의 함수 reelPos(k, t) 하나로 만들고, 흐림 샘플도 같은 함수를 다른 t 로 부른다(샘플 시각이 0 아래로 내려가면 t + 8 로 돌려서 부른다). 릴별 N_k·V·구간 시각은 시작할 때 한 번 계산해 둔다. 페이지 바깥 남는 곳의 배경색은 판 가운데색 #355850.
- 그리는 순서(매 프레임): 에나멜 판 → 창마다(덜컥임 적용) 크롬 링 → 금 링(lit) → 흰 링(반짝임) → 창 모양 clip 안에 누적 캔버스 → 원통 음영 판 → (벗겨짐·복귀 구간이면) 경계 오른쪽/왼쪽을 사각형 clip 으로 나눠 기계와 맨 문장을 각각 그림 → 경계선 → (4.24~4.84초면) 빛줄기.
- CONFIG.timing 에 이름 붙여 모은다: hitStart 1.50 · hitGap 0.28 · lineGap 0.12 · lastExtra 0.50 · lastDeadline 4.20 · preStart −0.85(= 7.15) / anticip 0.20 / stagger 0.025 · spinUp 0.45 · decel 0.75 / decelLast 1.30 · vh 6 / vhLast 2 · flashDecay 16 / flashDur 0.35 · litRamp 0.15 · glint[4.24,4.84] · peelOut[4.70,5.70] · peelIn[6.55,7.15] · loop 8.00.
[하지 말 것]
- 0초 화면에 정답 문장을 두지 않는다(띠의 n = 0 칸에 정답 넣기, 0초에 릴을 멈춰 두기) — 녹화는 0초부터 시작하므로 영상의 첫 장면이 결말이 된다. 첫 프레임은 8릴이 전부 도는 화면이다.
- 마지막 릴을 다른 릴과 같은 속도로 도착시키지 않는다 — 정답이 0.1초만 보여 다가오는 긴장이 생기지 않는다. 마지막 릴만 vhLast = 2.0.
- 속도를 프레임마다 깎는 방식(v *= 0.95 같은)으로 감속하지 않는다 — 정답 칸에 정확히 서지 않고, 멈추는 시각이 기기의 프레임률에 따라 달라진다. 멈출 자리와 시각은 위 닫힌 식만이 정한다.
- 정답 음절을 멈춘 뒤에 덮어 그리거나 크로스페이드로 바꿔 끼우지 않는다 — 「탁」은 정답 칸이 실제로 굴러 들어와 지나쳤다가 돌아오는 순간이어야 하고, 바꿔 끼우면 한 프레임 튐이 보인다.
- 굴러가는 글자를 프레임마다 새로 뽑지 않는다(매 프레임 Math.random, 시간으로 시드 바꾸기 등) — 같은 칸의 글자가 바뀌면 릴이 아니라 깜빡이는 소음이 된다. 글자는 띠의 칸 번호에 묶여 있다.
- 흐림을 ctx.filter 의 blur 나 그림자로 내지 않는다 — 사방으로 번져 세로 회전 느낌이 사라지고, 멈춘 글자까지 뿌옇게 된다. 흐림은 시간 샘플 평균으로만.
- 흐림을 한 프레임 전체(1/60초)로 넓히거나 샘플 간격을 8px 로 벌리지 않는다 — 최고 속도에서 창 안이 계단진 줄무늬 덩어리가 되어 「글자 슬롯」이 아니라 「잡음」이 된다. 반 프레임·4px 간격.
- 흐림 샘플을 globalAlpha = 1/K 고정으로 겹치지 않는다 — 먼저 그린 장이 더 진하게 남아 평균이 아니라 잔상이 된다. j번째 장은 1/(j+1).
- 창을 키우거나 칸 간격을 줄여 멈춘 뒤 이웃 칸이 보이게 하지 않는다 — 문장 위아래로 다른 글자 조각이 비치면 문장이 안 읽힌다. 같은 이유로 예비 당김은 0.10칸을 넘기지 않는다(0.20칸이면 아래 이웃 칸이 22px 비친다).
- 원통 음영·하이라이트 띠를 띠와 함께 움직이지 않는다 — 빛은 창에 고정이고 띠만 돈다. 같이 움직이면 원통이 아니라 그림이 흐르는 평면이 된다.
- 크롬을 회색 단색 테두리로 단순화하지 않는다 — 가운데의 어두운 수평선(0.48~0.52 스톱)이 있어야 금속으로 읽힌다. 같은 이유로 금 링도 노란 단색이 아니라 스톱 그대로.
- 레버·동전 투입구·기계 몸체 윤곽·전구 테두리 그림을 더하지 않는다 — 기계는 「화면 전체를 채운 에나멜 판에 뚫린 크롬 창」만으로 읽힌다. 더하면 그만큼 글자가 작아지고, 주인공이 문장에서 장난감으로 옮겨 간다.
- 「잭팟」 「777」 같은 글자, 과일·숫자 기호, 동전·별·반짝이·꽃가루 같은 입자, 깜빡이는 전구·반복 점멸, 화면 전체 흔들림, 네온 발광, 글자 그림자를 넣지 않는다 — 도박 화면처럼 싸 보이고, 입자는 막 완성된 글자를 가린다. 보상은 광택 면 위를 지나가는 빛줄기 한 번으로만 낸다(실제 에나멜과 금속이 빛을 받는 방식이라 싸지 않다). 빛은 링 반짝임(멈출 때 한 번)·줄 점등(한 번 켜져 유지)·마지막 빛줄기 한 번, 이 셋뿐이다.
- 판(바탕)을 채도 높은 원색(체리 빨강·코발트·형광 초록 등, OKLCH C 0.08 초과)으로 칠하지 않는다 — 판은 화면의 절반 이상이라 바탕이 소리치면 금 점등·빛줄기가 묻히고 화면이 경고 화면처럼 읽힌다. 그렇다고 남청·검정·크림 기본값으로 도망가지도 않는다 — 재질(광택·반사)과 중간 명도로 판을 세운다.
- 빛줄기를 'lighter'·'screen' 합성으로 그리거나 두 줄 이상 띄우거나 불투명도를 0.40 넘게 올리지 않는다 — 상아 띠가 하얗게 타서 글자가 사라진다. 한 줄, 최고 0.40, source-over.
- 판에 결·노이즈·무늬를 매 프레임 새로 그리거나 판을 움직이지 않는다 — 판은 시작할 때 한 번 그린 정지 면이다. 매 프레임 그리면 흐림 샘플과 겹쳐 약한 기기에서 프레임이 빠지고, 판이 움직이면 「도는 것은 릴뿐」이라는 대비가 사라진다.
- 링·창의 그림자를 ctx.shadowBlur 로 내지 않는다 — 매 프레임 8번 흐림 계산이 비싸다. 깊이는 링의 그라디언트·바깥 선·창 안쪽 홈 선으로 낸다.
- 아래 0.80H~1.00H 빈 곳을 장식으로 채우지 않는다 — 쇼츠·릴스에서 화면 아래 UI 가 덮는 자리다(빛줄기가 잠깐 지나가는 것은 장식이 아니다).
- 소리를 넣지 않는다 — 미리보기 자동 재생에서 막히고, 녹화에도 담기지 않는다.
- 3D 라이브러리나 WebGL 을 쓰지 않는다 — 원통은 위 sin·cos 배율로 충분하고, 외부 의존이 생긴다.
사용 방법
필요한 것: 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
추가 요청 예시
결과가 나온 뒤 같은 대화에서 이어서 요청하면 해당 부분만 수정됩니다.
- 문장을 「이번 주 할 일은 산책」으로 바꿔서 한 줄에 한 단어씩 네 줄로 나오게 하고, 칸 크기는 프롬프트의 크기 규칙대로 다시 맞춰 줘.
- 판 색을 비취 초록 대신 차분한 로즈우드 갈색(위 #74504C → 가운데 #644541 → 아래 #4A3330)으로 바꾸고, 칸 테두리 링의 바깥선도 판 아래쪽보다 어두운 같은 계열 #2E1F1D 로 맞춰 줘. 바꾼 뒤에도 프롬프트의 채도·대비 기준을 모두 통과하는지 확인해 줘.
- 글자가 하나씩 멈추는 간격을 0.28초에서 0.40초로 늘려 한 칸씩 더 뜸을 들이게 해 줘. 마지막 글자가 멈추는 시한(4.20초)과 그 뒤 빛줄기·벗겨짐·되돌아감·레버 당김 시각은 전부 0.9초씩 미뤄서 전체 8.9초로.
한글