zeroed-some/dougk / 5fa8df1

Browse files

more outfits

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
5fa8df1a139b99845a4df533c43e702970cd3e44
Parents
59f1865
Tree
23cc7b5

4 changed files

StatusFile+-
M src/renderers/three/duck.js 23 1
M src/renderers/three/narwhal.js 23 1
M src/renderers/three/octopus.js 23 1
M src/renderers/three/shop/items.js 815 1
src/renderers/three/duck.jsmodified
@@ -34,7 +34,8 @@ export function createDoug(scene, gradientMap) {
3434
   // Accessory tracking
3535
   const accessories = {
3636
     head: null,
37
-    face: null
37
+    face: null,
38
+    clothing: null
3839
   }
3940
 
4041
   // Mount points for accessories
@@ -501,6 +502,20 @@ export function createDoug(scene, gradientMap) {
501502
           group.add(accessories.face)
502503
         }
503504
         break
505
+
506
+      case 'clothing_body':
507
+        // Remove existing clothing
508
+        if (accessories.clothing) {
509
+          group.remove(accessories.clothing)
510
+          accessories.clothing = null
511
+        }
512
+        // Add new clothing
513
+        if (outfit.meshFactory) {
514
+          accessories.clothing = outfit.meshFactory(storedGradientMap)
515
+          // Clothing is positioned relative to body origin
516
+          group.add(accessories.clothing)
517
+        }
518
+        break
504519
     }
505520
   }
506521
 
@@ -531,6 +546,13 @@ export function createDoug(scene, gradientMap) {
531546
           accessories.face = null
532547
         }
533548
         break
549
+
550
+      case 'clothing_body':
551
+        if (accessories.clothing) {
552
+          group.remove(accessories.clothing)
553
+          accessories.clothing = null
554
+        }
555
+        break
534556
     }
535557
   }
536558
 
src/renderers/three/narwhal.jsmodified
@@ -44,7 +44,8 @@ export function createDonny(scene, gradientMap) {
4444
 
4545
   // Accessory tracking
4646
   const accessories = {
47
-    head: null
47
+    head: null,
48
+    clothing: null
4849
   }
4950
 
5051
   // Mount points
@@ -544,6 +545,20 @@ export function createDonny(scene, gradientMap) {
544545
           group.add(accessories.head)
545546
         }
546547
         break
548
+
549
+      case 'clothing_body':
550
+        // Remove existing clothing
551
+        if (accessories.clothing) {
552
+          group.remove(accessories.clothing)
553
+          accessories.clothing = null
554
+        }
555
+        // Add new clothing
556
+        if (outfit.meshFactory) {
557
+          accessories.clothing = outfit.meshFactory(storedGradientMap)
558
+          // Clothing is positioned relative to body origin
559
+          group.add(accessories.clothing)
560
+        }
561
+        break
547562
     }
548563
   }
549564
 
@@ -568,6 +583,13 @@ export function createDonny(scene, gradientMap) {
568583
           accessories.head = null
569584
         }
570585
         break
586
+
587
+      case 'clothing_body':
588
+        if (accessories.clothing) {
589
+          group.remove(accessories.clothing)
590
+          accessories.clothing = null
591
+        }
592
+        break
571593
     }
572594
   }
573595
 
src/renderers/three/octopus.jsmodified
@@ -45,7 +45,8 @@ export function createOllie(scene, gradientMap) {
4545
 
4646
   // Accessory tracking
4747
   const accessories = {
48
-    head: null
48
+    head: null,
49
+    clothing: null
4950
   }
5051
 
5152
   // Mount points
@@ -629,6 +630,20 @@ export function createOllie(scene, gradientMap) {
629630
           group.add(accessories.head)
630631
         }
631632
         break
633
+
634
+      case 'clothing_body':
635
+        // Remove existing clothing
636
+        if (accessories.clothing) {
637
+          group.remove(accessories.clothing)
638
+          accessories.clothing = null
639
+        }
640
+        // Add new clothing
641
+        if (outfit.meshFactory) {
642
+          accessories.clothing = outfit.meshFactory(storedGradientMap)
643
+          // Clothing is positioned relative to body origin
644
+          group.add(accessories.clothing)
645
+        }
646
+        break
632647
     }
633648
   }
634649
 
@@ -654,6 +669,13 @@ export function createOllie(scene, gradientMap) {
654669
           accessories.head = null
655670
         }
656671
         break
672
+
673
+      case 'clothing_body':
674
+        if (accessories.clothing) {
675
+          group.remove(accessories.clothing)
676
+          accessories.clothing = null
677
+        }
678
+        break
657679
     }
658680
   }
659681
 
src/renderers/three/shop/items.jsmodified
@@ -9,7 +9,8 @@ export const OUTFIT_TYPES = {
99
   COLOR_ACCENT: 'color_accent',
1010
   ACCESSORY_HEAD: 'accessory_head',
1111
   ACCESSORY_FACE: 'accessory_face',
12
-  ACCESSORY_HELD: 'accessory_held'
12
+  ACCESSORY_HELD: 'accessory_held',
13
+  CLOTHING_BODY: 'clothing_body'  // Actual rendered clothing meshes
1314
 }
1415
 
1516
 // Character IDs
@@ -127,6 +128,412 @@ export const OUTFITS = {
127128
     }
128129
   },
129130
 
131
+  // === DOUG CLOTHING - Actual rendered garments ===
132
+
133
+  // Simple tier - basic clothing
134
+  doug_tee_red: {
135
+    id: 'doug_tee_red',
136
+    name: 'Red T-Shirt',
137
+    character: CHARACTERS.DOUG,
138
+    type: OUTFIT_TYPES.CLOTHING_BODY,
139
+    price: 35,
140
+    meshFactory: (gradientMap) => {
141
+      const group = new THREE.Group()
142
+      const fabricMat = new THREE.MeshToonMaterial({ color: 0xcc3333, gradientMap })
143
+
144
+      // Main shirt body - wraps around duck torso
145
+      const torsoGeom = new THREE.SphereGeometry(0.44, 10, 8)
146
+      torsoGeom.scale(1.35, 0.72, 0.88)
147
+      const torso = new THREE.Mesh(torsoGeom, fabricMat)
148
+      torso.position.set(0, 0.28, 0)
149
+      group.add(torso)
150
+
151
+      // Collar (V-neck)
152
+      const collarMat = new THREE.MeshToonMaterial({ color: 0xaa2222, gradientMap })
153
+      const collarGeom = new THREE.TorusGeometry(0.12, 0.025, 4, 8, Math.PI)
154
+      const collar = new THREE.Mesh(collarGeom, collarMat)
155
+      collar.position.set(0.35, 0.42, 0)
156
+      collar.rotation.z = -Math.PI / 2
157
+      collar.rotation.y = Math.PI / 2
158
+      group.add(collar)
159
+
160
+      // Short sleeves
161
+      const sleeveGeom = new THREE.CylinderGeometry(0.08, 0.1, 0.12, 6)
162
+      const leftSleeve = new THREE.Mesh(sleeveGeom, fabricMat)
163
+      leftSleeve.position.set(0.05, 0.32, 0.36)
164
+      leftSleeve.rotation.x = Math.PI / 2
165
+      leftSleeve.rotation.z = 0.3
166
+      group.add(leftSleeve)
167
+
168
+      const rightSleeve = new THREE.Mesh(sleeveGeom, fabricMat)
169
+      rightSleeve.position.set(0.05, 0.32, -0.36)
170
+      rightSleeve.rotation.x = -Math.PI / 2
171
+      rightSleeve.rotation.z = 0.3
172
+      group.add(rightSleeve)
173
+
174
+      return group
175
+    }
176
+  },
177
+
178
+  doug_sailor_shirt: {
179
+    id: 'doug_sailor_shirt',
180
+    name: 'Sailor Stripes',
181
+    character: CHARACTERS.DOUG,
182
+    type: OUTFIT_TYPES.CLOTHING_BODY,
183
+    price: 55,
184
+    meshFactory: (gradientMap) => {
185
+      const group = new THREE.Group()
186
+      const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
187
+      const blueMat = new THREE.MeshToonMaterial({ color: 0x1e3a5f, gradientMap })
188
+
189
+      // Main shirt body
190
+      const torsoGeom = new THREE.SphereGeometry(0.44, 10, 8)
191
+      torsoGeom.scale(1.35, 0.72, 0.88)
192
+      const torso = new THREE.Mesh(torsoGeom, whiteMat)
193
+      torso.position.set(0, 0.28, 0)
194
+      group.add(torso)
195
+
196
+      // Horizontal stripes (rings around body)
197
+      for (let i = 0; i < 4; i++) {
198
+        const stripeGeom = new THREE.TorusGeometry(0.38 - i * 0.02, 0.02, 4, 16)
199
+        const stripe = new THREE.Mesh(stripeGeom, blueMat)
200
+        stripe.position.set(-0.05 + i * 0.12, 0.18 + i * 0.06, 0)
201
+        stripe.rotation.y = Math.PI / 2
202
+        stripe.rotation.z = 0.1
203
+        group.add(stripe)
204
+      }
205
+
206
+      // Sailor collar (big square back collar)
207
+      const collarGeom = new THREE.BoxGeometry(0.35, 0.02, 0.4)
208
+      const collar = new THREE.Mesh(collarGeom, blueMat)
209
+      collar.position.set(-0.15, 0.48, 0)
210
+      group.add(collar)
211
+
212
+      // Collar flaps hanging down back
213
+      const flapGeom = new THREE.BoxGeometry(0.18, 0.2, 0.02)
214
+      const leftFlap = new THREE.Mesh(flapGeom, blueMat)
215
+      leftFlap.position.set(-0.2, 0.38, 0.18)
216
+      leftFlap.rotation.x = 0.2
217
+      group.add(leftFlap)
218
+
219
+      const rightFlap = new THREE.Mesh(flapGeom, blueMat)
220
+      rightFlap.position.set(-0.2, 0.38, -0.18)
221
+      rightFlap.rotation.x = -0.2
222
+      group.add(rightFlap)
223
+
224
+      // Red neckerchief
225
+      const tieMat = new THREE.MeshToonMaterial({ color: 0xcc2222, gradientMap })
226
+      const tieGeom = new THREE.ConeGeometry(0.06, 0.15, 4)
227
+      const tie = new THREE.Mesh(tieGeom, tieMat)
228
+      tie.position.set(0.32, 0.32, 0)
229
+      tie.rotation.z = Math.PI
230
+      group.add(tie)
231
+
232
+      return group
233
+    }
234
+  },
235
+
236
+  doug_cozy_sweater: {
237
+    id: 'doug_cozy_sweater',
238
+    name: 'Cozy Sweater',
239
+    character: CHARACTERS.DOUG,
240
+    type: OUTFIT_TYPES.CLOTHING_BODY,
241
+    price: 70,
242
+    meshFactory: (gradientMap) => {
243
+      const group = new THREE.Group()
244
+      const woolMat = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap })
245
+      const trimMat = new THREE.MeshToonMaterial({ color: 0xdaa520, gradientMap })
246
+
247
+      // Chunky sweater body
248
+      const torsoGeom = new THREE.SphereGeometry(0.46, 10, 8)
249
+      torsoGeom.scale(1.38, 0.75, 0.92)
250
+      const torso = new THREE.Mesh(torsoGeom, woolMat)
251
+      torso.position.set(0, 0.28, 0)
252
+      group.add(torso)
253
+
254
+      // Ribbed collar (turtleneck)
255
+      const collarGeom = new THREE.CylinderGeometry(0.14, 0.16, 0.1, 8)
256
+      const collar = new THREE.Mesh(collarGeom, woolMat)
257
+      collar.position.set(0.38, 0.52, 0)
258
+      collar.rotation.z = -0.1
259
+      group.add(collar)
260
+
261
+      // Cable knit pattern (raised lines)
262
+      for (let i = 0; i < 3; i++) {
263
+        const cableGeom = new THREE.CylinderGeometry(0.015, 0.015, 0.5, 4)
264
+        const cable = new THREE.Mesh(cableGeom, trimMat)
265
+        cable.position.set(0.1, 0.28, -0.25 + i * 0.25)
266
+        cable.rotation.x = Math.PI / 2
267
+        cable.rotation.z = 0.2
268
+        group.add(cable)
269
+      }
270
+
271
+      // Long sleeves
272
+      const sleeveGeom = new THREE.CylinderGeometry(0.09, 0.11, 0.2, 6)
273
+      const leftSleeve = new THREE.Mesh(sleeveGeom, woolMat)
274
+      leftSleeve.position.set(-0.05, 0.30, 0.38)
275
+      leftSleeve.rotation.x = Math.PI / 2
276
+      leftSleeve.rotation.z = 0.4
277
+      group.add(leftSleeve)
278
+
279
+      const rightSleeve = new THREE.Mesh(sleeveGeom, woolMat)
280
+      rightSleeve.position.set(-0.05, 0.30, -0.38)
281
+      rightSleeve.rotation.x = -Math.PI / 2
282
+      rightSleeve.rotation.z = 0.4
283
+      group.add(rightSleeve)
284
+
285
+      // Bottom ribbing
286
+      const ribGeom = new THREE.TorusGeometry(0.42, 0.03, 4, 16)
287
+      const rib = new THREE.Mesh(ribGeom, trimMat)
288
+      rib.position.set(-0.1, 0.08, 0)
289
+      rib.rotation.y = Math.PI / 2
290
+      group.add(rib)
291
+
292
+      return group
293
+    }
294
+  },
295
+
296
+  doug_captain_jacket: {
297
+    id: 'doug_captain_jacket',
298
+    name: "Captain's Jacket",
299
+    character: CHARACTERS.DOUG,
300
+    type: OUTFIT_TYPES.CLOTHING_BODY,
301
+    price: 95,
302
+    meshFactory: (gradientMap) => {
303
+      const group = new THREE.Group()
304
+      const navyMat = new THREE.MeshToonMaterial({ color: 0x1a1a3a, gradientMap })
305
+      const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
306
+      const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
307
+
308
+      // Jacket body
309
+      const torsoGeom = new THREE.SphereGeometry(0.45, 10, 8)
310
+      torsoGeom.scale(1.4, 0.76, 0.9)
311
+      const torso = new THREE.Mesh(torsoGeom, navyMat)
312
+      torso.position.set(0, 0.28, 0)
313
+      group.add(torso)
314
+
315
+      // Jacket front panels (double-breasted)
316
+      const panelGeom = new THREE.BoxGeometry(0.08, 0.35, 0.25)
317
+      const leftPanel = new THREE.Mesh(panelGeom, navyMat)
318
+      leftPanel.position.set(0.38, 0.28, 0.12)
319
+      group.add(leftPanel)
320
+
321
+      const rightPanel = new THREE.Mesh(panelGeom, navyMat)
322
+      rightPanel.position.set(0.38, 0.28, -0.12)
323
+      group.add(rightPanel)
324
+
325
+      // Gold buttons (double row)
326
+      const buttonGeom = new THREE.SphereGeometry(0.025, 6, 4)
327
+      const buttonPositions = [
328
+        { x: 0.42, y: 0.38, z: 0.08 }, { x: 0.42, y: 0.28, z: 0.08 }, { x: 0.42, y: 0.18, z: 0.08 },
329
+        { x: 0.42, y: 0.38, z: -0.08 }, { x: 0.42, y: 0.28, z: -0.08 }, { x: 0.42, y: 0.18, z: -0.08 }
330
+      ]
331
+      for (const pos of buttonPositions) {
332
+        const button = new THREE.Mesh(buttonGeom, goldMat)
333
+        button.position.set(pos.x, pos.y, pos.z)
334
+        group.add(button)
335
+      }
336
+
337
+      // Epaulettes (shoulder boards)
338
+      const epauletteGeom = new THREE.BoxGeometry(0.08, 0.02, 0.12)
339
+      const leftEpaulette = new THREE.Mesh(epauletteGeom, goldMat)
340
+      leftEpaulette.position.set(0.1, 0.45, 0.32)
341
+      group.add(leftEpaulette)
342
+
343
+      const rightEpaulette = new THREE.Mesh(epauletteGeom, goldMat)
344
+      rightEpaulette.position.set(0.1, 0.45, -0.32)
345
+      group.add(rightEpaulette)
346
+
347
+      // High collar
348
+      const collarGeom = new THREE.CylinderGeometry(0.13, 0.15, 0.08, 8, 1, true)
349
+      const collar = new THREE.Mesh(collarGeom, navyMat)
350
+      collar.position.set(0.36, 0.50, 0)
351
+      collar.rotation.z = -0.1
352
+      group.add(collar)
353
+
354
+      // Gold collar trim
355
+      const collarTrimGeom = new THREE.TorusGeometry(0.14, 0.012, 4, 12)
356
+      const collarTrim = new THREE.Mesh(collarTrimGeom, goldMat)
357
+      collarTrim.position.set(0.36, 0.54, 0)
358
+      collarTrim.rotation.x = Math.PI / 2
359
+      collarTrim.rotation.z = -0.1
360
+      group.add(collarTrim)
361
+
362
+      // Sleeves with gold cuff trim
363
+      const sleeveGeom = new THREE.CylinderGeometry(0.09, 0.1, 0.18, 6)
364
+      const leftSleeve = new THREE.Mesh(sleeveGeom, navyMat)
365
+      leftSleeve.position.set(-0.02, 0.30, 0.38)
366
+      leftSleeve.rotation.x = Math.PI / 2
367
+      leftSleeve.rotation.z = 0.35
368
+      group.add(leftSleeve)
369
+
370
+      const rightSleeve = new THREE.Mesh(sleeveGeom, navyMat)
371
+      rightSleeve.position.set(-0.02, 0.30, -0.38)
372
+      rightSleeve.rotation.x = -Math.PI / 2
373
+      rightSleeve.rotation.z = 0.35
374
+      group.add(rightSleeve)
375
+
376
+      // Gold cuff rings
377
+      const cuffGeom = new THREE.TorusGeometry(0.095, 0.015, 4, 8)
378
+      const leftCuff = new THREE.Mesh(cuffGeom, goldMat)
379
+      leftCuff.position.set(-0.08, 0.26, 0.44)
380
+      leftCuff.rotation.y = Math.PI / 2 + 0.35
381
+      group.add(leftCuff)
382
+
383
+      const rightCuff = new THREE.Mesh(cuffGeom, goldMat)
384
+      rightCuff.position.set(-0.08, 0.26, -0.44)
385
+      rightCuff.rotation.y = Math.PI / 2 - 0.35
386
+      group.add(rightCuff)
387
+
388
+      return group
389
+    }
390
+  },
391
+
392
+  doug_pirate_captain: {
393
+    id: 'doug_pirate_captain',
394
+    name: 'Pirate Captain',
395
+    character: CHARACTERS.DOUG,
396
+    type: OUTFIT_TYPES.CLOTHING_BODY,
397
+    price: 150,
398
+    meshFactory: (gradientMap) => {
399
+      const group = new THREE.Group()
400
+      const coatMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
401
+      const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
402
+      const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
403
+      const whiteMat = new THREE.MeshToonMaterial({ color: 0xf5f5dc, gradientMap })
404
+      const beltMat = new THREE.MeshToonMaterial({ color: 0x4a3020, gradientMap })
405
+
406
+      // Inner white shirt (ruffle front)
407
+      const shirtGeom = new THREE.SphereGeometry(0.42, 10, 8)
408
+      shirtGeom.scale(1.3, 0.7, 0.85)
409
+      const shirt = new THREE.Mesh(shirtGeom, whiteMat)
410
+      shirt.position.set(0, 0.28, 0)
411
+      group.add(shirt)
412
+
413
+      // Ruffle details on shirt front
414
+      for (let i = 0; i < 4; i++) {
415
+        const ruffleGeom = new THREE.TorusGeometry(0.06 - i * 0.008, 0.015, 4, 8, Math.PI)
416
+        const ruffle = new THREE.Mesh(ruffleGeom, whiteMat)
417
+        ruffle.position.set(0.36, 0.35 - i * 0.06, 0)
418
+        ruffle.rotation.y = Math.PI / 2
419
+        ruffle.rotation.z = -Math.PI / 2
420
+        group.add(ruffle)
421
+      }
422
+
423
+      // Long pirate coat (open front)
424
+      const coatLeftGeom = new THREE.BoxGeometry(0.1, 0.45, 0.35)
425
+      const coatLeft = new THREE.Mesh(coatLeftGeom, coatMat)
426
+      coatLeft.position.set(0.15, 0.22, 0.25)
427
+      coatLeft.rotation.y = 0.15
428
+      group.add(coatLeft)
429
+
430
+      const coatRightGeom = new THREE.BoxGeometry(0.1, 0.45, 0.35)
431
+      const coatRight = new THREE.Mesh(coatRightGeom, coatMat)
432
+      coatRight.position.set(0.15, 0.22, -0.25)
433
+      coatRight.rotation.y = -0.15
434
+      group.add(coatRight)
435
+
436
+      // Coat back
437
+      const coatBackGeom = new THREE.BoxGeometry(0.08, 0.5, 0.55)
438
+      const coatBack = new THREE.Mesh(coatBackGeom, coatMat)
439
+      coatBack.position.set(-0.25, 0.25, 0)
440
+      group.add(coatBack)
441
+
442
+      // Coat tails (flowing behind)
443
+      const tailGeom = new THREE.BoxGeometry(0.06, 0.25, 0.2)
444
+      const leftTail = new THREE.Mesh(tailGeom, coatMat)
445
+      leftTail.position.set(-0.35, 0.08, 0.15)
446
+      leftTail.rotation.x = 0.2
447
+      leftTail.rotation.z = 0.1
448
+      group.add(leftTail)
449
+
450
+      const rightTail = new THREE.Mesh(tailGeom, coatMat)
451
+      rightTail.position.set(-0.35, 0.08, -0.15)
452
+      rightTail.rotation.x = -0.2
453
+      rightTail.rotation.z = 0.1
454
+      group.add(rightTail)
455
+
456
+      // Gold trim on coat edges
457
+      const trimGeom = new THREE.BoxGeometry(0.02, 0.4, 0.02)
458
+      const trimPositions = [
459
+        { x: 0.22, y: 0.22, z: 0.42 }, { x: 0.22, y: 0.22, z: -0.42 },
460
+        { x: 0.22, y: 0.22, z: 0.08 }, { x: 0.22, y: 0.22, z: -0.08 }
461
+      ]
462
+      for (const pos of trimPositions) {
463
+        const trim = new THREE.Mesh(trimGeom, goldMat)
464
+        trim.position.set(pos.x, pos.y, pos.z)
465
+        group.add(trim)
466
+      }
467
+
468
+      // Wide leather belt
469
+      const beltGeom = new THREE.TorusGeometry(0.4, 0.035, 4, 16)
470
+      const belt = new THREE.Mesh(beltGeom, beltMat)
471
+      belt.position.set(0, 0.15, 0)
472
+      belt.rotation.y = Math.PI / 2
473
+      belt.rotation.z = 0.05
474
+      group.add(belt)
475
+
476
+      // Belt buckle (skull shape simplified)
477
+      const buckleGeom = new THREE.BoxGeometry(0.08, 0.08, 0.03)
478
+      const buckle = new THREE.Mesh(buckleGeom, goldMat)
479
+      buckle.position.set(0.38, 0.15, 0)
480
+      group.add(buckle)
481
+
482
+      // Skull detail on buckle
483
+      const skullGeom = new THREE.SphereGeometry(0.025, 6, 4)
484
+      const skull = new THREE.Mesh(skullGeom, whiteMat)
485
+      skull.position.set(0.40, 0.15, 0)
486
+      group.add(skull)
487
+
488
+      // Diagonal bandolier strap
489
+      const strapGeom = new THREE.BoxGeometry(0.5, 0.04, 0.03)
490
+      const strap = new THREE.Mesh(strapGeom, beltMat)
491
+      strap.position.set(0.05, 0.32, 0)
492
+      strap.rotation.z = 0.6
493
+      group.add(strap)
494
+
495
+      // Coat sleeves
496
+      const sleeveGeom = new THREE.CylinderGeometry(0.1, 0.11, 0.22, 6)
497
+      const leftSleeve = new THREE.Mesh(sleeveGeom, coatMat)
498
+      leftSleeve.position.set(-0.05, 0.30, 0.4)
499
+      leftSleeve.rotation.x = Math.PI / 2
500
+      leftSleeve.rotation.z = 0.4
501
+      group.add(leftSleeve)
502
+
503
+      const rightSleeve = new THREE.Mesh(sleeveGeom, coatMat)
504
+      rightSleeve.position.set(-0.05, 0.30, -0.4)
505
+      rightSleeve.rotation.x = -Math.PI / 2
506
+      rightSleeve.rotation.z = 0.4
507
+      group.add(rightSleeve)
508
+
509
+      // Sleeve cuff ruffles
510
+      const cuffGeom = new THREE.TorusGeometry(0.1, 0.02, 4, 8)
511
+      const leftCuff = new THREE.Mesh(cuffGeom, whiteMat)
512
+      leftCuff.position.set(-0.12, 0.26, 0.48)
513
+      leftCuff.rotation.y = Math.PI / 2 + 0.4
514
+      group.add(leftCuff)
515
+
516
+      const rightCuff = new THREE.Mesh(cuffGeom, whiteMat)
517
+      rightCuff.position.set(-0.12, 0.26, -0.48)
518
+      rightCuff.rotation.y = Math.PI / 2 - 0.4
519
+      group.add(rightCuff)
520
+
521
+      // Gold buttons on coat
522
+      const buttonGeom = new THREE.SphereGeometry(0.02, 6, 4)
523
+      for (let i = 0; i < 3; i++) {
524
+        const leftBtn = new THREE.Mesh(buttonGeom, goldMat)
525
+        leftBtn.position.set(0.2, 0.35 - i * 0.08, 0.1)
526
+        group.add(leftBtn)
527
+
528
+        const rightBtn = new THREE.Mesh(buttonGeom, goldMat)
529
+        rightBtn.position.set(0.2, 0.35 - i * 0.08, -0.1)
530
+        group.add(rightBtn)
531
+      }
532
+
533
+      return group
534
+    }
535
+  },
536
+
130537
   // Donny outfits - starter tier
131538
   donny_seafoam: {
132539
     id: 'donny_seafoam',
@@ -186,6 +593,197 @@ export const OUTFITS = {
186593
     }
187594
   },
188595
 
596
+  // === DONNY CLOTHING - Narwhal garments ===
597
+
598
+  donny_sailor_vest: {
599
+    id: 'donny_sailor_vest',
600
+    name: 'Sailor Vest',
601
+    character: CHARACTERS.DONNY,
602
+    type: OUTFIT_TYPES.CLOTHING_BODY,
603
+    price: 45,
604
+    meshFactory: (gradientMap) => {
605
+      const group = new THREE.Group()
606
+      const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
607
+      const blueMat = new THREE.MeshToonMaterial({ color: 0x1e3a5f, gradientMap })
608
+
609
+      // Vest body - fits narwhal's torpedo shape
610
+      const vestGeom = new THREE.CylinderGeometry(0.38, 0.42, 0.6, 10)
611
+      const vest = new THREE.Mesh(vestGeom, whiteMat)
612
+      vest.position.set(0, 0.15, 0)
613
+      vest.rotation.x = Math.PI / 2
614
+      group.add(vest)
615
+
616
+      // Blue stripes on vest
617
+      for (let i = 0; i < 3; i++) {
618
+        const stripeGeom = new THREE.TorusGeometry(0.39 + i * 0.01, 0.02, 4, 16)
619
+        const stripe = new THREE.Mesh(stripeGeom, blueMat)
620
+        stripe.position.set(0, -0.1 + i * 0.15, 0)
621
+        stripe.rotation.x = Math.PI / 2
622
+        group.add(stripe)
623
+      }
624
+
625
+      // Collar
626
+      const collarGeom = new THREE.TorusGeometry(0.36, 0.04, 4, 12, Math.PI)
627
+      const collar = new THREE.Mesh(collarGeom, blueMat)
628
+      collar.position.set(0, 0.42, 0.1)
629
+      collar.rotation.x = Math.PI / 2 + 0.3
630
+      group.add(collar)
631
+
632
+      return group
633
+    }
634
+  },
635
+
636
+  donny_admiral_coat: {
637
+    id: 'donny_admiral_coat',
638
+    name: 'Admiral Coat',
639
+    character: CHARACTERS.DONNY,
640
+    type: OUTFIT_TYPES.CLOTHING_BODY,
641
+    price: 100,
642
+    meshFactory: (gradientMap) => {
643
+      const group = new THREE.Group()
644
+      const navyMat = new THREE.MeshToonMaterial({ color: 0x1a1a3a, gradientMap })
645
+      const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
646
+      const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
647
+
648
+      // Main coat body
649
+      const coatGeom = new THREE.CylinderGeometry(0.4, 0.45, 0.7, 10)
650
+      const coat = new THREE.Mesh(coatGeom, navyMat)
651
+      coat.position.set(0, 0.12, 0)
652
+      coat.rotation.x = Math.PI / 2
653
+      group.add(coat)
654
+
655
+      // High collar
656
+      const collarGeom = new THREE.CylinderGeometry(0.38, 0.35, 0.12, 10, 1, true)
657
+      const collar = new THREE.Mesh(collarGeom, navyMat)
658
+      collar.position.set(0, 0.48, 0)
659
+      collar.rotation.x = Math.PI / 2
660
+      group.add(collar)
661
+
662
+      // Gold collar trim
663
+      const collarTrimGeom = new THREE.TorusGeometry(0.36, 0.015, 4, 12)
664
+      const collarTrim = new THREE.Mesh(collarTrimGeom, goldMat)
665
+      collarTrim.position.set(0, 0.54, 0)
666
+      collarTrim.rotation.x = Math.PI / 2
667
+      group.add(collarTrim)
668
+
669
+      // Epaulettes
670
+      const epGeom = new THREE.BoxGeometry(0.15, 0.03, 0.1)
671
+      const leftEp = new THREE.Mesh(epGeom, goldMat)
672
+      leftEp.position.set(0.35, 0.35, 0.2)
673
+      leftEp.rotation.z = 0.3
674
+      group.add(leftEp)
675
+
676
+      const rightEp = new THREE.Mesh(epGeom, goldMat)
677
+      rightEp.position.set(0.35, 0.35, -0.2)
678
+      rightEp.rotation.z = 0.3
679
+      group.add(rightEp)
680
+
681
+      // Gold buttons (row down front)
682
+      const buttonGeom = new THREE.SphereGeometry(0.025, 6, 4)
683
+      for (let i = 0; i < 4; i++) {
684
+        const button = new THREE.Mesh(buttonGeom, goldMat)
685
+        button.position.set(0.1 - i * 0.12, 0.42, 0)
686
+        group.add(button)
687
+      }
688
+
689
+      // Medals/decorations
690
+      const medalGeom = new THREE.CircleGeometry(0.04, 6)
691
+      const medal1 = new THREE.Mesh(medalGeom, goldMat)
692
+      medal1.position.set(0.25, 0.38, 0.15)
693
+      medal1.rotation.y = -0.5
694
+      group.add(medal1)
695
+
696
+      const medal2 = new THREE.Mesh(medalGeom, goldMat)
697
+      medal2.position.set(0.2, 0.38, 0.12)
698
+      medal2.rotation.y = -0.5
699
+      group.add(medal2)
700
+
701
+      return group
702
+    }
703
+  },
704
+
705
+  donny_pirate_vest: {
706
+    id: 'donny_pirate_vest',
707
+    name: 'Pirate Vest',
708
+    character: CHARACTERS.DONNY,
709
+    type: OUTFIT_TYPES.CLOTHING_BODY,
710
+    price: 130,
711
+    meshFactory: (gradientMap) => {
712
+      const group = new THREE.Group()
713
+      const redMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
714
+      const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
715
+      const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
716
+      const beltMat = new THREE.MeshToonMaterial({ color: 0x4a3020, gradientMap })
717
+      const whiteMat = new THREE.MeshToonMaterial({ color: 0xf5f5dc, gradientMap })
718
+
719
+      // Inner shirt
720
+      const shirtGeom = new THREE.CylinderGeometry(0.36, 0.4, 0.55, 10)
721
+      const shirt = new THREE.Mesh(shirtGeom, whiteMat)
722
+      shirt.position.set(0, 0.15, 0)
723
+      shirt.rotation.x = Math.PI / 2
724
+      group.add(shirt)
725
+
726
+      // Vest panels (open front)
727
+      const panelGeom = new THREE.BoxGeometry(0.4, 0.08, 0.25)
728
+      const leftPanel = new THREE.Mesh(panelGeom, redMat)
729
+      leftPanel.position.set(0.1, 0.3, 0.2)
730
+      leftPanel.rotation.z = 0.2
731
+      group.add(leftPanel)
732
+
733
+      const rightPanel = new THREE.Mesh(panelGeom, redMat)
734
+      rightPanel.position.set(0.1, 0.3, -0.2)
735
+      rightPanel.rotation.z = 0.2
736
+      group.add(rightPanel)
737
+
738
+      // Vest back
739
+      const backGeom = new THREE.BoxGeometry(0.5, 0.08, 0.35)
740
+      const back = new THREE.Mesh(backGeom, redMat)
741
+      back.position.set(-0.15, 0.2, 0)
742
+      group.add(back)
743
+
744
+      // Gold trim on vest
745
+      const trimGeom = new THREE.BoxGeometry(0.35, 0.02, 0.02)
746
+      const leftTrim = new THREE.Mesh(trimGeom, goldMat)
747
+      leftTrim.position.set(0.12, 0.32, 0.32)
748
+      leftTrim.rotation.z = 0.2
749
+      group.add(leftTrim)
750
+
751
+      const rightTrim = new THREE.Mesh(trimGeom, goldMat)
752
+      rightTrim.position.set(0.12, 0.32, -0.32)
753
+      rightTrim.rotation.z = 0.2
754
+      group.add(rightTrim)
755
+
756
+      // Wide belt with skull buckle
757
+      const beltGeom = new THREE.TorusGeometry(0.4, 0.05, 4, 16)
758
+      const belt = new THREE.Mesh(beltGeom, beltMat)
759
+      belt.position.set(0, -0.1, 0)
760
+      belt.rotation.x = Math.PI / 2
761
+      group.add(belt)
762
+
763
+      // Buckle
764
+      const buckleGeom = new THREE.BoxGeometry(0.1, 0.08, 0.04)
765
+      const buckle = new THREE.Mesh(buckleGeom, goldMat)
766
+      buckle.position.set(0.38, -0.1, 0)
767
+      group.add(buckle)
768
+
769
+      // Skull on buckle
770
+      const skullGeom = new THREE.SphereGeometry(0.03, 6, 4)
771
+      const skull = new THREE.Mesh(skullGeom, whiteMat)
772
+      skull.position.set(0.40, -0.1, 0)
773
+      group.add(skull)
774
+
775
+      // Bandolier strap
776
+      const strapGeom = new THREE.BoxGeometry(0.6, 0.04, 0.03)
777
+      const strap = new THREE.Mesh(strapGeom, beltMat)
778
+      strap.position.set(0, 0.2, 0)
779
+      strap.rotation.z = 0.5
780
+      strap.rotation.y = Math.PI / 2
781
+      group.add(strap)
782
+
783
+      return group
784
+    }
785
+  },
786
+
189787
   // Ollie outfits
190788
   ollie_coral: {
191789
     id: 'ollie_coral',
@@ -234,6 +832,222 @@ export const OUTFITS = {
234832
       brim.rotation.z = -0.3
235833
       group.add(brim)
236834
 
835
+      return group
836
+    }
837
+  },
838
+
839
+  // === OLLIE CLOTHING - Octopus garments ===
840
+
841
+  ollie_bowtie: {
842
+    id: 'ollie_bowtie',
843
+    name: 'Fancy Bow Tie',
844
+    character: CHARACTERS.OLLIE,
845
+    type: OUTFIT_TYPES.CLOTHING_BODY,
846
+    price: 40,
847
+    meshFactory: (gradientMap) => {
848
+      const group = new THREE.Group()
849
+      const silkMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
850
+      const knotMat = new THREE.MeshToonMaterial({ color: 0x660000, gradientMap })
851
+
852
+      // Bow tie wings
853
+      const wingGeom = new THREE.BoxGeometry(0.12, 0.06, 0.04)
854
+      const leftWing = new THREE.Mesh(wingGeom, silkMat)
855
+      leftWing.position.set(0, 0.65, 0.08)
856
+      leftWing.rotation.z = 0.2
857
+      group.add(leftWing)
858
+
859
+      const rightWing = new THREE.Mesh(wingGeom, silkMat)
860
+      rightWing.position.set(0, 0.65, -0.08)
861
+      rightWing.rotation.z = -0.2
862
+      group.add(rightWing)
863
+
864
+      // Center knot
865
+      const knotGeom = new THREE.SphereGeometry(0.03, 6, 4)
866
+      const knot = new THREE.Mesh(knotGeom, knotMat)
867
+      knot.position.set(0, 0.65, 0)
868
+      knot.scale.set(1, 1, 1.5)
869
+      group.add(knot)
870
+
871
+      // Small dangling ribbon ends
872
+      const ribbonGeom = new THREE.BoxGeometry(0.02, 0.08, 0.03)
873
+      const ribbon1 = new THREE.Mesh(ribbonGeom, silkMat)
874
+      ribbon1.position.set(0, 0.60, 0.02)
875
+      ribbon1.rotation.z = 0.1
876
+      group.add(ribbon1)
877
+
878
+      const ribbon2 = new THREE.Mesh(ribbonGeom, silkMat)
879
+      ribbon2.position.set(0, 0.60, -0.02)
880
+      ribbon2.rotation.z = -0.1
881
+      group.add(ribbon2)
882
+
883
+      return group
884
+    }
885
+  },
886
+
887
+  ollie_dapper_vest: {
888
+    id: 'ollie_dapper_vest',
889
+    name: 'Dapper Vest',
890
+    character: CHARACTERS.OLLIE,
891
+    type: OUTFIT_TYPES.CLOTHING_BODY,
892
+    price: 75,
893
+    meshFactory: (gradientMap) => {
894
+      const group = new THREE.Group()
895
+      const vestMat = new THREE.MeshToonMaterial({ color: 0x4a4a4a, gradientMap })
896
+      const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
897
+      const silkMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
898
+
899
+      // Vest body - wraps around octopus mantle
900
+      const vestGeom = new THREE.SphereGeometry(0.42, 10, 8)
901
+      vestGeom.scale(1, 0.8, 1)
902
+      const vest = new THREE.Mesh(vestGeom, vestMat)
903
+      vest.position.set(0, 0.35, 0)
904
+      group.add(vest)
905
+
906
+      // Vest front opening (shows body)
907
+      const openingGeom = new THREE.PlaneGeometry(0.15, 0.4)
908
+      const openingMat = new THREE.MeshBasicMaterial({ color: 0xe07850, transparent: true, opacity: 0.3 })
909
+      const opening = new THREE.Mesh(openingGeom, openingMat)
910
+      opening.position.set(0.42, 0.35, 0)
911
+      opening.rotation.y = Math.PI / 2
912
+      group.add(opening)
913
+
914
+      // Lapels
915
+      const lapelGeom = new THREE.BoxGeometry(0.08, 0.2, 0.05)
916
+      const leftLapel = new THREE.Mesh(lapelGeom, silkMat)
917
+      leftLapel.position.set(0.4, 0.45, 0.1)
918
+      leftLapel.rotation.z = -0.2
919
+      leftLapel.rotation.y = 0.3
920
+      group.add(leftLapel)
921
+
922
+      const rightLapel = new THREE.Mesh(lapelGeom, silkMat)
923
+      rightLapel.position.set(0.4, 0.45, -0.1)
924
+      rightLapel.rotation.z = -0.2
925
+      rightLapel.rotation.y = -0.3
926
+      group.add(rightLapel)
927
+
928
+      // Gold buttons
929
+      const buttonGeom = new THREE.SphereGeometry(0.02, 6, 4)
930
+      for (let i = 0; i < 3; i++) {
931
+        const button = new THREE.Mesh(buttonGeom, goldMat)
932
+        button.position.set(0.42, 0.5 - i * 0.1, 0)
933
+        group.add(button)
934
+      }
935
+
936
+      // Watch chain
937
+      const chainGeom = new THREE.TorusGeometry(0.08, 0.008, 4, 8, Math.PI)
938
+      const chain = new THREE.Mesh(chainGeom, goldMat)
939
+      chain.position.set(0.35, 0.32, 0)
940
+      chain.rotation.y = Math.PI / 2
941
+      chain.rotation.x = Math.PI / 2
942
+      group.add(chain)
943
+
944
+      // Watch fob
945
+      const fobGeom = new THREE.SphereGeometry(0.02, 6, 4)
946
+      const fob = new THREE.Mesh(fobGeom, goldMat)
947
+      fob.position.set(0.35, 0.24, 0)
948
+      group.add(fob)
949
+
950
+      return group
951
+    }
952
+  },
953
+
954
+  ollie_gentleman_suit: {
955
+    id: 'ollie_gentleman_suit',
956
+    name: "Gentleman's Suit",
957
+    character: CHARACTERS.OLLIE,
958
+    type: OUTFIT_TYPES.CLOTHING_BODY,
959
+    price: 120,
960
+    meshFactory: (gradientMap) => {
961
+      const group = new THREE.Group()
962
+      const suitMat = new THREE.MeshToonMaterial({ color: 0x1a1a2e, gradientMap })
963
+      const shirtMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
964
+      const tieMat = new THREE.MeshToonMaterial({ color: 0x4a0000, gradientMap })
965
+      const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
966
+
967
+      // White shirt underneath
968
+      const shirtGeom = new THREE.SphereGeometry(0.38, 10, 8)
969
+      shirtGeom.scale(1, 0.78, 1)
970
+      const shirt = new THREE.Mesh(shirtGeom, shirtMat)
971
+      shirt.position.set(0, 0.35, 0)
972
+      group.add(shirt)
973
+
974
+      // Suit jacket
975
+      const jacketGeom = new THREE.SphereGeometry(0.44, 10, 8)
976
+      jacketGeom.scale(1, 0.82, 1)
977
+      const jacket = new THREE.Mesh(jacketGeom, suitMat)
978
+      jacket.position.set(0, 0.35, 0)
979
+      group.add(jacket)
980
+
981
+      // Jacket front cutaway (shows shirt and tie)
982
+      const cutawayGeom = new THREE.PlaneGeometry(0.2, 0.45)
983
+      const cutawayMat = new THREE.MeshBasicMaterial({ visible: false })
984
+      const cutaway = new THREE.Mesh(cutawayGeom, cutawayMat)
985
+      cutaway.position.set(0.45, 0.35, 0)
986
+      cutaway.rotation.y = Math.PI / 2
987
+      group.add(cutaway)
988
+
989
+      // Lapels
990
+      const lapelGeom = new THREE.BoxGeometry(0.12, 0.25, 0.04)
991
+      const leftLapel = new THREE.Mesh(lapelGeom, suitMat)
992
+      leftLapel.position.set(0.42, 0.48, 0.12)
993
+      leftLapel.rotation.z = -0.25
994
+      leftLapel.rotation.y = 0.4
995
+      group.add(leftLapel)
996
+
997
+      const rightLapel = new THREE.Mesh(lapelGeom, suitMat)
998
+      rightLapel.position.set(0.42, 0.48, -0.12)
999
+      rightLapel.rotation.z = -0.25
1000
+      rightLapel.rotation.y = -0.4
1001
+      group.add(rightLapel)
1002
+
1003
+      // Collar
1004
+      const collarGeom = new THREE.BoxGeometry(0.06, 0.08, 0.05)
1005
+      const leftCollar = new THREE.Mesh(collarGeom, shirtMat)
1006
+      leftCollar.position.set(0.38, 0.62, 0.06)
1007
+      leftCollar.rotation.z = -0.4
1008
+      group.add(leftCollar)
1009
+
1010
+      const rightCollar = new THREE.Mesh(collarGeom, shirtMat)
1011
+      rightCollar.position.set(0.38, 0.62, -0.06)
1012
+      rightCollar.rotation.z = -0.4
1013
+      group.add(rightCollar)
1014
+
1015
+      // Tie
1016
+      const tieKnotGeom = new THREE.BoxGeometry(0.04, 0.04, 0.03)
1017
+      const tieKnot = new THREE.Mesh(tieKnotGeom, tieMat)
1018
+      tieKnot.position.set(0.40, 0.58, 0)
1019
+      group.add(tieKnot)
1020
+
1021
+      const tieBodyGeom = new THREE.BoxGeometry(0.06, 0.25, 0.02)
1022
+      const tieBody = new THREE.Mesh(tieBodyGeom, tieMat)
1023
+      tieBody.position.set(0.42, 0.42, 0)
1024
+      tieBody.rotation.z = -0.05
1025
+      group.add(tieBody)
1026
+
1027
+      // Tie point
1028
+      const tiePointGeom = new THREE.ConeGeometry(0.035, 0.06, 4)
1029
+      const tiePoint = new THREE.Mesh(tiePointGeom, tieMat)
1030
+      tiePoint.position.set(0.43, 0.28, 0)
1031
+      tiePoint.rotation.z = Math.PI
1032
+      group.add(tiePoint)
1033
+
1034
+      // Pocket square
1035
+      const squareGeom = new THREE.BoxGeometry(0.04, 0.05, 0.02)
1036
+      const squareMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
1037
+      const square = new THREE.Mesh(squareGeom, squareMat)
1038
+      square.position.set(0.4, 0.5, -0.2)
1039
+      group.add(square)
1040
+
1041
+      // Buttons
1042
+      const buttonGeom = new THREE.SphereGeometry(0.015, 6, 4)
1043
+      const button1 = new THREE.Mesh(buttonGeom, goldMat)
1044
+      button1.position.set(0.44, 0.35, 0.15)
1045
+      group.add(button1)
1046
+
1047
+      const button2 = new THREE.Mesh(buttonGeom, goldMat)
1048
+      button2.position.set(0.44, 0.35, -0.15)
1049
+      group.add(button2)
1050
+
2371051
       return group
2381052
     }
2391053
   }