@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.
@@ -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. General Human Character
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|versus|vs|hype|adrenaline|power|velocity|fast)(?:[^\w]|$)/.test(text)
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|node|nodes|ai|model|neural|pipeline|architecture|server|system|mesh|connect|synapse|deep learning)(?:[^\w]|$)/.test(text)
408
- || /നെറ്റ്‌വർക്ക്|സിനാപ്സ്|ശൃംഖല|नेटवर्क/.test(text)) {
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
- // Fallbacks matched to aesthetic
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 'isometric_geometry';
486
+ if (aesthetic === 'minimal_luxury') return 'explainer_presentation';
426
487
  if (aesthetic === 'hype_sports') return 'speed_action';
427
- return 'waveform';
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();