@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,594 @@
1
+ /**
2
+ * CafeScene.js — Warm coffee shop with rain outside, cozy lighting
3
+ * Architecture matches OfficeScene: getWorkstations, renderChair, renderDesk
4
+ *
5
+ * Render order (back to front):
6
+ * 1. Background wall + rain + warm glow + decorations
7
+ * 2. Per agent: café chair → character → round table (hides legs) → laptop + latte
8
+ * 3. Name tags + bubbles on top
9
+ */
10
+ class CafeScene {
11
+ constructor(canvas, ctx, spriteGen) {
12
+ this.canvas = canvas;
13
+ this.ctx = ctx;
14
+ this.gen = spriteGen;
15
+ this.bgCanvas = null;
16
+ this.rainDrops = [];
17
+ this.steamParticles = [];
18
+ this.warmGlow = 0;
19
+ this.screenFlicker = 0;
20
+ this.name = 'cafe';
21
+ this.label = '☕ Cozy Café';
22
+ this.workstations = [];
23
+ this.poiList = [];
24
+ }
25
+
26
+ init() {
27
+ this.bgCanvas = this.gen.generateSceneBg('cafe', this.canvas.width, this.canvas.height);
28
+ this.rainDrops = [];
29
+ for (let i = 0; i < 50; i++) {
30
+ this.rainDrops.push({
31
+ x: Math.random() * this.canvas.width,
32
+ y: Math.random() * this.canvas.height * 0.5,
33
+ speed: 2 + Math.random() * 3,
34
+ length: 4 + Math.random() * 8,
35
+ opacity: 0.2 + Math.random() * 0.4,
36
+ });
37
+ }
38
+ this.steamParticles = [];
39
+ for (let i = 0; i < 8; i++) {
40
+ this.steamParticles.push({
41
+ x: 0, y: 0, life: Math.random() * 100, maxLife: 100,
42
+ vx: (Math.random() - 0.5) * 0.3, vy: -0.3 - Math.random() * 0.3,
43
+ });
44
+ }
45
+ }
46
+
47
+ getCharacterPosition() {
48
+ return { x: this.canvas.width / 2 - 48, y: this.canvas.height * 0.38 };
49
+ }
50
+
51
+ /**
52
+ * Calculate workstation layout for N agents.
53
+ * Café tables: round wooden tables with comfy chairs.
54
+ */
55
+ getWorkstations(count) {
56
+ const w = this.canvas.width;
57
+ const h = this.canvas.height;
58
+ const floorY = Math.round(h * 0.40);
59
+ const stations = [];
60
+
61
+ const cs = 2.5;
62
+ const charH = Math.round(32 * cs);
63
+ const charW = Math.round(32 * cs);
64
+
65
+ // Café tables are smaller and rounder-looking
66
+ const deskW = 110, deskH = 38;
67
+ const monW = 44, monH = 44;
68
+ const chairW = 36, chairH = 40;
69
+ const cupW = 16, cupH = 20;
70
+
71
+ const deskSurfaceY = floorY + 50;
72
+
73
+ if (count <= 0) return stations;
74
+
75
+ const positions = [];
76
+ if (count === 1) {
77
+ positions.push(w / 2);
78
+ } else if (count <= 4) {
79
+ for (let i = 0; i < count; i++) {
80
+ positions.push((w / (count + 1)) * (i + 1));
81
+ }
82
+ } else {
83
+ const topN = Math.ceil(count / 2);
84
+ const botN = count - topN;
85
+ for (let i = 0; i < topN; i++) positions.push((w / (topN + 1)) * (i + 1));
86
+ for (let i = 0; i < botN; i++) positions.push((w / (botN + 1)) * (i + 1));
87
+ }
88
+
89
+ for (let i = 0; i < count; i++) {
90
+ const cx = Math.round(positions[i]);
91
+ const isBackRow = count > 4 && i >= Math.ceil(count / 2);
92
+ const rowDeskY = isBackRow ? deskSurfaceY + 70 : deskSurfaceY;
93
+
94
+ const charY = rowDeskY - charH + 10;
95
+ const deskY = rowDeskY;
96
+
97
+ stations.push({
98
+ charX: cx - charW / 2,
99
+ charY,
100
+ charScale: cs,
101
+
102
+ chairX: cx - chairW / 2,
103
+ chairY: charY + charH * 0.3,
104
+ chairW, chairH,
105
+
106
+ deskX: cx - deskW / 2,
107
+ deskY,
108
+ deskW, deskH,
109
+
110
+ monX: cx - deskW / 2 + 6,
111
+ monY: deskY - monH + 10,
112
+ monW, monH,
113
+
114
+ cupX: cx + deskW / 2 - cupW - 12,
115
+ cupY: deskY - cupH + 8,
116
+ cupW, cupH,
117
+
118
+ cx, cy: rowDeskY,
119
+ index: i,
120
+ });
121
+ }
122
+
123
+ // Points of interest for agent wandering
124
+ const floorH = h - floorY;
125
+ this.poiList = [
126
+ { x: 80, y: floorY + 20, label: 'bookshelf' },
127
+ { x: w - 90, y: floorY + 20, label: 'counter' },
128
+ { x: w / 2, y: 30, label: 'window' },
129
+ { x: 80, y: floorY + floorH * 0.55, label: 'sofa' },
130
+ { x: w - 90, y: floorY + floorH * 0.45, label: 'pastry_case' },
131
+ { x: w - 90, y: floorY + floorH * 0.28, label: 'coffee_bar' },
132
+ { x: 80, y: floorY + floorH * 0.30, label: 'plant' },
133
+ { x: w - 90, y: floorY + floorH * 0.68, label: 'magazine_rack' },
134
+ ];
135
+
136
+ // Collision obstacles
137
+ this.obstacles = [
138
+ ...stations.map(s => ({ x: s.deskX - 5, y: s.deskY - 5, w: s.deskW + 10, h: s.deskH + 10, deskIndex: s.index })),
139
+ { x: 0, y: floorY + 5, w: 30, h: 80 }, // left bookshelf
140
+ { x: 0, y: floorY + floorH * 0.46, w: 45, h: 55 }, // left sofa
141
+ { x: w-28, y: floorY + 3, w: 28, h: 55 }, // right counter
142
+ { x: w-30, y: floorY + floorH * 0.23, w: 30, h: 80 }, // right bar + pastry
143
+ ];
144
+
145
+ this.workstations = stations;
146
+ return stations;
147
+ }
148
+
149
+ update(dt) {
150
+ this.warmGlow += dt * 0.002;
151
+ this.screenFlicker += dt;
152
+ this.rainDrops.forEach(d => {
153
+ d.y += d.speed;
154
+ d.x += 0.5;
155
+ if (d.y > this.canvas.height * 0.55) {
156
+ d.y = -d.length;
157
+ d.x = Math.random() * this.canvas.width;
158
+ }
159
+ });
160
+ this.steamParticles.forEach(p => {
161
+ p.life += dt * 0.05;
162
+ if (p.life > p.maxLife) {
163
+ p.life = 0;
164
+ p.x = 0; p.y = 0;
165
+ }
166
+ p.x += p.vx;
167
+ p.y += p.vy;
168
+ });
169
+ }
170
+
171
+ /** Render background + static decorations */
172
+ render(ctx) {
173
+ const w = this.canvas.width;
174
+ const h = this.canvas.height;
175
+
176
+ if (this.bgCanvas) ctx.drawImage(this.bgCanvas, 0, 0);
177
+
178
+ // ── Rain on window ──
179
+ const cwx = w - 150, cwy = 18, cww = 120, cwh = 70;
180
+ ctx.save();
181
+ ctx.beginPath();
182
+ ctx.rect(cwx, cwy, cww, cwh);
183
+ ctx.clip();
184
+ ctx.strokeStyle = 'rgba(180, 200, 220, 0.5)';
185
+ ctx.lineWidth = 1;
186
+ this.rainDrops.forEach(d => {
187
+ if (d.x > cwx && d.x < cwx + cww) {
188
+ ctx.globalAlpha = d.opacity;
189
+ ctx.beginPath();
190
+ ctx.moveTo(d.x, d.y);
191
+ ctx.lineTo(d.x + 1, d.y + d.length);
192
+ ctx.stroke();
193
+ }
194
+ });
195
+ ctx.restore();
196
+
197
+ // Rain streaks on window glass
198
+ ctx.save();
199
+ ctx.beginPath();
200
+ ctx.rect(cwx, cwy, cww, cwh);
201
+ ctx.clip();
202
+ ctx.strokeStyle = 'rgba(200, 220, 240, 0.15)';
203
+ ctx.lineWidth = 2;
204
+ for (let i = 0; i < 5; i++) {
205
+ const sx = cwx + 10 + i * 25;
206
+ ctx.beginPath();
207
+ ctx.moveTo(sx, cwy);
208
+ for (let j = 0; j < 8; j++) {
209
+ ctx.lineTo(sx + Math.sin(j + i) * 3, cwy + j * 10);
210
+ }
211
+ ctx.stroke();
212
+ }
213
+ ctx.restore();
214
+
215
+ // ── Warm ambient light ──
216
+ const glowIntensity = 0.08 + Math.sin(this.warmGlow) * 0.02;
217
+ const warmGrad = ctx.createRadialGradient(w * 0.35, h * 0.35, 10, w * 0.35, h * 0.35, 200);
218
+ warmGrad.addColorStop(0, `rgba(255, 200, 100, ${glowIntensity})`);
219
+ warmGrad.addColorStop(1, 'rgba(0, 0, 0, 0)');
220
+ ctx.fillStyle = warmGrad;
221
+ ctx.fillRect(0, 0, w, h);
222
+
223
+ const floorY = Math.round(h * 0.40);
224
+ const floorH = h - floorY;
225
+ const wallSafe = Math.round(h * 0.12);
226
+
227
+ // ── Wall decorations ──
228
+ this._drawHangingLamp(ctx, w * 0.30, 0);
229
+ this._drawHangingLamp(ctx, w * 0.70, 0);
230
+ this._drawMenuBoard(ctx, 25, wallSafe, 65, 44);
231
+ this._drawCafeArt(ctx, w * 0.55, wallSafe + 2, 50, 36);
232
+ this._drawClock(ctx, w - 40, wallSafe + 8);
233
+
234
+ // ── LEFT WALL: bookshelf + sofa ──
235
+ this._drawSideBookshelf(ctx, 0, floorY + 8, 22, 78, 'left');
236
+ this._drawSideSofa(ctx, 0, floorY + floorH * 0.46, 42, 50, 'left');
237
+ ctx.drawImage(this.gen.generateFurniture('plant'), 3, floorY + floorH * 0.28, 22, 30);
238
+
239
+ // ── RIGHT WALL: coffee counter + pastry case ──
240
+ this._drawSideCoffeeCounter(ctx, w - 25, floorY + 5, 25, 52, 'right');
241
+ this._drawSidePastryCase(ctx, w - 25, floorY + floorH * 0.25, 25, 75, 'right');
242
+ ctx.drawImage(this.gen.generateFurniture('plant'), w - 22, floorY + floorH * 0.68, 22, 30);
243
+ }
244
+
245
+ /** Render café chair (behind character) */
246
+ renderChair(ctx, s) {
247
+ const x = s.chairX, y = s.chairY, cw = s.chairW, ch = s.chairH;
248
+ // Wooden café chair with cushion
249
+ // Back rest — rounded wooden frame
250
+ ctx.fillStyle = '#8B5E3C';
251
+ ctx.fillRect(x + 4, y, cw - 8, ch * 0.45);
252
+ // Cross bar on back
253
+ ctx.fillStyle = '#7A4E2C';
254
+ ctx.fillRect(x + 6, y + 4, cw - 12, 2);
255
+ ctx.fillRect(x + 6, y + Math.floor(ch * 0.25), cw - 12, 2);
256
+ // Side posts
257
+ ctx.fillStyle = '#6B4226';
258
+ ctx.fillRect(x + 4, y, 3, ch * 0.65);
259
+ ctx.fillRect(x + cw - 7, y, 3, ch * 0.65);
260
+ // Seat — warm cushion
261
+ ctx.fillStyle = '#CD853F';
262
+ ctx.fillRect(x + 2, y + ch * 0.40, cw - 4, ch * 0.30);
263
+ // Cushion highlight
264
+ ctx.fillStyle = '#D4946A';
265
+ ctx.fillRect(x + 4, y + ch * 0.42, cw - 8, ch * 0.12);
266
+ // Cushion stitch
267
+ ctx.fillStyle = '#B8734A';
268
+ ctx.fillRect(x + cw / 2 - 1, y + ch * 0.44, 2, ch * 0.20);
269
+ // Legs
270
+ ctx.fillStyle = '#6B4226';
271
+ ctx.fillRect(x + 4, y + ch * 0.70, 3, ch * 0.30);
272
+ ctx.fillRect(x + cw - 7, y + ch * 0.70, 3, ch * 0.30);
273
+ // Front legs
274
+ ctx.fillStyle = '#7A4E2C';
275
+ ctx.fillRect(x + 6, y + ch - 4, 3, 4);
276
+ ctx.fillRect(x + cw - 9, y + ch - 4, 3, 4);
277
+ }
278
+
279
+ /** Render café table + laptop + latte (in front of character) */
280
+ renderDesk(ctx, s, agentState) {
281
+ const isWorking = ['typing', 'executing', 'browsing'].includes(agentState);
282
+
283
+ // ── Café table — warm wood with rounded feel ──
284
+ // Top surface
285
+ ctx.fillStyle = '#D4A56A';
286
+ ctx.fillRect(s.deskX, s.deskY, s.deskW, 7);
287
+ // Top highlight
288
+ ctx.fillStyle = '#E0B878';
289
+ ctx.fillRect(s.deskX + 2, s.deskY, s.deskW - 4, 2);
290
+ // Front face
291
+ ctx.fillStyle = '#8B5E3C';
292
+ ctx.fillRect(s.deskX, s.deskY + 7, s.deskW, s.deskH - 7);
293
+ // Apron trim
294
+ ctx.fillStyle = '#7A4E2C';
295
+ ctx.fillRect(s.deskX, s.deskY + 7, s.deskW, 3);
296
+ // Side edges
297
+ ctx.fillStyle = '#7A4E2C';
298
+ ctx.fillRect(s.deskX, s.deskY + 10, 2, s.deskH - 12);
299
+ ctx.fillRect(s.deskX + s.deskW - 2, s.deskY + 10, 2, s.deskH - 12);
300
+ // Bottom trim
301
+ ctx.fillStyle = '#6B4226';
302
+ ctx.fillRect(s.deskX + 2, s.deskY + s.deskH - 2, s.deskW - 4, 2);
303
+ // Legs — tapered wooden
304
+ ctx.fillStyle = '#6B4226';
305
+ ctx.fillRect(s.deskX + 8, s.deskY + s.deskH, 4, 5);
306
+ ctx.fillRect(s.deskX + s.deskW - 12, s.deskY + s.deskH, 4, 5);
307
+ // Wood grain hints
308
+ ctx.fillStyle = 'rgba(0,0,0,0.04)';
309
+ ctx.fillRect(s.deskX + 10, s.deskY + 2, 20, 1);
310
+ ctx.fillRect(s.deskX + 40, s.deskY + 4, 25, 1);
311
+
312
+ // ── Laptop on table ──
313
+ const monType = isWorking && Math.floor(this.screenFlicker / 400) % 2 === 0 ? 'laptop_active' : 'laptop';
314
+ ctx.drawImage(this.gen.generateFurniture(monType), s.monX, s.monY, s.monW, s.monH);
315
+
316
+ // Screen glow when working
317
+ if (isWorking) {
318
+ ctx.fillStyle = 'rgba(100,200,255,0.04)';
319
+ ctx.fillRect(s.monX - 6, s.monY - 3, s.monW + 12, s.monH + 6);
320
+ }
321
+
322
+ // ── Latte with art ──
323
+ const lx = s.cupX, ly = s.cupY;
324
+ // Saucer
325
+ ctx.fillStyle = '#ECE5D8';
326
+ ctx.fillRect(lx - 3, ly + s.cupH - 2, s.cupW + 4, 4);
327
+ // Cup body — wider, shorter
328
+ ctx.fillStyle = '#FFFFFF';
329
+ ctx.fillRect(lx, ly + 2, s.cupW - 2, s.cupH - 2);
330
+ // Latte art (top)
331
+ ctx.fillStyle = '#D4A574';
332
+ ctx.fillRect(lx, ly + 2, s.cupW - 2, 4);
333
+ // Heart latte art
334
+ ctx.fillStyle = '#F5E6D3';
335
+ ctx.fillRect(lx + 3, ly + 3, 3, 2);
336
+ ctx.fillRect(lx + 8, ly + 3, 3, 2);
337
+ ctx.fillRect(lx + 4, ly + 5, 6, 1);
338
+ // Handle
339
+ ctx.fillStyle = '#DDD';
340
+ ctx.fillRect(lx + s.cupW - 2, ly + 6, 3, 5);
341
+ ctx.fillRect(lx + s.cupW, ly + 5, 2, 1);
342
+ ctx.fillRect(lx + s.cupW, ly + 11, 2, 1);
343
+
344
+ // ── Steam from latte ──
345
+ this.steamParticles.forEach(p => {
346
+ const px = lx + s.cupW / 2 + p.x;
347
+ const py = ly + p.y * 15;
348
+ const alpha = Math.max(0, 1 - p.life / p.maxLife) * 0.35;
349
+ ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`;
350
+ ctx.beginPath();
351
+ ctx.arc(px, py, 2 + p.life * 0.02, 0, Math.PI * 2);
352
+ ctx.fill();
353
+ });
354
+
355
+ // ── Small pastry plate ──
356
+ const ppX = s.deskX + s.deskW / 2 + 8;
357
+ const ppY = s.deskY - 10;
358
+ // Plate
359
+ ctx.fillStyle = '#F5F0E8';
360
+ ctx.fillRect(ppX, ppY + 6, 16, 4);
361
+ // Croissant
362
+ ctx.fillStyle = '#D4A056';
363
+ ctx.fillRect(ppX + 2, ppY + 2, 12, 5);
364
+ ctx.fillStyle = '#C4903E';
365
+ ctx.fillRect(ppX + 4, ppY + 3, 8, 3);
366
+ ctx.fillStyle = '#E0B878';
367
+ ctx.fillRect(ppX + 5, ppY + 2, 4, 1);
368
+ }
369
+
370
+ // ── Wall decorations ──
371
+
372
+ _drawHangingLamp(ctx, x, y) {
373
+ // Warm pendant lamp
374
+ ctx.fillStyle = '#333';
375
+ ctx.fillRect(x - 1, y, 2, 28);
376
+ // Shade
377
+ ctx.fillStyle = '#F39C12';
378
+ ctx.beginPath();
379
+ ctx.moveTo(x - 12, 28);
380
+ ctx.lineTo(x + 12, 28);
381
+ ctx.lineTo(x + 6, 40);
382
+ ctx.lineTo(x - 6, 40);
383
+ ctx.closePath();
384
+ ctx.fill();
385
+ // Warm glow
386
+ const lampGlow = ctx.createRadialGradient(x, 40, 2, x, 40, 55);
387
+ lampGlow.addColorStop(0, 'rgba(255, 220, 130, 0.18)');
388
+ lampGlow.addColorStop(1, 'rgba(0, 0, 0, 0)');
389
+ ctx.fillStyle = lampGlow;
390
+ ctx.fillRect(x - 55, 28, 110, 80);
391
+ }
392
+
393
+ _drawMenuBoard(ctx, x, y, w, h) {
394
+ // Chalkboard menu
395
+ ctx.fillStyle = '#5D3D1A';
396
+ ctx.fillRect(x - 3, y - 3, w + 6, h + 6);
397
+ ctx.fillStyle = '#2C3E2C';
398
+ ctx.fillRect(x, y, w, h);
399
+ // Chalk text
400
+ ctx.fillStyle = 'rgba(255,255,255,0.8)';
401
+ ctx.font = '6px monospace';
402
+ ctx.fillText('MENU', x + 20, y + 10);
403
+ ctx.fillStyle = 'rgba(255,255,255,0.6)';
404
+ ctx.font = '5px monospace';
405
+ ctx.fillText('Latte $4', x + 4, y + 20);
406
+ ctx.fillText('Mocha $5', x + 4, y + 28);
407
+ ctx.fillText('Espresso $3', x + 4, y + 36);
408
+ // Chalk doodle
409
+ ctx.fillStyle = 'rgba(255,200,100,0.5)';
410
+ ctx.fillRect(x + 48, y + 18, 8, 8);
411
+ ctx.fillStyle = 'rgba(255,150,50,0.4)';
412
+ ctx.fillRect(x + 50, y + 20, 4, 4);
413
+ }
414
+
415
+ _drawCafeArt(ctx, x, y, w, h) {
416
+ // Framed art print — coffee cup illustration
417
+ ctx.fillStyle = '#8B5E3C';
418
+ ctx.fillRect(x - 2, y - 2, w + 4, h + 4);
419
+ ctx.fillStyle = '#FFF8E8';
420
+ ctx.fillRect(x, y, w, h);
421
+ // Simple coffee cup illustration
422
+ ctx.fillStyle = '#D4A574';
423
+ ctx.fillRect(x + 15, y + 10, 20, 16);
424
+ ctx.fillStyle = '#8B5E3C';
425
+ ctx.fillRect(x + 15, y + 10, 20, 3);
426
+ ctx.fillStyle = '#ECE5D8';
427
+ ctx.fillRect(x + 12, y + 26, 26, 3);
428
+ // Steam swirls
429
+ ctx.fillStyle = 'rgba(180,160,140,0.4)';
430
+ ctx.fillRect(x + 20, y + 5, 2, 5);
431
+ ctx.fillRect(x + 25, y + 6, 2, 4);
432
+ ctx.fillRect(x + 29, y + 5, 2, 5);
433
+ }
434
+
435
+ _drawClock(ctx, x, y) {
436
+ // Vintage round clock
437
+ ctx.fillStyle = '#5D3D1A';
438
+ ctx.beginPath(); ctx.arc(x, y + 10, 12, 0, Math.PI * 2); ctx.fill();
439
+ ctx.fillStyle = '#FFF8E8';
440
+ ctx.beginPath(); ctx.arc(x, y + 10, 10, 0, Math.PI * 2); ctx.fill();
441
+ // Hour marks
442
+ ctx.fillStyle = '#8B5E3C';
443
+ for (let i = 0; i < 12; i++) {
444
+ const a = i * Math.PI / 6;
445
+ ctx.fillRect(x + Math.cos(a) * 8 - 0.5, y + 10 + Math.sin(a) * 8 - 0.5, 1, 1);
446
+ }
447
+ const now = new Date(), hr = now.getHours() % 12, mn = now.getMinutes();
448
+ ctx.strokeStyle = '#5D3D1A'; ctx.lineWidth = 1.5;
449
+ const ha = (hr + mn / 60) * Math.PI / 6 - Math.PI / 2;
450
+ ctx.beginPath(); ctx.moveTo(x, y + 10); ctx.lineTo(x + Math.cos(ha) * 5, y + 10 + Math.sin(ha) * 5); ctx.stroke();
451
+ ctx.lineWidth = 1;
452
+ const ma = mn * Math.PI / 30 - Math.PI / 2;
453
+ ctx.beginPath(); ctx.moveTo(x, y + 10); ctx.lineTo(x + Math.cos(ma) * 7, y + 10 + Math.sin(ma) * 7); ctx.stroke();
454
+ ctx.fillStyle = '#8B5E3C';
455
+ ctx.beginPath(); ctx.arc(x, y + 10, 1.5, 0, Math.PI * 2); ctx.fill();
456
+ }
457
+
458
+ // ── Side-view floor furniture ──
459
+
460
+ _drawSideBookshelf(ctx, x, y, depth, h, side) {
461
+ const frameColor = '#6B4226', backColor = '#8B5E3C', shelfColor = '#5D3A1A';
462
+ const bookColors = ['#C0392B', '#2980B9', '#27AE60', '#F39C12', '#8E44AD', '#16A085', '#D35400'];
463
+ ctx.fillStyle = frameColor; ctx.fillRect(x, y, depth, h);
464
+ ctx.fillStyle = backColor; ctx.fillRect(x + 2, y + 2, depth - 4, h - 4);
465
+ const edgeX = side === 'left' ? x + depth - 3 : x;
466
+ ctx.fillStyle = shelfColor; ctx.fillRect(edgeX, y, 3, h);
467
+ const shelfCount = 4;
468
+ const shelfGap = Math.floor((h - 6) / shelfCount);
469
+ for (let i = 0; i < shelfCount; i++) {
470
+ const sy = y + 4 + i * shelfGap;
471
+ ctx.fillStyle = shelfColor; ctx.fillRect(x + 2, sy, depth - 4, 2);
472
+ for (let b = 0; b < 3; b++) {
473
+ const bh = shelfGap - 6;
474
+ const bw = 2 + (b % 2);
475
+ const bx = x + 4 + b * (bw + 1);
476
+ ctx.fillStyle = bookColors[(i * 3 + b) % bookColors.length];
477
+ ctx.fillRect(bx, sy - bh, bw, bh);
478
+ }
479
+ }
480
+ ctx.fillStyle = shelfColor;
481
+ ctx.fillRect(x - 1, y - 2, depth + 2, 3);
482
+ ctx.fillRect(x - 1, y + h - 1, depth + 2, 3);
483
+ }
484
+
485
+ _drawSideSofa(ctx, x, y, depth, h, side) {
486
+ // Warm leather sofa
487
+ const sofaColor = '#A0522D', sofaDark = '#8B4513', sofaLight = '#B8623D';
488
+ ctx.fillStyle = sofaDark; ctx.fillRect(x, y, depth, h);
489
+ ctx.fillStyle = sofaColor; ctx.fillRect(x + 3, y + 6, depth - 6, h - 10);
490
+ // Back rest
491
+ ctx.fillStyle = sofaDark; ctx.fillRect(x + 2, y, depth - 4, 10);
492
+ ctx.fillStyle = sofaLight; ctx.fillRect(x + 4, y + 2, depth - 8, 6);
493
+ // Seat cushion
494
+ ctx.fillStyle = sofaLight; ctx.fillRect(x + 5, y + 14, depth - 10, h - 22);
495
+ // Seams
496
+ ctx.fillStyle = sofaDark;
497
+ ctx.fillRect(x + 5, y + Math.floor(h * 0.45), depth - 10, 1);
498
+ ctx.fillRect(x + 5, y + Math.floor(h * 0.65), depth - 10, 1);
499
+ // Legs
500
+ ctx.fillStyle = '#4A3728';
501
+ ctx.fillRect(x + 4, y + h - 2, 4, 4);
502
+ ctx.fillRect(x + depth - 8, y + h - 2, 4, 4);
503
+ // Throw pillow
504
+ ctx.fillStyle = '#E8D4B8';
505
+ const px = side === 'left' ? x + depth - 12 : x + 4;
506
+ ctx.fillRect(px, y + 4, 8, 12);
507
+ ctx.fillStyle = '#D4C0A4'; ctx.fillRect(px + 1, y + 5, 6, 10);
508
+ }
509
+
510
+ _drawSideCoffeeCounter(ctx, x, y, depth, h, side) {
511
+ // Wooden coffee counter / bar
512
+ const woodColor = '#7B5B3A', woodDark = '#5D3A1A', woodLight = '#9B7B5A';
513
+ ctx.fillStyle = woodLight; ctx.fillRect(x, y, depth, 4);
514
+ ctx.fillStyle = woodColor; ctx.fillRect(x, y + 4, depth, h - 4);
515
+ // Wood grain
516
+ ctx.fillStyle = woodDark;
517
+ for (let ly = y + 12; ly < y + h; ly += 10) {
518
+ ctx.fillRect(x + 2, ly, depth - 4, 1);
519
+ }
520
+ // Front edge
521
+ const edgeX = side === 'right' ? x : x + depth - 3;
522
+ ctx.fillStyle = woodDark; ctx.fillRect(edgeX, y, 3, h);
523
+ // Top highlight
524
+ ctx.fillStyle = woodLight; ctx.fillRect(x, y, depth, 2);
525
+ // Coffee machine on top
526
+ ctx.fillStyle = '#708090'; ctx.fillRect(x + 3, y - 22, 14, 22);
527
+ ctx.fillStyle = '#5A6A7A'; ctx.fillRect(x + 5, y - 20, 10, 8);
528
+ // Power LED
529
+ ctx.fillStyle = '#2ECC71'; ctx.fillRect(x + 7, y - 15, 2, 2);
530
+ // Group head / drip area
531
+ ctx.fillStyle = '#555'; ctx.fillRect(x + 5, y - 10, 10, 8);
532
+ ctx.fillStyle = '#444'; ctx.fillRect(x + 7, y - 8, 6, 4);
533
+ // Tiny cup
534
+ ctx.fillStyle = '#FFF'; ctx.fillRect(x + 7, y - 4, 4, 3);
535
+ ctx.fillStyle = '#8B4513'; ctx.fillRect(x + 8, y - 3, 2, 1);
536
+ // Steam
537
+ ctx.fillStyle = 'rgba(200,200,200,0.4)';
538
+ ctx.fillRect(x + 8, y - 7, 1, 3);
539
+ ctx.fillRect(x + 11, y - 8, 1, 3);
540
+ }
541
+
542
+ _drawSidePastryCase(ctx, x, y, depth, h, side) {
543
+ // Glass pastry display case
544
+ // Frame
545
+ ctx.fillStyle = '#8B5E3C';
546
+ ctx.fillRect(x, y, depth, h);
547
+ // Glass panel (translucent)
548
+ ctx.fillStyle = 'rgba(200, 220, 240, 0.25)';
549
+ ctx.fillRect(x + 2, y + 2, depth - 4, h - 4);
550
+ // Front edge
551
+ const edgeX = side === 'right' ? x : x + depth - 3;
552
+ ctx.fillStyle = '#6B4226';
553
+ ctx.fillRect(edgeX, y, 3, h);
554
+ // Shelves
555
+ ctx.fillStyle = '#7A4E2C';
556
+ ctx.fillRect(x + 2, y + Math.floor(h * 0.33), depth - 4, 2);
557
+ ctx.fillRect(x + 2, y + Math.floor(h * 0.66), depth - 4, 2);
558
+ // Pastries on each shelf
559
+ // Top shelf — muffins
560
+ ctx.fillStyle = '#D4A056';
561
+ ctx.fillRect(x + 5, y + 8, 6, 6);
562
+ ctx.fillRect(x + 13, y + 9, 5, 5);
563
+ ctx.fillStyle = '#8B4513';
564
+ ctx.fillRect(x + 5, y + 12, 6, 2);
565
+ // Middle shelf — croissants
566
+ const midY = y + Math.floor(h * 0.33) + 4;
567
+ ctx.fillStyle = '#E0B878';
568
+ ctx.fillRect(x + 4, midY, 8, 5);
569
+ ctx.fillRect(x + 14, midY + 1, 7, 4);
570
+ ctx.fillStyle = '#C4903E';
571
+ ctx.fillRect(x + 5, midY + 2, 6, 2);
572
+ // Bottom shelf — cookies
573
+ const botY = y + Math.floor(h * 0.66) + 4;
574
+ ctx.fillStyle = '#C49250';
575
+ for (let c = 0; c < 3; c++) {
576
+ ctx.fillRect(x + 4 + c * 7, botY, 5, 5);
577
+ }
578
+ // Chocolate chips
579
+ ctx.fillStyle = '#5D3A1A';
580
+ ctx.fillRect(x + 5, botY + 2, 2, 2);
581
+ ctx.fillRect(x + 13, botY + 1, 2, 2);
582
+ // Top surface
583
+ ctx.fillStyle = '#8B5E3C';
584
+ ctx.fillRect(x - 1, y - 2, depth + 2, 3);
585
+ // Small vase on top
586
+ ctx.fillStyle = '#C0392B';
587
+ ctx.fillRect(x + 8, y - 10, 6, 8);
588
+ ctx.fillStyle = '#27AE60';
589
+ ctx.fillRect(x + 9, y - 14, 2, 5);
590
+ ctx.fillRect(x + 12, y - 13, 2, 4);
591
+ }
592
+ }
593
+
594
+ if (typeof window !== 'undefined') window.CafeScene = CafeScene;