@amg555/frameforge-mcp 2.4.8 → 2.4.10

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.
@@ -279,15 +279,147 @@ function detectAesthetic({
279
279
  return 'cyber_tech';
280
280
  }
281
281
 
282
+ // ── Anchor Requested Detector ───────────────────────────────────────────────
283
+ /** Returns true ONLY if caller explicitly asks for an on-screen anchor, presenter, host, avatar, or talking head in prompt. */
284
+ function detectAnchorRequested(text = '') {
285
+ if (!text || typeof text !== 'string') return false;
286
+ return /(?:with|show|display|include|enable|add)\s+(?:an?\s+)?(?:anchor|presenter|host|avatar|spokesperson|talking\s+head|narrator\s+character)|(?:anchor|presenter|host)\s*[:=]\s*(?:true|yes|1|on|always)/i.test(text);
287
+ }
288
+
282
289
  // ── Component Theme Detection ──────────────────────────────────────────────────
283
290
  /**
284
291
  * Detect the graphic entity to render on the full canvas for a given scene.
285
292
  */
286
- function detectComponentTheme({ visualDescription = '', visual_description = '', prompt = '', caption = '', narration = '', topic = '', imagePath = null, aesthetic = 'cyber_tech' } = {}) {
293
+ function detectComponentTheme({
294
+ visualDescription = '',
295
+ visual_description = '',
296
+ prompt = '',
297
+ caption = '',
298
+ narration = '',
299
+ topic = '',
300
+ imagePath = null,
301
+ aesthetic = 'cyber_tech',
302
+ anchorWanted = true,
303
+ } = {}) {
287
304
  if (imagePath) return 'image';
288
305
  const visDesc = visualDescription || visual_description || prompt || '';
289
306
  const text = `${visDesc} ${caption} ${narration} ${topic}`.toLowerCase();
290
307
 
308
+ // If anchor is NOT requested, suppress all humanoid doodle anchor characters
309
+ // and route to non-anchor procedural visual archetypes
310
+ if (!anchorWanted) {
311
+ // 1. Versus / Comparison / Split Screen
312
+ 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|split-screen|split screen|modern vs ancient|modern vs prehistoric|then and now|evolution of human)(?:[^\w]|$)/i.test(text)
313
+ || /വ്യത്യാസം|താരതമ്യം|എതിര്|പോരാട്ടം|തമ്മിൽ|തുലനം|താരതമ്യപ്പെടുത്തൽ/.test(text)) {
314
+ return 'versus_comparison';
315
+ }
316
+
317
+ // 2. Magnification / Microscope / Fiber / Macro Inspection / Lice
318
+ if (/(?:^|[^\w])(magnify|magnification|zoom|microscopic|microscope|macro|close up|closeup|inspection|loupe|examine|scrutiny|detail|focal point|focus in|specimen|fiber|weave|parasite|lice|louse)(?:[^\w]|$)/i.test(text)
319
+ || /സൂക്ഷ്മ|പരിശോധന|വലുതാക്കി കാണുക|സൂക്ഷ്മമായി|നിരീക്ഷണം|മൈക്രോസ്കോപ്പ്|പേൻ/.test(text)) {
320
+ return 'magnification_focus';
321
+ }
322
+
323
+ // 3. Cognitive Insight / Psychology / Brain / Eureka / Thought / Future Planning / Shame Reflection
324
+ if (/(?:^|[^\w])(mind|psychology|cognitive|brain|insight|revelation|eureka|intuition|subconscious|thinking|thought|contemplation|intelligence|mental|awareness|neuroscience|concept|idea|epiphany|planning for tomorrow|future planning|foresight|shame|blush|embarrass|mirror|reflection|theory of mind|self-aware|identity)(?:[^\w]|$)/i.test(text)
325
+ || /മനസ്സ്|ബുദ്ധി|ചിന്ത|മനഃശാസ്ത്രം|ജ്ഞാനം|മസ്തിഷ്കം|ബോധം|കണ്ണാടി|പ്രതിഫലനം|നാണം/.test(text)) {
326
+ return 'cognitive_insight';
327
+ }
328
+
329
+ // 4. Mechanism / Physics / Machinery / Engine / Needle / Stitching / Cold
330
+ if (/(?:^|[^\w])(engine|gear|gears|motor|physics|aerodynamics|thrust|turbine|propulsion|piston|hydraulic|kinetic|machine|machinery|circuit|mechanics|thermodynamics|levers|pulley|needle|bone needle|sew|sewing|stitch|stitching|shiver|shivering|cold|freeze|freezing|ice|winter|protection flaw)(?:[^\w]|$)/i.test(text)
331
+ || /യന്ത്രം|എഞ്ചിൻ|ഭൗതികശാസ്ത്രം|പ്രവർത്തനം|ചലനം|മെക്കാനിസം|ഭൗതികം|സൂചി|തുന്ന|വിറയ്ക്കുക|തണുപ്പ്/.test(text)) {
332
+ return 'mechanism_physics';
333
+ }
334
+
335
+ // 5. Process / Pipeline / Stages / Assembly / Tribe / Community / Hierarchy / Royal Status
336
+ 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|tribe|tribal|clan|stranger|meeting strangers|social language|status in society|hierarchy|royal robe|royal robes|crown|sceptre|ornaments|status and power|wealth authority)(?:[^\w]|$)/i.test(text)
337
+ || /ഘട്ടങ്ങൾ|പ്രക്രിയ|ഘട്ടം|ഉൽപ്പാദനം|വഴികൾ|പ്രവർത്തനരീതി|കൂട്ടമായി|സമൂഹം|ഗോത്രം|സാമൂഹിക സ്ഥാനം/.test(text)) {
338
+ return 'process_pipeline';
339
+ }
340
+
341
+ // 6. Radar Globe / World Map / Migration
342
+ if (/(?:^|[^\w])(globe|map|world|geography|radar|international|continent|territory|country|border|location|migration|migrating|migrate|africa highlighted|colder regions|out of africa)(?:[^\w]|$)/i.test(text)
343
+ || /ഭൂപടം|ലോകം|ഭൂഖണ്ഡം|ഭൂപട|നക്ശാ|ക്ലോബ്|കുടിയേറ്റം|മൈഗ്രേഷൻ/.test(text)) {
344
+ return 'radar_globe';
345
+ }
346
+
347
+ // 7. Celestial / Orbit
348
+ if (/(?:^|[^\w])(orbit|satellite|satellites|gps|planet|earth|mars|moon|galaxy|universe|solar|astronomy|gravity|space|telescope|interstellar)(?:[^\w]|$)/i.test(text)
349
+ || /ബഹിരാകാശ|ഗ്രഹം|ഉപഗ്രഹ|ഭൂമി|നക്ഷത്ര|പ്രപഞ്ച|अंतरिक्ष|ग्रह|तारा|വിൺവെളി/.test(text)) {
350
+ return 'celestial_orbit';
351
+ }
352
+
353
+ // 8. DNA / Biology
354
+ if (/(?:^|[^\w])(dna|helix|gene|cell|molecule|biology|organism|protein|virus|bacteria|medical|genetic|chromosome)(?:[^\w]|$)/i.test(text)
355
+ || /ഡിഎൻഎ|കോശം|തന്മാത്ര|ജീവശാസ്ത്ര|डीएनए|कोशिका/.test(text)) {
356
+ return 'dna_helix';
357
+ }
358
+
359
+ // 9. Code / Programming
360
+ if (/(?:^|[^\w])(code|function|syntax|api|javascript|python|typescript|json|sql|query|endpoint|algorithm|struct|cli|terminal|bash|shell|npm|git|compiler|linux|rust)(?:[^\w]|$)/.test(text)
361
+ || /കോഡിംഗ്|സോഫ്റ്റ്‌വെയർ|പ്രോഗ്രാമിംഗ്|कोड|सॉफ्टवेयर/.test(text)) {
362
+ return 'code';
363
+ }
364
+
365
+ // 10. Chart / Finance
366
+ if (/(?:^|[^\w])(chart|data|analytics|growth|graph|statistic|bars|volume|distribution|stock|crypto|bitcoin|market|invest|trading|inflation|economy|gdp|finance|gain|loss|bull|bear|candlestick|price)(?:[^\w]|$)/.test(text)
367
+ || /നിക്ഷേപ|ഓഹരി|വിപണി|പണം|ചാർട്ട്|शेयर|बाज़ार/.test(text)) {
368
+ return 'chart';
369
+ }
370
+
371
+ // 11. Nodes / Neural Net
372
+ if (/(?:^|[^\w])(network|nodes|neural network|neural net|distributed system|server mesh|deep learning)(?:[^\w]|$)/.test(text)
373
+ || /നെറ്റ്‌വർക്ക്|ശൃംഖല|नेटवर्क/.test(text)) {
374
+ return 'nodes';
375
+ }
376
+
377
+ // 12. 3D Isometric Geometry
378
+ if (/(?:^|[^\w])(cube|geometry|3d|wireframe|isometric|matrix|crystal|structure|polyhedron|dimension|architect|blueprint|math)(?:[^\w]|$)/.test(text)
379
+ || /ജ്യാമിതി|രൂപം|ത്രിമാന|संरचना/.test(text)) {
380
+ return 'isometric_geometry';
381
+ }
382
+
383
+ // 13. Speed Action / Hunter
384
+ if (/(?:^|[^\w])(speed|sprint|velocity|fast|run|race|motion|hunter|hunting|spear|savannah|blazing sun|mosquito)(?:[^\w]|$)/.test(text)
385
+ || /വേഗത|ഓട്ടം|കുന്തം|വേഗം/.test(text)) {
386
+ return 'speed_action';
387
+ }
388
+
389
+ // 14. Quote / Metric
390
+ if (/(?:^|[^\w])(quote|saying|proverb|statement|speech|wisdom)(?:[^\w]|$)/.test(text)) return 'quote';
391
+ if (/(?:^|[^\w])(metric|kpi|target|counter|percentage|ratio|score)(?:[^\w]|$)/.test(text)) return 'metric';
392
+
393
+ // 15. Timeline (strictly explicit roadmap/milestone chart requests only)
394
+ if (/(?:^|[^\w])(timeline|roadmap|milestone chart|milestone roadmap)(?:[^\w]|$)/i.test(text)
395
+ || /ടൈംലൈൻ|റോഡ്മാപ്പ്/.test(text)) {
396
+ return 'timeline';
397
+ }
398
+
399
+ // 16. Explainer Presentation (General visual lecture, whiteboard, tutorial, clothing topic, subscribe)
400
+ if (/(?:^|[^\w])(explain|explainer|presentation|lecture|teach|tutorial|whiteboard|blackboard|breakdown|guide|overview|theory|principles|fundamentals|introduction|summary|lesson|masterclass|subscribe|notification bell|outro|dress|dressing|wearing clothes|shirt|wardrobe|closet|hanger|outfit|wedding dress)(?:[^\w]|$)/i.test(text)
401
+ || /വിവരണം|പഠിപ്പിക്കുക|ക്ലാസ്|പാഠം|തത്വം|വിവരിക്കുക|സബ്സ്ക്രൈബ്|വസ്ത്രധാരണം/.test(text)) {
402
+ return 'explainer_presentation';
403
+ }
404
+
405
+ // 17. Waveform
406
+ if (/(?:^|[^\w])(waveform|audio|sound|equalizer|frequency|acoustics|hz|decibel|music spectrum|voice waveform)(?:[^\w]|$)/i.test(text)
407
+ || /ശബ്ദതരംഗം|തരംഗം|സംഗീതം|ധ്വനി/.test(text)) {
408
+ return 'waveform';
409
+ }
410
+
411
+ // Clean aesthetic fallbacks — strictly non-anchor
412
+ if (aesthetic === 'cosmic_space') return 'celestial_orbit';
413
+ if (aesthetic === 'cinematic_documentary') return 'explainer_presentation';
414
+ if (aesthetic === 'finance_executive') return 'chart';
415
+ if (aesthetic === 'nature_organic') return 'dna_helix';
416
+ if (aesthetic === 'breaking_news') return 'radar_globe';
417
+ if (aesthetic === 'cyber_tech') return 'nodes';
418
+ if (aesthetic === 'minimal_luxury') return 'explainer_presentation';
419
+ if (aesthetic === 'hype_sports') return 'speed_action';
420
+ return 'explainer_presentation';
421
+ }
422
+
291
423
  // ── Doodled Character Actions (Human Stories, History, Evolution, Daily Life) ──
292
424
  // 1. Microscope / Lice Theory / Parasite / Genetics (Scene 7)
293
425
  if (/(?:^|[^\w])(lice|louse|microscope|fiber|weave|parasite|lice evolution|genetic biology proof)(?:[^\w]|$)/i.test(text)
@@ -326,7 +458,7 @@ function detectComponentTheme({ visualDescription = '', visual_description = '',
326
458
  }
327
459
 
328
460
  // 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)
461
+ 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
462
  || /കണ്ണാടി|പ്രതിഫലനം|മറ്റുള്ളവർ എന്നെ|തിയറി ഓഫ് മൈൻഡ്|സ്വന്തം ശരീരം മറച്ചു|വ്യക്തിത്വം|ശ്രദ്ധിക്കുന്നുണ്ട്|യഥാർത്ഥ രഹസ്യം|നാണം മറയ്ക്കാൻ/.test(text)) {
331
463
  return 'doodle_shame_reflection';
332
464
  }
@@ -392,7 +524,7 @@ function detectComponentTheme({ visualDescription = '', visual_description = '',
392
524
  }
393
525
 
394
526
  // High Velocity Speed Action
395
- if (/(?:^|[^\w])(sports|workout|gym|athlete|speed|streaks|race|championship|extreme|versus|vs|hype|adrenaline|power|velocity|fast)(?:[^\w]|$)/.test(text)
527
+ if (/(?:^|[^\w])(sports|workout|gym|athlete|speed|streaks|race|championship|extreme|hype|adrenaline|velocity|sprint|fast motion)(?:[^\w]|$)/.test(text)
396
528
  || /കായിക|മത്സര|വേഗത|വ്യായാമം|खेल|दौड़/.test(text)) {
397
529
  return 'speed_action';
398
530
  }
@@ -421,9 +553,9 @@ function detectComponentTheme({ visualDescription = '', visual_description = '',
421
553
  return 'chart';
422
554
  }
423
555
 
424
- // Neural Network Nodes
425
- if (/(?:^|[^\w])(network|node|nodes|ai|model|neural|pipeline|architecture|server|system|mesh|connect|synapse|deep learning)(?:[^\w]|$)/.test(text)
426
- || /നെറ്റ്‌വർക്ക്|സിനാപ്സ്|ശൃംഖല|नेटवर्क/.test(text)) {
556
+ // Neural Network Nodes / AI Topology
557
+ if (/(?:^|[^\w])(network|nodes|neural network|neural net|distributed system|server mesh|deep learning)(?:[^\w]|$)/.test(text)
558
+ || /നെറ്റ്‌വർക്ക്|ശൃംഖല|नेटवर्क/.test(text)) {
427
559
  return 'nodes';
428
560
  }
429
561
 
@@ -433,16 +565,59 @@ function detectComponentTheme({ visualDescription = '', visual_description = '',
433
565
  return 'isometric_geometry';
434
566
  }
435
567
 
436
- // Fallbacks matched to aesthetic
568
+ // ── Universal Semantic Visual Archetypes (General Content & Education) ──
569
+ // 1. Versus / Comparison / Split Screen
570
+ 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)
571
+ || /വ്യത്യാസം|താരതമ്യം|എതിര്|പോരാട്ടം|തമ്മിൽ|തുലനം|താരതമ്യപ്പെടുത്തൽ/.test(text)) {
572
+ return 'versus_comparison';
573
+ }
574
+
575
+ // 2. Mechanism / Physics / Machinery / Engine / Aerodynamics
576
+ 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)
577
+ || /യന്ത്രം|എഞ്ചിൻ|ഭൗതികശാസ്ത്രം|പ്രവർത്തനം|ചലനം|മെക്കാനിസം|ഭൗതികം/.test(text)) {
578
+ return 'mechanism_physics';
579
+ }
580
+
581
+ // 3. Process / Pipeline / Workflow / Stages / Assembly / Lifecycle
582
+ 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)
583
+ || /ഘട്ടങ്ങൾ|പ്രക്രിയ|ഘട്ടം|ഉൽപ്പാദനം|വഴികൾ|പ്രവർത്തനരീതി/.test(text)) {
584
+ return 'process_pipeline';
585
+ }
586
+
587
+ // 4. Cognitive Insight / Psychology / Brain / Eureka / Revelation / Mind
588
+ 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)
589
+ || /മനസ്സ്|ബുദ്ധി|ചിന്ത|മനഃശാസ്ത്രം|ജ്ഞാനം|മസ്തിഷ്കം|ബോധം/.test(text)) {
590
+ return 'cognitive_insight';
591
+ }
592
+
593
+ // 5. Magnification / Loupe / Macro Zoom / Scrutiny / Detailed Inspection
594
+ if (/(?:^|[^\w])(magnify|magnification|zoom|microscopic|macro|close up|closeup|inspection|loupe|examine|scrutiny|detail|focal point|focus in|specimen)(?:[^\w]|$)/i.test(text)
595
+ || /സൂക്ഷ്മ|പരിശോധന|വലുതാക്കി കാണുക|സൂക്ഷ്മമായി|നിരീക്ഷണം/.test(text)) {
596
+ return 'magnification_focus';
597
+ }
598
+
599
+ // 6. Explainer / Presentation / Tutorial / Masterclass Whiteboard / Breakdown
600
+ if (/(?:^|[^\w])(explain|explainer|presentation|lecture|teach|tutorial|whiteboard|blackboard|breakdown|guide|overview|theory|principles|fundamentals|introduction|summary|lesson|masterclass)(?:[^\w]|$)/i.test(text)
601
+ || /വിവരണം|പഠിപ്പിക്കുക|ക്ലാസ്|പാഠം|തത്വം|വിവരിക്കുക/.test(text)) {
602
+ return 'explainer_presentation';
603
+ }
604
+
605
+ // 7. Waveform / Audio / Sound / Acoustics / Equalizer
606
+ if (/(?:^|[^\w])(waveform|audio|sound|equalizer|frequency|acoustics|hz|decibel|music spectrum|voice waveform)(?:[^\w]|$)/i.test(text)
607
+ || /ശബ്ദതരംഗം|തരംഗം|സംഗീതം|ധ്വനി/.test(text)) {
608
+ return 'waveform';
609
+ }
610
+
611
+ // Fallbacks matched to aesthetic (rich semantic defaults, never sterile)
437
612
  if (aesthetic === 'cosmic_space') return 'celestial_orbit';
438
613
  if (aesthetic === 'cinematic_documentary') return 'doodle_general_human';
439
614
  if (aesthetic === 'finance_executive') return 'chart';
440
615
  if (aesthetic === 'nature_organic') return 'dna_helix';
441
616
  if (aesthetic === 'breaking_news') return 'radar_globe';
442
617
  if (aesthetic === 'cyber_tech') return 'nodes';
443
- if (aesthetic === 'minimal_luxury') return 'isometric_geometry';
618
+ if (aesthetic === 'minimal_luxury') return 'explainer_presentation';
444
619
  if (aesthetic === 'hype_sports') return 'speed_action';
445
- return 'waveform';
620
+ return 'explainer_presentation';
446
621
  }
447
622
 
448
623
  // ── Harness HTML Wrapper for User / Agent Code ────────────────────────────────
@@ -573,12 +748,18 @@ function generateSceneHtml({
573
748
  height = 1080,
574
749
  sceneIndex = 0,
575
750
  showCaptions = false,
751
+ showAnchor = false,
576
752
  } = {}) {
577
753
  const captionsWanted = Boolean(
578
754
  showCaptions ||
579
755
  detectCaptionsRequested(prompt || topic) ||
580
756
  process.env.FRAMEFORGE_SHOW_CAPTIONS === '1'
581
757
  );
758
+ const anchorWanted = Boolean(
759
+ showAnchor ||
760
+ detectAnchorRequested(prompt || topic) ||
761
+ process.env.FRAMEFORGE_SHOW_ANCHOR === '1'
762
+ );
582
763
  const aestheticKey = detectAesthetic({
583
764
  topic,
584
765
  style,
@@ -599,6 +780,7 @@ function generateSceneHtml({
599
780
  topic,
600
781
  imagePath,
601
782
  aesthetic: aestheticKey,
783
+ anchorWanted,
602
784
  });
603
785
  const clampedDur = Math.max(1, Number(duration) || 3);
604
786
 
@@ -691,6 +873,7 @@ function spring(t, delay = 0, tension = ${aesthetic.springTension}, friction = $
691
873
  }
692
874
 
693
875
  const SHOW_CAPTIONS = ${JSON.stringify(captionsWanted)};
876
+ const SHOW_ANCHOR = ${JSON.stringify(anchorWanted)};
694
877
  const AESTHETIC = ${JSON.stringify(aestheticKey)};
695
878
  const BG_TYPE = ${JSON.stringify(aesthetic.backgroundType)};
696
879
  const COMP_THEME = ${JSON.stringify(compTheme)};
@@ -712,6 +895,8 @@ const IS_LIGHT = ${Boolean(pal.isLight)};
712
895
  const STROKE_COLOR = ${JSON.stringify(pal.isLight ? (pal.stroke || '#1c1917') : '#f8fafc')};
713
896
  const STROKE_ALT = ${JSON.stringify(pal.isLight ? (pal.strokeAlt || '#44403c') : 'rgba(255, 255, 255, 0.7)')};
714
897
  const AMBIENT_LINE = ${JSON.stringify(pal.isLight ? 'rgba(28, 25, 23, 0.22)' : 'rgba(255, 255, 255, 0.25)')};
898
+ const TEXT_MAIN = ${JSON.stringify(pal.isLight ? '#1c1917' : '#f8fafc')};
899
+ const TEXT_MUTED = ${JSON.stringify(pal.isLight ? '#78716c' : '#94a3b8')};
715
900
 
716
901
  let cachedImage = null;
717
902
  if (IMAGE_SRC) {
@@ -961,6 +1146,7 @@ function drawMainScene(t) {
961
1146
  showSpecular = false,
962
1147
  seed = 100,
963
1148
  } = {}) {
1149
+ if (!SHOW_ANCHOR) return;
964
1150
  ctx.save();
965
1151
  ctx.translate(x, y);
966
1152
  ctx.scale(scale, scale);
@@ -3313,7 +3499,679 @@ function drawMainScene(t) {
3313
3499
  ctx.drawImage(cachedImage, cx - imgW / 2, cy - imgH / 2, imgW, imgH);
3314
3500
  ctx.restore();
3315
3501
 
3316
- } else {
3502
+ } else if (COMP_THEME === 'versus_comparison') {
3503
+ // Universal Archetype: Split-Screen Versus / Comparison
3504
+ const splitSpring = spring(t, 0.05, 180, 14);
3505
+ const cardLeftSpring = spring(t, 0.15, 190, 15);
3506
+ const cardRightSpring = spring(t, 0.28, 190, 15);
3507
+ const badgeSpring = spring(t, 0.42, 220, 13);
3508
+
3509
+ // Center divider beam
3510
+ const divAlpha = Math.min(1, Math.max(0, splitSpring));
3511
+ ctx.save();
3512
+ ctx.globalAlpha = divAlpha;
3513
+ const seamGrad = ctx.createLinearGradient(0, cy - H * 0.42, 0, cy + H * 0.42);
3514
+ seamGrad.addColorStop(0, 'rgba(255,255,255,0)');
3515
+ seamGrad.addColorStop(0.5, ACCENT);
3516
+ seamGrad.addColorStop(1, 'rgba(255,255,255,0)');
3517
+ ctx.strokeStyle = seamGrad;
3518
+ ctx.lineWidth = 3;
3519
+ ctx.beginPath();
3520
+ ctx.moveTo(cx, cy - H * 0.42);
3521
+ ctx.lineTo(cx, cy + H * 0.42);
3522
+ ctx.stroke();
3523
+
3524
+ // Seam energy sparks
3525
+ for (let sp = 0; sp < 8; sp++) {
3526
+ const spY = cy - H * 0.35 + ((sp * 95 + t * 240) % (H * 0.7));
3527
+ const spR = 3 + Math.sin(t * 8 + sp) * 2;
3528
+ ctx.fillStyle = ACCENT;
3529
+ ctx.beginPath();
3530
+ ctx.arc(cx, spY, spR, 0, Math.PI * 2);
3531
+ ctx.fill();
3532
+ }
3533
+ ctx.restore();
3534
+
3535
+ // Left Card (Side A)
3536
+ const cardW = Math.min(W * 0.38, 520);
3537
+ const cardH = Math.min(H * 0.62, 540);
3538
+ const leftX = cx - cardW - 35;
3539
+ const cardY = cy - cardH / 2;
3540
+
3541
+ ctx.save();
3542
+ ctx.globalAlpha = Math.min(1, Math.max(0, cardLeftSpring));
3543
+ ctx.translate(leftX + (1 - cardLeftSpring) * -60, cardY);
3544
+ ctx.fillStyle = CARD_BG;
3545
+ ctx.strokeStyle = CARD_BORDER;
3546
+ ctx.lineWidth = 2;
3547
+ ctx.beginPath();
3548
+ ctx.roundRect(0, 0, cardW, cardH, 20);
3549
+ ctx.fill();
3550
+ ctx.stroke();
3551
+
3552
+ // Left Card Header & Accent Pill
3553
+ ctx.fillStyle = 'rgba(56, 189, 248, 0.15)';
3554
+ ctx.beginPath();
3555
+ ctx.roundRect(24, 24, 110, 32, 16);
3556
+ ctx.fill();
3557
+ ctx.fillStyle = '#38bdf8';
3558
+ ctx.font = 'bold 16px ' + FONT_DISPLAY;
3559
+ ctx.fillText('OPTION A', 42, 45);
3560
+
3561
+ // Left Metrics / Feature Bars
3562
+ for (let b = 0; b < 4; b++) {
3563
+ const barY = 90 + b * 75;
3564
+ const bProgress = Math.min(1, Math.max(0, spring(t, 0.3 + b * 0.1, 160, 14)));
3565
+ ctx.fillStyle = IS_LIGHT ? 'rgba(0,0,0,0.06)' : 'rgba(255,255,255,0.06)';
3566
+ ctx.beginPath();
3567
+ ctx.roundRect(24, barY + 22, cardW - 48, 14, 7);
3568
+ ctx.fill();
3569
+
3570
+ ctx.fillStyle = '#38bdf8';
3571
+ ctx.beginPath();
3572
+ const fillW = (cardW - 48) * (0.45 + 0.15 * Math.sin(b * 1.5 + 2)) * bProgress;
3573
+ ctx.roundRect(24, barY + 22, fillW, 14, 7);
3574
+ ctx.fill();
3575
+
3576
+ // Feature icon / indicator
3577
+ sCircle(36, barY + 8, 6, 100 + b, '#38bdf8', 2, '#38bdf8');
3578
+ ctx.fillStyle = TEXT_MAIN;
3579
+ ctx.font = '600 18px ' + FONT_DISPLAY;
3580
+ ctx.fillText('ATTRIBUTE 0' + (b + 1), 54, barY + 14);
3581
+ }
3582
+ ctx.restore();
3583
+
3584
+ // Right Card (Side B)
3585
+ const rightX = cx + 35;
3586
+ ctx.save();
3587
+ ctx.globalAlpha = Math.min(1, Math.max(0, cardRightSpring));
3588
+ ctx.translate(rightX + (1 - cardRightSpring) * 60, cardY);
3589
+ ctx.fillStyle = CARD_BG;
3590
+ ctx.strokeStyle = CARD_BORDER;
3591
+ ctx.lineWidth = 2;
3592
+ ctx.beginPath();
3593
+ ctx.roundRect(0, 0, cardW, cardH, 20);
3594
+ ctx.fill();
3595
+ ctx.stroke();
3596
+
3597
+ // Right Card Header & Accent Pill
3598
+ ctx.fillStyle = 'rgba(244, 63, 94, 0.15)';
3599
+ ctx.beginPath();
3600
+ ctx.roundRect(24, 24, 110, 32, 16);
3601
+ ctx.fill();
3602
+ ctx.fillStyle = '#f43f5e';
3603
+ ctx.font = 'bold 16px ' + FONT_DISPLAY;
3604
+ ctx.fillText('OPTION B', 42, 45);
3605
+
3606
+ // Right Metrics / Feature Bars
3607
+ for (let b = 0; b < 4; b++) {
3608
+ const barY = 90 + b * 75;
3609
+ const bProgress = Math.min(1, Math.max(0, spring(t, 0.35 + b * 0.1, 160, 14)));
3610
+ ctx.fillStyle = IS_LIGHT ? 'rgba(0,0,0,0.06)' : 'rgba(255,255,255,0.06)';
3611
+ ctx.beginPath();
3612
+ ctx.roundRect(24, barY + 22, cardW - 48, 14, 7);
3613
+ ctx.fill();
3614
+
3615
+ ctx.fillStyle = '#f43f5e';
3616
+ ctx.beginPath();
3617
+ const fillW = (cardW - 48) * (0.55 + 0.12 * Math.cos(b * 1.8 + 1)) * bProgress;
3618
+ ctx.roundRect(24, barY + 22, fillW, 14, 7);
3619
+ ctx.fill();
3620
+
3621
+ sCircle(36, barY + 8, 6, 200 + b, '#f43f5e', 2, '#f43f5e');
3622
+ ctx.fillStyle = TEXT_MAIN;
3623
+ ctx.font = '600 18px ' + FONT_DISPLAY;
3624
+ ctx.fillText('ATTRIBUTE 0' + (b + 1), 54, barY + 14);
3625
+ }
3626
+ ctx.restore();
3627
+
3628
+ // Center VS Emblem Badge
3629
+ ctx.save();
3630
+ ctx.translate(cx, cy);
3631
+ const bScale = Math.max(0, badgeSpring);
3632
+ ctx.scale(bScale, bScale);
3633
+ ctx.fillStyle = IS_LIGHT ? '#ffffff' : '#0f172a';
3634
+ ctx.strokeStyle = ACCENT;
3635
+ ctx.lineWidth = 4;
3636
+ ctx.beginPath();
3637
+ ctx.arc(0, 0, 48, 0, Math.PI * 2);
3638
+ ctx.fill();
3639
+ ctx.stroke();
3640
+
3641
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
3642
+ ctx.lineWidth = 1.5;
3643
+ ctx.beginPath();
3644
+ ctx.arc(0, 0, 42, 0, Math.PI * 2);
3645
+ ctx.stroke();
3646
+
3647
+ ctx.font = '900 28px ' + FONT_DISPLAY;
3648
+ ctx.fillStyle = ACCENT;
3649
+ ctx.textAlign = 'center';
3650
+ ctx.textBaseline = 'middle';
3651
+ ctx.fillText('VS', 0, 2);
3652
+ ctx.restore();
3653
+
3654
+ } else if (COMP_THEME === 'mechanism_physics') {
3655
+ // Universal Archetype: Mechanical Engineering, Cutaway Gears, Forces & Kinematics
3656
+ const engSpring = spring(t, 0.05, 170, 14);
3657
+ const vectorSpring = spring(t, 0.45, 180, 15);
3658
+
3659
+ // Background schematic blueprint grid lines
3660
+ ctx.save();
3661
+ ctx.strokeStyle = IS_LIGHT ? 'rgba(0,0,0,0.07)' : 'rgba(56, 189, 248, 0.12)';
3662
+ ctx.lineWidth = 1;
3663
+ for (let gx = cx - 450; gx <= cx + 450; gx += 60) {
3664
+ ctx.beginPath(); ctx.moveTo(gx, cy - 320); ctx.lineTo(gx, cy + 320); ctx.stroke();
3665
+ }
3666
+ for (let gy = cy - 300; gy <= cy + 300; gy += 60) {
3667
+ ctx.beginPath(); ctx.moveTo(cx - 480, gy); ctx.lineTo(cx + 480, gy); ctx.stroke();
3668
+ }
3669
+ ctx.strokeStyle = IS_LIGHT ? 'rgba(0,0,0,0.1)' : 'rgba(56, 189, 248, 0.2)';
3670
+ ctx.lineWidth = 1.5;
3671
+ ctx.beginPath(); ctx.arc(cx, cy, 340, 0, Math.PI * 2); ctx.stroke();
3672
+ ctx.restore();
3673
+
3674
+ // Helper: Draw precision technical gear with teeth
3675
+ function drawGear(gx, gy, radius, teeth, rotAngle, color, axleColor) {
3676
+ ctx.save();
3677
+ ctx.translate(gx, gy);
3678
+ ctx.rotate(rotAngle);
3679
+ ctx.fillStyle = color;
3680
+ ctx.strokeStyle = STROKE_COLOR;
3681
+ ctx.lineWidth = 2.5;
3682
+
3683
+ ctx.beginPath();
3684
+ const toothDepth = radius * 0.14;
3685
+ for (let i = 0; i < teeth; i++) {
3686
+ const a0 = (i * 2 * Math.PI) / teeth;
3687
+ const a1 = a0 + (Math.PI / teeth) * 0.35;
3688
+ const a2 = a0 + (Math.PI / teeth) * 0.65;
3689
+ const a3 = a0 + (Math.PI / teeth);
3690
+
3691
+ const rIn = radius - toothDepth;
3692
+ const rOut = radius + toothDepth;
3693
+
3694
+ if (i === 0) ctx.moveTo(rIn * Math.cos(a0), rIn * Math.sin(a0));
3695
+ else ctx.lineTo(rIn * Math.cos(a0), rIn * Math.sin(a0));
3696
+ ctx.lineTo(rOut * Math.cos(a1), rOut * Math.sin(a1));
3697
+ ctx.lineTo(rOut * Math.cos(a2), rOut * Math.sin(a2));
3698
+ ctx.lineTo(rIn * Math.cos(a3), rIn * Math.sin(a3));
3699
+ }
3700
+ ctx.closePath();
3701
+ ctx.fill();
3702
+ ctx.stroke();
3703
+
3704
+ // Hollow interior & spokes
3705
+ ctx.beginPath();
3706
+ ctx.arc(0, 0, radius * 0.62, 0, Math.PI * 2);
3707
+ ctx.fillStyle = IS_LIGHT ? '#ffffff' : '#080d1a';
3708
+ ctx.fill();
3709
+ ctx.stroke();
3710
+
3711
+ // 4 Precision Spokes
3712
+ for (let sp = 0; sp < 4; sp++) {
3713
+ ctx.save();
3714
+ ctx.rotate((sp * Math.PI) / 2);
3715
+ ctx.fillStyle = color;
3716
+ ctx.fillRect(-6, -radius * 0.58, 12, radius * 0.58 * 2);
3717
+ ctx.restore();
3718
+ }
3719
+
3720
+ // Center Axle Hub
3721
+ ctx.beginPath();
3722
+ ctx.arc(0, 0, radius * 0.25, 0, Math.PI * 2);
3723
+ ctx.fillStyle = axleColor;
3724
+ ctx.fill();
3725
+ ctx.stroke();
3726
+ sCircle(0, 0, radius * 0.1, 50, '#ffffff', 2, '#ffffff');
3727
+
3728
+ ctx.restore();
3729
+ }
3730
+
3731
+ ctx.save();
3732
+ const gScale = Math.min(1, Math.max(0, engSpring));
3733
+ ctx.translate(cx, cy);
3734
+ ctx.scale(gScale, gScale);
3735
+ ctx.translate(-cx, -cy);
3736
+
3737
+ // Primary Gear Train (Interlocking 3 gears)
3738
+ const gearSpeed = t * 1.4;
3739
+ drawGear(cx - 150, cy, 140, 24, gearSpeed, IS_LIGHT ? '#e2e8f0' : '#1e293b', ACCENT);
3740
+ drawGear(cx + 90, cy - 80, 105, 18, -gearSpeed * (24 / 18) + 0.17, IS_LIGHT ? '#cbd5e1' : '#334155', ACCENT2);
3741
+ drawGear(cx + 175, cy + 95, 70, 12, gearSpeed * (24 / 12) + 0.25, IS_LIGHT ? '#94a3b8' : '#475569', '#10b981');
3742
+
3743
+ // Dynamic Kinematic Linkage / Piston
3744
+ const pistonCycle = t * 3.5;
3745
+ const crankR = 60;
3746
+ const crankX = cx - 150 + Math.cos(gearSpeed) * crankR;
3747
+ const crankY = cy + Math.sin(gearSpeed) * crankR;
3748
+ const pistonY = cy + 180 + Math.sin(pistonCycle) * 35;
3749
+ const pistonX = cx - 150;
3750
+
3751
+ sLine(crankX, crankY, pistonX, pistonY, 301, ACCENT, 5);
3752
+ sCircle(crankX, crankY, 8, 302, '#ffffff', 2, ACCENT);
3753
+ sCircle(pistonX, pistonY, 8, 303, '#ffffff', 2, ACCENT);
3754
+
3755
+ // Piston Head in Cylinder
3756
+ ctx.fillStyle = IS_LIGHT ? '#64748b' : '#38bdf8';
3757
+ ctx.fillRect(pistonX - 35, pistonY - 15, 70, 30);
3758
+ ctx.strokeStyle = STROKE_COLOR;
3759
+ ctx.lineWidth = 2.5;
3760
+ ctx.strokeRect(pistonX - 35, pistonY - 15, 70, 30);
3761
+
3762
+ // Dynamic Force Vectors & Aerodynamic Flow Arrows
3763
+ if (vectorSpring > 0.05) {
3764
+ ctx.save();
3765
+ ctx.globalAlpha = Math.min(1, Math.max(0, vectorSpring));
3766
+
3767
+ const vX = cx + 290;
3768
+ const vY = cy - 140;
3769
+ sLine(vX, vY, vX + 110, vY, 401, '#10b981', 4);
3770
+ ctx.fillStyle = '#10b981';
3771
+ ctx.beginPath();
3772
+ ctx.moveTo(vX + 125, vY);
3773
+ ctx.lineTo(vX + 105, vY - 8);
3774
+ ctx.lineTo(vX + 105, vY + 8);
3775
+ ctx.closePath();
3776
+ ctx.fill();
3777
+ ctx.font = 'bold 15px ' + FONT_DISPLAY;
3778
+ ctx.fillText('F_THRUST = 4.8 kN', vX, vY - 16);
3779
+
3780
+ ctx.strokeStyle = '#f59e0b';
3781
+ ctx.lineWidth = 3;
3782
+ ctx.beginPath();
3783
+ ctx.arc(cx - 150, cy, 185, -0.6, 0.6);
3784
+ ctx.stroke();
3785
+ ctx.fillStyle = '#f59e0b';
3786
+ ctx.font = 'bold 15px ' + FONT_DISPLAY;
3787
+ ctx.fillText('TORQUE = 320 Nm', cx - 210, cy - 195);
3788
+ ctx.restore();
3789
+ }
3790
+
3791
+ ctx.restore();
3792
+
3793
+ } else if (COMP_THEME === 'process_pipeline') {
3794
+ // Universal Archetype: Multi-Stage Process / Pipeline / Supply Chain / Lifecycle
3795
+ const pipeSpring = spring(t, 0.05, 170, 14);
3796
+ const stages = [
3797
+ { name: 'INGEST', label: '01 / SOURCE', desc: 'Raw Signals' },
3798
+ { name: 'SYNTHESIS', label: '02 / TRANSFORM', desc: 'Active Process' },
3799
+ { name: 'VALIDATION', label: '03 / VERIFY', desc: 'Quality Gate' },
3800
+ { name: 'DEPLOYMENT', label: '04 / OUTPUT', desc: 'Master Release' },
3801
+ ];
3802
+
3803
+ const pipeY = cy;
3804
+ const startX = cx - 450;
3805
+ const totalW = 900;
3806
+ const segW = totalW / (stages.length - 1);
3807
+
3808
+ ctx.save();
3809
+ const pScale = Math.min(1, Math.max(0, pipeSpring));
3810
+ ctx.translate(cx, cy);
3811
+ ctx.scale(pScale, pScale);
3812
+ ctx.translate(-cx, -cy);
3813
+
3814
+ // Main Conduit Pipeline Track
3815
+ const conduitG = ctx.createLinearGradient(startX, 0, startX + totalW, 0);
3816
+ conduitG.addColorStop(0, ACCENT);
3817
+ conduitG.addColorStop(0.5, '#38bdf8');
3818
+ conduitG.addColorStop(1, '#10b981');
3819
+
3820
+ ctx.strokeStyle = IS_LIGHT ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.15)';
3821
+ ctx.lineWidth = 14;
3822
+ ctx.lineCap = 'round';
3823
+ ctx.beginPath();
3824
+ ctx.moveTo(startX, pipeY);
3825
+ ctx.lineTo(startX + totalW, pipeY);
3826
+ ctx.stroke();
3827
+
3828
+ ctx.strokeStyle = conduitG;
3829
+ ctx.lineWidth = 6;
3830
+ ctx.stroke();
3831
+
3832
+ // Flowing Data Packet Orbs along the conduit
3833
+ for (let pk = 0; pk < 5; pk++) {
3834
+ const pkProgress = ((t * 0.45 + pk * 0.22) % 1.0);
3835
+ const pkX = startX + pkProgress * totalW;
3836
+ ctx.fillStyle = '#ffffff';
3837
+ ctx.beginPath();
3838
+ ctx.arc(pkX, pipeY, 6, 0, Math.PI * 2);
3839
+ ctx.fill();
3840
+
3841
+ ctx.fillStyle = ACCENT;
3842
+ ctx.globalAlpha = 0.4;
3843
+ ctx.beginPath();
3844
+ ctx.arc(pkX, pipeY, 14, 0, Math.PI * 2);
3845
+ ctx.fill();
3846
+ ctx.globalAlpha = 1.0;
3847
+ }
3848
+
3849
+ // Stage Nodes & Info Cards
3850
+ for (let s = 0; s < stages.length; s++) {
3851
+ const st = stages[s];
3852
+ const nodeX = startX + s * segW;
3853
+ const nodeSpring = spring(t, 0.15 + s * 0.16, 180, 14);
3854
+ const nodeAlpha = Math.min(1, Math.max(0, nodeSpring));
3855
+
3856
+ const activeStage = Math.min(stages.length - 1, Math.floor((t / window.DURATION) * stages.length));
3857
+ const isActive = s === activeStage;
3858
+
3859
+ ctx.save();
3860
+ ctx.globalAlpha = nodeAlpha;
3861
+ ctx.translate(nodeX, pipeY);
3862
+
3863
+ const isTop = s % 2 === 0;
3864
+ const cardOffsetY = isTop ? -180 : 70;
3865
+ const cardW = 190;
3866
+ const cardH = 95;
3867
+
3868
+ sLine(0, 0, 0, isTop ? -50 : 50, 500 + s, isActive ? ACCENT : STROKE_ALT, 2.5);
3869
+
3870
+ ctx.fillStyle = CARD_BG;
3871
+ ctx.strokeStyle = isActive ? ACCENT : CARD_BORDER;
3872
+ ctx.lineWidth = isActive ? 3 : 1.5;
3873
+ ctx.beginPath();
3874
+ ctx.roundRect(-cardW / 2, cardOffsetY, cardW, cardH, 14);
3875
+ ctx.fill();
3876
+ ctx.stroke();
3877
+
3878
+ if (isActive) {
3879
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
3880
+ ctx.lineWidth = 1;
3881
+ ctx.beginPath();
3882
+ ctx.roundRect(-cardW / 2 + 4, cardOffsetY + 4, cardW - 8, cardH - 8, 10);
3883
+ ctx.stroke();
3884
+ }
3885
+
3886
+ ctx.textAlign = 'center';
3887
+ ctx.font = 'bold 12px ' + FONT_DISPLAY;
3888
+ ctx.fillStyle = isActive ? ACCENT : '#94a3b8';
3889
+ ctx.fillText(st.label, 0, cardOffsetY + 26);
3890
+
3891
+ ctx.font = '800 16px ' + FONT_DISPLAY;
3892
+ ctx.fillStyle = TEXT_MAIN;
3893
+ ctx.fillText(st.name, 0, cardOffsetY + 52);
3894
+
3895
+ ctx.font = '500 13px ' + FONT_BODY;
3896
+ ctx.fillStyle = TEXT_MUTED;
3897
+ ctx.fillText(st.desc, 0, cardOffsetY + 76);
3898
+
3899
+ const nodeR = isActive ? 22 : 16;
3900
+ ctx.fillStyle = IS_LIGHT ? '#ffffff' : '#0f172a';
3901
+ ctx.beginPath();
3902
+ ctx.arc(0, 0, nodeR, 0, Math.PI * 2);
3903
+ ctx.fill();
3904
+
3905
+ ctx.strokeStyle = isActive ? ACCENT : '#64748b';
3906
+ ctx.lineWidth = isActive ? 4 : 3;
3907
+ ctx.beginPath();
3908
+ ctx.arc(0, 0, nodeR, 0, Math.PI * 2);
3909
+ ctx.stroke();
3910
+
3911
+ sCircle(0, 0, isActive ? 8 : 5, 520 + s, isActive ? '#ffffff' : '#94a3b8', 2, isActive ? ACCENT : '#64748b');
3912
+
3913
+ ctx.restore();
3914
+ }
3915
+
3916
+ ctx.restore();
3917
+
3918
+ } else if (COMP_THEME === 'cognitive_insight') {
3919
+ // Universal Archetype: Cognitive Psychology, Neural Eureka, Theory of Mind & Idea Spark
3920
+ const brainSpring = spring(t, 0.05, 170, 14);
3921
+ const eurekaSpring = spring(t, window.DURATION * 0.35, 160, 12);
3922
+ const orbitSpring = spring(t, 0.40, 180, 15);
3923
+
3924
+ ctx.save();
3925
+ const cScale = Math.min(1, Math.max(0, brainSpring));
3926
+ ctx.translate(cx, cy);
3927
+ ctx.scale(cScale, cScale);
3928
+
3929
+ // Concentric Thought Wave Ripples
3930
+ for (let r = 0; r < 4; r++) {
3931
+ const ripPhase = (t * 0.8 + r * 0.25) % 1.0;
3932
+ const ripR = 90 + ripPhase * 240;
3933
+ ctx.strokeStyle = ACCENT;
3934
+ ctx.globalAlpha = Math.max(0, (1 - ripPhase) * 0.35);
3935
+ ctx.lineWidth = 2.5;
3936
+ ctx.beginPath();
3937
+ ctx.arc(0, 0, ripR, 0, Math.PI * 2);
3938
+ ctx.stroke();
3939
+ }
3940
+ ctx.globalAlpha = 1.0;
3941
+
3942
+ // Cerebral Neural Core / Brain Silhouette
3943
+ const brainR = 120;
3944
+ const coreGrad = ctx.createRadialGradient(0, -20, 20, 0, 0, brainR * 1.5);
3945
+ coreGrad.addColorStop(0, '#fef08a');
3946
+ coreGrad.addColorStop(0.3, ACCENT);
3947
+ coreGrad.addColorStop(0.8, '#6366f1');
3948
+ coreGrad.addColorStop(1, 'rgba(15, 23, 42, 0)');
3949
+
3950
+ ctx.fillStyle = coreGrad;
3951
+ ctx.beginPath();
3952
+ ctx.arc(0, 0, brainR * 1.4, 0, Math.PI * 2);
3953
+ ctx.fill();
3954
+
3955
+ // Neural Synapses
3956
+ const synNodes = [
3957
+ { x: -50, y: -60 }, { x: 0, y: -80 }, { x: 50, y: -60 },
3958
+ { x: -80, y: -20 }, { x: -30, y: -20 }, { x: 30, y: -20 }, { x: 80, y: -20 },
3959
+ { x: -60, y: 30 }, { x: 0, y: 25 }, { x: 60, y: 30 },
3960
+ { x: -25, y: 70 }, { x: 25, y: 70 },
3961
+ ];
3962
+
3963
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.35)';
3964
+ ctx.lineWidth = 2;
3965
+ for (let i = 0; i < synNodes.length; i++) {
3966
+ for (let j = i + 1; j < synNodes.length; j++) {
3967
+ const dx = synNodes[i].x - synNodes[j].x;
3968
+ const dy = synNodes[i].y - synNodes[j].y;
3969
+ const dist = Math.hypot(dx, dy);
3970
+ if (dist < 75) {
3971
+ ctx.beginPath();
3972
+ ctx.moveTo(synNodes[i].x, synNodes[i].y);
3973
+ ctx.lineTo(synNodes[j].x, synNodes[j].y);
3974
+ ctx.stroke();
3975
+
3976
+ const pulse = (t * 2.5 + i * 0.3 + j * 0.2) % 1.0;
3977
+ const px = synNodes[i].x + (synNodes[j].x - synNodes[i].x) * pulse;
3978
+ const py = synNodes[i].y + (synNodes[j].y - synNodes[i].y) * pulse;
3979
+ ctx.fillStyle = '#ffffff';
3980
+ ctx.beginPath();
3981
+ ctx.arc(px, py, 2.5, 0, Math.PI * 2);
3982
+ ctx.fill();
3983
+ }
3984
+ }
3985
+ }
3986
+
3987
+ for (let n = 0; n < synNodes.length; n++) {
3988
+ const sn = synNodes[n];
3989
+ const fire = Math.sin(t * 8 + n * 1.3);
3990
+ const dotR = 5 + (fire > 0.5 ? 2.5 : 0);
3991
+ sCircle(sn.x, sn.y, dotR, 600 + n, '#ffffff', 2, fire > 0.3 ? '#fef08a' : ACCENT);
3992
+ }
3993
+
3994
+ // Eureka Idea Spark Burst
3995
+ if (eurekaSpring > 0.05) {
3996
+ const eProgress = Math.min(1, Math.max(0, eurekaSpring));
3997
+ ctx.save();
3998
+ ctx.scale(eProgress, eProgress);
3999
+
4000
+ const rays = 12;
4001
+ for (let ry = 0; ry < rays; ry++) {
4002
+ const ang = (ry * Math.PI * 2) / rays + t * 0.4;
4003
+ const len = 140 + Math.sin(t * 6 + ry) * 25;
4004
+ ctx.strokeStyle = ry % 2 === 0 ? '#fef08a' : '#ffffff';
4005
+ ctx.lineWidth = ry % 2 === 0 ? 3 : 1.5;
4006
+ ctx.beginPath();
4007
+ ctx.moveTo(Math.cos(ang) * 50, Math.sin(ang) * 50);
4008
+ ctx.lineTo(Math.cos(ang) * len, Math.sin(ang) * len);
4009
+ ctx.stroke();
4010
+ }
4011
+
4012
+ sCircle(0, 0, 18, 650, '#ffffff', 3, '#f59e0b');
4013
+ sCircle(0, 0, 8, 651, '#ffffff', 2, '#ffffff');
4014
+ ctx.restore();
4015
+ }
4016
+
4017
+ // Orbiting Insight Badges
4018
+ if (orbitSpring > 0.05) {
4019
+ const badges = ['PERCEPTION', 'COGNITION', 'INTUITION', 'INSIGHT'];
4020
+ ctx.save();
4021
+ ctx.globalAlpha = Math.min(1, Math.max(0, orbitSpring));
4022
+
4023
+ for (let bg = 0; bg < badges.length; bg++) {
4024
+ const bgAngle = (bg * Math.PI * 2) / badges.length + t * 0.35;
4025
+ const bgX = Math.cos(bgAngle) * 310;
4026
+ const bgY = Math.sin(bgAngle) * 190;
4027
+
4028
+ ctx.fillStyle = CARD_BG;
4029
+ ctx.strokeStyle = CARD_BORDER;
4030
+ ctx.lineWidth = 1.5;
4031
+ ctx.beginPath();
4032
+ ctx.roundRect(bgX - 65, bgY - 18, 130, 36, 18);
4033
+ ctx.fill();
4034
+ ctx.stroke();
4035
+
4036
+ ctx.font = 'bold 13px ' + FONT_DISPLAY;
4037
+ ctx.fillStyle = TEXT_MAIN;
4038
+ ctx.textAlign = 'center';
4039
+ ctx.fillText(badges[bg], bgX, bgY + 5);
4040
+
4041
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.12)';
4042
+ ctx.beginPath();
4043
+ ctx.moveTo(0, 0);
4044
+ ctx.lineTo(bgX, bgY);
4045
+ ctx.stroke();
4046
+ }
4047
+ ctx.restore();
4048
+ }
4049
+
4050
+ ctx.restore();
4051
+
4052
+ } else if (COMP_THEME === 'magnification_focus') {
4053
+ // Universal Archetype: Optical Loupe, Precision Macro Zoom & Scrutiny Reticle
4054
+ const focusSpring = spring(t, 0.05, 170, 14);
4055
+ const loupeSpring = spring(t, 0.25, 180, 14);
4056
+
4057
+ // Deep research background grid & underlying specimen traces
4058
+ ctx.save();
4059
+ ctx.strokeStyle = IS_LIGHT ? 'rgba(0,0,0,0.06)' : 'rgba(255,255,255,0.08)';
4060
+ ctx.lineWidth = 1;
4061
+ for (let gx = 0; gx < W; gx += 40) {
4062
+ ctx.beginPath(); ctx.moveTo(gx, 0); ctx.lineTo(gx, H); ctx.stroke();
4063
+ }
4064
+ for (let gy = 0; gy < H; gy += 40) {
4065
+ ctx.beginPath(); ctx.moveTo(0, gy); ctx.lineTo(W, gy); ctx.stroke();
4066
+ }
4067
+
4068
+ for (let tr = 0; tr < 16; tr++) {
4069
+ const trX = cx - 350 + tr * 48;
4070
+ const trY = cy - 200 + ((tr * 73) % 400);
4071
+ sLine(trX, trY, trX + 80, trY + 40, 700 + tr, IS_LIGHT ? 'rgba(0,0,0,0.15)' : 'rgba(56, 189, 248, 0.2)', 2);
4072
+ }
4073
+ ctx.restore();
4074
+
4075
+ // Floating Optical Loupe (Lissajous gentle inspection movement)
4076
+ const loupeR = Math.min(W, H) * 0.26;
4077
+ const lissX = cx + Math.sin(t * 1.1) * 70;
4078
+ const lissY = cy + Math.cos(t * 0.85) * 45;
4079
+
4080
+ ctx.save();
4081
+ const lScale = Math.min(1, Math.max(0, loupeSpring));
4082
+ ctx.translate(lissX, lissY);
4083
+ ctx.scale(lScale, lScale);
4084
+
4085
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.45)';
4086
+ ctx.beginPath();
4087
+ ctx.arc(15, 20, loupeR, 0, Math.PI * 2);
4088
+ ctx.fill();
4089
+
4090
+ // Inside Magnified Specimen (Clipped Circle)
4091
+ ctx.save();
4092
+ ctx.beginPath();
4093
+ ctx.arc(0, 0, loupeR - 8, 0, Math.PI * 2);
4094
+ ctx.clip();
4095
+
4096
+ ctx.fillStyle = IS_LIGHT ? '#ffffff' : '#070f1e';
4097
+ ctx.fillRect(-loupeR, -loupeR, loupeR * 2, loupeR * 2);
4098
+
4099
+ ctx.save();
4100
+ ctx.scale(2.4, 2.4);
4101
+ for (let m = -8; m <= 8; m++) {
4102
+ sLine(m * 22, -loupeR, m * 22, loupeR, 720 + m, 'rgba(56, 189, 248, 0.3)', 1.5);
4103
+ sLine(-loupeR, m * 22, loupeR, m * 22, 740 + m, 'rgba(56, 189, 248, 0.3)', 1.5);
4104
+ }
4105
+ sCircle(0, 0, 36, 760, '#f59e0b', 3, 'rgba(245, 158, 11, 0.2)');
4106
+ sCircle(0, 0, 16, 761, '#ef4444', 2.5, '#ef4444');
4107
+ for (let br = 0; br < 6; br++) {
4108
+ const brA = (br * Math.PI * 2) / 6 + t * 0.5;
4109
+ sLine(0, 0, Math.cos(brA) * 55, Math.sin(brA) * 55, 770 + br, ACCENT, 2.5);
4110
+ sCircle(Math.cos(brA) * 55, Math.sin(brA) * 55, 5, 780 + br, '#ffffff', 1.5, ACCENT);
4111
+ }
4112
+ ctx.restore();
4113
+
4114
+ // Precision Crosshairs & Calibrated Reticle
4115
+ ctx.strokeStyle = '#38bdf8';
4116
+ ctx.lineWidth = 1.5;
4117
+ ctx.beginPath();
4118
+ ctx.moveTo(-loupeR + 15, 0); ctx.lineTo(-25, 0);
4119
+ ctx.moveTo(25, 0); ctx.lineTo(loupeR - 15, 0);
4120
+ ctx.moveTo(0, -loupeR + 15); ctx.lineTo(0, -25);
4121
+ ctx.moveTo(0, 25); ctx.lineTo(0, loupeR - 15);
4122
+ ctx.stroke();
4123
+
4124
+ for (let deg = 0; deg < 36; deg++) {
4125
+ const dRad = (deg * Math.PI * 2) / 36;
4126
+ const isMajor = deg % 9 === 0;
4127
+ const tLen = isMajor ? 16 : 8;
4128
+ const rOut = loupeR - 12;
4129
+ const rIn = rOut - tLen;
4130
+ ctx.strokeStyle = isMajor ? ACCENT : 'rgba(255,255,255,0.4)';
4131
+ ctx.lineWidth = isMajor ? 2 : 1;
4132
+ ctx.beginPath();
4133
+ ctx.moveTo(Math.cos(dRad) * rIn, Math.sin(dRad) * rIn);
4134
+ ctx.lineTo(Math.cos(dRad) * rOut, Math.sin(dRad) * rOut);
4135
+ ctx.stroke();
4136
+ }
4137
+ ctx.restore(); // End clipping
4138
+
4139
+ // Bezel
4140
+ ctx.strokeStyle = '#d4af37';
4141
+ ctx.lineWidth = 14;
4142
+ ctx.beginPath();
4143
+ ctx.arc(0, 0, loupeR, 0, Math.PI * 2);
4144
+ ctx.stroke();
4145
+
4146
+ ctx.strokeStyle = '#fef08a';
4147
+ ctx.lineWidth = 2.5;
4148
+ ctx.beginPath();
4149
+ ctx.arc(0, 0, loupeR - 6, 0, Math.PI * 2);
4150
+ ctx.stroke();
4151
+
4152
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
4153
+ ctx.lineWidth = 4;
4154
+ ctx.beginPath();
4155
+ ctx.arc(0, 0, loupeR - 24, -1.8, -0.6);
4156
+ ctx.stroke();
4157
+
4158
+ ctx.fillStyle = CARD_BG;
4159
+ ctx.strokeStyle = ACCENT;
4160
+ ctx.lineWidth = 1.5;
4161
+ ctx.beginPath();
4162
+ ctx.roundRect(loupeR - 40, -loupeR - 10, 160, 48, 10);
4163
+ ctx.fill();
4164
+ ctx.stroke();
4165
+
4166
+ ctx.font = 'bold 12px monospace';
4167
+ ctx.fillStyle = ACCENT;
4168
+ ctx.fillText('MAGNIFY: 250%', loupeR - 26, -loupeR + 12);
4169
+ ctx.fillStyle = TEXT_MAIN;
4170
+ ctx.fillText('POS: ' + Math.round(lissX) + ', ' + Math.round(lissY), loupeR - 26, -loupeR + 28);
4171
+
4172
+ ctx.restore();
4173
+
4174
+ } else if (COMP_THEME === 'waveform') {
3317
4175
  // Kinetic Frequency Equalizer Waveform
3318
4176
  const bars = 36;
3319
4177
  const barW = (W * 0.7) / bars;
@@ -3330,6 +4188,153 @@ function drawMainScene(t) {
3330
4188
  ctx.fillStyle = barGrad;
3331
4189
  ctx.beginPath(); ctx.roundRect(bx, by, barW - 6, hVal, 4); ctx.fill();
3332
4190
  }
4191
+
4192
+ } else {
4193
+ // Universal Semantic Default & Explainer Presentation (Whiteboard Masterclass & Concept Pillars)
4194
+ const boardSpring = spring(t, 0.05, 170, 14);
4195
+ const p1Spring = spring(t, 0.18, 190, 15);
4196
+ const p2Spring = spring(t, 0.32, 190, 15);
4197
+ const p3Spring = spring(t, 0.46, 190, 15);
4198
+
4199
+ const boardW = Math.min(W * 0.88, 1100);
4200
+ const boardH = Math.min(H * 0.68, 620);
4201
+ const boardX = cx - boardW / 2;
4202
+ const boardY = cy - boardH / 2;
4203
+
4204
+ ctx.save();
4205
+ const bScale = Math.min(1, Math.max(0, boardSpring));
4206
+ ctx.translate(cx, cy);
4207
+ ctx.scale(bScale, bScale);
4208
+ ctx.translate(-cx, -cy);
4209
+
4210
+ // Architectural Frosted Glass Presentation Board
4211
+ ctx.fillStyle = CARD_BG;
4212
+ ctx.strokeStyle = CARD_BORDER;
4213
+ ctx.lineWidth = 2.5;
4214
+ ctx.beginPath();
4215
+ ctx.roundRect(boardX, boardY, boardW, boardH, 24);
4216
+ ctx.fill();
4217
+ ctx.stroke();
4218
+
4219
+ // Board Header Accent Bar
4220
+ ctx.fillStyle = ACCENT;
4221
+ ctx.beginPath();
4222
+ ctx.roundRect(boardX + 32, boardY + 28, 6, 32, 3);
4223
+ ctx.fill();
4224
+
4225
+ ctx.font = '800 22px ' + FONT_DISPLAY;
4226
+ ctx.fillStyle = TEXT_MAIN;
4227
+ ctx.textAlign = 'left';
4228
+ ctx.fillText('CORE PRINCIPLES & ARCHITECTURE', boardX + 48, boardY + 50);
4229
+
4230
+ // Live Status Badge
4231
+ ctx.fillStyle = 'rgba(16, 185, 129, 0.15)';
4232
+ ctx.beginPath();
4233
+ ctx.roundRect(boardX + boardW - 160, boardY + 28, 128, 30, 15);
4234
+ ctx.fill();
4235
+ ctx.fillStyle = '#10b981';
4236
+ ctx.font = 'bold 13px ' + FONT_DISPLAY;
4237
+ ctx.fillText('● SYSTEM ACTIVE', boardX + boardW - 146, boardY + 48);
4238
+
4239
+ // 3 Structured Concept Pillars
4240
+ const pillarData = [
4241
+ { num: '01', title: 'FOUNDATION', desc: 'Core axioms & structure', icon: 'diamond', spr: p1Spring },
4242
+ { num: '02', title: 'DYNAMICS', desc: 'Kinetic transformation', icon: 'gear', spr: p2Spring },
4243
+ { num: '03', title: 'IMPACT', desc: 'Synthesized outcome', icon: 'star', spr: p3Spring },
4244
+ ];
4245
+
4246
+ const pillarW = (boardW - 128) / 3;
4247
+ const pillarH = boardH - 140;
4248
+ const pillarY = boardY + 95;
4249
+
4250
+ for (let p = 0; p < 3; p++) {
4251
+ const pd = pillarData[p];
4252
+ const pilX = boardX + 32 + p * (pillarW + 32);
4253
+ const pAlpha = Math.min(1, Math.max(0, pd.spr));
4254
+
4255
+ ctx.save();
4256
+ ctx.globalAlpha = pAlpha;
4257
+ ctx.translate(pilX, pillarY + (1 - pd.spr) * 40);
4258
+
4259
+ // Pillar Card
4260
+ ctx.fillStyle = IS_LIGHT ? 'rgba(255,255,255,0.7)' : 'rgba(255,255,255,0.04)';
4261
+ ctx.strokeStyle = p === 1 ? ACCENT : 'rgba(255,255,255,0.1)';
4262
+ ctx.lineWidth = p === 1 ? 2.5 : 1.5;
4263
+ ctx.beginPath();
4264
+ ctx.roundRect(0, 0, pillarW, pillarH, 16);
4265
+ ctx.fill();
4266
+ ctx.stroke();
4267
+
4268
+ // Number Tag
4269
+ ctx.font = '900 48px ' + FONT_DISPLAY;
4270
+ ctx.fillStyle = p === 1 ? ACCENT : (IS_LIGHT ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.12)');
4271
+ ctx.fillText(pd.num, 24, 60);
4272
+
4273
+ // Pillar Title
4274
+ ctx.font = '700 20px ' + FONT_DISPLAY;
4275
+ ctx.fillStyle = TEXT_MAIN;
4276
+ ctx.fillText(pd.title, 24, 110);
4277
+
4278
+ // Pillar Description
4279
+ ctx.font = '500 15px ' + FONT_BODY;
4280
+ ctx.fillStyle = TEXT_MUTED;
4281
+ ctx.fillText(pd.desc, 24, 138);
4282
+
4283
+ // Dynamic Animated Diagram Graphic inside Pillar
4284
+ const gfxY = pillarH * 0.65;
4285
+ const gfxX = pillarW / 2;
4286
+
4287
+ if (pd.icon === 'diamond') {
4288
+ ctx.save();
4289
+ ctx.translate(gfxX, gfxY);
4290
+ ctx.rotate(t * 1.2);
4291
+ ctx.strokeStyle = ACCENT;
4292
+ ctx.lineWidth = 3;
4293
+ ctx.strokeRect(-28, -28, 56, 56);
4294
+ sCircle(0, 0, 10, 800, '#ffffff', 2, ACCENT);
4295
+ ctx.restore();
4296
+ } else if (pd.icon === 'gear') {
4297
+ sCircle(gfxX, gfxY, 36, 810, ACCENT, 3);
4298
+ const pulseR = 20 + Math.sin(t * 5) * 8;
4299
+ sCircle(gfxX, gfxY, pulseR, 811, '#ffffff', 2, '#38bdf8');
4300
+ } else {
4301
+ for (let st = 0; st < 4; st++) {
4302
+ const ang = (st * Math.PI) / 2 + t * 0.8;
4303
+ sLine(gfxX, gfxY, gfxX + Math.cos(ang) * 36, gfxY + Math.sin(ang) * 36, 820 + st, '#f59e0b', 3);
4304
+ }
4305
+ sCircle(gfxX, gfxY, 8, 825, '#ffffff', 2, '#f59e0b');
4306
+ }
4307
+
4308
+ // Feature Check items
4309
+ for (let ch = 0; ch < 3; ch++) {
4310
+ const itemY = pillarH - 85 + ch * 24;
4311
+ sCircle(28, itemY, 4, 830 + ch, ACCENT, 2, ACCENT);
4312
+ ctx.fillStyle = TEXT_MAIN;
4313
+ ctx.font = '500 13px ' + FONT_BODY;
4314
+ ctx.fillText('Verification Key 0' + (ch + 1), 42, itemY + 4);
4315
+ }
4316
+
4317
+ ctx.restore();
4318
+ }
4319
+
4320
+ // Inter-Pillar Energy Connectors
4321
+ ctx.strokeStyle = ACCENT;
4322
+ ctx.lineWidth = 2.5;
4323
+ for (let c = 0; c < 2; c++) {
4324
+ const connX1 = boardX + 32 + (c + 1) * pillarW + c * 32 - 16;
4325
+ const connX2 = connX1 + 32;
4326
+ const connY = pillarY + 80;
4327
+ sLine(connX1, connY, connX2, connY, 850 + c, ACCENT, 3);
4328
+ ctx.fillStyle = ACCENT;
4329
+ ctx.beginPath();
4330
+ ctx.moveTo(connX2 + 4, connY);
4331
+ ctx.lineTo(connX2 - 4, connY - 5);
4332
+ ctx.lineTo(connX2 - 4, connY + 5);
4333
+ ctx.closePath();
4334
+ ctx.fill();
4335
+ }
4336
+
4337
+ ctx.restore();
3333
4338
  }
3334
4339
 
3335
4340
  ctx.restore();
@@ -3481,6 +4486,7 @@ module.exports = {
3481
4486
  detectAesthetic,
3482
4487
  detectComponentTheme,
3483
4488
  detectCaptionsRequested,
4489
+ detectAnchorRequested,
3484
4490
  getScriptFontStack,
3485
4491
  AESTHETICS,
3486
4492
  REGIONAL_FONTS,