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