@amg555/frameforge-mcp 2.4.7 → 2.4.9
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 +3 -3
- package/plugins/video-pipeline/skills/html-renderer/references/motion-craft.md +91 -0
- package/plugins/video-pipeline/skills/html-renderer/references/render-contract.md +65 -0
- package/plugins/video-pipeline/skills/video-pipeline/references/motion-craft.md +91 -0
- package/plugins/video-pipeline/skills/video-pipeline/references/render-contract.md +65 -0
- package/scripts/deep-validate.js +8 -2
- package/src/harness/bin/harness.mjs +32 -2
- package/src/harness/lib/capture.mjs +35 -4
- package/src/harness/lib/verify-quality.mjs +251 -0
- package/src/mcp-server/server.js +222 -2
- package/src/video-renderer/renderer.js +17 -0
- package/src/video-renderer/scene-animator.js +1228 -13
- package/src/workflows/render.js +1 -0
|
@@ -326,7 +326,7 @@ function detectComponentTheme({ visualDescription = '', visual_description = '',
|
|
|
326
326
|
}
|
|
327
327
|
|
|
328
328
|
// 7. Shame / Mirror / Reflection / Water / Self Awareness / Theory of Mind (Scene 3, 9)
|
|
329
|
-
if (/(?:^|[^\w])(shame|blush|embarrass|mirror|reflection|water pool|theory of mind|how others see|self-aware|identity|looking at me|psychology|mind-shift)(?:[^\w]|$)/i.test(text)
|
|
329
|
+
if (/(?:^|[^\w])(shame|blush|embarrass|mirror|reflection|water pool|theory of mind|how others see|self-aware|identity|looking at me|shame psychology|mind-shift)(?:[^\w]|$)/i.test(text)
|
|
330
330
|
|| /കണ്ണാടി|പ്രതിഫലനം|മറ്റുള്ളവർ എന്നെ|തിയറി ഓഫ് മൈൻഡ്|സ്വന്തം ശരീരം മറച്ചു|വ്യക്തിത്വം|ശ്രദ്ധിക്കുന്നുണ്ട്|യഥാർത്ഥ രഹസ്യം|നാണം മറയ്ക്കാൻ/.test(text)) {
|
|
331
331
|
return 'doodle_shame_reflection';
|
|
332
332
|
}
|
|
@@ -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';
|
|
@@ -374,7 +392,7 @@ function detectComponentTheme({ visualDescription = '', visual_description = '',
|
|
|
374
392
|
}
|
|
375
393
|
|
|
376
394
|
// High Velocity Speed Action
|
|
377
|
-
if (/(?:^|[^\w])(sports|workout|gym|athlete|speed|streaks|race|championship|extreme|
|
|
395
|
+
if (/(?:^|[^\w])(sports|workout|gym|athlete|speed|streaks|race|championship|extreme|hype|adrenaline|velocity|sprint|fast motion)(?:[^\w]|$)/.test(text)
|
|
378
396
|
|| /കായിക|മത്സര|വേഗത|വ്യായാമം|खेल|दौड़/.test(text)) {
|
|
379
397
|
return 'speed_action';
|
|
380
398
|
}
|
|
@@ -403,9 +421,9 @@ function detectComponentTheme({ visualDescription = '', visual_description = '',
|
|
|
403
421
|
return 'chart';
|
|
404
422
|
}
|
|
405
423
|
|
|
406
|
-
// Neural Network Nodes
|
|
407
|
-
if (/(?:^|[^\w])(network|
|
|
408
|
-
||
|
|
424
|
+
// Neural Network Nodes / AI Topology
|
|
425
|
+
if (/(?:^|[^\w])(network|nodes|neural network|neural net|distributed system|server mesh|deep learning)(?:[^\w]|$)/.test(text)
|
|
426
|
+
|| /നെറ്റ്വർക്ക്|ശൃംഖല|नेटवर्क/.test(text)) {
|
|
409
427
|
return 'nodes';
|
|
410
428
|
}
|
|
411
429
|
|
|
@@ -415,16 +433,59 @@ function detectComponentTheme({ visualDescription = '', visual_description = '',
|
|
|
415
433
|
return 'isometric_geometry';
|
|
416
434
|
}
|
|
417
435
|
|
|
418
|
-
//
|
|
436
|
+
// ── Universal Semantic Visual Archetypes (General Content & Education) ──
|
|
437
|
+
// 1. Versus / Comparison / Split Screen
|
|
438
|
+
if (/(?:^|[^\w])(versus|vs|comparison|compare|pros and cons|pros vs cons|difference between|dichotomy|contrast|side-by-side|before and after|before vs after)(?:[^\w]|$)/i.test(text)
|
|
439
|
+
|| /വ്യത്യാസം|താരതമ്യം|എതിര്|പോരാട്ടം|തമ്മിൽ|തുലനം|താരതമ്യപ്പെടുത്തൽ/.test(text)) {
|
|
440
|
+
return 'versus_comparison';
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// 2. Mechanism / Physics / Machinery / Engine / Aerodynamics
|
|
444
|
+
if (/(?:^|[^\w])(engine|gear|gears|motor|physics|aerodynamics|thrust|turbine|propulsion|piston|hydraulic|kinetic|machine|machinery|circuit|mechanics|thermodynamics|levers|pulley|internal combustion|mechanical|cutaway|airflow|lift|rotor|dynamo)(?:[^\w]|$)/i.test(text)
|
|
445
|
+
|| /യന്ത്രം|എഞ്ചിൻ|ഭൗതികശാസ്ത്രം|പ്രവർത്തനം|ചലനം|മെക്കാനിസം|ഭൗതികം/.test(text)) {
|
|
446
|
+
return 'mechanism_physics';
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// 3. Process / Pipeline / Workflow / Stages / Assembly / Lifecycle
|
|
450
|
+
if (/(?:^|[^\w])(pipeline|workflow|step by step|steps|stages|phase|phases|production line|assembly|manufacturing|lifecycle|funnel|conveyor|sequence|how it works|how it is made|procedure)(?:[^\w]|$)/i.test(text)
|
|
451
|
+
|| /ഘട്ടങ്ങൾ|പ്രക്രിയ|ഘട്ടം|ഉൽപ്പാദനം|വഴികൾ|പ്രവർത്തനരീതി/.test(text)) {
|
|
452
|
+
return 'process_pipeline';
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// 4. Cognitive Insight / Psychology / Brain / Eureka / Revelation / Mind
|
|
456
|
+
if (/(?:^|[^\w])(mind|psychology|cognitive|brain|insight|revelation|eureka|intuition|subconscious|thinking|thought|contemplation|intelligence|mental|awareness|neuroscience|concept|idea|epiphany)(?:[^\w]|$)/i.test(text)
|
|
457
|
+
|| /മനസ്സ്|ബുദ്ധി|ചിന്ത|മനഃശാസ്ത്രം|ജ്ഞാനം|മസ്തിഷ്കം|ബോധം/.test(text)) {
|
|
458
|
+
return 'cognitive_insight';
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// 5. Magnification / Loupe / Macro Zoom / Scrutiny / Detailed Inspection
|
|
462
|
+
if (/(?:^|[^\w])(magnify|magnification|zoom|microscopic|macro|close up|closeup|inspection|loupe|examine|scrutiny|detail|focal point|focus in|specimen)(?:[^\w]|$)/i.test(text)
|
|
463
|
+
|| /സൂക്ഷ്മ|പരിശോധന|വലുതാക്കി കാണുക|സൂക്ഷ്മമായി|നിരീക്ഷണം/.test(text)) {
|
|
464
|
+
return 'magnification_focus';
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// 6. Explainer / Presentation / Tutorial / Masterclass Whiteboard / Breakdown
|
|
468
|
+
if (/(?:^|[^\w])(explain|explainer|presentation|lecture|teach|tutorial|whiteboard|blackboard|breakdown|guide|overview|theory|principles|fundamentals|introduction|summary|lesson|masterclass)(?:[^\w]|$)/i.test(text)
|
|
469
|
+
|| /വിവരണം|പഠിപ്പിക്കുക|ക്ലാസ്|പാഠം|തത്വം|വിവരിക്കുക/.test(text)) {
|
|
470
|
+
return 'explainer_presentation';
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// 7. Waveform / Audio / Sound / Acoustics / Equalizer
|
|
474
|
+
if (/(?:^|[^\w])(waveform|audio|sound|equalizer|frequency|acoustics|hz|decibel|music spectrum|voice waveform)(?:[^\w]|$)/i.test(text)
|
|
475
|
+
|| /ശബ്ദതരംഗം|തരംഗം|സംഗീതം|ധ്വനി/.test(text)) {
|
|
476
|
+
return 'waveform';
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Fallbacks matched to aesthetic (rich semantic defaults, never sterile)
|
|
419
480
|
if (aesthetic === 'cosmic_space') return 'celestial_orbit';
|
|
420
481
|
if (aesthetic === 'cinematic_documentary') return 'doodle_general_human';
|
|
421
482
|
if (aesthetic === 'finance_executive') return 'chart';
|
|
422
483
|
if (aesthetic === 'nature_organic') return 'dna_helix';
|
|
423
484
|
if (aesthetic === 'breaking_news') return 'radar_globe';
|
|
424
485
|
if (aesthetic === 'cyber_tech') return 'nodes';
|
|
425
|
-
if (aesthetic === 'minimal_luxury') return '
|
|
486
|
+
if (aesthetic === 'minimal_luxury') return 'explainer_presentation';
|
|
426
487
|
if (aesthetic === 'hype_sports') return 'speed_action';
|
|
427
|
-
return '
|
|
488
|
+
return 'explainer_presentation';
|
|
428
489
|
}
|
|
429
490
|
|
|
430
491
|
// ── Harness HTML Wrapper for User / Agent Code ────────────────────────────────
|
|
@@ -575,6 +636,7 @@ function generateSceneHtml({
|
|
|
575
636
|
const aesthetic = AESTHETICS[aestheticKey] || AESTHETICS.cyber_tech;
|
|
576
637
|
const compTheme = detectComponentTheme({
|
|
577
638
|
visualDescription,
|
|
639
|
+
prompt,
|
|
578
640
|
caption,
|
|
579
641
|
narration,
|
|
580
642
|
topic,
|
|
@@ -693,6 +755,8 @@ const IS_LIGHT = ${Boolean(pal.isLight)};
|
|
|
693
755
|
const STROKE_COLOR = ${JSON.stringify(pal.isLight ? (pal.stroke || '#1c1917') : '#f8fafc')};
|
|
694
756
|
const STROKE_ALT = ${JSON.stringify(pal.isLight ? (pal.strokeAlt || '#44403c') : 'rgba(255, 255, 255, 0.7)')};
|
|
695
757
|
const AMBIENT_LINE = ${JSON.stringify(pal.isLight ? 'rgba(28, 25, 23, 0.22)' : 'rgba(255, 255, 255, 0.25)')};
|
|
758
|
+
const TEXT_MAIN = ${JSON.stringify(pal.isLight ? '#1c1917' : '#f8fafc')};
|
|
759
|
+
const TEXT_MUTED = ${JSON.stringify(pal.isLight ? '#78716c' : '#94a3b8')};
|
|
696
760
|
|
|
697
761
|
let cachedImage = null;
|
|
698
762
|
if (IMAGE_SRC) {
|
|
@@ -2510,6 +2574,338 @@ function drawMainScene(t) {
|
|
|
2510
2574
|
showShadow: true,
|
|
2511
2575
|
seed: 110,
|
|
2512
2576
|
});
|
|
2577
|
+
} else if (COMP_THEME === 'doodle_map_migration') {
|
|
2578
|
+
// Scene 10: Hand-Drawn Migration Map / Out of Africa / Ancient World Routes
|
|
2579
|
+
const mapSpring = spring(t, 0.05, 170, 14);
|
|
2580
|
+
const arrowSpring = spring(t, window.DURATION * 0.35, 180, 15);
|
|
2581
|
+
const walkProg = Math.min(1, Math.max(0, (t - window.DURATION * 0.2) / (window.DURATION * 0.75)));
|
|
2582
|
+
|
|
2583
|
+
// Antique parchment background wash
|
|
2584
|
+
ctx.save();
|
|
2585
|
+
ctx.fillStyle = IS_LIGHT ? '#faf5ec' : '#1c1917';
|
|
2586
|
+
ctx.fillRect(0, 0, W, H);
|
|
2587
|
+
|
|
2588
|
+
// Decorative map grid lines (latitude/longitude in subtle ink)
|
|
2589
|
+
ctx.strokeStyle = IS_LIGHT ? 'rgba(180, 83, 9, 0.12)' : 'rgba(255, 255, 255, 0.1)';
|
|
2590
|
+
ctx.lineWidth = 1.5;
|
|
2591
|
+
ctx.setLineDash([6, 6]);
|
|
2592
|
+
for (let gx = 100; gx < W; gx += 200) {
|
|
2593
|
+
ctx.beginPath(); ctx.moveTo(gx, 0); ctx.lineTo(gx, H); ctx.stroke();
|
|
2594
|
+
}
|
|
2595
|
+
for (let gy = 80; gy < H; gy += 160) {
|
|
2596
|
+
ctx.beginPath(); ctx.moveTo(0, gy); ctx.lineTo(W, gy); ctx.stroke();
|
|
2597
|
+
}
|
|
2598
|
+
ctx.setLineDash([]);
|
|
2599
|
+
|
|
2600
|
+
// Hand-drawn stylized continent outlines (Africa, Europe, Asia)
|
|
2601
|
+
const mapCenter = cx - 40;
|
|
2602
|
+
const mapY = cy - 20;
|
|
2603
|
+
|
|
2604
|
+
// 1. Africa Outline (Highlighted in warm terracotta wash)
|
|
2605
|
+
ctx.save();
|
|
2606
|
+
ctx.fillStyle = 'rgba(217, 119, 6, 0.20)';
|
|
2607
|
+
ctx.beginPath();
|
|
2608
|
+
ctx.moveTo(mapCenter - 140, mapY - 60); // West bulge
|
|
2609
|
+
ctx.bezierCurveTo(mapCenter - 160, mapY + 20, mapCenter - 80, mapY + 140, mapCenter - 40, mapY + 220); // South tip (Good Hope)
|
|
2610
|
+
ctx.bezierCurveTo(mapCenter + 10, mapY + 160, mapCenter + 40, mapY + 60, mapCenter + 60, mapY - 20); // Horn of Africa
|
|
2611
|
+
ctx.bezierCurveTo(mapCenter + 30, mapY - 60, mapCenter - 20, mapY - 80, mapCenter - 140, mapY - 60); // North Coast
|
|
2612
|
+
ctx.closePath();
|
|
2613
|
+
ctx.fill();
|
|
2614
|
+
|
|
2615
|
+
// Outline stroke with ink wobble
|
|
2616
|
+
sLine(mapCenter - 140, mapY - 60, mapCenter - 160, mapY + 20, 710, '#c2410c', 3);
|
|
2617
|
+
sLine(mapCenter - 160, mapY + 20, mapCenter - 40, mapY + 220, 711, '#c2410c', 3);
|
|
2618
|
+
sLine(mapCenter - 40, mapY + 220, mapCenter + 60, mapY - 20, 712, '#c2410c', 3);
|
|
2619
|
+
sLine(mapCenter + 60, mapY - 20, mapCenter - 140, mapY - 60, 713, '#c2410c', 3);
|
|
2620
|
+
|
|
2621
|
+
// Africa label badge
|
|
2622
|
+
ctx.fillStyle = '#b45309';
|
|
2623
|
+
ctx.font = '700 22px ' + FONT_DISPLAY;
|
|
2624
|
+
ctx.textAlign = 'center';
|
|
2625
|
+
ctx.fillText('AFRICA', mapCenter - 40, mapY + 60);
|
|
2626
|
+
|
|
2627
|
+
// Sun icon over Africa
|
|
2628
|
+
sCircle(mapCenter - 90, mapY + 40, 18, 714, '#d97706', 2.5, '#fef08a');
|
|
2629
|
+
for (let r = 0; r < 6; r++) {
|
|
2630
|
+
const ra = (r / 6) * Math.PI * 2 + t * 0.5;
|
|
2631
|
+
const rx1 = mapCenter - 90 + Math.cos(ra) * 22;
|
|
2632
|
+
const ry1 = mapY + 40 + Math.sin(ra) * 22;
|
|
2633
|
+
const rx2 = mapCenter - 90 + Math.cos(ra) * 32;
|
|
2634
|
+
const ry2 = mapY + 40 + Math.sin(ra) * 32;
|
|
2635
|
+
sLine(rx1, ry1, rx2, ry2, 715 + r, '#d97706', 2);
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
// 2. Eurasia / Northern Territory Outline (Colder destination)
|
|
2639
|
+
ctx.fillStyle = 'rgba(56, 189, 248, 0.12)';
|
|
2640
|
+
ctx.beginPath();
|
|
2641
|
+
ctx.moveTo(mapCenter - 130, mapY - 90);
|
|
2642
|
+
ctx.bezierCurveTo(mapCenter - 120, mapY - 200, mapCenter + 60, mapY - 220, mapCenter + 220, mapY - 170); // Northern Eurasia
|
|
2643
|
+
ctx.bezierCurveTo(mapCenter + 320, mapY - 130, mapCenter + 360, mapY - 40, mapCenter + 260, mapY + 10); // East Asia
|
|
2644
|
+
ctx.bezierCurveTo(mapCenter + 140, mapY - 30, mapCenter + 40, mapY - 70, mapCenter - 130, mapY - 90);
|
|
2645
|
+
ctx.closePath();
|
|
2646
|
+
ctx.fill();
|
|
2647
|
+
|
|
2648
|
+
sLine(mapCenter - 130, mapY - 90, mapCenter + 60, mapY - 210, 725, '#0284c7', 3);
|
|
2649
|
+
sLine(mapCenter + 60, mapY - 210, mapCenter + 280, mapY - 160, 726, '#0284c7', 3);
|
|
2650
|
+
sLine(mapCenter + 280, mapY - 160, mapCenter + 240, mapY + 10, 727, '#0284c7', 3);
|
|
2651
|
+
|
|
2652
|
+
// Snowflake climate icons over Eurasia
|
|
2653
|
+
for (let sf = 0; sf < 3; sf++) {
|
|
2654
|
+
const sfx = mapCenter + 60 + sf * 110;
|
|
2655
|
+
const sfy = mapY - 160 + (sf % 2) * 35;
|
|
2656
|
+
sLine(sfx - 14, sfy, sfx + 14, sfy, 730 + sf, '#0284c7', 2);
|
|
2657
|
+
sLine(sfx, sfy - 14, sfx, sfy + 14, 733 + sf, '#0284c7', 2);
|
|
2658
|
+
sLine(sfx - 10, sfy - 10, sfx + 10, sfy + 10, 736 + sf, '#0284c7', 2);
|
|
2659
|
+
sLine(sfx - 10, sfy + 10, sfx + 10, sfy - 10, 739 + sf, '#0284c7', 2);
|
|
2660
|
+
}
|
|
2661
|
+
ctx.restore();
|
|
2662
|
+
|
|
2663
|
+
// 3. Dynamic Migration Route Curves with Pulsing Arrowheads
|
|
2664
|
+
if (arrowSpring > 0.05) {
|
|
2665
|
+
ctx.save();
|
|
2666
|
+
ctx.strokeStyle = '#c2410c';
|
|
2667
|
+
ctx.lineWidth = 4;
|
|
2668
|
+
ctx.setLineDash([10, 8]);
|
|
2669
|
+
|
|
2670
|
+
// Route 1: Out of Africa -> Europe
|
|
2671
|
+
ctx.beginPath();
|
|
2672
|
+
ctx.moveTo(mapCenter + 20, mapY - 40);
|
|
2673
|
+
ctx.quadraticCurveTo(mapCenter - 20, mapY - 120, mapCenter - 60, mapY - 160);
|
|
2674
|
+
ctx.stroke();
|
|
2675
|
+
|
|
2676
|
+
// Route 2: Out of Africa -> Asia
|
|
2677
|
+
ctx.beginPath();
|
|
2678
|
+
ctx.moveTo(mapCenter + 40, mapY - 35);
|
|
2679
|
+
ctx.quadraticCurveTo(mapCenter + 120, mapY - 80, mapCenter + 220, mapY - 120);
|
|
2680
|
+
ctx.stroke();
|
|
2681
|
+
ctx.setLineDash([]);
|
|
2682
|
+
|
|
2683
|
+
// Pulsing migration markers
|
|
2684
|
+
const pulseR = 6 + Math.sin(t * 6) * 3;
|
|
2685
|
+
sCircle(mapCenter + 30, mapY - 40, pulseR, 750, '#ea580c', 2, '#fbbf24');
|
|
2686
|
+
sCircle(mapCenter - 60, mapY - 160, pulseR, 751, '#0284c7', 2, '#38bdf8');
|
|
2687
|
+
sCircle(mapCenter + 220, mapY - 120, pulseR, 752, '#0284c7', 2, '#38bdf8');
|
|
2688
|
+
ctx.restore();
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
// 4. Walking Doodle Explorer on the Migration Trail
|
|
2692
|
+
const trailStartX = mapCenter + 10;
|
|
2693
|
+
const trailStartY = mapY + 20;
|
|
2694
|
+
const trailEndX = mapCenter + 190;
|
|
2695
|
+
const trailEndY = mapY - 100;
|
|
2696
|
+
const curNomadX = trailStartX + (trailEndX - trailStartX) * walkProg;
|
|
2697
|
+
const curNomadY = trailStartY + (trailEndY - trailStartY) * walkProg;
|
|
2698
|
+
|
|
2699
|
+
drawDoodleCharacter({
|
|
2700
|
+
x: curNomadX,
|
|
2701
|
+
y: curNomadY + 120,
|
|
2702
|
+
scale: 1.15,
|
|
2703
|
+
hairStyle: 'wild',
|
|
2704
|
+
expression: 'focused',
|
|
2705
|
+
clothing: walkProg > 0.5 ? 'pelt' : 'none',
|
|
2706
|
+
armPose: 'hold_spear',
|
|
2707
|
+
showShadow: true,
|
|
2708
|
+
seed: 760,
|
|
2709
|
+
});
|
|
2710
|
+
|
|
2711
|
+
} else if (COMP_THEME === 'doodle_royal_status') {
|
|
2712
|
+
// Scene 16: Evolution of Clothing into Royal Mantles, Crowns & Social Status
|
|
2713
|
+
const crownSpring = spring(t, window.DURATION * 0.32, 190, 15);
|
|
2714
|
+
const arrowRise = spring(t, window.DURATION * 0.55, 170, 14);
|
|
2715
|
+
|
|
2716
|
+
// Warm dais platform
|
|
2717
|
+
ctx.fillStyle = IS_LIGHT ? '#faf7f2' : '#1c1917';
|
|
2718
|
+
ctx.fillRect(0, 0, W, H);
|
|
2719
|
+
|
|
2720
|
+
ctx.fillStyle = '#451a03';
|
|
2721
|
+
ctx.fillRect(0, cy + 140, W, H - (cy + 140));
|
|
2722
|
+
sLine(0, cy + 140, W, cy + 140, 770, '#d97706', 3.5);
|
|
2723
|
+
|
|
2724
|
+
// Prehistoric Human on Left (Simple pelt, admiring transformation)
|
|
2725
|
+
drawDoodleCharacter({
|
|
2726
|
+
x: cx - 260,
|
|
2727
|
+
y: cy + 140,
|
|
2728
|
+
scale: 1.3,
|
|
2729
|
+
hairStyle: 'wild',
|
|
2730
|
+
expression: 'curious',
|
|
2731
|
+
clothing: 'pelt',
|
|
2732
|
+
armPose: 'thinker',
|
|
2733
|
+
showShadow: true,
|
|
2734
|
+
seed: 771,
|
|
2735
|
+
});
|
|
2736
|
+
|
|
2737
|
+
// Royal Monarch on Right (Majestic regal cape / robe)
|
|
2738
|
+
const kingX = cx + 220;
|
|
2739
|
+
drawDoodleCharacter({
|
|
2740
|
+
x: kingX,
|
|
2741
|
+
y: cy + 140,
|
|
2742
|
+
scale: 1.35,
|
|
2743
|
+
hairStyle: 'modern',
|
|
2744
|
+
expression: 'smiling',
|
|
2745
|
+
clothing: 'cloak',
|
|
2746
|
+
armPose: 'peace_gesture',
|
|
2747
|
+
showShadow: true,
|
|
2748
|
+
seed: 772,
|
|
2749
|
+
});
|
|
2750
|
+
|
|
2751
|
+
// Ornate Golden Crown with Ruby jewels appearing above Monarch
|
|
2752
|
+
if (crownSpring > 0.05) {
|
|
2753
|
+
ctx.save();
|
|
2754
|
+
ctx.translate(kingX, cy - 20);
|
|
2755
|
+
ctx.scale(crownSpring, crownSpring);
|
|
2756
|
+
|
|
2757
|
+
// Golden crown base & peaks
|
|
2758
|
+
ctx.fillStyle = '#f59e0b';
|
|
2759
|
+
ctx.beginPath();
|
|
2760
|
+
ctx.moveTo(-36, 0);
|
|
2761
|
+
ctx.lineTo(-40, -32);
|
|
2762
|
+
ctx.lineTo(-20, -18);
|
|
2763
|
+
ctx.lineTo(0, -42);
|
|
2764
|
+
ctx.lineTo(20, -18);
|
|
2765
|
+
ctx.lineTo(40, -32);
|
|
2766
|
+
ctx.lineTo(36, 0);
|
|
2767
|
+
ctx.closePath();
|
|
2768
|
+
ctx.fill();
|
|
2769
|
+
ctx.strokeStyle = '#b45309';
|
|
2770
|
+
ctx.lineWidth = 2.5;
|
|
2771
|
+
ctx.stroke();
|
|
2772
|
+
|
|
2773
|
+
// Jewels on crown peaks
|
|
2774
|
+
sCircle(-40, -32, 4, 780, '#b91c1c', 1.5, '#ef4444');
|
|
2775
|
+
sCircle(0, -42, 5, 781, '#b91c1c', 1.5, '#ef4444');
|
|
2776
|
+
sCircle(40, -32, 4, 782, '#b91c1c', 1.5, '#ef4444');
|
|
2777
|
+
|
|
2778
|
+
// Golden Sceptre in hand
|
|
2779
|
+
sLine(42, 20, 52, 130, 785, '#d97706', 4.5);
|
|
2780
|
+
sCircle(42, 16, 10, 786, '#d97706', 2, '#fbbf24');
|
|
2781
|
+
ctx.restore();
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
// Status & Power indicator arrows rising centrally
|
|
2785
|
+
if (arrowRise > 0.05) {
|
|
2786
|
+
ctx.save();
|
|
2787
|
+
ctx.globalAlpha = Math.min(1, arrowRise);
|
|
2788
|
+
for (let arr = 0; arr < 3; arr++) {
|
|
2789
|
+
const arrX = cx - 30 + arr * 40;
|
|
2790
|
+
const arrY = cy + 40 - arr * 35 - Math.sin(t * 4 + arr) * 10;
|
|
2791
|
+
sLine(arrX, arrY + 40, arrX, arrY, 790 + arr, '#d97706', 3.5);
|
|
2792
|
+
sLine(arrX - 12, arrY + 14, arrX, arrY, 793 + arr, '#d97706', 3.5);
|
|
2793
|
+
sLine(arrX + 12, arrY + 14, arrX, arrY, 796 + arr, '#d97706', 3.5);
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
ctx.fillStyle = '#b45309';
|
|
2797
|
+
ctx.font = '700 20px ' + FONT_DISPLAY;
|
|
2798
|
+
ctx.textAlign = 'center';
|
|
2799
|
+
ctx.fillText('STATUS & POWER', cx + 10, cy - 90);
|
|
2800
|
+
ctx.restore();
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
} else if (COMP_THEME === 'doodle_outro_subscribe') {
|
|
2804
|
+
// Scene 25: YouTube Outro / Animated Question Mark / Subscribe & Bell Card
|
|
2805
|
+
const qSpring = spring(t, 0.05, 180, 14);
|
|
2806
|
+
const subSpring = spring(t, window.DURATION * 0.28, 200, 14);
|
|
2807
|
+
const bellSpring = spring(t, window.DURATION * 0.50, 190, 12);
|
|
2808
|
+
|
|
2809
|
+
ctx.fillStyle = IS_LIGHT ? '#faf7f2' : '#1c1917';
|
|
2810
|
+
ctx.fillRect(0, 0, W, H);
|
|
2811
|
+
|
|
2812
|
+
// Warm terrain ground
|
|
2813
|
+
ctx.fillStyle = '#451a03';
|
|
2814
|
+
ctx.fillRect(0, cy + 140, W, H - (cy + 140));
|
|
2815
|
+
sLine(0, cy + 140, W, cy + 140, 810, '#d97706', 3.5);
|
|
2816
|
+
|
|
2817
|
+
// Prehistoric character on left drawing with a reed/quill
|
|
2818
|
+
drawDoodleCharacter({
|
|
2819
|
+
x: cx - 280,
|
|
2820
|
+
y: cy + 140,
|
|
2821
|
+
scale: 1.35,
|
|
2822
|
+
hairStyle: 'wild',
|
|
2823
|
+
expression: 'smiling',
|
|
2824
|
+
clothing: 'pelt',
|
|
2825
|
+
armPose: 'peace_gesture',
|
|
2826
|
+
showShadow: true,
|
|
2827
|
+
seed: 811,
|
|
2828
|
+
});
|
|
2829
|
+
|
|
2830
|
+
// Big bold animated question mark drawn in ink
|
|
2831
|
+
if (qSpring > 0.05) {
|
|
2832
|
+
ctx.save();
|
|
2833
|
+
const qX = cx - 80;
|
|
2834
|
+
const qY = cy - 20;
|
|
2835
|
+
ctx.translate(qX, qY);
|
|
2836
|
+
ctx.scale(qSpring, qSpring);
|
|
2837
|
+
|
|
2838
|
+
ctx.strokeStyle = '#c2410c';
|
|
2839
|
+
ctx.lineWidth = 9;
|
|
2840
|
+
ctx.lineCap = 'round';
|
|
2841
|
+
ctx.beginPath();
|
|
2842
|
+
ctx.arc(0, -60, 44, Math.PI, 0, false);
|
|
2843
|
+
ctx.bezierCurveTo(44, -15, 0, 5, 0, 35);
|
|
2844
|
+
ctx.stroke();
|
|
2845
|
+
|
|
2846
|
+
sCircle(0, 72, 8, 815, '#c2410c', 3, '#c2410c');
|
|
2847
|
+
ctx.restore();
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
// Interactive YouTube Subscribe Pill Button & Swinging Notification Bell
|
|
2851
|
+
if (subSpring > 0.05) {
|
|
2852
|
+
ctx.save();
|
|
2853
|
+
const btnX = cx + 220;
|
|
2854
|
+
const btnY = cy - 20;
|
|
2855
|
+
ctx.translate(btnX, btnY);
|
|
2856
|
+
ctx.scale(subSpring, subSpring);
|
|
2857
|
+
|
|
2858
|
+
// Red Subscribe Pill
|
|
2859
|
+
ctx.fillStyle = '#dc2626';
|
|
2860
|
+
ctx.beginPath();
|
|
2861
|
+
ctx.roundRect(-140, -32, 280, 64, 32);
|
|
2862
|
+
ctx.fill();
|
|
2863
|
+
ctx.strokeStyle = '#991b1b';
|
|
2864
|
+
ctx.lineWidth = 3;
|
|
2865
|
+
ctx.stroke();
|
|
2866
|
+
|
|
2867
|
+
// White Subscribe text
|
|
2868
|
+
ctx.fillStyle = '#ffffff';
|
|
2869
|
+
ctx.font = '700 24px ' + FONT_DISPLAY;
|
|
2870
|
+
ctx.textAlign = 'center';
|
|
2871
|
+
ctx.textBaseline = 'middle';
|
|
2872
|
+
ctx.fillText('SUBSCRIBE', -15, 0);
|
|
2873
|
+
|
|
2874
|
+
// Play icon triangle
|
|
2875
|
+
ctx.beginPath();
|
|
2876
|
+
ctx.moveTo(-90, -12);
|
|
2877
|
+
ctx.lineTo(-72, 0);
|
|
2878
|
+
ctx.lineTo(-90, 12);
|
|
2879
|
+
ctx.closePath();
|
|
2880
|
+
ctx.fill();
|
|
2881
|
+
|
|
2882
|
+
// Notification Bell swinging on spring
|
|
2883
|
+
if (bellSpring > 0.05) {
|
|
2884
|
+
ctx.save();
|
|
2885
|
+
const bellSwing = Math.sin(t * 9) * 0.25 * Math.exp(-(t % 2.5));
|
|
2886
|
+
ctx.translate(180, 0);
|
|
2887
|
+
ctx.rotate(bellSwing);
|
|
2888
|
+
|
|
2889
|
+
// Golden bell body
|
|
2890
|
+
ctx.fillStyle = '#f59e0b';
|
|
2891
|
+
ctx.beginPath();
|
|
2892
|
+
ctx.arc(0, -12, 14, Math.PI, 0);
|
|
2893
|
+
ctx.quadraticCurveTo(18, 12, 22, 16);
|
|
2894
|
+
ctx.lineTo(-22, 16);
|
|
2895
|
+
ctx.quadraticCurveTo(-18, 12, 0, -12);
|
|
2896
|
+
ctx.closePath();
|
|
2897
|
+
ctx.fill();
|
|
2898
|
+
ctx.strokeStyle = '#b45309';
|
|
2899
|
+
ctx.lineWidth = 2.5;
|
|
2900
|
+
ctx.stroke();
|
|
2901
|
+
|
|
2902
|
+
// Bell clapper
|
|
2903
|
+
sCircle(0, 20, 5, 820, '#b45309', 2, '#d97706');
|
|
2904
|
+
ctx.restore();
|
|
2905
|
+
}
|
|
2906
|
+
ctx.restore();
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2513
2909
|
} else if (COMP_THEME === 'celestial_orbit') {
|
|
2514
2910
|
// 3D Celestial Body (Earth) with orbiting satellites & signal pulses
|
|
2515
2911
|
const earthR = Math.min(W, H) * 0.22;
|
|
@@ -2530,7 +2926,7 @@ function drawMainScene(t) {
|
|
|
2530
2926
|
ctx.beginPath(); ctx.arc(cx, cy, earthR, 0, Math.PI * 2); ctx.fill();
|
|
2531
2927
|
|
|
2532
2928
|
// Rotating longitude meridians
|
|
2533
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.22)';
|
|
2929
|
+
ctx.strokeStyle = IS_LIGHT ? 'rgba(28, 25, 23, 0.35)' : 'rgba(255, 255, 255, 0.22)';
|
|
2534
2930
|
ctx.lineWidth = 1.5;
|
|
2535
2931
|
for (let m = 0; m < 4; m++) {
|
|
2536
2932
|
const mAngle = t * 0.5 + (m * Math.PI) / 4;
|
|
@@ -2561,7 +2957,7 @@ function drawMainScene(t) {
|
|
|
2561
2957
|
ctx.save();
|
|
2562
2958
|
ctx.translate(cx, cy);
|
|
2563
2959
|
ctx.rotate(tilt);
|
|
2564
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.14)';
|
|
2960
|
+
ctx.strokeStyle = IS_LIGHT ? 'rgba(28, 25, 23, 0.25)' : 'rgba(255, 255, 255, 0.14)';
|
|
2565
2961
|
ctx.lineWidth = 1.5;
|
|
2566
2962
|
ctx.beginPath(); ctx.ellipse(0, 0, a, b, 0, 0, Math.PI * 2); ctx.stroke();
|
|
2567
2963
|
|
|
@@ -2790,7 +3186,7 @@ function drawMainScene(t) {
|
|
|
2790
3186
|
} else if (COMP_THEME === 'radar_globe') {
|
|
2791
3187
|
// 3D Tactical Wireframe Rotating Radar Globe
|
|
2792
3188
|
const globeR = 150;
|
|
2793
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
|
|
3189
|
+
ctx.strokeStyle = IS_LIGHT ? 'rgba(28, 25, 23, 0.35)' : 'rgba(255, 255, 255, 0.15)';
|
|
2794
3190
|
ctx.lineWidth = 1.5;
|
|
2795
3191
|
|
|
2796
3192
|
// Outer degree ring
|
|
@@ -2962,7 +3358,679 @@ function drawMainScene(t) {
|
|
|
2962
3358
|
ctx.drawImage(cachedImage, cx - imgW / 2, cy - imgH / 2, imgW, imgH);
|
|
2963
3359
|
ctx.restore();
|
|
2964
3360
|
|
|
2965
|
-
} else {
|
|
3361
|
+
} else if (COMP_THEME === 'versus_comparison') {
|
|
3362
|
+
// Universal Archetype: Split-Screen Versus / Comparison
|
|
3363
|
+
const splitSpring = spring(t, 0.05, 180, 14);
|
|
3364
|
+
const cardLeftSpring = spring(t, 0.15, 190, 15);
|
|
3365
|
+
const cardRightSpring = spring(t, 0.28, 190, 15);
|
|
3366
|
+
const badgeSpring = spring(t, 0.42, 220, 13);
|
|
3367
|
+
|
|
3368
|
+
// Center divider beam
|
|
3369
|
+
const divAlpha = Math.min(1, Math.max(0, splitSpring));
|
|
3370
|
+
ctx.save();
|
|
3371
|
+
ctx.globalAlpha = divAlpha;
|
|
3372
|
+
const seamGrad = ctx.createLinearGradient(0, cy - H * 0.42, 0, cy + H * 0.42);
|
|
3373
|
+
seamGrad.addColorStop(0, 'rgba(255,255,255,0)');
|
|
3374
|
+
seamGrad.addColorStop(0.5, ACCENT);
|
|
3375
|
+
seamGrad.addColorStop(1, 'rgba(255,255,255,0)');
|
|
3376
|
+
ctx.strokeStyle = seamGrad;
|
|
3377
|
+
ctx.lineWidth = 3;
|
|
3378
|
+
ctx.beginPath();
|
|
3379
|
+
ctx.moveTo(cx, cy - H * 0.42);
|
|
3380
|
+
ctx.lineTo(cx, cy + H * 0.42);
|
|
3381
|
+
ctx.stroke();
|
|
3382
|
+
|
|
3383
|
+
// Seam energy sparks
|
|
3384
|
+
for (let sp = 0; sp < 8; sp++) {
|
|
3385
|
+
const spY = cy - H * 0.35 + ((sp * 95 + t * 240) % (H * 0.7));
|
|
3386
|
+
const spR = 3 + Math.sin(t * 8 + sp) * 2;
|
|
3387
|
+
ctx.fillStyle = ACCENT;
|
|
3388
|
+
ctx.beginPath();
|
|
3389
|
+
ctx.arc(cx, spY, spR, 0, Math.PI * 2);
|
|
3390
|
+
ctx.fill();
|
|
3391
|
+
}
|
|
3392
|
+
ctx.restore();
|
|
3393
|
+
|
|
3394
|
+
// Left Card (Side A)
|
|
3395
|
+
const cardW = Math.min(W * 0.38, 520);
|
|
3396
|
+
const cardH = Math.min(H * 0.62, 540);
|
|
3397
|
+
const leftX = cx - cardW - 35;
|
|
3398
|
+
const cardY = cy - cardH / 2;
|
|
3399
|
+
|
|
3400
|
+
ctx.save();
|
|
3401
|
+
ctx.globalAlpha = Math.min(1, Math.max(0, cardLeftSpring));
|
|
3402
|
+
ctx.translate(leftX + (1 - cardLeftSpring) * -60, cardY);
|
|
3403
|
+
ctx.fillStyle = CARD_BG;
|
|
3404
|
+
ctx.strokeStyle = CARD_BORDER;
|
|
3405
|
+
ctx.lineWidth = 2;
|
|
3406
|
+
ctx.beginPath();
|
|
3407
|
+
ctx.roundRect(0, 0, cardW, cardH, 20);
|
|
3408
|
+
ctx.fill();
|
|
3409
|
+
ctx.stroke();
|
|
3410
|
+
|
|
3411
|
+
// Left Card Header & Accent Pill
|
|
3412
|
+
ctx.fillStyle = 'rgba(56, 189, 248, 0.15)';
|
|
3413
|
+
ctx.beginPath();
|
|
3414
|
+
ctx.roundRect(24, 24, 110, 32, 16);
|
|
3415
|
+
ctx.fill();
|
|
3416
|
+
ctx.fillStyle = '#38bdf8';
|
|
3417
|
+
ctx.font = 'bold 16px ' + FONT_DISPLAY;
|
|
3418
|
+
ctx.fillText('OPTION A', 42, 45);
|
|
3419
|
+
|
|
3420
|
+
// Left Metrics / Feature Bars
|
|
3421
|
+
for (let b = 0; b < 4; b++) {
|
|
3422
|
+
const barY = 90 + b * 75;
|
|
3423
|
+
const bProgress = Math.min(1, Math.max(0, spring(t, 0.3 + b * 0.1, 160, 14)));
|
|
3424
|
+
ctx.fillStyle = IS_LIGHT ? 'rgba(0,0,0,0.06)' : 'rgba(255,255,255,0.06)';
|
|
3425
|
+
ctx.beginPath();
|
|
3426
|
+
ctx.roundRect(24, barY + 22, cardW - 48, 14, 7);
|
|
3427
|
+
ctx.fill();
|
|
3428
|
+
|
|
3429
|
+
ctx.fillStyle = '#38bdf8';
|
|
3430
|
+
ctx.beginPath();
|
|
3431
|
+
const fillW = (cardW - 48) * (0.45 + 0.15 * Math.sin(b * 1.5 + 2)) * bProgress;
|
|
3432
|
+
ctx.roundRect(24, barY + 22, fillW, 14, 7);
|
|
3433
|
+
ctx.fill();
|
|
3434
|
+
|
|
3435
|
+
// Feature icon / indicator
|
|
3436
|
+
sCircle(36, barY + 8, 6, 100 + b, '#38bdf8', 2, '#38bdf8');
|
|
3437
|
+
ctx.fillStyle = TEXT_MAIN;
|
|
3438
|
+
ctx.font = '600 18px ' + FONT_DISPLAY;
|
|
3439
|
+
ctx.fillText('ATTRIBUTE 0' + (b + 1), 54, barY + 14);
|
|
3440
|
+
}
|
|
3441
|
+
ctx.restore();
|
|
3442
|
+
|
|
3443
|
+
// Right Card (Side B)
|
|
3444
|
+
const rightX = cx + 35;
|
|
3445
|
+
ctx.save();
|
|
3446
|
+
ctx.globalAlpha = Math.min(1, Math.max(0, cardRightSpring));
|
|
3447
|
+
ctx.translate(rightX + (1 - cardRightSpring) * 60, cardY);
|
|
3448
|
+
ctx.fillStyle = CARD_BG;
|
|
3449
|
+
ctx.strokeStyle = CARD_BORDER;
|
|
3450
|
+
ctx.lineWidth = 2;
|
|
3451
|
+
ctx.beginPath();
|
|
3452
|
+
ctx.roundRect(0, 0, cardW, cardH, 20);
|
|
3453
|
+
ctx.fill();
|
|
3454
|
+
ctx.stroke();
|
|
3455
|
+
|
|
3456
|
+
// Right Card Header & Accent Pill
|
|
3457
|
+
ctx.fillStyle = 'rgba(244, 63, 94, 0.15)';
|
|
3458
|
+
ctx.beginPath();
|
|
3459
|
+
ctx.roundRect(24, 24, 110, 32, 16);
|
|
3460
|
+
ctx.fill();
|
|
3461
|
+
ctx.fillStyle = '#f43f5e';
|
|
3462
|
+
ctx.font = 'bold 16px ' + FONT_DISPLAY;
|
|
3463
|
+
ctx.fillText('OPTION B', 42, 45);
|
|
3464
|
+
|
|
3465
|
+
// Right Metrics / Feature Bars
|
|
3466
|
+
for (let b = 0; b < 4; b++) {
|
|
3467
|
+
const barY = 90 + b * 75;
|
|
3468
|
+
const bProgress = Math.min(1, Math.max(0, spring(t, 0.35 + b * 0.1, 160, 14)));
|
|
3469
|
+
ctx.fillStyle = IS_LIGHT ? 'rgba(0,0,0,0.06)' : 'rgba(255,255,255,0.06)';
|
|
3470
|
+
ctx.beginPath();
|
|
3471
|
+
ctx.roundRect(24, barY + 22, cardW - 48, 14, 7);
|
|
3472
|
+
ctx.fill();
|
|
3473
|
+
|
|
3474
|
+
ctx.fillStyle = '#f43f5e';
|
|
3475
|
+
ctx.beginPath();
|
|
3476
|
+
const fillW = (cardW - 48) * (0.55 + 0.12 * Math.cos(b * 1.8 + 1)) * bProgress;
|
|
3477
|
+
ctx.roundRect(24, barY + 22, fillW, 14, 7);
|
|
3478
|
+
ctx.fill();
|
|
3479
|
+
|
|
3480
|
+
sCircle(36, barY + 8, 6, 200 + b, '#f43f5e', 2, '#f43f5e');
|
|
3481
|
+
ctx.fillStyle = TEXT_MAIN;
|
|
3482
|
+
ctx.font = '600 18px ' + FONT_DISPLAY;
|
|
3483
|
+
ctx.fillText('ATTRIBUTE 0' + (b + 1), 54, barY + 14);
|
|
3484
|
+
}
|
|
3485
|
+
ctx.restore();
|
|
3486
|
+
|
|
3487
|
+
// Center VS Emblem Badge
|
|
3488
|
+
ctx.save();
|
|
3489
|
+
ctx.translate(cx, cy);
|
|
3490
|
+
const bScale = Math.max(0, badgeSpring);
|
|
3491
|
+
ctx.scale(bScale, bScale);
|
|
3492
|
+
ctx.fillStyle = IS_LIGHT ? '#ffffff' : '#0f172a';
|
|
3493
|
+
ctx.strokeStyle = ACCENT;
|
|
3494
|
+
ctx.lineWidth = 4;
|
|
3495
|
+
ctx.beginPath();
|
|
3496
|
+
ctx.arc(0, 0, 48, 0, Math.PI * 2);
|
|
3497
|
+
ctx.fill();
|
|
3498
|
+
ctx.stroke();
|
|
3499
|
+
|
|
3500
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
|
|
3501
|
+
ctx.lineWidth = 1.5;
|
|
3502
|
+
ctx.beginPath();
|
|
3503
|
+
ctx.arc(0, 0, 42, 0, Math.PI * 2);
|
|
3504
|
+
ctx.stroke();
|
|
3505
|
+
|
|
3506
|
+
ctx.font = '900 28px ' + FONT_DISPLAY;
|
|
3507
|
+
ctx.fillStyle = ACCENT;
|
|
3508
|
+
ctx.textAlign = 'center';
|
|
3509
|
+
ctx.textBaseline = 'middle';
|
|
3510
|
+
ctx.fillText('VS', 0, 2);
|
|
3511
|
+
ctx.restore();
|
|
3512
|
+
|
|
3513
|
+
} else if (COMP_THEME === 'mechanism_physics') {
|
|
3514
|
+
// Universal Archetype: Mechanical Engineering, Cutaway Gears, Forces & Kinematics
|
|
3515
|
+
const engSpring = spring(t, 0.05, 170, 14);
|
|
3516
|
+
const vectorSpring = spring(t, 0.45, 180, 15);
|
|
3517
|
+
|
|
3518
|
+
// Background schematic blueprint grid lines
|
|
3519
|
+
ctx.save();
|
|
3520
|
+
ctx.strokeStyle = IS_LIGHT ? 'rgba(0,0,0,0.07)' : 'rgba(56, 189, 248, 0.12)';
|
|
3521
|
+
ctx.lineWidth = 1;
|
|
3522
|
+
for (let gx = cx - 450; gx <= cx + 450; gx += 60) {
|
|
3523
|
+
ctx.beginPath(); ctx.moveTo(gx, cy - 320); ctx.lineTo(gx, cy + 320); ctx.stroke();
|
|
3524
|
+
}
|
|
3525
|
+
for (let gy = cy - 300; gy <= cy + 300; gy += 60) {
|
|
3526
|
+
ctx.beginPath(); ctx.moveTo(cx - 480, gy); ctx.lineTo(cx + 480, gy); ctx.stroke();
|
|
3527
|
+
}
|
|
3528
|
+
ctx.strokeStyle = IS_LIGHT ? 'rgba(0,0,0,0.1)' : 'rgba(56, 189, 248, 0.2)';
|
|
3529
|
+
ctx.lineWidth = 1.5;
|
|
3530
|
+
ctx.beginPath(); ctx.arc(cx, cy, 340, 0, Math.PI * 2); ctx.stroke();
|
|
3531
|
+
ctx.restore();
|
|
3532
|
+
|
|
3533
|
+
// Helper: Draw precision technical gear with teeth
|
|
3534
|
+
function drawGear(gx, gy, radius, teeth, rotAngle, color, axleColor) {
|
|
3535
|
+
ctx.save();
|
|
3536
|
+
ctx.translate(gx, gy);
|
|
3537
|
+
ctx.rotate(rotAngle);
|
|
3538
|
+
ctx.fillStyle = color;
|
|
3539
|
+
ctx.strokeStyle = STROKE_COLOR;
|
|
3540
|
+
ctx.lineWidth = 2.5;
|
|
3541
|
+
|
|
3542
|
+
ctx.beginPath();
|
|
3543
|
+
const toothDepth = radius * 0.14;
|
|
3544
|
+
for (let i = 0; i < teeth; i++) {
|
|
3545
|
+
const a0 = (i * 2 * Math.PI) / teeth;
|
|
3546
|
+
const a1 = a0 + (Math.PI / teeth) * 0.35;
|
|
3547
|
+
const a2 = a0 + (Math.PI / teeth) * 0.65;
|
|
3548
|
+
const a3 = a0 + (Math.PI / teeth);
|
|
3549
|
+
|
|
3550
|
+
const rIn = radius - toothDepth;
|
|
3551
|
+
const rOut = radius + toothDepth;
|
|
3552
|
+
|
|
3553
|
+
if (i === 0) ctx.moveTo(rIn * Math.cos(a0), rIn * Math.sin(a0));
|
|
3554
|
+
else ctx.lineTo(rIn * Math.cos(a0), rIn * Math.sin(a0));
|
|
3555
|
+
ctx.lineTo(rOut * Math.cos(a1), rOut * Math.sin(a1));
|
|
3556
|
+
ctx.lineTo(rOut * Math.cos(a2), rOut * Math.sin(a2));
|
|
3557
|
+
ctx.lineTo(rIn * Math.cos(a3), rIn * Math.sin(a3));
|
|
3558
|
+
}
|
|
3559
|
+
ctx.closePath();
|
|
3560
|
+
ctx.fill();
|
|
3561
|
+
ctx.stroke();
|
|
3562
|
+
|
|
3563
|
+
// Hollow interior & spokes
|
|
3564
|
+
ctx.beginPath();
|
|
3565
|
+
ctx.arc(0, 0, radius * 0.62, 0, Math.PI * 2);
|
|
3566
|
+
ctx.fillStyle = IS_LIGHT ? '#ffffff' : '#080d1a';
|
|
3567
|
+
ctx.fill();
|
|
3568
|
+
ctx.stroke();
|
|
3569
|
+
|
|
3570
|
+
// 4 Precision Spokes
|
|
3571
|
+
for (let sp = 0; sp < 4; sp++) {
|
|
3572
|
+
ctx.save();
|
|
3573
|
+
ctx.rotate((sp * Math.PI) / 2);
|
|
3574
|
+
ctx.fillStyle = color;
|
|
3575
|
+
ctx.fillRect(-6, -radius * 0.58, 12, radius * 0.58 * 2);
|
|
3576
|
+
ctx.restore();
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
// Center Axle Hub
|
|
3580
|
+
ctx.beginPath();
|
|
3581
|
+
ctx.arc(0, 0, radius * 0.25, 0, Math.PI * 2);
|
|
3582
|
+
ctx.fillStyle = axleColor;
|
|
3583
|
+
ctx.fill();
|
|
3584
|
+
ctx.stroke();
|
|
3585
|
+
sCircle(0, 0, radius * 0.1, 50, '#ffffff', 2, '#ffffff');
|
|
3586
|
+
|
|
3587
|
+
ctx.restore();
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
ctx.save();
|
|
3591
|
+
const gScale = Math.min(1, Math.max(0, engSpring));
|
|
3592
|
+
ctx.translate(cx, cy);
|
|
3593
|
+
ctx.scale(gScale, gScale);
|
|
3594
|
+
ctx.translate(-cx, -cy);
|
|
3595
|
+
|
|
3596
|
+
// Primary Gear Train (Interlocking 3 gears)
|
|
3597
|
+
const gearSpeed = t * 1.4;
|
|
3598
|
+
drawGear(cx - 150, cy, 140, 24, gearSpeed, IS_LIGHT ? '#e2e8f0' : '#1e293b', ACCENT);
|
|
3599
|
+
drawGear(cx + 90, cy - 80, 105, 18, -gearSpeed * (24 / 18) + 0.17, IS_LIGHT ? '#cbd5e1' : '#334155', ACCENT2);
|
|
3600
|
+
drawGear(cx + 175, cy + 95, 70, 12, gearSpeed * (24 / 12) + 0.25, IS_LIGHT ? '#94a3b8' : '#475569', '#10b981');
|
|
3601
|
+
|
|
3602
|
+
// Dynamic Kinematic Linkage / Piston
|
|
3603
|
+
const pistonCycle = t * 3.5;
|
|
3604
|
+
const crankR = 60;
|
|
3605
|
+
const crankX = cx - 150 + Math.cos(gearSpeed) * crankR;
|
|
3606
|
+
const crankY = cy + Math.sin(gearSpeed) * crankR;
|
|
3607
|
+
const pistonY = cy + 180 + Math.sin(pistonCycle) * 35;
|
|
3608
|
+
const pistonX = cx - 150;
|
|
3609
|
+
|
|
3610
|
+
sLine(crankX, crankY, pistonX, pistonY, 301, ACCENT, 5);
|
|
3611
|
+
sCircle(crankX, crankY, 8, 302, '#ffffff', 2, ACCENT);
|
|
3612
|
+
sCircle(pistonX, pistonY, 8, 303, '#ffffff', 2, ACCENT);
|
|
3613
|
+
|
|
3614
|
+
// Piston Head in Cylinder
|
|
3615
|
+
ctx.fillStyle = IS_LIGHT ? '#64748b' : '#38bdf8';
|
|
3616
|
+
ctx.fillRect(pistonX - 35, pistonY - 15, 70, 30);
|
|
3617
|
+
ctx.strokeStyle = STROKE_COLOR;
|
|
3618
|
+
ctx.lineWidth = 2.5;
|
|
3619
|
+
ctx.strokeRect(pistonX - 35, pistonY - 15, 70, 30);
|
|
3620
|
+
|
|
3621
|
+
// Dynamic Force Vectors & Aerodynamic Flow Arrows
|
|
3622
|
+
if (vectorSpring > 0.05) {
|
|
3623
|
+
ctx.save();
|
|
3624
|
+
ctx.globalAlpha = Math.min(1, Math.max(0, vectorSpring));
|
|
3625
|
+
|
|
3626
|
+
const vX = cx + 290;
|
|
3627
|
+
const vY = cy - 140;
|
|
3628
|
+
sLine(vX, vY, vX + 110, vY, 401, '#10b981', 4);
|
|
3629
|
+
ctx.fillStyle = '#10b981';
|
|
3630
|
+
ctx.beginPath();
|
|
3631
|
+
ctx.moveTo(vX + 125, vY);
|
|
3632
|
+
ctx.lineTo(vX + 105, vY - 8);
|
|
3633
|
+
ctx.lineTo(vX + 105, vY + 8);
|
|
3634
|
+
ctx.closePath();
|
|
3635
|
+
ctx.fill();
|
|
3636
|
+
ctx.font = 'bold 15px ' + FONT_DISPLAY;
|
|
3637
|
+
ctx.fillText('F_THRUST = 4.8 kN', vX, vY - 16);
|
|
3638
|
+
|
|
3639
|
+
ctx.strokeStyle = '#f59e0b';
|
|
3640
|
+
ctx.lineWidth = 3;
|
|
3641
|
+
ctx.beginPath();
|
|
3642
|
+
ctx.arc(cx - 150, cy, 185, -0.6, 0.6);
|
|
3643
|
+
ctx.stroke();
|
|
3644
|
+
ctx.fillStyle = '#f59e0b';
|
|
3645
|
+
ctx.font = 'bold 15px ' + FONT_DISPLAY;
|
|
3646
|
+
ctx.fillText('TORQUE = 320 Nm', cx - 210, cy - 195);
|
|
3647
|
+
ctx.restore();
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
ctx.restore();
|
|
3651
|
+
|
|
3652
|
+
} else if (COMP_THEME === 'process_pipeline') {
|
|
3653
|
+
// Universal Archetype: Multi-Stage Process / Pipeline / Supply Chain / Lifecycle
|
|
3654
|
+
const pipeSpring = spring(t, 0.05, 170, 14);
|
|
3655
|
+
const stages = [
|
|
3656
|
+
{ name: 'INGEST', label: '01 / SOURCE', desc: 'Raw Signals' },
|
|
3657
|
+
{ name: 'SYNTHESIS', label: '02 / TRANSFORM', desc: 'Active Process' },
|
|
3658
|
+
{ name: 'VALIDATION', label: '03 / VERIFY', desc: 'Quality Gate' },
|
|
3659
|
+
{ name: 'DEPLOYMENT', label: '04 / OUTPUT', desc: 'Master Release' },
|
|
3660
|
+
];
|
|
3661
|
+
|
|
3662
|
+
const pipeY = cy;
|
|
3663
|
+
const startX = cx - 450;
|
|
3664
|
+
const totalW = 900;
|
|
3665
|
+
const segW = totalW / (stages.length - 1);
|
|
3666
|
+
|
|
3667
|
+
ctx.save();
|
|
3668
|
+
const pScale = Math.min(1, Math.max(0, pipeSpring));
|
|
3669
|
+
ctx.translate(cx, cy);
|
|
3670
|
+
ctx.scale(pScale, pScale);
|
|
3671
|
+
ctx.translate(-cx, -cy);
|
|
3672
|
+
|
|
3673
|
+
// Main Conduit Pipeline Track
|
|
3674
|
+
const conduitG = ctx.createLinearGradient(startX, 0, startX + totalW, 0);
|
|
3675
|
+
conduitG.addColorStop(0, ACCENT);
|
|
3676
|
+
conduitG.addColorStop(0.5, '#38bdf8');
|
|
3677
|
+
conduitG.addColorStop(1, '#10b981');
|
|
3678
|
+
|
|
3679
|
+
ctx.strokeStyle = IS_LIGHT ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.15)';
|
|
3680
|
+
ctx.lineWidth = 14;
|
|
3681
|
+
ctx.lineCap = 'round';
|
|
3682
|
+
ctx.beginPath();
|
|
3683
|
+
ctx.moveTo(startX, pipeY);
|
|
3684
|
+
ctx.lineTo(startX + totalW, pipeY);
|
|
3685
|
+
ctx.stroke();
|
|
3686
|
+
|
|
3687
|
+
ctx.strokeStyle = conduitG;
|
|
3688
|
+
ctx.lineWidth = 6;
|
|
3689
|
+
ctx.stroke();
|
|
3690
|
+
|
|
3691
|
+
// Flowing Data Packet Orbs along the conduit
|
|
3692
|
+
for (let pk = 0; pk < 5; pk++) {
|
|
3693
|
+
const pkProgress = ((t * 0.45 + pk * 0.22) % 1.0);
|
|
3694
|
+
const pkX = startX + pkProgress * totalW;
|
|
3695
|
+
ctx.fillStyle = '#ffffff';
|
|
3696
|
+
ctx.beginPath();
|
|
3697
|
+
ctx.arc(pkX, pipeY, 6, 0, Math.PI * 2);
|
|
3698
|
+
ctx.fill();
|
|
3699
|
+
|
|
3700
|
+
ctx.fillStyle = ACCENT;
|
|
3701
|
+
ctx.globalAlpha = 0.4;
|
|
3702
|
+
ctx.beginPath();
|
|
3703
|
+
ctx.arc(pkX, pipeY, 14, 0, Math.PI * 2);
|
|
3704
|
+
ctx.fill();
|
|
3705
|
+
ctx.globalAlpha = 1.0;
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3708
|
+
// Stage Nodes & Info Cards
|
|
3709
|
+
for (let s = 0; s < stages.length; s++) {
|
|
3710
|
+
const st = stages[s];
|
|
3711
|
+
const nodeX = startX + s * segW;
|
|
3712
|
+
const nodeSpring = spring(t, 0.15 + s * 0.16, 180, 14);
|
|
3713
|
+
const nodeAlpha = Math.min(1, Math.max(0, nodeSpring));
|
|
3714
|
+
|
|
3715
|
+
const activeStage = Math.min(stages.length - 1, Math.floor((t / window.DURATION) * stages.length));
|
|
3716
|
+
const isActive = s === activeStage;
|
|
3717
|
+
|
|
3718
|
+
ctx.save();
|
|
3719
|
+
ctx.globalAlpha = nodeAlpha;
|
|
3720
|
+
ctx.translate(nodeX, pipeY);
|
|
3721
|
+
|
|
3722
|
+
const isTop = s % 2 === 0;
|
|
3723
|
+
const cardOffsetY = isTop ? -180 : 70;
|
|
3724
|
+
const cardW = 190;
|
|
3725
|
+
const cardH = 95;
|
|
3726
|
+
|
|
3727
|
+
sLine(0, 0, 0, isTop ? -50 : 50, 500 + s, isActive ? ACCENT : STROKE_ALT, 2.5);
|
|
3728
|
+
|
|
3729
|
+
ctx.fillStyle = CARD_BG;
|
|
3730
|
+
ctx.strokeStyle = isActive ? ACCENT : CARD_BORDER;
|
|
3731
|
+
ctx.lineWidth = isActive ? 3 : 1.5;
|
|
3732
|
+
ctx.beginPath();
|
|
3733
|
+
ctx.roundRect(-cardW / 2, cardOffsetY, cardW, cardH, 14);
|
|
3734
|
+
ctx.fill();
|
|
3735
|
+
ctx.stroke();
|
|
3736
|
+
|
|
3737
|
+
if (isActive) {
|
|
3738
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
3739
|
+
ctx.lineWidth = 1;
|
|
3740
|
+
ctx.beginPath();
|
|
3741
|
+
ctx.roundRect(-cardW / 2 + 4, cardOffsetY + 4, cardW - 8, cardH - 8, 10);
|
|
3742
|
+
ctx.stroke();
|
|
3743
|
+
}
|
|
3744
|
+
|
|
3745
|
+
ctx.textAlign = 'center';
|
|
3746
|
+
ctx.font = 'bold 12px ' + FONT_DISPLAY;
|
|
3747
|
+
ctx.fillStyle = isActive ? ACCENT : '#94a3b8';
|
|
3748
|
+
ctx.fillText(st.label, 0, cardOffsetY + 26);
|
|
3749
|
+
|
|
3750
|
+
ctx.font = '800 16px ' + FONT_DISPLAY;
|
|
3751
|
+
ctx.fillStyle = TEXT_MAIN;
|
|
3752
|
+
ctx.fillText(st.name, 0, cardOffsetY + 52);
|
|
3753
|
+
|
|
3754
|
+
ctx.font = '500 13px ' + FONT_BODY;
|
|
3755
|
+
ctx.fillStyle = TEXT_MUTED;
|
|
3756
|
+
ctx.fillText(st.desc, 0, cardOffsetY + 76);
|
|
3757
|
+
|
|
3758
|
+
const nodeR = isActive ? 22 : 16;
|
|
3759
|
+
ctx.fillStyle = IS_LIGHT ? '#ffffff' : '#0f172a';
|
|
3760
|
+
ctx.beginPath();
|
|
3761
|
+
ctx.arc(0, 0, nodeR, 0, Math.PI * 2);
|
|
3762
|
+
ctx.fill();
|
|
3763
|
+
|
|
3764
|
+
ctx.strokeStyle = isActive ? ACCENT : '#64748b';
|
|
3765
|
+
ctx.lineWidth = isActive ? 4 : 3;
|
|
3766
|
+
ctx.beginPath();
|
|
3767
|
+
ctx.arc(0, 0, nodeR, 0, Math.PI * 2);
|
|
3768
|
+
ctx.stroke();
|
|
3769
|
+
|
|
3770
|
+
sCircle(0, 0, isActive ? 8 : 5, 520 + s, isActive ? '#ffffff' : '#94a3b8', 2, isActive ? ACCENT : '#64748b');
|
|
3771
|
+
|
|
3772
|
+
ctx.restore();
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
ctx.restore();
|
|
3776
|
+
|
|
3777
|
+
} else if (COMP_THEME === 'cognitive_insight') {
|
|
3778
|
+
// Universal Archetype: Cognitive Psychology, Neural Eureka, Theory of Mind & Idea Spark
|
|
3779
|
+
const brainSpring = spring(t, 0.05, 170, 14);
|
|
3780
|
+
const eurekaSpring = spring(t, window.DURATION * 0.35, 160, 12);
|
|
3781
|
+
const orbitSpring = spring(t, 0.40, 180, 15);
|
|
3782
|
+
|
|
3783
|
+
ctx.save();
|
|
3784
|
+
const cScale = Math.min(1, Math.max(0, brainSpring));
|
|
3785
|
+
ctx.translate(cx, cy);
|
|
3786
|
+
ctx.scale(cScale, cScale);
|
|
3787
|
+
|
|
3788
|
+
// Concentric Thought Wave Ripples
|
|
3789
|
+
for (let r = 0; r < 4; r++) {
|
|
3790
|
+
const ripPhase = (t * 0.8 + r * 0.25) % 1.0;
|
|
3791
|
+
const ripR = 90 + ripPhase * 240;
|
|
3792
|
+
ctx.strokeStyle = ACCENT;
|
|
3793
|
+
ctx.globalAlpha = Math.max(0, (1 - ripPhase) * 0.35);
|
|
3794
|
+
ctx.lineWidth = 2.5;
|
|
3795
|
+
ctx.beginPath();
|
|
3796
|
+
ctx.arc(0, 0, ripR, 0, Math.PI * 2);
|
|
3797
|
+
ctx.stroke();
|
|
3798
|
+
}
|
|
3799
|
+
ctx.globalAlpha = 1.0;
|
|
3800
|
+
|
|
3801
|
+
// Cerebral Neural Core / Brain Silhouette
|
|
3802
|
+
const brainR = 120;
|
|
3803
|
+
const coreGrad = ctx.createRadialGradient(0, -20, 20, 0, 0, brainR * 1.5);
|
|
3804
|
+
coreGrad.addColorStop(0, '#fef08a');
|
|
3805
|
+
coreGrad.addColorStop(0.3, ACCENT);
|
|
3806
|
+
coreGrad.addColorStop(0.8, '#6366f1');
|
|
3807
|
+
coreGrad.addColorStop(1, 'rgba(15, 23, 42, 0)');
|
|
3808
|
+
|
|
3809
|
+
ctx.fillStyle = coreGrad;
|
|
3810
|
+
ctx.beginPath();
|
|
3811
|
+
ctx.arc(0, 0, brainR * 1.4, 0, Math.PI * 2);
|
|
3812
|
+
ctx.fill();
|
|
3813
|
+
|
|
3814
|
+
// Neural Synapses
|
|
3815
|
+
const synNodes = [
|
|
3816
|
+
{ x: -50, y: -60 }, { x: 0, y: -80 }, { x: 50, y: -60 },
|
|
3817
|
+
{ x: -80, y: -20 }, { x: -30, y: -20 }, { x: 30, y: -20 }, { x: 80, y: -20 },
|
|
3818
|
+
{ x: -60, y: 30 }, { x: 0, y: 25 }, { x: 60, y: 30 },
|
|
3819
|
+
{ x: -25, y: 70 }, { x: 25, y: 70 },
|
|
3820
|
+
];
|
|
3821
|
+
|
|
3822
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.35)';
|
|
3823
|
+
ctx.lineWidth = 2;
|
|
3824
|
+
for (let i = 0; i < synNodes.length; i++) {
|
|
3825
|
+
for (let j = i + 1; j < synNodes.length; j++) {
|
|
3826
|
+
const dx = synNodes[i].x - synNodes[j].x;
|
|
3827
|
+
const dy = synNodes[i].y - synNodes[j].y;
|
|
3828
|
+
const dist = Math.hypot(dx, dy);
|
|
3829
|
+
if (dist < 75) {
|
|
3830
|
+
ctx.beginPath();
|
|
3831
|
+
ctx.moveTo(synNodes[i].x, synNodes[i].y);
|
|
3832
|
+
ctx.lineTo(synNodes[j].x, synNodes[j].y);
|
|
3833
|
+
ctx.stroke();
|
|
3834
|
+
|
|
3835
|
+
const pulse = (t * 2.5 + i * 0.3 + j * 0.2) % 1.0;
|
|
3836
|
+
const px = synNodes[i].x + (synNodes[j].x - synNodes[i].x) * pulse;
|
|
3837
|
+
const py = synNodes[i].y + (synNodes[j].y - synNodes[i].y) * pulse;
|
|
3838
|
+
ctx.fillStyle = '#ffffff';
|
|
3839
|
+
ctx.beginPath();
|
|
3840
|
+
ctx.arc(px, py, 2.5, 0, Math.PI * 2);
|
|
3841
|
+
ctx.fill();
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
for (let n = 0; n < synNodes.length; n++) {
|
|
3847
|
+
const sn = synNodes[n];
|
|
3848
|
+
const fire = Math.sin(t * 8 + n * 1.3);
|
|
3849
|
+
const dotR = 5 + (fire > 0.5 ? 2.5 : 0);
|
|
3850
|
+
sCircle(sn.x, sn.y, dotR, 600 + n, '#ffffff', 2, fire > 0.3 ? '#fef08a' : ACCENT);
|
|
3851
|
+
}
|
|
3852
|
+
|
|
3853
|
+
// Eureka Idea Spark Burst
|
|
3854
|
+
if (eurekaSpring > 0.05) {
|
|
3855
|
+
const eProgress = Math.min(1, Math.max(0, eurekaSpring));
|
|
3856
|
+
ctx.save();
|
|
3857
|
+
ctx.scale(eProgress, eProgress);
|
|
3858
|
+
|
|
3859
|
+
const rays = 12;
|
|
3860
|
+
for (let ry = 0; ry < rays; ry++) {
|
|
3861
|
+
const ang = (ry * Math.PI * 2) / rays + t * 0.4;
|
|
3862
|
+
const len = 140 + Math.sin(t * 6 + ry) * 25;
|
|
3863
|
+
ctx.strokeStyle = ry % 2 === 0 ? '#fef08a' : '#ffffff';
|
|
3864
|
+
ctx.lineWidth = ry % 2 === 0 ? 3 : 1.5;
|
|
3865
|
+
ctx.beginPath();
|
|
3866
|
+
ctx.moveTo(Math.cos(ang) * 50, Math.sin(ang) * 50);
|
|
3867
|
+
ctx.lineTo(Math.cos(ang) * len, Math.sin(ang) * len);
|
|
3868
|
+
ctx.stroke();
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
sCircle(0, 0, 18, 650, '#ffffff', 3, '#f59e0b');
|
|
3872
|
+
sCircle(0, 0, 8, 651, '#ffffff', 2, '#ffffff');
|
|
3873
|
+
ctx.restore();
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
// Orbiting Insight Badges
|
|
3877
|
+
if (orbitSpring > 0.05) {
|
|
3878
|
+
const badges = ['PERCEPTION', 'COGNITION', 'INTUITION', 'INSIGHT'];
|
|
3879
|
+
ctx.save();
|
|
3880
|
+
ctx.globalAlpha = Math.min(1, Math.max(0, orbitSpring));
|
|
3881
|
+
|
|
3882
|
+
for (let bg = 0; bg < badges.length; bg++) {
|
|
3883
|
+
const bgAngle = (bg * Math.PI * 2) / badges.length + t * 0.35;
|
|
3884
|
+
const bgX = Math.cos(bgAngle) * 310;
|
|
3885
|
+
const bgY = Math.sin(bgAngle) * 190;
|
|
3886
|
+
|
|
3887
|
+
ctx.fillStyle = CARD_BG;
|
|
3888
|
+
ctx.strokeStyle = CARD_BORDER;
|
|
3889
|
+
ctx.lineWidth = 1.5;
|
|
3890
|
+
ctx.beginPath();
|
|
3891
|
+
ctx.roundRect(bgX - 65, bgY - 18, 130, 36, 18);
|
|
3892
|
+
ctx.fill();
|
|
3893
|
+
ctx.stroke();
|
|
3894
|
+
|
|
3895
|
+
ctx.font = 'bold 13px ' + FONT_DISPLAY;
|
|
3896
|
+
ctx.fillStyle = TEXT_MAIN;
|
|
3897
|
+
ctx.textAlign = 'center';
|
|
3898
|
+
ctx.fillText(badges[bg], bgX, bgY + 5);
|
|
3899
|
+
|
|
3900
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.12)';
|
|
3901
|
+
ctx.beginPath();
|
|
3902
|
+
ctx.moveTo(0, 0);
|
|
3903
|
+
ctx.lineTo(bgX, bgY);
|
|
3904
|
+
ctx.stroke();
|
|
3905
|
+
}
|
|
3906
|
+
ctx.restore();
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
ctx.restore();
|
|
3910
|
+
|
|
3911
|
+
} else if (COMP_THEME === 'magnification_focus') {
|
|
3912
|
+
// Universal Archetype: Optical Loupe, Precision Macro Zoom & Scrutiny Reticle
|
|
3913
|
+
const focusSpring = spring(t, 0.05, 170, 14);
|
|
3914
|
+
const loupeSpring = spring(t, 0.25, 180, 14);
|
|
3915
|
+
|
|
3916
|
+
// Deep research background grid & underlying specimen traces
|
|
3917
|
+
ctx.save();
|
|
3918
|
+
ctx.strokeStyle = IS_LIGHT ? 'rgba(0,0,0,0.06)' : 'rgba(255,255,255,0.08)';
|
|
3919
|
+
ctx.lineWidth = 1;
|
|
3920
|
+
for (let gx = 0; gx < W; gx += 40) {
|
|
3921
|
+
ctx.beginPath(); ctx.moveTo(gx, 0); ctx.lineTo(gx, H); ctx.stroke();
|
|
3922
|
+
}
|
|
3923
|
+
for (let gy = 0; gy < H; gy += 40) {
|
|
3924
|
+
ctx.beginPath(); ctx.moveTo(0, gy); ctx.lineTo(W, gy); ctx.stroke();
|
|
3925
|
+
}
|
|
3926
|
+
|
|
3927
|
+
for (let tr = 0; tr < 16; tr++) {
|
|
3928
|
+
const trX = cx - 350 + tr * 48;
|
|
3929
|
+
const trY = cy - 200 + ((tr * 73) % 400);
|
|
3930
|
+
sLine(trX, trY, trX + 80, trY + 40, 700 + tr, IS_LIGHT ? 'rgba(0,0,0,0.15)' : 'rgba(56, 189, 248, 0.2)', 2);
|
|
3931
|
+
}
|
|
3932
|
+
ctx.restore();
|
|
3933
|
+
|
|
3934
|
+
// Floating Optical Loupe (Lissajous gentle inspection movement)
|
|
3935
|
+
const loupeR = Math.min(W, H) * 0.26;
|
|
3936
|
+
const lissX = cx + Math.sin(t * 1.1) * 70;
|
|
3937
|
+
const lissY = cy + Math.cos(t * 0.85) * 45;
|
|
3938
|
+
|
|
3939
|
+
ctx.save();
|
|
3940
|
+
const lScale = Math.min(1, Math.max(0, loupeSpring));
|
|
3941
|
+
ctx.translate(lissX, lissY);
|
|
3942
|
+
ctx.scale(lScale, lScale);
|
|
3943
|
+
|
|
3944
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.45)';
|
|
3945
|
+
ctx.beginPath();
|
|
3946
|
+
ctx.arc(15, 20, loupeR, 0, Math.PI * 2);
|
|
3947
|
+
ctx.fill();
|
|
3948
|
+
|
|
3949
|
+
// Inside Magnified Specimen (Clipped Circle)
|
|
3950
|
+
ctx.save();
|
|
3951
|
+
ctx.beginPath();
|
|
3952
|
+
ctx.arc(0, 0, loupeR - 8, 0, Math.PI * 2);
|
|
3953
|
+
ctx.clip();
|
|
3954
|
+
|
|
3955
|
+
ctx.fillStyle = IS_LIGHT ? '#ffffff' : '#070f1e';
|
|
3956
|
+
ctx.fillRect(-loupeR, -loupeR, loupeR * 2, loupeR * 2);
|
|
3957
|
+
|
|
3958
|
+
ctx.save();
|
|
3959
|
+
ctx.scale(2.4, 2.4);
|
|
3960
|
+
for (let m = -8; m <= 8; m++) {
|
|
3961
|
+
sLine(m * 22, -loupeR, m * 22, loupeR, 720 + m, 'rgba(56, 189, 248, 0.3)', 1.5);
|
|
3962
|
+
sLine(-loupeR, m * 22, loupeR, m * 22, 740 + m, 'rgba(56, 189, 248, 0.3)', 1.5);
|
|
3963
|
+
}
|
|
3964
|
+
sCircle(0, 0, 36, 760, '#f59e0b', 3, 'rgba(245, 158, 11, 0.2)');
|
|
3965
|
+
sCircle(0, 0, 16, 761, '#ef4444', 2.5, '#ef4444');
|
|
3966
|
+
for (let br = 0; br < 6; br++) {
|
|
3967
|
+
const brA = (br * Math.PI * 2) / 6 + t * 0.5;
|
|
3968
|
+
sLine(0, 0, Math.cos(brA) * 55, Math.sin(brA) * 55, 770 + br, ACCENT, 2.5);
|
|
3969
|
+
sCircle(Math.cos(brA) * 55, Math.sin(brA) * 55, 5, 780 + br, '#ffffff', 1.5, ACCENT);
|
|
3970
|
+
}
|
|
3971
|
+
ctx.restore();
|
|
3972
|
+
|
|
3973
|
+
// Precision Crosshairs & Calibrated Reticle
|
|
3974
|
+
ctx.strokeStyle = '#38bdf8';
|
|
3975
|
+
ctx.lineWidth = 1.5;
|
|
3976
|
+
ctx.beginPath();
|
|
3977
|
+
ctx.moveTo(-loupeR + 15, 0); ctx.lineTo(-25, 0);
|
|
3978
|
+
ctx.moveTo(25, 0); ctx.lineTo(loupeR - 15, 0);
|
|
3979
|
+
ctx.moveTo(0, -loupeR + 15); ctx.lineTo(0, -25);
|
|
3980
|
+
ctx.moveTo(0, 25); ctx.lineTo(0, loupeR - 15);
|
|
3981
|
+
ctx.stroke();
|
|
3982
|
+
|
|
3983
|
+
for (let deg = 0; deg < 36; deg++) {
|
|
3984
|
+
const dRad = (deg * Math.PI * 2) / 36;
|
|
3985
|
+
const isMajor = deg % 9 === 0;
|
|
3986
|
+
const tLen = isMajor ? 16 : 8;
|
|
3987
|
+
const rOut = loupeR - 12;
|
|
3988
|
+
const rIn = rOut - tLen;
|
|
3989
|
+
ctx.strokeStyle = isMajor ? ACCENT : 'rgba(255,255,255,0.4)';
|
|
3990
|
+
ctx.lineWidth = isMajor ? 2 : 1;
|
|
3991
|
+
ctx.beginPath();
|
|
3992
|
+
ctx.moveTo(Math.cos(dRad) * rIn, Math.sin(dRad) * rIn);
|
|
3993
|
+
ctx.lineTo(Math.cos(dRad) * rOut, Math.sin(dRad) * rOut);
|
|
3994
|
+
ctx.stroke();
|
|
3995
|
+
}
|
|
3996
|
+
ctx.restore(); // End clipping
|
|
3997
|
+
|
|
3998
|
+
// Bezel
|
|
3999
|
+
ctx.strokeStyle = '#d4af37';
|
|
4000
|
+
ctx.lineWidth = 14;
|
|
4001
|
+
ctx.beginPath();
|
|
4002
|
+
ctx.arc(0, 0, loupeR, 0, Math.PI * 2);
|
|
4003
|
+
ctx.stroke();
|
|
4004
|
+
|
|
4005
|
+
ctx.strokeStyle = '#fef08a';
|
|
4006
|
+
ctx.lineWidth = 2.5;
|
|
4007
|
+
ctx.beginPath();
|
|
4008
|
+
ctx.arc(0, 0, loupeR - 6, 0, Math.PI * 2);
|
|
4009
|
+
ctx.stroke();
|
|
4010
|
+
|
|
4011
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
|
|
4012
|
+
ctx.lineWidth = 4;
|
|
4013
|
+
ctx.beginPath();
|
|
4014
|
+
ctx.arc(0, 0, loupeR - 24, -1.8, -0.6);
|
|
4015
|
+
ctx.stroke();
|
|
4016
|
+
|
|
4017
|
+
ctx.fillStyle = CARD_BG;
|
|
4018
|
+
ctx.strokeStyle = ACCENT;
|
|
4019
|
+
ctx.lineWidth = 1.5;
|
|
4020
|
+
ctx.beginPath();
|
|
4021
|
+
ctx.roundRect(loupeR - 40, -loupeR - 10, 160, 48, 10);
|
|
4022
|
+
ctx.fill();
|
|
4023
|
+
ctx.stroke();
|
|
4024
|
+
|
|
4025
|
+
ctx.font = 'bold 12px monospace';
|
|
4026
|
+
ctx.fillStyle = ACCENT;
|
|
4027
|
+
ctx.fillText('MAGNIFY: 250%', loupeR - 26, -loupeR + 12);
|
|
4028
|
+
ctx.fillStyle = TEXT_MAIN;
|
|
4029
|
+
ctx.fillText('POS: ' + Math.round(lissX) + ', ' + Math.round(lissY), loupeR - 26, -loupeR + 28);
|
|
4030
|
+
|
|
4031
|
+
ctx.restore();
|
|
4032
|
+
|
|
4033
|
+
} else if (COMP_THEME === 'waveform') {
|
|
2966
4034
|
// Kinetic Frequency Equalizer Waveform
|
|
2967
4035
|
const bars = 36;
|
|
2968
4036
|
const barW = (W * 0.7) / bars;
|
|
@@ -2979,6 +4047,153 @@ function drawMainScene(t) {
|
|
|
2979
4047
|
ctx.fillStyle = barGrad;
|
|
2980
4048
|
ctx.beginPath(); ctx.roundRect(bx, by, barW - 6, hVal, 4); ctx.fill();
|
|
2981
4049
|
}
|
|
4050
|
+
|
|
4051
|
+
} else {
|
|
4052
|
+
// Universal Semantic Default & Explainer Presentation (Whiteboard Masterclass & Concept Pillars)
|
|
4053
|
+
const boardSpring = spring(t, 0.05, 170, 14);
|
|
4054
|
+
const p1Spring = spring(t, 0.18, 190, 15);
|
|
4055
|
+
const p2Spring = spring(t, 0.32, 190, 15);
|
|
4056
|
+
const p3Spring = spring(t, 0.46, 190, 15);
|
|
4057
|
+
|
|
4058
|
+
const boardW = Math.min(W * 0.88, 1100);
|
|
4059
|
+
const boardH = Math.min(H * 0.68, 620);
|
|
4060
|
+
const boardX = cx - boardW / 2;
|
|
4061
|
+
const boardY = cy - boardH / 2;
|
|
4062
|
+
|
|
4063
|
+
ctx.save();
|
|
4064
|
+
const bScale = Math.min(1, Math.max(0, boardSpring));
|
|
4065
|
+
ctx.translate(cx, cy);
|
|
4066
|
+
ctx.scale(bScale, bScale);
|
|
4067
|
+
ctx.translate(-cx, -cy);
|
|
4068
|
+
|
|
4069
|
+
// Architectural Frosted Glass Presentation Board
|
|
4070
|
+
ctx.fillStyle = CARD_BG;
|
|
4071
|
+
ctx.strokeStyle = CARD_BORDER;
|
|
4072
|
+
ctx.lineWidth = 2.5;
|
|
4073
|
+
ctx.beginPath();
|
|
4074
|
+
ctx.roundRect(boardX, boardY, boardW, boardH, 24);
|
|
4075
|
+
ctx.fill();
|
|
4076
|
+
ctx.stroke();
|
|
4077
|
+
|
|
4078
|
+
// Board Header Accent Bar
|
|
4079
|
+
ctx.fillStyle = ACCENT;
|
|
4080
|
+
ctx.beginPath();
|
|
4081
|
+
ctx.roundRect(boardX + 32, boardY + 28, 6, 32, 3);
|
|
4082
|
+
ctx.fill();
|
|
4083
|
+
|
|
4084
|
+
ctx.font = '800 22px ' + FONT_DISPLAY;
|
|
4085
|
+
ctx.fillStyle = TEXT_MAIN;
|
|
4086
|
+
ctx.textAlign = 'left';
|
|
4087
|
+
ctx.fillText('CORE PRINCIPLES & ARCHITECTURE', boardX + 48, boardY + 50);
|
|
4088
|
+
|
|
4089
|
+
// Live Status Badge
|
|
4090
|
+
ctx.fillStyle = 'rgba(16, 185, 129, 0.15)';
|
|
4091
|
+
ctx.beginPath();
|
|
4092
|
+
ctx.roundRect(boardX + boardW - 160, boardY + 28, 128, 30, 15);
|
|
4093
|
+
ctx.fill();
|
|
4094
|
+
ctx.fillStyle = '#10b981';
|
|
4095
|
+
ctx.font = 'bold 13px ' + FONT_DISPLAY;
|
|
4096
|
+
ctx.fillText('● SYSTEM ACTIVE', boardX + boardW - 146, boardY + 48);
|
|
4097
|
+
|
|
4098
|
+
// 3 Structured Concept Pillars
|
|
4099
|
+
const pillarData = [
|
|
4100
|
+
{ num: '01', title: 'FOUNDATION', desc: 'Core axioms & structure', icon: 'diamond', spr: p1Spring },
|
|
4101
|
+
{ num: '02', title: 'DYNAMICS', desc: 'Kinetic transformation', icon: 'gear', spr: p2Spring },
|
|
4102
|
+
{ num: '03', title: 'IMPACT', desc: 'Synthesized outcome', icon: 'star', spr: p3Spring },
|
|
4103
|
+
];
|
|
4104
|
+
|
|
4105
|
+
const pillarW = (boardW - 128) / 3;
|
|
4106
|
+
const pillarH = boardH - 140;
|
|
4107
|
+
const pillarY = boardY + 95;
|
|
4108
|
+
|
|
4109
|
+
for (let p = 0; p < 3; p++) {
|
|
4110
|
+
const pd = pillarData[p];
|
|
4111
|
+
const pilX = boardX + 32 + p * (pillarW + 32);
|
|
4112
|
+
const pAlpha = Math.min(1, Math.max(0, pd.spr));
|
|
4113
|
+
|
|
4114
|
+
ctx.save();
|
|
4115
|
+
ctx.globalAlpha = pAlpha;
|
|
4116
|
+
ctx.translate(pilX, pillarY + (1 - pd.spr) * 40);
|
|
4117
|
+
|
|
4118
|
+
// Pillar Card
|
|
4119
|
+
ctx.fillStyle = IS_LIGHT ? 'rgba(255,255,255,0.7)' : 'rgba(255,255,255,0.04)';
|
|
4120
|
+
ctx.strokeStyle = p === 1 ? ACCENT : 'rgba(255,255,255,0.1)';
|
|
4121
|
+
ctx.lineWidth = p === 1 ? 2.5 : 1.5;
|
|
4122
|
+
ctx.beginPath();
|
|
4123
|
+
ctx.roundRect(0, 0, pillarW, pillarH, 16);
|
|
4124
|
+
ctx.fill();
|
|
4125
|
+
ctx.stroke();
|
|
4126
|
+
|
|
4127
|
+
// Number Tag
|
|
4128
|
+
ctx.font = '900 48px ' + FONT_DISPLAY;
|
|
4129
|
+
ctx.fillStyle = p === 1 ? ACCENT : (IS_LIGHT ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.12)');
|
|
4130
|
+
ctx.fillText(pd.num, 24, 60);
|
|
4131
|
+
|
|
4132
|
+
// Pillar Title
|
|
4133
|
+
ctx.font = '700 20px ' + FONT_DISPLAY;
|
|
4134
|
+
ctx.fillStyle = TEXT_MAIN;
|
|
4135
|
+
ctx.fillText(pd.title, 24, 110);
|
|
4136
|
+
|
|
4137
|
+
// Pillar Description
|
|
4138
|
+
ctx.font = '500 15px ' + FONT_BODY;
|
|
4139
|
+
ctx.fillStyle = TEXT_MUTED;
|
|
4140
|
+
ctx.fillText(pd.desc, 24, 138);
|
|
4141
|
+
|
|
4142
|
+
// Dynamic Animated Diagram Graphic inside Pillar
|
|
4143
|
+
const gfxY = pillarH * 0.65;
|
|
4144
|
+
const gfxX = pillarW / 2;
|
|
4145
|
+
|
|
4146
|
+
if (pd.icon === 'diamond') {
|
|
4147
|
+
ctx.save();
|
|
4148
|
+
ctx.translate(gfxX, gfxY);
|
|
4149
|
+
ctx.rotate(t * 1.2);
|
|
4150
|
+
ctx.strokeStyle = ACCENT;
|
|
4151
|
+
ctx.lineWidth = 3;
|
|
4152
|
+
ctx.strokeRect(-28, -28, 56, 56);
|
|
4153
|
+
sCircle(0, 0, 10, 800, '#ffffff', 2, ACCENT);
|
|
4154
|
+
ctx.restore();
|
|
4155
|
+
} else if (pd.icon === 'gear') {
|
|
4156
|
+
sCircle(gfxX, gfxY, 36, 810, ACCENT, 3);
|
|
4157
|
+
const pulseR = 20 + Math.sin(t * 5) * 8;
|
|
4158
|
+
sCircle(gfxX, gfxY, pulseR, 811, '#ffffff', 2, '#38bdf8');
|
|
4159
|
+
} else {
|
|
4160
|
+
for (let st = 0; st < 4; st++) {
|
|
4161
|
+
const ang = (st * Math.PI) / 2 + t * 0.8;
|
|
4162
|
+
sLine(gfxX, gfxY, gfxX + Math.cos(ang) * 36, gfxY + Math.sin(ang) * 36, 820 + st, '#f59e0b', 3);
|
|
4163
|
+
}
|
|
4164
|
+
sCircle(gfxX, gfxY, 8, 825, '#ffffff', 2, '#f59e0b');
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4167
|
+
// Feature Check items
|
|
4168
|
+
for (let ch = 0; ch < 3; ch++) {
|
|
4169
|
+
const itemY = pillarH - 85 + ch * 24;
|
|
4170
|
+
sCircle(28, itemY, 4, 830 + ch, ACCENT, 2, ACCENT);
|
|
4171
|
+
ctx.fillStyle = TEXT_MAIN;
|
|
4172
|
+
ctx.font = '500 13px ' + FONT_BODY;
|
|
4173
|
+
ctx.fillText('Verification Key 0' + (ch + 1), 42, itemY + 4);
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
ctx.restore();
|
|
4177
|
+
}
|
|
4178
|
+
|
|
4179
|
+
// Inter-Pillar Energy Connectors
|
|
4180
|
+
ctx.strokeStyle = ACCENT;
|
|
4181
|
+
ctx.lineWidth = 2.5;
|
|
4182
|
+
for (let c = 0; c < 2; c++) {
|
|
4183
|
+
const connX1 = boardX + 32 + (c + 1) * pillarW + c * 32 - 16;
|
|
4184
|
+
const connX2 = connX1 + 32;
|
|
4185
|
+
const connY = pillarY + 80;
|
|
4186
|
+
sLine(connX1, connY, connX2, connY, 850 + c, ACCENT, 3);
|
|
4187
|
+
ctx.fillStyle = ACCENT;
|
|
4188
|
+
ctx.beginPath();
|
|
4189
|
+
ctx.moveTo(connX2 + 4, connY);
|
|
4190
|
+
ctx.lineTo(connX2 - 4, connY - 5);
|
|
4191
|
+
ctx.lineTo(connX2 - 4, connY + 5);
|
|
4192
|
+
ctx.closePath();
|
|
4193
|
+
ctx.fill();
|
|
4194
|
+
}
|
|
4195
|
+
|
|
4196
|
+
ctx.restore();
|
|
2982
4197
|
}
|
|
2983
4198
|
|
|
2984
4199
|
ctx.restore();
|