@timmeck/brain 1.8.0 → 1.8.2

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.
Files changed (177) hide show
  1. package/BRAIN_PLAN.md +3324 -3324
  2. package/LICENSE +21 -21
  3. package/dist/api/server.d.ts +4 -0
  4. package/dist/api/server.js +73 -0
  5. package/dist/api/server.js.map +1 -1
  6. package/dist/brain.js +2 -1
  7. package/dist/brain.js.map +1 -1
  8. package/dist/cli/commands/dashboard.js +606 -572
  9. package/dist/cli/commands/dashboard.js.map +1 -1
  10. package/dist/dashboard/server.js +25 -25
  11. package/dist/db/migrations/001_core_schema.js +115 -115
  12. package/dist/db/migrations/002_learning_schema.js +33 -33
  13. package/dist/db/migrations/003_code_schema.js +48 -48
  14. package/dist/db/migrations/004_synapses_schema.js +52 -52
  15. package/dist/db/migrations/005_fts_indexes.js +73 -73
  16. package/dist/db/migrations/007_feedback.js +8 -8
  17. package/dist/db/migrations/008_git_integration.js +33 -33
  18. package/dist/db/migrations/009_embeddings.js +3 -3
  19. package/dist/db/repositories/antipattern.repository.js +3 -3
  20. package/dist/db/repositories/code-module.repository.js +32 -32
  21. package/dist/db/repositories/notification.repository.js +3 -3
  22. package/dist/db/repositories/project.repository.js +21 -21
  23. package/dist/db/repositories/rule.repository.js +24 -24
  24. package/dist/db/repositories/solution.repository.js +50 -50
  25. package/dist/db/repositories/synapse.repository.js +18 -18
  26. package/dist/db/repositories/terminal.repository.js +24 -24
  27. package/dist/embeddings/engine.d.ts +2 -2
  28. package/dist/embeddings/engine.js +17 -4
  29. package/dist/embeddings/engine.js.map +1 -1
  30. package/dist/index.js +1 -1
  31. package/dist/ipc/server.d.ts +8 -0
  32. package/dist/ipc/server.js +67 -1
  33. package/dist/ipc/server.js.map +1 -1
  34. package/dist/matching/error-matcher.js +5 -5
  35. package/dist/matching/fingerprint.js +6 -1
  36. package/dist/matching/fingerprint.js.map +1 -1
  37. package/dist/mcp/http-server.js +8 -2
  38. package/dist/mcp/http-server.js.map +1 -1
  39. package/dist/services/code.service.d.ts +3 -0
  40. package/dist/services/code.service.js +33 -4
  41. package/dist/services/code.service.js.map +1 -1
  42. package/dist/services/error.service.js +4 -3
  43. package/dist/services/error.service.js.map +1 -1
  44. package/dist/services/git.service.js +14 -14
  45. package/package.json +49 -49
  46. package/src/api/server.ts +395 -321
  47. package/src/brain.ts +266 -265
  48. package/src/cli/colors.ts +116 -116
  49. package/src/cli/commands/config.ts +169 -169
  50. package/src/cli/commands/dashboard.ts +755 -720
  51. package/src/cli/commands/doctor.ts +118 -118
  52. package/src/cli/commands/explain.ts +83 -83
  53. package/src/cli/commands/export.ts +31 -31
  54. package/src/cli/commands/import.ts +199 -199
  55. package/src/cli/commands/insights.ts +65 -65
  56. package/src/cli/commands/learn.ts +24 -24
  57. package/src/cli/commands/modules.ts +53 -53
  58. package/src/cli/commands/network.ts +67 -67
  59. package/src/cli/commands/projects.ts +42 -42
  60. package/src/cli/commands/query.ts +120 -120
  61. package/src/cli/commands/start.ts +62 -62
  62. package/src/cli/commands/status.ts +75 -75
  63. package/src/cli/commands/stop.ts +34 -34
  64. package/src/cli/ipc-helper.ts +22 -22
  65. package/src/cli/update-check.ts +63 -63
  66. package/src/code/fingerprint.ts +87 -87
  67. package/src/code/parsers/generic.ts +29 -29
  68. package/src/code/parsers/python.ts +54 -54
  69. package/src/code/parsers/typescript.ts +65 -65
  70. package/src/code/registry.ts +60 -60
  71. package/src/dashboard/server.ts +142 -142
  72. package/src/db/connection.ts +22 -22
  73. package/src/db/migrations/001_core_schema.ts +120 -120
  74. package/src/db/migrations/002_learning_schema.ts +38 -38
  75. package/src/db/migrations/003_code_schema.ts +53 -53
  76. package/src/db/migrations/004_synapses_schema.ts +57 -57
  77. package/src/db/migrations/005_fts_indexes.ts +78 -78
  78. package/src/db/migrations/006_synapses_phase3.ts +17 -17
  79. package/src/db/migrations/007_feedback.ts +13 -13
  80. package/src/db/migrations/008_git_integration.ts +38 -38
  81. package/src/db/migrations/009_embeddings.ts +8 -8
  82. package/src/db/repositories/antipattern.repository.ts +66 -66
  83. package/src/db/repositories/code-module.repository.ts +142 -142
  84. package/src/db/repositories/notification.repository.ts +66 -66
  85. package/src/db/repositories/project.repository.ts +93 -93
  86. package/src/db/repositories/rule.repository.ts +108 -108
  87. package/src/db/repositories/solution.repository.ts +154 -154
  88. package/src/db/repositories/synapse.repository.ts +153 -153
  89. package/src/db/repositories/terminal.repository.ts +101 -101
  90. package/src/embeddings/engine.ts +238 -217
  91. package/src/index.ts +63 -63
  92. package/src/ipc/client.ts +118 -118
  93. package/src/ipc/protocol.ts +35 -35
  94. package/src/ipc/router.ts +133 -133
  95. package/src/ipc/server.ts +176 -110
  96. package/src/learning/decay.ts +46 -46
  97. package/src/learning/pattern-extractor.ts +90 -90
  98. package/src/learning/rule-generator.ts +74 -74
  99. package/src/matching/error-matcher.ts +5 -5
  100. package/src/matching/fingerprint.ts +34 -29
  101. package/src/matching/similarity.ts +61 -61
  102. package/src/matching/tfidf.ts +74 -74
  103. package/src/matching/tokenizer.ts +41 -41
  104. package/src/mcp/auto-detect.ts +93 -93
  105. package/src/mcp/http-server.ts +140 -137
  106. package/src/mcp/server.ts +73 -73
  107. package/src/parsing/error-parser.ts +28 -28
  108. package/src/parsing/parsers/compiler.ts +93 -93
  109. package/src/parsing/parsers/generic.ts +28 -28
  110. package/src/parsing/parsers/go.ts +97 -97
  111. package/src/parsing/parsers/node.ts +69 -69
  112. package/src/parsing/parsers/python.ts +62 -62
  113. package/src/parsing/parsers/rust.ts +50 -50
  114. package/src/parsing/parsers/shell.ts +42 -42
  115. package/src/parsing/types.ts +47 -47
  116. package/src/research/gap-analyzer.ts +135 -135
  117. package/src/research/insight-generator.ts +123 -123
  118. package/src/research/research-engine.ts +116 -116
  119. package/src/research/synergy-detector.ts +126 -126
  120. package/src/research/template-extractor.ts +130 -130
  121. package/src/research/trend-analyzer.ts +127 -127
  122. package/src/services/code.service.ts +271 -238
  123. package/src/services/error.service.ts +4 -3
  124. package/src/services/git.service.ts +132 -132
  125. package/src/services/notification.service.ts +41 -41
  126. package/src/services/synapse.service.ts +59 -59
  127. package/src/services/terminal.service.ts +81 -81
  128. package/src/synapses/activation.ts +80 -80
  129. package/src/synapses/decay.ts +38 -38
  130. package/src/synapses/hebbian.ts +69 -69
  131. package/src/synapses/pathfinder.ts +81 -81
  132. package/src/synapses/synapse-manager.ts +109 -109
  133. package/src/types/code.types.ts +52 -52
  134. package/src/types/error.types.ts +67 -67
  135. package/src/types/ipc.types.ts +8 -8
  136. package/src/types/mcp.types.ts +53 -53
  137. package/src/types/research.types.ts +28 -28
  138. package/src/types/solution.types.ts +30 -30
  139. package/src/utils/events.ts +45 -45
  140. package/src/utils/hash.ts +5 -5
  141. package/src/utils/logger.ts +48 -48
  142. package/src/utils/paths.ts +19 -19
  143. package/tests/e2e/test_code_intelligence.py +1015 -0
  144. package/tests/e2e/test_error_memory.py +451 -0
  145. package/tests/e2e/test_full_integration.py +534 -0
  146. package/tests/fixtures/code-modules/modules.ts +83 -83
  147. package/tests/fixtures/errors/go.ts +9 -9
  148. package/tests/fixtures/errors/node.ts +24 -24
  149. package/tests/fixtures/errors/python.ts +21 -21
  150. package/tests/fixtures/errors/rust.ts +25 -25
  151. package/tests/fixtures/errors/shell.ts +15 -15
  152. package/tests/fixtures/solutions/solutions.ts +27 -27
  153. package/tests/helpers/setup-db.ts +52 -52
  154. package/tests/integration/code-flow.test.ts +86 -86
  155. package/tests/integration/error-flow.test.ts +83 -83
  156. package/tests/integration/ipc-flow.test.ts +166 -166
  157. package/tests/integration/learning-cycle.test.ts +82 -82
  158. package/tests/integration/synapse-flow.test.ts +117 -117
  159. package/tests/unit/code/analyzer.test.ts +58 -58
  160. package/tests/unit/code/fingerprint.test.ts +51 -51
  161. package/tests/unit/code/scorer.test.ts +55 -55
  162. package/tests/unit/learning/confidence-scorer.test.ts +60 -60
  163. package/tests/unit/learning/decay.test.ts +45 -45
  164. package/tests/unit/learning/pattern-extractor.test.ts +50 -50
  165. package/tests/unit/matching/error-matcher.test.ts +69 -69
  166. package/tests/unit/matching/fingerprint.test.ts +47 -47
  167. package/tests/unit/matching/similarity.test.ts +65 -65
  168. package/tests/unit/matching/tfidf.test.ts +71 -71
  169. package/tests/unit/matching/tokenizer.test.ts +83 -83
  170. package/tests/unit/parsing/parsers.test.ts +113 -113
  171. package/tests/unit/research/gap-analyzer.test.ts +45 -45
  172. package/tests/unit/research/trend-analyzer.test.ts +45 -45
  173. package/tests/unit/synapses/activation.test.ts +80 -80
  174. package/tests/unit/synapses/decay.test.ts +27 -27
  175. package/tests/unit/synapses/hebbian.test.ts +96 -96
  176. package/tests/unit/synapses/pathfinder.test.ts +72 -72
  177. package/tsconfig.json +18 -18
@@ -64,8 +64,42 @@ export function dashboardCommand() {
64
64
  const outPath = opts.output
65
65
  ? resolve(opts.output)
66
66
  : resolve(import.meta.dirname, '../../../dashboard.html');
67
- writeFileSync(outPath, html, 'utf-8');
67
+ // Inject live SSE connection for --live mode
68
+ let finalHtml = html;
69
+ if (opts.live) {
70
+ const apiPort = opts.port || '7777';
71
+ const sseScript = `
72
+ <script>
73
+ (function(){
74
+ const evtSource = new EventSource('http://localhost:${apiPort}/api/v1/events');
75
+ evtSource.onmessage = function(e) {
76
+ try {
77
+ const data = JSON.parse(e.data);
78
+ if (data.type === 'stats_update') {
79
+ document.querySelectorAll('.stat-card').forEach(card => {
80
+ const label = card.querySelector('.stat-label')?.textContent?.toLowerCase();
81
+ const num = card.querySelector('.stat-number');
82
+ if (label && num && data.stats[label] !== undefined) {
83
+ num.textContent = Number(data.stats[label]).toLocaleString();
84
+ }
85
+ });
86
+ }
87
+ if (data.type === 'event') {
88
+ const dot = document.querySelector('.activity-dot');
89
+ if (dot) { dot.style.background = '#ff5577'; setTimeout(() => dot.style.background = '', 500); }
90
+ }
91
+ } catch {}
92
+ };
93
+ evtSource.onerror = function() { setTimeout(() => location.reload(), 5000); };
94
+ })();
95
+ </script>`;
96
+ finalHtml = html.replace('</body>', sseScript + '</body>');
97
+ }
98
+ writeFileSync(outPath, finalHtml, 'utf-8');
68
99
  console.log(`${icons.ok} ${c.success('Dashboard written to')} ${c.dim(outPath)}`);
100
+ if (opts.live) {
101
+ console.log(` ${c.info('Live mode:')} Connected to Brain daemon SSE on port ${opts.port || 7777}`);
102
+ }
69
103
  console.log(` ${c.label('Modules:')} ${c.value(data.stats.modules)} ${c.label('Synapses:')} ${c.value(data.stats.synapses)} ${c.label('Insights:')} ${c.value(data.stats.insights)}`);
70
104
  if (opts.open !== false) {
71
105
  const { exec } = await import('child_process');
@@ -97,577 +131,577 @@ function generateHtml(data) {
97
131
  }
98
132
  const totalKnowledge = stats.modules + stats.synapses + stats.errors + stats.solutions;
99
133
  const activityLevel = Math.min(100, Math.round((stats.insights / Math.max(1, totalKnowledge)) * 1000));
100
- return `<!DOCTYPE html>
101
- <html lang="de">
102
- <head>
103
- <meta charset="UTF-8">
104
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
105
- <title>Brain — Dashboard</title>
106
- <style>
107
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
108
- *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
109
- :root{
110
- --bg:#04060e;--bg2:rgba(10,12,24,.7);--bg3:rgba(20,24,50,.6);--bg4:rgba(30,35,70,.5);
111
- --glass:rgba(15,18,40,.55);--glass-border:rgba(100,120,255,.12);--glass-hover:rgba(100,120,255,.2);
112
- --text:#e8eaf6;--text2:#8b8fb0;--text3:#4a4d6e;
113
- --blue:#5b9cff;--red:#ff5577;--green:#3dffa0;
114
- --purple:#b47aff;--orange:#ffb347;--cyan:#47e5ff;
115
- --accent:linear-gradient(135deg,#b47aff,#5b9cff,#47e5ff);
116
- --radius:16px;--radius-sm:10px;
117
- }
118
- html{scroll-behavior:smooth}
119
- body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.6;min-height:100vh;overflow-x:hidden}
120
-
121
- /* Neural canvas background */
122
- #neural-bg{position:fixed;top:0;left:0;width:100%;height:100%;z-index:0;pointer-events:none}
123
-
124
- /* Ambient glow orbs */
125
- .orb{position:fixed;border-radius:50%;filter:blur(120px);opacity:.12;pointer-events:none;z-index:0}
126
- .orb-1{width:600px;height:600px;background:var(--purple);top:-200px;left:-100px;animation:orb-float 20s ease-in-out infinite}
127
- .orb-2{width:500px;height:500px;background:var(--blue);bottom:-150px;right:-100px;animation:orb-float 25s ease-in-out infinite reverse}
128
- .orb-3{width:400px;height:400px;background:var(--cyan);top:40%;left:50%;animation:orb-float 18s ease-in-out infinite 5s}
129
- @keyframes orb-float{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(60px,-40px) scale(1.1)}66%{transform:translate(-40px,60px) scale(.9)}}
130
-
131
- .container{max-width:1400px;margin:0 auto;padding:0 28px;position:relative;z-index:1}
132
-
133
- /* Reveal animations */
134
- .reveal{opacity:0;transform:translateY(30px);transition:opacity .6s ease,transform .6s ease}
135
- .reveal.visible{opacity:1;transform:translateY(0)}
136
- .reveal-delay-1{transition-delay:.1s}.reveal-delay-2{transition-delay:.2s}
137
- .reveal-delay-3{transition-delay:.3s}.reveal-delay-4{transition-delay:.4s}
138
- .reveal-delay-5{transition-delay:.5s}
139
-
140
- section{margin-bottom:56px}
141
-
142
- /* Header */
143
- header{padding:60px 0 24px;text-align:center;position:relative}
144
- .logo{display:flex;align-items:center;justify-content:center;gap:20px;margin-bottom:12px}
145
- .logo-icon{
146
- width:68px;height:68px;border-radius:18px;
147
- background:linear-gradient(135deg,var(--purple),var(--blue),var(--cyan));
148
- display:flex;align-items:center;justify-content:center;font-size:32px;
149
- box-shadow:0 0 60px rgba(170,102,255,.35),0 0 120px rgba(90,150,255,.15);
150
- animation:icon-breathe 4s ease-in-out infinite;
151
- position:relative;
152
- }
153
- .logo-icon::after{
154
- content:'';position:absolute;inset:-3px;border-radius:20px;
155
- background:linear-gradient(135deg,var(--purple),var(--cyan));
156
- opacity:.4;filter:blur(8px);z-index:-1;animation:icon-breathe 4s ease-in-out infinite reverse;
157
- }
158
- @keyframes icon-breathe{0%,100%{box-shadow:0 0 60px rgba(170,102,255,.35),0 0 120px rgba(90,150,255,.15)}50%{box-shadow:0 0 80px rgba(170,102,255,.5),0 0 160px rgba(90,150,255,.25)}}
159
- .logo h1{font-size:2.8rem;font-weight:900;letter-spacing:-1px;background:linear-gradient(135deg,#fff 0%,var(--blue) 50%,var(--purple) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
160
- .tagline{color:var(--text2);font-size:1.05rem;font-weight:300;letter-spacing:.5px}
161
-
162
- /* Activity indicator */
163
- .activity{display:inline-flex;align-items:center;gap:10px;margin-top:16px;padding:8px 20px;border-radius:30px;background:var(--glass);border:1px solid var(--glass-border);backdrop-filter:blur(20px)}
164
- .activity-dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 12px var(--green);animation:pulse-dot 2s ease-in-out infinite}
165
- @keyframes pulse-dot{0%,100%{opacity:1;box-shadow:0 0 12px var(--green)}50%{opacity:.5;box-shadow:0 0 20px var(--green)}}
166
- .activity-text{font-size:.8rem;color:var(--text2);font-weight:500}
167
- .activity-bar{width:80px;height:4px;border-radius:2px;background:var(--bg4);overflow:hidden}
168
- .activity-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,var(--green),var(--cyan));transition:width 1.5s ease}
169
-
170
- /* Nav */
171
- nav{display:flex;justify-content:center;gap:8px;flex-wrap:wrap;padding:20px 0;margin-bottom:40px}
172
- nav a{
173
- color:var(--text2);text-decoration:none;padding:8px 18px;border-radius:24px;font-size:.85rem;font-weight:500;
174
- transition:all .3s ease;border:1px solid transparent;backdrop-filter:blur(10px);
175
- }
176
- nav a:hover{color:var(--text);background:var(--glass);border-color:var(--glass-border);transform:translateY(-1px)}
177
- nav a.research{
178
- background:var(--glass);color:var(--cyan);border-color:rgba(71,229,255,.25);font-weight:600;
179
- box-shadow:0 0 20px rgba(71,229,255,.1);animation:nav-glow 3s ease-in-out infinite alternate;
180
- }
181
- @keyframes nav-glow{0%{box-shadow:0 0 20px rgba(71,229,255,.1)}100%{box-shadow:0 0 35px rgba(71,229,255,.2)}}
182
-
183
- /* Stats */
184
- .stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:18px}
185
- .stat-card{
186
- background:var(--glass);border:1px solid var(--glass-border);border-radius:var(--radius);
187
- padding:28px 22px;text-align:center;position:relative;overflow:hidden;
188
- transition:all .35s ease;backdrop-filter:blur(20px);
189
- }
190
- .stat-card:hover{transform:translateY(-4px);border-color:var(--glass-hover);box-shadow:0 20px 60px rgba(0,0,0,.3)}
191
- .stat-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px}
192
- .stat-card::after{content:'';position:absolute;top:0;left:0;right:0;bottom:0;background:radial-gradient(ellipse at 50% 0%,rgba(255,255,255,.03),transparent 70%);pointer-events:none}
193
- .stat-card.blue::before{background:linear-gradient(90deg,transparent,var(--blue),transparent)}
194
- .stat-card.purple::before{background:linear-gradient(90deg,transparent,var(--purple),transparent)}
195
- .stat-card.red::before{background:linear-gradient(90deg,transparent,var(--red),transparent)}
196
- .stat-card.green::before{background:linear-gradient(90deg,transparent,var(--green),transparent)}
197
- .stat-card.orange::before{background:linear-gradient(90deg,transparent,var(--orange),transparent)}
198
- .stat-card.cyan::before{background:linear-gradient(90deg,transparent,var(--cyan),transparent)}
199
- .stat-number{font-size:2.6rem;font-weight:900;letter-spacing:-2px}
200
- .stat-card.blue .stat-number{color:var(--blue)}.stat-card.purple .stat-number{color:var(--purple)}
201
- .stat-card.red .stat-number{color:var(--red)}.stat-card.green .stat-number{color:var(--green)}
202
- .stat-card.orange .stat-number{color:var(--orange)}.stat-card.cyan .stat-number{color:var(--cyan)}
203
- .stat-label{color:var(--text2);font-size:.82rem;margin-top:6px;font-weight:500;letter-spacing:.3px;text-transform:uppercase}
204
-
205
- /* Section titles */
206
- .section-title{font-size:1.5rem;font-weight:700;margin-bottom:24px;display:flex;align-items:center;gap:12px}
207
- .section-title .icon{font-size:1.2rem;width:38px;height:38px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;backdrop-filter:blur(10px)}
208
-
209
- /* Language chart */
210
- .lang-chart{max-width:650px}
211
- .lang-row{display:flex;align-items:center;gap:14px;margin-bottom:10px}
212
- .lang-name{width:100px;text-align:right;font-size:.85rem;color:var(--text2);font-weight:500}
213
- .lang-bar-bg{flex:1;height:28px;background:var(--bg3);border-radius:6px;overflow:hidden;border:1px solid var(--glass-border)}
214
- .lang-bar{height:100%;background:var(--accent);border-radius:6px;width:0;transition:width 1.2s cubic-bezier(.22,1,.36,1)}
215
- .lang-count{width:50px;font-size:.85rem;color:var(--text2);font-weight:600}
216
-
217
- /* Insight tabs */
218
- .tab-bar{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:24px}
219
- .tab-btn{
220
- padding:10px 20px;border-radius:24px;border:1px solid var(--glass-border);
221
- background:var(--glass);color:var(--text2);cursor:pointer;font-size:.85rem;font-weight:500;
222
- transition:all .3s ease;backdrop-filter:blur(10px);font-family:inherit;
223
- }
224
- .tab-btn:hover{border-color:var(--glass-hover);color:var(--text);transform:translateY(-1px)}
225
- .tab-btn.active{border-color:rgba(71,229,255,.35);color:var(--cyan);background:rgba(71,229,255,.08);box-shadow:0 0 20px rgba(71,229,255,.1)}
226
- .tab-btn .count{background:var(--bg4);padding:2px 8px;border-radius:12px;font-size:.72rem;margin-left:6px;font-weight:600}
227
- .tab-panel{display:none}.tab-panel.active{display:block}
228
- .insight-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(380px,1fr));gap:14px}
229
- .insight-card{
230
- background:var(--glass);border:1px solid var(--glass-border);border-radius:var(--radius-sm);
231
- padding:18px;border-left:3px solid var(--text3);transition:all .25s ease;backdrop-filter:blur(20px);
232
- }
233
- .insight-card:hover{transform:translateX(6px);border-color:var(--glass-hover);box-shadow:0 8px 30px rgba(0,0,0,.2)}
234
- .insight-card.cyan{border-left-color:var(--cyan)}.insight-card.orange{border-left-color:var(--orange)}
235
- .insight-card.green{border-left-color:var(--green)}.insight-card.red{border-left-color:var(--red)}
236
- .insight-card.purple{border-left-color:var(--purple)}.insight-card.blue{border-left-color:var(--blue)}
237
- .insight-header{display:flex;align-items:center;gap:8px;margin-bottom:8px;flex-wrap:wrap}
238
- .insight-card p{color:var(--text2);font-size:.85rem;line-height:1.5}
239
- .prio{font-size:.68rem;padding:3px 10px;border-radius:12px;text-transform:uppercase;font-weight:700;letter-spacing:.5px}
240
- .prio-critical{background:rgba(255,85,119,.15);color:var(--red);border:1px solid rgba(255,85,119,.25)}
241
- .prio-high{background:rgba(255,179,71,.15);color:var(--orange);border:1px solid rgba(255,179,71,.25)}
242
- .prio-medium{background:rgba(91,156,255,.15);color:var(--blue);border:1px solid rgba(91,156,255,.25)}
243
- .prio-low{background:rgba(139,143,176,.1);color:var(--text2);border:1px solid rgba(139,143,176,.2)}
244
- .empty{color:var(--text3);font-style:italic;padding:24px}
245
-
246
- /* Graph */
247
- .graph-container{position:relative;background:var(--glass);border:1px solid var(--glass-border);border-radius:var(--radius);overflow:hidden;backdrop-filter:blur(20px)}
248
- #synapse-graph{width:100%;height:500px;display:block;cursor:grab}
249
- #synapse-graph:active{cursor:grabbing}
250
- .graph-legend{display:flex;gap:16px;flex-wrap:wrap;padding:12px 20px;border-top:1px solid var(--glass-border);font-size:.8rem;color:var(--text2)}
251
- .legend-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:6px;vertical-align:middle}
252
- .graph-tooltip{position:absolute;display:none;background:var(--bg2);border:1px solid var(--glass-border);border-radius:8px;padding:8px 14px;font-size:.8rem;color:var(--text);pointer-events:none;z-index:10;backdrop-filter:blur(20px);box-shadow:0 8px 30px rgba(0,0,0,.3)}
253
-
254
- /* Footer */
255
- footer{text-align:center;padding:40px 0;border-top:1px solid var(--glass-border)}
256
- footer p{color:var(--text3);font-size:.8rem}
257
- footer code{background:var(--glass);padding:3px 10px;border-radius:6px;font-size:.78rem;border:1px solid var(--glass-border)}
258
-
259
- /* Responsive */
260
- @media(max-width:600px){.stats-grid{grid-template-columns:1fr 1fr}.insight-grid{grid-template-columns:1fr}.logo h1{font-size:2rem}}
261
- </style>
262
- </head>
263
- <body>
264
-
265
- <canvas id="neural-bg"></canvas>
266
- <div class="orb orb-1"></div>
267
- <div class="orb orb-2"></div>
268
- <div class="orb orb-3"></div>
269
-
270
- <div class="container">
271
- <header class="reveal">
272
- <div class="logo">
273
- <div class="logo-icon">&#129504;</div>
274
- <h1>Brain</h1>
275
- </div>
276
- <p class="tagline">Adaptive Code Intelligence</p>
277
- <div class="activity">
278
- <span class="activity-dot"></span>
279
- <span class="activity-text">Neural Activity</span>
280
- <div class="activity-bar"><div class="activity-fill" style="width:0%" data-target="${activityLevel}"></div></div>
281
- <span class="activity-text" style="color:var(--cyan);font-weight:700">${activityLevel}%</span>
282
- </div>
283
- </header>
284
-
285
- <nav class="reveal reveal-delay-1">
286
- <a href="#stats">Stats</a>
287
- <a href="#languages">Languages</a>
288
- <a href="#network">&#128300; Network</a>
289
- <a href="#research" class="research">&#128161; Research</a>
290
- </nav>
291
-
292
- <section id="stats" class="reveal reveal-delay-2">
293
- <div class="section-title"><div class="icon" style="background:rgba(91,156,255,.1)">&#128202;</div> Neural Status</div>
294
- <div class="stats-grid">
295
- <div class="stat-card blue"><div class="stat-number">${stats.modules.toLocaleString()}</div><div class="stat-label">Modules</div></div>
296
- <div class="stat-card purple"><div class="stat-number">${stats.synapses.toLocaleString()}</div><div class="stat-label">Synapses</div></div>
297
- <div class="stat-card cyan"><div class="stat-number">${stats.insights}</div><div class="stat-label">Insights</div></div>
298
- <div class="stat-card red"><div class="stat-number">${stats.errors}</div><div class="stat-label">Errors</div></div>
299
- <div class="stat-card green"><div class="stat-number">${stats.solutions}</div><div class="stat-label">Solutions</div></div>
300
- <div class="stat-card orange"><div class="stat-number">${stats.rules}</div><div class="stat-label">Rules</div></div>
301
- </div>
302
- </section>
303
-
304
- <section id="languages" class="reveal reveal-delay-3">
305
- <div class="section-title"><div class="icon" style="background:rgba(180,122,255,.1)">&#128187;</div> Languages</div>
306
- <div class="lang-chart">${langBars}</div>
307
- </section>
308
-
309
- <section id="network" class="reveal reveal-delay-4">
310
- <div class="section-title"><div class="icon" style="background:rgba(71,229,255,.1)">&#128300;</div> Synapse Network</div>
311
- <div class="graph-container">
312
- <canvas id="synapse-graph"></canvas>
313
- <div class="graph-legend">
314
- <span><span class="legend-dot" style="background:var(--blue)"></span> error</span>
315
- <span><span class="legend-dot" style="background:var(--green)"></span> solution</span>
316
- <span><span class="legend-dot" style="background:var(--purple)"></span> code_module</span>
317
- <span><span class="legend-dot" style="background:var(--orange)"></span> project</span>
318
- <span><span class="legend-dot" style="background:var(--cyan)"></span> other</span>
319
- </div>
320
- <div id="graph-tooltip" class="graph-tooltip"></div>
321
- </div>
322
- </section>
323
-
324
- <section id="research" class="reveal reveal-delay-5">
325
- <div class="section-title"><div class="icon" style="background:rgba(71,229,255,.1)">&#128300;</div> Research Insights</div>
326
- <div class="tab-bar">
327
- <button class="tab-btn active" data-tab="templates">&#127912; Templates <span class="count">${insights.templates.length}</span></button>
328
- <button class="tab-btn" data-tab="suggestions">&#128161; Suggestions <span class="count">${insights.suggestions.length}</span></button>
329
- <button class="tab-btn" data-tab="trends">&#128200; Trends <span class="count">${insights.trends.length}</span></button>
330
- <button class="tab-btn" data-tab="gaps">&#9888;&#65039; Gaps <span class="count">${insights.gaps.length}</span></button>
331
- <button class="tab-btn" data-tab="synergies">&#9889; Synergies <span class="count">${insights.synergies.length}</span></button>
332
- <button class="tab-btn" data-tab="warnings">&#128680; Warnings <span class="count">${insights.warnings.length}</span></button>
333
- </div>
334
- <div class="tab-panel active" id="tab-templates"><div class="insight-grid">${insightCards(insights.templates, 'cyan')}</div></div>
335
- <div class="tab-panel" id="tab-suggestions"><div class="insight-grid">${insightCards(insights.suggestions, 'orange')}</div></div>
336
- <div class="tab-panel" id="tab-trends"><div class="insight-grid">${insightCards(insights.trends, 'green')}</div></div>
337
- <div class="tab-panel" id="tab-gaps"><div class="insight-grid">${insightCards(insights.gaps, 'red')}</div></div>
338
- <div class="tab-panel" id="tab-synergies"><div class="insight-grid">${insightCards(insights.synergies, 'purple')}</div></div>
339
- <div class="tab-panel" id="tab-warnings"><div class="insight-grid">${insightCards(insights.warnings, 'red')}</div></div>
340
- </section>
341
-
342
- <footer class="reveal reveal-delay-5">
343
- <p>Brain v1.0 &mdash; <code>brain dashboard</code></p>
344
- </footer>
345
- </div>
346
-
347
- <script>
348
- // --- Neural Network Canvas ---
349
- (function(){
350
- const canvas = document.getElementById('neural-bg');
351
- const ctx = canvas.getContext('2d');
352
- let W, H, nodes = [], mouse = {x:-1000,y:-1000};
353
-
354
- function resize(){
355
- W = canvas.width = window.innerWidth;
356
- H = canvas.height = window.innerHeight;
357
- }
358
- resize();
359
- window.addEventListener('resize', resize);
360
- document.addEventListener('mousemove', e => { mouse.x = e.clientX; mouse.y = e.clientY; });
361
-
362
- const NODE_COUNT = Math.min(80, Math.floor(window.innerWidth / 18));
363
- const CONNECT_DIST = 180;
364
- const MOUSE_DIST = 200;
365
-
366
- for(let i = 0; i < NODE_COUNT; i++){
367
- nodes.push({
368
- x: Math.random() * W,
369
- y: Math.random() * H,
370
- vx: (Math.random() - 0.5) * 0.4,
371
- vy: (Math.random() - 0.5) * 0.4,
372
- r: Math.random() * 2 + 1,
373
- pulse: Math.random() * Math.PI * 2,
374
- });
375
- }
376
-
377
- function draw(){
378
- ctx.clearRect(0, 0, W, H);
379
-
380
- // Draw connections
381
- for(let i = 0; i < nodes.length; i++){
382
- for(let j = i + 1; j < nodes.length; j++){
383
- const dx = nodes[i].x - nodes[j].x;
384
- const dy = nodes[i].y - nodes[j].y;
385
- const dist = Math.sqrt(dx*dx + dy*dy);
386
- if(dist < CONNECT_DIST){
387
- const alpha = (1 - dist / CONNECT_DIST) * 0.15;
388
- ctx.strokeStyle = 'rgba(91,156,255,' + alpha + ')';
389
- ctx.lineWidth = 0.5;
390
- ctx.beginPath();
391
- ctx.moveTo(nodes[i].x, nodes[i].y);
392
- ctx.lineTo(nodes[j].x, nodes[j].y);
393
- ctx.stroke();
394
- }
395
- }
396
-
397
- // Mouse interaction
398
- const mdx = nodes[i].x - mouse.x;
399
- const mdy = nodes[i].y - mouse.y;
400
- const mDist = Math.sqrt(mdx*mdx + mdy*mdy);
401
- if(mDist < MOUSE_DIST){
402
- const alpha = (1 - mDist / MOUSE_DIST) * 0.4;
403
- ctx.strokeStyle = 'rgba(180,122,255,' + alpha + ')';
404
- ctx.lineWidth = 1;
405
- ctx.beginPath();
406
- ctx.moveTo(nodes[i].x, nodes[i].y);
407
- ctx.lineTo(mouse.x, mouse.y);
408
- ctx.stroke();
409
- }
410
- }
411
-
412
- // Draw nodes
413
- const time = Date.now() * 0.001;
414
- for(const n of nodes){
415
- const glow = 0.4 + Math.sin(time * 1.5 + n.pulse) * 0.3;
416
- ctx.fillStyle = 'rgba(91,156,255,' + glow + ')';
417
- ctx.beginPath();
418
- ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
419
- ctx.fill();
420
-
421
- n.x += n.vx;
422
- n.y += n.vy;
423
- if(n.x < 0 || n.x > W) n.vx *= -1;
424
- if(n.y < 0 || n.y > H) n.vy *= -1;
425
- }
426
-
427
- requestAnimationFrame(draw);
428
- }
429
- draw();
430
- })();
431
-
432
- // --- Reveal on scroll ---
433
- const observer = new IntersectionObserver(entries => {
434
- entries.forEach(e => { if(e.isIntersecting) e.target.classList.add('visible'); });
435
- }, {threshold: 0.1});
436
- document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
437
-
438
- // --- Tab switching ---
439
- document.querySelectorAll('.tab-btn').forEach(btn => {
440
- btn.addEventListener('click', () => {
441
- document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
442
- document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
443
- btn.classList.add('active');
444
- document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
445
- });
446
- });
447
-
448
- // --- Animate stat numbers ---
449
- const numObserver = new IntersectionObserver(entries => {
450
- entries.forEach(e => {
451
- if(!e.isIntersecting) return;
452
- const el = e.target;
453
- if(el.dataset.animated) return;
454
- el.dataset.animated = '1';
455
- const target = parseInt(el.textContent.replace(/\\D/g,''), 10);
456
- if(isNaN(target) || target === 0) return;
457
- const duration = 1200;
458
- const start = performance.now();
459
- function tick(now){
460
- const t = Math.min((now - start) / duration, 1);
461
- const ease = 1 - Math.pow(1 - t, 3);
462
- el.textContent = Math.round(target * ease).toLocaleString();
463
- if(t < 1) requestAnimationFrame(tick);
464
- }
465
- requestAnimationFrame(tick);
466
- });
467
- }, {threshold: 0.5});
468
- document.querySelectorAll('.stat-number').forEach(el => numObserver.observe(el));
469
-
470
- // --- Animate language bars ---
471
- setTimeout(() => {
472
- document.querySelectorAll('.lang-bar').forEach(bar => {
473
- bar.style.width = bar.dataset.width + '%';
474
- });
475
- }, 300);
476
-
477
- // --- Activity bar ---
478
- setTimeout(() => {
479
- document.querySelectorAll('.activity-fill').forEach(el => {
480
- el.style.width = el.dataset.target + '%';
481
- });
482
- }, 500);
483
-
484
- // --- Synapse Force-Directed Graph ---
485
- (function(){
486
- const edges = ${JSON.stringify(synapseEdges.map((e) => ({ s: e.source, t: e.target, type: e.type, w: e.weight })))};
487
- const canvas = document.getElementById('synapse-graph');
488
- if (!canvas || !edges.length) return;
489
- const ctx = canvas.getContext('2d');
490
- const container = canvas.parentElement;
491
- let W, H, dpr;
492
-
493
- const NODE_COLORS = {
494
- error: '#ff5577', solution: '#3dffa0', code_module: '#b47aff',
495
- project: '#ffb347', rule: '#5b9cff', antipattern: '#ff5577'
496
- };
497
- const DEFAULT_COLOR = '#47e5ff';
498
-
499
- // Build graph nodes & edges
500
- const nodeMap = new Map();
501
- const graphEdges = [];
502
- for (const e of edges) {
503
- if (!nodeMap.has(e.s)) nodeMap.set(e.s, { id: e.s, type: e.s.split(':')[0], x: 0, y: 0, vx: 0, vy: 0, connections: 0 });
504
- if (!nodeMap.has(e.t)) nodeMap.set(e.t, { id: e.t, type: e.t.split(':')[0], x: 0, y: 0, vx: 0, vy: 0, connections: 0 });
505
- nodeMap.get(e.s).connections++;
506
- nodeMap.get(e.t).connections++;
507
- graphEdges.push({ source: nodeMap.get(e.s), target: nodeMap.get(e.t), type: e.type, weight: e.w });
508
- }
509
- const nodes = [...nodeMap.values()];
510
-
511
- function resize() {
512
- dpr = window.devicePixelRatio || 1;
513
- W = container.clientWidth;
514
- H = 500;
515
- canvas.width = W * dpr;
516
- canvas.height = H * dpr;
517
- canvas.style.width = W + 'px';
518
- canvas.style.height = H + 'px';
519
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
520
- }
521
- resize();
522
- window.addEventListener('resize', resize);
523
-
524
- // Random initial positions
525
- for (const n of nodes) {
526
- n.x = W * 0.2 + Math.random() * W * 0.6;
527
- n.y = H * 0.2 + Math.random() * H * 0.6;
528
- }
529
-
530
- // Force simulation
531
- const REPULSION = 3000;
532
- const ATTRACTION = 0.008;
533
- const DAMPING = 0.85;
534
- const CENTER_GRAVITY = 0.002;
535
- let hovered = null;
536
- let dragging = null;
537
- let dragOff = {x:0,y:0};
538
-
539
- function simulate() {
540
- // Repulsion
541
- for (let i = 0; i < nodes.length; i++) {
542
- for (let j = i + 1; j < nodes.length; j++) {
543
- let dx = nodes[i].x - nodes[j].x;
544
- let dy = nodes[i].y - nodes[j].y;
545
- let dist = Math.sqrt(dx*dx + dy*dy) || 1;
546
- let force = REPULSION / (dist * dist);
547
- let fx = (dx / dist) * force;
548
- let fy = (dy / dist) * force;
549
- nodes[i].vx += fx; nodes[i].vy += fy;
550
- nodes[j].vx -= fx; nodes[j].vy -= fy;
551
- }
552
- }
553
- // Attraction along edges
554
- for (const e of graphEdges) {
555
- let dx = e.target.x - e.source.x;
556
- let dy = e.target.y - e.source.y;
557
- let dist = Math.sqrt(dx*dx + dy*dy) || 1;
558
- let force = (dist - 100) * ATTRACTION * e.weight;
559
- let fx = (dx / dist) * force;
560
- let fy = (dy / dist) * force;
561
- e.source.vx += fx; e.source.vy += fy;
562
- e.target.vx -= fx; e.target.vy -= fy;
563
- }
564
- // Center gravity
565
- for (const n of nodes) {
566
- n.vx += (W/2 - n.x) * CENTER_GRAVITY;
567
- n.vy += (H/2 - n.y) * CENTER_GRAVITY;
568
- }
569
- // Apply & damp
570
- for (const n of nodes) {
571
- if (n === dragging) continue;
572
- n.vx *= DAMPING; n.vy *= DAMPING;
573
- n.x += n.vx; n.y += n.vy;
574
- n.x = Math.max(20, Math.min(W - 20, n.x));
575
- n.y = Math.max(20, Math.min(H - 20, n.y));
576
- }
577
- }
578
-
579
- function getNodeRadius(n) { return Math.min(16, 5 + n.connections * 1.5); }
580
-
581
- function draw() {
582
- ctx.clearRect(0, 0, W, H);
583
- // Edges
584
- for (const e of graphEdges) {
585
- const alpha = 0.15 + e.weight * 0.5;
586
- ctx.strokeStyle = 'rgba(91,156,255,' + Math.min(0.8, alpha) + ')';
587
- ctx.lineWidth = 0.5 + e.weight * 2;
588
- ctx.beginPath();
589
- ctx.moveTo(e.source.x, e.source.y);
590
- ctx.lineTo(e.target.x, e.target.y);
591
- ctx.stroke();
592
- }
593
- // Nodes
594
- for (const n of nodes) {
595
- const r = getNodeRadius(n);
596
- const color = NODE_COLORS[n.type] || DEFAULT_COLOR;
597
- const isHover = n === hovered || n === dragging;
598
- // Glow
599
- if (isHover) {
600
- ctx.shadowColor = color;
601
- ctx.shadowBlur = 20;
602
- }
603
- ctx.fillStyle = color;
604
- ctx.globalAlpha = isHover ? 1 : 0.8;
605
- ctx.beginPath();
606
- ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
607
- ctx.fill();
608
- ctx.globalAlpha = 1;
609
- ctx.shadowBlur = 0;
610
- // Label for hovered or large nodes
611
- if (isHover || n.connections >= 4) {
612
- ctx.fillStyle = '#e8eaf6';
613
- ctx.font = (isHover ? 'bold ' : '') + '11px Inter, system-ui, sans-serif';
614
- ctx.textAlign = 'center';
615
- ctx.fillText(n.id, n.x, n.y - r - 6);
616
- }
617
- }
618
- simulate();
619
- requestAnimationFrame(draw);
620
- }
621
- draw();
622
-
623
- // Interaction
624
- const tooltip = document.getElementById('graph-tooltip');
625
- function getNodeAt(mx, my) {
626
- for (let i = nodes.length - 1; i >= 0; i--) {
627
- const n = nodes[i], r = getNodeRadius(n);
628
- if (Math.hypot(mx - n.x, my - n.y) <= r + 4) return n;
629
- }
630
- return null;
631
- }
632
- function getPos(e) {
633
- const rect = canvas.getBoundingClientRect();
634
- return { x: e.clientX - rect.left, y: e.clientY - rect.top };
635
- }
636
- canvas.addEventListener('mousemove', function(e) {
637
- const p = getPos(e);
638
- if (dragging) {
639
- dragging.x = p.x + dragOff.x;
640
- dragging.y = p.y + dragOff.y;
641
- dragging.vx = 0; dragging.vy = 0;
642
- return;
643
- }
644
- const n = getNodeAt(p.x, p.y);
645
- hovered = n;
646
- canvas.style.cursor = n ? 'pointer' : 'grab';
647
- if (n) {
648
- const conns = graphEdges.filter(e => e.source === n || e.target === n);
649
- tooltip.innerHTML = '<strong>' + n.id + '</strong><br>' + conns.length + ' connections';
650
- tooltip.style.display = 'block';
651
- tooltip.style.left = (p.x + 15) + 'px';
652
- tooltip.style.top = (p.y - 10) + 'px';
653
- } else {
654
- tooltip.style.display = 'none';
655
- }
656
- });
657
- canvas.addEventListener('mousedown', function(e) {
658
- const p = getPos(e);
659
- const n = getNodeAt(p.x, p.y);
660
- if (n) {
661
- dragging = n;
662
- dragOff = { x: n.x - p.x, y: n.y - p.y };
663
- canvas.style.cursor = 'grabbing';
664
- }
665
- });
666
- canvas.addEventListener('mouseup', function() { dragging = null; });
667
- canvas.addEventListener('mouseleave', function() { dragging = null; hovered = null; tooltip.style.display = 'none'; });
668
- })();
669
- </script>
670
- </body>
134
+ return `<!DOCTYPE html>
135
+ <html lang="de">
136
+ <head>
137
+ <meta charset="UTF-8">
138
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
139
+ <title>Brain — Dashboard</title>
140
+ <style>
141
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
142
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
143
+ :root{
144
+ --bg:#04060e;--bg2:rgba(10,12,24,.7);--bg3:rgba(20,24,50,.6);--bg4:rgba(30,35,70,.5);
145
+ --glass:rgba(15,18,40,.55);--glass-border:rgba(100,120,255,.12);--glass-hover:rgba(100,120,255,.2);
146
+ --text:#e8eaf6;--text2:#8b8fb0;--text3:#4a4d6e;
147
+ --blue:#5b9cff;--red:#ff5577;--green:#3dffa0;
148
+ --purple:#b47aff;--orange:#ffb347;--cyan:#47e5ff;
149
+ --accent:linear-gradient(135deg,#b47aff,#5b9cff,#47e5ff);
150
+ --radius:16px;--radius-sm:10px;
151
+ }
152
+ html{scroll-behavior:smooth}
153
+ body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.6;min-height:100vh;overflow-x:hidden}
154
+
155
+ /* Neural canvas background */
156
+ #neural-bg{position:fixed;top:0;left:0;width:100%;height:100%;z-index:0;pointer-events:none}
157
+
158
+ /* Ambient glow orbs */
159
+ .orb{position:fixed;border-radius:50%;filter:blur(120px);opacity:.12;pointer-events:none;z-index:0}
160
+ .orb-1{width:600px;height:600px;background:var(--purple);top:-200px;left:-100px;animation:orb-float 20s ease-in-out infinite}
161
+ .orb-2{width:500px;height:500px;background:var(--blue);bottom:-150px;right:-100px;animation:orb-float 25s ease-in-out infinite reverse}
162
+ .orb-3{width:400px;height:400px;background:var(--cyan);top:40%;left:50%;animation:orb-float 18s ease-in-out infinite 5s}
163
+ @keyframes orb-float{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(60px,-40px) scale(1.1)}66%{transform:translate(-40px,60px) scale(.9)}}
164
+
165
+ .container{max-width:1400px;margin:0 auto;padding:0 28px;position:relative;z-index:1}
166
+
167
+ /* Reveal animations */
168
+ .reveal{opacity:0;transform:translateY(30px);transition:opacity .6s ease,transform .6s ease}
169
+ .reveal.visible{opacity:1;transform:translateY(0)}
170
+ .reveal-delay-1{transition-delay:.1s}.reveal-delay-2{transition-delay:.2s}
171
+ .reveal-delay-3{transition-delay:.3s}.reveal-delay-4{transition-delay:.4s}
172
+ .reveal-delay-5{transition-delay:.5s}
173
+
174
+ section{margin-bottom:56px}
175
+
176
+ /* Header */
177
+ header{padding:60px 0 24px;text-align:center;position:relative}
178
+ .logo{display:flex;align-items:center;justify-content:center;gap:20px;margin-bottom:12px}
179
+ .logo-icon{
180
+ width:68px;height:68px;border-radius:18px;
181
+ background:linear-gradient(135deg,var(--purple),var(--blue),var(--cyan));
182
+ display:flex;align-items:center;justify-content:center;font-size:32px;
183
+ box-shadow:0 0 60px rgba(170,102,255,.35),0 0 120px rgba(90,150,255,.15);
184
+ animation:icon-breathe 4s ease-in-out infinite;
185
+ position:relative;
186
+ }
187
+ .logo-icon::after{
188
+ content:'';position:absolute;inset:-3px;border-radius:20px;
189
+ background:linear-gradient(135deg,var(--purple),var(--cyan));
190
+ opacity:.4;filter:blur(8px);z-index:-1;animation:icon-breathe 4s ease-in-out infinite reverse;
191
+ }
192
+ @keyframes icon-breathe{0%,100%{box-shadow:0 0 60px rgba(170,102,255,.35),0 0 120px rgba(90,150,255,.15)}50%{box-shadow:0 0 80px rgba(170,102,255,.5),0 0 160px rgba(90,150,255,.25)}}
193
+ .logo h1{font-size:2.8rem;font-weight:900;letter-spacing:-1px;background:linear-gradient(135deg,#fff 0%,var(--blue) 50%,var(--purple) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
194
+ .tagline{color:var(--text2);font-size:1.05rem;font-weight:300;letter-spacing:.5px}
195
+
196
+ /* Activity indicator */
197
+ .activity{display:inline-flex;align-items:center;gap:10px;margin-top:16px;padding:8px 20px;border-radius:30px;background:var(--glass);border:1px solid var(--glass-border);backdrop-filter:blur(20px)}
198
+ .activity-dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 12px var(--green);animation:pulse-dot 2s ease-in-out infinite}
199
+ @keyframes pulse-dot{0%,100%{opacity:1;box-shadow:0 0 12px var(--green)}50%{opacity:.5;box-shadow:0 0 20px var(--green)}}
200
+ .activity-text{font-size:.8rem;color:var(--text2);font-weight:500}
201
+ .activity-bar{width:80px;height:4px;border-radius:2px;background:var(--bg4);overflow:hidden}
202
+ .activity-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,var(--green),var(--cyan));transition:width 1.5s ease}
203
+
204
+ /* Nav */
205
+ nav{display:flex;justify-content:center;gap:8px;flex-wrap:wrap;padding:20px 0;margin-bottom:40px}
206
+ nav a{
207
+ color:var(--text2);text-decoration:none;padding:8px 18px;border-radius:24px;font-size:.85rem;font-weight:500;
208
+ transition:all .3s ease;border:1px solid transparent;backdrop-filter:blur(10px);
209
+ }
210
+ nav a:hover{color:var(--text);background:var(--glass);border-color:var(--glass-border);transform:translateY(-1px)}
211
+ nav a.research{
212
+ background:var(--glass);color:var(--cyan);border-color:rgba(71,229,255,.25);font-weight:600;
213
+ box-shadow:0 0 20px rgba(71,229,255,.1);animation:nav-glow 3s ease-in-out infinite alternate;
214
+ }
215
+ @keyframes nav-glow{0%{box-shadow:0 0 20px rgba(71,229,255,.1)}100%{box-shadow:0 0 35px rgba(71,229,255,.2)}}
216
+
217
+ /* Stats */
218
+ .stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:18px}
219
+ .stat-card{
220
+ background:var(--glass);border:1px solid var(--glass-border);border-radius:var(--radius);
221
+ padding:28px 22px;text-align:center;position:relative;overflow:hidden;
222
+ transition:all .35s ease;backdrop-filter:blur(20px);
223
+ }
224
+ .stat-card:hover{transform:translateY(-4px);border-color:var(--glass-hover);box-shadow:0 20px 60px rgba(0,0,0,.3)}
225
+ .stat-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px}
226
+ .stat-card::after{content:'';position:absolute;top:0;left:0;right:0;bottom:0;background:radial-gradient(ellipse at 50% 0%,rgba(255,255,255,.03),transparent 70%);pointer-events:none}
227
+ .stat-card.blue::before{background:linear-gradient(90deg,transparent,var(--blue),transparent)}
228
+ .stat-card.purple::before{background:linear-gradient(90deg,transparent,var(--purple),transparent)}
229
+ .stat-card.red::before{background:linear-gradient(90deg,transparent,var(--red),transparent)}
230
+ .stat-card.green::before{background:linear-gradient(90deg,transparent,var(--green),transparent)}
231
+ .stat-card.orange::before{background:linear-gradient(90deg,transparent,var(--orange),transparent)}
232
+ .stat-card.cyan::before{background:linear-gradient(90deg,transparent,var(--cyan),transparent)}
233
+ .stat-number{font-size:2.6rem;font-weight:900;letter-spacing:-2px}
234
+ .stat-card.blue .stat-number{color:var(--blue)}.stat-card.purple .stat-number{color:var(--purple)}
235
+ .stat-card.red .stat-number{color:var(--red)}.stat-card.green .stat-number{color:var(--green)}
236
+ .stat-card.orange .stat-number{color:var(--orange)}.stat-card.cyan .stat-number{color:var(--cyan)}
237
+ .stat-label{color:var(--text2);font-size:.82rem;margin-top:6px;font-weight:500;letter-spacing:.3px;text-transform:uppercase}
238
+
239
+ /* Section titles */
240
+ .section-title{font-size:1.5rem;font-weight:700;margin-bottom:24px;display:flex;align-items:center;gap:12px}
241
+ .section-title .icon{font-size:1.2rem;width:38px;height:38px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;backdrop-filter:blur(10px)}
242
+
243
+ /* Language chart */
244
+ .lang-chart{max-width:650px}
245
+ .lang-row{display:flex;align-items:center;gap:14px;margin-bottom:10px}
246
+ .lang-name{width:100px;text-align:right;font-size:.85rem;color:var(--text2);font-weight:500}
247
+ .lang-bar-bg{flex:1;height:28px;background:var(--bg3);border-radius:6px;overflow:hidden;border:1px solid var(--glass-border)}
248
+ .lang-bar{height:100%;background:var(--accent);border-radius:6px;width:0;transition:width 1.2s cubic-bezier(.22,1,.36,1)}
249
+ .lang-count{width:50px;font-size:.85rem;color:var(--text2);font-weight:600}
250
+
251
+ /* Insight tabs */
252
+ .tab-bar{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:24px}
253
+ .tab-btn{
254
+ padding:10px 20px;border-radius:24px;border:1px solid var(--glass-border);
255
+ background:var(--glass);color:var(--text2);cursor:pointer;font-size:.85rem;font-weight:500;
256
+ transition:all .3s ease;backdrop-filter:blur(10px);font-family:inherit;
257
+ }
258
+ .tab-btn:hover{border-color:var(--glass-hover);color:var(--text);transform:translateY(-1px)}
259
+ .tab-btn.active{border-color:rgba(71,229,255,.35);color:var(--cyan);background:rgba(71,229,255,.08);box-shadow:0 0 20px rgba(71,229,255,.1)}
260
+ .tab-btn .count{background:var(--bg4);padding:2px 8px;border-radius:12px;font-size:.72rem;margin-left:6px;font-weight:600}
261
+ .tab-panel{display:none}.tab-panel.active{display:block}
262
+ .insight-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(380px,1fr));gap:14px}
263
+ .insight-card{
264
+ background:var(--glass);border:1px solid var(--glass-border);border-radius:var(--radius-sm);
265
+ padding:18px;border-left:3px solid var(--text3);transition:all .25s ease;backdrop-filter:blur(20px);
266
+ }
267
+ .insight-card:hover{transform:translateX(6px);border-color:var(--glass-hover);box-shadow:0 8px 30px rgba(0,0,0,.2)}
268
+ .insight-card.cyan{border-left-color:var(--cyan)}.insight-card.orange{border-left-color:var(--orange)}
269
+ .insight-card.green{border-left-color:var(--green)}.insight-card.red{border-left-color:var(--red)}
270
+ .insight-card.purple{border-left-color:var(--purple)}.insight-card.blue{border-left-color:var(--blue)}
271
+ .insight-header{display:flex;align-items:center;gap:8px;margin-bottom:8px;flex-wrap:wrap}
272
+ .insight-card p{color:var(--text2);font-size:.85rem;line-height:1.5}
273
+ .prio{font-size:.68rem;padding:3px 10px;border-radius:12px;text-transform:uppercase;font-weight:700;letter-spacing:.5px}
274
+ .prio-critical{background:rgba(255,85,119,.15);color:var(--red);border:1px solid rgba(255,85,119,.25)}
275
+ .prio-high{background:rgba(255,179,71,.15);color:var(--orange);border:1px solid rgba(255,179,71,.25)}
276
+ .prio-medium{background:rgba(91,156,255,.15);color:var(--blue);border:1px solid rgba(91,156,255,.25)}
277
+ .prio-low{background:rgba(139,143,176,.1);color:var(--text2);border:1px solid rgba(139,143,176,.2)}
278
+ .empty{color:var(--text3);font-style:italic;padding:24px}
279
+
280
+ /* Graph */
281
+ .graph-container{position:relative;background:var(--glass);border:1px solid var(--glass-border);border-radius:var(--radius);overflow:hidden;backdrop-filter:blur(20px)}
282
+ #synapse-graph{width:100%;height:500px;display:block;cursor:grab}
283
+ #synapse-graph:active{cursor:grabbing}
284
+ .graph-legend{display:flex;gap:16px;flex-wrap:wrap;padding:12px 20px;border-top:1px solid var(--glass-border);font-size:.8rem;color:var(--text2)}
285
+ .legend-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:6px;vertical-align:middle}
286
+ .graph-tooltip{position:absolute;display:none;background:var(--bg2);border:1px solid var(--glass-border);border-radius:8px;padding:8px 14px;font-size:.8rem;color:var(--text);pointer-events:none;z-index:10;backdrop-filter:blur(20px);box-shadow:0 8px 30px rgba(0,0,0,.3)}
287
+
288
+ /* Footer */
289
+ footer{text-align:center;padding:40px 0;border-top:1px solid var(--glass-border)}
290
+ footer p{color:var(--text3);font-size:.8rem}
291
+ footer code{background:var(--glass);padding:3px 10px;border-radius:6px;font-size:.78rem;border:1px solid var(--glass-border)}
292
+
293
+ /* Responsive */
294
+ @media(max-width:600px){.stats-grid{grid-template-columns:1fr 1fr}.insight-grid{grid-template-columns:1fr}.logo h1{font-size:2rem}}
295
+ </style>
296
+ </head>
297
+ <body>
298
+
299
+ <canvas id="neural-bg"></canvas>
300
+ <div class="orb orb-1"></div>
301
+ <div class="orb orb-2"></div>
302
+ <div class="orb orb-3"></div>
303
+
304
+ <div class="container">
305
+ <header class="reveal">
306
+ <div class="logo">
307
+ <div class="logo-icon">&#129504;</div>
308
+ <h1>Brain</h1>
309
+ </div>
310
+ <p class="tagline">Adaptive Code Intelligence</p>
311
+ <div class="activity">
312
+ <span class="activity-dot"></span>
313
+ <span class="activity-text">Neural Activity</span>
314
+ <div class="activity-bar"><div class="activity-fill" style="width:0%" data-target="${activityLevel}"></div></div>
315
+ <span class="activity-text" style="color:var(--cyan);font-weight:700">${activityLevel}%</span>
316
+ </div>
317
+ </header>
318
+
319
+ <nav class="reveal reveal-delay-1">
320
+ <a href="#stats">Stats</a>
321
+ <a href="#languages">Languages</a>
322
+ <a href="#network">&#128300; Network</a>
323
+ <a href="#research" class="research">&#128161; Research</a>
324
+ </nav>
325
+
326
+ <section id="stats" class="reveal reveal-delay-2">
327
+ <div class="section-title"><div class="icon" style="background:rgba(91,156,255,.1)">&#128202;</div> Neural Status</div>
328
+ <div class="stats-grid">
329
+ <div class="stat-card blue"><div class="stat-number">${stats.modules.toLocaleString()}</div><div class="stat-label">Modules</div></div>
330
+ <div class="stat-card purple"><div class="stat-number">${stats.synapses.toLocaleString()}</div><div class="stat-label">Synapses</div></div>
331
+ <div class="stat-card cyan"><div class="stat-number">${stats.insights}</div><div class="stat-label">Insights</div></div>
332
+ <div class="stat-card red"><div class="stat-number">${stats.errors}</div><div class="stat-label">Errors</div></div>
333
+ <div class="stat-card green"><div class="stat-number">${stats.solutions}</div><div class="stat-label">Solutions</div></div>
334
+ <div class="stat-card orange"><div class="stat-number">${stats.rules}</div><div class="stat-label">Rules</div></div>
335
+ </div>
336
+ </section>
337
+
338
+ <section id="languages" class="reveal reveal-delay-3">
339
+ <div class="section-title"><div class="icon" style="background:rgba(180,122,255,.1)">&#128187;</div> Languages</div>
340
+ <div class="lang-chart">${langBars}</div>
341
+ </section>
342
+
343
+ <section id="network" class="reveal reveal-delay-4">
344
+ <div class="section-title"><div class="icon" style="background:rgba(71,229,255,.1)">&#128300;</div> Synapse Network</div>
345
+ <div class="graph-container">
346
+ <canvas id="synapse-graph"></canvas>
347
+ <div class="graph-legend">
348
+ <span><span class="legend-dot" style="background:var(--blue)"></span> error</span>
349
+ <span><span class="legend-dot" style="background:var(--green)"></span> solution</span>
350
+ <span><span class="legend-dot" style="background:var(--purple)"></span> code_module</span>
351
+ <span><span class="legend-dot" style="background:var(--orange)"></span> project</span>
352
+ <span><span class="legend-dot" style="background:var(--cyan)"></span> other</span>
353
+ </div>
354
+ <div id="graph-tooltip" class="graph-tooltip"></div>
355
+ </div>
356
+ </section>
357
+
358
+ <section id="research" class="reveal reveal-delay-5">
359
+ <div class="section-title"><div class="icon" style="background:rgba(71,229,255,.1)">&#128300;</div> Research Insights</div>
360
+ <div class="tab-bar">
361
+ <button class="tab-btn active" data-tab="templates">&#127912; Templates <span class="count">${insights.templates.length}</span></button>
362
+ <button class="tab-btn" data-tab="suggestions">&#128161; Suggestions <span class="count">${insights.suggestions.length}</span></button>
363
+ <button class="tab-btn" data-tab="trends">&#128200; Trends <span class="count">${insights.trends.length}</span></button>
364
+ <button class="tab-btn" data-tab="gaps">&#9888;&#65039; Gaps <span class="count">${insights.gaps.length}</span></button>
365
+ <button class="tab-btn" data-tab="synergies">&#9889; Synergies <span class="count">${insights.synergies.length}</span></button>
366
+ <button class="tab-btn" data-tab="warnings">&#128680; Warnings <span class="count">${insights.warnings.length}</span></button>
367
+ </div>
368
+ <div class="tab-panel active" id="tab-templates"><div class="insight-grid">${insightCards(insights.templates, 'cyan')}</div></div>
369
+ <div class="tab-panel" id="tab-suggestions"><div class="insight-grid">${insightCards(insights.suggestions, 'orange')}</div></div>
370
+ <div class="tab-panel" id="tab-trends"><div class="insight-grid">${insightCards(insights.trends, 'green')}</div></div>
371
+ <div class="tab-panel" id="tab-gaps"><div class="insight-grid">${insightCards(insights.gaps, 'red')}</div></div>
372
+ <div class="tab-panel" id="tab-synergies"><div class="insight-grid">${insightCards(insights.synergies, 'purple')}</div></div>
373
+ <div class="tab-panel" id="tab-warnings"><div class="insight-grid">${insightCards(insights.warnings, 'red')}</div></div>
374
+ </section>
375
+
376
+ <footer class="reveal reveal-delay-5">
377
+ <p>Brain v1.0 &mdash; <code>brain dashboard</code></p>
378
+ </footer>
379
+ </div>
380
+
381
+ <script>
382
+ // --- Neural Network Canvas ---
383
+ (function(){
384
+ const canvas = document.getElementById('neural-bg');
385
+ const ctx = canvas.getContext('2d');
386
+ let W, H, nodes = [], mouse = {x:-1000,y:-1000};
387
+
388
+ function resize(){
389
+ W = canvas.width = window.innerWidth;
390
+ H = canvas.height = window.innerHeight;
391
+ }
392
+ resize();
393
+ window.addEventListener('resize', resize);
394
+ document.addEventListener('mousemove', e => { mouse.x = e.clientX; mouse.y = e.clientY; });
395
+
396
+ const NODE_COUNT = Math.min(80, Math.floor(window.innerWidth / 18));
397
+ const CONNECT_DIST = 180;
398
+ const MOUSE_DIST = 200;
399
+
400
+ for(let i = 0; i < NODE_COUNT; i++){
401
+ nodes.push({
402
+ x: Math.random() * W,
403
+ y: Math.random() * H,
404
+ vx: (Math.random() - 0.5) * 0.4,
405
+ vy: (Math.random() - 0.5) * 0.4,
406
+ r: Math.random() * 2 + 1,
407
+ pulse: Math.random() * Math.PI * 2,
408
+ });
409
+ }
410
+
411
+ function draw(){
412
+ ctx.clearRect(0, 0, W, H);
413
+
414
+ // Draw connections
415
+ for(let i = 0; i < nodes.length; i++){
416
+ for(let j = i + 1; j < nodes.length; j++){
417
+ const dx = nodes[i].x - nodes[j].x;
418
+ const dy = nodes[i].y - nodes[j].y;
419
+ const dist = Math.sqrt(dx*dx + dy*dy);
420
+ if(dist < CONNECT_DIST){
421
+ const alpha = (1 - dist / CONNECT_DIST) * 0.15;
422
+ ctx.strokeStyle = 'rgba(91,156,255,' + alpha + ')';
423
+ ctx.lineWidth = 0.5;
424
+ ctx.beginPath();
425
+ ctx.moveTo(nodes[i].x, nodes[i].y);
426
+ ctx.lineTo(nodes[j].x, nodes[j].y);
427
+ ctx.stroke();
428
+ }
429
+ }
430
+
431
+ // Mouse interaction
432
+ const mdx = nodes[i].x - mouse.x;
433
+ const mdy = nodes[i].y - mouse.y;
434
+ const mDist = Math.sqrt(mdx*mdx + mdy*mdy);
435
+ if(mDist < MOUSE_DIST){
436
+ const alpha = (1 - mDist / MOUSE_DIST) * 0.4;
437
+ ctx.strokeStyle = 'rgba(180,122,255,' + alpha + ')';
438
+ ctx.lineWidth = 1;
439
+ ctx.beginPath();
440
+ ctx.moveTo(nodes[i].x, nodes[i].y);
441
+ ctx.lineTo(mouse.x, mouse.y);
442
+ ctx.stroke();
443
+ }
444
+ }
445
+
446
+ // Draw nodes
447
+ const time = Date.now() * 0.001;
448
+ for(const n of nodes){
449
+ const glow = 0.4 + Math.sin(time * 1.5 + n.pulse) * 0.3;
450
+ ctx.fillStyle = 'rgba(91,156,255,' + glow + ')';
451
+ ctx.beginPath();
452
+ ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
453
+ ctx.fill();
454
+
455
+ n.x += n.vx;
456
+ n.y += n.vy;
457
+ if(n.x < 0 || n.x > W) n.vx *= -1;
458
+ if(n.y < 0 || n.y > H) n.vy *= -1;
459
+ }
460
+
461
+ requestAnimationFrame(draw);
462
+ }
463
+ draw();
464
+ })();
465
+
466
+ // --- Reveal on scroll ---
467
+ const observer = new IntersectionObserver(entries => {
468
+ entries.forEach(e => { if(e.isIntersecting) e.target.classList.add('visible'); });
469
+ }, {threshold: 0.1});
470
+ document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
471
+
472
+ // --- Tab switching ---
473
+ document.querySelectorAll('.tab-btn').forEach(btn => {
474
+ btn.addEventListener('click', () => {
475
+ document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
476
+ document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
477
+ btn.classList.add('active');
478
+ document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
479
+ });
480
+ });
481
+
482
+ // --- Animate stat numbers ---
483
+ const numObserver = new IntersectionObserver(entries => {
484
+ entries.forEach(e => {
485
+ if(!e.isIntersecting) return;
486
+ const el = e.target;
487
+ if(el.dataset.animated) return;
488
+ el.dataset.animated = '1';
489
+ const target = parseInt(el.textContent.replace(/\\D/g,''), 10);
490
+ if(isNaN(target) || target === 0) return;
491
+ const duration = 1200;
492
+ const start = performance.now();
493
+ function tick(now){
494
+ const t = Math.min((now - start) / duration, 1);
495
+ const ease = 1 - Math.pow(1 - t, 3);
496
+ el.textContent = Math.round(target * ease).toLocaleString();
497
+ if(t < 1) requestAnimationFrame(tick);
498
+ }
499
+ requestAnimationFrame(tick);
500
+ });
501
+ }, {threshold: 0.5});
502
+ document.querySelectorAll('.stat-number').forEach(el => numObserver.observe(el));
503
+
504
+ // --- Animate language bars ---
505
+ setTimeout(() => {
506
+ document.querySelectorAll('.lang-bar').forEach(bar => {
507
+ bar.style.width = bar.dataset.width + '%';
508
+ });
509
+ }, 300);
510
+
511
+ // --- Activity bar ---
512
+ setTimeout(() => {
513
+ document.querySelectorAll('.activity-fill').forEach(el => {
514
+ el.style.width = el.dataset.target + '%';
515
+ });
516
+ }, 500);
517
+
518
+ // --- Synapse Force-Directed Graph ---
519
+ (function(){
520
+ const edges = ${JSON.stringify(synapseEdges.map((e) => ({ s: e.source, t: e.target, type: e.type, w: e.weight })))};
521
+ const canvas = document.getElementById('synapse-graph');
522
+ if (!canvas || !edges.length) return;
523
+ const ctx = canvas.getContext('2d');
524
+ const container = canvas.parentElement;
525
+ let W, H, dpr;
526
+
527
+ const NODE_COLORS = {
528
+ error: '#ff5577', solution: '#3dffa0', code_module: '#b47aff',
529
+ project: '#ffb347', rule: '#5b9cff', antipattern: '#ff5577'
530
+ };
531
+ const DEFAULT_COLOR = '#47e5ff';
532
+
533
+ // Build graph nodes & edges
534
+ const nodeMap = new Map();
535
+ const graphEdges = [];
536
+ for (const e of edges) {
537
+ if (!nodeMap.has(e.s)) nodeMap.set(e.s, { id: e.s, type: e.s.split(':')[0], x: 0, y: 0, vx: 0, vy: 0, connections: 0 });
538
+ if (!nodeMap.has(e.t)) nodeMap.set(e.t, { id: e.t, type: e.t.split(':')[0], x: 0, y: 0, vx: 0, vy: 0, connections: 0 });
539
+ nodeMap.get(e.s).connections++;
540
+ nodeMap.get(e.t).connections++;
541
+ graphEdges.push({ source: nodeMap.get(e.s), target: nodeMap.get(e.t), type: e.type, weight: e.w });
542
+ }
543
+ const nodes = [...nodeMap.values()];
544
+
545
+ function resize() {
546
+ dpr = window.devicePixelRatio || 1;
547
+ W = container.clientWidth;
548
+ H = 500;
549
+ canvas.width = W * dpr;
550
+ canvas.height = H * dpr;
551
+ canvas.style.width = W + 'px';
552
+ canvas.style.height = H + 'px';
553
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
554
+ }
555
+ resize();
556
+ window.addEventListener('resize', resize);
557
+
558
+ // Random initial positions
559
+ for (const n of nodes) {
560
+ n.x = W * 0.2 + Math.random() * W * 0.6;
561
+ n.y = H * 0.2 + Math.random() * H * 0.6;
562
+ }
563
+
564
+ // Force simulation
565
+ const REPULSION = 3000;
566
+ const ATTRACTION = 0.008;
567
+ const DAMPING = 0.85;
568
+ const CENTER_GRAVITY = 0.002;
569
+ let hovered = null;
570
+ let dragging = null;
571
+ let dragOff = {x:0,y:0};
572
+
573
+ function simulate() {
574
+ // Repulsion
575
+ for (let i = 0; i < nodes.length; i++) {
576
+ for (let j = i + 1; j < nodes.length; j++) {
577
+ let dx = nodes[i].x - nodes[j].x;
578
+ let dy = nodes[i].y - nodes[j].y;
579
+ let dist = Math.sqrt(dx*dx + dy*dy) || 1;
580
+ let force = REPULSION / (dist * dist);
581
+ let fx = (dx / dist) * force;
582
+ let fy = (dy / dist) * force;
583
+ nodes[i].vx += fx; nodes[i].vy += fy;
584
+ nodes[j].vx -= fx; nodes[j].vy -= fy;
585
+ }
586
+ }
587
+ // Attraction along edges
588
+ for (const e of graphEdges) {
589
+ let dx = e.target.x - e.source.x;
590
+ let dy = e.target.y - e.source.y;
591
+ let dist = Math.sqrt(dx*dx + dy*dy) || 1;
592
+ let force = (dist - 100) * ATTRACTION * e.weight;
593
+ let fx = (dx / dist) * force;
594
+ let fy = (dy / dist) * force;
595
+ e.source.vx += fx; e.source.vy += fy;
596
+ e.target.vx -= fx; e.target.vy -= fy;
597
+ }
598
+ // Center gravity
599
+ for (const n of nodes) {
600
+ n.vx += (W/2 - n.x) * CENTER_GRAVITY;
601
+ n.vy += (H/2 - n.y) * CENTER_GRAVITY;
602
+ }
603
+ // Apply & damp
604
+ for (const n of nodes) {
605
+ if (n === dragging) continue;
606
+ n.vx *= DAMPING; n.vy *= DAMPING;
607
+ n.x += n.vx; n.y += n.vy;
608
+ n.x = Math.max(20, Math.min(W - 20, n.x));
609
+ n.y = Math.max(20, Math.min(H - 20, n.y));
610
+ }
611
+ }
612
+
613
+ function getNodeRadius(n) { return Math.min(16, 5 + n.connections * 1.5); }
614
+
615
+ function draw() {
616
+ ctx.clearRect(0, 0, W, H);
617
+ // Edges
618
+ for (const e of graphEdges) {
619
+ const alpha = 0.15 + e.weight * 0.5;
620
+ ctx.strokeStyle = 'rgba(91,156,255,' + Math.min(0.8, alpha) + ')';
621
+ ctx.lineWidth = 0.5 + e.weight * 2;
622
+ ctx.beginPath();
623
+ ctx.moveTo(e.source.x, e.source.y);
624
+ ctx.lineTo(e.target.x, e.target.y);
625
+ ctx.stroke();
626
+ }
627
+ // Nodes
628
+ for (const n of nodes) {
629
+ const r = getNodeRadius(n);
630
+ const color = NODE_COLORS[n.type] || DEFAULT_COLOR;
631
+ const isHover = n === hovered || n === dragging;
632
+ // Glow
633
+ if (isHover) {
634
+ ctx.shadowColor = color;
635
+ ctx.shadowBlur = 20;
636
+ }
637
+ ctx.fillStyle = color;
638
+ ctx.globalAlpha = isHover ? 1 : 0.8;
639
+ ctx.beginPath();
640
+ ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
641
+ ctx.fill();
642
+ ctx.globalAlpha = 1;
643
+ ctx.shadowBlur = 0;
644
+ // Label for hovered or large nodes
645
+ if (isHover || n.connections >= 4) {
646
+ ctx.fillStyle = '#e8eaf6';
647
+ ctx.font = (isHover ? 'bold ' : '') + '11px Inter, system-ui, sans-serif';
648
+ ctx.textAlign = 'center';
649
+ ctx.fillText(n.id, n.x, n.y - r - 6);
650
+ }
651
+ }
652
+ simulate();
653
+ requestAnimationFrame(draw);
654
+ }
655
+ draw();
656
+
657
+ // Interaction
658
+ const tooltip = document.getElementById('graph-tooltip');
659
+ function getNodeAt(mx, my) {
660
+ for (let i = nodes.length - 1; i >= 0; i--) {
661
+ const n = nodes[i], r = getNodeRadius(n);
662
+ if (Math.hypot(mx - n.x, my - n.y) <= r + 4) return n;
663
+ }
664
+ return null;
665
+ }
666
+ function getPos(e) {
667
+ const rect = canvas.getBoundingClientRect();
668
+ return { x: e.clientX - rect.left, y: e.clientY - rect.top };
669
+ }
670
+ canvas.addEventListener('mousemove', function(e) {
671
+ const p = getPos(e);
672
+ if (dragging) {
673
+ dragging.x = p.x + dragOff.x;
674
+ dragging.y = p.y + dragOff.y;
675
+ dragging.vx = 0; dragging.vy = 0;
676
+ return;
677
+ }
678
+ const n = getNodeAt(p.x, p.y);
679
+ hovered = n;
680
+ canvas.style.cursor = n ? 'pointer' : 'grab';
681
+ if (n) {
682
+ const conns = graphEdges.filter(e => e.source === n || e.target === n);
683
+ tooltip.innerHTML = '<strong>' + n.id + '</strong><br>' + conns.length + ' connections';
684
+ tooltip.style.display = 'block';
685
+ tooltip.style.left = (p.x + 15) + 'px';
686
+ tooltip.style.top = (p.y - 10) + 'px';
687
+ } else {
688
+ tooltip.style.display = 'none';
689
+ }
690
+ });
691
+ canvas.addEventListener('mousedown', function(e) {
692
+ const p = getPos(e);
693
+ const n = getNodeAt(p.x, p.y);
694
+ if (n) {
695
+ dragging = n;
696
+ dragOff = { x: n.x - p.x, y: n.y - p.y };
697
+ canvas.style.cursor = 'grabbing';
698
+ }
699
+ });
700
+ canvas.addEventListener('mouseup', function() { dragging = null; });
701
+ canvas.addEventListener('mouseleave', function() { dragging = null; hovered = null; tooltip.style.display = 'none'; });
702
+ })();
703
+ </script>
704
+ </body>
671
705
  </html>`;
672
706
  }
673
707
  //# sourceMappingURL=dashboard.js.map