@amg555/frameforge-mcp 2.4.7 → 2.4.8
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amg555/frameforge-mcp",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.8",
|
|
4
4
|
"description": "FrameForge — AI video production engine for agentic IDEs. npx @amg555/frameforge-mcp. FFmpeg + TTS run on your machine. 21 MCP tools.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
package/scripts/deep-validate.js
CHANGED
|
@@ -20,7 +20,7 @@ check('batch handling', code.includes('Array.isArray(body)'));
|
|
|
20
20
|
check('ping handler', code.includes("method === 'ping'"));
|
|
21
21
|
check('notifications/init', code.includes("notifications/initialized"));
|
|
22
22
|
check('body size limit', code.includes('MAX_BODY_BYTES'));
|
|
23
|
-
check('SERVER_INFO version 2.4.
|
|
23
|
+
check('SERVER_INFO version 2.4.8', code.includes("version: '2.4.8'"));
|
|
24
24
|
check('18 tools defined (incl get_job_status)', code.includes('get_job_status'));
|
|
25
25
|
check('export_to_editor tool defined', code.includes('export_to_editor'));
|
|
26
26
|
check('edit_video tool defined', code.includes('edit_video'));
|
|
@@ -299,7 +299,7 @@ check('cf-worker has checkRateLimit', cfWorker.includes('checkRateLimit'));
|
|
|
299
299
|
check('cf-worker hashes token (SHA-256)', cfWorker.includes('SHA-256'));
|
|
300
300
|
check('cf-worker uses RATE_LIMITS KV', cfWorker.includes('env.RATE_LIMITS'));
|
|
301
301
|
check('cf-worker returns 429 on limit', cfWorker.includes('429'));
|
|
302
|
-
check('cf-worker version 2.4.
|
|
302
|
+
check('cf-worker version 2.4.8', cfWorker.includes("'2.4.8'"));
|
|
303
303
|
check('wrangler.toml has kv_namespaces',fs.readFileSync('./cloudflare-mcp/wrangler.toml','utf8').includes('kv_namespaces'));
|
|
304
304
|
|
|
305
305
|
// ── plugins/skills present ────────────────────────────────────────────────
|
|
@@ -23,6 +23,20 @@ export const VIRTUAL_CLOCK_INJECTION = `
|
|
|
23
23
|
|
|
24
24
|
// 1. Deterministic clocks
|
|
25
25
|
performance.now = () => _now;
|
|
26
|
+
const OriginalDate = window.Date;
|
|
27
|
+
class VirtualDate extends OriginalDate {
|
|
28
|
+
constructor(...args) {
|
|
29
|
+
if (args.length === 0) {
|
|
30
|
+
super(_startDate + _now);
|
|
31
|
+
} else {
|
|
32
|
+
super(...args);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
static now() {
|
|
36
|
+
return Math.floor(_startDate + _now);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
window.Date = VirtualDate;
|
|
26
40
|
Date.now = () => _startDate + _now;
|
|
27
41
|
|
|
28
42
|
// 2. Deterministic requestAnimationFrame
|
|
@@ -60,6 +74,9 @@ export const VIRTUAL_CLOCK_INJECTION = `
|
|
|
60
74
|
|
|
61
75
|
// 4. Deterministic Seeded PRNG fallback for unseeded Math.random()
|
|
62
76
|
let _seed = 0x2F6E2B1;
|
|
77
|
+
const resetSeed = (tSec) => {
|
|
78
|
+
_seed = (0x2F6E2B1 ^ Math.imul(Math.floor(tSec * 1000), 0x9E3779B9)) | 0;
|
|
79
|
+
};
|
|
63
80
|
window.Math.random = () => {
|
|
64
81
|
_seed = (_seed + 0x6D2B79F5) | 0;
|
|
65
82
|
let t = Math.imul(_seed ^ (_seed >>> 15), 1 | _seed);
|
|
@@ -84,16 +101,30 @@ export const VIRTUAL_CLOCK_INJECTION = `
|
|
|
84
101
|
currentRafs.forEach(cb => { try { cb(_now); } catch (_) {} });
|
|
85
102
|
|
|
86
103
|
try {
|
|
87
|
-
document.getAnimations
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
if (document.getAnimations) {
|
|
105
|
+
document.getAnimations().forEach(a => {
|
|
106
|
+
if (a.playState !== 'paused') a.pause();
|
|
107
|
+
a.currentTime = _now;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
91
110
|
} catch (_) {}
|
|
92
111
|
};
|
|
93
112
|
|
|
94
113
|
// 6. Absolute virtual seek
|
|
95
114
|
window.__seek = async (t) => {
|
|
96
115
|
_now = t * 1000;
|
|
116
|
+
resetSeed(t);
|
|
117
|
+
|
|
118
|
+
// Pause & seek CSS animations
|
|
119
|
+
try {
|
|
120
|
+
if (document.getAnimations) {
|
|
121
|
+
document.getAnimations().forEach(a => {
|
|
122
|
+
if (a.playState !== 'paused') a.pause();
|
|
123
|
+
a.currentTime = _now;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
} catch (_) {}
|
|
127
|
+
|
|
97
128
|
if (window.renderAt) {
|
|
98
129
|
await window.renderAt(t);
|
|
99
130
|
} else if (window.seek) {
|
package/src/mcp-server/server.js
CHANGED
|
@@ -148,6 +148,7 @@ class VideoRenderer {
|
|
|
148
148
|
voicePath,
|
|
149
149
|
caption = '',
|
|
150
150
|
narration = '',
|
|
151
|
+
prompt = '',
|
|
151
152
|
visualDescription = '',
|
|
152
153
|
visualType = 'code_animation',
|
|
153
154
|
topic = '',
|
|
@@ -277,6 +278,7 @@ class VideoRenderer {
|
|
|
277
278
|
voicePath,
|
|
278
279
|
caption,
|
|
279
280
|
narration,
|
|
281
|
+
prompt,
|
|
280
282
|
visualDescription,
|
|
281
283
|
visualType,
|
|
282
284
|
topic,
|
|
@@ -362,6 +364,7 @@ class VideoRenderer {
|
|
|
362
364
|
voicePath,
|
|
363
365
|
caption,
|
|
364
366
|
narration,
|
|
367
|
+
prompt = '',
|
|
365
368
|
visualDescription,
|
|
366
369
|
visualType = 'code_animation',
|
|
367
370
|
topic = '',
|
|
@@ -403,6 +406,7 @@ class VideoRenderer {
|
|
|
403
406
|
htmlContent = generateSceneHtml({
|
|
404
407
|
caption,
|
|
405
408
|
narration,
|
|
409
|
+
prompt,
|
|
406
410
|
visualDescription,
|
|
407
411
|
imagePath,
|
|
408
412
|
topic,
|
|
@@ -445,6 +449,19 @@ class VideoRenderer {
|
|
|
445
449
|
throw new Error(`Harness frame rendering failed: ${(res.stderr || res.stdout || '').slice(-400)}`);
|
|
446
450
|
}
|
|
447
451
|
|
|
452
|
+
// Closed-loop visual frame inspection (catches blank/blown frames before encoding)
|
|
453
|
+
try {
|
|
454
|
+
const { analyzeImageFile } = require('../harness/lib/pixels.mjs');
|
|
455
|
+
const midFrameIdx = Math.floor(lastFrame / 2);
|
|
456
|
+
const midFrameFile = path.join(framesDir, `f${String(midFrameIdx).padStart(6, '0')}.jpg`);
|
|
457
|
+
if (fs.existsSync(midFrameFile)) {
|
|
458
|
+
const analysis = analyzeImageFile(midFrameFile, renderWidth, renderHeight);
|
|
459
|
+
if (analysis.isBlank) {
|
|
460
|
+
console.warn(`[Renderer] ⚠️ Scene ${sceneIndex} visual warning: blank frame at t=${(midFrameIdx / fps).toFixed(2)}s`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
} catch (_) {}
|
|
464
|
+
|
|
448
465
|
const framesPattern = path.join(framesDir, 'f%06d.jpg');
|
|
449
466
|
const encResult = spawnSync('ffmpeg', [
|
|
450
467
|
'-nostdin', '-y', '-threads', '2',
|
|
@@ -343,7 +343,25 @@ function detectComponentTheme({ visualDescription = '', visual_description = '',
|
|
|
343
343
|
return 'doodle_dressing';
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
// 10.
|
|
346
|
+
// 10. Map / Migration / Ancient World / Out of Africa (Scene 10)
|
|
347
|
+
if (/(?:^|[^\w])(migration|migrating|migrate|map with africa|africa highlighted|colder regions|climate illustrations|out of africa|geographic migration|migration arrows|continent map|world map)(?:[^\w]|$)/i.test(text)
|
|
348
|
+
|| /കുടിയേറുന്നതിന്|കുടിയേറ്റം|ആഫ്രിക്ക|ഭൂപടത്തിൽ ആഫ്രിക്ക|മൈഗ്രേഷൻ|തണുപ്പുള്ള പ്രദേശങ്ങളിലേക്ക് കുടിയേറാൻ|ഭൂപടം|ഭൂഖണ്ഡം/.test(text)) {
|
|
349
|
+
return 'doodle_map_migration';
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// 11. Royal Robes / Social Status / Ornaments / Crown / Wealth & Authority (Scene 16)
|
|
353
|
+
if (/(?:^|[^\w])(royal robe|royal robes|crown|sceptre|ornaments|status and power|hierarchy|wealth authority|noble clothing|decorated garments)(?:[^\w]|$)/i.test(text)
|
|
354
|
+
|| /സമ്പത്തിന്റെയും അധികാരത്തിന്റെയും|രാജകീയ|കിരീടം|സാമൂഹിക സ്ഥാനം|അധികാരത്തിന്റെ അടയാളം/.test(text)) {
|
|
355
|
+
return 'doodle_royal_status';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// 12. Outro / Subscribe / Notification Bell / Channel Engagement (Scene 25)
|
|
359
|
+
if (/(?:^|[^\w])(subscribe|notification bell|youtube outro|channel subscribe|outro|subscribe button|like and subscribe)(?:[^\w]|$)/i.test(text)
|
|
360
|
+
|| /സബ്സ്ക്രൈബ്|ചാനൽ സബ്സ്ക്രൈബ്|സബ്സ്ക്രൈബ് ചെയ്യൂ|ബെൽ ഐക്കൺ|നന്ദി/.test(text)) {
|
|
361
|
+
return 'doodle_outro_subscribe';
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 13. General Human Character
|
|
347
365
|
if (/(?:^|[^\w])(human|person|man|woman|people|child|guy|doodle|character|actor)(?:[^\w]|$)/i.test(text)
|
|
348
366
|
|| /മനുഷ്യൻ|ആളുകൾ|വ്യക്തി/.test(text)) {
|
|
349
367
|
return 'doodle_general_human';
|
|
@@ -575,6 +593,7 @@ function generateSceneHtml({
|
|
|
575
593
|
const aesthetic = AESTHETICS[aestheticKey] || AESTHETICS.cyber_tech;
|
|
576
594
|
const compTheme = detectComponentTheme({
|
|
577
595
|
visualDescription,
|
|
596
|
+
prompt,
|
|
578
597
|
caption,
|
|
579
598
|
narration,
|
|
580
599
|
topic,
|
|
@@ -2510,6 +2529,338 @@ function drawMainScene(t) {
|
|
|
2510
2529
|
showShadow: true,
|
|
2511
2530
|
seed: 110,
|
|
2512
2531
|
});
|
|
2532
|
+
} else if (COMP_THEME === 'doodle_map_migration') {
|
|
2533
|
+
// Scene 10: Hand-Drawn Migration Map / Out of Africa / Ancient World Routes
|
|
2534
|
+
const mapSpring = spring(t, 0.05, 170, 14);
|
|
2535
|
+
const arrowSpring = spring(t, window.DURATION * 0.35, 180, 15);
|
|
2536
|
+
const walkProg = Math.min(1, Math.max(0, (t - window.DURATION * 0.2) / (window.DURATION * 0.75)));
|
|
2537
|
+
|
|
2538
|
+
// Antique parchment background wash
|
|
2539
|
+
ctx.save();
|
|
2540
|
+
ctx.fillStyle = IS_LIGHT ? '#faf5ec' : '#1c1917';
|
|
2541
|
+
ctx.fillRect(0, 0, W, H);
|
|
2542
|
+
|
|
2543
|
+
// Decorative map grid lines (latitude/longitude in subtle ink)
|
|
2544
|
+
ctx.strokeStyle = IS_LIGHT ? 'rgba(180, 83, 9, 0.12)' : 'rgba(255, 255, 255, 0.1)';
|
|
2545
|
+
ctx.lineWidth = 1.5;
|
|
2546
|
+
ctx.setLineDash([6, 6]);
|
|
2547
|
+
for (let gx = 100; gx < W; gx += 200) {
|
|
2548
|
+
ctx.beginPath(); ctx.moveTo(gx, 0); ctx.lineTo(gx, H); ctx.stroke();
|
|
2549
|
+
}
|
|
2550
|
+
for (let gy = 80; gy < H; gy += 160) {
|
|
2551
|
+
ctx.beginPath(); ctx.moveTo(0, gy); ctx.lineTo(W, gy); ctx.stroke();
|
|
2552
|
+
}
|
|
2553
|
+
ctx.setLineDash([]);
|
|
2554
|
+
|
|
2555
|
+
// Hand-drawn stylized continent outlines (Africa, Europe, Asia)
|
|
2556
|
+
const mapCenter = cx - 40;
|
|
2557
|
+
const mapY = cy - 20;
|
|
2558
|
+
|
|
2559
|
+
// 1. Africa Outline (Highlighted in warm terracotta wash)
|
|
2560
|
+
ctx.save();
|
|
2561
|
+
ctx.fillStyle = 'rgba(217, 119, 6, 0.20)';
|
|
2562
|
+
ctx.beginPath();
|
|
2563
|
+
ctx.moveTo(mapCenter - 140, mapY - 60); // West bulge
|
|
2564
|
+
ctx.bezierCurveTo(mapCenter - 160, mapY + 20, mapCenter - 80, mapY + 140, mapCenter - 40, mapY + 220); // South tip (Good Hope)
|
|
2565
|
+
ctx.bezierCurveTo(mapCenter + 10, mapY + 160, mapCenter + 40, mapY + 60, mapCenter + 60, mapY - 20); // Horn of Africa
|
|
2566
|
+
ctx.bezierCurveTo(mapCenter + 30, mapY - 60, mapCenter - 20, mapY - 80, mapCenter - 140, mapY - 60); // North Coast
|
|
2567
|
+
ctx.closePath();
|
|
2568
|
+
ctx.fill();
|
|
2569
|
+
|
|
2570
|
+
// Outline stroke with ink wobble
|
|
2571
|
+
sLine(mapCenter - 140, mapY - 60, mapCenter - 160, mapY + 20, 710, '#c2410c', 3);
|
|
2572
|
+
sLine(mapCenter - 160, mapY + 20, mapCenter - 40, mapY + 220, 711, '#c2410c', 3);
|
|
2573
|
+
sLine(mapCenter - 40, mapY + 220, mapCenter + 60, mapY - 20, 712, '#c2410c', 3);
|
|
2574
|
+
sLine(mapCenter + 60, mapY - 20, mapCenter - 140, mapY - 60, 713, '#c2410c', 3);
|
|
2575
|
+
|
|
2576
|
+
// Africa label badge
|
|
2577
|
+
ctx.fillStyle = '#b45309';
|
|
2578
|
+
ctx.font = '700 22px ' + FONT_DISPLAY;
|
|
2579
|
+
ctx.textAlign = 'center';
|
|
2580
|
+
ctx.fillText('AFRICA', mapCenter - 40, mapY + 60);
|
|
2581
|
+
|
|
2582
|
+
// Sun icon over Africa
|
|
2583
|
+
sCircle(mapCenter - 90, mapY + 40, 18, 714, '#d97706', 2.5, '#fef08a');
|
|
2584
|
+
for (let r = 0; r < 6; r++) {
|
|
2585
|
+
const ra = (r / 6) * Math.PI * 2 + t * 0.5;
|
|
2586
|
+
const rx1 = mapCenter - 90 + Math.cos(ra) * 22;
|
|
2587
|
+
const ry1 = mapY + 40 + Math.sin(ra) * 22;
|
|
2588
|
+
const rx2 = mapCenter - 90 + Math.cos(ra) * 32;
|
|
2589
|
+
const ry2 = mapY + 40 + Math.sin(ra) * 32;
|
|
2590
|
+
sLine(rx1, ry1, rx2, ry2, 715 + r, '#d97706', 2);
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
// 2. Eurasia / Northern Territory Outline (Colder destination)
|
|
2594
|
+
ctx.fillStyle = 'rgba(56, 189, 248, 0.12)';
|
|
2595
|
+
ctx.beginPath();
|
|
2596
|
+
ctx.moveTo(mapCenter - 130, mapY - 90);
|
|
2597
|
+
ctx.bezierCurveTo(mapCenter - 120, mapY - 200, mapCenter + 60, mapY - 220, mapCenter + 220, mapY - 170); // Northern Eurasia
|
|
2598
|
+
ctx.bezierCurveTo(mapCenter + 320, mapY - 130, mapCenter + 360, mapY - 40, mapCenter + 260, mapY + 10); // East Asia
|
|
2599
|
+
ctx.bezierCurveTo(mapCenter + 140, mapY - 30, mapCenter + 40, mapY - 70, mapCenter - 130, mapY - 90);
|
|
2600
|
+
ctx.closePath();
|
|
2601
|
+
ctx.fill();
|
|
2602
|
+
|
|
2603
|
+
sLine(mapCenter - 130, mapY - 90, mapCenter + 60, mapY - 210, 725, '#0284c7', 3);
|
|
2604
|
+
sLine(mapCenter + 60, mapY - 210, mapCenter + 280, mapY - 160, 726, '#0284c7', 3);
|
|
2605
|
+
sLine(mapCenter + 280, mapY - 160, mapCenter + 240, mapY + 10, 727, '#0284c7', 3);
|
|
2606
|
+
|
|
2607
|
+
// Snowflake climate icons over Eurasia
|
|
2608
|
+
for (let sf = 0; sf < 3; sf++) {
|
|
2609
|
+
const sfx = mapCenter + 60 + sf * 110;
|
|
2610
|
+
const sfy = mapY - 160 + (sf % 2) * 35;
|
|
2611
|
+
sLine(sfx - 14, sfy, sfx + 14, sfy, 730 + sf, '#0284c7', 2);
|
|
2612
|
+
sLine(sfx, sfy - 14, sfx, sfy + 14, 733 + sf, '#0284c7', 2);
|
|
2613
|
+
sLine(sfx - 10, sfy - 10, sfx + 10, sfy + 10, 736 + sf, '#0284c7', 2);
|
|
2614
|
+
sLine(sfx - 10, sfy + 10, sfx + 10, sfy - 10, 739 + sf, '#0284c7', 2);
|
|
2615
|
+
}
|
|
2616
|
+
ctx.restore();
|
|
2617
|
+
|
|
2618
|
+
// 3. Dynamic Migration Route Curves with Pulsing Arrowheads
|
|
2619
|
+
if (arrowSpring > 0.05) {
|
|
2620
|
+
ctx.save();
|
|
2621
|
+
ctx.strokeStyle = '#c2410c';
|
|
2622
|
+
ctx.lineWidth = 4;
|
|
2623
|
+
ctx.setLineDash([10, 8]);
|
|
2624
|
+
|
|
2625
|
+
// Route 1: Out of Africa -> Europe
|
|
2626
|
+
ctx.beginPath();
|
|
2627
|
+
ctx.moveTo(mapCenter + 20, mapY - 40);
|
|
2628
|
+
ctx.quadraticCurveTo(mapCenter - 20, mapY - 120, mapCenter - 60, mapY - 160);
|
|
2629
|
+
ctx.stroke();
|
|
2630
|
+
|
|
2631
|
+
// Route 2: Out of Africa -> Asia
|
|
2632
|
+
ctx.beginPath();
|
|
2633
|
+
ctx.moveTo(mapCenter + 40, mapY - 35);
|
|
2634
|
+
ctx.quadraticCurveTo(mapCenter + 120, mapY - 80, mapCenter + 220, mapY - 120);
|
|
2635
|
+
ctx.stroke();
|
|
2636
|
+
ctx.setLineDash([]);
|
|
2637
|
+
|
|
2638
|
+
// Pulsing migration markers
|
|
2639
|
+
const pulseR = 6 + Math.sin(t * 6) * 3;
|
|
2640
|
+
sCircle(mapCenter + 30, mapY - 40, pulseR, 750, '#ea580c', 2, '#fbbf24');
|
|
2641
|
+
sCircle(mapCenter - 60, mapY - 160, pulseR, 751, '#0284c7', 2, '#38bdf8');
|
|
2642
|
+
sCircle(mapCenter + 220, mapY - 120, pulseR, 752, '#0284c7', 2, '#38bdf8');
|
|
2643
|
+
ctx.restore();
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
// 4. Walking Doodle Explorer on the Migration Trail
|
|
2647
|
+
const trailStartX = mapCenter + 10;
|
|
2648
|
+
const trailStartY = mapY + 20;
|
|
2649
|
+
const trailEndX = mapCenter + 190;
|
|
2650
|
+
const trailEndY = mapY - 100;
|
|
2651
|
+
const curNomadX = trailStartX + (trailEndX - trailStartX) * walkProg;
|
|
2652
|
+
const curNomadY = trailStartY + (trailEndY - trailStartY) * walkProg;
|
|
2653
|
+
|
|
2654
|
+
drawDoodleCharacter({
|
|
2655
|
+
x: curNomadX,
|
|
2656
|
+
y: curNomadY + 120,
|
|
2657
|
+
scale: 1.15,
|
|
2658
|
+
hairStyle: 'wild',
|
|
2659
|
+
expression: 'focused',
|
|
2660
|
+
clothing: walkProg > 0.5 ? 'pelt' : 'none',
|
|
2661
|
+
armPose: 'hold_spear',
|
|
2662
|
+
showShadow: true,
|
|
2663
|
+
seed: 760,
|
|
2664
|
+
});
|
|
2665
|
+
|
|
2666
|
+
} else if (COMP_THEME === 'doodle_royal_status') {
|
|
2667
|
+
// Scene 16: Evolution of Clothing into Royal Mantles, Crowns & Social Status
|
|
2668
|
+
const crownSpring = spring(t, window.DURATION * 0.32, 190, 15);
|
|
2669
|
+
const arrowRise = spring(t, window.DURATION * 0.55, 170, 14);
|
|
2670
|
+
|
|
2671
|
+
// Warm dais platform
|
|
2672
|
+
ctx.fillStyle = IS_LIGHT ? '#faf7f2' : '#1c1917';
|
|
2673
|
+
ctx.fillRect(0, 0, W, H);
|
|
2674
|
+
|
|
2675
|
+
ctx.fillStyle = '#451a03';
|
|
2676
|
+
ctx.fillRect(0, cy + 140, W, H - (cy + 140));
|
|
2677
|
+
sLine(0, cy + 140, W, cy + 140, 770, '#d97706', 3.5);
|
|
2678
|
+
|
|
2679
|
+
// Prehistoric Human on Left (Simple pelt, admiring transformation)
|
|
2680
|
+
drawDoodleCharacter({
|
|
2681
|
+
x: cx - 260,
|
|
2682
|
+
y: cy + 140,
|
|
2683
|
+
scale: 1.3,
|
|
2684
|
+
hairStyle: 'wild',
|
|
2685
|
+
expression: 'curious',
|
|
2686
|
+
clothing: 'pelt',
|
|
2687
|
+
armPose: 'thinker',
|
|
2688
|
+
showShadow: true,
|
|
2689
|
+
seed: 771,
|
|
2690
|
+
});
|
|
2691
|
+
|
|
2692
|
+
// Royal Monarch on Right (Majestic regal cape / robe)
|
|
2693
|
+
const kingX = cx + 220;
|
|
2694
|
+
drawDoodleCharacter({
|
|
2695
|
+
x: kingX,
|
|
2696
|
+
y: cy + 140,
|
|
2697
|
+
scale: 1.35,
|
|
2698
|
+
hairStyle: 'modern',
|
|
2699
|
+
expression: 'smiling',
|
|
2700
|
+
clothing: 'cloak',
|
|
2701
|
+
armPose: 'peace_gesture',
|
|
2702
|
+
showShadow: true,
|
|
2703
|
+
seed: 772,
|
|
2704
|
+
});
|
|
2705
|
+
|
|
2706
|
+
// Ornate Golden Crown with Ruby jewels appearing above Monarch
|
|
2707
|
+
if (crownSpring > 0.05) {
|
|
2708
|
+
ctx.save();
|
|
2709
|
+
ctx.translate(kingX, cy - 20);
|
|
2710
|
+
ctx.scale(crownSpring, crownSpring);
|
|
2711
|
+
|
|
2712
|
+
// Golden crown base & peaks
|
|
2713
|
+
ctx.fillStyle = '#f59e0b';
|
|
2714
|
+
ctx.beginPath();
|
|
2715
|
+
ctx.moveTo(-36, 0);
|
|
2716
|
+
ctx.lineTo(-40, -32);
|
|
2717
|
+
ctx.lineTo(-20, -18);
|
|
2718
|
+
ctx.lineTo(0, -42);
|
|
2719
|
+
ctx.lineTo(20, -18);
|
|
2720
|
+
ctx.lineTo(40, -32);
|
|
2721
|
+
ctx.lineTo(36, 0);
|
|
2722
|
+
ctx.closePath();
|
|
2723
|
+
ctx.fill();
|
|
2724
|
+
ctx.strokeStyle = '#b45309';
|
|
2725
|
+
ctx.lineWidth = 2.5;
|
|
2726
|
+
ctx.stroke();
|
|
2727
|
+
|
|
2728
|
+
// Jewels on crown peaks
|
|
2729
|
+
sCircle(-40, -32, 4, 780, '#b91c1c', 1.5, '#ef4444');
|
|
2730
|
+
sCircle(0, -42, 5, 781, '#b91c1c', 1.5, '#ef4444');
|
|
2731
|
+
sCircle(40, -32, 4, 782, '#b91c1c', 1.5, '#ef4444');
|
|
2732
|
+
|
|
2733
|
+
// Golden Sceptre in hand
|
|
2734
|
+
sLine(42, 20, 52, 130, 785, '#d97706', 4.5);
|
|
2735
|
+
sCircle(42, 16, 10, 786, '#d97706', 2, '#fbbf24');
|
|
2736
|
+
ctx.restore();
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
// Status & Power indicator arrows rising centrally
|
|
2740
|
+
if (arrowRise > 0.05) {
|
|
2741
|
+
ctx.save();
|
|
2742
|
+
ctx.globalAlpha = Math.min(1, arrowRise);
|
|
2743
|
+
for (let arr = 0; arr < 3; arr++) {
|
|
2744
|
+
const arrX = cx - 30 + arr * 40;
|
|
2745
|
+
const arrY = cy + 40 - arr * 35 - Math.sin(t * 4 + arr) * 10;
|
|
2746
|
+
sLine(arrX, arrY + 40, arrX, arrY, 790 + arr, '#d97706', 3.5);
|
|
2747
|
+
sLine(arrX - 12, arrY + 14, arrX, arrY, 793 + arr, '#d97706', 3.5);
|
|
2748
|
+
sLine(arrX + 12, arrY + 14, arrX, arrY, 796 + arr, '#d97706', 3.5);
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
ctx.fillStyle = '#b45309';
|
|
2752
|
+
ctx.font = '700 20px ' + FONT_DISPLAY;
|
|
2753
|
+
ctx.textAlign = 'center';
|
|
2754
|
+
ctx.fillText('STATUS & POWER', cx + 10, cy - 90);
|
|
2755
|
+
ctx.restore();
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
} else if (COMP_THEME === 'doodle_outro_subscribe') {
|
|
2759
|
+
// Scene 25: YouTube Outro / Animated Question Mark / Subscribe & Bell Card
|
|
2760
|
+
const qSpring = spring(t, 0.05, 180, 14);
|
|
2761
|
+
const subSpring = spring(t, window.DURATION * 0.28, 200, 14);
|
|
2762
|
+
const bellSpring = spring(t, window.DURATION * 0.50, 190, 12);
|
|
2763
|
+
|
|
2764
|
+
ctx.fillStyle = IS_LIGHT ? '#faf7f2' : '#1c1917';
|
|
2765
|
+
ctx.fillRect(0, 0, W, H);
|
|
2766
|
+
|
|
2767
|
+
// Warm terrain ground
|
|
2768
|
+
ctx.fillStyle = '#451a03';
|
|
2769
|
+
ctx.fillRect(0, cy + 140, W, H - (cy + 140));
|
|
2770
|
+
sLine(0, cy + 140, W, cy + 140, 810, '#d97706', 3.5);
|
|
2771
|
+
|
|
2772
|
+
// Prehistoric character on left drawing with a reed/quill
|
|
2773
|
+
drawDoodleCharacter({
|
|
2774
|
+
x: cx - 280,
|
|
2775
|
+
y: cy + 140,
|
|
2776
|
+
scale: 1.35,
|
|
2777
|
+
hairStyle: 'wild',
|
|
2778
|
+
expression: 'smiling',
|
|
2779
|
+
clothing: 'pelt',
|
|
2780
|
+
armPose: 'peace_gesture',
|
|
2781
|
+
showShadow: true,
|
|
2782
|
+
seed: 811,
|
|
2783
|
+
});
|
|
2784
|
+
|
|
2785
|
+
// Big bold animated question mark drawn in ink
|
|
2786
|
+
if (qSpring > 0.05) {
|
|
2787
|
+
ctx.save();
|
|
2788
|
+
const qX = cx - 80;
|
|
2789
|
+
const qY = cy - 20;
|
|
2790
|
+
ctx.translate(qX, qY);
|
|
2791
|
+
ctx.scale(qSpring, qSpring);
|
|
2792
|
+
|
|
2793
|
+
ctx.strokeStyle = '#c2410c';
|
|
2794
|
+
ctx.lineWidth = 9;
|
|
2795
|
+
ctx.lineCap = 'round';
|
|
2796
|
+
ctx.beginPath();
|
|
2797
|
+
ctx.arc(0, -60, 44, Math.PI, 0, false);
|
|
2798
|
+
ctx.bezierCurveTo(44, -15, 0, 5, 0, 35);
|
|
2799
|
+
ctx.stroke();
|
|
2800
|
+
|
|
2801
|
+
sCircle(0, 72, 8, 815, '#c2410c', 3, '#c2410c');
|
|
2802
|
+
ctx.restore();
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
// Interactive YouTube Subscribe Pill Button & Swinging Notification Bell
|
|
2806
|
+
if (subSpring > 0.05) {
|
|
2807
|
+
ctx.save();
|
|
2808
|
+
const btnX = cx + 220;
|
|
2809
|
+
const btnY = cy - 20;
|
|
2810
|
+
ctx.translate(btnX, btnY);
|
|
2811
|
+
ctx.scale(subSpring, subSpring);
|
|
2812
|
+
|
|
2813
|
+
// Red Subscribe Pill
|
|
2814
|
+
ctx.fillStyle = '#dc2626';
|
|
2815
|
+
ctx.beginPath();
|
|
2816
|
+
ctx.roundRect(-140, -32, 280, 64, 32);
|
|
2817
|
+
ctx.fill();
|
|
2818
|
+
ctx.strokeStyle = '#991b1b';
|
|
2819
|
+
ctx.lineWidth = 3;
|
|
2820
|
+
ctx.stroke();
|
|
2821
|
+
|
|
2822
|
+
// White Subscribe text
|
|
2823
|
+
ctx.fillStyle = '#ffffff';
|
|
2824
|
+
ctx.font = '700 24px ' + FONT_DISPLAY;
|
|
2825
|
+
ctx.textAlign = 'center';
|
|
2826
|
+
ctx.textBaseline = 'middle';
|
|
2827
|
+
ctx.fillText('SUBSCRIBE', -15, 0);
|
|
2828
|
+
|
|
2829
|
+
// Play icon triangle
|
|
2830
|
+
ctx.beginPath();
|
|
2831
|
+
ctx.moveTo(-90, -12);
|
|
2832
|
+
ctx.lineTo(-72, 0);
|
|
2833
|
+
ctx.lineTo(-90, 12);
|
|
2834
|
+
ctx.closePath();
|
|
2835
|
+
ctx.fill();
|
|
2836
|
+
|
|
2837
|
+
// Notification Bell swinging on spring
|
|
2838
|
+
if (bellSpring > 0.05) {
|
|
2839
|
+
ctx.save();
|
|
2840
|
+
const bellSwing = Math.sin(t * 9) * 0.25 * Math.exp(-(t % 2.5));
|
|
2841
|
+
ctx.translate(180, 0);
|
|
2842
|
+
ctx.rotate(bellSwing);
|
|
2843
|
+
|
|
2844
|
+
// Golden bell body
|
|
2845
|
+
ctx.fillStyle = '#f59e0b';
|
|
2846
|
+
ctx.beginPath();
|
|
2847
|
+
ctx.arc(0, -12, 14, Math.PI, 0);
|
|
2848
|
+
ctx.quadraticCurveTo(18, 12, 22, 16);
|
|
2849
|
+
ctx.lineTo(-22, 16);
|
|
2850
|
+
ctx.quadraticCurveTo(-18, 12, 0, -12);
|
|
2851
|
+
ctx.closePath();
|
|
2852
|
+
ctx.fill();
|
|
2853
|
+
ctx.strokeStyle = '#b45309';
|
|
2854
|
+
ctx.lineWidth = 2.5;
|
|
2855
|
+
ctx.stroke();
|
|
2856
|
+
|
|
2857
|
+
// Bell clapper
|
|
2858
|
+
sCircle(0, 20, 5, 820, '#b45309', 2, '#d97706');
|
|
2859
|
+
ctx.restore();
|
|
2860
|
+
}
|
|
2861
|
+
ctx.restore();
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2513
2864
|
} else if (COMP_THEME === 'celestial_orbit') {
|
|
2514
2865
|
// 3D Celestial Body (Earth) with orbiting satellites & signal pulses
|
|
2515
2866
|
const earthR = Math.min(W, H) * 0.22;
|
|
@@ -2530,7 +2881,7 @@ function drawMainScene(t) {
|
|
|
2530
2881
|
ctx.beginPath(); ctx.arc(cx, cy, earthR, 0, Math.PI * 2); ctx.fill();
|
|
2531
2882
|
|
|
2532
2883
|
// Rotating longitude meridians
|
|
2533
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.22)';
|
|
2884
|
+
ctx.strokeStyle = IS_LIGHT ? 'rgba(28, 25, 23, 0.35)' : 'rgba(255, 255, 255, 0.22)';
|
|
2534
2885
|
ctx.lineWidth = 1.5;
|
|
2535
2886
|
for (let m = 0; m < 4; m++) {
|
|
2536
2887
|
const mAngle = t * 0.5 + (m * Math.PI) / 4;
|
|
@@ -2561,7 +2912,7 @@ function drawMainScene(t) {
|
|
|
2561
2912
|
ctx.save();
|
|
2562
2913
|
ctx.translate(cx, cy);
|
|
2563
2914
|
ctx.rotate(tilt);
|
|
2564
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.14)';
|
|
2915
|
+
ctx.strokeStyle = IS_LIGHT ? 'rgba(28, 25, 23, 0.25)' : 'rgba(255, 255, 255, 0.14)';
|
|
2565
2916
|
ctx.lineWidth = 1.5;
|
|
2566
2917
|
ctx.beginPath(); ctx.ellipse(0, 0, a, b, 0, 0, Math.PI * 2); ctx.stroke();
|
|
2567
2918
|
|
|
@@ -2790,7 +3141,7 @@ function drawMainScene(t) {
|
|
|
2790
3141
|
} else if (COMP_THEME === 'radar_globe') {
|
|
2791
3142
|
// 3D Tactical Wireframe Rotating Radar Globe
|
|
2792
3143
|
const globeR = 150;
|
|
2793
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
|
|
3144
|
+
ctx.strokeStyle = IS_LIGHT ? 'rgba(28, 25, 23, 0.35)' : 'rgba(255, 255, 255, 0.15)';
|
|
2794
3145
|
ctx.lineWidth = 1.5;
|
|
2795
3146
|
|
|
2796
3147
|
// Outer degree ring
|
package/src/workflows/render.js
CHANGED
|
@@ -61,6 +61,7 @@ async function runRender({
|
|
|
61
61
|
voicePath: asset.voicePath,
|
|
62
62
|
caption: asset.caption || '',
|
|
63
63
|
narration: asset.narration || '',
|
|
64
|
+
prompt: asset.prompt || '',
|
|
64
65
|
visualDescription: asset.visualDescription || '',
|
|
65
66
|
visualType: asset.visualType || 'code_animation',
|
|
66
67
|
topic: asset.topic || topic || '',
|