@almadar/ui 5.104.0 → 5.107.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/avl/index.cjs +1911 -4127
  2. package/dist/avl/index.js +1006 -3222
  3. package/dist/components/core/atoms/index.d.ts +1 -1
  4. package/dist/components/core/molecules/index.d.ts +1 -1
  5. package/dist/components/core/organisms/index.d.ts +1 -1
  6. package/dist/components/core/templates/index.d.ts +2 -2
  7. package/dist/components/game/{2d/atoms → atoms}/ActionButton.d.ts +1 -1
  8. package/dist/components/game/{2d/atoms → atoms}/ChoiceButton.d.ts +1 -1
  9. package/dist/components/game/{2d/atoms → atoms}/ControlButton.d.ts +1 -1
  10. package/dist/components/game/atoms/DrawShape.d.ts +50 -0
  11. package/dist/components/game/atoms/DrawSprite.d.ts +47 -0
  12. package/dist/components/game/atoms/DrawText.d.ts +31 -0
  13. package/dist/components/game/{2d/atoms → atoms}/GameAudioToggle.d.ts +1 -1
  14. package/dist/components/game/{2d/atoms → atoms}/GameIcon.d.ts +1 -1
  15. package/dist/components/game/{2d/atoms → atoms}/ItemSlot.d.ts +1 -1
  16. package/dist/components/game/{2d/atoms → atoms}/MiniMap.d.ts +1 -1
  17. package/dist/components/game/{2d/atoms → atoms}/ResourceCounter.d.ts +2 -2
  18. package/dist/components/game/{2d/atoms → atoms}/ScoreDisplay.d.ts +1 -1
  19. package/dist/components/game/{2d/atoms → atoms}/Sprite.d.ts +1 -1
  20. package/dist/components/game/{2d/atoms → atoms}/StateIndicator.d.ts +1 -1
  21. package/dist/components/game/{2d/atoms → atoms}/StatusEffect.d.ts +1 -1
  22. package/dist/components/game/{2d/atoms → atoms}/WaypointMarker.d.ts +1 -1
  23. package/dist/components/game/{shared/hooks → hooks}/useCamera.d.ts +1 -1
  24. package/dist/components/game/{shared/hooks → hooks}/useUnitSpriteAtlas.d.ts +2 -2
  25. package/dist/components/game/{shared → lib}/index.d.ts +1 -2
  26. package/dist/components/game/{shared → lib}/isometric.d.ts +38 -0
  27. package/dist/components/game/{shared → lib}/isometricTypes.d.ts +4 -0
  28. package/dist/components/game/{shared/lib → lib}/puzzleObject.d.ts +1 -1
  29. package/dist/components/game/{2d/molecules → molecules}/ActionTile.d.ts +1 -1
  30. package/dist/components/game/molecules/Canvas.d.ts +96 -0
  31. package/dist/components/game/molecules/Canvas2D.d.ts +120 -0
  32. package/dist/components/game/{2d/molecules → molecules}/ControlGrid.d.ts +1 -1
  33. package/dist/components/game/molecules/DrawShapeLayer.d.ts +19 -0
  34. package/dist/components/game/molecules/DrawSpriteLayer.d.ts +21 -0
  35. package/dist/components/game/molecules/DrawTextLayer.d.ts +22 -0
  36. package/dist/components/game/{2d/molecules → molecules}/GameHud.d.ts +1 -1
  37. package/dist/components/game/{2d/molecules → molecules}/InventoryGrid.d.ts +1 -1
  38. package/dist/components/game/{2d/molecules → molecules}/ResourceBar.d.ts +1 -1
  39. package/dist/components/game/{2d/molecules → molecules}/StatBadge.d.ts +1 -1
  40. package/dist/components/game/{2d/molecules → molecules}/StateNode.d.ts +1 -1
  41. package/dist/components/game/{2d/molecules → molecules}/TraitSlot.d.ts +1 -1
  42. package/dist/components/game/{2d/molecules → molecules}/TraitStateViewer.d.ts +1 -1
  43. package/dist/components/game/{2d/molecules → molecules}/TransitionArrow.d.ts +1 -1
  44. package/dist/components/game/{2d/molecules → molecules}/index.d.ts +9 -16
  45. package/dist/components/game/patterns.d.ts +20 -0
  46. package/dist/components/game/{shared/providers → providers}/GameAudioProvider.d.ts +1 -1
  47. package/dist/components/game/{2d/templates → templates}/GameTemplate.d.ts +1 -1
  48. package/dist/components/game/three/atoms/SpriteBillboard3D.d.ts +18 -0
  49. package/dist/components/game/three/game3dTheme.d.ts +55 -0
  50. package/dist/components/game/{shared → three}/hooks/useGameCanvas3DEvents.d.ts +4 -3
  51. package/dist/components/game/{3d → three}/index.cjs +787 -2986
  52. package/dist/components/game/{3d → three}/index.css +3 -3
  53. package/dist/components/game/{3d → three}/index.d.ts +0 -1
  54. package/dist/components/game/{3d → three}/index.js +644 -2842
  55. package/dist/components/game/{shared → three}/lib/game3dAssetManifest.d.ts +1 -1
  56. package/dist/components/game/three/molecules/GameCamera3D.d.ts +35 -0
  57. package/dist/components/game/three/molecules/GameCanvas3D.d.ts +175 -0
  58. package/dist/components/game/{3d → three}/molecules/Lighting3D.d.ts +10 -2
  59. package/dist/components/game/{3d → three}/molecules/index.d.ts +9 -12
  60. package/dist/components/game/{3d → three}/patterns.d.ts +0 -4
  61. package/dist/components/index.cjs +2326 -4362
  62. package/dist/components/index.js +1420 -3449
  63. package/dist/lib/drawable/Drawable3D.d.ts +20 -0
  64. package/dist/lib/drawable/contract.d.ts +68 -0
  65. package/dist/lib/drawable/hitTest.d.ts +23 -0
  66. package/dist/lib/drawable/mesh3d.d.ts +28 -0
  67. package/dist/lib/drawable/paintDispatch.d.ts +22 -0
  68. package/dist/lib/drawable/projector.d.ts +14 -0
  69. package/dist/lib/drawable/projector3d.d.ts +20 -0
  70. package/dist/lib/imageCache.d.ts +9 -0
  71. package/dist/lib/painter2d.d.ts +81 -0
  72. package/dist/lib/webPainter2d.d.ts +7 -0
  73. package/dist/marketing/index.cjs +10 -0
  74. package/dist/marketing/index.js +10 -0
  75. package/dist/providers/index.cjs +1756 -3976
  76. package/dist/providers/index.js +964 -3184
  77. package/dist/runtime/index.cjs +1766 -3982
  78. package/dist/runtime/index.js +980 -3196
  79. package/package.json +13 -13
  80. package/dist/components/game/2d/molecules/Canvas2D.d.ts +0 -175
  81. package/dist/components/game/2d/organisms/BuilderBoard.d.ts +0 -60
  82. package/dist/components/game/2d/organisms/ClassifierBoard.d.ts +0 -66
  83. package/dist/components/game/2d/organisms/DebuggerBoard.d.ts +0 -61
  84. package/dist/components/game/2d/organisms/EventHandlerBoard.d.ts +0 -42
  85. package/dist/components/game/2d/organisms/NegotiatorBoard.d.ts +0 -56
  86. package/dist/components/game/2d/organisms/SequencerBoard.d.ts +0 -69
  87. package/dist/components/game/2d/organisms/SimulatorBoard.d.ts +0 -58
  88. package/dist/components/game/2d/organisms/StateArchitectBoard.d.ts +0 -61
  89. package/dist/components/game/3d/molecules/GameCanvas3D.d.ts +0 -259
  90. package/dist/components/game/3d/organisms/GameBoard3D.d.ts +0 -66
  91. package/dist/components/game/3d/templates/GameCanvas3DBattleTemplate.d.ts +0 -82
  92. package/dist/components/game/3d/templates/GameCanvas3DCastleTemplate.d.ts +0 -87
  93. package/dist/components/game/3d/templates/GameCanvas3DWorldMapTemplate.d.ts +0 -95
  94. package/dist/components/game/shared/effects.d.ts +0 -253
  95. /package/dist/components/game/{2d/atoms → atoms}/ComboCounter.d.ts +0 -0
  96. /package/dist/components/game/{2d/atoms → atoms}/DamageNumber.d.ts +0 -0
  97. /package/dist/components/game/{2d/atoms → atoms}/DialogueBubble.d.ts +0 -0
  98. /package/dist/components/game/{2d/atoms → atoms}/GameCard.d.ts +0 -0
  99. /package/dist/components/game/{2d/atoms → atoms}/HealthBar.d.ts +0 -0
  100. /package/dist/components/game/{2d/atoms → atoms}/TimerDisplay.d.ts +0 -0
  101. /package/dist/components/game/{2d/atoms → atoms}/TurnIndicator.d.ts +0 -0
  102. /package/dist/components/game/{shared/hooks → hooks}/useGameAudio.d.ts +0 -0
  103. /package/dist/components/game/{shared → hooks}/useImageCache.d.ts +0 -0
  104. /package/dist/components/game/{shared → lib}/boardEntity.d.ts +0 -0
  105. /package/dist/components/game/{shared/lib → lib}/editorUtils.d.ts +0 -0
  106. /package/dist/components/game/{shared → lib}/game.d.ts +0 -0
  107. /package/dist/components/game/{shared → lib}/makeAsset.d.ts +0 -0
  108. /package/dist/components/game/{shared/lib → lib}/mechanics.d.ts +0 -0
  109. /package/dist/components/game/{shared/lib → lib}/physicsPresets.d.ts +0 -0
  110. /package/dist/components/game/{shared/lib → lib}/physicsTypes.d.ts +0 -0
  111. /package/dist/components/game/{shared → lib}/spriteAnimation.d.ts +0 -0
  112. /package/dist/components/game/{shared → lib}/spriteAnimationTypes.d.ts +0 -0
  113. /package/dist/components/game/{shared → lib}/spriteSheetConstants.d.ts +0 -0
  114. /package/dist/components/game/{2d/molecules → molecules}/ActionPalette.d.ts +0 -0
  115. /package/dist/components/game/{2d/molecules → molecules}/EventLog.d.ts +0 -0
  116. /package/dist/components/game/{2d/molecules → molecules}/GameMenu.d.ts +0 -0
  117. /package/dist/components/game/{2d/molecules → molecules}/ObjectRulePanel.d.ts +0 -0
  118. /package/dist/components/game/{2d/molecules → molecules}/RuleEditor.d.ts +0 -0
  119. /package/dist/components/game/{2d/molecules → molecules}/SequenceBar.d.ts +0 -0
  120. /package/dist/components/game/{2d/molecules → molecules}/StateGraph.d.ts +0 -0
  121. /package/dist/components/game/{2d/molecules → molecules}/StateJsonView.d.ts +0 -0
  122. /package/dist/components/game/{2d/molecules → molecules}/VariablePanel.d.ts +0 -0
  123. /package/dist/components/game/{2d/templates → templates}/GameShell.d.ts +0 -0
  124. /package/dist/components/game/{shared → three}/hooks/useAssetLoader.d.ts +0 -0
  125. /package/dist/components/game/{shared → three}/hooks/useRaycaster.d.ts +0 -0
  126. /package/dist/components/game/{shared → three}/hooks/useSceneGraph.d.ts +0 -0
  127. /package/dist/components/game/{shared → three}/hooks/useThree.d.ts +0 -0
  128. /package/dist/components/game/{shared → three}/lib/AssetLoader.d.ts +0 -0
  129. /package/dist/components/game/{shared → three}/lib/culling.d.ts +0 -0
  130. /package/dist/components/game/{shared → three}/lib/grid3D.d.ts +0 -0
  131. /package/dist/components/game/{3d → three}/molecules/Camera3D.d.ts +0 -0
  132. /package/dist/components/game/{3d → three}/molecules/Canvas3DErrorBoundary.d.ts +0 -0
  133. /package/dist/components/game/{3d → three}/molecules/Canvas3DLoadingState.d.ts +0 -0
  134. /package/dist/components/game/{3d → three}/molecules/ModelLoader.d.ts +0 -0
  135. /package/dist/components/game/{3d → three}/molecules/Scene3D.d.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var React8 = require('react');
3
+ var React7 = require('react');
4
4
  var fiber = require('@react-three/fiber');
5
5
  var THREE6 = require('three');
6
6
  var jsxRuntime = require('react/jsx-runtime');
@@ -14,10 +14,11 @@ var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
14
14
  var providers = require('@almadar/ui/providers');
15
15
  var clsx = require('clsx');
16
16
  var tailwindMerge = require('tailwind-merge');
17
- var LucideIcons = require('lucide-react');
18
17
  var postprocessing = require('@react-three/postprocessing');
19
18
  var hooks = require('@almadar/ui/hooks');
20
19
 
20
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
+
21
22
  function _interopNamespace(e) {
22
23
  if (e && e.__esModule) return e;
23
24
  var n = Object.create(null);
@@ -36,17 +37,16 @@ function _interopNamespace(e) {
36
37
  return Object.freeze(n);
37
38
  }
38
39
 
39
- var React8__namespace = /*#__PURE__*/_interopNamespace(React8);
40
+ var React7__default = /*#__PURE__*/_interopDefault(React7);
40
41
  var THREE6__namespace = /*#__PURE__*/_interopNamespace(THREE6);
41
- var LucideIcons__namespace = /*#__PURE__*/_interopNamespace(LucideIcons);
42
42
 
43
43
  var __defProp = Object.defineProperty;
44
44
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
45
45
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
46
46
  function Scene3D({ background = "#1a1a2e", fog, children }) {
47
47
  const { scene } = fiber.useThree();
48
- const initializedRef = React8.useRef(false);
49
- React8.useEffect(() => {
48
+ const initializedRef = React7.useRef(false);
49
+ React7.useEffect(() => {
50
50
  if (initializedRef.current) return;
51
51
  initializedRef.current = true;
52
52
  if (background.startsWith("#") || background.startsWith("rgb")) {
@@ -67,7 +67,7 @@ function Scene3D({ background = "#1a1a2e", fog, children }) {
67
67
  }, [scene, background, fog]);
68
68
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
69
69
  }
70
- var Camera3D = React8.forwardRef(
70
+ var Camera3D = React7.forwardRef(
71
71
  ({
72
72
  mode = "isometric",
73
73
  position = [10, 10, 10],
@@ -80,10 +80,10 @@ var Camera3D = React8.forwardRef(
80
80
  onChange
81
81
  }, ref) => {
82
82
  const { camera, set, viewport } = fiber.useThree();
83
- const controlsRef = React8.useRef(null);
84
- const initialPosition = React8.useRef(new THREE6__namespace.Vector3(...position));
85
- const initialTarget = React8.useRef(new THREE6__namespace.Vector3(...target));
86
- React8.useEffect(() => {
83
+ const controlsRef = React7.useRef(null);
84
+ const initialPosition = React7.useRef(new THREE6__namespace.Vector3(...position));
85
+ const initialTarget = React7.useRef(new THREE6__namespace.Vector3(...target));
86
+ React7.useEffect(() => {
87
87
  let newCamera;
88
88
  if (mode === "isometric") {
89
89
  const aspect = viewport.aspect;
@@ -114,7 +114,7 @@ var Camera3D = React8.forwardRef(
114
114
  onChange(camera);
115
115
  }
116
116
  });
117
- React8.useImperativeHandle(ref, () => ({
117
+ React7.useImperativeHandle(ref, () => ({
118
118
  getCamera: () => camera,
119
119
  setPosition: (x, y, z) => {
120
120
  camera.position.set(x, y, z);
@@ -170,6 +170,10 @@ function Lighting3D({
170
170
  shadows = true,
171
171
  shadowMapSize = 2048,
172
172
  shadowCameraSize = 20,
173
+ shadowBias = -1e-3,
174
+ shadowNormalBias,
175
+ shadowCameraNear = 0.1,
176
+ shadowCameraFar = 100,
173
177
  showHelpers = false
174
178
  }) {
175
179
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -186,9 +190,10 @@ function Lighting3D({
186
190
  "shadow-camera-right": shadowCameraSize,
187
191
  "shadow-camera-top": shadowCameraSize,
188
192
  "shadow-camera-bottom": -shadowCameraSize,
189
- "shadow-camera-near": 0.1,
190
- "shadow-camera-far": 100,
191
- "shadow-bias": -1e-3
193
+ "shadow-camera-near": shadowCameraNear,
194
+ "shadow-camera-far": shadowCameraFar,
195
+ "shadow-bias": shadowBias,
196
+ "shadow-normalBias": shadowNormalBias
192
197
  }
193
198
  ),
194
199
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -256,7 +261,7 @@ function Canvas3DLoadingState({
256
261
  ] });
257
262
  }
258
263
  var log = logger.createLogger("almadar:ui:game:canvas3d:error-boundary");
259
- var Canvas3DErrorBoundary = class extends React8.Component {
264
+ var Canvas3DErrorBoundary = class extends React7.Component {
260
265
  constructor(props) {
261
266
  super(props);
262
267
  __publicField(this, "handleReset", () => {
@@ -371,10 +376,10 @@ function stateFromEntry(entry) {
371
376
  return { model: null, clips: [], isLoading: !entry.error, error: entry.error ?? null };
372
377
  }
373
378
  function useGLTFModel(url, resourceBasePath) {
374
- const [state, setState] = React8.useState(
379
+ const [state, setState] = React7.useState(
375
380
  () => stateFromEntry(url ? loadGltfCached(url, resourceBasePath || detectAssetRoot(url)) : null)
376
381
  );
377
- React8.useEffect(() => {
382
+ React7.useEffect(() => {
378
383
  if (!url) {
379
384
  setState({ model: null, clips: [], isLoading: false, error: null });
380
385
  return;
@@ -409,7 +414,7 @@ function ModelLoader({
409
414
  tint
410
415
  }) {
411
416
  const { model: loadedModel, clips, isLoading, error } = useGLTFModel(url, resourceBasePath);
412
- const model = React8.useMemo(() => {
417
+ const model = React7.useMemo(() => {
413
418
  if (!loadedModel) return null;
414
419
  const cloned = SkeletonUtils.clone(loadedModel);
415
420
  cloned.updateMatrixWorld(true);
@@ -427,8 +432,8 @@ function ModelLoader({
427
432
  });
428
433
  return cloned;
429
434
  }, [loadedModel, castShadow, receiveShadow, tint]);
430
- const mixer = React8.useMemo(() => model ? new THREE6__namespace.AnimationMixer(model) : null, [model]);
431
- React8.useEffect(() => {
435
+ const mixer = React7.useMemo(() => model ? new THREE6__namespace.AnimationMixer(model) : null, [model]);
436
+ React7.useEffect(() => {
432
437
  if (!mixer || !animation || clips.length === 0) return;
433
438
  const wanted = animation.toLowerCase();
434
439
  const clip = clips.find((c) => c.name === animation) ?? clips.find((c) => c.name.toLowerCase() === wanted);
@@ -442,7 +447,7 @@ function ModelLoader({
442
447
  fiber.useFrame((_, delta) => {
443
448
  mixer?.update(delta);
444
449
  });
445
- const normFactor = React8.useMemo(() => {
450
+ const normFactor = React7.useMemo(() => {
446
451
  if (!model) return 1;
447
452
  const box = new THREE6__namespace.Box3().setFromObject(model);
448
453
  const size = new THREE6__namespace.Vector3();
@@ -451,11 +456,11 @@ function ModelLoader({
451
456
  if (!Number.isFinite(maxDim) || maxDim < 0.05) return 1;
452
457
  return 1 / maxDim;
453
458
  }, [model]);
454
- const scaleArray = React8.useMemo(() => {
459
+ const scaleArray = React7.useMemo(() => {
455
460
  const base = typeof scale === "number" ? [scale, scale, scale] : scale;
456
461
  return [base[0] * normFactor, base[1] * normFactor, base[2] * normFactor];
457
462
  }, [scale, normFactor]);
458
- const rotationRad = React8.useMemo(() => {
463
+ const rotationRad = React7.useMemo(() => {
459
464
  return [
460
465
  rotation[0] * Math.PI / 180,
461
466
  rotation[1] * Math.PI / 180,
@@ -723,7 +728,7 @@ var AssetLoader = class {
723
728
  };
724
729
  var assetLoader = new AssetLoader();
725
730
 
726
- // components/game/shared/hooks/useThree.ts
731
+ // components/game/three/hooks/useThree.ts
727
732
  var DEFAULT_OPTIONS = {
728
733
  cameraMode: "isometric",
729
734
  cameraPosition: [10, 10, 10],
@@ -735,21 +740,21 @@ var DEFAULT_OPTIONS = {
735
740
  };
736
741
  function useThree3(options = {}) {
737
742
  const opts = { ...DEFAULT_OPTIONS, ...options };
738
- const containerRef = React8.useRef(null);
739
- const canvasRef = React8.useRef(null);
740
- const rendererRef = React8.useRef(null);
741
- const sceneRef = React8.useRef(null);
742
- const cameraRef = React8.useRef(null);
743
- const controlsRef = React8.useRef(null);
744
- const gridHelperRef = React8.useRef(null);
745
- const rafRef = React8.useRef(0);
746
- const [isReady, setIsReady] = React8.useState(false);
747
- const [dimensions, setDimensions] = React8.useState({ width: 0, height: 0 });
748
- const initialCameraPosition = React8.useMemo(
743
+ const containerRef = React7.useRef(null);
744
+ const canvasRef = React7.useRef(null);
745
+ const rendererRef = React7.useRef(null);
746
+ const sceneRef = React7.useRef(null);
747
+ const cameraRef = React7.useRef(null);
748
+ const controlsRef = React7.useRef(null);
749
+ const gridHelperRef = React7.useRef(null);
750
+ const rafRef = React7.useRef(0);
751
+ const [isReady, setIsReady] = React7.useState(false);
752
+ const [dimensions, setDimensions] = React7.useState({ width: 0, height: 0 });
753
+ const initialCameraPosition = React7.useMemo(
749
754
  () => new THREE6__namespace.Vector3(...opts.cameraPosition),
750
755
  []
751
756
  );
752
- React8.useEffect(() => {
757
+ React7.useEffect(() => {
753
758
  if (!containerRef.current) return;
754
759
  const container = containerRef.current;
755
760
  const { clientWidth, clientHeight } = container;
@@ -842,7 +847,7 @@ function useThree3(options = {}) {
842
847
  scene.clear();
843
848
  };
844
849
  }, []);
845
- React8.useEffect(() => {
850
+ React7.useEffect(() => {
846
851
  if (!cameraRef.current || !containerRef.current) return;
847
852
  const container = containerRef.current;
848
853
  const { clientWidth, clientHeight } = container;
@@ -877,20 +882,20 @@ function useThree3(options = {}) {
877
882
  animate();
878
883
  }
879
884
  }, [opts.cameraMode]);
880
- const setCameraPosition = React8.useCallback((x, y, z) => {
885
+ const setCameraPosition = React7.useCallback((x, y, z) => {
881
886
  if (cameraRef.current) {
882
887
  cameraRef.current.position.set(x, y, z);
883
888
  controlsRef.current?.update();
884
889
  }
885
890
  }, []);
886
- const lookAt = React8.useCallback((x, y, z) => {
891
+ const lookAt = React7.useCallback((x, y, z) => {
887
892
  if (cameraRef.current) {
888
893
  cameraRef.current.lookAt(x, y, z);
889
894
  controlsRef.current?.target.set(x, y, z);
890
895
  controlsRef.current?.update();
891
896
  }
892
897
  }, []);
893
- const resetCamera = React8.useCallback(() => {
898
+ const resetCamera = React7.useCallback(() => {
894
899
  if (cameraRef.current) {
895
900
  cameraRef.current.position.copy(initialCameraPosition);
896
901
  cameraRef.current.lookAt(0, 0, 0);
@@ -900,7 +905,7 @@ function useThree3(options = {}) {
900
905
  }
901
906
  }
902
907
  }, [initialCameraPosition]);
903
- const fitView = React8.useCallback(
908
+ const fitView = React7.useCallback(
904
909
  (bounds) => {
905
910
  if (!cameraRef.current) return;
906
911
  const centerX = (bounds.minX + bounds.maxX) / 2;
@@ -931,20 +936,20 @@ function useThree3(options = {}) {
931
936
  }
932
937
  function useAssetLoader(options = {}) {
933
938
  const { preloadUrls = [], loader: customLoader } = options;
934
- const loaderRef = React8.useRef(customLoader || new AssetLoader());
935
- const [state, setState] = React8.useState({
939
+ const loaderRef = React7.useRef(customLoader || new AssetLoader());
940
+ const [state, setState] = React7.useState({
936
941
  isLoading: false,
937
942
  progress: 0,
938
943
  loaded: 0,
939
944
  total: 0,
940
945
  errors: []
941
946
  });
942
- React8.useEffect(() => {
947
+ React7.useEffect(() => {
943
948
  if (preloadUrls.length > 0) {
944
949
  preload(preloadUrls);
945
950
  }
946
951
  }, []);
947
- const updateProgress = React8.useCallback((loaded, total) => {
952
+ const updateProgress = React7.useCallback((loaded, total) => {
948
953
  setState((prev) => ({
949
954
  ...prev,
950
955
  loaded,
@@ -952,7 +957,7 @@ function useAssetLoader(options = {}) {
952
957
  progress: total > 0 ? Math.round(loaded / total * 100) : 0
953
958
  }));
954
959
  }, []);
955
- const loadModel = React8.useCallback(
960
+ const loadModel = React7.useCallback(
956
961
  async (url) => {
957
962
  setState((prev) => ({ ...prev, isLoading: true }));
958
963
  try {
@@ -975,7 +980,7 @@ function useAssetLoader(options = {}) {
975
980
  },
976
981
  []
977
982
  );
978
- const loadOBJ = React8.useCallback(
983
+ const loadOBJ = React7.useCallback(
979
984
  async (url) => {
980
985
  setState((prev) => ({ ...prev, isLoading: true }));
981
986
  try {
@@ -998,7 +1003,7 @@ function useAssetLoader(options = {}) {
998
1003
  },
999
1004
  []
1000
1005
  );
1001
- const loadTexture = React8.useCallback(
1006
+ const loadTexture = React7.useCallback(
1002
1007
  async (url) => {
1003
1008
  setState((prev) => ({ ...prev, isLoading: true }));
1004
1009
  try {
@@ -1021,7 +1026,7 @@ function useAssetLoader(options = {}) {
1021
1026
  },
1022
1027
  []
1023
1028
  );
1024
- const preload = React8.useCallback(
1029
+ const preload = React7.useCallback(
1025
1030
  async (urls) => {
1026
1031
  setState((prev) => ({
1027
1032
  ...prev,
@@ -1060,27 +1065,27 @@ function useAssetLoader(options = {}) {
1060
1065
  },
1061
1066
  [updateProgress]
1062
1067
  );
1063
- const hasModel = React8.useCallback((url) => {
1068
+ const hasModel = React7.useCallback((url) => {
1064
1069
  return loaderRef.current.hasModel(url);
1065
1070
  }, []);
1066
- const hasTexture = React8.useCallback((url) => {
1071
+ const hasTexture = React7.useCallback((url) => {
1067
1072
  return loaderRef.current.hasTexture(url);
1068
1073
  }, []);
1069
- const getModel = React8.useCallback((url) => {
1074
+ const getModel = React7.useCallback((url) => {
1070
1075
  try {
1071
1076
  return loaderRef.current.getModel(url);
1072
1077
  } catch {
1073
1078
  return void 0;
1074
1079
  }
1075
1080
  }, []);
1076
- const getTexture = React8.useCallback((url) => {
1081
+ const getTexture = React7.useCallback((url) => {
1077
1082
  try {
1078
1083
  return loaderRef.current.getTexture(url);
1079
1084
  } catch {
1080
1085
  return void 0;
1081
1086
  }
1082
1087
  }, []);
1083
- const clearCache = React8.useCallback(() => {
1088
+ const clearCache = React7.useCallback(() => {
1084
1089
  loaderRef.current.clearCache();
1085
1090
  setState({
1086
1091
  isLoading: false,
@@ -1104,25 +1109,25 @@ function useAssetLoader(options = {}) {
1104
1109
  };
1105
1110
  }
1106
1111
  function useSceneGraph() {
1107
- const nodesRef = React8.useRef(/* @__PURE__ */ new Map());
1108
- const addNode = React8.useCallback((node) => {
1112
+ const nodesRef = React7.useRef(/* @__PURE__ */ new Map());
1113
+ const addNode = React7.useCallback((node) => {
1109
1114
  const existing = nodesRef.current.get(node.id);
1110
1115
  if (existing) {
1111
1116
  existing.mesh.removeFromParent();
1112
1117
  }
1113
1118
  nodesRef.current.set(node.id, node);
1114
1119
  }, []);
1115
- const removeNode = React8.useCallback((id) => {
1120
+ const removeNode = React7.useCallback((id) => {
1116
1121
  const node = nodesRef.current.get(id);
1117
1122
  if (node) {
1118
1123
  node.mesh.removeFromParent();
1119
1124
  nodesRef.current.delete(id);
1120
1125
  }
1121
1126
  }, []);
1122
- const getNode = React8.useCallback((id) => {
1127
+ const getNode = React7.useCallback((id) => {
1123
1128
  return nodesRef.current.get(id);
1124
1129
  }, []);
1125
- const updateNodePosition = React8.useCallback(
1130
+ const updateNodePosition = React7.useCallback(
1126
1131
  (id, x, y, z) => {
1127
1132
  const node = nodesRef.current.get(id);
1128
1133
  if (node) {
@@ -1132,7 +1137,7 @@ function useSceneGraph() {
1132
1137
  },
1133
1138
  []
1134
1139
  );
1135
- const updateNodeGridPosition = React8.useCallback(
1140
+ const updateNodeGridPosition = React7.useCallback(
1136
1141
  (id, gridX, gridZ) => {
1137
1142
  const node = nodesRef.current.get(id);
1138
1143
  if (node) {
@@ -1141,7 +1146,7 @@ function useSceneGraph() {
1141
1146
  },
1142
1147
  []
1143
1148
  );
1144
- const getNodeAtGrid = React8.useCallback(
1149
+ const getNodeAtGrid = React7.useCallback(
1145
1150
  (x, z, type) => {
1146
1151
  return Array.from(nodesRef.current.values()).find((node) => {
1147
1152
  const matchesGrid = node.gridPosition.x === x && node.gridPosition.z === z;
@@ -1150,10 +1155,10 @@ function useSceneGraph() {
1150
1155
  },
1151
1156
  []
1152
1157
  );
1153
- const getNodesByType = React8.useCallback((type) => {
1158
+ const getNodesByType = React7.useCallback((type) => {
1154
1159
  return Array.from(nodesRef.current.values()).filter((node) => node.type === type);
1155
1160
  }, []);
1156
- const getNodesInBounds = React8.useCallback(
1161
+ const getNodesInBounds = React7.useCallback(
1157
1162
  (minX, maxX, minZ, maxZ) => {
1158
1163
  return Array.from(nodesRef.current.values()).filter((node) => {
1159
1164
  const { x, z } = node.gridPosition;
@@ -1162,13 +1167,13 @@ function useSceneGraph() {
1162
1167
  },
1163
1168
  []
1164
1169
  );
1165
- const clearNodes = React8.useCallback(() => {
1170
+ const clearNodes = React7.useCallback(() => {
1166
1171
  nodesRef.current.forEach((node) => {
1167
1172
  node.mesh.removeFromParent();
1168
1173
  });
1169
1174
  nodesRef.current.clear();
1170
1175
  }, []);
1171
- const countNodes = React8.useCallback((type) => {
1176
+ const countNodes = React7.useCallback((type) => {
1172
1177
  if (!type) {
1173
1178
  return nodesRef.current.size;
1174
1179
  }
@@ -1190,9 +1195,9 @@ function useSceneGraph() {
1190
1195
  }
1191
1196
  function useRaycaster(options) {
1192
1197
  const { camera, canvas, cellSize = 1, offsetX = 0, offsetZ = 0 } = options;
1193
- const raycaster = React8.useRef(new THREE6__namespace.Raycaster());
1194
- const mouse = React8.useRef(new THREE6__namespace.Vector2());
1195
- const clientToNDC = React8.useCallback(
1198
+ const raycaster = React7.useRef(new THREE6__namespace.Raycaster());
1199
+ const mouse = React7.useRef(new THREE6__namespace.Vector2());
1200
+ const clientToNDC = React7.useCallback(
1196
1201
  (clientX, clientY) => {
1197
1202
  if (!canvas) {
1198
1203
  return { x: 0, y: 0 };
@@ -1205,7 +1210,7 @@ function useRaycaster(options) {
1205
1210
  },
1206
1211
  [canvas]
1207
1212
  );
1208
- const isWithinCanvas = React8.useCallback(
1213
+ const isWithinCanvas = React7.useCallback(
1209
1214
  (clientX, clientY) => {
1210
1215
  if (!canvas) return false;
1211
1216
  const rect = canvas.getBoundingClientRect();
@@ -1213,7 +1218,7 @@ function useRaycaster(options) {
1213
1218
  },
1214
1219
  [canvas]
1215
1220
  );
1216
- const getIntersection = React8.useCallback(
1221
+ const getIntersection = React7.useCallback(
1217
1222
  (clientX, clientY, objects) => {
1218
1223
  if (!camera || !canvas) return null;
1219
1224
  const ndc = clientToNDC(clientX, clientY);
@@ -1236,7 +1241,7 @@ function useRaycaster(options) {
1236
1241
  },
1237
1242
  [camera, canvas, clientToNDC]
1238
1243
  );
1239
- const getAllIntersections = React8.useCallback(
1244
+ const getAllIntersections = React7.useCallback(
1240
1245
  (clientX, clientY, objects) => {
1241
1246
  if (!camera || !canvas) return [];
1242
1247
  const ndc = clientToNDC(clientX, clientY);
@@ -1255,7 +1260,7 @@ function useRaycaster(options) {
1255
1260
  },
1256
1261
  [camera, canvas, clientToNDC]
1257
1262
  );
1258
- const getGridCoordinates = React8.useCallback(
1263
+ const getGridCoordinates = React7.useCallback(
1259
1264
  (clientX, clientY) => {
1260
1265
  if (!camera || !canvas) return null;
1261
1266
  const ndc = clientToNDC(clientX, clientY);
@@ -1273,7 +1278,7 @@ function useRaycaster(options) {
1273
1278
  },
1274
1279
  [camera, canvas, cellSize, offsetX, offsetZ, clientToNDC]
1275
1280
  );
1276
- const getTileAtPosition = React8.useCallback(
1281
+ const getTileAtPosition = React7.useCallback(
1277
1282
  (clientX, clientY, scene) => {
1278
1283
  if (!camera || !canvas) return null;
1279
1284
  const tileMeshes = [];
@@ -1396,10 +1401,10 @@ var fallbackEventBus = {
1396
1401
  }
1397
1402
  };
1398
1403
  function useEventBus() {
1399
- const context = React8.useContext(providers.EventBusContext);
1404
+ const context = React7.useContext(providers.EventBusContext);
1400
1405
  const baseBus = context ?? getGlobalEventBus() ?? fallbackEventBus;
1401
1406
  const scope = providers.useTraitScope();
1402
- return React8.useMemo(() => {
1407
+ return React7.useMemo(() => {
1403
1408
  if (!scope) {
1404
1409
  return {
1405
1410
  ...baseBus,
@@ -1435,7 +1440,7 @@ function useEventBus() {
1435
1440
  }
1436
1441
  function useEmitEvent() {
1437
1442
  const eventBus = useEventBus();
1438
- return React8.useCallback(
1443
+ return React7.useCallback(
1439
1444
  (type, payload, source) => {
1440
1445
  eventBus.emit(type, payload, source);
1441
1446
  },
@@ -1443,7 +1448,7 @@ function useEmitEvent() {
1443
1448
  );
1444
1449
  }
1445
1450
 
1446
- // components/game/shared/hooks/useGameCanvas3DEvents.ts
1451
+ // components/game/three/hooks/useGameCanvas3DEvents.ts
1447
1452
  function useGameCanvas3DEvents(options) {
1448
1453
  const {
1449
1454
  tileClickEvent,
@@ -1462,9 +1467,9 @@ function useGameCanvas3DEvents(options) {
1462
1467
  onUnitAnimation
1463
1468
  } = options;
1464
1469
  const emit = useEmitEvent();
1465
- const optionsRef = React8.useRef(options);
1470
+ const optionsRef = React7.useRef(options);
1466
1471
  optionsRef.current = options;
1467
- const handleTileClick = React8.useCallback(
1472
+ const handleTileClick = React7.useCallback(
1468
1473
  (tile, event) => {
1469
1474
  if (tileClickEvent) {
1470
1475
  emit(tileClickEvent, {
@@ -1480,7 +1485,7 @@ function useGameCanvas3DEvents(options) {
1480
1485
  },
1481
1486
  [tileClickEvent, emit]
1482
1487
  );
1483
- const handleUnitClick = React8.useCallback(
1488
+ const handleUnitClick = React7.useCallback(
1484
1489
  (unit, event) => {
1485
1490
  if (unitClickEvent) {
1486
1491
  emit(unitClickEvent, {
@@ -1499,7 +1504,7 @@ function useGameCanvas3DEvents(options) {
1499
1504
  },
1500
1505
  [unitClickEvent, emit]
1501
1506
  );
1502
- const handleFeatureClick = React8.useCallback(
1507
+ const handleFeatureClick = React7.useCallback(
1503
1508
  (feature, event) => {
1504
1509
  if (featureClickEvent) {
1505
1510
  emit(featureClickEvent, {
@@ -1514,7 +1519,7 @@ function useGameCanvas3DEvents(options) {
1514
1519
  },
1515
1520
  [featureClickEvent, emit]
1516
1521
  );
1517
- const handleCanvasClick = React8.useCallback(
1522
+ const handleCanvasClick = React7.useCallback(
1518
1523
  (event) => {
1519
1524
  if (canvasClickEvent) {
1520
1525
  emit(canvasClickEvent, {
@@ -1527,7 +1532,7 @@ function useGameCanvas3DEvents(options) {
1527
1532
  },
1528
1533
  [canvasClickEvent, emit]
1529
1534
  );
1530
- const handleTileHover = React8.useCallback(
1535
+ const handleTileHover = React7.useCallback(
1531
1536
  (tile, event) => {
1532
1537
  if (tile) {
1533
1538
  if (tileHoverEvent) {
@@ -1547,7 +1552,7 @@ function useGameCanvas3DEvents(options) {
1547
1552
  },
1548
1553
  [tileHoverEvent, tileLeaveEvent, emit]
1549
1554
  );
1550
- const handleUnitAnimation = React8.useCallback(
1555
+ const handleUnitAnimation = React7.useCallback(
1551
1556
  (unitId, state) => {
1552
1557
  if (unitAnimationEvent) {
1553
1558
  emit(unitAnimationEvent, {
@@ -1560,7 +1565,7 @@ function useGameCanvas3DEvents(options) {
1560
1565
  },
1561
1566
  [unitAnimationEvent, emit]
1562
1567
  );
1563
- const handleCameraChange = React8.useCallback(
1568
+ const handleCameraChange = React7.useCallback(
1564
1569
  (position) => {
1565
1570
  if (cameraChangeEvent) {
1566
1571
  emit(cameraChangeEvent, {
@@ -1582,131 +1587,37 @@ function useGameCanvas3DEvents(options) {
1582
1587
  };
1583
1588
  }
1584
1589
 
1585
- // components/game/shared/spriteAnimation.ts
1586
- function resolveSheetDirection(facing) {
1587
- switch (facing) {
1588
- case "se":
1589
- return { sheetDir: "se", flipX: false };
1590
- case "sw":
1591
- return { sheetDir: "sw", flipX: false };
1592
- case "ne":
1593
- return { sheetDir: "sw", flipX: true };
1594
- case "nw":
1595
- return { sheetDir: "se", flipX: true };
1590
+ // lib/drawable/hitTest.ts
1591
+ function collectDrawnItems(nodes) {
1592
+ const out = [];
1593
+ for (const n of nodes) {
1594
+ switch (n.type) {
1595
+ case "draw-sprite":
1596
+ case "draw-shape":
1597
+ case "draw-text":
1598
+ out.push({ pos: n.position, id: n.id });
1599
+ break;
1600
+ case "draw-sprite-layer":
1601
+ case "draw-shape-layer":
1602
+ case "draw-text-layer":
1603
+ for (const it of n.items) out.push({ pos: it.position, id: it.id });
1604
+ break;
1605
+ }
1596
1606
  }
1607
+ return out;
1597
1608
  }
1598
- function frameRect(frame, row, columns, frameWidth, frameHeight) {
1599
- return {
1600
- sx: frame % columns * frameWidth,
1601
- sy: row * frameHeight,
1602
- sw: frameWidth,
1603
- sh: frameHeight
1604
- };
1605
- }
1606
-
1607
- // components/game/shared/hooks/useUnitSpriteAtlas.ts
1608
- function unitAtlasUrl(unit) {
1609
- return unit.spriteSheet?.url ?? null;
1610
- }
1611
- function resolveSheetUrl(atlasUrl, relativeSheetPath) {
1612
- try {
1613
- return new URL(relativeSheetPath, atlasUrl).toString();
1614
- } catch {
1615
- const base = atlasUrl.slice(0, atlasUrl.lastIndexOf("/") + 1);
1616
- return `${base}${relativeSheetPath.replace(/^\.\//, "")}`;
1609
+ function buildHitIndex(items) {
1610
+ const m = /* @__PURE__ */ new Map();
1611
+ for (const it of items) {
1612
+ if (it.id !== void 0) m.set(`${it.pos.x},${it.pos.y}`, it.id);
1617
1613
  }
1614
+ return m;
1618
1615
  }
1619
- function useUnitSpriteAtlas(units) {
1620
- const atlasCacheRef = React8.useRef(/* @__PURE__ */ new Map());
1621
- const loadingRef = React8.useRef(/* @__PURE__ */ new Set());
1622
- const [pendingCount, setPendingCount] = React8.useState(0);
1623
- const [, forceTick] = React8.useState(0);
1624
- const atlasUrls = React8.useMemo(() => {
1625
- const set = /* @__PURE__ */ new Set();
1626
- for (const unit of units) {
1627
- const url = unitAtlasUrl(unit);
1628
- if (url) set.add(url);
1629
- }
1630
- return [...set];
1631
- }, [units]);
1632
- React8.useEffect(() => {
1633
- const cache = atlasCacheRef.current;
1634
- const loading = loadingRef.current;
1635
- const toLoad = atlasUrls.filter((url) => !cache.has(url) && !loading.has(url));
1636
- if (toLoad.length === 0) return;
1637
- let cancelled = false;
1638
- setPendingCount((prev) => prev + toLoad.length);
1639
- for (const url of toLoad) {
1640
- loading.add(url);
1641
- fetch(url).then((res) => res.ok ? res.json() : Promise.reject(new Error(String(res.status)))).then((atlas) => {
1642
- if (cancelled) return;
1643
- cache.set(url, atlas);
1644
- }).catch(() => {
1645
- }).finally(() => {
1646
- if (cancelled) return;
1647
- loading.delete(url);
1648
- setPendingCount((prev) => Math.max(0, prev - 1));
1649
- forceTick((n) => n + 1);
1650
- });
1651
- }
1652
- return () => {
1653
- cancelled = true;
1654
- };
1655
- }, [atlasUrls]);
1656
- const sheetUrls = React8.useMemo(() => {
1657
- const urls = /* @__PURE__ */ new Set();
1658
- for (const unit of units) {
1659
- const atlasUrl = unitAtlasUrl(unit);
1660
- if (!atlasUrl) continue;
1661
- const atlas = atlasCacheRef.current.get(atlasUrl);
1662
- if (!atlas) continue;
1663
- for (const rel of Object.values(atlas.sheets)) {
1664
- if (rel) urls.add(resolveSheetUrl(atlasUrl, rel));
1665
- }
1666
- }
1667
- return [...urls];
1668
- }, [units, pendingCount]);
1669
- const resolveUnitFrame = React8.useCallback((unitId) => {
1670
- const unit = units.find((u) => u.id === unitId);
1671
- if (!unit) return null;
1672
- const atlasUrl = unitAtlasUrl(unit);
1673
- if (!atlasUrl) return null;
1674
- const atlas = atlasCacheRef.current.get(atlasUrl);
1675
- if (!atlas) return null;
1676
- const animation = unit.animation ?? "idle";
1677
- const frame = unit.frame ?? 0;
1678
- const def = atlas.animations[animation] ?? atlas.animations.idle;
1679
- if (!def) return null;
1680
- const { sheetDir, flipX } = resolveSheetDirection("se");
1681
- const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
1682
- if (!rel) return null;
1683
- const sheetUrl = resolveSheetUrl(atlasUrl, rel);
1684
- const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
1685
- return {
1686
- sheetUrl,
1687
- sx: rect.sx,
1688
- sy: rect.sy,
1689
- sw: rect.sw,
1690
- sh: rect.sh,
1691
- flipX,
1692
- applyBreathing: animation === "idle"
1693
- };
1694
- }, [units]);
1695
- return { sheetUrls, resolveUnitFrame, pendingCount };
1696
- }
1697
- function cn(...inputs) {
1698
- return tailwindMerge.twMerge(clsx.clsx(inputs));
1699
- }
1700
- var DEFAULT_GRID_CONFIG = {
1701
- cellSize: 1,
1702
- offsetX: 0,
1703
- offsetZ: 0
1704
- };
1705
- function CameraController({
1616
+ function CameraController3D({
1706
1617
  onCameraChange
1707
1618
  }) {
1708
1619
  const { camera } = fiber.useThree();
1709
- React8.useEffect(() => {
1620
+ React7.useEffect(() => {
1710
1621
  if (onCameraChange) {
1711
1622
  onCameraChange({
1712
1623
  x: camera.position.x,
@@ -1717,54 +1628,13 @@ function CameraController({
1717
1628
  }, [camera.position, onCameraChange]);
1718
1629
  return null;
1719
1630
  }
1720
- function UnitSpriteBillboard({
1721
- sheetUrl,
1722
- resolveFrame,
1723
- height = 1.2
1724
- }) {
1725
- const texture = fiber.useLoader(THREE6__namespace.TextureLoader, sheetUrl);
1726
- const meshRef = React8.useRef(null);
1727
- const matRef = React8.useRef(null);
1728
- const [aspect, setAspect] = React8.useState(1);
1729
- fiber.useFrame(() => {
1730
- const frame = resolveFrame();
1731
- if (!frame || !texture.image) return;
1732
- const imgW = texture.image.width;
1733
- const imgH = texture.image.height;
1734
- if (!imgW || !imgH) return;
1735
- texture.repeat.set((frame.flipX ? -1 : 1) * (frame.sw / imgW), frame.sh / imgH);
1736
- texture.offset.set(
1737
- frame.flipX ? (frame.sx + frame.sw) / imgW : frame.sx / imgW,
1738
- 1 - (frame.sy + frame.sh) / imgH
1739
- );
1740
- texture.magFilter = THREE6__namespace.NearestFilter;
1741
- texture.minFilter = THREE6__namespace.NearestFilter;
1742
- texture.needsUpdate = true;
1743
- const nextAspect = frame.sw / frame.sh;
1744
- if (Math.abs(nextAspect - aspect) > 1e-3) setAspect(nextAspect);
1745
- if (matRef.current) matRef.current.needsUpdate = true;
1746
- });
1747
- return /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ref: meshRef, position: [0, height / 2, 0], children: [
1748
- /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [height * aspect, height] }),
1749
- /* @__PURE__ */ jsxRuntime.jsx(
1750
- "meshBasicMaterial",
1751
- {
1752
- ref: matRef,
1753
- map: texture,
1754
- transparent: true,
1755
- alphaTest: 0.1,
1756
- side: THREE6__namespace.DoubleSide
1757
- }
1758
- )
1759
- ] });
1760
- }
1761
- function FollowCamera({
1631
+ function FollowCamera3D({
1762
1632
  target,
1763
1633
  offset
1764
1634
  }) {
1765
1635
  const { camera } = fiber.useThree();
1766
- const look = React8.useRef(new THREE6__namespace.Vector3(target[0], target[1], target[2]));
1767
- const goal = React8.useRef(new THREE6__namespace.Vector3());
1636
+ const look = React7.useRef(new THREE6__namespace.Vector3(target[0], target[1], target[2]));
1637
+ const goal = React7.useRef(new THREE6__namespace.Vector3());
1768
1638
  fiber.useFrame((_, delta) => {
1769
1639
  const t = Math.min(1, delta * 5);
1770
1640
  goal.current.set(target[0] + offset[0], target[1] + offset[1], target[2] + offset[2]);
@@ -1774,371 +1644,156 @@ function FollowCamera({
1774
1644
  });
1775
1645
  return null;
1776
1646
  }
1777
- function LerpedGroup({
1778
- target,
1779
- enabled,
1780
- children
1781
- }) {
1782
- const ref = React8.useRef(null);
1783
- const goal = React8.useRef(new THREE6__namespace.Vector3());
1784
- fiber.useFrame((_, delta) => {
1785
- const g = ref.current;
1786
- if (!g) return;
1787
- goal.current.set(target[0], target[1], target[2]);
1788
- if (enabled) g.position.lerp(goal.current, Math.min(1, delta * 10));
1789
- else g.position.copy(goal.current);
1790
- });
1791
- return /* @__PURE__ */ jsxRuntime.jsx("group", { ref, position: target, children });
1792
- }
1793
- var SIDE_PLATFORM_COLORS = {
1794
- ground: "#5b8c3e",
1795
- platform: "#8b5a2b",
1796
- hazard: "#cc4444",
1797
- goal: "#4488cc"
1798
- };
1799
- function SideScene({
1800
- player,
1801
- platforms,
1802
- worldHeight,
1803
- ppu,
1804
- playerSprite,
1805
- tileSprites,
1806
- interpolate,
1807
- features = [],
1808
- events = [],
1809
- assetManifest
1810
- }) {
1811
- const pw = player.width ?? 32;
1812
- const ph = player.height ?? 48;
1813
- const playerPos = [
1814
- (player.x + pw / 2) / ppu,
1815
- (worldHeight - player.y - ph) / ppu,
1816
- 0
1817
- ];
1818
- const playerH = Math.max(ph / ppu, 0.5);
1819
- return /* @__PURE__ */ jsxRuntime.jsxs("group", { children: [
1820
- platforms.map((p, i) => {
1821
- const platformType = p.type ?? "platform";
1822
- const model = tileSprites?.[platformType]?.url;
1823
- const topY = (worldHeight - p.y) / ppu;
1824
- if (model) {
1825
- const cells = Math.max(1, Math.round(p.width / ppu));
1826
- const x0 = p.x / ppu;
1827
- return /* @__PURE__ */ jsxRuntime.jsx("group", { children: Array.from({ length: cells }, (_, c) => /* @__PURE__ */ jsxRuntime.jsx("group", { position: [x0 + c + 0.5, topY - 0.475, 0], children: /* @__PURE__ */ jsxRuntime.jsx(ModelLoader, { url: model, scale: 0.95, fallbackGeometry: "box", castShadow: true, receiveShadow: true }) }, c)) }, `plat-${i}`);
1828
- }
1829
- return /* @__PURE__ */ jsxRuntime.jsxs(
1830
- "mesh",
1831
- {
1832
- position: [(p.x + p.width / 2) / ppu, topY - p.height / 2 / ppu, 0],
1833
- children: [
1834
- /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [p.width / ppu, p.height / ppu, 1] }),
1835
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: SIDE_PLATFORM_COLORS[platformType] ?? "#888888" })
1836
- ]
1837
- },
1838
- `plat-${i}`
1839
- );
1840
- }),
1841
- features.map((feature, i) => {
1842
- const model = feature.assetUrl ?? assetManifest?.features?.[feature.type];
1843
- const fx = feature.x / ppu;
1844
- const fy = (worldHeight - feature.y) / ppu;
1845
- if (!model?.url) return null;
1846
- return /* @__PURE__ */ jsxRuntime.jsx("group", { position: [fx, fy, 0], children: /* @__PURE__ */ jsxRuntime.jsx(ModelLoader, { url: model.url, scale: 0.6, tint: feature.color, fallbackGeometry: "sphere", castShadow: true }) }, feature.id ?? `sfeat-${i}`);
1847
- }),
1848
- events.map((ev, i) => /* @__PURE__ */ jsxRuntime.jsx(
1849
- EventMarker,
1647
+ function SpriteBillboard({ node, world }) {
1648
+ const texture = fiber.useLoader(THREE6__namespace.TextureLoader, node.asset.url);
1649
+ const img = texture.image;
1650
+ const imgW = img?.width || 1;
1651
+ const imgH = img?.height || 1;
1652
+ if (node.frame) {
1653
+ texture.repeat.set(node.frame.w / imgW, node.frame.h / imgH);
1654
+ texture.offset.set(node.frame.x / imgW, 1 - (node.frame.y + node.frame.h) / imgH);
1655
+ texture.magFilter = texture.minFilter = THREE6__namespace.NearestFilter;
1656
+ texture.needsUpdate = true;
1657
+ }
1658
+ const aspect = node.frame ? node.frame.w / node.frame.h : imgW / imgH;
1659
+ const height = node.height ?? 1;
1660
+ const width = node.width ?? height * aspect;
1661
+ return /* @__PURE__ */ jsxRuntime.jsx(drei.Billboard, { position: [world[0], world[1] + height / 2, world[2]], children: /* @__PURE__ */ jsxRuntime.jsxs("mesh", { children: [
1662
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [width, height] }),
1663
+ /* @__PURE__ */ jsxRuntime.jsx(
1664
+ "meshBasicMaterial",
1850
1665
  {
1851
- event: ev,
1852
- position: [ev.x / ppu, (worldHeight - (ev.y ?? 0)) / ppu + 0.8, 0.2]
1853
- },
1854
- ev.id ?? `sev-${i}`
1855
- )),
1856
- /* @__PURE__ */ jsxRuntime.jsx(LerpedGroup, { target: playerPos, enabled: interpolate, children: playerSprite?.url ? /* @__PURE__ */ jsxRuntime.jsx("group", { position: [0, playerH / 2, 0], children: /* @__PURE__ */ jsxRuntime.jsx(
1666
+ map: texture,
1667
+ transparent: true,
1668
+ alphaTest: 0.1,
1669
+ side: THREE6__namespace.DoubleSide,
1670
+ opacity: node.opacity ?? 1
1671
+ }
1672
+ )
1673
+ ] }) });
1674
+ }
1675
+ function Sprite3D({ node, projector }) {
1676
+ if (node.asset.dimension === "3d" && node.asset.url) {
1677
+ return /* @__PURE__ */ jsxRuntime.jsx("group", { position: projector.toWorld(node.position), children: /* @__PURE__ */ jsxRuntime.jsx(
1857
1678
  ModelLoader,
1858
1679
  {
1859
- url: playerSprite.url,
1860
- scale: playerH,
1861
- rotation: [0, player.facingRight ? 90 : -90, 0],
1862
- animation: player.animation ?? "idle",
1680
+ url: node.asset.url,
1681
+ scale: node.width ?? projector.cellSize,
1682
+ rotation: [0, node.rotation ?? 0, 0],
1863
1683
  fallbackGeometry: "box",
1864
- castShadow: true
1684
+ castShadow: true,
1685
+ receiveShadow: true
1865
1686
  }
1866
- ) }) : /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, playerH / 2, 0], children: [
1867
- /* @__PURE__ */ jsxRuntime.jsx("capsuleGeometry", { args: [playerH * 0.25, playerH * 0.5, 4, 8] }),
1868
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: "#4488ff" })
1869
- ] }) })
1687
+ ) });
1688
+ }
1689
+ if (!node.asset.url) return null;
1690
+ return /* @__PURE__ */ jsxRuntime.jsx(SpriteBillboard, { node, world: projector.toWorld(node.position) });
1691
+ }
1692
+ function Shape3D({ node, projector }) {
1693
+ const world = projector.toWorld(node.position);
1694
+ const color = node.fill ?? node.stroke ?? "#ffffff";
1695
+ let geometry;
1696
+ switch (node.shape) {
1697
+ case "cell":
1698
+ case "rect": {
1699
+ const w = node.shape === "cell" ? projector.cellSize * 0.95 : node.width ?? projector.cellSize;
1700
+ const h = node.shape === "cell" ? projector.cellSize * 0.95 : node.height ?? projector.cellSize;
1701
+ geometry = /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [w, h] });
1702
+ break;
1703
+ }
1704
+ case "ellipse": {
1705
+ const radiusX = node.radiusX ?? 0.4;
1706
+ geometry = node.stroke && !node.fill ? /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [Math.max(0, radiusX - 0.08), radiusX, 32] }) : /* @__PURE__ */ jsxRuntime.jsx("circleGeometry", { args: [radiusX, 32] });
1707
+ break;
1708
+ }
1709
+ case "poly": {
1710
+ if (!node.points || node.points.length === 0) return null;
1711
+ const s = new THREE6__namespace.Shape();
1712
+ node.points.forEach((p, i) => {
1713
+ if (i === 0) s.moveTo(p.x, p.y);
1714
+ else s.lineTo(p.x, p.y);
1715
+ });
1716
+ s.closePath();
1717
+ geometry = /* @__PURE__ */ jsxRuntime.jsx("shapeGeometry", { args: [s] });
1718
+ break;
1719
+ }
1720
+ default:
1721
+ return null;
1722
+ }
1723
+ return /* @__PURE__ */ jsxRuntime.jsxs("mesh", { rotation: [-Math.PI / 2, 0, 0], position: [world[0], world[1] + 0.02, world[2]], children: [
1724
+ geometry,
1725
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color, transparent: true, opacity: node.opacity ?? 1, side: THREE6__namespace.DoubleSide })
1870
1726
  ] });
1871
1727
  }
1872
- var UNIT_BASE_MODEL_SCALE = 0.5;
1873
- var UNIT_BASE_BILLBOARD_HEIGHT = 1.2;
1874
- var UNIT_BASE_PRIMITIVE_RADIUS = 0.3;
1875
- var TILE_TOP_OFFSET = 0.1;
1876
- var EVENT_COLORS = {
1877
- hit: "#ff5544",
1878
- damage: "#ff5544",
1879
- attack: "#ff8844",
1880
- heal: "#44dd66",
1881
- pickup: "#ffcc33",
1882
- score: "#ffcc33",
1883
- death: "#aa3333",
1884
- win: "#66ff88",
1885
- lose: "#ff6666"
1886
- };
1887
- function EventMarker({
1888
- event,
1889
- position
1890
- }) {
1891
- return /* @__PURE__ */ jsxRuntime.jsx(drei.Billboard, { position, children: /* @__PURE__ */ jsxRuntime.jsx(
1728
+ function Text3D({ node, projector }) {
1729
+ const world = projector.toWorld(node.position);
1730
+ return /* @__PURE__ */ jsxRuntime.jsx(drei.Billboard, { position: [world[0], world[1] + 1.2, world[2]], children: /* @__PURE__ */ jsxRuntime.jsx(
1892
1731
  drei.Text,
1893
1732
  {
1894
1733
  fontSize: 0.32,
1895
- color: EVENT_COLORS[event.type] ?? "#ffffff",
1896
- outlineWidth: 0.02,
1897
- outlineColor: "#000000",
1734
+ color: node.color,
1898
1735
  anchorX: "center",
1899
1736
  anchorY: "middle",
1900
- children: event.message ?? event.type
1737
+ outlineWidth: 0.02,
1738
+ outlineColor: "#000000",
1739
+ children: node.text
1901
1740
  }
1902
1741
  ) });
1903
1742
  }
1904
- function DefaultTile({
1905
- tile,
1906
- position,
1907
- model,
1908
- isSelected,
1909
- isHovered,
1910
- isValidMove,
1911
- isAttackTarget,
1912
- onTileClick,
1913
- onTileHover
1914
- }) {
1915
- let color = 8421504;
1916
- if (tile.type === "water") color = 4491468;
1917
- else if (tile.type === "grass") color = 4500036;
1918
- else if (tile.type === "sand") color = 14535816;
1919
- else if (tile.type === "rock") color = 8947848;
1920
- else if (tile.type === "snow") color = 15658734;
1921
- let emissive = 0;
1922
- if (isSelected) emissive = 4473924;
1923
- else if (isAttackTarget) emissive = 4456448;
1924
- else if (isValidMove) emissive = 17408;
1925
- else if (isHovered) emissive = 2236962;
1926
- if (model?.url) {
1927
- return /* @__PURE__ */ jsxRuntime.jsx(
1928
- "group",
1929
- {
1930
- position,
1931
- onClick: (e) => onTileClick(tile, e),
1932
- onPointerEnter: (e) => onTileHover(tile, e),
1933
- onPointerLeave: (e) => onTileHover(null, e),
1934
- userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
1935
- children: /* @__PURE__ */ jsxRuntime.jsx(
1936
- ModelLoader,
1937
- {
1938
- url: model.url,
1939
- scale: 0.95,
1940
- rotation: [0, tile.rotation ?? 0, 0],
1941
- fallbackGeometry: "box",
1942
- castShadow: true,
1943
- receiveShadow: true
1944
- }
1945
- )
1946
- }
1947
- );
1743
+ function Drawable3D({ node, projector }) {
1744
+ switch (node.type) {
1745
+ case "draw-sprite":
1746
+ return /* @__PURE__ */ jsxRuntime.jsx(Sprite3D, { node, projector });
1747
+ case "draw-shape":
1748
+ return /* @__PURE__ */ jsxRuntime.jsx(Shape3D, { node, projector });
1749
+ case "draw-text":
1750
+ return /* @__PURE__ */ jsxRuntime.jsx(Text3D, { node, projector });
1751
+ case "draw-sprite-layer":
1752
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(Sprite3D, { node: item, projector }, i)) });
1753
+ case "draw-shape-layer":
1754
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(Shape3D, { node: item, projector }, i)) });
1755
+ case "draw-text-layer":
1756
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: node.items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(Text3D, { node: item, projector }, i)) });
1948
1757
  }
1949
- return /* @__PURE__ */ jsxRuntime.jsxs(
1950
- "mesh",
1951
- {
1952
- position,
1953
- onClick: (e) => onTileClick(tile, e),
1954
- onPointerEnter: (e) => onTileHover(tile, e),
1955
- onPointerLeave: (e) => onTileHover(null, e),
1956
- userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
1957
- children: [
1958
- /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
1959
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color, emissive })
1960
- ]
1961
- }
1962
- );
1963
1758
  }
1964
- function DefaultUnit({
1965
- unit,
1966
- position,
1967
- model,
1968
- isSelected,
1969
- unitScale,
1970
- cellSize,
1971
- resolveUnitFrame,
1972
- onUnitClick
1973
- }) {
1974
- const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
1975
- const hasAtlas = unitAtlasUrl(unit) !== null;
1976
- const initialFrame = hasAtlas ? resolveUnitFrame(unit.id) : null;
1977
- const modelScale = UNIT_BASE_MODEL_SCALE * unitScale * cellSize;
1978
- const billboardHeight = UNIT_BASE_BILLBOARD_HEIGHT * unitScale * cellSize;
1979
- const primitiveRadius = UNIT_BASE_PRIMITIVE_RADIUS * unitScale * cellSize;
1980
- return /* @__PURE__ */ jsxRuntime.jsxs(
1981
- "group",
1982
- {
1983
- position,
1984
- onClick: (e) => onUnitClick(unit, e),
1985
- userData: { type: "unit", unitId: unit.id },
1986
- children: [
1987
- isSelected && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
1988
- /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
1989
- /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
1990
- ] }),
1991
- hasAtlas && initialFrame ? (
1992
- /* Animated sprite-sheet billboard — single cropped frame, by state */
1993
- /* @__PURE__ */ jsxRuntime.jsx(drei.Billboard, { children: /* @__PURE__ */ jsxRuntime.jsx(
1994
- UnitSpriteBillboard,
1995
- {
1996
- sheetUrl: initialFrame.sheetUrl,
1997
- resolveFrame: () => resolveUnitFrame(unit.id),
1998
- height: billboardHeight
1999
- }
2000
- ) })
2001
- ) : model?.url ? (
2002
- /* GLB unit model — LOLO's `animation` field drives the named clip;
2003
- `heading` (radians) faces the model along its travel direction (driving). */
2004
- /* @__PURE__ */ jsxRuntime.jsx(
2005
- ModelLoader,
2006
- {
2007
- url: model.url,
2008
- scale: modelScale,
2009
- rotation: [0, unit.heading ?? 0, 0],
2010
- animation: unit.animation ?? "idle",
2011
- fallbackGeometry: "box",
2012
- castShadow: true
2013
- }
2014
- )
2015
- ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2016
- /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, primitiveRadius, 0], children: [
2017
- /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [primitiveRadius, primitiveRadius, primitiveRadius * 0.33, 8] }),
2018
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
2019
- ] }),
2020
- /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, primitiveRadius * 2, 0], children: [
2021
- /* @__PURE__ */ jsxRuntime.jsx("capsuleGeometry", { args: [primitiveRadius * 0.67, primitiveRadius * 1.33, 4, 8] }),
2022
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
2023
- ] }),
2024
- /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, primitiveRadius * 3, 0], children: [
2025
- /* @__PURE__ */ jsxRuntime.jsx("sphereGeometry", { args: [primitiveRadius * 0.4, 8, 8] }),
2026
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
2027
- ] })
2028
- ] }),
2029
- unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("group", { position: [0, billboardHeight, 0], children: [
2030
- /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [-0.25, 0, 0], children: [
2031
- /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5, 0.05] }),
2032
- /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: 3355443 })
2033
- ] }),
2034
- /* @__PURE__ */ jsxRuntime.jsxs(
2035
- "mesh",
2036
- {
2037
- position: [
2038
- -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
2039
- 0,
2040
- 0.01
2041
- ],
2042
- children: [
2043
- /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
2044
- /* @__PURE__ */ jsxRuntime.jsx(
2045
- "meshBasicMaterial",
2046
- {
2047
- color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
2048
- }
2049
- )
2050
- ]
2051
- }
2052
- )
2053
- ] })
2054
- ]
2055
- }
2056
- );
1759
+
1760
+ // lib/drawable/projector3d.ts
1761
+ function create3DProjector(opts = {}) {
1762
+ const cellSize = opts.cellSize ?? 1;
1763
+ const offsetX = opts.offsetX ?? 0;
1764
+ const offsetZ = opts.offsetZ ?? 0;
1765
+ return {
1766
+ cellSize,
1767
+ toWorld: (pos) => [pos.x * cellSize + offsetX, pos.z ?? 0, pos.y * cellSize + offsetZ]
1768
+ };
2057
1769
  }
2058
- function DefaultFeature({
2059
- feature,
2060
- position,
2061
- model,
2062
- onFeatureClick
2063
- }) {
2064
- if (model?.url) {
2065
- return /* @__PURE__ */ jsxRuntime.jsx(
2066
- ModelLoader,
2067
- {
2068
- url: model.url,
2069
- position,
2070
- scale: 0.5,
2071
- rotation: [0, feature.rotation ?? 0, 0],
2072
- tint: feature.color,
2073
- onClick: () => onFeatureClick(feature, null),
2074
- fallbackGeometry: "box"
2075
- }
2076
- );
2077
- }
2078
- if (feature.type === "tree") {
2079
- return /* @__PURE__ */ jsxRuntime.jsxs(
2080
- "group",
2081
- {
2082
- position,
2083
- onClick: (e) => onFeatureClick(feature, e),
2084
- userData: { type: "feature", featureId: feature.id },
2085
- children: [
2086
- /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.4, 0], children: [
2087
- /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
2088
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 9127187 })
2089
- ] }),
2090
- /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
2091
- /* @__PURE__ */ jsxRuntime.jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
2092
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 2263842 })
2093
- ] })
2094
- ]
2095
- }
2096
- );
2097
- }
2098
- if (feature.type === "rock") {
2099
- return /* @__PURE__ */ jsxRuntime.jsxs(
2100
- "mesh",
2101
- {
2102
- position: [position[0], position[1] + 0.3, position[2]],
2103
- onClick: (e) => onFeatureClick(feature, e),
2104
- userData: { type: "feature", featureId: feature.id },
2105
- children: [
2106
- /* @__PURE__ */ jsxRuntime.jsx("dodecahedronGeometry", { args: [0.3, 0] }),
2107
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 8421504 })
2108
- ]
2109
- }
2110
- );
2111
- }
2112
- return null;
1770
+
1771
+ // components/game/three/game3dTheme.ts
1772
+ var GRID_COLORS_3D = {
1773
+ cell: "#444444",
1774
+ section: "#666666"
1775
+ };
1776
+ var DEFAULT_BACKGROUND_3D = "#1a1a2e";
1777
+ function cn(...inputs) {
1778
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
2113
1779
  }
2114
- var GameCanvas3D = React8.forwardRef(
1780
+ var DEFAULT_GRID_CONFIG = {
1781
+ cellSize: 1,
1782
+ offsetX: 0,
1783
+ offsetZ: 0
1784
+ };
1785
+ var GameCanvas3D = React7.forwardRef(
2115
1786
  ({
2116
- tiles = [],
2117
- units = [],
2118
- features = [],
2119
- events = [],
2120
- orientation = "standard",
2121
1787
  cameraMode = "isometric",
1788
+ followTarget,
2122
1789
  showGrid = true,
2123
- showCoordinates = false,
2124
- showTileInfo = false,
1790
+ orientation = "standard",
2125
1791
  overlay = "default",
2126
1792
  shadows = true,
2127
- backgroundColor = "#1a1a2e",
2128
- onTileClick,
2129
- onUnitClick,
2130
- onFeatureClick,
2131
- onCanvasClick,
2132
- onTileHover,
2133
- onUnitAnimation,
2134
- assetLoader: customAssetLoader,
2135
- tileRenderer: CustomTileRenderer,
2136
- unitRenderer: CustomUnitRenderer,
2137
- featureRenderer: CustomFeatureRenderer,
1793
+ backgroundColor = DEFAULT_BACKGROUND_3D,
2138
1794
  className,
2139
1795
  isLoading: externalLoading,
2140
1796
  error: externalError,
2141
- preloadAssets = [],
2142
1797
  tileClickEvent,
2143
1798
  unitClickEvent,
2144
1799
  featureClickEvent,
@@ -2148,31 +1803,17 @@ var GameCanvas3D = React8.forwardRef(
2148
1803
  unitAnimationEvent,
2149
1804
  cameraChangeEvent,
2150
1805
  loadingMessage = "Loading 3D Scene...",
2151
- useInstancing = true,
2152
- validMoves = [],
2153
- attackTargets = [],
2154
- selectedTileIds = [],
2155
- selectedUnitId = null,
2156
- unitScale = 1,
2157
1806
  keyMap,
2158
1807
  keyUpMap,
2159
- player,
2160
- platforms = [],
2161
- worldHeight = 400,
2162
- pixelsPerUnit = 32,
2163
- playerSprite,
2164
- tileSprites,
2165
- assetManifest,
2166
- interpolateUnits = false,
2167
- children
1808
+ children,
1809
+ drawables
2168
1810
  }, ref) => {
2169
- const containerRef = React8.useRef(null);
2170
- const controlsRef = React8.useRef(null);
2171
- const [hoveredTile, setHoveredTile] = React8.useState(null);
2172
- const [internalError, setInternalError] = React8.useState(null);
1811
+ const containerRef = React7.useRef(null);
1812
+ const controlsRef = React7.useRef(null);
1813
+ const [internalError, setInternalError] = React7.useState(null);
2173
1814
  const eventBus = useEventBus();
2174
- const keysRef = React8.useRef(/* @__PURE__ */ new Set());
2175
- React8.useEffect(() => {
1815
+ const keysRef = React7.useRef(/* @__PURE__ */ new Set());
1816
+ React7.useEffect(() => {
2176
1817
  if (!keyMap && !keyUpMap) return;
2177
1818
  const down = (e) => {
2178
1819
  if (keysRef.current.has(e.code)) return;
@@ -2195,35 +1836,6 @@ var GameCanvas3D = React8.forwardRef(
2195
1836
  window.removeEventListener("keyup", up);
2196
1837
  };
2197
1838
  }, [keyMap, keyUpMap, eventBus]);
2198
- React8.useEffect(() => {
2199
- const el = containerRef.current;
2200
- if (!el) return;
2201
- let node = el;
2202
- let depth = 0;
2203
- while (node && depth < 8) {
2204
- const cs = window.getComputedStyle(node);
2205
- const rect = node.getBoundingClientRect();
2206
- console.log("[almadar:ui:game:3d-height]", {
2207
- depth,
2208
- tag: node.tagName,
2209
- id: node.id || void 0,
2210
- className: node.className?.slice?.(0, 60) || void 0,
2211
- rectHeight: rect.height,
2212
- computedHeight: cs.height,
2213
- computedMinHeight: cs.minHeight,
2214
- computedFlex: cs.flex,
2215
- computedOverflow: cs.overflow
2216
- });
2217
- node = node.parentElement;
2218
- depth++;
2219
- }
2220
- }, []);
2221
- const { sheetUrls: atlasSheetUrls, resolveUnitFrame } = useUnitSpriteAtlas(units);
2222
- const preloadUrls = React8.useMemo(() => [...preloadAssets, ...atlasSheetUrls], [preloadAssets, atlasSheetUrls]);
2223
- const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
2224
- preloadUrls,
2225
- loader: customAssetLoader
2226
- });
2227
1839
  const eventHandlers = useGameCanvas3DEvents({
2228
1840
  tileClickEvent,
2229
1841
  unitClickEvent,
@@ -2232,35 +1844,36 @@ var GameCanvas3D = React8.forwardRef(
2232
1844
  tileHoverEvent,
2233
1845
  tileLeaveEvent,
2234
1846
  unitAnimationEvent,
2235
- cameraChangeEvent,
2236
- onTileClick,
2237
- onUnitClick,
2238
- onFeatureClick,
2239
- onCanvasClick,
2240
- onTileHover,
2241
- onUnitAnimation
1847
+ cameraChangeEvent
2242
1848
  });
2243
- const gridBounds = React8.useMemo(() => {
2244
- if (tiles.length === 0) {
1849
+ const drawnItems = React7.useMemo(() => collectDrawnItems(drawables ?? []), [drawables]);
1850
+ const scenePositions = React7.useMemo(() => drawnItems.map((i) => i.pos), [drawnItems]);
1851
+ const hitIndex = React7.useMemo(() => buildHitIndex(drawnItems), [drawnItems]);
1852
+ const gridBounds = React7.useMemo(() => {
1853
+ if (scenePositions.length === 0) {
2245
1854
  return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
2246
1855
  }
2247
- const xs = tiles.map((t) => t.x);
2248
- const zs = tiles.map((t) => t.z || t.y || 0);
2249
- return {
2250
- minX: Math.min(...xs),
2251
- maxX: Math.max(...xs),
2252
- minZ: Math.min(...zs),
2253
- maxZ: Math.max(...zs)
2254
- };
2255
- }, [tiles]);
2256
- const cameraTarget = React8.useMemo(() => {
2257
- return [
1856
+ let minX = Infinity;
1857
+ let maxX = -Infinity;
1858
+ let minZ = Infinity;
1859
+ let maxZ = -Infinity;
1860
+ for (const p of scenePositions) {
1861
+ if (p.x < minX) minX = p.x;
1862
+ if (p.x > maxX) maxX = p.x;
1863
+ if (p.y < minZ) minZ = p.y;
1864
+ if (p.y > maxZ) maxZ = p.y;
1865
+ }
1866
+ return { minX, maxX, minZ, maxZ };
1867
+ }, [scenePositions]);
1868
+ const cameraTarget = React7.useMemo(
1869
+ () => [
2258
1870
  (gridBounds.minX + gridBounds.maxX) / 2,
2259
1871
  0,
2260
1872
  (gridBounds.minZ + gridBounds.maxZ) / 2
2261
- ];
2262
- }, [gridBounds]);
2263
- const gridConfig = React8.useMemo(
1873
+ ],
1874
+ [gridBounds]
1875
+ );
1876
+ const gridConfig = React7.useMemo(
2264
1877
  () => ({
2265
1878
  ...DEFAULT_GRID_CONFIG,
2266
1879
  offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
@@ -2268,15 +1881,15 @@ var GameCanvas3D = React8.forwardRef(
2268
1881
  }),
2269
1882
  [gridBounds]
2270
1883
  );
2271
- const gridToWorld2 = React8.useCallback(
2272
- (x, z, y = 0) => {
2273
- const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
2274
- const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
2275
- return [worldX, y * gridConfig.cellSize, worldZ];
2276
- },
1884
+ const drawableProjector = React7.useMemo(
1885
+ () => create3DProjector({
1886
+ cellSize: gridConfig.cellSize,
1887
+ offsetX: -gridBounds.minX * gridConfig.cellSize,
1888
+ offsetZ: -gridBounds.minZ * gridConfig.cellSize
1889
+ }),
2277
1890
  [gridBounds, gridConfig]
2278
1891
  );
2279
- React8.useImperativeHandle(ref, () => ({
1892
+ React7.useImperativeHandle(ref, () => ({
2280
1893
  getCameraPosition: () => {
2281
1894
  if (controlsRef.current) {
2282
1895
  const pos = controlsRef.current.object.position;
@@ -2307,43 +1920,9 @@ var GameCanvas3D = React8.forwardRef(
2307
1920
  return canvas.toDataURL("image/png");
2308
1921
  }
2309
1922
  return null;
2310
- },
2311
- export: () => ({
2312
- tiles,
2313
- units,
2314
- features
2315
- })
1923
+ }
2316
1924
  }));
2317
- const handleTileClick = React8.useCallback(
2318
- (tile, event) => {
2319
- eventHandlers.handleTileClick(tile, event);
2320
- },
2321
- [eventHandlers]
2322
- );
2323
- const handleUnitClick = React8.useCallback(
2324
- (unit, event) => {
2325
- eventHandlers.handleUnitClick(unit, event);
2326
- },
2327
- [eventHandlers]
2328
- );
2329
- const handleFeatureClick = React8.useCallback(
2330
- (feature, event) => {
2331
- if (event) {
2332
- eventHandlers.handleFeatureClick(feature, event);
2333
- }
2334
- },
2335
- [eventHandlers]
2336
- );
2337
- const handleTileHover = React8.useCallback(
2338
- (tile, event) => {
2339
- setHoveredTile(tile);
2340
- if (event) {
2341
- eventHandlers.handleTileHover(tile, event);
2342
- }
2343
- },
2344
- [eventHandlers]
2345
- );
2346
- const cameraConfig = React8.useMemo(() => {
1925
+ const cameraConfig = React7.useMemo(() => {
2347
1926
  const size = Math.max(
2348
1927
  gridBounds.maxX - gridBounds.minX,
2349
1928
  gridBounds.maxZ - gridBounds.minZ,
@@ -2355,65 +1934,45 @@ var GameCanvas3D = React8.forwardRef(
2355
1934
  const d = size * 1;
2356
1935
  switch (cameraMode) {
2357
1936
  case "isometric":
2358
- return {
2359
- position: [cx + d, d * 0.8, cz + d],
2360
- fov: 45
2361
- };
1937
+ return { position: [cx + d, d * 0.8, cz + d], fov: 45 };
2362
1938
  case "top-down":
2363
- return {
2364
- position: [cx, d * 2, cz + d * 0.35],
2365
- fov: 45
2366
- };
1939
+ return { position: [cx, d * 2, cz + d * 0.35], fov: 45 };
2367
1940
  case "follow":
2368
- return {
2369
- position: [cx, d * 0.5, cz + d],
2370
- fov: 45
2371
- };
1941
+ return { position: [cx, d * 0.5, cz + d], fov: 45 };
2372
1942
  case "perspective":
2373
1943
  default:
2374
- return {
2375
- position: [cx + d, d, cz + d],
2376
- fov: 45
2377
- };
1944
+ return { position: [cx + d, d, cz + d], fov: 45 };
2378
1945
  }
2379
1946
  }, [cameraMode, gridBounds]);
2380
- const followTarget = React8.useMemo(() => {
2381
- if (player) {
2382
- return [
2383
- (player.x + (player.width ?? 32) / 2) / pixelsPerUnit,
2384
- (worldHeight - player.y - (player.height ?? 48) / 2) / pixelsPerUnit,
2385
- 0
2386
- ];
2387
- }
2388
- const selected = units.find((u) => u.id === selectedUnitId);
2389
- if (selected) {
2390
- const sx = selected.x ?? selected.position?.x ?? 0;
2391
- const sz = selected.z ?? selected.y ?? selected.position?.y ?? 0;
2392
- return [
2393
- (sx - gridBounds.minX) * gridConfig.cellSize,
2394
- 0,
2395
- (sz - gridBounds.minZ) * gridConfig.cellSize
2396
- ];
2397
- }
1947
+ const followWorld = React7.useMemo(() => {
1948
+ if (followTarget) return drawableProjector.toWorld(followTarget);
2398
1949
  return cameraTarget;
2399
- }, [player, pixelsPerUnit, worldHeight, units, selectedUnitId, gridBounds, gridConfig, cameraTarget]);
2400
- const followOffset = React8.useMemo(() => {
2401
- if (cameraMode === "chase") {
2402
- const selected = units.find((u) => u.id === selectedUnitId);
2403
- const h = selected?.heading ?? 0;
2404
- const dist = 7;
2405
- const height = 4;
2406
- return [-Math.sin(h) * dist, height, -Math.cos(h) * dist];
1950
+ }, [followTarget, drawableProjector, cameraTarget]);
1951
+ const followOffset = React7.useMemo(
1952
+ () => cameraMode === "chase" ? [0, 4, -7] : [5, 7, 5],
1953
+ [cameraMode]
1954
+ );
1955
+ const handleGroundClick = React7.useCallback((e) => {
1956
+ if (!tileClickEvent && !unitClickEvent) return;
1957
+ e.stopPropagation();
1958
+ const cell = {
1959
+ x: Math.round(e.point.x / gridConfig.cellSize + gridBounds.minX),
1960
+ y: Math.round(e.point.z / gridConfig.cellSize + gridBounds.minZ)
1961
+ };
1962
+ const hitId = hitIndex.get(`${cell.x},${cell.y}`);
1963
+ if (hitId !== void 0 && unitClickEvent) {
1964
+ eventBus.emit(`UI:${unitClickEvent}`, { unitId: hitId, x: cell.x, z: cell.y });
1965
+ } else if (tileClickEvent) {
1966
+ eventBus.emit(`UI:${tileClickEvent}`, { x: cell.x, z: cell.y });
2407
1967
  }
2408
- return player ? [0, 2, 9] : [5, 7, 5];
2409
- }, [cameraMode, units, selectedUnitId, player]);
2410
- if (externalLoading || assetsLoading && preloadAssets.length > 0) {
1968
+ }, [tileClickEvent, unitClickEvent, gridConfig, gridBounds, hitIndex, eventBus]);
1969
+ if (externalLoading) {
2411
1970
  return /* @__PURE__ */ jsxRuntime.jsx(
2412
1971
  Canvas3DLoadingState,
2413
1972
  {
2414
- progress,
2415
- loaded,
2416
- total,
1973
+ progress: 1,
1974
+ loaded: 1,
1975
+ total: 1,
2417
1976
  message: loadingMessage,
2418
1977
  className
2419
1978
  }
@@ -2431,7 +1990,7 @@ var GameCanvas3D = React8.forwardRef(
2431
1990
  {
2432
1991
  onError: (err) => setInternalError(err.message),
2433
1992
  onReset: () => setInternalError(null),
2434
- children: /* @__PURE__ */ jsxRuntime.jsxs(
1993
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2435
1994
  "div",
2436
1995
  {
2437
1996
  ref: containerRef,
@@ -2440,195 +1999,103 @@ var GameCanvas3D = React8.forwardRef(
2440
1999
  "data-orientation": orientation,
2441
2000
  "data-camera-mode": cameraMode,
2442
2001
  "data-overlay": overlay,
2443
- children: [
2444
- /* @__PURE__ */ jsxRuntime.jsxs(
2445
- fiber.Canvas,
2446
- {
2447
- shadows,
2448
- camera: {
2449
- position: cameraConfig.position,
2450
- fov: cameraConfig.fov,
2451
- near: 0.1,
2452
- far: 1e3
2453
- },
2454
- style: { background: backgroundColor, position: "absolute", inset: 0 },
2455
- onClick: (e) => {
2456
- if (e.target === e.currentTarget) {
2457
- eventHandlers.handleCanvasClick(e);
2002
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2003
+ fiber.Canvas,
2004
+ {
2005
+ shadows,
2006
+ camera: {
2007
+ position: cameraConfig.position,
2008
+ fov: cameraConfig.fov,
2009
+ near: 0.1,
2010
+ far: 1e3
2011
+ },
2012
+ style: { background: backgroundColor, position: "absolute", inset: 0 },
2013
+ onClick: (e) => {
2014
+ if (e.target === e.currentTarget) {
2015
+ eventHandlers.handleCanvasClick(e);
2016
+ }
2017
+ },
2018
+ children: [
2019
+ /* @__PURE__ */ jsxRuntime.jsx(CameraController3D, { onCameraChange: eventHandlers.handleCameraChange }),
2020
+ (cameraMode === "follow" || cameraMode === "chase") && /* @__PURE__ */ jsxRuntime.jsx(FollowCamera3D, { target: followWorld, offset: followOffset }),
2021
+ /* @__PURE__ */ jsxRuntime.jsx(
2022
+ Lighting3D,
2023
+ {
2024
+ shadows,
2025
+ shadowBias: -4e-4,
2026
+ shadowNormalBias: 0.04,
2027
+ shadowCameraSize: 5,
2028
+ shadowCameraNear: 0.5,
2029
+ shadowCameraFar: 500
2458
2030
  }
2459
- },
2460
- children: [
2461
- /* @__PURE__ */ jsxRuntime.jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
2462
- (cameraMode === "follow" || cameraMode === "chase") && /* @__PURE__ */ jsxRuntime.jsx(
2463
- FollowCamera,
2464
- {
2465
- target: followTarget,
2466
- offset: followOffset
2467
- }
2468
- ),
2469
- /* @__PURE__ */ jsxRuntime.jsx("ambientLight", { intensity: 0.6 }),
2470
- /* @__PURE__ */ jsxRuntime.jsx(
2471
- "directionalLight",
2472
- {
2473
- position: [10, 20, 10],
2474
- intensity: 0.8,
2475
- castShadow: shadows,
2476
- "shadow-mapSize": [2048, 2048],
2477
- "shadow-bias": -4e-4,
2478
- "shadow-normalBias": 0.04
2479
- }
2480
- ),
2481
- /* @__PURE__ */ jsxRuntime.jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
2482
- showGrid && /* @__PURE__ */ jsxRuntime.jsx(
2483
- drei.Grid,
2484
- {
2485
- args: [
2486
- Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
2487
- Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
2488
- ],
2489
- position: [
2490
- (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
2491
- 0,
2492
- (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
2493
- ],
2494
- cellSize: 1,
2495
- cellThickness: 1,
2496
- cellColor: "#444444",
2497
- sectionSize: 5,
2498
- sectionThickness: 1.5,
2499
- sectionColor: "#666666",
2500
- fadeDistance: 50,
2501
- fadeStrength: 1
2502
- }
2503
- ),
2504
- player ? (
2505
- /* Side-scroller mode — LOLO-owned pixel space mapped to world units */
2506
- /* @__PURE__ */ jsxRuntime.jsx(
2507
- SideScene,
2508
- {
2509
- player,
2510
- platforms,
2511
- worldHeight,
2512
- ppu: pixelsPerUnit,
2513
- playerSprite,
2514
- tileSprites,
2515
- interpolate: interpolateUnits,
2516
- features,
2517
- events,
2518
- assetManifest
2519
- }
2520
- )
2521
- ) : /* @__PURE__ */ jsxRuntime.jsxs("group", { children: [
2522
- tiles.map((tile, index) => {
2523
- const position = gridToWorld2(
2524
- tile.x,
2525
- tile.z ?? tile.y ?? 0,
2526
- tile.elevation ?? 0
2527
- );
2528
- const key = tile.id ?? `tile-${index}`;
2529
- if (CustomTileRenderer) {
2530
- return /* @__PURE__ */ jsxRuntime.jsx(CustomTileRenderer, { tile, position }, key);
2531
- }
2532
- return /* @__PURE__ */ jsxRuntime.jsx(
2533
- DefaultTile,
2534
- {
2535
- tile,
2536
- position,
2537
- model: tile.modelUrl ?? assetManifest?.terrains?.[tile.terrain ?? tile.type ?? ""],
2538
- isSelected: tile.id ? selectedTileIds.includes(tile.id) : false,
2539
- isHovered: hoveredTile?.id === tile.id,
2540
- isValidMove: validMoves.some((m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)),
2541
- isAttackTarget: attackTargets.some((m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)),
2542
- onTileClick: handleTileClick,
2543
- onTileHover: handleTileHover
2544
- },
2545
- key
2546
- );
2547
- }),
2548
- features.map((feature, index) => {
2549
- const position = gridToWorld2(
2550
- feature.x,
2551
- feature.z ?? feature.y ?? 0,
2552
- (feature.elevation ?? 0) + TILE_TOP_OFFSET
2553
- );
2554
- const key = feature.id ?? `feature-${index}`;
2555
- if (CustomFeatureRenderer) {
2556
- return /* @__PURE__ */ jsxRuntime.jsx(CustomFeatureRenderer, { feature, position }, key);
2557
- }
2558
- return /* @__PURE__ */ jsxRuntime.jsx(
2559
- DefaultFeature,
2560
- {
2561
- feature,
2562
- position,
2563
- model: feature.assetUrl ?? assetManifest?.features?.[feature.type],
2564
- onFeatureClick: handleFeatureClick
2565
- },
2566
- key
2567
- );
2568
- }),
2569
- units.map((unit) => {
2570
- const position = gridToWorld2(
2571
- unit.x ?? unit.position?.x ?? 0,
2572
- unit.z ?? unit.y ?? unit.position?.y ?? 0,
2573
- (unit.elevation ?? 0) + TILE_TOP_OFFSET
2574
- );
2575
- return /* @__PURE__ */ jsxRuntime.jsx(LerpedGroup, { target: position, enabled: interpolateUnits, children: CustomUnitRenderer ? /* @__PURE__ */ jsxRuntime.jsx(CustomUnitRenderer, { unit, position: [0, 0, 0] }) : /* @__PURE__ */ jsxRuntime.jsx(
2576
- DefaultUnit,
2031
+ ),
2032
+ showGrid && /* @__PURE__ */ jsxRuntime.jsx(
2033
+ drei.Grid,
2034
+ {
2035
+ args: [
2036
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
2037
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
2038
+ ],
2039
+ position: [
2040
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
2041
+ 0,
2042
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
2043
+ ],
2044
+ cellSize: 1,
2045
+ cellThickness: 1,
2046
+ cellColor: GRID_COLORS_3D.cell,
2047
+ sectionSize: 5,
2048
+ sectionThickness: 1.5,
2049
+ sectionColor: GRID_COLORS_3D.section,
2050
+ fadeDistance: 50,
2051
+ fadeStrength: 1
2052
+ }
2053
+ ),
2054
+ drawables && drawables.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("group", { children: drawables.map((node, i) => /* @__PURE__ */ jsxRuntime.jsx(Drawable3D, { node, projector: drawableProjector }, i)) }),
2055
+ (tileClickEvent || unitClickEvent) && /* @__PURE__ */ jsxRuntime.jsxs(
2056
+ "mesh",
2057
+ {
2058
+ rotation: [-Math.PI / 2, 0, 0],
2059
+ position: [
2060
+ (gridBounds.maxX - gridBounds.minX) / 2 * gridConfig.cellSize,
2061
+ 0,
2062
+ (gridBounds.maxZ - gridBounds.minZ) / 2 * gridConfig.cellSize
2063
+ ],
2064
+ onClick: handleGroundClick,
2065
+ children: [
2066
+ /* @__PURE__ */ jsxRuntime.jsx(
2067
+ "planeGeometry",
2577
2068
  {
2578
- unit,
2579
- position: [0, 0, 0],
2580
- model: unit.modelUrl ?? assetManifest?.units?.[unit.unitType ?? ""],
2581
- isSelected: selectedUnitId === unit.id,
2582
- unitScale,
2583
- cellSize: gridConfig.cellSize,
2584
- resolveUnitFrame,
2585
- onUnitClick: handleUnitClick
2069
+ args: [
2070
+ (gridBounds.maxX - gridBounds.minX + 4) * gridConfig.cellSize,
2071
+ (gridBounds.maxZ - gridBounds.minZ + 4) * gridConfig.cellSize
2072
+ ]
2586
2073
  }
2587
- ) }, unit.id);
2588
- }),
2589
- events.map((ev, i) => {
2590
- const position = gridToWorld2(ev.x, ev.z ?? ev.y ?? 0, 0);
2591
- return /* @__PURE__ */ jsxRuntime.jsx(
2592
- EventMarker,
2593
- {
2594
- event: ev,
2595
- position: [position[0], position[1] + 1.4, position[2]]
2596
- },
2597
- ev.id ?? `ev-${i}`
2598
- );
2599
- })
2600
- ] }),
2601
- children,
2602
- /* @__PURE__ */ jsxRuntime.jsx(
2603
- drei.OrbitControls,
2604
- {
2605
- ref: controlsRef,
2606
- enabled: cameraMode !== "follow" && cameraMode !== "chase",
2607
- target: cameraTarget,
2608
- enableDamping: true,
2609
- dampingFactor: 0.05,
2610
- enableZoom: true,
2611
- enablePan: true,
2612
- touches: { ONE: THREE6__namespace.TOUCH.ROTATE, TWO: THREE6__namespace.TOUCH.DOLLY_PAN },
2613
- minDistance: 2,
2614
- maxDistance: 100,
2615
- maxPolarAngle: Math.PI / 2 - 0.1
2616
- }
2617
- )
2618
- ]
2619
- }
2620
- ),
2621
- showCoordinates && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__coordinates", children: [
2622
- "X: ",
2623
- hoveredTile.x,
2624
- ", Z: ",
2625
- hoveredTile.z ?? hoveredTile.y ?? 0
2626
- ] }),
2627
- showTileInfo && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__tile-info", children: [
2628
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
2629
- hoveredTile.terrain && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
2630
- ] })
2631
- ]
2074
+ ),
2075
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { transparent: true, opacity: 0, depthWrite: false })
2076
+ ]
2077
+ }
2078
+ ),
2079
+ children,
2080
+ /* @__PURE__ */ jsxRuntime.jsx(
2081
+ drei.OrbitControls,
2082
+ {
2083
+ ref: controlsRef,
2084
+ enabled: cameraMode !== "follow" && cameraMode !== "chase",
2085
+ target: cameraTarget,
2086
+ enableDamping: true,
2087
+ dampingFactor: 0.05,
2088
+ enableZoom: true,
2089
+ enablePan: true,
2090
+ touches: { ONE: THREE6__namespace.TOUCH.ROTATE, TWO: THREE6__namespace.TOUCH.DOLLY_PAN },
2091
+ minDistance: 2,
2092
+ maxDistance: 100,
2093
+ maxPolarAngle: Math.PI / 2 - 0.1
2094
+ }
2095
+ )
2096
+ ]
2097
+ }
2098
+ )
2632
2099
  }
2633
2100
  )
2634
2101
  }
@@ -2636,1086 +2103,357 @@ var GameCanvas3D = React8.forwardRef(
2636
2103
  }
2637
2104
  );
2638
2105
  GameCanvas3D.displayName = "GameCanvas3D";
2639
- var DEFAULT_FAMILY = "lucide";
2640
- var VALID_FAMILIES = [
2641
- "lucide",
2642
- "phosphor-outline",
2643
- "phosphor-fill",
2644
- "phosphor-duotone",
2645
- "tabler",
2646
- "fa-solid"
2647
- ];
2648
- function getCurrentIconFamily() {
2649
- if (typeof window === "undefined" || typeof document === "undefined") {
2650
- return DEFAULT_FAMILY;
2651
- }
2652
- const raw = getComputedStyle(document.documentElement).getPropertyValue("--icon-family").trim().replace(/^["']|["']$/g, "");
2653
- return VALID_FAMILIES.includes(raw) ? raw : DEFAULT_FAMILY;
2654
- }
2655
- var cachedFamily = null;
2656
- var listeners = /* @__PURE__ */ new Set();
2657
- var observer = null;
2658
- function ensureObserver() {
2659
- if (typeof window === "undefined" || observer) return;
2660
- observer = new MutationObserver(() => {
2661
- const next = getCurrentIconFamily();
2662
- if (next !== cachedFamily) {
2663
- cachedFamily = next;
2664
- listeners.forEach((fn) => fn());
2665
- }
2666
- });
2667
- observer.observe(document.documentElement, {
2668
- attributes: true,
2669
- attributeFilter: ["data-theme", "style"]
2670
- });
2671
- cachedFamily = getCurrentIconFamily();
2672
- }
2673
- function subscribeIconFamily(notify) {
2674
- ensureObserver();
2675
- listeners.add(notify);
2676
- return () => {
2677
- listeners.delete(notify);
2678
- };
2679
- }
2680
- function getIconFamilySnapshot() {
2681
- if (cachedFamily !== null) return cachedFamily;
2682
- cachedFamily = getCurrentIconFamily();
2683
- return cachedFamily;
2684
- }
2685
- function getIconFamilyServerSnapshot() {
2686
- return DEFAULT_FAMILY;
2687
- }
2688
- function useIconFamily() {
2689
- return React8.useSyncExternalStore(
2690
- subscribeIconFamily,
2691
- getIconFamilySnapshot,
2692
- getIconFamilyServerSnapshot
2693
- );
2694
- }
2695
- function kebabToPascal(name) {
2696
- return name.split("-").map((part) => {
2697
- if (/^\d+$/.test(part)) return part;
2698
- return part.charAt(0).toUpperCase() + part.slice(1);
2699
- }).join("");
2700
- }
2701
- var libPromises = /* @__PURE__ */ new Map();
2702
- function loadLib(key, importer) {
2703
- let p = libPromises.get(key);
2704
- if (!p) {
2705
- p = importer();
2706
- libPromises.set(key, p);
2707
- }
2708
- return p;
2709
- }
2710
- function lazyFamilyIcon(libKey, importer, pick, fallbackName, family) {
2711
- const Lazy = React8__namespace.default.lazy(async () => {
2712
- const lib = await loadLib(libKey, importer);
2713
- const Comp = pick(lib);
2714
- if (!Comp) {
2715
- warnFallback(fallbackName, family);
2716
- return { default: makeLucideAdapter(fallbackName, true) };
2717
- }
2718
- return { default: Comp };
2719
- });
2720
- const Wrapped = (props) => /* @__PURE__ */ jsxRuntime.jsx(
2721
- React8__namespace.default.Suspense,
2722
- {
2723
- fallback: /* @__PURE__ */ jsxRuntime.jsx(
2724
- "span",
2725
- {
2726
- "aria-hidden": true,
2727
- className: props.className,
2728
- style: { display: "inline-block", ...props.style }
2729
- }
2730
- ),
2731
- children: /* @__PURE__ */ jsxRuntime.jsx(Lazy, { ...props })
2732
- }
2733
- );
2734
- Wrapped.displayName = `Lazy.${libKey}.${fallbackName}`;
2735
- return Wrapped;
2736
- }
2737
- var lucideAliases = {
2738
- close: LucideIcons__namespace.X,
2739
- trash: LucideIcons__namespace.Trash2,
2740
- loader: LucideIcons__namespace.Loader2,
2741
- stop: LucideIcons__namespace.Square,
2742
- volume: LucideIcons__namespace.Volume2,
2743
- "volume-off": LucideIcons__namespace.VolumeX,
2744
- refresh: LucideIcons__namespace.RefreshCw,
2745
- share: LucideIcons__namespace.Share2,
2746
- "sort-asc": LucideIcons__namespace.ArrowUpNarrowWide,
2747
- "sort-desc": LucideIcons__namespace.ArrowDownNarrowWide
2748
- };
2749
- function isComponentLike(v) {
2750
- return v != null && (typeof v === "function" || typeof v === "object");
2751
- }
2752
- function resolveLucide(name) {
2753
- if (lucideAliases[name]) return lucideAliases[name];
2754
- const pascal = kebabToPascal(name);
2755
- const lucideMap = LucideIcons__namespace;
2756
- const direct = lucideMap[pascal];
2757
- if (isComponentLike(direct)) return direct;
2758
- const asIs = lucideMap[name];
2759
- if (isComponentLike(asIs)) return asIs;
2760
- return LucideIcons__namespace.HelpCircle;
2761
- }
2762
- var phosphorAliases = {
2763
- // lucide name → phosphor PascalCase name
2764
- // Actions
2765
- plus: "Plus",
2766
- minus: "Minus",
2767
- x: "X",
2768
- check: "Check",
2769
- close: "X",
2770
- edit: "PencilSimple",
2771
- pencil: "PencilSimple",
2772
- trash: "Trash",
2773
- save: "FloppyDisk",
2774
- copy: "Copy",
2775
- share: "Share",
2776
- send: "PaperPlaneRight",
2777
- download: "DownloadSimple",
2778
- upload: "UploadSimple",
2779
- archive: "Archive",
2780
- refresh: "ArrowsClockwise",
2781
- loader: "CircleNotch",
2782
- link: "Link",
2783
- paperclip: "Paperclip",
2784
- // Navigation
2785
- "chevron-down": "CaretDown",
2786
- "chevron-up": "CaretUp",
2787
- "chevron-left": "CaretLeft",
2788
- "chevron-right": "CaretRight",
2789
- "arrow-up": "ArrowUp",
2790
- "arrow-down": "ArrowDown",
2791
- "arrow-left": "ArrowLeft",
2792
- "arrow-right": "ArrowRight",
2793
- menu: "List",
2794
- more: "DotsThree",
2795
- "more-vertical": "DotsThreeVertical",
2796
- "more-horizontal": "DotsThree",
2797
- external: "ArrowSquareOut",
2798
- "external-link": "ArrowSquareOut",
2799
- // Files
2800
- file: "File",
2801
- "file-text": "FileText",
2802
- "file-plus": "FilePlus",
2803
- "file-minus": "FileMinus",
2804
- folder: "Folder",
2805
- "folder-open": "FolderOpen",
2806
- document: "FileText",
2807
- // Charts
2808
- "bar-chart": "ChartBar",
2809
- "bar-chart-2": "ChartBar",
2810
- "bar-chart-3": "ChartBar",
2811
- "line-chart": "ChartLine",
2812
- "pie-chart": "ChartPie",
2813
- activity: "Pulse",
2814
- "trending-up": "TrendUp",
2815
- "trending-down": "TrendDown",
2816
- // Messages
2817
- message: "ChatCircle",
2818
- "message-circle": "ChatCircle",
2819
- "message-square": "ChatText",
2820
- "messages-square": "ChatsCircle",
2821
- comment: "ChatCircle",
2822
- comments: "ChatsCircle",
2823
- inbox: "Tray",
2824
- mail: "Envelope",
2825
- envelope: "Envelope",
2826
- // Status
2827
- "alert-circle": "WarningCircle",
2828
- "alert-triangle": "Warning",
2829
- "check-circle": "CheckCircle",
2830
- "x-circle": "XCircle",
2831
- info: "Info",
2832
- "help-circle": "Question",
2833
- "life-buoy": "Lifebuoy",
2834
- lifebuoy: "Lifebuoy",
2835
- warning: "Warning",
2836
- error: "WarningCircle",
2837
- // Media
2838
- image: "Image",
2839
- video: "VideoCamera",
2840
- film: "FilmStrip",
2841
- camera: "Camera",
2842
- music: "MusicNote",
2843
- play: "Play",
2844
- pause: "Pause",
2845
- stop: "Stop",
2846
- "skip-forward": "SkipForward",
2847
- "skip-back": "SkipBack",
2848
- volume: "SpeakerHigh",
2849
- "volume-2": "SpeakerHigh",
2850
- "volume-x": "SpeakerX",
2851
- mic: "Microphone",
2852
- "mic-off": "MicrophoneSlash",
2853
- // People
2854
- user: "User",
2855
- users: "Users",
2856
- "user-plus": "UserPlus",
2857
- "user-check": "UserCheck",
2858
- // Time
2859
- calendar: "Calendar",
2860
- clock: "Clock",
2861
- timer: "Timer",
2862
- // Location
2863
- map: "MapTrifold",
2864
- "map-pin": "MapPin",
2865
- navigation: "NavigationArrow",
2866
- compass: "Compass",
2867
- globe: "Globe",
2868
- target: "Target",
2869
- // Project / layout
2870
- kanban: "Kanban",
2871
- list: "List",
2872
- table: "Table",
2873
- grid: "GridFour",
2874
- layout: "Layout",
2875
- columns: "Columns",
2876
- rows: "Rows",
2877
- // Misc
2878
- bell: "Bell",
2879
- bookmark: "Bookmark",
2880
- briefcase: "Briefcase",
2881
- flag: "Flag",
2882
- tag: "Tag",
2883
- tags: "Tag",
2884
- star: "Star",
2885
- heart: "Heart",
2886
- home: "House",
2887
- settings: "Gear",
2888
- eye: "Eye",
2889
- "eye-off": "EyeSlash",
2890
- lock: "Lock",
2891
- unlock: "LockOpen",
2892
- key: "Key",
2893
- shield: "Shield",
2894
- search: "MagnifyingGlass",
2895
- filter: "Funnel",
2896
- "sort-asc": "SortAscending",
2897
- "sort-desc": "SortDescending",
2898
- zap: "Lightning",
2899
- sparkles: "Sparkle",
2900
- // Code
2901
- code: "Code",
2902
- terminal: "Terminal",
2903
- database: "Database",
2904
- server: "HardDrives",
2905
- cloud: "Cloud",
2906
- wifi: "WifiHigh",
2907
- package: "Package",
2908
- box: "Package",
2909
- // Theme
2910
- sun: "Sun",
2911
- moon: "Moon",
2912
- // Phone
2913
- phone: "Phone",
2914
- printer: "Printer",
2915
- // Hierarchy
2916
- tree: "Tree",
2917
- network: "Network"
2106
+ var DEFAULT_CONFIG = {
2107
+ cellSize: 1,
2108
+ offsetX: 0,
2109
+ offsetZ: 0,
2110
+ elevation: 0
2918
2111
  };
2919
- function resolvePhosphor(name, weight, family) {
2920
- const target = phosphorAliases[name] ?? kebabToPascal(name);
2921
- return lazyFamilyIcon(
2922
- "phosphor",
2923
- () => import('@phosphor-icons/react'),
2924
- (lib) => {
2925
- const PhosphorComp = lib[target];
2926
- if (!PhosphorComp) return null;
2927
- const Component2 = PhosphorComp;
2928
- const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
2929
- Component2,
2930
- {
2931
- weight,
2932
- className: props.className,
2933
- style: props.style,
2934
- size: props.size ?? "1em"
2935
- }
2936
- );
2937
- Adapter.displayName = `Phosphor.${target}.${weight}`;
2938
- return Adapter;
2939
- },
2940
- name,
2941
- family
2112
+ function gridToWorld(gridX, gridZ, config = DEFAULT_CONFIG) {
2113
+ const opts = { ...DEFAULT_CONFIG, ...config };
2114
+ return new THREE6__namespace.Vector3(
2115
+ gridX * opts.cellSize + opts.offsetX,
2116
+ opts.elevation,
2117
+ gridZ * opts.cellSize + opts.offsetZ
2942
2118
  );
2943
2119
  }
2944
- var tablerAliases = {
2945
- // lucide name tabler suffix (after the `Icon` prefix)
2946
- // Actions
2947
- plus: "Plus",
2948
- minus: "Minus",
2949
- x: "X",
2950
- check: "Check",
2951
- close: "X",
2952
- edit: "Pencil",
2953
- pencil: "Pencil",
2954
- trash: "Trash",
2955
- save: "DeviceFloppy",
2956
- copy: "Copy",
2957
- share: "Share",
2958
- send: "Send",
2959
- download: "Download",
2960
- upload: "Upload",
2961
- archive: "Archive",
2962
- refresh: "Refresh",
2963
- loader: "Loader2",
2964
- link: "Link",
2965
- paperclip: "Paperclip",
2966
- external: "ExternalLink",
2967
- "external-link": "ExternalLink",
2968
- // Navigation
2969
- "chevron-down": "ChevronDown",
2970
- "chevron-up": "ChevronUp",
2971
- "chevron-left": "ChevronLeft",
2972
- "chevron-right": "ChevronRight",
2973
- "arrow-down": "ArrowDown",
2974
- "arrow-up": "ArrowUp",
2975
- "arrow-left": "ArrowLeft",
2976
- "arrow-right": "ArrowRight",
2977
- menu: "Menu2",
2978
- more: "Dots",
2979
- "more-vertical": "DotsVertical",
2980
- // Files
2981
- file: "File",
2982
- "file-text": "FileText",
2983
- "file-plus": "FilePlus",
2984
- "file-check": "FileCheck",
2985
- "file-minus": "FileMinus",
2986
- folder: "Folder",
2987
- "folder-open": "FolderOpen",
2988
- document: "FileText",
2989
- // Charts
2990
- "bar-chart": "ChartBar",
2991
- "bar-chart-2": "ChartBar",
2992
- "bar-chart-3": "ChartBar",
2993
- "line-chart": "ChartLine",
2994
- "pie-chart": "ChartPie",
2995
- activity: "Activity",
2996
- "trending-up": "TrendingUp",
2997
- "trending-down": "TrendingDown",
2998
- // Messages
2999
- message: "Message",
3000
- "message-circle": "MessageCircle",
3001
- "message-square": "Message2",
3002
- "messages-square": "Messages",
3003
- comment: "Message",
3004
- comments: "Messages",
3005
- inbox: "Inbox",
3006
- mail: "Mail",
3007
- envelope: "Mail",
3008
- // Status
3009
- "alert-circle": "AlertCircle",
3010
- "alert-triangle": "AlertTriangle",
3011
- "check-circle": "CircleCheck",
3012
- "x-circle": "CircleX",
3013
- info: "InfoCircle",
3014
- "help-circle": "HelpCircle",
3015
- "life-buoy": "Lifebuoy",
3016
- warning: "AlertTriangle",
3017
- error: "AlertOctagon",
3018
- // Media
3019
- image: "Photo",
3020
- video: "Video",
3021
- camera: "Camera",
3022
- music: "Music",
3023
- play: "PlayerPlay",
3024
- pause: "PlayerPause",
3025
- stop: "PlayerStop",
3026
- "skip-forward": "PlayerSkipForward",
3027
- "skip-back": "PlayerSkipBack",
3028
- volume: "Volume",
3029
- "volume-2": "Volume",
3030
- "volume-x": "VolumeOff",
3031
- mic: "Microphone",
3032
- "mic-off": "MicrophoneOff",
3033
- // People
3034
- user: "User",
3035
- users: "Users",
3036
- "user-plus": "UserPlus",
3037
- "user-check": "UserCheck",
3038
- // Time
3039
- calendar: "Calendar",
3040
- clock: "Clock",
3041
- timer: "Hourglass",
3042
- // Location
3043
- map: "Map",
3044
- "map-pin": "MapPin",
3045
- navigation: "Navigation",
3046
- compass: "Compass",
3047
- globe: "World",
3048
- target: "Target",
3049
- // Project / layout
3050
- kanban: "LayoutKanban",
3051
- list: "List",
3052
- table: "Table",
3053
- grid: "LayoutGrid",
3054
- layout: "Layout",
3055
- columns: "LayoutColumns",
3056
- rows: "LayoutRows",
3057
- // Misc
3058
- bell: "Bell",
3059
- bookmark: "Bookmark",
3060
- briefcase: "Briefcase",
3061
- flag: "Flag",
3062
- tag: "Tag",
3063
- tags: "Tags",
3064
- star: "Star",
3065
- heart: "Heart",
3066
- home: "Home",
3067
- settings: "Settings",
3068
- eye: "Eye",
3069
- "eye-off": "EyeOff",
3070
- lock: "Lock",
3071
- unlock: "LockOpen",
3072
- key: "Key",
3073
- shield: "Shield",
3074
- search: "Search",
3075
- filter: "Filter",
3076
- "sort-asc": "SortAscending",
3077
- "sort-desc": "SortDescending",
3078
- zap: "Bolt",
3079
- sparkles: "Sparkles",
3080
- // Code / data
3081
- code: "Code",
3082
- terminal: "Terminal",
3083
- database: "Database",
3084
- server: "Server",
3085
- cloud: "Cloud",
3086
- wifi: "Wifi",
3087
- package: "Package",
3088
- box: "Box",
3089
- // Theme
3090
- sun: "Sun",
3091
- moon: "Moon",
3092
- // Phone
3093
- phone: "Phone",
3094
- printer: "Printer",
3095
- // Hierarchy
3096
- tree: "Hierarchy",
3097
- network: "Network"
3098
- };
3099
- function resolveTabler(name, family) {
3100
- const suffix = tablerAliases[name] ?? kebabToPascal(name);
3101
- const target = `Icon${suffix}`;
3102
- return lazyFamilyIcon(
3103
- "tabler",
3104
- () => import('@tabler/icons-react'),
3105
- (lib) => {
3106
- const TablerComp = lib[target];
3107
- if (!TablerComp) return null;
3108
- const Component2 = TablerComp;
3109
- const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
3110
- Component2,
3111
- {
3112
- stroke: props.strokeWidth ?? 1.5,
3113
- className: props.className,
3114
- style: props.style,
3115
- size: props.size ?? 24
3116
- }
3117
- );
3118
- Adapter.displayName = `Tabler.${target}`;
3119
- return Adapter;
3120
- },
3121
- name,
3122
- family
3123
- );
2120
+ function worldToGrid(worldX, worldZ, config = DEFAULT_CONFIG) {
2121
+ const opts = { ...DEFAULT_CONFIG, ...config };
2122
+ return {
2123
+ x: Math.round((worldX - opts.offsetX) / opts.cellSize),
2124
+ z: Math.round((worldZ - opts.offsetZ) / opts.cellSize)
2125
+ };
3124
2126
  }
3125
- var faAliases = {
3126
- // lucide name fa-solid suffix (after the `Fa` prefix).
3127
- // react-icons/fa ships FontAwesome 5 — names like `FaFileText` don't exist
3128
- // (FA renamed to `FaFileAlt`). When you see a console.warn from
3129
- // [iconFamily] about an unmapped lucide name in this family, add the
3130
- // closest FA5 sibling here so the fallback stays in-family.
3131
- search: "Search",
3132
- close: "Times",
3133
- x: "Times",
3134
- loader: "Spinner",
3135
- refresh: "Sync",
3136
- "sort-asc": "SortAmountUp",
3137
- "sort-desc": "SortAmountDown",
3138
- "chevron-down": "ChevronDown",
3139
- "chevron-up": "ChevronUp",
3140
- "chevron-left": "ChevronLeft",
3141
- "chevron-right": "ChevronRight",
3142
- "help-circle": "QuestionCircle",
3143
- "alert-triangle": "ExclamationTriangle",
3144
- "alert-circle": "ExclamationCircle",
3145
- "check-circle": "CheckCircle",
3146
- "x-circle": "TimesCircle",
3147
- edit: "Edit",
3148
- pencil: "PencilAlt",
3149
- trash: "Trash",
3150
- send: "PaperPlane",
3151
- share: "ShareAlt",
3152
- external: "ExternalLinkAlt",
3153
- plus: "Plus",
3154
- minus: "Minus",
3155
- check: "Check",
3156
- star: "Star",
3157
- heart: "Heart",
3158
- home: "Home",
3159
- user: "User",
3160
- users: "Users",
3161
- "user-plus": "UserPlus",
3162
- "user-check": "UserCheck",
3163
- settings: "Cog",
3164
- menu: "Bars",
3165
- "arrow-up": "ArrowUp",
3166
- "arrow-down": "ArrowDown",
3167
- "arrow-left": "ArrowLeft",
3168
- "arrow-right": "ArrowRight",
3169
- copy: "Copy",
3170
- download: "Download",
3171
- upload: "Upload",
3172
- filter: "Filter",
3173
- calendar: "Calendar",
3174
- clock: "Clock",
3175
- bell: "Bell",
3176
- mail: "Envelope",
3177
- envelope: "Envelope",
3178
- lock: "Lock",
3179
- unlock: "LockOpen",
3180
- eye: "Eye",
3181
- "eye-off": "EyeSlash",
3182
- more: "EllipsisH",
3183
- "more-vertical": "EllipsisV",
3184
- info: "InfoCircle",
3185
- warning: "ExclamationTriangle",
3186
- error: "ExclamationCircle",
3187
- // Time
3188
- timer: "Hourglass",
3189
- // Files (FA renamed FileText → FileAlt)
3190
- file: "File",
3191
- "file-text": "FileAlt",
3192
- "file-plus": "FileMedical",
3193
- "file-minus": "FileExcel",
3194
- "file-check": "FileSignature",
3195
- document: "FileAlt",
3196
- // Charts (lucide BarChart2 / BarChart3 → FA ChartBar)
3197
- "bar-chart": "ChartBar",
3198
- "bar-chart-2": "ChartBar",
3199
- "bar-chart-3": "ChartBar",
3200
- "line-chart": "ChartLine",
3201
- "pie-chart": "ChartPie",
3202
- activity: "ChartLine",
3203
- "trending-up": "ChartLine",
3204
- "trending-down": "ChartLine",
3205
- // Messages (lucide MessageCircle/MessageSquare → FA CommentDots/CommentAlt)
3206
- message: "Comment",
3207
- "message-circle": "CommentDots",
3208
- "message-square": "CommentAlt",
3209
- "messages-square": "Comments",
3210
- comment: "Comment",
3211
- comments: "Comments",
3212
- inbox: "Inbox",
3213
- // Support / help
3214
- "life-buoy": "LifeRing",
3215
- lifebuoy: "LifeRing",
3216
- // Project / kanban (FA has no kanban; closest semantic is Tasks/Columns)
3217
- kanban: "Tasks",
3218
- columns: "Columns",
3219
- rows: "Bars",
3220
- layout: "ThLarge",
3221
- grid: "Th",
3222
- list: "List",
3223
- table: "Table",
3224
- // Storage / folders
3225
- folder: "Folder",
3226
- "folder-open": "FolderOpen",
3227
- archive: "Archive",
3228
- bookmark: "Bookmark",
3229
- briefcase: "Briefcase",
3230
- package: "Box",
3231
- box: "Box",
3232
- // Map / location
3233
- map: "Map",
3234
- "map-pin": "MapMarkerAlt",
3235
- navigation: "LocationArrow",
3236
- compass: "Compass",
3237
- globe: "Globe",
3238
- target: "Bullseye",
3239
- // Media
3240
- image: "Image",
3241
- video: "Video",
3242
- film: "Film",
3243
- camera: "Camera",
3244
- music: "Music",
3245
- play: "Play",
3246
- pause: "Pause",
3247
- stop: "Stop",
3248
- "skip-forward": "Forward",
3249
- "skip-back": "Backward",
3250
- volume: "VolumeUp",
3251
- "volume-2": "VolumeUp",
3252
- "volume-x": "VolumeMute",
3253
- mic: "Microphone",
3254
- "mic-off": "MicrophoneSlash",
3255
- phone: "Phone",
3256
- // Code / data
3257
- code: "Code",
3258
- terminal: "Terminal",
3259
- database: "Database",
3260
- server: "Server",
3261
- cloud: "Cloud",
3262
- wifi: "Wifi",
3263
- // Security
3264
- shield: "ShieldAlt",
3265
- key: "Key",
3266
- // Misc actions
3267
- printer: "Print",
3268
- save: "Save",
3269
- link: "Link",
3270
- unlink: "Unlink",
3271
- paperclip: "Paperclip",
3272
- flag: "Flag",
3273
- tag: "Tag",
3274
- tags: "Tags",
3275
- zap: "Bolt",
3276
- sparkles: "Magic",
3277
- // Theme
3278
- sun: "Sun",
3279
- moon: "Moon",
3280
- // Hierarchy (FA has no Tree icon for hierarchies; Sitemap is the org-chart icon)
3281
- tree: "Sitemap",
3282
- network: "NetworkWired"
3283
- };
3284
- function resolveFa(name, family) {
3285
- const suffix = faAliases[name] ?? kebabToPascal(name);
3286
- const target = `Fa${suffix}`;
3287
- return lazyFamilyIcon(
3288
- "fa",
3289
- () => import('react-icons/fa'),
3290
- (lib) => {
3291
- const FaComp = lib[target];
3292
- if (!FaComp) return null;
3293
- const Component2 = FaComp;
3294
- const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
3295
- Component2,
3296
- {
3297
- className: props.className,
3298
- style: props.style,
3299
- size: props.size ?? "1em"
3300
- }
3301
- );
3302
- Adapter.displayName = `Fa.${target}`;
3303
- return Adapter;
3304
- },
3305
- name,
3306
- family
3307
- );
2127
+ function raycastToPlane(camera, mouseX, mouseY, planeY = 0) {
2128
+ const raycaster = new THREE6__namespace.Raycaster();
2129
+ const mouse = new THREE6__namespace.Vector2(mouseX, mouseY);
2130
+ raycaster.setFromCamera(mouse, camera);
2131
+ const plane = new THREE6__namespace.Plane(new THREE6__namespace.Vector3(0, 1, 0), -planeY);
2132
+ const target = new THREE6__namespace.Vector3();
2133
+ const intersection = raycaster.ray.intersectPlane(plane, target);
2134
+ return intersection ? target : null;
3308
2135
  }
3309
- var warned = /* @__PURE__ */ new Set();
3310
- function warnFallback(name, family) {
3311
- const key = `${family}::${name}`;
3312
- if (warned.has(key)) return;
3313
- warned.add(key);
3314
- if (typeof console !== "undefined") {
3315
- console.warn(
3316
- `[iconFamily] No '${name}' mapping in family '${family}'; falling back to lucide. Add an alias in lib/iconFamily.ts.`
3317
- );
3318
- }
2136
+ function raycastToObjects(camera, mouseX, mouseY, objects) {
2137
+ const raycaster = new THREE6__namespace.Raycaster();
2138
+ const mouse = new THREE6__namespace.Vector2(mouseX, mouseY);
2139
+ raycaster.setFromCamera(mouse, camera);
2140
+ const intersects = raycaster.intersectObjects(objects, true);
2141
+ return intersects.length > 0 ? intersects[0] : null;
3319
2142
  }
3320
- function makeLucideAdapter(name, isFallback = false) {
3321
- const LucideComp = resolveLucide(name);
3322
- const Adapter = (props) => {
3323
- const stroke = props.strokeWidth ?? (isFallback ? 2 : void 0);
3324
- const style = isFallback ? { ...props.style ?? {}, strokeWidth: stroke ?? 2 } : props.style;
3325
- return /* @__PURE__ */ jsxRuntime.jsx(
3326
- LucideComp,
3327
- {
3328
- className: props.className,
3329
- strokeWidth: stroke,
3330
- style,
3331
- size: props.size
3332
- }
3333
- );
3334
- };
3335
- Adapter.displayName = `Lucide.${name}${isFallback ? ".fallback" : ""}`;
3336
- return Adapter;
2143
+ function gridDistance(a, b) {
2144
+ const dx = b.x - a.x;
2145
+ const dz = b.z - a.z;
2146
+ return Math.sqrt(dx * dx + dz * dz);
3337
2147
  }
3338
- function resolveIconForFamily(name, family) {
3339
- switch (family) {
3340
- case "lucide":
3341
- return makeLucideAdapter(name, false);
3342
- // Non-lucide families resolve to a lazy, Suspense-wrapped component that
3343
- // dynamic-imports the library on first render and falls back to lucide
3344
- // internally when the family lacks the icon (see lazyFamilyIcon).
3345
- case "phosphor-outline":
3346
- return resolvePhosphor(name, "regular", family);
3347
- case "phosphor-fill":
3348
- return resolvePhosphor(name, "fill", family);
3349
- case "phosphor-duotone":
3350
- return resolvePhosphor(name, "duotone", family);
3351
- case "tabler":
3352
- return resolveTabler(name, family);
3353
- case "fa-solid":
3354
- return resolveFa(name, family);
3355
- }
2148
+ function gridManhattanDistance(a, b) {
2149
+ return Math.abs(b.x - a.x) + Math.abs(b.z - a.z);
3356
2150
  }
3357
- var colorTokenClasses = {
3358
- primary: "text-primary",
3359
- secondary: "text-secondary",
3360
- success: "text-success",
3361
- warning: "text-warning",
3362
- error: "text-error",
3363
- muted: "text-muted-foreground"
3364
- };
3365
- var sizeClasses = {
3366
- xs: "w-3 h-3",
3367
- sm: "w-4 h-4",
3368
- md: "h-icon-default w-icon-default",
3369
- lg: "w-6 h-6",
3370
- xl: "w-8 h-8"
3371
- };
3372
- var animationClasses = {
3373
- none: "",
3374
- spin: "animate-spin",
3375
- pulse: "animate-pulse"
3376
- };
3377
- var Icon = ({
3378
- icon,
3379
- name,
3380
- size = "md",
3381
- color,
3382
- animation = "none",
3383
- className,
3384
- strokeWidth,
3385
- style
3386
- }) => {
3387
- const directIcon = typeof icon === "string" ? void 0 : icon;
3388
- const effectiveName = typeof icon === "string" ? icon : name;
3389
- const family = useIconFamily();
3390
- const RenderedComponent = React8__namespace.default.useMemo(() => {
3391
- if (directIcon) return null;
3392
- return effectiveName ? resolveIconForFamily(effectiveName, family) : null;
3393
- }, [directIcon, effectiveName, family]);
3394
- const effectiveStrokeWidth = strokeWidth ?? void 0;
3395
- const inlineStyle = {
3396
- ...effectiveStrokeWidth === void 0 ? { strokeWidth: "var(--icon-stroke-width, 2)" } : {},
3397
- ...style
3398
- };
3399
- const resolvedColor = color ? color in colorTokenClasses ? colorTokenClasses[color] : color : "text-current";
3400
- const composedClassName = cn(
3401
- sizeClasses[size],
3402
- animationClasses[animation],
3403
- resolvedColor,
3404
- className
3405
- );
3406
- if (directIcon) {
3407
- const Direct = directIcon;
3408
- return /* @__PURE__ */ jsxRuntime.jsx(
3409
- Direct,
3410
- {
3411
- className: composedClassName,
3412
- strokeWidth: effectiveStrokeWidth,
3413
- style: inlineStyle
3414
- }
3415
- );
3416
- }
3417
- if (RenderedComponent) {
3418
- return /* @__PURE__ */ jsxRuntime.jsx(
3419
- RenderedComponent,
3420
- {
3421
- className: composedClassName,
3422
- strokeWidth: effectiveStrokeWidth,
3423
- style: inlineStyle
3424
- }
2151
+ function getNeighbors(x, z, includeDiagonal = false) {
2152
+ const neighbors = [
2153
+ { x: x + 1, z },
2154
+ { x: x - 1, z },
2155
+ { x, z: z + 1 },
2156
+ { x, z: z - 1 }
2157
+ ];
2158
+ if (includeDiagonal) {
2159
+ neighbors.push(
2160
+ { x: x + 1, z: z + 1 },
2161
+ { x: x - 1, z: z - 1 },
2162
+ { x: x + 1, z: z - 1 },
2163
+ { x: x - 1, z: z + 1 }
3425
2164
  );
3426
2165
  }
3427
- const Fallback = LucideIcons__namespace.HelpCircle;
3428
- return /* @__PURE__ */ jsxRuntime.jsx(
3429
- Fallback,
3430
- {
3431
- className: composedClassName,
3432
- strokeWidth: effectiveStrokeWidth,
3433
- style: inlineStyle
3434
- }
3435
- );
3436
- };
3437
- Icon.displayName = "Icon";
3438
-
3439
- // lib/atlasSlice.ts
3440
- var atlasCache = /* @__PURE__ */ new Map();
3441
- function isTilesheet(a) {
3442
- return typeof a.tileWidth === "number";
2166
+ return neighbors;
3443
2167
  }
3444
- function getAtlas(url, onReady) {
3445
- if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
3446
- atlasCache.set(url, void 0);
3447
- fetch(url).then((r) => r.json()).then((json) => {
3448
- atlasCache.set(url, json);
3449
- onReady();
3450
- }).catch(() => {
3451
- atlasCache.set(url, null);
3452
- });
3453
- return void 0;
2168
+ function isInBounds(x, z, bounds) {
2169
+ return x >= bounds.minX && x <= bounds.maxX && z >= bounds.minZ && z <= bounds.maxZ;
3454
2170
  }
3455
- function subRectFor(atlas, sprite) {
3456
- if (isTilesheet(atlas)) {
3457
- let col;
3458
- let row;
3459
- if (sprite.includes(",")) {
3460
- const [c, r] = sprite.split(",").map((n) => Number(n.trim()));
3461
- col = c;
3462
- row = r;
3463
- } else {
3464
- const i = Number(sprite);
3465
- if (!Number.isFinite(i)) return null;
3466
- col = i % atlas.columns;
3467
- row = Math.floor(i / atlas.columns);
2171
+ function getCellsInRadius(centerX, centerZ, radius) {
2172
+ const cells = [];
2173
+ const radiusSquared = radius * radius;
2174
+ const minX = Math.floor(centerX - radius);
2175
+ const maxX = Math.ceil(centerX + radius);
2176
+ const minZ = Math.floor(centerZ - radius);
2177
+ const maxZ = Math.ceil(centerZ + radius);
2178
+ for (let x = minX; x <= maxX; x++) {
2179
+ for (let z = minZ; z <= maxZ; z++) {
2180
+ const dx = x - centerX;
2181
+ const dz = z - centerZ;
2182
+ if (dx * dx + dz * dz <= radiusSquared) {
2183
+ cells.push({ x, z });
2184
+ }
3468
2185
  }
3469
- if (!Number.isFinite(col) || !Number.isFinite(row) || col < 0 || row < 0 || col >= atlas.columns || row >= atlas.rows) return null;
3470
- const margin = atlas.margin ?? 0;
3471
- const spacing = atlas.spacing ?? 0;
3472
- return {
3473
- sx: margin + col * (atlas.tileWidth + spacing),
3474
- sy: margin + row * (atlas.tileHeight + spacing),
3475
- sw: atlas.tileWidth,
3476
- sh: atlas.tileHeight
3477
- };
3478
2186
  }
3479
- const st = atlas.subTextures[sprite];
3480
- if (!st) return null;
3481
- return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
3482
- }
3483
- function isAtlasAsset(asset) {
3484
- return !!asset && typeof asset.atlas === "string" && typeof asset.sprite === "string";
2187
+ return cells;
3485
2188
  }
3486
- var imageCache = /* @__PURE__ */ new Map();
3487
- var imageWaiters = /* @__PURE__ */ new Map();
3488
- function getSheetImage(url, onReady) {
3489
- const cached = imageCache.get(url);
3490
- if (cached) return cached;
3491
- (imageWaiters.get(url) ?? imageWaiters.set(url, /* @__PURE__ */ new Set()).get(url)).add(onReady);
3492
- if (!imageCache.has(url)) {
3493
- imageCache.set(url, null);
3494
- const img = new Image();
3495
- img.crossOrigin = "anonymous";
3496
- img.onload = () => {
3497
- imageCache.set(url, img);
3498
- imageWaiters.get(url)?.forEach((fn) => fn());
3499
- imageWaiters.delete(url);
3500
- };
3501
- img.src = url;
3502
- }
3503
- return null;
2189
+ function createGridHighlight(color = 16776960, opacity = 0.3) {
2190
+ const geometry = new THREE6__namespace.PlaneGeometry(0.95, 0.95);
2191
+ const material = new THREE6__namespace.MeshBasicMaterial({
2192
+ color,
2193
+ transparent: true,
2194
+ opacity,
2195
+ side: THREE6__namespace.DoubleSide
2196
+ });
2197
+ const mesh = new THREE6__namespace.Mesh(geometry, material);
2198
+ mesh.rotation.x = -Math.PI / 2;
2199
+ mesh.position.y = 0.01;
2200
+ return mesh;
3504
2201
  }
3505
- function AtlasImage({
3506
- asset,
3507
- size,
3508
- width,
3509
- height,
3510
- fill = false,
3511
- fit = "contain",
3512
- alt,
3513
- className,
3514
- style,
3515
- "aria-hidden": ariaHidden
3516
- }) {
3517
- const [, bump] = React8__namespace.useReducer((x) => x + 1, 0);
3518
- const canvasRef = React8__namespace.useRef(null);
3519
- const sliced = isAtlasAsset(asset);
3520
- const atlas = sliced ? getAtlas(asset.atlas, bump) : void 0;
3521
- const img = sliced && asset?.url ? getSheetImage(asset.url, bump) : null;
3522
- const rect = sliced && atlas ? subRectFor(atlas, asset.sprite) : null;
3523
- React8__namespace.useEffect(() => {
3524
- const canvas = canvasRef.current;
3525
- if (!canvas || !img || !rect) return;
3526
- canvas.width = rect.sw;
3527
- canvas.height = rect.sh;
3528
- const ctx = canvas.getContext("2d");
3529
- if (!ctx) return;
3530
- ctx.imageSmoothingEnabled = false;
3531
- ctx.clearRect(0, 0, rect.sw, rect.sh);
3532
- ctx.drawImage(img, rect.sx, rect.sy, rect.sw, rect.sh, 0, 0, rect.sw, rect.sh);
3533
- }, [img, rect?.sx, rect?.sy, rect?.sw, rect?.sh]);
3534
- const w = fill ? "100%" : width ?? size;
3535
- const h = fill ? "100%" : height ?? size;
3536
- const boxStyle = {
3537
- ...fill ? { position: "absolute", inset: 0 } : {},
3538
- width: w,
3539
- height: h,
3540
- objectFit: fit,
3541
- imageRendering: "pixelated",
3542
- ...style
2202
+ function normalizeMouseCoordinates(clientX, clientY, element) {
2203
+ const rect = element.getBoundingClientRect();
2204
+ return {
2205
+ x: (clientX - rect.left) / rect.width * 2 - 1,
2206
+ y: -((clientY - rect.top) / rect.height) * 2 + 1
3543
2207
  };
3544
- if (!asset?.url) return null;
3545
- if (sliced) {
3546
- if (!rect || !img) {
3547
- return /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: cn("inline-block flex-shrink-0", className), style: { ...boxStyle, objectFit: void 0 } });
3548
- }
3549
- return /* @__PURE__ */ jsxRuntime.jsx(
3550
- "canvas",
3551
- {
3552
- ref: canvasRef,
3553
- role: ariaHidden ? void 0 : "img",
3554
- "aria-hidden": ariaHidden,
3555
- "aria-label": ariaHidden ? void 0 : alt ?? asset.name ?? asset.category ?? "",
3556
- className: cn("flex-shrink-0", className),
3557
- style: boxStyle
3558
- }
3559
- );
3560
- }
3561
- return /* @__PURE__ */ jsxRuntime.jsx(
3562
- "img",
3563
- {
3564
- src: asset.url,
3565
- alt: alt ?? asset.name ?? asset.category ?? "",
3566
- "aria-hidden": ariaHidden,
3567
- ...typeof w === "number" ? { width: w } : {},
3568
- ...typeof h === "number" ? { height: h } : {},
3569
- className: cn("flex-shrink-0", className),
3570
- style: boxStyle
3571
- }
2208
+ }
2209
+ function useTapReveal(options = {}) {
2210
+ const { onReveal, onDismiss, refs, enabled = true } = options;
2211
+ const [revealed, setRevealed] = React7.useState(false);
2212
+ const onRevealRef = React7.useRef(onReveal);
2213
+ const onDismissRef = React7.useRef(onDismiss);
2214
+ onRevealRef.current = onReveal;
2215
+ onDismissRef.current = onDismiss;
2216
+ const reveal = React7.useCallback(() => {
2217
+ setRevealed((wasRevealed) => {
2218
+ if (!wasRevealed) onRevealRef.current?.();
2219
+ return true;
2220
+ });
2221
+ }, []);
2222
+ const dismiss = React7.useCallback(() => {
2223
+ setRevealed((wasRevealed) => {
2224
+ if (wasRevealed) onDismissRef.current?.();
2225
+ return false;
2226
+ });
2227
+ }, []);
2228
+ const onPointerDown = React7.useCallback(
2229
+ (e) => {
2230
+ if (!enabled || e.pointerType === "mouse") return;
2231
+ if (revealed) dismiss();
2232
+ else reveal();
2233
+ },
2234
+ [enabled, revealed, reveal, dismiss]
3572
2235
  );
2236
+ React7.useEffect(() => {
2237
+ if (!revealed || typeof document === "undefined") return;
2238
+ const onDocDown = (ev) => {
2239
+ const target = ev.target;
2240
+ if (!(target instanceof Node)) return;
2241
+ const inside = (refs ?? []).some((r) => r.current?.contains(target));
2242
+ if (!inside) dismiss();
2243
+ };
2244
+ const id = setTimeout(() => document.addEventListener("pointerdown", onDocDown), 0);
2245
+ return () => {
2246
+ clearTimeout(id);
2247
+ document.removeEventListener("pointerdown", onDocDown);
2248
+ };
2249
+ }, [revealed, refs, dismiss]);
2250
+ return { revealed, triggerProps: { onPointerDown }, reveal, dismiss };
3573
2251
  }
3574
- AtlasImage.displayName = "AtlasImage";
3575
- var variantStyles = {
3576
- primary: [
3577
- "bg-primary text-primary-foreground",
3578
- "border-none",
3579
- "shadow-sm",
3580
- "hover:bg-primary-hover hover:shadow-lg",
3581
- "active:scale-[var(--active-scale)] active:shadow-sm"
3582
- ].join(" "),
3583
- secondary: [
3584
- "bg-transparent text-accent",
3585
- "border border-accent",
3586
- "hover:bg-accent hover:text-white hover:border-accent",
3587
- "active:scale-[var(--active-scale)]"
3588
- ].join(" "),
3589
- ghost: [
3590
- "bg-transparent text-muted-foreground",
3591
- "border border-transparent",
3592
- "hover:text-primary-foreground hover:bg-primary hover:border-primary",
3593
- "active:scale-[var(--active-scale)]"
3594
- ].join(" "),
3595
- danger: [
3596
- "bg-surface text-error",
3597
- "border-[length:var(--border-width)] border-error",
3598
- "shadow-sm",
3599
- "hover:bg-error hover:text-error-foreground hover:shadow-lg",
3600
- "active:scale-[var(--active-scale)] active:shadow-sm"
3601
- ].join(" "),
3602
- success: [
3603
- "bg-surface text-success",
3604
- "border-[length:var(--border-width)] border-success",
3605
- "shadow-sm",
3606
- "hover:bg-success hover:text-success-foreground hover:shadow-lg",
3607
- "active:scale-[var(--active-scale)] active:shadow-sm"
3608
- ].join(" "),
3609
- warning: [
3610
- "bg-surface text-warning",
3611
- "border-[length:var(--border-width)] border-warning",
3612
- "shadow-sm",
3613
- "hover:bg-warning hover:text-warning-foreground hover:shadow-lg",
3614
- "active:scale-[var(--active-scale)] active:shadow-sm"
3615
- ].join(" "),
3616
- // "default" is an alias for secondary
3617
- default: [
3618
- "bg-secondary text-secondary-foreground",
3619
- "border-[length:var(--border-width-thin)] border-border",
3620
- "hover:bg-secondary-hover",
3621
- "active:scale-[var(--active-scale)]"
3622
- ].join(" ")
2252
+
2253
+ // components/core/atoms/Box.tsx
2254
+ var paddingStyles = {
2255
+ none: "p-0",
2256
+ xs: "p-1",
2257
+ sm: "p-2",
2258
+ md: "p-4",
2259
+ lg: "p-6",
2260
+ xl: "p-8",
2261
+ "2xl": "p-12"
3623
2262
  };
3624
- variantStyles.destructive = variantStyles.danger;
3625
- var sizeStyles = {
3626
- sm: "h-button-sm px-3 text-sm",
3627
- md: "h-button-md px-4 text-sm",
3628
- lg: "h-button-lg px-6 text-base"
2263
+ var paddingXStyles = {
2264
+ none: "px-0",
2265
+ xs: "px-1",
2266
+ sm: "px-2",
2267
+ md: "px-4",
2268
+ lg: "px-6",
2269
+ xl: "px-8",
2270
+ "2xl": "px-12"
3629
2271
  };
3630
- var iconSizeStyles = {
3631
- sm: "h-icon-default w-icon-default",
3632
- md: "h-icon-default w-icon-default",
3633
- lg: "h-icon-default w-icon-default"
2272
+ var paddingYStyles = {
2273
+ none: "py-0",
2274
+ xs: "py-1",
2275
+ sm: "py-2",
2276
+ md: "py-4",
2277
+ lg: "py-6",
2278
+ xl: "py-8",
2279
+ "2xl": "py-12"
3634
2280
  };
3635
- function isIconLike(v) {
3636
- return typeof v.render === "function";
3637
- }
3638
- function resolveIconProp(value, sizeClass) {
3639
- if (!value) return null;
3640
- if (typeof value === "string") {
3641
- return /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: value, className: sizeClass });
3642
- }
3643
- if (typeof value === "function") {
3644
- const IconComp = value;
3645
- return /* @__PURE__ */ jsxRuntime.jsx(IconComp, { className: sizeClass });
3646
- }
3647
- if (React8__namespace.default.isValidElement(value)) {
3648
- return value;
3649
- }
3650
- if (typeof value === "object" && value !== null && isIconLike(value)) {
3651
- const IconComp = value;
3652
- return /* @__PURE__ */ jsxRuntime.jsx(IconComp, { className: sizeClass });
3653
- }
3654
- return value;
3655
- }
3656
- var Button = React8__namespace.default.forwardRef(
3657
- ({
3658
- className,
3659
- variant = "primary",
3660
- size = "md",
3661
- isLoading = false,
3662
- disabled,
3663
- leftIcon,
3664
- rightIcon,
3665
- icon: iconProp,
3666
- iconRight: iconRightProp,
3667
- iconAsset,
3668
- action,
3669
- actionPayload,
3670
- label,
3671
- children,
3672
- onClick,
3673
- "data-testid": dataTestId,
3674
- ...props
3675
- }, ref) => {
3676
- const eventBus = useEventBus();
3677
- const leftIconValue = leftIcon || iconProp;
3678
- const rightIconValue = rightIcon || iconRightProp;
3679
- const px = size === "sm" ? 16 : size === "lg" ? 20 : 16;
3680
- const resolvedLeftIcon = iconAsset?.url ? /* @__PURE__ */ jsxRuntime.jsx(AtlasImage, { asset: iconAsset, size: px, alt: iconAsset.name ?? iconAsset.category ?? "", className: "flex-shrink-0" }) : resolveIconProp(leftIconValue, iconSizeStyles[size]);
3681
- const resolvedRightIcon = resolveIconProp(rightIconValue, iconSizeStyles[size]);
3682
- const handleClick = (e) => {
3683
- if (action) {
3684
- eventBus.emit(`UI:${action}`, actionPayload ?? {});
3685
- }
3686
- onClick?.(e);
3687
- };
3688
- return /* @__PURE__ */ jsxRuntime.jsxs(
3689
- "button",
3690
- {
3691
- ref,
3692
- disabled: disabled || isLoading,
3693
- className: cn(
3694
- "inline-flex items-center justify-center gap-2",
3695
- "font-medium",
3696
- "rounded-sm",
3697
- "cursor-pointer",
3698
- "transition-all duration-[var(--transition-normal)]",
3699
- "focus:outline-none focus:ring-[length:var(--focus-ring-width)] focus:ring-ring focus:ring-offset-[length:var(--focus-ring-offset)]",
3700
- "disabled:opacity-50 disabled:cursor-not-allowed",
3701
- variantStyles[variant],
3702
- sizeStyles[size],
3703
- className
3704
- ),
3705
- onClick: handleClick,
3706
- ...props,
3707
- "data-testid": dataTestId ?? (action ? `action-${action}` : void 0),
3708
- children: [
3709
- isLoading ? /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Loader2, { className: "h-icon-default w-icon-default animate-spin" }) : resolvedLeftIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: resolvedLeftIcon }),
3710
- children || label,
3711
- resolvedRightIcon && !isLoading && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: resolvedRightIcon })
3712
- ]
2281
+ var marginStyles = {
2282
+ none: "m-0",
2283
+ xs: "m-1",
2284
+ sm: "m-2",
2285
+ md: "m-4",
2286
+ lg: "m-6",
2287
+ xl: "m-8",
2288
+ "2xl": "m-12",
2289
+ auto: "m-auto"
2290
+ };
2291
+ var marginXStyles = {
2292
+ none: "mx-0",
2293
+ xs: "mx-1",
2294
+ sm: "mx-2",
2295
+ md: "mx-4",
2296
+ lg: "mx-6",
2297
+ xl: "mx-8",
2298
+ "2xl": "mx-12",
2299
+ auto: "mx-auto"
2300
+ };
2301
+ var marginYStyles = {
2302
+ none: "my-0",
2303
+ xs: "my-1",
2304
+ sm: "my-2",
2305
+ md: "my-4",
2306
+ lg: "my-6",
2307
+ xl: "my-8",
2308
+ "2xl": "my-12",
2309
+ auto: "my-auto"
2310
+ };
2311
+ var bgStyles = {
2312
+ transparent: "bg-transparent",
2313
+ primary: "bg-primary text-primary-foreground",
2314
+ secondary: "bg-secondary text-secondary-foreground",
2315
+ muted: "bg-muted text-foreground",
2316
+ accent: "bg-accent text-accent-foreground",
2317
+ surface: "bg-card",
2318
+ overlay: "bg-card/80 backdrop-blur-sm"
2319
+ };
2320
+ var roundedStyles = {
2321
+ none: "rounded-none",
2322
+ sm: "rounded-sm",
2323
+ md: "rounded-md",
2324
+ lg: "rounded-lg",
2325
+ xl: "rounded-xl",
2326
+ "2xl": "rounded-xl",
2327
+ full: "rounded-full"
2328
+ };
2329
+ var shadowStyles = {
2330
+ none: "shadow-none",
2331
+ sm: "shadow-sm",
2332
+ md: "shadow",
2333
+ lg: "shadow-lg",
2334
+ xl: "shadow-lg"
2335
+ };
2336
+ var displayStyles = {
2337
+ block: "block",
2338
+ inline: "inline",
2339
+ "inline-block": "inline-block",
2340
+ flex: "flex",
2341
+ "inline-flex": "inline-flex",
2342
+ grid: "grid"
2343
+ };
2344
+ var overflowStyles = {
2345
+ auto: "overflow-auto",
2346
+ hidden: "overflow-hidden",
2347
+ visible: "overflow-visible",
2348
+ scroll: "overflow-scroll"
2349
+ };
2350
+ var positionStyles = {
2351
+ relative: "relative",
2352
+ absolute: "absolute",
2353
+ fixed: "fixed",
2354
+ sticky: "sticky"
2355
+ };
2356
+ var Box = React7__default.default.forwardRef(
2357
+ ({
2358
+ padding,
2359
+ paddingX,
2360
+ paddingY,
2361
+ margin,
2362
+ marginX,
2363
+ marginY,
2364
+ bg = "transparent",
2365
+ border = false,
2366
+ rounded = "none",
2367
+ shadow = "none",
2368
+ display,
2369
+ fullWidth = false,
2370
+ fullHeight = false,
2371
+ overflow,
2372
+ position,
2373
+ className,
2374
+ children,
2375
+ as: Component2 = "div",
2376
+ action,
2377
+ actionPayload,
2378
+ hoverEvent,
2379
+ tapReveal = true,
2380
+ maxWidth,
2381
+ onClick,
2382
+ onMouseEnter,
2383
+ onMouseLeave,
2384
+ onPointerDown,
2385
+ ...rest
2386
+ }, ref) => {
2387
+ const eventBus = useEventBus();
2388
+ const handleClick = React7.useCallback((e) => {
2389
+ if (action) {
2390
+ e.stopPropagation();
2391
+ eventBus.emit(`UI:${action}`, actionPayload ?? {});
2392
+ }
2393
+ onClick?.(e);
2394
+ }, [action, actionPayload, eventBus, onClick]);
2395
+ const handleMouseEnter = React7.useCallback((e) => {
2396
+ if (hoverEvent) {
2397
+ eventBus.emit(`UI:${hoverEvent}`, { hovered: true });
2398
+ }
2399
+ onMouseEnter?.(e);
2400
+ }, [hoverEvent, eventBus, onMouseEnter]);
2401
+ const handleMouseLeave = React7.useCallback((e) => {
2402
+ if (hoverEvent) {
2403
+ eventBus.emit(`UI:${hoverEvent}`, { hovered: false });
3713
2404
  }
2405
+ onMouseLeave?.(e);
2406
+ }, [hoverEvent, eventBus, onMouseLeave]);
2407
+ const { triggerProps } = useTapReveal({
2408
+ enabled: tapReveal && !!hoverEvent,
2409
+ onReveal: React7.useCallback(() => {
2410
+ if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: true });
2411
+ }, [hoverEvent, eventBus]),
2412
+ onDismiss: React7.useCallback(() => {
2413
+ if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: false });
2414
+ }, [hoverEvent, eventBus])
2415
+ });
2416
+ const handlePointerDown = React7.useCallback((e) => {
2417
+ if (hoverEvent && tapReveal) triggerProps.onPointerDown(e);
2418
+ onPointerDown?.(e);
2419
+ }, [hoverEvent, tapReveal, triggerProps, onPointerDown]);
2420
+ const isClickable = action || onClick;
2421
+ return React7__default.default.createElement(
2422
+ Component2,
2423
+ {
2424
+ ref,
2425
+ className: cn(
2426
+ padding && paddingStyles[padding],
2427
+ paddingX && paddingXStyles[paddingX],
2428
+ paddingY && paddingYStyles[paddingY],
2429
+ margin && marginStyles[margin],
2430
+ marginX && marginXStyles[marginX],
2431
+ marginY && marginYStyles[marginY],
2432
+ bgStyles[bg],
2433
+ border && "border-[length:var(--border-width)] border-border",
2434
+ roundedStyles[rounded],
2435
+ shadowStyles[shadow],
2436
+ display && displayStyles[display],
2437
+ fullWidth && "w-full",
2438
+ fullHeight && "h-full",
2439
+ overflow && overflowStyles[overflow],
2440
+ position && positionStyles[position],
2441
+ isClickable && "cursor-pointer",
2442
+ className
2443
+ ),
2444
+ onClick: isClickable ? handleClick : void 0,
2445
+ onMouseEnter: hoverEvent || onMouseEnter ? handleMouseEnter : void 0,
2446
+ onMouseLeave: hoverEvent || onMouseLeave ? handleMouseLeave : void 0,
2447
+ onPointerDown: hoverEvent && tapReveal || onPointerDown ? handlePointerDown : void 0,
2448
+ style: maxWidth ? { maxWidth, ...rest.style } : rest.style,
2449
+ ...rest
2450
+ },
2451
+ children
3714
2452
  );
3715
2453
  }
3716
2454
  );
3717
- Button.displayName = "Button";
3718
- var variantStyles2 = {
2455
+ Box.displayName = "Box";
2456
+ var variantStyles = {
3719
2457
  h1: "text-4xl font-bold tracking-tight text-foreground",
3720
2458
  h2: "text-3xl font-bold tracking-tight text-foreground",
3721
2459
  h3: "text-2xl font-bold text-foreground",
@@ -3776,7 +2514,7 @@ var typographySizeStyles = {
3776
2514
  "2xl": "text-2xl",
3777
2515
  "3xl": "text-3xl"
3778
2516
  };
3779
- var overflowStyles = {
2517
+ var overflowStyles2 = {
3780
2518
  visible: "overflow-visible",
3781
2519
  hidden: "overflow-hidden",
3782
2520
  wrap: "break-words overflow-hidden",
@@ -3801,18 +2539,18 @@ var Typography = ({
3801
2539
  }) => {
3802
2540
  const variant = variantProp ?? (level ? `h${level}` : "body1");
3803
2541
  const Component2 = as || defaultElements[variant];
3804
- return React8__namespace.default.createElement(
2542
+ return React7__default.default.createElement(
3805
2543
  Component2,
3806
2544
  {
3807
2545
  id,
3808
2546
  className: cn(
3809
- variantStyles2[variant],
2547
+ variantStyles[variant],
3810
2548
  colorStyles[color],
3811
2549
  weight && weightStyles[weight],
3812
2550
  size && typographySizeStyles[size],
3813
2551
  align && `text-${align}`,
3814
2552
  truncate && "truncate overflow-hidden text-ellipsis",
3815
- overflow && overflowStyles[overflow],
2553
+ overflow && overflowStyles2[overflow],
3816
2554
  className
3817
2555
  ),
3818
2556
  style
@@ -3874,7 +2612,7 @@ var Stack = ({
3874
2612
  };
3875
2613
  const isHorizontal = direction === "horizontal";
3876
2614
  const directionClass = responsive && isHorizontal ? reverse ? "flex-col-reverse md:flex-row-reverse" : "flex-col md:flex-row" : isHorizontal ? reverse ? "flex-row-reverse" : "flex-row" : reverse ? "flex-col-reverse" : "flex-col";
3877
- return React8__namespace.default.createElement(
2615
+ return React7__default.default.createElement(
3878
2616
  Component2,
3879
2617
  {
3880
2618
  className: cn(
@@ -3899,939 +2637,6 @@ var Stack = ({
3899
2637
  var VStack = (props) => /* @__PURE__ */ jsxRuntime.jsx(Stack, { direction: "vertical", ...props });
3900
2638
  var HStack = (props) => /* @__PURE__ */ jsxRuntime.jsx(Stack, { direction: "horizontal", ...props });
3901
2639
 
3902
- // components/game/shared/boardEntity.ts
3903
- function boardEntity(entity) {
3904
- if (!entity) return void 0;
3905
- return Array.isArray(entity) ? entity[0] : entity;
3906
- }
3907
- function str(v) {
3908
- return v == null ? "" : String(v);
3909
- }
3910
- function num(v, fallback = 0) {
3911
- const n = Number(v);
3912
- return Number.isFinite(n) ? n : fallback;
3913
- }
3914
- function rows(v) {
3915
- return Array.isArray(v) ? v : [];
3916
- }
3917
- function vec2(v) {
3918
- const o = v ?? {};
3919
- return { x: num(o.x), y: num(o.y) };
3920
- }
3921
- function unitPosition(u) {
3922
- return vec2(u.position);
3923
- }
3924
- function unitTeam(u) {
3925
- return str(u.team);
3926
- }
3927
- function unitHealth(u) {
3928
- return num(u.health);
3929
- }
3930
- function GameBoard3D({
3931
- entity,
3932
- tiles = [],
3933
- features = [],
3934
- cameraMode = "perspective",
3935
- backgroundColor = "#2a1a1a",
3936
- unitScale = 1,
3937
- scale = 0.45,
3938
- tileClickEvent,
3939
- unitClickEvent,
3940
- attackEvent,
3941
- endTurnEvent,
3942
- cancelEvent,
3943
- playAgainEvent,
3944
- gameEndEvent,
3945
- className
3946
- }) {
3947
- const row = boardEntity(entity);
3948
- const eventBus = useEventBus();
3949
- const entityUnits = row ? rows(row.units) : [];
3950
- const units = entityUnits.map((r) => ({
3951
- id: str(r.id),
3952
- x: num(r.x),
3953
- y: num(r.y),
3954
- z: num(r.z),
3955
- faction: str(r.faction) || "neutral",
3956
- team: str(r.team) || str(r.faction) || "neutral",
3957
- unitType: r.unitType == null ? void 0 : str(r.unitType),
3958
- name: r.name == null ? void 0 : str(r.name),
3959
- health: r.health == null ? void 0 : num(r.health),
3960
- maxHealth: r.maxHealth == null ? void 0 : num(r.maxHealth)
3961
- }));
3962
- const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
3963
- const phase = row ? str(row.phase) : "observation";
3964
- const result = row ? str(row.result) : "none";
3965
- const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
3966
- const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
3967
- const turn = row ? num(row.turn) : 0;
3968
- const currentTeam = row ? str(row.currentTeam) : "player";
3969
- const isGameOver = result !== "none";
3970
- const gameEndEmittedRef = React8.useRef(false);
3971
- React8.useEffect(() => {
3972
- if ((result === "victory" || result === "defeat") && gameEndEvent) {
3973
- if (!gameEndEmittedRef.current) {
3974
- gameEndEmittedRef.current = true;
3975
- eventBus.emit(`UI:${gameEndEvent}`, { result });
3976
- }
3977
- } else {
3978
- gameEndEmittedRef.current = false;
3979
- }
3980
- }, [result, gameEndEvent, eventBus]);
3981
- const checkGameEnd = React8.useCallback(() => {
3982
- const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
3983
- const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
3984
- if (alivePlayer.length === 0 && gameEndEvent) {
3985
- eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
3986
- } else if (aliveEnemy.length === 0 && gameEndEvent) {
3987
- eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
3988
- }
3989
- }, [entityUnits, gameEndEvent, eventBus]);
3990
- const handleUnitClickCallback = React8.useCallback((isoUnit) => {
3991
- if (phase !== "action" || !selectedUnitId || !attackEvent) return;
3992
- const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
3993
- const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
3994
- if (!attackerRow || !targetRow) return;
3995
- if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
3996
- const ap = unitPosition(attackerRow);
3997
- const tp = unitPosition(targetRow);
3998
- const dx = Math.abs(ap.x - tp.x);
3999
- const dy = Math.abs(ap.y - tp.y);
4000
- if (dx <= 1 && dy <= 1 && dx + dy > 0) {
4001
- const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
4002
- eventBus.emit(`UI:${attackEvent}`, {
4003
- attackerId: str(attackerRow.id),
4004
- targetId: str(targetRow.id),
4005
- damage
4006
- });
4007
- setTimeout(checkGameEnd, 100);
4008
- }
4009
- }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
4010
- const handleEndTurn = React8.useCallback(() => {
4011
- if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
4012
- }, [endTurnEvent, eventBus]);
4013
- const handleCancel = React8.useCallback(() => {
4014
- if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
4015
- }, [cancelEvent, eventBus]);
4016
- const handlePlayAgain = React8.useCallback(() => {
4017
- if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
4018
- }, [playAgainEvent, eventBus]);
4019
- return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
4020
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
4021
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
4022
- currentTeam && !isGameOver && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
4023
- "\u2014 ",
4024
- currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
4025
- ] }),
4026
- /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
4027
- "Turn ",
4028
- turn
4029
- ] })
4030
- ] }),
4031
- /* @__PURE__ */ jsxRuntime.jsx(
4032
- GameCanvas3D,
4033
- {
4034
- tiles,
4035
- units,
4036
- features,
4037
- cameraMode,
4038
- showGrid: true,
4039
- showCoordinates: false,
4040
- showTileInfo: false,
4041
- shadows: true,
4042
- backgroundColor,
4043
- tileClickEvent,
4044
- unitClickEvent,
4045
- onUnitClick: handleUnitClickCallback,
4046
- selectedUnitId,
4047
- validMoves,
4048
- attackTargets,
4049
- unitScale,
4050
- scale,
4051
- className: "game-board-3d__canvas w-full min-h-[85vh]"
4052
- }
4053
- ),
4054
- !isGameOver && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
4055
- (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsxRuntime.jsx(
4056
- Button,
4057
- {
4058
- variant: "secondary",
4059
- className: "shadow-xl",
4060
- onClick: handleCancel,
4061
- children: "Cancel"
4062
- }
4063
- ),
4064
- phase !== "enemy_turn" && /* @__PURE__ */ jsxRuntime.jsx(
4065
- Button,
4066
- {
4067
- variant: "primary",
4068
- className: "shadow-xl",
4069
- onClick: handleEndTurn,
4070
- children: "End Turn"
4071
- }
4072
- )
4073
- ] }),
4074
- isGameOver && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "game-board-3d__overlay absolute inset-0 z-20 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { align: "center", gap: "md", className: "overlay__card p-8 rounded-xl bg-gray-900/90 shadow-2xl", children: [
4075
- /* @__PURE__ */ jsxRuntime.jsx(
4076
- Typography,
4077
- {
4078
- variant: "h2",
4079
- className: cn(
4080
- "overlay__result",
4081
- result === "victory" ? "text-yellow-400" : "text-red-500"
4082
- ),
4083
- children: result === "victory" ? "Victory!" : "Defeat"
4084
- }
4085
- ),
4086
- /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
4087
- "Completed in ",
4088
- turn,
4089
- " turn",
4090
- turn !== 1 ? "s" : ""
4091
- ] }),
4092
- /* @__PURE__ */ jsxRuntime.jsx(
4093
- Button,
4094
- {
4095
- variant: "primary",
4096
- className: "px-8 py-3 font-semibold",
4097
- onClick: handlePlayAgain,
4098
- children: "Play Again"
4099
- }
4100
- )
4101
- ] }) })
4102
- ] });
4103
- }
4104
- GameBoard3D.displayName = "GameBoard3D";
4105
- function useTapReveal(options = {}) {
4106
- const { onReveal, onDismiss, refs, enabled = true } = options;
4107
- const [revealed, setRevealed] = React8.useState(false);
4108
- const onRevealRef = React8.useRef(onReveal);
4109
- const onDismissRef = React8.useRef(onDismiss);
4110
- onRevealRef.current = onReveal;
4111
- onDismissRef.current = onDismiss;
4112
- const reveal = React8.useCallback(() => {
4113
- setRevealed((wasRevealed) => {
4114
- if (!wasRevealed) onRevealRef.current?.();
4115
- return true;
4116
- });
4117
- }, []);
4118
- const dismiss = React8.useCallback(() => {
4119
- setRevealed((wasRevealed) => {
4120
- if (wasRevealed) onDismissRef.current?.();
4121
- return false;
4122
- });
4123
- }, []);
4124
- const onPointerDown = React8.useCallback(
4125
- (e) => {
4126
- if (!enabled || e.pointerType === "mouse") return;
4127
- if (revealed) dismiss();
4128
- else reveal();
4129
- },
4130
- [enabled, revealed, reveal, dismiss]
4131
- );
4132
- React8.useEffect(() => {
4133
- if (!revealed || typeof document === "undefined") return;
4134
- const onDocDown = (ev) => {
4135
- const target = ev.target;
4136
- if (!(target instanceof Node)) return;
4137
- const inside = (refs ?? []).some((r) => r.current?.contains(target));
4138
- if (!inside) dismiss();
4139
- };
4140
- const id = setTimeout(() => document.addEventListener("pointerdown", onDocDown), 0);
4141
- return () => {
4142
- clearTimeout(id);
4143
- document.removeEventListener("pointerdown", onDocDown);
4144
- };
4145
- }, [revealed, refs, dismiss]);
4146
- return { revealed, triggerProps: { onPointerDown }, reveal, dismiss };
4147
- }
4148
-
4149
- // components/core/atoms/Box.tsx
4150
- var paddingStyles = {
4151
- none: "p-0",
4152
- xs: "p-1",
4153
- sm: "p-2",
4154
- md: "p-4",
4155
- lg: "p-6",
4156
- xl: "p-8",
4157
- "2xl": "p-12"
4158
- };
4159
- var paddingXStyles = {
4160
- none: "px-0",
4161
- xs: "px-1",
4162
- sm: "px-2",
4163
- md: "px-4",
4164
- lg: "px-6",
4165
- xl: "px-8",
4166
- "2xl": "px-12"
4167
- };
4168
- var paddingYStyles = {
4169
- none: "py-0",
4170
- xs: "py-1",
4171
- sm: "py-2",
4172
- md: "py-4",
4173
- lg: "py-6",
4174
- xl: "py-8",
4175
- "2xl": "py-12"
4176
- };
4177
- var marginStyles = {
4178
- none: "m-0",
4179
- xs: "m-1",
4180
- sm: "m-2",
4181
- md: "m-4",
4182
- lg: "m-6",
4183
- xl: "m-8",
4184
- "2xl": "m-12",
4185
- auto: "m-auto"
4186
- };
4187
- var marginXStyles = {
4188
- none: "mx-0",
4189
- xs: "mx-1",
4190
- sm: "mx-2",
4191
- md: "mx-4",
4192
- lg: "mx-6",
4193
- xl: "mx-8",
4194
- "2xl": "mx-12",
4195
- auto: "mx-auto"
4196
- };
4197
- var marginYStyles = {
4198
- none: "my-0",
4199
- xs: "my-1",
4200
- sm: "my-2",
4201
- md: "my-4",
4202
- lg: "my-6",
4203
- xl: "my-8",
4204
- "2xl": "my-12",
4205
- auto: "my-auto"
4206
- };
4207
- var bgStyles = {
4208
- transparent: "bg-transparent",
4209
- primary: "bg-primary text-primary-foreground",
4210
- secondary: "bg-secondary text-secondary-foreground",
4211
- muted: "bg-muted text-foreground",
4212
- accent: "bg-accent text-accent-foreground",
4213
- surface: "bg-card",
4214
- overlay: "bg-card/80 backdrop-blur-sm"
4215
- };
4216
- var roundedStyles = {
4217
- none: "rounded-none",
4218
- sm: "rounded-sm",
4219
- md: "rounded-md",
4220
- lg: "rounded-lg",
4221
- xl: "rounded-xl",
4222
- "2xl": "rounded-xl",
4223
- full: "rounded-full"
4224
- };
4225
- var shadowStyles = {
4226
- none: "shadow-none",
4227
- sm: "shadow-sm",
4228
- md: "shadow",
4229
- lg: "shadow-lg",
4230
- xl: "shadow-lg"
4231
- };
4232
- var displayStyles = {
4233
- block: "block",
4234
- inline: "inline",
4235
- "inline-block": "inline-block",
4236
- flex: "flex",
4237
- "inline-flex": "inline-flex",
4238
- grid: "grid"
4239
- };
4240
- var overflowStyles2 = {
4241
- auto: "overflow-auto",
4242
- hidden: "overflow-hidden",
4243
- visible: "overflow-visible",
4244
- scroll: "overflow-scroll"
4245
- };
4246
- var positionStyles = {
4247
- relative: "relative",
4248
- absolute: "absolute",
4249
- fixed: "fixed",
4250
- sticky: "sticky"
4251
- };
4252
- var Box = React8__namespace.default.forwardRef(
4253
- ({
4254
- padding,
4255
- paddingX,
4256
- paddingY,
4257
- margin,
4258
- marginX,
4259
- marginY,
4260
- bg = "transparent",
4261
- border = false,
4262
- rounded = "none",
4263
- shadow = "none",
4264
- display,
4265
- fullWidth = false,
4266
- fullHeight = false,
4267
- overflow,
4268
- position,
4269
- className,
4270
- children,
4271
- as: Component2 = "div",
4272
- action,
4273
- actionPayload,
4274
- hoverEvent,
4275
- tapReveal = true,
4276
- maxWidth,
4277
- onClick,
4278
- onMouseEnter,
4279
- onMouseLeave,
4280
- onPointerDown,
4281
- ...rest
4282
- }, ref) => {
4283
- const eventBus = useEventBus();
4284
- const handleClick = React8.useCallback((e) => {
4285
- if (action) {
4286
- e.stopPropagation();
4287
- eventBus.emit(`UI:${action}`, actionPayload ?? {});
4288
- }
4289
- onClick?.(e);
4290
- }, [action, actionPayload, eventBus, onClick]);
4291
- const handleMouseEnter = React8.useCallback((e) => {
4292
- if (hoverEvent) {
4293
- eventBus.emit(`UI:${hoverEvent}`, { hovered: true });
4294
- }
4295
- onMouseEnter?.(e);
4296
- }, [hoverEvent, eventBus, onMouseEnter]);
4297
- const handleMouseLeave = React8.useCallback((e) => {
4298
- if (hoverEvent) {
4299
- eventBus.emit(`UI:${hoverEvent}`, { hovered: false });
4300
- }
4301
- onMouseLeave?.(e);
4302
- }, [hoverEvent, eventBus, onMouseLeave]);
4303
- const { triggerProps } = useTapReveal({
4304
- enabled: tapReveal && !!hoverEvent,
4305
- onReveal: React8.useCallback(() => {
4306
- if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: true });
4307
- }, [hoverEvent, eventBus]),
4308
- onDismiss: React8.useCallback(() => {
4309
- if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: false });
4310
- }, [hoverEvent, eventBus])
4311
- });
4312
- const handlePointerDown = React8.useCallback((e) => {
4313
- if (hoverEvent && tapReveal) triggerProps.onPointerDown(e);
4314
- onPointerDown?.(e);
4315
- }, [hoverEvent, tapReveal, triggerProps, onPointerDown]);
4316
- const isClickable = action || onClick;
4317
- return React8__namespace.default.createElement(
4318
- Component2,
4319
- {
4320
- ref,
4321
- className: cn(
4322
- padding && paddingStyles[padding],
4323
- paddingX && paddingXStyles[paddingX],
4324
- paddingY && paddingYStyles[paddingY],
4325
- margin && marginStyles[margin],
4326
- marginX && marginXStyles[marginX],
4327
- marginY && marginYStyles[marginY],
4328
- bgStyles[bg],
4329
- border && "border-[length:var(--border-width)] border-border",
4330
- roundedStyles[rounded],
4331
- shadowStyles[shadow],
4332
- display && displayStyles[display],
4333
- fullWidth && "w-full",
4334
- fullHeight && "h-full",
4335
- overflow && overflowStyles2[overflow],
4336
- position && positionStyles[position],
4337
- isClickable && "cursor-pointer",
4338
- className
4339
- ),
4340
- onClick: isClickable ? handleClick : void 0,
4341
- onMouseEnter: hoverEvent || onMouseEnter ? handleMouseEnter : void 0,
4342
- onMouseLeave: hoverEvent || onMouseLeave ? handleMouseLeave : void 0,
4343
- onPointerDown: hoverEvent && tapReveal || onPointerDown ? handlePointerDown : void 0,
4344
- style: maxWidth ? { maxWidth, ...rest.style } : rest.style,
4345
- ...rest
4346
- },
4347
- children
4348
- );
4349
- }
4350
- );
4351
- Box.displayName = "Box";
4352
-
4353
- // components/game/shared/makeAsset.ts
4354
- var ROLE_DEFAULTS = {
4355
- tile: { dimension: "2d", animations: ["static"], aspect: "1:1" },
4356
- decoration: { dimension: "2d", animations: ["static"], aspect: "1:1" },
4357
- effect: { dimension: "2d", animations: ["static"], aspect: "1:1" },
4358
- item: { dimension: "2d", animations: ["static"], aspect: "5:7" },
4359
- player: { dimension: "2d", animations: ["idle", "walk", "attack", "hit", "death"], aspect: "1:1" },
4360
- enemy: { dimension: "2d", animations: ["idle", "walk", "attack", "hit", "death"], aspect: "1:1" },
4361
- npc: { dimension: "2d", animations: ["idle", "walk", "attack", "hit", "death"], aspect: "1:1" },
4362
- projectile: { dimension: "2d", animations: ["static"], aspect: "1:1" },
4363
- vehicle: { dimension: "2d", animations: ["idle", "walk"], aspect: "1:1" },
4364
- ui: { dimension: "2d", animations: ["static"], aspect: "1:1" }
4365
- };
4366
- function makeAsset(url, role, overrides = {}) {
4367
- const defaults = ROLE_DEFAULTS[role] ?? { dimension: "2d", animations: ["static"], aspect: "1:1" };
4368
- return {
4369
- url,
4370
- role,
4371
- category: url.split("/").pop()?.replace(/\.[^.]+$/, "") ?? role,
4372
- ...defaults,
4373
- ...overrides
4374
- };
4375
- }
4376
-
4377
- // components/game/shared/lib/game3dAssetManifest.ts
4378
- function tileKind(type, passable) {
4379
- if (passable === false) return "wall";
4380
- if (type === "dirt" || type === "road") return "dirt";
4381
- return "open";
4382
- }
4383
- function resolveTilesWithModels(layout, manifest) {
4384
- return layout.map((t) => {
4385
- const kind = t.kind || tileKind(t.type, t.passable);
4386
- return {
4387
- id: t.id,
4388
- x: t.x,
4389
- y: t.y,
4390
- z: t.z,
4391
- type: t.type,
4392
- passable: t.passable,
4393
- modelUrl: manifest?.models?.[kind]
4394
- };
4395
- });
4396
- }
4397
- function resolvePropTiles(tiles, manifest) {
4398
- return tiles.map((t) => {
4399
- const kind = tileKind(t.type ?? "", t.passable);
4400
- return {
4401
- ...t,
4402
- modelUrl: t.modelUrl ?? manifest?.models?.[kind]
4403
- };
4404
- });
4405
- }
4406
- function resolveEntityTiles(entity, manifest) {
4407
- const row = boardEntity(entity);
4408
- if (!row) return [];
4409
- return rows(row.tiles).map((r) => {
4410
- const type = str(r.type) || str(r.terrain);
4411
- const passable = r.passable !== false;
4412
- const kind = tileKind(type, passable);
4413
- const explicitModelUrl = r.modelUrl == null ? void 0 : str(r.modelUrl);
4414
- const explicitModel = explicitModelUrl ? makeAsset(explicitModelUrl, "decoration", { dimension: "3d" }) : void 0;
4415
- return {
4416
- id: r.id == null ? void 0 : str(r.id),
4417
- x: num(r.x),
4418
- y: num(r.y),
4419
- z: r.z == null ? void 0 : num(r.z),
4420
- type,
4421
- passable,
4422
- modelUrl: explicitModel ?? manifest?.models?.[kind]
4423
- };
4424
- });
4425
- }
4426
- function resolveFeaturesWithModels(features, manifest) {
4427
- return features.map((f) => ({
4428
- ...f,
4429
- assetUrl: f.assetUrl ?? manifest?.features?.[f.type]
4430
- }));
4431
- }
4432
- function resolveEntityFeatures(entity, manifest) {
4433
- const row = boardEntity(entity);
4434
- if (!row) return [];
4435
- return rows(row.features).map((r) => {
4436
- const type = str(r.type);
4437
- const explicitUrl = r.assetUrl == null ? void 0 : str(r.assetUrl);
4438
- const explicit = explicitUrl ? makeAsset(explicitUrl, "decoration", { dimension: "3d" }) : void 0;
4439
- return {
4440
- id: r.id == null ? void 0 : str(r.id),
4441
- x: num(r.x),
4442
- y: num(r.y),
4443
- z: r.z == null ? void 0 : num(r.z),
4444
- type,
4445
- color: r.color == null ? void 0 : str(r.color),
4446
- assetUrl: explicit ?? manifest?.features?.[type]
4447
- };
4448
- });
4449
- }
4450
- var DEFAULT_3D_BATTLE_LAYOUT = [
4451
- { id: "t00", x: 0, y: 0, z: 0, type: "stone", passable: false, kind: "wall" },
4452
- { id: "t10", x: 1, y: 0, z: 0, type: "stone", passable: false, kind: "wall" },
4453
- { id: "t20", x: 2, y: 0, z: 0, type: "stone", passable: false, kind: "wall" },
4454
- { id: "t30", x: 3, y: 0, z: 0, type: "stone", passable: false, kind: "wall" },
4455
- { id: "t40", x: 4, y: 0, z: 0, type: "stone", passable: false, kind: "wall" },
4456
- { id: "t01", x: 0, y: 1, z: 1, type: "stone", passable: false, kind: "wall" },
4457
- { id: "t11", x: 1, y: 1, z: 1, type: "dirt", passable: true, kind: "dirt" },
4458
- { id: "t21", x: 2, y: 1, z: 1, type: "grass", passable: true, kind: "open" },
4459
- { id: "t31", x: 3, y: 1, z: 1, type: "grass", passable: true, kind: "open" },
4460
- { id: "t41", x: 4, y: 1, z: 1, type: "stone", passable: false, kind: "wall" },
4461
- { id: "t02", x: 0, y: 2, z: 2, type: "stone", passable: false, kind: "wall" },
4462
- { id: "t12", x: 1, y: 2, z: 2, type: "grass", passable: true, kind: "open" },
4463
- { id: "t22", x: 2, y: 2, z: 2, type: "dirt", passable: true, kind: "dirt" },
4464
- { id: "t32", x: 3, y: 2, z: 2, type: "grass", passable: true, kind: "open" },
4465
- { id: "t42", x: 4, y: 2, z: 2, type: "stone", passable: false, kind: "wall" },
4466
- { id: "t03", x: 0, y: 3, z: 3, type: "stone", passable: false, kind: "wall" },
4467
- { id: "t13", x: 1, y: 3, z: 3, type: "grass", passable: true, kind: "open" },
4468
- { id: "t23", x: 2, y: 3, z: 3, type: "grass", passable: true, kind: "open" },
4469
- { id: "t33", x: 3, y: 3, z: 3, type: "dirt", passable: true, kind: "dirt" },
4470
- { id: "t43", x: 4, y: 3, z: 3, type: "stone", passable: false, kind: "wall" },
4471
- { id: "t04", x: 0, y: 4, z: 4, type: "stone", passable: false, kind: "wall" },
4472
- { id: "t14", x: 1, y: 4, z: 4, type: "stone", passable: false, kind: "wall" },
4473
- { id: "t24", x: 2, y: 4, z: 4, type: "stone", passable: false, kind: "wall" },
4474
- { id: "t34", x: 3, y: 4, z: 4, type: "stone", passable: false, kind: "wall" },
4475
- { id: "t44", x: 4, y: 4, z: 4, type: "stone", passable: false, kind: "wall" }
4476
- ];
4477
- var DEFAULT_3D_BATTLE_FEATURES = [
4478
- { id: "f1", x: 2, y: 2, z: 2, type: "gold_mine", color: "#f4c542" },
4479
- { id: "f2", x: 3, y: 1, z: 1, type: "portal", color: "#8b5cf6" }
4480
- ];
4481
- function GameCanvas3DBattleTemplate({
4482
- entity,
4483
- tiles: propTiles,
4484
- features: propFeatures = DEFAULT_3D_BATTLE_FEATURES,
4485
- assetManifest,
4486
- cameraMode = "perspective",
4487
- backgroundColor = "#2a1a1a",
4488
- tileClickEvent,
4489
- unitClickEvent,
4490
- endTurnEvent,
4491
- cancelEvent,
4492
- attackEvent,
4493
- playAgainEvent,
4494
- gameEndEvent,
4495
- unitScale,
4496
- scale,
4497
- className
4498
- }) {
4499
- const row = boardEntity(entity);
4500
- const hasEntityTiles = !!row && Array.isArray(row.tiles) && row.tiles.length > 0;
4501
- const tiles = hasEntityTiles ? resolveEntityTiles(entity, assetManifest) : propTiles ? resolvePropTiles(propTiles, assetManifest) : resolveTilesWithModels(DEFAULT_3D_BATTLE_LAYOUT, assetManifest);
4502
- const features = row && Array.isArray(row.features) && row.features.length > 0 ? resolveEntityFeatures(entity, assetManifest) : resolveFeaturesWithModels(propFeatures, assetManifest);
4503
- const currentTurn = row?.currentTeam == null ? void 0 : String(row.currentTeam);
4504
- const round = row?.turn == null ? void 0 : Number(row.turn);
4505
- return /* @__PURE__ */ jsxRuntime.jsxs(
4506
- Box,
4507
- {
4508
- className: cn("game-canvas-3d-battle-template block relative w-full min-h-[85vh]", className),
4509
- children: [
4510
- /* @__PURE__ */ jsxRuntime.jsx(
4511
- GameBoard3D,
4512
- {
4513
- entity,
4514
- tiles,
4515
- features,
4516
- cameraMode,
4517
- backgroundColor,
4518
- unitScale,
4519
- scale,
4520
- tileClickEvent,
4521
- unitClickEvent,
4522
- endTurnEvent,
4523
- cancelEvent,
4524
- attackEvent,
4525
- playAgainEvent,
4526
- gameEndEvent,
4527
- className: "game-canvas-3d-battle-template__board"
4528
- }
4529
- ),
4530
- currentTurn && /* @__PURE__ */ jsxRuntime.jsxs(
4531
- HStack,
4532
- {
4533
- gap: "sm",
4534
- align: "center",
4535
- className: cn("battle-template__turn-indicator absolute top-3 right-3 z-10", `battle-template__turn-indicator--${currentTurn}`),
4536
- children: [
4537
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "turn-indicator__label", children: currentTurn === "player" ? "Your Turn" : "Enemy's Turn" }),
4538
- round != null && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "turn-indicator__round", children: [
4539
- "Round ",
4540
- round
4541
- ] })
4542
- ]
4543
- }
4544
- )
4545
- ]
4546
- }
4547
- );
4548
- }
4549
- GameCanvas3DBattleTemplate.displayName = "GameCanvas3DBattleTemplate";
4550
- var DEFAULT_3D_CASTLE_LAYOUT = [
4551
- { id: "t00", x: 0, y: 0, z: 0, type: "wall", passable: false, kind: "wall" },
4552
- { id: "t10", x: 1, y: 0, z: 0, type: "wall", passable: false, kind: "wall" },
4553
- { id: "t20", x: 2, y: 0, z: 0, type: "wall", passable: false, kind: "wall" },
4554
- { id: "t30", x: 3, y: 0, z: 0, type: "wall", passable: false, kind: "wall" },
4555
- { id: "t40", x: 4, y: 0, z: 0, type: "wall", passable: false, kind: "wall" },
4556
- { id: "t01", x: 0, y: 1, z: 1, type: "wall", passable: false, kind: "wall" },
4557
- { id: "t11", x: 1, y: 1, z: 1, type: "floor", passable: true, kind: "dirt" },
4558
- { id: "t21", x: 2, y: 1, z: 1, type: "floor", passable: true, kind: "open" },
4559
- { id: "t31", x: 3, y: 1, z: 1, type: "floor", passable: true, kind: "open" },
4560
- { id: "t41", x: 4, y: 1, z: 1, type: "wall", passable: false, kind: "wall" },
4561
- { id: "t02", x: 0, y: 2, z: 2, type: "wall", passable: false, kind: "wall" },
4562
- { id: "t12", x: 1, y: 2, z: 2, type: "floor", passable: true, kind: "open" },
4563
- { id: "t22", x: 2, y: 2, z: 2, type: "floor", passable: true, kind: "dirt" },
4564
- { id: "t32", x: 3, y: 2, z: 2, type: "floor", passable: true, kind: "open" },
4565
- { id: "t42", x: 4, y: 2, z: 2, type: "wall", passable: false, kind: "wall" },
4566
- { id: "t03", x: 0, y: 3, z: 3, type: "wall", passable: false, kind: "wall" },
4567
- { id: "t13", x: 1, y: 3, z: 3, type: "floor", passable: true, kind: "open" },
4568
- { id: "t23", x: 2, y: 3, z: 3, type: "floor", passable: true, kind: "open" },
4569
- { id: "t33", x: 3, y: 3, z: 3, type: "floor", passable: true, kind: "dirt" },
4570
- { id: "t43", x: 4, y: 3, z: 3, type: "wall", passable: false, kind: "wall" },
4571
- { id: "t04", x: 0, y: 4, z: 4, type: "wall", passable: false, kind: "wall" },
4572
- { id: "t14", x: 1, y: 4, z: 4, type: "wall", passable: false, kind: "wall" },
4573
- { id: "t24", x: 2, y: 4, z: 4, type: "wall", passable: false, kind: "wall" },
4574
- { id: "t34", x: 3, y: 4, z: 4, type: "wall", passable: false, kind: "wall" },
4575
- { id: "t44", x: 4, y: 4, z: 4, type: "wall", passable: false, kind: "wall" }
4576
- ];
4577
- var DEFAULT_3D_CASTLE_FEATURES = [
4578
- { id: "f1", x: 2, y: 2, z: 2, type: "chest", color: "#f4c542" },
4579
- { id: "f2", x: 3, y: 1, z: 1, type: "crystal", color: "#8b5cf6" }
4580
- ];
4581
- function GameCanvas3DCastleTemplate({
4582
- entity,
4583
- tiles: propTiles,
4584
- features: propFeatures = DEFAULT_3D_CASTLE_FEATURES,
4585
- assetManifest,
4586
- cameraMode = "isometric",
4587
- backgroundColor = "#1e1e2e",
4588
- buildingClickEvent,
4589
- unitClickEvent,
4590
- endTurnEvent,
4591
- cancelEvent,
4592
- attackEvent,
4593
- playAgainEvent,
4594
- gameEndEvent,
4595
- showHeader = true,
4596
- unitScale,
4597
- scale,
4598
- className
4599
- }) {
4600
- const row = boardEntity(entity);
4601
- const hasEntityTiles = !!row && Array.isArray(row.tiles) && row.tiles.length > 0;
4602
- const tiles = hasEntityTiles ? resolveEntityTiles(entity, assetManifest) : propTiles ? resolvePropTiles(propTiles, assetManifest) : resolveTilesWithModels(DEFAULT_3D_CASTLE_LAYOUT, assetManifest);
4603
- const features = row && Array.isArray(row.features) && row.features.length > 0 ? resolveEntityFeatures(entity, assetManifest) : resolveFeaturesWithModels(propFeatures, assetManifest);
4604
- const name = row?.name == null ? void 0 : str(row.name);
4605
- const level = row?.level == null ? void 0 : num(row.level);
4606
- const owner = row?.owner == null ? void 0 : str(row.owner);
4607
- const unitCount = row && Array.isArray(row.units) ? row.units.length : 0;
4608
- return /* @__PURE__ */ jsxRuntime.jsxs(
4609
- VStack,
4610
- {
4611
- className: cn("game-canvas-3d-castle-template block w-full min-h-[85vh]", className),
4612
- children: [
4613
- showHeader && name && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "md", align: "center", className: "castle-template__header", children: [
4614
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", className: "header__name", children: name }),
4615
- level != null && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "header__level", children: [
4616
- "Level ",
4617
- level
4618
- ] }),
4619
- owner && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", className: "header__owner", children: owner })
4620
- ] }),
4621
- /* @__PURE__ */ jsxRuntime.jsx(
4622
- GameBoard3D,
4623
- {
4624
- entity,
4625
- tiles,
4626
- features,
4627
- cameraMode,
4628
- backgroundColor,
4629
- unitScale,
4630
- scale,
4631
- tileClickEvent: buildingClickEvent,
4632
- unitClickEvent,
4633
- endTurnEvent,
4634
- cancelEvent,
4635
- attackEvent,
4636
- playAgainEvent,
4637
- gameEndEvent,
4638
- className: "game-canvas-3d-castle-template__board"
4639
- }
4640
- ),
4641
- unitCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", align: "center", className: "castle-template__garrison-info", children: [
4642
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "garrison-info__label", children: "Garrison:" }),
4643
- /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", weight: "bold", className: "garrison-info__count", children: [
4644
- unitCount,
4645
- " units"
4646
- ] })
4647
- ] })
4648
- ]
4649
- }
4650
- );
4651
- }
4652
- GameCanvas3DCastleTemplate.displayName = "GameCanvas3DCastleTemplate";
4653
- var DEFAULT_3D_WORLDMAP_LAYOUT = [
4654
- { id: "t00", x: 0, y: 0, z: 0, type: "mountain", passable: false, kind: "wall" },
4655
- { id: "t10", x: 1, y: 0, z: 0, type: "mountain", passable: false, kind: "wall" },
4656
- { id: "t20", x: 2, y: 0, z: 0, type: "water", passable: false, kind: "wall" },
4657
- { id: "t30", x: 3, y: 0, z: 0, type: "mountain", passable: false, kind: "wall" },
4658
- { id: "t40", x: 4, y: 0, z: 0, type: "mountain", passable: false, kind: "wall" },
4659
- { id: "t01", x: 0, y: 1, z: 1, type: "mountain", passable: false, kind: "wall" },
4660
- { id: "t11", x: 1, y: 1, z: 1, type: "grass", passable: true, kind: "open" },
4661
- { id: "t21", x: 2, y: 1, z: 1, type: "grass", passable: true, kind: "open" },
4662
- { id: "t31", x: 3, y: 1, z: 1, type: "grass", passable: true, kind: "open" },
4663
- { id: "t41", x: 4, y: 1, z: 1, type: "water", passable: false, kind: "wall" },
4664
- { id: "t02", x: 0, y: 2, z: 2, type: "water", passable: false, kind: "wall" },
4665
- { id: "t12", x: 1, y: 2, z: 2, type: "grass", passable: true, kind: "open" },
4666
- { id: "t22", x: 2, y: 2, z: 2, type: "road", passable: true, kind: "dirt" },
4667
- { id: "t32", x: 3, y: 2, z: 2, type: "grass", passable: true, kind: "open" },
4668
- { id: "t42", x: 4, y: 2, z: 2, type: "mountain", passable: false, kind: "wall" },
4669
- { id: "t03", x: 0, y: 3, z: 3, type: "mountain", passable: false, kind: "wall" },
4670
- { id: "t13", x: 1, y: 3, z: 3, type: "grass", passable: true, kind: "open" },
4671
- { id: "t23", x: 2, y: 3, z: 3, type: "grass", passable: true, kind: "open" },
4672
- { id: "t33", x: 3, y: 3, z: 3, type: "road", passable: true, kind: "dirt" },
4673
- { id: "t43", x: 4, y: 3, z: 3, type: "mountain", passable: false, kind: "wall" },
4674
- { id: "t04", x: 0, y: 4, z: 4, type: "mountain", passable: false, kind: "wall" },
4675
- { id: "t14", x: 1, y: 4, z: 4, type: "water", passable: false, kind: "wall" },
4676
- { id: "t24", x: 2, y: 4, z: 4, type: "grass", passable: true, kind: "open" },
4677
- { id: "t34", x: 3, y: 4, z: 4, type: "mountain", passable: false, kind: "wall" },
4678
- { id: "t44", x: 4, y: 4, z: 4, type: "mountain", passable: false, kind: "wall" }
4679
- ];
4680
- var DEFAULT_3D_WORLDMAP_FEATURES = [
4681
- { id: "f1", x: 2, y: 2, z: 2, type: "capital", color: "#f4c542" },
4682
- { id: "f2", x: 4, y: 2, z: 2, type: "power_node", color: "#8b5cf6" }
4683
- ];
4684
- function GameCanvas3DWorldMapTemplate({
4685
- entity,
4686
- tiles: propTiles,
4687
- features: propFeatures = DEFAULT_3D_WORLDMAP_FEATURES,
4688
- assetManifest,
4689
- cameraMode = "isometric",
4690
- backgroundColor = "#1a1a2e",
4691
- tileClickEvent,
4692
- unitClickEvent,
4693
- endTurnEvent,
4694
- cancelEvent,
4695
- attackEvent,
4696
- playAgainEvent,
4697
- gameEndEvent,
4698
- unitScale,
4699
- scale,
4700
- className
4701
- }) {
4702
- const row = boardEntity(entity);
4703
- const hasEntityTiles = !!row && Array.isArray(row.tiles) && row.tiles.length > 0;
4704
- const tiles = hasEntityTiles ? resolveEntityTiles(entity, assetManifest) : propTiles ? resolvePropTiles(propTiles, assetManifest) : resolveTilesWithModels(DEFAULT_3D_WORLDMAP_LAYOUT, assetManifest);
4705
- const features = row && Array.isArray(row.features) && row.features.length > 0 ? resolveEntityFeatures(entity, assetManifest) : resolveFeaturesWithModels(propFeatures, assetManifest);
4706
- return (
4707
- /* GameBoard3D reads selectedUnitId/validMoves/attackTargets from entity */
4708
- /* @__PURE__ */ jsxRuntime.jsx(
4709
- GameBoard3D,
4710
- {
4711
- entity,
4712
- tiles,
4713
- features,
4714
- cameraMode,
4715
- backgroundColor,
4716
- unitScale,
4717
- scale,
4718
- tileClickEvent,
4719
- unitClickEvent,
4720
- endTurnEvent,
4721
- cancelEvent,
4722
- attackEvent,
4723
- playAgainEvent,
4724
- gameEndEvent,
4725
- className
4726
- }
4727
- )
4728
- );
4729
- }
4730
- GameCanvas3DWorldMapTemplate.displayName = "GameCanvas3DWorldMapTemplate";
4731
- var DEFAULT_CONFIG = {
4732
- cellSize: 1,
4733
- offsetX: 0,
4734
- offsetZ: 0,
4735
- elevation: 0
4736
- };
4737
- function gridToWorld(gridX, gridZ, config = DEFAULT_CONFIG) {
4738
- const opts = { ...DEFAULT_CONFIG, ...config };
4739
- return new THREE6__namespace.Vector3(
4740
- gridX * opts.cellSize + opts.offsetX,
4741
- opts.elevation,
4742
- gridZ * opts.cellSize + opts.offsetZ
4743
- );
4744
- }
4745
- function worldToGrid(worldX, worldZ, config = DEFAULT_CONFIG) {
4746
- const opts = { ...DEFAULT_CONFIG, ...config };
4747
- return {
4748
- x: Math.round((worldX - opts.offsetX) / opts.cellSize),
4749
- z: Math.round((worldZ - opts.offsetZ) / opts.cellSize)
4750
- };
4751
- }
4752
- function raycastToPlane(camera, mouseX, mouseY, planeY = 0) {
4753
- const raycaster = new THREE6__namespace.Raycaster();
4754
- const mouse = new THREE6__namespace.Vector2(mouseX, mouseY);
4755
- raycaster.setFromCamera(mouse, camera);
4756
- const plane = new THREE6__namespace.Plane(new THREE6__namespace.Vector3(0, 1, 0), -planeY);
4757
- const target = new THREE6__namespace.Vector3();
4758
- const intersection = raycaster.ray.intersectPlane(plane, target);
4759
- return intersection ? target : null;
4760
- }
4761
- function raycastToObjects(camera, mouseX, mouseY, objects) {
4762
- const raycaster = new THREE6__namespace.Raycaster();
4763
- const mouse = new THREE6__namespace.Vector2(mouseX, mouseY);
4764
- raycaster.setFromCamera(mouse, camera);
4765
- const intersects = raycaster.intersectObjects(objects, true);
4766
- return intersects.length > 0 ? intersects[0] : null;
4767
- }
4768
- function gridDistance(a, b) {
4769
- const dx = b.x - a.x;
4770
- const dz = b.z - a.z;
4771
- return Math.sqrt(dx * dx + dz * dz);
4772
- }
4773
- function gridManhattanDistance(a, b) {
4774
- return Math.abs(b.x - a.x) + Math.abs(b.z - a.z);
4775
- }
4776
- function getNeighbors(x, z, includeDiagonal = false) {
4777
- const neighbors = [
4778
- { x: x + 1, z },
4779
- { x: x - 1, z },
4780
- { x, z: z + 1 },
4781
- { x, z: z - 1 }
4782
- ];
4783
- if (includeDiagonal) {
4784
- neighbors.push(
4785
- { x: x + 1, z: z + 1 },
4786
- { x: x - 1, z: z - 1 },
4787
- { x: x + 1, z: z - 1 },
4788
- { x: x - 1, z: z + 1 }
4789
- );
4790
- }
4791
- return neighbors;
4792
- }
4793
- function isInBounds(x, z, bounds) {
4794
- return x >= bounds.minX && x <= bounds.maxX && z >= bounds.minZ && z <= bounds.maxZ;
4795
- }
4796
- function getCellsInRadius(centerX, centerZ, radius) {
4797
- const cells = [];
4798
- const radiusSquared = radius * radius;
4799
- const minX = Math.floor(centerX - radius);
4800
- const maxX = Math.ceil(centerX + radius);
4801
- const minZ = Math.floor(centerZ - radius);
4802
- const maxZ = Math.ceil(centerZ + radius);
4803
- for (let x = minX; x <= maxX; x++) {
4804
- for (let z = minZ; z <= maxZ; z++) {
4805
- const dx = x - centerX;
4806
- const dz = z - centerZ;
4807
- if (dx * dx + dz * dz <= radiusSquared) {
4808
- cells.push({ x, z });
4809
- }
4810
- }
4811
- }
4812
- return cells;
4813
- }
4814
- function createGridHighlight(color = 16776960, opacity = 0.3) {
4815
- const geometry = new THREE6__namespace.PlaneGeometry(0.95, 0.95);
4816
- const material = new THREE6__namespace.MeshBasicMaterial({
4817
- color,
4818
- transparent: true,
4819
- opacity,
4820
- side: THREE6__namespace.DoubleSide
4821
- });
4822
- const mesh = new THREE6__namespace.Mesh(geometry, material);
4823
- mesh.rotation.x = -Math.PI / 2;
4824
- mesh.position.y = 0.01;
4825
- return mesh;
4826
- }
4827
- function normalizeMouseCoordinates(clientX, clientY, element) {
4828
- const rect = element.getBoundingClientRect();
4829
- return {
4830
- x: (clientX - rect.left) / rect.width * 2 - 1,
4831
- y: -((clientY - rect.top) / rect.height) * 2 + 1
4832
- };
4833
- }
4834
-
4835
2640
  // components/avl/lib/avl-schema-parser.ts
4836
2641
  function getEntity(orbital) {
4837
2642
  const entity = orbital.entity;
@@ -4926,10 +2731,10 @@ function parseApplicationLevel(schema) {
4926
2731
  }
4927
2732
  const count = schema.orbitals.length;
4928
2733
  const cols = Math.ceil(Math.sqrt(count));
4929
- const rows2 = Math.ceil(count / cols);
2734
+ const rows = Math.ceil(count / cols);
4930
2735
  const spacing = 200;
4931
2736
  const gridW = cols * spacing;
4932
- const gridH = rows2 * spacing;
2737
+ const gridH = rows * spacing;
4933
2738
  const originX = (600 - gridW) / 2 + spacing / 2;
4934
2739
  const originY = (400 - gridH) / 2 + spacing / 2;
4935
2740
  schema.orbitals.forEach((orbital, i) => {
@@ -5462,7 +3267,7 @@ Avl3DLabel.displayName = "Avl3DLabel";
5462
3267
  var Avl3DTooltip = ({
5463
3268
  position,
5464
3269
  title,
5465
- rows: rows2,
3270
+ rows,
5466
3271
  accentColor = "#5b9bd5"
5467
3272
  }) => {
5468
3273
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -5500,7 +3305,7 @@ var Avl3DTooltip = ({
5500
3305
  children: title
5501
3306
  }
5502
3307
  ),
5503
- rows2.map((row) => /* @__PURE__ */ jsxRuntime.jsxs(
3308
+ rows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs(
5504
3309
  Box,
5505
3310
  {
5506
3311
  style: {
@@ -5554,10 +3359,10 @@ var Avl3DOrbitalNode = ({
5554
3359
  position,
5555
3360
  onClick
5556
3361
  }) => {
5557
- const groupRef = React8.useRef(null);
5558
- const [hovered, setHovered] = React8.useState(false);
5559
- const scale = React8.useMemo(() => 0.6 + Math.min(traitCount, 8) * 0.3, [traitCount]);
5560
- const currentScale = React8.useRef(scale);
3362
+ const groupRef = React7.useRef(null);
3363
+ const [hovered, setHovered] = React7.useState(false);
3364
+ const scale = React7.useMemo(() => 0.6 + Math.min(traitCount, 8) * 0.3, [traitCount]);
3365
+ const currentScale = React7.useRef(scale);
5561
3366
  const targetScale = hovered ? scale * 1.06 : scale;
5562
3367
  fiber.useFrame((_, delta) => {
5563
3368
  if (!groupRef.current) return;
@@ -5699,7 +3504,7 @@ var Avl3DCrossWire = ({
5699
3504
  eventName,
5700
3505
  color = AVL_3D_COLORS.crossWire
5701
3506
  }) => {
5702
- const { tubeArgs, midpoint } = React8.useMemo(() => {
3507
+ const { tubeArgs, midpoint } = React7.useMemo(() => {
5703
3508
  const curve = arcCurve3D(from, to, 2);
5704
3509
  const mid = curve.getPoint(0.5);
5705
3510
  return {
@@ -5738,9 +3543,9 @@ var Avl3DApplicationScene = ({
5738
3543
  data,
5739
3544
  onOrbitalClick
5740
3545
  }) => {
5741
- const groupRef = React8.useRef(null);
5742
- const [, setHoverTrigger] = React8.useState(0);
5743
- const orbitalPositions = React8.useMemo(() => {
3546
+ const groupRef = React7.useRef(null);
3547
+ const [, setHoverTrigger] = React7.useState(0);
3548
+ const orbitalPositions = React7.useMemo(() => {
5744
3549
  const baseRadius = Math.max(4, data.orbitals.length * 1.5);
5745
3550
  if (data.crossLinks.length === 0) {
5746
3551
  return goldenSpiralPositions(data.orbitals.length, baseRadius);
@@ -5753,7 +3558,7 @@ var Avl3DApplicationScene = ({
5753
3558
  })).filter((e) => e.from >= 0 && e.to >= 0);
5754
3559
  return forceDirectedPositions(data.orbitals.length, edges, baseRadius);
5755
3560
  }, [data.orbitals, data.crossLinks]);
5756
- const positionByName = React8.useMemo(() => {
3561
+ const positionByName = React7.useMemo(() => {
5757
3562
  const map = /* @__PURE__ */ new Map();
5758
3563
  data.orbitals.forEach((orb, i) => {
5759
3564
  const pos = orbitalPositions[i];
@@ -5836,7 +3641,7 @@ var Avl3DEntityCore = ({
5836
3641
  position,
5837
3642
  fields
5838
3643
  }) => {
5839
- const coreRef = React8.useRef(null);
3644
+ const coreRef = React7.useRef(null);
5840
3645
  fiber.useFrame((_, delta) => {
5841
3646
  if (!coreRef.current) return;
5842
3647
  coreRef.current.rotation.y += delta * 0.3;
@@ -5989,8 +3794,8 @@ function TraitOrbit({
5989
3794
  onClick,
5990
3795
  onHover
5991
3796
  }) {
5992
- const groupRef = React8.useRef(null);
5993
- const angleRef = React8.useRef(Math.random() * Math.PI * 2);
3797
+ const groupRef = React7.useRef(null);
3798
+ const angleRef = React7.useRef(Math.random() * Math.PI * 2);
5994
3799
  fiber.useFrame((_, delta) => {
5995
3800
  angleRef.current += delta * speed;
5996
3801
  if (!groupRef.current) return;
@@ -6121,9 +3926,9 @@ var Avl3DOrbitalScene = ({
6121
3926
  highlightedTrait,
6122
3927
  onTraitHighlight
6123
3928
  }) => {
6124
- const [localHighlight, setLocalHighlight] = React8.useState(null);
3929
+ const [localHighlight, setLocalHighlight] = React7.useState(null);
6125
3930
  const activeHighlight = highlightedTrait ?? localHighlight;
6126
- const traitOrbits = React8.useMemo(() => {
3931
+ const traitOrbits = React7.useMemo(() => {
6127
3932
  return data.traits.map((trait, i) => ({
6128
3933
  name: trait.name,
6129
3934
  radius: 3 + i * 1.2,
@@ -6134,7 +3939,7 @@ var Avl3DOrbitalScene = ({
6134
3939
  transitionCount: trait.transitionCount
6135
3940
  }));
6136
3941
  }, [data.traits]);
6137
- const pagePositions = React8.useMemo(() => {
3942
+ const pagePositions = React7.useMemo(() => {
6138
3943
  const outerRadius = 3 + data.traits.length * 1.2 + 1.5;
6139
3944
  return data.pages.map((_, i) => {
6140
3945
  const angle = 2 * Math.PI * i / data.pages.length - Math.PI / 2;
@@ -6198,12 +4003,12 @@ var Avl3DStateNode = ({
6198
4003
  outgoingCount = 0,
6199
4004
  onClick
6200
4005
  }) => {
6201
- const [hovered, setHovered] = React8.useState(false);
4006
+ const [hovered, setHovered] = React7.useState(false);
6202
4007
  const baseColor = active ? AVL_3D_COLORS.stateActive : AVL_3D_COLORS.stateIdle;
6203
4008
  const emissiveColor = active ? AVL_3D_COLORS.stateActive : AVL_3D_COLORS.stateEdge;
6204
4009
  const emissiveIntensity = active ? 2 : hovered ? 0.8 : 0.3;
6205
4010
  const targetScale = hovered ? 1.08 : 1;
6206
- const currentScale = React8.useRef(1);
4011
+ const currentScale = React7.useRef(1);
6207
4012
  fiber.useFrame((_, delta) => {
6208
4013
  currentScale.current = THREE6.MathUtils.damp(currentScale.current, targetScale, 6, delta);
6209
4014
  });
@@ -6377,8 +4182,8 @@ var Avl3DTransitionArc = ({
6377
4182
  onClick,
6378
4183
  color = AVL_3D_COLORS.transitionArc
6379
4184
  }) => {
6380
- const [hovered, setHovered] = React8.useState(false);
6381
- const { tubeArgs, labelPos, guardPos, arrowPos, arrowQuat, effectPositions } = React8.useMemo(() => {
4185
+ const [hovered, setHovered] = React7.useState(false);
4186
+ const { tubeArgs, labelPos, guardPos, arrowPos, arrowQuat, effectPositions } = React7.useMemo(() => {
6382
4187
  const offset = 1.5 + index * 0.8;
6383
4188
  const curve = isSelf ? selfLoopCurve3D(from, 1.2) : arcCurve3D(from, to, offset);
6384
4189
  const mid = curve.getPoint(0.5);
@@ -6511,7 +4316,7 @@ var Avl3DTraitScene = ({
6511
4316
  data,
6512
4317
  onTransitionClick
6513
4318
  }) => {
6514
- const statePositions = React8.useMemo(() => {
4319
+ const statePositions = React7.useMemo(() => {
6515
4320
  const radius = Math.max(3, data.states.length * 0.8);
6516
4321
  const positions = fibonacciSpherePositions(data.states.length, radius);
6517
4322
  const posMap = /* @__PURE__ */ new Map();
@@ -6521,7 +4326,7 @@ var Avl3DTraitScene = ({
6521
4326
  });
6522
4327
  return posMap;
6523
4328
  }, [data.states]);
6524
- const transitionIndices = React8.useMemo(() => {
4329
+ const transitionIndices = React7.useMemo(() => {
6525
4330
  const pairCount = /* @__PURE__ */ new Map();
6526
4331
  return data.transitions.map((t) => {
6527
4332
  const key = `${t.from}->${t.to}`;
@@ -6530,7 +4335,7 @@ var Avl3DTraitScene = ({
6530
4335
  return idx;
6531
4336
  });
6532
4337
  }, [data.transitions]);
6533
- const transitionCounts = React8.useMemo(() => {
4338
+ const transitionCounts = React7.useMemo(() => {
6534
4339
  const incoming = /* @__PURE__ */ new Map();
6535
4340
  const outgoing = /* @__PURE__ */ new Map();
6536
4341
  for (const t of data.transitions) {
@@ -6640,11 +4445,11 @@ var Avl3DExprTree = ({
6640
4445
  expression,
6641
4446
  position
6642
4447
  }) => {
6643
- const layoutResults = React8.useMemo(() => {
4448
+ const layoutResults = React7.useMemo(() => {
6644
4449
  const origin = { x: position[0], y: position[1], z: position[2] };
6645
4450
  return treeLayout3D(expression, origin, 2.5, 1.5);
6646
4451
  }, [expression, position]);
6647
- const edges = React8.useMemo(() => {
4452
+ const edges = React7.useMemo(() => {
6648
4453
  const result = [];
6649
4454
  function collectEdges(node, parentPos) {
6650
4455
  const entry = layoutResults.find((r) => r.node === node);
@@ -6924,7 +4729,7 @@ var Avl3DEffects = ({
6924
4729
  const dof = DOF_CONFIG[level];
6925
4730
  const sparkles = SPARKLE_CONFIGS[level];
6926
4731
  const stars = STARS_CONFIG[level];
6927
- const starsKey = React8.useMemo(() => `stars-${level}`, [level]);
4732
+ const starsKey = React7.useMemo(() => `stars-${level}`, [level]);
6928
4733
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6929
4734
  /* @__PURE__ */ jsxRuntime.jsx(
6930
4735
  drei.Stars,
@@ -6988,16 +4793,16 @@ var DEFAULT_CONFIG2 = {
6988
4793
  modelOverrides: {},
6989
4794
  effectsEnabled: true
6990
4795
  };
6991
- var Avl3DContext = React8.createContext(DEFAULT_CONFIG2);
4796
+ var Avl3DContext = React7.createContext(DEFAULT_CONFIG2);
6992
4797
  function useAvl3DConfig() {
6993
- return React8.useContext(Avl3DContext);
4798
+ return React7.useContext(Avl3DContext);
6994
4799
  }
6995
- function CameraController2({ targetPosition, targetLookAt, animated }) {
4800
+ function CameraController({ targetPosition, targetLookAt, animated }) {
6996
4801
  const { camera } = fiber.useThree();
6997
- const targetPosVec = React8.useRef(new THREE6__namespace.Vector3(...targetPosition));
6998
- const targetLookVec = React8.useRef(new THREE6__namespace.Vector3(...targetLookAt));
6999
- const isAnimating = React8.useRef(false);
7000
- React8.useEffect(() => {
4802
+ const targetPosVec = React7.useRef(new THREE6__namespace.Vector3(...targetPosition));
4803
+ const targetLookVec = React7.useRef(new THREE6__namespace.Vector3(...targetLookAt));
4804
+ const isAnimating = React7.useRef(false);
4805
+ React7.useEffect(() => {
7001
4806
  const newTarget = new THREE6__namespace.Vector3(...targetPosition);
7002
4807
  const newLookAt = new THREE6__namespace.Vector3(...targetLookAt);
7003
4808
  if (!newTarget.equals(targetPosVec.current) || !newLookAt.equals(targetLookVec.current)) {
@@ -7026,8 +4831,8 @@ function CameraController2({ targetPosition, targetLookAt, animated }) {
7026
4831
  return null;
7027
4832
  }
7028
4833
  function SceneFade({ animating, children }) {
7029
- const groupRef = React8.useRef(null);
7030
- const opacityRef = React8.useRef(1);
4834
+ const groupRef = React7.useRef(null);
4835
+ const opacityRef = React7.useRef(1);
7031
4836
  fiber.useFrame((_, delta) => {
7032
4837
  if (!groupRef.current) return;
7033
4838
  const target = animating ? 0 : 1;
@@ -7051,7 +4856,7 @@ var Avl3DViewer = ({
7051
4856
  modelOverrides = {}
7052
4857
  }) => {
7053
4858
  const { t } = hooks.useTranslate();
7054
- const schema = React8.useMemo(() => {
4859
+ const schema = React7.useMemo(() => {
7055
4860
  if (typeof schemaProp === "string") {
7056
4861
  try {
7057
4862
  return JSON.parse(schemaProp);
@@ -7061,12 +4866,12 @@ var Avl3DViewer = ({
7061
4866
  }
7062
4867
  return schemaProp;
7063
4868
  }, [schemaProp]);
7064
- const configValue = React8.useMemo(() => ({
4869
+ const configValue = React7.useMemo(() => ({
7065
4870
  modelOverrides,
7066
4871
  effectsEnabled: effects
7067
4872
  }), [modelOverrides, effects]);
7068
- const [state, dispatch] = React8.useReducer(zoomReducer, initialZoomState);
7069
- React8.useEffect(() => {
4873
+ const [state, dispatch] = React7.useReducer(zoomReducer, initialZoomState);
4874
+ React7.useEffect(() => {
7070
4875
  if (initialOrbital) {
7071
4876
  dispatch({ type: "ZOOM_INTO_ORBITAL", orbital: initialOrbital, targetPosition: { x: 0, y: 0 } });
7072
4877
  setTimeout(() => dispatch({ type: "ANIMATION_COMPLETE" }), 0);
@@ -7078,35 +4883,35 @@ var Avl3DViewer = ({
7078
4883
  }
7079
4884
  }
7080
4885
  }, [initialOrbital, initialTrait]);
7081
- React8.useEffect(() => {
4886
+ React7.useEffect(() => {
7082
4887
  onZoomChange?.(state.level, {
7083
4888
  orbital: state.selectedOrbital ?? void 0,
7084
4889
  trait: state.selectedTrait ?? void 0
7085
4890
  });
7086
4891
  }, [state.level, state.selectedOrbital, state.selectedTrait, onZoomChange]);
7087
- React8.useEffect(() => {
4892
+ React7.useEffect(() => {
7088
4893
  if (!state.animating) return;
7089
4894
  const timer = setTimeout(() => {
7090
4895
  dispatch({ type: "ANIMATION_COMPLETE" });
7091
4896
  }, 800);
7092
4897
  return () => clearTimeout(timer);
7093
4898
  }, [state.animating]);
7094
- const handleKeyDown = React8.useCallback((e) => {
4899
+ const handleKeyDown = React7.useCallback((e) => {
7095
4900
  if (e.key === "Escape") {
7096
4901
  dispatch({ type: "ZOOM_OUT" });
7097
4902
  }
7098
4903
  }, []);
7099
- const handleOrbitalClick = React8.useCallback((name) => {
4904
+ const handleOrbitalClick = React7.useCallback((name) => {
7100
4905
  dispatch({ type: "ZOOM_INTO_ORBITAL", orbital: name, targetPosition: { x: 0, y: 0 } });
7101
4906
  }, []);
7102
- const handleTraitClick = React8.useCallback((name) => {
4907
+ const handleTraitClick = React7.useCallback((name) => {
7103
4908
  dispatch({ type: "ZOOM_INTO_TRAIT", trait: name, targetPosition: { x: 0, y: 0 } });
7104
4909
  }, []);
7105
- const [highlightedTrait, setHighlightedTrait] = React8__namespace.default.useState(null);
7106
- const handleTransitionClick = React8.useCallback((index) => {
4910
+ const [highlightedTrait, setHighlightedTrait] = React7__default.default.useState(null);
4911
+ const handleTransitionClick = React7.useCallback((index) => {
7107
4912
  dispatch({ type: "ZOOM_INTO_TRANSITION", transitionIndex: index, targetPosition: { x: 0, y: 0 } });
7108
4913
  }, []);
7109
- const handleBreadcrumbClick = React8.useCallback((targetLevel) => {
4914
+ const handleBreadcrumbClick = React7.useCallback((targetLevel) => {
7110
4915
  const levelOrder = ["application", "orbital", "trait", "transition"];
7111
4916
  const currentIdx = levelOrder.indexOf(state.level);
7112
4917
  const targetIdx = levelOrder.indexOf(targetLevel);
@@ -7115,7 +4920,7 @@ var Avl3DViewer = ({
7115
4920
  }
7116
4921
  }, [state.level]);
7117
4922
  const cameraConfig = CAMERA_POSITIONS[state.level];
7118
- const sceneContent = React8.useMemo(() => {
4923
+ const sceneContent = React7.useMemo(() => {
7119
4924
  switch (state.level) {
7120
4925
  case "application": {
7121
4926
  const data = parseApplicationLevel(schema);
@@ -7189,7 +4994,7 @@ var Avl3DViewer = ({
7189
4994
  gap: "xs",
7190
4995
  align: "center",
7191
4996
  className: "absolute top-2 left-2 z-10 bg-surface/80 backdrop-blur rounded-md px-3 py-1.5",
7192
- children: breadcrumbs.map((crumb, i) => /* @__PURE__ */ jsxRuntime.jsxs(React8__namespace.default.Fragment, { children: [
4997
+ children: breadcrumbs.map((crumb, i) => /* @__PURE__ */ jsxRuntime.jsxs(React7__default.default.Fragment, { children: [
7193
4998
  i > 0 && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", className: "mx-1", children: "/" }),
7194
4999
  i < breadcrumbs.length - 1 ? /* @__PURE__ */ jsxRuntime.jsx(
7195
5000
  Box,
@@ -7264,7 +5069,7 @@ var Avl3DViewer = ({
7264
5069
  }
7265
5070
  ),
7266
5071
  /* @__PURE__ */ jsxRuntime.jsx(
7267
- CameraController2,
5072
+ CameraController,
7268
5073
  {
7269
5074
  targetPosition: cameraConfig.position,
7270
5075
  targetLookAt: cameraConfig.target,
@@ -7478,11 +5283,7 @@ exports.CAMERA_POSITIONS = CAMERA_POSITIONS;
7478
5283
  exports.Camera3D = Camera3D;
7479
5284
  exports.Canvas3DErrorBoundary = Canvas3DErrorBoundary;
7480
5285
  exports.Canvas3DLoadingState = Canvas3DLoadingState;
7481
- exports.GameBoard3D = GameBoard3D;
7482
5286
  exports.GameCanvas3D = GameCanvas3D;
7483
- exports.GameCanvas3DBattleTemplate = GameCanvas3DBattleTemplate;
7484
- exports.GameCanvas3DCastleTemplate = GameCanvas3DCastleTemplate;
7485
- exports.GameCanvas3DWorldMapTemplate = GameCanvas3DWorldMapTemplate;
7486
5287
  exports.Lighting3D = Lighting3D;
7487
5288
  exports.ModelLoader = ModelLoader;
7488
5289
  exports.Scene3D = Scene3D;