kaizen-agent 1.47.0 → 1.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -84
- package/cli/build-web.ts +4 -2
- package/cli/hermes.ts +65 -0
- package/cli/install.ts +24 -10
- package/cli/localsend.test.ts +84 -0
- package/cli/localsend.ts +131 -0
- package/cli/notes.test.ts +6 -4
- package/cli/settings.ts +1 -0
- package/cli/state.test.ts +37 -0
- package/cli/state.ts +35 -20
- package/cli/video.test.ts +479 -0
- package/cli/video.ts +507 -0
- package/cli/web.ts +127 -1
- package/cli/worktree.test.ts +3 -1
- package/package.json +1 -1
- package/skills/kaizen/config.default.yml +4 -0
- package/skills/media/kaizen-book/SKILL.md +107 -0
- package/skills/media/kaizen-book/assets/reader.html +440 -0
- package/skills/media/kaizen-book/assets/styles.json +45 -0
- package/skills/media/kaizen-book/examples/book.json +14 -0
- package/skills/media/kaizen-book/examples/images/reach.svg +1 -0
- package/skills/media/kaizen-book/examples/manuscript.md +127 -0
- package/skills/media/kaizen-book/references/interview.md +85 -0
- package/skills/media/kaizen-book/references/proofread.md +47 -0
- package/skills/media/kaizen-book/references/writing.md +53 -0
- package/skills/media/kaizen-book/scripts/build_book.py +279 -0
- package/skills/media/kaizen-book/scripts/check.sh +10 -0
- package/skills/media/kaizen-book/scripts/check_book.py +128 -0
- package/skills/media/kaizen-book/scripts/deploy.py +58 -0
- package/skills/media/kaizen-book/scripts/pdf.mjs +99 -0
- package/skills/media/kaizen-book/scripts/setup.sh +9 -0
- package/skills/media/kaizen-book/scripts/verify.mjs +95 -0
- package/skills/{story-deck → media/kaizen-deck}/SKILL.md +1 -1
- package/skills/{story-deck → media/kaizen-deck}/scripts/check.sh +1 -1
- package/skills/{story-deck → media/kaizen-deck}/scripts/install_fonts.py +2 -2
- package/skills/media/kaizen-video/SKILL.md +112 -0
- package/skills/media/kaizen-video/assets/CREDITS.txt +16 -0
- package/skills/media/kaizen-video/assets/film-fonts/Fraunces.ttf +0 -0
- package/skills/media/kaizen-video/assets/film-fonts/Inter.ttf +0 -0
- package/skills/media/kaizen-video/assets/film-fonts/PlayfairDisplay-Italic.ttf +0 -0
- package/skills/media/kaizen-video/assets/film-fonts/PlayfairDisplay.ttf +0 -0
- package/skills/media/kaizen-video/assets/fonts/Fredoka.ttf +0 -0
- package/skills/media/kaizen-video/assets/fonts/Montserrat.ttf +0 -0
- package/skills/media/kaizen-video/assets/fonts/SpecialElite.ttf +0 -0
- package/skills/media/kaizen-video/assets/music/investigations.mp3 +0 -0
- package/skills/media/kaizen-video/assets/music/lights.mp3 +0 -0
- package/skills/media/kaizen-video/assets/music/sunny.mp3 +0 -0
- package/skills/media/kaizen-video/examples/config.json +152 -0
- package/skills/media/kaizen-video/examples/sample-film/README.md +10 -0
- package/skills/media/kaizen-video/examples/sample-film/film.json +63 -0
- package/skills/media/kaizen-video/film.html +246 -0
- package/skills/media/kaizen-video/hyperframes.json +12 -0
- package/skills/media/kaizen-video/presets/educational-warning/CREDITS.txt +3 -0
- package/skills/media/kaizen-video/presets/educational-warning/README.md +26 -0
- package/skills/media/kaizen-video/presets/educational-warning/assets/sfx/ending.wav +0 -0
- package/skills/media/kaizen-video/presets/educational-warning/assets/sfx/reveal.wav +0 -0
- package/skills/media/kaizen-video/presets/educational-warning/assets/sfx/shutter.wav +0 -0
- package/skills/media/kaizen-video/presets/educational-warning/assets/sfx/stamp.wav +0 -0
- package/skills/media/kaizen-video/presets/educational-warning/index.html +236 -0
- package/skills/media/kaizen-video/presets/educational-warning/package.json +10 -0
- package/skills/media/kaizen-video/presets/film-cinematic-drama/CREDITS.txt +1 -0
- package/skills/media/kaizen-video/presets/film-cinematic-drama/README.md +2 -0
- package/skills/media/kaizen-video/presets/film-cinematic-drama/style.json +39 -0
- package/skills/media/kaizen-video/presets/film-fun-energetic/CREDITS.txt +1 -0
- package/skills/media/kaizen-video/presets/film-fun-energetic/README.md +2 -0
- package/skills/media/kaizen-video/presets/film-fun-energetic/style.json +39 -0
- package/skills/media/kaizen-video/presets/film-promo-booth/CREDITS.txt +1 -0
- package/skills/media/kaizen-video/presets/film-promo-booth/README.md +2 -0
- package/skills/media/kaizen-video/presets/film-promo-booth/style.json +39 -0
- package/skills/media/kaizen-video/presets/film-viral-social/CREDITS.txt +1 -0
- package/skills/media/kaizen-video/presets/film-viral-social/README.md +2 -0
- package/skills/media/kaizen-video/presets/film-viral-social/style.json +39 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/CREDITS.txt +3 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/README.md +35 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/boing.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/chime.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/ding.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/ending.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/pop.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/reveal.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/shutter.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/stamp.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/whoosh.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/index.html +260 -0
- package/skills/media/kaizen-video/presets/tiktok-cheerful/package.json +10 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/CREDITS.txt +3 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/README.md +52 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/assets/fonts/InstrumentSerif.ttf +0 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/assets/fonts/SpaceMono.ttf +0 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/boing.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/ding.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/ending.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/pop.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/reveal.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/shutter.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/stamp.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/tick.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/whoosh.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/index.html +272 -0
- package/skills/media/kaizen-video/presets/tiktok-explainer/package.json +10 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/CREDITS.txt +12 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/README.md +51 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/clock.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/ding.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/ending.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/heart.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/projector.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/reveal.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/shutter.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/stamp.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/whoosh.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/index.html +260 -0
- package/skills/media/kaizen-video/presets/tiktok-mystery/package.json +10 -0
- package/skills/media/kaizen-video/presets/tiktok-scam-alert/CREDITS.txt +8 -0
- package/skills/media/kaizen-video/presets/tiktok-scam-alert/README.md +24 -0
- package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/clock.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/ding.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/ending.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/heart.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/reveal.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/shutter.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/stamp.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/whoosh.wav +0 -0
- package/skills/media/kaizen-video/presets/tiktok-scam-alert/index.html +266 -0
- package/skills/media/kaizen-video/presets/tiktok-scam-alert/package.json +10 -0
- package/skills/media/kaizen-video/references/presets.md +76 -0
- package/skills/{story-video → media/kaizen-video}/references/production.md +2 -2
- package/skills/{story-video → media/kaizen-video}/references/revisions.md +9 -9
- package/skills/media/kaizen-video/scripts/__pycache__/intake.cpython-314.pyc +0 -0
- package/skills/media/kaizen-video/scripts/__pycache__/make_film.cpython-314.pyc +0 -0
- package/skills/media/kaizen-video/scripts/__pycache__/make_tiktok.cpython-314.pyc +0 -0
- package/skills/media/kaizen-video/scripts/__pycache__/render_cut.cpython-314.pyc +0 -0
- package/skills/media/kaizen-video/scripts/__pycache__/sfx.cpython-314.pyc +0 -0
- package/skills/media/kaizen-video/scripts/__pycache__/srt.cpython-314.pyc +0 -0
- package/skills/media/kaizen-video/scripts/__pycache__/transcribe.cpython-314.pyc +0 -0
- package/skills/media/kaizen-video/scripts/__pycache__/vo.cpython-314.pyc +0 -0
- package/skills/media/kaizen-video/scripts/check.sh +22 -0
- package/skills/{story-video → media/kaizen-video}/scripts/intake.py +2 -2
- package/skills/media/kaizen-video/scripts/make_film.py +263 -0
- package/skills/media/kaizen-video/scripts/make_tiktok.py +213 -0
- package/skills/{story-video → media/kaizen-video}/scripts/master.sh +12 -11
- package/skills/media/kaizen-video/scripts/render_cut.py +88 -0
- package/skills/media/kaizen-video/scripts/setup.sh +14 -0
- package/skills/{story-video → media/kaizen-video}/scripts/sfx.py +3 -3
- package/skills/media/kaizen-video/scripts/srt.py +9 -0
- package/skills/media/kaizen-video/scripts/still.sh +7 -0
- package/skills/media/kaizen-video/scripts/transcribe.py +45 -0
- package/skills/media/kaizen-video/tools/carve.mjs +560 -0
- package/skills/media/kaizen-video/tools/lib/main-module.mjs +13 -0
- package/web/board/views.ts +1 -1
- package/web/board.html +3 -1
- package/web/board.js +2 -2
- package/web/notebook.js +1 -1
- package/web/notes.html +2 -0
- package/web/term.js +38 -36
- package/web/term.ts +43 -10
- package/web/video.html +298 -0
- package/web/video.js +81 -0
- package/web/video.ts +610 -0
- package/skills/story-video/SKILL.md +0 -108
- package/skills/story-video/engine/package.json +0 -12
- package/skills/story-video/engine/src/Root.tsx +0 -24
- package/skills/story-video/engine/src/engine/Film.tsx +0 -405
- package/skills/story-video/engine/src/engine/presets.json +0 -58
- package/skills/story-video/engine/src/index.ts +0 -3
- package/skills/story-video/engine/tsconfig.json +0 -1
- package/skills/story-video/examples/sample-film/README.md +0 -12
- package/skills/story-video/examples/sample-film/film.json +0 -16
- package/skills/story-video/references/presets.md +0 -21
- package/skills/story-video/scripts/check.sh +0 -19
- package/skills/story-video/scripts/setup.sh +0 -25
- package/skills/story-video/scripts/srt.py +0 -30
- package/skills/story-video/scripts/still.sh +0 -10
- /package/skills/{story-deck → media/kaizen-deck}/examples/build-example.js +0 -0
- /package/skills/{story-deck → media/kaizen-deck}/references/rules.md +0 -0
- /package/skills/{story-deck → media/kaizen-deck}/references/story-arc.md +0 -0
- /package/skills/{story-deck → media/kaizen-deck}/scripts/kit.js +0 -0
- /package/skills/{story-deck → media/kaizen-deck}/scripts/prep_images.py +0 -0
- /package/skills/{story-deck → media/kaizen-deck}/scripts/qa.sh +0 -0
- /package/skills/{story-video → media/kaizen-video}/examples/sample-film/lines.json +0 -0
- /package/skills/{story-video → media/kaizen-video}/references/licensing.md +0 -0
- /package/skills/{story-video → media/kaizen-video}/references/script.md +0 -0
- /package/skills/{story-video → media/kaizen-video}/scripts/music.sh +0 -0
- /package/skills/{story-video → media/kaizen-video}/scripts/vo.py +0 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="id" data-resolution="portrait">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=1080, height=1920" />
|
|
6
|
+
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
7
|
+
<script src="captions.js"></script>
|
|
8
|
+
<style>
|
|
9
|
+
@font-face { font-family: "Montserrat"; src: url("assets/fonts/Montserrat.ttf"); font-weight: 100 900; }
|
|
10
|
+
@font-face { font-family: "Fredoka"; src: url("assets/fonts/Fredoka.ttf"); font-weight: 300 700; }
|
|
11
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
12
|
+
html, body { width: 1080px; height: 1920px; overflow: hidden; background: #18120e; }
|
|
13
|
+
body { font-family: "Montserrat", sans-serif; color: #fffdf5; }
|
|
14
|
+
#root { position: relative; width: 100%; height: 100%; overflow: hidden; }
|
|
15
|
+
.fill { position: absolute; inset: 0; }
|
|
16
|
+
|
|
17
|
+
/* Camera: warm, sunlit Sunny Pop Explainer grade on the talking head. */
|
|
18
|
+
#cam { position: absolute; inset: 0; transform-origin: 50% 38%; filter: blur(0px) brightness(1); }
|
|
19
|
+
#a-roll { width: 100%; height: 100%; object-fit: cover;
|
|
20
|
+
filter: contrast(1.06) saturate(1.18) brightness(1.05); }
|
|
21
|
+
#tint { background: radial-gradient(circle at 50% 24%, rgba(255, 210, 63, .16), rgba(255, 122, 89, .08) 55%, rgba(255, 248, 231, .04)); mix-blend-mode: screen; }
|
|
22
|
+
#vignette { background: radial-gradient(ellipse 92% 80% at 50% 44%, transparent 66%, rgba(36, 22, 10, .24) 100%); }
|
|
23
|
+
#flash { background: #fff8e7; opacity: 0; }
|
|
24
|
+
#dim { background: rgba(24, 18, 14, .46); opacity: 0; }
|
|
25
|
+
|
|
26
|
+
/* Sunny pop burst transition: playful corner sparkles and sticker pill badge. */
|
|
27
|
+
#burst { opacity: 0; pointer-events: none; }
|
|
28
|
+
#burst i { position: absolute; width: 64px; height: 64px; display: grid; place-items: center;
|
|
29
|
+
font-family: "Fredoka", "Montserrat", sans-serif; font-weight: 700; font-size: 46px;
|
|
30
|
+
color: #1a1615; background: #ffd23f; border: 5px solid #1a1615; border-radius: 50%;
|
|
31
|
+
box-shadow: 0 6px 0 #1a1615; }
|
|
32
|
+
#burst i:nth-child(1) { left: 76px; top: 210px; }
|
|
33
|
+
#burst i:nth-child(2) { right: 76px; top: 210px; background: #ff6b6b; }
|
|
34
|
+
#burst i:nth-child(3) { left: 76px; bottom: 270px; background: #ff6b6b; }
|
|
35
|
+
#burst i:nth-child(4) { right: 76px; bottom: 270px; }
|
|
36
|
+
#burst .pop-ring { position: absolute; left: 370px; top: 740px; width: 340px; height: 340px;
|
|
37
|
+
border: 6px dashed #ffd23f; border-radius: 50%; opacity: 0; }
|
|
38
|
+
|
|
39
|
+
/* Captions: bold rounded chunks, active word sweeps sun-yellow. Equal side margins clear of TikTok UI. */
|
|
40
|
+
#captions { position: absolute; left: 110px; right: 110px; top: 1270px; height: 220px; }
|
|
41
|
+
.chunk { position: absolute; inset: 0; display: flex; flex-wrap: wrap; justify-content: center; align-content: center;
|
|
42
|
+
gap: 8px 26px; opacity: 0; }
|
|
43
|
+
.w { position: relative; display: block; font-family: "Fredoka", "Montserrat", sans-serif; font-weight: 700;
|
|
44
|
+
font-size: 64px; letter-spacing: 1px; text-transform: uppercase; color: #d8d0c5;
|
|
45
|
+
text-shadow: 0 5px 0 #1a1615, 0 8px 22px rgba(0,0,0,.85); }
|
|
46
|
+
/* Karaoke fill: sweeps sun-yellow left to right while spoken, then settles to crisp cream-white. */
|
|
47
|
+
.w .fill { position: absolute; inset: 0; color: #ffd23f; text-shadow: none; clip-path: inset(0 100% 0 0); }
|
|
48
|
+
|
|
49
|
+
/* Hook + keyword titles. */
|
|
50
|
+
.title { position: absolute; left: 0; right: 0; top: 620px; display: flex; flex-direction: column; align-items: center; opacity: 0; }
|
|
51
|
+
.title h1 { white-space: nowrap; font-family: "Fredoka", "Montserrat", sans-serif; font-weight: 700; font-size: 146px;
|
|
52
|
+
line-height: .96; letter-spacing: 2px; text-align: center; color: #ffd23f;
|
|
53
|
+
text-shadow: 0 10px 0 #1a1615, 0 18px 40px rgba(0,0,0,.75); }
|
|
54
|
+
.title p { margin-top: 26px; background: #fffdf5; color: #1a1615; font-weight: 800; font-size: 38px;
|
|
55
|
+
padding: 10px 28px; border: 5px solid #1a1615; border-radius: 999px; box-shadow: 0 8px 0 #1a1615; }
|
|
56
|
+
#hook { top: 340px; }
|
|
57
|
+
#hook h1 { font-size: 130px; color: #fffdf5; }
|
|
58
|
+
#hook .red { color: #ffd23f; }
|
|
59
|
+
#hook p { background: #ffd23f; color: #1a1615; }
|
|
60
|
+
.title.end { top: 0; bottom: 0; justify-content: center; gap: 10px; }
|
|
61
|
+
.end h1 { color: #ffd23f; font-size: 210px; line-height: .94;
|
|
62
|
+
text-shadow: 0 14px 0 #1a1615, 0 24px 50px rgba(0,0,0,.8); }
|
|
63
|
+
.end h1:nth-child(even) { color: #fffdf5; }
|
|
64
|
+
|
|
65
|
+
/* Sunny Pop sticker cards (replaces dark archive case file). */
|
|
66
|
+
.case { position: absolute; left: 125px; right: 125px; top: 490px; padding: 48px 50px 58px; opacity: 0;
|
|
67
|
+
background: #fffdf5; color: #1a1615; border: 6px solid #1a1615; border-radius: 36px;
|
|
68
|
+
box-shadow: 0 16px 0 #1a1615, 0 32px 70px rgba(0,0,0,.55); }
|
|
69
|
+
.case .k { display: inline-block; background: #ffd23f; color: #1a1615; border: 4px solid #1a1615;
|
|
70
|
+
border-radius: 999px; padding: 6px 22px; font-weight: 900; font-size: 28px; letter-spacing: 3px;
|
|
71
|
+
box-shadow: 0 5px 0 #1a1615; }
|
|
72
|
+
.case h2 { font-family: "Fredoka", "Montserrat", sans-serif; font-weight: 700; font-size: 92px;
|
|
73
|
+
margin-top: 24px; line-height: 1.02; color: #1a1615; }
|
|
74
|
+
.case .s { font-weight: 700; font-size: 38px; margin-top: 20px; line-height: 1.32; color: #2d2520; }
|
|
75
|
+
.case:has(.stamp) { padding-bottom: 165px; }
|
|
76
|
+
.stamp { position: absolute; right: 38px; bottom: 32px; background: #d92332; color: #fffdf5;
|
|
77
|
+
border: 5px solid #1a1615; border-radius: 20px; box-shadow: 0 8px 0 #1a1615;
|
|
78
|
+
font-family: "Fredoka", "Montserrat", sans-serif; font-weight: 700; font-size: 64px;
|
|
79
|
+
padding: 6px 24px; letter-spacing: 3px; opacity: 0; }
|
|
80
|
+
|
|
81
|
+
/* Polaroid / sticker photo cards (full vibrant color). */
|
|
82
|
+
.photo { position: absolute; left: 125px; right: 125px; top: 320px; opacity: 0; display: flex; flex-direction: column; align-items: center; }
|
|
83
|
+
.frame { width: 100%; height: 620px; border-radius: 32px; overflow: hidden;
|
|
84
|
+
background: #fffdf5; border: 6px solid #1a1615; box-shadow: 0 14px 0 #1a1615, 0 28px 65px rgba(0,0,0,.55); }
|
|
85
|
+
.frame img { width: 100%; height: 100%; object-fit: cover; filter: saturate(1.12) contrast(1.04) brightness(1.03); }
|
|
86
|
+
.photo p { margin-top: 28px; background: #ffd23f; color: #1a1615; font-weight: 800; font-size: 38px;
|
|
87
|
+
padding: 10px 28px; border: 5px solid #1a1615; border-radius: 999px; box-shadow: 0 8px 0 #1a1615; }
|
|
88
|
+
.title .frame { width: 820px; height: 540px; margin-bottom: 36px; }
|
|
89
|
+
.title.has-img { top: 290px; }
|
|
90
|
+
.case .frame { height: 400px; border-radius: 22px; margin-bottom: 24px; box-shadow: 0 8px 0 #1a1615; border: 4px solid #1a1615; }
|
|
91
|
+
.case .frame img { filter: saturate(1.1) contrast(1.04) brightness(1.02); }
|
|
92
|
+
|
|
93
|
+
/* Fun trivia checklist. */
|
|
94
|
+
#list { position: absolute; left: 125px; right: 125px; top: 520px; display: flex; flex-direction: column; gap: 28px; opacity: 0; }
|
|
95
|
+
#list .head { align-self: flex-start; background: #ffd23f; color: #1a1615; border: 5px solid #1a1615;
|
|
96
|
+
border-radius: 999px; padding: 10px 30px; font-family: "Fredoka", "Montserrat", sans-serif;
|
|
97
|
+
font-weight: 700; font-size: 46px; letter-spacing: 3px; box-shadow: 0 8px 0 #1a1615; opacity: 0; }
|
|
98
|
+
.item { display: flex; align-items: center; gap: 24px; background: #fffdf5; color: #1a1615;
|
|
99
|
+
border: 5px solid #1a1615; border-radius: 26px; padding: 18px 28px;
|
|
100
|
+
box-shadow: 0 10px 0 #1a1615, 0 22px 45px rgba(0,0,0,.45);
|
|
101
|
+
font-family: "Fredoka", "Montserrat", sans-serif; font-weight: 700; font-size: 52px;
|
|
102
|
+
text-transform: uppercase; opacity: 0; }
|
|
103
|
+
.item span { clip-path: inset(0 100% 0 0); }
|
|
104
|
+
.item b { flex: none; width: 68px; height: 68px; border-radius: 50%; background: #ffd23f;
|
|
105
|
+
border: 4px solid #1a1615; box-shadow: 0 5px 0 #1a1615; display: grid; place-items: center;
|
|
106
|
+
color: #1a1615; font-family: "Fredoka", "Montserrat", sans-serif; font-weight: 700; font-size: 40px; }
|
|
107
|
+
</style>
|
|
108
|
+
</head>
|
|
109
|
+
<body>
|
|
110
|
+
<div id="root" data-composition-id="main" data-start="0" data-duration="{{DURATION}}" data-width="1080" data-height="1920">
|
|
111
|
+
<div id="cam">
|
|
112
|
+
<video id="a-roll" class="clip" src="{{VIDEO}}" playsinline data-start="0" data-duration="{{VIDEO_DURATION}}" data-track-index="0" data-has-audio="true" data-fx-chain="{"version": 1, "nodes": [{"type": "highpass", "id": "n1", "label": "Remove Rumble", "params": {"frequency": 90, "q": 0.707, "poles": "2"}}, {"type": "compressor", "id": "n2", "label": "Even Out Loudness", "params": {"threshold": -26, "ratio": 3.2, "attack": 8, "release": 180, "knee": 2.83, "makeup": 7, "mix": 1}}, {"type": "gain", "id": "n3", "label": "Level Up", "params": {"gain": 6.5}}, {"type": "truepeak", "id": "n4", "label": "Peak Ceiling", "params": {"ceiling": -1.5, "lookahead": 3, "release": 80}}]}"></video>
|
|
113
|
+
</div>
|
|
114
|
+
<div id="tint" class="fill"></div>
|
|
115
|
+
<div id="vignette" class="fill"></div>
|
|
116
|
+
<div id="dim" class="fill"></div>
|
|
117
|
+
|
|
118
|
+
<div id="hook" class="title"><h1>{{HOOK_1}}</h1><h1 class="red">{{HOOK_2}}</h1><p>{{HOOK_SUB}}</p></div>
|
|
119
|
+
<div id="beats"></div>
|
|
120
|
+
<div id="list"></div>
|
|
121
|
+
<div id="captions"></div>
|
|
122
|
+
|
|
123
|
+
<div id="burst" class="fill"><i>★</i><i>✦</i><i>✦</i><i>★</i><div class="pop-ring"></div></div>
|
|
124
|
+
<div id="flash" class="fill"></div>
|
|
125
|
+
|
|
126
|
+
<audio id="music" src="assets/music.wav" data-start="0" data-duration="{{DURATION}}" data-track-index="2" data-volume="{{MUSIC_VOLUME}}"></audio>
|
|
127
|
+
<!-- SFX:BEGIN --><!-- SFX:END -->
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<script>
|
|
131
|
+
const END = {{DURATION}}, HOOK = 3.0;
|
|
132
|
+
const tl = gsap.timeline({ paused: true });
|
|
133
|
+
const el = (tag, cls, html) => { const e = document.createElement(tag); if (cls) e.className = cls; if (html) e.innerHTML = html; return e; };
|
|
134
|
+
|
|
135
|
+
// Dynamic framing: alternate between 1.0x and 1.12x on jump cuts with a gentle upbeat push.
|
|
136
|
+
SEG_STARTS.forEach((s, i) => {
|
|
137
|
+
const e = SEG_STARTS[i + 1] ?? END, base = i % 2 ? 1.12 : 1.0;
|
|
138
|
+
tl.fromTo("#cam", { scale: base }, { scale: base + 0.035, duration: e - s, ease: "none", immediateRender: i === 0 }, s);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Playful burst pop transition for every beat card.
|
|
142
|
+
const hit = (t, dur, focus = true) => {
|
|
143
|
+
const v = Math.max(0, t - 0.18);
|
|
144
|
+
tl.set("#burst", { opacity: 1 }, v);
|
|
145
|
+
tl.fromTo("#burst i", { scale: 0, rotation: -35 }, { scale: 1, rotation: 12, duration: 0.28, ease: "back.out(2.2)", immediateRender: false }, v);
|
|
146
|
+
tl.fromTo("#burst .pop-ring", { scale: 0.65, opacity: 0.9 }, { scale: 1.25, opacity: 0, duration: 0.35, ease: "power2.out", immediateRender: false }, t);
|
|
147
|
+
tl.to("#burst", { opacity: 0, duration: 0.18 }, t + 0.22);
|
|
148
|
+
tl.set("#flash", { opacity: 0.18 }, t);
|
|
149
|
+
tl.to("#flash", { opacity: 0, duration: 0.22, ease: "power2.out" }, t);
|
|
150
|
+
if (!focus) return; // hook: keep the speaker clear
|
|
151
|
+
tl.to("#dim", { opacity: 1, duration: 0.22 }, t);
|
|
152
|
+
tl.to("#cam", { filter: "blur(10px) brightness(1.02)", duration: 0.22 }, t);
|
|
153
|
+
if (t + dur < END) {
|
|
154
|
+
tl.to("#dim", { opacity: 0, duration: 0.28 }, t + dur - 0.28);
|
|
155
|
+
tl.to("#cam", { filter: "blur(0px) brightness(1)", duration: 0.28 }, t + dur - 0.28);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Shrink any title wider than the 840 px safe area (long words like PENGECUALIAN).
|
|
160
|
+
const fit = n => n.querySelectorAll("h1").forEach(h => { const w = h.scrollWidth; if (w > 840) h.style.fontSize = Math.floor(parseFloat(getComputedStyle(h).fontSize) * 840 / w) + "px"; });
|
|
161
|
+
|
|
162
|
+
// Hook: bouncy spring pop-in.
|
|
163
|
+
fit(document.getElementById("hook"));
|
|
164
|
+
hit(0.3, HOOK - 0.3, false);
|
|
165
|
+
tl.fromTo("#hook", { opacity: 0, scale: 0.72, y: 40 }, { opacity: 1, scale: 1, y: 0, duration: 0.48, ease: "back.out(1.8)" }, 0.05);
|
|
166
|
+
tl.to("#hook", { opacity: 0, scale: 0.9, duration: 0.25 }, HOOK - 0.28);
|
|
167
|
+
|
|
168
|
+
// Beats.
|
|
169
|
+
const beats = document.getElementById("beats"), list = document.getElementById("list");
|
|
170
|
+
let listStart = null;
|
|
171
|
+
const pic = b => b.img ? `<div class="frame"><img src="assets/img/${b.img}" alt="" data-layout-allow-overflow></div>` : "";
|
|
172
|
+
const kbAt = (n, t) => { const im = n.querySelector(".frame img"); if (im) tl.fromTo(im, { scale: 1.12 }, { scale: 1, duration: 3.2, ease: "power1.out" }, t); };
|
|
173
|
+
BEATS.forEach((b, i) => {
|
|
174
|
+
if (b.kind === "end") {
|
|
175
|
+
// Celebratory ending: bouncy staggered word pop-in with a cheerful float.
|
|
176
|
+
const n = el("div", "title end", b.title.split(" ").map(w => `<h1>${w}</h1>`).join(""));
|
|
177
|
+
beats.append(n); fit(n);
|
|
178
|
+
tl.to("#dim", { opacity: 1, duration: 0.25 }, b.t);
|
|
179
|
+
tl.to("#cam", { filter: "blur(12px) brightness(0.9)", duration: 0.25 }, b.t);
|
|
180
|
+
tl.set(n, { opacity: 1 }, b.t);
|
|
181
|
+
n.querySelectorAll("h1").forEach((h, j) =>
|
|
182
|
+
tl.fromTo(h, { opacity: 0, scale: 0.4, y: 60, rotation: j % 2 ? 6 : -6 },
|
|
183
|
+
{ opacity: 1, scale: 1, y: 0, rotation: 0, duration: 0.36, ease: "back.out(2.1)" }, b.t + j * 0.14));
|
|
184
|
+
const land = b.t + (n.children.length - 1) * 0.14 + 0.36;
|
|
185
|
+
tl.set("#flash", { opacity: 0.25 }, land);
|
|
186
|
+
tl.to("#flash", { opacity: 0, duration: 0.35 }, land);
|
|
187
|
+
tl.to(n, { scale: 1.05, duration: END - land, ease: "sine.inOut" }, land);
|
|
188
|
+
} else if (b.kind === "title") {
|
|
189
|
+
const d = 1.8;
|
|
190
|
+
const n = el("div", "title" + (b.img ? " has-img" : ""), pic(b) + `<h1>${b.title}</h1>` + (b.sub ? `<p>${b.sub}</p>` : ""));
|
|
191
|
+
beats.append(n); fit(n); hit(b.t, d); kbAt(n, b.t);
|
|
192
|
+
tl.fromTo(n, { opacity: 0, scale: 0.75, y: 50 }, { opacity: 1, scale: 1, y: 0, duration: 0.42, ease: "back.out(1.7)" }, b.t + 0.06);
|
|
193
|
+
tl.to(n, { opacity: 0, scale: 0.92, duration: 0.24 }, b.t + d - 0.24);
|
|
194
|
+
} else if (b.kind === "case") {
|
|
195
|
+
const d = 3.2;
|
|
196
|
+
const n = el("div", "case", pic(b) + `<div class="k">${b.kicker}</div><h2>${b.title}</h2>` +
|
|
197
|
+
(b.sub ? `<div class="s">${b.sub}</div>` : "") + (b.stamp ? `<div class="stamp" data-layout-allow-overlap>${b.stamp}</div>` : ""));
|
|
198
|
+
beats.append(n); hit(b.t, d); kbAt(n, b.t);
|
|
199
|
+
tl.fromTo(n, { opacity: 0, y: 110, scale: 0.88, rotation: -3 }, { opacity: 1, y: 0, scale: 1, rotation: -1, duration: 0.48, ease: "back.out(1.7)" }, b.t + 0.06);
|
|
200
|
+
const st = n.querySelector(".stamp");
|
|
201
|
+
if (st) tl.fromTo(st, { opacity: 0, scale: 0.3, rotation: 14 }, { opacity: 1, scale: 1, rotation: -8, duration: 0.34, ease: "back.out(2.4)" }, b.t + 0.9);
|
|
202
|
+
tl.to(n, { opacity: 0, y: -50, scale: 0.94, duration: 0.28 }, b.t + d - 0.28);
|
|
203
|
+
} else if (b.kind === "photo") {
|
|
204
|
+
const d = 2.8;
|
|
205
|
+
const n = el("div", "photo", pic(b) + `<p>${b.label}</p>`);
|
|
206
|
+
beats.append(n); hit(b.t, d); kbAt(n, b.t);
|
|
207
|
+
tl.fromTo(n, { opacity: 0, y: 80, scale: 0.88, rotation: 3 }, { opacity: 1, y: 0, scale: 1, rotation: 1, duration: 0.45, ease: "back.out(1.6)" }, b.t + 0.06);
|
|
208
|
+
tl.to(n, { opacity: 0, scale: 0.92, duration: 0.28 }, b.t + d - 0.28);
|
|
209
|
+
} else {
|
|
210
|
+
if (listStart === null) listStart = b.t;
|
|
211
|
+
// Cheerful checklist reveal: bouncy star badge pops in, sticker bar springs up, and text glides open.
|
|
212
|
+
const n = el("div", "item", `<b>★</b><span>${b.text}</span>`);
|
|
213
|
+
list.append(n);
|
|
214
|
+
const box = n.querySelector("b"), txt = n.querySelector("span");
|
|
215
|
+
tl.fromTo(n, { opacity: 0, y: 36, scale: 0.9 }, { opacity: 1, y: 0, scale: 1, duration: 0.38, ease: "back.out(1.8)" }, b.t);
|
|
216
|
+
tl.fromTo(box, { scale: 0, rotation: -90 }, { scale: 1, rotation: 0, duration: 0.42, ease: "back.out(2.4)" }, b.t + 0.04);
|
|
217
|
+
tl.fromTo(txt, { clipPath: "inset(0 100% 0 0)" }, { clipPath: "inset(0 0% 0 0)", duration: 0.45, ease: "power2.out" }, b.t + 0.08);
|
|
218
|
+
tl.fromTo(box, { scale: 1 }, { scale: 1.1, duration: 0.5, ease: "sine.inOut", yoyo: true, repeat: 3 }, b.t + 0.5);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// The checklist leaves when the speaker moves on: the next jump cut after the last item (max 6 s).
|
|
223
|
+
const items = BEATS.filter(b => b.kind === "item"), lastItem = items.length ? items[items.length - 1].t : 0;
|
|
224
|
+
const listEnd = Math.min(SEG_STARTS.find(s => s > lastItem + 1) ?? END, lastItem + 6);
|
|
225
|
+
if (items.length) {
|
|
226
|
+
const head = el("div", "head"); head.textContent = "KEMUNGKINAN:"; list.prepend(head);
|
|
227
|
+
tl.fromTo(head, { opacity: 0, scale: 0.7, y: 20 }, { opacity: 1, scale: 1, y: 0, duration: 0.38, ease: "back.out(2)" }, listStart - 0.6);
|
|
228
|
+
tl.fromTo("#list", { opacity: 0 }, { opacity: 1, duration: 0.25 }, listStart - 0.6);
|
|
229
|
+
tl.to("#dim", { opacity: 1, duration: 0.28 }, listStart - 0.6);
|
|
230
|
+
tl.to("#cam", { filter: "blur(10px) brightness(0.95)", duration: 0.35 }, listStart - 0.6);
|
|
231
|
+
tl.to(["#list", "#dim"], { opacity: 0, duration: 0.35 }, listEnd - 0.35);
|
|
232
|
+
tl.to("#cam", { filter: "blur(0px) brightness(1)", duration: 0.35 }, listEnd - 0.35);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Captions (hidden while a card or the list is on screen; kept visible under the hook).
|
|
236
|
+
const busy = BEATS.filter(b => b.kind !== "item" && b.kind !== "photo").map(b => [b.t, b.t + (b.kind === "case" ? 3.2 : b.kind === "end" ? END : 1.8)])
|
|
237
|
+
.concat(items.length ? [[listStart - 0.6, listEnd]] : []);
|
|
238
|
+
const cap = document.getElementById("captions");
|
|
239
|
+
CAPTIONS.forEach(chunk => {
|
|
240
|
+
const n = el("div", "chunk"); cap.append(n);
|
|
241
|
+
const start = chunk[0].s, end = chunk[chunk.length - 1].hold;
|
|
242
|
+
if (busy.some(([a, b]) => start < b && end > a)) return;
|
|
243
|
+
tl.fromTo(n, { opacity: 0, y: 20 }, { opacity: 1, y: 0, duration: 0.12, ease: "back.out(1.6)" }, start);
|
|
244
|
+
tl.set(n, { opacity: 0 }, end);
|
|
245
|
+
chunk.forEach((w, j) => {
|
|
246
|
+
const txt = w.w.replace(/[,.?!]+$/, "");
|
|
247
|
+
const s = el("span", "w"); s.textContent = txt; n.append(s);
|
|
248
|
+
const f = el("span", "fill"); f.textContent = txt; s.append(f);
|
|
249
|
+
const until = chunk[j + 1] ? chunk[j + 1].s : end;
|
|
250
|
+
const t0 = Math.max(start, w.s - 1 / 30), dur = Math.max(w.e - w.s, 0.1);
|
|
251
|
+
tl.fromTo(f, { clipPath: "inset(0 100% 0 0)" }, { clipPath: "inset(0 0% 0 0)", duration: dur, ease: "power1.out" }, t0);
|
|
252
|
+
tl.fromTo(s, { scale: 1.1 }, { scale: 1, duration: 0.18, ease: "back.out(2)" }, t0);
|
|
253
|
+
tl.set(f, { color: "#fffdf5" }, until);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
window.__timelines["main"] = tl;
|
|
258
|
+
</script>
|
|
259
|
+
</body>
|
|
260
|
+
</html>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
Sound effects: synthesized with ffmpeg for this preset, CC0 (shutter, whoosh, pop, tick, ding, stamp, reveal, boing, ending).
|
|
2
|
+
|
|
3
|
+
Fonts: "Montserrat" by Julieta Ulanovsky et al. (Google Fonts, SIL Open Font License 1.1); "Instrument Serif" by The Instrument Serif Project Authors (Google Fonts, SIL Open Font License 1.1); "Space Mono" by The Space Mono Project Authors (Google Fonts, SIL Open Font License 1.1).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# TikTok explainer preset
|
|
2
|
+
|
|
3
|
+
A 1080×1920 HyperFrames style for talking-head tips and how-to explainers. It was modeled on a short motion-graphics explainer: a near-black editor look in which the speaker plays inside a clip card, carried by big headlines.
|
|
4
|
+
|
|
5
|
+
## Look and motion
|
|
6
|
+
|
|
7
|
+
- **Palette:** near-black `#0c0b0a` with two soft diagonal light streaks that drift, cream `#efe6d6` text, orange `#f7a93b` accent, and red `#ef4b3a` for stamps.
|
|
8
|
+
- **Type:** Montserrat 900 headlines with tight tracking, small Instrument Serif setup lines above or below them, and Space Mono for timecodes and chips. A trailing `?`, `!` or `.` on any headline turns orange.
|
|
9
|
+
- **Clip card:** the footage sits in a rounded, cream-framed card (700×930, cropped to the upper face) with a running `● 0:07` timecode chip. It tilts ±1.6° and alternates scale at every jump cut, with a slow push inside each segment.
|
|
10
|
+
- **Hook:** `line1` is the serif setup line, `line2` the huge headline, and `sub` an orange serif line under it. It holds for 3 s above the card.
|
|
11
|
+
- **Captions:** kinetic. Each word pops in as it is spoken, in lowercase Montserrat 900, orange while spoken and cream afterwards. Unspoken words are not shown yet. Captions sit under the card and are hidden while a scene owns the screen (photo cards keep them).
|
|
12
|
+
- **Scenes** (beats):
|
|
13
|
+
- `title`: the card tucks to the top right. With `big` (for example `"1"`), a giant outlined numeral fills up in orange behind it, and the text sits bottom left; without it, the text is centered. Keys: `kicker` (serif, above), `title` (headline), `sub` (orange serif, below), optional `img`, and `dur` (default 1.8 s).
|
|
14
|
+
- `case`: a banner scene. `kicker` (serif) and `sub` (bold headline) sit on top, the card greys out, an orange tape with `title` slaps across it, and an optional red `stamp` (for example `"ZONK"`) slams on with the stamp sound. Optional `note` is a serif line at the bottom. Lasts 3.2 s.
|
|
15
|
+
- `photo`: the clip card is swept off to the left and an image card slides in from the right, with an optional mono `label` chip. Lasts 2.8 s.
|
|
16
|
+
- `item`: a steps list. Each row is an orange mono chip (`label`, default `01`, `02`, …) plus a bold line (`text`). The first item may carry `head`, an orange serif header. The card tucks aside while the list shows. The list leaves at the first jump cut after its last item, at most 6 s later.
|
|
17
|
+
- `end`: the card fades out. `title` (use `\n` for line breaks) stacks in, a giant orange glyph (`big`, default `?`) bounces in on the ending sting and wiggles, and `sub` (for example "jawab di komen ↓") fades in. It holds through `tail`.
|
|
18
|
+
- `mascot`: optional. A corner character from the video's `images/` (`img`, for example `mascot.png`, a transparent PNG) pops in at its word and bobs until the end.
|
|
19
|
+
|
|
20
|
+
## Audio
|
|
21
|
+
|
|
22
|
+
- **Music:** any shared track. `lights` at about 0.28 suits the reference's calm pace. It is carved around the voice like the other presets.
|
|
23
|
+
- **Voice:** the same chain as the other presets (rumble cut, compressor, gain, −1.5 dBTP ceiling). A test render landed at about −16.8 LUFS integrated with a −1.1 dBFS peak.
|
|
24
|
+
- **Sound effects** in `assets/sfx/`, all synthesized (CC0), with peaks around −7 to −9 dBFS:
|
|
25
|
+
- Automatic: `shutter` (a soft swipe on every `title`, `case` and `photo`), `stamp` (a thud under the stamp), `reveal` (a pop-and-chime on each `item`) and `ending` (a 1.2 s riser into a hit).
|
|
26
|
+
- Reactions: `pop`, `tick`, `ding`, `whoosh`, `boing`.
|
|
27
|
+
|
|
28
|
+
## Config example
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"preset": "tiktok-explainer",
|
|
33
|
+
"hook": { "line1": "klip lu dibuka pake", "line2": "BASA-BASI?", "sub": "momen serunya baru di tengah" },
|
|
34
|
+
"music": "lights", "music_volume": 0.28,
|
|
35
|
+
"beats": [
|
|
36
|
+
{ "kind": "mascot", "word": "klip", "img": "mascot.png" },
|
|
37
|
+
{ "kind": "title", "word": "detik", "big": "1", "kicker": "detik satu:", "title": "PUNCHLINE", "sub": "atau pertanyaan?", "dur": 2.6 },
|
|
38
|
+
{ "kind": "case", "word": "janji", "kicker": "asal judulnya", "sub": "jangan janjiin yang gak ada.", "title": "GAK NYANGKA ENDINGNYA!!", "stamp": "ZONK" },
|
|
39
|
+
{ "kind": "item", "word": "nyapa", "head": "yang lama:", "label": "0:01", "text": "nyapa dulu" },
|
|
40
|
+
{ "kind": "end", "word": "terakhir", "title": "momen terbaiknya\ndetik ke", "big": "?", "sub": "jawab di komen ↓" }
|
|
41
|
+
],
|
|
42
|
+
"tail": 2.5
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The other keys (`video`, `words`, `plan`, `fix`, `cold_open`, `reactions`, `crf`) work as in the other presets; see the skill's `examples/config.json`.
|
|
47
|
+
|
|
48
|
+
## Notes
|
|
49
|
+
|
|
50
|
+
- Cards should not overlap. A `title` with a `big` numeral reads best at a `dur` of 2.4–3 s.
|
|
51
|
+
- Keep `title` and banner text short. Headlines shrink to fit 900 px, but anything past about 14 characters on one line gets small.
|
|
52
|
+
- `hyperframes check` warns about low contrast on the tape text at the moment the banner fades out. That is the fade, not the design.
|
|
Binary file
|
|
Binary file
|