Komalpreet Kaur commited on
Commit
9be901b
Β·
unverified Β·
1 Parent(s): 2c333c8

feat: Add EEGWave component and refine UI aesthetics

Browse files
frontend/.gitignore CHANGED
@@ -22,3 +22,4 @@ dist-ssr
22
  *.njsproj
23
  *.sln
24
  *.sw?
 
 
22
  *.njsproj
23
  *.sln
24
  *.sw?
25
+ .vite/
frontend/src/App.css CHANGED
@@ -1,288 +1,309 @@
1
- /* ═══════════════════════════════════════════════
2
- SOMA β€” Three-Column Brain Layout
3
- Chat | Cognitive Process | Neural Mesh
4
- ═══════════════════════════════════════════════ */
5
 
6
- .app-container {
7
  display: flex;
8
  flex-direction: column;
9
  height: 100vh;
10
  width: 100vw;
11
  overflow: hidden;
 
12
  }
13
 
14
- /* ── Header ── */
 
 
15
  .app-header {
16
  display: flex;
17
- justify-content: space-between;
18
  align-items: center;
19
- padding: 0 20px;
20
- height: 52px;
21
- min-height: 52px;
22
- background: rgba(7, 3, 15, 0.92);
23
- backdrop-filter: blur(14px);
24
- border-bottom: 1px solid rgba(167, 139, 250, 0.12);
25
- z-index: 10;
26
  flex-shrink: 0;
 
 
27
  }
28
 
29
- /* Logo */
30
- .logo-section {
31
  display: flex;
32
  align-items: center;
33
- gap: 10px;
 
34
  }
35
 
36
- .brain-pulse-icon {
37
- width: 12px;
38
- height: 12px;
39
- border-radius: 50%;
40
- background: linear-gradient(135deg, var(--accent-synapse), var(--accent-impulse));
41
- box-shadow: 0 0 12px rgba(192, 132, 252, 0.6);
42
- animation: brain-pulse 3s ease-in-out infinite;
43
  flex-shrink: 0;
44
  }
45
 
46
- @keyframes brain-pulse {
47
- 0%, 100% { transform: scale(1); box-shadow: 0 0 10px rgba(192, 132, 252, 0.5); }
48
- 50% { transform: scale(1.3); box-shadow: 0 0 20px rgba(240, 171, 252, 0.7); }
49
  }
50
 
51
- .app-header h1 {
52
- margin: 0;
53
- font-size: 1.05rem;
54
- font-weight: 700;
55
- background: linear-gradient(90deg, var(--accent-synapse), var(--accent-impulse));
56
- -webkit-background-clip: text;
57
- -webkit-text-fill-color: transparent;
58
- background-clip: text;
59
- letter-spacing: 0.45em;
60
  }
61
 
62
- .logo-tagline {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  font-family: var(--font-mono);
64
- font-size: 0.48rem;
65
- color: #5a4f80;
66
- letter-spacing: 0.2em;
67
- text-transform: uppercase;
68
- margin-top: 2px;
 
 
 
 
 
 
69
  }
70
 
71
- /* Center tabs */
72
  .header-center {
73
  display: flex;
74
- gap: 4px;
75
- background: rgba(10, 5, 20, 0.7);
76
- border-radius: 8px;
77
- padding: 3px;
78
- border: 1px solid rgba(167, 139, 250, 0.1);
79
  }
80
 
81
- .tab-btn {
82
- font-family: var(--font-mono);
83
- font-size: 0.58rem;
84
- text-transform: uppercase;
85
- letter-spacing: 0.12em;
86
- padding: 6px 18px;
87
- border-radius: 6px;
88
- border: 1px solid transparent;
89
- background: transparent;
90
- color: #6b5fa0;
91
- cursor: pointer;
92
- transition: all 0.2s ease;
93
  }
94
 
95
- .tab-btn:hover {
96
- color: var(--text-secondary);
97
- background: rgba(192, 132, 252, 0.07);
98
- transform: none;
99
- box-shadow: none;
100
  }
101
 
102
- .tab-btn.active {
103
- background: rgba(192, 132, 252, 0.16);
104
- color: var(--accent-synapse);
105
- border-color: rgba(192, 132, 252, 0.3);
106
  }
107
 
108
- /* Status / right side */
109
- .status-indicator {
110
  display: flex;
111
  align-items: center;
112
- gap: 10px;
 
113
  }
114
 
115
- .state-pill {
 
116
  display: flex;
117
- align-items: center;
118
- gap: 6px;
 
 
 
 
 
 
119
  font-family: var(--font-mono);
120
- font-size: 0.55rem;
 
121
  text-transform: uppercase;
122
- letter-spacing: 0.12em;
123
- padding: 4px 10px;
124
- border-radius: 20px;
125
- border: 1px solid rgba(255,255,255,0.07);
126
- background: rgba(14, 8, 28, 0.7);
127
- color: #8a7ab8;
 
128
  }
129
 
130
- .state-pill.thinking {
131
- color: #10b981;
132
- border-color: rgba(16, 185, 129, 0.3);
133
- background: rgba(16, 185, 129, 0.06);
 
134
  }
135
 
136
- .state-dot {
137
- width: 6px;
138
- height: 6px;
139
- border-radius: 50%;
140
- background: currentColor;
141
  }
142
 
143
- .state-pill.thinking .state-dot {
144
- animation: dot-blink 1s infinite alternate;
 
 
 
 
 
 
 
 
 
 
145
  }
146
 
147
- @keyframes dot-blink {
148
- from { opacity: 1; transform: scale(1); }
149
- to { opacity: 0.3; transform: scale(0.6); }
 
 
 
150
  }
151
 
152
- .sleep-btn-header {
153
- font-family: var(--font-mono);
154
- font-size: 0.52rem;
155
- padding: 5px 12px;
 
 
 
 
156
  border-radius: 6px;
157
- background: rgba(124, 58, 237, 0.12);
158
- border: 1px solid rgba(192, 132, 252, 0.2);
159
- color: #9a8ac0;
160
  cursor: pointer;
161
- letter-spacing: 0.08em;
162
- transition: all 0.2s ease;
163
  }
164
 
165
- .sleep-btn-header:hover {
166
- background: rgba(192, 132, 252, 0.2);
167
- border-color: var(--accent-synapse);
168
- color: var(--accent-synapse);
169
- box-shadow: 0 0 10px rgba(192, 132, 252, 0.2);
170
- transform: none;
171
- }
172
 
173
- .sleep-btn-header:disabled {
174
- opacity: 0.35;
175
- cursor: not-allowed;
176
- transform: none;
177
  }
178
 
179
- .help-btn {
180
- width: 20px;
181
- height: 20px;
 
 
 
 
 
182
  border-radius: 50%;
183
- border: 1px solid rgba(192, 132, 252, 0.3);
184
  background: transparent;
185
- color: #8a7ab8;
186
- font-family: var(--font-mono);
187
- font-size: 0.6rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  display: flex;
189
  align-items: center;
190
  justify-content: center;
191
  padding: 0;
192
- cursor: pointer;
 
 
 
 
193
  transition: all 0.2s ease;
194
  }
195
 
196
  .help-btn:hover {
197
- background: rgba(192, 132, 252, 0.12);
198
- color: var(--accent-synapse);
199
- box-shadow: 0 0 8px rgba(192, 132, 252, 0.25);
200
  transform: none;
 
 
201
  }
202
 
203
- .persona-select {
204
- background: rgba(10, 5, 20, 0.8) !important;
205
- color: var(--accent-synapse) !important;
206
- border: 1px solid rgba(192, 132, 252, 0.25) !important;
207
- padding: 4px 8px !important;
208
- border-radius: 5px;
209
- outline: none;
210
- cursor: pointer;
211
- font-family: var(--font-mono) !important;
212
- font-size: 0.58rem !important;
213
- letter-spacing: 0.08em;
214
- }
215
-
216
- .persona-select option {
217
- background: #0a0510;
218
- color: var(--accent-synapse);
219
- }
220
-
221
- /* ═══ Three-Column Layout ═══ */
222
  .tri-layout {
223
  flex: 1;
224
  display: grid;
225
- grid-template-columns: 32% 30% 1fr;
226
  overflow: hidden;
227
  min-height: 0;
228
  }
229
 
230
- /* Chat column */
231
  .col-chat {
232
- display: flex;
233
- flex-direction: column;
234
  overflow: hidden;
235
- border-right: 1px solid rgba(167, 139, 250, 0.1);
236
- background: rgba(8, 4, 18, 0.4);
237
  }
238
 
239
- /* Brain process column */
240
  .col-brain {
241
- display: flex;
242
- flex-direction: column;
243
  overflow: hidden;
 
 
244
  }
245
 
246
- /* Graph / dreams column */
247
  .col-graph {
248
  position: relative;
249
  overflow: hidden;
250
- background: transparent;
251
- }
252
-
253
- /* ── Cognitive State Themes ── */
254
- .state-focused {
255
- --accent-primary: #34d399;
256
- --accent-glow: rgba(52, 211, 153, 0.2);
257
  }
258
 
 
259
  .state-sleeping {
260
- --bg-base: #030208;
261
- --accent-primary: #818cf8;
262
- --accent-glow: rgba(129, 140, 248, 0.15);
263
  }
264
 
265
- .state-overloaded {
266
- --accent-primary: var(--accent-error);
267
- --accent-glow: rgba(251, 113, 133, 0.3);
268
  }
269
 
270
  /* ── Responsive ── */
271
  @media (max-width: 1100px) {
272
- .tri-layout {
273
- grid-template-columns: 40% 1fr;
274
- grid-template-rows: 1fr;
275
- }
276
  .col-brain { display: none; }
277
  }
278
 
279
- @media (max-width: 768px) {
280
- .tri-layout {
281
- grid-template-columns: 1fr;
282
- grid-template-rows: 55% 45%;
283
- }
284
  .col-brain { display: none; }
285
  .col-graph { grid-row: 2; }
286
  .header-center { display: none; }
287
- .logo-tagline { display: none; }
288
  }
 
1
+ /* ═══════════════════════════════════════════════════════════════
2
+ SOMA β€” App Shell
3
+ ═══════════════════════════════════════════════════════════════ */
 
4
 
5
+ .app-root {
6
  display: flex;
7
  flex-direction: column;
8
  height: 100vh;
9
  width: 100vw;
10
  overflow: hidden;
11
+ background: var(--bg-0);
12
  }
13
 
14
+ /* ══════════════════════════════════════
15
+ Header
16
+ ══════════════════════════════════════ */
17
  .app-header {
18
  display: flex;
 
19
  align-items: center;
20
+ justify-content: space-between;
21
+ height: 56px;
22
+ min-height: 56px;
23
+ padding: 0 24px;
24
+ background: var(--bg-1);
25
+ border-bottom: 1px solid var(--border-0);
 
26
  flex-shrink: 0;
27
+ z-index: 20;
28
+ gap: 20px;
29
  }
30
 
31
+ /* ── Logo ── */
32
+ .header-logo {
33
  display: flex;
34
  align-items: center;
35
+ gap: 12px;
36
+ flex-shrink: 0;
37
  }
38
 
39
+ /* 3D Rotating Orb */
40
+ .logo-orb {
41
+ width: 28px;
42
+ height: 28px;
43
+ position: relative;
44
+ transform-style: preserve-3d;
45
+ animation: orb-spin 10s linear infinite;
46
  flex-shrink: 0;
47
  }
48
 
49
+ @keyframes orb-spin {
50
+ from { transform: perspective(80px) rotateY(0deg) rotateX(15deg); }
51
+ to { transform: perspective(80px) rotateY(360deg) rotateX(15deg); }
52
  }
53
 
54
+ .orb-ring {
55
+ position: absolute;
56
+ border-radius: 50%;
57
+ border: 1.5px solid var(--pulse);
58
+ opacity: 0.55;
 
 
 
 
59
  }
60
 
61
+ .orb-ring.r1 { inset: 2px; transform: rotateX(0deg); }
62
+ .orb-ring.r2 { inset: 2px; transform: rotateX(60deg); }
63
+ .orb-ring.r3 { inset: 2px; transform: rotateX(-60deg); }
64
+
65
+ .orb-core {
66
+ position: absolute;
67
+ inset: 9px;
68
+ background: var(--pulse);
69
+ border-radius: 50%;
70
+ opacity: 0.7;
71
+ filter: blur(1px);
72
+ box-shadow: 0 0 8px var(--pulse);
73
+ }
74
+
75
+ .logo-text {
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: 1px;
79
+ }
80
+
81
+ .logo-name {
82
  font-family: var(--font-mono);
83
+ font-size: 0.95rem;
84
+ font-weight: 700;
85
+ letter-spacing: 0.4em;
86
+ color: var(--text-0);
87
+ line-height: 1;
88
+ }
89
+
90
+ .logo-sub {
91
+ font-size: 0.46rem;
92
+ letter-spacing: 0.22em;
93
+ color: var(--text-2);
94
  }
95
 
96
+ /* ── Center: EEG ── */
97
  .header-center {
98
  display: flex;
99
+ align-items: center;
100
+ gap: 14px;
101
+ flex: 1;
102
+ justify-content: center;
 
103
  }
104
 
105
+ .state-label {
106
+ font-size: 0.52rem;
107
+ letter-spacing: 0.2em;
108
+ color: var(--text-2);
109
+ white-space: nowrap;
110
+ transition: color 0.3s ease;
 
 
 
 
 
 
111
  }
112
 
113
+ .state-label.firing {
114
+ color: var(--fire);
115
+ animation: label-blink 1s infinite alternate;
 
 
116
  }
117
 
118
+ @keyframes label-blink {
119
+ from { opacity: 1; }
120
+ to { opacity: 0.45; }
 
121
  }
122
 
123
+ /* ── Right Controls ── */
124
+ .header-controls {
125
  display: flex;
126
  align-items: center;
127
+ gap: 8px;
128
+ flex-shrink: 0;
129
  }
130
 
131
+ /* View tabs */
132
+ .view-tabs {
133
  display: flex;
134
+ gap: 2px;
135
+ background: var(--bg-2);
136
+ border: 1px solid var(--border-0);
137
+ border-radius: 7px;
138
+ padding: 3px;
139
+ }
140
+
141
+ .view-tab {
142
  font-family: var(--font-mono);
143
+ font-size: 0.56rem;
144
+ letter-spacing: 0.1em;
145
  text-transform: uppercase;
146
+ padding: 5px 14px;
147
+ border-radius: 5px;
148
+ border: 1px solid transparent;
149
+ background: transparent;
150
+ color: var(--text-2);
151
+ cursor: pointer;
152
+ transition: all 0.2s ease;
153
  }
154
 
155
+ .view-tab:hover {
156
+ color: var(--text-1);
157
+ background: var(--border-0);
158
+ transform: none;
159
+ box-shadow: none;
160
  }
161
 
162
+ .view-tab.active {
163
+ background: var(--bg-3);
164
+ color: var(--pulse);
165
+ border-color: var(--border-1);
166
+ box-shadow: var(--glow-pulse);
167
  }
168
 
169
+ /* Sleep btn */
170
+ .sleep-btn {
171
+ font-family: var(--font-mono);
172
+ font-size: 0.56rem;
173
+ letter-spacing: 0.12em;
174
+ text-transform: uppercase;
175
+ padding: 6px 14px;
176
+ border-radius: 6px;
177
+ background: transparent;
178
+ border: 1px solid var(--border-1);
179
+ color: var(--text-2);
180
+ transition: all 0.2s ease;
181
  }
182
 
183
+ .sleep-btn:hover {
184
+ background: var(--border-0);
185
+ color: var(--pulse);
186
+ border-color: var(--pulse);
187
+ box-shadow: var(--glow-pulse);
188
+ transform: none;
189
  }
190
 
191
+ /* Persona */
192
+ .persona-select {
193
+ font-family: var(--font-mono) !important;
194
+ font-size: 0.56rem !important;
195
+ letter-spacing: 0.1em;
196
+ background: var(--bg-2) !important;
197
+ color: var(--text-1) !important;
198
+ border: 1px solid var(--border-0) !important;
199
  border-radius: 6px;
200
+ padding: 5px 8px !important;
201
+ outline: none;
 
202
  cursor: pointer;
203
+ transition: border-color 0.2s ease;
 
204
  }
205
 
206
+ .persona-select:hover { border-color: var(--border-1) !important; }
 
 
 
 
 
 
207
 
208
+ .persona-select option {
209
+ background: var(--bg-2);
210
+ color: var(--text-0);
 
211
  }
212
 
213
+ /* Theme toggle */
214
+ .theme-toggle {
215
+ width: 32px;
216
+ height: 32px;
217
+ display: flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ padding: 0;
221
  border-radius: 50%;
 
222
  background: transparent;
223
+ border: 1px solid var(--border-1);
224
+ color: var(--text-1);
225
+ transition: all 0.3s ease;
226
+ }
227
+
228
+ .theme-toggle:hover {
229
+ background: var(--border-0);
230
+ color: var(--pulse);
231
+ border-color: var(--pulse);
232
+ transform: rotate(20deg);
233
+ box-shadow: var(--glow-pulse);
234
+ }
235
+
236
+ /* Help */
237
+ .help-btn {
238
+ width: 28px;
239
+ height: 28px;
240
  display: flex;
241
  align-items: center;
242
  justify-content: center;
243
  padding: 0;
244
+ border-radius: 50%;
245
+ font-size: 0.65rem;
246
+ background: transparent;
247
+ border: 1px solid var(--border-1);
248
+ color: var(--text-2);
249
  transition: all 0.2s ease;
250
  }
251
 
252
  .help-btn:hover {
253
+ color: var(--pulse);
254
+ border-color: var(--pulse);
 
255
  transform: none;
256
+ box-shadow: none;
257
+ background: var(--border-0);
258
  }
259
 
260
+ /* ══════════════════════════════════════
261
+ Three-Column Layout
262
+ ══════════════════════════════════════ */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  .tri-layout {
264
  flex: 1;
265
  display: grid;
266
+ grid-template-columns: 30% 32% 1fr;
267
  overflow: hidden;
268
  min-height: 0;
269
  }
270
 
 
271
  .col-chat {
 
 
272
  overflow: hidden;
273
+ border-right: 1px solid var(--border-0);
274
+ background: var(--bg-0);
275
  }
276
 
 
277
  .col-brain {
 
 
278
  overflow: hidden;
279
+ border-right: 1px solid var(--border-0);
280
+ background: var(--bg-1);
281
  }
282
 
 
283
  .col-graph {
284
  position: relative;
285
  overflow: hidden;
286
+ background: var(--bg-0);
 
 
 
 
 
 
287
  }
288
 
289
+ /* ── Cognitive state background shifts ── */
290
  .state-sleeping {
291
+ --bg-0: #04040a;
 
 
292
  }
293
 
294
+ [data-theme="light"] .state-sleeping {
295
+ --bg-0: #eee8f5;
 
296
  }
297
 
298
  /* ── Responsive ── */
299
  @media (max-width: 1100px) {
300
+ .tri-layout { grid-template-columns: 42% 1fr; }
 
 
 
301
  .col-brain { display: none; }
302
  }
303
 
304
+ @media (max-width: 680px) {
305
+ .tri-layout { grid-template-columns: 1fr; grid-template-rows: 55% 45%; }
 
 
 
306
  .col-brain { display: none; }
307
  .col-graph { grid-row: 2; }
308
  .header-center { display: none; }
 
309
  }
frontend/src/App.jsx CHANGED
@@ -4,6 +4,7 @@ import BrainProcess from './components/BrainProcess'
4
  import KnowledgeGraph from './components/KnowledgeGraph'
5
  import DreamSequence from './components/DreamSequence'
6
  import Onboarding from './components/Onboarding'
 
7
  import './App.css'
8
 
9
  function App() {
@@ -23,6 +24,17 @@ function App() {
23
  const [rightPanel, setRightPanel] = useState('graph')
24
  const [currentPersona, setCurrentPersona] = useState('User_Alpha')
25
  const [showOnboarding, setShowOnboarding] = useState(false)
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  useEffect(() => {
28
  const hasVisited = localStorage.getItem('soma_visited')
@@ -44,9 +56,7 @@ function App() {
44
  workingMemory: data.working,
45
  cognitiveState: data.state || 'IDLE'
46
  }))
47
- } catch (err) {
48
- console.error('Failed to fetch brain vitals:', err)
49
- }
50
  }
51
 
52
  const fetchSparks = async () => {
@@ -54,90 +64,89 @@ function App() {
54
  const res = await fetch(`/api/v1/brain/sparks?user_id=${currentPersona}`)
55
  const data = await res.json()
56
  setBrainState(prev => ({ ...prev, sparks: data }))
57
- } catch (err) {
58
- console.error('Failed to fetch neural sparks:', err)
59
- }
60
  }
61
 
62
  fetchVitals()
63
  fetchSparks()
64
- const interval = setInterval(() => { fetchVitals(); fetchSparks() }, 20000)
65
- return () => clearInterval(interval)
66
  }, [currentPersona])
67
 
68
  useEffect(() => {
69
- const fetchHistory = async () => {
70
  try {
71
  const res = await fetch(`/api/v1/history?user_id=${currentPersona}`)
72
  const data = await res.json()
73
  setMessages(data.messages || [])
74
- } catch (err) {
75
- console.error('Failed to fetch chat history:', err)
76
- }
77
  }
78
- fetchHistory()
79
  }, [currentPersona])
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  return (
82
- <div className={`app-container state-${brainState.cognitiveState.toLowerCase()}`}>
83
 
84
  {showOnboarding && <Onboarding onClose={() => setShowOnboarding(false)} />}
85
 
86
  {/* ── Header ── */}
87
  <header className="app-header">
88
- <div className="logo-section">
89
- <div className="brain-pulse-icon" />
90
- <h1>SOMA</h1>
91
- <span className="logo-tagline">Cognitive AI</span>
 
 
 
 
 
 
 
 
 
92
  </div>
93
 
 
94
  <div className="header-center">
95
- <button
96
- className={`tab-btn ${rightPanel === 'graph' ? 'active' : ''}`}
97
- onClick={() => setRightPanel('graph')}
98
- >Neural Mesh</button>
99
- <button
100
- className={`tab-btn ${rightPanel === 'dreams' ? 'active' : ''}`}
101
- onClick={() => setRightPanel('dreams')}
102
- >Dreams</button>
103
  </div>
104
 
105
- <div className="status-indicator">
106
- <button
107
- className="help-btn"
108
- onClick={() => setShowOnboarding(true)}
109
- title="System Overview"
110
- >?</button>
111
-
112
- <div className={`state-pill ${brainState.isLoading ? 'thinking' : 'idle'}`}>
113
- <span className="state-dot" />
114
- <span>{brainState.cognitiveState}</span>
 
115
  </div>
116
 
117
- <button
118
- className="sleep-btn-header"
119
- onClick={async () => {
120
- setBrainState(prev => ({ ...prev, isLoading: true, statusMessage: 'Sleeping...', cognitiveState: 'SLEEPING' }))
121
- try {
122
- const res = await fetch('/api/v1/sleep', { method: 'POST' })
123
- const data = await res.json()
124
- setBrainState(prev => ({
125
- ...prev,
126
- isLoading: false,
127
- statusMessage: `Sleep done. ${data.graph_relations_extracted} relations extracted.`,
128
- cognitiveState: 'IDLE'
129
- }))
130
- } catch {
131
- setBrainState(prev => ({ ...prev, isLoading: false, statusMessage: 'Sleep failed.', cognitiveState: 'IDLE' }))
132
- }
133
- }}
134
- disabled={brainState.isLoading}
135
- >
136
- πŸ’€ Sleep
137
  </button>
138
 
139
  <select
140
- className="persona-select label-mono"
141
  value={currentPersona}
142
  onChange={e => setCurrentPersona(e.target.value)}
143
  >
@@ -145,13 +154,34 @@ function App() {
145
  <option value="User_Beta">Beta</option>
146
  <option value="System_Admin">Admin</option>
147
  </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  </div>
149
  </header>
150
 
151
  {/* ── Three-Column Layout ── */}
152
  <div className="tri-layout">
153
 
154
- {/* LEFT β€” Chat */}
155
  <div className="col-chat">
156
  <ChatPanel
157
  messages={messages}
@@ -163,25 +193,18 @@ function App() {
163
  />
164
  </div>
165
 
166
- {/* CENTER β€” Cognitive Architecture */}
167
  <div className="col-brain">
168
  <BrainProcess
169
  brainState={brainState}
170
- setBrainState={setBrainState}
171
  currentPersona={currentPersona}
172
  />
173
  </div>
174
 
175
- {/* RIGHT β€” Knowledge Mesh or Dreams */}
176
  <div className="col-graph">
177
- {rightPanel === 'graph' ? (
178
- <KnowledgeGraph
179
- highlightedNodes={brainState.highlightedNodes}
180
- currentPersona={currentPersona}
181
- />
182
- ) : (
183
- <DreamSequence sparks={brainState.sparks} />
184
- )}
185
  </div>
186
 
187
  </div>
 
4
  import KnowledgeGraph from './components/KnowledgeGraph'
5
  import DreamSequence from './components/DreamSequence'
6
  import Onboarding from './components/Onboarding'
7
+ import EEGWave from './components/EEGWave'
8
  import './App.css'
9
 
10
  function App() {
 
24
  const [rightPanel, setRightPanel] = useState('graph')
25
  const [currentPersona, setCurrentPersona] = useState('User_Alpha')
26
  const [showOnboarding, setShowOnboarding] = useState(false)
27
+ const [theme, setTheme] = useState(() =>
28
+ localStorage.getItem('soma_theme') || 'dark'
29
+ )
30
+
31
+ // Apply theme to root
32
+ useEffect(() => {
33
+ document.documentElement.setAttribute('data-theme', theme)
34
+ localStorage.setItem('soma_theme', theme)
35
+ }, [theme])
36
+
37
+ const toggleTheme = () => setTheme(t => t === 'dark' ? 'light' : 'dark')
38
 
39
  useEffect(() => {
40
  const hasVisited = localStorage.getItem('soma_visited')
 
56
  workingMemory: data.working,
57
  cognitiveState: data.state || 'IDLE'
58
  }))
59
+ } catch {/* backend offline in dev */ }
 
 
60
  }
61
 
62
  const fetchSparks = async () => {
 
64
  const res = await fetch(`/api/v1/brain/sparks?user_id=${currentPersona}`)
65
  const data = await res.json()
66
  setBrainState(prev => ({ ...prev, sparks: data }))
67
+ } catch {/* silent */ }
 
 
68
  }
69
 
70
  fetchVitals()
71
  fetchSparks()
72
+ const id = setInterval(() => { fetchVitals(); fetchSparks() }, 20000)
73
+ return () => clearInterval(id)
74
  }, [currentPersona])
75
 
76
  useEffect(() => {
77
+ const load = async () => {
78
  try {
79
  const res = await fetch(`/api/v1/history?user_id=${currentPersona}`)
80
  const data = await res.json()
81
  setMessages(data.messages || [])
82
+ } catch {/* silent */ }
 
 
83
  }
84
+ load()
85
  }, [currentPersona])
86
 
87
+ const handleSleep = async () => {
88
+ setBrainState(prev => ({ ...prev, isLoading: true, cognitiveState: 'SLEEPING' }))
89
+ try {
90
+ const res = await fetch('/api/v1/sleep', { method: 'POST' })
91
+ const data = await res.json()
92
+ setBrainState(prev => ({
93
+ ...prev, isLoading: false, cognitiveState: 'IDLE',
94
+ statusMessage: `Consolidated. ${data.graph_relations_extracted} relations extracted.`
95
+ }))
96
+ } catch {
97
+ setBrainState(prev => ({ ...prev, isLoading: false, cognitiveState: 'IDLE' }))
98
+ }
99
+ }
100
+
101
  return (
102
+ <div className={`app-root state-${brainState.cognitiveState.toLowerCase()}`}>
103
 
104
  {showOnboarding && <Onboarding onClose={() => setShowOnboarding(false)} />}
105
 
106
  {/* ── Header ── */}
107
  <header className="app-header">
108
+
109
+ {/* Left: Logo */}
110
+ <div className="header-logo">
111
+ <div className="logo-orb">
112
+ <div className="orb-ring r1" />
113
+ <div className="orb-ring r2" />
114
+ <div className="orb-ring r3" />
115
+ <div className="orb-core" />
116
+ </div>
117
+ <div className="logo-text">
118
+ <span className="logo-name">SOMA</span>
119
+ <span className="logo-sub t-label">Cognitive AI</span>
120
+ </div>
121
  </div>
122
 
123
+ {/* Center: EEG + state */}
124
  <div className="header-center">
125
+ <EEGWave cognitiveState={brainState.cognitiveState} isActive={brainState.isLoading} />
126
+ <span className={`state-label t-label ${brainState.isLoading ? 'firing' : ''}`}>
127
+ {brainState.cognitiveState}
128
+ </span>
 
 
 
 
129
  </div>
130
 
131
+ {/* Right: Controls */}
132
+ <div className="header-controls">
133
+ <div className="view-tabs">
134
+ <button
135
+ className={`view-tab ${rightPanel === 'graph' ? 'active' : ''}`}
136
+ onClick={() => setRightPanel('graph')}
137
+ >Mesh</button>
138
+ <button
139
+ className={`view-tab ${rightPanel === 'dreams' ? 'active' : ''}`}
140
+ onClick={() => setRightPanel('dreams')}
141
+ >Dreams</button>
142
  </div>
143
 
144
+ <button className="sleep-btn" onClick={handleSleep} disabled={brainState.isLoading} title="Consolidate memories">
145
+ Sleep
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  </button>
147
 
148
  <select
149
+ className="persona-select t-label"
150
  value={currentPersona}
151
  onChange={e => setCurrentPersona(e.target.value)}
152
  >
 
154
  <option value="User_Beta">Beta</option>
155
  <option value="System_Admin">Admin</option>
156
  </select>
157
+
158
+ <button className="theme-toggle" onClick={toggleTheme} title="Toggle theme" aria-label="Toggle theme">
159
+ {theme === 'dark' ? (
160
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
161
+ <circle cx="12" cy="12" r="5"/>
162
+ <line x1="12" y1="1" x2="12" y2="3"/>
163
+ <line x1="12" y1="21" x2="12" y2="23"/>
164
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
165
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
166
+ <line x1="1" y1="12" x2="3" y2="12"/>
167
+ <line x1="21" y1="12" x2="23" y2="12"/>
168
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
169
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
170
+ </svg>
171
+ ) : (
172
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
173
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
174
+ </svg>
175
+ )}
176
+ </button>
177
+
178
+ <button className="help-btn t-label" onClick={() => setShowOnboarding(true)} title="About">?</button>
179
  </div>
180
  </header>
181
 
182
  {/* ── Three-Column Layout ── */}
183
  <div className="tri-layout">
184
 
 
185
  <div className="col-chat">
186
  <ChatPanel
187
  messages={messages}
 
193
  />
194
  </div>
195
 
 
196
  <div className="col-brain">
197
  <BrainProcess
198
  brainState={brainState}
 
199
  currentPersona={currentPersona}
200
  />
201
  </div>
202
 
 
203
  <div className="col-graph">
204
+ {rightPanel === 'graph'
205
+ ? <KnowledgeGraph highlightedNodes={brainState.highlightedNodes} currentPersona={currentPersona} />
206
+ : <DreamSequence sparks={brainState.sparks} />
207
+ }
 
 
 
 
208
  </div>
209
 
210
  </div>
frontend/src/components/BrainProcess.css CHANGED
@@ -1,433 +1,385 @@
1
- /* ═══════════════════════════════════════════
2
  BrainProcess β€” Cognitive Architecture Panel
3
- ═══════════════════════════════════════════ */
4
 
5
- .bp-panel {
6
- display: flex;
7
- flex-direction: column;
8
  height: 100%;
9
  overflow-y: auto;
10
  overflow-x: hidden;
11
- padding: 16px 14px;
12
- gap: 18px;
13
- background: rgba(10, 5, 22, 0.5);
14
- border-left: 1px solid rgba(167, 139, 250, 0.12);
15
- border-right: 1px solid rgba(167, 139, 250, 0.12);
16
- }
17
-
18
- .bp-panel::-webkit-scrollbar { width: 3px; }
19
- .bp-panel::-webkit-scrollbar-thumb { background: rgba(124, 58, 237, 0.35); border-radius: 2px; }
20
-
21
- /* ── Header ── */
22
- .bp-header {
23
- display: flex;
24
- justify-content: space-between;
25
- align-items: center;
26
- padding-bottom: 12px;
27
- border-bottom: 1px solid rgba(167, 139, 250, 0.15);
28
- flex-shrink: 0;
29
- }
30
-
31
- .bp-title {
32
- font-family: var(--font-mono);
33
- font-size: 0.6rem;
34
- font-weight: 700;
35
- letter-spacing: 0.22em;
36
- color: var(--accent-synapse);
37
- text-transform: uppercase;
38
- }
39
-
40
- .bp-status-badge {
41
  display: flex;
42
- align-items: center;
43
- gap: 6px;
44
- font-family: var(--font-mono);
45
- font-size: 0.52rem;
46
- letter-spacing: 0.12em;
47
- text-transform: uppercase;
48
- padding: 3px 8px;
49
- border-radius: 20px;
50
- border: 1px solid rgba(255,255,255,0.06);
51
- }
52
-
53
- .bp-status-badge.idle {
54
- color: #6b5fa0;
55
- background: rgba(15, 8, 30, 0.6);
56
- }
57
-
58
- .bp-status-badge.active {
59
- color: #10b981;
60
- background: rgba(16, 185, 129, 0.08);
61
- border-color: rgba(16, 185, 129, 0.25);
62
- }
63
-
64
- .bp-status-dot {
65
- width: 6px;
66
- height: 6px;
67
- border-radius: 50%;
68
- background: currentColor;
69
- }
70
-
71
- .bp-status-badge.active .bp-status-dot {
72
- animation: dot-pulse 1s infinite alternate;
73
  }
74
 
75
- @keyframes dot-pulse {
76
- from { opacity: 1; transform: scale(1); }
77
- to { opacity: 0.4; transform: scale(0.7); }
78
- }
79
 
80
- /* ── Sections ── */
81
  .bp-section { flex-shrink: 0; }
82
 
83
- .bp-section-label {
84
- font-family: var(--font-mono);
85
- font-size: 0.5rem;
86
- color: #6b5fa0;
87
- text-transform: uppercase;
88
- letter-spacing: 0.2em;
89
- margin-bottom: 10px;
90
- padding-left: 6px;
91
- border-left: 2px solid rgba(192, 132, 252, 0.35);
92
- }
93
-
94
- /* ── Pipeline ── */
 
 
 
 
95
  .bp-pipeline {
96
  display: flex;
97
  flex-direction: column;
98
  gap: 0;
 
 
99
  }
100
 
101
- .bp-pipeline-row {
102
- display: flex;
103
- flex-direction: column;
104
- align-items: stretch;
105
- }
106
 
107
  .bp-stage {
108
  position: relative;
109
- background: rgba(14, 8, 28, 0.85);
110
- border: 1px solid rgba(167, 139, 250, 0.15);
 
111
  border-radius: 10px;
112
- padding: 12px 14px;
113
  display: flex;
114
  align-items: flex-start;
115
- gap: 12px;
116
- transition: all 0.35s ease;
 
117
  overflow: hidden;
 
118
  }
119
 
120
- .bp-stage::before {
121
- content: '';
122
- position: absolute;
123
- inset: 0;
124
- border-radius: 10px;
125
- background: transparent;
126
- transition: background 0.35s ease;
127
- }
128
-
129
  .bp-stage.active {
130
- border-color: var(--stage-color);
131
- box-shadow: 0 0 22px rgba(192, 132, 252, 0.22), inset 0 0 18px rgba(124, 58, 237, 0.08);
 
 
 
 
132
  }
133
 
134
- /* Per-stage active glow colours */
135
- .bp-stage[style*="--stage-color: #f59e0b"].active {
136
- box-shadow: 0 0 22px rgba(245, 158, 11, 0.25), inset 0 0 18px rgba(245, 158, 11, 0.06);
137
- }
138
- .bp-stage[style*="--stage-color: #06b6d4"].active {
139
- box-shadow: 0 0 22px rgba(6, 182, 212, 0.25), inset 0 0 18px rgba(6, 182, 212, 0.06);
140
- }
141
- .bp-stage[style*="--stage-color: #10b981"].active {
142
- box-shadow: 0 0 22px rgba(16, 185, 129, 0.25), inset 0 0 18px rgba(16, 185, 129, 0.06);
143
- }
144
-
145
- .bp-stage.active::before {
146
- background: rgba(124, 58, 237, 0.06);
147
- }
148
-
149
- .bp-stage.waiting { opacity: 0.45; }
150
 
151
  .bp-stage.done {
152
- border-color: rgba(16, 185, 129, 0.3);
153
- opacity: 0.75;
154
  }
155
 
156
- .bp-stage-pulse-ring {
 
157
  position: absolute;
158
  inset: -2px;
159
  border-radius: 11px;
160
- border: 2px solid var(--stage-color);
161
- animation: pulse-ring 1.6s infinite;
162
  pointer-events: none;
163
  }
164
 
165
- @keyframes pulse-ring {
166
- 0% { transform: scale(1); opacity: 0.7; }
167
- 100% { transform: scale(1.03); opacity: 0; }
168
  }
169
 
170
- .bp-stage-icon {
171
- font-size: 1.3rem;
 
 
 
 
 
 
 
 
 
 
172
  flex-shrink: 0;
173
- margin-top: 1px;
174
- transition: filter 0.3s;
175
  }
176
 
177
- .bp-stage.active .bp-stage-icon {
178
- filter: drop-shadow(0 0 8px var(--stage-color));
179
  }
180
 
181
  .bp-stage-body { flex: 1; min-width: 0; }
182
 
183
- .bp-stage-label-text {
184
- font-family: var(--font-mono);
185
- font-size: 0.7rem;
186
- font-weight: 700;
187
- letter-spacing: 0.15em;
188
- color: #b8a8e8;
189
  margin-bottom: 2px;
 
190
  }
191
 
192
- .bp-stage.active .bp-stage-label-text {
193
- color: var(--stage-color);
194
- text-shadow: 0 0 10px var(--stage-color);
195
- }
196
 
197
  .bp-stage-region {
198
- font-family: var(--font-mono);
199
- font-size: 0.52rem;
200
- color: #5a4f80;
201
- letter-spacing: 0.1em;
202
- margin-bottom: 5px;
203
- text-transform: uppercase;
204
  }
205
 
206
- .bp-stage.active .bp-stage-region { color: #8a7ab0; }
207
-
208
  .bp-stage-desc {
209
- font-size: 0.65rem;
210
- color: #7a6fa0;
211
- line-height: 1.5;
 
212
  }
213
 
214
- .bp-stage.active .bp-stage-desc { color: #a898d0; }
215
 
216
- .bp-stage-check {
217
- font-size: 0.8rem;
218
- color: #10b981;
219
  font-weight: 700;
220
  flex-shrink: 0;
221
- margin-top: 2px;
222
  }
223
 
224
- /* ── Connector between stages ── */
225
- .bp-connector {
226
  display: flex;
227
  flex-direction: column;
228
  align-items: center;
229
- padding: 4px 0;
230
  gap: 0;
 
231
  }
232
 
233
- .bp-connector-line {
234
  width: 1px;
235
- height: 12px;
236
- background: rgba(167, 139, 250, 0.2);
237
- transition: background 0.3s;
238
  }
239
 
240
- .bp-connector.flowing .bp-connector-line {
241
- background: linear-gradient(to bottom, rgba(192, 132, 252, 0.6), rgba(192, 132, 252, 0.1));
242
- animation: line-flow 1.5s infinite;
243
  }
244
 
245
- @keyframes line-flow {
246
- 0%, 100% { opacity: 0.4; }
247
- 50% { opacity: 1; }
248
  }
249
 
250
- .bp-connector-arrow {
251
- font-size: 0.7rem;
252
- color: rgba(167, 139, 250, 0.35);
253
- line-height: 1;
254
- }
255
-
256
- .bp-connector.flowing .bp-connector-arrow {
257
- color: var(--accent-synapse);
258
- animation: line-flow 1.5s infinite;
259
  }
260
 
261
- /* ── Memory Grid ── */
 
 
262
  .bp-mem-grid {
263
  display: grid;
264
  grid-template-columns: 1fr 1fr;
265
- gap: 8px;
266
  }
267
 
268
  .bp-mem-card {
269
  position: relative;
270
- background: rgba(14, 8, 28, 0.85);
271
- border: 1px solid rgba(255,255,255,0.07);
272
- border-radius: 10px;
273
- padding: 10px 10px 14px 10px;
274
- display: flex;
275
- align-items: center;
276
- gap: 8px;
277
  overflow: hidden;
278
- transition: border-color 0.3s;
 
 
 
 
 
279
  }
280
 
281
- .bp-mem-card[data-layer="sensory"] { border-left: 3px solid #06b6d4; }
282
- .bp-mem-card[data-layer="semantic"] { border-left: 3px solid #a78bfa; }
283
- .bp-mem-card[data-layer="episodic"] { border-left: 3px solid #f59e0b; }
284
- .bp-mem-card[data-layer="working"] { border-left: 3px solid #10b981; }
285
-
286
- .bp-mem-card[data-layer="sensory"] { background: rgba(6, 182, 212, 0.04); }
287
- .bp-mem-card[data-layer="semantic"] { background: rgba(167, 139, 250, 0.04); }
288
- .bp-mem-card[data-layer="episodic"] { background: rgba(245, 158, 11, 0.04); }
289
- .bp-mem-card[data-layer="working"] { background: rgba(16, 185, 129, 0.04); }
290
 
291
- .bp-mem-icon-wrap { flex-shrink: 0; }
 
 
 
 
 
 
 
 
 
 
 
292
 
293
- .bp-mem-icon {
294
- font-size: 1.1rem;
295
- filter: drop-shadow(0 0 6px currentColor);
 
 
 
296
  }
297
 
298
- .bp-mem-info { flex: 1; min-width: 0; }
 
 
 
 
 
 
 
 
 
 
 
299
 
300
  .bp-mem-name {
301
- font-family: var(--font-mono);
302
- font-size: 0.62rem;
303
- font-weight: 700;
304
- color: #d4c8f8;
305
- letter-spacing: 0.1em;
306
- text-transform: uppercase;
307
  }
308
 
309
- .bp-mem-sub {
310
  font-family: var(--font-mono);
311
- font-size: 0.5rem;
312
- color: #6b5fa0;
313
- letter-spacing: 0.08em;
314
- margin-top: 2px;
 
315
  }
316
 
317
- .bp-mem-sub2 {
318
- font-size: 0.48rem;
319
- color: #4a4060;
320
- margin-top: 1px;
 
321
  }
322
 
323
- .bp-mem-count {
324
- font-family: var(--font-mono);
325
- font-size: 1.4rem;
326
- font-weight: 700;
327
- flex-shrink: 0;
328
- filter: drop-shadow(0 0 8px currentColor);
329
  line-height: 1;
330
  }
331
 
332
- /* thin bottom accent bar */
333
  .bp-mem-bar {
334
  position: absolute;
335
  bottom: 0;
336
- left: 0; right: 0;
 
337
  height: 2px;
338
- opacity: 0.25;
339
- border-radius: 0 0 10px 10px;
340
  }
341
 
342
- /* ── Internal Reflection ── */
 
 
343
  .bp-reflection {
344
- background: rgba(124, 58, 237, 0.08);
345
- border: 1px solid rgba(167, 139, 250, 0.2);
346
- border-radius: 10px;
347
- padding: 12px 14px;
348
  display: flex;
349
  gap: 10px;
350
  align-items: flex-start;
351
- animation: slide-in 0.4s ease;
352
  }
353
 
354
- @keyframes slide-in {
355
- from { opacity: 0; transform: translateY(-6px); }
356
- to { opacity: 1; transform: translateY(0); }
357
- }
358
-
359
- .bp-reflection-bubble {
360
- font-size: 1.1rem;
361
  flex-shrink: 0;
362
- margin-top: 1px;
 
363
  }
364
 
365
- .bp-reflection-text {
366
- font-size: 0.75rem;
367
- color: #c8b8f0;
368
- line-height: 1.65;
369
  font-style: italic;
370
  }
371
 
372
- /* ── Traces ── */
 
 
373
  .bp-traces {
374
  display: flex;
375
  flex-direction: column;
376
  gap: 5px;
377
  }
378
 
379
- .bp-trace-row {
380
  display: flex;
381
  gap: 8px;
382
  align-items: flex-start;
383
- padding: 6px 10px;
 
384
  border-radius: 7px;
385
- background: rgba(14, 8, 28, 0.6);
386
- animation: trace-in 0.25s ease both;
387
- }
388
-
389
- @keyframes trace-in {
390
- from { opacity: 0; transform: translateX(-6px); }
391
- to { opacity: 1; transform: translateX(0); }
392
  }
393
 
394
- .bp-trace-phase {
395
- font-family: var(--font-mono);
396
- font-size: 0.46rem;
397
- font-weight: 700;
398
- letter-spacing: 0.08em;
399
  padding: 2px 6px;
400
  border-radius: 3px;
401
  flex-shrink: 0;
402
  margin-top: 2px;
 
403
  }
404
 
405
- .bp-trace-row.perception .bp-trace-phase { background: rgba(245, 158, 11, 0.2); color: #f59e0b; }
406
- .bp-trace-row.recall .bp-trace-phase { background: rgba(6, 182, 212, 0.2); color: #06b6d4; }
407
- .bp-trace-row.association .bp-trace-phase { background: rgba(167, 139, 250, 0.2); color: #a78bfa; }
408
- .bp-trace-row.synthesis .bp-trace-phase,
409
- .bp-trace-row.reasoning .bp-trace-phase { background: rgba(16, 185, 129, 0.2); color: #10b981; }
410
 
411
  .bp-trace-msg {
412
- font-family: var(--font-mono);
413
- font-size: 0.6rem;
414
- color: #a898cc;
415
  line-height: 1.55;
 
416
  }
417
 
418
- /* ── Sparks ── */
419
- .bp-sparks {
420
- display: flex;
421
- flex-direction: column;
422
- gap: 8px;
 
 
 
 
423
  }
424
 
425
- .bp-spark-card {
426
- background: rgba(192, 132, 252, 0.05);
427
- border: 1px solid rgba(192, 132, 252, 0.14);
428
- border-radius: 8px;
429
- padding: 10px 12px;
430
- animation: slide-in 0.4s ease;
 
 
431
  }
432
 
433
  .bp-spark-tags {
@@ -438,92 +390,107 @@
438
  }
439
 
440
  .bp-spark-tag {
441
- font-family: var(--font-mono);
442
- font-size: 0.48rem;
443
- background: rgba(240, 171, 252, 0.1);
444
- color: #e879f9;
 
445
  padding: 2px 7px;
446
  border-radius: 3px;
447
- text-transform: uppercase;
448
- letter-spacing: 0.08em;
449
  }
450
 
451
- .bp-spark-text {
452
- font-size: 0.7rem;
453
- color: #c0b0e0;
454
  line-height: 1.55;
455
  }
456
 
457
- /* ── Idle state ── */
 
 
458
  .bp-idle {
459
  flex: 1;
460
  display: flex;
461
  flex-direction: column;
462
  align-items: center;
463
  justify-content: center;
464
- gap: 18px;
465
- padding: 30px 10px;
466
  text-align: center;
467
  }
468
 
469
- .bp-idle-orb {
470
- width: 64px;
471
- height: 64px;
472
- border-radius: 50%;
473
- border: 1px solid rgba(192, 132, 252, 0.2);
474
- background: rgba(124, 58, 237, 0.06);
475
- display: flex;
476
- align-items: center;
477
- justify-content: center;
 
 
 
478
  }
479
 
480
- .bp-idle-core {
481
- width: 22px;
482
- height: 22px;
483
  border-radius: 50%;
484
- background: rgba(192, 132, 252, 0.35);
485
- animation: idle-breathe 3.5s infinite ease-in-out;
486
  }
 
 
 
487
 
488
- @keyframes idle-breathe {
489
- 0%, 100% { transform: scale(1); opacity: 0.35; box-shadow: none; }
490
- 50% { transform: scale(1.4); opacity: 0.75; box-shadow: 0 0 20px rgba(192, 132, 252, 0.4); }
 
 
 
 
 
 
491
  }
492
 
493
- .bp-idle-hint {
494
- font-family: var(--font-mono);
495
- font-size: 0.58rem;
496
- color: #6b5fa0;
497
  line-height: 1.8;
 
 
498
  letter-spacing: 0.06em;
499
- text-transform: uppercase;
500
  }
501
 
502
- .bp-idle-legend {
 
 
 
 
 
503
  display: flex;
504
  flex-direction: column;
505
- gap: 8px;
506
- align-self: stretch;
507
- background: rgba(14, 8, 28, 0.5);
508
- border: 1px solid rgba(167, 139, 250, 0.1);
509
- border-radius: 8px;
510
- padding: 12px 14px;
511
  }
512
 
513
- .bp-legend-row {
514
  display: flex;
515
  align-items: center;
516
- gap: 8px;
517
- font-family: var(--font-mono);
518
- font-size: 0.56rem;
519
- color: #8a7ab8;
520
- letter-spacing: 0.05em;
521
  }
522
 
523
- .bp-legend-dot {
524
  width: 8px;
525
  height: 8px;
526
  border-radius: 50%;
527
  flex-shrink: 0;
528
  filter: drop-shadow(0 0 4px currentColor);
529
  }
 
 
 
 
 
 
 
1
+ /* ═══════════════════════════════════════════════
2
  BrainProcess β€” Cognitive Architecture Panel
3
+ ═══════════════════════════════════════════════ */
4
 
5
+ .bp {
 
 
6
  height: 100%;
7
  overflow-y: auto;
8
  overflow-x: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  display: flex;
10
+ flex-direction: column;
11
+ gap: 28px;
12
+ padding: 22px 20px 28px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  }
14
 
15
+ .bp::-webkit-scrollbar { width: 3px; }
16
+ .bp::-webkit-scrollbar-thumb { background: var(--border-1); border-radius: 2px; }
 
 
17
 
18
+ /* ── Section ── */
19
  .bp-section { flex-shrink: 0; }
20
 
21
+ /* Italic serif section heading β€” the key typographic choice */
22
+ .bp-sh {
23
+ font-family: var(--font-display);
24
+ font-style: italic;
25
+ font-size: 0.82rem;
26
+ font-weight: 400;
27
+ color: var(--text-1);
28
+ letter-spacing: 0.01em;
29
+ margin-bottom: 14px;
30
+ padding-bottom: 8px;
31
+ border-bottom: 1px solid var(--border-0);
32
+ }
33
+
34
+ /* ══════════════════════════════════════
35
+ Pipeline β€” 3D depth stack
36
+ ══════════════════════════════════════ */
37
  .bp-pipeline {
38
  display: flex;
39
  flex-direction: column;
40
  gap: 0;
41
+ perspective: 900px;
42
+ perspective-origin: 50% 0%;
43
  }
44
 
45
+ .bp-stage-wrap { display: flex; flex-direction: column; }
 
 
 
 
46
 
47
  .bp-stage {
48
  position: relative;
49
+ background: var(--bg-2);
50
+ border: 1px solid var(--border-0);
51
+ border-left: 3px solid var(--clr, var(--border-1));
52
  border-radius: 10px;
53
+ padding: 14px 16px;
54
  display: flex;
55
  align-items: flex-start;
56
+ gap: 14px;
57
+ transition: transform 0.35s ease, box-shadow 0.35s ease,
58
+ border-color 0.35s ease, background 0.35s ease;
59
  overflow: hidden;
60
+ transform: translateZ(0);
61
  }
62
 
63
+ /* Active: lift toward viewer */
 
 
 
 
 
 
 
 
64
  .bp-stage.active {
65
+ background: var(--bg-3);
66
+ border-color: var(--clr);
67
+ transform: perspective(600px) translateZ(12px) scale(1.01);
68
+ box-shadow: var(--glow-pulse),
69
+ 0 8px 24px rgba(0,0,0,0.25);
70
+ z-index: 2;
71
  }
72
 
73
+ .bp-stage.dim { opacity: 0.42; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
  .bp-stage.done {
76
+ opacity: 0.7;
77
+ border-left-color: var(--calm);
78
  }
79
 
80
+ /* Pulsing aura ring */
81
+ .bp-stage-aura {
82
  position: absolute;
83
  inset: -2px;
84
  border-radius: 11px;
85
+ border: 1.5px solid var(--clr);
86
+ animation: aura-pulse 1.8s infinite;
87
  pointer-events: none;
88
  }
89
 
90
+ @keyframes aura-pulse {
91
+ 0% { transform: scale(1); opacity: 0.7; }
92
+ 100% { transform: scale(1.04); opacity: 0; }
93
  }
94
 
95
+ /* Stage number badge */
96
+ .bp-stage-badge {
97
+ width: 26px;
98
+ height: 26px;
99
+ border-radius: 6px;
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ font-family: var(--font-mono);
104
+ font-size: 0.58rem;
105
+ font-weight: 700;
106
+ color: var(--bg-0);
107
  flex-shrink: 0;
108
+ letter-spacing: 0.04em;
109
+ transition: filter 0.3s ease;
110
  }
111
 
112
+ .bp-stage.active .bp-stage-badge {
113
+ filter: drop-shadow(0 0 6px var(--clr));
114
  }
115
 
116
  .bp-stage-body { flex: 1; min-width: 0; }
117
 
118
+ .bp-stage-name {
119
+ font-size: 0.85rem;
120
+ font-weight: 600;
121
+ color: var(--text-0);
122
+ letter-spacing: 0.01em;
 
123
  margin-bottom: 2px;
124
+ transition: color 0.3s;
125
  }
126
 
127
+ .bp-stage.active .bp-stage-name { color: var(--clr); }
 
 
 
128
 
129
  .bp-stage-region {
130
+ font-size: 0.48rem;
131
+ color: var(--text-2);
132
+ letter-spacing: 0.18em;
133
+ margin-bottom: 6px;
 
 
134
  }
135
 
 
 
136
  .bp-stage-desc {
137
+ font-size: 0.7rem;
138
+ color: var(--text-2);
139
+ line-height: 1.55;
140
+ transition: color 0.3s;
141
  }
142
 
143
+ .bp-stage.active .bp-stage-desc { color: var(--text-1); }
144
 
145
+ .bp-stage-done {
146
+ font-size: 0.75rem;
147
+ color: var(--calm);
148
  font-weight: 700;
149
  flex-shrink: 0;
150
+ margin-top: 4px;
151
  }
152
 
153
+ /* Arrow connector */
154
+ .bp-arrow {
155
  display: flex;
156
  flex-direction: column;
157
  align-items: center;
158
+ padding: 3px 0 3px 14px; /* align with badge center */
159
  gap: 0;
160
+ pointer-events: none;
161
  }
162
 
163
+ .bp-arrow-line {
164
  width: 1px;
165
+ height: 14px;
166
+ background: var(--border-0);
167
+ transition: background 0.5s ease;
168
  }
169
 
170
+ .bp-arrow.pulse .bp-arrow-line {
171
+ animation: line-drip 1.2s ease infinite;
 
172
  }
173
 
174
+ @keyframes line-drip {
175
+ 0%, 100% { opacity: 0.3; }
176
+ 50% { opacity: 0.9; }
177
  }
178
 
179
+ .bp-arrow-head {
180
+ width: 0;
181
+ height: 0;
182
+ border-left: 4px solid transparent;
183
+ border-right: 4px solid transparent;
184
+ border-top: 5px solid var(--border-1);
 
 
 
185
  }
186
 
187
+ /* ══════════════════════════════════════
188
+ Memory Cards β€” 3D tilt
189
+ ══════════════════════════════════════ */
190
  .bp-mem-grid {
191
  display: grid;
192
  grid-template-columns: 1fr 1fr;
193
+ gap: 10px;
194
  }
195
 
196
  .bp-mem-card {
197
  position: relative;
198
+ background: var(--bg-2);
199
+ border: 1px solid var(--border-0);
200
+ border-radius: 12px;
201
+ padding: 14px 14px 18px;
 
 
 
202
  overflow: hidden;
203
+ cursor: default;
204
+ /* 3D tilt */
205
+ transform: perspective(600px) rotateX(var(--tx, 0deg)) rotateY(var(--ty, 0deg));
206
+ transform-style: preserve-3d;
207
+ transition: transform 0.12s ease, box-shadow 0.12s ease;
208
+ will-change: transform;
209
  }
210
 
211
+ .bp-mem-card:hover {
212
+ box-shadow: var(--glow-pulse), 0 10px 28px rgba(0,0,0,0.3);
213
+ border-color: var(--clr);
214
+ z-index: 1;
215
+ }
 
 
 
 
216
 
217
+ /* Specular sheen that moves with tilt */
218
+ .bp-mem-shine {
219
+ position: absolute;
220
+ inset: 0;
221
+ background: radial-gradient(
222
+ circle at calc(50% + var(--ty, 0deg) * 3) calc(50% - var(--tx, 0deg) * 3),
223
+ rgba(255,255,255,0.07) 0%,
224
+ transparent 60%
225
+ );
226
+ pointer-events: none;
227
+ border-radius: inherit;
228
+ }
229
 
230
+ [data-theme="light"] .bp-mem-shine {
231
+ background: radial-gradient(
232
+ circle at 50% 30%,
233
+ rgba(255,255,255,0.4) 0%,
234
+ transparent 60%
235
+ );
236
  }
237
 
238
+ /* Per-layer left accent */
239
+ .bp-mem-card[data-layer="sensory"] { border-left: 3px solid #4ecdc4; }
240
+ .bp-mem-card[data-layer="semantic"] { border-left: 3px solid #a87ecf; }
241
+ .bp-mem-card[data-layer="episodic"] { border-left: 3px solid #d4a853; }
242
+ .bp-mem-card[data-layer="working"] { border-left: 3px solid #e07a38; }
243
+
244
+ .bp-mem-top {
245
+ display: flex;
246
+ justify-content: space-between;
247
+ align-items: flex-start;
248
+ margin-bottom: 5px;
249
+ }
250
 
251
  .bp-mem-name {
252
+ font-size: 0.78rem;
253
+ font-weight: 600;
254
+ color: var(--text-0);
255
+ letter-spacing: 0.01em;
 
 
256
  }
257
 
258
+ .bp-mem-count {
259
  font-family: var(--font-mono);
260
+ font-size: 1.5rem;
261
+ font-weight: 700;
262
+ line-height: 1;
263
+ letter-spacing: -0.02em;
264
+ filter: drop-shadow(0 0 6px currentColor);
265
  }
266
 
267
+ .bp-mem-tech {
268
+ font-size: 0.46rem;
269
+ letter-spacing: 0.2em;
270
+ color: var(--clr);
271
+ margin-bottom: 3px;
272
  }
273
 
274
+ .bp-mem-detail {
275
+ font-size: 0.65rem;
276
+ color: var(--text-2);
 
 
 
277
  line-height: 1;
278
  }
279
 
 
280
  .bp-mem-bar {
281
  position: absolute;
282
  bottom: 0;
283
+ left: 0;
284
+ right: 0;
285
  height: 2px;
286
+ opacity: 0.22;
287
+ border-radius: 0 0 12px 12px;
288
  }
289
 
290
+ /* ══════════════════════════════════════
291
+ Reflection
292
+ ══════════════════════════════════════ */
293
  .bp-reflection {
294
+ background: var(--bg-2);
295
+ border: 1px solid var(--border-1);
296
+ border-radius: 12px;
297
+ padding: 16px 18px;
298
  display: flex;
299
  gap: 10px;
300
  align-items: flex-start;
301
+ animation: soma-rise 0.4s ease both;
302
  }
303
 
304
+ .bp-ref-mark {
305
+ font-family: var(--font-display);
306
+ font-size: 2.5rem;
307
+ color: var(--pulse);
308
+ opacity: 0.5;
309
+ line-height: 0.7;
 
310
  flex-shrink: 0;
311
+ margin-top: 6px;
312
+ font-style: italic;
313
  }
314
 
315
+ .bp-reflection p {
316
+ font-size: 0.8rem;
317
+ color: var(--text-0);
318
+ line-height: 1.7;
319
  font-style: italic;
320
  }
321
 
322
+ /* ══════════════════════════════════════
323
+ Traces
324
+ ══════════════════════════════════════ */
325
  .bp-traces {
326
  display: flex;
327
  flex-direction: column;
328
  gap: 5px;
329
  }
330
 
331
+ .bp-trace {
332
  display: flex;
333
  gap: 8px;
334
  align-items: flex-start;
335
+ padding: 7px 10px;
336
+ background: var(--bg-2);
337
  border-radius: 7px;
338
+ animation: soma-rise 0.25s ease both;
 
 
 
 
 
 
339
  }
340
 
341
+ .bp-trace-ph {
342
+ font-size: 0.44rem;
343
+ letter-spacing: 0.12em;
 
 
344
  padding: 2px 6px;
345
  border-radius: 3px;
346
  flex-shrink: 0;
347
  margin-top: 2px;
348
+ font-weight: 700;
349
  }
350
 
351
+ .bp-trace.perception .bp-trace-ph { background: rgba(212,168,83,0.15); color: #d4a853; }
352
+ .bp-trace.recall .bp-trace-ph { background: rgba(78,205,196,0.15); color: #4ecdc4; }
353
+ .bp-trace.association .bp-trace-ph { background: rgba(168,126,207,0.15); color: #a87ecf; }
354
+ .bp-trace.synthesis .bp-trace-ph,
355
+ .bp-trace.reasoning .bp-trace-ph { background: rgba(224,122,56,0.15); color: #e07a38; }
356
 
357
  .bp-trace-msg {
358
+ font-size: 0.65rem;
359
+ color: var(--text-1);
 
360
  line-height: 1.55;
361
+ font-family: var(--font-mono);
362
  }
363
 
364
+ /* ══════════════════════════════════════
365
+ Sparks
366
+ ══════════════════════════════════════ */
367
+ .bp-spark-sub {
368
+ font-size: 0.46rem;
369
+ letter-spacing: 0.15em;
370
+ color: var(--text-2);
371
+ margin-bottom: 10px;
372
+ margin-top: -8px;
373
  }
374
 
375
+ .bp-sparks { display: flex; flex-direction: column; gap: 8px; }
376
+
377
+ .bp-spark {
378
+ background: var(--bg-2);
379
+ border: 1px solid var(--border-0);
380
+ border-radius: 10px;
381
+ padding: 12px 14px;
382
+ animation: soma-flip-in 0.4s ease both;
383
  }
384
 
385
  .bp-spark-tags {
 
390
  }
391
 
392
  .bp-spark-tag {
393
+ font-size: 0.44rem;
394
+ letter-spacing: 0.1em;
395
+ color: var(--pulse);
396
+ background: var(--border-0);
397
+ border: 1px solid var(--border-1);
398
  padding: 2px 7px;
399
  border-radius: 3px;
 
 
400
  }
401
 
402
+ .bp-spark p {
403
+ font-size: 0.72rem;
404
+ color: var(--text-1);
405
  line-height: 1.55;
406
  }
407
 
408
+ /* ══════════════════════════════════════
409
+ Idle
410
+ ══════════════════════════════════════ */
411
  .bp-idle {
412
  flex: 1;
413
  display: flex;
414
  flex-direction: column;
415
  align-items: center;
416
  justify-content: center;
417
+ gap: 22px;
418
+ padding: 20px 10px;
419
  text-align: center;
420
  }
421
 
422
+ /* Nested 3D rings as idle indicator */
423
+ .bp-idle-fig {
424
+ width: 72px;
425
+ height: 72px;
426
+ position: relative;
427
+ transform-style: preserve-3d;
428
+ animation: idle-fig-spin 14s linear infinite;
429
+ }
430
+
431
+ @keyframes idle-fig-spin {
432
+ from { transform: perspective(80px) rotateY(0deg) rotateX(20deg); }
433
+ to { transform: perspective(80px) rotateY(360deg) rotateX(20deg); }
434
  }
435
 
436
+ .bp-idle-r1, .bp-idle-r2, .bp-idle-r3 {
437
+ position: absolute;
438
+ inset: 0;
439
  border-radius: 50%;
440
+ border: 1px solid var(--pulse);
441
+ opacity: 0.35;
442
  }
443
+ .bp-idle-r1 { inset: 6px; transform: rotateX(0deg); }
444
+ .bp-idle-r2 { inset: 6px; transform: rotateX(60deg); }
445
+ .bp-idle-r3 { inset: 6px; transform: rotateX(-60deg); }
446
 
447
+ .bp-idle-dot {
448
+ position: absolute;
449
+ inset: 26px;
450
+ background: var(--pulse);
451
+ border-radius: 50%;
452
+ opacity: 0.55;
453
+ animation: soma-pulse 4s infinite ease-in-out;
454
+ filter: blur(1px);
455
+ box-shadow: 0 0 12px var(--pulse);
456
  }
457
 
458
+ .bp-idle-text {
459
+ font-size: 0.72rem;
460
+ color: var(--text-2);
 
461
  line-height: 1.8;
462
+ font-family: var(--font-mono);
463
+ font-size: 0.6rem;
464
  letter-spacing: 0.06em;
 
465
  }
466
 
467
+ .bp-idle-key {
468
+ align-self: stretch;
469
+ background: var(--bg-2);
470
+ border: 1px solid var(--border-0);
471
+ border-radius: 10px;
472
+ padding: 14px 16px;
473
  display: flex;
474
  flex-direction: column;
475
+ gap: 10px;
 
 
 
 
 
476
  }
477
 
478
+ .bp-idle-row {
479
  display: flex;
480
  align-items: center;
481
+ gap: 10px;
 
 
 
 
482
  }
483
 
484
+ .bp-idle-pip {
485
  width: 8px;
486
  height: 8px;
487
  border-radius: 50%;
488
  flex-shrink: 0;
489
  filter: drop-shadow(0 0 4px currentColor);
490
  }
491
+
492
+ .bp-idle-lbl {
493
+ font-size: 0.52rem;
494
+ color: var(--text-1);
495
+ letter-spacing: 0.12em;
496
+ }
frontend/src/components/BrainProcess.jsx CHANGED
@@ -1,127 +1,101 @@
1
  import './BrainProcess.css';
2
 
3
- const PIPELINE_STAGES = [
4
  {
5
  id: 'reflect',
6
- label: 'REFLECT',
7
  region: 'Prefrontal Cortex',
8
- desc: 'Analyses intent & sets cognitive direction',
9
- icon: 'β—ˆ',
10
  phases: ['perception'],
11
- color: '#f59e0b',
12
  },
13
  {
14
  id: 'retrieve',
15
- label: 'RETRIEVE',
16
  region: 'Hippocampus',
17
- desc: 'Searches sensory, semantic & episodic memory',
18
- icon: 'β—Ž',
19
  phases: ['recall', 'association'],
20
- color: '#06b6d4',
21
  },
22
  {
23
  id: 'synthesize',
24
- label: 'SYNTHESIZE',
25
  region: 'Neocortex',
26
- desc: 'Integrates memories β†’ generates response',
27
- icon: 'β—‰',
28
  phases: ['synthesis', 'reasoning'],
29
- color: '#10b981',
30
  },
31
  ];
32
 
33
  const MEMORY_LAYERS = [
34
- {
35
- id: 'sensory',
36
- label: 'Sensory',
37
- sublabel: 'ChromaDB',
38
- sublabel2: 'Vector embeddings',
39
- key: 'sensoryDocuments',
40
- color: '#06b6d4',
41
- icon: 'β—ˆ',
42
- },
43
- {
44
- id: 'semantic',
45
- label: 'Semantic',
46
- sublabel: 'Neo4j Graph',
47
- sublabel2: 'Knowledge graph',
48
- key: 'graphRelations',
49
- color: '#a78bfa',
50
- icon: '⬑',
51
- },
52
- {
53
- id: 'episodic',
54
- label: 'Episodic',
55
- sublabel: 'SQLite',
56
- sublabel2: 'Temporal log',
57
- key: 'workingMemory',
58
- color: '#f59e0b',
59
- icon: 'β—‡',
60
- },
61
- {
62
- id: 'working',
63
- label: 'Working',
64
- sublabel: 'Active Context',
65
- sublabel2: 'Short-term buffer',
66
- key: 'workingMemory',
67
- color: '#10b981',
68
- icon: 'β—‰',
69
- },
70
  ];
71
 
72
  function getActiveStage(traces, isLoading) {
73
  if (!isLoading || !traces?.length) return null;
74
  const last = traces[traces.length - 1];
75
- if (!last) return null;
76
- for (const stage of PIPELINE_STAGES) {
77
- if (stage.phases.includes(last.phase)) return stage.id;
78
  }
79
  return null;
80
  }
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  function BrainProcess({ brainState }) {
83
- const isActive = brainState.isLoading;
84
- const currentStage = getActiveStage(brainState.traces, isActive);
85
- const recentTraces = (brainState.traces || []).slice(-6);
 
 
86
 
87
  return (
88
- <div className="bp-panel">
89
 
90
- {/* ── Header ── */}
91
- <div className="bp-header">
92
- <span className="bp-title">Cognitive Architecture</span>
93
- <span className={`bp-status-badge ${isActive ? 'active' : 'idle'}`}>
94
- <span className="bp-status-dot" />
95
- {isActive ? 'Processing' : 'Idle'}
96
- </span>
97
- </div>
98
-
99
- {/* ── Pipeline ── */}
100
  <section className="bp-section">
101
- <div className="bp-section-label">Neural Pipeline</div>
102
- <div className="bp-pipeline">
103
- {PIPELINE_STAGES.map((stage, idx) => {
104
- const isThis = currentStage === stage.id;
105
- const isPast = currentStage && PIPELINE_STAGES.findIndex(s => s.id === currentStage) > idx && isActive;
106
  return (
107
- <div key={stage.id} className="bp-pipeline-row">
108
  <div
109
- className={`bp-stage ${isThis ? 'active' : ''} ${isPast ? 'done' : ''} ${isActive && !isThis && !isPast ? 'waiting' : ''}`}
110
- style={{ '--stage-color': stage.color }}
111
  >
112
- {isThis && <div className="bp-stage-pulse-ring" />}
113
- <div className="bp-stage-icon" style={{ color: stage.color }}>{stage.icon}</div>
 
 
114
  <div className="bp-stage-body">
115
- <div className="bp-stage-label-text">{stage.label}</div>
116
- <div className="bp-stage-region">{stage.region}</div>
117
- <div className="bp-stage-desc">{stage.desc}</div>
118
  </div>
119
- {isPast && <div className="bp-stage-check">βœ“</div>}
120
  </div>
121
- {idx < PIPELINE_STAGES.length - 1 && (
122
- <div className={`bp-connector ${isActive ? 'flowing' : ''}`}>
123
- <div className="bp-connector-line" />
124
- <div className="bp-connector-arrow">↓</div>
125
  </div>
126
  )}
127
  </div>
@@ -130,93 +104,98 @@ function BrainProcess({ brainState }) {
130
  </div>
131
  </section>
132
 
133
- {/* ── Memory Layers ── */}
134
  <section className="bp-section">
135
- <div className="bp-section-label">Memory Architecture</div>
136
- <div className="bp-mem-grid">
137
  {MEMORY_LAYERS.map(layer => (
138
  <div
139
  key={layer.id}
140
- className="bp-mem-card"
141
- style={{ '--mem-color': layer.color }}
142
  data-layer={layer.id}
 
 
 
143
  >
144
- <div className="bp-mem-icon-wrap">
145
- <span className="bp-mem-icon" style={{ color: layer.color }}>{layer.icon}</span>
146
- </div>
147
- <div className="bp-mem-info">
148
- <div className="bp-mem-name">{layer.label}</div>
149
- <div className="bp-mem-sub">{layer.sublabel}</div>
150
- <div className="bp-mem-sub2">{layer.sublabel2}</div>
151
- </div>
152
- <div className="bp-mem-count" style={{ color: layer.color }}>
153
- {layer.key ? (brainState[layer.key] ?? 0) : 'β€”'}
154
  </div>
 
 
155
  <div className="bp-mem-bar" style={{ background: layer.color }} />
156
  </div>
157
  ))}
158
  </div>
159
  </section>
160
 
161
- {/* ── Internal Reflection ── */}
162
  {brainState.reflection && (
163
  <section className="bp-section">
164
- <div className="bp-section-label">Internal Monologue</div>
165
  <div className="bp-reflection">
166
- <span className="bp-reflection-bubble">πŸ’­</span>
167
- <p className="bp-reflection-text">{brainState.reflection}</p>
168
  </div>
169
  </section>
170
  )}
171
 
172
- {/* ── Cognitive Trace ── */}
173
- {recentTraces.length > 0 && (
174
  <section className="bp-section">
175
- <div className="bp-section-label">Live Cognitive Trace</div>
176
  <div className="bp-traces">
177
- {recentTraces.map((trace, idx) => (
178
- <div key={idx} className={`bp-trace-row ${trace.phase || ''}`}>
179
- <span className="bp-trace-phase">{(trace.phase || 'info').toUpperCase()}</span>
180
- <span className="bp-trace-msg">{trace.message}</span>
181
  </div>
182
  ))}
183
  </div>
184
  </section>
185
  )}
186
 
187
- {/* ── Neural Sparks ── */}
188
- {brainState.sparks?.length > 0 && (
189
  <section className="bp-section">
190
- <div className="bp-section-label">Neural Sparks β€” Background Dreaming</div>
 
191
  <div className="bp-sparks">
192
- {brainState.sparks.slice(0, 3).map((spark, idx) => (
193
- <div key={idx} className="bp-spark-card">
194
  <div className="bp-spark-tags">
195
- {spark.entities?.map(e => (
196
- <span key={e} className="bp-spark-tag">#{e}</span>
197
- ))}
198
  </div>
199
- <p className="bp-spark-text">{spark.content}</p>
200
  </div>
201
  ))}
202
  </div>
203
  </section>
204
  )}
205
 
206
- {/* ── Idle placeholder ── */}
207
- {!isActive && recentTraces.length === 0 && !brainState.reflection && !brainState.sparks?.length && (
208
  <div className="bp-idle">
209
- <div className="bp-idle-orb">
210
- <div className="bp-idle-core" />
 
 
 
211
  </div>
212
- <p className="bp-idle-hint">
213
- Send a message to watch the<br />
214
- cognitive process unfold live.
215
  </p>
216
- <div className="bp-idle-legend">
217
- <div className="bp-legend-row"><span className="bp-legend-dot" style={{ background: '#f59e0b' }} />Reflect β€” set intent</div>
218
- <div className="bp-legend-row"><span className="bp-legend-dot" style={{ background: '#06b6d4' }} />Retrieve β€” search memory</div>
219
- <div className="bp-legend-row"><span className="bp-legend-dot" style={{ background: '#10b981' }} />Synthesize β€” form response</div>
 
 
 
220
  </div>
221
  </div>
222
  )}
 
1
  import './BrainProcess.css';
2
 
3
+ const STAGES = [
4
  {
5
  id: 'reflect',
6
+ label: 'Reflect',
7
  region: 'Prefrontal Cortex',
8
+ desc: 'Sets cognitive direction, interprets intent',
 
9
  phases: ['perception'],
10
+ color: '#d4a853', /* amber */
11
  },
12
  {
13
  id: 'retrieve',
14
+ label: 'Retrieve',
15
  region: 'Hippocampus',
16
+ desc: 'Searches sensory, semantic & episodic stores',
 
17
  phases: ['recall', 'association'],
18
+ color: '#4ecdc4', /* teal */
19
  },
20
  {
21
  id: 'synthesize',
22
+ label: 'Synthesize',
23
  region: 'Neocortex',
24
+ desc: 'Weaves memories into language & response',
 
25
  phases: ['synthesis', 'reasoning'],
26
+ color: '#e07a38', /* fire */
27
  },
28
  ];
29
 
30
  const MEMORY_LAYERS = [
31
+ { id: 'sensory', label: 'Sensory', tech: 'ChromaDB', detail: 'Vector embeddings', key: 'sensoryDocuments', color: '#4ecdc4' },
32
+ { id: 'semantic', label: 'Semantic', tech: 'Neo4j', detail: 'Knowledge graph', key: 'graphRelations', color: '#a87ecf' },
33
+ { id: 'episodic', label: 'Episodic', tech: 'SQLite', detail: 'Temporal log', key: null, color: '#d4a853' },
34
+ { id: 'working', label: 'Working', tech: 'Context', detail: 'Active buffer', key: 'workingMemory', color: '#e07a38' },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  ];
36
 
37
  function getActiveStage(traces, isLoading) {
38
  if (!isLoading || !traces?.length) return null;
39
  const last = traces[traces.length - 1];
40
+ for (const s of STAGES) {
41
+ if (s.phases.includes(last?.phase)) return s.id;
 
42
  }
43
  return null;
44
  }
45
 
46
+ // 3D card tilt on mouse move
47
+ function tiltCard(e) {
48
+ const el = e.currentTarget;
49
+ const r = el.getBoundingClientRect();
50
+ const dx = (e.clientX - (r.left + r.width / 2)) / (r.width / 2);
51
+ const dy = (e.clientY - (r.top + r.height / 2)) / (r.height / 2);
52
+ el.style.setProperty('--tx', `${dy * -7}deg`);
53
+ el.style.setProperty('--ty', `${dx * 7}deg`);
54
+ }
55
+
56
+ function resetTilt(e) {
57
+ e.currentTarget.style.setProperty('--tx', '0deg');
58
+ e.currentTarget.style.setProperty('--ty', '0deg');
59
+ }
60
+
61
  function BrainProcess({ brainState }) {
62
+ const isActive = brainState.isLoading;
63
+ const activeId = getActiveStage(brainState.traces, isActive);
64
+ const traces = (brainState.traces || []).slice(-6);
65
+ const hasSparks = brainState.sparks?.length > 0;
66
+ const showIdle = !isActive && traces.length === 0 && !brainState.reflection && !hasSparks;
67
 
68
  return (
69
+ <div className="bp">
70
 
71
+ {/* ── Section: Pipeline ── */}
 
 
 
 
 
 
 
 
 
72
  <section className="bp-section">
73
+ <h3 className="bp-sh t-display">Neural Pipeline</h3>
74
+ <div className={`bp-pipeline ${isActive ? 'running' : ''}`}>
75
+ {STAGES.map((stage, i) => {
76
+ const isThis = activeId === stage.id;
77
+ const isDone = activeId && STAGES.findIndex(s => s.id === activeId) > i && isActive;
78
  return (
79
+ <div key={stage.id} className="bp-stage-wrap">
80
  <div
81
+ className={`bp-stage ${isThis ? 'active' : ''} ${isDone ? 'done' : ''} ${isActive && !isThis ? 'dim' : ''}`}
82
+ style={{ '--clr': stage.color }}
83
  >
84
+ {isThis && <div className="bp-stage-aura" />}
85
+ <div className="bp-stage-badge" style={{ background: stage.color }}>
86
+ {String(i + 1).padStart(2, '0')}
87
+ </div>
88
  <div className="bp-stage-body">
89
+ <div className="bp-stage-name">{stage.label}</div>
90
+ <div className="bp-stage-region t-label">{stage.region}</div>
91
+ <p className="bp-stage-desc">{stage.desc}</p>
92
  </div>
93
+ {isDone && <div className="bp-stage-done">βœ“</div>}
94
  </div>
95
+ {i < STAGES.length - 1 && (
96
+ <div className={`bp-arrow ${isActive ? 'pulse' : ''}`}>
97
+ <div className="bp-arrow-line" style={{ background: isActive ? STAGES[i].color : undefined }} />
98
+ <div className="bp-arrow-head" />
99
  </div>
100
  )}
101
  </div>
 
104
  </div>
105
  </section>
106
 
107
+ {/* ── Section: Memory Architecture ── */}
108
  <section className="bp-section">
109
+ <h3 className="bp-sh t-display">Memory Architecture</h3>
110
+ <div className="bp-mem-grid scene-3d">
111
  {MEMORY_LAYERS.map(layer => (
112
  <div
113
  key={layer.id}
114
+ className="bp-mem-card noisy"
 
115
  data-layer={layer.id}
116
+ style={{ '--clr': layer.color }}
117
+ onMouseMove={tiltCard}
118
+ onMouseLeave={resetTilt}
119
  >
120
+ <div className="bp-mem-shine" />
121
+ <div className="bp-mem-top">
122
+ <span className="bp-mem-name">{layer.label}</span>
123
+ <span className="bp-mem-count" style={{ color: layer.color }}>
124
+ {layer.key ? (brainState[layer.key] ?? 0) : 'β€”'}
125
+ </span>
 
 
 
 
126
  </div>
127
+ <div className="bp-mem-tech t-label">{layer.tech}</div>
128
+ <div className="bp-mem-detail">{layer.detail}</div>
129
  <div className="bp-mem-bar" style={{ background: layer.color }} />
130
  </div>
131
  ))}
132
  </div>
133
  </section>
134
 
135
+ {/* ── Section: Internal monologue ── */}
136
  {brainState.reflection && (
137
  <section className="bp-section">
138
+ <h3 className="bp-sh t-display">Internal Monologue</h3>
139
  <div className="bp-reflection">
140
+ <span className="bp-ref-mark">"</span>
141
+ <p>{brainState.reflection}</p>
142
  </div>
143
  </section>
144
  )}
145
 
146
+ {/* ── Section: Live Trace ── */}
147
+ {traces.length > 0 && (
148
  <section className="bp-section">
149
+ <h3 className="bp-sh t-display">Cognitive Trace</h3>
150
  <div className="bp-traces">
151
+ {traces.map((tr, i) => (
152
+ <div key={i} className={`bp-trace ${tr.phase || ''}`}>
153
+ <span className="bp-trace-ph t-label">{tr.phase || 'info'}</span>
154
+ <span className="bp-trace-msg">{tr.message}</span>
155
  </div>
156
  ))}
157
  </div>
158
  </section>
159
  )}
160
 
161
+ {/* ── Section: Neural Sparks ── */}
162
+ {hasSparks && (
163
  <section className="bp-section">
164
+ <h3 className="bp-sh t-display">Neural Sparks</h3>
165
+ <p className="bp-spark-sub t-label">Background dreaming β€” spontaneous associations</p>
166
  <div className="bp-sparks">
167
+ {brainState.sparks.slice(0, 3).map((s, i) => (
168
+ <div key={i} className="bp-spark">
169
  <div className="bp-spark-tags">
170
+ {s.entities?.map(e => <span key={e} className="bp-spark-tag t-label">#{e}</span>)}
 
 
171
  </div>
172
+ <p>{s.content}</p>
173
  </div>
174
  ))}
175
  </div>
176
  </section>
177
  )}
178
 
179
+ {/* ── Idle state ── */}
180
+ {showIdle && (
181
  <div className="bp-idle">
182
+ <div className="bp-idle-fig">
183
+ <div className="bp-idle-r1" />
184
+ <div className="bp-idle-r2" />
185
+ <div className="bp-idle-r3" />
186
+ <div className="bp-idle-dot" />
187
  </div>
188
+ <p className="bp-idle-text">
189
+ Send a message to watch <br />
190
+ the cognitive process unfold live.
191
  </p>
192
+ <div className="bp-idle-key">
193
+ {STAGES.map(s => (
194
+ <div key={s.id} className="bp-idle-row">
195
+ <span className="bp-idle-pip" style={{ background: s.color }} />
196
+ <span className="bp-idle-lbl t-label">{s.label} β€” {s.region}</span>
197
+ </div>
198
+ ))}
199
  </div>
200
  </div>
201
  )}
frontend/src/components/ChatPanel.css CHANGED
@@ -1,224 +1,213 @@
1
- /* ═══ Chat Panel ═══ */
 
 
 
2
  .chat-panel {
3
  display: flex;
4
  flex-direction: column;
5
- flex: 1;
6
  overflow: hidden;
7
- background: transparent;
8
- padding: 18px 20px;
9
  }
10
 
11
- /* ── Chat Header ── */
12
- .chat-header-scifi {
13
  display: flex;
14
  align-items: center;
15
  justify-content: space-between;
16
- margin-bottom: 16px;
17
- padding-bottom: 12px;
18
- border-bottom: 1px solid rgba(167, 139, 250, 0.12);
19
  flex-shrink: 0;
20
  }
21
 
22
- .chat-header-scifi h2 {
23
- font-size: 0.62rem;
24
- color: var(--accent-synapse);
25
- letter-spacing: 0.22em;
26
- font-weight: 600;
 
 
 
 
 
 
 
 
 
27
  }
28
 
29
- /* ── Message list ── */
30
  .messages-container {
31
  flex: 1;
32
  overflow-y: auto;
33
- padding: 6px 2px;
34
  display: flex;
35
  flex-direction: column;
36
- gap: 18px;
37
- mask-image: linear-gradient(transparent 0%, black 4%, black 96%, transparent 100%);
38
- -webkit-mask-image: linear-gradient(transparent 0%, black 4%, black 96%, transparent 100%);
39
  }
40
 
41
  .messages-container::-webkit-scrollbar { width: 3px; }
42
- .messages-container::-webkit-scrollbar-thumb {
43
- background: rgba(124, 58, 237, 0.35);
44
- border-radius: 2px;
45
- }
46
 
47
- .message-wrapper {
 
48
  display: flex;
49
  flex-direction: column;
50
  max-width: 88%;
51
- animation: msg-in 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275) both;
52
- }
53
-
54
- @keyframes msg-in {
55
- from { opacity: 0; transform: translateY(10px) scale(0.98); }
56
- to { opacity: 1; transform: translateY(0) scale(1); }
57
  }
58
 
59
- .message-wrapper.user {
60
- align-self: flex-end;
61
- align-items: flex-end;
62
- }
63
 
64
- .message-wrapper.soma {
65
- align-self: flex-start;
66
- }
67
-
68
- /* ── Message label ── */
69
- .message-wrapper .label-mono {
70
- font-size: 0.5rem;
71
  margin-bottom: 5px;
72
- color: var(--text-dim);
73
- letter-spacing: 0.14em;
74
  }
75
 
76
- /* ── Bubbles ── */
77
- .message-bubble {
78
  padding: 12px 16px;
79
  font-size: 0.88rem;
80
- line-height: 1.7;
81
- border-radius: 10px;
82
  word-break: break-word;
83
- backdrop-filter: blur(10px);
84
- color: var(--text-primary);
85
  }
86
 
87
- .user .message-bubble {
88
- background: rgba(124, 58, 237, 0.18);
89
- border: 1px solid rgba(167, 139, 250, 0.35);
90
  border-bottom-right-radius: 3px;
91
- box-shadow: 0 4px 16px rgba(0,0,0,0.35);
 
 
 
 
 
92
  }
93
 
94
- .soma .message-bubble {
95
- background: rgba(14, 8, 28, 0.75);
96
- border: 1px solid rgba(167, 139, 250, 0.18);
97
- border-left: 2px solid rgba(192, 132, 252, 0.5);
98
  border-bottom-left-radius: 3px;
99
- box-shadow: 0 4px 20px rgba(0,0,0,0.4);
100
  }
101
 
102
  /* ── Empty state ── */
103
- .empty-state {
104
  flex: 1;
105
  display: flex;
106
  flex-direction: column;
107
  align-items: center;
108
  justify-content: center;
109
- gap: 12px;
 
110
  text-align: center;
111
- padding: 40px 20px;
112
  }
113
 
114
- .empty-state-icon {
115
- font-size: 2.2rem;
116
- opacity: 0.35;
 
117
  }
118
 
119
- .empty-state-text {
120
- font-family: var(--font-mono);
121
- font-size: 0.6rem;
122
- color: var(--text-dim);
123
  line-height: 1.9;
124
- text-transform: uppercase;
125
- letter-spacing: 0.12em;
126
- opacity: 0.7;
127
  }
128
 
129
- /* ── Input area ── */
130
- .chat-input-area {
131
- margin-top: auto;
132
- padding-top: 12px;
133
  flex-shrink: 0;
 
 
134
  }
135
 
136
- .chat-input-form {
137
  display: flex;
138
  align-items: center;
139
- background: rgba(10, 5, 22, 0.8);
140
- border: 1px solid rgba(167, 139, 250, 0.2);
141
  border-radius: 12px;
142
- padding: 4px;
143
- transition: border-color 0.25s ease, box-shadow 0.25s ease;
144
- backdrop-filter: blur(16px);
145
- }
146
-
147
- .chat-input-form:focus-within {
148
- border-color: rgba(192, 132, 252, 0.5);
149
- box-shadow: 0 0 0 2px rgba(192, 132, 252, 0.12);
150
  }
151
 
152
- .chat-input-prefix {
153
- padding: 0 10px;
154
- color: var(--accent-synapse);
155
- font-family: var(--font-mono);
156
- font-size: 0.8rem;
157
- flex-shrink: 0;
158
- opacity: 0.8;
159
  }
160
 
161
  .chat-input {
162
  flex: 1;
163
  background: transparent;
164
  border: none;
165
- color: var(--text-primary);
166
- padding: 11px 10px;
167
- font-family: var(--font-mono);
168
- font-size: 0.82rem;
169
  outline: none;
 
 
 
 
170
  min-width: 0;
171
  }
172
 
173
  .chat-input::placeholder {
174
- color: var(--text-dim);
175
- opacity: 0.6;
176
  }
177
 
178
- /* Submit button */
179
- .chat-input-form button[type="submit"] {
180
- background: linear-gradient(135deg, rgba(124, 58, 237, 0.6), rgba(192, 132, 252, 0.4));
181
- color: #f0ebff;
182
- border: 1px solid rgba(192, 132, 252, 0.4);
183
  border-radius: 9px;
 
 
 
184
  font-family: var(--font-mono);
185
  font-size: 0.6rem;
186
  font-weight: 700;
 
187
  text-transform: uppercase;
188
- letter-spacing: 0.12em;
189
- padding: 10px 18px;
190
  cursor: pointer;
191
- transition: all 0.25s ease;
192
  flex-shrink: 0;
193
  }
194
 
195
- .chat-input-form button[type="submit"]:hover {
196
- background: linear-gradient(135deg, rgba(124, 58, 237, 0.8), rgba(192, 132, 252, 0.6));
197
- box-shadow: 0 0 16px rgba(192, 132, 252, 0.3);
198
- border-color: var(--accent-synapse);
 
199
  transform: none;
200
  }
201
 
202
- .chat-input-form button[type="submit"]:disabled {
203
- background: rgba(10, 5, 20, 0.6);
204
- color: var(--text-dim);
205
- border-color: rgba(255,255,255,0.04);
206
  cursor: not-allowed;
207
- box-shadow: none;
208
  transform: none;
 
209
  }
210
 
211
- /* Status text */
212
- .chat-status-line {
 
213
  font-family: var(--font-mono);
214
- font-size: 0.52rem;
215
- color: var(--accent-synapse);
216
  letter-spacing: 0.1em;
217
- text-transform: uppercase;
218
- opacity: 0.7;
219
- padding: 6px 4px 0;
220
- min-height: 18px;
221
  overflow: hidden;
222
- white-space: nowrap;
223
  text-overflow: ellipsis;
 
 
224
  }
 
1
+ /* ═══════════════════════════════════════════════
2
+ ChatPanel
3
+ ═══════════════════════════════════════════════ */
4
+
5
  .chat-panel {
6
  display: flex;
7
  flex-direction: column;
8
+ height: 100%;
9
  overflow: hidden;
10
+ padding: 0;
 
11
  }
12
 
13
+ /* ── Header ── */
14
+ .chat-header {
15
  display: flex;
16
  align-items: center;
17
  justify-content: space-between;
18
+ padding: 16px 22px 14px;
19
+ border-bottom: 1px solid var(--border-0);
 
20
  flex-shrink: 0;
21
  }
22
 
23
+ .chat-header-title {
24
+ font-family: var(--font-display);
25
+ font-style: italic;
26
+ font-size: 0.82rem;
27
+ color: var(--text-1);
28
+ font-weight: 400;
29
+ }
30
+
31
+ .chat-header-session {
32
+ font-family: var(--font-mono);
33
+ font-size: 0.48rem;
34
+ letter-spacing: 0.16em;
35
+ text-transform: uppercase;
36
+ color: var(--text-2);
37
  }
38
 
39
+ /* ── Messages ── */
40
  .messages-container {
41
  flex: 1;
42
  overflow-y: auto;
43
+ padding: 18px 22px;
44
  display: flex;
45
  flex-direction: column;
46
+ gap: 20px;
47
+ mask-image: linear-gradient(transparent 0%, black 5%, black 94%, transparent 100%);
48
+ -webkit-mask-image: linear-gradient(transparent 0%, black 5%, black 94%, transparent 100%);
49
  }
50
 
51
  .messages-container::-webkit-scrollbar { width: 3px; }
52
+ .messages-container::-webkit-scrollbar-thumb { background: var(--border-1); }
 
 
 
53
 
54
+ /* Message wrapper */
55
+ .msg {
56
  display: flex;
57
  flex-direction: column;
58
  max-width: 88%;
59
+ animation: soma-flip-in 0.38s cubic-bezier(0.22, 1, 0.36, 1) both;
 
 
 
 
 
60
  }
61
 
62
+ .msg.user { align-self: flex-end; align-items: flex-end; }
63
+ .msg.soma { align-self: flex-start; align-items: flex-start; }
 
 
64
 
65
+ /* Role label */
66
+ .msg-role {
67
+ font-family: var(--font-mono);
68
+ font-size: 0.46rem;
69
+ letter-spacing: 0.18em;
70
+ text-transform: uppercase;
71
+ color: var(--text-2);
72
  margin-bottom: 5px;
 
 
73
  }
74
 
75
+ /* Bubbles */
76
+ .msg-bubble {
77
  padding: 12px 16px;
78
  font-size: 0.88rem;
79
+ line-height: 1.72;
 
80
  word-break: break-word;
81
+ color: var(--text-0);
82
+ border-radius: 12px;
83
  }
84
 
85
+ .msg.user .msg-bubble {
86
+ background: var(--bg-3);
87
+ border: 1px solid var(--border-1);
88
  border-bottom-right-radius: 3px;
89
+ box-shadow: 0 4px 16px rgba(0,0,0,0.22);
90
+ }
91
+
92
+ [data-theme="light"] .msg.user .msg-bubble {
93
+ background: rgba(107,63,160,0.06);
94
+ border-color: rgba(107,63,160,0.18);
95
  }
96
 
97
+ .msg.soma .msg-bubble {
98
+ background: var(--bg-2);
99
+ border: 1px solid var(--border-0);
100
+ border-left: 2px solid var(--pulse);
101
  border-bottom-left-radius: 3px;
102
+ box-shadow: 0 4px 18px rgba(0,0,0,0.18);
103
  }
104
 
105
  /* ── Empty state ── */
106
+ .chat-empty {
107
  flex: 1;
108
  display: flex;
109
  flex-direction: column;
110
  align-items: center;
111
  justify-content: center;
112
+ gap: 14px;
113
+ padding: 40px 24px;
114
  text-align: center;
 
115
  }
116
 
117
+ .chat-empty-brain {
118
+ font-size: 2rem;
119
+ opacity: 0.25;
120
+ animation: soma-pulse 5s infinite ease-in-out;
121
  }
122
 
123
+ .chat-empty-text {
124
+ font-size: 0.7rem;
125
+ color: var(--text-2);
 
126
  line-height: 1.9;
127
+ max-width: 220px;
 
 
128
  }
129
 
130
+ /* ── Input ── */
131
+ .chat-input-wrap {
 
 
132
  flex-shrink: 0;
133
+ padding: 14px 22px 18px;
134
+ border-top: 1px solid var(--border-0);
135
  }
136
 
137
+ .chat-form {
138
  display: flex;
139
  align-items: center;
140
+ background: var(--bg-2);
141
+ border: 1px solid var(--border-0);
142
  border-radius: 12px;
143
+ padding: 4px 4px 4px 14px;
144
+ gap: 8px;
145
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
 
 
 
 
 
146
  }
147
 
148
+ .chat-form:focus-within {
149
+ border-color: var(--border-1);
150
+ box-shadow: 0 0 0 2px var(--border-0);
 
 
 
 
151
  }
152
 
153
  .chat-input {
154
  flex: 1;
155
  background: transparent;
156
  border: none;
 
 
 
 
157
  outline: none;
158
+ color: var(--text-0);
159
+ font-family: var(--font-body);
160
+ font-size: 0.88rem;
161
+ padding: 10px 0;
162
  min-width: 0;
163
  }
164
 
165
  .chat-input::placeholder {
166
+ color: var(--text-2);
167
+ font-style: italic;
168
  }
169
 
170
+ .chat-submit {
171
+ padding: 9px 18px;
 
 
 
172
  border-radius: 9px;
173
+ background: var(--bg-3);
174
+ border: 1px solid var(--border-1);
175
+ color: var(--text-1);
176
  font-family: var(--font-mono);
177
  font-size: 0.6rem;
178
  font-weight: 700;
179
+ letter-spacing: 0.1em;
180
  text-transform: uppercase;
 
 
181
  cursor: pointer;
182
+ transition: all 0.2s ease;
183
  flex-shrink: 0;
184
  }
185
 
186
+ .chat-submit:hover:not(:disabled) {
187
+ background: var(--pulse);
188
+ color: var(--bg-0);
189
+ border-color: var(--pulse);
190
+ box-shadow: var(--glow-pulse);
191
  transform: none;
192
  }
193
 
194
+ .chat-submit:disabled {
195
+ opacity: 0.35;
 
 
196
  cursor: not-allowed;
 
197
  transform: none;
198
+ box-shadow: none;
199
  }
200
 
201
+ /* Status line */
202
+ .chat-status {
203
+ margin-top: 7px;
204
  font-family: var(--font-mono);
205
+ font-size: 0.5rem;
206
+ color: var(--pulse);
207
  letter-spacing: 0.1em;
208
+ min-height: 16px;
 
 
 
209
  overflow: hidden;
 
210
  text-overflow: ellipsis;
211
+ white-space: nowrap;
212
+ opacity: 0.75;
213
  }
frontend/src/components/ChatPanel.jsx CHANGED
@@ -13,10 +13,9 @@ function ChatPanel({ messages, setMessages, setBrainState, brainState, isLoading
13
  e.preventDefault();
14
  if (!inputText.trim() || isLoading) return;
15
 
16
- const userMessage = { role: 'user', content: inputText };
17
- setMessages(prev => [...prev, userMessage]);
18
  const query = inputText;
19
  setInputText('');
 
20
 
21
  setBrainState(prev => ({
22
  ...prev,
@@ -28,107 +27,101 @@ function ChatPanel({ messages, setMessages, setBrainState, brainState, isLoading
28
  }));
29
 
30
  try {
31
- const response = await fetch('/api/v1/query/stream', {
32
  method: 'POST',
33
  headers: { 'Content-Type': 'application/json' },
34
  body: JSON.stringify({ text: query, user_id: currentPersona }),
35
  });
36
 
37
- const reader = response.body.getReader();
38
  const decoder = new TextDecoder();
39
- let buffer = '';
40
 
41
  while (true) {
42
  const { value, done } = await reader.read();
43
  if (done) break;
44
-
45
- buffer += decoder.decode(value, { stream: true });
46
- const parts = buffer.split('\n\n');
47
- buffer = parts.pop();
48
 
49
  for (const part of parts) {
50
  if (!part.trim()) continue;
51
- const match = part.match(/event: (.*)\ndata: (.*)/);
52
- if (!match) continue;
53
- const [, eventType, rawData] = match;
54
- const data = JSON.parse(rawData);
55
 
56
- if (eventType === 'trace') {
57
  setBrainState(prev => ({
58
  ...prev,
59
  statusMessage: data.message,
60
  traces: [...prev.traces, data],
61
  highlightedNodes: data.touched || prev.highlightedNodes,
62
  }));
63
- } else if (eventType === 'reflection') {
64
  setBrainState(prev => ({ ...prev, reflection: data.message }));
65
- } else if (eventType === 'final_result') {
66
  setMessages(prev => [...prev, { role: 'soma', content: data.response }]);
67
- } else if (eventType === 'error') {
68
  throw new Error(data.detail);
69
  }
70
  }
71
  }
72
 
73
  setBrainState(prev => ({ ...prev, isLoading: false, statusMessage: 'Cognitive cycle complete.' }));
74
- } catch (error) {
75
- console.error('Stream error:', error);
76
- setMessages(prev => [...prev, { role: 'soma', content: `Neural Error: ${error.message}` }]);
77
  setBrainState(prev => ({ ...prev, isLoading: false, statusMessage: 'Process interrupted.' }));
78
  }
79
  };
80
 
81
  return (
82
  <div className="chat-panel">
83
- <div className="chat-header-scifi">
84
- <h2>Neural Feedback Interface</h2>
85
- <span className="label-mono" style={{ fontSize: '0.52rem', color: 'var(--text-dim)' }}>
86
- {currentPersona}
87
- </span>
88
  </div>
89
 
90
  <div className="messages-container">
91
  {messages.length === 0 ? (
92
- <div className="empty-state">
93
- <div className="empty-state-icon">🧠</div>
94
- <div className="empty-state-text">
95
- Awaiting neural input.<br />
96
- Ask anything β€” watch the brain think.
97
- </div>
98
  </div>
99
  ) : (
100
- messages.map((msg, idx) => (
101
- <div key={idx} className={`message-wrapper ${msg.role}`}>
102
- <div className="label-mono" style={{ fontSize: '0.5rem', marginBottom: '5px' }}>
103
  {msg.role === 'user' ? currentPersona : 'SOMA'}
104
  </div>
105
- <div className="message-bubble">{msg.content}</div>
106
  </div>
107
  ))
108
  )}
109
  <div ref={endRef} />
110
  </div>
111
 
112
- <div className="chat-input-area">
113
- <form className="chat-input-form" onSubmit={handleSend}>
114
- <span className="chat-input-prefix">⚑</span>
115
  <input
116
- type="text"
117
  className="chat-input"
 
118
  value={inputText}
119
  onChange={e => setInputText(e.target.value)}
120
- placeholder="Ask something..."
121
  disabled={isLoading}
122
  autoFocus
123
  />
124
- <button type="submit" disabled={isLoading || !inputText.trim()}>
125
- {isLoading ? '...' : 'Send'}
126
  </button>
127
  </form>
128
- <div className="chat-status-line">
129
- {brainState.statusMessage}
130
- </div>
131
  </div>
 
132
  </div>
133
  );
134
  }
 
13
  e.preventDefault();
14
  if (!inputText.trim() || isLoading) return;
15
 
 
 
16
  const query = inputText;
17
  setInputText('');
18
+ setMessages(prev => [...prev, { role: 'user', content: query }]);
19
 
20
  setBrainState(prev => ({
21
  ...prev,
 
27
  }));
28
 
29
  try {
30
+ const res = await fetch('/api/v1/query/stream', {
31
  method: 'POST',
32
  headers: { 'Content-Type': 'application/json' },
33
  body: JSON.stringify({ text: query, user_id: currentPersona }),
34
  });
35
 
36
+ const reader = res.body.getReader();
37
  const decoder = new TextDecoder();
38
+ let buf = '';
39
 
40
  while (true) {
41
  const { value, done } = await reader.read();
42
  if (done) break;
43
+ buf += decoder.decode(value, { stream: true });
44
+ const parts = buf.split('\n\n');
45
+ buf = parts.pop();
 
46
 
47
  for (const part of parts) {
48
  if (!part.trim()) continue;
49
+ const m = part.match(/event: (.*)\ndata: (.*)/);
50
+ if (!m) continue;
51
+ const [, evType, raw] = m;
52
+ const data = JSON.parse(raw);
53
 
54
+ if (evType === 'trace') {
55
  setBrainState(prev => ({
56
  ...prev,
57
  statusMessage: data.message,
58
  traces: [...prev.traces, data],
59
  highlightedNodes: data.touched || prev.highlightedNodes,
60
  }));
61
+ } else if (evType === 'reflection') {
62
  setBrainState(prev => ({ ...prev, reflection: data.message }));
63
+ } else if (evType === 'final_result') {
64
  setMessages(prev => [...prev, { role: 'soma', content: data.response }]);
65
+ } else if (evType === 'error') {
66
  throw new Error(data.detail);
67
  }
68
  }
69
  }
70
 
71
  setBrainState(prev => ({ ...prev, isLoading: false, statusMessage: 'Cognitive cycle complete.' }));
72
+ } catch (err) {
73
+ setMessages(prev => [...prev, { role: 'soma', content: `Neural Error: ${err.message}` }]);
 
74
  setBrainState(prev => ({ ...prev, isLoading: false, statusMessage: 'Process interrupted.' }));
75
  }
76
  };
77
 
78
  return (
79
  <div className="chat-panel">
80
+
81
+ <div className="chat-header">
82
+ <span className="chat-header-title">Neural Feedback</span>
83
+ <span className="chat-header-session">{currentPersona}</span>
 
84
  </div>
85
 
86
  <div className="messages-container">
87
  {messages.length === 0 ? (
88
+ <div className="chat-empty">
89
+ <div className="chat-empty-brain">🧠</div>
90
+ <p className="chat-empty-text">
91
+ Send a message β€” watch Soma reflect, retrieve memory, and synthesize a response in real-time.
92
+ </p>
 
93
  </div>
94
  ) : (
95
+ messages.map((msg, i) => (
96
+ <div key={i} className={`msg ${msg.role}`}>
97
+ <div className="msg-role">
98
  {msg.role === 'user' ? currentPersona : 'SOMA'}
99
  </div>
100
+ <div className="msg-bubble">{msg.content}</div>
101
  </div>
102
  ))
103
  )}
104
  <div ref={endRef} />
105
  </div>
106
 
107
+ <div className="chat-input-wrap">
108
+ <form className="chat-form" onSubmit={handleSend}>
 
109
  <input
 
110
  className="chat-input"
111
+ type="text"
112
  value={inputText}
113
  onChange={e => setInputText(e.target.value)}
114
+ placeholder="Ask anything…"
115
  disabled={isLoading}
116
  autoFocus
117
  />
118
+ <button className="chat-submit" type="submit" disabled={isLoading || !inputText.trim()}>
119
+ {isLoading ? '…' : 'Send'}
120
  </button>
121
  </form>
122
+ <div className="chat-status">{brainState.statusMessage}</div>
 
 
123
  </div>
124
+
125
  </div>
126
  );
127
  }
frontend/src/components/EEGWave.css ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .eeg-container {
2
+ width: 120px;
3
+ height: 36px;
4
+ flex-shrink: 0;
5
+ position: relative;
6
+ overflow: hidden;
7
+ }
8
+
9
+ .eeg-svg {
10
+ width: 100%;
11
+ height: 100%;
12
+ display: block;
13
+ }
14
+
15
+ /* Glow layer β€” blurred duplicate */
16
+ .eeg-glow {
17
+ fill: none;
18
+ stroke: var(--pulse);
19
+ stroke-width: 3;
20
+ filter: blur(3px);
21
+ opacity: 0.35;
22
+ }
23
+
24
+ /* Sharp trace */
25
+ .eeg-trace {
26
+ fill: none;
27
+ stroke: var(--pulse);
28
+ stroke-width: 1.4;
29
+ stroke-linecap: round;
30
+ stroke-linejoin: round;
31
+ }
32
+
33
+ /* Leading dot */
34
+ .eeg-lead {
35
+ fill: var(--fire);
36
+ filter: drop-shadow(0 0 4px var(--fire));
37
+ opacity: 0.9;
38
+ }
39
+
40
+ /* Active: brighter and faster implied by animation dur already */
41
+ .eeg-container.active .eeg-trace {
42
+ stroke: var(--fire);
43
+ }
44
+
45
+ .eeg-container.active .eeg-glow {
46
+ stroke: var(--fire);
47
+ opacity: 0.4;
48
+ }
49
+
50
+ [data-theme="light"] .eeg-glow,
51
+ [data-theme="light"] .eeg-trace {
52
+ opacity: 0.7;
53
+ }
frontend/src/components/EEGWave.jsx ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import './EEGWave.css';
2
+
3
+ // Each wave pattern is a realistic EEG signal shape for that brain state
4
+ const WAVES = {
5
+ idle: 'M0,20 L12,20 L14,19 L16,10 L18,30 L20,12 L22,20 L120,20',
6
+ thinking: 'M0,20 L6,20 L7,19 L8,8 L9,32 L10,6 L11,34 L12,10 L13,20 L18,20 L19,18 L20,9 L21,31 L22,8 L23,32 L24,20 L120,20',
7
+ sleeping: 'M0,20 L20,20 L40,18 L60,22 L80,16 L100,24 L120,20',
8
+ default: 'M0,20 L12,20 L14,18 L16,12 L18,28 L20,14 L22,20 L120,20',
9
+ };
10
+
11
+ function getWaveKey(state) {
12
+ if (!state) return 'default';
13
+ const s = state.toLowerCase();
14
+ if (s.includes('sleep')) return 'sleeping';
15
+ if (s === 'idle') return 'idle';
16
+ return 'thinking';
17
+ }
18
+
19
+ export default function EEGWave({ cognitiveState, isActive }) {
20
+ const key = isActive ? 'thinking' : getWaveKey(cognitiveState);
21
+
22
+ return (
23
+ <div className={`eeg-container ${isActive ? 'active' : 'calm'}`} aria-hidden>
24
+ <svg
25
+ className={`eeg-svg wave-${key}`}
26
+ viewBox="0 0 120 40"
27
+ preserveAspectRatio="none"
28
+ xmlns="http://www.w3.org/2000/svg"
29
+ >
30
+ {/* Glow copy */}
31
+ <path d={WAVES[key]} className="eeg-glow" />
32
+ {/* Sharp trace */}
33
+ <path d={WAVES[key]} className="eeg-trace" />
34
+ {/* Animated leading dot */}
35
+ <circle className="eeg-lead" r="2" cy="20">
36
+ <animateMotion
37
+ dur={isActive ? '1.2s' : '2.5s'}
38
+ repeatCount="indefinite"
39
+ path={WAVES[key]}
40
+ />
41
+ </circle>
42
+ </svg>
43
+ </div>
44
+ );
45
+ }
frontend/src/components/KnowledgeGraph.jsx CHANGED
@@ -121,20 +121,22 @@ function KnowledgeGraph({ highlightedNodes = [], currentPersona }) {
121
 
122
  const getNodeColor = node => {
123
  if (highlightedNodes.includes(node.id) || highlightedNodes.includes(node.label)) {
124
- return 'rgba(240, 171, 252, 1)';
125
  }
126
  if (node.isDemo) {
127
- // Gradient from violet β†’ cyan for demo anatomy nodes
128
  const hash = node.id.split('').reduce((a, c) => a + c.charCodeAt(0), 0);
129
- const hue = (hash * 37) % 60; // 0–60 β†’ violet to indigo range
130
- return `hsla(${260 + hue}, 80%, 72%, 0.9)`;
 
 
 
131
  }
132
- return 'rgba(192, 132, 252, 0.9)';
133
  };
134
 
135
  const getLinkColor = () => isDemo
136
- ? 'rgba(130, 100, 220, 0.25)'
137
- : 'rgba(167, 139, 250, 0.2)';
138
 
139
  return (
140
  <div className="graph-container" ref={containerRef}>
@@ -187,8 +189,8 @@ function KnowledgeGraph({ highlightedNodes = [], currentPersona }) {
187
  linkDirectionalParticleSpeed={0.006}
188
  linkDirectionalParticleWidth={isDemo ? 1.5 : 2}
189
  linkDirectionalParticleColor={() => isDemo
190
- ? 'rgba(167, 139, 250, 0.7)'
191
- : 'rgba(240, 171, 252, 0.85)'}
192
 
193
  linkColor={getLinkColor}
194
  linkOpacity={isDemo ? 0.45 : 0.5}
 
121
 
122
  const getNodeColor = node => {
123
  if (highlightedNodes.includes(node.id) || highlightedNodes.includes(node.label)) {
124
+ return 'rgba(224, 122, 56, 1)'; // fire β€” active node
125
  }
126
  if (node.isDemo) {
 
127
  const hash = node.id.split('').reduce((a, c) => a + c.charCodeAt(0), 0);
128
+ const hue = (hash * 41) % 80;
129
+ // Demo nodes span amber β†’ teal (warm, not purple)
130
+ return hue < 40
131
+ ? `hsla(${38 + hue * 0.4}, 75%, 60%, 0.9)` // amber range
132
+ : `hsla(${175 + hue * 0.3}, 60%, 55%, 0.9)`; // teal range
133
  }
134
+ return 'rgba(212, 168, 83, 0.85)'; // amber
135
  };
136
 
137
  const getLinkColor = () => isDemo
138
+ ? 'rgba(212, 168, 83, 0.18)'
139
+ : 'rgba(212, 168, 83, 0.22)';
140
 
141
  return (
142
  <div className="graph-container" ref={containerRef}>
 
189
  linkDirectionalParticleSpeed={0.006}
190
  linkDirectionalParticleWidth={isDemo ? 1.5 : 2}
191
  linkDirectionalParticleColor={() => isDemo
192
+ ? 'rgba(212, 168, 83, 0.8)'
193
+ : 'rgba(224, 122, 56, 0.9)'}
194
 
195
  linkColor={getLinkColor}
196
  linkOpacity={isDemo ? 0.45 : 0.5}
frontend/src/index.css CHANGED
@@ -1,274 +1,201 @@
1
- @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap');
2
 
3
- :root {
4
- /* ── Brain-Organic Palette (brightened) ── */
5
- --bg-base: #07030e;
6
- --bg-panel: rgba(14, 8, 26, 0.82);
7
-
8
- --accent-synapse: #c084fc; /* Soft violet */
9
- --accent-dendrite: #a78bfa; /* Lighter violet */
10
- --accent-axon: #7c3aed; /* Deep violet */
11
- --accent-impulse: #f0abfc; /* Warm pink */
12
- --accent-cortex: #818cf8; /* Indigo */
13
- --accent-error: #fb7185;
14
-
15
- --accent-primary: var(--accent-synapse);
16
- --accent-secondary: var(--accent-impulse);
17
- --accent-glow: rgba(192, 132, 252, 0.3);
18
- --accent-glow-pink: rgba(240, 171, 252, 0.3);
19
-
20
- /* Brighter text */
21
- --text-primary: #f0ebff;
22
- --text-secondary: #cfc0f8;
23
- --text-dim: #9d8fcc;
24
-
25
- --border-subtle: rgba(167, 139, 250, 0.14);
26
- --border-active: rgba(192, 132, 252, 0.4);
27
-
28
- --panel-shadow: 0 8px 32px rgba(0, 0, 0, 0.55);
29
-
30
- --font-main: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;
31
- --font-mono: 'JetBrains Mono', 'Roboto Mono', monospace;
32
- }
33
 
34
- * {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  margin: 0;
36
  padding: 0;
37
- box-sizing: border-box;
38
  }
39
 
40
  body {
41
- font-family: var(--font-main);
42
- background-color: var(--bg-base);
43
- color: var(--text-primary);
 
44
  height: 100vh;
45
  overflow: hidden;
46
- background-image:
47
- radial-gradient(ellipse at 15% 50%, rgba(124, 58, 237, 0.1), transparent 50%),
48
- radial-gradient(ellipse at 85% 25%, rgba(192, 132, 252, 0.07), transparent 40%),
49
- radial-gradient(ellipse at 50% 95%, rgba(240, 171, 252, 0.05), transparent 40%);
 
 
 
 
50
  }
51
 
52
  #root { height: 100%; }
53
 
54
- /* ── Glassmorphism ── */
55
- .glass-panel {
56
- background: var(--bg-panel);
57
- border: 1px solid var(--border-subtle);
58
- backdrop-filter: blur(16px);
59
- box-shadow: var(--panel-shadow);
60
- border-radius: 12px;
61
- position: relative;
62
- overflow: hidden;
63
  }
64
 
65
- .glass-panel::before {
66
- content: '';
67
- position: absolute;
68
- top: 0; left: 0; right: 0;
69
- height: 1px;
70
- background: linear-gradient(90deg, transparent, rgba(192, 132, 252, 0.2), transparent);
71
  }
72
 
73
- h1, h2, h3 {
 
 
 
74
  text-transform: uppercase;
75
- letter-spacing: 0.15em;
76
- font-weight: 600;
77
- color: var(--text-primary);
78
  }
79
 
80
- .label-mono {
81
- font-family: var(--font-mono);
82
- font-size: 0.68rem;
83
- text-transform: uppercase;
84
- color: var(--text-secondary);
85
- letter-spacing: 0.1em;
86
  }
87
 
88
  /* ── Scrollbar ── */
89
  ::-webkit-scrollbar { width: 3px; }
90
  ::-webkit-scrollbar-track { background: transparent; }
91
- ::-webkit-scrollbar-thumb { background: rgba(124, 58, 237, 0.35); border-radius: 2px; }
92
- ::-webkit-scrollbar-thumb:hover { background: rgba(192, 132, 252, 0.55); }
93
 
94
- /* ── Base Button ── */
95
  button {
96
- background: rgba(124, 58, 237, 0.15);
97
- color: var(--text-primary);
98
- border: 1px solid var(--border-active);
99
- border-radius: 8px;
100
- padding: 8px 16px;
101
- font-family: var(--font-main);
102
- font-weight: 500;
103
  cursor: pointer;
104
- transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
 
105
  }
106
 
107
  button:hover {
108
- background: rgba(192, 132, 252, 0.22);
109
- box-shadow: 0 0 16px var(--accent-glow);
110
- border-color: var(--accent-primary);
111
- transform: translateY(-1px);
112
  }
113
 
114
- button:active { transform: translateY(1px); }
115
 
116
  button:disabled {
117
- background: rgba(10, 5, 20, 0.7);
118
- color: var(--text-dim);
119
- border-color: var(--border-subtle);
120
  cursor: not-allowed;
121
- transform: none;
122
  box-shadow: none;
123
  }
124
 
125
- /* ── Brain Vitals HUD ── */
126
- .vitals-hud {
127
- background: rgba(14, 8, 26, 0.6);
 
128
  border-radius: 12px;
129
- padding: 16px;
130
- border-left: 3px solid var(--accent-impulse);
131
- box-shadow: inset 0 0 20px rgba(0,0,0,0.4);
132
- position: relative;
133
- overflow: hidden;
134
- }
135
-
136
- .vitals-hud::after {
137
- content: '';
138
- position: absolute;
139
- top: 0; left: 0; right: 0; bottom: 0;
140
- background: repeating-linear-gradient(
141
- 0deg,
142
- rgba(192, 132, 252, 0.015) 0px,
143
- transparent 1px,
144
- transparent 3px
145
- );
146
- pointer-events: none;
147
- }
148
-
149
- .vitals-grid {
150
- display: grid;
151
- grid-template-columns: repeat(2, 1fr);
152
- gap: 16px;
153
- margin-top: 12px;
154
- }
155
-
156
- .vital-item { display: flex; flex-direction: column; }
157
-
158
- .vital-label {
159
- font-family: var(--font-mono);
160
- font-size: 0.55rem;
161
- color: var(--text-dim);
162
- text-transform: uppercase;
163
- letter-spacing: 0.15em;
164
- }
165
-
166
- .vital-value {
167
- font-family: var(--font-mono);
168
- font-size: 1.25rem;
169
- font-weight: 700;
170
- color: var(--accent-primary);
171
- text-shadow: 0 0 10px var(--accent-glow);
172
- }
173
-
174
- .vital-value.purple {
175
- color: var(--accent-impulse);
176
- text-shadow: 0 0 10px var(--accent-glow-pink);
177
- }
178
-
179
- .vital-value.warning {
180
- color: var(--accent-error);
181
- animation: text-pulse 1s infinite alternate;
182
- }
183
-
184
- .warning-text { color: var(--accent-error); opacity: 0.9; }
185
-
186
- .internal-monologue {
187
- background: rgba(124, 58, 237, 0.1);
188
- border-left: 2px solid var(--accent-dendrite);
189
- padding: 14px;
190
- margin: 15px 0 20px 0;
191
- font-size: 0.77rem;
192
- line-height: 1.65;
193
- color: var(--text-primary);
194
- font-style: italic;
195
- border-radius: 6px;
196
- animation: monologue-fade 0.8s ease;
197
- }
198
-
199
- @keyframes monologue-fade {
200
- from { opacity: 0; filter: blur(4px); transform: translateX(-8px); }
201
- to { opacity: 1; filter: blur(0); transform: translateX(0); }
202
- }
203
-
204
- .sparks-container {
205
- display: flex;
206
- flex-direction: column;
207
- gap: 12px;
208
- max-height: 220px;
209
- overflow-y: auto;
210
- padding-right: 8px;
211
  }
212
 
213
- .spark-item {
214
- background: rgba(192, 132, 252, 0.06);
215
- border: 1px solid rgba(192, 132, 252, 0.15);
216
- border-radius: 6px;
217
- padding: 10px;
218
- font-size: 0.65rem;
219
- animation: spark-in 0.4s ease;
220
- border-left: 2px solid var(--accent-dendrite);
221
  }
222
 
223
- .spark-time {
224
- opacity: 0.55;
225
- margin-right: 8px;
226
- font-family: var(--font-mono);
227
- font-size: 0.52rem;
228
- }
229
-
230
- .spark-content { margin: 6px 0; color: var(--text-primary); line-height: 1.45; }
231
-
232
- .spark-tag {
233
- display: inline-block;
234
- background: rgba(240, 171, 252, 0.12);
235
- color: var(--accent-impulse);
236
- padding: 2px 6px;
237
- border-radius: 4px;
238
- font-size: 0.52rem;
239
- margin-right: 6px;
240
- text-transform: uppercase;
241
- font-family: var(--font-mono);
242
  }
243
 
244
- @keyframes spark-in {
245
- from { opacity: 0; transform: scale(0.96); }
246
- to { opacity: 1; transform: scale(1); }
 
 
 
 
 
 
 
247
  }
248
 
249
- /* ── Neural Pulse Animations ── */
250
- .pulse-active { animation: box-glow 2.5s infinite alternate ease-in-out; }
251
-
252
- @keyframes box-glow {
253
- 0% { border-color: var(--border-subtle); box-shadow: 0 0 5px rgba(192, 132, 252, 0.05); }
254
- 100% { border-color: var(--accent-primary); box-shadow: 0 0 22px var(--accent-glow); }
255
  }
256
 
257
- @keyframes text-pulse {
258
- from { opacity: 0.5; text-shadow: none; }
259
- to { opacity: 1; text-shadow: 0 0 10px var(--accent-error); }
260
  }
261
 
262
- .neural-scan-line {
263
- position: absolute;
264
- top: 0; left: 0; width: 100%; height: 1.5px;
265
- background: linear-gradient(90deg, transparent, var(--accent-synapse), transparent);
266
- animation: scan-move 2.5s linear infinite;
267
- z-index: 10;
268
- opacity: 0.6;
269
  }
270
 
271
- @keyframes scan-move {
272
- from { top: -5%; }
273
- to { top: 105%; }
274
  }
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@1,400;1,600&family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500;700&display=swap');
2
 
3
+ /* ═══════════════════════════════════════════════════════════════
4
+ SOMA β€” Theme System
5
+ Dark: Calcium imaging / two-photon microscopy aesthetic
6
+ Light: Histology lab / brain slice slide aesthetic
7
+ ═══════════════════════════════════════════════════════════════ */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ :root {
10
+ /* ── Dark theme (default) ── */
11
+ --bg-0: #070504;
12
+ --bg-1: #0d0a07;
13
+ --bg-2: #161210;
14
+ --bg-3: #1f1a16;
15
+
16
+ --fire: #e07a38; /* Neuron firing β€” burnt orange */
17
+ --pulse: #d4a853; /* Calcium indicator β€” amber */
18
+ --calm: #4ecdc4; /* Inhibitory signal β€” teal */
19
+ --synapse: #a87ecf; /* Synaptic vesicle β€” muted violet (used sparingly) */
20
+ --error: #e05555;
21
+
22
+ --text-0: #f2ece3;
23
+ --text-1: #b8a990;
24
+ --text-2: #6b5e4a;
25
+
26
+ --border-0: rgba(212, 168, 83, 0.07);
27
+ --border-1: rgba(212, 168, 83, 0.16);
28
+ --border-2: rgba(224, 122, 56, 0.35);
29
+
30
+ --glow-pulse: 0 0 20px rgba(212, 168, 83, 0.22);
31
+ --glow-fire: 0 0 20px rgba(224, 122, 56, 0.28);
32
+ --glow-calm: 0 0 14px rgba(78, 205, 196, 0.2);
33
+
34
+ --font-display: 'Playfair Display', Georgia, serif;
35
+ --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
36
+ --font-mono: 'JetBrains Mono', 'Fira Code', monospace;
37
+ }
38
+
39
+ [data-theme="light"] {
40
+ --bg-0: #f5f0e8; /* Histology glass slide */
41
+ --bg-1: #ede7db;
42
+ --bg-2: #e6dfd4;
43
+ --bg-3: #ffffff;
44
+
45
+ --fire: #c4424e; /* Nissl stain β€” red */
46
+ --pulse: #6b3fa0; /* Brain tissue β€” deep violet */
47
+ --calm: #2d7d9a; /* CSF β€” steel blue */
48
+ --synapse: #6b3fa0;
49
+ --error: #c4424e;
50
+
51
+ --text-0: #1c1209;
52
+ --text-1: #4a3d30;
53
+ --text-2: #8a7565;
54
+
55
+ --border-0: rgba(107, 63, 160, 0.07);
56
+ --border-1: rgba(107, 63, 160, 0.18);
57
+ --border-2: rgba(196, 66, 78, 0.32);
58
+
59
+ --glow-pulse: 0 0 16px rgba(107, 63, 160, 0.14);
60
+ --glow-fire: 0 0 14px rgba(196, 66, 78, 0.18);
61
+ --glow-calm: 0 0 12px rgba(45, 125, 154, 0.14);
62
+ }
63
+
64
+ /* ── Reset ── */
65
+ *, *::before, *::after {
66
+ box-sizing: border-box;
67
  margin: 0;
68
  padding: 0;
 
69
  }
70
 
71
  body {
72
+ font-family: var(--font-body);
73
+ font-size: 15px;
74
+ background: var(--bg-0);
75
+ color: var(--text-0);
76
  height: 100vh;
77
  overflow: hidden;
78
+ transition: background 0.45s ease, color 0.45s ease;
79
+ -webkit-font-smoothing: antialiased;
80
+ }
81
+
82
+ [data-theme="light"] body,
83
+ [data-theme="light"] {
84
+ background: var(--bg-0);
85
+ color: var(--text-0);
86
  }
87
 
88
  #root { height: 100%; }
89
 
90
+ /* ── Typography helpers ── */
91
+ .t-display {
92
+ font-family: var(--font-display);
93
+ font-style: italic;
 
 
 
 
 
94
  }
95
 
96
+ .t-mono {
97
+ font-family: var(--font-mono);
 
 
 
 
98
  }
99
 
100
+ .t-label {
101
+ font-family: var(--font-mono);
102
+ font-size: 0.58rem;
103
+ letter-spacing: 0.18em;
104
  text-transform: uppercase;
105
+ color: var(--text-2);
 
 
106
  }
107
 
108
+ .t-section {
109
+ font-family: var(--font-display);
110
+ font-style: italic;
111
+ font-size: 0.82rem;
112
+ color: var(--text-1);
113
+ letter-spacing: 0.02em;
114
  }
115
 
116
  /* ── Scrollbar ── */
117
  ::-webkit-scrollbar { width: 3px; }
118
  ::-webkit-scrollbar-track { background: transparent; }
119
+ ::-webkit-scrollbar-thumb { background: var(--border-1); border-radius: 2px; }
120
+ ::-webkit-scrollbar-thumb:hover { background: var(--pulse); opacity: 0.5; }
121
 
122
+ /* ── Base button ── */
123
  button {
124
+ font-family: var(--font-body);
125
+ background: transparent;
126
+ color: var(--text-1);
127
+ border: 1px solid var(--border-1);
128
+ border-radius: 7px;
129
+ padding: 7px 14px;
130
+ font-size: 0.82rem;
131
  cursor: pointer;
132
+ transition: background 0.2s ease, color 0.2s ease,
133
+ border-color 0.2s ease, box-shadow 0.2s ease;
134
  }
135
 
136
  button:hover {
137
+ background: var(--border-0);
138
+ color: var(--text-0);
139
+ border-color: var(--border-2);
 
140
  }
141
 
142
+ button:active { opacity: 0.8; }
143
 
144
  button:disabled {
145
+ opacity: 0.35;
 
 
146
  cursor: not-allowed;
 
147
  box-shadow: none;
148
  }
149
 
150
+ /* ── Glass panel ── */
151
+ .glass {
152
+ background: var(--bg-1);
153
+ border: 1px solid var(--border-0);
154
  border-radius: 12px;
155
+ backdrop-filter: blur(12px);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  }
157
 
158
+ /* ── Theme-aware transitions ── */
159
+ .app-container,
160
+ .app-container * {
161
+ transition: background-color 0.4s ease, border-color 0.4s ease, color 0.4s ease;
 
 
 
 
162
  }
163
 
164
+ /* ── 3D perspective base ── */
165
+ .scene-3d {
166
+ perspective: 1000px;
167
+ perspective-origin: 50% 50%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  }
169
 
170
+ /* ── Organic noise texture ── */
171
+ .noisy::after {
172
+ content: '';
173
+ position: absolute;
174
+ inset: 0;
175
+ border-radius: inherit;
176
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
177
+ opacity: 0.025;
178
+ pointer-events: none;
179
+ mix-blend-mode: overlay;
180
  }
181
 
182
+ /* ── Shared animations ── */
183
+ @keyframes soma-pulse {
184
+ 0%, 100% { opacity: 1; transform: scale(1); }
185
+ 50% { opacity: 0.6; transform: scale(0.92); }
 
 
186
  }
187
 
188
+ @keyframes soma-glow-on {
189
+ from { box-shadow: none; }
190
+ to { box-shadow: var(--glow-pulse); }
191
  }
192
 
193
+ @keyframes soma-rise {
194
+ from { opacity: 0; transform: translateY(10px) translateZ(-20px); }
195
+ to { opacity: 1; transform: translateY(0) translateZ(0); }
 
 
 
 
196
  }
197
 
198
+ @keyframes soma-flip-in {
199
+ from { opacity: 0; transform: perspective(600px) rotateY(-18deg) translateZ(-30px); }
200
+ to { opacity: 1; transform: perspective(600px) rotateY(0deg) translateZ(0); }
201
  }