@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,404 @@
1
+ /**
2
+ * OfficeScene.js — Pixel office with proper depth layering per workstation
3
+ *
4
+ * Render order (back to front):
5
+ * 1. Background wall + decorations
6
+ * 2. Per agent: chair → character → desk (hides legs) → monitor + cup on desk
7
+ * 3. Name tags + bubbles on top
8
+ *
9
+ * Characters SIT behind desks — desk surface crosses at waist level.
10
+ */
11
+ class OfficeScene {
12
+ constructor(canvas, ctx, spriteGen) {
13
+ this.canvas = canvas;
14
+ this.ctx = ctx;
15
+ this.gen = spriteGen;
16
+ this.bgCanvas = null;
17
+ this.cloudX = 0;
18
+ this.sunbeamTimer = 0;
19
+ this.screenFlicker = 0;
20
+ this.name = 'office';
21
+ this.label = '🏢 Office';
22
+ this.workstations = [];
23
+
24
+ // Wandering targets (shared locations agents can visit)
25
+ this.poiList = []; // populated in getWorkstations
26
+ }
27
+
28
+ init() {
29
+ this.bgCanvas = this.gen.generateSceneBg('office', this.canvas.width, this.canvas.height);
30
+ }
31
+
32
+ getCharacterPosition() {
33
+ return { x: this.canvas.width / 2 - 48, y: this.canvas.height * 0.38 };
34
+ }
35
+
36
+ /**
37
+ * Calculate workstation layout for N agents.
38
+ * All coordinates are absolute canvas pixels.
39
+ */
40
+ getWorkstations(count) {
41
+ const w = this.canvas.width;
42
+ const h = this.canvas.height;
43
+ const floorY = Math.round(h * 0.40); // floor line
44
+ const stations = [];
45
+
46
+ // Character scale
47
+ const cs = 2.5; // slightly smaller for better proportions
48
+ const charH = Math.round(32 * cs); // 80px
49
+ const charW = Math.round(32 * cs); // 80px
50
+
51
+ // Furniture sizes (display pixels) — 3/4 top-down view
52
+ const deskW = 120, deskH = 42; // desk (top surface + front face)
53
+ const monW = 44, monH = 44; // iMac back view — square-ish, Apple logo visible
54
+ const chairW = 36, chairH = 40; // 3/4 chair
55
+ const cupW = 16, cupH = 20;
56
+
57
+ // Desk surface Y — pushed down into the floor area, away from wall
58
+ const deskSurfaceY = floorY + 50;
59
+
60
+ if (count <= 0) return stations;
61
+
62
+ // Horizontal spacing
63
+ const positions = [];
64
+ if (count === 1) {
65
+ positions.push(w / 2);
66
+ } else if (count <= 4) {
67
+ for (let i = 0; i < count; i++) {
68
+ positions.push((w / (count + 1)) * (i + 1));
69
+ }
70
+ } else {
71
+ // Two rows for 5+
72
+ const topN = Math.ceil(count / 2);
73
+ const botN = count - topN;
74
+ for (let i = 0; i < topN; i++) positions.push((w / (topN + 1)) * (i + 1));
75
+ for (let i = 0; i < botN; i++) positions.push((w / (botN + 1)) * (i + 1));
76
+ }
77
+
78
+ for (let i = 0; i < count; i++) {
79
+ const cx = Math.round(positions[i]);
80
+ const isBackRow = count > 4 && i >= Math.ceil(count / 2);
81
+ const rowDeskY = isBackRow ? deskSurfaceY + 70 : deskSurfaceY;
82
+
83
+ // 3/4 RPG view: character sits close to desk
84
+ // Monitor is flat/tilted so it's very short — doesn't block upper body
85
+ const charY = rowDeskY - charH + 10; // character close to desk, legs slightly behind desk
86
+ const deskY = rowDeskY;
87
+
88
+ stations.push({
89
+ // Character: sitting at desk, upper body fully visible above the flat monitor
90
+ charX: cx - charW / 2,
91
+ charY: charY,
92
+ charScale: cs,
93
+
94
+ // Chair: behind character
95
+ chairX: cx - chairW / 2,
96
+ chairY: charY + charH * 0.3,
97
+ chairW, chairH,
98
+
99
+ // Desk: in front of character
100
+ deskX: cx - deskW / 2,
101
+ deskY: deskY,
102
+ deskW, deskH,
103
+
104
+ // iMac 45° angled: on desk LEFT side, away from character center
105
+ monX: cx - deskW / 2 + 6, // flush left on desk
106
+ monY: deskY - monH + 10, // base sits on desk surface
107
+ monW, monH,
108
+
109
+ // Coffee cup: on desk right side
110
+ cupX: cx + deskW / 2 - cupW - 12,
111
+ cupY: deskY - cupH + 8,
112
+ cupW, cupH,
113
+
114
+ // Center point (for returning from walks)
115
+ cx, cy: rowDeskY,
116
+ index: i,
117
+ });
118
+ }
119
+
120
+ // Points of interest for wandering — far enough from walls for 80px-wide characters
121
+ const floorH = h - floorY;
122
+ this.poiList = [
123
+ { x: w / 2, y: 30, label: 'window' },
124
+ { x: 80, y: floorY + 30, label: 'bookshelf' },
125
+ { x: w - 90, y: floorY + 20, label: 'fridge' },
126
+ { x: 90, y: floorY + floorH * 0.55, label: 'sofa' },
127
+ { x: w - 90, y: floorY + floorH * 0.45, label: 'bar' },
128
+ { x: w - 90, y: floorY + floorH * 0.28, label: 'coffee_machine' },
129
+ { x: 80, y: floorY + floorH * 0.30, label: 'plant' },
130
+ { x: w - 90, y: floorY + floorH * 0.68, label: 'plant' },
131
+ ];
132
+
133
+ // Collision obstacles — solid rects that agents/pets must avoid
134
+ this.obstacles = [
135
+ ...stations.map(s => ({x: s.deskX - 5, y: s.deskY - 5, w: s.deskW + 10, h: s.deskH + 10, deskIndex: s.index})),
136
+ {x: 0, y: floorY + 5, w: 30, h: 92}, // left bookshelf
137
+ {x: 0, y: floorY + floorH * 0.46, w: 50, h: 62}, // left sofa
138
+ {x: w - 28, y: floorY + 3, w: 28, h: 58}, // right fridge
139
+ {x: w - 30, y: floorY + floorH * 0.23, w: 30, h: 85}, // right bar + coffee
140
+ ];
141
+
142
+ this.workstations = stations;
143
+ return stations;
144
+ }
145
+
146
+ update(dt) {
147
+ this.cloudX += dt * 0.003;
148
+ this.sunbeamTimer += dt;
149
+ this.screenFlicker += dt;
150
+ }
151
+
152
+ /** Render background wall + static decorations (called once per frame) */
153
+ render(ctx) {
154
+ const w = this.canvas.width;
155
+ const h = this.canvas.height;
156
+
157
+ if (this.bgCanvas) ctx.drawImage(this.bgCanvas, 0, 0);
158
+
159
+ // Animated clouds — inside window, pushed down to match new window position
160
+ const wx = (w - 80) / 2;
161
+ const wy = Math.round(h * 0.08);
162
+ ctx.save();
163
+ ctx.beginPath(); ctx.rect(wx, wy + 2, 80, 70); ctx.clip();
164
+ ctx.fillStyle = '#FFF';
165
+ const cx1 = wx + ((this.cloudX * 20) % 120) - 20;
166
+ ctx.fillRect(cx1, wy + 20, 18, 6); ctx.fillRect(cx1 + 3, wy + 17, 11, 4);
167
+ const cx2 = wx + ((this.cloudX * 12 + 60) % 130) - 10;
168
+ ctx.fillRect(cx2, wy + 35, 14, 5); ctx.fillRect(cx2 + 2, wy + 32, 9, 4);
169
+ ctx.restore();
170
+
171
+ // Sunbeam
172
+ const floorLine = h * 0.40;
173
+ if (Math.sin(this.sunbeamTimer * 0.001) > 0) {
174
+ ctx.fillStyle = 'rgba(255,255,200,0.04)';
175
+ const bx = wx + 60;
176
+ ctx.beginPath(); ctx.moveTo(bx, wy + 80); ctx.lineTo(bx+40, wy); ctx.lineTo(bx+55, wy); ctx.lineTo(bx+80, h*0.55); ctx.fill();
177
+ ctx.fillStyle = 'rgba(255,255,200,0.3)';
178
+ for (let i = 0; i < 5; i++) {
179
+ const px = bx + 20 + Math.sin(this.sunbeamTimer*0.0005+i*2)*25;
180
+ const py = wy + 30 + ((this.sunbeamTimer*0.02+i*40)%(h*0.25));
181
+ ctx.beginPath(); ctx.arc(px,py,1.5,0,Math.PI*2); ctx.fill();
182
+ }
183
+ }
184
+
185
+ // Wall decorations — below top UI safe zone (~50px from top)
186
+ const wallSafe = Math.round(h * 0.12); // safe Y start below UI
187
+ ctx.drawImage(this.gen.generateFurniture('bookshelf'), 15, wallSafe, 48, 64);
188
+ ctx.drawImage(this.gen.generateFurniture('plant'), 75, floorLine - 36, 26, 36);
189
+ ctx.drawImage(this.gen.generateFurniture('lamp'), 100, wallSafe + 5, 18, 32);
190
+ ctx.drawImage(this.gen.generateFurniture('lamp'), w-110, wallSafe + 5, 18, 32);
191
+ this._drawWhiteboard(ctx, w*0.15, wallSafe + 2, 70, 44);
192
+ this._drawClock(ctx, w-45, wallSafe + 8);
193
+ this._drawWaterCooler(ctx, w-80, floorLine - 30);
194
+
195
+ // ── Floor decorations — side-view, pressed against left/right walls ──
196
+ const floorH = h - floorLine;
197
+
198
+ // LEFT WALL — furniture faces RIGHT (side-view: thin width = depth, height = visible front)
199
+ this._drawSideBookshelf(ctx, 0, floorLine + 8, 22, 90, 'left');
200
+ this._drawSideSofa(ctx, 0, floorLine + floorH * 0.46, 45, 55, 'left');
201
+ ctx.drawImage(this.gen.generateFurniture('plant'), 3, floorLine + floorH * 0.28, 22, 30);
202
+
203
+ // RIGHT WALL — furniture faces LEFT (mirrored side-view)
204
+ this._drawSideFridge(ctx, w - 22, floorLine + 5, 22, 55, 'right');
205
+ this._drawSideBar(ctx, w - 25, floorLine + floorH * 0.25, 25, 80, 'right');
206
+ this._drawSideCoffeeMachine(ctx, w - 18, floorLine + floorH * 0.25 - 32, 18, 30, 'right');
207
+ ctx.drawImage(this.gen.generateFurniture('plant'), w - 22, floorLine + floorH * 0.68, 22, 30);
208
+ }
209
+
210
+ /** Render chair (behind character) */
211
+ renderChair(ctx, s) {
212
+ ctx.drawImage(this.gen.generateFurniture('chair'), s.chairX, s.chairY, s.chairW, s.chairH);
213
+ }
214
+
215
+ /** Render desk + laptop + cup (in front of character in 3/4 view) */
216
+ renderDesk(ctx, s, agentState) {
217
+ const isWorking = ['typing','executing','browsing'].includes(agentState);
218
+
219
+ // Desk surface first (3/4 view — top surface + front face)
220
+ ctx.drawImage(this.gen.generateFurniture('desk'), s.deskX, s.deskY, s.deskW, s.deskH);
221
+
222
+ // Laptop on desk surface
223
+ const monType = isWorking && Math.floor(this.screenFlicker/400)%2===0 ? 'laptop_active' : 'laptop';
224
+ ctx.drawImage(this.gen.generateFurniture(monType), s.monX, s.monY, s.monW, s.monH);
225
+
226
+ // Laptop screen glow when working
227
+ if (isWorking) {
228
+ ctx.fillStyle = 'rgba(100,200,255,0.04)';
229
+ ctx.fillRect(s.monX-6, s.monY-3, s.monW+12, s.monH+6);
230
+ }
231
+
232
+ // Coffee cup on desk
233
+ ctx.drawImage(this.gen.generateFurniture('coffee_cup'), s.cupX, s.cupY, s.cupW, s.cupH);
234
+ }
235
+
236
+ _drawWaterCooler(ctx, x, y) {
237
+ ctx.fillStyle='#B0C4DE'; ctx.fillRect(x,y,14,26);
238
+ ctx.fillStyle='#87CEEB'; ctx.fillRect(x+2,y+2,10,10);
239
+ ctx.fillStyle='#708090'; ctx.fillRect(x-2,y+24,18,3);
240
+ ctx.fillStyle='#FFF'; ctx.fillRect(x+3,y+14,3,2);
241
+ ctx.fillStyle='#E74C3C'; ctx.fillRect(x+8,y+14,3,2);
242
+ }
243
+ _drawWhiteboard(ctx, x, y, w, h) {
244
+ ctx.fillStyle='#A0A0A0'; ctx.fillRect(x-2,y-2,w+4,h+4);
245
+ ctx.fillStyle='#F8F8F8'; ctx.fillRect(x,y,w,h);
246
+ ctx.fillStyle='#4A90D9'; ctx.fillRect(x+4,y+7,22,2); ctx.fillRect(x+4,y+12,30,2);
247
+ ctx.fillStyle='#E74C3C'; ctx.fillRect(x+40,y+7,12,12);
248
+ ctx.fillStyle='#2ECC71'; ctx.fillRect(x+4,y+24,25,2); ctx.fillRect(x+4,y+30,35,2);
249
+ }
250
+ _drawClock(ctx, x, y) {
251
+ ctx.fillStyle='#333'; ctx.beginPath(); ctx.arc(x,y+10,11,0,Math.PI*2); ctx.fill();
252
+ ctx.fillStyle='#F8F8F8'; ctx.beginPath(); ctx.arc(x,y+10,9,0,Math.PI*2); ctx.fill();
253
+ const now=new Date(), hr=now.getHours()%12, mn=now.getMinutes();
254
+ ctx.strokeStyle='#333'; ctx.lineWidth=1.5;
255
+ const ha=(hr+mn/60)*Math.PI/6-Math.PI/2;
256
+ ctx.beginPath(); ctx.moveTo(x,y+10); ctx.lineTo(x+Math.cos(ha)*5,y+10+Math.sin(ha)*5); ctx.stroke();
257
+ ctx.lineWidth=1; const ma=mn*Math.PI/30-Math.PI/2;
258
+ ctx.beginPath(); ctx.moveTo(x,y+10); ctx.lineTo(x+Math.cos(ma)*7,y+10+Math.sin(ma)*7); ctx.stroke();
259
+ ctx.fillStyle='#E74C3C'; ctx.beginPath(); ctx.arc(x,y+10,1.5,0,Math.PI*2); ctx.fill();
260
+ }
261
+
262
+ // ── Side-view floor decoration draw methods ──
263
+ // Furniture is pressed flat against the left/right wall.
264
+ // "depth" is the thin dimension going into the room; "h" is the tall front face.
265
+
266
+ _drawSideBookshelf(ctx, x, y, depth, h, side) {
267
+ // Side panel (visible depth going into room)
268
+ const frameColor = '#6B4226', backColor = '#8B5E3C', shelfColor = '#5D3A1A';
269
+ const bookColors = ['#E74C3C','#3498DB','#2ECC71','#F39C12','#9B59B6','#1ABC9C','#E67E22'];
270
+ // Outer frame
271
+ ctx.fillStyle = frameColor; ctx.fillRect(x, y, depth, h);
272
+ // Inner back
273
+ ctx.fillStyle = backColor; ctx.fillRect(x + 2, y + 2, depth - 4, h - 4);
274
+ // Front-facing edge (the side we see is the narrow depth)
275
+ const edgeX = side === 'left' ? x + depth - 3 : x;
276
+ ctx.fillStyle = shelfColor; ctx.fillRect(edgeX, y, 3, h);
277
+ // Shelves — horizontal dividers
278
+ const shelfCount = 5;
279
+ const shelfGap = Math.floor((h - 6) / shelfCount);
280
+ for (let i = 0; i < shelfCount; i++) {
281
+ const sy = y + 4 + i * shelfGap;
282
+ ctx.fillStyle = shelfColor; ctx.fillRect(x + 2, sy, depth - 4, 2);
283
+ // Books — vertical spines visible from the side
284
+ for (let b = 0; b < 3; b++) {
285
+ const bh = shelfGap - 6;
286
+ const bw = 2 + (b % 2);
287
+ const bx = x + 4 + b * (bw + 1);
288
+ ctx.fillStyle = bookColors[(i * 3 + b) % bookColors.length];
289
+ ctx.fillRect(bx, sy - bh, bw, bh);
290
+ }
291
+ }
292
+ // Top/bottom trim
293
+ ctx.fillStyle = shelfColor;
294
+ ctx.fillRect(x - 1, y - 2, depth + 2, 3);
295
+ ctx.fillRect(x - 1, y + h - 1, depth + 2, 3);
296
+ }
297
+
298
+ _drawSideSofa(ctx, x, y, depth, h, side) {
299
+ // Side-view sofa: we see the arm rest + cushion profile
300
+ const sofaColor = '#5B7BA5', sofaDark = '#4A6A8E', sofaLight = '#6B8BB5';
301
+ // Arm rest (the full side face)
302
+ ctx.fillStyle = sofaDark; ctx.fillRect(x, y, depth, h);
303
+ // Inner cushion area
304
+ ctx.fillStyle = sofaColor; ctx.fillRect(x + 3, y + 6, depth - 6, h - 10);
305
+ // Back rest (top portion)
306
+ ctx.fillStyle = sofaDark; ctx.fillRect(x + 2, y, depth - 4, 10);
307
+ ctx.fillStyle = sofaLight; ctx.fillRect(x + 4, y + 2, depth - 8, 6);
308
+ // Seat cushion profile
309
+ ctx.fillStyle = sofaLight; ctx.fillRect(x + 5, y + 14, depth - 10, h - 22);
310
+ // Cushion seam — horizontal line
311
+ ctx.fillStyle = sofaDark;
312
+ ctx.fillRect(x + 5, y + Math.floor(h * 0.45), depth - 10, 1);
313
+ ctx.fillRect(x + 5, y + Math.floor(h * 0.65), depth - 10, 1);
314
+ // Legs
315
+ ctx.fillStyle = '#4A3728';
316
+ ctx.fillRect(x + 4, y + h - 2, 4, 4);
317
+ ctx.fillRect(x + depth - 8, y + h - 2, 4, 4);
318
+ // Pillow leaning against back
319
+ ctx.fillStyle = '#E8D4B8';
320
+ const px = side === 'left' ? x + depth - 12 : x + 4;
321
+ ctx.fillRect(px, y + 4, 8, 12);
322
+ ctx.fillStyle = '#D4C0A4'; ctx.fillRect(px + 1, y + 5, 6, 10);
323
+ }
324
+
325
+ _drawSideFridge(ctx, x, y, depth, h, side) {
326
+ // Side-view fridge: we see the narrow side panel
327
+ ctx.fillStyle = '#C8CDD3'; ctx.fillRect(x, y, depth, h); // side panel
328
+ ctx.fillStyle = '#D0D5DB'; ctx.fillRect(x + 2, y + 2, depth - 4, h - 4); // inset
329
+ // Door edge line (front-facing edge)
330
+ const edgeX = side === 'right' ? x : x + depth - 3;
331
+ ctx.fillStyle = '#A0A8B0'; ctx.fillRect(edgeX, y, 3, h);
332
+ // Door split line
333
+ ctx.fillStyle = '#A0A8B0'; ctx.fillRect(x, y + Math.floor(h * 0.6), depth, 2);
334
+ // Handle on front edge
335
+ ctx.fillStyle = '#888';
336
+ ctx.fillRect(edgeX + 1, y + Math.floor(h * 0.25), 1, 8);
337
+ ctx.fillRect(edgeX + 1, y + Math.floor(h * 0.72), 1, 6);
338
+ // Top surface
339
+ ctx.fillStyle = '#E0E5EB'; ctx.fillRect(x, y - 2, depth, 3);
340
+ // Brand sticker on side
341
+ ctx.fillStyle = '#4A90D9'; ctx.fillRect(x + 6, y + 5, 8, 3);
342
+ // Magnet
343
+ ctx.fillStyle = '#E74C3C'; ctx.fillRect(x + 5, y + 14, 3, 3);
344
+ ctx.fillStyle = '#F1C40F'; ctx.fillRect(x + 12, y + 12, 3, 3);
345
+ }
346
+
347
+ _drawSideBar(ctx, x, y, depth, h, side) {
348
+ // Side-view bar counter: we see the narrow profile
349
+ const woodColor = '#7B5B3A', woodDark = '#5D3A1A', woodLight = '#9B7B5A';
350
+ // Counter top (narrow strip)
351
+ ctx.fillStyle = woodLight; ctx.fillRect(x, y, depth, 4);
352
+ ctx.fillStyle = woodColor; ctx.fillRect(x, y + 4, depth, h - 4);
353
+ // Wood grain — horizontal lines on side panel
354
+ ctx.fillStyle = woodDark;
355
+ for (let ly = y + 12; ly < y + h; ly += 12) {
356
+ ctx.fillRect(x + 2, ly, depth - 4, 1);
357
+ }
358
+ // Front-facing edge
359
+ const edgeX = side === 'right' ? x : x + depth - 3;
360
+ ctx.fillStyle = woodDark; ctx.fillRect(edgeX, y, 3, h);
361
+ // Top highlight
362
+ ctx.fillStyle = woodLight; ctx.fillRect(x, y, depth, 2);
363
+ // Items on top of counter — bottles (seen from side)
364
+ ctx.fillStyle = '#2ECC71'; ctx.fillRect(x + 4, y - 12, 4, 12);
365
+ ctx.fillStyle = '#27AE60'; ctx.fillRect(x + 5, y - 14, 2, 3);
366
+ ctx.fillStyle = '#E74C3C'; ctx.fillRect(x + 12, y - 10, 4, 10);
367
+ ctx.fillStyle = '#C0392B'; ctx.fillRect(x + 13, y - 12, 2, 3);
368
+ // Cup
369
+ ctx.fillStyle = '#FFF'; ctx.fillRect(x + 8, y - 6, 3, 6);
370
+ ctx.fillStyle = '#87CEEB'; ctx.fillRect(x + 9, y - 4, 1, 3);
371
+ // Bar stool in front (facing into room)
372
+ const stoolX = side === 'right' ? x - 14 : x + depth + 4;
373
+ this._drawSideBarStool(ctx, stoolX, y + h - 8);
374
+ this._drawSideBarStool(ctx, stoolX, y + h - 38);
375
+ }
376
+
377
+ _drawSideBarStool(ctx, x, y) {
378
+ ctx.fillStyle = '#4A4A4A'; ctx.fillRect(x + 3, y, 2, 12); // stem
379
+ ctx.fillStyle = '#333'; ctx.fillRect(x, y - 3, 8, 4); // seat top
380
+ ctx.fillStyle = '#555'; ctx.fillRect(x + 1, y + 11, 6, 2); // base
381
+ }
382
+
383
+ _drawSideCoffeeMachine(ctx, x, y, depth, h, side) {
384
+ // Side-view espresso machine sitting on the bar counter
385
+ ctx.fillStyle = '#708090'; ctx.fillRect(x, y, depth, h);
386
+ ctx.fillStyle = '#5A6A7A'; ctx.fillRect(x + 2, y + 2, depth - 4, 8);
387
+ // Power LED
388
+ ctx.fillStyle = '#2ECC71'; ctx.fillRect(x + 4, y + 5, 2, 2);
389
+ // Drip area
390
+ ctx.fillStyle = '#555'; ctx.fillRect(x + 3, y + 12, depth - 6, 10);
391
+ ctx.fillStyle = '#444'; ctx.fillRect(x + 5, y + 14, depth - 10, 6);
392
+ // Cup
393
+ ctx.fillStyle = '#FFF'; ctx.fillRect(x + 5, y + 18, 5, 4);
394
+ ctx.fillStyle = '#8B4513'; ctx.fillRect(x + 6, y + 19, 3, 2);
395
+ // Steam wisps
396
+ ctx.fillStyle = 'rgba(200,200,200,0.4)';
397
+ ctx.fillRect(x + 6, y + 15, 1, 3);
398
+ ctx.fillRect(x + 9, y + 14, 1, 3);
399
+ // Base
400
+ ctx.fillStyle = '#5A6A7A'; ctx.fillRect(x - 1, y + h - 2, depth + 2, 3);
401
+ }
402
+ }
403
+
404
+ if (typeof window !== 'undefined') window.OfficeScene = OfficeScene;