@almadar/ui 5.103.0 → 5.105.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 (80) hide show
  1. package/dist/avl/index.cjs +168 -88
  2. package/dist/avl/index.css +3 -3
  3. package/dist/avl/index.js +168 -88
  4. package/dist/components/core/atoms/AtlasImage.d.ts +2 -2
  5. package/dist/components/core/atoms/FlipContainer.d.ts +1 -1
  6. package/dist/components/core/molecules/Card.d.ts +1 -1
  7. package/dist/components/core/molecules/Chart.d.ts +3 -3
  8. package/dist/components/core/molecules/DataGrid.d.ts +1 -1
  9. package/dist/components/core/molecules/DataList.d.ts +1 -1
  10. package/dist/components/core/molecules/DocPagination.d.ts +8 -1
  11. package/dist/components/core/molecules/DocSearch.d.ts +8 -1
  12. package/dist/components/core/molecules/FlipCard.d.ts +1 -1
  13. package/dist/components/core/molecules/LineChart.d.ts +3 -1
  14. package/dist/components/core/molecules/MapView.d.ts +18 -1
  15. package/dist/components/core/molecules/SortableList.d.ts +1 -1
  16. package/dist/components/core/molecules/TableView.d.ts +1 -1
  17. package/dist/components/core/organisms/DataTable.d.ts +7 -3
  18. package/dist/components/core/organisms/debug/RuntimeDebugger.d.ts +10 -1
  19. package/dist/components/core/organisms/debug/tabs/EntitiesTab.d.ts +2 -1
  20. package/dist/components/core/organisms/debug/tabs/EventDispatcherTab.d.ts +2 -1
  21. package/dist/components/core/organisms/debug/tabs/EventFlowTab.d.ts +6 -1
  22. package/dist/components/core/organisms/debug/tabs/GuardsPanel.d.ts +6 -1
  23. package/dist/components/core/organisms/debug/tabs/ServerBridgeTab.d.ts +12 -1
  24. package/dist/components/core/organisms/debug/tabs/TicksTab.d.ts +2 -1
  25. package/dist/components/core/organisms/debug/tabs/TraitsTab.d.ts +2 -1
  26. package/dist/components/core/organisms/debug/tabs/TransitionTimeline.d.ts +11 -1
  27. package/dist/components/core/organisms/debug/tabs/VerificationTab.d.ts +8 -1
  28. package/dist/components/game/2d/atoms/ActionButton.d.ts +2 -1
  29. package/dist/components/game/2d/atoms/ChoiceButton.d.ts +2 -1
  30. package/dist/components/game/2d/atoms/ComboCounter.d.ts +2 -1
  31. package/dist/components/game/2d/atoms/ControlButton.d.ts +2 -1
  32. package/dist/components/game/2d/atoms/DamageNumber.d.ts +2 -1
  33. package/dist/components/game/2d/atoms/DialogueBubble.d.ts +2 -1
  34. package/dist/components/game/2d/atoms/GameIcon.d.ts +2 -1
  35. package/dist/components/game/2d/atoms/HealthBar.d.ts +2 -1
  36. package/dist/components/game/2d/atoms/ItemSlot.d.ts +2 -1
  37. package/dist/components/game/2d/atoms/MiniMap.d.ts +2 -1
  38. package/dist/components/game/2d/atoms/ResourceCounter.d.ts +2 -1
  39. package/dist/components/game/2d/atoms/ScoreDisplay.d.ts +2 -1
  40. package/dist/components/game/2d/atoms/StatusEffect.d.ts +2 -1
  41. package/dist/components/game/2d/atoms/TimerDisplay.d.ts +2 -1
  42. package/dist/components/game/2d/atoms/TurnIndicator.d.ts +2 -1
  43. package/dist/components/game/2d/atoms/WaypointMarker.d.ts +2 -1
  44. package/dist/components/game/2d/molecules/Canvas2D.d.ts +6 -5
  45. package/dist/components/game/2d/molecules/GameHud.d.ts +2 -1
  46. package/dist/components/game/2d/molecules/GameMenu.d.ts +2 -1
  47. package/dist/components/game/2d/molecules/InventoryGrid.d.ts +2 -1
  48. package/dist/components/game/2d/molecules/ResourceBar.d.ts +2 -1
  49. package/dist/components/game/2d/molecules/SideCanvas2D.d.ts +36 -0
  50. package/dist/components/game/2d/molecules/StatBadge.d.ts +2 -1
  51. package/dist/components/game/3d/atoms/EventMarker3D.d.ts +17 -0
  52. package/dist/components/game/3d/atoms/SpriteBillboard3D.d.ts +18 -0
  53. package/dist/components/game/3d/index.cjs +539 -496
  54. package/dist/components/game/3d/index.js +367 -324
  55. package/dist/components/game/3d/molecules/FeatureMesh3D.d.ts +21 -0
  56. package/dist/components/game/3d/molecules/GameCamera3D.d.ts +35 -0
  57. package/dist/components/game/3d/molecules/Lighting3D.d.ts +10 -2
  58. package/dist/components/game/3d/molecules/SideScene3D.d.ts +31 -0
  59. package/dist/components/game/3d/molecules/TileMesh3D.d.ts +28 -0
  60. package/dist/components/game/3d/molecules/UnitMesh3D.d.ts +29 -0
  61. package/dist/components/game/shared/game3dTheme.d.ts +55 -0
  62. package/dist/components/game/shared/isometric.d.ts +38 -0
  63. package/dist/components/game/shared/isometricTypes.d.ts +4 -0
  64. package/dist/components/game/shared/lib/editorUtils.d.ts +8 -8
  65. package/dist/components/index.cjs +156 -83
  66. package/dist/components/index.css +3 -3
  67. package/dist/components/index.js +160 -87
  68. package/dist/hooks/useGitHub.d.ts +7 -7
  69. package/dist/marketing/index.cjs +2 -2
  70. package/dist/marketing/index.js +2 -2
  71. package/dist/providers/EventBusProvider.d.ts +1 -1
  72. package/dist/providers/SelectionProvider.d.ts +1 -1
  73. package/dist/providers/ServerBridge.d.ts +1 -1
  74. package/dist/providers/index.cjs +164 -84
  75. package/dist/providers/index.css +3 -3
  76. package/dist/providers/index.js +164 -84
  77. package/dist/runtime/index.cjs +164 -84
  78. package/dist/runtime/index.css +3 -3
  79. package/dist/runtime/index.js +164 -84
  80. package/package.json +10 -15
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var React8 = require('react');
3
+ var React10 = require('react');
4
4
  var fiber = require('@react-three/fiber');
5
5
  var THREE6 = require('three');
6
6
  var jsxRuntime = require('react/jsx-runtime');
@@ -36,7 +36,7 @@ function _interopNamespace(e) {
36
36
  return Object.freeze(n);
37
37
  }
38
38
 
39
- var React8__namespace = /*#__PURE__*/_interopNamespace(React8);
39
+ var React10__namespace = /*#__PURE__*/_interopNamespace(React10);
40
40
  var THREE6__namespace = /*#__PURE__*/_interopNamespace(THREE6);
41
41
  var LucideIcons__namespace = /*#__PURE__*/_interopNamespace(LucideIcons);
42
42
 
@@ -45,8 +45,8 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
45
45
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
46
46
  function Scene3D({ background = "#1a1a2e", fog, children }) {
47
47
  const { scene } = fiber.useThree();
48
- const initializedRef = React8.useRef(false);
49
- React8.useEffect(() => {
48
+ const initializedRef = React10.useRef(false);
49
+ React10.useEffect(() => {
50
50
  if (initializedRef.current) return;
51
51
  initializedRef.current = true;
52
52
  if (background.startsWith("#") || background.startsWith("rgb")) {
@@ -67,7 +67,7 @@ function Scene3D({ background = "#1a1a2e", fog, children }) {
67
67
  }, [scene, background, fog]);
68
68
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
69
69
  }
70
- var Camera3D = React8.forwardRef(
70
+ var Camera3D = React10.forwardRef(
71
71
  ({
72
72
  mode = "isometric",
73
73
  position = [10, 10, 10],
@@ -80,10 +80,10 @@ var Camera3D = React8.forwardRef(
80
80
  onChange
81
81
  }, ref) => {
82
82
  const { camera, set, viewport } = fiber.useThree();
83
- const controlsRef = React8.useRef(null);
84
- const initialPosition = React8.useRef(new THREE6__namespace.Vector3(...position));
85
- const initialTarget = React8.useRef(new THREE6__namespace.Vector3(...target));
86
- React8.useEffect(() => {
83
+ const controlsRef = 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(() => {
87
87
  let newCamera;
88
88
  if (mode === "isometric") {
89
89
  const aspect = viewport.aspect;
@@ -114,7 +114,7 @@ var Camera3D = React8.forwardRef(
114
114
  onChange(camera);
115
115
  }
116
116
  });
117
- React8.useImperativeHandle(ref, () => ({
117
+ React10.useImperativeHandle(ref, () => ({
118
118
  getCamera: () => camera,
119
119
  setPosition: (x, y, z) => {
120
120
  camera.position.set(x, y, z);
@@ -170,6 +170,10 @@ function Lighting3D({
170
170
  shadows = true,
171
171
  shadowMapSize = 2048,
172
172
  shadowCameraSize = 20,
173
+ shadowBias = -1e-3,
174
+ shadowNormalBias,
175
+ shadowCameraNear = 0.1,
176
+ shadowCameraFar = 100,
173
177
  showHelpers = false
174
178
  }) {
175
179
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -186,9 +190,10 @@ function Lighting3D({
186
190
  "shadow-camera-right": shadowCameraSize,
187
191
  "shadow-camera-top": shadowCameraSize,
188
192
  "shadow-camera-bottom": -shadowCameraSize,
189
- "shadow-camera-near": 0.1,
190
- "shadow-camera-far": 100,
191
- "shadow-bias": -1e-3
193
+ "shadow-camera-near": shadowCameraNear,
194
+ "shadow-camera-far": shadowCameraFar,
195
+ "shadow-bias": shadowBias,
196
+ "shadow-normalBias": shadowNormalBias
192
197
  }
193
198
  ),
194
199
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -256,7 +261,7 @@ function Canvas3DLoadingState({
256
261
  ] });
257
262
  }
258
263
  var log = logger.createLogger("almadar:ui:game:canvas3d:error-boundary");
259
- var Canvas3DErrorBoundary = class extends React8.Component {
264
+ var Canvas3DErrorBoundary = class extends React10.Component {
260
265
  constructor(props) {
261
266
  super(props);
262
267
  __publicField(this, "handleReset", () => {
@@ -371,10 +376,10 @@ function stateFromEntry(entry) {
371
376
  return { model: null, clips: [], isLoading: !entry.error, error: entry.error ?? null };
372
377
  }
373
378
  function useGLTFModel(url, resourceBasePath) {
374
- const [state, setState] = React8.useState(
379
+ const [state, setState] = React10.useState(
375
380
  () => stateFromEntry(url ? loadGltfCached(url, resourceBasePath || detectAssetRoot(url)) : null)
376
381
  );
377
- React8.useEffect(() => {
382
+ React10.useEffect(() => {
378
383
  if (!url) {
379
384
  setState({ model: null, clips: [], isLoading: false, error: null });
380
385
  return;
@@ -409,7 +414,7 @@ function ModelLoader({
409
414
  tint
410
415
  }) {
411
416
  const { model: loadedModel, clips, isLoading, error } = useGLTFModel(url, resourceBasePath);
412
- const model = React8.useMemo(() => {
417
+ const model = React10.useMemo(() => {
413
418
  if (!loadedModel) return null;
414
419
  const cloned = SkeletonUtils.clone(loadedModel);
415
420
  cloned.updateMatrixWorld(true);
@@ -427,8 +432,8 @@ function ModelLoader({
427
432
  });
428
433
  return cloned;
429
434
  }, [loadedModel, castShadow, receiveShadow, tint]);
430
- const mixer = React8.useMemo(() => model ? new THREE6__namespace.AnimationMixer(model) : null, [model]);
431
- React8.useEffect(() => {
435
+ const mixer = React10.useMemo(() => model ? new THREE6__namespace.AnimationMixer(model) : null, [model]);
436
+ React10.useEffect(() => {
432
437
  if (!mixer || !animation || clips.length === 0) return;
433
438
  const wanted = animation.toLowerCase();
434
439
  const clip = clips.find((c) => c.name === animation) ?? clips.find((c) => c.name.toLowerCase() === wanted);
@@ -442,7 +447,7 @@ function ModelLoader({
442
447
  fiber.useFrame((_, delta) => {
443
448
  mixer?.update(delta);
444
449
  });
445
- const normFactor = React8.useMemo(() => {
450
+ const normFactor = React10.useMemo(() => {
446
451
  if (!model) return 1;
447
452
  const box = new THREE6__namespace.Box3().setFromObject(model);
448
453
  const size = new THREE6__namespace.Vector3();
@@ -451,11 +456,11 @@ function ModelLoader({
451
456
  if (!Number.isFinite(maxDim) || maxDim < 0.05) return 1;
452
457
  return 1 / maxDim;
453
458
  }, [model]);
454
- const scaleArray = React8.useMemo(() => {
459
+ const scaleArray = React10.useMemo(() => {
455
460
  const base = typeof scale === "number" ? [scale, scale, scale] : scale;
456
461
  return [base[0] * normFactor, base[1] * normFactor, base[2] * normFactor];
457
462
  }, [scale, normFactor]);
458
- const rotationRad = React8.useMemo(() => {
463
+ const rotationRad = React10.useMemo(() => {
459
464
  return [
460
465
  rotation[0] * Math.PI / 180,
461
466
  rotation[1] * Math.PI / 180,
@@ -735,21 +740,21 @@ var DEFAULT_OPTIONS = {
735
740
  };
736
741
  function useThree3(options = {}) {
737
742
  const opts = { ...DEFAULT_OPTIONS, ...options };
738
- const containerRef = React8.useRef(null);
739
- const canvasRef = React8.useRef(null);
740
- const rendererRef = React8.useRef(null);
741
- const sceneRef = React8.useRef(null);
742
- const cameraRef = React8.useRef(null);
743
- const controlsRef = React8.useRef(null);
744
- const gridHelperRef = React8.useRef(null);
745
- const rafRef = React8.useRef(0);
746
- const [isReady, setIsReady] = React8.useState(false);
747
- const [dimensions, setDimensions] = React8.useState({ width: 0, height: 0 });
748
- const initialCameraPosition = React8.useMemo(
743
+ const containerRef = 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(
749
754
  () => new THREE6__namespace.Vector3(...opts.cameraPosition),
750
755
  []
751
756
  );
752
- React8.useEffect(() => {
757
+ React10.useEffect(() => {
753
758
  if (!containerRef.current) return;
754
759
  const container = containerRef.current;
755
760
  const { clientWidth, clientHeight } = container;
@@ -842,7 +847,7 @@ function useThree3(options = {}) {
842
847
  scene.clear();
843
848
  };
844
849
  }, []);
845
- React8.useEffect(() => {
850
+ React10.useEffect(() => {
846
851
  if (!cameraRef.current || !containerRef.current) return;
847
852
  const container = containerRef.current;
848
853
  const { clientWidth, clientHeight } = container;
@@ -877,20 +882,20 @@ function useThree3(options = {}) {
877
882
  animate();
878
883
  }
879
884
  }, [opts.cameraMode]);
880
- const setCameraPosition = React8.useCallback((x, y, z) => {
885
+ const setCameraPosition = React10.useCallback((x, y, z) => {
881
886
  if (cameraRef.current) {
882
887
  cameraRef.current.position.set(x, y, z);
883
888
  controlsRef.current?.update();
884
889
  }
885
890
  }, []);
886
- const lookAt = React8.useCallback((x, y, z) => {
891
+ const lookAt = React10.useCallback((x, y, z) => {
887
892
  if (cameraRef.current) {
888
893
  cameraRef.current.lookAt(x, y, z);
889
894
  controlsRef.current?.target.set(x, y, z);
890
895
  controlsRef.current?.update();
891
896
  }
892
897
  }, []);
893
- const resetCamera = React8.useCallback(() => {
898
+ const resetCamera = React10.useCallback(() => {
894
899
  if (cameraRef.current) {
895
900
  cameraRef.current.position.copy(initialCameraPosition);
896
901
  cameraRef.current.lookAt(0, 0, 0);
@@ -900,7 +905,7 @@ function useThree3(options = {}) {
900
905
  }
901
906
  }
902
907
  }, [initialCameraPosition]);
903
- const fitView = React8.useCallback(
908
+ const fitView = React10.useCallback(
904
909
  (bounds) => {
905
910
  if (!cameraRef.current) return;
906
911
  const centerX = (bounds.minX + bounds.maxX) / 2;
@@ -931,20 +936,20 @@ function useThree3(options = {}) {
931
936
  }
932
937
  function useAssetLoader(options = {}) {
933
938
  const { preloadUrls = [], loader: customLoader } = options;
934
- const loaderRef = React8.useRef(customLoader || new AssetLoader());
935
- const [state, setState] = React8.useState({
939
+ const loaderRef = React10.useRef(customLoader || new AssetLoader());
940
+ const [state, setState] = React10.useState({
936
941
  isLoading: false,
937
942
  progress: 0,
938
943
  loaded: 0,
939
944
  total: 0,
940
945
  errors: []
941
946
  });
942
- React8.useEffect(() => {
947
+ React10.useEffect(() => {
943
948
  if (preloadUrls.length > 0) {
944
949
  preload(preloadUrls);
945
950
  }
946
951
  }, []);
947
- const updateProgress = React8.useCallback((loaded, total) => {
952
+ const updateProgress = React10.useCallback((loaded, total) => {
948
953
  setState((prev) => ({
949
954
  ...prev,
950
955
  loaded,
@@ -952,7 +957,7 @@ function useAssetLoader(options = {}) {
952
957
  progress: total > 0 ? Math.round(loaded / total * 100) : 0
953
958
  }));
954
959
  }, []);
955
- const loadModel = React8.useCallback(
960
+ const loadModel = React10.useCallback(
956
961
  async (url) => {
957
962
  setState((prev) => ({ ...prev, isLoading: true }));
958
963
  try {
@@ -975,7 +980,7 @@ function useAssetLoader(options = {}) {
975
980
  },
976
981
  []
977
982
  );
978
- const loadOBJ = React8.useCallback(
983
+ const loadOBJ = React10.useCallback(
979
984
  async (url) => {
980
985
  setState((prev) => ({ ...prev, isLoading: true }));
981
986
  try {
@@ -998,7 +1003,7 @@ function useAssetLoader(options = {}) {
998
1003
  },
999
1004
  []
1000
1005
  );
1001
- const loadTexture = React8.useCallback(
1006
+ const loadTexture = React10.useCallback(
1002
1007
  async (url) => {
1003
1008
  setState((prev) => ({ ...prev, isLoading: true }));
1004
1009
  try {
@@ -1021,7 +1026,7 @@ function useAssetLoader(options = {}) {
1021
1026
  },
1022
1027
  []
1023
1028
  );
1024
- const preload = React8.useCallback(
1029
+ const preload = React10.useCallback(
1025
1030
  async (urls) => {
1026
1031
  setState((prev) => ({
1027
1032
  ...prev,
@@ -1060,27 +1065,27 @@ function useAssetLoader(options = {}) {
1060
1065
  },
1061
1066
  [updateProgress]
1062
1067
  );
1063
- const hasModel = React8.useCallback((url) => {
1068
+ const hasModel = React10.useCallback((url) => {
1064
1069
  return loaderRef.current.hasModel(url);
1065
1070
  }, []);
1066
- const hasTexture = React8.useCallback((url) => {
1071
+ const hasTexture = React10.useCallback((url) => {
1067
1072
  return loaderRef.current.hasTexture(url);
1068
1073
  }, []);
1069
- const getModel = React8.useCallback((url) => {
1074
+ const getModel = React10.useCallback((url) => {
1070
1075
  try {
1071
1076
  return loaderRef.current.getModel(url);
1072
1077
  } catch {
1073
1078
  return void 0;
1074
1079
  }
1075
1080
  }, []);
1076
- const getTexture = React8.useCallback((url) => {
1081
+ const getTexture = React10.useCallback((url) => {
1077
1082
  try {
1078
1083
  return loaderRef.current.getTexture(url);
1079
1084
  } catch {
1080
1085
  return void 0;
1081
1086
  }
1082
1087
  }, []);
1083
- const clearCache = React8.useCallback(() => {
1088
+ const clearCache = React10.useCallback(() => {
1084
1089
  loaderRef.current.clearCache();
1085
1090
  setState({
1086
1091
  isLoading: false,
@@ -1104,25 +1109,25 @@ function useAssetLoader(options = {}) {
1104
1109
  };
1105
1110
  }
1106
1111
  function useSceneGraph() {
1107
- const nodesRef = React8.useRef(/* @__PURE__ */ new Map());
1108
- const addNode = React8.useCallback((node) => {
1112
+ const nodesRef = React10.useRef(/* @__PURE__ */ new Map());
1113
+ const addNode = React10.useCallback((node) => {
1109
1114
  const existing = nodesRef.current.get(node.id);
1110
1115
  if (existing) {
1111
1116
  existing.mesh.removeFromParent();
1112
1117
  }
1113
1118
  nodesRef.current.set(node.id, node);
1114
1119
  }, []);
1115
- const removeNode = React8.useCallback((id) => {
1120
+ const removeNode = React10.useCallback((id) => {
1116
1121
  const node = nodesRef.current.get(id);
1117
1122
  if (node) {
1118
1123
  node.mesh.removeFromParent();
1119
1124
  nodesRef.current.delete(id);
1120
1125
  }
1121
1126
  }, []);
1122
- const getNode = React8.useCallback((id) => {
1127
+ const getNode = React10.useCallback((id) => {
1123
1128
  return nodesRef.current.get(id);
1124
1129
  }, []);
1125
- const updateNodePosition = React8.useCallback(
1130
+ const updateNodePosition = React10.useCallback(
1126
1131
  (id, x, y, z) => {
1127
1132
  const node = nodesRef.current.get(id);
1128
1133
  if (node) {
@@ -1132,7 +1137,7 @@ function useSceneGraph() {
1132
1137
  },
1133
1138
  []
1134
1139
  );
1135
- const updateNodeGridPosition = React8.useCallback(
1140
+ const updateNodeGridPosition = React10.useCallback(
1136
1141
  (id, gridX, gridZ) => {
1137
1142
  const node = nodesRef.current.get(id);
1138
1143
  if (node) {
@@ -1141,7 +1146,7 @@ function useSceneGraph() {
1141
1146
  },
1142
1147
  []
1143
1148
  );
1144
- const getNodeAtGrid = React8.useCallback(
1149
+ const getNodeAtGrid = React10.useCallback(
1145
1150
  (x, z, type) => {
1146
1151
  return Array.from(nodesRef.current.values()).find((node) => {
1147
1152
  const matchesGrid = node.gridPosition.x === x && node.gridPosition.z === z;
@@ -1150,10 +1155,10 @@ function useSceneGraph() {
1150
1155
  },
1151
1156
  []
1152
1157
  );
1153
- const getNodesByType = React8.useCallback((type) => {
1158
+ const getNodesByType = React10.useCallback((type) => {
1154
1159
  return Array.from(nodesRef.current.values()).filter((node) => node.type === type);
1155
1160
  }, []);
1156
- const getNodesInBounds = React8.useCallback(
1161
+ const getNodesInBounds = React10.useCallback(
1157
1162
  (minX, maxX, minZ, maxZ) => {
1158
1163
  return Array.from(nodesRef.current.values()).filter((node) => {
1159
1164
  const { x, z } = node.gridPosition;
@@ -1162,13 +1167,13 @@ function useSceneGraph() {
1162
1167
  },
1163
1168
  []
1164
1169
  );
1165
- const clearNodes = React8.useCallback(() => {
1170
+ const clearNodes = React10.useCallback(() => {
1166
1171
  nodesRef.current.forEach((node) => {
1167
1172
  node.mesh.removeFromParent();
1168
1173
  });
1169
1174
  nodesRef.current.clear();
1170
1175
  }, []);
1171
- const countNodes = React8.useCallback((type) => {
1176
+ const countNodes = React10.useCallback((type) => {
1172
1177
  if (!type) {
1173
1178
  return nodesRef.current.size;
1174
1179
  }
@@ -1190,9 +1195,9 @@ function useSceneGraph() {
1190
1195
  }
1191
1196
  function useRaycaster(options) {
1192
1197
  const { camera, canvas, cellSize = 1, offsetX = 0, offsetZ = 0 } = options;
1193
- const raycaster = React8.useRef(new THREE6__namespace.Raycaster());
1194
- const mouse = React8.useRef(new THREE6__namespace.Vector2());
1195
- const clientToNDC = React8.useCallback(
1198
+ const raycaster = React10.useRef(new THREE6__namespace.Raycaster());
1199
+ const mouse = React10.useRef(new THREE6__namespace.Vector2());
1200
+ const clientToNDC = React10.useCallback(
1196
1201
  (clientX, clientY) => {
1197
1202
  if (!canvas) {
1198
1203
  return { x: 0, y: 0 };
@@ -1205,7 +1210,7 @@ function useRaycaster(options) {
1205
1210
  },
1206
1211
  [canvas]
1207
1212
  );
1208
- const isWithinCanvas = React8.useCallback(
1213
+ const isWithinCanvas = React10.useCallback(
1209
1214
  (clientX, clientY) => {
1210
1215
  if (!canvas) return false;
1211
1216
  const rect = canvas.getBoundingClientRect();
@@ -1213,7 +1218,7 @@ function useRaycaster(options) {
1213
1218
  },
1214
1219
  [canvas]
1215
1220
  );
1216
- const getIntersection = React8.useCallback(
1221
+ const getIntersection = React10.useCallback(
1217
1222
  (clientX, clientY, objects) => {
1218
1223
  if (!camera || !canvas) return null;
1219
1224
  const ndc = clientToNDC(clientX, clientY);
@@ -1236,7 +1241,7 @@ function useRaycaster(options) {
1236
1241
  },
1237
1242
  [camera, canvas, clientToNDC]
1238
1243
  );
1239
- const getAllIntersections = React8.useCallback(
1244
+ const getAllIntersections = React10.useCallback(
1240
1245
  (clientX, clientY, objects) => {
1241
1246
  if (!camera || !canvas) return [];
1242
1247
  const ndc = clientToNDC(clientX, clientY);
@@ -1255,7 +1260,7 @@ function useRaycaster(options) {
1255
1260
  },
1256
1261
  [camera, canvas, clientToNDC]
1257
1262
  );
1258
- const getGridCoordinates = React8.useCallback(
1263
+ const getGridCoordinates = React10.useCallback(
1259
1264
  (clientX, clientY) => {
1260
1265
  if (!camera || !canvas) return null;
1261
1266
  const ndc = clientToNDC(clientX, clientY);
@@ -1273,7 +1278,7 @@ function useRaycaster(options) {
1273
1278
  },
1274
1279
  [camera, canvas, cellSize, offsetX, offsetZ, clientToNDC]
1275
1280
  );
1276
- const getTileAtPosition = React8.useCallback(
1281
+ const getTileAtPosition = React10.useCallback(
1277
1282
  (clientX, clientY, scene) => {
1278
1283
  if (!camera || !canvas) return null;
1279
1284
  const tileMeshes = [];
@@ -1396,10 +1401,10 @@ var fallbackEventBus = {
1396
1401
  }
1397
1402
  };
1398
1403
  function useEventBus() {
1399
- const context = React8.useContext(providers.EventBusContext);
1404
+ const context = React10.useContext(providers.EventBusContext);
1400
1405
  const baseBus = context ?? getGlobalEventBus() ?? fallbackEventBus;
1401
1406
  const scope = providers.useTraitScope();
1402
- return React8.useMemo(() => {
1407
+ return React10.useMemo(() => {
1403
1408
  if (!scope) {
1404
1409
  return {
1405
1410
  ...baseBus,
@@ -1435,7 +1440,7 @@ function useEventBus() {
1435
1440
  }
1436
1441
  function useEmitEvent() {
1437
1442
  const eventBus = useEventBus();
1438
- return React8.useCallback(
1443
+ return React10.useCallback(
1439
1444
  (type, payload, source) => {
1440
1445
  eventBus.emit(type, payload, source);
1441
1446
  },
@@ -1462,9 +1467,9 @@ function useGameCanvas3DEvents(options) {
1462
1467
  onUnitAnimation
1463
1468
  } = options;
1464
1469
  const emit = useEmitEvent();
1465
- const optionsRef = React8.useRef(options);
1470
+ const optionsRef = React10.useRef(options);
1466
1471
  optionsRef.current = options;
1467
- const handleTileClick = React8.useCallback(
1472
+ const handleTileClick = React10.useCallback(
1468
1473
  (tile, event) => {
1469
1474
  if (tileClickEvent) {
1470
1475
  emit(tileClickEvent, {
@@ -1480,7 +1485,7 @@ function useGameCanvas3DEvents(options) {
1480
1485
  },
1481
1486
  [tileClickEvent, emit]
1482
1487
  );
1483
- const handleUnitClick = React8.useCallback(
1488
+ const handleUnitClick = React10.useCallback(
1484
1489
  (unit, event) => {
1485
1490
  if (unitClickEvent) {
1486
1491
  emit(unitClickEvent, {
@@ -1499,7 +1504,7 @@ function useGameCanvas3DEvents(options) {
1499
1504
  },
1500
1505
  [unitClickEvent, emit]
1501
1506
  );
1502
- const handleFeatureClick = React8.useCallback(
1507
+ const handleFeatureClick = React10.useCallback(
1503
1508
  (feature, event) => {
1504
1509
  if (featureClickEvent) {
1505
1510
  emit(featureClickEvent, {
@@ -1514,7 +1519,7 @@ function useGameCanvas3DEvents(options) {
1514
1519
  },
1515
1520
  [featureClickEvent, emit]
1516
1521
  );
1517
- const handleCanvasClick = React8.useCallback(
1522
+ const handleCanvasClick = React10.useCallback(
1518
1523
  (event) => {
1519
1524
  if (canvasClickEvent) {
1520
1525
  emit(canvasClickEvent, {
@@ -1527,7 +1532,7 @@ function useGameCanvas3DEvents(options) {
1527
1532
  },
1528
1533
  [canvasClickEvent, emit]
1529
1534
  );
1530
- const handleTileHover = React8.useCallback(
1535
+ const handleTileHover = React10.useCallback(
1531
1536
  (tile, event) => {
1532
1537
  if (tile) {
1533
1538
  if (tileHoverEvent) {
@@ -1547,7 +1552,7 @@ function useGameCanvas3DEvents(options) {
1547
1552
  },
1548
1553
  [tileHoverEvent, tileLeaveEvent, emit]
1549
1554
  );
1550
- const handleUnitAnimation = React8.useCallback(
1555
+ const handleUnitAnimation = React10.useCallback(
1551
1556
  (unitId, state) => {
1552
1557
  if (unitAnimationEvent) {
1553
1558
  emit(unitAnimationEvent, {
@@ -1560,7 +1565,7 @@ function useGameCanvas3DEvents(options) {
1560
1565
  },
1561
1566
  [unitAnimationEvent, emit]
1562
1567
  );
1563
- const handleCameraChange = React8.useCallback(
1568
+ const handleCameraChange = React10.useCallback(
1564
1569
  (position) => {
1565
1570
  if (cameraChangeEvent) {
1566
1571
  emit(cameraChangeEvent, {
@@ -1582,6 +1587,171 @@ function useGameCanvas3DEvents(options) {
1582
1587
  };
1583
1588
  }
1584
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
+ ]
1710
+ }
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
+
1585
1755
  // components/game/shared/spriteAnimation.ts
1586
1756
  function resolveSheetDirection(facing) {
1587
1757
  switch (facing) {
@@ -1617,11 +1787,11 @@ function resolveSheetUrl(atlasUrl, relativeSheetPath) {
1617
1787
  }
1618
1788
  }
1619
1789
  function useUnitSpriteAtlas(units) {
1620
- const atlasCacheRef = React8.useRef(/* @__PURE__ */ new Map());
1621
- const loadingRef = React8.useRef(/* @__PURE__ */ new Set());
1622
- const [pendingCount, setPendingCount] = React8.useState(0);
1623
- const [, forceTick] = React8.useState(0);
1624
- const atlasUrls = React8.useMemo(() => {
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(() => {
1625
1795
  const set = /* @__PURE__ */ new Set();
1626
1796
  for (const unit of units) {
1627
1797
  const url = unitAtlasUrl(unit);
@@ -1629,7 +1799,7 @@ function useUnitSpriteAtlas(units) {
1629
1799
  }
1630
1800
  return [...set];
1631
1801
  }, [units]);
1632
- React8.useEffect(() => {
1802
+ React10.useEffect(() => {
1633
1803
  const cache = atlasCacheRef.current;
1634
1804
  const loading = loadingRef.current;
1635
1805
  const toLoad = atlasUrls.filter((url) => !cache.has(url) && !loading.has(url));
@@ -1653,7 +1823,7 @@ function useUnitSpriteAtlas(units) {
1653
1823
  cancelled = true;
1654
1824
  };
1655
1825
  }, [atlasUrls]);
1656
- const sheetUrls = React8.useMemo(() => {
1826
+ const sheetUrls = React10.useMemo(() => {
1657
1827
  const urls = /* @__PURE__ */ new Set();
1658
1828
  for (const unit of units) {
1659
1829
  const atlasUrl = unitAtlasUrl(unit);
@@ -1666,7 +1836,7 @@ function useUnitSpriteAtlas(units) {
1666
1836
  }
1667
1837
  return [...urls];
1668
1838
  }, [units, pendingCount]);
1669
- const resolveUnitFrame = React8.useCallback((unitId) => {
1839
+ const resolveUnitFrame = React10.useCallback((unitId) => {
1670
1840
  const unit = units.find((u) => u.id === unitId);
1671
1841
  if (!unit) return null;
1672
1842
  const atlasUrl = unitAtlasUrl(unit);
@@ -1694,274 +1864,11 @@ function useUnitSpriteAtlas(units) {
1694
1864
  }, [units]);
1695
1865
  return { sheetUrls, resolveUnitFrame, pendingCount };
1696
1866
  }
1697
- function cn(...inputs) {
1698
- return tailwindMerge.twMerge(clsx.clsx(inputs));
1699
- }
1700
- var DEFAULT_GRID_CONFIG = {
1701
- cellSize: 1,
1702
- offsetX: 0,
1703
- offsetZ: 0
1704
- };
1705
- function CameraController({
1706
- onCameraChange
1707
- }) {
1708
- const { camera } = fiber.useThree();
1709
- React8.useEffect(() => {
1710
- if (onCameraChange) {
1711
- onCameraChange({
1712
- x: camera.position.x,
1713
- y: camera.position.y,
1714
- z: camera.position.z
1715
- });
1716
- }
1717
- }, [camera.position, onCameraChange]);
1718
- return null;
1719
- }
1720
- function UnitSpriteBillboard({
1721
- sheetUrl,
1722
- resolveFrame,
1723
- height = 1.2
1724
- }) {
1725
- const texture = fiber.useLoader(THREE6__namespace.TextureLoader, sheetUrl);
1726
- const meshRef = React8.useRef(null);
1727
- const matRef = React8.useRef(null);
1728
- const [aspect, setAspect] = React8.useState(1);
1729
- fiber.useFrame(() => {
1730
- const frame = resolveFrame();
1731
- if (!frame || !texture.image) return;
1732
- const imgW = texture.image.width;
1733
- const imgH = texture.image.height;
1734
- if (!imgW || !imgH) return;
1735
- texture.repeat.set((frame.flipX ? -1 : 1) * (frame.sw / imgW), frame.sh / imgH);
1736
- texture.offset.set(
1737
- frame.flipX ? (frame.sx + frame.sw) / imgW : frame.sx / imgW,
1738
- 1 - (frame.sy + frame.sh) / imgH
1739
- );
1740
- texture.magFilter = THREE6__namespace.NearestFilter;
1741
- texture.minFilter = THREE6__namespace.NearestFilter;
1742
- texture.needsUpdate = true;
1743
- const nextAspect = frame.sw / frame.sh;
1744
- if (Math.abs(nextAspect - aspect) > 1e-3) setAspect(nextAspect);
1745
- if (matRef.current) matRef.current.needsUpdate = true;
1746
- });
1747
- return /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ref: meshRef, position: [0, height / 2, 0], children: [
1748
- /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [height * aspect, height] }),
1749
- /* @__PURE__ */ jsxRuntime.jsx(
1750
- "meshBasicMaterial",
1751
- {
1752
- ref: matRef,
1753
- map: texture,
1754
- transparent: true,
1755
- alphaTest: 0.1,
1756
- side: THREE6__namespace.DoubleSide
1757
- }
1758
- )
1759
- ] });
1760
- }
1761
- function FollowCamera({
1762
- target,
1763
- offset
1764
- }) {
1765
- const { camera } = fiber.useThree();
1766
- const look = React8.useRef(new THREE6__namespace.Vector3(target[0], target[1], target[2]));
1767
- const goal = React8.useRef(new THREE6__namespace.Vector3());
1768
- fiber.useFrame((_, delta) => {
1769
- const t = Math.min(1, delta * 5);
1770
- goal.current.set(target[0] + offset[0], target[1] + offset[1], target[2] + offset[2]);
1771
- camera.position.lerp(goal.current, t);
1772
- look.current.lerp(goal.current.set(target[0], target[1], target[2]), t);
1773
- camera.lookAt(look.current);
1774
- });
1775
- return null;
1776
- }
1777
- function LerpedGroup({
1778
- target,
1779
- enabled,
1780
- children
1781
- }) {
1782
- const ref = React8.useRef(null);
1783
- const goal = React8.useRef(new THREE6__namespace.Vector3());
1784
- fiber.useFrame((_, delta) => {
1785
- const g = ref.current;
1786
- if (!g) return;
1787
- goal.current.set(target[0], target[1], target[2]);
1788
- if (enabled) g.position.lerp(goal.current, Math.min(1, delta * 10));
1789
- else g.position.copy(goal.current);
1790
- });
1791
- return /* @__PURE__ */ jsxRuntime.jsx("group", { ref, position: target, children });
1792
- }
1793
- var SIDE_PLATFORM_COLORS = {
1794
- ground: "#5b8c3e",
1795
- platform: "#8b5a2b",
1796
- hazard: "#cc4444",
1797
- goal: "#4488cc"
1798
- };
1799
- function SideScene({
1800
- player,
1801
- platforms,
1802
- worldHeight,
1803
- ppu,
1804
- playerSprite,
1805
- tileSprites,
1806
- interpolate,
1807
- features = [],
1808
- events = [],
1809
- assetManifest
1810
- }) {
1811
- const pw = player.width ?? 32;
1812
- const ph = player.height ?? 48;
1813
- const playerPos = [
1814
- (player.x + pw / 2) / ppu,
1815
- (worldHeight - player.y - ph) / ppu,
1816
- 0
1817
- ];
1818
- const playerH = Math.max(ph / ppu, 0.5);
1819
- return /* @__PURE__ */ jsxRuntime.jsxs("group", { children: [
1820
- platforms.map((p, i) => {
1821
- const platformType = p.type ?? "platform";
1822
- const model = tileSprites?.[platformType]?.url;
1823
- const topY = (worldHeight - p.y) / ppu;
1824
- if (model) {
1825
- const cells = Math.max(1, Math.round(p.width / ppu));
1826
- const x0 = p.x / ppu;
1827
- return /* @__PURE__ */ jsxRuntime.jsx("group", { children: Array.from({ length: cells }, (_, c) => /* @__PURE__ */ jsxRuntime.jsx("group", { position: [x0 + c + 0.5, topY - 0.475, 0], children: /* @__PURE__ */ jsxRuntime.jsx(ModelLoader, { url: model, scale: 0.95, fallbackGeometry: "box", castShadow: true, receiveShadow: true }) }, c)) }, `plat-${i}`);
1828
- }
1829
- return /* @__PURE__ */ jsxRuntime.jsxs(
1830
- "mesh",
1831
- {
1832
- position: [(p.x + p.width / 2) / ppu, topY - p.height / 2 / ppu, 0],
1833
- children: [
1834
- /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [p.width / ppu, p.height / ppu, 1] }),
1835
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: SIDE_PLATFORM_COLORS[platformType] ?? "#888888" })
1836
- ]
1837
- },
1838
- `plat-${i}`
1839
- );
1840
- }),
1841
- features.map((feature, i) => {
1842
- const model = feature.assetUrl ?? assetManifest?.features?.[feature.type];
1843
- const fx = feature.x / ppu;
1844
- const fy = (worldHeight - feature.y) / ppu;
1845
- if (!model?.url) return null;
1846
- return /* @__PURE__ */ jsxRuntime.jsx("group", { position: [fx, fy, 0], children: /* @__PURE__ */ jsxRuntime.jsx(ModelLoader, { url: model.url, scale: 0.6, tint: feature.color, fallbackGeometry: "sphere", castShadow: true }) }, feature.id ?? `sfeat-${i}`);
1847
- }),
1848
- events.map((ev, i) => /* @__PURE__ */ jsxRuntime.jsx(
1849
- EventMarker,
1850
- {
1851
- event: ev,
1852
- position: [ev.x / ppu, (worldHeight - (ev.y ?? 0)) / ppu + 0.8, 0.2]
1853
- },
1854
- ev.id ?? `sev-${i}`
1855
- )),
1856
- /* @__PURE__ */ jsxRuntime.jsx(LerpedGroup, { target: playerPos, enabled: interpolate, children: playerSprite?.url ? /* @__PURE__ */ jsxRuntime.jsx("group", { position: [0, playerH / 2, 0], children: /* @__PURE__ */ jsxRuntime.jsx(
1857
- ModelLoader,
1858
- {
1859
- url: playerSprite.url,
1860
- scale: playerH,
1861
- rotation: [0, player.facingRight ? 90 : -90, 0],
1862
- animation: player.animation ?? "idle",
1863
- fallbackGeometry: "box",
1864
- castShadow: true
1865
- }
1866
- ) }) : /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, playerH / 2, 0], children: [
1867
- /* @__PURE__ */ jsxRuntime.jsx("capsuleGeometry", { args: [playerH * 0.25, playerH * 0.5, 4, 8] }),
1868
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: "#4488ff" })
1869
- ] }) })
1870
- ] });
1871
- }
1872
1867
  var UNIT_BASE_MODEL_SCALE = 0.5;
1873
1868
  var UNIT_BASE_BILLBOARD_HEIGHT = 1.2;
1874
1869
  var UNIT_BASE_PRIMITIVE_RADIUS = 0.3;
1875
- var TILE_TOP_OFFSET = 0.1;
1876
- var EVENT_COLORS = {
1877
- hit: "#ff5544",
1878
- damage: "#ff5544",
1879
- attack: "#ff8844",
1880
- heal: "#44dd66",
1881
- pickup: "#ffcc33",
1882
- score: "#ffcc33",
1883
- death: "#aa3333",
1884
- win: "#66ff88",
1885
- lose: "#ff6666"
1886
- };
1887
- function EventMarker({
1888
- event,
1889
- position
1890
- }) {
1891
- return /* @__PURE__ */ jsxRuntime.jsx(drei.Billboard, { position, children: /* @__PURE__ */ jsxRuntime.jsx(
1892
- drei.Text,
1893
- {
1894
- fontSize: 0.32,
1895
- color: EVENT_COLORS[event.type] ?? "#ffffff",
1896
- outlineWidth: 0.02,
1897
- outlineColor: "#000000",
1898
- anchorX: "center",
1899
- anchorY: "middle",
1900
- children: event.message ?? event.type
1901
- }
1902
- ) });
1903
- }
1904
- function DefaultTile({
1905
- tile,
1906
- position,
1907
- model,
1908
- isSelected,
1909
- isHovered,
1910
- isValidMove,
1911
- isAttackTarget,
1912
- onTileClick,
1913
- onTileHover
1914
- }) {
1915
- let color = 8421504;
1916
- if (tile.type === "water") color = 4491468;
1917
- else if (tile.type === "grass") color = 4500036;
1918
- else if (tile.type === "sand") color = 14535816;
1919
- else if (tile.type === "rock") color = 8947848;
1920
- else if (tile.type === "snow") color = 15658734;
1921
- let emissive = 0;
1922
- if (isSelected) emissive = 4473924;
1923
- else if (isAttackTarget) emissive = 4456448;
1924
- else if (isValidMove) emissive = 17408;
1925
- else if (isHovered) emissive = 2236962;
1926
- if (model?.url) {
1927
- return /* @__PURE__ */ jsxRuntime.jsx(
1928
- "group",
1929
- {
1930
- position,
1931
- onClick: (e) => onTileClick(tile, e),
1932
- onPointerEnter: (e) => onTileHover(tile, e),
1933
- onPointerLeave: (e) => onTileHover(null, e),
1934
- userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
1935
- children: /* @__PURE__ */ jsxRuntime.jsx(
1936
- ModelLoader,
1937
- {
1938
- url: model.url,
1939
- scale: 0.95,
1940
- rotation: [0, tile.rotation ?? 0, 0],
1941
- fallbackGeometry: "box",
1942
- castShadow: true,
1943
- receiveShadow: true
1944
- }
1945
- )
1946
- }
1947
- );
1948
- }
1949
- return /* @__PURE__ */ jsxRuntime.jsxs(
1950
- "mesh",
1951
- {
1952
- position,
1953
- onClick: (e) => onTileClick(tile, e),
1954
- onPointerEnter: (e) => onTileHover(tile, e),
1955
- onPointerLeave: (e) => onTileHover(null, e),
1956
- userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
1957
- children: [
1958
- /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
1959
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color, emissive })
1960
- ]
1961
- }
1962
- );
1963
- }
1964
- function DefaultUnit({
1870
+ var HEALTHBAR_HEIGHT = 0.05;
1871
+ function UnitMesh3D({
1965
1872
  unit,
1966
1873
  position,
1967
1874
  model,
@@ -1971,7 +1878,7 @@ function DefaultUnit({
1971
1878
  resolveUnitFrame,
1972
1879
  onUnitClick
1973
1880
  }) {
1974
- const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
1881
+ const color = unit.tint ?? FACTION_COLORS_3D[unit.faction ?? ""] ?? FACTION_COLORS_3D.default;
1975
1882
  const hasAtlas = unitAtlasUrl(unit) !== null;
1976
1883
  const initialFrame = hasAtlas ? resolveUnitFrame(unit.id) : null;
1977
1884
  const modelScale = UNIT_BASE_MODEL_SCALE * unitScale * cellSize;
@@ -1986,12 +1893,12 @@ function DefaultUnit({
1986
1893
  children: [
1987
1894
  isSelected && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
1988
1895
  /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
1989
- /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
1896
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: SELECTION_RING_COLOR_3D, transparent: true, opacity: 0.8 })
1990
1897
  ] }),
1991
1898
  hasAtlas && initialFrame ? (
1992
1899
  /* Animated sprite-sheet billboard — single cropped frame, by state */
1993
1900
  /* @__PURE__ */ jsxRuntime.jsx(drei.Billboard, { children: /* @__PURE__ */ jsxRuntime.jsx(
1994
- UnitSpriteBillboard,
1901
+ SpriteBillboard3D,
1995
1902
  {
1996
1903
  sheetUrl: initialFrame.sheetUrl,
1997
1904
  resolveFrame: () => resolveUnitFrame(unit.id),
@@ -2027,26 +1934,21 @@ function DefaultUnit({
2027
1934
  ] })
2028
1935
  ] }),
2029
1936
  unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("group", { position: [0, billboardHeight, 0], children: [
2030
- /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [-0.25, 0, 0], children: [
2031
- /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5, 0.05] }),
2032
- /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: 3355443 })
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 })
2033
1940
  ] }),
2034
1941
  /* @__PURE__ */ jsxRuntime.jsxs(
2035
1942
  "mesh",
2036
1943
  {
2037
1944
  position: [
2038
- -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
1945
+ -0.5 / 2 + HEALTHBAR_3D.width * (unit.health / unit.maxHealth) / 2,
2039
1946
  0,
2040
1947
  0.01
2041
1948
  ],
2042
1949
  children: [
2043
- /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
2044
- /* @__PURE__ */ jsxRuntime.jsx(
2045
- "meshBasicMaterial",
2046
- {
2047
- color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
2048
- }
2049
- )
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) })
2050
1952
  ]
2051
1953
  }
2052
1954
  )
@@ -2055,7 +1957,7 @@ function DefaultUnit({
2055
1957
  }
2056
1958
  );
2057
1959
  }
2058
- function DefaultFeature({
1960
+ function FeatureMesh3D({
2059
1961
  feature,
2060
1962
  position,
2061
1963
  model,
@@ -2085,11 +1987,11 @@ function DefaultFeature({
2085
1987
  children: [
2086
1988
  /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.4, 0], children: [
2087
1989
  /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
2088
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 9127187 })
1990
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: TREE_COLORS_3D.trunk })
2089
1991
  ] }),
2090
1992
  /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
2091
1993
  /* @__PURE__ */ jsxRuntime.jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
2092
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 2263842 })
1994
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: TREE_COLORS_3D.foliage })
2093
1995
  ] })
2094
1996
  ]
2095
1997
  }
@@ -2104,14 +2006,157 @@ function DefaultFeature({
2104
2006
  userData: { type: "feature", featureId: feature.id },
2105
2007
  children: [
2106
2008
  /* @__PURE__ */ jsxRuntime.jsx("dodecahedronGeometry", { args: [0.3, 0] }),
2107
- /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 8421504 })
2009
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: ROCK_COLOR_3D })
2108
2010
  ]
2109
2011
  }
2110
2012
  );
2111
2013
  }
2112
2014
  return null;
2113
2015
  }
2114
- var GameCanvas3D = React8.forwardRef(
2016
+ function CameraController3D({
2017
+ onCameraChange
2018
+ }) {
2019
+ const { camera } = fiber.useThree();
2020
+ React10.useEffect(() => {
2021
+ if (onCameraChange) {
2022
+ onCameraChange({
2023
+ x: camera.position.x,
2024
+ y: camera.position.y,
2025
+ z: camera.position.z
2026
+ });
2027
+ }
2028
+ }, [camera.position, onCameraChange]);
2029
+ return null;
2030
+ }
2031
+ function FollowCamera3D({
2032
+ target,
2033
+ offset
2034
+ }) {
2035
+ 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());
2038
+ fiber.useFrame((_, delta) => {
2039
+ const t = Math.min(1, delta * 5);
2040
+ goal.current.set(target[0] + offset[0], target[1] + offset[1], target[2] + offset[2]);
2041
+ camera.position.lerp(goal.current, t);
2042
+ look.current.lerp(goal.current.set(target[0], target[1], target[2]), t);
2043
+ camera.lookAt(look.current);
2044
+ });
2045
+ return null;
2046
+ }
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 });
2062
+ }
2063
+ function EventMarker3D({ event, position }) {
2064
+ return /* @__PURE__ */ jsxRuntime.jsx(drei.Billboard, { position, children: /* @__PURE__ */ jsxRuntime.jsx(
2065
+ drei.Text,
2066
+ {
2067
+ fontSize: 0.32,
2068
+ color: EVENT_COLORS_3D[event.type] ?? EVENT_COLORS_3D.default,
2069
+ outlineWidth: 0.02,
2070
+ outlineColor: "#000000",
2071
+ anchorX: "center",
2072
+ anchorY: "middle",
2073
+ children: event.message ?? event.type
2074
+ }
2075
+ ) });
2076
+ }
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
+ ] });
2149
+ }
2150
+ function cn(...inputs) {
2151
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
2152
+ }
2153
+ var DEFAULT_GRID_CONFIG = {
2154
+ cellSize: 1,
2155
+ offsetX: 0,
2156
+ offsetZ: 0
2157
+ };
2158
+ var TILE_TOP_OFFSET = 0.1;
2159
+ var GameCanvas3D = React10.forwardRef(
2115
2160
  ({
2116
2161
  tiles = [],
2117
2162
  units = [],
@@ -2124,7 +2169,7 @@ var GameCanvas3D = React8.forwardRef(
2124
2169
  showTileInfo = false,
2125
2170
  overlay = "default",
2126
2171
  shadows = true,
2127
- backgroundColor = "#1a1a2e",
2172
+ backgroundColor = DEFAULT_BACKGROUND_3D,
2128
2173
  onTileClick,
2129
2174
  onUnitClick,
2130
2175
  onFeatureClick,
@@ -2166,13 +2211,13 @@ var GameCanvas3D = React8.forwardRef(
2166
2211
  interpolateUnits = false,
2167
2212
  children
2168
2213
  }, ref) => {
2169
- const containerRef = React8.useRef(null);
2170
- const controlsRef = React8.useRef(null);
2171
- const [hoveredTile, setHoveredTile] = React8.useState(null);
2172
- const [internalError, setInternalError] = React8.useState(null);
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);
2173
2218
  const eventBus = useEventBus();
2174
- const keysRef = React8.useRef(/* @__PURE__ */ new Set());
2175
- React8.useEffect(() => {
2219
+ const keysRef = React10.useRef(/* @__PURE__ */ new Set());
2220
+ React10.useEffect(() => {
2176
2221
  if (!keyMap && !keyUpMap) return;
2177
2222
  const down = (e) => {
2178
2223
  if (keysRef.current.has(e.code)) return;
@@ -2195,7 +2240,7 @@ var GameCanvas3D = React8.forwardRef(
2195
2240
  window.removeEventListener("keyup", up);
2196
2241
  };
2197
2242
  }, [keyMap, keyUpMap, eventBus]);
2198
- React8.useEffect(() => {
2243
+ React10.useEffect(() => {
2199
2244
  const el = containerRef.current;
2200
2245
  if (!el) return;
2201
2246
  let node = el;
@@ -2219,7 +2264,7 @@ var GameCanvas3D = React8.forwardRef(
2219
2264
  }
2220
2265
  }, []);
2221
2266
  const { sheetUrls: atlasSheetUrls, resolveUnitFrame } = useUnitSpriteAtlas(units);
2222
- const preloadUrls = React8.useMemo(() => [...preloadAssets, ...atlasSheetUrls], [preloadAssets, atlasSheetUrls]);
2267
+ const preloadUrls = React10.useMemo(() => [...preloadAssets, ...atlasSheetUrls], [preloadAssets, atlasSheetUrls]);
2223
2268
  const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
2224
2269
  preloadUrls,
2225
2270
  loader: customAssetLoader
@@ -2240,7 +2285,7 @@ var GameCanvas3D = React8.forwardRef(
2240
2285
  onTileHover,
2241
2286
  onUnitAnimation
2242
2287
  });
2243
- const gridBounds = React8.useMemo(() => {
2288
+ const gridBounds = React10.useMemo(() => {
2244
2289
  if (tiles.length === 0) {
2245
2290
  return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
2246
2291
  }
@@ -2253,14 +2298,14 @@ var GameCanvas3D = React8.forwardRef(
2253
2298
  maxZ: Math.max(...zs)
2254
2299
  };
2255
2300
  }, [tiles]);
2256
- const cameraTarget = React8.useMemo(() => {
2301
+ const cameraTarget = React10.useMemo(() => {
2257
2302
  return [
2258
2303
  (gridBounds.minX + gridBounds.maxX) / 2,
2259
2304
  0,
2260
2305
  (gridBounds.minZ + gridBounds.maxZ) / 2
2261
2306
  ];
2262
2307
  }, [gridBounds]);
2263
- const gridConfig = React8.useMemo(
2308
+ const gridConfig = React10.useMemo(
2264
2309
  () => ({
2265
2310
  ...DEFAULT_GRID_CONFIG,
2266
2311
  offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
@@ -2268,7 +2313,7 @@ var GameCanvas3D = React8.forwardRef(
2268
2313
  }),
2269
2314
  [gridBounds]
2270
2315
  );
2271
- const gridToWorld2 = React8.useCallback(
2316
+ const gridToWorld2 = React10.useCallback(
2272
2317
  (x, z, y = 0) => {
2273
2318
  const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
2274
2319
  const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
@@ -2276,7 +2321,7 @@ var GameCanvas3D = React8.forwardRef(
2276
2321
  },
2277
2322
  [gridBounds, gridConfig]
2278
2323
  );
2279
- React8.useImperativeHandle(ref, () => ({
2324
+ React10.useImperativeHandle(ref, () => ({
2280
2325
  getCameraPosition: () => {
2281
2326
  if (controlsRef.current) {
2282
2327
  const pos = controlsRef.current.object.position;
@@ -2314,19 +2359,19 @@ var GameCanvas3D = React8.forwardRef(
2314
2359
  features
2315
2360
  })
2316
2361
  }));
2317
- const handleTileClick = React8.useCallback(
2362
+ const handleTileClick = React10.useCallback(
2318
2363
  (tile, event) => {
2319
2364
  eventHandlers.handleTileClick(tile, event);
2320
2365
  },
2321
2366
  [eventHandlers]
2322
2367
  );
2323
- const handleUnitClick = React8.useCallback(
2368
+ const handleUnitClick = React10.useCallback(
2324
2369
  (unit, event) => {
2325
2370
  eventHandlers.handleUnitClick(unit, event);
2326
2371
  },
2327
2372
  [eventHandlers]
2328
2373
  );
2329
- const handleFeatureClick = React8.useCallback(
2374
+ const handleFeatureClick = React10.useCallback(
2330
2375
  (feature, event) => {
2331
2376
  if (event) {
2332
2377
  eventHandlers.handleFeatureClick(feature, event);
@@ -2334,7 +2379,7 @@ var GameCanvas3D = React8.forwardRef(
2334
2379
  },
2335
2380
  [eventHandlers]
2336
2381
  );
2337
- const handleTileHover = React8.useCallback(
2382
+ const handleTileHover = React10.useCallback(
2338
2383
  (tile, event) => {
2339
2384
  setHoveredTile(tile);
2340
2385
  if (event) {
@@ -2343,7 +2388,7 @@ var GameCanvas3D = React8.forwardRef(
2343
2388
  },
2344
2389
  [eventHandlers]
2345
2390
  );
2346
- const cameraConfig = React8.useMemo(() => {
2391
+ const cameraConfig = React10.useMemo(() => {
2347
2392
  const size = Math.max(
2348
2393
  gridBounds.maxX - gridBounds.minX,
2349
2394
  gridBounds.maxZ - gridBounds.minZ,
@@ -2377,7 +2422,7 @@ var GameCanvas3D = React8.forwardRef(
2377
2422
  };
2378
2423
  }
2379
2424
  }, [cameraMode, gridBounds]);
2380
- const followTarget = React8.useMemo(() => {
2425
+ const followTarget = React10.useMemo(() => {
2381
2426
  if (player) {
2382
2427
  return [
2383
2428
  (player.x + (player.width ?? 32) / 2) / pixelsPerUnit,
@@ -2397,7 +2442,7 @@ var GameCanvas3D = React8.forwardRef(
2397
2442
  }
2398
2443
  return cameraTarget;
2399
2444
  }, [player, pixelsPerUnit, worldHeight, units, selectedUnitId, gridBounds, gridConfig, cameraTarget]);
2400
- const followOffset = React8.useMemo(() => {
2445
+ const followOffset = React10.useMemo(() => {
2401
2446
  if (cameraMode === "chase") {
2402
2447
  const selected = units.find((u) => u.id === selectedUnitId);
2403
2448
  const h = selected?.heading ?? 0;
@@ -2458,27 +2503,25 @@ var GameCanvas3D = React8.forwardRef(
2458
2503
  }
2459
2504
  },
2460
2505
  children: [
2461
- /* @__PURE__ */ jsxRuntime.jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
2506
+ /* @__PURE__ */ jsxRuntime.jsx(CameraController3D, { onCameraChange: eventHandlers.handleCameraChange }),
2462
2507
  (cameraMode === "follow" || cameraMode === "chase") && /* @__PURE__ */ jsxRuntime.jsx(
2463
- FollowCamera,
2508
+ FollowCamera3D,
2464
2509
  {
2465
2510
  target: followTarget,
2466
2511
  offset: followOffset
2467
2512
  }
2468
2513
  ),
2469
- /* @__PURE__ */ jsxRuntime.jsx("ambientLight", { intensity: 0.6 }),
2470
2514
  /* @__PURE__ */ jsxRuntime.jsx(
2471
- "directionalLight",
2515
+ Lighting3D,
2472
2516
  {
2473
- position: [10, 20, 10],
2474
- intensity: 0.8,
2475
- castShadow: shadows,
2476
- "shadow-mapSize": [2048, 2048],
2477
- "shadow-bias": -4e-4,
2478
- "shadow-normalBias": 0.04
2517
+ shadows,
2518
+ shadowBias: -4e-4,
2519
+ shadowNormalBias: 0.04,
2520
+ shadowCameraSize: 5,
2521
+ shadowCameraNear: 0.5,
2522
+ shadowCameraFar: 500
2479
2523
  }
2480
2524
  ),
2481
- /* @__PURE__ */ jsxRuntime.jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
2482
2525
  showGrid && /* @__PURE__ */ jsxRuntime.jsx(
2483
2526
  drei.Grid,
2484
2527
  {
@@ -2493,10 +2536,10 @@ var GameCanvas3D = React8.forwardRef(
2493
2536
  ],
2494
2537
  cellSize: 1,
2495
2538
  cellThickness: 1,
2496
- cellColor: "#444444",
2539
+ cellColor: GRID_COLORS_3D.cell,
2497
2540
  sectionSize: 5,
2498
2541
  sectionThickness: 1.5,
2499
- sectionColor: "#666666",
2542
+ sectionColor: GRID_COLORS_3D.section,
2500
2543
  fadeDistance: 50,
2501
2544
  fadeStrength: 1
2502
2545
  }
@@ -2504,7 +2547,7 @@ var GameCanvas3D = React8.forwardRef(
2504
2547
  player ? (
2505
2548
  /* Side-scroller mode — LOLO-owned pixel space mapped to world units */
2506
2549
  /* @__PURE__ */ jsxRuntime.jsx(
2507
- SideScene,
2550
+ SideScene3D,
2508
2551
  {
2509
2552
  player,
2510
2553
  platforms,
@@ -2530,7 +2573,7 @@ var GameCanvas3D = React8.forwardRef(
2530
2573
  return /* @__PURE__ */ jsxRuntime.jsx(CustomTileRenderer, { tile, position }, key);
2531
2574
  }
2532
2575
  return /* @__PURE__ */ jsxRuntime.jsx(
2533
- DefaultTile,
2576
+ TileMesh3D,
2534
2577
  {
2535
2578
  tile,
2536
2579
  position,
@@ -2556,7 +2599,7 @@ var GameCanvas3D = React8.forwardRef(
2556
2599
  return /* @__PURE__ */ jsxRuntime.jsx(CustomFeatureRenderer, { feature, position }, key);
2557
2600
  }
2558
2601
  return /* @__PURE__ */ jsxRuntime.jsx(
2559
- DefaultFeature,
2602
+ FeatureMesh3D,
2560
2603
  {
2561
2604
  feature,
2562
2605
  position,
@@ -2572,8 +2615,8 @@ var GameCanvas3D = React8.forwardRef(
2572
2615
  unit.z ?? unit.y ?? unit.position?.y ?? 0,
2573
2616
  (unit.elevation ?? 0) + TILE_TOP_OFFSET
2574
2617
  );
2575
- return /* @__PURE__ */ jsxRuntime.jsx(LerpedGroup, { target: position, enabled: interpolateUnits, children: CustomUnitRenderer ? /* @__PURE__ */ jsxRuntime.jsx(CustomUnitRenderer, { unit, position: [0, 0, 0] }) : /* @__PURE__ */ jsxRuntime.jsx(
2576
- DefaultUnit,
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,
2577
2620
  {
2578
2621
  unit,
2579
2622
  position: [0, 0, 0],
@@ -2589,7 +2632,7 @@ var GameCanvas3D = React8.forwardRef(
2589
2632
  events.map((ev, i) => {
2590
2633
  const position = gridToWorld2(ev.x, ev.z ?? ev.y ?? 0, 0);
2591
2634
  return /* @__PURE__ */ jsxRuntime.jsx(
2592
- EventMarker,
2635
+ EventMarker3D,
2593
2636
  {
2594
2637
  event: ev,
2595
2638
  position: [position[0], position[1] + 1.4, position[2]]
@@ -2686,7 +2729,7 @@ function getIconFamilyServerSnapshot() {
2686
2729
  return DEFAULT_FAMILY;
2687
2730
  }
2688
2731
  function useIconFamily() {
2689
- return React8.useSyncExternalStore(
2732
+ return React10.useSyncExternalStore(
2690
2733
  subscribeIconFamily,
2691
2734
  getIconFamilySnapshot,
2692
2735
  getIconFamilyServerSnapshot
@@ -2708,7 +2751,7 @@ function loadLib(key, importer) {
2708
2751
  return p;
2709
2752
  }
2710
2753
  function lazyFamilyIcon(libKey, importer, pick, fallbackName, family) {
2711
- const Lazy = React8__namespace.default.lazy(async () => {
2754
+ const Lazy = React10__namespace.default.lazy(async () => {
2712
2755
  const lib = await loadLib(libKey, importer);
2713
2756
  const Comp = pick(lib);
2714
2757
  if (!Comp) {
@@ -2718,7 +2761,7 @@ function lazyFamilyIcon(libKey, importer, pick, fallbackName, family) {
2718
2761
  return { default: Comp };
2719
2762
  });
2720
2763
  const Wrapped = (props) => /* @__PURE__ */ jsxRuntime.jsx(
2721
- React8__namespace.default.Suspense,
2764
+ React10__namespace.default.Suspense,
2722
2765
  {
2723
2766
  fallback: /* @__PURE__ */ jsxRuntime.jsx(
2724
2767
  "span",
@@ -3387,7 +3430,7 @@ var Icon = ({
3387
3430
  const directIcon = typeof icon === "string" ? void 0 : icon;
3388
3431
  const effectiveName = typeof icon === "string" ? icon : name;
3389
3432
  const family = useIconFamily();
3390
- const RenderedComponent = React8__namespace.default.useMemo(() => {
3433
+ const RenderedComponent = React10__namespace.default.useMemo(() => {
3391
3434
  if (directIcon) return null;
3392
3435
  return effectiveName ? resolveIconForFamily(effectiveName, family) : null;
3393
3436
  }, [directIcon, effectiveName, family]);
@@ -3514,13 +3557,13 @@ function AtlasImage({
3514
3557
  style,
3515
3558
  "aria-hidden": ariaHidden
3516
3559
  }) {
3517
- const [, bump] = React8__namespace.useReducer((x) => x + 1, 0);
3518
- const canvasRef = React8__namespace.useRef(null);
3560
+ const [, bump] = React10__namespace.useReducer((x) => x + 1, 0);
3561
+ const canvasRef = React10__namespace.useRef(null);
3519
3562
  const sliced = isAtlasAsset(asset);
3520
3563
  const atlas = sliced ? getAtlas(asset.atlas, bump) : void 0;
3521
3564
  const img = sliced && asset?.url ? getSheetImage(asset.url, bump) : null;
3522
3565
  const rect = sliced && atlas ? subRectFor(atlas, asset.sprite) : null;
3523
- React8__namespace.useEffect(() => {
3566
+ React10__namespace.useEffect(() => {
3524
3567
  const canvas = canvasRef.current;
3525
3568
  if (!canvas || !img || !rect) return;
3526
3569
  canvas.width = rect.sw;
@@ -3644,7 +3687,7 @@ function resolveIconProp(value, sizeClass) {
3644
3687
  const IconComp = value;
3645
3688
  return /* @__PURE__ */ jsxRuntime.jsx(IconComp, { className: sizeClass });
3646
3689
  }
3647
- if (React8__namespace.default.isValidElement(value)) {
3690
+ if (React10__namespace.default.isValidElement(value)) {
3648
3691
  return value;
3649
3692
  }
3650
3693
  if (typeof value === "object" && value !== null && isIconLike(value)) {
@@ -3653,7 +3696,7 @@ function resolveIconProp(value, sizeClass) {
3653
3696
  }
3654
3697
  return value;
3655
3698
  }
3656
- var Button = React8__namespace.default.forwardRef(
3699
+ var Button = React10__namespace.default.forwardRef(
3657
3700
  ({
3658
3701
  className,
3659
3702
  variant = "primary",
@@ -3801,7 +3844,7 @@ var Typography = ({
3801
3844
  }) => {
3802
3845
  const variant = variantProp ?? (level ? `h${level}` : "body1");
3803
3846
  const Component2 = as || defaultElements[variant];
3804
- return React8__namespace.default.createElement(
3847
+ return React10__namespace.default.createElement(
3805
3848
  Component2,
3806
3849
  {
3807
3850
  id,
@@ -3874,7 +3917,7 @@ var Stack = ({
3874
3917
  };
3875
3918
  const isHorizontal = direction === "horizontal";
3876
3919
  const directionClass = responsive && isHorizontal ? reverse ? "flex-col-reverse md:flex-row-reverse" : "flex-col md:flex-row" : isHorizontal ? reverse ? "flex-row-reverse" : "flex-row" : reverse ? "flex-col-reverse" : "flex-col";
3877
- return React8__namespace.default.createElement(
3920
+ return React10__namespace.default.createElement(
3878
3921
  Component2,
3879
3922
  {
3880
3923
  className: cn(
@@ -3967,8 +4010,8 @@ function GameBoard3D({
3967
4010
  const turn = row ? num(row.turn) : 0;
3968
4011
  const currentTeam = row ? str(row.currentTeam) : "player";
3969
4012
  const isGameOver = result !== "none";
3970
- const gameEndEmittedRef = React8.useRef(false);
3971
- React8.useEffect(() => {
4013
+ const gameEndEmittedRef = React10.useRef(false);
4014
+ React10.useEffect(() => {
3972
4015
  if ((result === "victory" || result === "defeat") && gameEndEvent) {
3973
4016
  if (!gameEndEmittedRef.current) {
3974
4017
  gameEndEmittedRef.current = true;
@@ -3978,7 +4021,7 @@ function GameBoard3D({
3978
4021
  gameEndEmittedRef.current = false;
3979
4022
  }
3980
4023
  }, [result, gameEndEvent, eventBus]);
3981
- const checkGameEnd = React8.useCallback(() => {
4024
+ const checkGameEnd = React10.useCallback(() => {
3982
4025
  const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
3983
4026
  const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
3984
4027
  if (alivePlayer.length === 0 && gameEndEvent) {
@@ -3987,7 +4030,7 @@ function GameBoard3D({
3987
4030
  eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
3988
4031
  }
3989
4032
  }, [entityUnits, gameEndEvent, eventBus]);
3990
- const handleUnitClickCallback = React8.useCallback((isoUnit) => {
4033
+ const handleUnitClickCallback = React10.useCallback((isoUnit) => {
3991
4034
  if (phase !== "action" || !selectedUnitId || !attackEvent) return;
3992
4035
  const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
3993
4036
  const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
@@ -4007,13 +4050,13 @@ function GameBoard3D({
4007
4050
  setTimeout(checkGameEnd, 100);
4008
4051
  }
4009
4052
  }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
4010
- const handleEndTurn = React8.useCallback(() => {
4053
+ const handleEndTurn = React10.useCallback(() => {
4011
4054
  if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
4012
4055
  }, [endTurnEvent, eventBus]);
4013
- const handleCancel = React8.useCallback(() => {
4056
+ const handleCancel = React10.useCallback(() => {
4014
4057
  if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
4015
4058
  }, [cancelEvent, eventBus]);
4016
- const handlePlayAgain = React8.useCallback(() => {
4059
+ const handlePlayAgain = React10.useCallback(() => {
4017
4060
  if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
4018
4061
  }, [playAgainEvent, eventBus]);
4019
4062
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
@@ -4104,24 +4147,24 @@ function GameBoard3D({
4104
4147
  GameBoard3D.displayName = "GameBoard3D";
4105
4148
  function useTapReveal(options = {}) {
4106
4149
  const { onReveal, onDismiss, refs, enabled = true } = options;
4107
- const [revealed, setRevealed] = React8.useState(false);
4108
- const onRevealRef = React8.useRef(onReveal);
4109
- const onDismissRef = React8.useRef(onDismiss);
4150
+ const [revealed, setRevealed] = React10.useState(false);
4151
+ const onRevealRef = React10.useRef(onReveal);
4152
+ const onDismissRef = React10.useRef(onDismiss);
4110
4153
  onRevealRef.current = onReveal;
4111
4154
  onDismissRef.current = onDismiss;
4112
- const reveal = React8.useCallback(() => {
4155
+ const reveal = React10.useCallback(() => {
4113
4156
  setRevealed((wasRevealed) => {
4114
4157
  if (!wasRevealed) onRevealRef.current?.();
4115
4158
  return true;
4116
4159
  });
4117
4160
  }, []);
4118
- const dismiss = React8.useCallback(() => {
4161
+ const dismiss = React10.useCallback(() => {
4119
4162
  setRevealed((wasRevealed) => {
4120
4163
  if (wasRevealed) onDismissRef.current?.();
4121
4164
  return false;
4122
4165
  });
4123
4166
  }, []);
4124
- const onPointerDown = React8.useCallback(
4167
+ const onPointerDown = React10.useCallback(
4125
4168
  (e) => {
4126
4169
  if (!enabled || e.pointerType === "mouse") return;
4127
4170
  if (revealed) dismiss();
@@ -4129,7 +4172,7 @@ function useTapReveal(options = {}) {
4129
4172
  },
4130
4173
  [enabled, revealed, reveal, dismiss]
4131
4174
  );
4132
- React8.useEffect(() => {
4175
+ React10.useEffect(() => {
4133
4176
  if (!revealed || typeof document === "undefined") return;
4134
4177
  const onDocDown = (ev) => {
4135
4178
  const target = ev.target;
@@ -4249,7 +4292,7 @@ var positionStyles = {
4249
4292
  fixed: "fixed",
4250
4293
  sticky: "sticky"
4251
4294
  };
4252
- var Box = React8__namespace.default.forwardRef(
4295
+ var Box = React10__namespace.default.forwardRef(
4253
4296
  ({
4254
4297
  padding,
4255
4298
  paddingX,
@@ -4281,20 +4324,20 @@ var Box = React8__namespace.default.forwardRef(
4281
4324
  ...rest
4282
4325
  }, ref) => {
4283
4326
  const eventBus = useEventBus();
4284
- const handleClick = React8.useCallback((e) => {
4327
+ const handleClick = React10.useCallback((e) => {
4285
4328
  if (action) {
4286
4329
  e.stopPropagation();
4287
4330
  eventBus.emit(`UI:${action}`, actionPayload ?? {});
4288
4331
  }
4289
4332
  onClick?.(e);
4290
4333
  }, [action, actionPayload, eventBus, onClick]);
4291
- const handleMouseEnter = React8.useCallback((e) => {
4334
+ const handleMouseEnter = React10.useCallback((e) => {
4292
4335
  if (hoverEvent) {
4293
4336
  eventBus.emit(`UI:${hoverEvent}`, { hovered: true });
4294
4337
  }
4295
4338
  onMouseEnter?.(e);
4296
4339
  }, [hoverEvent, eventBus, onMouseEnter]);
4297
- const handleMouseLeave = React8.useCallback((e) => {
4340
+ const handleMouseLeave = React10.useCallback((e) => {
4298
4341
  if (hoverEvent) {
4299
4342
  eventBus.emit(`UI:${hoverEvent}`, { hovered: false });
4300
4343
  }
@@ -4302,19 +4345,19 @@ var Box = React8__namespace.default.forwardRef(
4302
4345
  }, [hoverEvent, eventBus, onMouseLeave]);
4303
4346
  const { triggerProps } = useTapReveal({
4304
4347
  enabled: tapReveal && !!hoverEvent,
4305
- onReveal: React8.useCallback(() => {
4348
+ onReveal: React10.useCallback(() => {
4306
4349
  if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: true });
4307
4350
  }, [hoverEvent, eventBus]),
4308
- onDismiss: React8.useCallback(() => {
4351
+ onDismiss: React10.useCallback(() => {
4309
4352
  if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: false });
4310
4353
  }, [hoverEvent, eventBus])
4311
4354
  });
4312
- const handlePointerDown = React8.useCallback((e) => {
4355
+ const handlePointerDown = React10.useCallback((e) => {
4313
4356
  if (hoverEvent && tapReveal) triggerProps.onPointerDown(e);
4314
4357
  onPointerDown?.(e);
4315
4358
  }, [hoverEvent, tapReveal, triggerProps, onPointerDown]);
4316
4359
  const isClickable = action || onClick;
4317
- return React8__namespace.default.createElement(
4360
+ return React10__namespace.default.createElement(
4318
4361
  Component2,
4319
4362
  {
4320
4363
  ref,
@@ -5554,10 +5597,10 @@ var Avl3DOrbitalNode = ({
5554
5597
  position,
5555
5598
  onClick
5556
5599
  }) => {
5557
- const groupRef = React8.useRef(null);
5558
- const [hovered, setHovered] = React8.useState(false);
5559
- const scale = React8.useMemo(() => 0.6 + Math.min(traitCount, 8) * 0.3, [traitCount]);
5560
- const currentScale = React8.useRef(scale);
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);
5561
5604
  const targetScale = hovered ? scale * 1.06 : scale;
5562
5605
  fiber.useFrame((_, delta) => {
5563
5606
  if (!groupRef.current) return;
@@ -5699,7 +5742,7 @@ var Avl3DCrossWire = ({
5699
5742
  eventName,
5700
5743
  color = AVL_3D_COLORS.crossWire
5701
5744
  }) => {
5702
- const { tubeArgs, midpoint } = React8.useMemo(() => {
5745
+ const { tubeArgs, midpoint } = React10.useMemo(() => {
5703
5746
  const curve = arcCurve3D(from, to, 2);
5704
5747
  const mid = curve.getPoint(0.5);
5705
5748
  return {
@@ -5738,9 +5781,9 @@ var Avl3DApplicationScene = ({
5738
5781
  data,
5739
5782
  onOrbitalClick
5740
5783
  }) => {
5741
- const groupRef = React8.useRef(null);
5742
- const [, setHoverTrigger] = React8.useState(0);
5743
- const orbitalPositions = React8.useMemo(() => {
5784
+ const groupRef = React10.useRef(null);
5785
+ const [, setHoverTrigger] = React10.useState(0);
5786
+ const orbitalPositions = React10.useMemo(() => {
5744
5787
  const baseRadius = Math.max(4, data.orbitals.length * 1.5);
5745
5788
  if (data.crossLinks.length === 0) {
5746
5789
  return goldenSpiralPositions(data.orbitals.length, baseRadius);
@@ -5753,7 +5796,7 @@ var Avl3DApplicationScene = ({
5753
5796
  })).filter((e) => e.from >= 0 && e.to >= 0);
5754
5797
  return forceDirectedPositions(data.orbitals.length, edges, baseRadius);
5755
5798
  }, [data.orbitals, data.crossLinks]);
5756
- const positionByName = React8.useMemo(() => {
5799
+ const positionByName = React10.useMemo(() => {
5757
5800
  const map = /* @__PURE__ */ new Map();
5758
5801
  data.orbitals.forEach((orb, i) => {
5759
5802
  const pos = orbitalPositions[i];
@@ -5836,7 +5879,7 @@ var Avl3DEntityCore = ({
5836
5879
  position,
5837
5880
  fields
5838
5881
  }) => {
5839
- const coreRef = React8.useRef(null);
5882
+ const coreRef = React10.useRef(null);
5840
5883
  fiber.useFrame((_, delta) => {
5841
5884
  if (!coreRef.current) return;
5842
5885
  coreRef.current.rotation.y += delta * 0.3;
@@ -5989,8 +6032,8 @@ function TraitOrbit({
5989
6032
  onClick,
5990
6033
  onHover
5991
6034
  }) {
5992
- const groupRef = React8.useRef(null);
5993
- const angleRef = React8.useRef(Math.random() * Math.PI * 2);
6035
+ const groupRef = React10.useRef(null);
6036
+ const angleRef = React10.useRef(Math.random() * Math.PI * 2);
5994
6037
  fiber.useFrame((_, delta) => {
5995
6038
  angleRef.current += delta * speed;
5996
6039
  if (!groupRef.current) return;
@@ -6121,9 +6164,9 @@ var Avl3DOrbitalScene = ({
6121
6164
  highlightedTrait,
6122
6165
  onTraitHighlight
6123
6166
  }) => {
6124
- const [localHighlight, setLocalHighlight] = React8.useState(null);
6167
+ const [localHighlight, setLocalHighlight] = React10.useState(null);
6125
6168
  const activeHighlight = highlightedTrait ?? localHighlight;
6126
- const traitOrbits = React8.useMemo(() => {
6169
+ const traitOrbits = React10.useMemo(() => {
6127
6170
  return data.traits.map((trait, i) => ({
6128
6171
  name: trait.name,
6129
6172
  radius: 3 + i * 1.2,
@@ -6134,7 +6177,7 @@ var Avl3DOrbitalScene = ({
6134
6177
  transitionCount: trait.transitionCount
6135
6178
  }));
6136
6179
  }, [data.traits]);
6137
- const pagePositions = React8.useMemo(() => {
6180
+ const pagePositions = React10.useMemo(() => {
6138
6181
  const outerRadius = 3 + data.traits.length * 1.2 + 1.5;
6139
6182
  return data.pages.map((_, i) => {
6140
6183
  const angle = 2 * Math.PI * i / data.pages.length - Math.PI / 2;
@@ -6198,12 +6241,12 @@ var Avl3DStateNode = ({
6198
6241
  outgoingCount = 0,
6199
6242
  onClick
6200
6243
  }) => {
6201
- const [hovered, setHovered] = React8.useState(false);
6244
+ const [hovered, setHovered] = React10.useState(false);
6202
6245
  const baseColor = active ? AVL_3D_COLORS.stateActive : AVL_3D_COLORS.stateIdle;
6203
6246
  const emissiveColor = active ? AVL_3D_COLORS.stateActive : AVL_3D_COLORS.stateEdge;
6204
6247
  const emissiveIntensity = active ? 2 : hovered ? 0.8 : 0.3;
6205
6248
  const targetScale = hovered ? 1.08 : 1;
6206
- const currentScale = React8.useRef(1);
6249
+ const currentScale = React10.useRef(1);
6207
6250
  fiber.useFrame((_, delta) => {
6208
6251
  currentScale.current = THREE6.MathUtils.damp(currentScale.current, targetScale, 6, delta);
6209
6252
  });
@@ -6377,8 +6420,8 @@ var Avl3DTransitionArc = ({
6377
6420
  onClick,
6378
6421
  color = AVL_3D_COLORS.transitionArc
6379
6422
  }) => {
6380
- const [hovered, setHovered] = React8.useState(false);
6381
- const { tubeArgs, labelPos, guardPos, arrowPos, arrowQuat, effectPositions } = React8.useMemo(() => {
6423
+ const [hovered, setHovered] = React10.useState(false);
6424
+ const { tubeArgs, labelPos, guardPos, arrowPos, arrowQuat, effectPositions } = React10.useMemo(() => {
6382
6425
  const offset = 1.5 + index * 0.8;
6383
6426
  const curve = isSelf ? selfLoopCurve3D(from, 1.2) : arcCurve3D(from, to, offset);
6384
6427
  const mid = curve.getPoint(0.5);
@@ -6511,7 +6554,7 @@ var Avl3DTraitScene = ({
6511
6554
  data,
6512
6555
  onTransitionClick
6513
6556
  }) => {
6514
- const statePositions = React8.useMemo(() => {
6557
+ const statePositions = React10.useMemo(() => {
6515
6558
  const radius = Math.max(3, data.states.length * 0.8);
6516
6559
  const positions = fibonacciSpherePositions(data.states.length, radius);
6517
6560
  const posMap = /* @__PURE__ */ new Map();
@@ -6521,7 +6564,7 @@ var Avl3DTraitScene = ({
6521
6564
  });
6522
6565
  return posMap;
6523
6566
  }, [data.states]);
6524
- const transitionIndices = React8.useMemo(() => {
6567
+ const transitionIndices = React10.useMemo(() => {
6525
6568
  const pairCount = /* @__PURE__ */ new Map();
6526
6569
  return data.transitions.map((t) => {
6527
6570
  const key = `${t.from}->${t.to}`;
@@ -6530,7 +6573,7 @@ var Avl3DTraitScene = ({
6530
6573
  return idx;
6531
6574
  });
6532
6575
  }, [data.transitions]);
6533
- const transitionCounts = React8.useMemo(() => {
6576
+ const transitionCounts = React10.useMemo(() => {
6534
6577
  const incoming = /* @__PURE__ */ new Map();
6535
6578
  const outgoing = /* @__PURE__ */ new Map();
6536
6579
  for (const t of data.transitions) {
@@ -6640,11 +6683,11 @@ var Avl3DExprTree = ({
6640
6683
  expression,
6641
6684
  position
6642
6685
  }) => {
6643
- const layoutResults = React8.useMemo(() => {
6686
+ const layoutResults = React10.useMemo(() => {
6644
6687
  const origin = { x: position[0], y: position[1], z: position[2] };
6645
6688
  return treeLayout3D(expression, origin, 2.5, 1.5);
6646
6689
  }, [expression, position]);
6647
- const edges = React8.useMemo(() => {
6690
+ const edges = React10.useMemo(() => {
6648
6691
  const result = [];
6649
6692
  function collectEdges(node, parentPos) {
6650
6693
  const entry = layoutResults.find((r) => r.node === node);
@@ -6924,7 +6967,7 @@ var Avl3DEffects = ({
6924
6967
  const dof = DOF_CONFIG[level];
6925
6968
  const sparkles = SPARKLE_CONFIGS[level];
6926
6969
  const stars = STARS_CONFIG[level];
6927
- const starsKey = React8.useMemo(() => `stars-${level}`, [level]);
6970
+ const starsKey = React10.useMemo(() => `stars-${level}`, [level]);
6928
6971
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6929
6972
  /* @__PURE__ */ jsxRuntime.jsx(
6930
6973
  drei.Stars,
@@ -6988,16 +7031,16 @@ var DEFAULT_CONFIG2 = {
6988
7031
  modelOverrides: {},
6989
7032
  effectsEnabled: true
6990
7033
  };
6991
- var Avl3DContext = React8.createContext(DEFAULT_CONFIG2);
7034
+ var Avl3DContext = React10.createContext(DEFAULT_CONFIG2);
6992
7035
  function useAvl3DConfig() {
6993
- return React8.useContext(Avl3DContext);
7036
+ return React10.useContext(Avl3DContext);
6994
7037
  }
6995
- function CameraController2({ targetPosition, targetLookAt, animated }) {
7038
+ function CameraController({ targetPosition, targetLookAt, animated }) {
6996
7039
  const { camera } = fiber.useThree();
6997
- const targetPosVec = React8.useRef(new THREE6__namespace.Vector3(...targetPosition));
6998
- const targetLookVec = React8.useRef(new THREE6__namespace.Vector3(...targetLookAt));
6999
- const isAnimating = React8.useRef(false);
7000
- React8.useEffect(() => {
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(() => {
7001
7044
  const newTarget = new THREE6__namespace.Vector3(...targetPosition);
7002
7045
  const newLookAt = new THREE6__namespace.Vector3(...targetLookAt);
7003
7046
  if (!newTarget.equals(targetPosVec.current) || !newLookAt.equals(targetLookVec.current)) {
@@ -7026,8 +7069,8 @@ function CameraController2({ targetPosition, targetLookAt, animated }) {
7026
7069
  return null;
7027
7070
  }
7028
7071
  function SceneFade({ animating, children }) {
7029
- const groupRef = React8.useRef(null);
7030
- const opacityRef = React8.useRef(1);
7072
+ const groupRef = React10.useRef(null);
7073
+ const opacityRef = React10.useRef(1);
7031
7074
  fiber.useFrame((_, delta) => {
7032
7075
  if (!groupRef.current) return;
7033
7076
  const target = animating ? 0 : 1;
@@ -7051,7 +7094,7 @@ var Avl3DViewer = ({
7051
7094
  modelOverrides = {}
7052
7095
  }) => {
7053
7096
  const { t } = hooks.useTranslate();
7054
- const schema = React8.useMemo(() => {
7097
+ const schema = React10.useMemo(() => {
7055
7098
  if (typeof schemaProp === "string") {
7056
7099
  try {
7057
7100
  return JSON.parse(schemaProp);
@@ -7061,12 +7104,12 @@ var Avl3DViewer = ({
7061
7104
  }
7062
7105
  return schemaProp;
7063
7106
  }, [schemaProp]);
7064
- const configValue = React8.useMemo(() => ({
7107
+ const configValue = React10.useMemo(() => ({
7065
7108
  modelOverrides,
7066
7109
  effectsEnabled: effects
7067
7110
  }), [modelOverrides, effects]);
7068
- const [state, dispatch] = React8.useReducer(zoomReducer, initialZoomState);
7069
- React8.useEffect(() => {
7111
+ const [state, dispatch] = React10.useReducer(zoomReducer, initialZoomState);
7112
+ React10.useEffect(() => {
7070
7113
  if (initialOrbital) {
7071
7114
  dispatch({ type: "ZOOM_INTO_ORBITAL", orbital: initialOrbital, targetPosition: { x: 0, y: 0 } });
7072
7115
  setTimeout(() => dispatch({ type: "ANIMATION_COMPLETE" }), 0);
@@ -7078,35 +7121,35 @@ var Avl3DViewer = ({
7078
7121
  }
7079
7122
  }
7080
7123
  }, [initialOrbital, initialTrait]);
7081
- React8.useEffect(() => {
7124
+ React10.useEffect(() => {
7082
7125
  onZoomChange?.(state.level, {
7083
7126
  orbital: state.selectedOrbital ?? void 0,
7084
7127
  trait: state.selectedTrait ?? void 0
7085
7128
  });
7086
7129
  }, [state.level, state.selectedOrbital, state.selectedTrait, onZoomChange]);
7087
- React8.useEffect(() => {
7130
+ React10.useEffect(() => {
7088
7131
  if (!state.animating) return;
7089
7132
  const timer = setTimeout(() => {
7090
7133
  dispatch({ type: "ANIMATION_COMPLETE" });
7091
7134
  }, 800);
7092
7135
  return () => clearTimeout(timer);
7093
7136
  }, [state.animating]);
7094
- const handleKeyDown = React8.useCallback((e) => {
7137
+ const handleKeyDown = React10.useCallback((e) => {
7095
7138
  if (e.key === "Escape") {
7096
7139
  dispatch({ type: "ZOOM_OUT" });
7097
7140
  }
7098
7141
  }, []);
7099
- const handleOrbitalClick = React8.useCallback((name) => {
7142
+ const handleOrbitalClick = React10.useCallback((name) => {
7100
7143
  dispatch({ type: "ZOOM_INTO_ORBITAL", orbital: name, targetPosition: { x: 0, y: 0 } });
7101
7144
  }, []);
7102
- const handleTraitClick = React8.useCallback((name) => {
7145
+ const handleTraitClick = React10.useCallback((name) => {
7103
7146
  dispatch({ type: "ZOOM_INTO_TRAIT", trait: name, targetPosition: { x: 0, y: 0 } });
7104
7147
  }, []);
7105
- const [highlightedTrait, setHighlightedTrait] = React8__namespace.default.useState(null);
7106
- const handleTransitionClick = React8.useCallback((index) => {
7148
+ const [highlightedTrait, setHighlightedTrait] = React10__namespace.default.useState(null);
7149
+ const handleTransitionClick = React10.useCallback((index) => {
7107
7150
  dispatch({ type: "ZOOM_INTO_TRANSITION", transitionIndex: index, targetPosition: { x: 0, y: 0 } });
7108
7151
  }, []);
7109
- const handleBreadcrumbClick = React8.useCallback((targetLevel) => {
7152
+ const handleBreadcrumbClick = React10.useCallback((targetLevel) => {
7110
7153
  const levelOrder = ["application", "orbital", "trait", "transition"];
7111
7154
  const currentIdx = levelOrder.indexOf(state.level);
7112
7155
  const targetIdx = levelOrder.indexOf(targetLevel);
@@ -7115,7 +7158,7 @@ var Avl3DViewer = ({
7115
7158
  }
7116
7159
  }, [state.level]);
7117
7160
  const cameraConfig = CAMERA_POSITIONS[state.level];
7118
- const sceneContent = React8.useMemo(() => {
7161
+ const sceneContent = React10.useMemo(() => {
7119
7162
  switch (state.level) {
7120
7163
  case "application": {
7121
7164
  const data = parseApplicationLevel(schema);
@@ -7189,7 +7232,7 @@ var Avl3DViewer = ({
7189
7232
  gap: "xs",
7190
7233
  align: "center",
7191
7234
  className: "absolute top-2 left-2 z-10 bg-surface/80 backdrop-blur rounded-md px-3 py-1.5",
7192
- children: breadcrumbs.map((crumb, i) => /* @__PURE__ */ jsxRuntime.jsxs(React8__namespace.default.Fragment, { children: [
7235
+ children: breadcrumbs.map((crumb, i) => /* @__PURE__ */ jsxRuntime.jsxs(React10__namespace.default.Fragment, { children: [
7193
7236
  i > 0 && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", className: "mx-1", children: "/" }),
7194
7237
  i < breadcrumbs.length - 1 ? /* @__PURE__ */ jsxRuntime.jsx(
7195
7238
  Box,
@@ -7264,7 +7307,7 @@ var Avl3DViewer = ({
7264
7307
  }
7265
7308
  ),
7266
7309
  /* @__PURE__ */ jsxRuntime.jsx(
7267
- CameraController2,
7310
+ CameraController,
7268
7311
  {
7269
7312
  targetPosition: cameraConfig.position,
7270
7313
  targetLookAt: cameraConfig.target,