@koda-sl/baker-cli 0.128.0-dev.70bf43ce4 → 0.131.0-dev.597b50181

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.
@@ -112,7 +112,7 @@
112
112
 
113
113
  group.forEach((word, wi) => {
114
114
  const span = document.getElementById('w_' + gi + '_' + wi);
115
- tl.set(span, { className: '+=active-word' }, word.start);
115
+ tl.set(span, { className: 'active-word' }, word.start);
116
116
  });
117
117
 
118
118
  tl.fromTo(el,
@@ -81,6 +81,27 @@
81
81
  after another (e.g. coverage chips appearing as each is spoken), not as a clump. */
82
82
  .chips { display: flex; flex-wrap: wrap; gap: 14px; justify-content: center; max-width: 92%; }
83
83
 
84
+ /* BUILT-IN CAPTION TRACK (renders only when a transcript.json is wired by the
85
+ scaffold — the merged overlay+captions pass). Restyle freely; the karaoke
86
+ grouping/sync is driven by the script below off the word-level transcript. */
87
+ .caption-container {
88
+ position: absolute; bottom: 320px; left: 0; right: 0;
89
+ display: flex; align-items: center; justify-content: center;
90
+ padding: 0 60px; pointer-events: none;
91
+ }
92
+ .caption-group {
93
+ position: absolute;
94
+ font-family: 'BrandFont', 'Arial Black', 'Helvetica Neue', sans-serif;
95
+ font-size: {{font_size}}px; font-weight: 900; color: {{text_color}};
96
+ text-align: center; text-transform: uppercase; line-height: 1.2;
97
+ text-shadow:
98
+ 2px 2px 0 rgba(0,0,0,0.9), -2px -2px 0 rgba(0,0,0,0.9),
99
+ 2px -2px 0 rgba(0,0,0,0.9), -2px 2px 0 rgba(0,0,0,0.9),
100
+ 0 4px 8px rgba(0,0,0,0.6);
101
+ visibility: hidden;
102
+ }
103
+ .caption-group .active-word { color: {{highlight_color}}; }
104
+
84
105
  /* 9-grid position helpers (absolute). Tweak the insets or add your own. */
85
106
  .pos-top-left { top: 90px; left: 56px; text-align: left; }
86
107
  .pos-top-center { top: 90px; left: 50%; transform: translateX(-50%); }
@@ -116,17 +137,20 @@
116
137
  data-start, data-duration and data-track-index. See
117
138
  references/hyperframes/blueprints-and-transitions.md. -->
118
139
 
119
- <!-- CAPTION TRACK SLOT (optional, recommended for sound-off feeds): the feed
120
- plays muted, so burn in captions from the word-level transcript the
121
- deconstruct produced. Floor: `npx hyperframes add caption-highlight`
122
- (TikTok karaoke); escalate by register (caption-kinetic-slam, etc.). The
123
- scaffold already wires a default caption track as a separate composition;
140
+ <!-- CAPTION TRACK: built in below (#captions renders the word-synced karaoke
141
+ track whenever the scaffold wires a transcript.json same render pass).
142
+ To escalate the register (caption-kinetic-slam, caption-editorial-emphasis,
143
+ caption-neon-glow) pull a block: `npx hyperframes add <id>` and restyle;
124
144
  see references/hyperframes/captions-and-audio.md for the sync contract. -->
125
145
  </div>
146
+ <!-- Captions live OUTSIDE #overlay-root on purpose: the generic overlay runtime
147
+ animates every [data-start] element under #overlay-root, and the caption
148
+ groups carry their own karaoke timing — nesting them would double-animate. -->
149
+ <div class="caption-container" id="captions" data-layout-allow-occlusion></div>
126
150
  </div>
127
151
 
128
152
  <script>
129
- (() => {
153
+ (async () => {
130
154
  const DURATION = parseFloat('{{duration}}');
131
155
  const tl = gsap.timeline({ paused: true });
132
156
  const els = Array.from(document.querySelectorAll('#overlay-root [data-start]'));
@@ -163,6 +187,54 @@
163
187
  if (tx) el.style.transform = tx;
164
188
  }
165
189
 
190
+ // BUILT-IN CAPTION TRACK — active only when the scaffold staged a transcript
191
+ // (the merged overlay+captions pass). No transcript.json ⇒ captions skip
192
+ // silently and this composition is a pure overlay layer.
193
+ const transcript = await fetch('./transcript.json')
194
+ .then((r) => (r.ok ? r.json() : null))
195
+ .catch(() => null);
196
+ if (Array.isArray(transcript) && transcript.length > 0) {
197
+ const WORDS_PER_GROUP = parseInt('{{words_per_group}}', 10) || 3;
198
+ const container = document.getElementById('captions');
199
+ const groups = [];
200
+ for (let i = 0; i < transcript.length; i += WORDS_PER_GROUP) {
201
+ groups.push(transcript.slice(i, i + WORDS_PER_GROUP));
202
+ }
203
+ groups.forEach((group, gi) => {
204
+ const el = document.createElement('div');
205
+ // `clip` marks this as a hyperframes-timed element (this same GSAP
206
+ // timeline drives its show/hide); without it the pre-render gate flags
207
+ // every group as visible for the whole composition.
208
+ el.className = 'caption-group clip';
209
+ el.id = 'cg_' + gi;
210
+ el.setAttribute('data-start', String(group[0].start));
211
+ el.setAttribute('data-duration', String((group[group.length - 1].end - group[0].start) || 0.5));
212
+ group.forEach((word, wi) => {
213
+ const span = document.createElement('span');
214
+ span.id = 'w_' + gi + '_' + wi;
215
+ span.textContent = word.text + ' ';
216
+ el.appendChild(span);
217
+ });
218
+ container.appendChild(el);
219
+
220
+ const groupStart = group[0].start;
221
+ const groupEnd = group[group.length - 1].end;
222
+ gsap.set(el, { visibility: 'hidden' });
223
+ tl.set(el, { visibility: 'visible' }, groupStart);
224
+ group.forEach((word, wi) => {
225
+ const span = document.getElementById('w_' + gi + '_' + wi);
226
+ tl.set(span, { className: 'active-word' }, word.start);
227
+ });
228
+ tl.fromTo(
229
+ el,
230
+ { scale: 0.8, opacity: 0 },
231
+ { scale: 1, opacity: 1, duration: 0.1, ease: 'back.out(1.5)' },
232
+ groupStart
233
+ );
234
+ tl.set(el, { visibility: 'hidden' }, groupEnd + 0.05);
235
+ });
236
+ }
237
+
166
238
  tl.to({}, { duration: 0.01 }, Math.max(0.01, DURATION - 0.01));
167
239
  window.__timelines = window.__timelines || {};
168
240
  window.__timelines.main = tl;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "video-overlay",
3
3
  "title": "Overlay layer: agent-authored HTML/CSS composited over a video",
4
- "description": "A paint-it-yourself overlay layer, NOT a prop renderer. The scaffold seeds #overlay-root in index.html with the reference ad's overlays as real, editable HTML elements (text + a position class + data-start/data-dur timing); a tiny generic runtime only shows/hides each element at its timestamp (with an optional data-anim entrance). Every styling decision bars, tickers, colors, fonts, a real logo <img> dropped into this dir lives in the HTML/CSS you edit, not in props. Drop brand-bold.otf / brand-regular.otf for on-brand type. Fixed 1080x1920; copy + edit width/height for other ratios.",
4
+ "description": "A paint-it-yourself overlay layer, NOT a prop renderer. The scaffold seeds #overlay-root in index.html with the reference ad's overlays as real, editable HTML elements (text + a position class + data-start/data-dur timing); a tiny generic runtime only shows/hides each element at its timestamp (with an optional data-anim entrance). Every styling decision \u2014 bars, tickers, colors, fonts, a real logo <img> dropped into this dir \u2014 lives in the HTML/CSS you edit, not in props. Drop brand-bold.otf / brand-regular.otf for on-brand type. Fixed 1080x1920; copy + edit width/height for other ratios. When speech is present the scaffold also wires the optional `transcript` input, and this composition renders the word-synced caption track in the SAME pass (one headless render, one less encode) \u2014 captions skip silently when no transcript is wired.",
5
5
  "width": 1080,
6
6
  "height": 1920,
7
7
  "fps": 30,
@@ -12,7 +12,35 @@
12
12
  "required": true,
13
13
  "staged_as": "background.mp4",
14
14
  "description": "The base video to composite overlays onto (e.g. the concatenated reproduction clips)."
15
+ },
16
+ "transcript": {
17
+ "kind": "json",
18
+ "required": false,
19
+ "staged_as": "transcript.json",
20
+ "description": "Optional word-level transcript JSON from video_transcribe: [{text, start, end}, ...]. When wired, the built-in TikTok-style caption track renders in this same pass."
15
21
  }
16
22
  },
17
- "params": {}
23
+ "params": {
24
+ "words_per_group": {
25
+ "kind": "integer",
26
+ "min": 1,
27
+ "max": 8,
28
+ "default": 3,
29
+ "description": "How many caption words to show at once."
30
+ },
31
+ "font_size": {
32
+ "kind": "integer",
33
+ "min": 24,
34
+ "max": 200,
35
+ "default": 72
36
+ },
37
+ "text_color": {
38
+ "kind": "color",
39
+ "default": "#ffffff"
40
+ },
41
+ "highlight_color": {
42
+ "kind": "color",
43
+ "default": "#FFD700"
44
+ }
45
+ }
18
46
  }