@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.
@@ -11286,16 +11286,26 @@ var init_useUnitSpriteAtlas = __esm({
11286
11286
  });
11287
11287
 
11288
11288
  // components/game/organisms/utils/isometric.ts
11289
- function isoToScreen(tileX, tileY, scale, baseOffsetX) {
11289
+ function isoToScreen(tileX, tileY, scale, baseOffsetX, layout = "isometric") {
11290
11290
  const scaledTileWidth = TILE_WIDTH * scale;
11291
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
+ }
11292
11297
  const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
11293
11298
  const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
11294
11299
  return { x: screenX, y: screenY };
11295
11300
  }
11296
- function screenToIso(screenX, screenY, scale, baseOffsetX) {
11301
+ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric") {
11297
11302
  const scaledTileWidth = TILE_WIDTH * scale;
11298
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
+ }
11299
11309
  const adjustedX = screenX - baseOffsetX;
11300
11310
  const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
11301
11311
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
@@ -11345,6 +11355,7 @@ function IsometricCanvas({
11345
11355
  tileHoverEvent,
11346
11356
  tileLeaveEvent,
11347
11357
  // Rendering options
11358
+ tileLayout = "isometric",
11348
11359
  scale = 0.4,
11349
11360
  debug: debug2 = false,
11350
11361
  backgroundImage = "",
@@ -11414,13 +11425,17 @@ function IsometricCanvas({
11414
11425
  );
11415
11426
  const sortedTiles = React85.useMemo(() => {
11416
11427
  const tiles = [...tilesProp];
11417
- tiles.sort((a, b) => {
11418
- const depthA = a.x + a.y;
11419
- const depthB = b.x + b.y;
11420
- return depthA !== depthB ? depthA - depthB : a.y - b.y;
11421
- });
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
+ }
11422
11437
  return tiles;
11423
- }, [tilesProp]);
11438
+ }, [tilesProp, tileLayout]);
11424
11439
  const gridWidth = React85.useMemo(() => {
11425
11440
  if (sortedTiles.length === 0) return 0;
11426
11441
  return Math.max(...sortedTiles.map((t2) => t2.x)) + 1;
@@ -11534,7 +11549,7 @@ function IsometricCanvas({
11534
11549
  miniCanvas.width = mW;
11535
11550
  miniCanvas.height = mH;
11536
11551
  mCtx.clearRect(0, 0, mW, mH);
11537
- 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));
11538
11553
  const minX = Math.min(...allScreenPos.map((p2) => p2.x));
11539
11554
  const maxX = Math.max(...allScreenPos.map((p2) => p2.x + scaledTileWidth));
11540
11555
  const minY = Math.min(...allScreenPos.map((p2) => p2.y));
@@ -11545,7 +11560,7 @@ function IsometricCanvas({
11545
11560
  const offsetMx = (mW - worldW * scaleM) / 2;
11546
11561
  const offsetMy = (mH - worldH * scaleM) / 2;
11547
11562
  for (const tile of sortedTiles) {
11548
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11563
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11549
11564
  const mx = (pos.x - minX) * scaleM + offsetMx;
11550
11565
  const my = (pos.y - minY) * scaleM + offsetMy;
11551
11566
  const mTileW = scaledTileWidth * scaleM;
@@ -11561,7 +11576,7 @@ function IsometricCanvas({
11561
11576
  }
11562
11577
  for (const unit of units) {
11563
11578
  if (!unit.position) continue;
11564
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11579
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11565
11580
  const mx = (pos.x + scaledTileWidth / 2 - minX) * scaleM + offsetMx;
11566
11581
  const my = (pos.y + scaledTileHeight / 2 - minY) * scaleM + offsetMy;
11567
11582
  mCtx.fillStyle = unit.team === "player" ? "#60a5fa" : unit.team === "enemy" ? "#f87171" : "#9ca3af";
@@ -11577,7 +11592,7 @@ function IsometricCanvas({
11577
11592
  mCtx.strokeStyle = "rgba(255, 255, 255, 0.8)";
11578
11593
  mCtx.lineWidth = 1;
11579
11594
  mCtx.strokeRect(vLeft, vTop, vW, vH);
11580
- }, [showMinimap, sortedTiles, units, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11595
+ }, [showMinimap, sortedTiles, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11581
11596
  const draw = React85.useCallback((animTime) => {
11582
11597
  const canvas = canvasRef.current;
11583
11598
  if (!canvas) return;
@@ -11617,7 +11632,7 @@ function IsometricCanvas({
11617
11632
  const visTop = cam.y - viewportSize.height / cam.zoom;
11618
11633
  const visBottom = cam.y + viewportSize.height * 2 / cam.zoom;
11619
11634
  for (const tile of sortedTiles) {
11620
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11635
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11621
11636
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11622
11637
  continue;
11623
11638
  }
@@ -11697,12 +11712,13 @@ function IsometricCanvas({
11697
11712
  }
11698
11713
  }
11699
11714
  const sortedFeatures = [...features].sort((a, b) => {
11715
+ if (tileLayout === "hex") return a.y !== b.y ? a.y - b.y : a.x - b.x;
11700
11716
  const depthA = a.x + a.y;
11701
11717
  const depthB = b.x + b.y;
11702
11718
  return depthA !== depthB ? depthA - depthB : a.y - b.y;
11703
11719
  });
11704
11720
  for (const feature of sortedFeatures) {
11705
- const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX);
11721
+ const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX, tileLayout);
11706
11722
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11707
11723
  continue;
11708
11724
  }
@@ -11737,12 +11753,13 @@ function IsometricCanvas({
11737
11753
  }
11738
11754
  const unitsWithPosition = units.filter((u) => !!u.position);
11739
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;
11740
11757
  const depthA = a.position.x + a.position.y;
11741
11758
  const depthB = b.position.x + b.position.y;
11742
11759
  return depthA !== depthB ? depthA - depthB : a.position.y - b.position.y;
11743
11760
  });
11744
11761
  for (const unit of sortedUnits) {
11745
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11762
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11746
11763
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11747
11764
  continue;
11748
11765
  }
@@ -11762,7 +11779,7 @@ function IsometricCanvas({
11762
11779
  drawH = maxUnitW / ar;
11763
11780
  }
11764
11781
  if (unit.previousPosition && (unit.previousPosition.x !== unit.position.x || unit.previousPosition.y !== unit.position.y)) {
11765
- const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX);
11782
+ const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX, tileLayout);
11766
11783
  const ghostCenterX = ghostPos.x + scaledTileWidth / 2;
11767
11784
  const ghostGroundY = ghostPos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
11768
11785
  ctx.save();
@@ -11891,6 +11908,7 @@ function IsometricCanvas({
11891
11908
  units,
11892
11909
  features,
11893
11910
  selectedUnitId,
11911
+ tileLayout,
11894
11912
  scale,
11895
11913
  debug2,
11896
11914
  resolveTerrainSpriteUrl,
@@ -11919,14 +11937,14 @@ function IsometricCanvas({
11919
11937
  if (!selectedUnitId) return;
11920
11938
  const unit = units.find((u) => u.id === selectedUnitId);
11921
11939
  if (!unit?.position) return;
11922
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11940
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11923
11941
  const centerX = pos.x + scaledTileWidth / 2;
11924
11942
  const centerY = pos.y + scaledDiamondTopY + scaledFloorHeight / 2;
11925
11943
  targetCameraRef.current = {
11926
11944
  x: centerX - viewportSize.width / 2,
11927
11945
  y: centerY - viewportSize.height / 2
11928
11946
  };
11929
- }, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11947
+ }, [selectedUnitId, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11930
11948
  React85.useEffect(() => {
11931
11949
  const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
11932
11950
  draw(animTimeRef.current);
@@ -11959,13 +11977,13 @@ function IsometricCanvas({
11959
11977
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11960
11978
  const adjustedX = world.x - scaledTileWidth / 2;
11961
11979
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11962
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11980
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11963
11981
  const tileExists = tilesProp.some((t2) => t2.x === isoPos.x && t2.y === isoPos.y);
11964
11982
  if (tileExists) {
11965
11983
  if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
11966
11984
  onTileHover?.(isoPos.x, isoPos.y);
11967
11985
  }
11968
- }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11986
+ }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, tileLayout, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11969
11987
  const handleCanvasPointerUp = React85.useCallback((e) => {
11970
11988
  singlePointerActiveRef.current = false;
11971
11989
  if (enableCamera) handlePointerUp();
@@ -11974,7 +11992,7 @@ function IsometricCanvas({
11974
11992
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11975
11993
  const adjustedX = world.x - scaledTileWidth / 2;
11976
11994
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11977
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11995
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11978
11996
  const clickedUnit = units.find((u) => u.position?.x === isoPos.x && u.position?.y === isoPos.y);
11979
11997
  if (clickedUnit && (onUnitClick || unitClickEvent)) {
11980
11998
  if (unitClickEvent) eventBus.emit(`UI:${unitClickEvent}`, { unitId: clickedUnit.id });
@@ -11986,7 +12004,7 @@ function IsometricCanvas({
11986
12004
  onTileClick?.(isoPos.x, isoPos.y);
11987
12005
  }
11988
12006
  }
11989
- }, [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]);
11990
12008
  const handleCanvasPointerLeave = React85.useCallback(() => {
11991
12009
  handleMouseLeave();
11992
12010
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
@@ -44561,6 +44579,48 @@ var init_HeroOrganism = __esm({
44561
44579
  _HeroClickInterceptor.displayName = "_HeroClickInterceptor";
44562
44580
  }
44563
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
+ });
44564
44624
  var LandingPageTemplate;
44565
44625
  var init_LandingPageTemplate = __esm({
44566
44626
  "components/marketing/templates/LandingPageTemplate.tsx"() {
@@ -52370,6 +52430,7 @@ var init_component_registry_generated = __esm({
52370
52430
  init_HealthPanel();
52371
52431
  init_HeroOrganism();
52372
52432
  init_HeroSection();
52433
+ init_HexStrategyBoard();
52373
52434
  init_Icon();
52374
52435
  init_InfiniteScrollSentinel();
52375
52436
  init_Input();
@@ -52706,6 +52767,7 @@ var init_component_registry_generated = __esm({
52706
52767
  "HealthPanel": HealthPanel,
52707
52768
  "HeroOrganism": HeroOrganism,
52708
52769
  "HeroSection": HeroSection,
52770
+ "HexStrategyBoard": HexStrategyBoard,
52709
52771
  "Icon": Icon,
52710
52772
  "InfiniteScrollSentinel": InfiniteScrollSentinel,
52711
52773
  "Input": Input,
@@ -11239,16 +11239,26 @@ var init_useUnitSpriteAtlas = __esm({
11239
11239
  });
11240
11240
 
11241
11241
  // components/game/organisms/utils/isometric.ts
11242
- function isoToScreen(tileX, tileY, scale, baseOffsetX) {
11242
+ function isoToScreen(tileX, tileY, scale, baseOffsetX, layout = "isometric") {
11243
11243
  const scaledTileWidth = TILE_WIDTH * scale;
11244
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
+ }
11245
11250
  const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
11246
11251
  const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
11247
11252
  return { x: screenX, y: screenY };
11248
11253
  }
11249
- function screenToIso(screenX, screenY, scale, baseOffsetX) {
11254
+ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric") {
11250
11255
  const scaledTileWidth = TILE_WIDTH * scale;
11251
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
+ }
11252
11262
  const adjustedX = screenX - baseOffsetX;
11253
11263
  const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
11254
11264
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
@@ -11298,6 +11308,7 @@ function IsometricCanvas({
11298
11308
  tileHoverEvent,
11299
11309
  tileLeaveEvent,
11300
11310
  // Rendering options
11311
+ tileLayout = "isometric",
11301
11312
  scale = 0.4,
11302
11313
  debug: debug2 = false,
11303
11314
  backgroundImage = "",
@@ -11367,13 +11378,17 @@ function IsometricCanvas({
11367
11378
  );
11368
11379
  const sortedTiles = useMemo(() => {
11369
11380
  const tiles = [...tilesProp];
11370
- tiles.sort((a, b) => {
11371
- const depthA = a.x + a.y;
11372
- const depthB = b.x + b.y;
11373
- return depthA !== depthB ? depthA - depthB : a.y - b.y;
11374
- });
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
+ }
11375
11390
  return tiles;
11376
- }, [tilesProp]);
11391
+ }, [tilesProp, tileLayout]);
11377
11392
  const gridWidth = useMemo(() => {
11378
11393
  if (sortedTiles.length === 0) return 0;
11379
11394
  return Math.max(...sortedTiles.map((t2) => t2.x)) + 1;
@@ -11487,7 +11502,7 @@ function IsometricCanvas({
11487
11502
  miniCanvas.width = mW;
11488
11503
  miniCanvas.height = mH;
11489
11504
  mCtx.clearRect(0, 0, mW, mH);
11490
- 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));
11491
11506
  const minX = Math.min(...allScreenPos.map((p2) => p2.x));
11492
11507
  const maxX = Math.max(...allScreenPos.map((p2) => p2.x + scaledTileWidth));
11493
11508
  const minY = Math.min(...allScreenPos.map((p2) => p2.y));
@@ -11498,7 +11513,7 @@ function IsometricCanvas({
11498
11513
  const offsetMx = (mW - worldW * scaleM) / 2;
11499
11514
  const offsetMy = (mH - worldH * scaleM) / 2;
11500
11515
  for (const tile of sortedTiles) {
11501
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11516
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11502
11517
  const mx = (pos.x - minX) * scaleM + offsetMx;
11503
11518
  const my = (pos.y - minY) * scaleM + offsetMy;
11504
11519
  const mTileW = scaledTileWidth * scaleM;
@@ -11514,7 +11529,7 @@ function IsometricCanvas({
11514
11529
  }
11515
11530
  for (const unit of units) {
11516
11531
  if (!unit.position) continue;
11517
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11532
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11518
11533
  const mx = (pos.x + scaledTileWidth / 2 - minX) * scaleM + offsetMx;
11519
11534
  const my = (pos.y + scaledTileHeight / 2 - minY) * scaleM + offsetMy;
11520
11535
  mCtx.fillStyle = unit.team === "player" ? "#60a5fa" : unit.team === "enemy" ? "#f87171" : "#9ca3af";
@@ -11530,7 +11545,7 @@ function IsometricCanvas({
11530
11545
  mCtx.strokeStyle = "rgba(255, 255, 255, 0.8)";
11531
11546
  mCtx.lineWidth = 1;
11532
11547
  mCtx.strokeRect(vLeft, vTop, vW, vH);
11533
- }, [showMinimap, sortedTiles, units, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11548
+ }, [showMinimap, sortedTiles, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
11534
11549
  const draw = useCallback((animTime) => {
11535
11550
  const canvas = canvasRef.current;
11536
11551
  if (!canvas) return;
@@ -11570,7 +11585,7 @@ function IsometricCanvas({
11570
11585
  const visTop = cam.y - viewportSize.height / cam.zoom;
11571
11586
  const visBottom = cam.y + viewportSize.height * 2 / cam.zoom;
11572
11587
  for (const tile of sortedTiles) {
11573
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
11588
+ const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX, tileLayout);
11574
11589
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11575
11590
  continue;
11576
11591
  }
@@ -11650,12 +11665,13 @@ function IsometricCanvas({
11650
11665
  }
11651
11666
  }
11652
11667
  const sortedFeatures = [...features].sort((a, b) => {
11668
+ if (tileLayout === "hex") return a.y !== b.y ? a.y - b.y : a.x - b.x;
11653
11669
  const depthA = a.x + a.y;
11654
11670
  const depthB = b.x + b.y;
11655
11671
  return depthA !== depthB ? depthA - depthB : a.y - b.y;
11656
11672
  });
11657
11673
  for (const feature of sortedFeatures) {
11658
- const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX);
11674
+ const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX, tileLayout);
11659
11675
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11660
11676
  continue;
11661
11677
  }
@@ -11690,12 +11706,13 @@ function IsometricCanvas({
11690
11706
  }
11691
11707
  const unitsWithPosition = units.filter((u) => !!u.position);
11692
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;
11693
11710
  const depthA = a.position.x + a.position.y;
11694
11711
  const depthB = b.position.x + b.position.y;
11695
11712
  return depthA !== depthB ? depthA - depthB : a.position.y - b.position.y;
11696
11713
  });
11697
11714
  for (const unit of sortedUnits) {
11698
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11715
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11699
11716
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
11700
11717
  continue;
11701
11718
  }
@@ -11715,7 +11732,7 @@ function IsometricCanvas({
11715
11732
  drawH = maxUnitW / ar;
11716
11733
  }
11717
11734
  if (unit.previousPosition && (unit.previousPosition.x !== unit.position.x || unit.previousPosition.y !== unit.position.y)) {
11718
- const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX);
11735
+ const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX, tileLayout);
11719
11736
  const ghostCenterX = ghostPos.x + scaledTileWidth / 2;
11720
11737
  const ghostGroundY = ghostPos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
11721
11738
  ctx.save();
@@ -11844,6 +11861,7 @@ function IsometricCanvas({
11844
11861
  units,
11845
11862
  features,
11846
11863
  selectedUnitId,
11864
+ tileLayout,
11847
11865
  scale,
11848
11866
  debug2,
11849
11867
  resolveTerrainSpriteUrl,
@@ -11872,14 +11890,14 @@ function IsometricCanvas({
11872
11890
  if (!selectedUnitId) return;
11873
11891
  const unit = units.find((u) => u.id === selectedUnitId);
11874
11892
  if (!unit?.position) return;
11875
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
11893
+ const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX, tileLayout);
11876
11894
  const centerX = pos.x + scaledTileWidth / 2;
11877
11895
  const centerY = pos.y + scaledDiamondTopY + scaledFloorHeight / 2;
11878
11896
  targetCameraRef.current = {
11879
11897
  x: centerX - viewportSize.width / 2,
11880
11898
  y: centerY - viewportSize.height / 2
11881
11899
  };
11882
- }, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11900
+ }, [selectedUnitId, units, tileLayout, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
11883
11901
  useEffect(() => {
11884
11902
  const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
11885
11903
  draw(animTimeRef.current);
@@ -11912,13 +11930,13 @@ function IsometricCanvas({
11912
11930
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11913
11931
  const adjustedX = world.x - scaledTileWidth / 2;
11914
11932
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11915
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11933
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11916
11934
  const tileExists = tilesProp.some((t2) => t2.x === isoPos.x && t2.y === isoPos.y);
11917
11935
  if (tileExists) {
11918
11936
  if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
11919
11937
  onTileHover?.(isoPos.x, isoPos.y);
11920
11938
  }
11921
- }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11939
+ }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, tileLayout, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11922
11940
  const handleCanvasPointerUp = useCallback((e) => {
11923
11941
  singlePointerActiveRef.current = false;
11924
11942
  if (enableCamera) handlePointerUp();
@@ -11927,7 +11945,7 @@ function IsometricCanvas({
11927
11945
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11928
11946
  const adjustedX = world.x - scaledTileWidth / 2;
11929
11947
  const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
11930
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
11948
+ const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX, tileLayout);
11931
11949
  const clickedUnit = units.find((u) => u.position?.x === isoPos.x && u.position?.y === isoPos.y);
11932
11950
  if (clickedUnit && (onUnitClick || unitClickEvent)) {
11933
11951
  if (unitClickEvent) eventBus.emit(`UI:${unitClickEvent}`, { unitId: clickedUnit.id });
@@ -11939,7 +11957,7 @@ function IsometricCanvas({
11939
11957
  onTileClick?.(isoPos.x, isoPos.y);
11940
11958
  }
11941
11959
  }
11942
- }, [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]);
11943
11961
  const handleCanvasPointerLeave = useCallback(() => {
11944
11962
  handleMouseLeave();
11945
11963
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
@@ -44514,6 +44532,48 @@ var init_HeroOrganism = __esm({
44514
44532
  _HeroClickInterceptor.displayName = "_HeroClickInterceptor";
44515
44533
  }
44516
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
+ });
44517
44577
  var LandingPageTemplate;
44518
44578
  var init_LandingPageTemplate = __esm({
44519
44579
  "components/marketing/templates/LandingPageTemplate.tsx"() {
@@ -52323,6 +52383,7 @@ var init_component_registry_generated = __esm({
52323
52383
  init_HealthPanel();
52324
52384
  init_HeroOrganism();
52325
52385
  init_HeroSection();
52386
+ init_HexStrategyBoard();
52326
52387
  init_Icon();
52327
52388
  init_InfiniteScrollSentinel();
52328
52389
  init_Input();
@@ -52659,6 +52720,7 @@ var init_component_registry_generated = __esm({
52659
52720
  "HealthPanel": HealthPanel,
52660
52721
  "HeroOrganism": HeroOrganism,
52661
52722
  "HeroSection": HeroSection,
52723
+ "HexStrategyBoard": HexStrategyBoard,
52662
52724
  "Icon": Icon,
52663
52725
  "InfiniteScrollSentinel": InfiniteScrollSentinel,
52664
52726
  "Input": Input,