@almadar/ui 5.63.2 → 5.65.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.
@@ -45,6 +45,7 @@ var THREE3 = require('three');
45
45
  var GLTFLoader_js = require('three/examples/jsm/loaders/GLTFLoader.js');
46
46
  var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
47
47
  var GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader');
48
+ var SkeletonUtils = require('three/examples/jsm/utils/SkeletonUtils');
48
49
  var fiber = require('@react-three/fiber');
49
50
  var drei = require('@react-three/drei');
50
51
  var patterns = require('@almadar/patterns');
@@ -11285,16 +11286,26 @@ var init_useUnitSpriteAtlas = __esm({
11285
11286
  });
11286
11287
 
11287
11288
  // components/game/organisms/utils/isometric.ts
11288
- function isoToScreen(tileX, tileY, scale, baseOffsetX) {
11289
+ function isoToScreen(tileX, tileY, scale, baseOffsetX, layout = "isometric") {
11289
11290
  const scaledTileWidth = TILE_WIDTH * scale;
11290
11291
  const scaledFloorHeight = FLOOR_HEIGHT * scale;
11292
+ if (layout === "hex") {
11293
+ const screenX2 = tileX * scaledTileWidth + (tileY & 1) * (scaledTileWidth / 2) + baseOffsetX;
11294
+ const screenY2 = tileY * (scaledFloorHeight * 0.75);
11295
+ return { x: screenX2, y: screenY2 };
11296
+ }
11291
11297
  const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
11292
11298
  const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
11293
11299
  return { x: screenX, y: screenY };
11294
11300
  }
11295
- function screenToIso(screenX, screenY, scale, baseOffsetX) {
11301
+ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric") {
11296
11302
  const scaledTileWidth = TILE_WIDTH * scale;
11297
11303
  const scaledFloorHeight = FLOOR_HEIGHT * scale;
11304
+ if (layout === "hex") {
11305
+ const row = Math.round(screenY / (scaledFloorHeight * 0.75));
11306
+ const col = Math.round((screenX - (row & 1) * (scaledTileWidth / 2) - baseOffsetX) / scaledTileWidth);
11307
+ return { x: col, y: row };
11308
+ }
11298
11309
  const adjustedX = screenX - baseOffsetX;
11299
11310
  const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
11300
11311
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
@@ -11344,6 +11355,7 @@ function IsometricCanvas({
11344
11355
  tileHoverEvent,
11345
11356
  tileLeaveEvent,
11346
11357
  // Rendering options
11358
+ tileLayout = "isometric",
11347
11359
  scale = 0.4,
11348
11360
  debug: debug2 = false,
11349
11361
  backgroundImage = "",
@@ -11413,13 +11425,17 @@ function IsometricCanvas({
11413
11425
  );
11414
11426
  const sortedTiles = React85.useMemo(() => {
11415
11427
  const tiles = [...tilesProp];
11416
- tiles.sort((a, b) => {
11417
- const depthA = a.x + a.y;
11418
- const depthB = b.x + b.y;
11419
- return depthA !== depthB ? depthA - depthB : a.y - b.y;
11420
- });
11428
+ if (tileLayout === "hex") {
11429
+ tiles.sort((a, b) => a.y !== b.y ? a.y - b.y : a.x - b.x);
11430
+ } else {
11431
+ tiles.sort((a, b) => {
11432
+ const depthA = a.x + a.y;
11433
+ const depthB = b.x + b.y;
11434
+ return depthA !== depthB ? depthA - depthB : a.y - b.y;
11435
+ });
11436
+ }
11421
11437
  return tiles;
11422
- }, [tilesProp]);
11438
+ }, [tilesProp, tileLayout]);
11423
11439
  const gridWidth = React85.useMemo(() => {
11424
11440
  if (sortedTiles.length === 0) return 0;
11425
11441
  return Math.max(...sortedTiles.map((t2) => t2.x)) + 1;
@@ -11533,7 +11549,7 @@ function IsometricCanvas({
11533
11549
  miniCanvas.width = mW;
11534
11550
  miniCanvas.height = mH;
11535
11551
  mCtx.clearRect(0, 0, mW, mH);
11536
- const allScreenPos = sortedTiles.map((t2) => isoToScreen(t2.x, t2.y, scale, baseOffsetX));
11552
+ const allScreenPos = sortedTiles.map((t2) => isoToScreen(t2.x, t2.y, scale, baseOffsetX, tileLayout));
11537
11553
  const minX = Math.min(...allScreenPos.map((p2) => p2.x));
11538
11554
  const maxX = Math.max(...allScreenPos.map((p2) => p2.x + scaledTileWidth));
11539
11555
  const minY = Math.min(...allScreenPos.map((p2) => p2.y));
@@ -11544,7 +11560,7 @@ function IsometricCanvas({
11544
11560
  const offsetMx = (mW - worldW * scaleM) / 2;
11545
11561
  const offsetMy = (mH - worldH * scaleM) / 2;
11546
11562
  for (const tile of sortedTiles) {
11547
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11563
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11548
11564
  const mx = (pos.x - minX) * scaleM + offsetMx;
11549
11565
  const my = (pos.y - minY) * scaleM + offsetMy;
11550
11566
  const mTileW = scaledTileWidth * scaleM;
@@ -11560,7 +11576,7 @@ function IsometricCanvas({
11560
11576
  }
11561
11577
  for (const unit of units) {
11562
11578
  if (!unit.position) continue;
11563
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11579
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11564
11580
  const mx = (pos.x + scaledTileWidth / 2 - minX) * scaleM + offsetMx;
11565
11581
  const my = (pos.y + scaledTileHeight / 2 - minY) * scaleM + offsetMy;
11566
11582
  mCtx.fillStyle = unit.team === "player" ? "#60a5fa" : unit.team === "enemy" ? "#f87171" : "#9ca3af";
@@ -11576,7 +11592,7 @@ function IsometricCanvas({
11576
11592
  mCtx.strokeStyle = "rgba(255, 255, 255, 0.8)";
11577
11593
  mCtx.lineWidth = 1;
11578
11594
  mCtx.strokeRect(vLeft, vTop, vW, vH);
11579
- }, [showMinimap, sortedTiles, units, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11595
+ }, [showMinimap, sortedTiles, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11580
11596
  const draw = React85.useCallback((animTime) => {
11581
11597
  const canvas = canvasRef.current;
11582
11598
  if (!canvas) return;
@@ -11616,7 +11632,7 @@ function IsometricCanvas({
11616
11632
  const visTop = cam.y - viewportSize.height / cam.zoom;
11617
11633
  const visBottom = cam.y + viewportSize.height * 2 / cam.zoom;
11618
11634
  for (const tile of sortedTiles) {
11619
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11635
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11620
11636
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11621
11637
  continue;
11622
11638
  }
@@ -11696,12 +11712,13 @@ function IsometricCanvas({
11696
11712
  }
11697
11713
  }
11698
11714
  const sortedFeatures = [...features].sort((a, b) => {
11715
+ if (tileLayout === "hex") return a.y !== b.y ? a.y - b.y : a.x - b.x;
11699
11716
  const depthA = a.x + a.y;
11700
11717
  const depthB = b.x + b.y;
11701
11718
  return depthA !== depthB ? depthA - depthB : a.y - b.y;
11702
11719
  });
11703
11720
  for (const feature of sortedFeatures) {
11704
- const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX);
11721
+ const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX, tileLayout);
11705
11722
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11706
11723
  continue;
11707
11724
  }
@@ -11736,12 +11753,13 @@ function IsometricCanvas({
11736
11753
  }
11737
11754
  const unitsWithPosition = units.filter((u) => !!u.position);
11738
11755
  const sortedUnits = [...unitsWithPosition].sort((a, b) => {
11756
+ if (tileLayout === "hex") return a.position.y !== b.position.y ? a.position.y - b.position.y : a.position.x - b.position.x;
11739
11757
  const depthA = a.position.x + a.position.y;
11740
11758
  const depthB = b.position.x + b.position.y;
11741
11759
  return depthA !== depthB ? depthA - depthB : a.position.y - b.position.y;
11742
11760
  });
11743
11761
  for (const unit of sortedUnits) {
11744
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11762
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11745
11763
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11746
11764
  continue;
11747
11765
  }
@@ -11761,7 +11779,7 @@ function IsometricCanvas({
11761
11779
  drawH = maxUnitW / ar;
11762
11780
  }
11763
11781
  if (unit.previousPosition && (unit.previousPosition.x !== unit.position.x || unit.previousPosition.y !== unit.position.y)) {
11764
- const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX);
11782
+ const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX, tileLayout);
11765
11783
  const ghostCenterX = ghostPos.x + scaledTileWidth / 2;
11766
11784
  const ghostGroundY = ghostPos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
11767
11785
  ctx.save();
@@ -11890,6 +11908,7 @@ function IsometricCanvas({
11890
11908
  units,
11891
11909
  features,
11892
11910
  selectedUnitId,
11911
+ tileLayout,
11893
11912
  scale,
11894
11913
  debug2,
11895
11914
  resolveTerrainSpriteUrl,
@@ -11918,14 +11937,14 @@ function IsometricCanvas({
11918
11937
  if (!selectedUnitId) return;
11919
11938
  const unit = units.find((u) => u.id === selectedUnitId);
11920
11939
  if (!unit?.position) return;
11921
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11940
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11922
11941
  const centerX = pos.x + scaledTileWidth / 2;
11923
11942
  const centerY = pos.y + scaledDiamondTopY + scaledFloorHeight / 2;
11924
11943
  targetCameraRef.current = {
11925
11944
  x: centerX - viewportSize.width / 2,
11926
11945
  y: centerY - viewportSize.height / 2
11927
11946
  };
11928
- }, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11947
+ }, [selectedUnitId, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11929
11948
  React85.useEffect(() => {
11930
11949
  const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
11931
11950
  draw(animTimeRef.current);
@@ -11958,13 +11977,13 @@ function IsometricCanvas({
11958
11977
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11959
11978
  const adjustedX = world.x - scaledTileWidth / 2;
11960
11979
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11961
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11980
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11962
11981
  const tileExists = tilesProp.some((t2) => t2.x === isoPos.x && t2.y === isoPos.y);
11963
11982
  if (tileExists) {
11964
11983
  if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
11965
11984
  onTileHover?.(isoPos.x, isoPos.y);
11966
11985
  }
11967
- }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11986
+ }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, tileLayout, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11968
11987
  const handleCanvasPointerUp = React85.useCallback((e) => {
11969
11988
  singlePointerActiveRef.current = false;
11970
11989
  if (enableCamera) handlePointerUp();
@@ -11973,7 +11992,7 @@ function IsometricCanvas({
11973
11992
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11974
11993
  const adjustedX = world.x - scaledTileWidth / 2;
11975
11994
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11976
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11995
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11977
11996
  const clickedUnit = units.find((u) => u.position?.x === isoPos.x && u.position?.y === isoPos.y);
11978
11997
  if (clickedUnit && (onUnitClick || unitClickEvent)) {
11979
11998
  if (unitClickEvent) eventBus.emit(`UI:${unitClickEvent}`, { unitId: clickedUnit.id });
@@ -11985,7 +12004,7 @@ function IsometricCanvas({
11985
12004
  onTileClick?.(isoPos.x, isoPos.y);
11986
12005
  }
11987
12006
  }
11988
- }, [enableCamera, handlePointerUp, dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
12007
+ }, [enableCamera, handlePointerUp, dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, tileLayout, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
11989
12008
  const handleCanvasPointerLeave = React85.useCallback(() => {
11990
12009
  handleMouseLeave();
11991
12010
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
@@ -43261,7 +43280,8 @@ function ModelLoader({
43261
43280
  const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
43262
43281
  const model = React85.useMemo(() => {
43263
43282
  if (!loadedModel) return null;
43264
- const cloned = loadedModel.clone();
43283
+ const cloned = SkeletonUtils.clone(loadedModel);
43284
+ cloned.updateMatrixWorld(true);
43265
43285
  cloned.traverse((child) => {
43266
43286
  if (child instanceof THREE3__namespace.Mesh) {
43267
43287
  child.castShadow = castShadow;
@@ -43276,7 +43296,8 @@ function ModelLoader({
43276
43296
  const size = new THREE3__namespace.Vector3();
43277
43297
  box.getSize(size);
43278
43298
  const maxDim = Math.max(size.x, size.y, size.z);
43279
- return maxDim > 0 && Number.isFinite(maxDim) ? 1 / maxDim : 1;
43299
+ if (!Number.isFinite(maxDim) || maxDim < 0.05) return 1;
43300
+ return 1 / maxDim;
43280
43301
  }, [model]);
43281
43302
  const scaleArray = React85.useMemo(() => {
43282
43303
  const base = typeof scale === "number" ? [scale, scale, scale] : scale;
@@ -44558,6 +44579,48 @@ var init_HeroOrganism = __esm({
44558
44579
  _HeroClickInterceptor.displayName = "_HeroClickInterceptor";
44559
44580
  }
44560
44581
  });
44582
+ function HexStrategyBoard({
44583
+ tiles,
44584
+ units,
44585
+ features,
44586
+ assetManifest,
44587
+ assetBaseUrl,
44588
+ scale = 0.45,
44589
+ showMinimap = true,
44590
+ enableCamera = true,
44591
+ tileClickEvent,
44592
+ unitClickEvent,
44593
+ isLoading,
44594
+ error,
44595
+ className
44596
+ }) {
44597
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("hex-strategy-board relative w-full h-full", className), children: /* @__PURE__ */ jsxRuntime.jsx(
44598
+ IsometricCanvas_default,
44599
+ {
44600
+ tileLayout: "hex",
44601
+ tiles,
44602
+ units,
44603
+ features,
44604
+ assetManifest,
44605
+ assetBaseUrl,
44606
+ scale,
44607
+ showMinimap,
44608
+ enableCamera,
44609
+ tileClickEvent,
44610
+ unitClickEvent,
44611
+ isLoading,
44612
+ error
44613
+ }
44614
+ ) });
44615
+ }
44616
+ var init_HexStrategyBoard = __esm({
44617
+ "components/game/organisms/HexStrategyBoard.tsx"() {
44618
+ "use client";
44619
+ init_cn();
44620
+ init_IsometricCanvas();
44621
+ HexStrategyBoard.displayName = "HexStrategyBoard";
44622
+ }
44623
+ });
44561
44624
  var LandingPageTemplate;
44562
44625
  var init_LandingPageTemplate = __esm({
44563
44626
  "components/marketing/templates/LandingPageTemplate.tsx"() {
@@ -52367,6 +52430,7 @@ var init_component_registry_generated = __esm({
52367
52430
  init_HealthPanel();
52368
52431
  init_HeroOrganism();
52369
52432
  init_HeroSection();
52433
+ init_HexStrategyBoard();
52370
52434
  init_Icon();
52371
52435
  init_InfiniteScrollSentinel();
52372
52436
  init_Input();
@@ -52703,6 +52767,7 @@ var init_component_registry_generated = __esm({
52703
52767
  "HealthPanel": HealthPanel,
52704
52768
  "HeroOrganism": HeroOrganism,
52705
52769
  "HeroSection": HeroSection,
52770
+ "HexStrategyBoard": HexStrategyBoard,
52706
52771
  "Icon": Icon,
52707
52772
  "InfiniteScrollSentinel": InfiniteScrollSentinel,
52708
52773
  "Input": Input,
@@ -45,6 +45,7 @@ import * as THREE3 from 'three';
45
45
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
46
46
  import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
47
47
  import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
48
+ import { clone } from 'three/examples/jsm/utils/SkeletonUtils';
48
49
  import { Canvas, useLoader, useFrame, useThree } from '@react-three/fiber';
49
50
  import { Billboard, Grid as Grid$1, OrbitControls } from '@react-three/drei';
50
51
  import { getPatternDefinition, getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
@@ -11238,16 +11239,26 @@ var init_useUnitSpriteAtlas = __esm({
11238
11239
  });
11239
11240
 
11240
11241
  // components/game/organisms/utils/isometric.ts
11241
- function isoToScreen(tileX, tileY, scale, baseOffsetX) {
11242
+ function isoToScreen(tileX, tileY, scale, baseOffsetX, layout = "isometric") {
11242
11243
  const scaledTileWidth = TILE_WIDTH * scale;
11243
11244
  const scaledFloorHeight = FLOOR_HEIGHT * scale;
11245
+ if (layout === "hex") {
11246
+ const screenX2 = tileX * scaledTileWidth + (tileY & 1) * (scaledTileWidth / 2) + baseOffsetX;
11247
+ const screenY2 = tileY * (scaledFloorHeight * 0.75);
11248
+ return { x: screenX2, y: screenY2 };
11249
+ }
11244
11250
  const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
11245
11251
  const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
11246
11252
  return { x: screenX, y: screenY };
11247
11253
  }
11248
- function screenToIso(screenX, screenY, scale, baseOffsetX) {
11254
+ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric") {
11249
11255
  const scaledTileWidth = TILE_WIDTH * scale;
11250
11256
  const scaledFloorHeight = FLOOR_HEIGHT * scale;
11257
+ if (layout === "hex") {
11258
+ const row = Math.round(screenY / (scaledFloorHeight * 0.75));
11259
+ const col = Math.round((screenX - (row & 1) * (scaledTileWidth / 2) - baseOffsetX) / scaledTileWidth);
11260
+ return { x: col, y: row };
11261
+ }
11251
11262
  const adjustedX = screenX - baseOffsetX;
11252
11263
  const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
11253
11264
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
@@ -11297,6 +11308,7 @@ function IsometricCanvas({
11297
11308
  tileHoverEvent,
11298
11309
  tileLeaveEvent,
11299
11310
  // Rendering options
11311
+ tileLayout = "isometric",
11300
11312
  scale = 0.4,
11301
11313
  debug: debug2 = false,
11302
11314
  backgroundImage = "",
@@ -11366,13 +11378,17 @@ function IsometricCanvas({
11366
11378
  );
11367
11379
  const sortedTiles = useMemo(() => {
11368
11380
  const tiles = [...tilesProp];
11369
- tiles.sort((a, b) => {
11370
- const depthA = a.x + a.y;
11371
- const depthB = b.x + b.y;
11372
- return depthA !== depthB ? depthA - depthB : a.y - b.y;
11373
- });
11381
+ if (tileLayout === "hex") {
11382
+ tiles.sort((a, b) => a.y !== b.y ? a.y - b.y : a.x - b.x);
11383
+ } else {
11384
+ tiles.sort((a, b) => {
11385
+ const depthA = a.x + a.y;
11386
+ const depthB = b.x + b.y;
11387
+ return depthA !== depthB ? depthA - depthB : a.y - b.y;
11388
+ });
11389
+ }
11374
11390
  return tiles;
11375
- }, [tilesProp]);
11391
+ }, [tilesProp, tileLayout]);
11376
11392
  const gridWidth = useMemo(() => {
11377
11393
  if (sortedTiles.length === 0) return 0;
11378
11394
  return Math.max(...sortedTiles.map((t2) => t2.x)) + 1;
@@ -11486,7 +11502,7 @@ function IsometricCanvas({
11486
11502
  miniCanvas.width = mW;
11487
11503
  miniCanvas.height = mH;
11488
11504
  mCtx.clearRect(0, 0, mW, mH);
11489
- const allScreenPos = sortedTiles.map((t2) => isoToScreen(t2.x, t2.y, scale, baseOffsetX));
11505
+ const allScreenPos = sortedTiles.map((t2) => isoToScreen(t2.x, t2.y, scale, baseOffsetX, tileLayout));
11490
11506
  const minX = Math.min(...allScreenPos.map((p2) => p2.x));
11491
11507
  const maxX = Math.max(...allScreenPos.map((p2) => p2.x + scaledTileWidth));
11492
11508
  const minY = Math.min(...allScreenPos.map((p2) => p2.y));
@@ -11497,7 +11513,7 @@ function IsometricCanvas({
11497
11513
  const offsetMx = (mW - worldW * scaleM) / 2;
11498
11514
  const offsetMy = (mH - worldH * scaleM) / 2;
11499
11515
  for (const tile of sortedTiles) {
11500
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11516
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11501
11517
  const mx = (pos.x - minX) * scaleM + offsetMx;
11502
11518
  const my = (pos.y - minY) * scaleM + offsetMy;
11503
11519
  const mTileW = scaledTileWidth * scaleM;
@@ -11513,7 +11529,7 @@ function IsometricCanvas({
11513
11529
  }
11514
11530
  for (const unit of units) {
11515
11531
  if (!unit.position) continue;
11516
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11532
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11517
11533
  const mx = (pos.x + scaledTileWidth / 2 - minX) * scaleM + offsetMx;
11518
11534
  const my = (pos.y + scaledTileHeight / 2 - minY) * scaleM + offsetMy;
11519
11535
  mCtx.fillStyle = unit.team === "player" ? "#60a5fa" : unit.team === "enemy" ? "#f87171" : "#9ca3af";
@@ -11529,7 +11545,7 @@ function IsometricCanvas({
11529
11545
  mCtx.strokeStyle = "rgba(255, 255, 255, 0.8)";
11530
11546
  mCtx.lineWidth = 1;
11531
11547
  mCtx.strokeRect(vLeft, vTop, vW, vH);
11532
- }, [showMinimap, sortedTiles, units, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11548
+ }, [showMinimap, sortedTiles, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11533
11549
  const draw = useCallback((animTime) => {
11534
11550
  const canvas = canvasRef.current;
11535
11551
  if (!canvas) return;
@@ -11569,7 +11585,7 @@ function IsometricCanvas({
11569
11585
  const visTop = cam.y - viewportSize.height / cam.zoom;
11570
11586
  const visBottom = cam.y + viewportSize.height * 2 / cam.zoom;
11571
11587
  for (const tile of sortedTiles) {
11572
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11588
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11573
11589
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11574
11590
  continue;
11575
11591
  }
@@ -11649,12 +11665,13 @@ function IsometricCanvas({
11649
11665
  }
11650
11666
  }
11651
11667
  const sortedFeatures = [...features].sort((a, b) => {
11668
+ if (tileLayout === "hex") return a.y !== b.y ? a.y - b.y : a.x - b.x;
11652
11669
  const depthA = a.x + a.y;
11653
11670
  const depthB = b.x + b.y;
11654
11671
  return depthA !== depthB ? depthA - depthB : a.y - b.y;
11655
11672
  });
11656
11673
  for (const feature of sortedFeatures) {
11657
- const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX);
11674
+ const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX, tileLayout);
11658
11675
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11659
11676
  continue;
11660
11677
  }
@@ -11689,12 +11706,13 @@ function IsometricCanvas({
11689
11706
  }
11690
11707
  const unitsWithPosition = units.filter((u) => !!u.position);
11691
11708
  const sortedUnits = [...unitsWithPosition].sort((a, b) => {
11709
+ if (tileLayout === "hex") return a.position.y !== b.position.y ? a.position.y - b.position.y : a.position.x - b.position.x;
11692
11710
  const depthA = a.position.x + a.position.y;
11693
11711
  const depthB = b.position.x + b.position.y;
11694
11712
  return depthA !== depthB ? depthA - depthB : a.position.y - b.position.y;
11695
11713
  });
11696
11714
  for (const unit of sortedUnits) {
11697
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11715
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11698
11716
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11699
11717
  continue;
11700
11718
  }
@@ -11714,7 +11732,7 @@ function IsometricCanvas({
11714
11732
  drawH = maxUnitW / ar;
11715
11733
  }
11716
11734
  if (unit.previousPosition && (unit.previousPosition.x !== unit.position.x || unit.previousPosition.y !== unit.position.y)) {
11717
- const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX);
11735
+ const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX, tileLayout);
11718
11736
  const ghostCenterX = ghostPos.x + scaledTileWidth / 2;
11719
11737
  const ghostGroundY = ghostPos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
11720
11738
  ctx.save();
@@ -11843,6 +11861,7 @@ function IsometricCanvas({
11843
11861
  units,
11844
11862
  features,
11845
11863
  selectedUnitId,
11864
+ tileLayout,
11846
11865
  scale,
11847
11866
  debug2,
11848
11867
  resolveTerrainSpriteUrl,
@@ -11871,14 +11890,14 @@ function IsometricCanvas({
11871
11890
  if (!selectedUnitId) return;
11872
11891
  const unit = units.find((u) => u.id === selectedUnitId);
11873
11892
  if (!unit?.position) return;
11874
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11893
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11875
11894
  const centerX = pos.x + scaledTileWidth / 2;
11876
11895
  const centerY = pos.y + scaledDiamondTopY + scaledFloorHeight / 2;
11877
11896
  targetCameraRef.current = {
11878
11897
  x: centerX - viewportSize.width / 2,
11879
11898
  y: centerY - viewportSize.height / 2
11880
11899
  };
11881
- }, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11900
+ }, [selectedUnitId, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11882
11901
  useEffect(() => {
11883
11902
  const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
11884
11903
  draw(animTimeRef.current);
@@ -11911,13 +11930,13 @@ function IsometricCanvas({
11911
11930
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11912
11931
  const adjustedX = world.x - scaledTileWidth / 2;
11913
11932
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11914
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11933
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11915
11934
  const tileExists = tilesProp.some((t2) => t2.x === isoPos.x && t2.y === isoPos.y);
11916
11935
  if (tileExists) {
11917
11936
  if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
11918
11937
  onTileHover?.(isoPos.x, isoPos.y);
11919
11938
  }
11920
- }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11939
+ }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, tileLayout, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11921
11940
  const handleCanvasPointerUp = useCallback((e) => {
11922
11941
  singlePointerActiveRef.current = false;
11923
11942
  if (enableCamera) handlePointerUp();
@@ -11926,7 +11945,7 @@ function IsometricCanvas({
11926
11945
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11927
11946
  const adjustedX = world.x - scaledTileWidth / 2;
11928
11947
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11929
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11948
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11930
11949
  const clickedUnit = units.find((u) => u.position?.x === isoPos.x && u.position?.y === isoPos.y);
11931
11950
  if (clickedUnit && (onUnitClick || unitClickEvent)) {
11932
11951
  if (unitClickEvent) eventBus.emit(`UI:${unitClickEvent}`, { unitId: clickedUnit.id });
@@ -11938,7 +11957,7 @@ function IsometricCanvas({
11938
11957
  onTileClick?.(isoPos.x, isoPos.y);
11939
11958
  }
11940
11959
  }
11941
- }, [enableCamera, handlePointerUp, dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
11960
+ }, [enableCamera, handlePointerUp, dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, tileLayout, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
11942
11961
  const handleCanvasPointerLeave = useCallback(() => {
11943
11962
  handleMouseLeave();
11944
11963
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
@@ -43214,7 +43233,8 @@ function ModelLoader({
43214
43233
  const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
43215
43234
  const model = useMemo(() => {
43216
43235
  if (!loadedModel) return null;
43217
- const cloned = loadedModel.clone();
43236
+ const cloned = clone(loadedModel);
43237
+ cloned.updateMatrixWorld(true);
43218
43238
  cloned.traverse((child) => {
43219
43239
  if (child instanceof THREE3.Mesh) {
43220
43240
  child.castShadow = castShadow;
@@ -43229,7 +43249,8 @@ function ModelLoader({
43229
43249
  const size = new THREE3.Vector3();
43230
43250
  box.getSize(size);
43231
43251
  const maxDim = Math.max(size.x, size.y, size.z);
43232
- return maxDim > 0 && Number.isFinite(maxDim) ? 1 / maxDim : 1;
43252
+ if (!Number.isFinite(maxDim) || maxDim < 0.05) return 1;
43253
+ return 1 / maxDim;
43233
43254
  }, [model]);
43234
43255
  const scaleArray = useMemo(() => {
43235
43256
  const base = typeof scale === "number" ? [scale, scale, scale] : scale;
@@ -44511,6 +44532,48 @@ var init_HeroOrganism = __esm({
44511
44532
  _HeroClickInterceptor.displayName = "_HeroClickInterceptor";
44512
44533
  }
44513
44534
  });
44535
+ function HexStrategyBoard({
44536
+ tiles,
44537
+ units,
44538
+ features,
44539
+ assetManifest,
44540
+ assetBaseUrl,
44541
+ scale = 0.45,
44542
+ showMinimap = true,
44543
+ enableCamera = true,
44544
+ tileClickEvent,
44545
+ unitClickEvent,
44546
+ isLoading,
44547
+ error,
44548
+ className
44549
+ }) {
44550
+ return /* @__PURE__ */ jsx("div", { className: cn("hex-strategy-board relative w-full h-full", className), children: /* @__PURE__ */ jsx(
44551
+ IsometricCanvas_default,
44552
+ {
44553
+ tileLayout: "hex",
44554
+ tiles,
44555
+ units,
44556
+ features,
44557
+ assetManifest,
44558
+ assetBaseUrl,
44559
+ scale,
44560
+ showMinimap,
44561
+ enableCamera,
44562
+ tileClickEvent,
44563
+ unitClickEvent,
44564
+ isLoading,
44565
+ error
44566
+ }
44567
+ ) });
44568
+ }
44569
+ var init_HexStrategyBoard = __esm({
44570
+ "components/game/organisms/HexStrategyBoard.tsx"() {
44571
+ "use client";
44572
+ init_cn();
44573
+ init_IsometricCanvas();
44574
+ HexStrategyBoard.displayName = "HexStrategyBoard";
44575
+ }
44576
+ });
44514
44577
  var LandingPageTemplate;
44515
44578
  var init_LandingPageTemplate = __esm({
44516
44579
  "components/marketing/templates/LandingPageTemplate.tsx"() {
@@ -52320,6 +52383,7 @@ var init_component_registry_generated = __esm({
52320
52383
  init_HealthPanel();
52321
52384
  init_HeroOrganism();
52322
52385
  init_HeroSection();
52386
+ init_HexStrategyBoard();
52323
52387
  init_Icon();
52324
52388
  init_InfiniteScrollSentinel();
52325
52389
  init_Input();
@@ -52656,6 +52720,7 @@ var init_component_registry_generated = __esm({
52656
52720
  "HealthPanel": HealthPanel,
52657
52721
  "HeroOrganism": HeroOrganism,
52658
52722
  "HeroSection": HeroSection,
52723
+ "HexStrategyBoard": HexStrategyBoard,
52659
52724
  "Icon": Icon,
52660
52725
  "InfiniteScrollSentinel": InfiniteScrollSentinel,
52661
52726
  "Input": Input,