dixiebone13-a11y commited on
Commit
4ad4378
·
0 Parent(s):

Synapse Link HEBB-9 - Hebbian learning neural sim

Browse files
Files changed (12) hide show
  1. .gitignore +4 -0
  2. Dockerfile +26 -0
  3. README.md +34 -0
  4. index.html +12 -0
  5. package.json +25 -0
  6. postcss.config.js +6 -0
  7. src/App.jsx +438 -0
  8. src/components/GridCell.jsx +38 -0
  9. src/index.css +3 -0
  10. src/main.jsx +10 -0
  11. tailwind.config.js +8 -0
  12. vite.config.js +10 -0
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ node_modules
2
+ dist
3
+ .DS_Store
4
+ *.local
Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:20-slim AS build
2
+ WORKDIR /app
3
+ COPY package*.json ./
4
+ RUN npm install
5
+ COPY . .
6
+ RUN npm run build
7
+
8
+ FROM nginx:alpine
9
+ COPY --from=build /app/dist /usr/share/nginx/html
10
+ EXPOSE 7860
11
+ CMD ["nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf"]
12
+ COPY <<'EOF' /etc/nginx/nginx.conf
13
+ worker_processes auto;
14
+ events { worker_connections 1024; }
15
+ http {
16
+ include /etc/nginx/mime.types;
17
+ server {
18
+ listen 7860;
19
+ root /usr/share/nginx/html;
20
+ index index.html;
21
+ location / {
22
+ try_files $uri $uri/ /index.html;
23
+ }
24
+ }
25
+ }
26
+ EOF
README.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Synapse Link
3
+ emoji: 🧠
4
+ colorFrom: green
5
+ colorTo: red
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ ---
10
+
11
+ # Synapse_Link HEBB-9
12
+
13
+ Interactive Hebbian Learning neural network simulation. Watch synaptic connections strengthen and decay in real-time as the memory grid learns to reconstruct the input signal.
14
+
15
+ ## Controls
16
+
17
+ - **Click grid cells** to toggle stimulus neurons
18
+ - **Sync Source** — Generate a random input pattern
19
+ - **Enable Drift** — Shift the signal pattern over time
20
+ - **Purge** — Reset all grids and learned weights
21
+
22
+ ## Parameters
23
+
24
+ | Param | Effect |
25
+ |-------|--------|
26
+ | SNR_Amp | Signal-to-noise strength — higher = cleaner memory encoding |
27
+ | Refresh_Hz | Simulation tick rate |
28
+ | Persistence | How long inactive cells retain their state |
29
+ | Cross_Talk | Spatial neighbor influence (like lateral inhibition) |
30
+ | Plasticity | Hebbian learning rate — how fast weights adapt |
31
+
32
+ ## Tech
33
+
34
+ React 18 + Vite + Tailwind CSS, deployed as Docker on HuggingFace Spaces.
index.html ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Synapse_Link HEBB-9</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.jsx"></script>
11
+ </body>
12
+ </html>
package.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "synapse-link",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "lucide-react": "^0.454.0",
13
+ "react": "^18.3.1",
14
+ "react-dom": "^18.3.1"
15
+ },
16
+ "devDependencies": {
17
+ "@types/react": "^18.3.12",
18
+ "@types/react-dom": "^18.3.1",
19
+ "@vitejs/plugin-react": "^4.3.4",
20
+ "autoprefixer": "^10.4.20",
21
+ "postcss": "^8.4.49",
22
+ "tailwindcss": "^3.4.15",
23
+ "vite": "^6.0.2"
24
+ }
25
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
src/App.jsx ADDED
@@ -0,0 +1,438 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
2
+ import {
3
+ Zap, Trash2, RefreshCw, Waves,
4
+ ShieldAlert, Binary, BrainCircuit, Dna
5
+ } from 'lucide-react';
6
+ import GridCell from './components/GridCell';
7
+
8
+ const GRID_SIZE = 8;
9
+ const CELL_COUNT = GRID_SIZE * GRID_SIZE;
10
+ const MAX_HISTORY = 50;
11
+
12
+ // Pre-allocate empty arrays to avoid recreating on every clear
13
+ const EMPTY_BOOLS = Object.freeze(Array(CELL_COUNT).fill(false));
14
+ const EMPTY_WEIGHTS = Object.freeze(Array(CELL_COUNT).fill(0));
15
+ const FULL_HISTORY = Object.freeze(Array(MAX_HISTORY).fill(100));
16
+
17
+ const App = () => {
18
+ // ── Render state (drives UI) ──
19
+ const [signalGrid, setSignalGrid] = useState([...EMPTY_BOOLS]);
20
+ const [memoryGrid, setMemoryGrid] = useState([...EMPTY_BOOLS]);
21
+ const [weights, setWeights] = useState([...EMPTY_WEIGHTS]);
22
+ const [accuracy, setAccuracy] = useState(100);
23
+ const [history, setHistory] = useState([...FULL_HISTORY]);
24
+ const [glitch, setGlitch] = useState(false);
25
+ const [isDrifting, setIsDrifting] = useState(false);
26
+
27
+ // Slider state (UI display)
28
+ const [strength, setStrength] = useState(85);
29
+ const [frequency, setFrequency] = useState(50);
30
+ const [persistence, setPersistence] = useState(30);
31
+ const [crossTalk, setCrossTalk] = useState(20);
32
+ const [plasticity, setPlasticity] = useState(40);
33
+
34
+ // ── Refs (simulation reads from these — no dependency churn) ──
35
+ const signalRef = useRef(signalGrid);
36
+ const memoryRef = useRef(memoryGrid);
37
+ const weightsRef = useRef(weights);
38
+ const strengthRef = useRef(strength);
39
+ const frequencyRef = useRef(frequency);
40
+ const persistenceRef = useRef(persistence);
41
+ const crossTalkRef = useRef(crossTalk);
42
+ const plasticityRef = useRef(plasticity);
43
+ const isDriftingRef = useRef(isDrifting);
44
+
45
+ const simTimerRef = useRef(null);
46
+ const driftTimerRef = useRef(null);
47
+ const waveCanvasRef = useRef(null);
48
+ const waveFrameRef = useRef(null);
49
+ const waveFrameCount = useRef(0);
50
+ const accuracyRef = useRef(100);
51
+ const containerRef = useRef(null);
52
+
53
+ // Keep refs in sync with state
54
+ useEffect(() => { signalRef.current = signalGrid; }, [signalGrid]);
55
+ useEffect(() => { strengthRef.current = strength; }, [strength]);
56
+ useEffect(() => { frequencyRef.current = frequency; }, [frequency]);
57
+ useEffect(() => { persistenceRef.current = persistence; }, [persistence]);
58
+ useEffect(() => { crossTalkRef.current = crossTalk; }, [crossTalk]);
59
+ useEffect(() => { plasticityRef.current = plasticity; }, [plasticity]);
60
+ useEffect(() => { isDriftingRef.current = isDrifting; }, [isDrifting]);
61
+
62
+ // ── Actions ──
63
+ const generatePattern = useCallback(() => {
64
+ const newGrid = Array.from({ length: CELL_COUNT }, () => Math.random() > 0.8);
65
+ setSignalGrid(newGrid);
66
+ signalRef.current = newGrid;
67
+ setGlitch(true);
68
+ setTimeout(() => setGlitch(false), 150);
69
+ }, []);
70
+
71
+ const clearGrids = useCallback(() => {
72
+ const s = [...EMPTY_BOOLS];
73
+ const m = [...EMPTY_BOOLS];
74
+ const w = [...EMPTY_WEIGHTS];
75
+ setSignalGrid(s);
76
+ setMemoryGrid(m);
77
+ setWeights(w);
78
+ setHistory([...FULL_HISTORY]);
79
+ signalRef.current = s;
80
+ memoryRef.current = m;
81
+ weightsRef.current = w;
82
+ accuracyRef.current = 100;
83
+ }, []);
84
+
85
+ const toggleBlock = useCallback((index) => {
86
+ setSignalGrid(prev => {
87
+ const next = [...prev];
88
+ next[index] = !next[index];
89
+ signalRef.current = next;
90
+ return next;
91
+ });
92
+ }, []);
93
+
94
+ // ── Signal Drift ──
95
+ useEffect(() => {
96
+ const runDrift = () => {
97
+ if (!isDriftingRef.current) return;
98
+ setSignalGrid(prev => {
99
+ const next = [...prev];
100
+ const last = next.pop();
101
+ next.unshift(last);
102
+ signalRef.current = next;
103
+ return next;
104
+ });
105
+ };
106
+
107
+ // Restart drift interval when frequency or drift toggle changes
108
+ clearInterval(driftTimerRef.current);
109
+ if (isDrifting) {
110
+ driftTimerRef.current = setInterval(runDrift, 2000 - frequency * 15);
111
+ }
112
+ return () => clearInterval(driftTimerRef.current);
113
+ }, [isDrifting, frequency]);
114
+
115
+ // ── Main Simulation Loop (runs once on mount) ──
116
+ useEffect(() => {
117
+ const tick = () => {
118
+ const signal = signalRef.current;
119
+ const prevMemory = memoryRef.current;
120
+ const prevWeights = weightsRef.current;
121
+ const str = strengthRef.current;
122
+ const freq = frequencyRef.current;
123
+ const pers = persistenceRef.current;
124
+ const ct = crossTalkRef.current;
125
+ const plast = plasticityRef.current;
126
+
127
+ const baseSnrThreshold = (100 - str) / 100;
128
+
129
+ // Build next memory
130
+ const nextGrid = new Array(CELL_COUNT);
131
+ for (let i = 0; i < CELL_COUNT; i++) {
132
+ const noise = Math.random();
133
+ const shouldUpdate = Math.random() < freq / 100;
134
+
135
+ if (!shouldUpdate) {
136
+ nextGrid[i] = prevMemory[i];
137
+ continue;
138
+ }
139
+
140
+ const localWeight = prevWeights[i] * (plast / 100);
141
+ const effectiveThreshold = Math.max(0.01, baseSnrThreshold - localWeight);
142
+
143
+ if (signal[i]) {
144
+ nextGrid[i] = noise > effectiveThreshold;
145
+ } else if (prevMemory[i] && Math.random() < pers / 100) {
146
+ nextGrid[i] = true;
147
+ } else {
148
+ nextGrid[i] = noise < effectiveThreshold / 20;
149
+ }
150
+ }
151
+
152
+ // Spatial cross-talk
153
+ let finalGrid = nextGrid;
154
+ if (ct > 0) {
155
+ finalGrid = new Array(CELL_COUNT);
156
+ for (let i = 0; i < CELL_COUNT; i++) {
157
+ if (nextGrid[i]) {
158
+ finalGrid[i] = true;
159
+ continue;
160
+ }
161
+ const row = (i / GRID_SIZE) | 0;
162
+ const col = i % GRID_SIZE;
163
+ let activeNeighbors = 0;
164
+ for (let r = -1; r <= 1; r++) {
165
+ for (let c = -1; c <= 1; c++) {
166
+ if (r === 0 && c === 0) continue;
167
+ const nr = row + r, nc = col + c;
168
+ if (nr >= 0 && nr < GRID_SIZE && nc >= 0 && nc < GRID_SIZE) {
169
+ if (nextGrid[nr * GRID_SIZE + nc]) activeNeighbors++;
170
+ }
171
+ }
172
+ }
173
+ finalGrid[i] = Math.random() < activeNeighbors * (ct / 1000);
174
+ }
175
+ }
176
+
177
+ // Hebbian weight update
178
+ const nextWeights = new Array(CELL_COUNT);
179
+ for (let i = 0; i < CELL_COUNT; i++) {
180
+ if (signal[i] && finalGrid[i]) {
181
+ nextWeights[i] = Math.min(1, prevWeights[i] + 0.02);
182
+ } else if (!signal[i] && !finalGrid[i]) {
183
+ nextWeights[i] = prevWeights[i];
184
+ } else {
185
+ nextWeights[i] = Math.max(0, prevWeights[i] - 0.01);
186
+ }
187
+ }
188
+
189
+ // Accuracy
190
+ let matches = 0;
191
+ for (let i = 0; i < CELL_COUNT; i++) {
192
+ if (signal[i] === finalGrid[i]) matches++;
193
+ }
194
+ const acc = Math.round((matches / CELL_COUNT) * 100);
195
+
196
+ // Commit to refs
197
+ memoryRef.current = finalGrid;
198
+ weightsRef.current = nextWeights;
199
+ accuracyRef.current = acc;
200
+
201
+ // Batch state updates
202
+ setMemoryGrid(finalGrid);
203
+ setWeights(nextWeights);
204
+ setAccuracy(acc);
205
+ setHistory(prev => {
206
+ const next = [...prev];
207
+ next.shift();
208
+ next.push(acc);
209
+ return next;
210
+ });
211
+ };
212
+
213
+ // Adaptive tick rate — re-read frequency ref each cycle
214
+ const scheduleNext = () => {
215
+ const intervalTime = Math.max(16, 1000 - frequencyRef.current * 9.8);
216
+ simTimerRef.current = setTimeout(() => {
217
+ tick();
218
+ scheduleNext();
219
+ }, intervalTime);
220
+ };
221
+
222
+ scheduleNext();
223
+ return () => clearTimeout(simTimerRef.current);
224
+ }, []); // mount-only — reads everything from refs
225
+
226
+ // ── Waveform Canvas (ResizeObserver + proper rAF cleanup) ──
227
+ useEffect(() => {
228
+ const canvas = waveCanvasRef.current;
229
+ if (!canvas) return;
230
+ const ctx = canvas.getContext('2d');
231
+ const container = canvas.parentElement;
232
+
233
+ // Sync canvas resolution to container
234
+ const resizeCanvas = () => {
235
+ const rect = container.getBoundingClientRect();
236
+ const dpr = window.devicePixelRatio || 1;
237
+ canvas.width = rect.width * dpr;
238
+ canvas.height = rect.height * dpr;
239
+ ctx.scale(dpr, dpr);
240
+ canvas.style.width = rect.width + 'px';
241
+ canvas.style.height = rect.height + 'px';
242
+ };
243
+
244
+ const ro = new ResizeObserver(resizeCanvas);
245
+ ro.observe(container);
246
+ resizeCanvas();
247
+
248
+ const animate = () => {
249
+ waveFrameCount.current++;
250
+ const frame = waveFrameCount.current;
251
+ const w = canvas.width / (window.devicePixelRatio || 1);
252
+ const h = canvas.height / (window.devicePixelRatio || 1);
253
+
254
+ ctx.clearRect(0, 0, w, h);
255
+ ctx.beginPath();
256
+ ctx.strokeStyle = accuracyRef.current < 50 ? '#ff0055' : '#00ff9f';
257
+ ctx.lineWidth = 2;
258
+
259
+ const freq = frequencyRef.current;
260
+ const str = strengthRef.current;
261
+ const freqMod = freq / 20;
262
+ const ampMod = str / 4;
263
+ const noiseMod = (100 - str) / 5;
264
+
265
+ for (let x = 0; x < w; x++) {
266
+ const y = h / 2 +
267
+ Math.sin(x * 0.05 * freqMod + frame * 0.1) * ampMod +
268
+ Math.sin(x * 0.1 + frame * 0.2) * noiseMod;
269
+ if (x === 0) ctx.moveTo(x, y);
270
+ else ctx.lineTo(x, y);
271
+ }
272
+ ctx.stroke();
273
+
274
+ waveFrameRef.current = requestAnimationFrame(animate);
275
+ };
276
+
277
+ waveFrameRef.current = requestAnimationFrame(animate);
278
+
279
+ return () => {
280
+ cancelAnimationFrame(waveFrameRef.current);
281
+ ro.disconnect();
282
+ };
283
+ }, []);
284
+
285
+ // ── Slider helpers ──
286
+ const sliderHandler = useCallback((setter, ref) => (e) => {
287
+ const v = parseInt(e.target.value);
288
+ setter(v);
289
+ ref.current = v;
290
+ }, []);
291
+
292
+ return (
293
+ <div className={`min-h-screen p-4 md:p-6 font-mono selection:bg-[#00ff9f] selection:text-black transition-colors duration-500 ${accuracy < 25 ? 'bg-[#200] text-[#ff0055]' : 'bg-[#020202] text-[#00ff9f]'}`}>
294
+ {/* Scanline overlay */}
295
+ <div className="fixed inset-0 pointer-events-none z-50 opacity-[0.05] bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,255,0.06))] bg-[length:100%_2px,3px_100%]" />
296
+
297
+ <div className="max-w-7xl mx-auto space-y-6">
298
+ {/* Header */}
299
+ <header className="flex flex-col md:flex-row items-start md:items-center justify-between p-6 bg-[#0a0a0a] border-2 border-[#00ff9f]/20 rounded-sm relative overflow-hidden">
300
+ <div className="z-10 flex items-center gap-5">
301
+ <div className={`p-3 rounded-none shadow-lg transition-colors ${accuracy < 25 ? 'bg-[#ff0055] shadow-[#ff0055]' : 'bg-[#00ff9f] shadow-[#00ff9f]'}`}>
302
+ <BrainCircuit size={32} className="text-black" />
303
+ </div>
304
+ <div>
305
+ <h1 className={`text-4xl font-black uppercase tracking-tighter ${glitch || accuracy < 25 ? 'animate-pulse' : ''}`}>
306
+ Synapse_Link <span className="text-white text-2xl font-light opacity-50">HEBB-9</span>
307
+ </h1>
308
+ <div className="flex items-center gap-3 text-[10px] mt-1 font-bold opacity-60 uppercase">
309
+ <span className="flex items-center gap-1"><Dna size={12} /> PLASTICITY: ACTIVE</span>
310
+ <span className={`flex items-center gap-1 ${accuracy < 40 ? 'text-[#ff0055] animate-bounce' : ''}`}>
311
+ <ShieldAlert size={12} /> {accuracy < 25 ? 'CRITICAL DECOHERENCE' : 'SIGNAL STABLE'}
312
+ </span>
313
+ </div>
314
+ </div>
315
+ </div>
316
+
317
+ <div className="mt-4 md:mt-0 flex flex-wrap gap-3">
318
+ <button
319
+ onClick={() => setIsDrifting(d => !d)}
320
+ className={`px-6 py-3 font-black uppercase tracking-widest text-xs transition-all flex items-center gap-2 ${isDrifting ? 'bg-white text-black' : 'border border-[#00ff9f] text-[#00ff9f]'}`}
321
+ >
322
+ <Waves size={16} /> {isDrifting ? 'Stop Drift' : 'Enable Drift'}
323
+ </button>
324
+ <button onClick={generatePattern} className="bg-[#00ff9f] text-black px-6 py-3 font-black uppercase tracking-widest text-xs hover:bg-white transition-all shadow-md flex items-center gap-2">
325
+ <RefreshCw size={16} /> Sync Source
326
+ </button>
327
+ <button onClick={clearGrids} className="border border-[#ff0055] text-[#ff0055] px-6 py-3 font-black uppercase tracking-widest text-xs hover:bg-[#ff0055] hover:text-black transition-all flex items-center gap-2">
328
+ <Trash2 size={16} /> Purge
329
+ </button>
330
+ </div>
331
+ </header>
332
+
333
+ {/* Waveform + Fidelity */}
334
+ <div className="grid lg:grid-cols-4 gap-6">
335
+ <div className="lg:col-span-3 bg-[#0a0a0a] border border-[#00ff9f]/20 p-4 relative h-32" ref={containerRef}>
336
+ <div className="absolute top-2 left-2 text-[8px] opacity-40 uppercase font-black">Bio_Feedback_Wave</div>
337
+ <canvas ref={waveCanvasRef} className="w-full h-full" />
338
+ </div>
339
+ <div className={`bg-[#0a0a0a] border border-[#00ff9f]/20 p-4 flex flex-col justify-center items-center ${accuracy < 25 ? 'border-[#ff0055]' : ''}`}>
340
+ <div className="text-[10px] uppercase opacity-40 font-black mb-1">Engram Fidelity</div>
341
+ <div className={`text-4xl font-black italic tracking-tighter ${accuracy < 60 ? 'text-[#ff0055]' : 'text-white'}`}>
342
+ {accuracy}.0
343
+ </div>
344
+ <div className="w-full bg-[#111] h-1 mt-2">
345
+ <div className="bg-[#00ff9f] h-full transition-all" style={{ width: `${accuracy}%` }} />
346
+ </div>
347
+ </div>
348
+ </div>
349
+
350
+ {/* Grids */}
351
+ <div className="grid lg:grid-cols-2 gap-8">
352
+ {/* Signal Input */}
353
+ <div className="bg-[#0a0a0a] border-l-4 border-[#00ff9f] p-6 relative">
354
+ <div className="flex items-center justify-between mb-6">
355
+ <h3 className="font-black uppercase tracking-widest text-white flex items-center gap-2">
356
+ <Zap size={18} className="text-[#00ff9f]" /> Stimulus Source
357
+ </h3>
358
+ <div className="text-[10px] opacity-40 font-bold">MANUAL_OVERRIDE_ENABLED</div>
359
+ </div>
360
+ <div className="grid grid-cols-8 gap-2 aspect-square bg-black/40 p-3 border border-[#00ff9f]/10" role="grid" aria-label="Stimulus grid">
361
+ {signalGrid.map((active, i) => (
362
+ <GridCell key={i} active={active} variant="signal" weight={0} mismatch={false} onClick={() => toggleBlock(i)} />
363
+ ))}
364
+ </div>
365
+ </div>
366
+
367
+ {/* Memory Output */}
368
+ <div className="bg-[#0a0a0a] border-r-4 border-[#ff0055] p-6 relative">
369
+ <div className="flex items-center justify-between mb-6">
370
+ <h3 className="font-black uppercase tracking-widest text-white flex items-center gap-2">
371
+ <BrainCircuit size={18} className="text-[#ff0055]" /> Synaptic Cache
372
+ </h3>
373
+ <div className="flex items-center gap-2 text-[8px] font-bold">
374
+ <div className="w-2 h-2 bg-white rounded-full animate-ping" />
375
+ LEARNING_ACTIVE
376
+ </div>
377
+ </div>
378
+ <div className="grid grid-cols-8 gap-2 aspect-square bg-black/40 p-3 border border-[#ff0055]/10" role="grid" aria-label="Synaptic memory grid">
379
+ {memoryGrid.map((active, i) => (
380
+ <GridCell key={i} active={active} variant="memory" weight={weights[i]} mismatch={signalGrid[i] !== active} />
381
+ ))}
382
+ </div>
383
+ </div>
384
+ </div>
385
+
386
+ {/* Sliders */}
387
+ <div className="grid md:grid-cols-3 lg:grid-cols-5 gap-4">
388
+ <div className="bg-[#0a0a0a] p-4 border border-[#00ff9f]/20">
389
+ <label className="flex justify-between text-[10px] font-black uppercase mb-3">
390
+ <span>SNR_Amp</span><span>{strength}%</span>
391
+ </label>
392
+ <input type="range" min="0" max="100" value={strength} onChange={sliderHandler(setStrength, strengthRef)} className="w-full accent-[#00ff9f]" />
393
+ </div>
394
+ <div className="bg-[#0a0a0a] p-4 border border-[#00ff9f]/20">
395
+ <label className="flex justify-between text-[10px] font-black uppercase mb-3">
396
+ <span>Refresh_Hz</span><span>{frequency}%</span>
397
+ </label>
398
+ <input type="range" min="1" max="100" value={frequency} onChange={sliderHandler(setFrequency, frequencyRef)} className="w-full accent-[#00ff9f]" />
399
+ </div>
400
+ <div className="bg-[#0a0a0a] p-4 border border-[#ff0055]/20">
401
+ <label className="flex justify-between text-[10px] font-black uppercase mb-3 text-[#ff0055]">
402
+ <span>Persistence</span><span>{persistence}%</span>
403
+ </label>
404
+ <input type="range" min="0" max="100" value={persistence} onChange={sliderHandler(setPersistence, persistenceRef)} className="w-full accent-[#ff0055]" />
405
+ </div>
406
+ <div className="bg-[#0a0a0a] p-4 border border-[#ff0055]/20">
407
+ <label className="flex justify-between text-[10px] font-black uppercase mb-3 text-[#ff0055]">
408
+ <span>Cross_Talk</span><span>{crossTalk}%</span>
409
+ </label>
410
+ <input type="range" min="0" max="100" value={crossTalk} onChange={sliderHandler(setCrossTalk, crossTalkRef)} className="w-full accent-[#ff0055]" />
411
+ </div>
412
+ <div className="bg-[#111] p-4 border border-white/20">
413
+ <label className="flex justify-between text-[10px] font-black uppercase mb-3 text-white">
414
+ <span>Plasticity</span><span>{plasticity}%</span>
415
+ </label>
416
+ <input type="range" min="0" max="100" value={plasticity} onChange={sliderHandler(setPlasticity, plasticityRef)} className="w-full accent-white" />
417
+ </div>
418
+ </div>
419
+
420
+ {/* Telemetry Graph */}
421
+ <div className="bg-[#0a0a0a] border border-[#00ff9f]/10 p-4 h-20 flex items-end gap-1 relative overflow-hidden" aria-label="Fidelity history">
422
+ <div className="absolute inset-0 bg-gradient-to-t from-[#ff0055]/10 to-transparent pointer-events-none" />
423
+ {history.map((val, i) => (
424
+ <div key={i} className={`flex-1 transition-all ${val < 50 ? 'bg-[#ff0055]' : 'bg-[#00ff9f]/40'}`} style={{ height: `${val}%` }} />
425
+ ))}
426
+ </div>
427
+
428
+ {/* Footer */}
429
+ <footer className="p-4 bg-[#0a0a0a] border border-white/5 text-[9px] uppercase tracking-tighter opacity-60 grid grid-cols-2 gap-4">
430
+ <div className="flex gap-2"><Binary size={12} /> DATA_PAGING: STOCHASTIC_GRADIENT_ENABLED</div>
431
+ <div className="text-right">SYNAPTIC_WEIGHTS_NORMALIZED // HEBBIAN_COEFFICIENT: {(plasticity / 100).toFixed(2)}</div>
432
+ </footer>
433
+ </div>
434
+ </div>
435
+ );
436
+ };
437
+
438
+ export default App;
src/components/GridCell.jsx ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const GridCell = React.memo(({ active, mismatch, weight, variant, onClick }) => {
4
+ if (variant === 'signal') {
5
+ return (
6
+ <button
7
+ onClick={onClick}
8
+ aria-label={active ? 'Active neuron' : 'Inactive neuron'}
9
+ className={`w-full h-full transition-all duration-75 border ${
10
+ active
11
+ ? 'bg-[#00ff9f] border-white shadow-[0_0_15px_#00ff9f] scale-95'
12
+ : 'bg-transparent border-[#00ff9f]/5 hover:border-[#00ff9f]/40 hover:bg-[#00ff9f]/5'
13
+ }`}
14
+ />
15
+ );
16
+ }
17
+
18
+ // Memory variant
19
+ const style = weight > 0.5
20
+ ? { boxShadow: `0 0 ${weight * 20}px rgba(255,255,255,0.4)`, borderColor: `rgba(255,255,255,${weight})` }
21
+ : { borderColor: `rgba(255,255,255,${weight})` };
22
+
23
+ return (
24
+ <div
25
+ aria-label={active ? 'Remembered neuron' : 'Empty synapse'}
26
+ style={style}
27
+ className={`w-full h-full transition-all duration-300 border ${
28
+ active
29
+ ? 'bg-[#ff0055] shadow-[0_0_15px_#ff0055]'
30
+ : 'bg-transparent border-[#ff0055]/5'
31
+ } ${mismatch ? 'opacity-50 border-dotted scale-90' : ''}`}
32
+ />
33
+ );
34
+ });
35
+
36
+ GridCell.displayName = 'GridCell';
37
+
38
+ export default GridCell;
src/index.css ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App';
4
+ import './index.css';
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ );
tailwind.config.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: ['./index.html', './src/**/*.{js,jsx}'],
4
+ theme: {
5
+ extend: {},
6
+ },
7
+ plugins: [],
8
+ };
vite.config.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ host: '0.0.0.0',
8
+ port: 7860,
9
+ },
10
+ });