@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');
@@ -10983,16 +10984,26 @@ var init_useUnitSpriteAtlas = __esm({
10983
10984
  });
10984
10985
 
10985
10986
  // components/game/organisms/utils/isometric.ts
10986
- function isoToScreen(tileX, tileY, scale, baseOffsetX) {
10987
+ function isoToScreen(tileX, tileY, scale, baseOffsetX, layout = "isometric") {
10987
10988
  const scaledTileWidth = TILE_WIDTH * scale;
10988
10989
  const scaledFloorHeight = FLOOR_HEIGHT * scale;
10990
+ if (layout === "hex") {
10991
+ const screenX2 = tileX * scaledTileWidth + (tileY & 1) * (scaledTileWidth / 2) + baseOffsetX;
10992
+ const screenY2 = tileY * (scaledFloorHeight * 0.75);
10993
+ return { x: screenX2, y: screenY2 };
10994
+ }
10989
10995
  const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
10990
10996
  const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
10991
10997
  return { x: screenX, y: screenY };
10992
10998
  }
10993
- function screenToIso(screenX, screenY, scale, baseOffsetX) {
10999
+ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric") {
10994
11000
  const scaledTileWidth = TILE_WIDTH * scale;
10995
11001
  const scaledFloorHeight = FLOOR_HEIGHT * scale;
11002
+ if (layout === "hex") {
11003
+ const row = Math.round(screenY / (scaledFloorHeight * 0.75));
11004
+ const col = Math.round((screenX - (row & 1) * (scaledTileWidth / 2) - baseOffsetX) / scaledTileWidth);
11005
+ return { x: col, y: row };
11006
+ }
10996
11007
  const adjustedX = screenX - baseOffsetX;
10997
11008
  const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
10998
11009
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
@@ -11042,6 +11053,7 @@ function IsometricCanvas({
11042
11053
  tileHoverEvent,
11043
11054
  tileLeaveEvent,
11044
11055
  // Rendering options
11056
+ tileLayout = "isometric",
11045
11057
  scale = 0.4,
11046
11058
  debug: debug2 = false,
11047
11059
  backgroundImage = "",
@@ -11111,13 +11123,17 @@ function IsometricCanvas({
11111
11123
  );
11112
11124
  const sortedTiles = React84.useMemo(() => {
11113
11125
  const tiles = [...tilesProp];
11114
- tiles.sort((a, b) => {
11115
- const depthA = a.x + a.y;
11116
- const depthB = b.x + b.y;
11117
- return depthA !== depthB ? depthA - depthB : a.y - b.y;
11118
- });
11126
+ if (tileLayout === "hex") {
11127
+ tiles.sort((a, b) => a.y !== b.y ? a.y - b.y : a.x - b.x);
11128
+ } else {
11129
+ tiles.sort((a, b) => {
11130
+ const depthA = a.x + a.y;
11131
+ const depthB = b.x + b.y;
11132
+ return depthA !== depthB ? depthA - depthB : a.y - b.y;
11133
+ });
11134
+ }
11119
11135
  return tiles;
11120
- }, [tilesProp]);
11136
+ }, [tilesProp, tileLayout]);
11121
11137
  const gridWidth = React84.useMemo(() => {
11122
11138
  if (sortedTiles.length === 0) return 0;
11123
11139
  return Math.max(...sortedTiles.map((t2) => t2.x)) + 1;
@@ -11231,7 +11247,7 @@ function IsometricCanvas({
11231
11247
  miniCanvas.width = mW;
11232
11248
  miniCanvas.height = mH;
11233
11249
  mCtx.clearRect(0, 0, mW, mH);
11234
- const allScreenPos = sortedTiles.map((t2) => isoToScreen(t2.x, t2.y, scale, baseOffsetX));
11250
+ const allScreenPos = sortedTiles.map((t2) => isoToScreen(t2.x, t2.y, scale, baseOffsetX, tileLayout));
11235
11251
  const minX = Math.min(...allScreenPos.map((p2) => p2.x));
11236
11252
  const maxX = Math.max(...allScreenPos.map((p2) => p2.x + scaledTileWidth));
11237
11253
  const minY = Math.min(...allScreenPos.map((p2) => p2.y));
@@ -11242,7 +11258,7 @@ function IsometricCanvas({
11242
11258
  const offsetMx = (mW - worldW * scaleM) / 2;
11243
11259
  const offsetMy = (mH - worldH * scaleM) / 2;
11244
11260
  for (const tile of sortedTiles) {
11245
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11261
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11246
11262
  const mx = (pos.x - minX) * scaleM + offsetMx;
11247
11263
  const my = (pos.y - minY) * scaleM + offsetMy;
11248
11264
  const mTileW = scaledTileWidth * scaleM;
@@ -11258,7 +11274,7 @@ function IsometricCanvas({
11258
11274
  }
11259
11275
  for (const unit of units) {
11260
11276
  if (!unit.position) continue;
11261
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11277
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11262
11278
  const mx = (pos.x + scaledTileWidth / 2 - minX) * scaleM + offsetMx;
11263
11279
  const my = (pos.y + scaledTileHeight / 2 - minY) * scaleM + offsetMy;
11264
11280
  mCtx.fillStyle = unit.team === "player" ? "#60a5fa" : unit.team === "enemy" ? "#f87171" : "#9ca3af";
@@ -11274,7 +11290,7 @@ function IsometricCanvas({
11274
11290
  mCtx.strokeStyle = "rgba(255, 255, 255, 0.8)";
11275
11291
  mCtx.lineWidth = 1;
11276
11292
  mCtx.strokeRect(vLeft, vTop, vW, vH);
11277
- }, [showMinimap, sortedTiles, units, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11293
+ }, [showMinimap, sortedTiles, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11278
11294
  const draw = React84.useCallback((animTime) => {
11279
11295
  const canvas = canvasRef.current;
11280
11296
  if (!canvas) return;
@@ -11314,7 +11330,7 @@ function IsometricCanvas({
11314
11330
  const visTop = cam.y - viewportSize.height / cam.zoom;
11315
11331
  const visBottom = cam.y + viewportSize.height * 2 / cam.zoom;
11316
11332
  for (const tile of sortedTiles) {
11317
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11333
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11318
11334
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11319
11335
  continue;
11320
11336
  }
@@ -11394,12 +11410,13 @@ function IsometricCanvas({
11394
11410
  }
11395
11411
  }
11396
11412
  const sortedFeatures = [...features].sort((a, b) => {
11413
+ if (tileLayout === "hex") return a.y !== b.y ? a.y - b.y : a.x - b.x;
11397
11414
  const depthA = a.x + a.y;
11398
11415
  const depthB = b.x + b.y;
11399
11416
  return depthA !== depthB ? depthA - depthB : a.y - b.y;
11400
11417
  });
11401
11418
  for (const feature of sortedFeatures) {
11402
- const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX);
11419
+ const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX, tileLayout);
11403
11420
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11404
11421
  continue;
11405
11422
  }
@@ -11434,12 +11451,13 @@ function IsometricCanvas({
11434
11451
  }
11435
11452
  const unitsWithPosition = units.filter((u) => !!u.position);
11436
11453
  const sortedUnits = [...unitsWithPosition].sort((a, b) => {
11454
+ if (tileLayout === "hex") return a.position.y !== b.position.y ? a.position.y - b.position.y : a.position.x - b.position.x;
11437
11455
  const depthA = a.position.x + a.position.y;
11438
11456
  const depthB = b.position.x + b.position.y;
11439
11457
  return depthA !== depthB ? depthA - depthB : a.position.y - b.position.y;
11440
11458
  });
11441
11459
  for (const unit of sortedUnits) {
11442
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11460
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11443
11461
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11444
11462
  continue;
11445
11463
  }
@@ -11459,7 +11477,7 @@ function IsometricCanvas({
11459
11477
  drawH = maxUnitW / ar;
11460
11478
  }
11461
11479
  if (unit.previousPosition && (unit.previousPosition.x !== unit.position.x || unit.previousPosition.y !== unit.position.y)) {
11462
- const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX);
11480
+ const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX, tileLayout);
11463
11481
  const ghostCenterX = ghostPos.x + scaledTileWidth / 2;
11464
11482
  const ghostGroundY = ghostPos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
11465
11483
  ctx.save();
@@ -11588,6 +11606,7 @@ function IsometricCanvas({
11588
11606
  units,
11589
11607
  features,
11590
11608
  selectedUnitId,
11609
+ tileLayout,
11591
11610
  scale,
11592
11611
  debug2,
11593
11612
  resolveTerrainSpriteUrl,
@@ -11616,14 +11635,14 @@ function IsometricCanvas({
11616
11635
  if (!selectedUnitId) return;
11617
11636
  const unit = units.find((u) => u.id === selectedUnitId);
11618
11637
  if (!unit?.position) return;
11619
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11638
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11620
11639
  const centerX = pos.x + scaledTileWidth / 2;
11621
11640
  const centerY = pos.y + scaledDiamondTopY + scaledFloorHeight / 2;
11622
11641
  targetCameraRef.current = {
11623
11642
  x: centerX - viewportSize.width / 2,
11624
11643
  y: centerY - viewportSize.height / 2
11625
11644
  };
11626
- }, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11645
+ }, [selectedUnitId, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11627
11646
  React84.useEffect(() => {
11628
11647
  const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
11629
11648
  draw(animTimeRef.current);
@@ -11656,13 +11675,13 @@ function IsometricCanvas({
11656
11675
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11657
11676
  const adjustedX = world.x - scaledTileWidth / 2;
11658
11677
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11659
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11678
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11660
11679
  const tileExists = tilesProp.some((t2) => t2.x === isoPos.x && t2.y === isoPos.y);
11661
11680
  if (tileExists) {
11662
11681
  if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
11663
11682
  onTileHover?.(isoPos.x, isoPos.y);
11664
11683
  }
11665
- }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11684
+ }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, tileLayout, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11666
11685
  const handleCanvasPointerUp = React84.useCallback((e) => {
11667
11686
  singlePointerActiveRef.current = false;
11668
11687
  if (enableCamera) handlePointerUp();
@@ -11671,7 +11690,7 @@ function IsometricCanvas({
11671
11690
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11672
11691
  const adjustedX = world.x - scaledTileWidth / 2;
11673
11692
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11674
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11693
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11675
11694
  const clickedUnit = units.find((u) => u.position?.x === isoPos.x && u.position?.y === isoPos.y);
11676
11695
  if (clickedUnit && (onUnitClick || unitClickEvent)) {
11677
11696
  if (unitClickEvent) eventBus.emit(`UI:${unitClickEvent}`, { unitId: clickedUnit.id });
@@ -11683,7 +11702,7 @@ function IsometricCanvas({
11683
11702
  onTileClick?.(isoPos.x, isoPos.y);
11684
11703
  }
11685
11704
  }
11686
- }, [enableCamera, handlePointerUp, dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
11705
+ }, [enableCamera, handlePointerUp, dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, tileLayout, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
11687
11706
  const handleCanvasPointerLeave = React84.useCallback(() => {
11688
11707
  handleMouseLeave();
11689
11708
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
@@ -42959,7 +42978,8 @@ function ModelLoader({
42959
42978
  const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
42960
42979
  const model = React84.useMemo(() => {
42961
42980
  if (!loadedModel) return null;
42962
- const cloned = loadedModel.clone();
42981
+ const cloned = SkeletonUtils.clone(loadedModel);
42982
+ cloned.updateMatrixWorld(true);
42963
42983
  cloned.traverse((child) => {
42964
42984
  if (child instanceof THREE3__namespace.Mesh) {
42965
42985
  child.castShadow = castShadow;
@@ -42974,7 +42994,8 @@ function ModelLoader({
42974
42994
  const size = new THREE3__namespace.Vector3();
42975
42995
  box.getSize(size);
42976
42996
  const maxDim = Math.max(size.x, size.y, size.z);
42977
- return maxDim > 0 && Number.isFinite(maxDim) ? 1 / maxDim : 1;
42997
+ if (!Number.isFinite(maxDim) || maxDim < 0.05) return 1;
42998
+ return 1 / maxDim;
42978
42999
  }, [model]);
42979
43000
  const scaleArray = React84.useMemo(() => {
42980
43001
  const base = typeof scale === "number" ? [scale, scale, scale] : scale;
@@ -44256,6 +44277,48 @@ var init_HeroOrganism = __esm({
44256
44277
  _HeroClickInterceptor.displayName = "_HeroClickInterceptor";
44257
44278
  }
44258
44279
  });
44280
+ function HexStrategyBoard({
44281
+ tiles,
44282
+ units,
44283
+ features,
44284
+ assetManifest,
44285
+ assetBaseUrl,
44286
+ scale = 0.45,
44287
+ showMinimap = true,
44288
+ enableCamera = true,
44289
+ tileClickEvent,
44290
+ unitClickEvent,
44291
+ isLoading,
44292
+ error,
44293
+ className
44294
+ }) {
44295
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("hex-strategy-board relative w-full h-full", className), children: /* @__PURE__ */ jsxRuntime.jsx(
44296
+ IsometricCanvas_default,
44297
+ {
44298
+ tileLayout: "hex",
44299
+ tiles,
44300
+ units,
44301
+ features,
44302
+ assetManifest,
44303
+ assetBaseUrl,
44304
+ scale,
44305
+ showMinimap,
44306
+ enableCamera,
44307
+ tileClickEvent,
44308
+ unitClickEvent,
44309
+ isLoading,
44310
+ error
44311
+ }
44312
+ ) });
44313
+ }
44314
+ var init_HexStrategyBoard = __esm({
44315
+ "components/game/organisms/HexStrategyBoard.tsx"() {
44316
+ "use client";
44317
+ init_cn();
44318
+ init_IsometricCanvas();
44319
+ HexStrategyBoard.displayName = "HexStrategyBoard";
44320
+ }
44321
+ });
44259
44322
  var LandingPageTemplate;
44260
44323
  var init_LandingPageTemplate = __esm({
44261
44324
  "components/marketing/templates/LandingPageTemplate.tsx"() {
@@ -52084,6 +52147,7 @@ var init_component_registry_generated = __esm({
52084
52147
  init_HealthPanel();
52085
52148
  init_HeroOrganism();
52086
52149
  init_HeroSection();
52150
+ init_HexStrategyBoard();
52087
52151
  init_Icon();
52088
52152
  init_InfiniteScrollSentinel();
52089
52153
  init_Input();
@@ -52420,6 +52484,7 @@ var init_component_registry_generated = __esm({
52420
52484
  "HealthPanel": HealthPanel,
52421
52485
  "HeroOrganism": HeroOrganism,
52422
52486
  "HeroSection": HeroSection,
52487
+ "HexStrategyBoard": HexStrategyBoard,
52423
52488
  "Icon": Icon,
52424
52489
  "InfiniteScrollSentinel": InfiniteScrollSentinel,
52425
52490
  "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';
@@ -10936,16 +10937,26 @@ var init_useUnitSpriteAtlas = __esm({
10936
10937
  });
10937
10938
 
10938
10939
  // components/game/organisms/utils/isometric.ts
10939
- function isoToScreen(tileX, tileY, scale, baseOffsetX) {
10940
+ function isoToScreen(tileX, tileY, scale, baseOffsetX, layout = "isometric") {
10940
10941
  const scaledTileWidth = TILE_WIDTH * scale;
10941
10942
  const scaledFloorHeight = FLOOR_HEIGHT * scale;
10943
+ if (layout === "hex") {
10944
+ const screenX2 = tileX * scaledTileWidth + (tileY & 1) * (scaledTileWidth / 2) + baseOffsetX;
10945
+ const screenY2 = tileY * (scaledFloorHeight * 0.75);
10946
+ return { x: screenX2, y: screenY2 };
10947
+ }
10942
10948
  const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
10943
10949
  const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
10944
10950
  return { x: screenX, y: screenY };
10945
10951
  }
10946
- function screenToIso(screenX, screenY, scale, baseOffsetX) {
10952
+ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric") {
10947
10953
  const scaledTileWidth = TILE_WIDTH * scale;
10948
10954
  const scaledFloorHeight = FLOOR_HEIGHT * scale;
10955
+ if (layout === "hex") {
10956
+ const row = Math.round(screenY / (scaledFloorHeight * 0.75));
10957
+ const col = Math.round((screenX - (row & 1) * (scaledTileWidth / 2) - baseOffsetX) / scaledTileWidth);
10958
+ return { x: col, y: row };
10959
+ }
10949
10960
  const adjustedX = screenX - baseOffsetX;
10950
10961
  const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
10951
10962
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
@@ -10995,6 +11006,7 @@ function IsometricCanvas({
10995
11006
  tileHoverEvent,
10996
11007
  tileLeaveEvent,
10997
11008
  // Rendering options
11009
+ tileLayout = "isometric",
10998
11010
  scale = 0.4,
10999
11011
  debug: debug2 = false,
11000
11012
  backgroundImage = "",
@@ -11064,13 +11076,17 @@ function IsometricCanvas({
11064
11076
  );
11065
11077
  const sortedTiles = useMemo(() => {
11066
11078
  const tiles = [...tilesProp];
11067
- tiles.sort((a, b) => {
11068
- const depthA = a.x + a.y;
11069
- const depthB = b.x + b.y;
11070
- return depthA !== depthB ? depthA - depthB : a.y - b.y;
11071
- });
11079
+ if (tileLayout === "hex") {
11080
+ tiles.sort((a, b) => a.y !== b.y ? a.y - b.y : a.x - b.x);
11081
+ } else {
11082
+ tiles.sort((a, b) => {
11083
+ const depthA = a.x + a.y;
11084
+ const depthB = b.x + b.y;
11085
+ return depthA !== depthB ? depthA - depthB : a.y - b.y;
11086
+ });
11087
+ }
11072
11088
  return tiles;
11073
- }, [tilesProp]);
11089
+ }, [tilesProp, tileLayout]);
11074
11090
  const gridWidth = useMemo(() => {
11075
11091
  if (sortedTiles.length === 0) return 0;
11076
11092
  return Math.max(...sortedTiles.map((t2) => t2.x)) + 1;
@@ -11184,7 +11200,7 @@ function IsometricCanvas({
11184
11200
  miniCanvas.width = mW;
11185
11201
  miniCanvas.height = mH;
11186
11202
  mCtx.clearRect(0, 0, mW, mH);
11187
- const allScreenPos = sortedTiles.map((t2) => isoToScreen(t2.x, t2.y, scale, baseOffsetX));
11203
+ const allScreenPos = sortedTiles.map((t2) => isoToScreen(t2.x, t2.y, scale, baseOffsetX, tileLayout));
11188
11204
  const minX = Math.min(...allScreenPos.map((p2) => p2.x));
11189
11205
  const maxX = Math.max(...allScreenPos.map((p2) => p2.x + scaledTileWidth));
11190
11206
  const minY = Math.min(...allScreenPos.map((p2) => p2.y));
@@ -11195,7 +11211,7 @@ function IsometricCanvas({
11195
11211
  const offsetMx = (mW - worldW * scaleM) / 2;
11196
11212
  const offsetMy = (mH - worldH * scaleM) / 2;
11197
11213
  for (const tile of sortedTiles) {
11198
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11214
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11199
11215
  const mx = (pos.x - minX) * scaleM + offsetMx;
11200
11216
  const my = (pos.y - minY) * scaleM + offsetMy;
11201
11217
  const mTileW = scaledTileWidth * scaleM;
@@ -11211,7 +11227,7 @@ function IsometricCanvas({
11211
11227
  }
11212
11228
  for (const unit of units) {
11213
11229
  if (!unit.position) continue;
11214
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11230
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11215
11231
  const mx = (pos.x + scaledTileWidth / 2 - minX) * scaleM + offsetMx;
11216
11232
  const my = (pos.y + scaledTileHeight / 2 - minY) * scaleM + offsetMy;
11217
11233
  mCtx.fillStyle = unit.team === "player" ? "#60a5fa" : unit.team === "enemy" ? "#f87171" : "#9ca3af";
@@ -11227,7 +11243,7 @@ function IsometricCanvas({
11227
11243
  mCtx.strokeStyle = "rgba(255, 255, 255, 0.8)";
11228
11244
  mCtx.lineWidth = 1;
11229
11245
  mCtx.strokeRect(vLeft, vTop, vW, vH);
11230
- }, [showMinimap, sortedTiles, units, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11246
+ }, [showMinimap, sortedTiles, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11231
11247
  const draw = useCallback((animTime) => {
11232
11248
  const canvas = canvasRef.current;
11233
11249
  if (!canvas) return;
@@ -11267,7 +11283,7 @@ function IsometricCanvas({
11267
11283
  const visTop = cam.y - viewportSize.height / cam.zoom;
11268
11284
  const visBottom = cam.y + viewportSize.height * 2 / cam.zoom;
11269
11285
  for (const tile of sortedTiles) {
11270
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11286
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11271
11287
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11272
11288
  continue;
11273
11289
  }
@@ -11347,12 +11363,13 @@ function IsometricCanvas({
11347
11363
  }
11348
11364
  }
11349
11365
  const sortedFeatures = [...features].sort((a, b) => {
11366
+ if (tileLayout === "hex") return a.y !== b.y ? a.y - b.y : a.x - b.x;
11350
11367
  const depthA = a.x + a.y;
11351
11368
  const depthB = b.x + b.y;
11352
11369
  return depthA !== depthB ? depthA - depthB : a.y - b.y;
11353
11370
  });
11354
11371
  for (const feature of sortedFeatures) {
11355
- const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX);
11372
+ const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX, tileLayout);
11356
11373
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11357
11374
  continue;
11358
11375
  }
@@ -11387,12 +11404,13 @@ function IsometricCanvas({
11387
11404
  }
11388
11405
  const unitsWithPosition = units.filter((u) => !!u.position);
11389
11406
  const sortedUnits = [...unitsWithPosition].sort((a, b) => {
11407
+ if (tileLayout === "hex") return a.position.y !== b.position.y ? a.position.y - b.position.y : a.position.x - b.position.x;
11390
11408
  const depthA = a.position.x + a.position.y;
11391
11409
  const depthB = b.position.x + b.position.y;
11392
11410
  return depthA !== depthB ? depthA - depthB : a.position.y - b.position.y;
11393
11411
  });
11394
11412
  for (const unit of sortedUnits) {
11395
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11413
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11396
11414
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11397
11415
  continue;
11398
11416
  }
@@ -11412,7 +11430,7 @@ function IsometricCanvas({
11412
11430
  drawH = maxUnitW / ar;
11413
11431
  }
11414
11432
  if (unit.previousPosition && (unit.previousPosition.x !== unit.position.x || unit.previousPosition.y !== unit.position.y)) {
11415
- const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX);
11433
+ const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX, tileLayout);
11416
11434
  const ghostCenterX = ghostPos.x + scaledTileWidth / 2;
11417
11435
  const ghostGroundY = ghostPos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
11418
11436
  ctx.save();
@@ -11541,6 +11559,7 @@ function IsometricCanvas({
11541
11559
  units,
11542
11560
  features,
11543
11561
  selectedUnitId,
11562
+ tileLayout,
11544
11563
  scale,
11545
11564
  debug2,
11546
11565
  resolveTerrainSpriteUrl,
@@ -11569,14 +11588,14 @@ function IsometricCanvas({
11569
11588
  if (!selectedUnitId) return;
11570
11589
  const unit = units.find((u) => u.id === selectedUnitId);
11571
11590
  if (!unit?.position) return;
11572
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11591
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11573
11592
  const centerX = pos.x + scaledTileWidth / 2;
11574
11593
  const centerY = pos.y + scaledDiamondTopY + scaledFloorHeight / 2;
11575
11594
  targetCameraRef.current = {
11576
11595
  x: centerX - viewportSize.width / 2,
11577
11596
  y: centerY - viewportSize.height / 2
11578
11597
  };
11579
- }, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11598
+ }, [selectedUnitId, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11580
11599
  useEffect(() => {
11581
11600
  const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
11582
11601
  draw(animTimeRef.current);
@@ -11609,13 +11628,13 @@ function IsometricCanvas({
11609
11628
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11610
11629
  const adjustedX = world.x - scaledTileWidth / 2;
11611
11630
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11612
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11631
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11613
11632
  const tileExists = tilesProp.some((t2) => t2.x === isoPos.x && t2.y === isoPos.y);
11614
11633
  if (tileExists) {
11615
11634
  if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
11616
11635
  onTileHover?.(isoPos.x, isoPos.y);
11617
11636
  }
11618
- }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11637
+ }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, tileLayout, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11619
11638
  const handleCanvasPointerUp = useCallback((e) => {
11620
11639
  singlePointerActiveRef.current = false;
11621
11640
  if (enableCamera) handlePointerUp();
@@ -11624,7 +11643,7 @@ function IsometricCanvas({
11624
11643
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11625
11644
  const adjustedX = world.x - scaledTileWidth / 2;
11626
11645
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11627
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11646
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11628
11647
  const clickedUnit = units.find((u) => u.position?.x === isoPos.x && u.position?.y === isoPos.y);
11629
11648
  if (clickedUnit && (onUnitClick || unitClickEvent)) {
11630
11649
  if (unitClickEvent) eventBus.emit(`UI:${unitClickEvent}`, { unitId: clickedUnit.id });
@@ -11636,7 +11655,7 @@ function IsometricCanvas({
11636
11655
  onTileClick?.(isoPos.x, isoPos.y);
11637
11656
  }
11638
11657
  }
11639
- }, [enableCamera, handlePointerUp, dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
11658
+ }, [enableCamera, handlePointerUp, dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, tileLayout, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
11640
11659
  const handleCanvasPointerLeave = useCallback(() => {
11641
11660
  handleMouseLeave();
11642
11661
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
@@ -42912,7 +42931,8 @@ function ModelLoader({
42912
42931
  const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
42913
42932
  const model = useMemo(() => {
42914
42933
  if (!loadedModel) return null;
42915
- const cloned = loadedModel.clone();
42934
+ const cloned = clone(loadedModel);
42935
+ cloned.updateMatrixWorld(true);
42916
42936
  cloned.traverse((child) => {
42917
42937
  if (child instanceof THREE3.Mesh) {
42918
42938
  child.castShadow = castShadow;
@@ -42927,7 +42947,8 @@ function ModelLoader({
42927
42947
  const size = new THREE3.Vector3();
42928
42948
  box.getSize(size);
42929
42949
  const maxDim = Math.max(size.x, size.y, size.z);
42930
- return maxDim > 0 && Number.isFinite(maxDim) ? 1 / maxDim : 1;
42950
+ if (!Number.isFinite(maxDim) || maxDim < 0.05) return 1;
42951
+ return 1 / maxDim;
42931
42952
  }, [model]);
42932
42953
  const scaleArray = useMemo(() => {
42933
42954
  const base = typeof scale === "number" ? [scale, scale, scale] : scale;
@@ -44209,6 +44230,48 @@ var init_HeroOrganism = __esm({
44209
44230
  _HeroClickInterceptor.displayName = "_HeroClickInterceptor";
44210
44231
  }
44211
44232
  });
44233
+ function HexStrategyBoard({
44234
+ tiles,
44235
+ units,
44236
+ features,
44237
+ assetManifest,
44238
+ assetBaseUrl,
44239
+ scale = 0.45,
44240
+ showMinimap = true,
44241
+ enableCamera = true,
44242
+ tileClickEvent,
44243
+ unitClickEvent,
44244
+ isLoading,
44245
+ error,
44246
+ className
44247
+ }) {
44248
+ return /* @__PURE__ */ jsx("div", { className: cn("hex-strategy-board relative w-full h-full", className), children: /* @__PURE__ */ jsx(
44249
+ IsometricCanvas_default,
44250
+ {
44251
+ tileLayout: "hex",
44252
+ tiles,
44253
+ units,
44254
+ features,
44255
+ assetManifest,
44256
+ assetBaseUrl,
44257
+ scale,
44258
+ showMinimap,
44259
+ enableCamera,
44260
+ tileClickEvent,
44261
+ unitClickEvent,
44262
+ isLoading,
44263
+ error
44264
+ }
44265
+ ) });
44266
+ }
44267
+ var init_HexStrategyBoard = __esm({
44268
+ "components/game/organisms/HexStrategyBoard.tsx"() {
44269
+ "use client";
44270
+ init_cn();
44271
+ init_IsometricCanvas();
44272
+ HexStrategyBoard.displayName = "HexStrategyBoard";
44273
+ }
44274
+ });
44212
44275
  var LandingPageTemplate;
44213
44276
  var init_LandingPageTemplate = __esm({
44214
44277
  "components/marketing/templates/LandingPageTemplate.tsx"() {
@@ -52037,6 +52100,7 @@ var init_component_registry_generated = __esm({
52037
52100
  init_HealthPanel();
52038
52101
  init_HeroOrganism();
52039
52102
  init_HeroSection();
52103
+ init_HexStrategyBoard();
52040
52104
  init_Icon();
52041
52105
  init_InfiniteScrollSentinel();
52042
52106
  init_Input();
@@ -52373,6 +52437,7 @@ var init_component_registry_generated = __esm({
52373
52437
  "HealthPanel": HealthPanel,
52374
52438
  "HeroOrganism": HeroOrganism,
52375
52439
  "HeroSection": HeroSection,
52440
+ "HexStrategyBoard": HexStrategyBoard,
52376
52441
  "Icon": Icon,
52377
52442
  "InfiniteScrollSentinel": InfiniteScrollSentinel,
52378
52443
  "Input": Input,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.63.2",
3
+ "version": "5.65.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [