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.
Files changed (184) hide show
  1. package/README.md +111 -84
  2. package/cli/build-web.ts +4 -2
  3. package/cli/hermes.ts +65 -0
  4. package/cli/install.ts +24 -10
  5. package/cli/localsend.test.ts +84 -0
  6. package/cli/localsend.ts +131 -0
  7. package/cli/notes.test.ts +6 -4
  8. package/cli/settings.ts +1 -0
  9. package/cli/state.test.ts +37 -0
  10. package/cli/state.ts +35 -20
  11. package/cli/video.test.ts +479 -0
  12. package/cli/video.ts +507 -0
  13. package/cli/web.ts +127 -1
  14. package/cli/worktree.test.ts +3 -1
  15. package/package.json +1 -1
  16. package/skills/kaizen/config.default.yml +4 -0
  17. package/skills/media/kaizen-book/SKILL.md +107 -0
  18. package/skills/media/kaizen-book/assets/reader.html +440 -0
  19. package/skills/media/kaizen-book/assets/styles.json +45 -0
  20. package/skills/media/kaizen-book/examples/book.json +14 -0
  21. package/skills/media/kaizen-book/examples/images/reach.svg +1 -0
  22. package/skills/media/kaizen-book/examples/manuscript.md +127 -0
  23. package/skills/media/kaizen-book/references/interview.md +85 -0
  24. package/skills/media/kaizen-book/references/proofread.md +47 -0
  25. package/skills/media/kaizen-book/references/writing.md +53 -0
  26. package/skills/media/kaizen-book/scripts/build_book.py +279 -0
  27. package/skills/media/kaizen-book/scripts/check.sh +10 -0
  28. package/skills/media/kaizen-book/scripts/check_book.py +128 -0
  29. package/skills/media/kaizen-book/scripts/deploy.py +58 -0
  30. package/skills/media/kaizen-book/scripts/pdf.mjs +99 -0
  31. package/skills/media/kaizen-book/scripts/setup.sh +9 -0
  32. package/skills/media/kaizen-book/scripts/verify.mjs +95 -0
  33. package/skills/{story-deck → media/kaizen-deck}/SKILL.md +1 -1
  34. package/skills/{story-deck → media/kaizen-deck}/scripts/check.sh +1 -1
  35. package/skills/{story-deck → media/kaizen-deck}/scripts/install_fonts.py +2 -2
  36. package/skills/media/kaizen-video/SKILL.md +112 -0
  37. package/skills/media/kaizen-video/assets/CREDITS.txt +16 -0
  38. package/skills/media/kaizen-video/assets/film-fonts/Fraunces.ttf +0 -0
  39. package/skills/media/kaizen-video/assets/film-fonts/Inter.ttf +0 -0
  40. package/skills/media/kaizen-video/assets/film-fonts/PlayfairDisplay-Italic.ttf +0 -0
  41. package/skills/media/kaizen-video/assets/film-fonts/PlayfairDisplay.ttf +0 -0
  42. package/skills/media/kaizen-video/assets/fonts/Fredoka.ttf +0 -0
  43. package/skills/media/kaizen-video/assets/fonts/Montserrat.ttf +0 -0
  44. package/skills/media/kaizen-video/assets/fonts/SpecialElite.ttf +0 -0
  45. package/skills/media/kaizen-video/assets/music/investigations.mp3 +0 -0
  46. package/skills/media/kaizen-video/assets/music/lights.mp3 +0 -0
  47. package/skills/media/kaizen-video/assets/music/sunny.mp3 +0 -0
  48. package/skills/media/kaizen-video/examples/config.json +152 -0
  49. package/skills/media/kaizen-video/examples/sample-film/README.md +10 -0
  50. package/skills/media/kaizen-video/examples/sample-film/film.json +63 -0
  51. package/skills/media/kaizen-video/film.html +246 -0
  52. package/skills/media/kaizen-video/hyperframes.json +12 -0
  53. package/skills/media/kaizen-video/presets/educational-warning/CREDITS.txt +3 -0
  54. package/skills/media/kaizen-video/presets/educational-warning/README.md +26 -0
  55. package/skills/media/kaizen-video/presets/educational-warning/assets/sfx/ending.wav +0 -0
  56. package/skills/media/kaizen-video/presets/educational-warning/assets/sfx/reveal.wav +0 -0
  57. package/skills/media/kaizen-video/presets/educational-warning/assets/sfx/shutter.wav +0 -0
  58. package/skills/media/kaizen-video/presets/educational-warning/assets/sfx/stamp.wav +0 -0
  59. package/skills/media/kaizen-video/presets/educational-warning/index.html +236 -0
  60. package/skills/media/kaizen-video/presets/educational-warning/package.json +10 -0
  61. package/skills/media/kaizen-video/presets/film-cinematic-drama/CREDITS.txt +1 -0
  62. package/skills/media/kaizen-video/presets/film-cinematic-drama/README.md +2 -0
  63. package/skills/media/kaizen-video/presets/film-cinematic-drama/style.json +39 -0
  64. package/skills/media/kaizen-video/presets/film-fun-energetic/CREDITS.txt +1 -0
  65. package/skills/media/kaizen-video/presets/film-fun-energetic/README.md +2 -0
  66. package/skills/media/kaizen-video/presets/film-fun-energetic/style.json +39 -0
  67. package/skills/media/kaizen-video/presets/film-promo-booth/CREDITS.txt +1 -0
  68. package/skills/media/kaizen-video/presets/film-promo-booth/README.md +2 -0
  69. package/skills/media/kaizen-video/presets/film-promo-booth/style.json +39 -0
  70. package/skills/media/kaizen-video/presets/film-viral-social/CREDITS.txt +1 -0
  71. package/skills/media/kaizen-video/presets/film-viral-social/README.md +2 -0
  72. package/skills/media/kaizen-video/presets/film-viral-social/style.json +39 -0
  73. package/skills/media/kaizen-video/presets/tiktok-cheerful/CREDITS.txt +3 -0
  74. package/skills/media/kaizen-video/presets/tiktok-cheerful/README.md +35 -0
  75. package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/boing.wav +0 -0
  76. package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/chime.wav +0 -0
  77. package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/ding.wav +0 -0
  78. package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/ending.wav +0 -0
  79. package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/pop.wav +0 -0
  80. package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/reveal.wav +0 -0
  81. package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/shutter.wav +0 -0
  82. package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/stamp.wav +0 -0
  83. package/skills/media/kaizen-video/presets/tiktok-cheerful/assets/sfx/whoosh.wav +0 -0
  84. package/skills/media/kaizen-video/presets/tiktok-cheerful/index.html +260 -0
  85. package/skills/media/kaizen-video/presets/tiktok-cheerful/package.json +10 -0
  86. package/skills/media/kaizen-video/presets/tiktok-explainer/CREDITS.txt +3 -0
  87. package/skills/media/kaizen-video/presets/tiktok-explainer/README.md +52 -0
  88. package/skills/media/kaizen-video/presets/tiktok-explainer/assets/fonts/InstrumentSerif.ttf +0 -0
  89. package/skills/media/kaizen-video/presets/tiktok-explainer/assets/fonts/SpaceMono.ttf +0 -0
  90. package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/boing.wav +0 -0
  91. package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/ding.wav +0 -0
  92. package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/ending.wav +0 -0
  93. package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/pop.wav +0 -0
  94. package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/reveal.wav +0 -0
  95. package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/shutter.wav +0 -0
  96. package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/stamp.wav +0 -0
  97. package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/tick.wav +0 -0
  98. package/skills/media/kaizen-video/presets/tiktok-explainer/assets/sfx/whoosh.wav +0 -0
  99. package/skills/media/kaizen-video/presets/tiktok-explainer/index.html +272 -0
  100. package/skills/media/kaizen-video/presets/tiktok-explainer/package.json +10 -0
  101. package/skills/media/kaizen-video/presets/tiktok-mystery/CREDITS.txt +12 -0
  102. package/skills/media/kaizen-video/presets/tiktok-mystery/README.md +51 -0
  103. package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/clock.wav +0 -0
  104. package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/ding.wav +0 -0
  105. package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/ending.wav +0 -0
  106. package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/heart.wav +0 -0
  107. package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/projector.wav +0 -0
  108. package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/reveal.wav +0 -0
  109. package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/shutter.wav +0 -0
  110. package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/stamp.wav +0 -0
  111. package/skills/media/kaizen-video/presets/tiktok-mystery/assets/sfx/whoosh.wav +0 -0
  112. package/skills/media/kaizen-video/presets/tiktok-mystery/index.html +260 -0
  113. package/skills/media/kaizen-video/presets/tiktok-mystery/package.json +10 -0
  114. package/skills/media/kaizen-video/presets/tiktok-scam-alert/CREDITS.txt +8 -0
  115. package/skills/media/kaizen-video/presets/tiktok-scam-alert/README.md +24 -0
  116. package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/clock.wav +0 -0
  117. package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/ding.wav +0 -0
  118. package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/ending.wav +0 -0
  119. package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/heart.wav +0 -0
  120. package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/reveal.wav +0 -0
  121. package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/shutter.wav +0 -0
  122. package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/stamp.wav +0 -0
  123. package/skills/media/kaizen-video/presets/tiktok-scam-alert/assets/sfx/whoosh.wav +0 -0
  124. package/skills/media/kaizen-video/presets/tiktok-scam-alert/index.html +266 -0
  125. package/skills/media/kaizen-video/presets/tiktok-scam-alert/package.json +10 -0
  126. package/skills/media/kaizen-video/references/presets.md +76 -0
  127. package/skills/{story-video → media/kaizen-video}/references/production.md +2 -2
  128. package/skills/{story-video → media/kaizen-video}/references/revisions.md +9 -9
  129. package/skills/media/kaizen-video/scripts/__pycache__/intake.cpython-314.pyc +0 -0
  130. package/skills/media/kaizen-video/scripts/__pycache__/make_film.cpython-314.pyc +0 -0
  131. package/skills/media/kaizen-video/scripts/__pycache__/make_tiktok.cpython-314.pyc +0 -0
  132. package/skills/media/kaizen-video/scripts/__pycache__/render_cut.cpython-314.pyc +0 -0
  133. package/skills/media/kaizen-video/scripts/__pycache__/sfx.cpython-314.pyc +0 -0
  134. package/skills/media/kaizen-video/scripts/__pycache__/srt.cpython-314.pyc +0 -0
  135. package/skills/media/kaizen-video/scripts/__pycache__/transcribe.cpython-314.pyc +0 -0
  136. package/skills/media/kaizen-video/scripts/__pycache__/vo.cpython-314.pyc +0 -0
  137. package/skills/media/kaizen-video/scripts/check.sh +22 -0
  138. package/skills/{story-video → media/kaizen-video}/scripts/intake.py +2 -2
  139. package/skills/media/kaizen-video/scripts/make_film.py +263 -0
  140. package/skills/media/kaizen-video/scripts/make_tiktok.py +213 -0
  141. package/skills/{story-video → media/kaizen-video}/scripts/master.sh +12 -11
  142. package/skills/media/kaizen-video/scripts/render_cut.py +88 -0
  143. package/skills/media/kaizen-video/scripts/setup.sh +14 -0
  144. package/skills/{story-video → media/kaizen-video}/scripts/sfx.py +3 -3
  145. package/skills/media/kaizen-video/scripts/srt.py +9 -0
  146. package/skills/media/kaizen-video/scripts/still.sh +7 -0
  147. package/skills/media/kaizen-video/scripts/transcribe.py +45 -0
  148. package/skills/media/kaizen-video/tools/carve.mjs +560 -0
  149. package/skills/media/kaizen-video/tools/lib/main-module.mjs +13 -0
  150. package/web/board/views.ts +1 -1
  151. package/web/board.html +3 -1
  152. package/web/board.js +2 -2
  153. package/web/notebook.js +1 -1
  154. package/web/notes.html +2 -0
  155. package/web/term.js +38 -36
  156. package/web/term.ts +43 -10
  157. package/web/video.html +298 -0
  158. package/web/video.js +81 -0
  159. package/web/video.ts +610 -0
  160. package/skills/story-video/SKILL.md +0 -108
  161. package/skills/story-video/engine/package.json +0 -12
  162. package/skills/story-video/engine/src/Root.tsx +0 -24
  163. package/skills/story-video/engine/src/engine/Film.tsx +0 -405
  164. package/skills/story-video/engine/src/engine/presets.json +0 -58
  165. package/skills/story-video/engine/src/index.ts +0 -3
  166. package/skills/story-video/engine/tsconfig.json +0 -1
  167. package/skills/story-video/examples/sample-film/README.md +0 -12
  168. package/skills/story-video/examples/sample-film/film.json +0 -16
  169. package/skills/story-video/references/presets.md +0 -21
  170. package/skills/story-video/scripts/check.sh +0 -19
  171. package/skills/story-video/scripts/setup.sh +0 -25
  172. package/skills/story-video/scripts/srt.py +0 -30
  173. package/skills/story-video/scripts/still.sh +0 -10
  174. /package/skills/{story-deck → media/kaizen-deck}/examples/build-example.js +0 -0
  175. /package/skills/{story-deck → media/kaizen-deck}/references/rules.md +0 -0
  176. /package/skills/{story-deck → media/kaizen-deck}/references/story-arc.md +0 -0
  177. /package/skills/{story-deck → media/kaizen-deck}/scripts/kit.js +0 -0
  178. /package/skills/{story-deck → media/kaizen-deck}/scripts/prep_images.py +0 -0
  179. /package/skills/{story-deck → media/kaizen-deck}/scripts/qa.sh +0 -0
  180. /package/skills/{story-video → media/kaizen-video}/examples/sample-film/lines.json +0 -0
  181. /package/skills/{story-video → media/kaizen-video}/references/licensing.md +0 -0
  182. /package/skills/{story-video → media/kaizen-video}/references/script.md +0 -0
  183. /package/skills/{story-video → media/kaizen-video}/scripts/music.sh +0 -0
  184. /package/skills/{story-video → media/kaizen-video}/scripts/vo.py +0 -0
@@ -0,0 +1,236 @@
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
+ * { margin: 0; padding: 0; box-sizing: border-box; }
11
+ html, body { width: 1080px; height: 1920px; overflow: hidden; background: #f4f1ea; }
12
+ body { font-family: "Montserrat", sans-serif; color: #1d2430; }
13
+ #root { position: relative; width: 100%; height: 100%; overflow: hidden; }
14
+ .fill { position: absolute; inset: 0; }
15
+
16
+ /* Grid paper behind the camera, seen when the camera steps back for a card. */
17
+ #paper { background-color: #f4f1ea;
18
+ background-image: linear-gradient(rgba(29,36,48,.07) 2px, transparent 2px), linear-gradient(90deg, rgba(29,36,48,.07) 2px, transparent 2px);
19
+ background-size: 60px 60px; }
20
+ /* Camera: bright and natural (slight lift, no vignette, no grain). */
21
+ #cam { position: absolute; inset: 0; overflow: hidden; transform-origin: 50% 40%; border-radius: 0; }
22
+ #a-roll { width: 100%; height: 100%; object-fit: cover; filter: brightness(1.05) contrast(1.02) saturate(1.06); }
23
+ #wash { background: #fffdf8; opacity: 0; }
24
+
25
+ /* Swipe transition: an amber band and a navy band sweep across the frame. */
26
+ .band { position: absolute; top: -10%; height: 120%; }
27
+ #band-a { left: 0; width: 70%; background: #ffb81c; }
28
+ #band-b { left: 0; width: 22%; background: #1d2430; }
29
+
30
+ /* Warning triangle (hook + ending). */
31
+ .sign { display: block; }
32
+
33
+ /* Captions: white box, spoken words dark, the current word on an amber marker. */
34
+ #captions { position: absolute; left: 110px; right: 110px; top: 1290px; height: 220px; }
35
+ .chunk { position: absolute; inset: 0; display: flex; justify-content: center; align-items: center; opacity: 0; }
36
+ .chunk .box { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px 10px; background: #fff; border-radius: 22px;
37
+ padding: 16px 24px; box-shadow: 0 10px 30px rgba(29,36,48,.25); max-width: 860px; }
38
+ .w { font-weight: 800; font-size: 58px; line-height: 1.15; color: #9aa1ab; padding: 0 10px; border-radius: 12px; }
39
+
40
+ /* Hook card. */
41
+ #hook { position: absolute; left: 110px; right: 110px; top: 300px; background: #fff; border-radius: 40px; padding: 50px 40px 56px;
42
+ display: flex; flex-direction: column; align-items: center; box-shadow: 0 30px 70px rgba(29,36,48,.3); opacity: 0; }
43
+ #hook .sign { width: 170px; height: 150px; margin-bottom: 18px; }
44
+ .line { overflow: hidden; }
45
+ .line > span { display: block; }
46
+ #hook h1 { white-space: nowrap; font-weight: 900; text-align: center; line-height: 1; }
47
+ #hook h1.l1 { font-size: 170px; color: #e0352b; }
48
+ #hook h1.l2 { font-size: 100px; color: #1d2430; margin-top: 10px; }
49
+ #hook p { margin-top: 30px; background: #ffb81c; color: #1d2430; font-weight: 800; font-size: 40px; padding: 10px 26px; border-radius: 999px; text-align: center; }
50
+
51
+ /* Titles: dark headline on white, an amber marker stroke grows behind it. */
52
+ .title { position: absolute; left: 0; right: 0; top: 700px; display: flex; flex-direction: column; align-items: center; opacity: 0; }
53
+ .title .hlw { position: relative; padding: 18px 40px; background: #fff; border-radius: 28px; box-shadow: 0 24px 60px rgba(29,36,48,.3); }
54
+ .title .hl { position: absolute; left: 30px; right: 30px; bottom: 26px; height: 42%; background: #ffd56b; border-radius: 10px; transform-origin: 0 50%; }
55
+ .title h1 { position: relative; white-space: nowrap; font-weight: 900; font-size: 110px; line-height: 1.05; color: #1d2430; text-align: center; }
56
+ .title p { margin-top: 26px; background: #138a8a; color: #fff; font-weight: 700; font-size: 40px; padding: 8px 24px; border-radius: 999px; }
57
+
58
+ /* Fact cards: white card with a teal header strip and an optional red round badge. */
59
+ .case { position: absolute; left: 120px; right: 120px; top: 520px; background: #fff; border-radius: 34px; overflow: visible; opacity: 0;
60
+ box-shadow: 0 30px 70px rgba(29,36,48,.3); transform-origin: 50% 0; }
61
+ .case .k { background: #138a8a; color: #fff; font-weight: 800; font-size: 34px; letter-spacing: 5px; padding: 22px 46px; border-radius: 34px 34px 0 0; }
62
+ .case h2 { font-weight: 900; font-size: 84px; line-height: 1.02; padding: 34px 46px 0; color: #1d2430; }
63
+ .case .s { font-weight: 600; font-size: 40px; line-height: 1.3; padding: 22px 46px 52px; color: #3a4452; }
64
+ .badge { position: absolute; right: -30px; top: -60px; width: 190px; height: 190px; border-radius: 50%; background: #e0352b; color: #fff;
65
+ display: grid; place-items: center; text-align: center; font-weight: 900; font-size: 34px; line-height: 1.05; padding: 20px;
66
+ box-shadow: 0 14px 30px rgba(224,53,43,.45); border: 8px solid #fff; opacity: 0; }
67
+
68
+ /* Example photos: a white print with an "CONTOH" tag and a red circle drawn on it. */
69
+ .photo { position: absolute; left: 130px; right: 130px; top: 380px; background: #fff; padding: 22px 22px 30px; border-radius: 18px; opacity: 0;
70
+ box-shadow: 0 30px 80px rgba(29,36,48,.35); }
71
+ .photo .frame { position: relative; height: 700px; border-radius: 8px; overflow: hidden; }
72
+ .photo .frame img { width: 100%; height: 100%; object-fit: cover; }
73
+ .photo svg.ring { position: absolute; inset: 0; width: 100%; height: 100%; }
74
+ .photo .tag { position: absolute; left: -26px; top: -30px; background: #e0352b; color: #fff; font-weight: 900; font-size: 34px; letter-spacing: 4px;
75
+ padding: 10px 22px; border-radius: 12px; transform: rotate(-5deg); }
76
+ .photo p { margin-top: 24px; font-weight: 800; font-size: 42px; line-height: 1.2; text-align: center; color: #1d2430; }
77
+
78
+ /* Checklist on a clipboard card. */
79
+ #list { position: absolute; left: 100px; right: 100px; top: 470px; background: #fff; border-radius: 36px; padding: 0 0 40px; opacity: 0;
80
+ box-shadow: 0 30px 70px rgba(29,36,48,.3); }
81
+ #list .head { background: #ffb81c; color: #1d2430; font-weight: 900; font-size: 46px; padding: 26px 44px; border-radius: 36px 36px 0 0; }
82
+ .item { display: flex; align-items: center; gap: 28px; padding: 26px 44px 0; font-weight: 800; font-size: 46px; line-height: 1.15; opacity: 0; }
83
+ .item b { flex: none; width: 76px; height: 76px; border-radius: 50%; border: 6px solid #138a8a; color: #138a8a; display: grid; place-items: center; font-size: 40px; font-weight: 900; }
84
+
85
+ /* Ending: a swinging warning sign. */
86
+ .end { position: absolute; left: 0; right: 0; top: 0; bottom: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; opacity: 0; }
87
+ .end .hang { display: flex; flex-direction: column; align-items: center; transform-origin: 50% 0; }
88
+ .end .sign { width: 520px; height: 460px; }
89
+ .end h1 { margin-top: 20px; background: #fff; border-radius: 30px; padding: 10px 46px; font-weight: 900; font-size: 160px; color: #e0352b; white-space: nowrap;
90
+ box-shadow: 0 24px 60px rgba(29,36,48,.3); }
91
+ </style>
92
+ </head>
93
+ <body>
94
+ <div id="root" data-composition-id="main" data-start="0" data-duration="{{DURATION}}" data-width="1080" data-height="1920">
95
+ <div id="paper" class="fill"></div>
96
+ <div id="cam">
97
+ <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="{&quot;version&quot;: 1, &quot;nodes&quot;: [{&quot;type&quot;: &quot;highpass&quot;, &quot;id&quot;: &quot;n1&quot;, &quot;label&quot;: &quot;Remove Rumble&quot;, &quot;params&quot;: {&quot;frequency&quot;: 90, &quot;q&quot;: 0.707, &quot;poles&quot;: &quot;2&quot;}}, {&quot;type&quot;: &quot;compressor&quot;, &quot;id&quot;: &quot;n2&quot;, &quot;label&quot;: &quot;Even Out Loudness&quot;, &quot;params&quot;: {&quot;threshold&quot;: -26, &quot;ratio&quot;: 3, &quot;attack&quot;: 10, &quot;release&quot;: 200, &quot;knee&quot;: 2.83, &quot;makeup&quot;: 6, &quot;mix&quot;: 1}}, {&quot;type&quot;: &quot;gain&quot;, &quot;id&quot;: &quot;n3&quot;, &quot;label&quot;: &quot;Level Up&quot;, &quot;params&quot;: {&quot;gain&quot;: 5}}, {&quot;type&quot;: &quot;truepeak&quot;, &quot;id&quot;: &quot;n4&quot;, &quot;label&quot;: &quot;Peak Ceiling&quot;, &quot;params&quot;: {&quot;ceiling&quot;: -1.5, &quot;lookahead&quot;: 3, &quot;release&quot;: 80}}]}"></video>
98
+ <div id="wash" class="fill"></div>
99
+ </div>
100
+
101
+ <div id="hook">
102
+ <svg class="sign" viewBox="0 0 120 106"><path d="M60 6 L114 100 H6 Z" fill="#ffb81c" stroke="#1d2430" stroke-width="8" stroke-linejoin="round"/><rect x="54" y="36" width="12" height="36" rx="5" fill="#1d2430"/><circle cx="60" cy="84" r="7" fill="#1d2430"/></svg>
103
+ <div class="line"><span><h1 class="l1">{{HOOK_1}}</h1></span></div>
104
+ <div class="line"><span><h1 class="l2">{{HOOK_2}}</h1></span></div>
105
+ <p>{{HOOK_SUB}}</p>
106
+ </div>
107
+ <div id="beats"></div>
108
+ <div id="list"></div>
109
+ <div id="captions"></div>
110
+
111
+ <div id="band-b" class="band"></div>
112
+ <div id="band-a" class="band"></div>
113
+
114
+ <audio id="music" src="assets/music.wav" data-start="0" data-duration="{{DURATION}}" data-track-index="2" data-volume="{{MUSIC_VOLUME}}"></audio>
115
+ <!-- SFX:BEGIN --><!-- SFX:END -->
116
+ </div>
117
+
118
+ <script>
119
+ const END = {{DURATION}}, HOOK = 3.0;
120
+ const tl = gsap.timeline({ paused: true });
121
+ const el = (tag, cls, html) => { const e = document.createElement(tag); if (cls) e.className = cls; if (html) e.innerHTML = html; return e; };
122
+ const SIGN = document.querySelector("#hook .sign").outerHTML;
123
+
124
+ // Jump cuts: a small settle-in on each new segment instead of alternating framing.
125
+ SEG_STARTS.forEach((s, i) => tl.fromTo("#cam", { scale: 1.035 }, { scale: 1, duration: 0.35, ease: "power2.out", immediateRender: i === 0 }, s));
126
+
127
+ // Swipe: amber + navy bands cross the frame, covering the card's arrival.
128
+ gsap.set(".band", { skewX: -14, x: -1700 });
129
+ const swipe = t => {
130
+ tl.fromTo("#band-b", { x: -1700 }, { x: 1900, duration: 0.5, ease: "power2.inOut", immediateRender: false }, t - 0.2);
131
+ tl.fromTo("#band-a", { x: -1700 }, { x: 1900, duration: 0.5, ease: "power2.inOut", immediateRender: false }, t - 0.16);
132
+ };
133
+ // Step back: the camera shrinks onto the grid paper with rounded corners while a card is up.
134
+ const stepBack = (t, dur) => {
135
+ tl.to("#cam", { scale: 0.84, y: -40, borderRadius: 56, duration: 0.45, ease: "power3.inOut" }, t - 0.1);
136
+ tl.to("#wash", { opacity: 0.5, duration: 0.45 }, t - 0.1);
137
+ if (t + dur < END) {
138
+ tl.to("#cam", { scale: 1, y: 0, borderRadius: 0, duration: 0.4, ease: "power3.inOut" }, t + dur - 0.35);
139
+ tl.to("#wash", { opacity: 0, duration: 0.4 }, t + dur - 0.35);
140
+ }
141
+ };
142
+
143
+ // Shrink any headline wider than the safe area.
144
+ const fit = (n, max = 820) => n.querySelectorAll("h1").forEach(h => { const w = h.scrollWidth; if (w > max) h.style.fontSize = Math.floor(parseFloat(getComputedStyle(h).fontSize) * max / w) + "px"; });
145
+
146
+ // Hook: card pops, sign bounces, lines rise out of their masks.
147
+ fit(document.getElementById("hook"), 780);
148
+ tl.fromTo("#hook", { opacity: 0, y: 60, scale: 0.9 }, { opacity: 1, y: 0, scale: 1, duration: 0.45, ease: "back.out(1.6)" }, 0.05);
149
+ tl.fromTo("#hook .sign", { scale: 0, rotation: -30 }, { scale: 1, rotation: 0, duration: 0.55, ease: "elastic.out(1, 0.5)" }, 0.2);
150
+ tl.fromTo("#hook .line > span", { yPercent: 105 }, { yPercent: 0, duration: 0.45, ease: "power3.out", stagger: 0.14 }, 0.35);
151
+ tl.fromTo("#hook p", { opacity: 0, scale: 0.6 }, { opacity: 1, scale: 1, duration: 0.3, ease: "back.out(2)" }, 0.8);
152
+ tl.to("#hook", { opacity: 0, y: -40, duration: 0.3 }, HOOK - 0.3);
153
+
154
+ const beats = document.getElementById("beats"), list = document.getElementById("list");
155
+ let listStart = null;
156
+ BEATS.forEach(b => {
157
+ if (b.kind === "end") {
158
+ const n = el("div", "end", `<div class="hang">${SIGN}<h1>${b.title}</h1></div>`);
159
+ beats.append(n); fit(n, 900);
160
+ stepBack(b.t, END - b.t); swipe(b.t);
161
+ tl.to("#wash", { opacity: 1, duration: 0.4 }, b.t);
162
+ tl.set(n, { opacity: 1 }, b.t);
163
+ const hang = n.querySelector(".hang");
164
+ tl.fromTo(hang, { y: -1400 }, { y: 0, duration: 0.5, ease: "power3.in" }, b.t);
165
+ // Swing settles like a hanging sign, landing with the chord hit at +1.2 s.
166
+ [16, -11, 7, -4, 2, 0].forEach((r, j) => tl.to(hang, { rotation: r, duration: 0.24, ease: "sine.inOut" }, b.t + 0.5 + j * 0.24));
167
+ } else if (b.kind === "title") {
168
+ const d = 1.8;
169
+ const n = el("div", "title", `<div class="hlw"><div class="hl"></div><h1>${b.title}</h1></div>` + (b.sub ? `<p>${b.sub}</p>` : ""));
170
+ beats.append(n); fit(n); swipe(b.t); stepBack(b.t, d);
171
+ tl.fromTo(n, { opacity: 0, y: 40 }, { opacity: 1, y: 0, duration: 0.35, ease: "power3.out" }, b.t + 0.05);
172
+ tl.fromTo(n.querySelector(".hl"), { scaleX: 0 }, { scaleX: 1, duration: 0.4, ease: "power2.out" }, b.t + 0.3);
173
+ if (b.sub) tl.fromTo(n.querySelector("p"), { opacity: 0, x: -40 }, { opacity: 1, x: 0, duration: 0.3, ease: "power2.out" }, b.t + 0.45);
174
+ tl.to(n, { opacity: 0, y: -30, duration: 0.25 }, b.t + d - 0.25);
175
+ } else if (b.kind === "case") {
176
+ const d = 3.2;
177
+ const n = el("div", "case", `<div class="k">${b.kicker}</div><h2>${b.title}</h2>` + (b.sub ? `<div class="s">${b.sub}</div>` : "<div class='s'></div>") +
178
+ (b.stamp ? `<div class="badge" data-layout-allow-overlap>${b.stamp}</div>` : ""));
179
+ beats.append(n); swipe(b.t); stepBack(b.t, d);
180
+ // Card flips up from its top edge like a flash card.
181
+ tl.fromTo(n, { opacity: 0, rotationX: -80, transformPerspective: 1400 }, { opacity: 1, rotationX: 0, duration: 0.6, ease: "back.out(1.3)" }, b.t + 0.05);
182
+ const bd = n.querySelector(".badge");
183
+ if (bd) tl.fromTo(bd, { opacity: 0, scale: 0, rotation: 40 }, { opacity: 1, scale: 1, rotation: 12, duration: 0.4, ease: "back.out(2.5)" }, b.t + 0.95);
184
+ tl.to(n, { opacity: 0, rotationX: 70, duration: 0.3, ease: "power2.in" }, b.t + d - 0.3);
185
+ } else if (b.kind === "photo") {
186
+ const d = b.dur ?? 2.8;
187
+ const n = el("div", "photo", `<div class="tag" data-layout-allow-overlap>CONTOH</div><div class="frame"><img src="assets/img/${b.img}" alt="" data-layout-allow-overflow>` +
188
+ `<svg class="ring" viewBox="0 0 100 100" preserveAspectRatio="none"><ellipse cx="${b.cx ?? 50}" cy="${b.cy ?? 50}" rx="${b.rx ?? 30}" ry="${b.ry ?? 26}" fill="none" stroke="#e0352b" stroke-width="1.6" pathLength="100" stroke-dasharray="100" stroke-dashoffset="100"/></svg></div><p>${b.label}</p>`);
189
+ if (b.pos) n.querySelector(".frame img").style.objectPosition = b.pos;
190
+ beats.append(n); swipe(b.t); stepBack(b.t, d);
191
+ tl.fromTo(n, { opacity: 0, scale: 0.6, rotation: -8 }, { opacity: 1, scale: 1, rotation: -1.5, duration: 0.5, ease: "back.out(1.5)" }, b.t + 0.05);
192
+ tl.fromTo(n.querySelector(".tag"), { scale: 0 }, { scale: 1, duration: 0.3, ease: "back.out(3)" }, b.t + 0.4);
193
+ tl.fromTo(n.querySelector("ellipse"), { attr: { "stroke-dashoffset": 100 } }, { attr: { "stroke-dashoffset": 0 }, duration: 0.6, ease: "power1.inOut" }, b.t + 0.6);
194
+ tl.to(n, { opacity: 0, y: 60, duration: 0.3 }, b.t + d - 0.3);
195
+ } else {
196
+ if (listStart === null) listStart = b.t;
197
+ const n = el("div", "item", `<b>${list.querySelectorAll(".item").length + 1}</b><span>${b.text}</span>`);
198
+ list.append(n);
199
+ tl.fromTo(n, { opacity: 0, x: -80, filter: "blur(8px)" }, { opacity: 1, x: 0, filter: "blur(0px)", duration: 0.45, ease: "power3.out" }, b.t);
200
+ tl.fromTo(n.querySelector("b"), { backgroundColor: "#ffffff", color: "#138a8a" }, { backgroundColor: "#138a8a", color: "#ffffff", duration: 0.2 }, b.t + 0.3);
201
+ }
202
+ });
203
+ // Checklist: stays until the next jump cut after the last item (max 6 s).
204
+ const items = BEATS.filter(b => b.kind === "item"), lastItem = items.length ? items[items.length - 1].t : 0;
205
+ const listEnd = Math.min(SEG_STARTS.find(s => s > lastItem + 1) ?? END, lastItem + 6);
206
+ if (items.length) {
207
+ list.prepend(el("div", "head", window.LIST_TITLE ?? "SEBELUM BELI, INGAT:"));
208
+ swipe(listStart - 0.6); stepBack(listStart - 0.6, listEnd - listStart + 0.6);
209
+ tl.fromTo("#list", { opacity: 0, y: 80 }, { opacity: 1, y: 0, duration: 0.45, ease: "back.out(1.4)" }, listStart - 0.55);
210
+ tl.to("#list", { opacity: 0, y: -40, duration: 0.35 }, listEnd - 0.35);
211
+ }
212
+
213
+ // Captions (hidden while a card or the list is up).
214
+ const busy = BEATS.filter(b => b.kind !== "item").map(b => [b.t, b.t + (b.kind === "case" ? 3.2 : b.kind === "end" ? END : b.kind === "photo" ? b.dur ?? 2.8 : 1.8)])
215
+ .concat(items.length ? [[listStart - 0.6, listEnd]] : []);
216
+ const cap = document.getElementById("captions");
217
+ CAPTIONS.forEach(chunk => {
218
+ const n = el("div", "chunk"), box = el("div", "box"); n.append(box); cap.append(n);
219
+ const start = chunk[0].s, end = chunk[chunk.length - 1].hold;
220
+ if (busy.some(([a, b]) => start < b && end > a)) return;
221
+ tl.fromTo(n, { opacity: 0, scale: 0.92 }, { opacity: 1, scale: 1, duration: 0.14, ease: "back.out(2)" }, start);
222
+ tl.set(n, { opacity: 0 }, end);
223
+ chunk.forEach((w, j) => {
224
+ const s = el("span", "w"); s.textContent = w.w.replace(/[,.?!]+$/, ""); box.append(s);
225
+ const until = chunk[j + 1] ? chunk[j + 1].s : end;
226
+ const t0 = Math.max(start, w.s - 1 / 30);
227
+ // Grey upcoming, amber marker while spoken, dark once said.
228
+ tl.set(s, { backgroundColor: "#ffd56b", color: "#1d2430" }, t0);
229
+ tl.set(s, { backgroundColor: "rgba(0,0,0,0)" }, until);
230
+ });
231
+ });
232
+
233
+ window.__timelines["main"] = tl;
234
+ </script>
235
+ </body>
236
+ </html>
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "educational-warning-video",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "npx --yes hyperframes@0.8.176 preview",
7
+ "check": "npx --yes hyperframes@0.8.176 check"
8
+ },
9
+ "devDependencies": { "@hyperframes/core": "0.8.176" }
10
+ }
@@ -0,0 +1 @@
1
+ Fonts: "Playfair Display" by Claus Eggers Sørensen, "Inter" by Rasmus Andersson (SIL Open Font License 1.1 (Google Fonts)).
@@ -0,0 +1,2 @@
1
+ # film-cinematic-drama
2
+ Pitch opener, brand film. Dark, full-bleed photos with a slow push-in, rise text, grain and vignette, fades. Quiet music, a hit on the reveal, a riser into the question. Subtitle-bar captions, -16 LUFS. Script: full story arc, 70-110 s. Fields: see `style.json` and `references/presets.md` (Film presets).
@@ -0,0 +1,39 @@
1
+ {
2
+ "palette": {
3
+ "bg": "#14110F",
4
+ "fg": "#F3EDE3",
5
+ "muted": "#B8AFA3",
6
+ "accent": "#C9A24E",
7
+ "accentBg": "#C9A96E"
8
+ },
9
+ "fonts": {
10
+ "display": "Playfair Display",
11
+ "body": "Inter"
12
+ },
13
+ "pace": {
14
+ "lead": 0.8,
15
+ "tail": 1.1
16
+ },
17
+ "text": "rise",
18
+ "transition": "fade",
19
+ "layout": "fullbleed",
20
+ "grade": {
21
+ "grain": 0.07,
22
+ "vignette": 0.55,
23
+ "leak": false
24
+ },
25
+ "captions": "subtitle",
26
+ "music": {
27
+ "base": 0.2,
28
+ "lift": 0.32,
29
+ "track": "music.mp3"
30
+ },
31
+ "sfx": {
32
+ "cut": "",
33
+ "text": "",
34
+ "reveal": "hit",
35
+ "question": "riser"
36
+ },
37
+ "kenburns": 0.08,
38
+ "loudness": -16
39
+ }
@@ -0,0 +1 @@
1
+ Fonts: "Fraunces" by Undercase Type, "Inter" by Rasmus Andersson (SIL Open Font License 1.1 (Google Fonts)).
@@ -0,0 +1,2 @@
1
+ # film-fun-energetic
2
+ Social posts, events. Cream background, rounded photo cards that bounce in (16:9), pop text, light leaks, pops and a chime. Subtitle-bar captions, -14 LUFS. Script: 35-50 s, playful lines. Note: the accent orange on cream is below 3:1 contrast; `hyperframes check` warns, change `brand.palette.accent` for stricter needs.
@@ -0,0 +1,39 @@
1
+ {
2
+ "palette": {
3
+ "bg": "#FFF6E8",
4
+ "fg": "#1E1A17",
5
+ "muted": "#5A524A",
6
+ "accent": "#E8833A",
7
+ "accentBg": "#F4C95D"
8
+ },
9
+ "fonts": {
10
+ "display": "Fraunces",
11
+ "body": "Inter"
12
+ },
13
+ "pace": {
14
+ "lead": 0.2,
15
+ "tail": 0.35
16
+ },
17
+ "text": "pop",
18
+ "transition": "punch",
19
+ "layout": "card",
20
+ "grade": {
21
+ "grain": 0.0,
22
+ "vignette": 0.0,
23
+ "leak": true
24
+ },
25
+ "captions": "subtitle",
26
+ "music": {
27
+ "base": 0.38,
28
+ "lift": 0.5,
29
+ "track": "music.mp3"
30
+ },
31
+ "sfx": {
32
+ "cut": "pop",
33
+ "text": "pop",
34
+ "reveal": "chime",
35
+ "question": "pop"
36
+ },
37
+ "kenburns": 0.1,
38
+ "loudness": -14
39
+ }
@@ -0,0 +1 @@
1
+ Fonts: "Playfair Display" by Claus Eggers Sørensen, "Inter" by Rasmus Andersson (SIL Open Font License 1.1 (Google Fonts)).
@@ -0,0 +1,2 @@
1
+ # film-promo-booth
2
+ Exhibition loop, deck-faithful promo. Photo in a framed window beside the text (16:9; full-bleed in 9:16), wipes, slide-in text, staged builds, whoosh on cuts, tick on titles. Subtitle-bar captions, -16 LUFS. Script: the deck's beats in order, 80-100 s.
@@ -0,0 +1,39 @@
1
+ {
2
+ "palette": {
3
+ "bg": "#14110F",
4
+ "fg": "#F3EDE3",
5
+ "muted": "#B8AFA3",
6
+ "accent": "#C9A24E",
7
+ "accentBg": "#C9A96E"
8
+ },
9
+ "fonts": {
10
+ "display": "Playfair Display",
11
+ "body": "Inter"
12
+ },
13
+ "pace": {
14
+ "lead": 0.45,
15
+ "tail": 0.7
16
+ },
17
+ "text": "slide",
18
+ "transition": "wipe",
19
+ "layout": "window",
20
+ "grade": {
21
+ "grain": 0.04,
22
+ "vignette": 0.35,
23
+ "leak": false
24
+ },
25
+ "captions": "subtitle",
26
+ "music": {
27
+ "base": 0.28,
28
+ "lift": 0.4,
29
+ "track": "music.mp3"
30
+ },
31
+ "sfx": {
32
+ "cut": "whoosh",
33
+ "text": "tick",
34
+ "reveal": "hit",
35
+ "question": "riser"
36
+ },
37
+ "kenburns": 0.05,
38
+ "loudness": -16
39
+ }
@@ -0,0 +1 @@
1
+ Fonts: "Inter" by Rasmus Andersson (SIL Open Font License 1.1 (Google Fonts)).
@@ -0,0 +1,2 @@
1
+ # film-viral-social
2
+ TikTok / Reels / Shorts. Full-bleed, fast punch-in cuts, bold Inter, strong Ken Burns, kinetic 2-3 word captions with the spoken word in the accent colour, whooshes. -14 LUFS. Script: 30 s or less, line 1 is the hook.
@@ -0,0 +1,39 @@
1
+ {
2
+ "palette": {
3
+ "bg": "#0E0C0B",
4
+ "fg": "#FFFFFF",
5
+ "muted": "#D6CEC4",
6
+ "accent": "#F2C14E",
7
+ "accentBg": "#F2C14E"
8
+ },
9
+ "fonts": {
10
+ "display": "Inter",
11
+ "body": "Inter"
12
+ },
13
+ "pace": {
14
+ "lead": 0.05,
15
+ "tail": 0.2
16
+ },
17
+ "text": "pop",
18
+ "transition": "punch",
19
+ "layout": "fullbleed",
20
+ "grade": {
21
+ "grain": 0.03,
22
+ "vignette": 0.45,
23
+ "leak": false
24
+ },
25
+ "captions": "kinetic",
26
+ "music": {
27
+ "base": 0.3,
28
+ "lift": 0.42,
29
+ "track": "music.mp3"
30
+ },
31
+ "sfx": {
32
+ "cut": "whoosh",
33
+ "text": "",
34
+ "reveal": "hit",
35
+ "question": "riser"
36
+ },
37
+ "kenburns": 0.14,
38
+ "loudness": -14
39
+ }
@@ -0,0 +1,3 @@
1
+ Sound effects: shutter.wav, stamp.wav, reveal.wav, ending.wav, ding.wav, whoosh.wav, pop.wav, chime.wav and boing.wav synthesized in Python (marimba, glockenspiel and bell partials with pitch envelopes), CC0.
2
+
3
+ Fonts: "Fredoka" by Milena Brandao and Ben Nathan (Google Fonts, SIL Open Font License 1.1); "Montserrat" by Julieta Ulanovsky et al. (Google Fonts, SIL Open Font License 1.1).
@@ -0,0 +1,35 @@
1
+ # TikTok cheerful preset (`tiktok-cheerful`)
2
+
3
+ A 1080×1920 HyperFrames style for talking-head explainers with a warm, upbeat **Sunny Pop Explainer** tone.
4
+
5
+ ## Look and motion
6
+
7
+ - **Cold open plus hook:** an optional replay of one strong line opens the video. The two-line hook title (cream-white and sun-yellow `#FFD23F` in rounded Fredoka display type, with a sun-yellow pill subtitle) bounces in above the speaker's face while karaoke captions run underneath.
8
+ - **Grade:** warm golden sunlit grade (`contrast(1.06) saturate(1.18) brightness(1.05)`), soft peach/gold screen glow, and a gentle warm edge vignette — no horror grain or light flicker.
9
+ - **Jump cuts:** framing alternates between 1.0× and a 1.12× punch-in at every cut, with a smooth push-in inside each segment.
10
+ - **Captions:** 3-word chunks in a karaoke fill using rounded `Fredoka` typography. Each word starts warm grey (`#D8D0C5`), sweeps sun-yellow (`#FFD23F`) left to right with a bouncy pop over its spoken duration, and settles to cream-white (`#FFFDF5`) once the next word starts.
11
+ - **Cards**, which enter with a playful sparkle-burst pop transition (`#burst` corner stars + dashed ring + soft warm flash) and bouncy `back.out` spring motion:
12
+ - `title`: big sun-yellow rounded display word with a cream sticker-pill subtitle and an optional vibrant framed photo above.
13
+ - `case`: a cream sticker card (`#FFFDF5`) with thick dark-ink (`#1A1615`) borders, sun-yellow pill kicker, optional full-color photo, and a cherry-coral badge stamp that bounces in.
14
+ - `photo`: a floating cream-bordered polaroid card with full-color imagery and a sun-yellow pill caption.
15
+ - `item`: a trivia checklist entry under a sun-yellow `"KEMUNGKINAN:"` pill header. Each entry is a cream sticker bar with a spinning `★` sun-yellow badge and smooth text reveal.
16
+ - `end`: alternating sun-yellow and cream-white display words popping in with a bouncy `back.out` stagger and gentle float.
17
+ - **Layout:** content is centered with equal side margins (captions 110 px, cards 125 px). Titles shrink to fit 840 px via `fit()`.
18
+
19
+ ## Audio
20
+
21
+ - **Music:** `assets/music/sunny.mp3` ("Carefree" by Kevin MacLeod via Wikimedia Commons / incompetech.com, CC BY 4.0). Carved around the voice with `tools/carve.mjs` from `hyperframes-audio`.
22
+ - **Voice:** `afftdn` denoise and de-esser during source processing, then highpass, compressor, gain, and true-peak ceiling at −1.5 dBTP inside the composition.
23
+ - **Sound effects** in `assets/sfx/`:
24
+ - Automatic: `shutter` (bouncy pop-swoosh on every card), `stamp` (badge pop/chime), `reveal` (upbeat marimba/chime arpeggio for checklist items), and `ending` (bright major-chord fanfare sting).
25
+ - Usable as reactions in `config.json`: `pop`, `chime`, `boing`, `ding`, `whoosh`.
26
+
27
+ ## Usage
28
+
29
+ Set `"preset": "tiktok-cheerful"` and `"music": "sunny"` in `videos/<name>/config.json`, then run:
30
+
31
+ ```bash
32
+ python3 <skill>/scripts/make_tiktok.py videos/<name> --render
33
+ ```
34
+
35
+ The rendered video lands at `output/<name>/tiktok-cheerful/<name>.mp4` with `CREDITS.txt` beside it.