버스 뭉침 쇼츠
0.4초 늦은 버스 하나 때문에 7초 만에 두 대가 붙는 20초 시뮬레이션.
설정값 미리보기
바꾼 문구·색으로 다시 재생합니다
프롬프트 설정
Claude 가 읽는 제작 지시문입니다. 직접 읽지 않아도 됩니다.
9:16 세로, 20초짜리 쇼츠용 설명 영상을 HTML 파일 하나로 만들어 줘. 「버스는 왜 두 대씩 붙어서 올까」를 위에서 내려다본 둥근 순환 노선 하나로 답한다. 똑같은 버스 4대를 똑같은 간격으로 내보냈는데, 한 대가 0.4초 늦은 것만으로 — 늦은 버스는 태울 승객이 쌓여 더 늦어지고, 뒤 버스는 손님이 적어 더 빨리 따라붙어 — 7초 만에 두 대가 붙는다. 시뮬레이션은 진짜로 돈다(정해진 규칙 한 줄로).
핵심 아이디어: 아무도 실수하지 않았는데 저절로 붙는다는 것을 눈으로 본다. 늦은 버스 하나에만 강조색 테두리를 둘러 관객이 그 버스를 따라가게 하고, 정류장에 쌓이는 승객 점의 개수가 이유를 말한다. 뒤 장면에서는 「계획대로라면 있어야 할 자리」(흐린 유령 버스 4대)가 함께 돌아, 실제 버스가 그 자리에서 얼마나 벗어났는지가 보인다. 글은 블로그 글의 문장이다.
쓰임: 유튜브 쇼츠·인스타 릴스. 소리는 따로 입힌다 — 소리 날 자리를 목록(CUES)으로 내놓는다.
[바꿀 곳]
- Q = ["버스는 왜", "두 대씩 붙어서 올까"]
- CAP_A = ["늦은 버스는 태울 승객이 쌓여", "더 늦어진다"] · CAP_B = ["뒤 버스는 손님이 적어", "더 빨리 따라붙는다"]
- BIG = "두 대씩 붙어 온다"
- CAP_C = ["완벽한 등간격으로 내보내도", "뭉침은 똑같이 일어난다"]
- END = ["작은 어긋남이", "스스로 커진다"] (둘째 줄만 강조색)
- TITLE = ["버스 뭉침은", "우연이 아니다"] · BRAND = "내 채널"
- LATE_LABEL = "늦은 버스" · BACK_LABEL = "뒤 버스"
- PALETTE: BG #E6EAE6(바탕 — 아주 옅은 회녹색) · INK #161A18(글·버스) · ROAD #C9D1CB(노선 띠) · STOP #8E9891(정류장 표시) · RIDER #5F6A63(승객 점) · ME #F2A93B(늦은 버스 테두리·강조 낱말·로고 위 점 — 이 색은 이 셋에만) · GHOST #AEB8B1(유령 버스)
[크기]
- 기준 화면 1080×1920, 30fps, DURATION = 20.0초(프레임 0~599).
- 쇼츠·릴스 화면의 위·아래는 앱 단추가 덮는다: 글은 y 230~1500, x 72~1008 안. 왼쪽 정렬, 왼쪽 경계 x 72(가운데 정렬은 끝 카드뿐).
- 글 크기: Q 104px 굵기 800(기준선 y 330·450) · CAP 두 줄 60px 굵기 800 INK(기준선 y 320·405) · BIG 96px 굵기 800(기준선 y 380) · END 첫 줄 72px 굵기 800 INK · 둘째 줄 96px 굵기 800 ME(기준선 y 340·470) · 이름표 44px 굵기 800. 어느 글도 44px 아래로 줄이지 않는다. 줄이 x 1008 을 넘으면 그 줄 크기만 비율대로 줄인다(44px 아래로는 안 줄인다).
- 노선: 가운데 (540, 1080), 반지름 R = 300 의 원. 노선 띠 = ROAD 색 선 굵기 44, 끝 없이 이어진 고리. 각도 θ(도)는 위쪽이 0, 시계 방향으로 커진다: x = 540 + R·sin θ, y = 1080 − R·cos θ. 버스는 시계 방향으로 달린다.
- 정류장 8개: θ = 0, 45, 90, …, 315. 정류장 표시 = 노선 바깥쪽 가장자리에 붙은 STOP 색 둥근 막대 14×36(노선에 수직).
- 버스 = INK 둥근 사각 72×40(둥근 12), 노선 위 진행 방향으로 놓인다(긴 변이 원의 접선). 버스 앞 유리 표시로 앞쪽 끝에 BG 색 6×28 띠 하나.
- 승객 = RIDER 원 지름 22, 정류장마다 노선 바깥쪽으로 한 줄로 쌓인다(정류장 표시 바깥 끝에서 8px 띄우고 점 사이 6px, 노선의 바깥 방향). 표시하는 개수 = floor(대기 w + 0.5), 최대 8개.
- 선은 2px 이상, 강조 테두리 3px 이상(늦은 버스 테두리 = ME 5px, 버스 바깥 4px 띄워 둥근 사각).
[시뮬레이션] — 시작할 때 한 번 미리 계산해 표로 두고, render(t) 는 표를 읽어 그린다(프레임 사이에 쌓이는 상태 없음). 아래 규칙을 이 순서 그대로 구현한다(순서가 바뀌면 붙는 시각이 달라진다).
- 버스 4대, 처음 각도 θ_i = 90·i (i = 0,1,2,3). 버스 i 의 앞차는 버스 i+1(3번의 앞차는 0번). 처음 다음 정류장 = 지금 자리 바로 다음 정류장.
- 상수: 빠르기 ω = 60°/초 · 승객이 정류장에 오는 빠르기 λ = 1.5명/초 · 한 명 태우는 시간 b = 0.15초 · 늦음 NUDGE = 0.4초(버스 1 만, 시뮬레이션 시작 때 제자리에서 기다림) · 최소 간격 13°(앞차 꽁무니).
- 정류장 처음 대기 w_j = λ × (정류장 j 에서 시계 반대 방향으로 가장 가까운 버스까지의 각거리 ÷ ω).
- 시뮬레이션 시간 s 를 dt = 1/120 초씩 0 부터 15.0 초까지:
1) 모든 정류장 w_j += λ·dt.
2) 버스 i = 0,1,2,3 차례로: 기다림(NUDGE)이 남았으면 줄이고 넘어간다. 정차 중이면 그 정류장 w_j −= dt / b 하고, w_j ≤ 0 이면 w_j = 0 으로 두고 출발 상태로 바꾼 뒤 넘어간다. 달리는 중이면 앞차까지 각거리 − ω·dt < 13 이면 그 자리에서 기다리고 넘어간다(겹쳐 지나가지 않는다). 아니면 θ_i += ω·dt, 그 걸음에 다음 정류장을 지났으면 θ_i = 그 정류장 각도로 맞추고 정차 상태로, 다음 정류장 번호를 하나 올린다.
3) 이 걸음의 θ·정차 여부·w 를 표에 적는다.
- 영상 시각 t 와의 관계: s = t − 3.0. t < 3.0 이면 s = 0 상태(처음 배치, 멈춤). 표는 1/120 초 간격이므로 t 에서 가까운 두 칸을 각도 기준으로(360° 넘김 주의) 보간한다.
- 기대값(자기 대조에서 확인, 각도는 ±2°): 버스 0 → 버스 1 간격이 처음 14° 이하가 되는 s ≈ 7.18초(영상 10.18초). s = 6.0 에서 θ ≈ [235, 270, 45, 135] · 간격 ≈ [35, 135, 90, 100]. s = 10.0 에서 간격 ≈ [13.5, 164.5, 62, 120]. s = 14.0 에서 간격 ≈ [13.5, 218, 17.5, 111] (버스 2·3 도 붙어 간다). 정류장 대기 최대 ≈ 7명.
[장면] — 120BPM 박자 격자(1박 0.5초). 이징 셋: ENTER cubic-bezier(0.16,1,0.3,1) · EXIT (0.7,0,0.84,0) · MOVE (0.65,0,0.35,1).
장면 1 — 물음 (0.0~3.0초)
- 프레임 0: BG 만(반복 이음매).
- 0.1~0.6초: 노선 고리가 θ = 0 에서 시계 방향으로 한 바퀴 그려진다(선 그리기 MOVE 0.5초). 정류장 표시 8개가 0.4초부터 0.03초 간격으로 ENTER. 0.6초부터 버스 4대가 0.05초 간격으로 제자리에 ENTER(크기 0.9 → 1, 0.3초), 승객 점이 0.8초부터 정류장마다 아래 점부터 쌓인다(점마다 0.03초, ENTER).
- Q 두 줄을 0.3초부터 한 글자씩 친다([필름 문법]). 2.8초까지 그대로, 2.8~3.0초 위로 닫힌다.
- CUES: 0.1 swish, 글자마다 key(0.05초 안에 둘이면 하나로), 버스마다 pop.
장면 2 — 저절로 붙는다 (3.0~11.8초)
- 3.0초 시뮬레이션 시작(s = 0). 버스 1 둘레에 ME 테두리가 3.0~3.3초에 그려지고(둥근 사각 둘레 선 그리기), 버스 바로 바깥에 LATE_LABEL 이름표(ME 바탕 흰 글자 44px, 둥근 12, 버스를 따라다닌다 — 노선 바깥쪽 60px 띄움, 글자는 늘 똑바로).
- 3.3초 CAP_A 두 줄 층 시차 마스크 리빌(0.12초 간격). 6.2초까지(읽을 시간 2.9초). 6.2~6.4초 닫힘.
- 6.4초: 버스 0 바깥에 BACK_LABEL 이름표(INK 바탕 흰 글자, 같은 방식)가 붙는다. 6.6초 CAP_B 두 줄 리빌. 9.8초까지. 9.8~10.0초 닫힘.
- 10.18초(버스 0 이 버스 1 꽁무니에 닿는 순간): 두 버스를 함께 감싸는 ME 테두리 하나로 바뀐다(두 이름표는 EXIT 0.2초, 새 테두리는 두 버스를 감싸는 호 모양 둥근 띠로 선 그리기 0.3초). 10.25초 BIG 이 위쪽에 마스크 리빌(96px). 11.6초까지. 11.6~11.8초 닫힘.
- CUES: 버스가 정류장에 설 때마다 tick(0.05초 안에 둘 이상이면 하나로), 3.0 click, 10.18 thud, 10.25 pop.
장면 3 — 계획은 등간격이었다 (11.8~15.0초)
- 11.8초부터 유령 버스 4대가 나타난다(GHOST 색 테두리만 3px, 속 빔, 버스와 같은 크기): 처음 배치(90° 간격)에서 시뮬레이션 시작부터 평균 빠르기로 쉬지 않고 돌았다면 있을 자리 = θ_i(0) + ω_avg·s, ω_avg = 그때까지 버스 4대가 실제로 돈 각도의 평균 ÷ s(표에서 계산). 유령은 11.8~12.2초에 ENTER, 그 뒤 계속 돈다. 실제 버스와 유령 사이를 잇는 가는 선(2px GHOST)으로 「벗어난 거리」를 보인다.
- 12.0초 CAP_C 두 줄 리빌. 14.8초까지(읽을 시간 2.8초). 14.8~15.0초 닫힘.
- CUES: 11.8 swish.
장면 4 — 결론 (15.0~17.6초)
- 유령·잇는 선은 15.0~15.2초 EXIT. 시뮬레이션은 계속 돈다(s 12~14.6).
- 15.2초 END 두 줄 리빌(0.15초 간격). 둘째 줄 ME 96px. 17.4초까지.
- CUES: 15.2 pop, 15.35 thud(작게).
장면 5 — 끝 카드 (17.6~20.0초) — [필름 문법]의 끝 카드 서명. 로고 점이 되는 7개 = 그 순간 화면에 있는 승객 점 가운데 정류장 순서로 앞에서부터 7개(7개가 안 되면 버스 가운데 점으로 채운다). 나머지(노선·정류장·버스·END 글·남은 점)는 17.6~17.8초에 EXIT 로 닫힌다. 로고 → BRAND → TITLE(두 줄, 가운데 정렬). 19.5초까지 정지, 19.5~20.0초 닫혀 프레임 599 = 프레임 0.
- CUES: 17.7 whoosh, 점이 앉을 때마다 tick, 18.4 thud.
[필름 문법 — 모든 장면에 지킨다]
- 한 자리에 두 장면의 글이 동시에 있지 않는다. 다음 글이 들어오기 전에 앞 글은 다 닫혀 있다(닫힘 끝 → 다음 시작 사이 ≥ 0.05초).
- 장면 글은 INK(강조 낱말만 ME).
- 반투명으로 겹쳐 보이는 순간을 만들지 않는다(유령 버스는 테두리만이라 겹쳐도 된다).
- 카메라가 완전히 멈춘 장면은 없다: 장면마다 그림(노선·버스·점) 전체가 아주 느리게 다가온다(장면 길이 동안 크기 1.00 → 1.03, 노선 가운데 기준, 선형). 다음 장면은 1.00 으로 MOVE 0.3초 돌아간 뒤 다시 다가온다. 글은 이 확대에 넣지 않는다.
- 물음 글은 한 글자씩 친다(글자당 0.05초, 끝에 깜박이는 막대 커서 6px × 0.9em ME 색 — 칠 때는 켜진 채, 다 치면 0.5초 주기로 깜박이다 닫힐 때 같이 사라진다).
- 끝 카드 서명: 정한 점 7개가 화면 가운데(540, 860)로 날아와 반지름 46px 원 위에 고르게 앉아 로고 점이 된다(0.05초 간격, MOVE 0.45초, 점 지름은 앉으며 22·16 으로 번갈아, 위쪽 3개는 ME, 나머지 INK). 이어 로고 아래 가운데에 BRAND 56px 굵기 800 INK(기준선 y 1010) 마스크 리빌, 0.2초 뒤 TITLE 두 줄 64px 굵기 800 INK(기준선 y 1110·1190). 정지 ≥ 0.5초 뒤 모두 닫혀 BG.
[구현]
- 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)은 스크립트 맨 끝에서 한 번만 호출한다. 정의되기 전에 쓰면 첫 화면에서 멈춘다.
이 작품만의 구현:
- 첫 <script> 맨 앞에 CONFIG 객체를 두고 바꿀 곳 절과 이 글의 숫자(장면 시각표·시뮬레이션 상수·크기·색·이징)를 이름 붙여 모은다. 장면 시각은 CONFIG.T 표 하나에.
- 캔버스 하나(기준 1080×1920)에 그린다. render(t) 는 t 와 미리 계산한 표만으로 그린다. requestAnimationFrame 의 타임스탬프로 t = (경과 초) mod DURATION. setTimeout·setInterval·CSS 애니메이션으로 움직이지 않는다. 같은 t 는 언제나 같은 그림이다.
- window.renderAt = t => render(t), window.DURATION = 20 을 열어 둔다(renderAt 이 한 번이라도 불리면 재생 루프는 멈춘다 — 영상 굽기용).
- window.CUES = [{t: 초, kind: "swish"|"pop"|"tick"|"thud"|"whoosh"|"click"|"key"}] 를 시각 순으로(위 장면들이 말한 자리 전부, 표에서 계산). 소리는 이 파일에서 내지 않는다.
- 화면 맞추기: 창 크기에 맞춰 화면비를 지키며 가운데에 꽉 차게(남는 곳은 BG). devicePixelRatio 는 2 까지만. 창 크기가 0 이면 다음 프레임에 다시.
- 마스크 리빌 = 글 줄 상자로 클립하고 안의 글을 기준선 아래 1.1em 에서 0 으로(ENTER 0.45초). 불투명도는 움직임 길이의 55% 안에 0 → 1. 페이드만으로 나타나는 글은 없다. 닫힘은 거꾸로(위로 1.1em, EXIT 0.2초).
- 이징은 CONFIG.EASE 에 셋, 베지어는 뉴턴법 6회 + 이분법 20회, 입력 0·1 에서 정확히 0·1.
- 브라우저 저장소를 쓰지 않는다. 모든 함수·상수를 먼저 정의하고 시작은 맨 끝에서 한 번. 콘솔 오류 없이.
- 자기 대조(시작할 때 한 번, console.table · 어긋나면 console.warn, try/catch): 처음 붙는 s(기대 7.18 ± 0.1) · s = 6·10·14 의 간격(기대값 ± 2°) · 대기 최대 · renderAt(0) 이 BG 한 색 · 어느 시각이든 서로 다른 장면의 글이 같이 보이는 프레임 수 = 0(0.1초 간격으로 훑어 센다).
[하지 말 것] — 이유와 함께
- 남색·보라 바탕, 그라디언트, 발광, 입자, 반짝이 — AI 영상의 기본값이다. 이 영상은 지도 위 평면 그림처럼 깔끔하다.
- 버스에 얼굴·표정, 사람 모양 승객 — 점과 사각으로 충분하다.
- 글을 가운데 정렬로 쌓기 — 가운데는 끝 카드뿐이다.
- 모든 등장이 페이드+위로 20px, 모든 요소가 같은 이징으로 동시에 — 생성물의 표지다.
- 이 글에 없는 숫자·문장을 지어내지 않는다(블로그 글의 문장이다). 「배차 간격 몇 분」 같은 실제 시간 단위를 붙이지 않는다.
- 시뮬레이션 결과를 손으로 맞춘 경로로 바꾸지 않는다 — 위 규칙이 만드는 대로 붙어야 한다.
사용 방법
필요한 것: claude.ai 계정 — 무료 계정에서도 Claude Opus 5.5 를 고를 수 있습니다
- claude.ai 에서 새 대화를 엽니다.
- 모델 메뉴에서 Claude Opus 5.5 를 고르고, 같은 메뉴의 Effort(답변 깊이)를 Medium 로 둡니다.
- 복사한 프롬프트를 붙여 넣고 전송합니다. 결과로 나온 HTML 파일을 브라우저로 열면 재생됩니다.
- 권장 모델
- Claude Opus 5.5 · Effort Medium
- 화면비
- 9:16 (세로)
- 재생 길이
- 20초
- 결과물
- HTML 파일 하나 · 따로 설치할 것 없음
- 영상 파일
- 해당 없음
추가 요청 예시
결과가 나온 뒤 같은 대화에서 이어서 요청하면 해당 부분만 수정됩니다.
- 버스를 여섯 대로 늘리고 정류장을 열두 개로 해 줘. 붙는 시각을 자기 대조 표에 다시 적어 줘.
- 계획대로라면 있을 자리(유령 버스)를 더 진하게, 실제 버스와 잇는 선도 굵게 해 줘.
- 노선을 원 대신 둥근 직사각형 모양으로 바꿔 줘.
데이터