solanapolis 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.
Files changed (202) hide show
  1. package/README.md +518 -0
  2. package/bin/solanapolis.js +197 -0
  3. package/convex/_generated/api.d.ts +175 -0
  4. package/convex/_generated/api.js +23 -0
  5. package/convex/_generated/dataModel.d.ts +60 -0
  6. package/convex/_generated/server.d.ts +143 -0
  7. package/convex/_generated/server.js +93 -0
  8. package/convex/agent/conversation.ts +352 -0
  9. package/convex/agent/embeddingsCache.ts +110 -0
  10. package/convex/agent/memory.ts +450 -0
  11. package/convex/agent/schema.ts +53 -0
  12. package/convex/aiChat.ts +54 -0
  13. package/convex/aiTown/agent.ts +382 -0
  14. package/convex/aiTown/agentDescription.ts +27 -0
  15. package/convex/aiTown/agentInputs.ts +155 -0
  16. package/convex/aiTown/agentOperations.ts +178 -0
  17. package/convex/aiTown/conversation.ts +395 -0
  18. package/convex/aiTown/conversationMembership.ts +38 -0
  19. package/convex/aiTown/game.ts +371 -0
  20. package/convex/aiTown/ids.ts +32 -0
  21. package/convex/aiTown/inputHandler.ts +9 -0
  22. package/convex/aiTown/inputs.ts +25 -0
  23. package/convex/aiTown/insertInput.ts +20 -0
  24. package/convex/aiTown/location.ts +32 -0
  25. package/convex/aiTown/main.ts +154 -0
  26. package/convex/aiTown/movement.ts +189 -0
  27. package/convex/aiTown/player.ts +310 -0
  28. package/convex/aiTown/playerDescription.ts +35 -0
  29. package/convex/aiTown/schema.ts +79 -0
  30. package/convex/aiTown/world.ts +65 -0
  31. package/convex/aiTown/worldMap.ts +74 -0
  32. package/convex/chat.ts +79 -0
  33. package/convex/constants.ts +78 -0
  34. package/convex/convex.config.ts +6 -0
  35. package/convex/crons.ts +89 -0
  36. package/convex/engine/abstractGame.ts +199 -0
  37. package/convex/engine/historicalObject.ts +355 -0
  38. package/convex/engine/schema.ts +56 -0
  39. package/convex/http.ts +36 -0
  40. package/convex/init.ts +110 -0
  41. package/convex/messages.ts +53 -0
  42. package/convex/npcCarAgents.ts +415 -0
  43. package/convex/schema.ts +61 -0
  44. package/convex/streaming.ts +23 -0
  45. package/convex/testing.ts +202 -0
  46. package/convex/tsconfig.json +18 -0
  47. package/convex/util/FastIntegerCompression.ts +221 -0
  48. package/convex/util/assertNever.ts +4 -0
  49. package/convex/util/asyncMap.ts +20 -0
  50. package/convex/util/compression.ts +71 -0
  51. package/convex/util/geometry.ts +132 -0
  52. package/convex/util/isSimpleObject.ts +11 -0
  53. package/convex/util/llm.ts +724 -0
  54. package/convex/util/minheap.ts +38 -0
  55. package/convex/util/object.ts +22 -0
  56. package/convex/util/sleep.ts +3 -0
  57. package/convex/util/types.ts +33 -0
  58. package/convex/util/xxhash.ts +228 -0
  59. package/convex/world.ts +257 -0
  60. package/data/animations/campfire.json +45 -0
  61. package/data/animations/gentlesparkle.json +37 -0
  62. package/data/animations/gentlesplash.json +61 -0
  63. package/data/animations/gentlewaterfall.json +61 -0
  64. package/data/animations/windmill.json +78 -0
  65. package/data/characters.ts +121 -0
  66. package/data/convertMap.js +74 -0
  67. package/data/gentle.js +330 -0
  68. package/data/spritesheets/f1.ts +75 -0
  69. package/data/spritesheets/f2.ts +75 -0
  70. package/data/spritesheets/f3.ts +75 -0
  71. package/data/spritesheets/f4.ts +75 -0
  72. package/data/spritesheets/f5.ts +75 -0
  73. package/data/spritesheets/f6.ts +75 -0
  74. package/data/spritesheets/f7.ts +75 -0
  75. package/data/spritesheets/f8.ts +75 -0
  76. package/data/spritesheets/p1.ts +59 -0
  77. package/data/spritesheets/p2.ts +59 -0
  78. package/data/spritesheets/p3.ts +59 -0
  79. package/data/spritesheets/player.ts +59 -0
  80. package/data/spritesheets/types.ts +26 -0
  81. package/eslint.config.mjs +37 -0
  82. package/next.config.ts +7 -0
  83. package/package.json +85 -0
  84. package/postcss.config.mjs +7 -0
  85. package/public/file.svg +1 -0
  86. package/public/globe.svg +1 -0
  87. package/public/helius-icon.svg +84 -0
  88. package/public/helius-logo.svg +85 -0
  89. package/public/next.svg +1 -0
  90. package/public/plane.glb +0 -0
  91. package/public/vercel.svg +1 -0
  92. package/public/window.svg +1 -0
  93. package/scripts/clear-city.ts +74 -0
  94. package/scripts/seed-wallets.ts +185 -0
  95. package/scripts/setup-webhook.ts +73 -0
  96. package/src/app/api/auth/callback/route.ts +6 -0
  97. package/src/app/api/auth/link-wallet/route.ts +6 -0
  98. package/src/app/api/auth/phantom/route.ts +6 -0
  99. package/src/app/api/broadcast-position/route.ts +59 -0
  100. package/src/app/api/leaderboard/route.ts +85 -0
  101. package/src/app/api/network-stats/route.ts +86 -0
  102. package/src/app/api/parcel-reward/route.ts +181 -0
  103. package/src/app/api/queue-status/route.ts +30 -0
  104. package/src/app/api/snapshots/route.ts +37 -0
  105. package/src/app/api/transactions/enhanced/route.ts +57 -0
  106. package/src/app/api/treasury/route.ts +83 -0
  107. package/src/app/api/wallet/[address]/balances/route.ts +124 -0
  108. package/src/app/api/wallet/[address]/identity/route.ts +32 -0
  109. package/src/app/api/wallet/[address]/route.ts +216 -0
  110. package/src/app/api/wallet/[address]/traded-tokens/route.ts +41 -0
  111. package/src/app/api/wallets/route.ts +68 -0
  112. package/src/app/api/webhooks/helius/route.ts +76 -0
  113. package/src/app/auth/callback/page.tsx +29 -0
  114. package/src/app/favicon.ico +0 -0
  115. package/src/app/globals.css +39 -0
  116. package/src/app/layout.tsx +43 -0
  117. package/src/app/page.tsx +16 -0
  118. package/src/components/AITownNPCs.tsx +206 -0
  119. package/src/components/ActivityFeed.tsx +189 -0
  120. package/src/components/AuthPanel.tsx +163 -0
  121. package/src/components/BeachScene.tsx +280 -0
  122. package/src/components/Building.tsx +138 -0
  123. package/src/components/CesiumFlight.tsx +1768 -0
  124. package/src/components/CesiumGlobe.tsx +616 -0
  125. package/src/components/CitizenCard.tsx +442 -0
  126. package/src/components/CitizenCardModal.tsx +153 -0
  127. package/src/components/CityGrid.tsx +313 -0
  128. package/src/components/CityLandmarks.tsx +427 -0
  129. package/src/components/CityScene.tsx +1289 -0
  130. package/src/components/CitySlotsBadge.tsx +68 -0
  131. package/src/components/CockpitHUD.tsx +460 -0
  132. package/src/components/ConvexWrapper.tsx +19 -0
  133. package/src/components/DubaiDistrict.tsx +630 -0
  134. package/src/components/FlightMiniMap.tsx +133 -0
  135. package/src/components/GameChat.tsx +383 -0
  136. package/src/components/GameHUD.tsx +393 -0
  137. package/src/components/Ground.tsx +14 -0
  138. package/src/components/HowItWorksModal.tsx +251 -0
  139. package/src/components/IngestionBanner.tsx +123 -0
  140. package/src/components/InstancedBuildings.tsx +316 -0
  141. package/src/components/InstancedCars.tsx +504 -0
  142. package/src/components/InstancedCityPlanes.tsx +259 -0
  143. package/src/components/InstancedHouses.tsx +246 -0
  144. package/src/components/InstancedLampPosts.tsx +201 -0
  145. package/src/components/InstancedResidentCars.tsx +357 -0
  146. package/src/components/InstancedRoadDashes.tsx +42 -0
  147. package/src/components/InstancedSkyscrapers.tsx +434 -0
  148. package/src/components/InstancedTrees.tsx +67 -0
  149. package/src/components/LeaderboardPanel.tsx +136 -0
  150. package/src/components/MultiplayerPlanes.tsx +128 -0
  151. package/src/components/NetworkStats.tsx +83 -0
  152. package/src/components/NewBuildingSpotlight.tsx +93 -0
  153. package/src/components/ParcelChallengeBanner.tsx +242 -0
  154. package/src/components/ParcelReward.tsx +191 -0
  155. package/src/components/Park.tsx +42 -0
  156. package/src/components/PhantomWrapper.tsx +22 -0
  157. package/src/components/PixelStreamViewer.tsx +335 -0
  158. package/src/components/PlaneMode.tsx +190 -0
  159. package/src/components/PlayerCar.tsx +211 -0
  160. package/src/components/PlayerPlane.tsx +255 -0
  161. package/src/components/ProjectileRenderer.tsx +249 -0
  162. package/src/components/QueueStatusBanner.tsx +86 -0
  163. package/src/components/RealPlayerTags.tsx +82 -0
  164. package/src/components/SceneLighting.tsx +382 -0
  165. package/src/components/SelectionBeam.tsx +59 -0
  166. package/src/components/SwapPanel.tsx +104 -0
  167. package/src/components/SwapParticles.tsx +237 -0
  168. package/src/components/TreasureGate.tsx +505 -0
  169. package/src/components/WalletPanel.tsx +421 -0
  170. package/src/components/WalletSearch.tsx +244 -0
  171. package/src/components/WelcomeOverlay.tsx +135 -0
  172. package/src/components/WindowTooltip.tsx +498 -0
  173. package/src/context/AuthContext.tsx +230 -0
  174. package/src/lib/bot-detection.ts +125 -0
  175. package/src/lib/building-math.ts +136 -0
  176. package/src/lib/building-shader.ts +253 -0
  177. package/src/lib/car-paths.ts +244 -0
  178. package/src/lib/car-system.ts +182 -0
  179. package/src/lib/city-constants.ts +29 -0
  180. package/src/lib/city-slots.ts +35 -0
  181. package/src/lib/city-zoning.ts +64 -0
  182. package/src/lib/collision-map.ts +147 -0
  183. package/src/lib/day-night.ts +252 -0
  184. package/src/lib/export-card.ts +28 -0
  185. package/src/lib/helius-webhook.ts +90 -0
  186. package/src/lib/helius.ts +74 -0
  187. package/src/lib/house-shader.ts +119 -0
  188. package/src/lib/mock-data.ts +56 -0
  189. package/src/lib/multiplayer-manager.ts +329 -0
  190. package/src/lib/plane-physics.ts +66 -0
  191. package/src/lib/player-car.ts +147 -0
  192. package/src/lib/player-plane.ts +200 -0
  193. package/src/lib/projectile-system.ts +272 -0
  194. package/src/lib/skyscraper-types.ts +52 -0
  195. package/src/lib/sound-engine.ts +464 -0
  196. package/src/lib/supabase-admin.ts +9 -0
  197. package/src/lib/supabase.ts +8 -0
  198. package/src/lib/swap-events.ts +70 -0
  199. package/src/middleware.ts +37 -0
  200. package/src/types/phantom.d.ts +16 -0
  201. package/src/types/wallet.ts +20 -0
  202. package/tsconfig.json +34 -0
@@ -0,0 +1,427 @@
1
+ "use client";
2
+
3
+ import React, { useMemo, useRef } from "react";
4
+ import { useFrame } from "@react-three/fiber";
5
+ import * as THREE from "three";
6
+ import { Html } from "@react-three/drei";
7
+
8
+ /**
9
+ * Iconic landmark structures placed around the city for visual interest and navigation.
10
+ * Each landmark is a stylized low-poly version built from primitives.
11
+ */
12
+
13
+ interface LandmarkDef {
14
+ name: string;
15
+ emoji: string;
16
+ x: number;
17
+ z: number;
18
+ build: (mats: LandmarkMats) => React.ReactNode;
19
+ }
20
+
21
+ interface LandmarkMats {
22
+ steel: THREE.MeshStandardMaterial;
23
+ gold: THREE.MeshStandardMaterial;
24
+ stone: THREE.MeshStandardMaterial;
25
+ glass: THREE.MeshStandardMaterial;
26
+ copper: THREE.MeshStandardMaterial;
27
+ white: THREE.MeshStandardMaterial;
28
+ red: THREE.MeshStandardMaterial;
29
+ dark: THREE.MeshStandardMaterial;
30
+ neon: THREE.MeshStandardMaterial;
31
+ }
32
+
33
+ // ─── Empire State Building ───────────────────────────────────────────────────
34
+ function EmpireState({ mats }: { mats: LandmarkMats }) {
35
+ return (
36
+ <group>
37
+ {/* Main tower base */}
38
+ <mesh position={[0, 15, 0]} material={mats.stone}>
39
+ <boxGeometry args={[8, 30, 8]} />
40
+ </mesh>
41
+ {/* Mid section */}
42
+ <mesh position={[0, 35, 0]} material={mats.stone}>
43
+ <boxGeometry args={[6, 10, 6]} />
44
+ </mesh>
45
+ {/* Upper section */}
46
+ <mesh position={[0, 45, 0]} material={mats.stone}>
47
+ <boxGeometry args={[4, 10, 4]} />
48
+ </mesh>
49
+ {/* Spire */}
50
+ <mesh position={[0, 56, 0]} material={mats.steel}>
51
+ <cylinderGeometry args={[0.3, 1.2, 12, 8]} />
52
+ </mesh>
53
+ {/* Antenna tip — glowing */}
54
+ <mesh position={[0, 63, 0]} material={mats.neon}>
55
+ <sphereGeometry args={[0.4, 8, 8]} />
56
+ </mesh>
57
+ {/* Windows (dark strips) */}
58
+ {[10, 20, 30, 38, 44].map((y, i) => (
59
+ <mesh key={i} position={[0, y, 4.01]} material={mats.glass}>
60
+ <planeGeometry args={[6, 1]} />
61
+ </mesh>
62
+ ))}
63
+ {/* Art deco crown detail */}
64
+ <mesh position={[0, 41, 0]} material={mats.gold}>
65
+ <boxGeometry args={[7, 0.5, 7]} />
66
+ </mesh>
67
+ </group>
68
+ );
69
+ }
70
+
71
+ // ─── Solana Obelisk (Washington Monument style) ──────────────────────────────
72
+ function SolanaObelisk({ mats }: { mats: LandmarkMats }) {
73
+ return (
74
+ <group>
75
+ {/* Main shaft */}
76
+ <mesh position={[0, 25, 0]} material={mats.white}>
77
+ <boxGeometry args={[4, 50, 4]} />
78
+ </mesh>
79
+ {/* Pyramid cap */}
80
+ <mesh position={[0, 52, 0]} material={mats.gold}>
81
+ <coneGeometry args={[2.8, 5, 4]} />
82
+ </mesh>
83
+ {/* Solana logo accent rings */}
84
+ {[10, 20, 30, 40].map((y, i) => (
85
+ <mesh key={i} position={[0, y, 0]} rotation={[0, Math.PI / 4, 0]} material={mats.neon}>
86
+ <torusGeometry args={[2.5, 0.1, 4, 4]} />
87
+ </mesh>
88
+ ))}
89
+ {/* Base platform */}
90
+ <mesh position={[0, 0.25, 0]} material={mats.stone}>
91
+ <boxGeometry args={[10, 0.5, 10]} />
92
+ </mesh>
93
+ </group>
94
+ );
95
+ }
96
+
97
+ // ─── Crypto Colosseum (Roman Colosseum) ──────────────────────────────────────
98
+ function CryptoColosseum({ mats }: { mats: LandmarkMats }) {
99
+ return (
100
+ <group>
101
+ {/* Outer ring */}
102
+ <mesh position={[0, 5, 0]} material={mats.stone}>
103
+ <cylinderGeometry args={[12, 13, 10, 24, 1, true]} />
104
+ </mesh>
105
+ {/* Inner ring */}
106
+ <mesh position={[0, 4, 0]} material={mats.stone}>
107
+ <cylinderGeometry args={[9, 10, 8, 20, 1, true]} />
108
+ </mesh>
109
+ {/* Floor */}
110
+ <mesh position={[0, 0.1, 0]} rotation={[-Math.PI / 2, 0, 0]} material={mats.dark}>
111
+ <circleGeometry args={[9, 20]} />
112
+ </mesh>
113
+ {/* Arches — decorative columns around the rim */}
114
+ {Array.from({ length: 16 }).map((_, i) => {
115
+ const angle = (i / 16) * Math.PI * 2;
116
+ return (
117
+ <mesh
118
+ key={i}
119
+ position={[Math.cos(angle) * 12.5, 5, Math.sin(angle) * 12.5]}
120
+ material={mats.stone}
121
+ >
122
+ <boxGeometry args={[0.5, 10, 1.5]} />
123
+ </mesh>
124
+ );
125
+ })}
126
+ {/* Top crown */}
127
+ <mesh position={[0, 10.5, 0]} material={mats.copper}>
128
+ <torusGeometry args={[12.5, 0.3, 4, 24]} />
129
+ </mesh>
130
+ </group>
131
+ );
132
+ }
133
+
134
+ // ─── DeFi Tower (Eiffel Tower inspired) ──────────────────────────────────────
135
+ function DeFiTower({ mats }: { mats: LandmarkMats }) {
136
+ return (
137
+ <group>
138
+ {/* Four legs */}
139
+ {[[-3, 3], [3, 3], [-3, -3], [3, -3]].map(([xo, zo], i) => (
140
+ <mesh
141
+ key={i}
142
+ position={[xo * 0.5, 12, zo * 0.5]}
143
+ rotation={[
144
+ zo > 0 ? -0.15 : 0.15,
145
+ 0,
146
+ xo > 0 ? 0.15 : -0.15,
147
+ ]}
148
+ material={mats.steel}
149
+ >
150
+ <boxGeometry args={[1.5, 26, 1.5]} />
151
+ </mesh>
152
+ ))}
153
+ {/* Platform 1 */}
154
+ <mesh position={[0, 10, 0]} material={mats.steel}>
155
+ <boxGeometry args={[7, 0.5, 7]} />
156
+ </mesh>
157
+ {/* Platform 2 */}
158
+ <mesh position={[0, 20, 0]} material={mats.steel}>
159
+ <boxGeometry args={[4, 0.5, 4]} />
160
+ </mesh>
161
+ {/* Upper shaft */}
162
+ <mesh position={[0, 30, 0]} material={mats.steel}>
163
+ <boxGeometry args={[1.5, 18, 1.5]} />
164
+ </mesh>
165
+ {/* Antenna */}
166
+ <mesh position={[0, 41, 0]} material={mats.steel}>
167
+ <cylinderGeometry args={[0.1, 0.5, 4, 6]} />
168
+ </mesh>
169
+ {/* Top beacon */}
170
+ <mesh position={[0, 43.5, 0]} material={mats.neon}>
171
+ <sphereGeometry args={[0.5, 8, 8]} />
172
+ </mesh>
173
+ {/* Cross braces */}
174
+ {[5, 15].map((y, i) => (
175
+ <group key={i}>
176
+ <mesh position={[0, y, 0]} rotation={[0, 0, Math.PI / 4]} material={mats.steel}>
177
+ <boxGeometry args={[0.2, 8, 0.2]} />
178
+ </mesh>
179
+ <mesh position={[0, y, 0]} rotation={[0, Math.PI / 2, Math.PI / 4]} material={mats.steel}>
180
+ <boxGeometry args={[0.2, 8, 0.2]} />
181
+ </mesh>
182
+ </group>
183
+ ))}
184
+ </group>
185
+ );
186
+ }
187
+
188
+ // ─── Moon Pyramid (Pyramid of Giza) ─────────────────────────────────────────
189
+ function MoonPyramid({ mats }: { mats: LandmarkMats }) {
190
+ return (
191
+ <group>
192
+ {/* Main pyramid */}
193
+ <mesh position={[0, 10, 0]} material={mats.gold}>
194
+ <coneGeometry args={[14, 20, 4]} />
195
+ </mesh>
196
+ {/* Smaller companion pyramid */}
197
+ <mesh position={[18, 5, 4]} material={mats.gold}>
198
+ <coneGeometry args={[7, 10, 4]} />
199
+ </mesh>
200
+ {/* Even smaller one */}
201
+ <mesh position={[14, 3, -10]} material={mats.gold}>
202
+ <coneGeometry args={[4, 6, 4]} />
203
+ </mesh>
204
+ {/* Glowing capstone */}
205
+ <mesh position={[0, 20.5, 0]} material={mats.neon}>
206
+ <coneGeometry args={[1.5, 2.5, 4]} />
207
+ </mesh>
208
+ {/* Sand base */}
209
+ <mesh position={[5, 0.1, 0]} rotation={[-Math.PI / 2, 0, 0]} material={mats.stone}>
210
+ <circleGeometry args={[22, 6]} />
211
+ </mesh>
212
+ </group>
213
+ );
214
+ }
215
+
216
+ // ─── SOL Gate (Golden Gate Bridge section) ───────────────────────────────────
217
+ function SolGate({ mats }: { mats: LandmarkMats }) {
218
+ return (
219
+ <group>
220
+ {/* Two towers */}
221
+ <mesh position={[-10, 18, 0]} material={mats.red}>
222
+ <boxGeometry args={[2.5, 36, 2.5]} />
223
+ </mesh>
224
+ <mesh position={[10, 18, 0]} material={mats.red}>
225
+ <boxGeometry args={[2.5, 36, 2.5]} />
226
+ </mesh>
227
+ {/* Top beam */}
228
+ <mesh position={[0, 35, 0]} material={mats.red}>
229
+ <boxGeometry args={[22, 1.5, 2.5]} />
230
+ </mesh>
231
+ {/* Road deck */}
232
+ <mesh position={[0, 8, 0]} material={mats.dark}>
233
+ <boxGeometry args={[24, 0.8, 6]} />
234
+ </mesh>
235
+ {/* Suspension cables (simplified as thin boxes) */}
236
+ {[-7, -4, 0, 4, 7].map((x, i) => (
237
+ <mesh key={i} position={[x, 22, 0]} rotation={[0, 0, x * 0.02]} material={mats.red}>
238
+ <boxGeometry args={[0.15, 28, 0.15]} />
239
+ </mesh>
240
+ ))}
241
+ {/* Road stripes */}
242
+ {[-6, 0, 6].map((x, i) => (
243
+ <mesh key={`s${i}`} position={[x, 8.41, 0]} material={mats.gold}>
244
+ <boxGeometry args={[0.2, 0.01, 6]} />
245
+ </mesh>
246
+ ))}
247
+ </group>
248
+ );
249
+ }
250
+
251
+ // ─── Ape Tower (Big Ben / Clock Tower) ───────────────────────────────────────
252
+ function ApeTower({ mats }: { mats: LandmarkMats }) {
253
+ return (
254
+ <group>
255
+ {/* Main shaft */}
256
+ <mesh position={[0, 17, 0]} material={mats.stone}>
257
+ <boxGeometry args={[5, 34, 5]} />
258
+ </mesh>
259
+ {/* Clock face (4 sides) */}
260
+ {[
261
+ [0, 30, 2.51, 0] as const,
262
+ [0, 30, -2.51, Math.PI] as const,
263
+ [2.51, 30, 0, Math.PI / 2] as const,
264
+ [-2.51, 30, 0, -Math.PI / 2] as const,
265
+ ].map(([x, y, z, ry], i) => (
266
+ <group key={i} position={[x, y, z]} rotation={[0, ry, 0]}>
267
+ <mesh material={mats.white}>
268
+ <circleGeometry args={[2, 16]} />
269
+ </mesh>
270
+ {/* Hour hand */}
271
+ <mesh position={[0, 0.3, 0.05]} material={mats.dark}>
272
+ <boxGeometry args={[0.12, 1.2, 0.05]} />
273
+ </mesh>
274
+ {/* Minute hand */}
275
+ <mesh position={[0.3, 0.1, 0.05]} rotation={[0, 0, -0.8]} material={mats.dark}>
276
+ <boxGeometry args={[0.08, 1.6, 0.05]} />
277
+ </mesh>
278
+ </group>
279
+ ))}
280
+ {/* Roof */}
281
+ <mesh position={[0, 36, 0]} material={mats.copper}>
282
+ <coneGeometry args={[3.5, 5, 4]} />
283
+ </mesh>
284
+ {/* Spire */}
285
+ <mesh position={[0, 40, 0]} material={mats.gold}>
286
+ <cylinderGeometry args={[0.15, 0.6, 3, 6]} />
287
+ </mesh>
288
+ </group>
289
+ );
290
+ }
291
+
292
+ // ─── Token Sphinx ────────────────────────────────────────────────────────────
293
+ function TokenSphinx({ mats }: { mats: LandmarkMats }) {
294
+ return (
295
+ <group>
296
+ {/* Body */}
297
+ <mesh position={[0, 2, 0]} material={mats.stone}>
298
+ <boxGeometry args={[5, 4, 12]} />
299
+ </mesh>
300
+ {/* Head */}
301
+ <mesh position={[0, 5.5, -5]} material={mats.stone}>
302
+ <boxGeometry args={[3.5, 4, 3.5]} />
303
+ </mesh>
304
+ {/* Headdress */}
305
+ <mesh position={[0, 7, -5]} material={mats.gold}>
306
+ <boxGeometry args={[4, 1.5, 4]} />
307
+ </mesh>
308
+ {/* Paws */}
309
+ <mesh position={[-2, 0.5, -8]} material={mats.stone}>
310
+ <boxGeometry args={[1.5, 1, 4]} />
311
+ </mesh>
312
+ <mesh position={[2, 0.5, -8]} material={mats.stone}>
313
+ <boxGeometry args={[1.5, 1, 4]} />
314
+ </mesh>
315
+ {/* Glowing eyes */}
316
+ <mesh position={[-0.8, 5.5, -6.76]} material={mats.neon}>
317
+ <sphereGeometry args={[0.25, 6, 6]} />
318
+ </mesh>
319
+ <mesh position={[0.8, 5.5, -6.76]} material={mats.neon}>
320
+ <sphereGeometry args={[0.25, 6, 6]} />
321
+ </mesh>
322
+ </group>
323
+ );
324
+ }
325
+
326
+ // ─── Landmark definitions with positions ────────────────────────────────────
327
+ const LANDMARKS: LandmarkDef[] = [
328
+ {
329
+ name: "Empire State NFT",
330
+ emoji: "🏙️",
331
+ x: -200, z: -180,
332
+ build: (m) => <EmpireState mats={m} />,
333
+ },
334
+ {
335
+ name: "Solana Obelisk",
336
+ emoji: "⚡",
337
+ x: 0, z: -220,
338
+ build: (m) => <SolanaObelisk mats={m} />,
339
+ },
340
+ {
341
+ name: "Crypto Colosseum",
342
+ emoji: "🏛️",
343
+ x: 200, z: 180,
344
+ build: (m) => <CryptoColosseum mats={m} />,
345
+ },
346
+ {
347
+ name: "DeFi Tower",
348
+ emoji: "🗼",
349
+ x: -180, z: 200,
350
+ build: (m) => <DeFiTower mats={m} />,
351
+ },
352
+ {
353
+ name: "Moon Pyramids",
354
+ emoji: "🔺",
355
+ x: 220, z: -200,
356
+ build: (m) => <MoonPyramid mats={m} />,
357
+ },
358
+ {
359
+ name: "SOL Gate Bridge",
360
+ emoji: "🌉",
361
+ x: 0, z: 260,
362
+ build: (m) => <SolGate mats={m} />,
363
+ },
364
+ {
365
+ name: "Ape Tower",
366
+ emoji: "🕰️",
367
+ x: -230, z: 0,
368
+ build: (m) => <ApeTower mats={m} />,
369
+ },
370
+ {
371
+ name: "Token Sphinx",
372
+ emoji: "🦁",
373
+ x: 230, z: -30,
374
+ build: (m) => <TokenSphinx mats={m} />,
375
+ },
376
+ ];
377
+
378
+ export default function CityLandmarks() {
379
+ const beaconRef = useRef<THREE.Mesh>(null);
380
+
381
+ // Shared materials
382
+ const mats = useMemo<LandmarkMats>(() => ({
383
+ steel: new THREE.MeshStandardMaterial({ color: "#8899aa", metalness: 0.8, roughness: 0.3 }),
384
+ gold: new THREE.MeshStandardMaterial({ color: "#d4a843", metalness: 0.9, roughness: 0.2, emissive: "#d4a843", emissiveIntensity: 0.15 }),
385
+ stone: new THREE.MeshStandardMaterial({ color: "#b8a99a", metalness: 0.1, roughness: 0.9 }),
386
+ glass: new THREE.MeshStandardMaterial({ color: "#1a2a3a", metalness: 0.5, roughness: 0.1, transparent: true, opacity: 0.7 }),
387
+ copper: new THREE.MeshStandardMaterial({ color: "#5d8a6e", metalness: 0.7, roughness: 0.4 }),
388
+ white: new THREE.MeshStandardMaterial({ color: "#f0ede8", metalness: 0.1, roughness: 0.6 }),
389
+ red: new THREE.MeshStandardMaterial({ color: "#c4372a", metalness: 0.3, roughness: 0.5 }),
390
+ dark: new THREE.MeshStandardMaterial({ color: "#2a2a2a", metalness: 0.2, roughness: 0.8 }),
391
+ neon: new THREE.MeshStandardMaterial({ color: "#E35930", emissive: "#E35930", emissiveIntensity: 2.0, toneMapped: false }),
392
+ }), []);
393
+
394
+ // Animate neon glow
395
+ useFrame(({ clock }) => {
396
+ const t = clock.getElapsedTime();
397
+ mats.neon.emissiveIntensity = 1.5 + Math.sin(t * 2) * 0.8;
398
+ });
399
+
400
+ return (
401
+ <group>
402
+ {LANDMARKS.map((lm) => (
403
+ <group key={lm.name} position={[lm.x, 0, lm.z]}>
404
+ {/* The landmark geometry */}
405
+ {lm.build(mats)}
406
+
407
+ {/* Floating name label */}
408
+ <Html
409
+ center
410
+ position={[0, 50, 0]}
411
+ distanceFactor={30}
412
+ style={{ pointerEvents: "none" }}
413
+ >
414
+ <div className="text-center whitespace-nowrap">
415
+ <div className="px-3 py-1.5 rounded-xl bg-black/70 backdrop-blur-md border border-white/10 shadow-lg shadow-black/30">
416
+ <span className="text-sm mr-1.5">{lm.emoji}</span>
417
+ <span className="text-[10px] font-bold text-white/80 uppercase tracking-wider">
418
+ {lm.name}
419
+ </span>
420
+ </div>
421
+ </div>
422
+ </Html>
423
+ </group>
424
+ ))}
425
+ </group>
426
+ );
427
+ }