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,382 @@
1
+ "use client";
2
+
3
+ import { useRef, useEffect, useState, useMemo } from "react";
4
+ import { useFrame, useThree } from "@react-three/fiber";
5
+ import { Stars, Sky } from "@react-three/drei";
6
+ import * as THREE from "three";
7
+ import { getDayNightState, CYCLE_DURATION } from "@/lib/day-night";
8
+
9
+ interface SceneLightingProps {
10
+ timeRef: React.MutableRefObject<number>;
11
+ autoModeRef: React.MutableRefObject<boolean>;
12
+ }
13
+
14
+ // Sub-component: updates Sky props at ~2fps (plenty for slow day/night cycle)
15
+ function SkyController({ timeRef }: { timeRef: React.MutableRefObject<number> }) {
16
+ const [params, setParams] = useState<{
17
+ sunPosition: [number, number, number];
18
+ turbidity: number;
19
+ rayleigh: number;
20
+ mieCoefficient: number;
21
+ mieDirectionalG: number;
22
+ }>({
23
+ sunPosition: [200, 60, 30],
24
+ turbidity: 4,
25
+ rayleigh: 2,
26
+ mieCoefficient: 0.005,
27
+ mieDirectionalG: 0.8,
28
+ });
29
+
30
+ const lastRef = useRef(0);
31
+
32
+ useFrame(() => {
33
+ const now = performance.now();
34
+ if (now - lastRef.current < 500) return; // 2fps sky updates
35
+ lastRef.current = now;
36
+
37
+ const s = getDayNightState(timeRef.current);
38
+ setParams({
39
+ sunPosition: [s.sunPosition.x, s.sunPosition.y, s.sunPosition.z],
40
+ turbidity: s.turbidity,
41
+ rayleigh: s.rayleigh,
42
+ mieCoefficient: s.mieCoefficient,
43
+ mieDirectionalG: s.mieDirectionalG,
44
+ });
45
+ });
46
+
47
+ return (
48
+ <Sky
49
+ distance={4500}
50
+ sunPosition={params.sunPosition}
51
+ turbidity={params.turbidity}
52
+ rayleigh={params.rayleigh}
53
+ mieCoefficient={params.mieCoefficient}
54
+ mieDirectionalG={params.mieDirectionalG}
55
+ />
56
+ );
57
+ }
58
+
59
+ // ── Shooting Stars — animated meteor streaks at night ──────────────────────────
60
+ const METEOR_COUNT = 3;
61
+
62
+ function ShootingStars({ timeRef }: { timeRef: React.MutableRefObject<number> }) {
63
+ const groupRef = useRef<THREE.Group>(null);
64
+
65
+ // Pre-create meteor line geometries
66
+ const meteors = useMemo(() => {
67
+ return Array.from({ length: METEOR_COUNT }, () => {
68
+ const geo = new THREE.BufferGeometry();
69
+ const positions = new Float32Array(6); // 2 points × 3
70
+ geo.setAttribute("position", new THREE.BufferAttribute(positions, 3));
71
+ const mat = new THREE.LineBasicMaterial({
72
+ color: new THREE.Color("#ffffff"),
73
+ transparent: true,
74
+ opacity: 0,
75
+ linewidth: 1,
76
+ });
77
+ return {
78
+ geo,
79
+ mat,
80
+ // Lifecycle
81
+ startTime: Math.random() * 10,
82
+ duration: 1.5 + Math.random() * 2,
83
+ cooldown: 4 + Math.random() * 8,
84
+ elapsed: 0,
85
+ active: false,
86
+ // Trajectory
87
+ origin: new THREE.Vector3(),
88
+ direction: new THREE.Vector3(),
89
+ tailLength: 30 + Math.random() * 50,
90
+ };
91
+ });
92
+ }, []);
93
+
94
+ function resetMeteor(m: typeof meteors[0]) {
95
+ // Random position in upper hemisphere
96
+ const theta = Math.random() * Math.PI * 2;
97
+ const phi = Math.random() * 0.5 + 0.2; // above horizon
98
+ const r = 500 + Math.random() * 200;
99
+ m.origin.set(
100
+ Math.cos(theta) * Math.sin(phi) * r,
101
+ Math.cos(phi) * r,
102
+ Math.sin(theta) * Math.sin(phi) * r,
103
+ );
104
+ // Downward trajectory with random angle
105
+ m.direction.set(
106
+ (Math.random() - 0.5) * 0.6,
107
+ -0.8 - Math.random() * 0.2,
108
+ (Math.random() - 0.5) * 0.6,
109
+ ).normalize();
110
+ m.tailLength = 30 + Math.random() * 60;
111
+ m.duration = 0.8 + Math.random() * 1.5;
112
+ m.cooldown = 3 + Math.random() * 12;
113
+ m.elapsed = 0;
114
+ m.active = true;
115
+ }
116
+
117
+ useFrame((_, delta) => {
118
+ const state = getDayNightState(timeRef.current);
119
+ const nightFactor = state.starsOpacity;
120
+
121
+ for (const m of meteors) {
122
+ if (!m.active) {
123
+ m.cooldown -= delta;
124
+ if (m.cooldown <= 0 && nightFactor > 0.3) {
125
+ resetMeteor(m);
126
+ }
127
+ continue;
128
+ }
129
+
130
+ m.elapsed += delta;
131
+ const t = m.elapsed / m.duration;
132
+
133
+ if (t >= 1) {
134
+ m.active = false;
135
+ m.mat.opacity = 0;
136
+ m.cooldown = 3 + Math.random() * 12;
137
+ continue;
138
+ }
139
+
140
+ // Fade in/out: quick ramp up, slow fade
141
+ const fade = t < 0.15 ? t / 0.15 : 1 - Math.pow((t - 0.15) / 0.85, 2);
142
+ m.mat.opacity = fade * nightFactor * 0.85;
143
+
144
+ // Head position
145
+ const speed = 400;
146
+ const headX = m.origin.x + m.direction.x * speed * m.elapsed;
147
+ const headY = m.origin.y + m.direction.y * speed * m.elapsed;
148
+ const headZ = m.origin.z + m.direction.z * speed * m.elapsed;
149
+
150
+ // Tail position
151
+ const tailX = headX - m.direction.x * m.tailLength;
152
+ const tailY = headY - m.direction.y * m.tailLength;
153
+ const tailZ = headZ - m.direction.z * m.tailLength;
154
+
155
+ const positions = m.geo.attributes.position as THREE.BufferAttribute;
156
+ positions.setXYZ(0, tailX, tailY, tailZ);
157
+ positions.setXYZ(1, headX, headY, headZ);
158
+ positions.needsUpdate = true;
159
+
160
+ // Color: white-hot at head, blue-ish at tail based on speed
161
+ m.mat.color.setHSL(0.15, 0.1, 0.95);
162
+ }
163
+ });
164
+
165
+ // Imperatively create THREE.Line objects (avoid JSX <line> SVG type clash)
166
+ useEffect(() => {
167
+ const group = groupRef.current;
168
+ if (!group) return;
169
+ const lines: THREE.Line[] = [];
170
+ for (const m of meteors) {
171
+ const ln = new THREE.Line(m.geo, m.mat);
172
+ ln.frustumCulled = false;
173
+ group.add(ln);
174
+ lines.push(ln);
175
+ }
176
+ return () => {
177
+ for (const ln of lines) group.remove(ln);
178
+ };
179
+ }, [meteors]);
180
+
181
+ return <group ref={groupRef} />;
182
+ }
183
+
184
+ // ── City Glow — ambient light haze over the city at night ─────────────────────
185
+ function CityGlow({ timeRef }: { timeRef: React.MutableRefObject<number> }) {
186
+ const lightRef = useRef<THREE.PointLight>(null);
187
+ const light2Ref = useRef<THREE.PointLight>(null);
188
+
189
+ useFrame(() => {
190
+ const state = getDayNightState(timeRef.current);
191
+ const nightFactor = state.starsOpacity;
192
+
193
+ // Warm city glow from below — visible at night
194
+ if (lightRef.current) {
195
+ lightRef.current.intensity = nightFactor * 1.8;
196
+ const pulse = Math.sin(performance.now() * 0.0008) * 0.1 + 0.9;
197
+ lightRef.current.intensity *= pulse;
198
+ }
199
+
200
+ // Cool accent overhead — subtle purple city-reflection-on-clouds feel
201
+ if (light2Ref.current) {
202
+ light2Ref.current.intensity = nightFactor * 0.4;
203
+ }
204
+ });
205
+
206
+ return (
207
+ <>
208
+ <pointLight
209
+ ref={lightRef}
210
+ position={[0, -10, 0]}
211
+ color="#ff8844"
212
+ intensity={0}
213
+ distance={800}
214
+ decay={1.5}
215
+ />
216
+ <pointLight
217
+ ref={light2Ref}
218
+ position={[0, 250, 0]}
219
+ color="#6366f1"
220
+ intensity={0}
221
+ distance={1000}
222
+ decay={1.0}
223
+ />
224
+ </>
225
+ );
226
+ }
227
+
228
+ export default function SceneLighting({ timeRef, autoModeRef }: SceneLightingProps) {
229
+ const { scene } = useThree();
230
+ const sceneRef = useRef(scene);
231
+ const fogRef = useRef<THREE.Fog | null>(null);
232
+ const ambientRef = useRef<THREE.AmbientLight>(null);
233
+ const sunRef = useRef<THREE.DirectionalLight>(null);
234
+ const hemiRef = useRef<THREE.HemisphereLight>(null);
235
+ const accentRef = useRef<THREE.PointLight>(null);
236
+ const moonRef = useRef<THREE.DirectionalLight>(null);
237
+ const starsGroupRef = useRef<THREE.Group>(null);
238
+
239
+ // Clear flat background — Sky component renders the sky sphere
240
+ useEffect(() => {
241
+ const sceneObj = sceneRef.current;
242
+ sceneObj.background = null;
243
+
244
+ const fog = new THREE.Fog("#0a0a14", 500, 1200);
245
+ sceneObj.fog = fog;
246
+ fogRef.current = fog;
247
+
248
+ return () => {
249
+ if (sceneObj.fog === fog) sceneObj.fog = null;
250
+ };
251
+ }, []);
252
+
253
+ useFrame((_, delta) => {
254
+ if (autoModeRef.current) {
255
+ timeRef.current = (timeRef.current + delta / CYCLE_DURATION) % 1;
256
+ }
257
+
258
+ const state = getDayNightState(timeRef.current);
259
+
260
+ // ── Fog ────────────────────────────────────────────────────────────────────
261
+ const fog = fogRef.current;
262
+ if (fog) {
263
+ fog.color.copy(state.fogColor);
264
+ fog.near = state.fogNear;
265
+ fog.far = state.fogFar;
266
+ }
267
+
268
+ // ── Lights ────────────────────────────────────────────────────────────────
269
+ if (ambientRef.current) {
270
+ ambientRef.current.color.copy(state.ambientColor);
271
+ ambientRef.current.intensity = state.ambientIntensity;
272
+ }
273
+
274
+ if (sunRef.current) {
275
+ sunRef.current.color.copy(state.sunColor);
276
+ sunRef.current.intensity = state.sunIntensity;
277
+ sunRef.current.position.copy(state.sunPosition);
278
+ }
279
+
280
+ if (hemiRef.current) {
281
+ hemiRef.current.color.copy(state.hemiSkyColor);
282
+ hemiRef.current.groundColor.copy(state.hemiGroundColor);
283
+ hemiRef.current.intensity = state.hemiIntensity;
284
+ }
285
+
286
+ if (accentRef.current) {
287
+ accentRef.current.color.copy(state.accentColor);
288
+ accentRef.current.intensity = state.accentIntensity;
289
+ }
290
+
291
+ // Moon — subtle cool fill during night, invisible during day
292
+ if (moonRef.current) {
293
+ moonRef.current.intensity = state.starsOpacity * 0.35;
294
+ moonRef.current.position.set(
295
+ -state.sunPosition.x * 0.8,
296
+ Math.abs(state.sunPosition.y) * 0.4 + 80,
297
+ -state.sunPosition.z,
298
+ );
299
+ }
300
+
301
+ // ── Stars opacity ─────────────────────────────────────────────────────────
302
+ if (starsGroupRef.current) {
303
+ starsGroupRef.current.traverse((child) => {
304
+ if (child instanceof THREE.Points) {
305
+ const mat = child.material as THREE.PointsMaterial;
306
+ mat.transparent = true;
307
+ mat.opacity = state.starsOpacity;
308
+ }
309
+ });
310
+ }
311
+ });
312
+
313
+ return (
314
+ <>
315
+ {/* Physically-based Preetham atmospheric sky — slow-updated for performance */}
316
+ <SkyController timeRef={timeRef} />
317
+
318
+ {/* Primary sun — key light */}
319
+ <directionalLight
320
+ ref={sunRef}
321
+ position={[50, 100, 20]}
322
+ intensity={1.2}
323
+ color="#fffae8"
324
+ castShadow={false}
325
+ />
326
+
327
+ {/* Ambient fill */}
328
+ <ambientLight ref={ambientRef} intensity={0.5} color="#dde8ff" />
329
+
330
+ {/* Hemisphere sky/ground fill */}
331
+ <hemisphereLight ref={hemiRef} args={["#80c4e8", "#6a4a28", 0.4]} />
332
+
333
+ {/* Atmospheric accent — indigo at night, warm at sunset */}
334
+ <pointLight
335
+ ref={accentRef}
336
+ position={[-30, 60, -30]}
337
+ intensity={0.6}
338
+ color="#6366f1"
339
+ distance={600}
340
+ decay={1.2}
341
+ />
342
+
343
+ {/* Moon fill — cool blue at night */}
344
+ <directionalLight
345
+ ref={moonRef}
346
+ position={[-180, 120, -30]}
347
+ intensity={0}
348
+ color="#c8d8ff"
349
+ />
350
+
351
+ {/* Stars — fade in at dusk, out at dawn */}
352
+ <group ref={starsGroupRef}>
353
+ {/* Primary star field — bright nearby stars */}
354
+ <Stars
355
+ radius={700}
356
+ depth={300}
357
+ count={6000}
358
+ factor={5.5}
359
+ saturation={0.35}
360
+ fade
361
+ speed={0.5}
362
+ />
363
+ {/* Deep star backdrop — subtle dense star dust */}
364
+ <Stars
365
+ radius={1200}
366
+ depth={400}
367
+ count={3000}
368
+ factor={3}
369
+ saturation={0.15}
370
+ fade
371
+ speed={0.2}
372
+ />
373
+ </group>
374
+
375
+ {/* Shooting stars — animated meteor streaks at night */}
376
+ <ShootingStars timeRef={timeRef} />
377
+
378
+ {/* City glow — warm haze from below at night */}
379
+ <CityGlow timeRef={timeRef} />
380
+ </>
381
+ );
382
+ }
@@ -0,0 +1,59 @@
1
+ "use client";
2
+
3
+ import { useRef } from "react";
4
+ import { useFrame } from "@react-three/fiber";
5
+ import * as THREE from "three";
6
+
7
+ interface SelectionBeamProps {
8
+ position: [number, number, number];
9
+ buildingHeight: number;
10
+ }
11
+
12
+ export default function SelectionBeam({ position, buildingHeight }: SelectionBeamProps) {
13
+ const beamRef = useRef<THREE.Mesh>(null);
14
+ const glowRef = useRef<THREE.PointLight>(null);
15
+
16
+ useFrame(({ clock }) => {
17
+ const t = clock.getElapsedTime();
18
+
19
+ // Pulsing beam opacity
20
+ if (beamRef.current) {
21
+ const mat = beamRef.current.material as THREE.MeshBasicMaterial;
22
+ mat.opacity = 0.25 + Math.sin(t * 2) * 0.1;
23
+ }
24
+
25
+ // Pulsing glow light
26
+ if (glowRef.current) {
27
+ glowRef.current.intensity = 8 + Math.sin(t * 2) * 3;
28
+ }
29
+ });
30
+
31
+ const beamHeight = 80;
32
+ const beamY = buildingHeight + beamHeight / 2;
33
+
34
+ return (
35
+ <group position={position}>
36
+ {/* Vertical beam */}
37
+ <mesh ref={beamRef} position={[0, beamY - position[1], 0]}>
38
+ <cylinderGeometry args={[0.15, 0.4, beamHeight, 8]} />
39
+ <meshBasicMaterial
40
+ color="#8b5cf6"
41
+ transparent
42
+ opacity={0.3}
43
+ side={THREE.DoubleSide}
44
+ depthWrite={false}
45
+ />
46
+ </mesh>
47
+
48
+ {/* Point light glow at top of building */}
49
+ <pointLight
50
+ ref={glowRef}
51
+ position={[0, buildingHeight - position[1] + 2, 0]}
52
+ color="#8b5cf6"
53
+ intensity={8}
54
+ distance={30}
55
+ decay={2}
56
+ />
57
+ </group>
58
+ );
59
+ }
@@ -0,0 +1,104 @@
1
+ "use client";
2
+
3
+ interface SwapInfo {
4
+ walletAddress: string;
5
+ signature: string;
6
+ tokenIn: string | null;
7
+ tokenOut: string | null;
8
+ amountSol: number | null;
9
+ }
10
+
11
+ interface SwapPanelProps {
12
+ swap: SwapInfo | null;
13
+ onClose: () => void;
14
+ }
15
+
16
+ function shortenAddress(addr: string): string {
17
+ return `${addr.slice(0, 4)}...${addr.slice(-4)}`;
18
+ }
19
+
20
+ function formatAmount(n: number): string {
21
+ if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`;
22
+ if (n >= 1_000) return `${(n / 1_000).toFixed(2)}K`;
23
+ if (n >= 1) return n.toFixed(4);
24
+ if (n >= 0.0001) return n.toFixed(6);
25
+ return n.toExponential(2);
26
+ }
27
+
28
+ export default function SwapPanel({ swap, onClose }: SwapPanelProps) {
29
+ if (!swap) return null;
30
+
31
+ return (
32
+ <div className="w-full sm:w-80 bg-black/50 backdrop-blur-xl border border-white/[0.08] rounded-t-2xl sm:rounded-2xl p-5 text-white">
33
+ <div className="flex justify-between items-center mb-4">
34
+ <h3 className="text-xs font-semibold text-white/40 uppercase tracking-wider">
35
+ Swap
36
+ </h3>
37
+ <button
38
+ onClick={onClose}
39
+ className="w-6 h-6 flex items-center justify-center rounded-lg text-white/30 hover:text-white hover:bg-white/[0.06] transition-colors cursor-pointer"
40
+ >
41
+ <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.5">
42
+ <path d="M1 1l8 8M9 1l-8 8" />
43
+ </svg>
44
+ </button>
45
+ </div>
46
+
47
+ {/* Wallet address */}
48
+ <p className="font-mono text-sm text-purple-300/80 break-all mb-5">
49
+ {swap.walletAddress}
50
+ </p>
51
+
52
+ {/* Swap direction */}
53
+ <div className="mb-5 pb-4 border-b border-white/[0.06]">
54
+ <h4 className="text-xs font-semibold text-white/40 uppercase tracking-wider mb-3">
55
+ Direction
56
+ </h4>
57
+ <div className="flex items-center gap-3">
58
+ <div className="flex-1 min-w-0">
59
+ <div className="text-xs text-white/35 mb-1">Sold</div>
60
+ <div className="font-mono text-sm text-white/70 truncate">
61
+ {swap.tokenIn ? shortenAddress(swap.tokenIn) : "Unknown"}
62
+ </div>
63
+ </div>
64
+ <div className="text-white/25 text-lg shrink-0">&rarr;</div>
65
+ <div className="flex-1 min-w-0">
66
+ <div className="text-xs text-white/35 mb-1">Bought</div>
67
+ <div className="font-mono text-sm text-white/70 truncate">
68
+ {swap.tokenOut ? shortenAddress(swap.tokenOut) : "Unknown"}
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </div>
73
+
74
+ {/* Details */}
75
+ <div className="mb-5 pb-4 border-b border-white/[0.06] space-y-2.5 text-sm">
76
+ {swap.amountSol != null && (
77
+ <div className="flex justify-between">
78
+ <span className="text-white/45">Amount</span>
79
+ <span className="font-mono text-white/80">{formatAmount(swap.amountSol)} SOL</span>
80
+ </div>
81
+ )}
82
+ <div className="flex justify-between">
83
+ <span className="text-white/45">Signature</span>
84
+ <span className="font-mono text-white/60 truncate ml-4 max-w-[160px]">
85
+ {shortenAddress(swap.signature)}
86
+ </span>
87
+ </div>
88
+ </div>
89
+
90
+ {/* Links */}
91
+ <div className="space-y-2">
92
+ <a
93
+ href={`https://orbmarkets.io/tx/${swap.signature}`}
94
+ target="_blank"
95
+ rel="noopener noreferrer"
96
+ className="flex items-center justify-between w-full px-3 py-2 bg-white/[0.04] hover:bg-white/[0.08] border border-white/[0.08] rounded-xl text-sm text-white/60 hover:text-white/80 transition-colors"
97
+ >
98
+ <span>View on Orb</span>
99
+ <span className="text-white/30">&rarr;</span>
100
+ </a>
101
+ </div>
102
+ </div>
103
+ );
104
+ }