zeroed-some/dougk / 0c90bc9

Browse files

new outfits

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
0c90bc97058a3c904deaae12673c78833d41efe1
Parents
2b9ade3
Tree
1a71527

2 changed files

StatusFile+-
M src/renderers/three/shop/inventory.js 18 7
M src/renderers/three/shop/items.js 909 0
src/renderers/three/shop/inventory.jsmodified
@@ -56,7 +56,7 @@ const inventory = {
56
   },
56
   },
57
 
57
 
58
   // Equip an outfit to a character
58
   // Equip an outfit to a character
59
-  // Automatically unequips any other outfit of the same type
59
+  // Automatically unequips any other outfit of the same type or conflicting types
60
   equip(character, itemId) {
60
   equip(character, itemId) {
61
     if (!this.equipped[character]) {
61
     if (!this.equipped[character]) {
62
       this.equipped[character] = []
62
       this.equipped[character] = []
@@ -66,14 +66,25 @@ const inventory = {
66
     const newItem = getItem(itemId)
66
     const newItem = getItem(itemId)
67
     if (!newItem) return
67
     if (!newItem) return
68
 
68
 
69
-    // Find and unequip any item of the same type
69
+    // Build list of types to unequip: same type + any conflicting types
70
-    const sameTypeItems = this.equipped[character].filter(equippedId => {
70
+    const typesToUnequip = [newItem.type]
71
+    if (newItem.conflictsWith && Array.isArray(newItem.conflictsWith)) {
72
+      typesToUnequip.push(...newItem.conflictsWith)
73
+    }
74
+
75
+    // Find and unequip any item of conflicting types
76
+    const conflictingItems = this.equipped[character].filter(equippedId => {
71
       const equippedItem = getItem(equippedId)
77
       const equippedItem = getItem(equippedId)
72
-      return equippedItem && equippedItem.type === newItem.type
78
+      if (!equippedItem) return false
79
+      // Check if equipped item's type is in our unequip list
80
+      if (typesToUnequip.includes(equippedItem.type)) return true
81
+      // Also check if the equipped item conflicts with our new item's type
82
+      if (equippedItem.conflictsWith && equippedItem.conflictsWith.includes(newItem.type)) return true
83
+      return false
73
     })
84
     })
74
 
85
 
75
-    // Remove items of the same type
86
+    // Remove conflicting items
76
-    for (const oldId of sameTypeItems) {
87
+    for (const oldId of conflictingItems) {
77
       this.equipped[character] = this.equipped[character].filter(id => id !== oldId)
88
       this.equipped[character] = this.equipped[character].filter(id => id !== oldId)
78
     }
89
     }
79
 
90
 
@@ -84,7 +95,7 @@ const inventory = {
84
     this.save()
95
     this.save()
85
 
96
 
86
     // Return the unequipped items so the caller can update visuals
97
     // Return the unequipped items so the caller can update visuals
87
-    return sameTypeItems
98
+    return conflictingItems
88
   },
99
   },
89
 
100
 
90
   // Unequip an outfit from a character
101
   // Unequip an outfit from a character
src/renderers/three/shop/items.jsmodified
@@ -676,6 +676,280 @@ export const OUTFITS = {
676
     }
676
     }
677
   },
677
   },
678
 
678
 
679
+  // Doug outfits - full costumes with accessories
680
+  doug_chef: {
681
+    id: 'doug_chef',
682
+    name: 'Chef Doug',
683
+    character: CHARACTERS.DOUG,
684
+    type: OUTFIT_TYPES.CLOTHING_BODY,
685
+    conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD],
686
+    price: 65,
687
+    meshFactory: (gradientMap) => {
688
+      const group = new THREE.Group()
689
+      const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
690
+      const apronMat = new THREE.MeshToonMaterial({ color: 0xf5f5f5, gradientMap })
691
+      const strapMat = new THREE.MeshToonMaterial({ color: 0xe8e8e8, gradientMap })
692
+
693
+      // Chef's toque (tall puffy hat)
694
+      const toqueBase = new THREE.CylinderGeometry(0.18, 0.2, 0.08, 12)
695
+      const toqueBaseMesh = new THREE.Mesh(toqueBase, whiteMat)
696
+      toqueBaseMesh.position.set(0.1, 0.75, 0)
697
+      group.add(toqueBaseMesh)
698
+
699
+      // Puffy top of toque (multiple spheres for volume)
700
+      const puffGeom = new THREE.SphereGeometry(0.14, 8, 6)
701
+      const puff1 = new THREE.Mesh(puffGeom, whiteMat)
702
+      puff1.position.set(0.1, 0.9, 0)
703
+      group.add(puff1)
704
+
705
+      const puff2 = new THREE.Mesh(puffGeom, whiteMat)
706
+      puff2.position.set(0.08, 0.98, 0.05)
707
+      puff2.scale.set(0.9, 0.85, 0.9)
708
+      group.add(puff2)
709
+
710
+      const puff3 = new THREE.Mesh(puffGeom, whiteMat)
711
+      puff3.position.set(0.12, 0.95, -0.05)
712
+      puff3.scale.set(0.85, 0.8, 0.85)
713
+      group.add(puff3)
714
+
715
+      // Apron body
716
+      const apronBodyGeom = new THREE.BoxGeometry(0.08, 0.4, 0.45)
717
+      const apronBody = new THREE.Mesh(apronBodyGeom, apronMat)
718
+      apronBody.position.set(0.38, 0.2, 0)
719
+      group.add(apronBody)
720
+
721
+      // Apron bib (upper part)
722
+      const bibGeom = new THREE.BoxGeometry(0.06, 0.2, 0.3)
723
+      const bib = new THREE.Mesh(bibGeom, apronMat)
724
+      bib.position.set(0.4, 0.45, 0)
725
+      group.add(bib)
726
+
727
+      // Neck strap
728
+      const neckStrapGeom = new THREE.TorusGeometry(0.15, 0.015, 4, 12, Math.PI)
729
+      const neckStrap = new THREE.Mesh(neckStrapGeom, strapMat)
730
+      neckStrap.position.set(0.25, 0.55, 0)
731
+      neckStrap.rotation.z = Math.PI
732
+      neckStrap.rotation.y = Math.PI / 2
733
+      group.add(neckStrap)
734
+
735
+      // Waist ties (hanging at sides)
736
+      const tieGeom = new THREE.BoxGeometry(0.2, 0.02, 0.03)
737
+      const leftTie = new THREE.Mesh(tieGeom, strapMat)
738
+      leftTie.position.set(0.25, 0.25, 0.25)
739
+      leftTie.rotation.y = 0.3
740
+      group.add(leftTie)
741
+
742
+      const rightTie = new THREE.Mesh(tieGeom, strapMat)
743
+      rightTie.position.set(0.25, 0.25, -0.25)
744
+      rightTie.rotation.y = -0.3
745
+      group.add(rightTie)
746
+
747
+      // Apron pocket
748
+      const pocketGeom = new THREE.BoxGeometry(0.02, 0.1, 0.15)
749
+      const pocket = new THREE.Mesh(pocketGeom, strapMat)
750
+      pocket.position.set(0.42, 0.18, 0)
751
+      group.add(pocket)
752
+
753
+      return group
754
+    }
755
+  },
756
+
757
+  doug_superhero: {
758
+    id: 'doug_superhero',
759
+    name: 'Super Duck',
760
+    character: CHARACTERS.DOUG,
761
+    type: OUTFIT_TYPES.CLOTHING_BODY,
762
+    price: 80,
763
+    meshFactory: (gradientMap) => {
764
+      const group = new THREE.Group()
765
+      const capeMat = new THREE.MeshToonMaterial({ color: 0xcc0000, gradientMap, side: THREE.DoubleSide })
766
+      const capeInnerMat = new THREE.MeshToonMaterial({ color: 0xffcc00, gradientMap, side: THREE.DoubleSide })
767
+      const maskMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
768
+      const emblemMat = new THREE.MeshToonMaterial({ color: 0xffcc00, gradientMap })
769
+
770
+      // Cape - draped from shoulders
771
+      const capeShape = new THREE.Shape()
772
+      capeShape.moveTo(-0.35, 0)
773
+      capeShape.lineTo(-0.4, -0.5)
774
+      capeShape.quadraticCurveTo(0, -0.6, 0.4, -0.5)
775
+      capeShape.lineTo(0.35, 0)
776
+      capeShape.quadraticCurveTo(0, 0.05, -0.35, 0)
777
+
778
+      const capeGeom = new THREE.ShapeGeometry(capeShape)
779
+      const cape = new THREE.Mesh(capeGeom, capeMat)
780
+      cape.position.set(-0.2, 0.4, 0)
781
+      cape.rotation.y = Math.PI / 2
782
+      cape.rotation.x = 0.2
783
+      group.add(cape)
784
+
785
+      // Cape inner lining (yellow)
786
+      const capeInner = new THREE.Mesh(capeGeom, capeInnerMat)
787
+      capeInner.position.set(-0.22, 0.4, 0)
788
+      capeInner.rotation.y = Math.PI / 2
789
+      capeInner.rotation.x = 0.2
790
+      group.add(capeInner)
791
+
792
+      // Cape clasp at neck
793
+      const claspGeom = new THREE.SphereGeometry(0.04, 8, 6)
794
+      const claspL = new THREE.Mesh(claspGeom, emblemMat)
795
+      claspL.position.set(0.15, 0.52, 0.18)
796
+      group.add(claspL)
797
+
798
+      const claspR = new THREE.Mesh(claspGeom, emblemMat)
799
+      claspR.position.set(0.15, 0.52, -0.18)
800
+      group.add(claspR)
801
+
802
+      // Domino mask
803
+      const maskGeom = new THREE.BoxGeometry(0.06, 0.08, 0.35)
804
+      const mask = new THREE.Mesh(maskGeom, maskMat)
805
+      mask.position.set(0.48, 0.58, 0)
806
+      group.add(mask)
807
+
808
+      // Eye holes in mask
809
+      const eyeHoleGeom = new THREE.CircleGeometry(0.04, 8)
810
+      const eyeHoleMat = new THREE.MeshBasicMaterial({ color: 0x000000 })
811
+      const eyeL = new THREE.Mesh(eyeHoleGeom, eyeHoleMat)
812
+      eyeL.position.set(0.52, 0.58, 0.08)
813
+      eyeL.rotation.y = Math.PI / 2
814
+      group.add(eyeL)
815
+
816
+      const eyeR = new THREE.Mesh(eyeHoleGeom, eyeHoleMat)
817
+      eyeR.position.set(0.52, 0.58, -0.08)
818
+      eyeR.rotation.y = Math.PI / 2
819
+      group.add(eyeR)
820
+
821
+      // Chest emblem - "D" shield
822
+      const shieldGeom = new THREE.CircleGeometry(0.1, 6)
823
+      const shield = new THREE.Mesh(shieldGeom, emblemMat)
824
+      shield.position.set(0.42, 0.38, 0)
825
+      shield.rotation.y = Math.PI / 2
826
+      group.add(shield)
827
+
828
+      // D letter on shield
829
+      const dGeom = new THREE.TorusGeometry(0.04, 0.015, 4, 8, Math.PI)
830
+      const dLetter = new THREE.Mesh(dGeom, capeMat)
831
+      dLetter.position.set(0.44, 0.38, 0)
832
+      dLetter.rotation.y = Math.PI / 2
833
+      dLetter.rotation.z = -Math.PI / 2
834
+      group.add(dLetter)
835
+
836
+      return group
837
+    }
838
+  },
839
+
840
+  doug_hoodie: {
841
+    id: 'doug_hoodie',
842
+    name: 'Cozy Hoodie',
843
+    character: CHARACTERS.DOUG,
844
+    type: OUTFIT_TYPES.CLOTHING_BODY,
845
+    conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD],
846
+    price: 55,
847
+    meshFactory: (gradientMap) => {
848
+      const group = new THREE.Group()
849
+      const hoodieMat = new THREE.MeshToonMaterial({ color: 0x4a5568, gradientMap })
850
+      const hoodInnerMat = new THREE.MeshToonMaterial({ color: 0x2d3748, gradientMap })
851
+      const stringMat = new THREE.MeshToonMaterial({ color: 0xe2e8f0, gradientMap })
852
+
853
+      // Hoodie body
854
+      const bodyGeom = new THREE.SphereGeometry(0.46, 10, 8)
855
+      bodyGeom.scale(1.38, 0.76, 0.92)
856
+      const body = new THREE.Mesh(bodyGeom, hoodieMat)
857
+      body.position.set(0, 0.28, 0)
858
+      group.add(body)
859
+
860
+      // Hood (up, around head)
861
+      const hoodGeom = new THREE.SphereGeometry(0.28, 10, 8, 0, Math.PI * 2, 0, Math.PI * 0.6)
862
+      const hood = new THREE.Mesh(hoodGeom, hoodieMat)
863
+      hood.position.set(0.15, 0.58, 0)
864
+      hood.rotation.z = -0.3
865
+      hood.rotation.x = 0.1
866
+      group.add(hood)
867
+
868
+      // Hood inner lining
869
+      const hoodInnerGeom = new THREE.SphereGeometry(0.26, 10, 8, 0, Math.PI * 2, 0, Math.PI * 0.55)
870
+      const hoodInner = new THREE.Mesh(hoodInnerGeom, hoodInnerMat)
871
+      hoodInner.position.set(0.16, 0.58, 0)
872
+      hoodInner.rotation.z = -0.3
873
+      hoodInner.rotation.x = 0.1
874
+      group.add(hoodInner)
875
+
876
+      // Hoodie opening for face
877
+      const openingGeom = new THREE.TorusGeometry(0.18, 0.025, 6, 12)
878
+      const opening = new THREE.Mesh(openingGeom, hoodInnerMat)
879
+      opening.position.set(0.38, 0.6, 0)
880
+      opening.rotation.y = Math.PI / 2
881
+      opening.rotation.z = -0.1
882
+      group.add(opening)
883
+
884
+      // Drawstrings
885
+      const stringGeom = new THREE.CylinderGeometry(0.008, 0.008, 0.2, 4)
886
+      const stringL = new THREE.Mesh(stringGeom, stringMat)
887
+      stringL.position.set(0.42, 0.42, 0.1)
888
+      stringL.rotation.z = 0.3
889
+      group.add(stringL)
890
+
891
+      const stringR = new THREE.Mesh(stringGeom, stringMat)
892
+      stringR.position.set(0.42, 0.42, -0.1)
893
+      stringR.rotation.z = 0.3
894
+      group.add(stringR)
895
+
896
+      // String tips (little plastic aglets)
897
+      const agletGeom = new THREE.CylinderGeometry(0.012, 0.008, 0.03, 4)
898
+      const agletL = new THREE.Mesh(agletGeom, stringMat)
899
+      agletL.position.set(0.48, 0.32, 0.1)
900
+      group.add(agletL)
901
+
902
+      const agletR = new THREE.Mesh(agletGeom, stringMat)
903
+      agletR.position.set(0.48, 0.32, -0.1)
904
+      group.add(agletR)
905
+
906
+      // Front pocket (kangaroo pouch)
907
+      const pocketGeom = new THREE.SphereGeometry(0.2, 8, 6, 0, Math.PI, 0, Math.PI)
908
+      pocketGeom.scale(1.2, 0.5, 0.8)
909
+      const pocket = new THREE.Mesh(pocketGeom, hoodInnerMat)
910
+      pocket.position.set(0.4, 0.15, 0)
911
+      pocket.rotation.y = Math.PI / 2
912
+      pocket.rotation.z = Math.PI
913
+      group.add(pocket)
914
+
915
+      // Pocket opening
916
+      const pocketOpenGeom = new THREE.TorusGeometry(0.12, 0.015, 4, 12, Math.PI)
917
+      const pocketOpen = new THREE.Mesh(pocketOpenGeom, hoodieMat)
918
+      pocketOpen.position.set(0.42, 0.18, 0)
919
+      pocketOpen.rotation.y = Math.PI / 2
920
+      pocketOpen.rotation.x = Math.PI / 2
921
+      group.add(pocketOpen)
922
+
923
+      // Sleeves
924
+      const sleeveGeom = new THREE.CylinderGeometry(0.09, 0.11, 0.18, 6)
925
+      const leftSleeve = new THREE.Mesh(sleeveGeom, hoodieMat)
926
+      leftSleeve.position.set(0.05, 0.3, 0.38)
927
+      leftSleeve.rotation.x = Math.PI / 2
928
+      leftSleeve.rotation.z = 0.3
929
+      group.add(leftSleeve)
930
+
931
+      const rightSleeve = new THREE.Mesh(sleeveGeom, hoodieMat)
932
+      rightSleeve.position.set(0.05, 0.3, -0.38)
933
+      rightSleeve.rotation.x = -Math.PI / 2
934
+      rightSleeve.rotation.z = 0.3
935
+      group.add(rightSleeve)
936
+
937
+      // Ribbed cuffs
938
+      const cuffGeom = new THREE.TorusGeometry(0.085, 0.02, 4, 8)
939
+      const leftCuff = new THREE.Mesh(cuffGeom, hoodInnerMat)
940
+      leftCuff.position.set(0.0, 0.28, 0.46)
941
+      leftCuff.rotation.y = Math.PI / 2
942
+      group.add(leftCuff)
943
+
944
+      const rightCuff = new THREE.Mesh(cuffGeom, hoodInnerMat)
945
+      rightCuff.position.set(0.0, 0.28, -0.46)
946
+      rightCuff.rotation.y = Math.PI / 2
947
+      group.add(rightCuff)
948
+
949
+      return group
950
+    }
951
+  },
952
+
679
   // Donny outfits - starter tier
953
   // Donny outfits - starter tier
680
   donny_seafoam: {
954
   donny_seafoam: {
681
     id: 'donny_seafoam',
955
     id: 'donny_seafoam',
@@ -1068,6 +1342,285 @@ export const OUTFITS = {
1068
     }
1342
     }
1069
   },
1343
   },
1070
 
1344
 
1345
+  // Donny outfits - full costumes
1346
+  donny_tuxedo: {
1347
+    id: 'donny_tuxedo',
1348
+    name: 'Tuxedo',
1349
+    character: CHARACTERS.DONNY,
1350
+    type: OUTFIT_TYPES.CLOTHING_BODY,
1351
+    price: 90,
1352
+    meshFactory: (gradientMap) => {
1353
+      const group = new THREE.Group()
1354
+      const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
1355
+      const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
1356
+      const bowMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
1357
+
1358
+      // White shirt front
1359
+      const shirtGeom = new THREE.CylinderGeometry(0.32, 0.36, 0.5, 10, 1, false, -Math.PI * 0.3, Math.PI * 0.6)
1360
+      const shirt = new THREE.Mesh(shirtGeom, whiteMat)
1361
+      shirt.position.set(0, 0.2, 0.15)
1362
+      shirt.rotation.x = Math.PI / 2
1363
+      group.add(shirt)
1364
+
1365
+      // Tuxedo jacket body
1366
+      const jacketGeom = new THREE.CylinderGeometry(0.38, 0.44, 0.65, 10)
1367
+      const jacket = new THREE.Mesh(jacketGeom, blackMat)
1368
+      jacket.position.set(0, 0.15, 0)
1369
+      jacket.rotation.x = Math.PI / 2
1370
+      group.add(jacket)
1371
+
1372
+      // Jacket lapels (satin)
1373
+      const lapelGeom = new THREE.BoxGeometry(0.35, 0.04, 0.12)
1374
+      const leftLapel = new THREE.Mesh(lapelGeom, blackMat)
1375
+      leftLapel.position.set(0.08, 0.4, 0.2)
1376
+      leftLapel.rotation.z = 0.3
1377
+      leftLapel.rotation.y = 0.2
1378
+      group.add(leftLapel)
1379
+
1380
+      const rightLapel = new THREE.Mesh(lapelGeom, blackMat)
1381
+      rightLapel.position.set(0.08, 0.4, -0.2)
1382
+      rightLapel.rotation.z = 0.3
1383
+      rightLapel.rotation.y = -0.2
1384
+      group.add(rightLapel)
1385
+
1386
+      // Bow tie
1387
+      const bowWingGeom = new THREE.BoxGeometry(0.08, 0.04, 0.03)
1388
+      const bowL = new THREE.Mesh(bowWingGeom, bowMat)
1389
+      bowL.position.set(0.05, 0.52, 0.06)
1390
+      bowL.rotation.z = 0.2
1391
+      group.add(bowL)
1392
+
1393
+      const bowR = new THREE.Mesh(bowWingGeom, bowMat)
1394
+      bowR.position.set(0.05, 0.52, -0.06)
1395
+      bowR.rotation.z = -0.2
1396
+      group.add(bowR)
1397
+
1398
+      const bowKnotGeom = new THREE.SphereGeometry(0.025, 6, 4)
1399
+      const bowKnot = new THREE.Mesh(bowKnotGeom, bowMat)
1400
+      bowKnot.position.set(0.05, 0.52, 0)
1401
+      group.add(bowKnot)
1402
+
1403
+      // Jacket tails (back)
1404
+      const tailGeom = new THREE.BoxGeometry(0.3, 0.06, 0.15)
1405
+      const leftTail = new THREE.Mesh(tailGeom, blackMat)
1406
+      leftTail.position.set(-0.25, -0.05, 0.12)
1407
+      leftTail.rotation.x = -0.3
1408
+      group.add(leftTail)
1409
+
1410
+      const rightTail = new THREE.Mesh(tailGeom, blackMat)
1411
+      rightTail.position.set(-0.25, -0.05, -0.12)
1412
+      rightTail.rotation.x = 0.3
1413
+      group.add(rightTail)
1414
+
1415
+      // Shirt buttons
1416
+      const buttonGeom = new THREE.SphereGeometry(0.015, 6, 4)
1417
+      for (let i = 0; i < 3; i++) {
1418
+        const btn = new THREE.Mesh(buttonGeom, blackMat)
1419
+        btn.position.set(0.08, 0.35 - i * 0.1, 0.28)
1420
+        group.add(btn)
1421
+      }
1422
+
1423
+      return group
1424
+    }
1425
+  },
1426
+
1427
+  donny_captain: {
1428
+    id: 'donny_captain',
1429
+    name: 'Sea Captain',
1430
+    character: CHARACTERS.DONNY,
1431
+    type: OUTFIT_TYPES.CLOTHING_BODY,
1432
+    conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD],
1433
+    price: 110,
1434
+    meshFactory: (gradientMap) => {
1435
+      const group = new THREE.Group()
1436
+      const navyMat = new THREE.MeshToonMaterial({ color: 0x1a1a3a, gradientMap })
1437
+      const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1438
+      const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
1439
+      const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
1440
+
1441
+      // Captain's coat
1442
+      const coatGeom = new THREE.CylinderGeometry(0.4, 0.46, 0.7, 10)
1443
+      const coat = new THREE.Mesh(coatGeom, navyMat)
1444
+      coat.position.set(0, 0.12, 0)
1445
+      coat.rotation.x = Math.PI / 2
1446
+      group.add(coat)
1447
+
1448
+      // High collar
1449
+      const collarGeom = new THREE.CylinderGeometry(0.36, 0.34, 0.1, 10, 1, true)
1450
+      const collar = new THREE.Mesh(collarGeom, navyMat)
1451
+      collar.position.set(0, 0.5, 0)
1452
+      collar.rotation.x = Math.PI / 2
1453
+      group.add(collar)
1454
+
1455
+      // Gold collar trim
1456
+      const trimGeom = new THREE.TorusGeometry(0.35, 0.015, 4, 12)
1457
+      const trim = new THREE.Mesh(trimGeom, goldMat)
1458
+      trim.position.set(0, 0.55, 0)
1459
+      trim.rotation.x = Math.PI / 2
1460
+      group.add(trim)
1461
+
1462
+      // Epaulettes
1463
+      const epGeom = new THREE.BoxGeometry(0.12, 0.03, 0.1)
1464
+      const epL = new THREE.Mesh(epGeom, goldMat)
1465
+      epL.position.set(0, 0.45, 0.38)
1466
+      group.add(epL)
1467
+
1468
+      const epR = new THREE.Mesh(epGeom, goldMat)
1469
+      epR.position.set(0, 0.45, -0.38)
1470
+      group.add(epR)
1471
+
1472
+      // Gold fringe on epaulettes
1473
+      for (let i = 0; i < 3; i++) {
1474
+        const fringeGeom = new THREE.CylinderGeometry(0.008, 0.005, 0.06, 4)
1475
+        const fringeL = new THREE.Mesh(fringeGeom, goldMat)
1476
+        fringeL.position.set(-0.02, 0.42, 0.35 + i * 0.03)
1477
+        group.add(fringeL)
1478
+
1479
+        const fringeR = new THREE.Mesh(fringeGeom, goldMat)
1480
+        fringeR.position.set(-0.02, 0.42, -0.35 - i * 0.03)
1481
+        group.add(fringeR)
1482
+      }
1483
+
1484
+      // Gold buttons
1485
+      const buttonGeom = new THREE.SphereGeometry(0.02, 6, 4)
1486
+      for (let i = 0; i < 4; i++) {
1487
+        const btn = new THREE.Mesh(buttonGeom, goldMat)
1488
+        btn.position.set(0.08, 0.35 - i * 0.1, 0.15)
1489
+        group.add(btn)
1490
+      }
1491
+
1492
+      // Captain's peaked cap
1493
+      const capBrimGeom = new THREE.CylinderGeometry(0.22, 0.22, 0.03, 12)
1494
+      const capBrim = new THREE.Mesh(capBrimGeom, blackMat)
1495
+      capBrim.position.set(0.2, 0.7, 0)
1496
+      capBrim.rotation.z = -0.2
1497
+      group.add(capBrim)
1498
+
1499
+      // Cap crown
1500
+      const capCrownGeom = new THREE.CylinderGeometry(0.18, 0.2, 0.12, 12)
1501
+      const capCrown = new THREE.Mesh(capCrownGeom, navyMat)
1502
+      capCrown.position.set(0.18, 0.78, 0)
1503
+      capCrown.rotation.z = -0.2
1504
+      group.add(capCrown)
1505
+
1506
+      // Cap badge
1507
+      const badgeGeom = new THREE.CircleGeometry(0.06, 8)
1508
+      const badge = new THREE.Mesh(badgeGeom, goldMat)
1509
+      badge.position.set(0.32, 0.78, 0)
1510
+      badge.rotation.y = Math.PI / 2
1511
+      badge.rotation.z = -0.2
1512
+      group.add(badge)
1513
+
1514
+      // Anchor on badge
1515
+      const anchorGeom = new THREE.TorusGeometry(0.025, 0.006, 4, 8, Math.PI)
1516
+      const anchor = new THREE.Mesh(anchorGeom, whiteMat)
1517
+      anchor.position.set(0.34, 0.78, 0)
1518
+      anchor.rotation.y = Math.PI / 2
1519
+      anchor.rotation.x = Math.PI
1520
+      group.add(anchor)
1521
+
1522
+      return group
1523
+    }
1524
+  },
1525
+
1526
+  donny_magician: {
1527
+    id: 'donny_magician',
1528
+    name: 'Magician',
1529
+    character: CHARACTERS.DONNY,
1530
+    type: OUTFIT_TYPES.CLOTHING_BODY,
1531
+    conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD],
1532
+    price: 95,
1533
+    meshFactory: (gradientMap) => {
1534
+      const group = new THREE.Group()
1535
+      const capeMat = new THREE.MeshToonMaterial({ color: 0x2d0a4e, gradientMap, side: THREE.DoubleSide })
1536
+      const capeInnerMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap, side: THREE.DoubleSide })
1537
+      const hatMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
1538
+      const starMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1539
+      const vestMat = new THREE.MeshToonMaterial({ color: 0x4a0080, gradientMap })
1540
+
1541
+      // Vest underneath
1542
+      const vestGeom = new THREE.CylinderGeometry(0.36, 0.4, 0.5, 10)
1543
+      const vest = new THREE.Mesh(vestGeom, vestMat)
1544
+      vest.position.set(0, 0.18, 0)
1545
+      vest.rotation.x = Math.PI / 2
1546
+      group.add(vest)
1547
+
1548
+      // Cape (draped around shoulders)
1549
+      const capeShape = new THREE.Shape()
1550
+      capeShape.moveTo(-0.4, 0)
1551
+      capeShape.lineTo(-0.45, -0.55)
1552
+      capeShape.quadraticCurveTo(0, -0.65, 0.45, -0.55)
1553
+      capeShape.lineTo(0.4, 0)
1554
+      capeShape.quadraticCurveTo(0, 0.05, -0.4, 0)
1555
+
1556
+      const capeGeom = new THREE.ShapeGeometry(capeShape)
1557
+      const cape = new THREE.Mesh(capeGeom, capeMat)
1558
+      cape.position.set(-0.15, 0.35, 0)
1559
+      cape.rotation.y = Math.PI / 2
1560
+      group.add(cape)
1561
+
1562
+      // Cape inner (gold lining)
1563
+      const capeInner = new THREE.Mesh(capeGeom, capeInnerMat)
1564
+      capeInner.position.set(-0.17, 0.35, 0)
1565
+      capeInner.rotation.y = Math.PI / 2
1566
+      group.add(capeInner)
1567
+
1568
+      // Stars on cape
1569
+      const starPositions = [
1570
+        { x: -0.2, y: 0.25, z: 0.2 },
1571
+        { x: -0.25, y: 0.1, z: -0.15 },
1572
+        { x: -0.22, y: 0.0, z: 0.25 }
1573
+      ]
1574
+      for (const pos of starPositions) {
1575
+        const starGeom = new THREE.CircleGeometry(0.03, 5)
1576
+        const star = new THREE.Mesh(starGeom, starMat)
1577
+        star.position.set(pos.x, pos.y, pos.z)
1578
+        star.rotation.y = Math.PI / 2
1579
+        star.rotation.z = Math.PI / 10
1580
+        group.add(star)
1581
+      }
1582
+
1583
+      // Cape clasp (gem)
1584
+      const claspGeom = new THREE.OctahedronGeometry(0.04)
1585
+      const claspMat = new THREE.MeshToonMaterial({ color: 0xff0066, gradientMap })
1586
+      const clasp = new THREE.Mesh(claspGeom, claspMat)
1587
+      clasp.position.set(0.08, 0.5, 0)
1588
+      group.add(clasp)
1589
+
1590
+      // Top hat
1591
+      const hatBrimGeom = new THREE.CylinderGeometry(0.22, 0.22, 0.03, 12)
1592
+      const hatBrim = new THREE.Mesh(hatBrimGeom, hatMat)
1593
+      hatBrim.position.set(0.15, 0.68, 0)
1594
+      hatBrim.rotation.z = -0.15
1595
+      group.add(hatBrim)
1596
+
1597
+      // Hat crown (tall)
1598
+      const hatCrownGeom = new THREE.CylinderGeometry(0.14, 0.14, 0.25, 12)
1599
+      const hatCrown = new THREE.Mesh(hatCrownGeom, hatMat)
1600
+      hatCrown.position.set(0.13, 0.82, 0)
1601
+      hatCrown.rotation.z = -0.15
1602
+      group.add(hatCrown)
1603
+
1604
+      // Hat band
1605
+      const bandGeom = new THREE.TorusGeometry(0.14, 0.015, 4, 12)
1606
+      const band = new THREE.Mesh(bandGeom, starMat)
1607
+      band.position.set(0.14, 0.72, 0)
1608
+      band.rotation.x = Math.PI / 2
1609
+      band.rotation.y = 0.15
1610
+      group.add(band)
1611
+
1612
+      // Star on hat
1613
+      const hatStarGeom = new THREE.CircleGeometry(0.035, 5)
1614
+      const hatStar = new THREE.Mesh(hatStarGeom, starMat)
1615
+      hatStar.position.set(0.27, 0.82, 0)
1616
+      hatStar.rotation.y = Math.PI / 2
1617
+      hatStar.rotation.z = Math.PI / 10
1618
+      group.add(hatStar)
1619
+
1620
+      return group
1621
+    }
1622
+  },
1623
+
1071
   // Ollie outfits
1624
   // Ollie outfits
1072
   ollie_coral: {
1625
   ollie_coral: {
1073
     id: 'ollie_coral',
1626
     id: 'ollie_coral',
@@ -1482,6 +2035,362 @@ export const OUTFITS = {
1482
       button2.position.set(0.44, 0.35, -0.15)
2035
       button2.position.set(0.44, 0.35, -0.15)
1483
       group.add(button2)
2036
       group.add(button2)
1484
 
2037
 
2038
+      return group
2039
+    }
2040
+  },
2041
+
2042
+  // Ollie outfits - full costumes
2043
+  ollie_scientist: {
2044
+    id: 'ollie_scientist',
2045
+    name: 'Mad Scientist',
2046
+    character: CHARACTERS.OLLIE,
2047
+    type: OUTFIT_TYPES.CLOTHING_BODY,
2048
+    conflictsWith: [OUTFIT_TYPES.ACCESSORY_FACE],
2049
+    price: 85,
2050
+    meshFactory: (gradientMap) => {
2051
+      const group = new THREE.Group()
2052
+      const labCoatMat = new THREE.MeshToonMaterial({ color: 0xf5f5f5, gradientMap })
2053
+      const goggleMat = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap })
2054
+      const lensMat = new THREE.MeshBasicMaterial({ color: 0x66ffff, transparent: true, opacity: 0.7 })
2055
+      const flaskMat = new THREE.MeshBasicMaterial({ color: 0x88ff88, transparent: true, opacity: 0.6 })
2056
+      const glassMat = new THREE.MeshBasicMaterial({ color: 0xccffcc, transparent: true, opacity: 0.4 })
2057
+
2058
+      // Lab coat body
2059
+      const coatGeom = new THREE.SphereGeometry(0.46, 10, 8)
2060
+      coatGeom.scale(1, 0.85, 1)
2061
+      const coat = new THREE.Mesh(coatGeom, labCoatMat)
2062
+      coat.position.set(0, 0.35, 0)
2063
+      group.add(coat)
2064
+
2065
+      // Coat collar
2066
+      const collarGeom = new THREE.TorusGeometry(0.28, 0.04, 4, 12, Math.PI)
2067
+      const collar = new THREE.Mesh(collarGeom, labCoatMat)
2068
+      collar.position.set(0, 0.62, 0.15)
2069
+      collar.rotation.x = Math.PI / 2 + 0.3
2070
+      group.add(collar)
2071
+
2072
+      // Coat lapels
2073
+      const lapelGeom = new THREE.BoxGeometry(0.08, 0.2, 0.06)
2074
+      const lapelL = new THREE.Mesh(lapelGeom, labCoatMat)
2075
+      lapelL.position.set(0.38, 0.5, 0.12)
2076
+      lapelL.rotation.z = -0.2
2077
+      group.add(lapelL)
2078
+
2079
+      const lapelR = new THREE.Mesh(lapelGeom, labCoatMat)
2080
+      lapelR.position.set(0.38, 0.5, -0.12)
2081
+      lapelR.rotation.z = -0.2
2082
+      group.add(lapelR)
2083
+
2084
+      // Pocket with pens
2085
+      const pocketGeom = new THREE.BoxGeometry(0.02, 0.1, 0.08)
2086
+      const pocket = new THREE.Mesh(pocketGeom, labCoatMat)
2087
+      pocket.position.set(0.45, 0.45, 0.15)
2088
+      group.add(pocket)
2089
+
2090
+      // Pens in pocket
2091
+      const penMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
2092
+      const penGeom = new THREE.CylinderGeometry(0.008, 0.008, 0.08, 4)
2093
+      const pen1 = new THREE.Mesh(penGeom, penMat)
2094
+      pen1.position.set(0.46, 0.52, 0.13)
2095
+      group.add(pen1)
2096
+
2097
+      const pen2Mat = new THREE.MeshToonMaterial({ color: 0x0000aa, gradientMap })
2098
+      const pen2 = new THREE.Mesh(penGeom, pen2Mat)
2099
+      pen2.position.set(0.46, 0.51, 0.17)
2100
+      pen2.rotation.z = 0.1
2101
+      group.add(pen2)
2102
+
2103
+      // Wild goggles (multiple lenses!)
2104
+      const goggleStrapGeom = new THREE.TorusGeometry(0.22, 0.025, 6, 16)
2105
+      const goggleStrap = new THREE.Mesh(goggleStrapGeom, goggleMat)
2106
+      goggleStrap.position.set(0.15, 0.7, 0)
2107
+      goggleStrap.rotation.y = Math.PI / 2
2108
+      group.add(goggleStrap)
2109
+
2110
+      // Main lens (large center)
2111
+      const mainLensGeom = new THREE.CylinderGeometry(0.08, 0.08, 0.04, 12)
2112
+      const mainLensFrame = new THREE.Mesh(mainLensGeom, goggleMat)
2113
+      mainLensFrame.position.set(0.35, 0.7, 0)
2114
+      mainLensFrame.rotation.z = Math.PI / 2
2115
+      group.add(mainLensFrame)
2116
+
2117
+      const mainLens = new THREE.Mesh(new THREE.CircleGeometry(0.07, 12), lensMat)
2118
+      mainLens.position.set(0.38, 0.7, 0)
2119
+      mainLens.rotation.y = Math.PI / 2
2120
+      group.add(mainLens)
2121
+
2122
+      // Extra lens (smaller, offset)
2123
+      const extraLensGeom = new THREE.CylinderGeometry(0.04, 0.04, 0.03, 10)
2124
+      const extraLensFrame = new THREE.Mesh(extraLensGeom, goggleMat)
2125
+      extraLensFrame.position.set(0.38, 0.78, 0.08)
2126
+      extraLensFrame.rotation.z = Math.PI / 2
2127
+      group.add(extraLensFrame)
2128
+
2129
+      const extraLens = new THREE.Mesh(new THREE.CircleGeometry(0.035, 10), lensMat)
2130
+      extraLens.position.set(0.40, 0.78, 0.08)
2131
+      extraLens.rotation.y = Math.PI / 2
2132
+      group.add(extraLens)
2133
+
2134
+      // Bubbling flask (held by tentacle)
2135
+      const flaskGeom = new THREE.CylinderGeometry(0.02, 0.06, 0.15, 8)
2136
+      const flask = new THREE.Mesh(flaskGeom, glassMat)
2137
+      flask.position.set(0.25, 0.1, 0.35)
2138
+      group.add(flask)
2139
+
2140
+      // Flask neck
2141
+      const neckGeom = new THREE.CylinderGeometry(0.02, 0.02, 0.05, 6)
2142
+      const neck = new THREE.Mesh(neckGeom, glassMat)
2143
+      neck.position.set(0.25, 0.2, 0.35)
2144
+      group.add(neck)
2145
+
2146
+      // Bubbling liquid
2147
+      const liquidGeom = new THREE.SphereGeometry(0.045, 8, 6)
2148
+      const liquid = new THREE.Mesh(liquidGeom, flaskMat)
2149
+      liquid.position.set(0.25, 0.08, 0.35)
2150
+      group.add(liquid)
2151
+
2152
+      // Bubbles
2153
+      const bubbleGeom = new THREE.SphereGeometry(0.012, 6, 4)
2154
+      const bubbleMat = new THREE.MeshBasicMaterial({ color: 0xaaffaa, transparent: true, opacity: 0.5 })
2155
+      for (let i = 0; i < 3; i++) {
2156
+        const bubble = new THREE.Mesh(bubbleGeom, bubbleMat)
2157
+        bubble.position.set(0.25 + (Math.random() - 0.5) * 0.03, 0.12 + i * 0.03, 0.35)
2158
+        group.add(bubble)
2159
+      }
2160
+
2161
+      return group
2162
+    }
2163
+  },
2164
+
2165
+  ollie_explorer: {
2166
+    id: 'ollie_explorer',
2167
+    name: 'Explorer',
2168
+    character: CHARACTERS.OLLIE,
2169
+    type: OUTFIT_TYPES.CLOTHING_BODY,
2170
+    conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD],
2171
+    price: 75,
2172
+    meshFactory: (gradientMap) => {
2173
+      const group = new THREE.Group()
2174
+      const vestMat = new THREE.MeshToonMaterial({ color: 0xc4a574, gradientMap })
2175
+      const pocketMat = new THREE.MeshToonMaterial({ color: 0xa08050, gradientMap })
2176
+      const helmetMat = new THREE.MeshToonMaterial({ color: 0xf5f5dc, gradientMap })
2177
+      const bandMat = new THREE.MeshToonMaterial({ color: 0x4a3728, gradientMap })
2178
+      const buttonMat = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap })
2179
+
2180
+      // Safari vest body
2181
+      const vestGeom = new THREE.SphereGeometry(0.44, 10, 8)
2182
+      vestGeom.scale(1, 0.82, 1)
2183
+      const vest = new THREE.Mesh(vestGeom, vestMat)
2184
+      vest.position.set(0, 0.35, 0)
2185
+      group.add(vest)
2186
+
2187
+      // Multiple pockets (it's a safari vest!)
2188
+      const pocketGeom = new THREE.BoxGeometry(0.03, 0.1, 0.1)
2189
+
2190
+      // Front pockets
2191
+      const pocket1 = new THREE.Mesh(pocketGeom, pocketMat)
2192
+      pocket1.position.set(0.44, 0.3, 0.15)
2193
+      group.add(pocket1)
2194
+
2195
+      const pocket2 = new THREE.Mesh(pocketGeom, pocketMat)
2196
+      pocket2.position.set(0.44, 0.3, -0.15)
2197
+      group.add(pocket2)
2198
+
2199
+      const pocket3 = new THREE.Mesh(pocketGeom, pocketMat)
2200
+      pocket3.position.set(0.44, 0.45, 0.1)
2201
+      pocket3.scale.set(1, 0.7, 0.8)
2202
+      group.add(pocket3)
2203
+
2204
+      // Pocket flaps
2205
+      const flapGeom = new THREE.BoxGeometry(0.02, 0.02, 0.1)
2206
+      const flap1 = new THREE.Mesh(flapGeom, pocketMat)
2207
+      flap1.position.set(0.45, 0.36, 0.15)
2208
+      group.add(flap1)
2209
+
2210
+      const flap2 = new THREE.Mesh(flapGeom, pocketMat)
2211
+      flap2.position.set(0.45, 0.36, -0.15)
2212
+      group.add(flap2)
2213
+
2214
+      // Buttons on pockets
2215
+      const buttonGeom = new THREE.SphereGeometry(0.012, 6, 4)
2216
+      const btn1 = new THREE.Mesh(buttonGeom, buttonMat)
2217
+      btn1.position.set(0.46, 0.36, 0.15)
2218
+      group.add(btn1)
2219
+
2220
+      const btn2 = new THREE.Mesh(buttonGeom, buttonMat)
2221
+      btn2.position.set(0.46, 0.36, -0.15)
2222
+      group.add(btn2)
2223
+
2224
+      // Vest collar
2225
+      const collarGeom = new THREE.TorusGeometry(0.2, 0.03, 4, 10, Math.PI)
2226
+      const collar = new THREE.Mesh(collarGeom, vestMat)
2227
+      collar.position.set(0.1, 0.6, 0.1)
2228
+      collar.rotation.x = Math.PI / 2 + 0.3
2229
+      collar.rotation.z = 0.2
2230
+      group.add(collar)
2231
+
2232
+      // Pith helmet
2233
+      const helmetDomeGeom = new THREE.SphereGeometry(0.2, 12, 8, 0, Math.PI * 2, 0, Math.PI * 0.6)
2234
+      const helmetDome = new THREE.Mesh(helmetDomeGeom, helmetMat)
2235
+      helmetDome.position.set(0.12, 0.72, 0)
2236
+      group.add(helmetDome)
2237
+
2238
+      // Helmet brim
2239
+      const brimGeom = new THREE.TorusGeometry(0.2, 0.04, 4, 16)
2240
+      const brim = new THREE.Mesh(brimGeom, helmetMat)
2241
+      brim.position.set(0.12, 0.68, 0)
2242
+      brim.rotation.x = Math.PI / 2
2243
+      group.add(brim)
2244
+
2245
+      // Helmet band
2246
+      const helmetBandGeom = new THREE.TorusGeometry(0.17, 0.02, 4, 16)
2247
+      const helmetBand = new THREE.Mesh(helmetBandGeom, bandMat)
2248
+      helmetBand.position.set(0.12, 0.72, 0)
2249
+      helmetBand.rotation.x = Math.PI / 2
2250
+      group.add(helmetBand)
2251
+
2252
+      // Helmet top button
2253
+      const topButtonGeom = new THREE.SphereGeometry(0.025, 6, 4)
2254
+      const topButton = new THREE.Mesh(topButtonGeom, bandMat)
2255
+      topButton.position.set(0.12, 0.88, 0)
2256
+      group.add(topButton)
2257
+
2258
+      return group
2259
+    }
2260
+  },
2261
+
2262
+  ollie_artist: {
2263
+    id: 'ollie_artist',
2264
+    name: 'Artist',
2265
+    character: CHARACTERS.OLLIE,
2266
+    type: OUTFIT_TYPES.CLOTHING_BODY,
2267
+    conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD],
2268
+    price: 70,
2269
+    meshFactory: (gradientMap) => {
2270
+      const group = new THREE.Group()
2271
+      const smockMat = new THREE.MeshToonMaterial({ color: 0x4a4a6a, gradientMap })
2272
+      const beretMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
2273
+      const paletteMat = new THREE.MeshToonMaterial({ color: 0xdeb887, gradientMap })
2274
+
2275
+      // Paint splatter colors
2276
+      const paintColors = [0xff4444, 0x44ff44, 0x4444ff, 0xffff44, 0xff44ff]
2277
+
2278
+      // Smock body
2279
+      const smockGeom = new THREE.SphereGeometry(0.46, 10, 8)
2280
+      smockGeom.scale(1, 0.85, 1)
2281
+      const smock = new THREE.Mesh(smockGeom, smockMat)
2282
+      smock.position.set(0, 0.35, 0)
2283
+      group.add(smock)
2284
+
2285
+      // Paint splatters on smock
2286
+      const splatPositions = [
2287
+        { x: 0.4, y: 0.4, z: 0.2 },
2288
+        { x: 0.42, y: 0.25, z: -0.15 },
2289
+        { x: 0.38, y: 0.45, z: -0.1 },
2290
+        { x: 0.35, y: 0.3, z: 0.25 },
2291
+        { x: 0.4, y: 0.2, z: 0.1 }
2292
+      ]
2293
+      for (let i = 0; i < splatPositions.length; i++) {
2294
+        const splatGeom = new THREE.SphereGeometry(0.03 + Math.random() * 0.02, 6, 4)
2295
+        splatGeom.scale(1, 0.3, 1)
2296
+        const splatMat = new THREE.MeshToonMaterial({ color: paintColors[i], gradientMap })
2297
+        const splat = new THREE.Mesh(splatGeom, splatMat)
2298
+        splat.position.set(splatPositions[i].x, splatPositions[i].y, splatPositions[i].z)
2299
+        splat.rotation.y = Math.random() * Math.PI
2300
+        group.add(splat)
2301
+      }
2302
+
2303
+      // Smock collar/neckline
2304
+      const collarGeom = new THREE.TorusGeometry(0.22, 0.03, 4, 12)
2305
+      const collar = new THREE.Mesh(collarGeom, smockMat)
2306
+      collar.position.set(0.05, 0.6, 0)
2307
+      collar.rotation.x = Math.PI / 2
2308
+      collar.rotation.y = 0.2
2309
+      group.add(collar)
2310
+
2311
+      // Beret
2312
+      const beretGeom = new THREE.SphereGeometry(0.18, 10, 8)
2313
+      beretGeom.scale(1.3, 0.5, 1.3)
2314
+      const beret = new THREE.Mesh(beretGeom, beretMat)
2315
+      beret.position.set(0.15, 0.78, 0.05)
2316
+      beret.rotation.z = -0.3
2317
+      group.add(beret)
2318
+
2319
+      // Beret stem/tip
2320
+      const stemGeom = new THREE.SphereGeometry(0.03, 6, 4)
2321
+      const stem = new THREE.Mesh(stemGeom, beretMat)
2322
+      stem.position.set(0.15, 0.85, 0.05)
2323
+      group.add(stem)
2324
+
2325
+      // Beret band
2326
+      const bandGeom = new THREE.TorusGeometry(0.15, 0.015, 4, 12)
2327
+      const band = new THREE.Mesh(bandGeom, beretMat)
2328
+      band.position.set(0.13, 0.72, 0)
2329
+      band.rotation.x = Math.PI / 2
2330
+      band.rotation.y = 0.3
2331
+      group.add(band)
2332
+
2333
+      // Painter's palette (held by tentacle)
2334
+      const paletteShape = new THREE.Shape()
2335
+      paletteShape.moveTo(0, 0)
2336
+      paletteShape.quadraticCurveTo(0.1, 0.05, 0.12, 0)
2337
+      paletteShape.quadraticCurveTo(0.14, -0.06, 0.08, -0.1)
2338
+      paletteShape.quadraticCurveTo(0, -0.12, -0.08, -0.1)
2339
+      paletteShape.quadraticCurveTo(-0.12, -0.04, -0.1, 0.02)
2340
+      paletteShape.quadraticCurveTo(-0.06, 0.06, 0, 0)
2341
+
2342
+      const paletteGeom = new THREE.ExtrudeGeometry(paletteShape, {
2343
+        steps: 1, depth: 0.015, bevelEnabled: false
2344
+      })
2345
+      const palette = new THREE.Mesh(paletteGeom, paletteMat)
2346
+      palette.position.set(0.2, 0.15, 0.35)
2347
+      palette.rotation.x = -0.5
2348
+      palette.rotation.y = 0.3
2349
+      group.add(palette)
2350
+
2351
+      // Thumb hole
2352
+      const holeGeom = new THREE.TorusGeometry(0.02, 0.008, 4, 8)
2353
+      const holeMat = new THREE.MeshToonMaterial({ color: 0x000000, gradientMap })
2354
+      const hole = new THREE.Mesh(holeGeom, holeMat)
2355
+      hole.position.set(0.12, 0.15, 0.35)
2356
+      hole.rotation.x = -0.5 + Math.PI / 2
2357
+      hole.rotation.z = 0.3
2358
+      group.add(hole)
2359
+
2360
+      // Paint blobs on palette
2361
+      const blobPositions = [
2362
+        { x: 0.22, y: 0.18, z: 0.32 },
2363
+        { x: 0.18, y: 0.17, z: 0.38 },
2364
+        { x: 0.25, y: 0.16, z: 0.36 },
2365
+        { x: 0.2, y: 0.19, z: 0.34 }
2366
+      ]
2367
+      for (let i = 0; i < blobPositions.length; i++) {
2368
+        const blobGeom = new THREE.SphereGeometry(0.015, 6, 4)
2369
+        blobGeom.scale(1, 0.4, 1)
2370
+        const blobMat = new THREE.MeshToonMaterial({ color: paintColors[i], gradientMap })
2371
+        const blob = new THREE.Mesh(blobGeom, blobMat)
2372
+        blob.position.set(blobPositions[i].x, blobPositions[i].y, blobPositions[i].z)
2373
+        group.add(blob)
2374
+      }
2375
+
2376
+      // Paintbrush (tiny, held)
2377
+      const brushHandleGeom = new THREE.CylinderGeometry(0.008, 0.01, 0.12, 4)
2378
+      const brushHandleMat = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap })
2379
+      const brushHandle = new THREE.Mesh(brushHandleGeom, brushHandleMat)
2380
+      brushHandle.position.set(0.15, 0.08, 0.4)
2381
+      brushHandle.rotation.z = 0.8
2382
+      brushHandle.rotation.x = 0.3
2383
+      group.add(brushHandle)
2384
+
2385
+      // Brush bristles
2386
+      const bristleGeom = new THREE.ConeGeometry(0.015, 0.03, 6)
2387
+      const bristleMat = new THREE.MeshToonMaterial({ color: 0x4444ff, gradientMap })
2388
+      const bristles = new THREE.Mesh(bristleGeom, bristleMat)
2389
+      bristles.position.set(0.1, 0.12, 0.42)
2390
+      bristles.rotation.z = 0.8 + Math.PI
2391
+      bristles.rotation.x = 0.3
2392
+      group.add(bristles)
2393
+
1485
       return group
2394
       return group
1486
     }
2395
     }
1487
   }
2396
   }