minhvtt commited on
Commit
ac2e2a9
·
verified ·
1 Parent(s): 7e905ae

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1039 -19
index.html CHANGED
@@ -1,19 +1,1039 @@
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="vi">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title> Đồ Dòng Điện - Trình Mô Phỏng Tương Tác</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
9
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
10
+ <style>
11
+ body {
12
+ font-family: 'Inter', sans-serif;
13
+ background-color: #0f172a;
14
+ color: #e2e8f0;
15
+ overflow-x: hidden;
16
+ }
17
+ .mono {
18
+ font-family: 'JetBrains Mono', monospace;
19
+ }
20
+ /* Custom Scrollbar */
21
+ ::-webkit-scrollbar {
22
+ width: 8px;
23
+ height: 8px;
24
+ }
25
+ ::-webkit-scrollbar-track {
26
+ background: #1e293b;
27
+ }
28
+ ::-webkit-scrollbar-thumb {
29
+ background: #475569;
30
+ border-radius: 4px;
31
+ }
32
+ ::-webkit-scrollbar-thumb:hover {
33
+ background: #64748b;
34
+ }
35
+
36
+ /* Glassmorphism */
37
+ .glass {
38
+ background: rgba(30, 41, 59, 0.7);
39
+ backdrop-filter: blur(12px);
40
+ -webkit-backdrop-filter: blur(12px);
41
+ border: 1px solid rgba(255, 255, 255, 0.1);
42
+ }
43
+
44
+ /* Neon Glows */
45
+ .glow-text {
46
+ text-shadow: 0 0 10px rgba(56, 189, 248, 0.5);
47
+ }
48
+ .glow-border {
49
+ box-shadow: 0 0 15px rgba(56, 189, 248, 0.2);
50
+ }
51
+
52
+ /* Canvas Container */
53
+ #canvas-container {
54
+ background-image:
55
+ radial-gradient(circle at 1px 1px, rgba(255,255,255,0.05) 1px, transparent 0);
56
+ background-size: 20px 20px;
57
+ cursor: crosshair;
58
+ }
59
+
60
+ /* Range Slider Styling */
61
+ input[type=range] {
62
+ -webkit-appearance: none;
63
+ background: transparent;
64
+ }
65
+ input[type=range]::-webkit-slider-thumb {
66
+ -webkit-appearance: none;
67
+ height: 16px;
68
+ width: 16px;
69
+ border-radius: 50%;
70
+ background: #38bdf8;
71
+ cursor: pointer;
72
+ margin-top: -6px;
73
+ box-shadow: 0 0 10px #38bdf8;
74
+ }
75
+ input[type=range]::-webkit-slider-runnable-track {
76
+ width: 100%;
77
+ height: 4px;
78
+ cursor: pointer;
79
+ background: #334155;
80
+ border-radius: 2px;
81
+ }
82
+
83
+ /* Animations */
84
+ @keyframes pulse-ring {
85
+ 0% { transform: scale(0.8); opacity: 0.5; }
86
+ 100% { transform: scale(1.2); opacity: 0; }
87
+ }
88
+ .animate-pulse-ring::before {
89
+ content: '';
90
+ position: absolute;
91
+ left: 0; top: 0; right: 0; bottom: 0;
92
+ border-radius: 50%;
93
+ border: 2px solid #38bdf8;
94
+ animation: pulse-ring 2s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
95
+ }
96
+
97
+ .component-btn {
98
+ transition: all 0.2s ease;
99
+ }
100
+ .component-btn:hover {
101
+ transform: translateY(-2px);
102
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
103
+ border-color: #38bdf8;
104
+ }
105
+ .component-btn.active {
106
+ background: #38bdf8;
107
+ color: #0f172a;
108
+ border-color: #38bdf8;
109
+ }
110
+ </style>
111
+ </head>
112
+ <body class="h-screen flex flex-col">
113
+
114
+ <!-- Header -->
115
+ <header class="h-16 border-b border-slate-700 bg-slate-900/90 flex items-center justify-between px-6 z-20 relative">
116
+ <div class="flex items-center gap-3">
117
+ <div class="w-8 h-8 bg-blue-500 rounded flex items-center justify-center shadow-[0_0_15px_rgba(59,130,246,0.5)]">
118
+ <i class="fa-solid fa-bolt text-white"></i>
119
+ </div>
120
+ <h1 class="text-xl font-bold tracking-tight text-white glow-text">Circuit<span class="text-blue-400">Sim</span> <span class="text-xs font-normal text-slate-400 ml-2 border border-slate-700 px-2 py-0.5 rounded">v2.0</span></h1>
121
+ </div>
122
+
123
+ <div class="hidden md:flex items-center gap-6 text-sm text-slate-400">
124
+ <div class="flex items-center gap-2">
125
+ <span class="w-2 h-2 rounded-full bg-green-500 animate-pulse"></span>
126
+ <span>Hệ thống sẵn sàng</span>
127
+ </div>
128
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="hover:text-blue-400 transition-colors text-xs border-b border-dashed border-slate-600 pb-0.5">
129
+ Built with anycoder
130
+ </a>
131
+ </div>
132
+
133
+ <div class="flex gap-3">
134
+ <button onclick="app.clearCanvas()" class="px-4 py-2 text-sm font-medium text-slate-300 hover:text-white hover:bg-slate-800 rounded transition-colors">
135
+ <i class="fa-solid fa-trash-can mr-2"></i>Xóa
136
+ </button>
137
+ <button onclick="app.toggleSimulation()" id="sim-btn" class="px-4 py-2 text-sm font-bold bg-blue-600 hover:bg-blue-500 text-white rounded shadow-[0_0_15px_rgba(37,99,235,0.4)] transition-all flex items-center gap-2">
138
+ <i class="fa-solid fa-play"></i> <span id="sim-text">Chạy Mô Phỏng</span>
139
+ </button>
140
+ </div>
141
+ </header>
142
+
143
+ <!-- Main Workspace -->
144
+ <div class="flex-1 flex overflow-hidden relative">
145
+
146
+ <!-- Left Sidebar: Components -->
147
+ <aside class="w-64 bg-slate-900 border-r border-slate-700 flex flex-col z-10">
148
+ <div class="p-4 border-b border-slate-800">
149
+ <h2 class="text-xs font-bold text-slate-500 uppercase tracking-wider mb-1">Thư viện linh kiện</h2>
150
+ <p class="text-[10px] text-slate-600">Kéo thả hoặc click để thêm</p>
151
+ </div>
152
+
153
+ <div class="flex-1 overflow-y-auto p-4 space-y-3">
154
+ <!-- Component Items -->
155
+ <div class="component-btn group cursor-pointer bg-slate-800 p-3 rounded border border-slate-700 flex items-center gap-3" onclick="app.setTool('wire')">
156
+ <div class="w-8 h-8 rounded bg-slate-700 flex items-center justify-center group-hover:bg-slate-600">
157
+ <i class="fa-solid fa-minus text-slate-300"></i>
158
+ </div>
159
+ <div>
160
+ <div class="text-sm font-medium text-slate-200">Dây dẫn</div>
161
+ <div class="text-[10px] text-slate-500">Nối các linh kiện</div>
162
+ </div>
163
+ </div>
164
+
165
+ <div class="component-btn group cursor-pointer bg-slate-800 p-3 rounded border border-slate-700 flex items-center gap-3" onclick="app.setTool('resistor')">
166
+ <div class="w-8 h-8 rounded bg-slate-700 flex items-center justify-center group-hover:bg-slate-600">
167
+ <span class="font-serif font-bold text-slate-300">R</span>
168
+ </div>
169
+ <div>
170
+ <div class="text-sm font-medium text-slate-200">Điện trở</div>
171
+ <div class="text-[10px] text-slate-500">Cản trở dòng điện</div>
172
+ </div>
173
+ </div>
174
+
175
+ <div class="component-btn group cursor-pointer bg-slate-800 p-3 rounded border border-slate-700 flex items-center gap-3" onclick="app.setTool('battery')">
176
+ <div class="w-8 h-8 rounded bg-slate-700 flex items-center justify-center group-hover:bg-slate-600 text-yellow-400">
177
+ <i class="fa-solid fa-car-battery"></i>
178
+ </div>
179
+ <div>
180
+ <div class="text-sm font-medium text-slate-200">Nguồn điện</div>
181
+ <div class="text-[10px] text-slate-500">Cung cấp hiệu điện thế</div>
182
+ </div>
183
+ </div>
184
+
185
+ <div class="component-btn group cursor-pointer bg-slate-800 p-3 rounded border border-slate-700 flex items-center gap-3" onclick="app.setTool('bulb')">
186
+ <div class="w-8 h-8 rounded bg-slate-700 flex items-center justify-center group-hover:bg-slate-600 text-yellow-200">
187
+ <i class="fa-regular fa-lightbulb"></i>
188
+ </div>
189
+ <div>
190
+ <div class="text-sm font-medium text-slate-200">Bóng đèn</div>
191
+ <div class="text-[10px] text-slate-500">Tải tiêu thụ điện</div>
192
+ </div>
193
+ </div>
194
+
195
+ <div class="component-btn group cursor-pointer bg-slate-800 p-3 rounded border border-slate-700 flex items-center gap-3" onclick="app.setTool('switch')">
196
+ <div class="w-8 h-8 rounded bg-slate-700 flex items-center justify-center group-hover:bg-slate-600 text-green-400">
197
+ <i class="fa-solid fa-toggle-off"></i>
198
+ </div>
199
+ <div>
200
+ <div class="text-sm font-medium text-slate-200">Công tắc</div>
201
+ <div class="text-[10px] text-slate-500">Đóng/ngắt mạch</div>
202
+ </div>
203
+ </div>
204
+ </div>
205
+
206
+ <div class="p-4 border-t border-slate-800 text-[10px] text-slate-600 text-center">
207
+ Click để chọn công cụ<br>Double click linh kiện để xóa
208
+ </div>
209
+ </aside>
210
+
211
+ <!-- Center: Canvas -->
212
+ <main class="flex-1 relative bg-slate-950" id="canvas-wrapper">
213
+ <div id="canvas-container" class="w-full h-full relative">
214
+ <canvas id="circuitCanvas"></canvas>
215
+
216
+ <!-- Floating Info Overlay -->
217
+ <div class="absolute top-4 left-4 glass px-4 py-2 rounded text-xs text-slate-300 pointer-events-none select-none">
218
+ <div class="flex items-center gap-2 mb-1">
219
+ <div class="w-2 h-2 rounded-full bg-yellow-400"></div>
220
+ <span>Dòng điện chạy: + -> -</span>
221
+ </div>
222
+ <div class="flex items-center gap-2">
223
+ <div class="w-2 h-2 rounded-full bg-blue-400"></div>
224
+ <span>Electron chạy: - -> +</span>
225
+ </div>
226
+ </div>
227
+ </div>
228
+ </main>
229
+
230
+ <!-- Right Sidebar: Properties -->
231
+ <aside class="w-72 bg-slate-900 border-l border-slate-700 flex flex-col z-10 transition-transform duration-300" id="properties-panel">
232
+ <div class="p-4 border-b border-slate-800">
233
+ <h2 class="text-xs font-bold text-slate-500 uppercase tracking-wider">Thông số kỹ thuật</h2>
234
+ </div>
235
+
236
+ <div id="empty-state" class="flex-1 flex flex-col items-center justify-center text-slate-600 p-6 text-center">
237
+ <i class="fa-solid fa-arrow-pointer text-3xl mb-3 opacity-50"></i>
238
+ <p class="text-sm">Chọn một linh kiện trên sơ đồ để chỉnh sửa thông số</p>
239
+ </div>
240
+
241
+ <div id="properties-content" class="hidden flex-1 overflow-y-auto p-4 space-y-6">
242
+ <!-- Dynamic Content Injected Here -->
243
+ <div>
244
+ <label class="block text-xs font-medium text-slate-400 mb-2">Loại linh kiện</label>
245
+ <div id="prop-type" class="text-lg font-bold text-white capitalize">Resistor</div>
246
+ </div>
247
+
248
+ <div id="prop-control-resistance" class="space-y-2">
249
+ <div class="flex justify-between text-xs text-slate-400">
250
+ <label>Điện trở (R)</label>
251
+ <span id="val-resistance" class="mono text-blue-400">100 Ω</span>
252
+ </div>
253
+ <input type="range" min="10" max="1000" step="10" id="input-resistance" class="w-full">
254
+ </div>
255
+
256
+ <div id="prop-control-voltage" class="space-y-2">
257
+ <div class="flex justify-between text-xs text-slate-400">
258
+ <label>Hiệu điện thế (V)</label>
259
+ <span id="val-voltage" class="mono text-yellow-400">9 V</span>
260
+ </div>
261
+ <input type="range" min="1" max="24" step="1" id="input-voltage" class="w-full">
262
+ </div>
263
+
264
+ <div class="p-3 bg-slate-800/50 rounded border border-slate-700/50">
265
+ <h3 class="text-[10px] uppercase text-slate-500 font-bold mb-2">Phân tích mạch</h3>
266
+ <div class="space-y-2">
267
+ <div class="flex justify-between text-xs">
268
+ <span class="text-slate-400">Cường độ dòng điện (I):</span>
269
+ <span id="analysis-current" class="mono text-white">0.00 A</span>
270
+ </div>
271
+ <div class="flex justify-between text-xs">
272
+ <span class="text-slate-400">Công suất (P):</span>
273
+ <span id="analysis-power" class="mono text-white">0.00 W</span>
274
+ </div>
275
+ </div>
276
+ </div>
277
+
278
+ <div class="pt-4 border-t border-slate-800">
279
+ <button onclick="app.deleteSelected()" class="w-full py-2 bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/30 rounded text-xs transition-colors">
280
+ Xóa linh kiện này
281
+ </button>
282
+ </div>
283
+ </div>
284
+ </aside>
285
+ </div>
286
+
287
+ <script>
288
+ /**
289
+ * Circuit Simulator Logic
290
+ * Implements a simple grid-based circuit editor with visual simulation of current flow.
291
+ */
292
+ class CircuitApp {
293
+ constructor() {
294
+ this.canvas = document.getElementById('circuitCanvas');
295
+ this.ctx = this.canvas.getContext('2d');
296
+ this.container = document.getElementById('canvas-container');
297
+
298
+ // State
299
+ this.components = [];
300
+ this.wires = []; // Connections between components
301
+ this.selectedTool = 'wire'; // wire, resistor, battery, bulb, switch
302
+ this.isSimulating = false;
303
+ this.selectedComponent = null;
304
+ this.draggedComponent = null;
305
+ this.hoveredComponent = null;
306
+
307
+ // Interaction State
308
+ this.isDrawingWire = false;
309
+ this.wireStartNode = null;
310
+ this.mousePos = { x: 0, y: 0 };
311
+
312
+ // Animation
313
+ this.electrons = []; // Array of electron particles
314
+ this.animationFrame = null;
315
+
316
+ // Constants
317
+ this.gridSize = 20;
318
+ this.componentSize = 40;
319
+
320
+ this.init();
321
+ }
322
+
323
+ init() {
324
+ this.resize();
325
+ window.addEventListener('resize', () => this.resize());
326
+
327
+ // Mouse Events
328
+ this.canvas.addEventListener('mousedown', (e) => this.handleMouseDown(e));
329
+ this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));
330
+ this.canvas.addEventListener('mouseup', (e) => this.handleMouseUp(e));
331
+ this.canvas.addEventListener('dblclick', (e) => this.handleDoubleClick(e));
332
+
333
+ // UI Bindings
334
+ this.bindPropertiesPanel();
335
+
336
+ // Start Loop
337
+ this.loop();
338
+ }
339
+
340
+ resize() {
341
+ this.canvas.width = this.container.clientWidth;
342
+ this.canvas.height = this.container.clientHeight;
343
+ }
344
+
345
+ // --- Core Logic: Components & Wires ---
346
+
347
+ addComponent(type, x, y) {
348
+ // Snap to grid
349
+ const snapX = Math.round(x / this.gridSize) * this.gridSize;
350
+ const snapY = Math.round(y / this.gridSize) * this.gridSize;
351
+
352
+ const comp = {
353
+ id: Date.now(),
354
+ type: type,
355
+ x: snapX,
356
+ y: snapY,
357
+ rotation: 0, // 0, 1, 2, 3 (x90 deg)
358
+ value: type === 'resistor' ? 100 : (type === 'battery' ? 9 : 0),
359
+ state: type === 'switch' ? false : true, // false = open, true = closed
360
+ nodes: this.calculateNodes(snapX, snapY, 0)
361
+ };
362
+
363
+ this.components.push(comp);
364
+ this.selectComponent(comp);
365
+ }
366
+
367
+ calculateNodes(x, y, rotation) {
368
+ // Define connection points relative to center
369
+ // 0 deg: Left (-1,0) and Right (1,0)
370
+ const offset = this.componentSize / 2;
371
+ return [
372
+ { x: x - offset, y: y, id: `n-${x}-${y}-l` }, // Left
373
+ { x: x + offset, y: y, id: `n-${x}-${y}-r` } // Right
374
+ ];
375
+ }
376
+
377
+ getComponentAt(x, y) {
378
+ // Check wires first (line segment check)
379
+ for (let w of this.wires) {
380
+ const dist = this.pointToLineDistance(x, y, w.x1, w.y1, w.x2, w.y2);
381
+ if (dist < 5) return { type: 'wire', obj: w };
382
+ }
383
+
384
+ // Check components
385
+ const size = this.componentSize / 2 + 5; // tolerance
386
+ return this.components.find(c =>
387
+ x >= c.x - size && x <= c.x + size &&
388
+ y >= c.y - size && y <= c.y + size
389
+ );
390
+ }
391
+
392
+ pointToLineDistance(x, y, x1, y1, x2, y2) {
393
+ const A = x - x1;
394
+ const B = y - y1;
395
+ const C = x2 - x1;
396
+ const D = y2 - y1;
397
+
398
+ const dot = A * C + B * D;
399
+ const len_sq = C * C + D * D;
400
+ let param = -1;
401
+ if (len_sq !== 0) param = dot / len_sq;
402
+
403
+ let xx, yy;
404
+
405
+ if (param < 0) {
406
+ xx = x1; yy = y1;
407
+ } else if (param > 1) {
408
+ xx = x2; yy = y2;
409
+ } else {
410
+ xx = x1 + param * C;
411
+ yy = y1 + param * D;
412
+ }
413
+
414
+ const dx = x - xx;
415
+ const dy = y - yy;
416
+ return Math.sqrt(dx * dx + dy * dy);
417
+ }
418
+
419
+ // --- Interaction Handlers ---
420
+
421
+ handleMouseDown(e) {
422
+ const rect = this.canvas.getBoundingClientRect();
423
+ const x = e.clientX - rect.left;
424
+ const y = e.clientY - rect.top;
425
+
426
+ const clicked = this.getComponentAt(x, y);
427
+
428
+ if (this.selectedTool === 'wire') {
429
+ if (clicked && clicked.type !== 'wire') {
430
+ // Start wire from component node
431
+ const node = this.getClosestNode(clicked.obj, x, y);
432
+ this.isDrawingWire = true;
433
+ this.wireStartNode = { x: node.x, y: node.y, comp: clicked.obj, nodeIndex: clicked.obj.nodes.indexOf(node) };
434
+ } else if (!clicked) {
435
+ // Start wire from empty space (less common but supported)
436
+ this.isDrawingWire = true;
437
+ this.wireStartNode = { x: Math.round(x/10)*10, y: Math.round(y/10)*10, comp: null };
438
+ }
439
+ } else {
440
+ // Placing components or selecting
441
+ if (!clicked) {
442
+ if (this.selectedTool !== 'wire') {
443
+ this.addComponent(this.selectedTool, x, y);
444
+ } else {
445
+ this.selectComponent(null);
446
+ }
447
+ } else if (clicked.type !== 'wire') {
448
+ this.draggedComponent = clicked.obj;
449
+ this.selectComponent(clicked.obj);
450
+ }
451
+ }
452
+ }
453
+
454
+ handleMouseMove(e) {
455
+ const rect = this.canvas.getBoundingClientRect();
456
+ this.mousePos.x = e.clientX - rect.left;
457
+ this.mousePos.y = e.clientY - rect.top;
458
+
459
+ if (this.draggedComponent) {
460
+ const snapX = Math.round(this.mousePos.x / this.gridSize) * this.gridSize;
461
+ const snapY = Math.round(this.mousePos.y / this.gridSize) * this.gridSize;
462
+ this.draggedComponent.x = snapX;
463
+ this.draggedComponent.y = snapY;
464
+ this.draggedComponent.nodes = this.calculateNodes(snapX, snapY, 0);
465
+
466
+ // Update connected wires
467
+ this.wires.forEach(w => {
468
+ if (w.from.comp === this.draggedComponent) {
469
+ const node = this.draggedComponent.nodes[w.from.nodeIndex];
470
+ w.x1 = node.x; w.y1 = node.y;
471
+ }
472
+ if (w.to.comp === this.draggedComponent) {
473
+ const node = this.draggedComponent.nodes[w.to.nodeIndex];
474
+ w.x2 = node.x; w.y2 = node.y;
475
+ }
476
+ });
477
+ }
478
+
479
+ // Hover effect
480
+ const hovered = this.getComponentAt(this.mousePos.x, this.mousePos.y);
481
+ this.hoveredComponent = hovered ? hovered.obj : null;
482
+ }
483
+
484
+ handleMouseUp(e) {
485
+ if (this.isDrawingWire && this.wireStartNode) {
486
+ const rect = this.canvas.getBoundingClientRect();
487
+ const x = e.clientX - rect.left;
488
+ const y = e.clientY - rect.top;
489
+ const target = this.getComponentAt(x, y);
490
+
491
+ if (target && target.type !== 'wire' && target.obj !== this.wireStartNode.comp) {
492
+ // Connect to component
493
+ const node = this.getClosestNode(target.obj, x, y);
494
+ this.wires.push({
495
+ x1: this.wireStartNode.x, y1: this.wireStartNode.y,
496
+ x2: node.x, y2: node.y,
497
+ from: this.wireStartNode,
498
+ to: { x: node.x, y: node.y, comp: target.obj, nodeIndex: target.obj.nodes.indexOf(node) }
499
+ });
500
+ } else if (!target) {
501
+ // Connect to empty space (junction)
502
+ this.wires.push({
503
+ x1: this.wireStartNode.x, y1: this.wireStartNode.y,
504
+ x2: Math.round(x/10)*10, y2: Math.round(y/10)*10,
505
+ from: this.wireStartNode,
506
+ to: null // Floating end? No, let's just snap to grid for now
507
+ });
508
+ }
509
+ this.isDrawingWire = false;
510
+ this.wireStartNode = null;
511
+ }
512
+
513
+ this.draggedComponent = null;
514
+ }
515
+
516
+ handleDoubleClick(e) {
517
+ const rect = this.canvas.getBoundingClientRect();
518
+ const x = e.clientX - rect.left;
519
+ const y = e.clientY - rect.top;
520
+ const clicked = this.getComponentAt(x, y);
521
+
522
+ if (clicked) {
523
+ if (clicked.type === 'wire') {
524
+ this.wires = this.wires.filter(w => w !== clicked.obj);
525
+ } else {
526
+ // Toggle switch
527
+ if (clicked.obj.type === 'switch') {
528
+ clicked.obj.state = !clicked.obj.state;
529
+ }
530
+ // Delete others
531
+ else {
532
+ this.components = this.components.filter(c => c !== clicked.obj);
533
+ // Remove attached wires
534
+ this.wires = this.wires.filter(w => w.from.comp !== clicked.obj && w.to.comp !== clicked.obj);
535
+ if (this.selectedComponent === clicked.obj) this.selectComponent(null);
536
+ }
537
+ }
538
+ }
539
+ }
540
+
541
+ getClosestNode(comp, x, y) {
542
+ // Simple distance check to nodes
543
+ let closest = comp.nodes[0];
544
+ let minDist = Infinity;
545
+ comp.nodes.forEach(n => {
546
+ const d = Math.hypot(n.x - x, n.y - y);
547
+ if (d < minDist) {
548
+ minDist = d;
549
+ closest = n;
550
+ }
551
+ });
552
+ return closest;
553
+ }
554
+
555
+ // --- Simulation Logic ---
556
+
557
+ toggleSimulation() {
558
+ this.isSimulating = !this.isSimulating;
559
+ const btn = document.getElementById('sim-btn');
560
+ const txt = document.getElementById('sim-text');
561
+ const icon = btn.querySelector('i');
562
+
563
+ if (this.isSimulating) {
564
+ btn.classList.replace('bg-blue-600', 'bg-red-600');
565
+ btn.classList.replace('hover:bg-blue-500', 'hover:bg-red-500');
566
+ txt.innerText = "Dừng Mô Phỏng";
567
+ icon.classList.replace('fa-play', 'fa-stop');
568
+ this.startSimulation();
569
+ } else {
570
+ btn.classList.replace('bg-red-600', 'bg-blue-600');
571
+ btn.classList.replace('hover:bg-red-500', 'hover:bg-blue-500');
572
+ txt.innerText = "Chạy Mô Phỏng";
573
+ icon.classList.replace('fa-stop', 'fa-play');
574
+ this.electrons = [];
575
+ }
576
+ }
577
+
578
+ startSimulation() {
579
+ // 1. Find Battery
580
+ const battery = this.components.find(c => c.type === 'battery');
581
+ if (!battery) return;
582
+
583
+ // 2. Trace Path (Simplified: assume single loop for this demo)
584
+ // In a real app, we'd use MNA or Nodal Analysis.
585
+ // Here we find paths from battery + to battery -.
586
+
587
+ const path = this.findPath(battery, 0); // Start at node 0 (left)
588
+
589
+ if (path) {
590
+ this.currentPath = path;
591
+ // Calculate Total Resistance
592
+ let totalR = 0;
593
+ path.forEach(item => {
594
+ if (item.comp.type === 'resistor') totalR += item.comp.value;
595
+ if (item.comp.type === 'bulb') totalR += 50; // Fixed bulb resistance
596
+ if (item.comp.type === 'switch' && !item.comp.state) totalR += 999999; // Open switch
597
+ });
598
+
599
+ const current = battery.value / totalR;
600
+ this.circuitCurrent = current; // Amps
601
+
602
+ // Spawn electrons based on current
603
+ // We visualize electrons moving along the path
604
+ this.spawnElectrons(path, current);
605
+ } else {
606
+ this.circuitCurrent = 0;
607
+ }
608
+ }
609
+
610
+ findPath(startComp, startNodeIndex) {
611
+ // DFS to find loop back to battery
612
+ // This is a very basic pathfinder for visual demo purposes
613
+ // It assumes a simple series circuit layout.
614
+
615
+ let path = [];
616
+ let visited = new Set();
617
+
618
+ const traverse = (comp, entryNodeIdx) => {
619
+ if (visited.has(comp.id)) return false; // Loop detected or dead end
620
+ visited.add(comp.id);
621
+
622
+ // Determine exit node (the other node)
623
+ const exitNodeIdx = entryNodeIdx === 0 ? 1 : 0;
624
+
625
+ path.push({ comp: comp, entry: entryNodeIdx, exit: exitNodeIdx });
626
+
627
+ if (comp.type === 'battery' && path.length > 1) {
628
+ return true; // Completed loop
629
+ }
630
+
631
+ // Find wire connected to exit node
632
+ const exitNode = comp.nodes[exitNodeIdx];
633
+ const wire = this.wires.find(w =>
634
+ (w.x1 === exitNode.x && w.y1 === exitNode.y) ||
635
+ (w.x2 === exitNode.x && w.y2 === exitNode.y)
636
+ );
637
+
638
+ if (!wire) return false;
639
+
640
+ // Find next component connected to other end of wire
641
+ const otherEnd = (wire.x1 === exitNode.x && wire.y1 === exitNode.y) ?
642
+ { x: wire.x2, y: wire.y2 } : { x: wire.x1, y: wire.y1 };
643
+
644
+ // Find component at other end
645
+ // Check wires connected to this point
646
+ const nextWire = this.wires.find(w =>
647
+ (w.x1 === otherEnd.x && w.y1 === otherEnd.y && w !== wire) ||
648
+ (w.x2 === otherEnd.x && w.y2 === otherEnd.y && w !== wire)
649
+ );
650
+
651
+ // Find component connected to this wire
652
+ let nextComp = null;
653
+ let nextNodeIdx = -1;
654
+
655
+ // Check all components
656
+ for (let c of this.components) {
657
+ if (c === comp) continue;
658
+ c.nodes.forEach((n, idx) => {
659
+ if (n.x === otherEnd.x && n.y === otherEnd.y) {
660
+ nextComp = c;
661
+ nextNodeIdx = idx;
662
+ }
663
+ });
664
+ }
665
+
666
+ if (nextComp) {
667
+ return traverse(nextComp, nextNodeIdx);
668
+ }
669
+
670
+ return false;
671
+ };
672
+
673
+ const found = traverse(startComp, startNodeIndex);
674
+ return found ? path : null;
675
+ }
676
+
677
+ spawnElectrons(path, current) {
678
+ this.electrons = [];
679
+ // Create segments from path
680
+ this.pathSegments = [];
681
+
682
+ for (let i = 0; i < path.length; i++) {
683
+ const item = path[i];
684
+ const nextItem = path[(i + 1) % path.length];
685
+
686
+ // Segment 1: Inside component (Entry to Exit)
687
+ // Segment 2: Wire (Exit to Next Entry)
688
+
689
+ // For visual simplicity, we treat the whole path as a continuous line
690
+ // We need coordinates.
691
+
692
+ const startNode = item.comp.nodes[item.entry];
693
+ const endNode = item.comp.nodes[item.exit];
694
+
695
+ // Add wire to next component
696
+ const wire = this.wires.find(w =>
697
+ (w.x1 === endNode.x && w.y1 === endNode.y) ||
698
+ (w.x2 === endNode.x && w.y2 === endNode.y)
699
+ );
700
+
701
+ if (wire) {
702
+ const wireStart = (wire.x1 === endNode.x && wire.y1 === endNode.y) ?
703
+ {x: wire.x1, y: wire.y1} : {x: wire.x2, y: wire.y2};
704
+ const wireEnd = (wire.x1 === endNode.x && wire.y1 === endNode.y) ?
705
+ {x: wire.x2, y: wire.y2} : {x: wire.x1, y: wire.y1};
706
+
707
+ this.pathSegments.push({
708
+ from: {x: startNode.x, y: startNode.y},
709
+ to: {x: endNode.x, y: endNode.y},
710
+ type: 'comp',
711
+ comp: item.comp
712
+ });
713
+
714
+ this.pathSegments.push({
715
+ from: {x: wireStart.x, y: wireStart.y},
716
+ to: {x: wireEnd.x, y: wireEnd.y},
717
+ type: 'wire'
718
+ });
719
+ }
720
+ }
721
+
722
+ // Spawn initial electrons
723
+ const speed = Math.min(Math.max(current * 2, 0.5), 5); // Visual speed cap
724
+ for(let i=0; i<20; i++) {
725
+ this.electrons.push({
726
+ segmentIndex: 0,
727
+ progress: i / 20, // 0 to 1 along segment
728
+ speed: speed
729
+ });
730
+ }
731
+ }
732
+
733
+ updateElectrons() {
734
+ if (!this.isSimulating || !this.pathSegments) return;
735
+
736
+ // Check if circuit is broken (switch open)
737
+ const broken = this.currentPath && this.currentPath.some(p => p.comp.type === 'switch' && !p.comp.state);
738
+ if (broken) {
739
+ this.electrons = []; // Stop electrons
740
+ return;
741
+ }
742
+
743
+ this.electrons.forEach(e => {
744
+ e.progress += 0.005 * e.speed;
745
+ if (e.progress >= 1) {
746
+ e.progress = 0;
747
+ e.segmentIndex = (e.segmentIndex + 1) % this.pathSegments.length;
748
+ }
749
+ });
750
+ }
751
+
752
+ // --- Rendering ---
753
+
754
+ loop() {
755
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
756
+
757
+ this.drawGrid();
758
+ this.drawWires();
759
+ this.drawComponents();
760
+ this.drawUIOverlay();
761
+
762
+ if (this.isSimulating) {
763
+ this.updateElectrons();
764
+ this.drawElectrons();
765
+ }
766
+
767
+ requestAnimationFrame(() => this.loop());
768
+ }
769
+
770
+ drawGrid() {
771
+ this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)';
772
+ this.ctx.lineWidth = 1;
773
+ this.ctx.beginPath();
774
+ for (let x = 0; x < this.canvas.width; x += this.gridSize) {
775
+ this.ctx.moveTo(x, 0);
776
+ this.ctx.lineTo(x, this.canvas.height);
777
+ }
778
+ for (let y = 0; y < this.canvas.height; y += this.gridSize) {
779
+ this.ctx.moveTo(0, y);
780
+ this.ctx.lineTo(this.canvas.width, y);
781
+ }
782
+ this.ctx.stroke();
783
+ }
784
+
785
+ drawWires() {
786
+ this.ctx.lineCap = 'round';
787
+ this.ctx.lineJoin = 'round';
788
+
789
+ this.wires.forEach(w => {
790
+ this.ctx.beginPath();
791
+ this.ctx.moveTo(w.x1, w.y1);
792
+ this.ctx.lineTo(w.x2, w.y2);
793
+
794
+ // Style
795
+ if (this.hoveredComponent === w) {
796
+ this.ctx.strokeStyle = '#38bdf8';
797
+ this.ctx.lineWidth = 4;
798
+ this.ctx.shadowBlur = 10;
799
+ this.ctx.shadowColor = '#38bdf8';
800
+ } else {
801
+ this.ctx.strokeStyle = '#64748b';
802
+ this.ctx.lineWidth = 3;
803
+ this.ctx.shadowBlur = 0;
804
+ }
805
+
806
+ this.ctx.stroke();
807
+ this.ctx.shadowBlur = 0; // Reset
808
+
809
+ // Draw junction dots
810
+ this.drawJunction(w.x1, w.y1);
811
+ this.drawJunction(w.x2, w.y2);
812
+ });
813
+
814
+ // Drawing line while dragging
815
+ if (this.isDrawingWire && this.wireStartNode) {
816
+ this.ctx.beginPath();
817
+ this.ctx.moveTo(this.wireStartNode.x, this.wireStartNode.y);
818
+ // Orthogonal routing hint
819
+ const midX = (this.wireStartNode.x + this.mousePos.x) / 2;
820
+ this.ctx.lineTo(midX, this.wireStartNode.y);
821
+ this.ctx.lineTo(midX, this.mousePos.y);
822
+ this.ctx.lineTo(this.mousePos.x, this.mousePos.y);
823
+
824
+ this.ctx.strokeStyle = '#38bdf8';
825
+ this.ctx.lineWidth = 2;
826
+ this.ctx.setLineDash([5, 5]);
827
+ this.ctx.stroke();
828
+ this.ctx.setLineDash([]);
829
+ }
830
+ }
831
+
832
+ drawJunction(x, y) {
833
+ this.ctx.fillStyle = '#94a3b8';
834
+ this.ctx.beginPath();
835
+ this.ctx.arc(x, y, 3, 0, Math.PI * 2);
836
+ this.ctx.fill();
837
+ }
838
+
839
+ drawComponents() {
840
+ this.components.forEach(c => {
841
+ this.ctx.save();
842
+ this.ctx.translate(c.x, c.y);
843
+
844
+ // Highlight selected
845
+ if (this.selectedComponent === c) {
846
+ this.ctx.shadowColor = '#38bdf8';
847
+ this.ctx.shadowBlur = 15;
848
+ } else if (this.hoveredComponent === c) {
849
+ this.ctx.shadowColor = 'rgba(255,255,255,0.3)';
850
+ this.ctx.shadowBlur = 10;
851
+ }
852
+
853
+ // Draw Component Body
854
+ this.ctx.fillStyle = '#1e293b';
855
+ this.ctx.strokeStyle = this.selectedComponent === c ? '#38bdf8' : '#94a3b8';
856
+ this.ctx.lineWidth = 2;
857
+
858
+ // Rotation
859
+ // this.ctx.rotate(c.rotation * Math.PI / 2);
860
+
861
+ // Draw based on type
862
+ const s = this.componentSize / 2; // half size
863
+
864
+ // Connection points (visual only, logic handled by nodes)
865
+ this.ctx.fillStyle = '#94a3b8';
866
+ this.ctx.beginPath(); this.ctx.arc(-s, 0, 4, 0, Math.PI*2); this.ctx.fill();
867
+ this.ctx.beginPath(); this.ctx.arc(s, 0, 4, 0, Math.PI*2); this.ctx.fill();
868
+
869
+ this.ctx.beginPath();
870
+
871
+ if (c.type === 'resistor') {
872
+ // Zigzag
873
+ this.ctx.moveTo(-s, 0);
874
+ this.ctx.lineTo(-s + 5, 0);
875
+ this.ctx.lineTo(-s + 10, -8);
876
+ this.ctx.lineTo(-s + 20, 8);
877
+ this.ctx.lineTo(-s + 30, -8);
878
+ this.ctx.lineTo(-s + 40, 8);
879
+ this.ctx.lineTo(s - 5, 0);
880
+ this.ctx.lineTo(s, 0);
881
+ this.ctx.stroke();
882
+
883
+ // Text
884
+ this.ctx.fillStyle = '#fff';
885
+ this.ctx.font = '10px Inter';
886
+ this.ctx.textAlign = 'center';
887
+ this.ctx.fillText(`${c.value}Ω`, 0, -12);
888
+
889
+ } else if (c.type === 'battery') {
890
+ // Long and short line
891
+ // Left (Neg)
892
+ this.ctx.moveTo(-s, 0); this.ctx.lineTo(-10, 0);
893
+ this.ctx.moveTo(-10, -15); this.ctx.lineTo(-10, 15); // Long
894
+ // Right (Pos)
895
+ this.ctx.moveTo(10, -8); this.ctx.lineTo(10, 8); // Short
896
+ this.ctx.moveTo(10, 0); this.ctx.lineTo(s, 0);
897
+ this.ctx.stroke();
898
+
899
+ // Signs
900
+ this.ctx.fillStyle = '#38bdf8';
901
+ this.ctx.font = 'bold 14px sans-serif';
902
+ this.ctx.fillText('-', -18, 4);
903
+ this.ctx.fillStyle = '#ef4444';
904
+ this.ctx.fillText('+', 18, 4);
905
+
906
+ this.ctx.fillStyle = '#fbbf24';
907
+ this.ctx.font = '10px Inter';
908
+ this.ctx.fillText(`${c.value}V`, 0, 25);
909
+
910
+ } else if (c.type === 'bulb') {
911
+ // Circle
912
+ this.ctx.beginPath();
913
+ this.ctx.arc(0, 0, 15, 0, Math.PI * 2);
914
+ this.ctx.fillStyle = '#334155';
915
+ this.ctx.fill();
916
+ this.ctx.stroke();
917
+
918
+ // Cross
919
+ this.ctx.beginPath();
920
+ this.ctx.moveTo(-8, -8); this.ctx.lineTo(8, 8);
921
+ this.ctx.moveTo(8, -8); this.ctx.lineTo(-8, 8);
922
+ this.ctx.stroke();
923
+
924
+ // Glow if simulating and current flowing
925
+ if (this.isSimulating && this.circuitCurrent > 0) {
926
+ const alpha = Math.min(this.circuitCurrent / 0.5, 1);
927
+ this.ctx.shadowColor = `rgba(250, 204, 21, ${alpha})`;
928
+ this.ctx.shadowBlur = 20 + (alpha * 20);
929
+ this.ctx.fillStyle = `rgba(250, 204, 21, ${alpha * 0.5})`;
930
+ this.ctx.beginPath();
931
+ this.ctx.arc(0, 0, 12, 0, Math.PI*2);
932
+ this.ctx.fill();
933
+ }
934
+
935
+ } else if (c.type === 'switch') {
936
+ // Line with break
937
+ this.ctx.moveTo(-s, 0); this.ctx.lineTo(-10, 0);
938
+ this.ctx.moveTo(10, 0); this.ctx.lineTo(s, 0);
939
+ this.ctx.stroke();
940
+
941
+ // Lever
942
+ this.ctx.beginPath();
943
+ if (c.state) { // Closed
944
+ this.ctx.moveTo(-10, 0);
945
+ this.ctx.lineTo(10, 0);
946
+ this.ctx.strokeStyle = '#4ade80';
947
+ } else { // Open
948
+ this.ctx.moveTo(-10, 0);
949
+ this.ctx.lineTo(8, -15);
950
+ }
951
+ this.ctx.stroke();
952
+
953
+ // Dots
954
+ this.ctx.fillStyle = c.state ? '#4ade80' : '#94a3b8';
955
+ this.ctx.beginPath(); this.ctx.arc(-10, 0, 3, 0, Math.PI*2); this.ctx.fill();
956
+ this.ctx.beginPath(); this.ctx.arc(10, 0, 3, 0, Math.PI*2); this.ctx.fill();
957
+ }
958
+
959
+ this.ctx.restore();
960
+ });
961
+ }
962
+
963
+ drawElectrons() {
964
+ if (!this.pathSegments) return;
965
+
966
+ this.ctx.fillStyle = '#facc15'; // Electron yellow
967
+ this.ctx.shadowColor = '#facc15';
968
+ this.ctx.shadowBlur = 5;
969
+
970
+ this.electrons.forEach(e => {
971
+ const seg = this.pathSegments[e.segmentIndex];
972
+ if (!seg) return;
973
+
974
+ const x = seg.from.x + (seg.to.x - seg.from.x) * e.progress;
975
+ const y = seg.from.y + (seg.to.y - seg.from.y) * e.progress;
976
+
977
+ this.ctx.beginPath();
978
+ this.ctx.arc(x, y, 3, 0, Math.PI * 2);
979
+ this.ctx.fill();
980
+ });
981
+
982
+ this.ctx.shadowBlur = 0;
983
+ }
984
+
985
+ drawUIOverlay() {
986
+ // Tooltip for hover
987
+ if (this.hoveredComponent && typeof this.hoveredComponent === 'object') {
988
+ // Handled by cursor mostly, but could add tooltips here
989
+ }
990
+ }
991
+
992
+ // --- UI Logic ---
993
+
994
+ setTool(tool) {
995
+ this.selectedTool = tool;
996
+ // Update UI classes
997
+ document.querySelectorAll('.component-btn').forEach(btn => btn.classList.remove('active'));
998
+ // Find button with onclick containing tool name (simple hack)
999
+ const btns = document.querySelectorAll('.component-btn');
1000
+ if(tool === 'wire') btns[0].classList.add('active');
1001
+ if(tool === 'resistor') btns[1].classList.add('active');
1002
+ if(tool === 'battery') btns[2].classList.add('active');
1003
+ if(tool === 'bulb') btns[3].classList.add('active');
1004
+ if(tool === 'switch') btns[4].classList.add('active');
1005
+ }
1006
+
1007
+ selectComponent(comp) {
1008
+ this.selectedComponent = comp;
1009
+ const panel = document.getElementById('properties-panel');
1010
+ const empty = document.getElementById('empty-state');
1011
+ const content = document.getElementById('properties-content');
1012
+
1013
+ if (!comp) {
1014
+ empty.classList.remove('hidden');
1015
+ content.classList.add('hidden');
1016
+ return;
1017
+ }
1018
+
1019
+ empty.classList.add('hidden');
1020
+ content.classList.remove('hidden');
1021
+
1022
+ // Populate fields
1023
+ document.getElementById('prop-type').innerText = comp.type;
1024
+
1025
+ // Show/Hide relevant controls
1026
+ const rControl = document.getElementById('prop-control-resistance');
1027
+ const vControl = document.getElementById('prop-control-voltage');
1028
+
1029
+ if (comp.type === 'resistor') {
1030
+ rControl.classList.remove('hidden');
1031
+ vControl.classList.add('hidden');
1032
+ document.getElementById('input-resistance').value = comp.value;
1033
+ document.getElementById('val-resistance').innerText = comp.value + ' Ω';
1034
+ } else if (comp.type === 'battery') {
1035
+ rControl.classList.add('hidden');
1036
+ vControl.classList.remove('hidden');
1037
+ document.getElementById('input-voltage').value = comp.value;
1038
+ document.getElementById('val-voltage').innerText = comp.value + ' V';
1039
+ } else {