@amg555/frameforge-mcp 2.3.4 → 2.3.6
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/CHANGELOG.md +45 -0
- package/README.md +2 -2
- package/out/frames/f000000.jpg +0 -0
- package/out/frames/f000001.jpg +0 -0
- package/out/frames/f000002.jpg +0 -0
- package/out/frames/f000003.jpg +0 -0
- package/out/frames/f000004.jpg +0 -0
- package/out/frames/f000005.jpg +0 -0
- package/out/frames/f000006.jpg +0 -0
- package/out/frames/f000007.jpg +0 -0
- package/out/frames/f000008.jpg +0 -0
- package/out/frames/f000009.jpg +0 -0
- package/out/frames/f000010.jpg +0 -0
- package/out/frames/f000011.jpg +0 -0
- package/out/frames/f000012.jpg +0 -0
- package/out/frames/f000013.jpg +0 -0
- package/out/frames/f000014.jpg +0 -0
- package/out/frames/f000015.jpg +0 -0
- package/out/frames/f000016.jpg +0 -0
- package/out/frames/f000017.jpg +0 -0
- package/out/frames/f000018.jpg +0 -0
- package/out/frames/f000019.jpg +0 -0
- package/out/frames/f000020.jpg +0 -0
- package/out/frames/f000021.jpg +0 -0
- package/out/frames/f000022.jpg +0 -0
- package/out/frames/f000023.jpg +0 -0
- package/out/frames/f000024.jpg +0 -0
- package/out/frames/f000025.jpg +0 -0
- package/out/frames/f000026.jpg +0 -0
- package/out/frames/f000027.jpg +0 -0
- package/out/frames/f000028.jpg +0 -0
- package/out/frames/f000029.jpg +0 -0
- package/out/frames/f000030.jpg +0 -0
- package/out/frames/f000031.jpg +0 -0
- package/out/frames/f000032.jpg +0 -0
- package/out/frames/f000033.jpg +0 -0
- package/out/frames/f000034.jpg +0 -0
- package/out/frames/f000035.jpg +0 -0
- package/out/frames/f000036.jpg +0 -0
- package/out/frames/f000037.jpg +0 -0
- package/out/frames/f000038.jpg +0 -0
- package/out/frames/f000039.jpg +0 -0
- package/out/frames/f000040.jpg +0 -0
- package/out/frames/f000041.jpg +0 -0
- package/out/frames/f000042.jpg +0 -0
- package/out/frames/f000043.jpg +0 -0
- package/out/frames/f000044.jpg +0 -0
- package/out/frames/f000045.jpg +0 -0
- package/out/frames/f000046.jpg +0 -0
- package/out/frames/f000047.jpg +0 -0
- package/package.json +2 -2
- package/plugins/video-pipeline/skills/video-pipeline/SKILL.md +25 -12
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.css +2 -2
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.js +1 -1
- package/scripts/deep-validate.js +2 -2
- package/scripts/html-renderer/new-project.mjs +2 -2
- package/src/cli/make.js +6 -0
- package/src/harness/bin/harness.mjs +20 -7
- package/src/mcp-server/server.js +57 -7
- package/src/video-renderer/renderer.js +268 -60
- package/src/video-renderer/scene-animator.js +1241 -0
- package/src/workflows/assets.js +23 -2
- package/src/workflows/render.js +32 -9
- package/src/workflows/script.js +2 -0
|
@@ -0,0 +1,1241 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Scene Animator — Dynamic, Context-Driven Animation Engine
|
|
4
|
+
*
|
|
5
|
+
* Generates self-contained HTML/Canvas animation scenes tailored to the video's
|
|
6
|
+
* topic, style, language, and script narrative. Every scene is a pure function of t
|
|
7
|
+
* under the FrameForge harness contract.
|
|
8
|
+
*
|
|
9
|
+
* Supported Aesthetics:
|
|
10
|
+
* - cyber_tech: Neon cyan/magenta, perspective grids, circuit lines, neural mesh
|
|
11
|
+
* - cinematic_documentary: Warm sepia/gold, dust motes, archival maps, monuments, royal seals
|
|
12
|
+
* - finance_executive: Deep sapphire, emerald bull green, candlestick charts, tickers
|
|
13
|
+
* - retro_synthwave: Indigo sunset, hot pink, horizon sun, 80s scanlines, laser grids
|
|
14
|
+
* - nature_organic: Forest emerald, sage, 3D DNA double-helix, floating pollen spores
|
|
15
|
+
* - hype_sports: Asphalt black, crimson/neon lime, angled speed streaks, acoustic shockwaves
|
|
16
|
+
* - minimal_luxury: Matte black, champagne/rose gold, 3D wireframe polyhedra, architectural dots
|
|
17
|
+
* - breaking_news: Newsroom slate, alert red, rotating 3D wireframe globe, radar sweeps
|
|
18
|
+
* - cosmic_space: Deep nebula, 3D Earth, orbiting satellites, microwave wavefronts
|
|
19
|
+
* - warm_creative: Terracotta, sunshine amber, organic morphing geometry, playful springs
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
|
|
25
|
+
// ── Multilingual Font Resolution by Script ─────────────────────────────────────
|
|
26
|
+
const REGIONAL_FONTS = {
|
|
27
|
+
malayalam: `'Nirmala UI', 'Noto Sans Malayalam', 'Malayalam Sangam MN', 'Kartika', 'Meera', 'Manjari'`,
|
|
28
|
+
devanagari: `'Nirmala UI', 'Noto Sans Devanagari', 'Mangal', 'Aparajita'`,
|
|
29
|
+
tamil: `'Nirmala UI', 'Noto Sans Tamil', 'Latha', 'Vijaya'`,
|
|
30
|
+
telugu: `'Nirmala UI', 'Noto Sans Telugu', 'Gautami', 'Vani'`,
|
|
31
|
+
arabic: `'Segoe UI', 'Amiri', 'Noto Sans Arabic', 'Traditional Arabic'`,
|
|
32
|
+
cjk: `'Yu Gothic', 'Meiryo', 'Hiragino Sans', 'Microsoft YaHei', 'Malgun Gothic', 'Noto Sans CJK'`,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function getScriptFontStack(text = '', language = '') {
|
|
36
|
+
const lang = (language || '').toLowerCase().slice(0, 2);
|
|
37
|
+
if (lang === 'ml' || /[\u0D00-\u0D7F]/.test(text)) return REGIONAL_FONTS.malayalam;
|
|
38
|
+
if (lang === 'hi' || lang === 'mr' || lang === 'ne' || /[\u0900-\u097F]/.test(text)) return REGIONAL_FONTS.devanagari;
|
|
39
|
+
if (lang === 'ta' || /[\u0B80-\u0BFF]/.test(text)) return REGIONAL_FONTS.tamil;
|
|
40
|
+
if (lang === 'te' || /[\u0C00-\u0C7F]/.test(text)) return REGIONAL_FONTS.telugu;
|
|
41
|
+
if (lang === 'ar' || lang === 'ur' || /[\u0600-\u06FF]/.test(text)) return REGIONAL_FONTS.arabic;
|
|
42
|
+
if (lang === 'ja' || lang === 'zh' || lang === 'ko' || /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff]/.test(text)) return REGIONAL_FONTS.cjk;
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Aesthetic Definitions ──────────────────────────────────────────────────────
|
|
47
|
+
const AESTHETICS = {
|
|
48
|
+
cyber_tech: {
|
|
49
|
+
name: 'Cyber Tech',
|
|
50
|
+
palettes: [
|
|
51
|
+
{ bg0: '#142036', bg1: '#0b111c', bg2: '#05080f', accent: '#00f0ff', accent2: '#d946ef', cardBg: 'rgba(9, 16, 29, 0.85)', cardBorder: 'rgba(0, 240, 255, 0.3)', tag: 'CYBER TECH' },
|
|
52
|
+
{ bg0: '#1a1033', bg1: '#0e081c', bg2: '#05030b', accent: '#818cf8', accent2: '#38bdf8', cardBg: 'rgba(16, 11, 31, 0.85)', cardBorder: 'rgba(129, 140, 248, 0.3)', tag: 'SYSTEM MATRIX' },
|
|
53
|
+
{ bg0: '#082522', bg1: '#041614', bg2: '#020908', accent: '#10b981', accent2: '#06b6d4', cardBg: 'rgba(6, 26, 23, 0.85)', cardBorder: 'rgba(16, 185, 129, 0.3)', tag: 'NEURAL LINK' },
|
|
54
|
+
],
|
|
55
|
+
fontFamily: (script) => script ? `${script}, 'Rajdhani', -apple-system, sans-serif` : `'Rajdhani', 'Segoe UI', -apple-system, sans-serif`,
|
|
56
|
+
fontDisplayWeight: '800',
|
|
57
|
+
backgroundType: 'grid_perspective',
|
|
58
|
+
springTension: 200,
|
|
59
|
+
springFriction: 13,
|
|
60
|
+
},
|
|
61
|
+
cinematic_documentary: {
|
|
62
|
+
name: 'Cinematic Documentary',
|
|
63
|
+
palettes: [
|
|
64
|
+
{ bg0: '#261b11', bg1: '#150e09', bg2: '#0a0604', accent: '#d4af37', accent2: '#f59e0b', cardBg: 'rgba(28, 19, 13, 0.88)', cardBorder: 'rgba(212, 175, 55, 0.35)', tag: 'HISTORICAL ARCHIVE' },
|
|
65
|
+
{ bg0: '#1e1c18', bg1: '#11100e', bg2: '#080807', accent: '#e2d9c8', accent2: '#d4af37', cardBg: 'rgba(22, 20, 17, 0.88)', cardBorder: 'rgba(226, 217, 200, 0.3)', tag: 'CHRONICLE' },
|
|
66
|
+
{ bg0: '#1a1f1d', bg1: '#0e1211', bg2: '#060807', accent: '#5eead4', accent2: '#e2d9c8', cardBg: 'rgba(18, 23, 21, 0.88)', cardBorder: 'rgba(94, 234, 212, 0.3)', tag: 'HERITAGE' },
|
|
67
|
+
],
|
|
68
|
+
fontFamily: (script) => script ? `${script}, 'Cinzel', 'Georgia', serif` : `'Cinzel', 'Georgia', 'Times New Roman', serif`,
|
|
69
|
+
fontDisplayWeight: '700',
|
|
70
|
+
backgroundType: 'particles_dust',
|
|
71
|
+
springTension: 120,
|
|
72
|
+
springFriction: 18,
|
|
73
|
+
},
|
|
74
|
+
finance_executive: {
|
|
75
|
+
name: 'Finance Executive',
|
|
76
|
+
palettes: [
|
|
77
|
+
{ bg0: '#091e2b', bg1: '#041017', bg2: '#02070a', accent: '#10b981', accent2: '#38bdf8', cardBg: 'rgba(7, 22, 33, 0.9)', cardBorder: 'rgba(16, 185, 129, 0.35)', tag: 'MARKET INTELLIGENCE' },
|
|
78
|
+
{ bg0: '#161426', bg1: '#0b0a14', bg2: '#040308', accent: '#6366f1', accent2: '#10b981', cardBg: 'rgba(15, 13, 28, 0.9)', cardBorder: 'rgba(99, 102, 241, 0.35)', tag: 'QUANT ANALYSIS' },
|
|
79
|
+
{ bg0: '#211516', bg1: '#120a0b', bg2: '#060303', accent: '#ef4444', accent2: '#f59e0b', cardBg: 'rgba(24, 13, 14, 0.9)', cardBorder: 'rgba(239, 68, 68, 0.35)', tag: 'VOLATILITY DESK' },
|
|
80
|
+
],
|
|
81
|
+
fontFamily: (script) => script ? `${script}, 'Inter', 'Helvetica Neue', sans-serif` : `'Inter', 'Helvetica Neue', Arial, sans-serif`,
|
|
82
|
+
fontDisplayWeight: '800',
|
|
83
|
+
backgroundType: 'candlestick_charts',
|
|
84
|
+
springTension: 170,
|
|
85
|
+
springFriction: 14,
|
|
86
|
+
},
|
|
87
|
+
retro_synthwave: {
|
|
88
|
+
name: 'Retro Synthwave',
|
|
89
|
+
palettes: [
|
|
90
|
+
{ bg0: '#2d0938', bg1: '#16041c', bg2: '#08010a', accent: '#ff2a85', accent2: '#00f0ff', cardBg: 'rgba(35, 7, 45, 0.85)', cardBorder: 'rgba(255, 42, 133, 0.45)', tag: 'SYNTH RUNNER' },
|
|
91
|
+
{ bg0: '#140c38', bg1: '#08041c', bg2: '#02010a', accent: '#a855f7', accent2: '#f43f5e', cardBg: 'rgba(18, 10, 48, 0.85)', cardBorder: 'rgba(168, 85, 247, 0.45)', tag: 'NEON HORIZON' },
|
|
92
|
+
],
|
|
93
|
+
fontFamily: (script) => script ? `${script}, 'Orbitron', 'Courier New', monospace` : `'Orbitron', 'Courier New', monospace`,
|
|
94
|
+
fontDisplayWeight: '800',
|
|
95
|
+
backgroundType: 'retro_horizon',
|
|
96
|
+
springTension: 190,
|
|
97
|
+
springFriction: 11,
|
|
98
|
+
},
|
|
99
|
+
nature_organic: {
|
|
100
|
+
name: 'Nature Organic',
|
|
101
|
+
palettes: [
|
|
102
|
+
{ bg0: '#0c2617', bg1: '#05140b', bg2: '#020804', accent: '#22c55e', accent2: '#86efac', cardBg: 'rgba(9, 28, 17, 0.85)', cardBorder: 'rgba(34, 197, 94, 0.35)', tag: 'BIOSPHERE' },
|
|
103
|
+
{ bg0: '#0c2326', bg1: '#051314', bg2: '#020708', accent: '#14b8a6', accent2: '#a7f3d0', cardBg: 'rgba(9, 26, 28, 0.85)', cardBorder: 'rgba(20, 184, 166, 0.35)', tag: 'HYDRO WAVE' },
|
|
104
|
+
],
|
|
105
|
+
fontFamily: (script) => script ? `${script}, 'DM Sans', -apple-system, sans-serif` : `'DM Sans', -apple-system, sans-serif`,
|
|
106
|
+
fontDisplayWeight: '700',
|
|
107
|
+
backgroundType: 'organic_ripples',
|
|
108
|
+
springTension: 140,
|
|
109
|
+
springFriction: 15,
|
|
110
|
+
},
|
|
111
|
+
hype_sports: {
|
|
112
|
+
name: 'Hype Sports',
|
|
113
|
+
palettes: [
|
|
114
|
+
{ bg0: '#2b0c10', bg1: '#170507', bg2: '#0a0102', accent: '#ef4444', accent2: '#f59e0b', cardBg: 'rgba(32, 8, 12, 0.9)', cardBorder: 'rgba(239, 68, 68, 0.45)', tag: 'HIGH VOLTAGE' },
|
|
115
|
+
{ bg0: '#262208', bg1: '#141203', bg2: '#080701', accent: '#84cc16', accent2: '#ef4444', cardBg: 'rgba(28, 25, 6, 0.9)', cardBorder: 'rgba(132, 204, 22, 0.45)', tag: 'MAX INTENSITY' },
|
|
116
|
+
{ bg0: '#141416', bg1: '#0c0c0d', bg2: '#040405', accent: '#ffffff', accent2: '#ef4444', cardBg: 'rgba(18, 18, 20, 0.9)', cardBorder: 'rgba(255, 255, 255, 0.35)', tag: 'OVERDRIVE' },
|
|
117
|
+
],
|
|
118
|
+
fontFamily: (script) => script ? `${script}, 'Anton', 'Teko', 'Arial Black', sans-serif` : `'Anton', 'Teko', 'Arial Black', sans-serif`,
|
|
119
|
+
fontDisplayWeight: '900',
|
|
120
|
+
backgroundType: 'speed_streaks',
|
|
121
|
+
springTension: 240,
|
|
122
|
+
springFriction: 12,
|
|
123
|
+
},
|
|
124
|
+
minimal_luxury: {
|
|
125
|
+
name: 'Minimal Luxury',
|
|
126
|
+
palettes: [
|
|
127
|
+
{ bg0: '#1a1a1c', bg1: '#101012', bg2: '#080809', accent: '#ffffff', accent2: '#a87954', cardBg: 'rgba(18, 18, 20, 0.8)', cardBorder: 'rgba(255, 255, 255, 0.15)', tag: 'ATELIER' },
|
|
128
|
+
{ bg0: '#1c1618', bg1: '#110d0f', bg2: '#080506', accent: '#f43f5e', accent2: '#e2e8f0', cardBg: 'rgba(20, 15, 17, 0.8)', cardBorder: 'rgba(244, 63, 94, 0.25)', tag: 'COUTURE' },
|
|
129
|
+
{ bg0: '#13181a', bg1: '#0b0f10', bg2: '#050708', accent: '#38bdf8', accent2: '#cbd5e1', cardBg: 'rgba(14, 19, 21, 0.8)', cardBorder: 'rgba(56, 189, 248, 0.25)', tag: 'ARCHITECT' },
|
|
130
|
+
],
|
|
131
|
+
fontFamily: (script) => script ? `${script}, 'Plus Jakarta Sans', 'Poppins', -apple-system, sans-serif` : `'Plus Jakarta Sans', 'Poppins', -apple-system, sans-serif`,
|
|
132
|
+
fontDisplayWeight: '600',
|
|
133
|
+
backgroundType: 'geometric_silhouettes',
|
|
134
|
+
springTension: 130,
|
|
135
|
+
springFriction: 17,
|
|
136
|
+
},
|
|
137
|
+
breaking_news: {
|
|
138
|
+
name: 'Breaking News',
|
|
139
|
+
palettes: [
|
|
140
|
+
{ bg0: '#240f13', bg1: '#14070a', bg2: '#080203', accent: '#dc2626', accent2: '#ffffff', cardBg: 'rgba(26, 10, 14, 0.92)', cardBorder: 'rgba(220, 38, 38, 0.45)', tag: 'BREAKING NEWS' },
|
|
141
|
+
{ bg0: '#101c2e', bg1: '#08101c', bg2: '#03060d', accent: '#38bdf8', accent2: '#dc2626', cardBg: 'rgba(11, 22, 38, 0.92)', cardBorder: 'rgba(56, 189, 248, 0.4)', tag: 'SPECIAL REPORT' },
|
|
142
|
+
],
|
|
143
|
+
fontFamily: (script) => script ? `${script}, 'Oswald', 'Franklin Gothic Medium', sans-serif` : `'Oswald', 'Franklin Gothic Medium', sans-serif`,
|
|
144
|
+
fontDisplayWeight: '700',
|
|
145
|
+
backgroundType: 'radar_globe',
|
|
146
|
+
springTension: 220,
|
|
147
|
+
springFriction: 14,
|
|
148
|
+
},
|
|
149
|
+
cosmic_space: {
|
|
150
|
+
name: 'Cosmic Space',
|
|
151
|
+
palettes: [
|
|
152
|
+
{ bg0: '#131138', bg1: '#09081f', bg2: '#03020a', accent: '#38bdf8', accent2: '#c084fc', cardBg: 'rgba(12, 10, 38, 0.88)', cardBorder: 'rgba(56, 189, 248, 0.4)', tag: 'ORBITAL MECHANICS' },
|
|
153
|
+
{ bg0: '#0d1f36', bg1: '#06101c', bg2: '#02050a', accent: '#00f0ff', accent2: '#38bdf8', cardBg: 'rgba(8, 20, 38, 0.88)', cardBorder: 'rgba(0, 240, 255, 0.4)', tag: 'DEEP SPACE' },
|
|
154
|
+
{ bg0: '#1f0d2b', bg1: '#100617', bg2: '#060209', accent: '#f43f5e', accent2: '#e879f9', cardBg: 'rgba(20, 8, 28, 0.88)', cardBorder: 'rgba(244, 63, 94, 0.4)', tag: 'SUPERNOVA' },
|
|
155
|
+
],
|
|
156
|
+
fontFamily: (script) => script ? `${script}, 'Space Grotesk', -apple-system, sans-serif` : `'Space Grotesk', 'Segoe UI', -apple-system, sans-serif`,
|
|
157
|
+
fontDisplayWeight: '700',
|
|
158
|
+
backgroundType: 'cosmic_stars',
|
|
159
|
+
springTension: 150,
|
|
160
|
+
springFriction: 14,
|
|
161
|
+
},
|
|
162
|
+
warm_creative: {
|
|
163
|
+
name: 'Warm Creative',
|
|
164
|
+
palettes: [
|
|
165
|
+
{ bg0: '#2b1910', bg1: '#190d07', bg2: '#0a0502', accent: '#f97316', accent2: '#fbbf24', cardBg: 'rgba(32, 18, 11, 0.85)', cardBorder: 'rgba(249, 115, 22, 0.35)', tag: 'CREATIVE LAB' },
|
|
166
|
+
{ bg0: '#2b1020', bg1: '#180711', bg2: '#090206', accent: '#f43f5e', accent2: '#fbbf24', cardBg: 'rgba(31, 10, 22, 0.85)', cardBorder: 'rgba(244, 63, 94, 0.35)', tag: 'VIBRANT LIFE' },
|
|
167
|
+
],
|
|
168
|
+
fontFamily: (script) => script ? `${script}, 'Outfit', 'Comfortaa', -apple-system, sans-serif` : `'Outfit', 'Comfortaa', -apple-system, sans-serif`,
|
|
169
|
+
fontDisplayWeight: '700',
|
|
170
|
+
backgroundType: 'particles_dust',
|
|
171
|
+
springTension: 160,
|
|
172
|
+
springFriction: 12,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// ── Smart Aesthetic Inference ──────────────────────────────────────────────────
|
|
177
|
+
/**
|
|
178
|
+
* Detect the overall aesthetic from prompt/script metadata, topic, style, and tone.
|
|
179
|
+
*/
|
|
180
|
+
function detectAesthetic({
|
|
181
|
+
topic = '',
|
|
182
|
+
style = '',
|
|
183
|
+
theme = '',
|
|
184
|
+
title = '',
|
|
185
|
+
caption = '',
|
|
186
|
+
narration = '',
|
|
187
|
+
visualDescription = '',
|
|
188
|
+
language = '',
|
|
189
|
+
musicMood = '',
|
|
190
|
+
} = {}) {
|
|
191
|
+
// Explicit override wins
|
|
192
|
+
const explicit = (theme || style || '').toLowerCase().trim();
|
|
193
|
+
if (AESTHETICS[explicit]) return explicit;
|
|
194
|
+
if (/cyber|tech|code|ai|crypto|hacker|futur/i.test(explicit)) return 'cyber_tech';
|
|
195
|
+
if (/doc|history|ancient|story|cinema|kerala|temple|culture/i.test(explicit)) return 'cinematic_documentary';
|
|
196
|
+
if (/finan|market|stock|money|invest|crypto|business/i.test(explicit)) return 'finance_executive';
|
|
197
|
+
if (/synth|retro|80s|vapor|arcade|game|anime/i.test(explicit)) return 'retro_synthwave';
|
|
198
|
+
if (/nature|green|earth|eco|wild|health|meditat|biology/i.test(explicit)) return 'nature_organic';
|
|
199
|
+
if (/sport|hype|fitness|gym|action|extreme|battle|speed/i.test(explicit)) return 'hype_sports';
|
|
200
|
+
if (/luxury|minimal|design|architect|clean|fashion|apple/i.test(explicit)) return 'minimal_luxury';
|
|
201
|
+
if (/news|alert|urgent|crisis|report|scandal/i.test(explicit)) return 'breaking_news';
|
|
202
|
+
if (/space|cosmos|universe|planet|astro|quantum/i.test(explicit)) return 'cosmic_space';
|
|
203
|
+
|
|
204
|
+
// Keyword scan across all textual signals (English + Multilingual)
|
|
205
|
+
const combined = `${topic} ${title} ${visualDescription} ${narration} ${caption} ${musicMood}`.toLowerCase();
|
|
206
|
+
|
|
207
|
+
// Cosmic Space / Quantum / Orbital
|
|
208
|
+
if (/(?:^|[^\w])(quantum|astronomy|galaxy|universe|space|mars|satellite|telescope|cosmos|interstellar|orbit|gps|satellites)(?:[^\w]|$)/.test(combined)
|
|
209
|
+
|| /പ്രപഞ്ച|ബഹിരാകാശ|നക്ഷത്ര|ഗ്രഹം|ഉപഗ്രഹ|ജ്യോതിശാസ്ത്ര|अंतरिक्ष|ब्रह्मांड|तारा|விண்வெளி|பிரபஞ்சம்/.test(combined)) {
|
|
210
|
+
return 'cosmic_space';
|
|
211
|
+
}
|
|
212
|
+
// Cinematic Documentary / History / Heritage / Culture
|
|
213
|
+
if (/(?:^|[^\w])(history|ancient|king|temple|civilization|century|heritage|monument|museum|kerala|parashurama|samuthiri|thiruvithamkoor|tradition|folklore|epic|archive|biography|documentary|empire)(?:[^\w]|$)/.test(combined)
|
|
214
|
+
|| /ചരിത്ര|പുരാതന|സാമ്രാജ്യ|രാജവംശ|ക്ഷേത്ര|പാരമ്പര്യ|സംസ്കാര|കേരള|നാട്ടുരാജ്യ|ഇതിഹാസ|സ്മാരക|വാസ്തുവിദ്യ|इतिहास|प्राचीन|संस्कृति|साम्राज्य|मंदिर|धरोहर|வரலாறு|பழங்கால|பேரரசு|கோவில்|பாரம்பரியம்/.test(combined)) {
|
|
215
|
+
return 'cinematic_documentary';
|
|
216
|
+
}
|
|
217
|
+
// Finance / Markets / Wealth
|
|
218
|
+
if (/(?:^|[^\w])(stock|crypto|bitcoin|market|invest|trading|inflation|economy|gdp|finance|fund|valuation|revenue|profit|banking|shares|trillion|billion|wealth)(?:[^\w]|$)/.test(combined)
|
|
219
|
+
|| /നിക്ഷേപ|ഓഹരി|വിപണി|പണം|സാമ്പത്തിക|ലാഭം|ബാങ്ക്|സമ്പത്ത്|वित्त|बाज़ार|शेयर|निवेश|व्यापार|धन|मुதலீடு|பங்குச்சந்தை|பொருளாதாரம்/.test(combined)) {
|
|
220
|
+
return 'finance_executive';
|
|
221
|
+
}
|
|
222
|
+
// Cyber / Tech / Software
|
|
223
|
+
if (/(?:^|[^\w])(code|algorithm|software|developer|api|python|javascript|rust|neural|llm|compiler|linux|server|cloud|github|framework|database|terminal|cyber|robot)(?:[^\w]|$)/.test(combined)
|
|
224
|
+
|| /സാങ്കേതിക|കോഡിംഗ്|സോഫ്റ്റ്വെയർ|കമ്പ്യൂട്ടർ|പ്രോഗ്രാമിംഗ്|तकनीक|सॉफ्टवेयर|कंप्यूटर|தொழில்நுட்பம்|மென்பொருள்/.test(combined)) {
|
|
225
|
+
return 'cyber_tech';
|
|
226
|
+
}
|
|
227
|
+
// Retro Synthwave
|
|
228
|
+
if (/(?:^|[^\w])(synthwave|80s|neon|retrowave|arcade|pixel|game|vaporwave|outrun|retro)(?:[^\w]|$)/.test(combined)) {
|
|
229
|
+
return 'retro_synthwave';
|
|
230
|
+
}
|
|
231
|
+
// Nature / Organic
|
|
232
|
+
if (/(?:^|[^\w])(nature|forest|ocean|plant|wildlife|animal|tree|climate|carbon|organic|meditation|ayurveda|ecology|water|river|green)(?:[^\w]|$)/.test(combined)
|
|
233
|
+
|| /പ്രകൃതി|വനം|കാട്|പരിസ്ഥിതി|ആയുർവേദ|ജീവൻ|പച്ചപ്പ്|നദി|പ്രകൃതിദത്ത|प्रकृति|जंगल|पर्यावरण|आयुर्वेद|இயற்கை|சுற்றுச்சூழல்/.test(combined)) {
|
|
234
|
+
return 'nature_organic';
|
|
235
|
+
}
|
|
236
|
+
// Hype Sports
|
|
237
|
+
if (/(?:^|[^\w])(sports|workout|gym|athlete|championship|extreme|speed|race|muscle|football|cricket|danger|vs|versus|hype|adrenaline)(?:[^\w]|$)/.test(combined)
|
|
238
|
+
|| /കായിക|മത്സര|വേഗത|വ്യായാമം|ചാമ്പ്യൻ|खेल|कसरत|प्रतियोगिता|விளையாட்டு/.test(combined)) {
|
|
239
|
+
return 'hype_sports';
|
|
240
|
+
}
|
|
241
|
+
// Breaking News
|
|
242
|
+
if (/(?:^|[^\w])(breaking|news|headline|reported|urgent|scandal|investigation|exclusive|update|alert|police|crisis)(?:[^\w]|$)/.test(combined)
|
|
243
|
+
|| /വാർത്ത|അടിയന്തര|ബ്രേക്കിംഗ്|അറിയിപ്പ്|समाचार|ताज़ा खबर|आपातकाल|செய்தி|முக்கிய செய்தி/.test(combined)) {
|
|
244
|
+
return 'breaking_news';
|
|
245
|
+
}
|
|
246
|
+
// Minimal Luxury
|
|
247
|
+
if (/(?:^|[^\w])(luxury|minimal|architect|premium|bespoke|couture|gallery|elegance|aesthetic|curated|pure)(?:[^\w]|$)/.test(combined)) {
|
|
248
|
+
return 'minimal_luxury';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Fallback by music mood if available
|
|
252
|
+
if (musicMood === 'energetic') return 'hype_sports';
|
|
253
|
+
if (musicMood === 'tense') return 'breaking_news';
|
|
254
|
+
if (musicMood === 'dramatic') return 'cinematic_documentary';
|
|
255
|
+
if (musicMood === 'calm') return 'nature_organic';
|
|
256
|
+
if (musicMood === 'mysterious') return 'cosmic_space';
|
|
257
|
+
|
|
258
|
+
// Default to versatile cyber_tech for general modern explainers
|
|
259
|
+
return 'cyber_tech';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ── Component Theme Detection ──────────────────────────────────────────────────
|
|
263
|
+
/**
|
|
264
|
+
* Detect the graphic entity to render on the full canvas for a given scene.
|
|
265
|
+
*/
|
|
266
|
+
function detectComponentTheme({ visualDescription = '', caption = '', narration = '', topic = '', imagePath = null, aesthetic = 'cyber_tech' } = {}) {
|
|
267
|
+
if (imagePath) return 'image';
|
|
268
|
+
const text = `${visualDescription} ${caption} ${narration} ${topic}`.toLowerCase();
|
|
269
|
+
|
|
270
|
+
// Celestial / Orbiting satellites (e.g. GPS, orbital mechanics, planetary science)
|
|
271
|
+
if (/(?:^|[^\w])(orbit|satellite|satellites|gps|planet|earth|mars|moon|galaxy|universe|solar|astronomy|gravity|space|telescope|interstellar)(?:[^\w]|$)/.test(text)
|
|
272
|
+
|| /ബഹിരാകാശ|ഗ്രഹം|ഉപഗ്രഹ|ഭൂമി|നക്ഷത്ര|പ്രപഞ്ച|अंतरिक्ष|ग्रह|तारा|விண்வெளி/.test(text)) {
|
|
273
|
+
return 'celestial_orbit';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// DNA / Organic cellular helix
|
|
277
|
+
if (/(?:^|[^\w])(dna|helix|gene|cell|molecule|biology|organism|protein|virus|bacteria|medical|genetic|chromosome)(?:[^\w]|$)/.test(text)
|
|
278
|
+
|| /ഡിഎൻഎ|കോശം|തന്മാത്ര|ജീവശാസ്ത്ര|डीएनए|कोशिका/.test(text)) {
|
|
279
|
+
return 'dna_helix';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Radar Globe / World Map
|
|
283
|
+
if (/(?:^|[^\w])(globe|map|world|geography|radar|international|continent|territory|country|border|location)(?:[^\w]|$)/.test(text)
|
|
284
|
+
|| /ഭൂപടം|ലോകം|ഭൂഖണ്ഡം|ഭൂപട|नक्शा|ग्लोब/.test(text)) {
|
|
285
|
+
return 'radar_globe';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Quote / Speech
|
|
289
|
+
if (/(?:^|[^\w])(quote|speech|said|proverb|statement|narrator|words|motto|philosophy|wisdom)(?:[^\w]|$)/.test(text)
|
|
290
|
+
|| /വാചകം|ഉദ്ധരണി|വാക്കുകൾ|कथन/.test(text)) {
|
|
291
|
+
return 'quote';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// High Velocity Speed Action
|
|
295
|
+
if (/(?:^|[^\w])(sports|workout|gym|athlete|speed|streaks|race|championship|extreme|versus|vs|hype|adrenaline|power|velocity|fast)(?:[^\w]|$)/.test(text)
|
|
296
|
+
|| /കായിക|മത്സര|വേഗത|വ്യായാമം|खेल|दौड़/.test(text)) {
|
|
297
|
+
return 'speed_action';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Metric / Numerical KPI
|
|
301
|
+
if (/(?:^|[^\w])(metric|counter|number|percentage|score|stat|gain|roi|accuracy|target|kpi|growth)(?:[^\w]|$)/.test(text)
|
|
302
|
+
|| /ശതമാനം|അളവ്|പ്രകടനം|प्रतिशत/.test(text)) {
|
|
303
|
+
return 'metric';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Timeline / Milestones / Chronology
|
|
307
|
+
if (/(?:^|[^\w])(timeline|milestone|chronology|history|evolution|roadmap|phase|journey|centuries|years|era|ancient|century|dynasty|temple|monument)(?:[^\w]|$)/.test(text)
|
|
308
|
+
|| /ചരിത്ര|പുരാതന|സാമ്രാജ്യ|രാജവംശ|പാരമ്പര്യ|ഇതിഹാസ|इतिहास|प्राचीन|धरोहर/.test(text)) {
|
|
309
|
+
return 'timeline';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Code / Programming IDE
|
|
313
|
+
if (/(?:^|[^\w])(code|function|syntax|api|javascript|python|typescript|json|sql|query|endpoint|algorithm|struct|cli|terminal|bash|shell|npm|git|compiler|linux|rust)(?:[^\w]|$)/.test(text)
|
|
314
|
+
|| /കോഡിംഗ്|സോഫ്റ്റ്വെയർ|പ്രോഗ്രാമിംഗ്|कोड|सॉफ्टवेयर/.test(text)) {
|
|
315
|
+
return 'code';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Chart / Finance / Economics
|
|
319
|
+
if (/(?:^|[^\w])(chart|data|analytics|growth|graph|statistic|bars|volume|distribution|stock|crypto|bitcoin|market|invest|trading|inflation|economy|gdp|finance|gain|loss|bull|bear|candlestick|price)(?:[^\w]|$)/.test(text)
|
|
320
|
+
|| /നിക്ഷേപ|ഓഹരി|വിപണി|പണം|ചാർട്ട്|शेयर|बाज़ार/.test(text)) {
|
|
321
|
+
return 'chart';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Neural Network Nodes
|
|
325
|
+
if (/(?:^|[^\w])(network|node|nodes|ai|model|neural|pipeline|architecture|server|system|mesh|connect|synapse|deep learning)(?:[^\w]|$)/.test(text)
|
|
326
|
+
|| /നെറ്റ്വർക്ക്|സിനാപ്സ്|ശൃംഖല|नेटवर्क/.test(text)) {
|
|
327
|
+
return 'nodes';
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// 3D Isometric Geometry / Polyhedron
|
|
331
|
+
if (/(?:^|[^\w])(cube|geometry|3d|wireframe|isometric|matrix|crystal|structure|polyhedron|dimension|architect|blueprint|math)(?:[^\w]|$)/.test(text)
|
|
332
|
+
|| /ജ്യാമിതി|രൂപം|ത്രിമാന|संरचना/.test(text)) {
|
|
333
|
+
return 'isometric_geometry';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Fallbacks matched to aesthetic
|
|
337
|
+
if (aesthetic === 'cosmic_space') return 'celestial_orbit';
|
|
338
|
+
if (aesthetic === 'cinematic_documentary') return 'timeline';
|
|
339
|
+
if (aesthetic === 'finance_executive') return 'chart';
|
|
340
|
+
if (aesthetic === 'nature_organic') return 'dna_helix';
|
|
341
|
+
if (aesthetic === 'breaking_news') return 'radar_globe';
|
|
342
|
+
if (aesthetic === 'cyber_tech') return 'nodes';
|
|
343
|
+
if (aesthetic === 'minimal_luxury') return 'isometric_geometry';
|
|
344
|
+
if (aesthetic === 'hype_sports') return 'speed_action';
|
|
345
|
+
return 'waveform';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ── Harness HTML Wrapper for User / Agent Code ────────────────────────────────
|
|
349
|
+
/**
|
|
350
|
+
* Wrap agent-provided Canvas drawing code into a pure-function-of-t harness page.
|
|
351
|
+
*/
|
|
352
|
+
function wrapCodeInHarnessHtml({
|
|
353
|
+
code = '',
|
|
354
|
+
duration = 3,
|
|
355
|
+
width = 1920,
|
|
356
|
+
height = 1080,
|
|
357
|
+
language = '',
|
|
358
|
+
style = '',
|
|
359
|
+
topic = '',
|
|
360
|
+
} = {}) {
|
|
361
|
+
const scriptFont = getScriptFontStack('', language);
|
|
362
|
+
const aestheticKey = detectAesthetic({ topic, style, language });
|
|
363
|
+
const aesthetic = AESTHETICS[aestheticKey] || AESTHETICS.cyber_tech;
|
|
364
|
+
const fontDisplay = aesthetic.fontFamily(scriptFont);
|
|
365
|
+
const fontBody = scriptFont ? `${scriptFont}, -apple-system, sans-serif` : `'Segoe UI', -apple-system, sans-serif`;
|
|
366
|
+
|
|
367
|
+
return `<!doctype html>
|
|
368
|
+
<html lang="en">
|
|
369
|
+
<head>
|
|
370
|
+
<meta charset="utf-8">
|
|
371
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
372
|
+
<style>
|
|
373
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
374
|
+
html, body {
|
|
375
|
+
width: 100%; height: 100%; overflow: hidden;
|
|
376
|
+
background: #06090e;
|
|
377
|
+
font-family: ${fontBody};
|
|
378
|
+
-webkit-font-smoothing: antialiased;
|
|
379
|
+
}
|
|
380
|
+
canvas { width: 100%; height: 100%; display: block; }
|
|
381
|
+
</style>
|
|
382
|
+
</head>
|
|
383
|
+
<body>
|
|
384
|
+
<canvas id="stage" width="${width}" height="${height}"></canvas>
|
|
385
|
+
<script>
|
|
386
|
+
window.DURATION = ${Math.max(1, Number(duration) || 3)};
|
|
387
|
+
const canvas = document.getElementById('stage');
|
|
388
|
+
const ctx = canvas.getContext('2d');
|
|
389
|
+
const W = ${width};
|
|
390
|
+
const H = ${height};
|
|
391
|
+
const FONT_DISPLAY = ${JSON.stringify(fontDisplay)};
|
|
392
|
+
const FONT_BODY = ${JSON.stringify(fontBody)};
|
|
393
|
+
|
|
394
|
+
// Seeded PRNG for deterministic pure-t rendering
|
|
395
|
+
function sfc32(a, b, c, d) {
|
|
396
|
+
return function() {
|
|
397
|
+
a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0;
|
|
398
|
+
let t = (a + b | 0) + d | 0;
|
|
399
|
+
d = d + 1 | 0;
|
|
400
|
+
a = b ^ b >>> 9;
|
|
401
|
+
b = c + (c << 3) | 0;
|
|
402
|
+
c = c << 21 | c >>> 11;
|
|
403
|
+
c = c + t | 0;
|
|
404
|
+
return (t >>> 0) / 4294967296;
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const hash = (i) => sfc32(i * 19 + 42, i * 29 + 1337, i * 13 + 7, 99)();
|
|
408
|
+
|
|
409
|
+
// Spring dynamics
|
|
410
|
+
function spring(t, delay = 0, tension = 180, friction = 14) {
|
|
411
|
+
const dt = t - delay;
|
|
412
|
+
if (dt <= 0) return 0;
|
|
413
|
+
const omega = Math.sqrt(tension);
|
|
414
|
+
const zeta = friction / (2 * omega);
|
|
415
|
+
if (zeta < 1) {
|
|
416
|
+
const omegaD = omega * Math.sqrt(1 - zeta * zeta);
|
|
417
|
+
return 1 - Math.exp(-zeta * omega * dt) * (Math.cos(omegaD * dt) + (zeta * omega / omegaD) * Math.sin(omegaD * dt));
|
|
418
|
+
}
|
|
419
|
+
return 1 - Math.exp(-omega * dt);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
window.seek = async (t) => {
|
|
423
|
+
${code}
|
|
424
|
+
};
|
|
425
|
+
</script>
|
|
426
|
+
</body>
|
|
427
|
+
</html>`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ── HTML Scene Code Generator ──────────────────────────────────────────────────
|
|
431
|
+
/**
|
|
432
|
+
* Generate a complete, self-contained HTML scene conforming to window.seek(t).
|
|
433
|
+
* Paints full-canvas cinematic procedural animations — zero PPT card slideshow patterns.
|
|
434
|
+
*/
|
|
435
|
+
function generateSceneHtml({
|
|
436
|
+
caption = '',
|
|
437
|
+
narration = '',
|
|
438
|
+
visualDescription = '',
|
|
439
|
+
imagePath = null,
|
|
440
|
+
topic = '',
|
|
441
|
+
style = '',
|
|
442
|
+
theme = '',
|
|
443
|
+
title = '',
|
|
444
|
+
language = '',
|
|
445
|
+
musicMood = 'neutral',
|
|
446
|
+
duration = 3,
|
|
447
|
+
width = 1920,
|
|
448
|
+
height = 1080,
|
|
449
|
+
sceneIndex = 0,
|
|
450
|
+
} = {}) {
|
|
451
|
+
const aestheticKey = detectAesthetic({
|
|
452
|
+
topic,
|
|
453
|
+
style,
|
|
454
|
+
theme,
|
|
455
|
+
title,
|
|
456
|
+
narration,
|
|
457
|
+
caption,
|
|
458
|
+
visualDescription,
|
|
459
|
+
language,
|
|
460
|
+
musicMood,
|
|
461
|
+
});
|
|
462
|
+
const aesthetic = AESTHETICS[aestheticKey] || AESTHETICS.cyber_tech;
|
|
463
|
+
const compTheme = detectComponentTheme({
|
|
464
|
+
visualDescription,
|
|
465
|
+
caption,
|
|
466
|
+
narration,
|
|
467
|
+
topic,
|
|
468
|
+
imagePath,
|
|
469
|
+
aesthetic: aestheticKey,
|
|
470
|
+
});
|
|
471
|
+
const clampedDur = Math.max(1, Number(duration) || 3);
|
|
472
|
+
|
|
473
|
+
// Select palette variation based on sceneIndex
|
|
474
|
+
const pal = aesthetic.palettes[sceneIndex % aesthetic.palettes.length];
|
|
475
|
+
|
|
476
|
+
// Resolve script-specific fonts
|
|
477
|
+
const scriptFont = getScriptFontStack(`${caption} ${narration}`, language);
|
|
478
|
+
const fontDisplay = aesthetic.fontFamily(scriptFont);
|
|
479
|
+
const fontBody = scriptFont ? `${scriptFont}, -apple-system, sans-serif` : `'Segoe UI', -apple-system, sans-serif`;
|
|
480
|
+
|
|
481
|
+
// Convert imagePath to data URI if available
|
|
482
|
+
let imageDataUri = '';
|
|
483
|
+
if (imagePath && fs.existsSync(imagePath)) {
|
|
484
|
+
try {
|
|
485
|
+
const ext = path.extname(imagePath).toLowerCase().slice(1) || 'jpeg';
|
|
486
|
+
const mime = ext === 'png' ? 'image/png' : ext === 'webp' ? 'image/webp' : 'image/jpeg';
|
|
487
|
+
const b64 = fs.readFileSync(imagePath).toString('base64');
|
|
488
|
+
imageDataUri = `data:${mime};base64,${b64}`;
|
|
489
|
+
} catch (_) {}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return `<!doctype html>
|
|
493
|
+
<html lang="en">
|
|
494
|
+
<head>
|
|
495
|
+
<meta charset="utf-8">
|
|
496
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
497
|
+
<style>
|
|
498
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
499
|
+
html, body {
|
|
500
|
+
width: 100%;
|
|
501
|
+
height: 100%;
|
|
502
|
+
overflow: hidden;
|
|
503
|
+
background: ${pal.bg2};
|
|
504
|
+
font-family: ${fontBody};
|
|
505
|
+
-webkit-font-smoothing: antialiased;
|
|
506
|
+
}
|
|
507
|
+
canvas {
|
|
508
|
+
width: 100%;
|
|
509
|
+
height: 100%;
|
|
510
|
+
display: block;
|
|
511
|
+
}
|
|
512
|
+
</style>
|
|
513
|
+
</head>
|
|
514
|
+
<body>
|
|
515
|
+
<canvas id="stage" width="${width}" height="${height}"></canvas>
|
|
516
|
+
<script>
|
|
517
|
+
window.DURATION = ${clampedDur};
|
|
518
|
+
const canvas = document.getElementById('stage');
|
|
519
|
+
const ctx = canvas.getContext('2d');
|
|
520
|
+
const W = ${width};
|
|
521
|
+
const H = ${height};
|
|
522
|
+
|
|
523
|
+
// Seeded PRNG for frame purity
|
|
524
|
+
function sfc32(a, b, c, d) {
|
|
525
|
+
return function() {
|
|
526
|
+
a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0;
|
|
527
|
+
let t = (a + b | 0) + d | 0;
|
|
528
|
+
d = d + 1 | 0;
|
|
529
|
+
a = b ^ b >>> 9;
|
|
530
|
+
b = c + (c << 3) | 0;
|
|
531
|
+
c = c << 21 | c >>> 11;
|
|
532
|
+
c = c + t | 0;
|
|
533
|
+
return (t >>> 0) / 4294967296;
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const rng = sfc32(${42 + sceneIndex * 19}, ${1337 + sceneIndex * 29}, ${7 + sceneIndex * 13}, 99);
|
|
538
|
+
const PARTICLES = Array.from({ length: 75 }, () => ({
|
|
539
|
+
x: rng() * W,
|
|
540
|
+
y: rng() * H,
|
|
541
|
+
vx: (rng() - 0.5) * 40,
|
|
542
|
+
vy: (rng() - 0.5) * 30 - 10,
|
|
543
|
+
r: 1.5 + rng() * 3.5,
|
|
544
|
+
alpha: 0.2 + rng() * 0.6
|
|
545
|
+
}));
|
|
546
|
+
|
|
547
|
+
// Spring dynamics function
|
|
548
|
+
function spring(t, delay = 0, tension = ${aesthetic.springTension}, friction = ${aesthetic.springFriction}) {
|
|
549
|
+
const dt = t - delay;
|
|
550
|
+
if (dt <= 0) return 0;
|
|
551
|
+
const omega = Math.sqrt(tension);
|
|
552
|
+
const zeta = friction / (2 * omega);
|
|
553
|
+
if (zeta < 1) {
|
|
554
|
+
const omegaD = omega * Math.sqrt(1 - zeta * zeta);
|
|
555
|
+
return 1 - Math.exp(-zeta * omega * dt) * (Math.cos(omegaD * dt) + (zeta * omega / omegaD) * Math.sin(omegaD * dt));
|
|
556
|
+
}
|
|
557
|
+
return 1 - Math.exp(-omega * dt);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const AESTHETIC = ${JSON.stringify(aestheticKey)};
|
|
561
|
+
const BG_TYPE = ${JSON.stringify(aesthetic.backgroundType)};
|
|
562
|
+
const COMP_THEME = ${JSON.stringify(compTheme)};
|
|
563
|
+
const CAPTION = ${JSON.stringify(caption)};
|
|
564
|
+
const NARRATION = ${JSON.stringify(narration)};
|
|
565
|
+
const TAG = ${JSON.stringify(pal.tag)};
|
|
566
|
+
const ACCENT = ${JSON.stringify(pal.accent)};
|
|
567
|
+
const ACCENT2 = ${JSON.stringify(pal.accent2)};
|
|
568
|
+
const BG0 = ${JSON.stringify(pal.bg0)};
|
|
569
|
+
const BG1 = ${JSON.stringify(pal.bg1)};
|
|
570
|
+
const BG2 = ${JSON.stringify(pal.bg2)};
|
|
571
|
+
const CARD_BG = ${JSON.stringify(pal.cardBg)};
|
|
572
|
+
const CARD_BORDER = ${JSON.stringify(pal.cardBorder)};
|
|
573
|
+
const FONT_DISPLAY = ${JSON.stringify(fontDisplay)};
|
|
574
|
+
const FONT_BODY = ${JSON.stringify(fontBody)};
|
|
575
|
+
const FONT_WEIGHT = ${JSON.stringify(aesthetic.fontDisplayWeight)};
|
|
576
|
+
const IMAGE_SRC = ${JSON.stringify(imageDataUri)};
|
|
577
|
+
|
|
578
|
+
let cachedImage = null;
|
|
579
|
+
if (IMAGE_SRC) {
|
|
580
|
+
cachedImage = new Image();
|
|
581
|
+
cachedImage.src = IMAGE_SRC;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// ── Background Ambient Layers ──────────────────────────────────────────────────
|
|
585
|
+
function drawBackground(t) {
|
|
586
|
+
const cx = W / 2 + Math.sin(t * 0.5) * 120;
|
|
587
|
+
const cy = H / 2 + Math.cos(t * 0.4) * 70;
|
|
588
|
+
const bgGrad = ctx.createRadialGradient(cx, cy, 60, W / 2, H / 2, W * 0.95);
|
|
589
|
+
bgGrad.addColorStop(0, BG0);
|
|
590
|
+
bgGrad.addColorStop(0.55, BG1);
|
|
591
|
+
bgGrad.addColorStop(1, BG2);
|
|
592
|
+
ctx.fillStyle = bgGrad;
|
|
593
|
+
ctx.fillRect(0, 0, W, H);
|
|
594
|
+
|
|
595
|
+
ctx.save();
|
|
596
|
+
if (BG_TYPE === 'grid_perspective') {
|
|
597
|
+
// 3D Perspective Grid
|
|
598
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
|
|
599
|
+
ctx.lineWidth = 1;
|
|
600
|
+
const horizon = H * 0.52;
|
|
601
|
+
const shift = (t * 40) % 50;
|
|
602
|
+
for (let x = -W * 0.5; x <= W * 1.5; x += 90) {
|
|
603
|
+
ctx.beginPath(); ctx.moveTo(W / 2, horizon); ctx.lineTo(x, H); ctx.stroke();
|
|
604
|
+
}
|
|
605
|
+
for (let y = horizon + shift; y <= H; y += 40) {
|
|
606
|
+
const p = (y - horizon) / (H - horizon);
|
|
607
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, ' + (0.02 + p * 0.08) + ')';
|
|
608
|
+
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
|
|
609
|
+
}
|
|
610
|
+
} else if (BG_TYPE === 'candlestick_charts') {
|
|
611
|
+
// Ambient market backdrop grid
|
|
612
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)';
|
|
613
|
+
ctx.lineWidth = 1;
|
|
614
|
+
for (let y = 80; y < H; y += 80) {
|
|
615
|
+
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
|
|
616
|
+
}
|
|
617
|
+
for (let x = 120; x < W; x += 120) {
|
|
618
|
+
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke();
|
|
619
|
+
}
|
|
620
|
+
} else if (BG_TYPE === 'retro_horizon') {
|
|
621
|
+
// Retrowave Sun & Floor Grid
|
|
622
|
+
const sunY = H * 0.44;
|
|
623
|
+
const sunR = 150;
|
|
624
|
+
const sunGrad = ctx.createLinearGradient(0, sunY - sunR, 0, sunY + sunR);
|
|
625
|
+
sunGrad.addColorStop(0, '#ffd000');
|
|
626
|
+
sunGrad.addColorStop(0.5, '#ff7300');
|
|
627
|
+
sunGrad.addColorStop(1, '#ff2a85');
|
|
628
|
+
ctx.fillStyle = sunGrad;
|
|
629
|
+
ctx.beginPath(); ctx.arc(W / 2, sunY, sunR, Math.PI, 0); ctx.fill();
|
|
630
|
+
|
|
631
|
+
ctx.strokeStyle = ACCENT;
|
|
632
|
+
const fShift = (t * 50) % 40;
|
|
633
|
+
for (let y = sunY + fShift; y <= H; y += 35) {
|
|
634
|
+
const a = (y - sunY) / (H - sunY);
|
|
635
|
+
ctx.strokeStyle = 'rgba(255, 42, 133, ' + (a * 0.3) + ')';
|
|
636
|
+
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
|
|
637
|
+
}
|
|
638
|
+
} else if (BG_TYPE === 'organic_ripples') {
|
|
639
|
+
ctx.lineWidth = 2;
|
|
640
|
+
for (let w = 0; w < 4; w++) {
|
|
641
|
+
ctx.strokeStyle = 'rgba(34, 197, 94, ' + (0.05 + w * 0.03) + ')';
|
|
642
|
+
ctx.beginPath();
|
|
643
|
+
for (let x = 0; x <= W; x += 30) {
|
|
644
|
+
const y = H * (0.35 + w * 0.15) + Math.sin(x * 0.005 + t * 1.5 + w) * 35;
|
|
645
|
+
if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
646
|
+
}
|
|
647
|
+
ctx.stroke();
|
|
648
|
+
}
|
|
649
|
+
} else if (BG_TYPE === 'speed_streaks') {
|
|
650
|
+
ctx.strokeStyle = 'rgba(239, 68, 68, 0.12)';
|
|
651
|
+
ctx.lineWidth = 2;
|
|
652
|
+
for (let i = 0; i < 20; i++) {
|
|
653
|
+
const sx = ((i * 110 + t * 600) % (W * 1.4)) - W * 0.2;
|
|
654
|
+
ctx.beginPath(); ctx.moveTo(sx, 0); ctx.lineTo(sx - 200, H); ctx.stroke();
|
|
655
|
+
}
|
|
656
|
+
} else if (BG_TYPE === 'particles_dust' || BG_TYPE === 'cosmic_stars') {
|
|
657
|
+
// Parallax particle atmosphere
|
|
658
|
+
for (let i = 0; i < PARTICLES.length; i++) {
|
|
659
|
+
const p = PARTICLES[i];
|
|
660
|
+
const px = (p.x + p.vx * t + W * 2) % W;
|
|
661
|
+
const py = (p.y + p.vy * t + H * 2) % H;
|
|
662
|
+
ctx.fillStyle = ACCENT;
|
|
663
|
+
ctx.globalAlpha = p.alpha * (0.4 + 0.6 * Math.sin(t * 2 + i));
|
|
664
|
+
ctx.beginPath(); ctx.arc(px, py, p.r, 0, Math.PI * 2); ctx.fill();
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
ctx.restore();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// ── Full-Canvas Cinematic Visual Directors ─────────────────────────────────────
|
|
671
|
+
function drawMainScene(t) {
|
|
672
|
+
ctx.save();
|
|
673
|
+
const enterSpring = spring(t, 0.05);
|
|
674
|
+
const sceneScale = Math.min(1.02, Math.max(0.9, enterSpring));
|
|
675
|
+
ctx.translate(W / 2, H * 0.40);
|
|
676
|
+
ctx.scale(sceneScale, sceneScale);
|
|
677
|
+
ctx.translate(-W / 2, -H * 0.40);
|
|
678
|
+
|
|
679
|
+
const cx = W / 2;
|
|
680
|
+
const cy = H * 0.40;
|
|
681
|
+
|
|
682
|
+
if (COMP_THEME === 'celestial_orbit') {
|
|
683
|
+
// 3D Celestial Body (Earth) with orbiting satellites & signal pulses
|
|
684
|
+
const earthR = Math.min(W, H) * 0.15;
|
|
685
|
+
|
|
686
|
+
// Atmospheric halo
|
|
687
|
+
const haloGrad = ctx.createRadialGradient(cx, cy, earthR * 0.9, cx, cy, earthR * 1.45);
|
|
688
|
+
haloGrad.addColorStop(0, 'rgba(56, 189, 248, 0.4)');
|
|
689
|
+
haloGrad.addColorStop(1, 'rgba(56, 189, 248, 0)');
|
|
690
|
+
ctx.fillStyle = haloGrad;
|
|
691
|
+
ctx.beginPath(); ctx.arc(cx, cy, earthR * 1.45, 0, Math.PI * 2); ctx.fill();
|
|
692
|
+
|
|
693
|
+
// Planet body
|
|
694
|
+
const planetGrad = ctx.createRadialGradient(cx - earthR * 0.35, cy - earthR * 0.35, earthR * 0.1, cx, cy, earthR);
|
|
695
|
+
planetGrad.addColorStop(0, '#38bdf8');
|
|
696
|
+
planetGrad.addColorStop(0.7, '#0284c7');
|
|
697
|
+
planetGrad.addColorStop(1, '#082f49');
|
|
698
|
+
ctx.fillStyle = planetGrad;
|
|
699
|
+
ctx.beginPath(); ctx.arc(cx, cy, earthR, 0, Math.PI * 2); ctx.fill();
|
|
700
|
+
|
|
701
|
+
// Rotating longitude meridians
|
|
702
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.22)';
|
|
703
|
+
ctx.lineWidth = 1.5;
|
|
704
|
+
for (let m = 0; m < 4; m++) {
|
|
705
|
+
const mAngle = t * 0.5 + (m * Math.PI) / 4;
|
|
706
|
+
const mWidth = earthR * Math.cos(mAngle);
|
|
707
|
+
ctx.beginPath();
|
|
708
|
+
ctx.ellipse(cx, cy, Math.abs(mWidth), earthR, 0, 0, Math.PI * 2);
|
|
709
|
+
ctx.stroke();
|
|
710
|
+
}
|
|
711
|
+
// Latitude parallel rings
|
|
712
|
+
for (let lat = -1; lat <= 1; lat += 2) {
|
|
713
|
+
const latY = cy + lat * earthR * 0.5;
|
|
714
|
+
const latW = earthR * Math.cos(Math.asin(0.5));
|
|
715
|
+
ctx.beginPath();
|
|
716
|
+
ctx.ellipse(cx, latY, latW, earthR * 0.15, 0, 0, Math.PI * 2);
|
|
717
|
+
ctx.stroke();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// 3 3D-inclined Elliptical Orbits with Satellites
|
|
721
|
+
const orbitAngles = [-0.55, 0.45, 1.2];
|
|
722
|
+
for (let k = 0; k < 3; k++) {
|
|
723
|
+
const a = earthR * (1.85 + k * 0.35);
|
|
724
|
+
const b = a * 0.45;
|
|
725
|
+
const tilt = orbitAngles[k];
|
|
726
|
+
const satSpeed = 0.85 + k * 0.25;
|
|
727
|
+
const satAngle = t * satSpeed + k * 2.1;
|
|
728
|
+
|
|
729
|
+
// Draw orbit path
|
|
730
|
+
ctx.save();
|
|
731
|
+
ctx.translate(cx, cy);
|
|
732
|
+
ctx.rotate(tilt);
|
|
733
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.14)';
|
|
734
|
+
ctx.lineWidth = 1.5;
|
|
735
|
+
ctx.beginPath(); ctx.ellipse(0, 0, a, b, 0, 0, Math.PI * 2); ctx.stroke();
|
|
736
|
+
|
|
737
|
+
// Satellite coordinates in tilted frame
|
|
738
|
+
const sxLoc = a * Math.cos(satAngle);
|
|
739
|
+
const syLoc = b * Math.sin(satAngle);
|
|
740
|
+
ctx.restore();
|
|
741
|
+
|
|
742
|
+
// Convert to canvas coordinates
|
|
743
|
+
const sx = cx + sxLoc * Math.cos(tilt) - syLoc * Math.sin(tilt);
|
|
744
|
+
const sy = cy + sxLoc * Math.sin(tilt) + syLoc * Math.cos(tilt);
|
|
745
|
+
|
|
746
|
+
// Spherical microwave signal pulses from satellite
|
|
747
|
+
for (let wave = 0; wave < 3; wave++) {
|
|
748
|
+
const waveProgress = ((t * 1.6 + wave * 0.33 + k * 0.2) % 1);
|
|
749
|
+
const waveR = waveProgress * earthR * 1.6;
|
|
750
|
+
const waveAlpha = (1 - waveProgress) * 0.5;
|
|
751
|
+
ctx.strokeStyle = k === 0 ? ACCENT : ACCENT2;
|
|
752
|
+
ctx.globalAlpha = waveAlpha;
|
|
753
|
+
ctx.lineWidth = 2;
|
|
754
|
+
ctx.beginPath(); ctx.arc(sx, sy, waveR, 0, Math.PI * 2); ctx.stroke();
|
|
755
|
+
}
|
|
756
|
+
ctx.globalAlpha = 1;
|
|
757
|
+
|
|
758
|
+
// Draw satellite body
|
|
759
|
+
ctx.fillStyle = '#ffffff';
|
|
760
|
+
ctx.beginPath(); ctx.arc(sx, sy, 5, 0, Math.PI * 2); ctx.fill();
|
|
761
|
+
// Solar panel wings
|
|
762
|
+
ctx.fillStyle = ACCENT;
|
|
763
|
+
ctx.fillRect(sx - 12, sy - 3, 24, 6);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Target beacon on Earth surface
|
|
767
|
+
const beaconX = cx + earthR * 0.28;
|
|
768
|
+
const beaconY = cy + earthR * 0.32;
|
|
769
|
+
const beaconPulse = 1 + 0.35 * Math.sin(t * 5);
|
|
770
|
+
ctx.strokeStyle = ACCENT;
|
|
771
|
+
ctx.lineWidth = 2;
|
|
772
|
+
ctx.beginPath(); ctx.arc(beaconX, beaconY, 14 * beaconPulse, 0, Math.PI * 2); ctx.stroke();
|
|
773
|
+
ctx.fillStyle = '#ffffff';
|
|
774
|
+
ctx.beginPath(); ctx.arc(beaconX, beaconY, 4, 0, Math.PI * 2); ctx.fill();
|
|
775
|
+
|
|
776
|
+
} else if (COMP_THEME === 'dna_helix') {
|
|
777
|
+
// 3D Rotating DNA Double Helix
|
|
778
|
+
const rungs = 24;
|
|
779
|
+
const spanW = W * 0.72;
|
|
780
|
+
const startX = cx - spanW / 2;
|
|
781
|
+
const stepX = spanW / (rungs - 1);
|
|
782
|
+
const amp = 105;
|
|
783
|
+
|
|
784
|
+
for (let i = 0; i < rungs; i++) {
|
|
785
|
+
const rx = startX + i * stepX;
|
|
786
|
+
const phase = rx * 0.007 + t * 2.2;
|
|
787
|
+
const y1 = cy + Math.sin(phase) * amp;
|
|
788
|
+
const y2 = cy - Math.sin(phase) * amp;
|
|
789
|
+
const depth = Math.cos(phase); // z-depth
|
|
790
|
+
|
|
791
|
+
// Connecting nucleotide rung
|
|
792
|
+
ctx.strokeStyle = depth > 0 ? ACCENT : 'rgba(255, 255, 255, 0.2)';
|
|
793
|
+
ctx.lineWidth = depth > 0 ? 3 : 1.5;
|
|
794
|
+
ctx.beginPath(); ctx.moveTo(rx, y1); ctx.lineTo(rx, y2); ctx.stroke();
|
|
795
|
+
|
|
796
|
+
// Nodes on strands with depth perspective
|
|
797
|
+
const r1 = 5 + depth * 3;
|
|
798
|
+
const r2 = 5 - depth * 3;
|
|
799
|
+
ctx.fillStyle = ACCENT;
|
|
800
|
+
ctx.beginPath(); ctx.arc(rx, y1, Math.max(2, r1), 0, Math.PI * 2); ctx.fill();
|
|
801
|
+
ctx.fillStyle = ACCENT2;
|
|
802
|
+
ctx.beginPath(); ctx.arc(rx, y2, Math.max(2, r2), 0, Math.PI * 2); ctx.fill();
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
} else if (COMP_THEME === 'chart') {
|
|
806
|
+
// Full-Canvas High-Resolution Japanese Candlestick Chart
|
|
807
|
+
const chartW = W * 0.75;
|
|
808
|
+
const chartH = H * 0.44;
|
|
809
|
+
const chartX = cx - chartW / 2;
|
|
810
|
+
const chartY = cy - chartH / 2;
|
|
811
|
+
const baseLine = chartY + chartH;
|
|
812
|
+
|
|
813
|
+
// Grid levels
|
|
814
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.06)';
|
|
815
|
+
ctx.lineWidth = 1;
|
|
816
|
+
for (let l = 0; l <= 4; l++) {
|
|
817
|
+
const gy = chartY + (chartH / 4) * l;
|
|
818
|
+
ctx.beginPath(); ctx.moveTo(chartX, gy); ctx.lineTo(chartX + chartW, gy); ctx.stroke();
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// 20 Japanese Candlesticks with wicks
|
|
822
|
+
const candles = 20;
|
|
823
|
+
const cStep = chartW / candles;
|
|
824
|
+
const revealProg = Math.min(1, t / (window.DURATION * 0.8));
|
|
825
|
+
const activeCandles = Math.floor(candles * revealProg);
|
|
826
|
+
|
|
827
|
+
const pricePoints = [];
|
|
828
|
+
for (let c = 0; c < activeCandles; c++) {
|
|
829
|
+
const cSeed = c * 37 + 13;
|
|
830
|
+
const isBull = Math.sin(cSeed) > -0.2;
|
|
831
|
+
const bodyH = 25 + Math.abs(Math.sin(cSeed * 1.5)) * 65;
|
|
832
|
+
const wickTopH = 15 + Math.abs(Math.cos(cSeed)) * 30;
|
|
833
|
+
const wickBotH = 12 + Math.abs(Math.sin(cSeed * 2)) * 25;
|
|
834
|
+
const candleMidY = chartY + chartH * 0.5 + Math.sin(c * 0.35 + t * 0.8) * (chartH * 0.25);
|
|
835
|
+
const candleX = chartX + c * cStep + cStep / 2;
|
|
836
|
+
|
|
837
|
+
ctx.strokeStyle = isBull ? '#10b981' : '#ef4444';
|
|
838
|
+
ctx.fillStyle = isBull ? '#10b981' : '#ef4444';
|
|
839
|
+
ctx.lineWidth = 2;
|
|
840
|
+
|
|
841
|
+
// Wicks
|
|
842
|
+
ctx.beginPath();
|
|
843
|
+
ctx.moveTo(candleX, candleMidY - bodyH / 2 - wickTopH);
|
|
844
|
+
ctx.lineTo(candleX, candleMidY + bodyH / 2 + wickBotH);
|
|
845
|
+
ctx.stroke();
|
|
846
|
+
|
|
847
|
+
// Body
|
|
848
|
+
ctx.fillRect(candleX - cStep * 0.35, candleMidY - bodyH / 2, cStep * 0.7, bodyH);
|
|
849
|
+
pricePoints.push({ x: candleX, y: candleMidY });
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Glowing EMA Moving Average Ribbon
|
|
853
|
+
if (pricePoints.length > 1) {
|
|
854
|
+
ctx.strokeStyle = ACCENT;
|
|
855
|
+
ctx.lineWidth = 3;
|
|
856
|
+
ctx.beginPath();
|
|
857
|
+
ctx.moveTo(pricePoints[0].x, pricePoints[0].y);
|
|
858
|
+
for (let p = 1; p < pricePoints.length; p++) {
|
|
859
|
+
const xc = (pricePoints[p - 1].x + pricePoints[p].x) / 2;
|
|
860
|
+
const yc = (pricePoints[p - 1].y + pricePoints[p].y) / 2;
|
|
861
|
+
ctx.quadraticCurveTo(pricePoints[p - 1].x, pricePoints[p - 1].y, xc, yc);
|
|
862
|
+
}
|
|
863
|
+
ctx.stroke();
|
|
864
|
+
|
|
865
|
+
// Ticker badge on last point
|
|
866
|
+
const lastPt = pricePoints[pricePoints.length - 1];
|
|
867
|
+
ctx.fillStyle = ACCENT;
|
|
868
|
+
ctx.fillRect(lastPt.x + 8, lastPt.y - 12, 70, 24);
|
|
869
|
+
ctx.font = 'bold 13px monospace';
|
|
870
|
+
ctx.fillStyle = '#000000';
|
|
871
|
+
ctx.textAlign = 'center';
|
|
872
|
+
ctx.fillText('+34.8%', lastPt.x + 43, lastPt.y + 4);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
} else if (COMP_THEME === 'timeline') {
|
|
876
|
+
// 3D Milestone Roadmap with Royal Golden Seals
|
|
877
|
+
const stations = 5;
|
|
878
|
+
const spanW = W * 0.76;
|
|
879
|
+
const startX = cx - spanW / 2;
|
|
880
|
+
const stepX = spanW / (stations - 1);
|
|
881
|
+
const lineY = cy + 15;
|
|
882
|
+
|
|
883
|
+
// Glowing road baseline
|
|
884
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.18)';
|
|
885
|
+
ctx.lineWidth = 3;
|
|
886
|
+
ctx.beginPath(); ctx.moveTo(startX, lineY); ctx.lineTo(startX + spanW, lineY); ctx.stroke();
|
|
887
|
+
|
|
888
|
+
const activeDist = Math.min(1, t / (window.DURATION * 0.85));
|
|
889
|
+
ctx.strokeStyle = ACCENT;
|
|
890
|
+
ctx.lineWidth = 4;
|
|
891
|
+
ctx.beginPath(); ctx.moveTo(startX, lineY); ctx.lineTo(startX + spanW * activeDist, lineY); ctx.stroke();
|
|
892
|
+
|
|
893
|
+
for (let s = 0; s < stations; s++) {
|
|
894
|
+
const sx = startX + s * stepX;
|
|
895
|
+
const sActive = (s / (stations - 1)) <= activeDist;
|
|
896
|
+
const sScale = spring(t, 0.1 + s * 0.14);
|
|
897
|
+
|
|
898
|
+
// Milestone seal node
|
|
899
|
+
ctx.fillStyle = sActive ? ACCENT : 'rgba(255, 255, 255, 0.15)';
|
|
900
|
+
ctx.beginPath(); ctx.arc(sx, lineY, 18 * Math.min(1, sScale), 0, Math.PI * 2); ctx.fill();
|
|
901
|
+
|
|
902
|
+
ctx.strokeStyle = '#ffffff';
|
|
903
|
+
ctx.lineWidth = 2;
|
|
904
|
+
ctx.beginPath(); ctx.arc(sx, lineY, 8 * Math.min(1, sScale), 0, Math.PI * 2); ctx.stroke();
|
|
905
|
+
|
|
906
|
+
// Milestone Year / Chapter Label
|
|
907
|
+
ctx.font = 'bold 15px ' + FONT_DISPLAY;
|
|
908
|
+
ctx.fillStyle = sActive ? '#ffffff' : 'rgba(255, 255, 255, 0.4)';
|
|
909
|
+
ctx.textAlign = 'center';
|
|
910
|
+
ctx.fillText('ERA 0' + (s + 1), sx, lineY - 35);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
} else if (COMP_THEME === 'nodes') {
|
|
914
|
+
// Full-Canvas Neural Network / Distributed Topology
|
|
915
|
+
const tiers = [
|
|
916
|
+
[[-W * 0.32, -80], [-W * 0.32, 0], [-W * 0.32, 80]],
|
|
917
|
+
[[-W * 0.12, -110], [-W * 0.12, -40], [-W * 0.12, 40], [-W * 0.12, 110]],
|
|
918
|
+
[[W * 0.12, -110], [W * 0.12, -40], [W * 0.12, 40], [W * 0.12, 110]],
|
|
919
|
+
[[W * 0.32, -60], [W * 0.32, 60]]
|
|
920
|
+
];
|
|
921
|
+
|
|
922
|
+
ctx.lineWidth = 1.5;
|
|
923
|
+
// Synapse connections
|
|
924
|
+
for (let tier = 0; tier < tiers.length - 1; tier++) {
|
|
925
|
+
for (const [x1, y1] of tiers[tier]) {
|
|
926
|
+
for (const [x2, y2] of tiers[tier + 1]) {
|
|
927
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.08)';
|
|
928
|
+
ctx.beginPath(); ctx.moveTo(cx + x1, cy + y1); ctx.lineTo(cx + x2, cy + y2); ctx.stroke();
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Traveling glowing energy pulses
|
|
934
|
+
for (let p = 0; p < 8; p++) {
|
|
935
|
+
const pProg = (t * 0.7 + p * 0.14) % 1;
|
|
936
|
+
const tierIdx = Math.floor(pProg * 3);
|
|
937
|
+
const subProg = (pProg * 3) % 1;
|
|
938
|
+
const startTier = tiers[tierIdx];
|
|
939
|
+
const endTier = tiers[tierIdx + 1];
|
|
940
|
+
const nodeA = startTier[p % startTier.length];
|
|
941
|
+
const nodeB = endTier[(p + 1) % endTier.length];
|
|
942
|
+
const ex = cx + nodeA[0] + (nodeB[0] - nodeA[0]) * subProg;
|
|
943
|
+
const ey = cy + nodeA[1] + (nodeB[1] - nodeA[1]) * subProg;
|
|
944
|
+
|
|
945
|
+
ctx.fillStyle = ACCENT;
|
|
946
|
+
ctx.beginPath(); ctx.arc(ex, ey, 5, 0, Math.PI * 2); ctx.fill();
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Node glyphs
|
|
950
|
+
for (const tier of tiers) {
|
|
951
|
+
for (const [nx, ny] of tier) {
|
|
952
|
+
ctx.fillStyle = ACCENT2;
|
|
953
|
+
ctx.beginPath(); ctx.arc(cx + nx, cy + ny, 9, 0, Math.PI * 2); ctx.fill();
|
|
954
|
+
ctx.fillStyle = '#ffffff';
|
|
955
|
+
ctx.beginPath(); ctx.arc(cx + nx, cy + ny, 4, 0, Math.PI * 2); ctx.fill();
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
} else if (COMP_THEME === 'radar_globe') {
|
|
960
|
+
// 3D Tactical Wireframe Rotating Radar Globe
|
|
961
|
+
const globeR = 150;
|
|
962
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
|
|
963
|
+
ctx.lineWidth = 1.5;
|
|
964
|
+
|
|
965
|
+
// Outer degree ring
|
|
966
|
+
ctx.beginPath(); ctx.arc(cx, cy, globeR, 0, Math.PI * 2); ctx.stroke();
|
|
967
|
+
for (let deg = 0; deg < 360; deg += 30) {
|
|
968
|
+
const rad = (deg * Math.PI) / 180;
|
|
969
|
+
ctx.beginPath();
|
|
970
|
+
ctx.moveTo(cx + Math.cos(rad) * (globeR - 6), cy + Math.sin(rad) * (globeR - 6));
|
|
971
|
+
ctx.lineTo(cx + Math.cos(rad) * (globeR + 6), cy + Math.sin(rad) * (globeR + 6));
|
|
972
|
+
ctx.stroke();
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Rotating longitude circles
|
|
976
|
+
for (let m = 0; m < 4; m++) {
|
|
977
|
+
const mAngle = t * 0.6 + (m * Math.PI) / 4;
|
|
978
|
+
const mW = globeR * Math.cos(mAngle);
|
|
979
|
+
ctx.beginPath(); ctx.ellipse(cx, cy, Math.abs(mW), globeR, 0, 0, Math.PI * 2); ctx.stroke();
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Sweeping radar beam
|
|
983
|
+
const sweepAngle = t * 2.2;
|
|
984
|
+
const sweepGrad = ctx.createRadialGradient(cx, cy, 10, cx, cy, globeR);
|
|
985
|
+
sweepGrad.addColorStop(0, 'rgba(56, 189, 248, 0.45)');
|
|
986
|
+
sweepGrad.addColorStop(1, 'rgba(56, 189, 248, 0)');
|
|
987
|
+
ctx.fillStyle = sweepGrad;
|
|
988
|
+
ctx.beginPath();
|
|
989
|
+
ctx.moveTo(cx, cy);
|
|
990
|
+
ctx.arc(cx, cy, globeR, sweepAngle - 0.6, sweepAngle);
|
|
991
|
+
ctx.fill();
|
|
992
|
+
|
|
993
|
+
} else if (COMP_THEME === 'isometric_geometry') {
|
|
994
|
+
// True 3D Wireframe Polyhedron (Rotating Octahedron)
|
|
995
|
+
const rotX = t * 0.7;
|
|
996
|
+
const rotY = t * 1.1;
|
|
997
|
+
const verts3D = [
|
|
998
|
+
[0, -110, 0], [110, 0, 0], [0, 0, 110], [-110, 0, 0], [0, 0, -110], [0, 110, 0]
|
|
999
|
+
];
|
|
1000
|
+
const edges = [
|
|
1001
|
+
[0,1], [0,2], [0,3], [0,4],
|
|
1002
|
+
[5,1], [5,2], [5,3], [5,4],
|
|
1003
|
+
[1,2], [2,3], [3,4], [4,1]
|
|
1004
|
+
];
|
|
1005
|
+
|
|
1006
|
+
// Project vertices
|
|
1007
|
+
const projected = verts3D.map(([x, y, z]) => {
|
|
1008
|
+
// Rotate Y
|
|
1009
|
+
const x1 = x * Math.cos(rotY) + z * Math.sin(rotY);
|
|
1010
|
+
const z1 = -x * Math.sin(rotY) + z * Math.cos(rotY);
|
|
1011
|
+
// Rotate X
|
|
1012
|
+
const y2 = y * Math.cos(rotX) - z1 * Math.sin(rotX);
|
|
1013
|
+
const z2 = y * Math.sin(rotX) + z1 * Math.cos(rotX);
|
|
1014
|
+
const fov = 350;
|
|
1015
|
+
const scale = fov / (fov + z2);
|
|
1016
|
+
return [cx + x1 * scale, cy + y2 * scale];
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
ctx.strokeStyle = ACCENT;
|
|
1020
|
+
ctx.lineWidth = 2.5;
|
|
1021
|
+
for (const [v1, v2] of edges) {
|
|
1022
|
+
ctx.beginPath();
|
|
1023
|
+
ctx.moveTo(projected[v1][0], projected[v1][1]);
|
|
1024
|
+
ctx.lineTo(projected[v2][0], projected[v2][1]);
|
|
1025
|
+
ctx.stroke();
|
|
1026
|
+
}
|
|
1027
|
+
for (const [px, py] of projected) {
|
|
1028
|
+
ctx.fillStyle = '#ffffff';
|
|
1029
|
+
ctx.beginPath(); ctx.arc(px, py, 4, 0, Math.PI * 2); ctx.fill();
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
} else if (COMP_THEME === 'code') {
|
|
1033
|
+
// Sleek Developer Terminal Window
|
|
1034
|
+
const winW = W * 0.65;
|
|
1035
|
+
const winH = H * 0.42;
|
|
1036
|
+
const winX = cx - winW / 2;
|
|
1037
|
+
const winY = cy - winH / 2;
|
|
1038
|
+
|
|
1039
|
+
ctx.fillStyle = 'rgba(10, 15, 26, 0.9)';
|
|
1040
|
+
ctx.strokeStyle = CARD_BORDER;
|
|
1041
|
+
ctx.lineWidth = 2;
|
|
1042
|
+
ctx.beginPath(); ctx.roundRect(winX, winY, winW, winH, 14); ctx.fill(); ctx.stroke();
|
|
1043
|
+
|
|
1044
|
+
// Window controls
|
|
1045
|
+
ctx.fillStyle = '#ff5f56'; ctx.beginPath(); ctx.arc(winX + 24, winY + 22, 6, 0, Math.PI * 2); ctx.fill();
|
|
1046
|
+
ctx.fillStyle = '#ffbd2e'; ctx.beginPath(); ctx.arc(winX + 44, winY + 22, 6, 0, Math.PI * 2); ctx.fill();
|
|
1047
|
+
ctx.fillStyle = '#27c93f'; ctx.beginPath(); ctx.arc(winX + 64, winY + 22, 6, 0, Math.PI * 2); ctx.fill();
|
|
1048
|
+
|
|
1049
|
+
const codeLines = [
|
|
1050
|
+
'import { FrameForge } from "@amg555/frameforge";',
|
|
1051
|
+
'const engine = new FrameForge.AestheticEngine();',
|
|
1052
|
+
'await engine.renderScene({',
|
|
1053
|
+
' style: "' + AESTHETIC + '",',
|
|
1054
|
+
' fidelity: "CINEMATIC_24FPS"',
|
|
1055
|
+
'});'
|
|
1056
|
+
];
|
|
1057
|
+
ctx.font = '19px "Fira Code", monospace';
|
|
1058
|
+
const typeProg = Math.min(1, t / (window.DURATION * 0.75));
|
|
1059
|
+
const totalChars = codeLines.join('\\n').length;
|
|
1060
|
+
let charsToShow = Math.floor(totalChars * typeProg);
|
|
1061
|
+
|
|
1062
|
+
let lineY = winY + 70;
|
|
1063
|
+
for (let l = 0; l < codeLines.length; l++) {
|
|
1064
|
+
const line = codeLines[l];
|
|
1065
|
+
const sliceLen = Math.min(line.length, charsToShow);
|
|
1066
|
+
const textToDraw = line.slice(0, sliceLen);
|
|
1067
|
+
charsToShow = Math.max(0, charsToShow - line.length - 1);
|
|
1068
|
+
|
|
1069
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.25)';
|
|
1070
|
+
ctx.textAlign = 'left';
|
|
1071
|
+
ctx.fillText(String(l + 1).padStart(2, '0'), winX + 26, lineY);
|
|
1072
|
+
ctx.fillStyle = l === 0 ? ACCENT : l === 1 ? ACCENT2 : '#e2e8f0';
|
|
1073
|
+
ctx.fillText(textToDraw, winX + 70, lineY);
|
|
1074
|
+
lineY += 32;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
} else if (COMP_THEME === 'speed_action') {
|
|
1078
|
+
// High-Energy Speed Streaks & Shockwaves
|
|
1079
|
+
const waveR = ((t * 1.8) % 1) * 220;
|
|
1080
|
+
ctx.strokeStyle = ACCENT;
|
|
1081
|
+
ctx.lineWidth = 3;
|
|
1082
|
+
ctx.beginPath(); ctx.arc(cx, cy, waveR, 0, Math.PI * 2); ctx.stroke();
|
|
1083
|
+
|
|
1084
|
+
for (let s = 0; s < 12; s++) {
|
|
1085
|
+
const sx = ((s * 90 + t * 900) % (W * 1.2)) - W * 0.1;
|
|
1086
|
+
ctx.strokeStyle = ACCENT2;
|
|
1087
|
+
ctx.lineWidth = 2.5;
|
|
1088
|
+
ctx.beginPath(); ctx.moveTo(sx, cy - 80); ctx.lineTo(sx - 150, cy + 80); ctx.stroke();
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
} else if (COMP_THEME === 'quote') {
|
|
1092
|
+
// Editorial Quote
|
|
1093
|
+
ctx.font = 'bold 110px serif';
|
|
1094
|
+
ctx.fillStyle = ACCENT;
|
|
1095
|
+
ctx.textAlign = 'center';
|
|
1096
|
+
ctx.fillText('“', cx, cy - 40);
|
|
1097
|
+
|
|
1098
|
+
ctx.font = 'italic 30px ' + FONT_DISPLAY;
|
|
1099
|
+
ctx.fillStyle = '#ffffff';
|
|
1100
|
+
const quoteText = NARRATION.length > 95 ? NARRATION.slice(0, 95) + '...' : NARRATION;
|
|
1101
|
+
ctx.fillText(quoteText, cx, cy + 25);
|
|
1102
|
+
|
|
1103
|
+
} else if (COMP_THEME === 'metric') {
|
|
1104
|
+
// Massive Animated KPI Gauge
|
|
1105
|
+
const centerVal = Math.round(spring(t, 0.15) * 98.4);
|
|
1106
|
+
const radius = 95;
|
|
1107
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
|
|
1108
|
+
ctx.lineWidth = 16;
|
|
1109
|
+
ctx.beginPath(); ctx.arc(cx, cy, radius, 0, Math.PI * 2); ctx.stroke();
|
|
1110
|
+
|
|
1111
|
+
ctx.strokeStyle = ACCENT;
|
|
1112
|
+
ctx.lineWidth = 16;
|
|
1113
|
+
ctx.beginPath();
|
|
1114
|
+
const arcEnd = -Math.PI / 2 + (Math.PI * 2) * (spring(t, 0.2) * 0.984);
|
|
1115
|
+
ctx.arc(cx, cy, radius, -Math.PI / 2, arcEnd);
|
|
1116
|
+
ctx.stroke();
|
|
1117
|
+
|
|
1118
|
+
ctx.font = 'bold 54px monospace';
|
|
1119
|
+
ctx.fillStyle = '#ffffff';
|
|
1120
|
+
ctx.textAlign = 'center';
|
|
1121
|
+
ctx.fillText(centerVal + '%', cx, cy + 18);
|
|
1122
|
+
|
|
1123
|
+
} else if (COMP_THEME === 'image' && cachedImage && cachedImage.complete && cachedImage.naturalWidth > 0) {
|
|
1124
|
+
// Floating Ken-Burns Image Card
|
|
1125
|
+
const imgW = W * 0.65;
|
|
1126
|
+
const imgH = H * 0.44;
|
|
1127
|
+
const zoom = 1.0 + (t / window.DURATION) * 0.08;
|
|
1128
|
+
ctx.save();
|
|
1129
|
+
ctx.beginPath(); ctx.roundRect(cx - imgW / 2, cy - imgH / 2, imgW, imgH, 16); ctx.clip();
|
|
1130
|
+
ctx.scale(zoom, zoom);
|
|
1131
|
+
ctx.drawImage(cachedImage, cx - imgW / 2, cy - imgH / 2, imgW, imgH);
|
|
1132
|
+
ctx.restore();
|
|
1133
|
+
|
|
1134
|
+
} else {
|
|
1135
|
+
// Kinetic Frequency Equalizer Waveform
|
|
1136
|
+
const bars = 36;
|
|
1137
|
+
const barW = (W * 0.7) / bars;
|
|
1138
|
+
const startX = cx - (W * 0.7) / 2;
|
|
1139
|
+
for (let b = 0; b < bars; b++) {
|
|
1140
|
+
const phase = t * 4.5 + b * 0.28;
|
|
1141
|
+
const hVal = 25 + 110 * Math.abs(Math.sin(phase) * Math.cos(phase * 0.8));
|
|
1142
|
+
const bx = startX + b * barW;
|
|
1143
|
+
const by = cy - hVal / 2;
|
|
1144
|
+
|
|
1145
|
+
const barGrad = ctx.createLinearGradient(0, by, 0, by + hVal);
|
|
1146
|
+
barGrad.addColorStop(0, ACCENT);
|
|
1147
|
+
barGrad.addColorStop(1, ACCENT2);
|
|
1148
|
+
ctx.fillStyle = barGrad;
|
|
1149
|
+
ctx.beginPath(); ctx.roundRect(bx, by, barW - 6, hVal, 4); ctx.fill();
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
ctx.restore();
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// ── Cinematic Broadcast Lower-Third Overlay ────────────────────────────────────
|
|
1157
|
+
function drawCinematicOverlay(t) {
|
|
1158
|
+
ctx.save();
|
|
1159
|
+
const textSpring = spring(t, 0.18);
|
|
1160
|
+
const bannerY = H * 0.74 + (1 - textSpring) * 45;
|
|
1161
|
+
const bannerH = H * 0.18;
|
|
1162
|
+
const bannerW = W * 0.88;
|
|
1163
|
+
const bannerX = (W - bannerW) / 2;
|
|
1164
|
+
|
|
1165
|
+
ctx.globalAlpha = Math.min(1, Math.max(0, textSpring));
|
|
1166
|
+
|
|
1167
|
+
// Glassmorphic Lower-Third Banner
|
|
1168
|
+
ctx.fillStyle = 'rgba(8, 12, 22, 0.82)';
|
|
1169
|
+
ctx.strokeStyle = CARD_BORDER;
|
|
1170
|
+
ctx.lineWidth = 1.5;
|
|
1171
|
+
ctx.beginPath();
|
|
1172
|
+
ctx.roundRect(bannerX, bannerY, bannerW, bannerH, 16);
|
|
1173
|
+
ctx.fill();
|
|
1174
|
+
ctx.stroke();
|
|
1175
|
+
|
|
1176
|
+
// Accent Left Bar
|
|
1177
|
+
ctx.fillStyle = ACCENT;
|
|
1178
|
+
ctx.beginPath();
|
|
1179
|
+
ctx.roundRect(bannerX + 16, bannerY + 16, 6, bannerH - 32, 3);
|
|
1180
|
+
ctx.fill();
|
|
1181
|
+
|
|
1182
|
+
// Tag Pill
|
|
1183
|
+
ctx.font = 'bold 13px monospace';
|
|
1184
|
+
ctx.fillStyle = ACCENT;
|
|
1185
|
+
ctx.textAlign = 'left';
|
|
1186
|
+
ctx.fillText(TAG, bannerX + 38, bannerY + 36);
|
|
1187
|
+
|
|
1188
|
+
// Primary Caption Headline
|
|
1189
|
+
ctx.font = FONT_WEIGHT + ' 44px ' + FONT_DISPLAY;
|
|
1190
|
+
ctx.fillStyle = '#ffffff';
|
|
1191
|
+
ctx.shadowColor = 'rgba(0, 0, 0, 0.9)';
|
|
1192
|
+
ctx.shadowBlur = 16;
|
|
1193
|
+
ctx.shadowOffsetY = 4;
|
|
1194
|
+
ctx.textAlign = 'left';
|
|
1195
|
+
const cleanCap = CAPTION.length > 55 ? CAPTION.slice(0, 55) + '...' : CAPTION;
|
|
1196
|
+
ctx.fillText(cleanCap, bannerX + 38, bannerY + 84);
|
|
1197
|
+
|
|
1198
|
+
// Subtitle / Narration preview
|
|
1199
|
+
if (NARRATION) {
|
|
1200
|
+
ctx.font = '400 23px ' + FONT_BODY;
|
|
1201
|
+
ctx.fillStyle = 'rgba(226, 232, 240, 0.88)';
|
|
1202
|
+
ctx.shadowBlur = 8;
|
|
1203
|
+
const cleanNarr = NARRATION.length > 95 ? NARRATION.slice(0, 95) + '...' : NARRATION;
|
|
1204
|
+
ctx.fillText(cleanNarr, bannerX + 38, bannerY + 120);
|
|
1205
|
+
}
|
|
1206
|
+
ctx.restore();
|
|
1207
|
+
|
|
1208
|
+
// Bottom Progress Scrubber
|
|
1209
|
+
ctx.save();
|
|
1210
|
+
const progress = Math.min(1, Math.max(0, t / window.DURATION));
|
|
1211
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.08)';
|
|
1212
|
+
ctx.fillRect(0, H - 6, W, 6);
|
|
1213
|
+
|
|
1214
|
+
const progGrad = ctx.createLinearGradient(0, 0, W, 0);
|
|
1215
|
+
progGrad.addColorStop(0, ACCENT);
|
|
1216
|
+
progGrad.addColorStop(1, ACCENT2);
|
|
1217
|
+
ctx.fillStyle = progGrad;
|
|
1218
|
+
ctx.fillRect(0, H - 6, W * progress, 6);
|
|
1219
|
+
ctx.restore();
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// ── Master Seek Hook ───────────────────────────────────────────────────────────
|
|
1223
|
+
window.seek = async (t) => {
|
|
1224
|
+
drawBackground(t);
|
|
1225
|
+
drawMainScene(t);
|
|
1226
|
+
drawCinematicOverlay(t);
|
|
1227
|
+
};
|
|
1228
|
+
</script>
|
|
1229
|
+
</body>
|
|
1230
|
+
</html>`;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
module.exports = {
|
|
1234
|
+
generateSceneHtml,
|
|
1235
|
+
wrapCodeInHarnessHtml,
|
|
1236
|
+
detectAesthetic,
|
|
1237
|
+
detectComponentTheme,
|
|
1238
|
+
getScriptFontStack,
|
|
1239
|
+
AESTHETICS,
|
|
1240
|
+
REGIONAL_FONTS,
|
|
1241
|
+
};
|