blanchon commited on
Commit
ca4340e
·
1 Parent(s): 43d8994
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Dockerfile +1 -1
  2. bun.lock +245 -0
  3. components.json +16 -0
  4. package.json +15 -0
  5. src-python/src/main.py +40 -38
  6. src/app.css +122 -1
  7. src/lib/components/{scene → 3d}/Floor.svelte +6 -16
  8. src/lib/components/3d/Robot.svelte +53 -0
  9. src/lib/components/3d/Selectable.svelte +105 -0
  10. src/lib/components/{scene → 3d}/robot/URDF/createRobot.svelte.ts +0 -0
  11. src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfBox.ts +0 -0
  12. src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfCylinder.ts +0 -0
  13. src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfJoint.ts +0 -0
  14. src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfLink.ts +0 -0
  15. src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfMesh.ts +0 -0
  16. src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfRobot.ts +0 -0
  17. src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfVisual.ts +0 -0
  18. src/lib/components/{scene → 3d}/robot/URDF/interfaces/index.ts +0 -0
  19. src/lib/components/{scene → 3d}/robot/URDF/mesh/DAE.svelte +0 -0
  20. src/lib/components/{scene → 3d}/robot/URDF/mesh/OBJ.svelte +0 -0
  21. src/lib/components/{scene → 3d}/robot/URDF/mesh/STL.svelte +0 -0
  22. src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfJoint.svelte +22 -19
  23. src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfLink.svelte +4 -4
  24. src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfThree.svelte +0 -0
  25. src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfVisual.svelte +14 -11
  26. src/lib/components/{scene → 3d}/robot/URDF/runes/urdf_state.svelte.ts +0 -0
  27. src/lib/components/{scene → 3d}/robot/URDF/utils/UrdfParser.ts +0 -0
  28. src/lib/components/{scene → 3d}/robot/URDF/utils/helper.ts +0 -0
  29. src/lib/components/ButtonBar.svelte +161 -0
  30. src/lib/components/ControlsSheet.svelte +76 -0
  31. src/lib/components/Overlay.svelte +36 -0
  32. src/lib/components/PanelWrapper.svelte +32 -0
  33. src/lib/components/RobotStatusDemo.svelte +65 -0
  34. src/lib/components/SettingsSheet.svelte +109 -0
  35. src/lib/components/StatusCard.svelte +79 -0
  36. src/lib/components/panel/ControlPanel.svelte +1 -0
  37. src/lib/components/panel/RobotControlPanel.old.svelte +659 -0
  38. src/lib/components/panel/RobotControlPanel.svelte +448 -383
  39. src/lib/components/panel/SettingsPanel.svelte +1 -1
  40. src/lib/components/scene/Robot.svelte +0 -44
  41. src/lib/components/scene/Selectable.svelte +0 -68
  42. src/lib/components/ui/accordion/accordion-content.svelte +25 -0
  43. src/lib/components/ui/accordion/accordion-item.svelte +17 -0
  44. src/lib/components/ui/accordion/accordion-root.svelte +16 -0
  45. src/lib/components/ui/accordion/accordion-trigger.svelte +32 -0
  46. src/lib/components/ui/accordion/index.ts +16 -0
  47. src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +18 -0
  48. src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +18 -0
  49. src/lib/components/ui/alert-dialog/alert-dialog-content.svelte +27 -0
  50. src/lib/components/ui/alert-dialog/alert-dialog-description.svelte +17 -0
Dockerfile CHANGED
@@ -61,4 +61,4 @@ WORKDIR $HOME/app
61
  EXPOSE 7860
62
 
63
  # Start the FastAPI server (serves both frontend and backend)
64
- CMD ["sh", "-c", "cd src-python && uv run python start_server.py"]
 
61
  EXPOSE 7860
62
 
63
  # Start the FastAPI server (serves both frontend and backend)
64
+ CMD ["sh", "-c", "EXPOSE_FRONTEND=true cd src-python && uv run python start_server.py"]
bun.lock CHANGED
@@ -10,28 +10,43 @@
10
  "clsx": "^2.1.1",
11
  "tailwind-merge": "^3.3.0",
12
  "three": "^0.177.0",
 
13
  "zod": "^3.25.42",
14
  },
15
  "devDependencies": {
16
  "@eslint/compat": "^1.2.5",
17
  "@eslint/js": "^9.18.0",
 
 
 
 
18
  "@sveltejs/adapter-auto": "^6.0.0",
19
  "@sveltejs/adapter-static": "^3.0.8",
20
  "@sveltejs/kit": "^2.16.0",
21
  "@sveltejs/vite-plugin-svelte": "^5.0.0",
22
  "@tailwindcss/vite": "^4.0.0",
 
 
 
23
  "eslint": "^9.18.0",
24
  "eslint-config-prettier": "^10.0.1",
25
  "eslint-plugin-svelte": "^3.0.0",
26
  "globals": "^16.0.0",
 
 
 
27
  "prettier": "^3.4.2",
28
  "prettier-plugin-svelte": "^3.3.3",
29
  "prettier-plugin-tailwindcss": "^0.6.11",
30
  "svelte": "^5.0.0",
31
  "svelte-check": "^4.0.0",
 
 
32
  "tailwindcss": "^4.0.0",
 
33
  "typescript": "^5.0.0",
34
  "typescript-eslint": "^8.20.0",
 
35
  "vite": "^6.2.6",
36
  },
37
  },
@@ -39,6 +54,14 @@
39
  "packages": {
40
  "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
41
 
 
 
 
 
 
 
 
 
42
  "@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.12.0", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="],
43
 
44
  "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="],
@@ -111,6 +134,12 @@
111
 
112
  "@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.1", "", { "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" } }, "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w=="],
113
 
 
 
 
 
 
 
114
  "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
115
 
116
  "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
@@ -119,6 +148,16 @@
119
 
120
  "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
121
 
 
 
 
 
 
 
 
 
 
 
122
  "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
123
 
124
  "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
@@ -131,6 +170,16 @@
131
 
132
  "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
133
 
 
 
 
 
 
 
 
 
 
 
134
  "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
135
 
136
  "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
@@ -139,6 +188,8 @@
139
 
140
  "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
141
 
 
 
142
  "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.41.1", "", { "os": "android", "cpu": "arm" }, "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw=="],
143
 
144
  "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.41.1", "", { "os": "android", "cpu": "arm64" }, "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA=="],
@@ -191,6 +242,8 @@
191
 
192
  "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="],
193
 
 
 
194
  "@tailwindcss/node": ["@tailwindcss/node@4.1.8", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.8" } }, "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q=="],
195
 
196
  "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.8", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.8", "@tailwindcss/oxide-darwin-arm64": "4.1.8", "@tailwindcss/oxide-darwin-x64": "4.1.8", "@tailwindcss/oxide-freebsd-x64": "4.1.8", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8", "@tailwindcss/oxide-linux-arm64-musl": "4.1.8", "@tailwindcss/oxide-linux-x64-gnu": "4.1.8", "@tailwindcss/oxide-linux-x64-musl": "4.1.8", "@tailwindcss/oxide-wasm32-wasi": "4.1.8", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8", "@tailwindcss/oxide-win32-x64-msvc": "4.1.8" } }, "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A=="],
@@ -221,6 +274,8 @@
221
 
222
  "@tailwindcss/vite": ["@tailwindcss/vite@4.1.8", "", { "dependencies": { "@tailwindcss/node": "4.1.8", "@tailwindcss/oxide": "4.1.8", "tailwindcss": "4.1.8" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-CQ+I8yxNV5/6uGaJjiuymgw0kEQiNKRinYbZXPdx1fk5WgiyReG0VaUx/Xq6aVNSUNJFzxm6o8FNKS5aMaim5A=="],
223
 
 
 
224
  "@threejs-kit/instanced-sprite-mesh": ["@threejs-kit/instanced-sprite-mesh@2.5.1", "", { "dependencies": { "diet-sprite": "^0.0.1", "earcut": "^2.2.4", "maath": "^0.10.7", "three-instanced-uniforms-mesh": "^0.52.4", "troika-three-utils": "^0.52.4" }, "peerDependencies": { "three": ">=0.170.0" } }, "sha512-pmt1ALRhbHhCJQTj2FuthH6PeLIeaM4hOuS2JO3kWSwlnvx/9xuUkjFR3JOi/myMqsH7pSsLIROSaBxDfttjeA=="],
225
 
226
  "@threlte/core": ["@threlte/core@8.0.4", "", { "dependencies": { "mitt": "^3.0.1" }, "peerDependencies": { "svelte": ">=5", "three": ">=0.155" } }, "sha512-kg0nTq00RqgTExIEKkyO/yCupCt73lybuFZObKHCRaY7dPcCvRD4tHkyKIFeYLYV4R6u1nqIuzfgDzYJqHbLlQ=="],
@@ -231,10 +286,14 @@
231
 
232
  "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
233
 
 
 
234
  "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
235
 
236
  "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
237
 
 
 
238
  "@types/stats.js": ["@types/stats.js@0.17.4", "", {}, "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="],
239
 
240
  "@types/three": ["@types/three@0.176.0", "", { "dependencies": { "@dimforge/rapier3d-compat": "^0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": "*", "@webgpu/types": "*", "fflate": "~0.8.2", "meshoptimizer": "~0.18.1" } }, "sha512-FwfPXxCqOtP7EdYMagCFePNKoG1AGBDUEVKtluv2BTVRpSt7b+X27xNsirPCTCqY1pGYsPUzaM3jgWP7dXSxlw=="],
@@ -281,6 +340,8 @@
281
 
282
  "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
283
 
 
 
284
  "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
285
 
286
  "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
@@ -301,28 +362,96 @@
301
 
302
  "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
303
 
 
 
304
  "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
305
 
 
 
306
  "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
307
 
308
  "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
309
 
 
 
310
  "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
311
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
313
 
314
  "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
315
 
316
  "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
317
 
 
 
318
  "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
319
 
320
  "devalue": ["devalue@5.1.1", "", {}, "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="],
321
 
322
  "diet-sprite": ["diet-sprite@0.0.1", "", {}, "sha512-zSHI2WDAn1wJqJYxcmjWfJv3Iw8oL9reQIbEyx2x2/EZ4/qmUTIo8/5qOCurnAcq61EwtJJaZ0XTy2NRYqpB5A=="],
323
 
 
 
324
  "earcut": ["earcut@2.2.4", "", {}, "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ=="],
325
 
 
 
 
 
 
 
326
  "enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
327
 
328
  "esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="],
@@ -351,8 +480,12 @@
351
 
352
  "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
353
 
 
 
354
  "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
355
 
 
 
356
  "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
357
 
358
  "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
@@ -389,12 +522,18 @@
389
 
390
  "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
391
 
 
 
392
  "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
393
 
394
  "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
395
 
396
  "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
397
 
 
 
 
 
398
  "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
399
 
400
  "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
@@ -421,6 +560,10 @@
421
 
422
  "known-css-properties": ["known-css-properties@0.36.0", "", {}, "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA=="],
423
 
 
 
 
 
424
  "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
425
 
426
  "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
@@ -447,10 +590,14 @@
447
 
448
  "lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
449
 
 
 
450
  "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
451
 
452
  "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
453
 
 
 
454
  "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
455
 
456
  "maath": ["maath@0.10.8", "", { "peerDependencies": { "@types/three": ">=0.134.0", "three": ">=0.134.0" } }, "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g=="],
@@ -473,6 +620,10 @@
473
 
474
  "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
475
 
 
 
 
 
476
  "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
477
 
478
  "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
@@ -489,16 +640,24 @@
489
 
490
  "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
491
 
 
 
 
 
492
  "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
493
 
494
  "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
495
 
496
  "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
497
 
 
 
498
  "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
499
 
500
  "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
501
 
 
 
502
  "postcss": ["postcss@8.5.4", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w=="],
503
 
504
  "postcss-load-config": ["postcss-load-config@3.1.4", "", { "dependencies": { "lilconfig": "^2.0.5", "yaml": "^1.10.2" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" }, "optionalPeers": ["postcss", "ts-node"] }, "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg=="],
@@ -509,6 +668,10 @@
509
 
510
  "postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
511
 
 
 
 
 
512
  "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
513
 
514
  "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
@@ -519,6 +682,8 @@
519
 
520
  "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
521
 
 
 
522
  "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
523
 
524
  "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
@@ -529,12 +694,20 @@
529
 
530
  "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
531
 
 
 
532
  "rollup": ["rollup@4.41.1", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.41.1", "@rollup/rollup-android-arm64": "4.41.1", "@rollup/rollup-darwin-arm64": "4.41.1", "@rollup/rollup-darwin-x64": "4.41.1", "@rollup/rollup-freebsd-arm64": "4.41.1", "@rollup/rollup-freebsd-x64": "4.41.1", "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", "@rollup/rollup-linux-arm-musleabihf": "4.41.1", "@rollup/rollup-linux-arm64-gnu": "4.41.1", "@rollup/rollup-linux-arm64-musl": "4.41.1", "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-musl": "4.41.1", "@rollup/rollup-linux-s390x-gnu": "4.41.1", "@rollup/rollup-linux-x64-gnu": "4.41.1", "@rollup/rollup-linux-x64-musl": "4.41.1", "@rollup/rollup-win32-arm64-msvc": "4.41.1", "@rollup/rollup-win32-ia32-msvc": "4.41.1", "@rollup/rollup-win32-x64-msvc": "4.41.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw=="],
533
 
534
  "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
535
 
 
 
 
 
536
  "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
537
 
 
 
538
  "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
539
 
540
  "set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
@@ -549,6 +722,8 @@
549
 
550
  "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
551
 
 
 
552
  "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
553
 
554
  "svelte": ["svelte@5.33.10", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-/yArPQIBoQS2p86LKnvJywOXkVHeEXnFgrDPSxkEfIAEkykopYuy2bF6UUqHG4IbZlJD6OurLxJT8Kn7kTk9WA=="],
@@ -557,8 +732,16 @@
557
 
558
  "svelte-eslint-parser": ["svelte-eslint-parser@1.2.0", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-mbPtajIeuiyU80BEyGvwAktBeTX7KCr5/0l+uRGLq1dafwRNrjfM5kHGJScEBlPG3ipu6dJqfW/k0/fujvIEVw=="],
559
 
 
 
 
 
 
 
560
  "tailwind-merge": ["tailwind-merge@3.3.0", "", {}, "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ=="],
561
 
 
 
562
  "tailwindcss": ["tailwindcss@4.1.8", "", {}, "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og=="],
563
 
564
  "tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
@@ -573,8 +756,14 @@
573
 
574
  "three-perf": ["three-perf@github:jerzakm/three-perf#322d7d3", { "dependencies": { "troika-three-text": "^0.52.0", "tweakpane": "^3.1.10" }, "peerDependencies": { "three": ">=0.170" } }, "jerzakm-three-perf-322d7d3"],
575
 
 
 
576
  "three-viewport-gizmo": ["three-viewport-gizmo@2.2.0", "", { "peerDependencies": { "three": ">=0.162.0 <1.0.0" } }, "sha512-Jo9Liur1rUmdKk75FZumLU/+hbF+RtJHi1qsKZpntjKlCYScK6tjbYoqvJ9M+IJphrlQJF5oReFW7Sambh0N4Q=="],
577
 
 
 
 
 
578
  "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
579
 
580
  "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
@@ -589,6 +778,10 @@
589
 
590
  "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
591
 
 
 
 
 
592
  "tweakpane": ["tweakpane@3.1.10", "", {}, "sha512-rqwnl/pUa7+inhI2E9ayGTqqP0EPOOn/wVvSWjZsRbZUItzNShny7pzwL3hVlaN4m9t/aZhsP0aFQ9U5VVR2VQ=="],
593
 
594
  "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
@@ -597,12 +790,20 @@
597
 
598
  "typescript-eslint": ["typescript-eslint@8.33.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.33.0", "@typescript-eslint/parser": "8.33.0", "@typescript-eslint/utils": "8.33.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ=="],
599
 
 
 
600
  "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
601
 
602
  "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
603
 
 
 
604
  "vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
605
 
 
 
 
 
606
  "vitefu": ["vitefu@1.0.6", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["vite"] }, "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA=="],
607
 
608
  "webgl-sdf-generator": ["webgl-sdf-generator@1.1.1", "", {}, "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="],
@@ -627,6 +828,12 @@
627
 
628
  "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
629
 
 
 
 
 
 
 
630
  "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
631
 
632
  "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
@@ -643,10 +850,48 @@
643
 
644
  "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
645
 
 
 
 
 
646
  "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
647
 
648
  "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
649
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
650
  "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
 
 
 
 
 
 
 
 
651
  }
652
  }
 
10
  "clsx": "^2.1.1",
11
  "tailwind-merge": "^3.3.0",
12
  "three": "^0.177.0",
13
+ "threlte-postprocessing": "^0.0.9",
14
  "zod": "^3.25.42",
15
  },
16
  "devDependencies": {
17
  "@eslint/compat": "^1.2.5",
18
  "@eslint/js": "^9.18.0",
19
+ "@iconify/json": "^2.2.344",
20
+ "@iconify/tailwind4": "^1.0.6",
21
+ "@internationalized/date": "^3.5.6",
22
+ "@lucide/svelte": "^0.511.0",
23
  "@sveltejs/adapter-auto": "^6.0.0",
24
  "@sveltejs/adapter-static": "^3.0.8",
25
  "@sveltejs/kit": "^2.16.0",
26
  "@sveltejs/vite-plugin-svelte": "^5.0.0",
27
  "@tailwindcss/vite": "^4.0.0",
28
+ "@tanstack/table-core": "^8.20.5",
29
+ "bits-ui": "^2.4.1",
30
+ "embla-carousel-svelte": "^8.6.0",
31
  "eslint": "^9.18.0",
32
  "eslint-config-prettier": "^10.0.1",
33
  "eslint-plugin-svelte": "^3.0.0",
34
  "globals": "^16.0.0",
35
+ "layerchart": "2.0.0-next.10",
36
+ "mode-watcher": "^1.0.6",
37
+ "paneforge": "^1.0.0-next.5",
38
  "prettier": "^3.4.2",
39
  "prettier-plugin-svelte": "^3.3.3",
40
  "prettier-plugin-tailwindcss": "^0.6.11",
41
  "svelte": "^5.0.0",
42
  "svelte-check": "^4.0.0",
43
+ "svelte-sonner": "^1.0.1",
44
+ "tailwind-variants": "^1.0.0",
45
  "tailwindcss": "^4.0.0",
46
+ "tw-animate-css": "^1.3.3",
47
  "typescript": "^5.0.0",
48
  "typescript-eslint": "^8.20.0",
49
+ "vaul-svelte": "^1.0.0-next.7",
50
  "vite": "^6.2.6",
51
  },
52
  },
 
54
  "packages": {
55
  "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
56
 
57
+ "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
58
+
59
+ "@antfu/utils": ["@antfu/utils@8.1.1", "", {}, "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ=="],
60
+
61
+ "@dagrejs/dagre": ["@dagrejs/dagre@1.1.4", "", { "dependencies": { "@dagrejs/graphlib": "2.2.4" } }, "sha512-QUTc54Cg/wvmlEUxB+uvoPVKFazM1H18kVHBQNmK2NbrDR5ihOCR6CXLnDSZzMcSQKJtabPUWridBOlJM3WkDg=="],
62
+
63
+ "@dagrejs/graphlib": ["@dagrejs/graphlib@2.2.4", "", {}, "sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw=="],
64
+
65
  "@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.12.0", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="],
66
 
67
  "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="],
 
134
 
135
  "@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.1", "", { "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" } }, "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w=="],
136
 
137
+ "@floating-ui/core": ["@floating-ui/core@1.7.1", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw=="],
138
+
139
+ "@floating-ui/dom": ["@floating-ui/dom@1.7.1", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/utils": "^0.2.9" } }, "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ=="],
140
+
141
+ "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="],
142
+
143
  "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
144
 
145
  "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
 
148
 
149
  "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
150
 
151
+ "@iconify/json": ["@iconify/json@2.2.344", "", { "dependencies": { "@iconify/types": "*", "pathe": "^1.1.2" } }, "sha512-wY+TYoq4WSAaNunAbecUS+5S4VobTW/3cWpTLEWezRW9Wv4V3MqguNirNN3c/jSq/dfIMJNvr9Gz+N9iNFMInA=="],
152
+
153
+ "@iconify/tailwind4": ["@iconify/tailwind4@1.0.6", "", { "dependencies": { "@iconify/types": "^2.0.0", "@iconify/utils": "^2.2.1" }, "peerDependencies": { "tailwindcss": ">= 4" } }, "sha512-43ZXe+bC7CuE2LCgROdqbQeFYJi/J7L/k1UpSy8KDQlWVsWxPzLSWbWhlJx4uRYLOh1NRyw02YlDOgzBOFNd+A=="],
154
+
155
+ "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
156
+
157
+ "@iconify/utils": ["@iconify/utils@2.3.0", "", { "dependencies": { "@antfu/install-pkg": "^1.0.0", "@antfu/utils": "^8.1.0", "@iconify/types": "^2.0.0", "debug": "^4.4.0", "globals": "^15.14.0", "kolorist": "^1.8.0", "local-pkg": "^1.0.0", "mlly": "^1.7.4" } }, "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA=="],
158
+
159
+ "@internationalized/date": ["@internationalized/date@3.8.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA=="],
160
+
161
  "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
162
 
163
  "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
 
170
 
171
  "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
172
 
173
+ "@layerstack/svelte-actions": ["@layerstack/svelte-actions@1.0.1-next.2", "", { "dependencies": { "@floating-ui/dom": "^1.7.0", "@layerstack/utils": "1.1.0-next.2", "d3-array": "^3.2.4", "d3-scale": "^4.0.2", "date-fns": "^4.1.0", "lodash-es": "^4.17.21" } }, "sha512-s4iGrFIt+obdHP+Mzre+drCiZttqs5UYSw6CfQs1D2IeqTFkkbvRvYosh47flMi6ElN4psCrCcwn8KntClhMow=="],
174
+
175
+ "@layerstack/svelte-state": ["@layerstack/svelte-state@0.1.0-next.7", "", { "dependencies": { "@layerstack/utils": "2.0.0-next.3" } }, "sha512-9JQ5EyN/ghcSBKXjv1ueiM0TlsJv4eEWgW8mvd4dqxMLUzs+/fPJlXlxqBqvH/T64yauxvFUfytCGPnx1lRJsA=="],
176
+
177
+ "@layerstack/tailwind": ["@layerstack/tailwind@2.0.0-next.4", "", { "dependencies": { "@layerstack/utils": "^1.1.0-next.2", "clsx": "^2.1.1", "culori": "^4.0.1", "d3-array": "^3.2.4", "date-fns": "^4.1.0", "lodash-es": "^4.17.21", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.5" } }, "sha512-46QUWvMaYGlGmi17vfT2o6mV7/0ZaWi0qJ4vjPKrayBsxn1mVjqX0cbW7EXC6JnmMVYt5CZu0768rWCM4nGCzA=="],
178
+
179
+ "@layerstack/utils": ["@layerstack/utils@2.0.0-next.3", "", { "dependencies": { "d3-array": "^3.2.4", "date-fns": "^4.1.0", "lodash-es": "^4.17.21" } }, "sha512-sTOF9cdh5lb9bqWW10kWwo4toSTjGOH4RNTr++59obVvdGp/hXo6LAN2WsdzkK9QFqBgEpuJH0rZ+D77lMSdeQ=="],
180
+
181
+ "@lucide/svelte": ["@lucide/svelte@0.511.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-aLCSPMUJmHlCuLXzXENXa4Z1NV2mN1iAZAFKk4bEbey+/MdsNlu+/DqwVkgW3Yvj6p8y8Vn5xZ2v9CLmPlA6Vw=="],
182
+
183
  "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
184
 
185
  "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
 
188
 
189
  "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
190
 
191
+ "@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
192
+
193
  "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.41.1", "", { "os": "android", "cpu": "arm" }, "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw=="],
194
 
195
  "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.41.1", "", { "os": "android", "cpu": "arm64" }, "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA=="],
 
242
 
243
  "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="],
244
 
245
+ "@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
246
+
247
  "@tailwindcss/node": ["@tailwindcss/node@4.1.8", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.8" } }, "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q=="],
248
 
249
  "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.8", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.8", "@tailwindcss/oxide-darwin-arm64": "4.1.8", "@tailwindcss/oxide-darwin-x64": "4.1.8", "@tailwindcss/oxide-freebsd-x64": "4.1.8", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8", "@tailwindcss/oxide-linux-arm64-musl": "4.1.8", "@tailwindcss/oxide-linux-x64-gnu": "4.1.8", "@tailwindcss/oxide-linux-x64-musl": "4.1.8", "@tailwindcss/oxide-wasm32-wasi": "4.1.8", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8", "@tailwindcss/oxide-win32-x64-msvc": "4.1.8" } }, "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A=="],
 
274
 
275
  "@tailwindcss/vite": ["@tailwindcss/vite@4.1.8", "", { "dependencies": { "@tailwindcss/node": "4.1.8", "@tailwindcss/oxide": "4.1.8", "tailwindcss": "4.1.8" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-CQ+I8yxNV5/6uGaJjiuymgw0kEQiNKRinYbZXPdx1fk5WgiyReG0VaUx/Xq6aVNSUNJFzxm6o8FNKS5aMaim5A=="],
276
 
277
+ "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
278
+
279
  "@threejs-kit/instanced-sprite-mesh": ["@threejs-kit/instanced-sprite-mesh@2.5.1", "", { "dependencies": { "diet-sprite": "^0.0.1", "earcut": "^2.2.4", "maath": "^0.10.7", "three-instanced-uniforms-mesh": "^0.52.4", "troika-three-utils": "^0.52.4" }, "peerDependencies": { "three": ">=0.170.0" } }, "sha512-pmt1ALRhbHhCJQTj2FuthH6PeLIeaM4hOuS2JO3kWSwlnvx/9xuUkjFR3JOi/myMqsH7pSsLIROSaBxDfttjeA=="],
280
 
281
  "@threlte/core": ["@threlte/core@8.0.4", "", { "dependencies": { "mitt": "^3.0.1" }, "peerDependencies": { "svelte": ">=5", "three": ">=0.155" } }, "sha512-kg0nTq00RqgTExIEKkyO/yCupCt73lybuFZObKHCRaY7dPcCvRD4tHkyKIFeYLYV4R6u1nqIuzfgDzYJqHbLlQ=="],
 
286
 
287
  "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
288
 
289
+ "@types/draco3d": ["@types/draco3d@1.4.10", "", {}, "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw=="],
290
+
291
  "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
292
 
293
  "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
294
 
295
+ "@types/offscreencanvas": ["@types/offscreencanvas@2019.7.3", "", {}, "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A=="],
296
+
297
  "@types/stats.js": ["@types/stats.js@0.17.4", "", {}, "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="],
298
 
299
  "@types/three": ["@types/three@0.176.0", "", { "dependencies": { "@dimforge/rapier3d-compat": "^0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": "*", "@webgpu/types": "*", "fflate": "~0.8.2", "meshoptimizer": "~0.18.1" } }, "sha512-FwfPXxCqOtP7EdYMagCFePNKoG1AGBDUEVKtluv2BTVRpSt7b+X27xNsirPCTCqY1pGYsPUzaM3jgWP7dXSxlw=="],
 
340
 
341
  "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
342
 
343
+ "bits-ui": ["bits-ui@2.4.1", "", { "dependencies": { "@floating-ui/core": "^1.7.0", "@floating-ui/dom": "^1.7.0", "css.escape": "^1.5.1", "esm-env": "^1.1.2", "runed": "^0.28.0", "svelte-toolbelt": "^0.9.1", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-Z0qZkPgtxP5dkEOyZqCK2PQ1oFXsD0AlfFMtsrMLJqaYtjUBoQGosHVKEH03M9q4G8kxVnG74Zb0F9pS0X4+6w=="],
344
+
345
  "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
346
 
347
  "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
 
362
 
363
  "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
364
 
365
+ "commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
366
+
367
  "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
368
 
369
+ "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
370
+
371
  "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
372
 
373
  "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
374
 
375
+ "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
376
+
377
  "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
378
 
379
+ "culori": ["culori@4.0.1", "", {}, "sha512-LSnjA6HuIUOlkfKVbzi2OlToZE8OjFi667JWN9qNymXVXzGDmvuP60SSgC+e92sd7B7158f7Fy3Mb6rXS5EDPw=="],
380
+
381
+ "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
382
+
383
+ "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
384
+
385
+ "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="],
386
+
387
+ "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="],
388
+
389
+ "d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="],
390
+
391
+ "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="],
392
+
393
+ "d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="],
394
+
395
+ "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="],
396
+
397
+ "d3-geo-voronoi": ["d3-geo-voronoi@2.1.0", "", { "dependencies": { "d3-array": "3", "d3-delaunay": "6", "d3-geo": "3", "d3-tricontour": "1" } }, "sha512-kqE4yYuOjPbKdBXG0xztCacPwkVSK2REF1opSNrnqqtXJmNcM++UbwQ8SxvwP6IQTj9RvIjjK4qeiVsEfj0Z2Q=="],
398
+
399
+ "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="],
400
+
401
+ "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
402
+
403
+ "d3-interpolate-path": ["d3-interpolate-path@2.3.0", "", {}, "sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ=="],
404
+
405
+ "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
406
+
407
+ "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="],
408
+
409
+ "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="],
410
+
411
+ "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="],
412
+
413
+ "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
414
+
415
+ "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="],
416
+
417
+ "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
418
+
419
+ "d3-tile": ["d3-tile@1.0.0", "", {}, "sha512-79fnTKpPMPDS5xQ0xuS9ir0165NEwwkFpe/DSOmc2Gl9ldYzKKRDWogmTTE8wAJ8NA7PMapNfEcyKhI9Lxdu5Q=="],
420
+
421
+ "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
422
+
423
+ "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
424
+
425
+ "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
426
+
427
+ "d3-tricontour": ["d3-tricontour@1.0.2", "", { "dependencies": { "d3-delaunay": "6", "d3-scale": "4" } }, "sha512-HIRxHzHagPtUPNabjOlfcyismJYIsc+Xlq4mlsts4e8eAcwyq9Tgk/sYdyhlBpQ0MHwVquc/8j+e29YjXnmxeA=="],
428
+
429
+ "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
430
+
431
  "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
432
 
433
  "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
434
 
435
  "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
436
 
437
+ "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="],
438
+
439
  "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
440
 
441
  "devalue": ["devalue@5.1.1", "", {}, "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="],
442
 
443
  "diet-sprite": ["diet-sprite@0.0.1", "", {}, "sha512-zSHI2WDAn1wJqJYxcmjWfJv3Iw8oL9reQIbEyx2x2/EZ4/qmUTIo8/5qOCurnAcq61EwtJJaZ0XTy2NRYqpB5A=="],
444
 
445
+ "draco3d": ["draco3d@1.5.7", "", {}, "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ=="],
446
+
447
  "earcut": ["earcut@2.2.4", "", {}, "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ=="],
448
 
449
+ "embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
450
+
451
+ "embla-carousel-reactive-utils": ["embla-carousel-reactive-utils@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A=="],
452
+
453
+ "embla-carousel-svelte": ["embla-carousel-svelte@8.6.0", "", { "dependencies": { "embla-carousel": "8.6.0", "embla-carousel-reactive-utils": "8.6.0" }, "peerDependencies": { "svelte": "^3.49.0 || ^4.0.0 || ^5.0.0" } }, "sha512-ZDsKk8Sdv+AUTygMYcwZjfRd1DTh+JSUzxkOo8b9iKAkYjg+39mzbY/lwHsE3jXSpKxdKWS69hPSNuzlOGtR2Q=="],
454
+
455
  "enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
456
 
457
  "esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="],
 
480
 
481
  "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
482
 
483
+ "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
484
+
485
  "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
486
 
487
+ "exsolve": ["exsolve@1.0.5", "", {}, "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg=="],
488
+
489
  "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
490
 
491
  "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
 
522
 
523
  "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
524
 
525
+ "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
526
+
527
  "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
528
 
529
  "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
530
 
531
  "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
532
 
533
+ "inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="],
534
+
535
+ "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
536
+
537
  "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
538
 
539
  "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
 
560
 
561
  "known-css-properties": ["known-css-properties@0.36.0", "", {}, "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA=="],
562
 
563
+ "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="],
564
+
565
+ "layerchart": ["layerchart@2.0.0-next.10", "", { "dependencies": { "@dagrejs/dagre": "^1.1.4", "@layerstack/svelte-actions": "1.0.1-next.2", "@layerstack/svelte-state": "0.1.0-next.7", "@layerstack/tailwind": "2.0.0-next.4", "@layerstack/utils": "2.0.0-next.3", "d3-array": "^3.2.4", "d3-color": "^3.1.0", "d3-delaunay": "^6.0.4", "d3-dsv": "^3.0.1", "d3-force": "^3.0.0", "d3-geo": "^3.1.1", "d3-geo-voronoi": "^2.1.0", "d3-hierarchy": "^3.1.2", "d3-interpolate": "^3.0.1", "d3-interpolate-path": "^2.3.0", "d3-path": "^3.1.0", "d3-quadtree": "^3.0.1", "d3-random": "^3.0.1", "d3-sankey": "^0.12.3", "d3-scale": "^4.0.2", "d3-scale-chromatic": "^3.1.0", "d3-shape": "^3.2.0", "d3-tile": "^1.0.0", "d3-time": "^3.1.0", "date-fns": "^4.1.0", "lodash-es": "^4.17.21", "runed": "^0.28.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-Jw0EMWRDjecl0esHlHeyqObhPJCOXD9eQLWOu3I37+XCHXDHp4QL/5avcmBIwflmLE1e/fVGoMaeTZjyezZGhA=="],
566
+
567
  "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
568
 
569
  "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
 
590
 
591
  "lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
592
 
593
+ "local-pkg": ["local-pkg@1.1.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.0.1", "quansync": "^0.2.8" } }, "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg=="],
594
+
595
  "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
596
 
597
  "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
598
 
599
+ "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
600
+
601
  "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
602
 
603
  "maath": ["maath@0.10.8", "", { "peerDependencies": { "@types/three": ">=0.134.0", "three": ">=0.134.0" } }, "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g=="],
 
620
 
621
  "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
622
 
623
+ "mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="],
624
+
625
+ "mode-watcher": ["mode-watcher@1.0.7", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-ZGA7ZGdOvBJeTQkzdBOnXSgTkO6U6iIFWJoyGCTt6oHNg9XP9NBvS26De+V4W2aqI+B0yYXUskFG2VnEo3zyMQ=="],
626
+
627
  "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
628
 
629
  "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
 
640
 
641
  "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
642
 
643
+ "package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="],
644
+
645
+ "paneforge": ["paneforge@1.0.0-next.5", "", { "dependencies": { "runed": "^0.23.4", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.20.0" } }, "sha512-1ArDM+GMEO+o6pixEAFobhTkWkyxUDdHyw2bKruvQIXBStJmdRP7HoV4jNBZ/2i9UHDzmczxJzA3D2tKa91phw=="],
646
+
647
  "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
648
 
649
  "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
650
 
651
  "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
652
 
653
+ "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
654
+
655
  "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
656
 
657
  "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
658
 
659
+ "pkg-types": ["pkg-types@2.1.0", "", { "dependencies": { "confbox": "^0.2.1", "exsolve": "^1.0.1", "pathe": "^2.0.3" } }, "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A=="],
660
+
661
  "postcss": ["postcss@8.5.4", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w=="],
662
 
663
  "postcss-load-config": ["postcss-load-config@3.1.4", "", { "dependencies": { "lilconfig": "^2.0.5", "yaml": "^1.10.2" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" }, "optionalPeers": ["postcss", "ts-node"] }, "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg=="],
 
668
 
669
  "postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
670
 
671
+ "postprocessing": ["postprocessing@6.37.3", "", { "peerDependencies": { "three": ">= 0.157.0 < 0.177.0" } }, "sha512-2FzfZg41Ml/ddvSalLdCz7H53Wmr/ZvK238fauTG1CI97+65BDgo1dNH6AKhAWc5q9ngpyG5EYiXVbNCl2t1iA=="],
672
+
673
+ "potpack": ["potpack@1.0.2", "", {}, "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="],
674
+
675
  "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
676
 
677
  "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
 
682
 
683
  "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
684
 
685
+ "quansync": ["quansync@0.2.10", "", {}, "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A=="],
686
+
687
  "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
688
 
689
  "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
 
694
 
695
  "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
696
 
697
+ "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="],
698
+
699
  "rollup": ["rollup@4.41.1", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.41.1", "@rollup/rollup-android-arm64": "4.41.1", "@rollup/rollup-darwin-arm64": "4.41.1", "@rollup/rollup-darwin-x64": "4.41.1", "@rollup/rollup-freebsd-arm64": "4.41.1", "@rollup/rollup-freebsd-x64": "4.41.1", "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", "@rollup/rollup-linux-arm-musleabihf": "4.41.1", "@rollup/rollup-linux-arm64-gnu": "4.41.1", "@rollup/rollup-linux-arm64-musl": "4.41.1", "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-musl": "4.41.1", "@rollup/rollup-linux-s390x-gnu": "4.41.1", "@rollup/rollup-linux-x64-gnu": "4.41.1", "@rollup/rollup-linux-x64-musl": "4.41.1", "@rollup/rollup-win32-arm64-msvc": "4.41.1", "@rollup/rollup-win32-ia32-msvc": "4.41.1", "@rollup/rollup-win32-x64-msvc": "4.41.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw=="],
700
 
701
  "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
702
 
703
+ "runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="],
704
+
705
+ "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
706
+
707
  "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
708
 
709
+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
710
+
711
  "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
712
 
713
  "set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
 
722
 
723
  "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
724
 
725
+ "style-to-object": ["style-to-object@1.0.8", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="],
726
+
727
  "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
728
 
729
  "svelte": ["svelte@5.33.10", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-/yArPQIBoQS2p86LKnvJywOXkVHeEXnFgrDPSxkEfIAEkykopYuy2bF6UUqHG4IbZlJD6OurLxJT8Kn7kTk9WA=="],
 
732
 
733
  "svelte-eslint-parser": ["svelte-eslint-parser@1.2.0", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-mbPtajIeuiyU80BEyGvwAktBeTX7KCr5/0l+uRGLq1dafwRNrjfM5kHGJScEBlPG3ipu6dJqfW/k0/fujvIEVw=="],
734
 
735
+ "svelte-sonner": ["svelte-sonner@1.0.4", "", { "dependencies": { "runed": "^0.26.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-ctm9jeV0Rf3im2J6RU1emccrJFjRSdNSPsLlxaF62TLZw9bB1D40U/U7+wqEgohJY/X7FBdghdj0BFQF/IqKPQ=="],
736
+
737
+ "svelte-toolbelt": ["svelte-toolbelt@0.9.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.28.0", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.30.2" } }, "sha512-wBX6MtYw/kpht80j5zLpxJyR9soLizXPIAIWEVd9llAi17SR44ZdG291bldjB7r/K5duC0opDFcuhk2cA1hb8g=="],
738
+
739
+ "tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="],
740
+
741
  "tailwind-merge": ["tailwind-merge@3.3.0", "", {}, "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ=="],
742
 
743
+ "tailwind-variants": ["tailwind-variants@1.0.0", "", { "dependencies": { "tailwind-merge": "3.0.2" }, "peerDependencies": { "tailwindcss": "*" } }, "sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA=="],
744
+
745
  "tailwindcss": ["tailwindcss@4.1.8", "", {}, "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og=="],
746
 
747
  "tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
 
756
 
757
  "three-perf": ["three-perf@github:jerzakm/three-perf#322d7d3", { "dependencies": { "troika-three-text": "^0.52.0", "tweakpane": "^3.1.10" }, "peerDependencies": { "three": ">=0.170" } }, "jerzakm-three-perf-322d7d3"],
758
 
759
+ "three-stdlib": ["three-stdlib@2.36.0", "", { "dependencies": { "@types/draco3d": "^1.4.0", "@types/offscreencanvas": "^2019.6.4", "@types/webxr": "^0.5.2", "draco3d": "^1.4.1", "fflate": "^0.6.9", "potpack": "^1.0.1" }, "peerDependencies": { "three": ">=0.128.0" } }, "sha512-kv0Byb++AXztEGsULgMAs8U2jgUdz6HPpAB/wDJnLiLlaWQX2APHhiTJIN7rqW+Of0eRgcp7jn05U1BsCP3xBA=="],
760
+
761
  "three-viewport-gizmo": ["three-viewport-gizmo@2.2.0", "", { "peerDependencies": { "three": ">=0.162.0 <1.0.0" } }, "sha512-Jo9Liur1rUmdKk75FZumLU/+hbF+RtJHi1qsKZpntjKlCYScK6tjbYoqvJ9M+IJphrlQJF5oReFW7Sambh0N4Q=="],
762
 
763
+ "threlte-postprocessing": ["threlte-postprocessing@0.0.9", "", { "dependencies": { "@threlte/core": "^8.0.1", "@threlte/extras": "^9.0.1", "postprocessing": "^6.36.7", "three": "^0.173.0", "three-stdlib": "^2.35.13", "vite-plugin-glsl": "^1.3.1", "vite-plugin-string": "^1.2.3" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-LzxJXh/z+DeT8xfmIKGCRTw5sOAGaoLXjuxMu9qw84DCmrAueGjXqCHOgPSVF7nbye0ecQYomlUIctcw32by1w=="],
764
+
765
+ "tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
766
+
767
  "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
768
 
769
  "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
 
778
 
779
  "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
780
 
781
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
782
+
783
+ "tw-animate-css": ["tw-animate-css@1.3.3", "", {}, "sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q=="],
784
+
785
  "tweakpane": ["tweakpane@3.1.10", "", {}, "sha512-rqwnl/pUa7+inhI2E9ayGTqqP0EPOOn/wVvSWjZsRbZUItzNShny7pzwL3hVlaN4m9t/aZhsP0aFQ9U5VVR2VQ=="],
786
 
787
  "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
 
790
 
791
  "typescript-eslint": ["typescript-eslint@8.33.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.33.0", "@typescript-eslint/parser": "8.33.0", "@typescript-eslint/utils": "8.33.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ=="],
792
 
793
+ "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
794
+
795
  "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
796
 
797
  "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
798
 
799
+ "vaul-svelte": ["vaul-svelte@1.0.0-next.7", "", { "dependencies": { "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-7zN7Bi3dFQixvvbUJY9uGDe7Ws/dGZeBQR2pXdXmzQiakjrxBvWo0QrmsX3HK+VH+SZOltz378cmgmCS9f9rSg=="],
800
+
801
  "vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
802
 
803
+ "vite-plugin-glsl": ["vite-plugin-glsl@1.4.2", "", { "dependencies": { "@rollup/pluginutils": "^5.1.4" }, "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "sha512-GQHx5tUPFna1zOrX9tbpSHLSVEv/wXZGPEey3Pk8ktHR/OJCEg82Vx2oE9d8mOeMhLfelrtpXg0TBD4riRs39Q=="],
804
+
805
+ "vite-plugin-string": ["vite-plugin-string@1.2.3", "", { "dependencies": { "@rollup/pluginutils": ">=4.1.0" }, "peerDependencies": { "vite": ">=2" } }, "sha512-zw2jL0c4B6CAvxO8PshX04494jTcqJjNH2kW1AugBH+fImRY0evdNtVgmeS1i1VFdua/OFhL7fMqIPh0uF21/Q=="],
806
+
807
  "vitefu": ["vitefu@1.0.6", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["vite"] }, "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA=="],
808
 
809
  "webgl-sdf-generator": ["webgl-sdf-generator@1.1.1", "", {}, "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="],
 
828
 
829
  "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
830
 
831
+ "@iconify/utils/globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="],
832
+
833
+ "@layerstack/svelte-actions/@layerstack/utils": ["@layerstack/utils@1.1.0-next.2", "", { "dependencies": { "d3-array": "^3.2.4", "date-fns": "^4.1.0", "lodash-es": "^4.17.21" } }, "sha512-9Y4TOAE0iIVvnrf1mkft79rF/vTO84bZuc4hjhJB7XguFjy2f00pEfrDjF+IqleWImMFfRu31K2IMM6wRWEBXQ=="],
834
+
835
+ "@layerstack/tailwind/@layerstack/utils": ["@layerstack/utils@1.1.0-next.2", "", { "dependencies": { "d3-array": "^3.2.4", "date-fns": "^4.1.0", "lodash-es": "^4.17.21" } }, "sha512-9Y4TOAE0iIVvnrf1mkft79rF/vTO84bZuc4hjhJB7XguFjy2f00pEfrDjF+IqleWImMFfRu31K2IMM6wRWEBXQ=="],
836
+
837
  "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
838
 
839
  "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
 
850
 
851
  "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
852
 
853
+ "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="],
854
+
855
+ "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
856
+
857
  "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
858
 
859
  "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
860
 
861
+ "mlly/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
862
+
863
+ "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
864
+
865
+ "mode-watcher/runed": ["runed@0.25.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg=="],
866
+
867
+ "mode-watcher/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
868
+
869
+ "paneforge/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
870
+
871
+ "paneforge/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
872
+
873
+ "pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
874
+
875
+ "svelte-sonner/runed": ["runed@0.26.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-qWFv0cvLVRd8pdl/AslqzvtQyEn5KaIugEernwg9G98uJVSZcs/ygvPBvF80LA46V8pwRvSKnaVLDI3+i2wubw=="],
876
+
877
+ "tailwind-variants/tailwind-merge": ["tailwind-merge@3.0.2", "", {}, "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw=="],
878
+
879
+ "three-stdlib/fflate": ["fflate@0.6.10", "", {}, "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg=="],
880
+
881
+ "threlte-postprocessing/three": ["three@0.173.0", "", {}, "sha512-AUwVmViIEUgBwxJJ7stnF0NkPpZxx1aZ6WiAbQ/Qq61h6I9UR4grXtZDmO8mnlaNORhHnIBlXJ1uBxILEKuVyw=="],
882
+
883
+ "vaul-svelte/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
884
+
885
+ "vaul-svelte/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
886
+
887
  "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
888
+
889
+ "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="],
890
+
891
+ "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="],
892
+
893
+ "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
894
+
895
+ "mode-watcher/svelte-toolbelt/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
896
  }
897
  }
components.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://next.shadcn-svelte.com/schema.json",
3
+ "tailwind": {
4
+ "css": "src/app.css",
5
+ "baseColor": "stone"
6
+ },
7
+ "aliases": {
8
+ "components": "$lib/components",
9
+ "utils": "$lib/utils",
10
+ "ui": "$lib/components/ui",
11
+ "hooks": "$lib/hooks",
12
+ "lib": "$lib"
13
+ },
14
+ "typescript": true,
15
+ "registry": "https://next.shadcn-svelte.com/registry"
16
+ }
package.json CHANGED
@@ -16,23 +16,37 @@
16
  "devDependencies": {
17
  "@eslint/compat": "^1.2.5",
18
  "@eslint/js": "^9.18.0",
 
 
 
 
19
  "@sveltejs/adapter-auto": "^6.0.0",
20
  "@sveltejs/adapter-static": "^3.0.8",
21
  "@sveltejs/kit": "^2.16.0",
22
  "@sveltejs/vite-plugin-svelte": "^5.0.0",
23
  "@tailwindcss/vite": "^4.0.0",
 
 
 
24
  "eslint": "^9.18.0",
25
  "eslint-config-prettier": "^10.0.1",
26
  "eslint-plugin-svelte": "^3.0.0",
27
  "globals": "^16.0.0",
 
 
 
28
  "prettier": "^3.4.2",
29
  "prettier-plugin-svelte": "^3.3.3",
30
  "prettier-plugin-tailwindcss": "^0.6.11",
31
  "svelte": "^5.0.0",
32
  "svelte-check": "^4.0.0",
 
 
33
  "tailwindcss": "^4.0.0",
 
34
  "typescript": "^5.0.0",
35
  "typescript-eslint": "^8.20.0",
 
36
  "vite": "^6.2.6"
37
  },
38
  "dependencies": {
@@ -42,6 +56,7 @@
42
  "clsx": "^2.1.1",
43
  "tailwind-merge": "^3.3.0",
44
  "three": "^0.177.0",
 
45
  "zod": "^3.25.42"
46
  }
47
  }
 
16
  "devDependencies": {
17
  "@eslint/compat": "^1.2.5",
18
  "@eslint/js": "^9.18.0",
19
+ "@iconify/json": "^2.2.344",
20
+ "@iconify/tailwind4": "^1.0.6",
21
+ "@internationalized/date": "^3.5.6",
22
+ "@lucide/svelte": "^0.511.0",
23
  "@sveltejs/adapter-auto": "^6.0.0",
24
  "@sveltejs/adapter-static": "^3.0.8",
25
  "@sveltejs/kit": "^2.16.0",
26
  "@sveltejs/vite-plugin-svelte": "^5.0.0",
27
  "@tailwindcss/vite": "^4.0.0",
28
+ "@tanstack/table-core": "^8.20.5",
29
+ "bits-ui": "^2.4.1",
30
+ "embla-carousel-svelte": "^8.6.0",
31
  "eslint": "^9.18.0",
32
  "eslint-config-prettier": "^10.0.1",
33
  "eslint-plugin-svelte": "^3.0.0",
34
  "globals": "^16.0.0",
35
+ "layerchart": "2.0.0-next.10",
36
+ "mode-watcher": "^1.0.6",
37
+ "paneforge": "^1.0.0-next.5",
38
  "prettier": "^3.4.2",
39
  "prettier-plugin-svelte": "^3.3.3",
40
  "prettier-plugin-tailwindcss": "^0.6.11",
41
  "svelte": "^5.0.0",
42
  "svelte-check": "^4.0.0",
43
+ "svelte-sonner": "^1.0.1",
44
+ "tailwind-variants": "^1.0.0",
45
  "tailwindcss": "^4.0.0",
46
+ "tw-animate-css": "^1.3.3",
47
  "typescript": "^5.0.0",
48
  "typescript-eslint": "^8.20.0",
49
+ "vaul-svelte": "^1.0.0-next.7",
50
  "vite": "^6.2.6"
51
  },
52
  "dependencies": {
 
56
  "clsx": "^2.1.1",
57
  "tailwind-merge": "^3.3.0",
58
  "three": "^0.177.0",
59
+ "threlte-postprocessing": "^0.0.9",
60
  "zod": "^3.25.42"
61
  }
62
  }
src-python/src/main.py CHANGED
@@ -641,50 +641,52 @@ async def sync_slave_with_current_state(robot_id: str, websocket: WebSocket):
641
 
642
  # ============= FRONTEND SERVING ROUTES (Must be last) =============
643
 
644
-
645
- # Serve static assets from the _app directory
646
- @app.get("/_app/{path:path}")
647
- async def serve_app_assets(path: str):
648
- """Serve Svelte app assets"""
649
- static_dir = get_static_dir()
650
- if not static_dir:
651
- raise HTTPException(status_code=404, detail="Frontend not found")
652
-
653
- file_path = os.path.join(static_dir, "_app", path)
654
- if os.path.exists(file_path) and os.path.isfile(file_path):
655
- return FileResponse(file_path)
656
- raise HTTPException(status_code=404, detail="File not found")
657
-
658
-
659
- @app.get("/")
660
- async def serve_frontend():
661
- """Serve the main frontend page"""
662
- static_dir = get_static_dir()
663
- if not static_dir:
 
 
 
 
 
 
 
 
664
  return {
665
  "message": "Frontend not built. Run 'bun run build' to build the frontend."
666
  }
667
 
668
- index_file = os.path.join(static_dir, "index.html")
669
- if os.path.exists(index_file):
670
- return FileResponse(index_file)
671
- return {"message": "Frontend not built. Run 'bun run build' to build the frontend."}
672
-
673
-
674
- # Catch-all route for client-side routing (SPA fallback) - MUST BE LAST
675
- @app.get("/{path:path}")
676
- async def serve_spa_fallback(path: str):
677
- """Serve the frontend for client-side routing"""
678
- # For all paths not handled by other routes, serve the index.html (SPA)
679
- static_dir = get_static_dir()
680
- if not static_dir:
681
  raise HTTPException(status_code=404, detail="Frontend not found")
682
 
683
- index_file = os.path.join(static_dir, "index.html")
684
- if os.path.exists(index_file):
685
- return FileResponse(index_file)
686
- raise HTTPException(status_code=404, detail="Frontend not found")
687
-
688
 
689
  if __name__ == "__main__":
690
  import uvicorn
 
641
 
642
  # ============= FRONTEND SERVING ROUTES (Must be last) =============
643
 
644
+ EXPOSE_FRONTEND = os.getenv("EXPOSE_FRONTEND", "false").lower() == "true"
645
+
646
+ if EXPOSE_FRONTEND:
647
+ # Serve static assets from the _app directory
648
+ @app.get("/_app/{path:path}")
649
+ async def serve_app_assets(path: str):
650
+ """Serve Svelte app assets"""
651
+ static_dir = get_static_dir()
652
+ if not static_dir:
653
+ raise HTTPException(status_code=404, detail="Frontend not found")
654
+
655
+ file_path = os.path.join(static_dir, "_app", path)
656
+ if os.path.exists(file_path) and os.path.isfile(file_path):
657
+ return FileResponse(file_path)
658
+ raise HTTPException(status_code=404, detail="File not found")
659
+
660
+ @app.get("/")
661
+ async def serve_frontend():
662
+ """Serve the main frontend page"""
663
+ static_dir = get_static_dir()
664
+ if not static_dir:
665
+ return {
666
+ "message": "Frontend not built. Run 'bun run build' to build the frontend."
667
+ }
668
+
669
+ index_file = os.path.join(static_dir, "index.html")
670
+ if os.path.exists(index_file):
671
+ return FileResponse(index_file)
672
  return {
673
  "message": "Frontend not built. Run 'bun run build' to build the frontend."
674
  }
675
 
676
+ # Catch-all route for client-side routing (SPA fallback) - MUST BE LAST
677
+ @app.get("/{path:path}")
678
+ async def serve_spa_fallback(path: str):
679
+ """Serve the frontend for client-side routing"""
680
+ # For all paths not handled by other routes, serve the index.html (SPA)
681
+ static_dir = get_static_dir()
682
+ if not static_dir:
683
+ raise HTTPException(status_code=404, detail="Frontend not found")
684
+
685
+ index_file = os.path.join(static_dir, "index.html")
686
+ if os.path.exists(index_file):
687
+ return FileResponse(index_file)
 
688
  raise HTTPException(status_code=404, detail="Frontend not found")
689
 
 
 
 
 
 
690
 
691
  if __name__ == "__main__":
692
  import uvicorn
src/app.css CHANGED
@@ -1 +1,122 @@
1
- @import 'tailwindcss';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "tailwindcss";
2
+
3
+ @import "tw-animate-css";
4
+ @plugin "@iconify/tailwind4";
5
+
6
+ @custom-variant dark (&:is(.dark *));
7
+
8
+ :root {
9
+ --radius: 0.625rem;
10
+ --background: oklch(1 0 0);
11
+ --foreground: oklch(0.147 0.004 49.25);
12
+ --card: oklch(1 0 0);
13
+ --card-foreground: oklch(0.147 0.004 49.25);
14
+ --popover: oklch(1 0 0);
15
+ --popover-foreground: oklch(0.147 0.004 49.25);
16
+ --primary: oklch(0.216 0.006 56.043);
17
+ --primary-foreground: oklch(0.985 0.001 106.423);
18
+ --secondary: oklch(0.97 0.001 106.424);
19
+ --secondary-foreground: oklch(0.216 0.006 56.043);
20
+ --muted: oklch(0.97 0.001 106.424);
21
+ --muted-foreground: oklch(0.553 0.013 58.071);
22
+ --accent: oklch(0.97 0.001 106.424);
23
+ --accent-foreground: oklch(0.216 0.006 56.043);
24
+ --destructive: oklch(0.577 0.245 27.325);
25
+ --border: oklch(0.923 0.003 48.717);
26
+ --input: oklch(0.923 0.003 48.717);
27
+ --ring: oklch(0.709 0.01 56.259);
28
+ --chart-1: oklch(0.646 0.222 41.116);
29
+ --chart-2: oklch(0.6 0.118 184.704);
30
+ --chart-3: oklch(0.398 0.07 227.392);
31
+ --chart-4: oklch(0.828 0.189 84.429);
32
+ --chart-5: oklch(0.769 0.188 70.08);
33
+ --sidebar: oklch(0.985 0.001 106.423);
34
+ --sidebar-foreground: oklch(0.147 0.004 49.25);
35
+ --sidebar-primary: oklch(0.216 0.006 56.043);
36
+ --sidebar-primary-foreground: oklch(0.985 0.001 106.423);
37
+ --sidebar-accent: oklch(0.97 0.001 106.424);
38
+ --sidebar-accent-foreground: oklch(0.216 0.006 56.043);
39
+ --sidebar-border: oklch(0.923 0.003 48.717);
40
+ --sidebar-ring: oklch(0.709 0.01 56.259);
41
+ }
42
+
43
+ .dark {
44
+ --background: oklch(0.147 0.004 49.25);
45
+ --foreground: oklch(0.985 0.001 106.423);
46
+ --card: oklch(0.216 0.006 56.043);
47
+ --card-foreground: oklch(0.985 0.001 106.423);
48
+ --popover: oklch(0.216 0.006 56.043);
49
+ --popover-foreground: oklch(0.985 0.001 106.423);
50
+ --primary: oklch(0.923 0.003 48.717);
51
+ --primary-foreground: oklch(0.216 0.006 56.043);
52
+ --secondary: oklch(0.268 0.007 34.298);
53
+ --secondary-foreground: oklch(0.985 0.001 106.423);
54
+ --muted: oklch(0.268 0.007 34.298);
55
+ --muted-foreground: oklch(0.709 0.01 56.259);
56
+ --accent: oklch(0.268 0.007 34.298);
57
+ --accent-foreground: oklch(0.985 0.001 106.423);
58
+ --destructive: oklch(0.704 0.191 22.216);
59
+ --border: oklch(1 0 0 / 10%);
60
+ --input: oklch(1 0 0 / 15%);
61
+ --ring: oklch(0.553 0.013 58.071);
62
+ --chart-1: oklch(0.488 0.243 264.376);
63
+ --chart-2: oklch(0.696 0.17 162.48);
64
+ --chart-3: oklch(0.769 0.188 70.08);
65
+ --chart-4: oklch(0.627 0.265 303.9);
66
+ --chart-5: oklch(0.645 0.246 16.439);
67
+ --sidebar: oklch(0.216 0.006 56.043);
68
+ --sidebar-foreground: oklch(0.985 0.001 106.423);
69
+ --sidebar-primary: oklch(0.488 0.243 264.376);
70
+ --sidebar-primary-foreground: oklch(0.985 0.001 106.423);
71
+ --sidebar-accent: oklch(0.268 0.007 34.298);
72
+ --sidebar-accent-foreground: oklch(0.985 0.001 106.423);
73
+ --sidebar-border: oklch(1 0 0 / 10%);
74
+ --sidebar-ring: oklch(0.553 0.013 58.071);
75
+ }
76
+
77
+ @theme inline {
78
+ --radius-sm: calc(var(--radius) - 4px);
79
+ --radius-md: calc(var(--radius) - 2px);
80
+ --radius-lg: var(--radius);
81
+ --radius-xl: calc(var(--radius) + 4px);
82
+ --color-background: var(--background);
83
+ --color-foreground: var(--foreground);
84
+ --color-card: var(--card);
85
+ --color-card-foreground: var(--card-foreground);
86
+ --color-popover: var(--popover);
87
+ --color-popover-foreground: var(--popover-foreground);
88
+ --color-primary: var(--primary);
89
+ --color-primary-foreground: var(--primary-foreground);
90
+ --color-secondary: var(--secondary);
91
+ --color-secondary-foreground: var(--secondary-foreground);
92
+ --color-muted: var(--muted);
93
+ --color-muted-foreground: var(--muted-foreground);
94
+ --color-accent: var(--accent);
95
+ --color-accent-foreground: var(--accent-foreground);
96
+ --color-destructive: var(--destructive);
97
+ --color-border: var(--border);
98
+ --color-input: var(--input);
99
+ --color-ring: var(--ring);
100
+ --color-chart-1: var(--chart-1);
101
+ --color-chart-2: var(--chart-2);
102
+ --color-chart-3: var(--chart-3);
103
+ --color-chart-4: var(--chart-4);
104
+ --color-chart-5: var(--chart-5);
105
+ --color-sidebar: var(--sidebar);
106
+ --color-sidebar-foreground: var(--sidebar-foreground);
107
+ --color-sidebar-primary: var(--sidebar-primary);
108
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
109
+ --color-sidebar-accent: var(--sidebar-accent);
110
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
111
+ --color-sidebar-border: var(--sidebar-border);
112
+ --color-sidebar-ring: var(--sidebar-ring);
113
+ }
114
+
115
+ @layer base {
116
+ * {
117
+ @apply border-border outline-ring/50;
118
+ }
119
+ body {
120
+ @apply bg-background text-foreground;
121
+ }
122
+ }
src/lib/components/{scene → 3d}/Floor.svelte RENAMED
@@ -2,19 +2,15 @@
2
  import { T } from '@threlte/core';
3
  import * as THREE from 'three';
4
 
5
- /**
6
- * Creates a grid texture for the ground plane
7
- * Matches the original React implementation for visual consistency
8
- */
9
  function createGridTexture(): THREE.CanvasTexture {
10
- const size = 200;
11
  const canvas = document.createElement('canvas');
12
  canvas.width = canvas.height = size;
13
  const ctx = canvas.getContext('2d');
14
 
15
  if (ctx) {
16
- // Base color
17
- ctx.fillStyle = '#aaa';
18
  ctx.fillRect(0, 0, size, size);
19
 
20
  // Grid lines
@@ -31,26 +27,20 @@
31
 
32
  const texture = new THREE.CanvasTexture(canvas);
33
  texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
34
- texture.repeat.set(100, 100);
35
  return texture;
36
  }
37
 
38
- // Create texture reactively using Svelte 5 runes
39
  const gridTexture = createGridTexture();
40
  </script>
41
 
42
- <!-- Ground plane with grid texture matching React version -->
43
  <T.Mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
44
- <T.PlaneGeometry args={[30, 30]} />
45
  <T.MeshPhysicalMaterial
46
  color={0x808080}
47
- metalness={0.7}
48
- roughness={0.3}
49
- reflectivity={0.1}
50
- clearcoat={0.3}
51
  side={THREE.DoubleSide}
52
  transparent
53
- opacity={0.7}
54
  map={gridTexture}
55
  />
56
  </T.Mesh>
 
2
  import { T } from '@threlte/core';
3
  import * as THREE from 'three';
4
 
 
 
 
 
5
  function createGridTexture(): THREE.CanvasTexture {
6
+ const size = 100;
7
  const canvas = document.createElement('canvas');
8
  canvas.width = canvas.height = size;
9
  const ctx = canvas.getContext('2d');
10
 
11
  if (ctx) {
12
+ // Base color - updated to match Tailwind's bg-stone-500
13
+ ctx.fillStyle = '#71717A';
14
  ctx.fillRect(0, 0, size, size);
15
 
16
  // Grid lines
 
27
 
28
  const texture = new THREE.CanvasTexture(canvas);
29
  texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
30
+ texture.repeat.set(10, 10);
31
  return texture;
32
  }
33
 
 
34
  const gridTexture = createGridTexture();
35
  </script>
36
 
 
37
  <T.Mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
38
+ <T.PlaneGeometry args={[10, 10]} />
39
  <T.MeshPhysicalMaterial
40
  color={0x808080}
 
 
 
 
41
  side={THREE.DoubleSide}
42
  transparent
43
+ opacity={1}
44
  map={gridTexture}
45
  />
46
  </T.Mesh>
src/lib/components/3d/Robot.svelte ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { T } from '@threlte/core';
3
+ import { getRootLinks } from '$lib/components/3d/robot/URDF/utils/UrdfParser';
4
+ import UrdfLink from '$lib/components/3d/robot/URDF/primitives/UrdfLink.svelte';
5
+ import { robotManager } from '$lib/robot/RobotManager.svelte';
6
+ import Selectable from './Selectable.svelte';
7
+
8
+ interface Props {}
9
+
10
+ let {}: Props = $props();
11
+
12
+ // Get all robots from the manager
13
+ const robots = $derived(robotManager.robots);
14
+ </script>
15
+
16
+ {#each robots as robot, index (robot.id)}
17
+ {@const xPosition = index * 5}
18
+ <T.Group position.x={xPosition} position.y={0} position.z={0} quaternion={[0, 0, 0, 1]} scale={[10, 10, 10]} rotation={[-Math.PI / 2, 0, 0]}>
19
+ <Selectable
20
+ hoverColor="#ff6b6b"
21
+ hoverOpacity={0.5}
22
+ enableEdit={true}
23
+ >
24
+ {#snippet content({ isHighlighted })}
25
+ {#each getRootLinks(robot.robotState.robot) as link}
26
+ <UrdfLink
27
+ robot={robot.robotState.robot}
28
+ {link}
29
+ textScale={0.2}
30
+ showName={isHighlighted}
31
+ showVisual={true}
32
+ showCollision={false}
33
+ visualColor="#333333"
34
+ visualOpacity={isHighlighted ? 0.1 : 1.0}
35
+ collisionOpacity={1.0}
36
+ collisionColor="#813d9c"
37
+ jointNames={isHighlighted}
38
+ joints={isHighlighted}
39
+ jointColor="#62a0ea"
40
+ jointIndicatorColor="#f66151"
41
+ nameHeight={0.1}
42
+ selectedLink={robot.robotState.selection.selectedLink}
43
+ selectedJoint={robot.robotState.selection.selectedJoint}
44
+ highlightColor="#ffa348"
45
+ showLine={isHighlighted}
46
+ opacity={1}
47
+ isInteractive={false}
48
+ />
49
+ {/each}
50
+ {/snippet}
51
+ </Selectable>
52
+ </T.Group>
53
+ {/each}
src/lib/components/3d/Selectable.svelte ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { T } from '@threlte/core';
3
+ import { TransformControls, interactivity } from '@threlte/extras';
4
+ import type { Snippet } from 'svelte';
5
+ import { Spring } from 'svelte/motion';
6
+ import { useCursor } from '@threlte/extras';
7
+ import { setContext } from 'svelte';
8
+
9
+ interface Props {
10
+ content: Snippet<[{ isHighlighted: boolean }]> // renderable
11
+ enableEdit?: boolean;
12
+ hoverColor?: string;
13
+ defaultColor?: string;
14
+ hoverOpacity?: number;
15
+ }
16
+
17
+ let {
18
+ content,
19
+ enableEdit = $bindable(true),
20
+ hoverColor = '#ffa348',
21
+ hoverOpacity = 0.5,
22
+ }: Props = $props();
23
+
24
+ interactivity();
25
+ const scale = new Spring(1);
26
+
27
+ // Position state to persist transforms
28
+ let position = $state<[number, number, number]>([0, 0, 0]);
29
+ let rotation = $state<[number, number, number]>([0, 0, 0]);
30
+
31
+ // Hover state
32
+ let isHovered = $state(false);
33
+ let isSelected = $state(false);
34
+ let isHighlighted = $derived(enableEdit && (isSelected || isHovered));
35
+
36
+ $effect(() => {
37
+ // If isHighlighted is true, set the color to hoverColor and opacity to hoverOpacity
38
+ if (isHighlighted) {
39
+ scale.target = 1.05;
40
+ } else {
41
+ scale.target = 1;
42
+ }
43
+ });
44
+
45
+ const { onPointerEnter, onPointerLeave } = useCursor();
46
+
47
+ // Handle keyboard events for deselection
48
+ $effect(() => {
49
+ const handleKeyDown = (event: KeyboardEvent) => {
50
+ if (event.key === 'Escape' && isSelected) {
51
+ isSelected = false;
52
+ }
53
+ };
54
+
55
+ if (isSelected) {
56
+ document.addEventListener('keydown', handleKeyDown);
57
+ return () => {
58
+ document.removeEventListener('keydown', handleKeyDown);
59
+ };
60
+ }
61
+ });
62
+
63
+ // Handle transform changes
64
+ const handleTransform = (ref: any) => {
65
+ ref.updateMatrix();
66
+ // Update our position state from the object's current position
67
+ position = ref.position.toArray();
68
+ rotation = ref.rotation.toArray();
69
+ };
70
+ </script>
71
+
72
+ <T.Group
73
+ {position}
74
+ {rotation}
75
+ onclick={() => {
76
+ isSelected = true;
77
+
78
+ }}
79
+ onpointerenter={() => {
80
+ onPointerEnter();
81
+ isHovered = true;
82
+ }}
83
+ onpointerleave={() => {
84
+ onPointerLeave();
85
+ isHovered = false;
86
+ }}
87
+ scale={scale.current}
88
+ >
89
+ {#snippet children({ ref })}
90
+ {@render content({ isHighlighted })}
91
+ {#if isSelected && enableEdit}
92
+ <TransformControls
93
+ object={ref}
94
+ mode="translate"
95
+ showY={false}
96
+ axis="XZ"
97
+ space="world"
98
+ on:objectChange={() => handleTransform(ref)}
99
+ on:mouseUp={() => handleTransform(ref)}
100
+ />
101
+ {/if}
102
+ {/snippet}
103
+ </T.Group>
104
+
105
+ <!-- From https://github.com/brean/urdf-viewer -->
src/lib/components/{scene → 3d}/robot/URDF/createRobot.svelte.ts RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfBox.ts RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfCylinder.ts RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfJoint.ts RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfLink.ts RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfMesh.ts RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfRobot.ts RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/interfaces/IUrdfVisual.ts RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/interfaces/index.ts RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/mesh/DAE.svelte RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/mesh/OBJ.svelte RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/mesh/STL.svelte RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfJoint.svelte RENAMED
@@ -10,7 +10,6 @@
10
  Text,
11
  type InteractivityProps
12
  } from '@threlte/extras';
13
- import Selectable from '../../../Selectable.svelte';
14
 
15
  import type IUrdfLink from '../interfaces/IUrdfLink';
16
  import type IUrdfRobot from '../interfaces/IUrdfRobot';
@@ -72,20 +71,6 @@
72
  </script>
73
 
74
  {@html `<!-- Joint ${joint.name} (${joint.type}) -->`}
75
- {#if showName}
76
- <Billboard
77
- position.x={joint.origin_xyz[0]}
78
- position.y={joint.origin_xyz[1]}
79
- position.z={joint.origin_xyz[2]}
80
- >
81
- <Text
82
- scale={nameHeight}
83
- color={selectedJoint == joint ? highlightColor : jointColor}
84
- text={joint.name}
85
- characters="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
86
- ></Text>
87
- </Billboard>
88
- {/if}
89
 
90
  <!-- draw line from parent-frame to joint origin -->
91
  {#if showLine}
@@ -100,8 +85,8 @@
100
  </T.Line>
101
  {/if}
102
 
103
- <Selectable origin={joint} selected={selectedJoint == joint}>
104
- <T.Group rotation={joint.rotation || [0, 0, 0]}>
105
  {#if joint.child}
106
  <UrdfLink
107
  {robot}
@@ -121,7 +106,7 @@
121
  {selectedLink}
122
  {selectedJoint}
123
  {highlightColor}
124
- showLine={true}
125
  opacity={1}
126
  isInteractive={true}
127
  />
@@ -143,6 +128,24 @@
143
  </T.Mesh>
144
  {/if}
145
  </T.Group>
146
- </Selectable>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
  <!-- From https://github.com/brean/urdf-viewer -->
 
10
  Text,
11
  type InteractivityProps
12
  } from '@threlte/extras';
 
13
 
14
  import type IUrdfLink from '../interfaces/IUrdfLink';
15
  import type IUrdfRobot from '../interfaces/IUrdfRobot';
 
71
  </script>
72
 
73
  {@html `<!-- Joint ${joint.name} (${joint.type}) -->`}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
  <!-- draw line from parent-frame to joint origin -->
76
  {#if showLine}
 
85
  </T.Line>
86
  {/if}
87
 
88
+ <T.Group position={joint.origin_xyz} rotation={joint.origin_rpy}>
89
+ <T.Group rotation={joint.rotation}>
90
  {#if joint.child}
91
  <UrdfLink
92
  {robot}
 
106
  {selectedLink}
107
  {selectedJoint}
108
  {highlightColor}
109
+ {showLine}
110
  opacity={1}
111
  isInteractive={true}
112
  />
 
128
  </T.Mesh>
129
  {/if}
130
  </T.Group>
131
+ </T.Group>
132
+
133
+ {#if showName}
134
+ <Billboard
135
+ position.x={joint.origin_xyz[0] + 0.04}
136
+ position.y={joint.origin_xyz[1] + 0.01}
137
+ position.z={joint.origin_xyz[2] + 0.03}
138
+ renderOrder={999}
139
+ frustumCulled={false}
140
+ >
141
+ <Text
142
+ scale={nameHeight}
143
+ color={selectedJoint == joint ? highlightColor : jointColor}
144
+ text={joint.name}
145
+ characters="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
146
+ renderOrder={999}
147
+ ></Text>
148
+ </Billboard>
149
+ {/if}
150
 
151
  <!-- From https://github.com/brean/urdf-viewer -->
src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfLink.svelte RENAMED
@@ -59,10 +59,10 @@
59
 
60
  {@html `<!-- Link ${link.name} -->`}
61
  {#if showName}
62
- <!-- <Billboard position.x={0} position.y={0} position.z={0}>
63
  <Text anchorY={-0.2} scale={textScale} text={link.name} characters="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
64
  ></Text>
65
- </Billboard> -->
66
  {/if}
67
 
68
  {#if showVisual}
@@ -85,10 +85,10 @@
85
  {/each}
86
  {/if}
87
 
88
- {#each getChildJoints(robot, link) as joint}
89
  <UrdfJoint
90
  {robot}
91
- joint={joint}
92
  {selectedJoint}
93
  {selectedLink}
94
  {showName}
 
59
 
60
  {@html `<!-- Link ${link.name} -->`}
61
  {#if showName}
62
+ <Billboard position.x={0} position.y={0} position.z={0}>
63
  <Text anchorY={-0.2} scale={textScale} text={link.name} characters="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
64
  ></Text>
65
+ </Billboard>
66
  {/if}
67
 
68
  {#if showVisual}
 
85
  {/each}
86
  {/if}
87
 
88
+ {#each getChildJoints(robot, link) as joint (joint.name)}
89
  <UrdfJoint
90
  {robot}
91
+ {joint}
92
  {selectedJoint}
93
  {selectedLink}
94
  {showName}
src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfThree.svelte RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/primitives/UrdfVisual.svelte RENAMED
@@ -8,6 +8,7 @@
8
  import { interactivity, type InteractivityProps } from '@threlte/extras';
9
  import type IUrdfLink from '../interfaces/IUrdfLink';
10
  import { DoubleSide, type Side } from 'three';
 
11
 
12
  type Props = InteractivityProps & {
13
  visual: IUrdfVisual;
@@ -36,17 +37,19 @@
36
  ...restProps
37
  }: Props = $props();
38
 
39
- const color = visual?.color_rgba
 
40
  ? numberArrayToColor(visual.color_rgba.slice(0, 3) as [number, number, number])
41
  : defaultColor;
 
42
 
43
  </script>
44
 
45
  {#if visual.type === 'mesh'}
46
  {#if visual.geometry.type === 'stl'}
47
  <STL
48
- {color}
49
- {opacity}
50
  filename={visual.geometry.filename}
51
  scale={visual.geometry.scale}
52
  {position}
@@ -59,8 +62,8 @@
59
  />
60
  {:else if visual.geometry.type === 'obj'}
61
  <OBJ
62
- {color}
63
- {opacity}
64
  scale={visual.geometry.scale}
65
  filename={visual.geometry.filename}
66
  {position}
@@ -74,8 +77,8 @@
74
  {:else if visual.geometry.type === 'dae'}
75
  <DAE
76
  filename={visual.geometry.filename}
77
- {color}
78
- {opacity}
79
  scale={visual.geometry.scale}
80
  {position}
81
  {rotation}
@@ -92,8 +95,8 @@
92
  args={[visual.geometry.radius, visual.geometry.radius, visual.geometry.length]}
93
  />
94
  <T.MeshBasicMaterial
95
- {color}
96
- opacity={opacity < 1.0 ? opacity : undefined}
97
  transparent={opacity < 1.0}
98
  />
99
  </T.Mesh>
@@ -101,8 +104,8 @@
101
  <T.Mesh castShadow receiveShadow scale={visual.geometry.size} {onclick}>
102
  <T.BoxGeometry />
103
  <T.MeshBasicMaterial
104
- {color}
105
- opacity={opacity < 1.0 ? opacity : undefined}
106
  transparent={opacity < 1.0}
107
  />
108
  </T.Mesh>
 
8
  import { interactivity, type InteractivityProps } from '@threlte/extras';
9
  import type IUrdfLink from '../interfaces/IUrdfLink';
10
  import { DoubleSide, type Side } from 'three';
11
+ import { getContext } from 'svelte';
12
 
13
  type Props = InteractivityProps & {
14
  visual: IUrdfVisual;
 
37
  ...restProps
38
  }: Props = $props();
39
 
40
+ // Use context color only when hovering, otherwise use original logic
41
+ const baseColor = visual?.color_rgba
42
  ? numberArrayToColor(visual.color_rgba.slice(0, 3) as [number, number, number])
43
  : defaultColor;
44
+
45
 
46
  </script>
47
 
48
  {#if visual.type === 'mesh'}
49
  {#if visual.geometry.type === 'stl'}
50
  <STL
51
+ color={baseColor}
52
+ opacity={opacity}
53
  filename={visual.geometry.filename}
54
  scale={visual.geometry.scale}
55
  {position}
 
62
  />
63
  {:else if visual.geometry.type === 'obj'}
64
  <OBJ
65
+ color={baseColor}
66
+ opacity={opacity}
67
  scale={visual.geometry.scale}
68
  filename={visual.geometry.filename}
69
  {position}
 
77
  {:else if visual.geometry.type === 'dae'}
78
  <DAE
79
  filename={visual.geometry.filename}
80
+ color={baseColor}
81
+ opacity={opacity}
82
  scale={visual.geometry.scale}
83
  {position}
84
  {rotation}
 
95
  args={[visual.geometry.radius, visual.geometry.radius, visual.geometry.length]}
96
  />
97
  <T.MeshBasicMaterial
98
+ color={baseColor}
99
+ opacity={opacity}
100
  transparent={opacity < 1.0}
101
  />
102
  </T.Mesh>
 
104
  <T.Mesh castShadow receiveShadow scale={visual.geometry.size} {onclick}>
105
  <T.BoxGeometry />
106
  <T.MeshBasicMaterial
107
+ color={baseColor}
108
+ opacity={opacity}
109
  transparent={opacity < 1.0}
110
  />
111
  </T.Mesh>
src/lib/components/{scene → 3d}/robot/URDF/runes/urdf_state.svelte.ts RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/utils/UrdfParser.ts RENAMED
File without changes
src/lib/components/{scene → 3d}/robot/URDF/utils/helper.ts RENAMED
File without changes
src/lib/components/ButtonBar.svelte ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { Button } from '$lib/components/ui/button';
3
+ import { Badge } from '$lib/components/ui/badge';
4
+ import * as Popover from '$lib/components/ui/popover';
5
+ import { toast } from 'svelte-sonner';
6
+ import { cn } from '$lib/utils';
7
+ import { robotManager } from "$lib/robot/RobotManager.svelte";
8
+ import { robotUrdfConfigMap } from "$lib/configs/robotUrdfConfig";
9
+
10
+ interface Props {
11
+ controlsOpen?: boolean;
12
+ onToggleControls?: () => void;
13
+ }
14
+
15
+ let {
16
+ controlsOpen = $bindable(false),
17
+ onToggleControls
18
+ }: Props = $props();
19
+
20
+ let isCreating = $state(false);
21
+ let selectedRobotType = $state("so-arm100"); // Default to SO-100
22
+ let showRobotSelector = $state(false);
23
+
24
+ // Get available robot types
25
+ const robotTypes = Object.keys(robotUrdfConfigMap);
26
+
27
+ // Function to add robot using the proper robot creation logic
28
+ async function addRobot() {
29
+ if (isCreating) return;
30
+
31
+ console.log(`Creating ${selectedRobotType} robot...`);
32
+ isCreating = true;
33
+
34
+ try {
35
+ const urdfConfig = robotUrdfConfigMap[selectedRobotType];
36
+
37
+ if (!urdfConfig) {
38
+ throw new Error(`Unknown robot type: ${selectedRobotType}`);
39
+ }
40
+
41
+ const robotId = `robot-${Date.now()}`;
42
+ console.log('Creating robot with ID:', robotId, 'and config:', urdfConfig);
43
+
44
+ const robot = await robotManager.createRobot(robotId, urdfConfig);
45
+ console.log('Robot created successfully:', robot);
46
+
47
+ toast.success("Robot Added", {
48
+ description: `${selectedRobotType} robot ${robotId.slice(0, 12)}... created successfully.`
49
+ });
50
+
51
+ showRobotSelector = false; // Close selector on success
52
+
53
+ } catch (error) {
54
+ console.error('Robot creation failed:', error);
55
+ toast.error("Failed to Add Robot", {
56
+ description: `Could not create ${selectedRobotType} robot: ${error}`
57
+ });
58
+ } finally {
59
+ isCreating = false;
60
+ }
61
+ }
62
+
63
+ // Quick add SO-100 function
64
+ async function quickAddSO100() {
65
+ selectedRobotType = "so-arm100";
66
+ await addRobot();
67
+ }
68
+ </script>
69
+
70
+ <!-- Button Bar Container -->
71
+ <div class="fixed top-4 left-4 z-50 flex gap-2">
72
+ <!-- Add Robot Button with Dropdown -->
73
+ <div class="flex">
74
+ <!-- Main Add Button (SO-100) -->
75
+ <Button
76
+ variant="default"
77
+ size="sm"
78
+ onclick={quickAddSO100}
79
+ disabled={isCreating}
80
+ class="shadow-lg bg-green-600 hover:bg-green-500 disabled:bg-gray-600 text-white border-green-500 transition-all duration-200 group rounded-r-none border-r-0"
81
+ >
82
+ <span class={cn(
83
+ "size-4 mr-2 transition-transform duration-200",
84
+ isCreating ? "icon-[mdi--loading] animate-spin" : "icon-[mdi--plus] group-hover:rotate-90"
85
+ )}></span>
86
+ {isCreating ? 'Creating...' : 'Add SO-100'}
87
+ </Button>
88
+
89
+ <!-- Dropdown Button -->
90
+ <Popover.Root bind:open={showRobotSelector}>
91
+ <Popover.Trigger>
92
+ {#snippet child({ props })}
93
+ <Button
94
+ {...props}
95
+ variant="default"
96
+ size="sm"
97
+ disabled={isCreating}
98
+ class="shadow-lg bg-green-600 hover:bg-green-500 disabled:bg-gray-600 text-white border-green-500 transition-all duration-200 rounded-l-none border-l border-green-400 px-2"
99
+ >
100
+ <span class="icon-[mdi--chevron-down] size-4"></span>
101
+ </Button>
102
+ {/snippet}
103
+ </Popover.Trigger>
104
+ <Popover.Content class="w-64 p-3 bg-slate-800 border-slate-600">
105
+ <div class="space-y-3">
106
+ <h4 class="text-sm font-medium text-slate-100">Select Robot Type</h4>
107
+ <div class="space-y-2">
108
+ {#each robotTypes as robotType}
109
+ <Button
110
+ variant={selectedRobotType === robotType ? "default" : "ghost"}
111
+ size="sm"
112
+ onclick={async () => {
113
+ selectedRobotType = robotType;
114
+ await addRobot();
115
+ }}
116
+ disabled={isCreating}
117
+ class="w-full justify-start text-left"
118
+ >
119
+ <span class="icon-[mdi--robot] size-4 mr-2"></span>
120
+ {robotType}
121
+ {#if robotType === "so-arm100"}
122
+ <Badge variant="secondary" class="ml-auto text-xs">SO-100</Badge>
123
+ {/if}
124
+ </Button>
125
+ {/each}
126
+ </div>
127
+ </div>
128
+ </Popover.Content>
129
+ </Popover.Root>
130
+ </div>
131
+
132
+ <!-- Controls Button -->
133
+ <Button
134
+ variant="outline"
135
+ size="sm"
136
+ onclick={onToggleControls}
137
+ class={cn(
138
+ "shadow-lg transition-all duration-200 border-slate-600 backdrop-blur-sm group",
139
+ controlsOpen
140
+ ? "bg-blue-600/90 text-white hover:bg-blue-500 border-blue-500"
141
+ : "bg-slate-800/90 text-slate-100 hover:bg-slate-700"
142
+ )}
143
+ >
144
+ <span class={cn(
145
+ "size-4 mr-2 transition-transform duration-200",
146
+ controlsOpen
147
+ ? "icon-[mdi--close] group-hover:rotate-90"
148
+ : "icon-[mdi--gamepad-variant] group-hover:scale-110"
149
+ )}></span>
150
+ {controlsOpen ? 'Close' : 'Controls'}
151
+
152
+ {#if !controlsOpen}
153
+ <Badge
154
+ variant="secondary"
155
+ class="ml-2 bg-blue-400/20 text-blue-300 border-blue-400/30 text-xs"
156
+ >
157
+ Robot
158
+ </Badge>
159
+ {/if}
160
+ </Button>
161
+ </div>
src/lib/components/ControlsSheet.svelte ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { Button } from '$lib/components/ui/button';
3
+ import * as Sheet from '$lib/components/ui/sheet';
4
+ import { Badge } from '$lib/components/ui/badge';
5
+ import { Separator } from '$lib/components/ui/separator';
6
+ import ControlPanel from '$lib/components/panel/ControlPanel.svelte';
7
+
8
+ interface Props {
9
+ isOpen?: boolean;
10
+ }
11
+
12
+ let { isOpen = $bindable(false) }: Props = $props();
13
+
14
+ function toggleSheet() {
15
+ isOpen = !isOpen;
16
+ }
17
+ </script>
18
+
19
+ <!-- Controls Sheet -->
20
+ <Sheet.Root bind:open={isOpen}>
21
+ <Sheet.Content
22
+ side="right"
23
+ class="w-80 sm:w-96 p-0 bg-gradient-to-b from-slate-700 to-slate-800 text-white border-l border-slate-600"
24
+ >
25
+ <!-- Header -->
26
+ <Sheet.Header class="border-b border-slate-600 bg-slate-700/80 backdrop-blur-sm p-6">
27
+ <div class="flex items-center justify-between">
28
+ <div class="flex items-center gap-3">
29
+ <span class="icon-[mdi--gamepad-variant] size-6 text-blue-400"></span>
30
+ <div>
31
+ <Sheet.Title class="text-xl font-semibold text-slate-100">
32
+ Robot Controls
33
+ </Sheet.Title>
34
+ <p class="text-sm text-slate-400 mt-1">
35
+ Control your robot
36
+ </p>
37
+ </div>
38
+ </div>
39
+ <Badge variant="secondary" class="bg-blue-400 text-slate-900 font-medium px-3">
40
+ Active
41
+ </Badge>
42
+ </div>
43
+ </Sheet.Header>
44
+
45
+ <!-- Content -->
46
+ <div class="flex-1 overflow-y-auto scrollbar-thin scrollbar-track-slate-700 scrollbar-thumb-slate-500 px-4">
47
+ <div class="space-y-4">
48
+ <div class="flex items-center gap-2 text-sm text-slate-300">
49
+ <span class="icon-[mdi--information-outline] size-4"></span>
50
+ Control your robot's movements and actions
51
+ </div>
52
+ <Separator class="bg-slate-600" />
53
+ <ControlPanel />
54
+ </div>
55
+ </div>
56
+
57
+ <!-- Footer -->
58
+ <div class="border-t border-slate-600 bg-slate-800/50 p-4">
59
+ <div class="flex items-center justify-between">
60
+ <div class="flex items-center gap-2 text-xs text-slate-400">
61
+ <span class="icon-[mdi--gamepad-variant] size-4"></span>
62
+ <span>Robot Controls</span>
63
+ </div>
64
+ <Button
65
+ variant="ghost"
66
+ size="sm"
67
+ onclick={toggleSheet}
68
+ class="text-slate-400 hover:text-slate-100 text-xs px-2 py-1 h-auto"
69
+ >
70
+ <span class="icon-[mdi--close] size-3 mr-1"></span>
71
+ Close
72
+ </Button>
73
+ </div>
74
+ </div>
75
+ </Sheet.Content>
76
+ </Sheet.Root>
src/lib/components/Overlay.svelte ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import ButtonBar from '$lib/components/ButtonBar.svelte';
3
+ import ControlsSheet from '$lib/components/ControlsSheet.svelte';
4
+ import SettingsSheet from '$lib/components/SettingsSheet.svelte';
5
+
6
+ interface Props {
7
+ // Optional props for controlling the state externally if needed
8
+ controlsOpen?: boolean;
9
+ settingsOpen?: boolean;
10
+ }
11
+
12
+ let {
13
+ controlsOpen = $bindable(false),
14
+ settingsOpen = $bindable(false)
15
+ }: Props = $props();
16
+
17
+ function toggleControls() {
18
+ controlsOpen = !controlsOpen;
19
+ }
20
+
21
+ function toggleSettings() {
22
+ settingsOpen = !settingsOpen;
23
+ }
24
+ </script>
25
+
26
+ <!-- Button Bar with Add Robot and Controls (Top Left) -->
27
+ <ButtonBar
28
+ bind:controlsOpen
29
+ onToggleControls={toggleControls}
30
+ />
31
+
32
+ <!-- Controls Sheet (Right Side) -->
33
+ <ControlsSheet bind:isOpen={controlsOpen} />
34
+
35
+ <!-- Settings Button and Sheet (Top Right, Left Side) -->
36
+ <SettingsSheet bind:isOpen={settingsOpen} />
src/lib/components/PanelWrapper.svelte ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import * as Card from '$lib/components/ui/card';
3
+ import { Separator } from '$lib/components/ui/separator';
4
+
5
+ interface Props {
6
+ title?: string;
7
+ subtitle?: string;
8
+ children: any;
9
+ }
10
+
11
+ let { title, subtitle, children }: Props = $props();
12
+ </script>
13
+
14
+ <Card.Root class="bg-slate-700/50 border-slate-600">
15
+ {#if title}
16
+ <Card.Header class="pb-3">
17
+ <Card.Title class="text-slate-100 flex items-center gap-2">
18
+ {title}
19
+ </Card.Title>
20
+ {#if subtitle}
21
+ <Card.Description class="text-slate-400 text-sm">
22
+ {subtitle}
23
+ </Card.Description>
24
+ {/if}
25
+ </Card.Header>
26
+ <Separator class="bg-slate-600" />
27
+ {/if}
28
+
29
+ <Card.Content class="p-4 space-y-4">
30
+ {@render children()}
31
+ </Card.Content>
32
+ </Card.Root>
src/lib/components/RobotStatusDemo.svelte ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import StatusCard from '$lib/components/StatusCard.svelte';
3
+ import { Button } from '$lib/components/ui/button';
4
+ import { Badge } from '$lib/components/ui/badge';
5
+ import { environment } from '$lib/runes/env.svelte';
6
+
7
+ // Sample usage of StatusCard component
8
+ const robotStats = $derived(() => {
9
+ const robotCount = Object.keys(environment.robots).length;
10
+ return {
11
+ robotCount,
12
+ status: (robotCount > 0 ? 'active' : 'inactive') as 'active' | 'inactive',
13
+ lastUpdate: new Date().toLocaleTimeString()
14
+ };
15
+ });
16
+ </script>
17
+
18
+ <div class="space-y-4">
19
+ <h3 class="text-lg font-semibold text-slate-100 mb-4">Robot Status Overview</h3>
20
+
21
+ <div class="grid grid-cols-1 gap-3">
22
+ <StatusCard
23
+ title="Connected Robots"
24
+ status={robotStats().status}
25
+ value={robotStats().robotCount}
26
+ description="Active robot instances"
27
+ icon="icon-[mdi--robot]"
28
+ >
29
+ {#if robotStats().robotCount > 0}
30
+ <div class="flex flex-wrap gap-1">
31
+ {#each Object.entries(environment.robots) as [id, robot]}
32
+ <Badge variant="outline" class="text-xs text-slate-300 border-slate-500">
33
+ {id.slice(0, 8)}...
34
+ </Badge>
35
+ {/each}
36
+ </div>
37
+ {:else}
38
+ <p class="text-xs text-slate-500">No robots currently active</p>
39
+ {/if}
40
+ </StatusCard>
41
+
42
+ <StatusCard
43
+ title="System Status"
44
+ status="active"
45
+ description="All systems operational"
46
+ icon="icon-[mdi--check-circle]"
47
+ >
48
+ <div class="text-xs text-slate-400">
49
+ Last updated: {robotStats().lastUpdate}
50
+ </div>
51
+ </StatusCard>
52
+
53
+ <StatusCard
54
+ title="Control Mode"
55
+ status="active"
56
+ value="Manual"
57
+ description="Direct joint control enabled"
58
+ icon="icon-[mdi--gamepad-variant]"
59
+ >
60
+ <Button size="sm" variant="outline" class="w-full text-xs">
61
+ Switch to Autonomous
62
+ </Button>
63
+ </StatusCard>
64
+ </div>
65
+ </div>
src/lib/components/SettingsSheet.svelte ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { Button } from '$lib/components/ui/button';
3
+ import * as Sheet from '$lib/components/ui/sheet';
4
+ import { Badge } from '$lib/components/ui/badge';
5
+ import { Separator } from '$lib/components/ui/separator';
6
+ import SettingsPanel from '$lib/components/panel/SettingsPanel.svelte';
7
+ import { cn } from '$lib/utils';
8
+
9
+ interface Props {
10
+ isOpen?: boolean;
11
+ }
12
+
13
+ let { isOpen = $bindable(false) }: Props = $props();
14
+
15
+ function toggleSheet() {
16
+ isOpen = !isOpen;
17
+ }
18
+ </script>
19
+
20
+ <!-- Settings Trigger Button -->
21
+ <div class="fixed top-4 right-4 z-50">
22
+ <Button
23
+ variant="outline"
24
+ size="sm"
25
+ onclick={toggleSheet}
26
+ class={cn(
27
+ "shadow-lg transition-all duration-200 border-slate-600 backdrop-blur-sm group",
28
+ isOpen
29
+ ? "bg-orange-600/90 text-white hover:bg-orange-500 border-orange-500"
30
+ : "bg-slate-800/90 text-slate-100 hover:bg-slate-700"
31
+ )}
32
+ >
33
+ <span class={cn(
34
+ "size-4 mr-2 transition-transform duration-200",
35
+ isOpen
36
+ ? "icon-[mdi--close] group-hover:rotate-90"
37
+ : "icon-[mdi--cog] group-hover:rotate-45"
38
+ )}></span>
39
+ {isOpen ? 'Close' : 'Settings'}
40
+
41
+ {#if !isOpen}
42
+ <Badge
43
+ variant="secondary"
44
+ class="ml-2 bg-orange-400/20 text-orange-300 border-orange-400/30 text-xs"
45
+ >
46
+ Joints
47
+ </Badge>
48
+ {/if}
49
+ </Button>
50
+ </div>
51
+
52
+ <!-- Settings Sheet -->
53
+ <Sheet.Root bind:open={isOpen}>
54
+ <Sheet.Content
55
+ side="left"
56
+ class="w-80 sm:w-96 p-0 bg-gradient-to-b from-slate-700 to-slate-800 text-white border-r border-slate-600"
57
+ >
58
+ <!-- Header -->
59
+ <Sheet.Header class="border-b border-slate-600 bg-slate-700/80 backdrop-blur-sm p-6">
60
+ <div class="flex items-center justify-between">
61
+ <div class="flex items-center gap-3">
62
+ <span class="icon-[mdi--cog] size-6 text-orange-400"></span>
63
+ <div>
64
+ <Sheet.Title class="text-xl font-semibold text-slate-100">
65
+ Robot Settings
66
+ </Sheet.Title>
67
+ <p class="text-sm text-slate-400 mt-1">
68
+ Configure joints and robot parameters
69
+ </p>
70
+ </div>
71
+ </div>
72
+ <Badge variant="secondary" class="bg-orange-400 text-slate-900 font-medium px-3">
73
+ Config
74
+ </Badge>
75
+ </div>
76
+ </Sheet.Header>
77
+
78
+ <!-- Content -->
79
+ <div class="flex-1 overflow-y-auto scrollbar-thin scrollbar-track-slate-700 scrollbar-thumb-slate-500 p-4">
80
+ <div class="space-y-4">
81
+ <div class="flex items-center gap-2 text-sm text-slate-300">
82
+ <span class="icon-[mdi--wrench] size-4"></span>
83
+ Configure joints and robot parameters
84
+ </div>
85
+ <Separator class="bg-slate-600" />
86
+ <SettingsPanel />
87
+ </div>
88
+ </div>
89
+
90
+ <!-- Footer -->
91
+ <div class="border-t border-slate-600 bg-slate-800/50 p-4">
92
+ <div class="flex items-center justify-between">
93
+ <div class="flex items-center gap-2 text-xs text-slate-400">
94
+ <span class="icon-[mdi--cog] size-4"></span>
95
+ <span>Robot Settings</span>
96
+ </div>
97
+ <Button
98
+ variant="ghost"
99
+ size="sm"
100
+ onclick={toggleSheet}
101
+ class="text-slate-400 hover:text-slate-100 text-xs px-2 py-1 h-auto"
102
+ >
103
+ <span class="icon-[mdi--close] size-3 mr-1"></span>
104
+ Close
105
+ </Button>
106
+ </div>
107
+ </div>
108
+ </Sheet.Content>
109
+ </Sheet.Root>
src/lib/components/StatusCard.svelte ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import * as Card from '$lib/components/ui/card';
3
+ import { Badge } from '$lib/components/ui/badge';
4
+ import { cn } from '$lib/utils';
5
+
6
+ interface Props {
7
+ title: string;
8
+ status?: 'active' | 'inactive' | 'warning' | 'error';
9
+ value?: string | number;
10
+ description?: string;
11
+ icon?: string;
12
+ class?: string;
13
+ children?: any;
14
+ }
15
+
16
+ let {
17
+ title,
18
+ status = 'inactive',
19
+ value,
20
+ description,
21
+ icon,
22
+ class: className = '',
23
+ children
24
+ }: Props = $props();
25
+
26
+ const statusConfig = {
27
+ active: { bg: 'bg-green-400/10', text: 'text-green-400', border: 'border-green-400/30' },
28
+ inactive: { bg: 'bg-slate-400/10', text: 'text-slate-400', border: 'border-slate-400/30' },
29
+ warning: { bg: 'bg-yellow-400/10', text: 'text-yellow-400', border: 'border-yellow-400/30' },
30
+ error: { bg: 'bg-red-400/10', text: 'text-red-400', border: 'border-red-400/30' }
31
+ };
32
+
33
+ const currentStatus = statusConfig[status];
34
+ </script>
35
+
36
+ <Card.Root class={cn("bg-slate-700/40 border-slate-600/50 hover:border-slate-500/70 transition-colors", className)}>
37
+ <Card.Content class="p-4">
38
+ <div class="flex items-start justify-between">
39
+ <div class="flex items-start gap-3">
40
+ {#if icon}
41
+ <div class={cn("p-2 rounded-md", currentStatus.bg)}>
42
+ <span class={cn(icon, "size-4", currentStatus.text)}></span>
43
+ </div>
44
+ {/if}
45
+
46
+ <div class="space-y-1">
47
+ <h4 class="text-sm font-medium text-slate-200 leading-none">
48
+ {title}
49
+ </h4>
50
+ {#if description}
51
+ <p class="text-xs text-slate-400">
52
+ {description}
53
+ </p>
54
+ {/if}
55
+ </div>
56
+ </div>
57
+
58
+ <div class="flex flex-col items-end gap-2">
59
+ <Badge
60
+ variant="outline"
61
+ class={cn("text-xs capitalize", currentStatus.bg, currentStatus.text, currentStatus.border)}
62
+ >
63
+ {status}
64
+ </Badge>
65
+ {#if value !== undefined}
66
+ <span class="text-lg font-mono font-semibold text-slate-100">
67
+ {value}
68
+ </span>
69
+ {/if}
70
+ </div>
71
+ </div>
72
+
73
+ {#if children}
74
+ <div class="mt-3 pt-3 border-t border-slate-600/50">
75
+ {@render children()}
76
+ </div>
77
+ {/if}
78
+ </Card.Content>
79
+ </Card.Root>
src/lib/components/panel/ControlPanel.svelte CHANGED
@@ -9,6 +9,7 @@
9
 
10
  <div class="space-y-6">
11
  <!-- Robot Control Section -->
 
12
  <RobotControlPanel />
13
 
14
  </div>
 
9
 
10
  <div class="space-y-6">
11
  <!-- Robot Control Section -->
12
+ <!-- Note: Robot creation is now handled by the main ButtonBar -->
13
  <RobotControlPanel />
14
 
15
  </div>
src/lib/components/panel/RobotControlPanel.old.svelte ADDED
@@ -0,0 +1,659 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { robotManager } from "$lib/robot/RobotManager.svelte";
3
+ import { robotUrdfConfigMap } from "$lib/configs/robotUrdfConfig";
4
+ import type { Robot } from "$lib/robot/Robot.svelte";
5
+ import { getApiBaseUrl, getWebSocketBaseUrl, getEnvironmentInfo } from "$lib/utils/config";
6
+
7
+ interface Props {}
8
+
9
+ let {}: Props = $props();
10
+
11
+ // Local state
12
+ let selectedRobotType = $state("so-arm100");
13
+ let isCreating = $state(false);
14
+ let error = $state<string | undefined>(undefined);
15
+
16
+ // Robot selection modal state
17
+ let showRobotSelectionModal = $state(false);
18
+ let availableServerRobots = $state<{ id: string; name: string; robot_type: string }[]>([]);
19
+ let selectedServerRobotId = $state<string>("");
20
+ let pendingLocalRobot: Robot | null = $state(null);
21
+
22
+ // Reactive values
23
+ const robots = $derived(robotManager.robots);
24
+ const robotsWithSlaves = $derived(robotManager.robotsWithSlaves.length);
25
+ const robotsWithMaster = $derived(robotManager.robotsWithMaster.length);
26
+
27
+ // Get URLs from configuration
28
+ const apiBaseUrl = getApiBaseUrl();
29
+ const wsBaseUrl = getWebSocketBaseUrl();
30
+
31
+ // Log environment info for debugging
32
+ console.log('Environment info:', getEnvironmentInfo());
33
+
34
+ async function createRobot() {
35
+ if (isCreating) return;
36
+
37
+ console.log('Creating robot...');
38
+ isCreating = true;
39
+ error = undefined;
40
+
41
+ try {
42
+ const urdfConfig = robotUrdfConfigMap[selectedRobotType];
43
+ if (!urdfConfig) {
44
+ throw new Error(`Unknown robot type: ${selectedRobotType}`);
45
+ }
46
+
47
+ const robotId = `robot-${Date.now()}`;
48
+ console.log('Creating robot with ID:', robotId, 'and config:', urdfConfig);
49
+ const robot = await robotManager.createRobot(robotId, urdfConfig);
50
+ console.log('Robot created successfully:', robot);
51
+
52
+ } catch (err) {
53
+ error = `Failed to create robot: ${err}`;
54
+ console.error('Robot creation failed:', err);
55
+ } finally {
56
+ isCreating = false;
57
+ }
58
+ }
59
+
60
+ // Master connection functions
61
+ async function connectDemoSequences(robot: Robot) {
62
+ try {
63
+ await robotManager.connectDemoSequences(robot.id);
64
+ } catch (err) {
65
+ error = `Failed to connect demo sequences: ${err}`;
66
+ console.error(err);
67
+ }
68
+ }
69
+
70
+ async function connectRemoteServerMaster(robot: Robot) {
71
+ try {
72
+ const config: import('$lib/types/robotDriver').MasterDriverConfig = {
73
+ type: "remote-server",
74
+ url: wsBaseUrl,
75
+ apiKey: undefined,
76
+ pollInterval: 100
77
+ };
78
+ await robotManager.connectMaster(robot.id, config);
79
+ } catch (err) {
80
+ error = `Failed to connect remote server: ${err}`;
81
+ console.error(err);
82
+ }
83
+ }
84
+
85
+ async function disconnectMaster(robot: Robot) {
86
+ try {
87
+ await robotManager.disconnectMaster(robot.id);
88
+ } catch (err) {
89
+ error = `Failed to disconnect master: ${err}`;
90
+ console.error(err);
91
+ }
92
+ }
93
+
94
+ // Slave connection functions
95
+ async function connectMockSlave(robot: Robot) {
96
+ try {
97
+ await robotManager.connectMockSlave(robot.id, 50);
98
+ } catch (err) {
99
+ error = `Failed to connect mock slave: ${err}`;
100
+ console.error(err);
101
+ }
102
+ }
103
+
104
+ async function connectUSBSlave(robot: Robot) {
105
+ try {
106
+ await robotManager.connectUSBSlave(robot.id);
107
+ } catch (err) {
108
+ error = `Failed to connect USB slave: ${err}`;
109
+ console.error(err);
110
+ }
111
+ }
112
+
113
+ async function connectRemoteServerSlave(robot: Robot) {
114
+ try {
115
+ // First, fetch available robots from the server
116
+ const response = await fetch(`${apiBaseUrl}/api/robots`);
117
+
118
+ if (!response.ok) {
119
+ throw new Error(`Server responded with ${response.status}: ${response.statusText}`);
120
+ }
121
+
122
+ const robots = await response.json();
123
+
124
+ if (robots.length === 0) {
125
+ error = "No robots available on the server. Create a robot on the server first.";
126
+ return;
127
+ }
128
+
129
+ // Show modal for robot selection
130
+ availableServerRobots = robots;
131
+ pendingLocalRobot = robot;
132
+ selectedServerRobotId = robots[0]?.id || "";
133
+ showRobotSelectionModal = true;
134
+
135
+ } catch (err) {
136
+ error = `Failed to fetch server robots: ${err}`;
137
+ console.error(err);
138
+ }
139
+ }
140
+
141
+ async function confirmRobotSelection() {
142
+ if (!pendingLocalRobot || !selectedServerRobotId) return;
143
+
144
+ try {
145
+ await robotManager.connectRemoteServerSlave(
146
+ pendingLocalRobot.id,
147
+ wsBaseUrl,
148
+ undefined,
149
+ selectedServerRobotId
150
+ );
151
+
152
+ // Close modal
153
+ showRobotSelectionModal = false;
154
+ pendingLocalRobot = null;
155
+
156
+ } catch (err) {
157
+ error = `Failed to connect remote server slave: ${err}`;
158
+ console.error(err);
159
+ }
160
+ }
161
+
162
+ function cancelRobotSelection() {
163
+ showRobotSelectionModal = false;
164
+ pendingLocalRobot = null;
165
+ selectedServerRobotId = "";
166
+ }
167
+
168
+ async function disconnectSlave(robot: Robot, slaveId: string) {
169
+ try {
170
+ await robotManager.disconnectSlave(robot.id, slaveId);
171
+ } catch (err) {
172
+ error = `Failed to disconnect slave: ${err}`;
173
+ console.error(err);
174
+ }
175
+ }
176
+
177
+ // Robot management
178
+ async function calibrateRobot(robot: Robot) {
179
+ try {
180
+ await robot.calibrateRobot();
181
+ } catch (err) {
182
+ error = `Failed to calibrate: ${err}`;
183
+ console.error(err);
184
+ }
185
+ }
186
+
187
+ async function moveToRest(robot: Robot) {
188
+ try {
189
+ await robot.moveToRestPosition();
190
+ } catch (err) {
191
+ error = `Failed to move to rest: ${err}`;
192
+ console.error(err);
193
+ }
194
+ }
195
+
196
+ function clearCalibration(robot: Robot) {
197
+ robot.clearCalibration();
198
+ }
199
+
200
+ async function removeRobot(robot: Robot) {
201
+ try {
202
+ await robotManager.removeRobot(robot.id);
203
+ } catch (err) {
204
+ error = `Failed to remove robot: ${err}`;
205
+ console.error(err);
206
+ }
207
+ }
208
+
209
+ function clearError() {
210
+ error = undefined;
211
+ }
212
+
213
+ // Helper functions
214
+ function getConnectionStatusText(robot: Robot): string {
215
+ const hasActiveMaster = robot.controlState.hasActiveMaster;
216
+ const connectedSlaves = robot.connectedSlaves.length;
217
+ const totalSlaves = robot.slaves.length;
218
+
219
+ if (hasActiveMaster && connectedSlaves > 0) {
220
+ return `Master + ${connectedSlaves}/${totalSlaves} Slaves`;
221
+ } else if (hasActiveMaster) {
222
+ return `Master Only`;
223
+ } else if (connectedSlaves > 0) {
224
+ return `${connectedSlaves}/${totalSlaves} Slaves`;
225
+ } else {
226
+ return "Manual Control";
227
+ }
228
+ }
229
+
230
+ function getConnectionStatusColor(robot: Robot): string {
231
+ const hasActiveMaster = robot.controlState.hasActiveMaster;
232
+ const connectedSlaves = robot.connectedSlaves.length;
233
+
234
+ if (hasActiveMaster && connectedSlaves > 0) {
235
+ return "green"; // Full master-slave setup
236
+ } else if (hasActiveMaster || connectedSlaves > 0) {
237
+ return "yellow"; // Partial connection
238
+ } else {
239
+ return "red"; // No connections
240
+ }
241
+ }
242
+
243
+ async function connectUSBMaster(robot: Robot) {
244
+ try {
245
+ await robotManager.connectUSBMaster(robot.id, {
246
+ pollInterval: 200,
247
+ smoothing: true
248
+ });
249
+ } catch (err) {
250
+ error = `Failed to connect USB master: ${err}`;
251
+ console.error(err);
252
+ }
253
+ }
254
+ </script>
255
+
256
+ <div class="space-y-6 p-4">
257
+ <div class="flex items-center justify-between">
258
+ <h2 class="text-xl font-bold text-slate-100">Robot Control - Master/Slave Architecture</h2>
259
+ <div class="text-sm text-slate-400">
260
+ {robots.length} robots, {robotsWithMaster} with masters, {robotsWithSlaves} with slaves
261
+ </div>
262
+ </div>
263
+
264
+ <!-- Error Display -->
265
+ {#if error}
266
+ <div class="bg-red-500/20 border border-red-500 rounded-lg p-3 flex items-center justify-between">
267
+ <span class="text-red-200 text-sm">{error}</span>
268
+ <button
269
+ onclick={clearError}
270
+ class="text-red-200 hover:text-white"
271
+ >×</button>
272
+ </div>
273
+ {/if}
274
+
275
+ <!-- Create Robot Section (Minimized - main creation now in ButtonBar) -->
276
+ <div class="bg-slate-800 rounded-lg p-3 space-y-3">
277
+ <div class="flex items-center justify-between">
278
+ <h3 class="text-md font-semibold text-slate-100">Advanced Robot Creation</h3>
279
+ <div class="text-xs text-slate-400">Use ButtonBar for quick SO-100 creation</div>
280
+ </div>
281
+
282
+ <div class="flex gap-3">
283
+ <select
284
+ bind:value={selectedRobotType}
285
+ class="flex-1 px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-slate-100 text-sm"
286
+ >
287
+ {#each Object.keys(robotUrdfConfigMap) as robotType}
288
+ <option value={robotType}>{robotType}</option>
289
+ {/each}
290
+ </select>
291
+
292
+ <button
293
+ onclick={createRobot}
294
+ disabled={isCreating}
295
+ class="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-slate-600 disabled:cursor-not-allowed text-white rounded-md transition-colors text-sm"
296
+ >
297
+ {isCreating ? "Creating..." : "Create"}
298
+ </button>
299
+ </div>
300
+ </div>
301
+
302
+ <!-- Robots List -->
303
+ <div class="space-y-3">
304
+ {#each robots as robot (robot.id)}
305
+ <div class="bg-slate-800 rounded-lg p-4">
306
+ <div class="flex items-center justify-between mb-3">
307
+ <div>
308
+ <h4 class="font-semibold text-slate-100">{robot.id}</h4>
309
+ <div class="text-sm text-slate-400">
310
+ Status:
311
+ <span class="text-{getConnectionStatusColor(robot)}-400">
312
+ {getConnectionStatusText(robot)}
313
+ </span>
314
+ {#if robot.controlState.lastCommandSource !== "none"}
315
+ <span class="text-blue-400 ml-2">• Last: {robot.controlState.lastCommandSource}</span>
316
+ {/if}
317
+ {#if robot.isCalibrated}
318
+ <span class="text-green-400 ml-2">• Calibrated</span>
319
+ {/if}
320
+ </div>
321
+ </div>
322
+
323
+ <button
324
+ onclick={() => removeRobot(robot)}
325
+ class="px-3 py-1 bg-red-600 hover:bg-red-700 text-white text-sm rounded transition-colors"
326
+ >
327
+ Remove
328
+ </button>
329
+ </div>
330
+
331
+ <!-- Master Controls -->
332
+ <div class="mb-4 p-3 bg-orange-500/10 border border-orange-500/30 rounded-lg">
333
+ <div class="text-sm text-orange-200 mb-2">
334
+ <strong>Masters (Control Sources)</strong>
335
+ {#if robot.controlState.hasActiveMaster}
336
+ - <span class="text-green-400">Connected: {robot.controlState.masterName}</span>
337
+ {:else}
338
+ - <span class="text-slate-400">None Connected</span>
339
+ {/if}
340
+ </div>
341
+
342
+ <div class="flex flex-wrap gap-2">
343
+ <button
344
+ class="px-3 py-1.5 bg-orange-500 text-white rounded text-sm hover:bg-orange-600 transition-colors disabled:bg-slate-600 disabled:cursor-not-allowed"
345
+ onclick={() => connectDemoSequences(robot)}
346
+ disabled={robot.controlState.hasActiveMaster}
347
+ >
348
+ Demo Sequences
349
+ </button>
350
+
351
+ <button
352
+ class="px-3 py-1.5 bg-purple-500 text-white rounded text-sm hover:bg-purple-600 transition-colors disabled:bg-slate-600 disabled:cursor-not-allowed"
353
+ onclick={() => connectRemoteServerMaster(robot)}
354
+ disabled={robot.controlState.hasActiveMaster}
355
+ >
356
+ Connect Remote Server
357
+ </button>
358
+
359
+ <button
360
+ class="px-3 py-1.5 bg-blue-500 text-white rounded text-sm hover:bg-blue-600 transition-colors disabled:bg-slate-600 disabled:cursor-not-allowed"
361
+ onclick={() => connectUSBMaster(robot)}
362
+ disabled={robot.controlState.hasActiveMaster}
363
+ >
364
+ Connect USB Master
365
+ </button>
366
+
367
+ {#if robot.controlState.hasActiveMaster}
368
+ <button
369
+ class="px-3 py-1.5 bg-red-500 text-white rounded text-sm hover:bg-red-600 transition-colors"
370
+ onclick={() => disconnectMaster(robot)}
371
+ >
372
+ Disconnect Master
373
+ </button>
374
+ {/if}
375
+ </div>
376
+ </div>
377
+
378
+ <!-- Slave Controls -->
379
+ <div class="mb-4 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
380
+ <div class="text-sm text-blue-200 mb-2">
381
+ <strong>Slaves (Execution Targets)</strong>
382
+ - <span class="text-green-400">{robot.connectedSlaves.length} Connected</span>
383
+ / <span class="text-slate-400">{robot.slaves.length} Total</span>
384
+ </div>
385
+
386
+ <div class="flex gap-2 mb-2">
387
+ <button
388
+ onclick={() => connectMockSlave(robot)}
389
+ class="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 text-white text-sm rounded transition-colors"
390
+ >
391
+ Add Mock Slave
392
+ </button>
393
+ <button
394
+ onclick={() => connectUSBSlave(robot)}
395
+ class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors"
396
+ >
397
+ Add USB Slave
398
+ </button>
399
+ <button
400
+ onclick={() => connectRemoteServerSlave(robot)}
401
+ class="px-3 py-1 bg-purple-600 hover:bg-purple-700 text-white text-sm rounded transition-colors"
402
+ >
403
+ Add Remote Server Slave
404
+ </button>
405
+ </div>
406
+
407
+ {#if robot.slaves.length > 0}
408
+ <div class="text-xs text-blue-300">
409
+ <strong>Connected Slaves:</strong>
410
+ {#each robot.slaves as slave}
411
+ <div class="flex items-center justify-between mt-1">
412
+ <span>{slave.name} ({slave.id})</span>
413
+ <button
414
+ onclick={() => disconnectSlave(robot, slave.id)}
415
+ class="px-2 py-1 bg-red-500 hover:bg-red-600 text-white text-xs rounded"
416
+ >
417
+ Remove
418
+ </button>
419
+ </div>
420
+ {/each}
421
+ </div>
422
+ {/if}
423
+ </div>
424
+
425
+ <!-- Calibration Section (for USB slaves) -->
426
+ {#if robot.connectedSlaves.some(slave => slave.name.includes("USB"))}
427
+ <div class="mb-3 p-3 bg-orange-500/10 border border-orange-500/30 rounded-lg">
428
+ <div class="text-sm text-orange-200 mb-2">
429
+ <strong>USB Robot Calibration</strong>
430
+ </div>
431
+ {#if !robot.isCalibrated}
432
+ <div class="text-xs text-orange-300 mb-2">
433
+ 1. Manually position your robot to match the digital twin's rest pose<br>
434
+ 2. Click "Calibrate" when positioned correctly
435
+ </div>
436
+ <div class="flex gap-2">
437
+ <button
438
+ onclick={() => moveToRest(robot)}
439
+ class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition-colors"
440
+ >
441
+ Show Rest Pose
442
+ </button>
443
+ <button
444
+ onclick={() => calibrateRobot(robot)}
445
+ class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors"
446
+ >
447
+ Calibrate
448
+ </button>
449
+ </div>
450
+ {:else}
451
+ <div class="text-xs text-green-300 mb-2">
452
+ ✓ Robot calibrated at {robot.calibrationState.calibrationTime?.toLocaleTimeString()}
453
+ </div>
454
+ <button
455
+ onclick={() => clearCalibration(robot)}
456
+ class="px-3 py-1 bg-gray-600 hover:bg-gray-700 text-white text-sm rounded transition-colors"
457
+ >
458
+ Clear Calibration
459
+ </button>
460
+ {/if}
461
+ </div>
462
+ {/if}
463
+
464
+ <!-- Manual Joint Controls (only when no master) -->
465
+ {#if robot.manualControlEnabled}
466
+ <div class="space-y-3">
467
+ <h5 class="text-sm font-medium text-slate-300">Manual Control ({robot.activeJoints.length} active joints)</h5>
468
+ {#each robot.activeJoints as joint}
469
+ <div class="joint-control">
470
+ <div class="joint-header">
471
+ <span class="joint-name">{joint.name}</span>
472
+ <div class="joint-values">
473
+ <span class="virtual-value">{joint.virtualValue.toFixed(0)}°</span>
474
+ {#if joint.realValue !== undefined}
475
+ <span class="real-value" title="Real robot position">
476
+ Real: {joint.realValue.toFixed(0)}°
477
+ </span>
478
+ {:else}
479
+ <span class="real-value disconnected">N/A</span>
480
+ {/if}
481
+ </div>
482
+ </div>
483
+ <input
484
+ type="range"
485
+ min="-180"
486
+ max="180"
487
+ step="1"
488
+ value={joint.virtualValue}
489
+ oninput={(e) => {
490
+ const target = e.target as HTMLInputElement;
491
+ robot.updateJointValue(joint.name, parseFloat(target.value));
492
+ }}
493
+ class="joint-slider"
494
+ />
495
+ </div>
496
+ {/each}
497
+
498
+ {#if robot.activeJoints.length === 0}
499
+ <div class="text-sm text-slate-500 italic">No active joints</div>
500
+ {/if}
501
+ </div>
502
+ {:else}
503
+ <div class="p-3 bg-purple-500/10 border border-purple-500/30 rounded-lg">
504
+ <div class="text-sm text-purple-200">
505
+ 🎮 <strong>Master Control Active</strong><br>
506
+ <span class="text-xs text-purple-300">
507
+ Robot is controlled by: {robot.controlState.masterName}<br>
508
+ Manual controls are disabled. Disconnect master to regain manual control.
509
+ </span>
510
+ </div>
511
+ </div>
512
+ {/if}
513
+ </div>
514
+ {/each}
515
+
516
+ {#if robots.length === 0}
517
+ <div class="text-center text-slate-500 py-8">
518
+ No robots created yet. Create one above to get started with the master-slave architecture.
519
+ </div>
520
+ {/if}
521
+ </div>
522
+ </div>
523
+
524
+ <!-- Robot Selection Modal -->
525
+ {#if showRobotSelectionModal}
526
+ <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
527
+ <div class="bg-slate-800 rounded-lg p-6 w-96 max-w-full mx-4">
528
+ <h3 class="text-lg font-semibold text-slate-100 mb-4">Select Server Robot</h3>
529
+
530
+ <div class="mb-4">
531
+ <label class="block text-sm font-medium text-slate-300 mb-2">
532
+ Available robots on server:
533
+ </label>
534
+ <select
535
+ bind:value={selectedServerRobotId}
536
+ class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-slate-100"
537
+ >
538
+ {#each availableServerRobots as serverRobot}
539
+ <option value={serverRobot.id}>
540
+ {serverRobot.name} ({serverRobot.id}) - {serverRobot.robot_type}
541
+ </option>
542
+ {/each}
543
+ </select>
544
+ </div>
545
+
546
+ <div class="text-sm text-slate-400 mb-4">
547
+ This will connect your local robot "{pendingLocalRobot?.id}" as a slave to
548
+ receive commands from the selected server robot.
549
+ </div>
550
+
551
+ <div class="flex gap-3 justify-end">
552
+ <button
553
+ onclick={cancelRobotSelection}
554
+ class="px-4 py-2 bg-slate-600 hover:bg-slate-700 text-white rounded-md transition-colors"
555
+ >
556
+ Cancel
557
+ </button>
558
+ <button
559
+ onclick={confirmRobotSelection}
560
+ class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-md transition-colors"
561
+ disabled={!selectedServerRobotId}
562
+ >
563
+ Connect Slave
564
+ </button>
565
+ </div>
566
+ </div>
567
+ </div>
568
+ {/if}
569
+
570
+ <style>
571
+ .joint-control {
572
+ background: rgba(71, 85, 105, 0.3);
573
+ border-radius: 8px;
574
+ padding: 12px;
575
+ border: 1px solid rgba(71, 85, 105, 0.5);
576
+ }
577
+
578
+ .joint-header {
579
+ display: flex;
580
+ justify-content: space-between;
581
+ align-items: center;
582
+ margin-bottom: 8px;
583
+ }
584
+
585
+ .joint-name {
586
+ font-weight: 500;
587
+ color: #e2e8f0;
588
+ font-size: 14px;
589
+ }
590
+
591
+ .joint-values {
592
+ display: flex;
593
+ gap: 12px;
594
+ font-size: 12px;
595
+ }
596
+
597
+ .virtual-value {
598
+ color: #60a5fa;
599
+ font-weight: 500;
600
+ }
601
+
602
+ .real-value {
603
+ color: #34d399;
604
+ font-weight: 500;
605
+ }
606
+
607
+ .real-value.disconnected {
608
+ color: #f87171;
609
+ }
610
+
611
+ .joint-slider {
612
+ width: 100%;
613
+ height: 6px;
614
+ background: #374151;
615
+ border-radius: 3px;
616
+ outline: none;
617
+ cursor: pointer;
618
+ appearance: none;
619
+ }
620
+
621
+ .joint-slider::-webkit-slider-thumb {
622
+ appearance: none;
623
+ width: 18px;
624
+ height: 18px;
625
+ border-radius: 50%;
626
+ background: #3b82f6;
627
+ cursor: pointer;
628
+ border: 2px solid #1e293b;
629
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
630
+ transition: all 0.15s ease;
631
+ }
632
+
633
+ .joint-slider::-webkit-slider-thumb:hover {
634
+ background: #2563eb;
635
+ transform: scale(1.1);
636
+ }
637
+
638
+ .joint-slider::-moz-range-thumb {
639
+ width: 18px;
640
+ height: 18px;
641
+ border-radius: 50%;
642
+ background: #3b82f6;
643
+ cursor: pointer;
644
+ border: 2px solid #1e293b;
645
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
646
+ transition: all 0.15s ease;
647
+ }
648
+
649
+ .joint-slider::-moz-range-track {
650
+ height: 6px;
651
+ background: #374151;
652
+ border-radius: 3px;
653
+ border: none;
654
+ }
655
+
656
+ .joint-slider:focus {
657
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
658
+ }
659
+ </style>
src/lib/components/panel/RobotControlPanel.svelte CHANGED
@@ -1,16 +1,20 @@
1
  <script lang="ts">
2
  import { robotManager } from "$lib/robot/RobotManager.svelte";
3
- import { robotUrdfConfigMap } from "$lib/configs/robotUrdfConfig";
4
  import type { Robot } from "$lib/robot/Robot.svelte";
5
  import { getApiBaseUrl, getWebSocketBaseUrl, getEnvironmentInfo } from "$lib/utils/config";
 
 
 
 
 
 
 
6
 
7
  interface Props {}
8
 
9
  let {}: Props = $props();
10
 
11
  // Local state
12
- let selectedRobotType = $state("so-arm100");
13
- let isCreating = $state(false);
14
  let error = $state<string | undefined>(undefined);
15
 
16
  // Robot selection modal state
@@ -31,36 +35,11 @@
31
  // Log environment info for debugging
32
  console.log('Environment info:', getEnvironmentInfo());
33
 
34
- async function createRobot() {
35
- if (isCreating) return;
36
-
37
- console.log('Creating robot...');
38
- isCreating = true;
39
- error = undefined;
40
-
41
- try {
42
- const urdfConfig = robotUrdfConfigMap[selectedRobotType];
43
- if (!urdfConfig) {
44
- throw new Error(`Unknown robot type: ${selectedRobotType}`);
45
- }
46
-
47
- const robotId = `robot-${Date.now()}`;
48
- console.log('Creating robot with ID:', robotId, 'and config:', urdfConfig);
49
- const robot = await robotManager.createRobot(robotId, urdfConfig);
50
- console.log('Robot created successfully:', robot);
51
-
52
- } catch (err) {
53
- error = `Failed to create robot: ${err}`;
54
- console.error('Robot creation failed:', err);
55
- } finally {
56
- isCreating = false;
57
- }
58
- }
59
-
60
  // Master connection functions
61
  async function connectDemoSequences(robot: Robot) {
62
  try {
63
  await robotManager.connectDemoSequences(robot.id);
 
64
  } catch (err) {
65
  error = `Failed to connect demo sequences: ${err}`;
66
  console.error(err);
@@ -76,15 +55,30 @@
76
  pollInterval: 100
77
  };
78
  await robotManager.connectMaster(robot.id, config);
 
79
  } catch (err) {
80
  error = `Failed to connect remote server: ${err}`;
81
  console.error(err);
82
  }
83
  }
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  async function disconnectMaster(robot: Robot) {
86
  try {
87
  await robotManager.disconnectMaster(robot.id);
 
88
  } catch (err) {
89
  error = `Failed to disconnect master: ${err}`;
90
  console.error(err);
@@ -95,6 +89,7 @@
95
  async function connectMockSlave(robot: Robot) {
96
  try {
97
  await robotManager.connectMockSlave(robot.id, 50);
 
98
  } catch (err) {
99
  error = `Failed to connect mock slave: ${err}`;
100
  console.error(err);
@@ -104,6 +99,7 @@
104
  async function connectUSBSlave(robot: Robot) {
105
  try {
106
  await robotManager.connectUSBSlave(robot.id);
 
107
  } catch (err) {
108
  error = `Failed to connect USB slave: ${err}`;
109
  console.error(err);
@@ -152,6 +148,7 @@
152
  // Close modal
153
  showRobotSelectionModal = false;
154
  pendingLocalRobot = null;
 
155
 
156
  } catch (err) {
157
  error = `Failed to connect remote server slave: ${err}`;
@@ -168,6 +165,7 @@
168
  async function disconnectSlave(robot: Robot, slaveId: string) {
169
  try {
170
  await robotManager.disconnectSlave(robot.id, slaveId);
 
171
  } catch (err) {
172
  error = `Failed to disconnect slave: ${err}`;
173
  console.error(err);
@@ -178,6 +176,7 @@
178
  async function calibrateRobot(robot: Robot) {
179
  try {
180
  await robot.calibrateRobot();
 
181
  } catch (err) {
182
  error = `Failed to calibrate: ${err}`;
183
  console.error(err);
@@ -187,6 +186,7 @@
187
  async function moveToRest(robot: Robot) {
188
  try {
189
  await robot.moveToRestPosition();
 
190
  } catch (err) {
191
  error = `Failed to move to rest: ${err}`;
192
  console.error(err);
@@ -200,6 +200,7 @@
200
  async function removeRobot(robot: Robot) {
201
  try {
202
  await robotManager.removeRobot(robot.id);
 
203
  } catch (err) {
204
  error = `Failed to remove robot: ${err}`;
205
  console.error(err);
@@ -227,395 +228,459 @@
227
  }
228
  }
229
 
230
- function getConnectionStatusColor(robot: Robot): string {
231
  const hasActiveMaster = robot.controlState.hasActiveMaster;
232
  const connectedSlaves = robot.connectedSlaves.length;
233
 
234
  if (hasActiveMaster && connectedSlaves > 0) {
235
- return "green"; // Full master-slave setup
236
  } else if (hasActiveMaster || connectedSlaves > 0) {
237
- return "yellow"; // Partial connection
238
  } else {
239
- return "red"; // No connections
240
- }
241
- }
242
-
243
- async function connectUSBMaster(robot: Robot) {
244
- try {
245
- await robotManager.connectUSBMaster(robot.id, {
246
- pollInterval: 200,
247
- smoothing: true
248
- });
249
- } catch (err) {
250
- error = `Failed to connect USB master: ${err}`;
251
- console.error(err);
252
  }
253
  }
254
  </script>
255
 
256
- <div class="space-y-6 p-4">
257
- <div class="flex items-center justify-between">
258
- <h2 class="text-xl font-bold text-slate-100">Robot Control - Master/Slave Architecture</h2>
259
- <div class="text-sm text-slate-400">
260
- {robots.length} robots, {robotsWithMaster} with masters, {robotsWithSlaves} with slaves
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  </div>
262
  </div>
263
 
264
  <!-- Error Display -->
265
  {#if error}
266
- <div class="bg-red-500/20 border border-red-500 rounded-lg p-3 flex items-center justify-between">
267
- <span class="text-red-200 text-sm">{error}</span>
268
- <button
269
- onclick={clearError}
270
- class="text-red-200 hover:text-white"
271
- >×</button>
272
- </div>
 
 
 
273
  {/if}
274
 
275
- <!-- Create Robot Section -->
276
- <div class="bg-slate-800 rounded-lg p-4 space-y-4">
277
- <h3 class="text-lg font-semibold text-slate-100">Create Robot</h3>
278
-
279
- <div class="flex gap-3">
280
- <select
281
- bind:value={selectedRobotType}
282
- class="flex-1 px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-slate-100"
283
- >
284
- {#each Object.keys(robotUrdfConfigMap) as robotType}
285
- <option value={robotType}>{robotType}</option>
286
- {/each}
287
- </select>
288
-
289
- <button
290
- onclick={createRobot}
291
- disabled={isCreating}
292
- class="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-slate-600 disabled:cursor-not-allowed text-white rounded-md transition-colors"
293
- >
294
- {isCreating ? "Creating..." : "Create Robot"}
295
- </button>
296
- </div>
297
- </div>
298
-
299
  <!-- Robots List -->
300
- <div class="space-y-3">
301
- {#each robots as robot (robot.id)}
302
- <div class="bg-slate-800 rounded-lg p-4">
303
- <div class="flex items-center justify-between mb-3">
304
- <div>
305
- <h4 class="font-semibold text-slate-100">{robot.id}</h4>
306
- <div class="text-sm text-slate-400">
307
- Status:
308
- <span class="text-{getConnectionStatusColor(robot)}-400">
309
- {getConnectionStatusText(robot)}
310
- </span>
311
- {#if robot.controlState.lastCommandSource !== "none"}
312
- <span class="text-blue-400 ml-2">• Last: {robot.controlState.lastCommandSource}</span>
313
- {/if}
314
- {#if robot.isCalibrated}
315
- <span class="text-green-400 ml-2">• Calibrated</span>
316
- {/if}
317
- </div>
318
- </div>
319
-
320
- <button
321
- onclick={() => removeRobot(robot)}
322
- class="px-3 py-1 bg-red-600 hover:bg-red-700 text-white text-sm rounded transition-colors"
323
- >
324
- Remove
325
- </button>
326
  </div>
327
-
328
- <!-- Master Controls -->
329
- <div class="mb-4 p-3 bg-orange-500/10 border border-orange-500/30 rounded-lg">
330
- <div class="text-sm text-orange-200 mb-2">
331
- <strong>Masters (Control Sources)</strong>
332
- {#if robot.controlState.hasActiveMaster}
333
- - <span class="text-green-400">Connected: {robot.controlState.masterName}</span>
334
- {:else}
335
- - <span class="text-slate-400">None Connected</span>
336
- {/if}
337
- </div>
338
-
339
- <div class="flex flex-wrap gap-2">
340
- <button
341
- class="px-3 py-1.5 bg-orange-500 text-white rounded text-sm hover:bg-orange-600 transition-colors disabled:bg-slate-600 disabled:cursor-not-allowed"
342
- onclick={() => connectDemoSequences(robot)}
343
- disabled={robot.controlState.hasActiveMaster}
344
- >
345
- Demo Sequences
346
- </button>
347
-
348
- <button
349
- class="px-3 py-1.5 bg-purple-500 text-white rounded text-sm hover:bg-purple-600 transition-colors disabled:bg-slate-600 disabled:cursor-not-allowed"
350
- onclick={() => connectRemoteServerMaster(robot)}
351
- disabled={robot.controlState.hasActiveMaster}
352
- >
353
- Connect Remote Server
354
- </button>
355
-
356
- <button
357
- class="px-3 py-1.5 bg-blue-500 text-white rounded text-sm hover:bg-blue-600 transition-colors disabled:bg-slate-600 disabled:cursor-not-allowed"
358
- onclick={() => connectUSBMaster(robot)}
359
- disabled={robot.controlState.hasActiveMaster}
360
- >
361
- Connect USB Master
362
- </button>
363
-
364
- {#if robot.controlState.hasActiveMaster}
365
- <button
366
- class="px-3 py-1.5 bg-red-500 text-white rounded text-sm hover:bg-red-600 transition-colors"
367
- onclick={() => disconnectMaster(robot)}
368
- >
369
- Disconnect Master
370
- </button>
371
- {/if}
372
- </div>
373
- </div>
374
-
375
- <!-- Slave Controls -->
376
- <div class="mb-4 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
377
- <div class="text-sm text-blue-200 mb-2">
378
- <strong>Slaves (Execution Targets)</strong>
379
- - <span class="text-green-400">{robot.connectedSlaves.length} Connected</span>
380
- / <span class="text-slate-400">{robot.slaves.length} Total</span>
381
- </div>
382
-
383
- <div class="flex gap-2 mb-2">
384
- <button
385
- onclick={() => connectMockSlave(robot)}
386
- class="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 text-white text-sm rounded transition-colors"
387
- >
388
- Add Mock Slave
389
- </button>
390
- <button
391
- onclick={() => connectUSBSlave(robot)}
392
- class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors"
393
- >
394
- Add USB Slave
395
- </button>
396
- <button
397
- onclick={() => connectRemoteServerSlave(robot)}
398
- class="px-3 py-1 bg-purple-600 hover:bg-purple-700 text-white text-sm rounded transition-colors"
399
- >
400
- Add Remote Server Slave
401
- </button>
402
- </div>
403
-
404
- {#if robot.slaves.length > 0}
405
- <div class="text-xs text-blue-300">
406
- <strong>Connected Slaves:</strong>
407
- {#each robot.slaves as slave}
408
- <div class="flex items-center justify-between mt-1">
409
- <span>{slave.name} ({slave.id})</span>
410
- <button
411
- onclick={() => disconnectSlave(robot, slave.id)}
412
- class="px-2 py-1 bg-red-500 hover:bg-red-600 text-white text-xs rounded"
413
- >
414
- Remove
415
- </button>
416
  </div>
417
- {/each}
418
- </div>
419
- {/if}
420
- </div>
421
-
422
- <!-- Calibration Section (for USB slaves) -->
423
- {#if robot.connectedSlaves.some(slave => slave.name.includes("USB"))}
424
- <div class="mb-3 p-3 bg-orange-500/10 border border-orange-500/30 rounded-lg">
425
- <div class="text-sm text-orange-200 mb-2">
426
- <strong>USB Robot Calibration</strong>
427
- </div>
428
- {#if !robot.isCalibrated}
429
- <div class="text-xs text-orange-300 mb-2">
430
- 1. Manually position your robot to match the digital twin's rest pose<br>
431
- 2. Click "Calibrate" when positioned correctly
432
  </div>
433
- <div class="flex gap-2">
434
- <button
435
- onclick={() => moveToRest(robot)}
436
- class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition-colors"
437
- >
438
- Show Rest Pose
439
- </button>
440
- <button
441
- onclick={() => calibrateRobot(robot)}
442
- class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors"
443
- >
444
- Calibrate
445
- </button>
446
- </div>
447
- {:else}
448
- <div class="text-xs text-green-300 mb-2">
449
- ✓ Robot calibrated at {robot.calibrationState.calibrationTime?.toLocaleTimeString()}
450
- </div>
451
- <button
452
- onclick={() => clearCalibration(robot)}
453
- class="px-3 py-1 bg-gray-600 hover:bg-gray-700 text-white text-sm rounded transition-colors"
454
- >
455
- Clear Calibration
456
- </button>
457
- {/if}
458
- </div>
459
- {/if}
460
-
461
- <!-- Manual Joint Controls (only when no master) -->
462
- {#if robot.manualControlEnabled}
463
- <div class="space-y-3">
464
- <h5 class="text-sm font-medium text-slate-300">Manual Control ({robot.activeJoints.length} active joints)</h5>
465
- {#each robot.activeJoints as joint}
466
- <div class="joint-control">
467
- <div class="joint-header">
468
- <span class="joint-name">{joint.name}</span>
469
- <div class="joint-values">
470
- <span class="virtual-value">{joint.virtualValue.toFixed(0)}°</span>
471
- {#if joint.realValue !== undefined}
472
- <span class="real-value" title="Real robot position">
473
- Real: {joint.realValue.toFixed(0)}°
474
- </span>
475
  {:else}
476
- <span class="real-value disconnected">N/A</span>
 
 
477
  {/if}
478
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
  </div>
480
- <input
481
- type="range"
482
- min="-180"
483
- max="180"
484
- step="1"
485
- value={joint.virtualValue}
486
- oninput={(e) => {
487
- const target = e.target as HTMLInputElement;
488
- robot.updateJointValue(joint.name, parseFloat(target.value));
489
- }}
490
- class="joint-slider"
491
- />
492
- </div>
493
- {/each}
494
-
495
- {#if robot.activeJoints.length === 0}
496
- <div class="text-sm text-slate-500 italic">No active joints</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
  {/if}
498
- </div>
499
- {:else}
500
- <div class="p-3 bg-purple-500/10 border border-purple-500/30 rounded-lg">
501
- <div class="text-sm text-purple-200">
502
- 🎮 <strong>Master Control Active</strong><br>
503
- <span class="text-xs text-purple-300">
504
- Robot is controlled by: {robot.controlState.masterName}<br>
505
- Manual controls are disabled. Disconnect master to regain manual control.
506
- </span>
507
- </div>
508
- </div>
509
- {/if}
510
- </div>
511
- {/each}
512
-
513
- {#if robots.length === 0}
514
- <div class="text-center text-slate-500 py-8">
515
- No robots created yet. Create one above to get started with the master-slave architecture.
516
- </div>
517
- {/if}
518
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
  </div>
520
 
521
  <!-- Robot Selection Modal -->
522
  {#if showRobotSelectionModal}
523
- <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
524
- <div class="bg-slate-800 rounded-lg p-6 w-96 max-w-full mx-4">
525
- <h3 class="text-lg font-semibold text-slate-100 mb-4">Select Server Robot</h3>
526
-
527
- <div class="mb-4">
528
- <label class="block text-sm font-medium text-slate-300 mb-2">
529
- Available robots on server:
530
- </label>
531
- <select
532
- bind:value={selectedServerRobotId}
533
- class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-slate-100"
534
- >
535
- {#each availableServerRobots as serverRobot}
536
- <option value={serverRobot.id}>
537
- {serverRobot.name} ({serverRobot.id}) - {serverRobot.robot_type}
538
- </option>
539
- {/each}
540
- </select>
541
- </div>
542
-
543
- <div class="text-sm text-slate-400 mb-4">
544
- This will connect your local robot "{pendingLocalRobot?.id}" as a slave to
545
- receive commands from the selected server robot.
546
- </div>
547
-
548
- <div class="flex gap-3 justify-end">
549
- <button
550
- onclick={cancelRobotSelection}
551
- class="px-4 py-2 bg-slate-600 hover:bg-slate-700 text-white rounded-md transition-colors"
552
- >
 
 
 
 
 
 
553
  Cancel
554
- </button>
555
- <button
556
  onclick={confirmRobotSelection}
557
- class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-md transition-colors"
558
  disabled={!selectedServerRobotId}
 
559
  >
 
560
  Connect Slave
561
- </button>
562
- </div>
563
- </div>
564
  </div>
565
  {/if}
566
 
567
  <style>
568
- .joint-control {
569
- background: rgba(71, 85, 105, 0.3);
570
- border-radius: 8px;
571
- padding: 12px;
572
- border: 1px solid rgba(71, 85, 105, 0.5);
573
- }
574
-
575
- .joint-header {
576
- display: flex;
577
- justify-content: space-between;
578
- align-items: center;
579
- margin-bottom: 8px;
580
- }
581
-
582
- .joint-name {
583
- font-weight: 500;
584
- color: #e2e8f0;
585
- font-size: 14px;
586
- }
587
-
588
- .joint-values {
589
- display: flex;
590
- gap: 12px;
591
- font-size: 12px;
592
- }
593
-
594
- .virtual-value {
595
- color: #60a5fa;
596
- font-weight: 500;
597
- }
598
-
599
- .real-value {
600
- color: #34d399;
601
- font-weight: 500;
602
- }
603
-
604
- .real-value.disconnected {
605
- color: #f87171;
606
- }
607
-
608
- .joint-slider {
609
- width: 100%;
610
- height: 6px;
611
- background: #374151;
612
- border-radius: 3px;
613
- outline: none;
614
- cursor: pointer;
615
- appearance: none;
616
- }
617
-
618
- .joint-slider::-webkit-slider-thumb {
619
  appearance: none;
620
  width: 18px;
621
  height: 18px;
@@ -627,12 +692,12 @@
627
  transition: all 0.15s ease;
628
  }
629
 
630
- .joint-slider::-webkit-slider-thumb:hover {
631
  background: #2563eb;
632
  transform: scale(1.1);
633
  }
634
 
635
- .joint-slider::-moz-range-thumb {
636
  width: 18px;
637
  height: 18px;
638
  border-radius: 50%;
@@ -643,14 +708,14 @@
643
  transition: all 0.15s ease;
644
  }
645
 
646
- .joint-slider::-moz-range-track {
647
  height: 6px;
648
  background: #374151;
649
  border-radius: 3px;
650
  border: none;
651
  }
652
 
653
- .joint-slider:focus {
654
  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
655
  }
656
  </style>
 
1
  <script lang="ts">
2
  import { robotManager } from "$lib/robot/RobotManager.svelte";
 
3
  import type { Robot } from "$lib/robot/Robot.svelte";
4
  import { getApiBaseUrl, getWebSocketBaseUrl, getEnvironmentInfo } from "$lib/utils/config";
5
+ import * as Card from '$lib/components/ui/card';
6
+ import { Button } from '$lib/components/ui/button';
7
+ import { Badge } from '$lib/components/ui/badge';
8
+ import { Separator } from '$lib/components/ui/separator';
9
+ import * as Alert from '$lib/components/ui/alert';
10
+ import { Progress } from '$lib/components/ui/progress';
11
+ import { cn } from '$lib/utils';
12
 
13
  interface Props {}
14
 
15
  let {}: Props = $props();
16
 
17
  // Local state
 
 
18
  let error = $state<string | undefined>(undefined);
19
 
20
  // Robot selection modal state
 
35
  // Log environment info for debugging
36
  console.log('Environment info:', getEnvironmentInfo());
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  // Master connection functions
39
  async function connectDemoSequences(robot: Robot) {
40
  try {
41
  await robotManager.connectDemoSequences(robot.id);
42
+ clearError();
43
  } catch (err) {
44
  error = `Failed to connect demo sequences: ${err}`;
45
  console.error(err);
 
55
  pollInterval: 100
56
  };
57
  await robotManager.connectMaster(robot.id, config);
58
+ clearError();
59
  } catch (err) {
60
  error = `Failed to connect remote server: ${err}`;
61
  console.error(err);
62
  }
63
  }
64
 
65
+ async function connectUSBMaster(robot: Robot) {
66
+ try {
67
+ await robotManager.connectUSBMaster(robot.id, {
68
+ pollInterval: 200,
69
+ smoothing: true
70
+ });
71
+ clearError();
72
+ } catch (err) {
73
+ error = `Failed to connect USB master: ${err}`;
74
+ console.error(err);
75
+ }
76
+ }
77
+
78
  async function disconnectMaster(robot: Robot) {
79
  try {
80
  await robotManager.disconnectMaster(robot.id);
81
+ clearError();
82
  } catch (err) {
83
  error = `Failed to disconnect master: ${err}`;
84
  console.error(err);
 
89
  async function connectMockSlave(robot: Robot) {
90
  try {
91
  await robotManager.connectMockSlave(robot.id, 50);
92
+ clearError();
93
  } catch (err) {
94
  error = `Failed to connect mock slave: ${err}`;
95
  console.error(err);
 
99
  async function connectUSBSlave(robot: Robot) {
100
  try {
101
  await robotManager.connectUSBSlave(robot.id);
102
+ clearError();
103
  } catch (err) {
104
  error = `Failed to connect USB slave: ${err}`;
105
  console.error(err);
 
148
  // Close modal
149
  showRobotSelectionModal = false;
150
  pendingLocalRobot = null;
151
+ clearError();
152
 
153
  } catch (err) {
154
  error = `Failed to connect remote server slave: ${err}`;
 
165
  async function disconnectSlave(robot: Robot, slaveId: string) {
166
  try {
167
  await robotManager.disconnectSlave(robot.id, slaveId);
168
+ clearError();
169
  } catch (err) {
170
  error = `Failed to disconnect slave: ${err}`;
171
  console.error(err);
 
176
  async function calibrateRobot(robot: Robot) {
177
  try {
178
  await robot.calibrateRobot();
179
+ clearError();
180
  } catch (err) {
181
  error = `Failed to calibrate: ${err}`;
182
  console.error(err);
 
186
  async function moveToRest(robot: Robot) {
187
  try {
188
  await robot.moveToRestPosition();
189
+ clearError();
190
  } catch (err) {
191
  error = `Failed to move to rest: ${err}`;
192
  console.error(err);
 
200
  async function removeRobot(robot: Robot) {
201
  try {
202
  await robotManager.removeRobot(robot.id);
203
+ clearError();
204
  } catch (err) {
205
  error = `Failed to remove robot: ${err}`;
206
  console.error(err);
 
228
  }
229
  }
230
 
231
+ function getConnectionStatusVariant(robot: Robot): "default" | "secondary" | "destructive" | "outline" {
232
  const hasActiveMaster = robot.controlState.hasActiveMaster;
233
  const connectedSlaves = robot.connectedSlaves.length;
234
 
235
  if (hasActiveMaster && connectedSlaves > 0) {
236
+ return "default"; // Full master-slave setup (green)
237
  } else if (hasActiveMaster || connectedSlaves > 0) {
238
+ return "secondary"; // Partial connection (blue)
239
  } else {
240
+ return "outline"; // No connections (gray)
 
 
 
 
 
 
 
 
 
 
 
 
241
  }
242
  }
243
  </script>
244
 
245
+ <div class="space-y-6">
246
+ <!-- Header -->
247
+ <div class="flex flex-col gap-2 items-center justify-between">
248
+ <div>
249
+ <h2 class="text-2xl font-bold text-slate-100">Robot Control Center</h2>
250
+ <p class="text-sm text-slate-400 mt-1">Master/Slave Architecture</p>
251
+ </div>
252
+ <div class="flex items-center gap-3">
253
+ <Badge variant="outline" class="text-slate-300">
254
+ <span class="icon-[mdi--robot] size-4 mr-1"></span>
255
+ {robots.length} Robots
256
+ </Badge>
257
+ <Badge variant="secondary" class="text-slate-300">
258
+ <span class="icon-[mdi--connection] size-4 mr-1"></span>
259
+ {robotsWithMaster} Masters
260
+ </Badge>
261
+ <Badge variant="secondary" class="text-slate-300">
262
+ <span class="icon-[mdi--devices] size-4 mr-1"></span>
263
+ {robotsWithSlaves} Slaves
264
+ </Badge>
265
  </div>
266
  </div>
267
 
268
  <!-- Error Display -->
269
  {#if error}
270
+ <Alert.Root variant="destructive">
271
+ <span class="icon-[mdi--alert-circle] size-4"></span>
272
+ <Alert.Title>Error</Alert.Title>
273
+ <Alert.Description class="flex items-center justify-between">
274
+ <span>{error}</span>
275
+ <Button variant="ghost" size="sm" onclick={clearError} class="text-red-200 hover:text-white h-auto p-1">
276
+ <span class="icon-[mdi--close] size-4"></span>
277
+ </Button>
278
+ </Alert.Description>
279
+ </Alert.Root>
280
  {/if}
281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  <!-- Robots List -->
283
+ {#if robots.length === 0}
284
+ <Card.Root class="bg-slate-800/50 border-slate-600">
285
+ <Card.Content class="p-8 text-center">
286
+ <div class="flex flex-col items-center gap-3">
287
+ <span class="icon-[mdi--robot-confused] size-12 text-slate-500"></span>
288
+ <h3 class="text-lg font-medium text-slate-300">No Robots Available</h3>
289
+ <p class="text-sm text-slate-500 max-w-md">
290
+ Create a robot using the "Add Robot" button in the top-left to get started with the master-slave architecture.
291
+ </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  </div>
293
+ </Card.Content>
294
+ </Card.Root>
295
+ {:else}
296
+ <div class="space-y-4">
297
+ {#each robots as robot (robot.id)}
298
+ <Card.Root class="bg-slate-800/60 border-slate-600 hover:border-slate-500 transition-all">
299
+ <Card.Header>
300
+ <div class="flex items-center justify-between">
301
+ <div class="flex items-center gap-3">
302
+ <span class="icon-[mdi--robot] size-6 text-blue-400"></span>
303
+ <div>
304
+ <Card.Title class="text-slate-100 text-lg">{robot.id}</Card.Title>
305
+ <div class="flex items-center gap-2 mt-1">
306
+ <Badge variant={getConnectionStatusVariant(robot)} class="text-xs text-slate-300">
307
+ {getConnectionStatusText(robot)}
308
+ </Badge>
309
+ {#if robot.controlState.lastCommandSource !== "none"}
310
+ <Badge variant="outline" class="text-blue-300 text-xs">
311
+ Last: {robot.controlState.lastCommandSource}
312
+ </Badge>
313
+ {/if}
314
+ {#if robot.isCalibrated}
315
+ <Badge variant="default" class="bg-green-600 text-xs">
316
+ <span class="icon-[mdi--check-circle] size-3 mr-1"></span>
317
+ Calibrated
318
+ </Badge>
319
+ {/if}
320
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  </div>
323
+
324
+ </div>
325
+ <Button variant="destructive" size="sm" onclick={() => removeRobot(robot)}>
326
+ <span class="icon-[mdi--delete] size-4 mr-1"></span>
327
+ Remove
328
+ </Button>
329
+ </Card.Header>
330
+
331
+ <Card.Content class="space-y-4">
332
+ <!-- Master Controls -->
333
+ <Card.Root class="bg-orange-500/10 border-orange-500/30">
334
+ <Card.Header>
335
+ <Card.Title class="text-orange-200 text-sm flex flex-col gap-2 text-pretty">
336
+ <div class="flex items-center gap-2 text-lg">
337
+ <span class="icon-[mdi--account-supervisor] size-6"></span>
338
+ Masters (Sources)
339
+ </div>
340
+ <div class="flex items-center gap-2">
341
+ {#if robot.controlState.hasActiveMaster}
342
+ <Badge variant="default" class="bg-green-600 text-xs ml-auto">
343
+ Connected: {robot.controlState.masterName}
344
+ </Badge>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  {:else}
346
+ <Badge variant="outline" class="text-slate-400 text-xs ml-auto">
347
+ None Connected
348
+ </Badge>
349
  {/if}
350
  </div>
351
+ </Card.Title>
352
+ </Card.Header>
353
+ <Card.Content class="pt-2">
354
+ <div class="flex flex-col gap-2">
355
+ <Button
356
+ variant="secondary"
357
+ size="sm"
358
+ onclick={() => connectDemoSequences(robot)}
359
+ disabled={robot.controlState.hasActiveMaster}
360
+ class="bg-orange-600 hover:bg-orange-700 text-white disabled:bg-slate-600"
361
+ >
362
+ <span class="icon-[mdi--play-circle] size-4 mr-1"></span>
363
+ Demo Sequences
364
+ </Button>
365
+
366
+ <Button
367
+ variant="secondary"
368
+ size="sm"
369
+ onclick={() => connectRemoteServerMaster(robot)}
370
+ disabled={robot.controlState.hasActiveMaster}
371
+ class="bg-purple-600 hover:bg-purple-700 text-white disabled:bg-slate-600"
372
+ >
373
+ <span class="icon-[mdi--cloud] size-4 mr-1"></span>
374
+ Remote Server
375
+ </Button>
376
+
377
+ <Button
378
+ variant="secondary"
379
+ size="sm"
380
+ onclick={() => connectUSBMaster(robot)}
381
+ disabled={robot.controlState.hasActiveMaster}
382
+ class="bg-blue-600 hover:bg-blue-700 text-white disabled:bg-slate-600"
383
+ >
384
+ <span class="icon-[mdi--usb] size-4 mr-1"></span>
385
+ USB Master
386
+ </Button>
387
+
388
+ {#if robot.controlState.hasActiveMaster}
389
+ <Button
390
+ variant="destructive"
391
+ size="sm"
392
+ onclick={() => disconnectMaster(robot)}
393
+ >
394
+ <span class="icon-[mdi--close-circle] size-4 mr-1"></span>
395
+ Disconnect Master
396
+ </Button>
397
+ {/if}
398
  </div>
399
+ </Card.Content>
400
+ </Card.Root>
401
+
402
+ <!-- Slave Controls -->
403
+ <Card.Root class="bg-blue-500/10 border-blue-500/30">
404
+ <Card.Header>
405
+ <Card.Title class="text-blue-200 text-sm flex flex-col gap-2 text-pretty">
406
+ <div class="flex items-center gap-2 text-lg">
407
+ <span class="icon-[mdi--devices] size-6"></span>
408
+ Slaves (Targets)
409
+ </div>
410
+ <div class="flex items-center gap-2">
411
+ <Badge variant="default" class="bg-green-600 text-xs ml-auto">
412
+ {robot.connectedSlaves.length} Connected
413
+ </Badge>
414
+ <Badge variant="outline" class="text-slate-400 text-xs">
415
+ / {robot.slaves.length} Total
416
+ </Badge>
417
+ </div>
418
+ </Card.Title>
419
+ </Card.Header>
420
+ <Card.Content class="pt-2 space-y-3">
421
+ <div class="flex flex-col gap-2">
422
+ <Button
423
+ variant="secondary"
424
+ size="sm"
425
+ onclick={() => connectMockSlave(robot)}
426
+ class="bg-yellow-600 hover:bg-yellow-700 text-white"
427
+ >
428
+ <span class="icon-[mdi--robot-confused] size-4 mr-1"></span>
429
+ Add Mock Slave
430
+ </Button>
431
+ <Button
432
+ variant="secondary"
433
+ size="sm"
434
+ onclick={() => connectUSBSlave(robot)}
435
+ class="bg-green-600 hover:bg-green-700 text-white"
436
+ >
437
+ <span class="icon-[mdi--usb] size-4 mr-1"></span>
438
+ Add USB Slave
439
+ </Button>
440
+ <Button
441
+ variant="secondary"
442
+ size="sm"
443
+ onclick={() => connectRemoteServerSlave(robot)}
444
+ class="bg-purple-600 hover:bg-purple-700 text-white"
445
+ >
446
+ <span class="icon-[mdi--cloud] size-4 mr-1"></span>
447
+ Add Remote Server Slave
448
+ </Button>
449
+ </div>
450
+
451
+ {#if robot.slaves.length > 0}
452
+ <div class="space-y-2">
453
+ <p class="text-xs text-blue-300 font-medium">Connected Slaves:</p>
454
+ <div class="space-y-1">
455
+ {#each robot.slaves as slave}
456
+ <div class="flex items-center justify-between p-2 bg-slate-700/50 rounded-md">
457
+ <div class="flex items-center gap-2">
458
+ <span class="icon-[mdi--circle] size-2 text-green-400"></span>
459
+ <span class="text-sm text-slate-300">{slave.name}</span>
460
+ <Badge variant="outline" class="text-xs">{slave.id}</Badge>
461
+ </div>
462
+ <Button
463
+ variant="destructive"
464
+ size="sm"
465
+ onclick={() => disconnectSlave(robot, slave.id)}
466
+ class="h-6 px-2 text-xs"
467
+ >
468
+ <span class="icon-[mdi--close] size-3"></span>
469
+ </Button>
470
+ </div>
471
+ {/each}
472
+ </div>
473
+ </div>
474
+ {/if}
475
+ </Card.Content>
476
+ </Card.Root>
477
+
478
+ <!-- Calibration Section (for USB slaves) -->
479
+ {#if robot.connectedSlaves.some(slave => slave.name.includes("USB"))}
480
+ <Card.Root class="bg-emerald-500/10 border-emerald-500/30">
481
+ <Card.Header>
482
+ <Card.Title class="text-emerald-200 text-sm flex items-center gap-2">
483
+ <span class="icon-[mdi--crosshairs-gps] size-4"></span>
484
+ USB Robot Calibration
485
+ {#if robot.isCalibrated}
486
+ <Badge variant="default" class="bg-green-600 text-xs ml-auto">
487
+ <span class="icon-[mdi--check] size-3 mr-1"></span>
488
+ Calibrated
489
+ </Badge>
490
+ {:else}
491
+ <Badge variant="destructive" class="text-xs ml-auto">
492
+ <span class="icon-[mdi--alert] size-3 mr-1"></span>
493
+ Not Calibrated
494
+ </Badge>
495
+ {/if}
496
+ </Card.Title>
497
+ </Card.Header>
498
+ <Card.Content class="pt-2">
499
+ {#if !robot.isCalibrated}
500
+ <div class="space-y-3">
501
+ <Alert.Root>
502
+ <span class="icon-[mdi--information] size-4"></span>
503
+ <Alert.Title>Calibration Steps</Alert.Title>
504
+ <Alert.Description>
505
+ <ol class="text-xs space-y-1 mt-2">
506
+ <li>1. Manually position your robot to match the digital twin's rest pose</li>
507
+ <li>2. Click "Calibrate" when positioned correctly</li>
508
+ </ol>
509
+ </Alert.Description>
510
+ </Alert.Root>
511
+ <div class="flex gap-2">
512
+ <Button
513
+ variant="outline"
514
+ size="sm"
515
+ onclick={() => moveToRest(robot)}
516
+ >
517
+ <span class="icon-[mdi--human-handsup] size-4 mr-1"></span>
518
+ Show Rest Pose
519
+ </Button>
520
+ <Button
521
+ variant="default"
522
+ size="sm"
523
+ onclick={() => calibrateRobot(robot)}
524
+ class="bg-green-600 hover:bg-green-700"
525
+ >
526
+ <span class="icon-[mdi--crosshairs-gps] size-4 mr-1"></span>
527
+ Calibrate
528
+ </Button>
529
+ </div>
530
+ </div>
531
+ {:else}
532
+ <div class="space-y-3">
533
+ <div class="flex items-center gap-2 text-sm text-green-300">
534
+ <span class="icon-[mdi--check-circle] size-4"></span>
535
+ Robot calibrated at {robot.calibrationState.calibrationTime?.toLocaleTimeString()}
536
+ </div>
537
+ <Button
538
+ variant="outline"
539
+ size="sm"
540
+ onclick={() => clearCalibration(robot)}
541
+ >
542
+ <span class="icon-[mdi--refresh] size-4 mr-1"></span>
543
+ Clear Calibration
544
+ </Button>
545
+ </div>
546
+ {/if}
547
+ </Card.Content>
548
+ </Card.Root>
549
  {/if}
550
+
551
+ <!-- Manual Joint Controls or Master Control Status -->
552
+ {#if robot.manualControlEnabled}
553
+ <Card.Root class="bg-slate-700/30 border-slate-600">
554
+ <Card.Header>
555
+ <Card.Title class="text-slate-200 text-sm flex flex-col gap-2 text-pretty">
556
+ <div class="flex items-center gap-2 text-lg">
557
+ <span class="icon-[mdi--tune] size-6"></span>
558
+ Manual Control
559
+ </div>
560
+ <div class="flex items-center gap-2">
561
+ <Badge variant="default" class="bg-green-600 text-xs ml-auto">
562
+ {robot.activeJoints.length} Active Joints
563
+ </Badge>
564
+ </div>
565
+ </Card.Title>
566
+ </Card.Header>
567
+ <Card.Content class="pt-2">
568
+ {#if robot.activeJoints.length === 0}
569
+ <p class="text-sm text-slate-500 italic text-center py-4">No active joints</p>
570
+ {:else}
571
+ <div class="space-y-4">
572
+ {#each robot.activeJoints as joint}
573
+ {@const lower = (joint.urdfJoint.limit?.lower != undefined) ? (joint.urdfJoint.limit.lower * 180) / Math.PI : -180}
574
+ {@const upper = (joint.urdfJoint.limit?.upper != undefined) ? (joint.urdfJoint.limit.upper * 180) / Math.PI : 180}
575
+ <div class="space-y-2 p-3 bg-slate-800/50 rounded-lg border border-slate-600">
576
+ <span class="text-sm font-medium text-slate-300">{joint.name}</span>
577
+ <div class="space-y-2">
578
+ <input
579
+ type="range"
580
+ min={lower}
581
+ max={upper}
582
+ step="1"
583
+ value={joint.virtualValue}
584
+ oninput={(e) => {
585
+ const target = e.target as HTMLInputElement;
586
+ robot.updateJointValue(joint.name, parseFloat(target.value));
587
+ }}
588
+ class="w-full h-2 bg-slate-600 rounded-lg appearance-none cursor-pointer slider"
589
+ />
590
+ <div class="flex justify-between text-xs text-slate-500">
591
+ <span>-180°</span>
592
+ <span>180°</span>
593
+ </div>
594
+ </div>
595
+ <div class="flex justify-between items-center">
596
+
597
+ <div class="flex items-center gap-3 text-xs">
598
+ <span class="text-blue-400 font-mono">Virtual: {joint.virtualValue.toFixed(0)}°</span>
599
+ {#if joint.realValue !== undefined}
600
+ <span class="text-green-400 font-mono">Real: {joint.realValue.toFixed(0)}°</span>
601
+ {:else}
602
+ <span class="text-red-400 font-mono">Real: N/A</span>
603
+ {/if}
604
+ </div>
605
+ </div>
606
+ </div>
607
+ {/each}
608
+ </div>
609
+ {/if}
610
+ </Card.Content>
611
+ </Card.Root>
612
+ {:else}
613
+ <Alert.Root class="bg-purple-500/10 border-purple-500/30">
614
+ <span class="icon-[mdi--gamepad-variant] size-4"></span>
615
+ <Alert.Title class="text-purple-200">Master Control Active</Alert.Title>
616
+ <Alert.Description class="text-purple-300">
617
+ Robot is controlled by: <strong>{robot.controlState.masterName}</strong><br>
618
+ Manual controls are disabled. Disconnect master to regain manual control.
619
+ </Alert.Description>
620
+ </Alert.Root>
621
+ {/if}
622
+ </Card.Content>
623
+ </Card.Root>
624
+ {/each}
625
+ </div>
626
+ {/if}
627
  </div>
628
 
629
  <!-- Robot Selection Modal -->
630
  {#if showRobotSelectionModal}
631
+ <div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 backdrop-blur-sm">
632
+ <Card.Root class="w-96 max-w-full mx-4 bg-slate-800 border-slate-600">
633
+ <Card.Header>
634
+ <Card.Title class="text-slate-100 flex items-center gap-2">
635
+ <span class="icon-[mdi--robot] size-5"></span>
636
+ Select Server Robot
637
+ </Card.Title>
638
+ <Card.Description class="text-slate-400">
639
+ Choose which server robot to connect as a slave target
640
+ </Card.Description>
641
+ </Card.Header>
642
+ <Card.Content class="space-y-4">
643
+ <div class="space-y-2">
644
+ <label class="text-sm font-medium text-slate-300">Available robots on server:</label>
645
+ <select
646
+ bind:value={selectedServerRobotId}
647
+ class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-slate-100 text-sm"
648
+ >
649
+ {#each availableServerRobots as serverRobot}
650
+ <option value={serverRobot.id}>
651
+ {serverRobot.name} ({serverRobot.id}) - {serverRobot.robot_type}
652
+ </option>
653
+ {/each}
654
+ </select>
655
+ </div>
656
+
657
+ <Alert.Root>
658
+ <span class="icon-[mdi--information] size-4"></span>
659
+ <Alert.Description>
660
+ This will connect your local robot <strong>"{pendingLocalRobot?.id}"</strong> as a slave to
661
+ receive commands from the selected server robot.
662
+ </Alert.Description>
663
+ </Alert.Root>
664
+ </Card.Content>
665
+ <Card.Footer class="flex gap-3 justify-end">
666
+ <Button variant="outline" onclick={cancelRobotSelection}>
667
  Cancel
668
+ </Button>
669
+ <Button
670
  onclick={confirmRobotSelection}
 
671
  disabled={!selectedServerRobotId}
672
+ class="bg-purple-600 hover:bg-purple-700"
673
  >
674
+ <span class="icon-[mdi--link] size-4 mr-1"></span>
675
  Connect Slave
676
+ </Button>
677
+ </Card.Footer>
678
+ </Card.Root>
679
  </div>
680
  {/if}
681
 
682
  <style>
683
+ .slider::-webkit-slider-thumb {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
  appearance: none;
685
  width: 18px;
686
  height: 18px;
 
692
  transition: all 0.15s ease;
693
  }
694
 
695
+ .slider::-webkit-slider-thumb:hover {
696
  background: #2563eb;
697
  transform: scale(1.1);
698
  }
699
 
700
+ .slider::-moz-range-thumb {
701
  width: 18px;
702
  height: 18px;
703
  border-radius: 50%;
 
708
  transition: all 0.15s ease;
709
  }
710
 
711
+ .slider::-moz-range-track {
712
  height: 6px;
713
  background: #374151;
714
  border-radius: 3px;
715
  border: none;
716
  }
717
 
718
+ .slider:focus {
719
  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
720
  }
721
  </style>
src/lib/components/panel/SettingsPanel.svelte CHANGED
@@ -1,6 +1,6 @@
1
  <script lang="ts">
2
  import { environment } from '$lib/runes/env.svelte';
3
- import type IUrdfJoint from '$lib/components/scene/robot/URDF/interfaces/IUrdfJoint';
4
 
5
  interface Props {}
6
 
 
1
  <script lang="ts">
2
  import { environment } from '$lib/runes/env.svelte';
3
+ import type IUrdfJoint from '$lib/components/3d/robot/URDF/interfaces/IUrdfJoint';
4
 
5
  interface Props {}
6
 
src/lib/components/scene/Robot.svelte DELETED
@@ -1,44 +0,0 @@
1
- <script lang="ts">
2
- import { T } from '@threlte/core';
3
- import { getRootLinks } from '$lib/components/scene/robot/URDF/utils/UrdfParser';
4
- import UrdfLink from '$lib/components/scene/robot/URDF/primitives/UrdfLink.svelte';
5
- import { robotManager } from '$lib/robot/RobotManager.svelte';
6
-
7
- interface Props {}
8
-
9
- let {}: Props = $props();
10
-
11
- // Get all robots from the manager
12
- const robots = $derived(robotManager.robots);
13
- </script>
14
-
15
- {#each robots as robot, index (robot.id)}
16
- {@const xPosition = index * 5} <!-- Space robots 5 units apart -->
17
- <T.Group position.x={xPosition} position.y={0} position.z={0} quaternion={[0, 0, 0, 1]} scale={[10, 10, 10]} rotation={[-Math.PI / 2, 0, 0]}>
18
- {#each getRootLinks(robot.robotState.robot) as link}
19
- <UrdfLink
20
- robot={robot.robotState.robot}
21
- {link}
22
- textScale={0.2}
23
- showName={true}
24
- showVisual={true}
25
- showCollision={false}
26
- visualColor="#333333"
27
- visualOpacity={0.7}
28
- collisionOpacity={0.7}
29
- collisionColor="#813d9c"
30
- jointNames={true}
31
- joints={true}
32
- jointColor="#62a0ea"
33
- jointIndicatorColor="#f66151"
34
- nameHeight={0.1}
35
- selectedLink={robot.robotState.selection.selectedLink}
36
- selectedJoint={robot.robotState.selection.selectedJoint}
37
- highlightColor="#ffa348"
38
- showLine={false}
39
- opacity={1}
40
- isInteractive={false}
41
- />
42
- {/each}
43
- </T.Group>
44
- {/each}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/scene/Selectable.svelte DELETED
@@ -1,68 +0,0 @@
1
- <script lang="ts">
2
- import { T } from '@threlte/core';
3
- import { TransformControls, interactivity } from '@threlte/extras';
4
- import type { Object3D } from 'three';
5
- import type { Snippet } from 'svelte';
6
- import type IUrdfJoint from './robot/URDF/interfaces/IUrdfJoint';
7
- import { updateOrigin } from './robot/URDF/utils/UrdfParser';
8
- import type { IUrdfVisual } from './robot/URDF/interfaces/IUrdfVisual';
9
- import type { TransformControlsMode } from 'three/examples/jsm/Addons.js';
10
-
11
- interface Props {
12
- origin: IUrdfJoint | IUrdfVisual;
13
- children?: Snippet; // renderable
14
- selected?: boolean;
15
- translationSnap?: number;
16
- scaleSnap?: number;
17
- rotationSnap?: number;
18
- tool?: TransformControlsMode;
19
- enableEdit?: boolean;
20
- }
21
-
22
- let {
23
- origin,
24
- children,
25
- selected = false,
26
- translationSnap = 0.1,
27
- scaleSnap = 0.1,
28
- rotationSnap = 0.1,
29
- tool = 'translate',
30
- enableEdit = true
31
- }: Props = $props();
32
-
33
- const updateData = (obj: Object3D) => {
34
- origin.origin_xyz = obj.position.toArray();
35
- origin.origin_rpy = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
36
- updateOrigin(origin);
37
- };
38
-
39
- const onobjectChange = (event: any) => {
40
- if (!event.target) {
41
- return;
42
- }
43
- const obj = event.target.object;
44
- updateData(obj);
45
- };
46
-
47
- interactivity();
48
- </script>
49
-
50
- {#if selected && enableEdit}
51
- <TransformControls
52
- {translationSnap}
53
- {scaleSnap}
54
- rotationSnap={Math.PI / rotationSnap}
55
- position={origin.origin_xyz}
56
- rotation={origin.origin_rpy}
57
- mode={tool}
58
- {onobjectChange}
59
- >
60
- {@render children?.()}
61
- </TransformControls>
62
- {:else}
63
- <T.Group position={origin.origin_xyz} rotation={origin.origin_rpy}>
64
- {@render children?.()}
65
- </T.Group>
66
- {/if}
67
-
68
- <!-- From https://github.com/brean/urdf-viewer -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/ui/accordion/accordion-content.svelte ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { Accordion as AccordionPrimitive } from "bits-ui";
3
+ import { cn, type WithoutChild } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithoutChild<AccordionPrimitive.ContentProps> = $props();
11
+ </script>
12
+
13
+ <AccordionPrimitive.Content
14
+ bind:ref
15
+ data-slot="accordion-content"
16
+ class={cn(
17
+ "data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm",
18
+ className
19
+ )}
20
+ {...restProps}
21
+ >
22
+ <div class="pb-4 pt-0">
23
+ {@render children?.()}
24
+ </div>
25
+ </AccordionPrimitive.Content>
src/lib/components/ui/accordion/accordion-item.svelte ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { Accordion as AccordionPrimitive } from "bits-ui";
3
+ import { cn } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ ...restProps
9
+ }: AccordionPrimitive.ItemProps = $props();
10
+ </script>
11
+
12
+ <AccordionPrimitive.Item
13
+ bind:ref
14
+ data-slot="accordion-item"
15
+ class={cn("border-b last:border-b-0", className)}
16
+ {...restProps}
17
+ />
src/lib/components/ui/accordion/accordion-root.svelte ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { Accordion as AccordionPrimitive } from "bits-ui";
3
+
4
+ let {
5
+ ref = $bindable(null),
6
+ value = $bindable(),
7
+ ...restProps
8
+ }: AccordionPrimitive.RootProps = $props();
9
+ </script>
10
+
11
+ <AccordionPrimitive.Root
12
+ bind:ref
13
+ bind:value={value as never}
14
+ data-slot="accordion"
15
+ {...restProps}
16
+ />
src/lib/components/ui/accordion/accordion-trigger.svelte ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { Accordion as AccordionPrimitive } from "bits-ui";
3
+ import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
4
+ import { cn, type WithoutChild } from "$lib/utils.js";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ level = 3,
10
+ children,
11
+ ...restProps
12
+ }: WithoutChild<AccordionPrimitive.TriggerProps> & {
13
+ level?: AccordionPrimitive.HeaderProps["level"];
14
+ } = $props();
15
+ </script>
16
+
17
+ <AccordionPrimitive.Header {level} class="flex">
18
+ <AccordionPrimitive.Trigger
19
+ data-slot="accordion-trigger"
20
+ bind:ref
21
+ class={cn(
22
+ "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium outline-none transition-all hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
23
+ className
24
+ )}
25
+ {...restProps}
26
+ >
27
+ {@render children?.()}
28
+ <ChevronDownIcon
29
+ class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200"
30
+ />
31
+ </AccordionPrimitive.Trigger>
32
+ </AccordionPrimitive.Header>
src/lib/components/ui/accordion/index.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Root from "./accordion-root.svelte";
2
+ import Content from "./accordion-content.svelte";
3
+ import Item from "./accordion-item.svelte";
4
+ import Trigger from "./accordion-trigger.svelte";
5
+
6
+ export {
7
+ Root,
8
+ Content,
9
+ Item,
10
+ Trigger,
11
+ //
12
+ Root as Accordion,
13
+ Content as AccordionContent,
14
+ Item as AccordionItem,
15
+ Trigger as AccordionTrigger,
16
+ };
src/lib/components/ui/alert-dialog/alert-dialog-action.svelte ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
3
+ import { buttonVariants } from "$lib/components/ui/button/index.js";
4
+ import { cn } from "$lib/utils.js";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ ...restProps
10
+ }: AlertDialogPrimitive.ActionProps = $props();
11
+ </script>
12
+
13
+ <AlertDialogPrimitive.Action
14
+ bind:ref
15
+ data-slot="alert-dialog-action"
16
+ class={cn(buttonVariants(), className)}
17
+ {...restProps}
18
+ />
src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
3
+ import { buttonVariants } from "$lib/components/ui/button/index.js";
4
+ import { cn } from "$lib/utils.js";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ ...restProps
10
+ }: AlertDialogPrimitive.CancelProps = $props();
11
+ </script>
12
+
13
+ <AlertDialogPrimitive.Cancel
14
+ bind:ref
15
+ data-slot="alert-dialog-cancel"
16
+ class={cn(buttonVariants({ variant: "outline" }), className)}
17
+ {...restProps}
18
+ />
src/lib/components/ui/alert-dialog/alert-dialog-content.svelte ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
3
+ import AlertDialogOverlay from "./alert-dialog-overlay.svelte";
4
+ import { cn, type WithoutChild, type WithoutChildrenOrChild } from "$lib/utils.js";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ portalProps,
10
+ ...restProps
11
+ }: WithoutChild<AlertDialogPrimitive.ContentProps> & {
12
+ portalProps?: WithoutChildrenOrChild<AlertDialogPrimitive.PortalProps>;
13
+ } = $props();
14
+ </script>
15
+
16
+ <AlertDialogPrimitive.Portal {...portalProps}>
17
+ <AlertDialogOverlay />
18
+ <AlertDialogPrimitive.Content
19
+ bind:ref
20
+ data-slot="alert-dialog-content"
21
+ class={cn(
22
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed left-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
23
+ className
24
+ )}
25
+ {...restProps}
26
+ />
27
+ </AlertDialogPrimitive.Portal>
src/lib/components/ui/alert-dialog/alert-dialog-description.svelte ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
3
+ import { cn } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ ...restProps
9
+ }: AlertDialogPrimitive.DescriptionProps = $props();
10
+ </script>
11
+
12
+ <AlertDialogPrimitive.Description
13
+ bind:ref
14
+ data-slot="alert-dialog-description"
15
+ class={cn("text-muted-foreground text-sm", className)}
16
+ {...restProps}
17
+ />