kimhyunwoo commited on
Commit
55eec5f
·
verified ·
1 Parent(s): 4e3dab2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +635 -19
index.html CHANGED
@@ -1,19 +1,635 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>3D CNN Visualizer</title>
7
+
8
+ <!-- Tailwind CSS -->
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <script>
11
+ tailwind.config = {
12
+ theme: {
13
+ extend: {
14
+ colors: {
15
+ 'neon-green': '#00ff00',
16
+ 'dark-green': '#002200',
17
+ },
18
+ fontFamily: {
19
+ mono: ['ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'monospace'],
20
+ }
21
+ }
22
+ }
23
+ }
24
+ </script>
25
+
26
+ <!-- Import Map for React & Three.js ecosystem -->
27
+ <script type="importmap">
28
+ {
29
+ "imports": {
30
+ "react": "https://esm.sh/react@18.2.0",
31
+ "react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
32
+ "three": "https://esm.sh/three@0.160.0",
33
+ "@react-three/fiber": "https://esm.sh/@react-three/fiber@8.15.12?external=react,react-dom,three",
34
+ "@react-three/drei": "https://esm.sh/@react-three/drei@9.96.1?external=react,react-dom,three,@react-three/fiber",
35
+ "lucide-react": "https://esm.sh/lucide-react@0.303.0?external=react"
36
+ }
37
+ }
38
+ </script>
39
+
40
+ <!-- Babel for JSX -->
41
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
42
+
43
+ <style>
44
+ body { margin: 0; background-color: #000; overflow: hidden; color: white; }
45
+ canvas { touch-action: none; }
46
+
47
+ .hud-panel {
48
+ background: rgba(0, 10, 0, 0.85);
49
+ border: 1px solid #004400;
50
+ box-shadow: 0 0 20px rgba(0, 255, 0, 0.1);
51
+ backdrop-filter: blur(10px);
52
+ }
53
+
54
+ .btn-holo {
55
+ background: linear-gradient(180deg, rgba(0,40,0,0.8) 0%, rgba(0,20,0,0.9) 100%);
56
+ border: 1px solid #00ff00;
57
+ color: #00ff00;
58
+ text-shadow: 0 0 5px rgba(0,255,0,0.5);
59
+ }
60
+ .btn-holo:hover:not(:disabled) {
61
+ background: #00ff00;
62
+ color: #000;
63
+ box-shadow: 0 0 15px #00ff00;
64
+ }
65
+ .btn-holo:disabled {
66
+ border-color: #004400;
67
+ color: #004400;
68
+ cursor: not-allowed;
69
+ }
70
+
71
+ .neon-text {
72
+ text-shadow: 0 0 10px rgba(0, 255, 0, 0.6);
73
+ }
74
+
75
+ /* Scanline effect */
76
+ .scanlines {
77
+ position: fixed;
78
+ top: 0; left: 0; width: 100%; height: 100%;
79
+ background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 50%, rgba(0,0,0,0.1));
80
+ background-size: 100% 4px;
81
+ pointer-events: none;
82
+ z-index: 50;
83
+ opacity: 0.3;
84
+ }
85
+ </style>
86
+ </head>
87
+ <body>
88
+ <div id="root"></div>
89
+ <div class="scanlines"></div>
90
+
91
+ <script type="text/babel" data-type="module">
92
+ import React, { useState, useEffect, useRef, useMemo, useLayoutEffect } from 'react';
93
+ import { createRoot } from 'react-dom/client';
94
+ import { Canvas, useFrame } from '@react-three/fiber';
95
+ import { Text, Stars, Environment, Grid } from '@react-three/drei';
96
+ import * as THREE from 'three';
97
+ import { Play, RotateCcw, Activity, Layers, Cpu, Scan, Zap, BrainCircuit, Eraser } from 'lucide-react';
98
+
99
+ // --- 1. CONFIGURATION ---
100
+ const ARCHITECTURE = [
101
+ { id: 'input', type: 'input', width: 28, height: 28, depth: 1, z: 0, label: 'INPUT (28x28)' },
102
+ { id: 'conv1', type: 'conv', width: 24, height: 24, depth: 4, z: -15, label: 'CONV LAYER 1' },
103
+ { id: 'pool1', type: 'pool', width: 12, height: 12, depth: 4, z: -25, label: 'POOLING 1' },
104
+ { id: 'conv2', type: 'conv', width: 8, height: 8, depth: 8, z: -35, label: 'CONV LAYER 2' },
105
+ { id: 'pool2', type: 'pool', width: 4, height: 4, depth: 8, z: -45, label: 'POOLING 2' },
106
+ { id: 'flat', type: 'flatten', width: 16, height: 8, depth: 1, z: -52, label: 'FLATTEN' },
107
+ { id: 'fc', type: 'fc', width: 1, height: 10, depth: 1, z: -62, label: 'CLASSIFICATION' }
108
+ ];
109
+
110
+ // --- 2. MATH ENGINE (Synthethic Neural Network) ---
111
+
112
+ // Define vector strokes for digits to generate "ideal" weights
113
+ const DIGIT_STROKES = {
114
+ 0: [[[10,2],[4,6],[4,14],[10,18],[16,14],[16,6],[10,2]]],
115
+ 1: [[[10,2],[10,18]]],
116
+ 2: [[[4,6],[6,2],[14,2],[16,6],[4,18],[18,18]]],
117
+ 3: [[[4,4],[16,4],[10,10],[16,16],[4,16]]],
118
+ 4: [[[14,2],[4,12],[18,12]],[[14,2],[14,18]]],
119
+ 5: [[[16,4],[6,4],[4,10],[16,12],[4,18]]],
120
+ 6: [[[16,4],[4,12],[4,16],[10,18],[16,14],[10,10]]],
121
+ 7: [[[4,4],[16,4],[8,18]]],
122
+ 8: [[[10,10],[4,6],[10,2],[16,6],[10,10],[4,14],[10,18],[16,14],[10,10]]],
123
+ 9: [[[16,10],[10,4],[4,8],[10,12],[16,10],[10,18]]]
124
+ };
125
+
126
+ const createGrid = (w, h, val = 0) => Array.from({ length: h }, () => Array(w).fill(val));
127
+
128
+ // Rasterize lines into grid
129
+ const drawLine = (grid, p1, p2, intensity) => {
130
+ let x0 = Math.round(p1[0]), y0 = Math.round(p1[1]);
131
+ let x1 = Math.round(p2[0]), y1 = Math.round(p2[1]);
132
+ const dx = Math.abs(x1 - x0), dy = Math.abs(y1 - y0);
133
+ const sx = (x0 < x1) ? 1 : -1, sy = (y0 < y1) ? 1 : -1;
134
+ let err = dx - dy;
135
+ while(true) {
136
+ if(y0 >= 0 && y0 < 20 && x0 >= 0 && x0 < 20) grid[y0][x0] = Math.max(grid[y0][x0], intensity);
137
+ if(x0 === x1 && y0 === y1) break;
138
+ const e2 = 2 * err;
139
+ if(e2 > -dy) { err -= dy; x0 += sx; }
140
+ if(e2 < dx) { err += dx; y0 += sy; }
141
+ }
142
+ };
143
+
144
+ const gaussianBlur = (grid) => {
145
+ const size = 20;
146
+ const result = createGrid(size, size);
147
+ const kernel = [[0.06, 0.12, 0.06], [0.12, 0.25, 0.12], [0.06, 0.12, 0.06]];
148
+ for(let y=1; y<size-1; y++) {
149
+ for(let x=1; x<size-1; x++) {
150
+ let sum = 0;
151
+ for(let ky=-1; ky<=1; ky++) for(let kx=-1; kx<=1; kx++) sum += grid[y+ky][x+kx] * kernel[ky+1][kx+1];
152
+ result[y][x] = sum;
153
+ }
154
+ }
155
+ return result;
156
+ };
157
+
158
+ // Initialize Brain
159
+ let TRAINED_WEIGHTS = {};
160
+ const trainBrain = () => {
161
+ Object.keys(DIGIT_STROKES).forEach(key => {
162
+ const strokes = DIGIT_STROKES[key];
163
+ const grid = createGrid(20, 20);
164
+ strokes.forEach(part => {
165
+ for(let i=0; i<part.length-1; i++) drawLine(grid, part[i], part[i+1], 1.0);
166
+ });
167
+ // Blur heavily to create a "probability field"
168
+ TRAINED_WEIGHTS[key] = gaussianBlur(gaussianBlur(grid));
169
+ });
170
+ };
171
+
172
+ // Preprocessing
173
+ const getBoundingBox = (grid) => {
174
+ let minX = 28, minY = 28, maxX = 0, maxY = 0, hasPixels = false;
175
+ for(let y=0; y<28; y++) for(let x=0; x<28; x++) if(grid[y][x] > 0.1) {
176
+ hasPixels = true;
177
+ if(x < minX) minX = x;
178
+ if(x > maxX) maxX = x;
179
+ if(y < minY) minY = y;
180
+ if(y > maxY) maxY = y;
181
+ }
182
+ return { minX, minY, w: maxX - minX + 1, h: maxY - minY + 1, hasPixels };
183
+ };
184
+
185
+ const scaleToFit = (grid) => {
186
+ const box = getBoundingBox(grid);
187
+ if(!box.hasPixels) return grid;
188
+
189
+ const canvas = document.createElement('canvas');
190
+ canvas.width = 28; canvas.height = 28;
191
+ const ctx = canvas.getContext('2d');
192
+
193
+ // Draw original data to canvas
194
+ const imgData = ctx.createImageData(28, 28);
195
+ for(let i=0; i<28*28; i++) {
196
+ const val = grid[Math.floor(i/28)][i%28] * 255;
197
+ imgData.data[i*4+0] = val; imgData.data[i*4+1] = val; imgData.data[i*4+2] = val; imgData.data[i*4+3] = 255;
198
+ }
199
+
200
+ // Crop to temporary canvas
201
+ const temp = document.createElement('canvas');
202
+ temp.width = box.w; temp.height = box.h;
203
+ temp.getContext('2d').putImageData(imgData, -box.minX, -box.minY, 0, 0, 28, 28);
204
+
205
+ // Scale and center back to 28x28 (target inner 20x20)
206
+ ctx.clearRect(0,0,28,28);
207
+ const scale = Math.min(20/box.w, 20/box.h);
208
+ ctx.drawImage(temp, (28 - box.w*scale)/2, (28 - box.h*scale)/2, box.w*scale, box.h*scale);
209
+
210
+ const finalData = ctx.getImageData(0,0,28,28).data;
211
+ const finalGrid = createGrid(28,28);
212
+ for(let i=0; i<28*28; i++) finalGrid[Math.floor(i/28)][i%28] = finalData[i*4+1] / 255;
213
+ return finalGrid;
214
+ };
215
+
216
+ const predict = (rawGrid) => {
217
+ const scaled = scaleToFit(rawGrid);
218
+ // Extract center 20x20
219
+ const inputVec = [];
220
+ for(let y=4; y<24; y++) for(let x=4; x<24; x++) inputVec.push(scaled[y][x]);
221
+
222
+ const inputMag = Math.sqrt(inputVec.reduce((acc, v) => acc + v*v, 0));
223
+ const scores = Array(10).fill(0);
224
+
225
+ if(inputMag > 0.1) {
226
+ for(let d=0; d<=9; d++) {
227
+ const proto = TRAINED_WEIGHTS[d];
228
+ const protoVec = [];
229
+ proto.forEach(row => row.forEach(v => protoVec.push(v)));
230
+
231
+ let dot = 0, protoMag = 0;
232
+ for(let i=0; i<400; i++) {
233
+ dot += inputVec[i] * protoVec[i];
234
+ protoMag += protoVec[i] * protoVec[i];
235
+ }
236
+ scores[d] = dot / (inputMag * Math.sqrt(protoMag) + 0.0001);
237
+ }
238
+ }
239
+
240
+ // Softmax
241
+ const exp = scores.map(s => Math.exp(s * 10)); // Amplify differences
242
+ const sum = exp.reduce((a,b) => a+b, 0);
243
+ const probs = sum > 0 ? exp.map(e => e/sum) : Array(10).fill(0);
244
+ return { probs, scaled };
245
+ };
246
+
247
+ const convolve = (input, kernels) => {
248
+ const h = input.length, w = input[0].length, kSize = kernels[0].length;
249
+ return kernels.map(k => {
250
+ const dim = h - kSize + 1;
251
+ const layer = createGrid(dim, dim);
252
+ for(let y=0; y<dim; y++) for(let x=0; x<dim; x++) {
253
+ let sum = 0;
254
+ for(let ky=0; ky<kSize; ky++) for(let kx=0; kx<kSize; kx++) sum += input[y+ky][x+kx] * k[ky][kx];
255
+ layer[y][x] = Math.max(0, sum); // ReLU
256
+ }
257
+ return layer;
258
+ });
259
+ };
260
+
261
+ const maxPool = (inputs) => inputs.map(layer => {
262
+ const h = layer.length, w = layer[0].length;
263
+ const out = createGrid(Math.floor(w/2), Math.floor(h/2));
264
+ for(let y=0; y<out.length; y++) for(let x=0; x<out[0].length; x++)
265
+ out[y][x] = Math.max(layer[y*2][x*2], layer[y*2][x*2+1], layer[y*2+1][x*2], layer[y*2+1][x*2+1]);
266
+ return out;
267
+ });
268
+
269
+ // --- 3. AUDIO ENGINE ---
270
+ class AudioEngine {
271
+ constructor() {
272
+ this.ctx = null;
273
+ this.master = null;
274
+ }
275
+ init() {
276
+ if(this.ctx) return;
277
+ const Ctx = window.AudioContext || window.webkitAudioContext;
278
+ this.ctx = new Ctx();
279
+ this.master = this.ctx.createGain();
280
+ this.master.gain.value = 0.1;
281
+ this.master.connect(this.ctx.destination);
282
+ }
283
+ playTone(freq, type, dur) {
284
+ if(!this.ctx) return;
285
+ if(this.ctx.state === 'suspended') this.ctx.resume();
286
+ const osc = this.ctx.createOscillator();
287
+ const gain = this.ctx.createGain();
288
+ osc.type = type;
289
+ osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
290
+ gain.gain.setValueAtTime(0, this.ctx.currentTime);
291
+ gain.gain.linearRampToValueAtTime(1, this.ctx.currentTime + 0.01);
292
+ gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + dur);
293
+ osc.connect(gain);
294
+ gain.connect(this.master);
295
+ osc.start();
296
+ osc.stop(this.ctx.currentTime + dur);
297
+ }
298
+ playStep(step) {
299
+ if(step===0) this.playTone(400, 'sine', 0.1);
300
+ if(step===1) this.playTone(150, 'sawtooth', 0.1);
301
+ if(step===2) this.playTone(200, 'square', 0.1);
302
+ if(step===3) this.playTone(300, 'sawtooth', 0.1);
303
+ if(step===4) this.playTone(600, 'square', 0.1);
304
+ if(step===5) this.playTone(800, 'triangle', 0.1);
305
+ if(step===6) { this.playTone(440, 'sine', 0.3); setTimeout(()=>this.playTone(660,'sine',0.3), 100); }
306
+ }
307
+ }
308
+ const audio = new AudioEngine();
309
+
310
+ // --- 4. 3D COMPONENTS ---
311
+
312
+ const VoxelLayer = ({ config, data, active }) => {
313
+ const meshRef = useRef();
314
+ const dummy = useMemo(() => new THREE.Object3D(), []);
315
+ const color = useMemo(() => new THREE.Color(), []);
316
+
317
+ // Calculate total instances needed
318
+ const count = config.type === 'fc' ? 10 : (config.type === 'flatten' ? 128 : config.width * config.height * config.depth);
319
+
320
+ useLayoutEffect(() => {
321
+ if(!meshRef.current) return;
322
+ let idx = 0;
323
+
324
+ // --- VISUALIZATION LOGIC ---
325
+ if(config.type === 'fc') {
326
+ // Output bars
327
+ for(let i=0; i<10; i++) {
328
+ const val = data[0]?.[i]?.[0] || 0;
329
+ dummy.position.set(0, (4.5 - i) * 0.7, 0);
330
+ dummy.scale.set(val > 0.01 ? 0.5 + val * 4 : 0.5, 0.5, 0.5);
331
+ dummy.updateMatrix();
332
+ meshRef.current.setMatrixAt(idx, dummy.matrix);
333
+ if(active && val > 0.01) color.setHSL(0.33, 1, 0.5);
334
+ else color.setHex(0x002200);
335
+ meshRef.current.setColorAt(idx, color);
336
+ idx++;
337
+ }
338
+ } else if (config.type === 'flatten') {
339
+ // Flatten grid representation
340
+ const flatData = [];
341
+ if(data.length) data.forEach(slice => slice.forEach(row => row.forEach(v => flatData.push(v))));
342
+ for(let y=0; y<8; y++) {
343
+ for(let x=0; x<16; x++) {
344
+ const val = flatData[idx] || 0;
345
+ dummy.position.set((x-8)*0.25, (y-4)*0.25, 0);
346
+ if(val > 0.1) {
347
+ dummy.scale.set(0.9, 0.9, 0.9);
348
+ color.setHSL(0.35, 1, 0.2 + val*0.8);
349
+ } else {
350
+ dummy.scale.set(0,0,0);
351
+ }
352
+ dummy.updateMatrix();
353
+ meshRef.current.setMatrixAt(idx, dummy.matrix);
354
+ meshRef.current.setColorAt(idx, color);
355
+ idx++;
356
+ }
357
+ }
358
+ } else {
359
+ // Convolutional Volumes
360
+ const gap = 0.3;
361
+ const layerW = config.width * 0.2;
362
+ const totalW = (layerW + gap) * config.depth;
363
+ const startX = -totalW / 2;
364
+
365
+ for(let z=0; z<config.depth; z++) {
366
+ const zOff = startX + z * (layerW + gap);
367
+ for(let y=0; y<config.height; y++) {
368
+ for(let x=0; x<config.width; x++) {
369
+ const val = data[z]?.[config.height-1-y]?.[x] || 0;
370
+ dummy.position.set(zOff + x*0.2, (y - config.height/2)*0.2, 0);
371
+ if(val > 0.1) {
372
+ dummy.scale.set(0.9,0.9,0.9);
373
+ color.setHSL(0.35, 1, 0.2 + val*0.8);
374
+ } else {
375
+ dummy.scale.set(0,0,0);
376
+ }
377
+ dummy.updateMatrix();
378
+ meshRef.current.setMatrixAt(idx, dummy.matrix);
379
+ meshRef.current.setColorAt(idx, color);
380
+ idx++;
381
+ }
382
+ }
383
+ }
384
+ }
385
+ meshRef.current.instanceMatrix.needsUpdate = true;
386
+ if(meshRef.current.instanceColor) meshRef.current.instanceColor.needsUpdate = true;
387
+ }, [config, data, active]);
388
+
389
+ return (
390
+ <group position={[0, 0, config.z]}>
391
+ <Text position={[0, config.height * 0.12 + 2, 0]} fontSize={0.6} color={active?"#fff":"#004400"}>
392
+ {config.label}
393
+ </Text>
394
+ <instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
395
+ <boxGeometry args={[0.2, 0.2, 0.2]} />
396
+ <meshStandardMaterial color="#0f0" transparent opacity={0.9} blending={THREE.AdditiveBlending} toneMapped={false} />
397
+ </instancedMesh>
398
+ {config.type === 'fc' && Array.from({length:10}).map((_, i) => (
399
+ <group key={i} position={[0, (4.5 - i) * 0.7, 0]}>
400
+ <Text position={[-1.5, 0, 0]} fontSize={0.4} color="#0f0">{i}</Text>
401
+ <Text position={[4, 0, 0]} fontSize={0.4} color="#fff">
402
+ {((data[0]?.[i]?.[0] || 0) * 100).toFixed(1)}%
403
+ </Text>
404
+ </group>
405
+ ))}
406
+ </group>
407
+ );
408
+ };
409
+
410
+ const CameraController = ({ step }) => {
411
+ useFrame((state) => {
412
+ const targetPos = new THREE.Vector3();
413
+ const targetLook = new THREE.Vector3();
414
+ if(step === -1) {
415
+ targetPos.set(25, 10, 5);
416
+ targetLook.set(0, 0, -35);
417
+ } else {
418
+ const zMap = {0:0, 1:-15, 2:-25, 3:-35, 4:-45, 5:-52, 6:-62};
419
+ const z = zMap[step] || 0;
420
+ targetPos.set(18, 5, z + 8);
421
+ targetLook.set(0, 0, z);
422
+ }
423
+ state.camera.position.lerp(targetPos, 0.05);
424
+ const look = new THREE.Vector3(0,0,-1).applyQuaternion(state.camera.quaternion).add(state.camera.position);
425
+ look.lerp(targetLook, 0.05);
426
+ state.camera.lookAt(look);
427
+ });
428
+ return null;
429
+ };
430
+
431
+ // --- 5. DRAWING PAD ---
432
+ const DrawingPad = ({ data, onChange, disabled }) => {
433
+ const canvasRef = useRef(null);
434
+ const [isDrawing, setIsDrawing] = useState(false);
435
+
436
+ useEffect(() => {
437
+ const ctx = canvasRef.current?.getContext('2d');
438
+ if(ctx && data.every(r=>r.every(v=>v===0))) {
439
+ ctx.fillStyle = 'black'; ctx.fillRect(0,0,280,280);
440
+ // Draw Grid
441
+ ctx.strokeStyle = '#002200'; ctx.lineWidth = 1;
442
+ ctx.beginPath();
443
+ for(let i=0; i<=280; i+=28) { ctx.moveTo(i,0); ctx.lineTo(i,280); ctx.moveTo(0,i); ctx.lineTo(280,i); }
444
+ ctx.stroke();
445
+ }
446
+ }, [data]);
447
+
448
+ const getPos = (e) => {
449
+ const r = canvasRef.current.getBoundingClientRect();
450
+ const x = (e.touches?e.touches[0].clientX:e.clientX) - r.left;
451
+ const y = (e.touches?e.touches[0].clientY:e.clientY) - r.top;
452
+ const scaleX = canvasRef.current.width / r.width;
453
+ const scaleY = canvasRef.current.height / r.height;
454
+ return { x: x*scaleX, y: y*scaleY };
455
+ };
456
+
457
+ const draw = (e) => {
458
+ if(disabled || !isDrawing) return;
459
+ const ctx = canvasRef.current.getContext('2d');
460
+ const {x,y} = getPos(e);
461
+ ctx.strokeStyle = '#0f0';
462
+ ctx.lineWidth = 25;
463
+ ctx.lineCap = 'round';
464
+ ctx.shadowBlur = 10; ctx.shadowColor = '#0f0';
465
+ ctx.lineTo(x,y); ctx.stroke();
466
+ ctx.beginPath(); ctx.moveTo(x,y);
467
+ };
468
+
469
+ const start = (e) => {
470
+ if(disabled) return;
471
+ setIsDrawing(true);
472
+ const {x,y} = getPos(e);
473
+ const ctx = canvasRef.current.getContext('2d');
474
+ ctx.beginPath(); ctx.moveTo(x,y);
475
+ };
476
+
477
+ const end = () => {
478
+ if(!isDrawing) return;
479
+ setIsDrawing(false);
480
+ const ctx = canvasRef.current.getContext('2d');
481
+ const temp = document.createElement('canvas'); temp.width=28; temp.height=28;
482
+ temp.getContext('2d').drawImage(canvasRef.current,0,0,28,28);
483
+ const img = temp.getContext('2d').getImageData(0,0,28,28).data;
484
+ const grid = createGrid(28,28);
485
+ for(let i=0; i<28*28; i++) grid[Math.floor(i/28)][i%28] = img[i*4+1]/255;
486
+ onChange(grid);
487
+ };
488
+
489
+ return (
490
+ <canvas ref={canvasRef} width={280} height={280}
491
+ className={`w-[220px] h-[220px] rounded border border-green-800 bg-black cursor-crosshair ${disabled ? 'opacity-50 pointer-events-none' : ''}`}
492
+ onMouseDown={start} onMouseMove={draw} onMouseUp={end} onMouseLeave={()=>setIsDrawing(false)}
493
+ onTouchStart={start} onTouchMove={draw} onTouchEnd={end}
494
+ />
495
+ );
496
+ };
497
+
498
+ // --- 6. MAIN APP ---
499
+ const App = () => {
500
+ const [activations, setActivations] = useState({});
501
+ const [step, setStep] = useState(-1);
502
+ const [processing, setProcessing] = useState(false);
503
+ const [inputData, setInputData] = useState(createGrid(28,28));
504
+
505
+ useEffect(() => {
506
+ trainBrain();
507
+ reset();
508
+ audio.init();
509
+ }, []);
510
+
511
+ const reset = () => {
512
+ setStep(-1);
513
+ setActivations(ARCHITECTURE.reduce((acc,l) => ({...acc, [l.id]: []}), {}));
514
+ setInputData(createGrid(28,28));
515
+ };
516
+
517
+ const delay = ms => new Promise(r => setTimeout(r, ms));
518
+
519
+ const run = async () => {
520
+ if(processing) return;
521
+ setProcessing(true);
522
+
523
+ // Inference
524
+ const { probs, scaled } = predict(inputData);
525
+
526
+ // Pipeline Animation
527
+ setActivations(prev => ({...prev, input: [scaled]}));
528
+ setStep(0); audio.playStep(0); await delay(600);
529
+
530
+ const k1 = [[[1,1,1],[0,0,0],[-1,-1,-1]], [[1,0,-1],[1,0,-1],[1,0,-1]], [[0,-1,0],[-1,4,-1],[0,-1,0]], [[-1,-1,-1],[-1,8,-1],[-1,-1,-1]]];
531
+ const c1 = convolve(scaled, k1);
532
+ setActivations(prev => ({...prev, conv1: c1}));
533
+ setStep(1); audio.playStep(1); await delay(800);
534
+
535
+ const p1 = maxPool(c1);
536
+ setActivations(prev => ({...prev, pool1: p1}));
537
+ setStep(2); audio.playStep(2); await delay(800);
538
+
539
+ const c2 = p1.map(layer => convolve(layer, [[[0.5,0.5],[0.5,0.5]]])[0]).concat(p1.map(l => l)); // Fake doubling depth
540
+ setActivations(prev => ({...prev, conv2: c2}));
541
+ setStep(3); audio.playStep(3); await delay(800);
542
+
543
+ const p2 = maxPool(c2);
544
+ setActivations(prev => ({...prev, pool2: p2}));
545
+ setStep(4); audio.playStep(4); await delay(800);
546
+
547
+ setActivations(prev => ({...prev, flat: p2}));
548
+ setStep(5); audio.playStep(5); await delay(600);
549
+
550
+ setActivations(prev => ({...prev, fc: [probs.map(p=>[p])]}));
551
+ setStep(6); audio.playStep(6);
552
+
553
+ await delay(3000);
554
+ setProcessing(false);
555
+ setStep(-1);
556
+ };
557
+
558
+ return (
559
+ <div className="w-full h-screen relative bg-black font-mono">
560
+ <Canvas shadows camera={{ position: [25, 10, 5], fov: 45 }}>
561
+ <CameraController step={step} />
562
+ <color attach="background" args={['#000200']} />
563
+ <fog attach="fog" args={['#000200', 20, 90]} />
564
+ <ambientLight intensity={0.2} />
565
+ <pointLight position={[10, 20, 10]} intensity={1.5} color="#00ff00" distance={50} />
566
+
567
+ <group>
568
+ {ARCHITECTURE.map((cfg, i) => (
569
+ <VoxelLayer key={cfg.id} config={cfg} data={activations[cfg.id] || []} active={step===i} />
570
+ ))}
571
+ <Grid args={[200, 200]} cellSize={1} cellThickness={1} sectionSize={5} sectionThickness={1.5} fadeDistance={60} sectionColor="#004400" cellColor="#001100" position={[0, -5, -30]} />
572
+ </group>
573
+
574
+ <Stars radius={100} depth={50} count={3000} factor={4} saturation={0} fade speed={1} />
575
+ <Environment preset="city" />
576
+ </Canvas>
577
+
578
+ {/* HUD UI */}
579
+ <div className="absolute inset-0 pointer-events-none flex flex-col justify-between p-4 z-10">
580
+ {/* Header */}
581
+ <div className="flex justify-between items-start">
582
+ <div className="hud-panel p-4 rounded-br-2xl border-l-4 border-l-green-500">
583
+ <h1 className="text-2xl md:text-4xl font-black tracking-tighter neon-text flex items-center gap-3">
584
+ <Cpu className="text-neon-green animate-pulse" /> DEEP <span className="text-neon-green">CNN</span>
585
+ </h1>
586
+ <div className="text-xs text-green-400 mt-2 flex items-center gap-2">
587
+ <Activity size={12} /> SYSTEM: {processing ? "PROCESSING TENSORS..." : "ONLINE"}
588
+ </div>
589
+ </div>
590
+ </div>
591
+
592
+ {/* Controls */}
593
+ <div className="flex flex-col md:flex-row items-end gap-6 pointer-events-auto">
594
+ <div className="hud-panel p-4 rounded-tr-2xl backdrop-blur-xl max-w-sm">
595
+ <div className="flex justify-between items-center mb-2 text-green-400">
596
+ <div className="text-xs font-bold tracking-widest flex items-center gap-2">
597
+ <Scan size={14} /> INPUT SENSOR
598
+ </div>
599
+ <button onClick={reset} disabled={processing} className="hover:text-white transition-colors">
600
+ <RotateCcw size={16} />
601
+ </button>
602
+ </div>
603
+ <DrawingPad data={inputData} onChange={setInputData} disabled={processing} />
604
+ <button onClick={run} disabled={processing} className="w-full mt-4 py-3 rounded btn-holo flex justify-center items-center gap-2 font-bold transition-all">
605
+ {processing ? <Activity className="animate-spin" size={18} /> : <Play size={18} fill="currentColor" />}
606
+ {processing ? 'CALCULATING...' : 'RUN INFERENCE'}
607
+ </button>
608
+ </div>
609
+
610
+ {/* Pipeline Status */}
611
+ <div className="hud-panel p-5 hidden md:block rounded-t-xl min-w-[260px] border-b-0">
612
+ <div className="text-xs text-green-500 font-bold mb-3 flex items-center gap-2">
613
+ <Layers size={14} /> PIPELINE STATUS
614
+ </div>
615
+ <div className="space-y-2">
616
+ {ARCHITECTURE.map((l, i) => (
617
+ <div key={l.id} className={`flex items-center gap-3 text-xs transition-all duration-300 ${step===i ? 'text-white translate-x-2' : 'text-green-900'}`}>
618
+ <div className={`w-2 h-2 rounded-sm ${step===i ? 'bg-neon-green shadow-[0_0_8px_#0f0]' : 'bg-green-900'}`} />
619
+ <span className={step===i ? 'font-bold' : ''}>{l.label}</span>
620
+ {step===i && <Zap size={10} className="ml-auto text-yellow-400 animate-pulse" />}
621
+ </div>
622
+ ))}
623
+ </div>
624
+ </div>
625
+ </div>
626
+ </div>
627
+ </div>
628
+ );
629
+ };
630
+
631
+ const root = createRoot(document.getElementById('root'));
632
+ root.render(<App />);
633
+ </script>
634
+ </body>
635
+ </html>