@almadar/ui 5.64.0 → 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.
@@ -10984,16 +10984,26 @@ var init_useUnitSpriteAtlas = __esm({
10984
10984
  });
10985
10985
 
10986
10986
  // components/game/organisms/utils/isometric.ts
10987
- function isoToScreen(tileX, tileY, scale, baseOffsetX) {
10987
+ function isoToScreen(tileX, tileY, scale, baseOffsetX, layout = "isometric") {
10988
10988
  const scaledTileWidth = TILE_WIDTH * scale;
10989
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
+ }
10990
10995
  const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
10991
10996
  const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
10992
10997
  return { x: screenX, y: screenY };
10993
10998
  }
10994
- function screenToIso(screenX, screenY, scale, baseOffsetX) {
10999
+ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric") {
10995
11000
  const scaledTileWidth = TILE_WIDTH * scale;
10996
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
+ }
10997
11007
  const adjustedX = screenX - baseOffsetX;
10998
11008
  const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
10999
11009
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
@@ -11043,6 +11053,7 @@ function IsometricCanvas({
11043
11053
  tileHoverEvent,
11044
11054
  tileLeaveEvent,
11045
11055
  // Rendering options
11056
+ tileLayout = "isometric",
11046
11057
  scale = 0.4,
11047
11058
  debug: debug2 = false,
11048
11059
  backgroundImage = "",
@@ -11112,13 +11123,17 @@ function IsometricCanvas({
11112
11123
  );
11113
11124
  const sortedTiles = React84.useMemo(() => {
11114
11125
  const tiles = [...tilesProp];
11115
- tiles.sort((a, b) => {
11116
- const depthA = a.x + a.y;
11117
- const depthB = b.x + b.y;
11118
- return depthA !== depthB ? depthA - depthB : a.y - b.y;
11119
- });
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
+ }
11120
11135
  return tiles;
11121
- }, [tilesProp]);
11136
+ }, [tilesProp, tileLayout]);
11122
11137
  const gridWidth = React84.useMemo(() => {
11123
11138
  if (sortedTiles.length === 0) return 0;
11124
11139
  return Math.max(...sortedTiles.map((t2) => t2.x)) + 1;
@@ -11232,7 +11247,7 @@ function IsometricCanvas({
11232
11247
  miniCanvas.width = mW;
11233
11248
  miniCanvas.height = mH;
11234
11249
  mCtx.clearRect(0, 0, mW, mH);
11235
- 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));
11236
11251
  const minX = Math.min(...allScreenPos.map((p2) => p2.x));
11237
11252
  const maxX = Math.max(...allScreenPos.map((p2) => p2.x + scaledTileWidth));
11238
11253
  const minY = Math.min(...allScreenPos.map((p2) => p2.y));
@@ -11243,7 +11258,7 @@ function IsometricCanvas({
11243
11258
  const offsetMx = (mW - worldW * scaleM) / 2;
11244
11259
  const offsetMy = (mH - worldH * scaleM) / 2;
11245
11260
  for (const tile of sortedTiles) {
11246
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11261
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11247
11262
  const mx = (pos.x - minX) * scaleM + offsetMx;
11248
11263
  const my = (pos.y - minY) * scaleM + offsetMy;
11249
11264
  const mTileW = scaledTileWidth * scaleM;
@@ -11259,7 +11274,7 @@ function IsometricCanvas({
11259
11274
  }
11260
11275
  for (const unit of units) {
11261
11276
  if (!unit.position) continue;
11262
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11277
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11263
11278
  const mx = (pos.x + scaledTileWidth / 2 - minX) * scaleM + offsetMx;
11264
11279
  const my = (pos.y + scaledTileHeight / 2 - minY) * scaleM + offsetMy;
11265
11280
  mCtx.fillStyle = unit.team === "player" ? "#60a5fa" : unit.team === "enemy" ? "#f87171" : "#9ca3af";
@@ -11275,7 +11290,7 @@ function IsometricCanvas({
11275
11290
  mCtx.strokeStyle = "rgba(255, 255, 255, 0.8)";
11276
11291
  mCtx.lineWidth = 1;
11277
11292
  mCtx.strokeRect(vLeft, vTop, vW, vH);
11278
- }, [showMinimap, sortedTiles, units, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11293
+ }, [showMinimap, sortedTiles, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11279
11294
  const draw = React84.useCallback((animTime) => {
11280
11295
  const canvas = canvasRef.current;
11281
11296
  if (!canvas) return;
@@ -11315,7 +11330,7 @@ function IsometricCanvas({
11315
11330
  const visTop = cam.y - viewportSize.height / cam.zoom;
11316
11331
  const visBottom = cam.y + viewportSize.height * 2 / cam.zoom;
11317
11332
  for (const tile of sortedTiles) {
11318
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11333
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11319
11334
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11320
11335
  continue;
11321
11336
  }
@@ -11395,12 +11410,13 @@ function IsometricCanvas({
11395
11410
  }
11396
11411
  }
11397
11412
  const sortedFeatures = [...features].sort((a, b) => {
11413
+ if (tileLayout === "hex") return a.y !== b.y ? a.y - b.y : a.x - b.x;
11398
11414
  const depthA = a.x + a.y;
11399
11415
  const depthB = b.x + b.y;
11400
11416
  return depthA !== depthB ? depthA - depthB : a.y - b.y;
11401
11417
  });
11402
11418
  for (const feature of sortedFeatures) {
11403
- const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX);
11419
+ const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX, tileLayout);
11404
11420
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11405
11421
  continue;
11406
11422
  }
@@ -11435,12 +11451,13 @@ function IsometricCanvas({
11435
11451
  }
11436
11452
  const unitsWithPosition = units.filter((u) => !!u.position);
11437
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;
11438
11455
  const depthA = a.position.x + a.position.y;
11439
11456
  const depthB = b.position.x + b.position.y;
11440
11457
  return depthA !== depthB ? depthA - depthB : a.position.y - b.position.y;
11441
11458
  });
11442
11459
  for (const unit of sortedUnits) {
11443
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11460
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11444
11461
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11445
11462
  continue;
11446
11463
  }
@@ -11460,7 +11477,7 @@ function IsometricCanvas({
11460
11477
  drawH = maxUnitW / ar;
11461
11478
  }
11462
11479
  if (unit.previousPosition && (unit.previousPosition.x !== unit.position.x || unit.previousPosition.y !== unit.position.y)) {
11463
- const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX);
11480
+ const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX, tileLayout);
11464
11481
  const ghostCenterX = ghostPos.x + scaledTileWidth / 2;
11465
11482
  const ghostGroundY = ghostPos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
11466
11483
  ctx.save();
@@ -11589,6 +11606,7 @@ function IsometricCanvas({
11589
11606
  units,
11590
11607
  features,
11591
11608
  selectedUnitId,
11609
+ tileLayout,
11592
11610
  scale,
11593
11611
  debug2,
11594
11612
  resolveTerrainSpriteUrl,
@@ -11617,14 +11635,14 @@ function IsometricCanvas({
11617
11635
  if (!selectedUnitId) return;
11618
11636
  const unit = units.find((u) => u.id === selectedUnitId);
11619
11637
  if (!unit?.position) return;
11620
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11638
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11621
11639
  const centerX = pos.x + scaledTileWidth / 2;
11622
11640
  const centerY = pos.y + scaledDiamondTopY + scaledFloorHeight / 2;
11623
11641
  targetCameraRef.current = {
11624
11642
  x: centerX - viewportSize.width / 2,
11625
11643
  y: centerY - viewportSize.height / 2
11626
11644
  };
11627
- }, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11645
+ }, [selectedUnitId, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11628
11646
  React84.useEffect(() => {
11629
11647
  const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
11630
11648
  draw(animTimeRef.current);
@@ -11657,13 +11675,13 @@ function IsometricCanvas({
11657
11675
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11658
11676
  const adjustedX = world.x - scaledTileWidth / 2;
11659
11677
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11660
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11678
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11661
11679
  const tileExists = tilesProp.some((t2) => t2.x === isoPos.x && t2.y === isoPos.y);
11662
11680
  if (tileExists) {
11663
11681
  if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
11664
11682
  onTileHover?.(isoPos.x, isoPos.y);
11665
11683
  }
11666
- }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11684
+ }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, tileLayout, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11667
11685
  const handleCanvasPointerUp = React84.useCallback((e) => {
11668
11686
  singlePointerActiveRef.current = false;
11669
11687
  if (enableCamera) handlePointerUp();
@@ -11672,7 +11690,7 @@ function IsometricCanvas({
11672
11690
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11673
11691
  const adjustedX = world.x - scaledTileWidth / 2;
11674
11692
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11675
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11693
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11676
11694
  const clickedUnit = units.find((u) => u.position?.x === isoPos.x && u.position?.y === isoPos.y);
11677
11695
  if (clickedUnit && (onUnitClick || unitClickEvent)) {
11678
11696
  if (unitClickEvent) eventBus.emit(`UI:${unitClickEvent}`, { unitId: clickedUnit.id });
@@ -11684,7 +11702,7 @@ function IsometricCanvas({
11684
11702
  onTileClick?.(isoPos.x, isoPos.y);
11685
11703
  }
11686
11704
  }
11687
- }, [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]);
11688
11706
  const handleCanvasPointerLeave = React84.useCallback(() => {
11689
11707
  handleMouseLeave();
11690
11708
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
@@ -44259,6 +44277,48 @@ var init_HeroOrganism = __esm({
44259
44277
  _HeroClickInterceptor.displayName = "_HeroClickInterceptor";
44260
44278
  }
44261
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
+ });
44262
44322
  var LandingPageTemplate;
44263
44323
  var init_LandingPageTemplate = __esm({
44264
44324
  "components/marketing/templates/LandingPageTemplate.tsx"() {
@@ -52087,6 +52147,7 @@ var init_component_registry_generated = __esm({
52087
52147
  init_HealthPanel();
52088
52148
  init_HeroOrganism();
52089
52149
  init_HeroSection();
52150
+ init_HexStrategyBoard();
52090
52151
  init_Icon();
52091
52152
  init_InfiniteScrollSentinel();
52092
52153
  init_Input();
@@ -52423,6 +52484,7 @@ var init_component_registry_generated = __esm({
52423
52484
  "HealthPanel": HealthPanel,
52424
52485
  "HeroOrganism": HeroOrganism,
52425
52486
  "HeroSection": HeroSection,
52487
+ "HexStrategyBoard": HexStrategyBoard,
52426
52488
  "Icon": Icon,
52427
52489
  "InfiniteScrollSentinel": InfiniteScrollSentinel,
52428
52490
  "Input": Input,
@@ -10937,16 +10937,26 @@ var init_useUnitSpriteAtlas = __esm({
10937
10937
  });
10938
10938
 
10939
10939
  // components/game/organisms/utils/isometric.ts
10940
- function isoToScreen(tileX, tileY, scale, baseOffsetX) {
10940
+ function isoToScreen(tileX, tileY, scale, baseOffsetX, layout = "isometric") {
10941
10941
  const scaledTileWidth = TILE_WIDTH * scale;
10942
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
+ }
10943
10948
  const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
10944
10949
  const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
10945
10950
  return { x: screenX, y: screenY };
10946
10951
  }
10947
- function screenToIso(screenX, screenY, scale, baseOffsetX) {
10952
+ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric") {
10948
10953
  const scaledTileWidth = TILE_WIDTH * scale;
10949
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
+ }
10950
10960
  const adjustedX = screenX - baseOffsetX;
10951
10961
  const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
10952
10962
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
@@ -10996,6 +11006,7 @@ function IsometricCanvas({
10996
11006
  tileHoverEvent,
10997
11007
  tileLeaveEvent,
10998
11008
  // Rendering options
11009
+ tileLayout = "isometric",
10999
11010
  scale = 0.4,
11000
11011
  debug: debug2 = false,
11001
11012
  backgroundImage = "",
@@ -11065,13 +11076,17 @@ function IsometricCanvas({
11065
11076
  );
11066
11077
  const sortedTiles = useMemo(() => {
11067
11078
  const tiles = [...tilesProp];
11068
- tiles.sort((a, b) => {
11069
- const depthA = a.x + a.y;
11070
- const depthB = b.x + b.y;
11071
- return depthA !== depthB ? depthA - depthB : a.y - b.y;
11072
- });
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
+ }
11073
11088
  return tiles;
11074
- }, [tilesProp]);
11089
+ }, [tilesProp, tileLayout]);
11075
11090
  const gridWidth = useMemo(() => {
11076
11091
  if (sortedTiles.length === 0) return 0;
11077
11092
  return Math.max(...sortedTiles.map((t2) => t2.x)) + 1;
@@ -11185,7 +11200,7 @@ function IsometricCanvas({
11185
11200
  miniCanvas.width = mW;
11186
11201
  miniCanvas.height = mH;
11187
11202
  mCtx.clearRect(0, 0, mW, mH);
11188
- 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));
11189
11204
  const minX = Math.min(...allScreenPos.map((p2) => p2.x));
11190
11205
  const maxX = Math.max(...allScreenPos.map((p2) => p2.x + scaledTileWidth));
11191
11206
  const minY = Math.min(...allScreenPos.map((p2) => p2.y));
@@ -11196,7 +11211,7 @@ function IsometricCanvas({
11196
11211
  const offsetMx = (mW - worldW * scaleM) / 2;
11197
11212
  const offsetMy = (mH - worldH * scaleM) / 2;
11198
11213
  for (const tile of sortedTiles) {
11199
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11214
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11200
11215
  const mx = (pos.x - minX) * scaleM + offsetMx;
11201
11216
  const my = (pos.y - minY) * scaleM + offsetMy;
11202
11217
  const mTileW = scaledTileWidth * scaleM;
@@ -11212,7 +11227,7 @@ function IsometricCanvas({
11212
11227
  }
11213
11228
  for (const unit of units) {
11214
11229
  if (!unit.position) continue;
11215
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11230
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11216
11231
  const mx = (pos.x + scaledTileWidth / 2 - minX) * scaleM + offsetMx;
11217
11232
  const my = (pos.y + scaledTileHeight / 2 - minY) * scaleM + offsetMy;
11218
11233
  mCtx.fillStyle = unit.team === "player" ? "#60a5fa" : unit.team === "enemy" ? "#f87171" : "#9ca3af";
@@ -11228,7 +11243,7 @@ function IsometricCanvas({
11228
11243
  mCtx.strokeStyle = "rgba(255, 255, 255, 0.8)";
11229
11244
  mCtx.lineWidth = 1;
11230
11245
  mCtx.strokeRect(vLeft, vTop, vW, vH);
11231
- }, [showMinimap, sortedTiles, units, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11246
+ }, [showMinimap, sortedTiles, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11232
11247
  const draw = useCallback((animTime) => {
11233
11248
  const canvas = canvasRef.current;
11234
11249
  if (!canvas) return;
@@ -11268,7 +11283,7 @@ function IsometricCanvas({
11268
11283
  const visTop = cam.y - viewportSize.height / cam.zoom;
11269
11284
  const visBottom = cam.y + viewportSize.height * 2 / cam.zoom;
11270
11285
  for (const tile of sortedTiles) {
11271
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11286
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11272
11287
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11273
11288
  continue;
11274
11289
  }
@@ -11348,12 +11363,13 @@ function IsometricCanvas({
11348
11363
  }
11349
11364
  }
11350
11365
  const sortedFeatures = [...features].sort((a, b) => {
11366
+ if (tileLayout === "hex") return a.y !== b.y ? a.y - b.y : a.x - b.x;
11351
11367
  const depthA = a.x + a.y;
11352
11368
  const depthB = b.x + b.y;
11353
11369
  return depthA !== depthB ? depthA - depthB : a.y - b.y;
11354
11370
  });
11355
11371
  for (const feature of sortedFeatures) {
11356
- const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX);
11372
+ const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX, tileLayout);
11357
11373
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11358
11374
  continue;
11359
11375
  }
@@ -11388,12 +11404,13 @@ function IsometricCanvas({
11388
11404
  }
11389
11405
  const unitsWithPosition = units.filter((u) => !!u.position);
11390
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;
11391
11408
  const depthA = a.position.x + a.position.y;
11392
11409
  const depthB = b.position.x + b.position.y;
11393
11410
  return depthA !== depthB ? depthA - depthB : a.position.y - b.position.y;
11394
11411
  });
11395
11412
  for (const unit of sortedUnits) {
11396
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11413
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11397
11414
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11398
11415
  continue;
11399
11416
  }
@@ -11413,7 +11430,7 @@ function IsometricCanvas({
11413
11430
  drawH = maxUnitW / ar;
11414
11431
  }
11415
11432
  if (unit.previousPosition && (unit.previousPosition.x !== unit.position.x || unit.previousPosition.y !== unit.position.y)) {
11416
- const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX);
11433
+ const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX, tileLayout);
11417
11434
  const ghostCenterX = ghostPos.x + scaledTileWidth / 2;
11418
11435
  const ghostGroundY = ghostPos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
11419
11436
  ctx.save();
@@ -11542,6 +11559,7 @@ function IsometricCanvas({
11542
11559
  units,
11543
11560
  features,
11544
11561
  selectedUnitId,
11562
+ tileLayout,
11545
11563
  scale,
11546
11564
  debug2,
11547
11565
  resolveTerrainSpriteUrl,
@@ -11570,14 +11588,14 @@ function IsometricCanvas({
11570
11588
  if (!selectedUnitId) return;
11571
11589
  const unit = units.find((u) => u.id === selectedUnitId);
11572
11590
  if (!unit?.position) return;
11573
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11591
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11574
11592
  const centerX = pos.x + scaledTileWidth / 2;
11575
11593
  const centerY = pos.y + scaledDiamondTopY + scaledFloorHeight / 2;
11576
11594
  targetCameraRef.current = {
11577
11595
  x: centerX - viewportSize.width / 2,
11578
11596
  y: centerY - viewportSize.height / 2
11579
11597
  };
11580
- }, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11598
+ }, [selectedUnitId, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11581
11599
  useEffect(() => {
11582
11600
  const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
11583
11601
  draw(animTimeRef.current);
@@ -11610,13 +11628,13 @@ function IsometricCanvas({
11610
11628
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11611
11629
  const adjustedX = world.x - scaledTileWidth / 2;
11612
11630
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11613
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11631
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11614
11632
  const tileExists = tilesProp.some((t2) => t2.x === isoPos.x && t2.y === isoPos.y);
11615
11633
  if (tileExists) {
11616
11634
  if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
11617
11635
  onTileHover?.(isoPos.x, isoPos.y);
11618
11636
  }
11619
- }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11637
+ }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, tileLayout, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11620
11638
  const handleCanvasPointerUp = useCallback((e) => {
11621
11639
  singlePointerActiveRef.current = false;
11622
11640
  if (enableCamera) handlePointerUp();
@@ -11625,7 +11643,7 @@ function IsometricCanvas({
11625
11643
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11626
11644
  const adjustedX = world.x - scaledTileWidth / 2;
11627
11645
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11628
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11646
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11629
11647
  const clickedUnit = units.find((u) => u.position?.x === isoPos.x && u.position?.y === isoPos.y);
11630
11648
  if (clickedUnit && (onUnitClick || unitClickEvent)) {
11631
11649
  if (unitClickEvent) eventBus.emit(`UI:${unitClickEvent}`, { unitId: clickedUnit.id });
@@ -11637,7 +11655,7 @@ function IsometricCanvas({
11637
11655
  onTileClick?.(isoPos.x, isoPos.y);
11638
11656
  }
11639
11657
  }
11640
- }, [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]);
11641
11659
  const handleCanvasPointerLeave = useCallback(() => {
11642
11660
  handleMouseLeave();
11643
11661
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
@@ -44212,6 +44230,48 @@ var init_HeroOrganism = __esm({
44212
44230
  _HeroClickInterceptor.displayName = "_HeroClickInterceptor";
44213
44231
  }
44214
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
+ });
44215
44275
  var LandingPageTemplate;
44216
44276
  var init_LandingPageTemplate = __esm({
44217
44277
  "components/marketing/templates/LandingPageTemplate.tsx"() {
@@ -52040,6 +52100,7 @@ var init_component_registry_generated = __esm({
52040
52100
  init_HealthPanel();
52041
52101
  init_HeroOrganism();
52042
52102
  init_HeroSection();
52103
+ init_HexStrategyBoard();
52043
52104
  init_Icon();
52044
52105
  init_InfiniteScrollSentinel();
52045
52106
  init_Input();
@@ -52376,6 +52437,7 @@ var init_component_registry_generated = __esm({
52376
52437
  "HealthPanel": HealthPanel,
52377
52438
  "HeroOrganism": HeroOrganism,
52378
52439
  "HeroSection": HeroSection,
52440
+ "HexStrategyBoard": HexStrategyBoard,
52379
52441
  "Icon": Icon,
52380
52442
  "InfiniteScrollSentinel": InfiniteScrollSentinel,
52381
52443
  "Input": Input,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.64.0",
3
+ "version": "5.65.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [