@clawlabz/clawskin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,527 @@
1
+ /**
2
+ * HackerScene.js — Dark hacker den with dual monitors, code rain, neon glow
3
+ * Architecture matches OfficeScene: getWorkstations, renderChair, renderDesk
4
+ *
5
+ * Render order (back to front):
6
+ * 1. Background wall + code rain + neon pulse + decorations
7
+ * 2. Per agent: gaming chair → character → dark desk (hides legs) → monitors + energy drink
8
+ * 3. Name tags + bubbles on top
9
+ */
10
+ class HackerScene {
11
+ constructor(canvas, ctx, spriteGen) {
12
+ this.canvas = canvas;
13
+ this.ctx = ctx;
14
+ this.gen = spriteGen;
15
+ this.bgCanvas = null;
16
+ this.codeRainDrops = [];
17
+ this.neonPulse = 0;
18
+ this.screenGlitch = 0;
19
+ this.name = 'hacker';
20
+ this.label = '💻 Hacker Den';
21
+ this.workstations = [];
22
+ this.poiList = [];
23
+ }
24
+
25
+ init() {
26
+ this.bgCanvas = this.gen.generateSceneBg('hacker', this.canvas.width, this.canvas.height);
27
+ this.codeRainDrops = [];
28
+ for (let i = 0; i < 40; i++) {
29
+ this.codeRainDrops.push({
30
+ x: Math.random() * this.canvas.width,
31
+ y: Math.random() * this.canvas.height,
32
+ speed: 0.5 + Math.random() * 2,
33
+ char: String.fromCharCode(0x30A0 + Math.floor(Math.random() * 96)),
34
+ opacity: 0.1 + Math.random() * 0.3,
35
+ size: 8 + Math.floor(Math.random() * 4),
36
+ });
37
+ }
38
+ }
39
+
40
+ getCharacterPosition() {
41
+ return { x: this.canvas.width / 2 - 48, y: this.canvas.height * 0.38 };
42
+ }
43
+
44
+ /**
45
+ * Calculate workstation layout for N agents.
46
+ * Hacker desks: wide dark desks with dual monitors.
47
+ */
48
+ getWorkstations(count) {
49
+ const w = this.canvas.width;
50
+ const h = this.canvas.height;
51
+ const floorY = Math.round(h * 0.40);
52
+ const stations = [];
53
+
54
+ const cs = 2.5;
55
+ const charH = Math.round(32 * cs);
56
+ const charW = Math.round(32 * cs);
57
+
58
+ const deskW = 130, deskH = 42;
59
+ const monW = 44, monH = 44;
60
+ const chairW = 36, chairH = 40;
61
+ const cupW = 16, cupH = 20;
62
+
63
+ const deskSurfaceY = floorY + 50;
64
+
65
+ if (count <= 0) return stations;
66
+
67
+ const positions = [];
68
+ if (count === 1) {
69
+ positions.push(w / 2);
70
+ } else if (count <= 4) {
71
+ for (let i = 0; i < count; i++) {
72
+ positions.push((w / (count + 1)) * (i + 1));
73
+ }
74
+ } else {
75
+ const topN = Math.ceil(count / 2);
76
+ const botN = count - topN;
77
+ for (let i = 0; i < topN; i++) positions.push((w / (topN + 1)) * (i + 1));
78
+ for (let i = 0; i < botN; i++) positions.push((w / (botN + 1)) * (i + 1));
79
+ }
80
+
81
+ for (let i = 0; i < count; i++) {
82
+ const cx = Math.round(positions[i]);
83
+ const isBackRow = count > 4 && i >= Math.ceil(count / 2);
84
+ const rowDeskY = isBackRow ? deskSurfaceY + 70 : deskSurfaceY;
85
+
86
+ const charY = rowDeskY - charH + 10;
87
+ const deskY = rowDeskY;
88
+
89
+ stations.push({
90
+ charX: cx - charW / 2,
91
+ charY,
92
+ charScale: cs,
93
+
94
+ chairX: cx - chairW / 2,
95
+ chairY: charY + charH * 0.3,
96
+ chairW, chairH,
97
+
98
+ deskX: cx - deskW / 2,
99
+ deskY,
100
+ deskW, deskH,
101
+
102
+ monX: cx - deskW / 2 + 6,
103
+ monY: deskY - monH + 10,
104
+ monW, monH,
105
+
106
+ cupX: cx + deskW / 2 - cupW - 12,
107
+ cupY: deskY - cupH + 8,
108
+ cupW, cupH,
109
+
110
+ cx, cy: rowDeskY,
111
+ index: i,
112
+ });
113
+ }
114
+
115
+ // Points of interest for agent wandering
116
+ const floorH = h - floorY;
117
+ this.poiList = [
118
+ { x: 80, y: floorY + 20, label: 'server_rack' },
119
+ { x: w - 90, y: floorY + 20, label: 'server_rack' },
120
+ { x: w / 2, y: 30, label: 'led_wall' },
121
+ { x: 80, y: floorY + floorH * 0.55, label: 'bean_bag' },
122
+ { x: w - 90, y: floorY + floorH * 0.45, label: 'mini_fridge' },
123
+ { x: w / 2, y: floorY + floorH * 0.70, label: 'corner' },
124
+ { x: 90, y: floorY + floorH * 0.30, label: 'cable_corner' },
125
+ { x: w - 90, y: floorY + floorH * 0.68, label: 'arcade' },
126
+ ];
127
+
128
+ // Collision obstacles
129
+ this.obstacles = [
130
+ ...stations.map(s => ({ x: s.deskX - 5, y: s.deskY - 5, w: s.deskW + 10, h: s.deskH + 10, deskIndex: s.index })),
131
+ { x: 0, y: floorY + 5, w: 30, h: 90 }, // left server rack
132
+ { x: 0, y: floorY + floorH * 0.46, w: 45, h: 45 }, // left bean bag
133
+ { x: w-28, y: floorY + 5, w: 28, h: 55 }, // right fridge
134
+ { x: w-30, y: floorY + floorH * 0.30, w: 30, h: 70 }, // right arcade
135
+ ];
136
+
137
+ this.workstations = stations;
138
+ return stations;
139
+ }
140
+
141
+ update(dt) {
142
+ this.neonPulse += dt * 0.003;
143
+ this.screenGlitch += dt;
144
+ this.codeRainDrops.forEach(d => {
145
+ d.y += d.speed;
146
+ if (d.y > this.canvas.height) {
147
+ d.y = -10;
148
+ d.x = Math.random() * this.canvas.width;
149
+ d.char = String.fromCharCode(0x30A0 + Math.floor(Math.random() * 96));
150
+ }
151
+ });
152
+ }
153
+
154
+ /** Render background + static decorations */
155
+ render(ctx) {
156
+ const w = this.canvas.width;
157
+ const h = this.canvas.height;
158
+
159
+ if (this.bgCanvas) ctx.drawImage(this.bgCanvas, 0, 0);
160
+
161
+ // ── Code rain (behind everything) ──
162
+ ctx.save();
163
+ ctx.globalAlpha = 0.5;
164
+ this.codeRainDrops.forEach(d => {
165
+ ctx.fillStyle = `rgba(0, 255, 65, ${d.opacity})`;
166
+ ctx.font = `${d.size}px monospace`;
167
+ ctx.fillText(d.char, d.x, d.y);
168
+ });
169
+ ctx.restore();
170
+
171
+ // ── Neon ambient pulse ──
172
+ const pulse = Math.sin(this.neonPulse) * 0.03 + 0.05;
173
+ ctx.fillStyle = `rgba(155, 89, 182, ${pulse})`;
174
+ ctx.fillRect(0, 0, w, h);
175
+
176
+ const floorY = Math.round(h * 0.40);
177
+ const floorH = h - floorY;
178
+ const wallSafe = Math.round(h * 0.12);
179
+
180
+ // ── Wall decorations ──
181
+ this._drawHexDisplay(ctx, 25, wallSafe, 65, 38);
182
+ this._drawNeonSign(ctx, w / 2 - 30, wallSafe - 5);
183
+ this._drawLEDPanel(ctx, w - 95, wallSafe, 60, 35);
184
+ this._drawClock(ctx, w - 30, wallSafe + 8);
185
+
186
+ // ── LEFT WALL: server rack + bean bag ──
187
+ this._drawSideServerRack(ctx, 0, floorY + 8, 25, 88, 'left');
188
+ this._drawSideBeanBag(ctx, 0, floorY + floorH * 0.48, 40, 38, 'left');
189
+
190
+ // ── RIGHT WALL: mini fridge + arcade cabinet ──
191
+ this._drawSideMiniFridge(ctx, w - 22, floorY + 5, 22, 52, 'right');
192
+ this._drawSideArcade(ctx, w - 28, floorY + floorH * 0.30, 28, 68, 'right');
193
+ }
194
+
195
+ /** Render gaming chair (behind character) */
196
+ renderChair(ctx, s) {
197
+ const x = s.chairX, y = s.chairY, cw = s.chairW, ch = s.chairH;
198
+ // Gaming chair — black with cyan accent stripe
199
+ // Seat base
200
+ ctx.fillStyle = '#1a1a2e';
201
+ ctx.fillRect(x + 2, y + ch * 0.35, cw - 4, ch * 0.45);
202
+ // Back rest (tall)
203
+ ctx.fillStyle = '#111128';
204
+ ctx.fillRect(x + 4, y, cw - 8, ch * 0.55);
205
+ // Top of back rest — rounded
206
+ ctx.fillStyle = '#111128';
207
+ ctx.fillRect(x + 6, y - 4, cw - 12, 6);
208
+ // Cyan racing stripe
209
+ ctx.fillStyle = '#00e5ff';
210
+ ctx.fillRect(x + cw / 2 - 2, y + 2, 4, ch * 0.50);
211
+ ctx.globalAlpha = 0.3;
212
+ ctx.fillStyle = '#00e5ff';
213
+ ctx.fillRect(x + cw / 2 - 4, y + 2, 8, ch * 0.50);
214
+ ctx.globalAlpha = 1;
215
+ // Arm rests
216
+ ctx.fillStyle = '#222240';
217
+ ctx.fillRect(x, y + ch * 0.35, 4, ch * 0.25);
218
+ ctx.fillRect(x + cw - 4, y + ch * 0.35, 4, ch * 0.25);
219
+ // Wheel base
220
+ ctx.fillStyle = '#333';
221
+ ctx.fillRect(x + 4, y + ch - 4, cw - 8, 4);
222
+ // Wheels
223
+ ctx.fillStyle = '#444';
224
+ ctx.fillRect(x + 2, y + ch - 2, 4, 3);
225
+ ctx.fillRect(x + cw - 6, y + ch - 2, 4, 3);
226
+ ctx.fillRect(x + cw / 2 - 2, y + ch - 1, 4, 2);
227
+ }
228
+
229
+ /** Render dark desk + dual monitors + RGB keyboard + energy drink */
230
+ renderDesk(ctx, s, agentState) {
231
+ const isWorking = ['typing', 'executing', 'browsing'].includes(agentState);
232
+
233
+ // ── Dark desk ──
234
+ // Top surface (carbon fiber look)
235
+ ctx.fillStyle = '#1a1a2e';
236
+ ctx.fillRect(s.deskX, s.deskY, s.deskW, 8);
237
+ // Highlight strip on top edge
238
+ ctx.fillStyle = '#252545';
239
+ ctx.fillRect(s.deskX, s.deskY, s.deskW, 2);
240
+ // Front face
241
+ ctx.fillStyle = '#111125';
242
+ ctx.fillRect(s.deskX, s.deskY + 8, s.deskW, s.deskH - 8);
243
+ // Side edges
244
+ ctx.fillStyle = '#0d0d1e';
245
+ ctx.fillRect(s.deskX, s.deskY + 8, 2, s.deskH - 8);
246
+ ctx.fillRect(s.deskX + s.deskW - 2, s.deskY + 8, 2, s.deskH - 8);
247
+ // Bottom edge
248
+ ctx.fillStyle = '#0a0a18';
249
+ ctx.fillRect(s.deskX + 2, s.deskY + s.deskH - 2, s.deskW - 4, 2);
250
+ // Legs
251
+ ctx.fillStyle = '#0d0d1e';
252
+ ctx.fillRect(s.deskX + 6, s.deskY + s.deskH, 4, 4);
253
+ ctx.fillRect(s.deskX + s.deskW - 10, s.deskY + s.deskH, 4, 4);
254
+
255
+ // ── Neon underglow ──
256
+ const neonAlpha = 0.35 + Math.sin(this.neonPulse) * 0.2;
257
+ ctx.fillStyle = `rgba(0, 229, 255, ${neonAlpha})`;
258
+ ctx.fillRect(s.deskX + 4, s.deskY + s.deskH - 1, s.deskW - 8, 1);
259
+ // Underglow reflection on floor
260
+ ctx.fillStyle = `rgba(0, 229, 255, ${neonAlpha * 0.15})`;
261
+ ctx.fillRect(s.deskX + 10, s.deskY + s.deskH + 2, s.deskW - 20, 6);
262
+
263
+ // ── Dual monitors ──
264
+ const monType = isWorking && Math.floor(this.screenGlitch / 400) % 2 === 0 ? 'laptop_active' : 'laptop';
265
+ // Left monitor
266
+ ctx.drawImage(this.gen.generateFurniture(monType), s.monX, s.monY, s.monW, s.monH);
267
+ // Right monitor
268
+ const mon2X = s.monX + 50;
269
+ ctx.drawImage(this.gen.generateFurniture(monType), mon2X, s.monY, s.monW, s.monH);
270
+
271
+ // Screen glow when working
272
+ if (isWorking) {
273
+ ctx.fillStyle = 'rgba(0,255,100,0.04)';
274
+ ctx.fillRect(s.monX - 6, s.monY - 3, s.monW + 60, s.monH + 6);
275
+ }
276
+
277
+ // ── RGB keyboard between monitors ──
278
+ const kbX = s.deskX + s.deskW / 2 - 22;
279
+ const kbY = s.deskY - 8;
280
+ ctx.fillStyle = '#111';
281
+ ctx.fillRect(kbX, kbY, 44, 10);
282
+ ctx.fillStyle = '#1a1a1a';
283
+ ctx.fillRect(kbX + 1, kbY + 1, 42, 8);
284
+ // Rainbow key LEDs
285
+ for (let k = 0; k < 9; k++) {
286
+ const hue = (this.neonPulse * 50 + k * 40) % 360;
287
+ ctx.fillStyle = `hsl(${hue}, 80%, 50%)`;
288
+ ctx.fillRect(kbX + 3 + k * 4.5, kbY + 3, 3, 2);
289
+ ctx.fillRect(kbX + 3 + k * 4.5, kbY + 6, 3, 2);
290
+ }
291
+
292
+ // ── Energy drink (instead of coffee) ──
293
+ const edX = s.cupX, edY = s.cupY;
294
+ // Can body
295
+ ctx.fillStyle = '#111';
296
+ ctx.fillRect(edX, edY, s.cupW - 2, s.cupH);
297
+ ctx.fillStyle = '#00e676';
298
+ ctx.fillRect(edX + 1, edY + 3, s.cupW - 4, s.cupH - 6);
299
+ // Label
300
+ ctx.fillStyle = '#004d40';
301
+ ctx.fillRect(edX + 2, edY + 5, s.cupW - 6, 4);
302
+ // Lightning bolt
303
+ ctx.fillStyle = '#ffea00';
304
+ ctx.fillRect(edX + 5, edY + 4, 2, 3);
305
+ ctx.fillRect(edX + 4, edY + 7, 2, 3);
306
+ ctx.fillRect(edX + 6, edY + 6, 2, 2);
307
+ // Can top
308
+ ctx.fillStyle = '#333';
309
+ ctx.fillRect(edX + 1, edY, s.cupW - 4, 3);
310
+ ctx.fillStyle = '#555';
311
+ ctx.fillRect(edX + 3, edY + 1, 4, 1);
312
+ }
313
+
314
+ // ── Wall decorations ──
315
+
316
+ _drawHexDisplay(ctx, x, y, w, h) {
317
+ // Dark monitor showing hex code on wall
318
+ ctx.fillStyle = '#111';
319
+ ctx.fillRect(x - 2, y - 2, w + 4, h + 4);
320
+ ctx.fillStyle = '#0a0a1a';
321
+ ctx.fillRect(x, y, w, h);
322
+ // Hex lines
323
+ ctx.font = '5px monospace';
324
+ const colors = ['#0f0', '#0a0', '#080', '#0d0'];
325
+ for (let line = 0; line < 6; line++) {
326
+ ctx.fillStyle = colors[line % colors.length];
327
+ let txt = '';
328
+ for (let c = 0; c < 8; c++) {
329
+ txt += Math.floor(Math.random() * 16).toString(16).toUpperCase();
330
+ }
331
+ ctx.fillText(txt, x + 3, y + 7 + line * 5);
332
+ }
333
+ // Scanline
334
+ const scanY = y + ((this.screenGlitch * 0.02) % h);
335
+ ctx.fillStyle = 'rgba(0,255,0,0.08)';
336
+ ctx.fillRect(x, scanY, w, 2);
337
+ }
338
+
339
+ _drawNeonSign(ctx, x, y) {
340
+ // Glowing neon "< / >" sign
341
+ const glow = 0.6 + Math.sin(this.neonPulse * 2) * 0.3;
342
+ ctx.save();
343
+ ctx.globalAlpha = glow;
344
+ ctx.font = 'bold 14px monospace';
345
+ ctx.fillStyle = '#ff00ff';
346
+ ctx.fillText('< / >', x, y + 14);
347
+ // Glow effect
348
+ ctx.shadowColor = '#ff00ff';
349
+ ctx.shadowBlur = 8;
350
+ ctx.fillText('< / >', x, y + 14);
351
+ ctx.restore();
352
+ }
353
+
354
+ _drawLEDPanel(ctx, x, y, w, h) {
355
+ // LED dot matrix panel on wall
356
+ ctx.fillStyle = '#0a0a0a';
357
+ ctx.fillRect(x - 1, y - 1, w + 2, h + 2);
358
+ ctx.fillStyle = '#050510';
359
+ ctx.fillRect(x, y, w, h);
360
+ // LED dots — scrolling pattern
361
+ const dotSize = 3;
362
+ const cols = Math.floor(w / (dotSize + 1));
363
+ const rows = Math.floor(h / (dotSize + 1));
364
+ for (let r = 0; r < rows; r++) {
365
+ for (let c = 0; c < cols; c++) {
366
+ const offset = (this.screenGlitch * 0.01 + c * 0.3 + r * 0.2) % 6;
367
+ const on = Math.sin(offset) > 0.3;
368
+ if (on) {
369
+ const hue = (c * 20 + r * 30 + this.neonPulse * 30) % 360;
370
+ ctx.fillStyle = `hsla(${hue}, 90%, 55%, 0.7)`;
371
+ } else {
372
+ ctx.fillStyle = 'rgba(50,50,80,0.3)';
373
+ }
374
+ ctx.fillRect(x + 2 + c * (dotSize + 1), y + 2 + r * (dotSize + 1), dotSize, dotSize);
375
+ }
376
+ }
377
+ }
378
+
379
+ _drawClock(ctx, x, y) {
380
+ // Digital clock (hacker style)
381
+ ctx.fillStyle = '#111';
382
+ ctx.fillRect(x - 14, y, 28, 14);
383
+ ctx.fillStyle = '#0a0a18';
384
+ ctx.fillRect(x - 13, y + 1, 26, 12);
385
+ const now = new Date();
386
+ const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
387
+ ctx.font = '7px monospace';
388
+ ctx.fillStyle = '#0f0';
389
+ ctx.fillText(timeStr, x - 11, y + 10);
390
+ }
391
+
392
+ // ── Side-view floor furniture ──
393
+
394
+ _drawSideServerRack(ctx, x, y, depth, h, side) {
395
+ // Server rack against wall
396
+ ctx.fillStyle = '#1a1a2e';
397
+ ctx.fillRect(x, y, depth, h);
398
+ ctx.fillStyle = '#111125';
399
+ ctx.fillRect(x + 2, y + 2, depth - 4, h - 4);
400
+ // Front edge
401
+ const edgeX = side === 'left' ? x + depth - 3 : x;
402
+ ctx.fillStyle = '#0d0d1e';
403
+ ctx.fillRect(edgeX, y, 3, h);
404
+ // Server units with LEDs
405
+ for (let i = 0; i < 6; i++) {
406
+ const uy = y + 4 + i * 14;
407
+ ctx.fillStyle = '#252545';
408
+ ctx.fillRect(x + 3, uy, depth - 6, 11);
409
+ // Blinking LED
410
+ const ledOn = Math.floor(this.screenGlitch / (300 + i * 100)) % 2 === 0;
411
+ ctx.fillStyle = ledOn ? ['#0f0', '#0ff', '#f0f', '#ff0', '#0f0', '#0ff'][i] : '#222';
412
+ ctx.fillRect(x + 5, uy + 4, 3, 3);
413
+ // Ventilation lines
414
+ ctx.fillStyle = '#1a1a30';
415
+ ctx.fillRect(x + 10, uy + 2, depth - 14, 1);
416
+ ctx.fillRect(x + 10, uy + 5, depth - 14, 1);
417
+ ctx.fillRect(x + 10, uy + 8, depth - 14, 1);
418
+ }
419
+ // Top/bottom trim
420
+ ctx.fillStyle = '#333';
421
+ ctx.fillRect(x - 1, y - 1, depth + 2, 2);
422
+ ctx.fillRect(x - 1, y + h - 1, depth + 2, 2);
423
+ }
424
+
425
+ _drawSideBeanBag(ctx, x, y, depth, h, side) {
426
+ // Bean bag chair — blob shape
427
+ ctx.fillStyle = '#2d1b4e';
428
+ ctx.fillRect(x + 4, y + 6, depth - 8, h - 8);
429
+ ctx.fillStyle = '#3a2463';
430
+ ctx.fillRect(x + 2, y + 4, depth - 4, h - 6);
431
+ // Round top
432
+ ctx.fillStyle = '#3a2463';
433
+ ctx.fillRect(x + 6, y, depth - 12, 8);
434
+ // Highlight
435
+ ctx.fillStyle = '#4a3478';
436
+ ctx.fillRect(x + 6, y + 6, depth - 14, h * 0.4);
437
+ // Crease
438
+ ctx.fillStyle = '#2d1b4e';
439
+ ctx.fillRect(x + 8, y + Math.floor(h * 0.5), depth - 16, 1);
440
+ // Small glow sticker
441
+ ctx.fillStyle = '#00e5ff';
442
+ ctx.fillRect(x + depth - 10, y + 8, 4, 4);
443
+ }
444
+
445
+ _drawSideMiniFridge(ctx, x, y, depth, h, side) {
446
+ // Black mini-fridge with stickers
447
+ ctx.fillStyle = '#1a1a2e';
448
+ ctx.fillRect(x, y, depth, h);
449
+ ctx.fillStyle = '#222240';
450
+ ctx.fillRect(x + 2, y + 2, depth - 4, h - 4);
451
+ // Door edge
452
+ const edgeX = side === 'right' ? x : x + depth - 3;
453
+ ctx.fillStyle = '#111128';
454
+ ctx.fillRect(edgeX, y, 3, h);
455
+ // Handle
456
+ ctx.fillStyle = '#555';
457
+ ctx.fillRect(edgeX + 1, y + Math.floor(h * 0.3), 1, 8);
458
+ // Top
459
+ ctx.fillStyle = '#252545';
460
+ ctx.fillRect(x, y - 2, depth, 3);
461
+ // Stickers
462
+ ctx.fillStyle = '#ff00ff';
463
+ ctx.fillRect(x + 5, y + 8, 5, 5);
464
+ ctx.fillStyle = '#00e5ff';
465
+ ctx.fillRect(x + 5, y + 18, 6, 4);
466
+ ctx.fillStyle = '#ffea00';
467
+ ctx.fillRect(x + 12, y + 12, 4, 4);
468
+ // Energy drinks on top
469
+ ctx.fillStyle = '#00e676';
470
+ ctx.fillRect(x + 4, y - 10, 5, 8);
471
+ ctx.fillStyle = '#004d40';
472
+ ctx.fillRect(x + 4, y - 8, 5, 3);
473
+ ctx.fillStyle = '#00e676';
474
+ ctx.fillRect(x + 12, y - 8, 5, 6);
475
+ }
476
+
477
+ _drawSideArcade(ctx, x, y, depth, h, side) {
478
+ // Retro arcade cabinet — side view
479
+ // Cabinet body
480
+ ctx.fillStyle = '#1a1a2e';
481
+ ctx.fillRect(x, y, depth, h);
482
+ ctx.fillStyle = '#222240';
483
+ ctx.fillRect(x + 2, y + 2, depth - 4, h - 4);
484
+ // Front edge
485
+ const edgeX = side === 'right' ? x : x + depth - 3;
486
+ ctx.fillStyle = '#0d0d1e';
487
+ ctx.fillRect(edgeX, y, 3, h);
488
+ // Screen area (visible from side as a bright strip)
489
+ const screenY = y + 8;
490
+ ctx.fillStyle = '#000';
491
+ ctx.fillRect(x + 3, screenY, depth - 6, 22);
492
+ // Screen glow (cycling colors)
493
+ const screenHue = (this.neonPulse * 40) % 360;
494
+ ctx.fillStyle = `hsla(${screenHue}, 80%, 40%, 0.6)`;
495
+ ctx.fillRect(x + 4, screenY + 1, depth - 8, 20);
496
+ // Pixel art on screen
497
+ ctx.fillStyle = `hsla(${screenHue + 120}, 80%, 60%, 0.8)`;
498
+ ctx.fillRect(x + 6, screenY + 5, 4, 4);
499
+ ctx.fillRect(x + 12, screenY + 8, 3, 6);
500
+ ctx.fillRect(x + 8, screenY + 14, 5, 3);
501
+ // Control panel area
502
+ ctx.fillStyle = '#333';
503
+ ctx.fillRect(x + 3, y + 34, depth - 6, 12);
504
+ // Joystick
505
+ ctx.fillStyle = '#e74c3c';
506
+ ctx.fillRect(x + 8, y + 36, 4, 8);
507
+ ctx.fillRect(x + 7, y + 35, 6, 3);
508
+ // Buttons
509
+ ctx.fillStyle = '#3498db';
510
+ ctx.fillRect(x + 16, y + 38, 4, 4);
511
+ ctx.fillStyle = '#2ecc71';
512
+ ctx.fillRect(x + 16, y + 44, 4, 4);
513
+ // Marquee on top
514
+ ctx.fillStyle = '#ff00ff';
515
+ ctx.fillRect(x + 2, y, depth - 4, 6);
516
+ ctx.fillStyle = '#ffea00';
517
+ ctx.fillRect(x + 4, y + 1, depth - 8, 4);
518
+ // Coin slot
519
+ ctx.fillStyle = '#555';
520
+ ctx.fillRect(x + 8, y + 50, 6, 2);
521
+ // Base/legs
522
+ ctx.fillStyle = '#111';
523
+ ctx.fillRect(x + 2, y + h - 4, depth - 4, 4);
524
+ }
525
+ }
526
+
527
+ if (typeof window !== 'undefined') window.HackerScene = HackerScene;