@almadar/ui 5.48.0 → 5.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/avl/index.cjs +2681 -148
  2. package/dist/avl/index.css +504 -0
  3. package/dist/avl/index.js +2681 -149
  4. package/dist/components/core/templates/index.d.ts +3 -0
  5. package/dist/components/game/atoms/ActionButton.d.ts +4 -1
  6. package/dist/components/game/atoms/ControlButton.d.ts +4 -2
  7. package/dist/components/game/atoms/DamageNumber.d.ts +4 -1
  8. package/dist/components/game/atoms/ScoreDisplay.d.ts +4 -1
  9. package/dist/components/game/atoms/TurnIndicator.d.ts +4 -1
  10. package/dist/components/game/molecules/EnemyPlate.d.ts +4 -1
  11. package/dist/components/game/molecules/StatBadge.d.ts +4 -1
  12. package/dist/components/game/molecules/three/index.cjs +1371 -283
  13. package/dist/components/game/molecules/three/index.js +1372 -284
  14. package/dist/components/game/organisms/GameBoard3D.d.ts +62 -0
  15. package/dist/components/game/organisms/PlatformerBoard.d.ts +51 -0
  16. package/dist/components/game/organisms/RoguelikeBoard.d.ts +59 -0
  17. package/dist/components/game/organisms/TowerDefenseBoard.d.ts +64 -0
  18. package/dist/components/game/organisms/index.d.ts +4 -0
  19. package/dist/components/game/templates/GameCanvas3DBattleTemplate.d.ts +22 -35
  20. package/dist/components/game/templates/GameCanvas3DCastleTemplate.d.ts +23 -23
  21. package/dist/components/game/templates/GameCanvas3DWorldMapTemplate.d.ts +27 -29
  22. package/dist/components/game/templates/PlatformerTemplate.d.ts +26 -0
  23. package/dist/components/game/templates/RoguelikeTemplate.d.ts +23 -0
  24. package/dist/components/game/templates/TowerDefenseTemplate.d.ts +43 -0
  25. package/dist/components/index.cjs +2674 -127
  26. package/dist/components/index.css +504 -0
  27. package/dist/components/index.js +2668 -129
  28. package/dist/docs/index.css +504 -0
  29. package/dist/providers/index.cjs +2664 -131
  30. package/dist/providers/index.css +504 -0
  31. package/dist/providers/index.js +2664 -132
  32. package/dist/runtime/index.cjs +2665 -132
  33. package/dist/runtime/index.css +504 -0
  34. package/dist/runtime/index.js +2665 -133
  35. package/package.json +1 -1
@@ -39,6 +39,12 @@ var core = require('@almadar/core');
39
39
  var core$1 = require('@dnd-kit/core');
40
40
  var sortable = require('@dnd-kit/sortable');
41
41
  var utilities = require('@dnd-kit/utilities');
42
+ var THREE = require('three');
43
+ var GLTFLoader_js = require('three/examples/jsm/loaders/GLTFLoader.js');
44
+ var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
45
+ var GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader');
46
+ var fiber = require('@react-three/fiber');
47
+ var drei = require('@react-three/drei');
42
48
  var patterns = require('@almadar/patterns');
43
49
  var OrbitalServerRuntime = require('@almadar/runtime/OrbitalServerRuntime');
44
50
  var runtime = require('@almadar/runtime');
@@ -88,6 +94,7 @@ var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown);
88
94
  var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
89
95
  var remarkMath__default = /*#__PURE__*/_interopDefault(remarkMath);
90
96
  var rehypeKatex__default = /*#__PURE__*/_interopDefault(rehypeKatex);
97
+ var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
91
98
 
92
99
  var __defProp = Object.defineProperty;
93
100
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -3219,8 +3226,8 @@ var init_Typography = __esm({
3219
3226
  children
3220
3227
  }) => {
3221
3228
  const variant = variantProp ?? (level ? `h${level}` : "body1");
3222
- const Component = as || defaultElements[variant];
3223
- const Comp = Component;
3229
+ const Component2 = as || defaultElements[variant];
3230
+ const Comp = Component2;
3224
3231
  return /* @__PURE__ */ jsxRuntime.jsx(
3225
3232
  Comp,
3226
3233
  {
@@ -3529,7 +3536,7 @@ var init_Box = __esm({
3529
3536
  position,
3530
3537
  className,
3531
3538
  children,
3532
- as: Component = "div",
3539
+ as: Component2 = "div",
3533
3540
  action,
3534
3541
  actionPayload,
3535
3542
  hoverEvent,
@@ -3561,7 +3568,7 @@ var init_Box = __esm({
3561
3568
  }, [hoverEvent, eventBus, onMouseLeave]);
3562
3569
  const isClickable = action || onClick;
3563
3570
  return React91__namespace.default.createElement(
3564
- Component,
3571
+ Component2,
3565
3572
  {
3566
3573
  ref,
3567
3574
  className: cn(
@@ -3637,7 +3644,7 @@ var init_Stack = __esm({
3637
3644
  className,
3638
3645
  style,
3639
3646
  children,
3640
- as: Component = "div",
3647
+ as: Component2 = "div",
3641
3648
  onClick,
3642
3649
  onKeyDown,
3643
3650
  role,
@@ -3655,7 +3662,7 @@ var init_Stack = __esm({
3655
3662
  };
3656
3663
  const isHorizontal = direction === "horizontal";
3657
3664
  const directionClass = responsive && isHorizontal ? reverse ? "flex-col-reverse md:flex-row-reverse" : "flex-col md:flex-row" : isHorizontal ? reverse ? "flex-row-reverse" : "flex-row" : reverse ? "flex-col-reverse" : "flex-col";
3658
- const Comp = Component;
3665
+ const Comp = Component2;
3659
3666
  return /* @__PURE__ */ jsxRuntime.jsx(
3660
3667
  Comp,
3661
3668
  {
@@ -4569,9 +4576,9 @@ function resolvePhosphor(name, weight, family) {
4569
4576
  (lib) => {
4570
4577
  const PhosphorComp = lib[target];
4571
4578
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
4572
- const Component = PhosphorComp;
4579
+ const Component2 = PhosphorComp;
4573
4580
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
4574
- Component,
4581
+ Component2,
4575
4582
  {
4576
4583
  weight,
4577
4584
  className: props.className,
@@ -4595,9 +4602,9 @@ function resolveTabler(name, family) {
4595
4602
  (lib) => {
4596
4603
  const TablerComp = lib[target];
4597
4604
  if (!TablerComp || typeof TablerComp !== "object") return null;
4598
- const Component = TablerComp;
4605
+ const Component2 = TablerComp;
4599
4606
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
4600
- Component,
4607
+ Component2,
4601
4608
  {
4602
4609
  stroke: props.strokeWidth ?? 1.5,
4603
4610
  className: props.className,
@@ -4621,9 +4628,9 @@ function resolveFa(name, family) {
4621
4628
  (lib) => {
4622
4629
  const FaComp = lib[target];
4623
4630
  if (!FaComp || typeof FaComp !== "function") return null;
4624
- const Component = FaComp;
4631
+ const Component2 = FaComp;
4625
4632
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
4626
- Component,
4633
+ Component2,
4627
4634
  {
4628
4635
  className: props.className,
4629
4636
  style: props.style,
@@ -8015,10 +8022,10 @@ var init_Center = __esm({
8015
8022
  className,
8016
8023
  style,
8017
8024
  children,
8018
- as: Component = "div"
8025
+ as: Component2 = "div"
8019
8026
  }) => {
8020
8027
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
8021
- const Comp = Component;
8028
+ const Comp = Component2;
8022
8029
  return /* @__PURE__ */ jsxRuntime.jsx(
8023
8030
  Comp,
8024
8031
  {
@@ -10812,6 +10819,7 @@ var init_HealthBar = __esm({
10812
10819
  }
10813
10820
  });
10814
10821
  function ScoreDisplay({
10822
+ assetUrl = DEFAULT_ASSET_URL,
10815
10823
  value,
10816
10824
  label,
10817
10825
  icon,
@@ -10858,14 +10866,24 @@ function ScoreDisplay({
10858
10866
  className
10859
10867
  ),
10860
10868
  children: [
10861
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) }),
10869
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
10870
+ "img",
10871
+ {
10872
+ src: assetUrl,
10873
+ alt: "",
10874
+ width: 20,
10875
+ height: 20,
10876
+ style: { imageRendering: "pixelated", objectFit: "contain" },
10877
+ className: "flex-shrink-0"
10878
+ }
10879
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) }) : null,
10862
10880
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: label }),
10863
10881
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: formattedValue })
10864
10882
  ]
10865
10883
  }
10866
10884
  );
10867
10885
  }
10868
- var sizeMap2;
10886
+ var sizeMap2, DEFAULT_ASSET_URL;
10869
10887
  var init_ScoreDisplay = __esm({
10870
10888
  "components/game/atoms/ScoreDisplay.tsx"() {
10871
10889
  "use client";
@@ -10877,10 +10895,12 @@ var init_ScoreDisplay = __esm({
10877
10895
  lg: "text-2xl",
10878
10896
  xl: "text-4xl"
10879
10897
  };
10898
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
10880
10899
  ScoreDisplay.displayName = "ScoreDisplay";
10881
10900
  }
10882
10901
  });
10883
10902
  function ControlButton({
10903
+ assetUrl = DEFAULT_ASSET_URL2,
10884
10904
  label,
10885
10905
  icon,
10886
10906
  size = "md",
@@ -10927,7 +10947,7 @@ function ControlButton({
10927
10947
  },
10928
10948
  [isPressed, releaseEvent, eventBus, onRelease]
10929
10949
  );
10930
- return /* @__PURE__ */ jsxRuntime.jsxs(
10950
+ return /* @__PURE__ */ jsxRuntime.jsx(
10931
10951
  "button",
10932
10952
  {
10933
10953
  type: "button",
@@ -10948,20 +10968,27 @@ function ControlButton({
10948
10968
  disabled && "opacity-50 cursor-not-allowed",
10949
10969
  className
10950
10970
  ),
10951
- children: [
10952
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
10953
- const I = resolveIcon(icon);
10954
- return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" }) : null;
10955
- })() : icon : /* @__PURE__ */ (() => {
10956
- const I = icon;
10957
- return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" });
10958
- })() }),
10959
- label && !icon && /* @__PURE__ */ jsxRuntime.jsx("span", { children: label })
10960
- ]
10971
+ children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
10972
+ "img",
10973
+ {
10974
+ src: assetUrl,
10975
+ alt: "",
10976
+ width: 24,
10977
+ height: 24,
10978
+ style: { imageRendering: "pixelated", objectFit: "contain" },
10979
+ className: "flex-shrink-0"
10980
+ }
10981
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
10982
+ const I = resolveIcon(icon);
10983
+ return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" }) : null;
10984
+ })() : icon : /* @__PURE__ */ (() => {
10985
+ const I = icon;
10986
+ return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" });
10987
+ })() }) : label ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }) : null
10961
10988
  }
10962
10989
  );
10963
10990
  }
10964
- var sizeMap3, shapeMap, variantMap;
10991
+ var sizeMap3, shapeMap, variantMap, DEFAULT_ASSET_URL2;
10965
10992
  var init_ControlButton = __esm({
10966
10993
  "components/game/atoms/ControlButton.tsx"() {
10967
10994
  "use client";
@@ -10984,6 +11011,7 @@ var init_ControlButton = __esm({
10984
11011
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
10985
11012
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
10986
11013
  };
11014
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
10987
11015
  ControlButton.displayName = "ControlButton";
10988
11016
  }
10989
11017
  });
@@ -11063,7 +11091,7 @@ var init_Sprite = __esm({
11063
11091
  }
11064
11092
  });
11065
11093
  function StateIndicator({
11066
- assetUrl = DEFAULT_ASSET_URL,
11094
+ assetUrl = DEFAULT_ASSET_URL3,
11067
11095
  state = "idle",
11068
11096
  label,
11069
11097
  size = "md",
@@ -11102,13 +11130,13 @@ function StateIndicator({
11102
11130
  }
11103
11131
  );
11104
11132
  }
11105
- var DEFAULT_ASSET_URL, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
11133
+ var DEFAULT_ASSET_URL3, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
11106
11134
  var init_StateIndicator = __esm({
11107
11135
  "components/game/atoms/StateIndicator.tsx"() {
11108
11136
  init_Box();
11109
11137
  init_Icon();
11110
11138
  init_cn();
11111
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11139
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11112
11140
  DEFAULT_STATE_STYLES = {
11113
11141
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
11114
11142
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -11183,7 +11211,7 @@ var init_TimerDisplay = __esm({
11183
11211
  }
11184
11212
  });
11185
11213
  function ResourceCounter({
11186
- assetUrl = DEFAULT_ASSET_URL2,
11214
+ assetUrl = DEFAULT_ASSET_URL4,
11187
11215
  icon,
11188
11216
  label = "Gold",
11189
11217
  value = 250,
@@ -11226,7 +11254,7 @@ function ResourceCounter({
11226
11254
  }
11227
11255
  );
11228
11256
  }
11229
- var colorTokenClasses2, DEFAULT_ASSET_URL2, sizeMap5;
11257
+ var colorTokenClasses2, DEFAULT_ASSET_URL4, sizeMap5;
11230
11258
  var init_ResourceCounter = __esm({
11231
11259
  "components/game/atoms/ResourceCounter.tsx"() {
11232
11260
  init_cn();
@@ -11239,7 +11267,7 @@ var init_ResourceCounter = __esm({
11239
11267
  error: "text-error",
11240
11268
  muted: "text-muted-foreground"
11241
11269
  };
11242
- DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
11270
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
11243
11271
  sizeMap5 = {
11244
11272
  sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
11245
11273
  md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
@@ -11249,7 +11277,7 @@ var init_ResourceCounter = __esm({
11249
11277
  }
11250
11278
  });
11251
11279
  function ItemSlot({
11252
- assetUrl = DEFAULT_ASSET_URL3,
11280
+ assetUrl = DEFAULT_ASSET_URL5,
11253
11281
  icon = "sword",
11254
11282
  label = "Iron Sword",
11255
11283
  quantity,
@@ -11308,7 +11336,7 @@ function ItemSlot({
11308
11336
  }
11309
11337
  );
11310
11338
  }
11311
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL3, assetSizeMap;
11339
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL5, assetSizeMap;
11312
11340
  var init_ItemSlot = __esm({
11313
11341
  "components/game/atoms/ItemSlot.tsx"() {
11314
11342
  "use client";
@@ -11333,7 +11361,7 @@ var init_ItemSlot = __esm({
11333
11361
  epic: "shadow-lg",
11334
11362
  legendary: "shadow-lg"
11335
11363
  };
11336
- DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11364
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11337
11365
  assetSizeMap = {
11338
11366
  sm: 28,
11339
11367
  md: 40,
@@ -11343,6 +11371,7 @@ var init_ItemSlot = __esm({
11343
11371
  }
11344
11372
  });
11345
11373
  function TurnIndicator({
11374
+ assetUrl = DEFAULT_ASSET_URL6,
11346
11375
  currentTurn = 1,
11347
11376
  maxTurns,
11348
11377
  activeTeam,
@@ -11375,14 +11404,24 @@ function TurnIndicator({
11375
11404
  ] }),
11376
11405
  activeTeam && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11377
11406
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "|" }),
11378
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
11407
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11408
+ "img",
11409
+ {
11410
+ src: assetUrl,
11411
+ alt: "",
11412
+ width: 12,
11413
+ height: 12,
11414
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11415
+ className: "flex-shrink-0"
11416
+ }
11417
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
11379
11418
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-success", children: activeTeam })
11380
11419
  ] })
11381
11420
  ]
11382
11421
  }
11383
11422
  );
11384
11423
  }
11385
- var sizeMap7;
11424
+ var sizeMap7, DEFAULT_ASSET_URL6;
11386
11425
  var init_TurnIndicator = __esm({
11387
11426
  "components/game/atoms/TurnIndicator.tsx"() {
11388
11427
  init_cn();
@@ -11391,6 +11430,7 @@ var init_TurnIndicator = __esm({
11391
11430
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
11392
11431
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
11393
11432
  };
11433
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
11394
11434
  TurnIndicator.displayName = "TurnIndicator";
11395
11435
  }
11396
11436
  });
@@ -11406,7 +11446,7 @@ function getComboScale(combo) {
11406
11446
  return "";
11407
11447
  }
11408
11448
  function ComboCounter({
11409
- assetUrl = DEFAULT_ASSET_URL4,
11449
+ assetUrl = DEFAULT_ASSET_URL7,
11410
11450
  combo = 5,
11411
11451
  multiplier,
11412
11452
  streak,
@@ -11451,11 +11491,11 @@ function ComboCounter({
11451
11491
  }
11452
11492
  );
11453
11493
  }
11454
- var DEFAULT_ASSET_URL4, sizeMap8;
11494
+ var DEFAULT_ASSET_URL7, sizeMap8;
11455
11495
  var init_ComboCounter = __esm({
11456
11496
  "components/game/atoms/ComboCounter.tsx"() {
11457
11497
  init_cn();
11458
- DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
11498
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
11459
11499
  sizeMap8 = {
11460
11500
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
11461
11501
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -11549,7 +11589,7 @@ var init_XPBar = __esm({
11549
11589
  }
11550
11590
  });
11551
11591
  function WaypointMarker({
11552
- assetUrl = DEFAULT_ASSET_URL5,
11592
+ assetUrl = DEFAULT_ASSET_URL8,
11553
11593
  label,
11554
11594
  icon,
11555
11595
  active = true,
@@ -11615,12 +11655,12 @@ function WaypointMarker({
11615
11655
  )
11616
11656
  ] });
11617
11657
  }
11618
- var DEFAULT_ASSET_URL5, sizeMap10, checkIcon;
11658
+ var DEFAULT_ASSET_URL8, sizeMap10, checkIcon;
11619
11659
  var init_WaypointMarker = __esm({
11620
11660
  "components/game/atoms/WaypointMarker.tsx"() {
11621
11661
  init_cn();
11622
11662
  init_Icon();
11623
- DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
11663
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
11624
11664
  sizeMap10 = {
11625
11665
  sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
11626
11666
  md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
@@ -11637,7 +11677,7 @@ function formatDuration(seconds) {
11637
11677
  return `${Math.round(seconds)}s`;
11638
11678
  }
11639
11679
  function StatusEffect({
11640
- assetUrl = DEFAULT_ASSET_URL6,
11680
+ assetUrl = DEFAULT_ASSET_URL9,
11641
11681
  icon = "shield",
11642
11682
  label = "Shield",
11643
11683
  duration = 30,
@@ -11695,12 +11735,12 @@ function StatusEffect({
11695
11735
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
11696
11736
  ] });
11697
11737
  }
11698
- var DEFAULT_ASSET_URL6, sizeMap11, variantStyles7;
11738
+ var DEFAULT_ASSET_URL9, sizeMap11, variantStyles7;
11699
11739
  var init_StatusEffect = __esm({
11700
11740
  "components/game/atoms/StatusEffect.tsx"() {
11701
11741
  init_cn();
11702
11742
  init_Icon();
11703
- DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
11743
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
11704
11744
  sizeMap11 = {
11705
11745
  sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]", img: 20 },
11706
11746
  md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs", img: 28 },
@@ -11715,6 +11755,7 @@ var init_StatusEffect = __esm({
11715
11755
  }
11716
11756
  });
11717
11757
  function DamageNumber({
11758
+ assetUrl = DEFAULT_ASSET_URL10,
11718
11759
  value = 42,
11719
11760
  type = "damage",
11720
11761
  size = "md",
@@ -11723,22 +11764,35 @@ function DamageNumber({
11723
11764
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
11724
11765
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11725
11766
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: floatKeyframes }),
11726
- /* @__PURE__ */ jsxRuntime.jsx(
11767
+ /* @__PURE__ */ jsxRuntime.jsxs(
11727
11768
  "span",
11728
11769
  {
11729
11770
  className: cn(
11730
- "inline-block font-mono select-none pointer-events-none",
11771
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
11731
11772
  sizeMap12[size],
11732
11773
  typeStyles[type],
11733
11774
  className
11734
11775
  ),
11735
11776
  style: { animation: "damageFloat 1s ease-out forwards" },
11736
- children: displayText
11777
+ children: [
11778
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
11779
+ "img",
11780
+ {
11781
+ src: assetUrl,
11782
+ alt: "",
11783
+ width: 14,
11784
+ height: 14,
11785
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11786
+ className: "flex-shrink-0"
11787
+ }
11788
+ ),
11789
+ displayText
11790
+ ]
11737
11791
  }
11738
11792
  )
11739
11793
  ] });
11740
11794
  }
11741
- var sizeMap12, typeStyles, floatKeyframes;
11795
+ var sizeMap12, typeStyles, floatKeyframes, DEFAULT_ASSET_URL10;
11742
11796
  var init_DamageNumber = __esm({
11743
11797
  "components/game/atoms/DamageNumber.tsx"() {
11744
11798
  init_cn();
@@ -11760,6 +11814,7 @@ var init_DamageNumber = __esm({
11760
11814
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
11761
11815
  }
11762
11816
  `;
11817
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
11763
11818
  DamageNumber.displayName = "DamageNumber";
11764
11819
  }
11765
11820
  });
@@ -11848,6 +11903,7 @@ var init_ChoiceButton = __esm({
11848
11903
  }
11849
11904
  });
11850
11905
  function ActionButton({
11906
+ assetUrl = DEFAULT_ASSET_URL11,
11851
11907
  label = "Attack",
11852
11908
  icon,
11853
11909
  cooldown = 0,
@@ -11887,13 +11943,23 @@ function ActionButton({
11887
11943
  }
11888
11944
  }
11889
11945
  ),
11890
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
11946
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11947
+ "img",
11948
+ {
11949
+ src: assetUrl,
11950
+ alt: "",
11951
+ width: 16,
11952
+ height: 16,
11953
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11954
+ className: cn("flex-shrink-0", sizes.icon)
11955
+ }
11956
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
11891
11957
  const I = resolveIcon(icon);
11892
11958
  return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-4 h-4" }) : null;
11893
11959
  })() : /* @__PURE__ */ (() => {
11894
11960
  const I = icon;
11895
11961
  return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-4 h-4" });
11896
- })() }),
11962
+ })() }) : null,
11897
11963
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative z-10", children: label }),
11898
11964
  hotkey && /* @__PURE__ */ jsxRuntime.jsx(
11899
11965
  "span",
@@ -11909,7 +11975,7 @@ function ActionButton({
11909
11975
  }
11910
11976
  );
11911
11977
  }
11912
- var sizeMap13, variantStyles8;
11978
+ var sizeMap13, variantStyles8, DEFAULT_ASSET_URL11;
11913
11979
  var init_ActionButton = __esm({
11914
11980
  "components/game/atoms/ActionButton.tsx"() {
11915
11981
  init_cn();
@@ -11924,6 +11990,7 @@ var init_ActionButton = __esm({
11924
11990
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
11925
11991
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
11926
11992
  };
11993
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
11927
11994
  ActionButton.displayName = "ActionButton";
11928
11995
  }
11929
11996
  });
@@ -12436,10 +12503,10 @@ var init_Container = __esm({
12436
12503
  center = true,
12437
12504
  className,
12438
12505
  children,
12439
- as: Component = "div"
12506
+ as: Component2 = "div"
12440
12507
  }) => {
12441
12508
  const resolvedSize = maxWidth ?? size ?? "lg";
12442
- const Comp = Component;
12509
+ const Comp = Component2;
12443
12510
  return /* @__PURE__ */ jsxRuntime.jsx(
12444
12511
  Comp,
12445
12512
  {
@@ -27616,7 +27683,7 @@ var init_Flex = __esm({
27616
27683
  basis,
27617
27684
  className,
27618
27685
  children,
27619
- as: Component = "div"
27686
+ as: Component2 = "div"
27620
27687
  }) => {
27621
27688
  const flexStyle = {};
27622
27689
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -27628,7 +27695,7 @@ var init_Flex = __esm({
27628
27695
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
27629
27696
  }
27630
27697
  }
27631
- const Comp = Component;
27698
+ const Comp = Component2;
27632
27699
  return /* @__PURE__ */ jsxRuntime.jsx(
27633
27700
  Comp,
27634
27701
  {
@@ -27748,11 +27815,11 @@ var init_Grid = __esm({
27748
27815
  className,
27749
27816
  style,
27750
27817
  children,
27751
- as: Component = "div"
27818
+ as: Component2 = "div"
27752
27819
  }) => {
27753
27820
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
27754
27821
  return React91__namespace.default.createElement(
27755
- Component,
27822
+ Component2,
27756
27823
  {
27757
27824
  className: cn(
27758
27825
  "grid",
@@ -29578,6 +29645,7 @@ var init_ProgressDots = __esm({
29578
29645
  }
29579
29646
  });
29580
29647
  function StatBadge({
29648
+ assetUrl = DEFAULT_ASSET_URL12,
29581
29649
  label,
29582
29650
  value = 0,
29583
29651
  max,
@@ -29601,7 +29669,17 @@ function StatBadge({
29601
29669
  className
29602
29670
  ),
29603
29671
  children: [
29604
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 text-lg", children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, className: "w-4 h-4" }) }),
29672
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
29673
+ "img",
29674
+ {
29675
+ src: assetUrl,
29676
+ alt: "",
29677
+ width: 16,
29678
+ height: 16,
29679
+ style: { imageRendering: "pixelated", objectFit: "contain" },
29680
+ className: "flex-shrink-0"
29681
+ }
29682
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 text-lg", children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, className: "w-4 h-4" }) }) : null,
29605
29683
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground font-medium", children: label }),
29606
29684
  format === "hearts" && max && /* @__PURE__ */ jsxRuntime.jsx(
29607
29685
  HealthBar,
@@ -29634,7 +29712,7 @@ function StatBadge({
29634
29712
  }
29635
29713
  );
29636
29714
  }
29637
- var sizeMap17, variantMap2;
29715
+ var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
29638
29716
  var init_StatBadge = __esm({
29639
29717
  "components/game/molecules/StatBadge.tsx"() {
29640
29718
  init_cn();
@@ -29653,6 +29731,7 @@ var init_StatBadge = __esm({
29653
29731
  warning: "bg-warning/15 border-warning/40 text-foreground",
29654
29732
  danger: "bg-error/15 border-error/40 text-foreground"
29655
29733
  };
29734
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
29656
29735
  StatBadge.displayName = "StatBadge";
29657
29736
  }
29658
29737
  });
@@ -30309,6 +30388,7 @@ var init_TurnPanel = __esm({
30309
30388
  }
30310
30389
  });
30311
30390
  function EnemyPlate({
30391
+ assetUrl = DEFAULT_ASSET_URL13,
30312
30392
  name = "Shadow Guard",
30313
30393
  health = 80,
30314
30394
  maxHealth = 100,
@@ -30326,15 +30406,28 @@ function EnemyPlate({
30326
30406
  ),
30327
30407
  children: [
30328
30408
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
30329
- /* @__PURE__ */ jsxRuntime.jsx(
30330
- Typography,
30331
- {
30332
- variant: "caption",
30333
- weight: "bold",
30334
- className: "text-[var(--color-foreground)] truncate",
30335
- children: name
30336
- }
30337
- ),
30409
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
30410
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
30411
+ "img",
30412
+ {
30413
+ src: assetUrl,
30414
+ alt: name,
30415
+ width: 24,
30416
+ height: 24,
30417
+ style: { objectFit: "cover", borderRadius: "50%" },
30418
+ className: "flex-shrink-0"
30419
+ }
30420
+ ),
30421
+ /* @__PURE__ */ jsxRuntime.jsx(
30422
+ Typography,
30423
+ {
30424
+ variant: "caption",
30425
+ weight: "bold",
30426
+ className: "text-[var(--color-foreground)] truncate",
30427
+ children: name
30428
+ }
30429
+ )
30430
+ ] }),
30338
30431
  level != null && /* @__PURE__ */ jsxRuntime.jsxs(Badge, { variant: "neutral", size: "sm", children: [
30339
30432
  "Lv.",
30340
30433
  level
@@ -30379,7 +30472,7 @@ function EnemyPlate({
30379
30472
  }
30380
30473
  );
30381
30474
  }
30382
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
30475
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
30383
30476
  var init_EnemyPlate = __esm({
30384
30477
  "components/game/molecules/EnemyPlate.tsx"() {
30385
30478
  "use client";
@@ -30396,6 +30489,7 @@ var init_EnemyPlate = __esm({
30396
30489
  DEFAULT_ENEMY_EFFECTS = [
30397
30490
  { icon: "flame", label: "Burn", variant: "debuff" }
30398
30491
  ];
30492
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
30399
30493
  EnemyPlate.displayName = "EnemyPlate";
30400
30494
  }
30401
30495
  });
@@ -31927,13 +32021,13 @@ var init_MapView = __esm({
31927
32021
  shadowSize: [41, 41]
31928
32022
  });
31929
32023
  L.Marker.prototype.options.icon = defaultIcon;
31930
- const { useEffect: useEffect78, useRef: useRef70, useCallback: useCallback117, useState: useState115 } = React91__namespace.default;
32024
+ const { useEffect: useEffect84, useRef: useRef76, useCallback: useCallback124, useState: useState120 } = React91__namespace.default;
31931
32025
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31932
- const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
32026
+ const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31933
32027
  function MapUpdater({ centerLat, centerLng, zoom }) {
31934
32028
  const map = useMap();
31935
- const prevRef = useRef70({ centerLat, centerLng, zoom });
31936
- useEffect78(() => {
32029
+ const prevRef = useRef76({ centerLat, centerLng, zoom });
32030
+ useEffect84(() => {
31937
32031
  const prev = prevRef.current;
31938
32032
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
31939
32033
  map.setView([centerLat, centerLng], zoom);
@@ -31944,7 +32038,7 @@ var init_MapView = __esm({
31944
32038
  }
31945
32039
  function MapClickHandler({ onMapClick }) {
31946
32040
  const map = useMap();
31947
- useEffect78(() => {
32041
+ useEffect84(() => {
31948
32042
  if (!onMapClick) return;
31949
32043
  const handler = (e) => {
31950
32044
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -31971,9 +32065,9 @@ var init_MapView = __esm({
31971
32065
  className,
31972
32066
  showAttribution = true
31973
32067
  }) {
31974
- const eventBus = useEventBus3();
31975
- const [clickedPosition, setClickedPosition] = useState115(null);
31976
- const handleMapClick = useCallback117((lat, lng) => {
32068
+ const eventBus = useEventBus4();
32069
+ const [clickedPosition, setClickedPosition] = useState120(null);
32070
+ const handleMapClick = useCallback124((lat, lng) => {
31977
32071
  if (showClickedPin) {
31978
32072
  setClickedPosition({ lat, lng });
31979
32073
  }
@@ -31982,7 +32076,7 @@ var init_MapView = __esm({
31982
32076
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
31983
32077
  }
31984
32078
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
31985
- const handleMarkerClick = useCallback117((marker) => {
32079
+ const handleMarkerClick = useCallback124((marker) => {
31986
32080
  onMarkerClick?.(marker);
31987
32081
  if (markerClickEvent) {
31988
32082
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -38700,10 +38794,10 @@ var init_Section = __esm({
38700
38794
  children,
38701
38795
  headerClassName,
38702
38796
  contentClassName,
38703
- as: Component = "section"
38797
+ as: Component2 = "section"
38704
38798
  }) => {
38705
38799
  const hasHeader = title || description || action;
38706
- const Comp = Component;
38800
+ const Comp = Component2;
38707
38801
  return /* @__PURE__ */ jsxRuntime.jsxs(
38708
38802
  Comp,
38709
38803
  {
@@ -43268,6 +43362,1568 @@ var init_GameAudioToggle = __esm({
43268
43362
  GameAudioToggle.displayName = "GameAudioToggle";
43269
43363
  }
43270
43364
  });
43365
+ function detectAssetRoot(modelUrl) {
43366
+ const idx = modelUrl.indexOf("/3d/");
43367
+ if (idx !== -1) {
43368
+ return modelUrl.substring(0, idx + 4);
43369
+ }
43370
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
43371
+ }
43372
+ function createGLTFLoaderForUrl(url) {
43373
+ const loader = new GLTFLoader_js.GLTFLoader();
43374
+ loader.setResourcePath(detectAssetRoot(url));
43375
+ return loader;
43376
+ }
43377
+ var AssetLoader;
43378
+ var init_AssetLoader = __esm({
43379
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
43380
+ AssetLoader = class {
43381
+ constructor() {
43382
+ __publicField(this, "objLoader");
43383
+ __publicField(this, "textureLoader");
43384
+ __publicField(this, "modelCache");
43385
+ __publicField(this, "textureCache");
43386
+ __publicField(this, "loadingPromises");
43387
+ this.objLoader = new OBJLoader_js.OBJLoader();
43388
+ this.textureLoader = new THREE__namespace.TextureLoader();
43389
+ this.modelCache = /* @__PURE__ */ new Map();
43390
+ this.textureCache = /* @__PURE__ */ new Map();
43391
+ this.loadingPromises = /* @__PURE__ */ new Map();
43392
+ }
43393
+ /**
43394
+ * Load a GLB/GLTF model
43395
+ * @param url - URL to the .glb or .gltf file
43396
+ * @returns Promise with loaded model scene and animations
43397
+ */
43398
+ async loadModel(url) {
43399
+ if (this.modelCache.has(url)) {
43400
+ return this.modelCache.get(url);
43401
+ }
43402
+ if (this.loadingPromises.has(url)) {
43403
+ return this.loadingPromises.get(url);
43404
+ }
43405
+ const loader = createGLTFLoaderForUrl(url);
43406
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
43407
+ const result = {
43408
+ scene: gltf.scene,
43409
+ animations: gltf.animations || []
43410
+ };
43411
+ this.modelCache.set(url, result);
43412
+ this.loadingPromises.delete(url);
43413
+ return result;
43414
+ }).catch((error) => {
43415
+ this.loadingPromises.delete(url);
43416
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
43417
+ });
43418
+ this.loadingPromises.set(url, loadPromise);
43419
+ return loadPromise;
43420
+ }
43421
+ /**
43422
+ * Load an OBJ model (fallback for non-GLB assets)
43423
+ * @param url - URL to the .obj file
43424
+ * @returns Promise with loaded object group
43425
+ */
43426
+ async loadOBJ(url) {
43427
+ if (this.modelCache.has(url)) {
43428
+ return this.modelCache.get(url).scene;
43429
+ }
43430
+ if (this.loadingPromises.has(url)) {
43431
+ const result = await this.loadingPromises.get(url);
43432
+ return result.scene;
43433
+ }
43434
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
43435
+ const result = {
43436
+ scene: group,
43437
+ animations: []
43438
+ };
43439
+ this.modelCache.set(url, result);
43440
+ this.loadingPromises.delete(url);
43441
+ return result;
43442
+ }).catch((error) => {
43443
+ this.loadingPromises.delete(url);
43444
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
43445
+ });
43446
+ this.loadingPromises.set(url, loadPromise);
43447
+ return (await loadPromise).scene;
43448
+ }
43449
+ /**
43450
+ * Load a texture
43451
+ * @param url - URL to the texture image
43452
+ * @returns Promise with loaded texture
43453
+ */
43454
+ async loadTexture(url) {
43455
+ if (this.textureCache.has(url)) {
43456
+ return this.textureCache.get(url);
43457
+ }
43458
+ if (this.loadingPromises.has(`texture:${url}`)) {
43459
+ return this.loadingPromises.get(`texture:${url}`);
43460
+ }
43461
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
43462
+ texture.colorSpace = THREE__namespace.SRGBColorSpace;
43463
+ this.textureCache.set(url, texture);
43464
+ this.loadingPromises.delete(`texture:${url}`);
43465
+ return texture;
43466
+ }).catch((error) => {
43467
+ this.loadingPromises.delete(`texture:${url}`);
43468
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
43469
+ });
43470
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
43471
+ return loadPromise;
43472
+ }
43473
+ /**
43474
+ * Preload multiple assets
43475
+ * @param urls - Array of asset URLs to preload
43476
+ * @returns Promise that resolves when all assets are loaded
43477
+ */
43478
+ async preload(urls) {
43479
+ const promises = urls.map((url) => {
43480
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
43481
+ return this.loadModel(url).catch(() => null);
43482
+ } else if (url.endsWith(".obj")) {
43483
+ return this.loadOBJ(url).catch(() => null);
43484
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
43485
+ return this.loadTexture(url).catch(() => null);
43486
+ }
43487
+ return Promise.resolve(null);
43488
+ });
43489
+ await Promise.all(promises);
43490
+ }
43491
+ /**
43492
+ * Check if a model is cached
43493
+ * @param url - Model URL
43494
+ */
43495
+ hasModel(url) {
43496
+ return this.modelCache.has(url);
43497
+ }
43498
+ /**
43499
+ * Check if a texture is cached
43500
+ * @param url - Texture URL
43501
+ */
43502
+ hasTexture(url) {
43503
+ return this.textureCache.has(url);
43504
+ }
43505
+ /**
43506
+ * Get cached model (throws if not cached)
43507
+ * @param url - Model URL
43508
+ */
43509
+ getModel(url) {
43510
+ const model = this.modelCache.get(url);
43511
+ if (!model) {
43512
+ throw new Error(`Model ${url} not in cache`);
43513
+ }
43514
+ return model;
43515
+ }
43516
+ /**
43517
+ * Get cached texture (throws if not cached)
43518
+ * @param url - Texture URL
43519
+ */
43520
+ getTexture(url) {
43521
+ const texture = this.textureCache.get(url);
43522
+ if (!texture) {
43523
+ throw new Error(`Texture ${url} not in cache`);
43524
+ }
43525
+ return texture;
43526
+ }
43527
+ /**
43528
+ * Clear all caches
43529
+ */
43530
+ clearCache() {
43531
+ this.textureCache.forEach((texture) => {
43532
+ texture.dispose();
43533
+ });
43534
+ this.modelCache.forEach((model) => {
43535
+ model.scene.traverse((child) => {
43536
+ if (child instanceof THREE__namespace.Mesh) {
43537
+ child.geometry.dispose();
43538
+ if (Array.isArray(child.material)) {
43539
+ child.material.forEach((m) => m.dispose());
43540
+ } else {
43541
+ child.material.dispose();
43542
+ }
43543
+ }
43544
+ });
43545
+ });
43546
+ this.modelCache.clear();
43547
+ this.textureCache.clear();
43548
+ this.loadingPromises.clear();
43549
+ }
43550
+ /**
43551
+ * Get cache statistics
43552
+ */
43553
+ getStats() {
43554
+ return {
43555
+ models: this.modelCache.size,
43556
+ textures: this.textureCache.size,
43557
+ loading: this.loadingPromises.size
43558
+ };
43559
+ }
43560
+ };
43561
+ new AssetLoader();
43562
+ }
43563
+ });
43564
+ function useAssetLoader(options = {}) {
43565
+ const { preloadUrls = [], loader: customLoader } = options;
43566
+ const loaderRef = React91.useRef(customLoader || new AssetLoader());
43567
+ const [state, setState] = React91.useState({
43568
+ isLoading: false,
43569
+ progress: 0,
43570
+ loaded: 0,
43571
+ total: 0,
43572
+ errors: []
43573
+ });
43574
+ React91.useEffect(() => {
43575
+ if (preloadUrls.length > 0) {
43576
+ preload(preloadUrls);
43577
+ }
43578
+ }, []);
43579
+ const updateProgress = React91.useCallback((loaded, total) => {
43580
+ setState((prev) => ({
43581
+ ...prev,
43582
+ loaded,
43583
+ total,
43584
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
43585
+ }));
43586
+ }, []);
43587
+ const loadModel = React91.useCallback(
43588
+ async (url) => {
43589
+ setState((prev) => ({ ...prev, isLoading: true }));
43590
+ try {
43591
+ const model = await loaderRef.current.loadModel(url);
43592
+ setState((prev) => ({
43593
+ ...prev,
43594
+ isLoading: false,
43595
+ loaded: prev.loaded + 1
43596
+ }));
43597
+ return model;
43598
+ } catch (error) {
43599
+ const errorMsg = error instanceof Error ? error.message : String(error);
43600
+ setState((prev) => ({
43601
+ ...prev,
43602
+ isLoading: false,
43603
+ errors: [...prev.errors, errorMsg]
43604
+ }));
43605
+ throw error;
43606
+ }
43607
+ },
43608
+ []
43609
+ );
43610
+ const loadOBJ = React91.useCallback(
43611
+ async (url) => {
43612
+ setState((prev) => ({ ...prev, isLoading: true }));
43613
+ try {
43614
+ const model = await loaderRef.current.loadOBJ(url);
43615
+ setState((prev) => ({
43616
+ ...prev,
43617
+ isLoading: false,
43618
+ loaded: prev.loaded + 1
43619
+ }));
43620
+ return model;
43621
+ } catch (error) {
43622
+ const errorMsg = error instanceof Error ? error.message : String(error);
43623
+ setState((prev) => ({
43624
+ ...prev,
43625
+ isLoading: false,
43626
+ errors: [...prev.errors, errorMsg]
43627
+ }));
43628
+ throw error;
43629
+ }
43630
+ },
43631
+ []
43632
+ );
43633
+ const loadTexture = React91.useCallback(
43634
+ async (url) => {
43635
+ setState((prev) => ({ ...prev, isLoading: true }));
43636
+ try {
43637
+ const texture = await loaderRef.current.loadTexture(url);
43638
+ setState((prev) => ({
43639
+ ...prev,
43640
+ isLoading: false,
43641
+ loaded: prev.loaded + 1
43642
+ }));
43643
+ return texture;
43644
+ } catch (error) {
43645
+ const errorMsg = error instanceof Error ? error.message : String(error);
43646
+ setState((prev) => ({
43647
+ ...prev,
43648
+ isLoading: false,
43649
+ errors: [...prev.errors, errorMsg]
43650
+ }));
43651
+ throw error;
43652
+ }
43653
+ },
43654
+ []
43655
+ );
43656
+ const preload = React91.useCallback(
43657
+ async (urls) => {
43658
+ setState((prev) => ({
43659
+ ...prev,
43660
+ isLoading: true,
43661
+ total: urls.length,
43662
+ loaded: 0,
43663
+ errors: []
43664
+ }));
43665
+ let completed = 0;
43666
+ const errors = [];
43667
+ await Promise.all(
43668
+ urls.map(async (url) => {
43669
+ try {
43670
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
43671
+ await loaderRef.current.loadModel(url);
43672
+ } else if (url.endsWith(".obj")) {
43673
+ await loaderRef.current.loadOBJ(url);
43674
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
43675
+ await loaderRef.current.loadTexture(url);
43676
+ }
43677
+ completed++;
43678
+ updateProgress(completed, urls.length);
43679
+ } catch (error) {
43680
+ const errorMsg = error instanceof Error ? error.message : String(error);
43681
+ errors.push(`${url}: ${errorMsg}`);
43682
+ completed++;
43683
+ updateProgress(completed, urls.length);
43684
+ }
43685
+ })
43686
+ );
43687
+ setState((prev) => ({
43688
+ ...prev,
43689
+ isLoading: false,
43690
+ errors
43691
+ }));
43692
+ },
43693
+ [updateProgress]
43694
+ );
43695
+ const hasModel = React91.useCallback((url) => {
43696
+ return loaderRef.current.hasModel(url);
43697
+ }, []);
43698
+ const hasTexture = React91.useCallback((url) => {
43699
+ return loaderRef.current.hasTexture(url);
43700
+ }, []);
43701
+ const getModel = React91.useCallback((url) => {
43702
+ try {
43703
+ return loaderRef.current.getModel(url);
43704
+ } catch {
43705
+ return void 0;
43706
+ }
43707
+ }, []);
43708
+ const getTexture = React91.useCallback((url) => {
43709
+ try {
43710
+ return loaderRef.current.getTexture(url);
43711
+ } catch {
43712
+ return void 0;
43713
+ }
43714
+ }, []);
43715
+ const clearCache = React91.useCallback(() => {
43716
+ loaderRef.current.clearCache();
43717
+ setState({
43718
+ isLoading: false,
43719
+ progress: 0,
43720
+ loaded: 0,
43721
+ total: 0,
43722
+ errors: []
43723
+ });
43724
+ }, []);
43725
+ return {
43726
+ ...state,
43727
+ loadModel,
43728
+ loadOBJ,
43729
+ loadTexture,
43730
+ preload,
43731
+ hasModel,
43732
+ hasTexture,
43733
+ getModel,
43734
+ getTexture,
43735
+ clearCache
43736
+ };
43737
+ }
43738
+ var init_useAssetLoader = __esm({
43739
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
43740
+ "use client";
43741
+ init_AssetLoader();
43742
+ }
43743
+ });
43744
+ function useGameCanvas3DEvents(options) {
43745
+ const {
43746
+ tileClickEvent,
43747
+ unitClickEvent,
43748
+ featureClickEvent,
43749
+ canvasClickEvent,
43750
+ tileHoverEvent,
43751
+ tileLeaveEvent,
43752
+ unitAnimationEvent,
43753
+ cameraChangeEvent,
43754
+ onTileClick,
43755
+ onUnitClick,
43756
+ onFeatureClick,
43757
+ onCanvasClick,
43758
+ onTileHover,
43759
+ onUnitAnimation
43760
+ } = options;
43761
+ const emit = useEmitEvent();
43762
+ const optionsRef = React91.useRef(options);
43763
+ optionsRef.current = options;
43764
+ const handleTileClick = React91.useCallback(
43765
+ (tile, event) => {
43766
+ if (tileClickEvent) {
43767
+ emit(tileClickEvent, {
43768
+ tileId: tile.id,
43769
+ x: tile.x,
43770
+ z: tile.z ?? tile.y ?? 0,
43771
+ type: tile.type,
43772
+ terrain: tile.terrain,
43773
+ elevation: tile.elevation
43774
+ });
43775
+ }
43776
+ optionsRef.current.onTileClick?.(tile, event);
43777
+ },
43778
+ [tileClickEvent, emit]
43779
+ );
43780
+ const handleUnitClick = React91.useCallback(
43781
+ (unit, event) => {
43782
+ if (unitClickEvent) {
43783
+ emit(unitClickEvent, {
43784
+ unitId: unit.id,
43785
+ x: unit.x,
43786
+ z: unit.z ?? unit.y ?? 0,
43787
+ unitType: unit.unitType,
43788
+ name: unit.name,
43789
+ team: unit.team,
43790
+ faction: unit.faction,
43791
+ health: unit.health,
43792
+ maxHealth: unit.maxHealth
43793
+ });
43794
+ }
43795
+ optionsRef.current.onUnitClick?.(unit, event);
43796
+ },
43797
+ [unitClickEvent, emit]
43798
+ );
43799
+ const handleFeatureClick = React91.useCallback(
43800
+ (feature, event) => {
43801
+ if (featureClickEvent) {
43802
+ emit(featureClickEvent, {
43803
+ featureId: feature.id,
43804
+ x: feature.x,
43805
+ z: feature.z ?? feature.y ?? 0,
43806
+ type: feature.type,
43807
+ elevation: feature.elevation
43808
+ });
43809
+ }
43810
+ optionsRef.current.onFeatureClick?.(feature, event);
43811
+ },
43812
+ [featureClickEvent, emit]
43813
+ );
43814
+ const handleCanvasClick = React91.useCallback(
43815
+ (event) => {
43816
+ if (canvasClickEvent) {
43817
+ emit(canvasClickEvent, {
43818
+ clientX: event.clientX,
43819
+ clientY: event.clientY,
43820
+ button: event.button
43821
+ });
43822
+ }
43823
+ optionsRef.current.onCanvasClick?.(event);
43824
+ },
43825
+ [canvasClickEvent, emit]
43826
+ );
43827
+ const handleTileHover = React91.useCallback(
43828
+ (tile, event) => {
43829
+ if (tile) {
43830
+ if (tileHoverEvent) {
43831
+ emit(tileHoverEvent, {
43832
+ tileId: tile.id,
43833
+ x: tile.x,
43834
+ z: tile.z ?? tile.y ?? 0,
43835
+ type: tile.type
43836
+ });
43837
+ }
43838
+ } else {
43839
+ if (tileLeaveEvent) {
43840
+ emit(tileLeaveEvent, {});
43841
+ }
43842
+ }
43843
+ optionsRef.current.onTileHover?.(tile, event);
43844
+ },
43845
+ [tileHoverEvent, tileLeaveEvent, emit]
43846
+ );
43847
+ const handleUnitAnimation = React91.useCallback(
43848
+ (unitId, state) => {
43849
+ if (unitAnimationEvent) {
43850
+ emit(unitAnimationEvent, {
43851
+ unitId,
43852
+ state,
43853
+ timestamp: Date.now()
43854
+ });
43855
+ }
43856
+ optionsRef.current.onUnitAnimation?.(unitId, state);
43857
+ },
43858
+ [unitAnimationEvent, emit]
43859
+ );
43860
+ const handleCameraChange = React91.useCallback(
43861
+ (position) => {
43862
+ if (cameraChangeEvent) {
43863
+ emit(cameraChangeEvent, {
43864
+ position,
43865
+ timestamp: Date.now()
43866
+ });
43867
+ }
43868
+ },
43869
+ [cameraChangeEvent, emit]
43870
+ );
43871
+ return {
43872
+ handleTileClick,
43873
+ handleUnitClick,
43874
+ handleFeatureClick,
43875
+ handleCanvasClick,
43876
+ handleTileHover,
43877
+ handleUnitAnimation,
43878
+ handleCameraChange
43879
+ };
43880
+ }
43881
+ var init_useGameCanvas3DEvents = __esm({
43882
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
43883
+ "use client";
43884
+ init_useEventBus();
43885
+ }
43886
+ });
43887
+
43888
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
43889
+ var init_Canvas3DLoadingState = __esm({
43890
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
43891
+ }
43892
+ });
43893
+ function Canvas3DLoadingState({
43894
+ progress = 0,
43895
+ loaded = 0,
43896
+ total = 0,
43897
+ message = "Loading 3D Scene...",
43898
+ details,
43899
+ showSpinner = true,
43900
+ className
43901
+ }) {
43902
+ const clampedProgress = Math.max(0, Math.min(100, progress));
43903
+ const hasProgress = total > 0;
43904
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
43905
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__content", children: [
43906
+ showSpinner && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__spinner", children: [
43907
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner__ring" }),
43908
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner__ring spinner__ring--secondary" })
43909
+ ] }),
43910
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__message", children: message }),
43911
+ details && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__details", children: details }),
43912
+ hasProgress && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__progress", children: [
43913
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsxRuntime.jsx(
43914
+ "div",
43915
+ {
43916
+ className: "progress__fill",
43917
+ style: { width: `${clampedProgress}%` }
43918
+ }
43919
+ ) }),
43920
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "progress__text", children: [
43921
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "progress__percentage", children: [
43922
+ clampedProgress,
43923
+ "%"
43924
+ ] }),
43925
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "progress__count", children: [
43926
+ "(",
43927
+ loaded,
43928
+ "/",
43929
+ total,
43930
+ ")"
43931
+ ] })
43932
+ ] })
43933
+ ] })
43934
+ ] }),
43935
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg__grid" }) })
43936
+ ] });
43937
+ }
43938
+ var init_Canvas3DLoadingState2 = __esm({
43939
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
43940
+ init_Canvas3DLoadingState();
43941
+ }
43942
+ });
43943
+
43944
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
43945
+ var init_Canvas3DErrorBoundary = __esm({
43946
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
43947
+ }
43948
+ });
43949
+ var log9, Canvas3DErrorBoundary;
43950
+ var init_Canvas3DErrorBoundary2 = __esm({
43951
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
43952
+ init_Canvas3DErrorBoundary();
43953
+ log9 = logger.createLogger("almadar:ui:game:canvas3d:error-boundary");
43954
+ Canvas3DErrorBoundary = class extends React91.Component {
43955
+ constructor(props) {
43956
+ super(props);
43957
+ __publicField(this, "handleReset", () => {
43958
+ this.setState({
43959
+ hasError: false,
43960
+ error: null,
43961
+ errorInfo: null
43962
+ });
43963
+ this.props.onReset?.();
43964
+ });
43965
+ this.state = {
43966
+ hasError: false,
43967
+ error: null,
43968
+ errorInfo: null
43969
+ };
43970
+ }
43971
+ static getDerivedStateFromError(error) {
43972
+ return {
43973
+ hasError: true,
43974
+ error,
43975
+ errorInfo: null
43976
+ };
43977
+ }
43978
+ componentDidCatch(error, errorInfo) {
43979
+ this.setState({ errorInfo });
43980
+ this.props.onError?.(error, errorInfo);
43981
+ log9.error("Error caught", { error });
43982
+ log9.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
43983
+ }
43984
+ render() {
43985
+ if (this.state.hasError) {
43986
+ if (this.props.fallback) {
43987
+ return this.props.fallback;
43988
+ }
43989
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-error__content", children: [
43990
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
43991
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
43992
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
43993
+ this.state.error && /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "canvas-3d-error__details", children: [
43994
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Error Details" }),
43995
+ /* @__PURE__ */ jsxRuntime.jsxs("pre", { className: "error__stack", children: [
43996
+ this.state.error.message,
43997
+ "\n",
43998
+ this.state.error.stack
43999
+ ] }),
44000
+ this.state.errorInfo && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
44001
+ ] }),
44002
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-error__actions", children: [
44003
+ /* @__PURE__ */ jsxRuntime.jsx(
44004
+ "button",
44005
+ {
44006
+ className: "error__button error__button--primary",
44007
+ onClick: this.handleReset,
44008
+ children: "Try Again"
44009
+ }
44010
+ ),
44011
+ /* @__PURE__ */ jsxRuntime.jsx(
44012
+ "button",
44013
+ {
44014
+ className: "error__button error__button--secondary",
44015
+ onClick: () => window.location.reload(),
44016
+ children: "Reload Page"
44017
+ }
44018
+ )
44019
+ ] })
44020
+ ] }) });
44021
+ }
44022
+ return this.props.children;
44023
+ }
44024
+ };
44025
+ }
44026
+ });
44027
+ function detectAssetRoot2(modelUrl) {
44028
+ const idx = modelUrl.indexOf("/3d/");
44029
+ if (idx !== -1) {
44030
+ return modelUrl.substring(0, idx + 4);
44031
+ }
44032
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
44033
+ }
44034
+ function useGLTFModel(url, resourceBasePath) {
44035
+ const [state, setState] = React91.useState({
44036
+ model: null,
44037
+ isLoading: false,
44038
+ error: null
44039
+ });
44040
+ React91.useEffect(() => {
44041
+ if (!url) {
44042
+ setState({ model: null, isLoading: false, error: null });
44043
+ return;
44044
+ }
44045
+ log10.debug("Loading", { url });
44046
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
44047
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
44048
+ const loader = new GLTFLoader.GLTFLoader();
44049
+ loader.setResourcePath(assetRoot);
44050
+ loader.load(
44051
+ url,
44052
+ (gltf) => {
44053
+ log10.debug("Loaded", { url });
44054
+ setState({
44055
+ model: gltf.scene,
44056
+ isLoading: false,
44057
+ error: null
44058
+ });
44059
+ },
44060
+ void 0,
44061
+ (err) => {
44062
+ log10.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
44063
+ setState({
44064
+ model: null,
44065
+ isLoading: false,
44066
+ error: err instanceof Error ? err : new Error(String(err))
44067
+ });
44068
+ }
44069
+ );
44070
+ }, [url, resourceBasePath]);
44071
+ return state;
44072
+ }
44073
+ function ModelLoader({
44074
+ url,
44075
+ position = [0, 0, 0],
44076
+ scale = 1,
44077
+ rotation = [0, 0, 0],
44078
+ isSelected = false,
44079
+ isHovered = false,
44080
+ onClick,
44081
+ onHover,
44082
+ fallbackGeometry = "box",
44083
+ castShadow = true,
44084
+ receiveShadow = true,
44085
+ resourceBasePath
44086
+ }) {
44087
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
44088
+ const model = React91.useMemo(() => {
44089
+ if (!loadedModel) return null;
44090
+ const cloned = loadedModel.clone();
44091
+ cloned.traverse((child) => {
44092
+ if (child instanceof THREE__namespace.Mesh) {
44093
+ child.castShadow = castShadow;
44094
+ child.receiveShadow = receiveShadow;
44095
+ }
44096
+ });
44097
+ return cloned;
44098
+ }, [loadedModel, castShadow, receiveShadow]);
44099
+ const scaleArray = React91.useMemo(() => {
44100
+ if (typeof scale === "number") {
44101
+ return [scale, scale, scale];
44102
+ }
44103
+ return scale;
44104
+ }, [scale]);
44105
+ const rotationRad = React91.useMemo(() => {
44106
+ return [
44107
+ rotation[0] * Math.PI / 180,
44108
+ rotation[1] * Math.PI / 180,
44109
+ rotation[2] * Math.PI / 180
44110
+ ];
44111
+ }, [rotation]);
44112
+ if (isLoading) {
44113
+ return /* @__PURE__ */ jsxRuntime.jsx("group", { position, children: /* @__PURE__ */ jsxRuntime.jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
44114
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
44115
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
44116
+ ] }) });
44117
+ }
44118
+ if (error || !model) {
44119
+ if (fallbackGeometry === "none") {
44120
+ return /* @__PURE__ */ jsxRuntime.jsx("group", { position });
44121
+ }
44122
+ const fallbackProps = {
44123
+ onClick,
44124
+ onPointerOver: () => onHover?.(true),
44125
+ onPointerOut: () => onHover?.(false)
44126
+ };
44127
+ return /* @__PURE__ */ jsxRuntime.jsxs("group", { position, children: [
44128
+ (isSelected || isHovered) && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
44129
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
44130
+ /* @__PURE__ */ jsxRuntime.jsx(
44131
+ "meshBasicMaterial",
44132
+ {
44133
+ color: isSelected ? 16755200 : 16777215,
44134
+ transparent: true,
44135
+ opacity: 0.5
44136
+ }
44137
+ )
44138
+ ] }),
44139
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
44140
+ /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
44141
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
44142
+ ] }),
44143
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
44144
+ /* @__PURE__ */ jsxRuntime.jsx("sphereGeometry", { args: [0.4, 16, 16] }),
44145
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
44146
+ ] }),
44147
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
44148
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
44149
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
44150
+ ] })
44151
+ ] });
44152
+ }
44153
+ return /* @__PURE__ */ jsxRuntime.jsxs(
44154
+ "group",
44155
+ {
44156
+ position,
44157
+ rotation: rotationRad,
44158
+ onClick,
44159
+ onPointerOver: () => onHover?.(true),
44160
+ onPointerOut: () => onHover?.(false),
44161
+ children: [
44162
+ (isSelected || isHovered) && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
44163
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
44164
+ /* @__PURE__ */ jsxRuntime.jsx(
44165
+ "meshBasicMaterial",
44166
+ {
44167
+ color: isSelected ? 16755200 : 16777215,
44168
+ transparent: true,
44169
+ opacity: 0.5
44170
+ }
44171
+ )
44172
+ ] }),
44173
+ /* @__PURE__ */ jsxRuntime.jsx("primitive", { object: model, scale: scaleArray })
44174
+ ]
44175
+ }
44176
+ );
44177
+ }
44178
+ var log10;
44179
+ var init_ModelLoader = __esm({
44180
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
44181
+ "use client";
44182
+ log10 = logger.createLogger("almadar:ui:game:model-loader");
44183
+ }
44184
+ });
44185
+
44186
+ // components/game/molecules/GameCanvas3D.css
44187
+ var init_GameCanvas3D = __esm({
44188
+ "components/game/molecules/GameCanvas3D.css"() {
44189
+ }
44190
+ });
44191
+ function CameraController({
44192
+ onCameraChange
44193
+ }) {
44194
+ const { camera } = fiber.useThree();
44195
+ React91.useEffect(() => {
44196
+ if (onCameraChange) {
44197
+ onCameraChange({
44198
+ x: camera.position.x,
44199
+ y: camera.position.y,
44200
+ z: camera.position.z
44201
+ });
44202
+ }
44203
+ }, [camera.position, onCameraChange]);
44204
+ return null;
44205
+ }
44206
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
44207
+ var init_GameCanvas3D2 = __esm({
44208
+ "components/game/molecules/GameCanvas3D.tsx"() {
44209
+ "use client";
44210
+ init_AssetLoader();
44211
+ init_useAssetLoader();
44212
+ init_useGameCanvas3DEvents();
44213
+ init_Canvas3DLoadingState2();
44214
+ init_Canvas3DErrorBoundary2();
44215
+ init_ModelLoader();
44216
+ init_cn();
44217
+ init_GameCanvas3D();
44218
+ DEFAULT_GRID_CONFIG = {
44219
+ cellSize: 1,
44220
+ offsetX: 0,
44221
+ offsetZ: 0
44222
+ };
44223
+ GameCanvas3D = React91.forwardRef(
44224
+ ({
44225
+ tiles = [],
44226
+ units = [],
44227
+ features = [],
44228
+ events: events2 = [],
44229
+ orientation = "standard",
44230
+ cameraMode = "isometric",
44231
+ showGrid = true,
44232
+ showCoordinates = false,
44233
+ showTileInfo = false,
44234
+ overlay = "default",
44235
+ shadows = true,
44236
+ backgroundColor = "#1a1a2e",
44237
+ onTileClick,
44238
+ onUnitClick,
44239
+ onFeatureClick,
44240
+ onCanvasClick,
44241
+ onTileHover,
44242
+ onUnitAnimation,
44243
+ assetLoader: customAssetLoader,
44244
+ tileRenderer: CustomTileRenderer,
44245
+ unitRenderer: CustomUnitRenderer,
44246
+ featureRenderer: CustomFeatureRenderer,
44247
+ className,
44248
+ isLoading: externalLoading,
44249
+ error: externalError,
44250
+ entity,
44251
+ preloadAssets = [],
44252
+ tileClickEvent,
44253
+ unitClickEvent,
44254
+ featureClickEvent,
44255
+ canvasClickEvent,
44256
+ tileHoverEvent,
44257
+ tileLeaveEvent,
44258
+ unitAnimationEvent,
44259
+ cameraChangeEvent,
44260
+ loadingMessage = "Loading 3D Scene...",
44261
+ useInstancing = true,
44262
+ validMoves = [],
44263
+ attackTargets = [],
44264
+ selectedTileIds = [],
44265
+ selectedUnitId = null,
44266
+ children
44267
+ }, ref) => {
44268
+ const containerRef = React91.useRef(null);
44269
+ const controlsRef = React91.useRef(null);
44270
+ const [hoveredTile, setHoveredTile] = React91.useState(null);
44271
+ const [internalError, setInternalError] = React91.useState(null);
44272
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
44273
+ preloadUrls: preloadAssets,
44274
+ loader: customAssetLoader
44275
+ });
44276
+ const eventHandlers = useGameCanvas3DEvents({
44277
+ tileClickEvent,
44278
+ unitClickEvent,
44279
+ featureClickEvent,
44280
+ canvasClickEvent,
44281
+ tileHoverEvent,
44282
+ tileLeaveEvent,
44283
+ unitAnimationEvent,
44284
+ cameraChangeEvent,
44285
+ onTileClick,
44286
+ onUnitClick,
44287
+ onFeatureClick,
44288
+ onCanvasClick,
44289
+ onTileHover,
44290
+ onUnitAnimation
44291
+ });
44292
+ const gridBounds = React91.useMemo(() => {
44293
+ if (tiles.length === 0) {
44294
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
44295
+ }
44296
+ const xs = tiles.map((t) => t.x);
44297
+ const zs = tiles.map((t) => t.z || t.y || 0);
44298
+ return {
44299
+ minX: Math.min(...xs),
44300
+ maxX: Math.max(...xs),
44301
+ minZ: Math.min(...zs),
44302
+ maxZ: Math.max(...zs)
44303
+ };
44304
+ }, [tiles]);
44305
+ const cameraTarget = React91.useMemo(() => {
44306
+ return [
44307
+ (gridBounds.minX + gridBounds.maxX) / 2,
44308
+ 0,
44309
+ (gridBounds.minZ + gridBounds.maxZ) / 2
44310
+ ];
44311
+ }, [gridBounds]);
44312
+ const gridConfig = React91.useMemo(
44313
+ () => ({
44314
+ ...DEFAULT_GRID_CONFIG,
44315
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
44316
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
44317
+ }),
44318
+ [gridBounds]
44319
+ );
44320
+ const gridToWorld = React91.useCallback(
44321
+ (x, z, y = 0) => {
44322
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
44323
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
44324
+ return [worldX, y * gridConfig.cellSize, worldZ];
44325
+ },
44326
+ [gridBounds, gridConfig]
44327
+ );
44328
+ React91.useImperativeHandle(ref, () => ({
44329
+ getCameraPosition: () => {
44330
+ if (controlsRef.current) {
44331
+ const pos = controlsRef.current.object.position;
44332
+ return new THREE__namespace.Vector3(pos.x, pos.y, pos.z);
44333
+ }
44334
+ return null;
44335
+ },
44336
+ setCameraPosition: (x, y, z) => {
44337
+ if (controlsRef.current) {
44338
+ controlsRef.current.object.position.set(x, y, z);
44339
+ controlsRef.current.update();
44340
+ }
44341
+ },
44342
+ lookAt: (x, y, z) => {
44343
+ if (controlsRef.current) {
44344
+ controlsRef.current.target.set(x, y, z);
44345
+ controlsRef.current.update();
44346
+ }
44347
+ },
44348
+ resetCamera: () => {
44349
+ if (controlsRef.current) {
44350
+ controlsRef.current.reset();
44351
+ }
44352
+ },
44353
+ screenshot: () => {
44354
+ const canvas = containerRef.current?.querySelector("canvas");
44355
+ if (canvas) {
44356
+ return canvas.toDataURL("image/png");
44357
+ }
44358
+ return null;
44359
+ },
44360
+ export: () => ({
44361
+ tiles,
44362
+ units,
44363
+ features
44364
+ })
44365
+ }));
44366
+ const handleTileClick = React91.useCallback(
44367
+ (tile, event) => {
44368
+ eventHandlers.handleTileClick(tile, event);
44369
+ },
44370
+ [eventHandlers]
44371
+ );
44372
+ const handleUnitClick = React91.useCallback(
44373
+ (unit, event) => {
44374
+ eventHandlers.handleUnitClick(unit, event);
44375
+ },
44376
+ [eventHandlers]
44377
+ );
44378
+ const handleFeatureClick = React91.useCallback(
44379
+ (feature, event) => {
44380
+ if (event) {
44381
+ eventHandlers.handleFeatureClick(feature, event);
44382
+ }
44383
+ },
44384
+ [eventHandlers]
44385
+ );
44386
+ const handleTileHover = React91.useCallback(
44387
+ (tile, event) => {
44388
+ setHoveredTile(tile);
44389
+ if (event) {
44390
+ eventHandlers.handleTileHover(tile, event);
44391
+ }
44392
+ },
44393
+ [eventHandlers]
44394
+ );
44395
+ const cameraConfig = React91.useMemo(() => {
44396
+ const size = Math.max(
44397
+ gridBounds.maxX - gridBounds.minX,
44398
+ gridBounds.maxZ - gridBounds.minZ
44399
+ );
44400
+ const distance = size * 1.5;
44401
+ switch (cameraMode) {
44402
+ case "isometric":
44403
+ return {
44404
+ position: [distance, distance * 0.8, distance],
44405
+ fov: 45
44406
+ };
44407
+ case "top-down":
44408
+ return {
44409
+ position: [0, distance * 2, 0],
44410
+ fov: 45
44411
+ };
44412
+ case "perspective":
44413
+ default:
44414
+ return {
44415
+ position: [distance, distance, distance],
44416
+ fov: 45
44417
+ };
44418
+ }
44419
+ }, [cameraMode, gridBounds]);
44420
+ const DefaultTileRenderer = React91.useCallback(
44421
+ ({ tile, position }) => {
44422
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
44423
+ const isHovered = hoveredTile?.id === tile.id;
44424
+ const isValidMove = validMoves.some(
44425
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
44426
+ );
44427
+ const isAttackTarget = attackTargets.some(
44428
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
44429
+ );
44430
+ let color = 8421504;
44431
+ if (tile.type === "water") color = 4491468;
44432
+ else if (tile.type === "grass") color = 4500036;
44433
+ else if (tile.type === "sand") color = 14535816;
44434
+ else if (tile.type === "rock") color = 8947848;
44435
+ else if (tile.type === "snow") color = 15658734;
44436
+ let emissive = 0;
44437
+ if (isSelected) emissive = 4473924;
44438
+ else if (isAttackTarget) emissive = 4456448;
44439
+ else if (isValidMove) emissive = 17408;
44440
+ else if (isHovered) emissive = 2236962;
44441
+ if (tile.modelUrl) {
44442
+ return /* @__PURE__ */ jsxRuntime.jsx(
44443
+ "group",
44444
+ {
44445
+ position,
44446
+ onClick: (e) => handleTileClick(tile, e),
44447
+ onPointerEnter: (e) => handleTileHover(tile, e),
44448
+ onPointerLeave: (e) => handleTileHover(null, e),
44449
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
44450
+ children: /* @__PURE__ */ jsxRuntime.jsx(
44451
+ ModelLoader,
44452
+ {
44453
+ url: tile.modelUrl,
44454
+ scale: 0.95,
44455
+ fallbackGeometry: "box",
44456
+ castShadow: true,
44457
+ receiveShadow: true
44458
+ }
44459
+ )
44460
+ }
44461
+ );
44462
+ }
44463
+ return /* @__PURE__ */ jsxRuntime.jsxs(
44464
+ "mesh",
44465
+ {
44466
+ position,
44467
+ onClick: (e) => handleTileClick(tile, e),
44468
+ onPointerEnter: (e) => handleTileHover(tile, e),
44469
+ onPointerLeave: (e) => handleTileHover(null, e),
44470
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
44471
+ children: [
44472
+ /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
44473
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color, emissive })
44474
+ ]
44475
+ }
44476
+ );
44477
+ },
44478
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
44479
+ );
44480
+ const DefaultUnitRenderer = React91.useCallback(
44481
+ ({ unit, position }) => {
44482
+ const isSelected = selectedUnitId === unit.id;
44483
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
44484
+ return /* @__PURE__ */ jsxRuntime.jsxs(
44485
+ "group",
44486
+ {
44487
+ position,
44488
+ onClick: (e) => handleUnitClick(unit, e),
44489
+ userData: { type: "unit", unitId: unit.id },
44490
+ children: [
44491
+ isSelected && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
44492
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
44493
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
44494
+ ] }),
44495
+ unit.modelUrl ? (
44496
+ /* GLB unit model (box fallback while loading / on error) */
44497
+ /* @__PURE__ */ jsxRuntime.jsx(
44498
+ ModelLoader,
44499
+ {
44500
+ url: unit.modelUrl,
44501
+ scale: 0.5,
44502
+ fallbackGeometry: "box",
44503
+ castShadow: true
44504
+ }
44505
+ )
44506
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
44507
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.3, 0], children: [
44508
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
44509
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
44510
+ ] }),
44511
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.6, 0], children: [
44512
+ /* @__PURE__ */ jsxRuntime.jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
44513
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
44514
+ ] }),
44515
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
44516
+ /* @__PURE__ */ jsxRuntime.jsx("sphereGeometry", { args: [0.12, 8, 8] }),
44517
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
44518
+ ] })
44519
+ ] }),
44520
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("group", { position: [0, 1.2, 0], children: [
44521
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [-0.25, 0, 0], children: [
44522
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5, 0.05] }),
44523
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: 3355443 })
44524
+ ] }),
44525
+ /* @__PURE__ */ jsxRuntime.jsxs(
44526
+ "mesh",
44527
+ {
44528
+ position: [
44529
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
44530
+ 0,
44531
+ 0.01
44532
+ ],
44533
+ children: [
44534
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
44535
+ /* @__PURE__ */ jsxRuntime.jsx(
44536
+ "meshBasicMaterial",
44537
+ {
44538
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
44539
+ }
44540
+ )
44541
+ ]
44542
+ }
44543
+ )
44544
+ ] })
44545
+ ]
44546
+ }
44547
+ );
44548
+ },
44549
+ [selectedUnitId, handleUnitClick]
44550
+ );
44551
+ const DefaultFeatureRenderer = React91.useCallback(
44552
+ ({
44553
+ feature,
44554
+ position
44555
+ }) => {
44556
+ if (feature.assetUrl) {
44557
+ return /* @__PURE__ */ jsxRuntime.jsx(
44558
+ ModelLoader,
44559
+ {
44560
+ url: feature.assetUrl,
44561
+ position,
44562
+ scale: 0.5,
44563
+ rotation: [0, feature.rotation ?? 0, 0],
44564
+ onClick: () => handleFeatureClick(feature, null),
44565
+ fallbackGeometry: "box"
44566
+ },
44567
+ feature.id
44568
+ );
44569
+ }
44570
+ if (feature.type === "tree") {
44571
+ return /* @__PURE__ */ jsxRuntime.jsxs(
44572
+ "group",
44573
+ {
44574
+ position,
44575
+ onClick: (e) => handleFeatureClick(feature, e),
44576
+ userData: { type: "feature", featureId: feature.id },
44577
+ children: [
44578
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.4, 0], children: [
44579
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
44580
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 9127187 })
44581
+ ] }),
44582
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
44583
+ /* @__PURE__ */ jsxRuntime.jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
44584
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 2263842 })
44585
+ ] })
44586
+ ]
44587
+ }
44588
+ );
44589
+ }
44590
+ if (feature.type === "rock") {
44591
+ return /* @__PURE__ */ jsxRuntime.jsxs(
44592
+ "mesh",
44593
+ {
44594
+ position: [position[0], position[1] + 0.3, position[2]],
44595
+ onClick: (e) => handleFeatureClick(feature, e),
44596
+ userData: { type: "feature", featureId: feature.id },
44597
+ children: [
44598
+ /* @__PURE__ */ jsxRuntime.jsx("dodecahedronGeometry", { args: [0.3, 0] }),
44599
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 8421504 })
44600
+ ]
44601
+ }
44602
+ );
44603
+ }
44604
+ return null;
44605
+ },
44606
+ [handleFeatureClick]
44607
+ );
44608
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
44609
+ return /* @__PURE__ */ jsxRuntime.jsx(
44610
+ Canvas3DLoadingState,
44611
+ {
44612
+ progress,
44613
+ loaded,
44614
+ total,
44615
+ message: loadingMessage,
44616
+ className
44617
+ }
44618
+ );
44619
+ }
44620
+ const displayError = externalError || internalError;
44621
+ if (displayError) {
44622
+ return /* @__PURE__ */ jsxRuntime.jsx(Canvas3DErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "game-canvas-3d game-canvas-3d--error", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__error", children: [
44623
+ "Error: ",
44624
+ displayError
44625
+ ] }) }) });
44626
+ }
44627
+ return /* @__PURE__ */ jsxRuntime.jsx(
44628
+ Canvas3DErrorBoundary,
44629
+ {
44630
+ onError: (err) => setInternalError(err.message),
44631
+ onReset: () => setInternalError(null),
44632
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
44633
+ "div",
44634
+ {
44635
+ ref: containerRef,
44636
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
44637
+ "data-orientation": orientation,
44638
+ "data-camera-mode": cameraMode,
44639
+ "data-overlay": overlay,
44640
+ children: [
44641
+ /* @__PURE__ */ jsxRuntime.jsxs(
44642
+ fiber.Canvas,
44643
+ {
44644
+ shadows,
44645
+ camera: {
44646
+ position: cameraConfig.position,
44647
+ fov: cameraConfig.fov,
44648
+ near: 0.1,
44649
+ far: 1e3
44650
+ },
44651
+ style: { background: backgroundColor },
44652
+ onClick: (e) => {
44653
+ if (e.target === e.currentTarget) {
44654
+ eventHandlers.handleCanvasClick(e);
44655
+ }
44656
+ },
44657
+ children: [
44658
+ /* @__PURE__ */ jsxRuntime.jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
44659
+ /* @__PURE__ */ jsxRuntime.jsx("ambientLight", { intensity: 0.6 }),
44660
+ /* @__PURE__ */ jsxRuntime.jsx(
44661
+ "directionalLight",
44662
+ {
44663
+ position: [10, 20, 10],
44664
+ intensity: 0.8,
44665
+ castShadow: shadows,
44666
+ "shadow-mapSize": [2048, 2048]
44667
+ }
44668
+ ),
44669
+ /* @__PURE__ */ jsxRuntime.jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
44670
+ showGrid && /* @__PURE__ */ jsxRuntime.jsx(
44671
+ drei.Grid,
44672
+ {
44673
+ args: [
44674
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
44675
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
44676
+ ],
44677
+ position: [
44678
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
44679
+ 0,
44680
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
44681
+ ],
44682
+ cellSize: 1,
44683
+ cellThickness: 1,
44684
+ cellColor: "#444444",
44685
+ sectionSize: 5,
44686
+ sectionThickness: 1.5,
44687
+ sectionColor: "#666666",
44688
+ fadeDistance: 50,
44689
+ fadeStrength: 1
44690
+ }
44691
+ ),
44692
+ tiles.map((tile, index) => {
44693
+ const position = gridToWorld(
44694
+ tile.x,
44695
+ tile.z ?? tile.y ?? 0,
44696
+ tile.elevation ?? 0
44697
+ );
44698
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
44699
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
44700
+ }),
44701
+ features.map((feature, index) => {
44702
+ const position = gridToWorld(
44703
+ feature.x,
44704
+ feature.z ?? feature.y ?? 0,
44705
+ (feature.elevation ?? 0) + 0.5
44706
+ );
44707
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
44708
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
44709
+ }),
44710
+ units.map((unit) => {
44711
+ const position = gridToWorld(
44712
+ unit.x ?? 0,
44713
+ unit.z ?? unit.y ?? 0,
44714
+ (unit.elevation ?? 0) + 0.5
44715
+ );
44716
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
44717
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { unit, position }, unit.id);
44718
+ }),
44719
+ children,
44720
+ /* @__PURE__ */ jsxRuntime.jsx(
44721
+ drei.OrbitControls,
44722
+ {
44723
+ ref: controlsRef,
44724
+ target: cameraTarget,
44725
+ enableDamping: true,
44726
+ dampingFactor: 0.05,
44727
+ minDistance: 2,
44728
+ maxDistance: 100,
44729
+ maxPolarAngle: Math.PI / 2 - 0.1
44730
+ }
44731
+ )
44732
+ ]
44733
+ }
44734
+ ),
44735
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__coordinates", children: [
44736
+ "X: ",
44737
+ hoveredTile.x,
44738
+ ", Z: ",
44739
+ hoveredTile.z ?? hoveredTile.y ?? 0
44740
+ ] }),
44741
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__tile-info", children: [
44742
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
44743
+ hoveredTile.terrain && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
44744
+ ] })
44745
+ ]
44746
+ }
44747
+ )
44748
+ }
44749
+ );
44750
+ }
44751
+ );
44752
+ GameCanvas3D.displayName = "GameCanvas3D";
44753
+ }
44754
+ });
44755
+ function GameBoard3D({
44756
+ entity,
44757
+ tiles = [],
44758
+ features = [],
44759
+ cameraMode = "perspective",
44760
+ backgroundColor = "#2a1a1a",
44761
+ tileClickEvent,
44762
+ unitClickEvent,
44763
+ attackEvent,
44764
+ endTurnEvent,
44765
+ cancelEvent,
44766
+ playAgainEvent,
44767
+ gameEndEvent,
44768
+ className
44769
+ }) {
44770
+ const row = boardEntity(entity);
44771
+ const eventBus = useEventBus();
44772
+ const entityUnits = row ? rows(row.units) : [];
44773
+ const units = entityUnits;
44774
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
44775
+ const phase = row ? str(row.phase) : "observation";
44776
+ const result = row ? str(row.result) : "none";
44777
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
44778
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
44779
+ const turn = row ? num(row.turn) : 0;
44780
+ const currentTeam = row ? str(row.currentTeam) : "player";
44781
+ const isGameOver = result !== "none";
44782
+ const gameEndEmittedRef = React91.useRef(false);
44783
+ React91.useEffect(() => {
44784
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
44785
+ if (!gameEndEmittedRef.current) {
44786
+ gameEndEmittedRef.current = true;
44787
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44788
+ }
44789
+ } else {
44790
+ gameEndEmittedRef.current = false;
44791
+ }
44792
+ }, [result, gameEndEvent, eventBus]);
44793
+ const checkGameEnd = React91.useCallback(() => {
44794
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
44795
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
44796
+ if (alivePlayer.length === 0 && gameEndEvent) {
44797
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
44798
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
44799
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
44800
+ }
44801
+ }, [entityUnits, gameEndEvent, eventBus]);
44802
+ const handleUnitClickCallback = React91.useCallback((isoUnit) => {
44803
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
44804
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
44805
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
44806
+ if (!attackerRow || !targetRow) return;
44807
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
44808
+ const ap = unitPosition(attackerRow);
44809
+ const tp = unitPosition(targetRow);
44810
+ const dx = Math.abs(ap.x - tp.x);
44811
+ const dy = Math.abs(ap.y - tp.y);
44812
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
44813
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
44814
+ eventBus.emit(`UI:${attackEvent}`, {
44815
+ attackerId: str(attackerRow.id),
44816
+ targetId: str(targetRow.id),
44817
+ damage
44818
+ });
44819
+ setTimeout(checkGameEnd, 100);
44820
+ }
44821
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
44822
+ const handleEndTurn = React91.useCallback(() => {
44823
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
44824
+ }, [endTurnEvent, eventBus]);
44825
+ const handleCancel = React91.useCallback(() => {
44826
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
44827
+ }, [cancelEvent, eventBus]);
44828
+ const handlePlayAgain = React91.useCallback(() => {
44829
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
44830
+ }, [playAgainEvent, eventBus]);
44831
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
44832
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
44833
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
44834
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
44835
+ "\u2014 ",
44836
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
44837
+ ] }),
44838
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
44839
+ "Turn ",
44840
+ turn
44841
+ ] })
44842
+ ] }),
44843
+ /* @__PURE__ */ jsxRuntime.jsx(
44844
+ GameCanvas3D,
44845
+ {
44846
+ tiles,
44847
+ units,
44848
+ features,
44849
+ cameraMode,
44850
+ showGrid: true,
44851
+ showCoordinates: false,
44852
+ showTileInfo: false,
44853
+ shadows: true,
44854
+ backgroundColor,
44855
+ tileClickEvent,
44856
+ unitClickEvent,
44857
+ onUnitClick: handleUnitClickCallback,
44858
+ selectedUnitId,
44859
+ validMoves,
44860
+ attackTargets,
44861
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
44862
+ }
44863
+ ),
44864
+ !isGameOver && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
44865
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsxRuntime.jsx(
44866
+ Button,
44867
+ {
44868
+ variant: "secondary",
44869
+ className: "shadow-xl",
44870
+ onClick: handleCancel,
44871
+ children: "Cancel"
44872
+ }
44873
+ ),
44874
+ phase !== "enemy_turn" && /* @__PURE__ */ jsxRuntime.jsx(
44875
+ Button,
44876
+ {
44877
+ variant: "primary",
44878
+ className: "shadow-xl",
44879
+ onClick: handleEndTurn,
44880
+ children: "End Turn"
44881
+ }
44882
+ )
44883
+ ] }),
44884
+ isGameOver && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "game-board-3d__overlay absolute inset-0 z-20 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { align: "center", gap: "md", className: "overlay__card p-8 rounded-xl bg-gray-900/90 shadow-2xl", children: [
44885
+ /* @__PURE__ */ jsxRuntime.jsx(
44886
+ Typography,
44887
+ {
44888
+ variant: "h2",
44889
+ className: cn(
44890
+ "overlay__result",
44891
+ result === "victory" ? "text-yellow-400" : "text-red-500"
44892
+ ),
44893
+ children: result === "victory" ? "Victory!" : "Defeat"
44894
+ }
44895
+ ),
44896
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
44897
+ "Completed in ",
44898
+ turn,
44899
+ " turn",
44900
+ turn !== 1 ? "s" : ""
44901
+ ] }),
44902
+ /* @__PURE__ */ jsxRuntime.jsx(
44903
+ Button,
44904
+ {
44905
+ variant: "primary",
44906
+ className: "px-8 py-3 font-semibold",
44907
+ onClick: handlePlayAgain,
44908
+ children: "Play Again"
44909
+ }
44910
+ )
44911
+ ] }) })
44912
+ ] });
44913
+ }
44914
+ var init_GameBoard3D = __esm({
44915
+ "components/game/organisms/GameBoard3D.tsx"() {
44916
+ "use client";
44917
+ init_cn();
44918
+ init_Button();
44919
+ init_Typography();
44920
+ init_Stack();
44921
+ init_useEventBus();
44922
+ init_GameCanvas3D2();
44923
+ init_boardEntity();
44924
+ GameBoard3D.displayName = "GameBoard3D";
44925
+ }
44926
+ });
43271
44927
  var GameShell;
43272
44928
  var init_GameShell = __esm({
43273
44929
  "components/game/templates/GameShell.tsx"() {
@@ -44810,6 +46466,236 @@ var init_NegotiatorBoard = __esm({
44810
46466
  NegotiatorBoard.displayName = "NegotiatorBoard";
44811
46467
  }
44812
46468
  });
46469
+ function resolveEntity(entity) {
46470
+ if (!entity) return {};
46471
+ if (Array.isArray(entity)) return entity[0] ?? {};
46472
+ return entity;
46473
+ }
46474
+ function numField(row, key, fallback) {
46475
+ const v = row[key];
46476
+ return typeof v === "number" ? v : fallback;
46477
+ }
46478
+ function strField(row, key, fallback) {
46479
+ const v = row[key];
46480
+ return typeof v === "string" ? v : fallback;
46481
+ }
46482
+ function PlatformerBoard({
46483
+ entity,
46484
+ player: propPlayer,
46485
+ platforms: propPlatforms,
46486
+ result: propResult,
46487
+ lives: propLives,
46488
+ score: propScore,
46489
+ level: propLevel,
46490
+ worldWidth = 800,
46491
+ worldHeight = 400,
46492
+ canvasWidth = 800,
46493
+ canvasHeight = 400,
46494
+ playerSprite,
46495
+ tileSprites,
46496
+ bgColor,
46497
+ leftEvent = "LEFT",
46498
+ rightEvent = "RIGHT",
46499
+ jumpEvent = "JUMP",
46500
+ stopEvent = "STOP",
46501
+ playAgainEvent = "PLAY_AGAIN",
46502
+ gameEndEvent = "GAME_END",
46503
+ className
46504
+ }) {
46505
+ const row = resolveEntity(entity);
46506
+ const eventBus = useEventBus();
46507
+ const { t } = hooks.useTranslate();
46508
+ const result = propResult ?? strField(row, "result", "none");
46509
+ propLives ?? numField(row, "lives", 3);
46510
+ const score = propScore ?? numField(row, "score", 0);
46511
+ const level = propLevel ?? numField(row, "level", 1);
46512
+ const entityPlayer = row["player"];
46513
+ const player = propPlayer ?? entityPlayer ?? {
46514
+ x: 80,
46515
+ y: 320,
46516
+ width: 32,
46517
+ height: 48,
46518
+ vx: 0,
46519
+ vy: 0,
46520
+ grounded: false,
46521
+ facingRight: true
46522
+ };
46523
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
46524
+ const platforms = propPlatforms ?? entityPlatforms ?? [
46525
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
46526
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
46527
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
46528
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
46529
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
46530
+ ];
46531
+ const handleRestart = React91.useCallback(() => {
46532
+ if (playAgainEvent) {
46533
+ eventBus.emit(`UI:${playAgainEvent}`, {});
46534
+ }
46535
+ }, [playAgainEvent, eventBus]);
46536
+ const isGameOver = result === "won" || result === "lost";
46537
+ const gameEndEmittedRef = React91.useRef(false);
46538
+ React91.useEffect(() => {
46539
+ if ((result === "won" || result === "lost") && gameEndEvent) {
46540
+ if (!gameEndEmittedRef.current) {
46541
+ gameEndEmittedRef.current = true;
46542
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
46543
+ }
46544
+ } else {
46545
+ gameEndEmittedRef.current = false;
46546
+ }
46547
+ }, [result, gameEndEvent, eventBus]);
46548
+ return /* @__PURE__ */ jsxRuntime.jsx(
46549
+ VStack,
46550
+ {
46551
+ className: cn("platformer-board relative bg-background", className),
46552
+ gap: "none",
46553
+ children: /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative", children: [
46554
+ /* @__PURE__ */ jsxRuntime.jsx(
46555
+ PlatformerCanvas,
46556
+ {
46557
+ player,
46558
+ platforms,
46559
+ worldWidth,
46560
+ worldHeight,
46561
+ canvasWidth,
46562
+ canvasHeight,
46563
+ followCamera: true,
46564
+ bgColor,
46565
+ playerSprite,
46566
+ tileSprites,
46567
+ leftEvent,
46568
+ rightEvent,
46569
+ jumpEvent,
46570
+ stopEvent
46571
+ }
46572
+ ),
46573
+ isGameOver && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
46574
+ /* @__PURE__ */ jsxRuntime.jsx(
46575
+ Typography,
46576
+ {
46577
+ variant: "h2",
46578
+ className: cn(
46579
+ "text-4xl font-black tracking-widest uppercase",
46580
+ result === "won" ? "text-warning" : "text-error"
46581
+ ),
46582
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
46583
+ }
46584
+ ),
46585
+ result === "won" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
46586
+ t("platformer.level"),
46587
+ " ",
46588
+ level,
46589
+ " \u2014 ",
46590
+ t("platformer.score"),
46591
+ ": ",
46592
+ score
46593
+ ] }),
46594
+ result === "lost" && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
46595
+ /* @__PURE__ */ jsxRuntime.jsx(
46596
+ Button,
46597
+ {
46598
+ variant: "primary",
46599
+ className: "px-8 py-3 font-semibold",
46600
+ onClick: handleRestart,
46601
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
46602
+ }
46603
+ )
46604
+ ] }) })
46605
+ ] })
46606
+ }
46607
+ );
46608
+ }
46609
+ var init_PlatformerBoard = __esm({
46610
+ "components/game/organisms/PlatformerBoard.tsx"() {
46611
+ "use client";
46612
+ init_cn();
46613
+ init_useEventBus();
46614
+ init_Box();
46615
+ init_Button();
46616
+ init_Typography();
46617
+ init_Stack();
46618
+ init_PlatformerCanvas();
46619
+ PlatformerBoard.displayName = "PlatformerBoard";
46620
+ }
46621
+ });
46622
+ function PlatformerTemplate({
46623
+ entity,
46624
+ platforms = DEFAULT_PLATFORMS,
46625
+ worldWidth = 800,
46626
+ worldHeight = 400,
46627
+ canvasWidth = 800,
46628
+ canvasHeight = 400,
46629
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
46630
+ tileSprites = DEFAULT_TILE_SPRITES,
46631
+ bgColor = "#5c94fc",
46632
+ gameEndEvent = "GAME_END",
46633
+ className
46634
+ }) {
46635
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
46636
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
46637
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
46638
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
46639
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
46640
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsx(
46641
+ GameHud,
46642
+ {
46643
+ position: "top",
46644
+ stats: [
46645
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
46646
+ { label: "Level", value: level, format: "number", variant: "default" }
46647
+ ]
46648
+ }
46649
+ ) }),
46650
+ /* @__PURE__ */ jsxRuntime.jsx(
46651
+ PlatformerBoard,
46652
+ {
46653
+ entity,
46654
+ platforms,
46655
+ worldWidth,
46656
+ worldHeight,
46657
+ canvasWidth,
46658
+ canvasHeight,
46659
+ playerSprite,
46660
+ tileSprites,
46661
+ bgColor,
46662
+ leftEvent: "LEFT",
46663
+ rightEvent: "RIGHT",
46664
+ jumpEvent: "JUMP",
46665
+ stopEvent: "STOP",
46666
+ playAgainEvent: "PLAY_AGAIN",
46667
+ gameEndEvent
46668
+ }
46669
+ ),
46670
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(ScoreBoard, { score, level }) })
46671
+ ] });
46672
+ }
46673
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
46674
+ var init_PlatformerTemplate = __esm({
46675
+ "components/game/templates/PlatformerTemplate.tsx"() {
46676
+ init_cn();
46677
+ init_Box();
46678
+ init_Stack();
46679
+ init_PlatformerBoard();
46680
+ init_GameHud();
46681
+ init_ScoreBoard();
46682
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
46683
+ DEFAULT_TILE_SPRITES = {
46684
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
46685
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
46686
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
46687
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
46688
+ };
46689
+ DEFAULT_PLATFORMS = [
46690
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
46691
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
46692
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
46693
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
46694
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
46695
+ ];
46696
+ PlatformerTemplate.displayName = "PlatformerTemplate";
46697
+ }
46698
+ });
44813
46699
  var PricingOrganism;
44814
46700
  var init_PricingOrganism = __esm({
44815
46701
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -44932,6 +46818,356 @@ var init_PricingPageTemplate = __esm({
44932
46818
  PricingPageTemplate.displayName = "PricingPageTemplate";
44933
46819
  }
44934
46820
  });
46821
+ function RoguelikeBoard({
46822
+ entity,
46823
+ tiles: propTiles,
46824
+ enemies: propEnemies,
46825
+ items: propItems,
46826
+ player: propPlayer,
46827
+ playerHp: propPlayerHp,
46828
+ playerMaxHp: propPlayerMaxHp,
46829
+ playerAttack: propPlayerAttack,
46830
+ depth: propDepth,
46831
+ result: propResult,
46832
+ phase: propPhase,
46833
+ stairsX: propStairsX,
46834
+ stairsY: propStairsY,
46835
+ assetManifest: propAssetManifest,
46836
+ scale = 0.45,
46837
+ unitScale = 1,
46838
+ moveEvent,
46839
+ playAgainEvent,
46840
+ gameEndEvent,
46841
+ className
46842
+ }) {
46843
+ const board = boardEntity(entity) ?? {};
46844
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
46845
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
46846
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
46847
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
46848
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
46849
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
46850
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
46851
+ const depth = propDepth ?? num(board.depth, 1);
46852
+ const result = propResult ?? (str(board.result) || "none");
46853
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
46854
+ propStairsX ?? num(board.stairsX, 3);
46855
+ propStairsY ?? num(board.stairsY, 3);
46856
+ const eventBus = useEventBus();
46857
+ const { t } = hooks.useTranslate();
46858
+ const [hoveredTile, setHoveredTile] = React91.useState(null);
46859
+ const emitMove = React91.useCallback((dx, dy) => {
46860
+ if (!moveEvent) return;
46861
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
46862
+ }, [moveEvent, eventBus]);
46863
+ React91.useEffect(() => {
46864
+ const handler = (e) => {
46865
+ if (result !== "none" || phase !== "player_turn") return;
46866
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
46867
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
46868
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
46869
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
46870
+ };
46871
+ window.addEventListener("keydown", handler);
46872
+ return () => window.removeEventListener("keydown", handler);
46873
+ }, [result, phase, emitMove]);
46874
+ React91.useEffect(() => {
46875
+ if ((result === "won" || result === "lost") && gameEndEvent) {
46876
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
46877
+ }
46878
+ }, [result, gameEndEvent, eventBus]);
46879
+ const handleTileClick = React91.useCallback((x, y) => {
46880
+ if (result !== "none" || phase !== "player_turn") return;
46881
+ const dx = x - player.x;
46882
+ const dy = y - player.y;
46883
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
46884
+ emitMove(dx, dy);
46885
+ }
46886
+ }, [result, phase, player, emitMove]);
46887
+ const handleReset = React91.useCallback(() => {
46888
+ if (!playAgainEvent) return;
46889
+ eventBus.emit(`UI:${playAgainEvent}`, {});
46890
+ }, [playAgainEvent, eventBus]);
46891
+ const isoUnits = React91.useMemo(() => {
46892
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
46893
+ return [
46894
+ {
46895
+ id: "player",
46896
+ position: player,
46897
+ name: t("roguelike.player") || "Hero",
46898
+ team: "player",
46899
+ health: playerHp,
46900
+ maxHealth: playerMaxHp,
46901
+ unitType: "player",
46902
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
46903
+ },
46904
+ ...liveEnemies.map((e) => ({
46905
+ id: e.id,
46906
+ position: { x: e.x, y: e.y },
46907
+ name: t("roguelike.enemy") || "Enemy",
46908
+ team: "enemy",
46909
+ health: e.hp,
46910
+ maxHealth: 6,
46911
+ unitType: "enemy",
46912
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
46913
+ }))
46914
+ ];
46915
+ }, [player, enemies, playerHp, playerMaxHp, t]);
46916
+ const isoFeatures = React91.useMemo(
46917
+ () => items.map((it) => ({
46918
+ id: it.id,
46919
+ x: it.x,
46920
+ y: it.y,
46921
+ type: it.kind,
46922
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
46923
+ })),
46924
+ [items]
46925
+ );
46926
+ const validMoves = React91.useMemo(() => {
46927
+ if (result !== "none" || phase !== "player_turn") return [];
46928
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
46929
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
46930
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
46931
+ return tile?.passable ?? false;
46932
+ });
46933
+ }, [result, phase, player, tiles]);
46934
+ const isGameOver = result === "won" || result === "lost";
46935
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
46936
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
46937
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
46938
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
46939
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
46940
+ ] }),
46941
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
46942
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
46943
+ /* @__PURE__ */ jsxRuntime.jsxs(
46944
+ Typography,
46945
+ {
46946
+ variant: "body1",
46947
+ className: cn(
46948
+ "font-bold tabular-nums",
46949
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
46950
+ ),
46951
+ children: [
46952
+ playerHp,
46953
+ " / ",
46954
+ playerMaxHp
46955
+ ]
46956
+ }
46957
+ )
46958
+ ] }),
46959
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
46960
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
46961
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
46962
+ ] }),
46963
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
46964
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
46965
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
46966
+ ] })
46967
+ ] }),
46968
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
46969
+ IsometricCanvas_default,
46970
+ {
46971
+ tiles,
46972
+ units: isoUnits,
46973
+ features: isoFeatures,
46974
+ selectedUnitId: null,
46975
+ validMoves,
46976
+ attackTargets: [],
46977
+ hoveredTile,
46978
+ onTileClick: handleTileClick,
46979
+ onUnitClick: () => void 0,
46980
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
46981
+ onTileLeave: () => setHoveredTile(null),
46982
+ scale,
46983
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
46984
+ assetManifest: propAssetManifest,
46985
+ unitScale
46986
+ }
46987
+ ) }),
46988
+ !isGameOver && /* @__PURE__ */ jsxRuntime.jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
46989
+ { label: "\u2191", dx: 0, dy: -1 },
46990
+ { label: "\u2193", dx: 0, dy: 1 },
46991
+ { label: "\u2190", dx: -1, dy: 0 },
46992
+ { label: "\u2192", dx: 1, dy: 0 }
46993
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsxRuntime.jsx(
46994
+ Button,
46995
+ {
46996
+ variant: "secondary",
46997
+ className: "w-9 h-9 p-0 text-base font-mono",
46998
+ onClick: () => emitMove(dx, dy),
46999
+ disabled: phase !== "player_turn",
47000
+ children: label
47001
+ },
47002
+ label
47003
+ )) }),
47004
+ isGameOver && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/75 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
47005
+ /* @__PURE__ */ jsxRuntime.jsx(
47006
+ Typography,
47007
+ {
47008
+ variant: "h2",
47009
+ className: cn(
47010
+ "text-4xl font-black tracking-widest uppercase",
47011
+ result === "won" ? "text-warning" : "text-error"
47012
+ ),
47013
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
47014
+ }
47015
+ ),
47016
+ result === "won" && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
47017
+ /* @__PURE__ */ jsxRuntime.jsx(
47018
+ Button,
47019
+ {
47020
+ variant: "primary",
47021
+ className: "px-8 py-3 font-semibold",
47022
+ onClick: handleReset,
47023
+ children: t("roguelike.playAgain") || "Play Again"
47024
+ }
47025
+ )
47026
+ ] }) })
47027
+ ] });
47028
+ }
47029
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
47030
+ var init_RoguelikeBoard = __esm({
47031
+ "components/game/organisms/RoguelikeBoard.tsx"() {
47032
+ "use client";
47033
+ init_cn();
47034
+ init_useEventBus();
47035
+ init_Box();
47036
+ init_Button();
47037
+ init_Typography();
47038
+ init_Stack();
47039
+ init_IsometricCanvas();
47040
+ init_boardEntity();
47041
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
47042
+ DEFAULT_TILES2 = [
47043
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47044
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47045
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47046
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47047
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47048
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47049
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47050
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47051
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47052
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47053
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47054
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47055
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47056
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47057
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47058
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47059
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47060
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47061
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
47062
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47063
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47064
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47065
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47066
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47067
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
47068
+ ];
47069
+ DEFAULT_ENEMIES = [
47070
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
47071
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
47072
+ ];
47073
+ DEFAULT_ITEMS = [
47074
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
47075
+ ];
47076
+ FEATURE_SPRITE = {
47077
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
47078
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
47079
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
47080
+ };
47081
+ RoguelikeBoard.displayName = "RoguelikeBoard";
47082
+ }
47083
+ });
47084
+ function RoguelikeTemplate({
47085
+ entity,
47086
+ scale = 0.45,
47087
+ unitScale = 1,
47088
+ tiles = DEFAULT_ROGUELIKE_TILES,
47089
+ enemies,
47090
+ items,
47091
+ stairsX,
47092
+ stairsY,
47093
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
47094
+ gameEndEvent,
47095
+ className
47096
+ }) {
47097
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
47098
+ if (!resolved && !tiles && !assetManifest) return null;
47099
+ return /* @__PURE__ */ jsxRuntime.jsx(
47100
+ RoguelikeBoard,
47101
+ {
47102
+ entity: resolved,
47103
+ tiles,
47104
+ enemies,
47105
+ items,
47106
+ stairsX,
47107
+ stairsY,
47108
+ assetManifest,
47109
+ scale,
47110
+ unitScale,
47111
+ moveEvent: "MOVE",
47112
+ playAgainEvent: "PLAY_AGAIN",
47113
+ gameEndEvent: gameEndEvent ?? "GAME_END",
47114
+ className
47115
+ }
47116
+ );
47117
+ }
47118
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
47119
+ var init_RoguelikeTemplate = __esm({
47120
+ "components/game/templates/RoguelikeTemplate.tsx"() {
47121
+ init_RoguelikeBoard();
47122
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
47123
+ DEFAULT_ROGUELIKE_TILES = [
47124
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47125
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47126
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47127
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47128
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47129
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47130
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47131
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47132
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47133
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47134
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47135
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47136
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47137
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47138
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47139
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47140
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47141
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47142
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
47143
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47144
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47145
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47146
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47147
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47148
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
47149
+ ];
47150
+ DEFAULT_ROGUELIKE_MANIFEST = {
47151
+ baseUrl: CDN5,
47152
+ terrains: {
47153
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
47154
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
47155
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
47156
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
47157
+ },
47158
+ units: {
47159
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
47160
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
47161
+ },
47162
+ features: {
47163
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
47164
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
47165
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
47166
+ }
47167
+ };
47168
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
47169
+ }
47170
+ });
44935
47171
 
44936
47172
  // lib/traitRegistry.ts
44937
47173
  function notifyListeners2() {
@@ -46185,7 +48421,7 @@ function getAllEvents(traits2) {
46185
48421
  function EventDispatcherTab({ traits: traits2, schema }) {
46186
48422
  const eventBus = useEventBus();
46187
48423
  const { t } = hooks.useTranslate();
46188
- const [log13, setLog] = React91__namespace.useState([]);
48424
+ const [log15, setLog] = React91__namespace.useState([]);
46189
48425
  const prevStatesRef = React91__namespace.useRef(/* @__PURE__ */ new Map());
46190
48426
  React91__namespace.useEffect(() => {
46191
48427
  for (const trait of traits2) {
@@ -46249,9 +48485,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
46249
48485
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
46250
48486
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1", children: unavailableEvents.map((event) => /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", className: "opacity-50", children: event }, event)) })
46251
48487
  ] }),
46252
- log13.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
48488
+ log15.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
46253
48489
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
46254
- /* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log13.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
48490
+ /* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log15.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
46255
48491
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-purple-400", children: entry.traitName }),
46256
48492
  " ",
46257
48493
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -48172,9 +50408,9 @@ function VariablePanel({
48172
50408
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
48173
50409
  variables.map((v) => {
48174
50410
  const name = v.name == null ? "" : String(v.name);
48175
- const value = numField(v.value);
48176
- const max = numField(v.max, 100);
48177
- const min = numField(v.min, 0);
50411
+ const value = numField2(v.value);
50412
+ const max = numField2(v.max, 100);
50413
+ const min = numField2(v.min, 0);
48178
50414
  const unit = v.unit == null ? "" : String(v.unit);
48179
50415
  const pct = Math.round((value - min) / (max - min) * 100);
48180
50416
  const isHigh = pct > 80;
@@ -48204,12 +50440,12 @@ function VariablePanel({
48204
50440
  })
48205
50441
  ] });
48206
50442
  }
48207
- var numField;
50443
+ var numField2;
48208
50444
  var init_VariablePanel = __esm({
48209
50445
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
48210
50446
  init_atoms2();
48211
50447
  init_cn();
48212
- numField = (v, fallback = 0) => {
50448
+ numField2 = (v, fallback = 0) => {
48213
50449
  const n = Number(v);
48214
50450
  return Number.isFinite(n) ? n : fallback;
48215
50451
  };
@@ -49062,6 +51298,289 @@ var init_ToastSlot = __esm({
49062
51298
  ToastSlot.displayName = "ToastSlot";
49063
51299
  }
49064
51300
  });
51301
+ function tilesToIso(tiles) {
51302
+ return tiles.map((t) => ({
51303
+ x: t.x,
51304
+ y: t.y,
51305
+ terrain: t.terrain,
51306
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
51307
+ passable: t.passable
51308
+ }));
51309
+ }
51310
+ function towersToUnits(towers) {
51311
+ return towers.map((t) => ({
51312
+ id: t.id,
51313
+ position: { x: t.x, y: t.y },
51314
+ name: "Tower",
51315
+ team: "player",
51316
+ sprite: TOWER_SPRITE,
51317
+ unitType: "guardian",
51318
+ health: 1,
51319
+ maxHealth: 1
51320
+ }));
51321
+ }
51322
+ function creepsToUnits(creeps) {
51323
+ return creeps.map((c) => ({
51324
+ id: c.id,
51325
+ position: { x: c.x, y: c.y },
51326
+ name: "Creep",
51327
+ team: "enemy",
51328
+ sprite: CREEP_SPRITE,
51329
+ unitType: "scrapper",
51330
+ health: c.hp,
51331
+ maxHealth: c.maxHp
51332
+ }));
51333
+ }
51334
+ function pathToFeatures(path) {
51335
+ return path.map((p2, i) => ({
51336
+ id: `path-${i}`,
51337
+ x: p2.x,
51338
+ y: p2.y,
51339
+ type: "path-marker",
51340
+ sprite: `${CDN6}world-map/road_straight.png`
51341
+ }));
51342
+ }
51343
+ function TowerDefenseBoard({
51344
+ entity,
51345
+ tiles: propTiles,
51346
+ path: propPath,
51347
+ towers: propTowers,
51348
+ creeps: propCreeps,
51349
+ gold: propGold,
51350
+ lives: propLives,
51351
+ wave: propWave,
51352
+ maxWaves: propMaxWaves,
51353
+ result: propResult,
51354
+ waveActive: propWaveActive,
51355
+ towerCost = 25,
51356
+ scale = 0.45,
51357
+ placeTowerEvent,
51358
+ startWaveEvent,
51359
+ playAgainEvent,
51360
+ gameEndEvent,
51361
+ className
51362
+ }) {
51363
+ const board = boardEntity(entity) ?? {};
51364
+ const eventBus = useEventBus();
51365
+ const { t } = hooks.useTranslate();
51366
+ const tiles = propTiles ?? rows(board.tiles);
51367
+ const path = propPath ?? rows(board.path);
51368
+ const towers = propTowers ?? rows(board.towers);
51369
+ const creeps = propCreeps ?? rows(board.creeps);
51370
+ const gold = propGold ?? num(board.gold, 100);
51371
+ const lives = propLives ?? num(board.lives, 20);
51372
+ const wave = propWave ?? num(board.wave, 1);
51373
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
51374
+ const result = propResult ?? (str(board.result) || "none");
51375
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
51376
+ const [hoveredTile, setHoveredTile] = React91.useState(null);
51377
+ const emittedGameEnd = React91.useRef(false);
51378
+ if (result !== "none" && !emittedGameEnd.current) {
51379
+ emittedGameEnd.current = true;
51380
+ if (gameEndEvent) {
51381
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
51382
+ }
51383
+ }
51384
+ if (result === "none") {
51385
+ emittedGameEnd.current = false;
51386
+ }
51387
+ const towerPositions = React91.useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
51388
+ const pathPositions = React91.useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
51389
+ const validMoves = React91.useMemo(() => {
51390
+ if (result !== "none" || gold < towerCost) return [];
51391
+ return tiles.filter(
51392
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
51393
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
51394
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
51395
+ const isoTiles = React91.useMemo(() => tilesToIso(tiles), [tiles]);
51396
+ const towerUnits = React91.useMemo(() => towersToUnits(towers), [towers]);
51397
+ const creepUnits = React91.useMemo(() => creepsToUnits(creeps), [creeps]);
51398
+ const isoUnits = React91.useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
51399
+ const pathFeatures = React91.useMemo(() => pathToFeatures(path), [path]);
51400
+ const handleTileClick = React91.useCallback((x, y) => {
51401
+ if (result !== "none") return;
51402
+ if (pathPositions.has(`${x},${y}`)) return;
51403
+ if (towerPositions.has(`${x},${y}`)) return;
51404
+ if (gold < towerCost) return;
51405
+ if (placeTowerEvent) {
51406
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
51407
+ }
51408
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
51409
+ const handleStartWave = React91.useCallback(() => {
51410
+ if (startWaveEvent) {
51411
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
51412
+ }
51413
+ }, [startWaveEvent, wave, eventBus]);
51414
+ const handlePlayAgain = React91.useCallback(() => {
51415
+ if (playAgainEvent) {
51416
+ eventBus.emit(`UI:${playAgainEvent}`, {});
51417
+ }
51418
+ }, [playAgainEvent, eventBus]);
51419
+ const canPlaceTower = gold >= towerCost && result === "none";
51420
+ const canStartWave = !waveActive && result === "none";
51421
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
51422
+ /* @__PURE__ */ jsxRuntime.jsxs(
51423
+ HStack,
51424
+ {
51425
+ className: "px-4 py-2 border-b border-border bg-surface",
51426
+ gap: "lg",
51427
+ align: "center",
51428
+ children: [
51429
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
51430
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
51431
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
51432
+ ] }),
51433
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
51434
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
51435
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
51436
+ ] }),
51437
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
51438
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
51439
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body2", weight: "bold", children: [
51440
+ wave,
51441
+ " / ",
51442
+ maxWaves
51443
+ ] })
51444
+ ] }),
51445
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "ml-auto", children: [
51446
+ canStartWave && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
51447
+ waveActive && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
51448
+ ] })
51449
+ ]
51450
+ }
51451
+ ),
51452
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative flex-1", children: [
51453
+ /* @__PURE__ */ jsxRuntime.jsx(
51454
+ IsometricCanvas_default,
51455
+ {
51456
+ tiles: isoTiles,
51457
+ units: isoUnits,
51458
+ features: pathFeatures,
51459
+ validMoves,
51460
+ hoveredTile,
51461
+ onTileClick: handleTileClick,
51462
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
51463
+ onTileLeave: () => setHoveredTile(null),
51464
+ scale
51465
+ }
51466
+ ),
51467
+ hoveredTile && canPlaceTower && validMoves.some((m) => m.x === hoveredTile.x && m.y === hoveredTile.y) && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "absolute bottom-4 left-1/2 -translate-x-1/2 z-10 bg-background/80 rounded px-3 py-1 backdrop-blur-sm pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", children: t("td.placeTower") ?? `Place tower (${towerCost} gold)` }) })
51468
+ ] }),
51469
+ result !== "none" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
51470
+ /* @__PURE__ */ jsxRuntime.jsx(
51471
+ Typography,
51472
+ {
51473
+ variant: "h2",
51474
+ className: cn(
51475
+ "text-4xl font-black tracking-widest uppercase",
51476
+ result === "won" ? "text-warning" : "text-error"
51477
+ ),
51478
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
51479
+ }
51480
+ ),
51481
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: result === "won" ? t("td.survivedAllWaves") ?? `Survived all ${maxWaves} waves!` : t("td.livesReachedZero") ?? "Your base was overrun." }),
51482
+ /* @__PURE__ */ jsxRuntime.jsx(
51483
+ Button,
51484
+ {
51485
+ variant: "primary",
51486
+ className: "px-8 py-3 font-semibold",
51487
+ onClick: handlePlayAgain,
51488
+ children: t("td.playAgain") ?? "Play Again"
51489
+ }
51490
+ )
51491
+ ] }) })
51492
+ ] });
51493
+ }
51494
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
51495
+ var init_TowerDefenseBoard = __esm({
51496
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
51497
+ "use client";
51498
+ init_cn();
51499
+ init_useEventBus();
51500
+ init_Box();
51501
+ init_Button();
51502
+ init_Typography();
51503
+ init_Stack();
51504
+ init_IsometricCanvas();
51505
+ init_boardEntity();
51506
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
51507
+ TERRAIN_SPRITES = {
51508
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
51509
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
51510
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
51511
+ };
51512
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
51513
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
51514
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
51515
+ }
51516
+ });
51517
+ function TowerDefenseTemplate({
51518
+ entity,
51519
+ title = "Tower Defense",
51520
+ tiles,
51521
+ path,
51522
+ towers,
51523
+ gold,
51524
+ lives,
51525
+ maxWaves,
51526
+ towerCost,
51527
+ scale,
51528
+ placeTowerEvent,
51529
+ startWaveEvent,
51530
+ playAgainEvent,
51531
+ gameEndEvent,
51532
+ className
51533
+ }) {
51534
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
51535
+ return /* @__PURE__ */ jsxRuntime.jsxs(
51536
+ Box,
51537
+ {
51538
+ display: "flex",
51539
+ fullHeight: true,
51540
+ className: cn("tower-defense-template flex-col", className),
51541
+ children: [
51542
+ /* @__PURE__ */ jsxRuntime.jsx(
51543
+ HStack,
51544
+ {
51545
+ gap: "sm",
51546
+ align: "center",
51547
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
51548
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title })
51549
+ }
51550
+ ),
51551
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
51552
+ TowerDefenseBoard,
51553
+ {
51554
+ entity: resolved,
51555
+ tiles,
51556
+ path,
51557
+ towers,
51558
+ gold,
51559
+ lives,
51560
+ maxWaves,
51561
+ towerCost,
51562
+ scale,
51563
+ placeTowerEvent,
51564
+ startWaveEvent,
51565
+ playAgainEvent,
51566
+ gameEndEvent,
51567
+ className: "h-full"
51568
+ }
51569
+ ) })
51570
+ ]
51571
+ }
51572
+ );
51573
+ }
51574
+ var init_TowerDefenseTemplate = __esm({
51575
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
51576
+ init_cn();
51577
+ init_Box();
51578
+ init_Stack();
51579
+ init_Typography();
51580
+ init_TowerDefenseBoard();
51581
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
51582
+ }
51583
+ });
49065
51584
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
49066
51585
  const eventBus = useEventBus();
49067
51586
  const {
@@ -49571,48 +52090,48 @@ function WorldMapTemplate({
49571
52090
  }
49572
52091
  );
49573
52092
  }
49574
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
52093
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
49575
52094
  var init_WorldMapTemplate = __esm({
49576
52095
  "components/game/templates/WorldMapTemplate.tsx"() {
49577
52096
  init_WorldMapBoard();
49578
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
52097
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
49579
52098
  DEFAULT_WORLDMAP_TILES = [
49580
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49581
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49582
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49583
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49584
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49585
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49586
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49587
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49588
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49589
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49590
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49591
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49592
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49593
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49594
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49595
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49596
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49597
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49598
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49599
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49600
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49601
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49602
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49603
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49604
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
52099
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52100
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52101
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52102
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52103
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52104
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52105
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52106
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52107
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52108
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52109
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52110
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52111
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
52112
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52113
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52114
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52115
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52116
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52117
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
52118
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52119
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52120
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52121
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52122
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52123
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
49605
52124
  ];
49606
52125
  DEFAULT_WORLDMAP_UNITS = [
49607
- { id: "h1", position: { x: 1, y: 1 }, unitType: "hero", name: "Amir", team: "player", health: 10, maxHealth: 10, sprite: `${CDN3}/sprite-sheets/amir-sprite-sheet-se.png` },
49608
- { id: "h2", position: { x: 3, y: 3 }, unitType: "scout", name: "Archivist", team: "player", health: 10, maxHealth: 10, sprite: `${CDN3}/sprite-sheets/archivist-sprite-sheet-se.png` }
52126
+ { id: "h1", position: { x: 1, y: 1 }, unitType: "hero", name: "Amir", team: "player", health: 10, maxHealth: 10, sprite: `${CDN7}/sprite-sheets/amir-sprite-sheet-se.png` },
52127
+ { id: "h2", position: { x: 3, y: 3 }, unitType: "scout", name: "Archivist", team: "player", health: 10, maxHealth: 10, sprite: `${CDN7}/sprite-sheets/archivist-sprite-sheet-se.png` }
49609
52128
  ];
49610
52129
  DEFAULT_WORLDMAP_FEATURES = [
49611
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
49612
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
52130
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
52131
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
49613
52132
  ];
49614
52133
  DEFAULT_WORLDMAP_MANIFEST = {
49615
- baseUrl: CDN3,
52134
+ baseUrl: CDN7,
49616
52135
  terrains: {
49617
52136
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
49618
52137
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -49657,7 +52176,7 @@ function lazyThree(name, loader) {
49657
52176
  ThreeWrapper.displayName = `Lazy(${name})`;
49658
52177
  return ThreeWrapper;
49659
52178
  }
49660
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
52179
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
49661
52180
  var init_component_registry_generated = __esm({
49662
52181
  "components/core/organisms/component-registry.generated.ts"() {
49663
52182
  init_AboutPageTemplate();
@@ -49767,6 +52286,7 @@ var init_component_registry_generated = __esm({
49767
52286
  init_FormSectionHeader();
49768
52287
  init_GameAudioProvider();
49769
52288
  init_GameAudioToggle();
52289
+ init_GameBoard3D();
49770
52290
  init_GameCanvas2D();
49771
52291
  init_GameHud();
49772
52292
  init_GameMenu();
@@ -49824,7 +52344,9 @@ var init_component_registry_generated = __esm({
49824
52344
  init_PageHeader();
49825
52345
  init_Pagination();
49826
52346
  init_PatternTile();
52347
+ init_PlatformerBoard();
49827
52348
  init_PlatformerCanvas();
52349
+ init_PlatformerTemplate();
49828
52350
  init_Popover();
49829
52351
  init_PositionedCanvas();
49830
52352
  init_PowerupSlots();
@@ -49848,6 +52370,8 @@ var init_component_registry_generated = __esm({
49848
52370
  init_ResourceBar();
49849
52371
  init_ResourceCounter();
49850
52372
  init_RichBlockEditor();
52373
+ init_RoguelikeBoard();
52374
+ init_RoguelikeTemplate();
49851
52375
  init_RuntimeDebugger2();
49852
52376
  init_ScaledDiagram();
49853
52377
  init_ScoreBoard();
@@ -49920,6 +52444,8 @@ var init_component_registry_generated = __esm({
49920
52444
  init_Toast();
49921
52445
  init_ToastSlot();
49922
52446
  init_Tooltip();
52447
+ init_TowerDefenseBoard();
52448
+ init_TowerDefenseTemplate();
49923
52449
  init_TraitFrame();
49924
52450
  init_TraitSlot();
49925
52451
  init_TrendIndicator();
@@ -49964,7 +52490,7 @@ var init_component_registry_generated = __esm({
49964
52490
  }
49965
52491
  };
49966
52492
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
49967
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
52493
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
49968
52494
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
49969
52495
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
49970
52496
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -50083,8 +52609,9 @@ var init_component_registry_generated = __esm({
50083
52609
  "FormSectionHeader": FormSectionHeader,
50084
52610
  "GameAudioProvider": GameAudioProvider,
50085
52611
  "GameAudioToggle": GameAudioToggle,
52612
+ "GameBoard3D": GameBoard3D,
50086
52613
  "GameCanvas2D": GameCanvas2D,
50087
- "GameCanvas3D": GameCanvas3D,
52614
+ "GameCanvas3D": GameCanvas3D2,
50088
52615
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
50089
52616
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
50090
52617
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -50146,7 +52673,9 @@ var init_component_registry_generated = __esm({
50146
52673
  "PageHeader": PageHeader,
50147
52674
  "Pagination": Pagination,
50148
52675
  "PatternTile": PatternTile,
52676
+ "PlatformerBoard": PlatformerBoard,
50149
52677
  "PlatformerCanvas": PlatformerCanvas,
52678
+ "PlatformerTemplate": PlatformerTemplate,
50150
52679
  "Popover": Popover,
50151
52680
  "PositionedCanvas": PositionedCanvas,
50152
52681
  "PowerupSlots": PowerupSlots,
@@ -50170,6 +52699,8 @@ var init_component_registry_generated = __esm({
50170
52699
  "ResourceBar": ResourceBar,
50171
52700
  "ResourceCounter": ResourceCounter,
50172
52701
  "RichBlockEditor": RichBlockEditor,
52702
+ "RoguelikeBoard": RoguelikeBoard,
52703
+ "RoguelikeTemplate": RoguelikeTemplate,
50173
52704
  "RuntimeDebugger": RuntimeDebugger,
50174
52705
  "ScaledDiagram": ScaledDiagram,
50175
52706
  "ScoreBoard": ScoreBoard,
@@ -50245,6 +52776,8 @@ var init_component_registry_generated = __esm({
50245
52776
  "Toast": Toast,
50246
52777
  "ToastSlot": ToastSlot,
50247
52778
  "Tooltip": Tooltip,
52779
+ "TowerDefenseBoard": TowerDefenseBoard,
52780
+ "TowerDefenseTemplate": TowerDefenseTemplate,
50248
52781
  "TraitFrame": TraitFrame,
50249
52782
  "TraitSlot": TraitSlot,
50250
52783
  "TrendIndicator": TrendIndicator,
@@ -54731,7 +57264,7 @@ function convertObjectProps(props) {
54731
57264
  function convertFnFormLambdasInProps(props) {
54732
57265
  return convertObjectProps(props);
54733
57266
  }
54734
- var log9 = logger.createLogger("almadar:ui:effects:client-handlers");
57267
+ var log11 = logger.createLogger("almadar:ui:effects:client-handlers");
54735
57268
  function createClientEffectHandlers(options) {
54736
57269
  const { eventBus, slotSetter, navigate, notify, callService } = options;
54737
57270
  return {
@@ -54740,10 +57273,10 @@ function createClientEffectHandlers(options) {
54740
57273
  eventBus.emit(prefixedEvent, payload);
54741
57274
  },
54742
57275
  persist: async () => {
54743
- log9.warn("persist is server-side only, ignored on client");
57276
+ log11.warn("persist is server-side only, ignored on client");
54744
57277
  },
54745
57278
  set: () => {
54746
- log9.warn("set is server-side only, ignored on client");
57279
+ log11.warn("set is server-side only, ignored on client");
54747
57280
  },
54748
57281
  callService: async (service, action, params) => {
54749
57282
  if (callService) return callService(service, action, params);
@@ -54772,10 +57305,10 @@ function createClientEffectHandlers(options) {
54772
57305
  slotSetter.addPattern(slot, pattern, props);
54773
57306
  },
54774
57307
  navigate: navigate ?? ((path) => {
54775
- log9.warn("No navigate handler, ignoring", { path });
57308
+ log11.warn("No navigate handler, ignoring", { path });
54776
57309
  }),
54777
57310
  notify: notify ?? ((msg, type) => {
54778
- log9.debug("notify", { type, message: msg });
57311
+ log11.debug("notify", { type, message: msg });
54779
57312
  })
54780
57313
  };
54781
57314
  }
@@ -55940,7 +58473,7 @@ OrbitalThemeProvider.displayName = "OrbitalThemeProvider";
55940
58473
  init_navigation();
55941
58474
  init_verificationRegistry();
55942
58475
  var PERF_NAMESPACE = "almadar:perf:canvas";
55943
- var log10 = logger.createLogger(PERF_NAMESPACE);
58476
+ var log12 = logger.createLogger(PERF_NAMESPACE);
55944
58477
  var RING_SIZE = 50;
55945
58478
  var ring = [];
55946
58479
  var writeIdx = 0;
@@ -55991,7 +58524,7 @@ function perfEnd(name, startToken, detail) {
55991
58524
  }
55992
58525
  }
55993
58526
  push({ name, durationMs, ts: endTs, detail });
55994
- log10.debug(name, () => ({ durationMs, ...detail ?? {} }));
58527
+ log12.debug(name, () => ({ durationMs, ...detail ?? {} }));
55995
58528
  }
55996
58529
  var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, commitTime) => {
55997
58530
  if (!isEnabled()) return;
@@ -56001,7 +58534,7 @@ var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, com
56001
58534
  ts: commitTime,
56002
58535
  detail: { baseDuration }
56003
58536
  });
56004
- log10.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
58537
+ log12.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
56005
58538
  };
56006
58539
 
56007
58540
  // runtime/prepareSchemaForPreview.ts
@@ -56644,7 +59177,7 @@ init_useEventBus();
56644
59177
  // components/avl/molecules/useCanvasDnd.tsx
56645
59178
  init_useEventBus();
56646
59179
  init_useAlmadarDndCollision();
56647
- var log11 = logger.createLogger("almadar:ui:canvas-dnd");
59180
+ var log13 = logger.createLogger("almadar:ui:canvas-dnd");
56648
59181
  function useCanvasDraggable({
56649
59182
  id,
56650
59183
  payload,
@@ -56683,7 +59216,7 @@ function defaultEmit(eventBus, drop) {
56683
59216
  if (payload.kind === "pattern") {
56684
59217
  const patternType = payload.data.type;
56685
59218
  if (typeof patternType !== "string") {
56686
- log11.warn("default-emit:pattern:missing-type");
59219
+ log13.warn("default-emit:pattern:missing-type");
56687
59220
  return;
56688
59221
  }
56689
59222
  const out = { patternType, containerNode: target.containerNode };
@@ -56692,23 +59225,23 @@ function defaultEmit(eventBus, drop) {
56692
59225
  out.index = resolved.index;
56693
59226
  }
56694
59227
  eventBus.emit("UI:PATTERN_DROP", out);
56695
- log11.info("default-emit:pattern", { patternType, level: target.level });
59228
+ log13.info("default-emit:pattern", { patternType, level: target.level });
56696
59229
  return;
56697
59230
  }
56698
59231
  if (payload.kind === "behavior") {
56699
59232
  const behaviorName = payload.data.name;
56700
59233
  if (typeof behaviorName !== "string") {
56701
- log11.warn("default-emit:behavior:missing-name");
59234
+ log13.warn("default-emit:behavior:missing-name");
56702
59235
  return;
56703
59236
  }
56704
59237
  eventBus.emit("UI:BEHAVIOR_DROP", {
56705
59238
  behaviorName,
56706
59239
  containerNode: target.containerNode
56707
59240
  });
56708
- log11.info("default-emit:behavior", { behaviorName, level: target.level });
59241
+ log13.info("default-emit:behavior", { behaviorName, level: target.level });
56709
59242
  return;
56710
59243
  }
56711
- log11.debug("default-emit:unhandled-kind", { kind: payload.kind });
59244
+ log13.debug("default-emit:unhandled-kind", { kind: payload.kind });
56712
59245
  }
56713
59246
  function CanvasDndProvider({
56714
59247
  children,
@@ -56724,9 +59257,9 @@ function CanvasDndProvider({
56724
59257
  if (payload) {
56725
59258
  setActivePayload(payload);
56726
59259
  eventBus.emit("UI:DRAG_START", { kind: payload.kind, data: payload.data });
56727
- log11.info("dragStart", { id: e.active.id, kind: payload.kind });
59260
+ log13.info("dragStart", { id: e.active.id, kind: payload.kind });
56728
59261
  } else {
56729
- log11.warn("dragStart:missing-payload", { id: e.active.id });
59262
+ log13.warn("dragStart:missing-payload", { id: e.active.id });
56730
59263
  }
56731
59264
  }, [eventBus]);
56732
59265
  const handleDragEnd = React91__namespace.default.useCallback((e) => {
@@ -56736,7 +59269,7 @@ function CanvasDndProvider({
56736
59269
  const overData = e.over?.data.current;
56737
59270
  const target = overData?.target;
56738
59271
  const accepts = overData?.accepts;
56739
- log11.info("dragEnd", {
59272
+ log13.info("dragEnd", {
56740
59273
  activeId: e.active.id,
56741
59274
  overId: e.over?.id,
56742
59275
  hasPayload: !!payload,
@@ -56748,7 +59281,7 @@ function CanvasDndProvider({
56748
59281
  }
56749
59282
  if (!payload || !target) return;
56750
59283
  if (accepts && !accepts.includes(payload.kind)) {
56751
- log11.debug("dragEnd:rejected:kind", { kind: payload.kind, accepts: [...accepts] });
59284
+ log13.debug("dragEnd:rejected:kind", { kind: payload.kind, accepts: [...accepts] });
56752
59285
  return;
56753
59286
  }
56754
59287
  const activator = e.activatorEvent;
@@ -56760,7 +59293,7 @@ function CanvasDndProvider({
56760
59293
  }, [eventBus, onDrop]);
56761
59294
  const handleDragCancel = React91__namespace.default.useCallback(() => {
56762
59295
  setActivePayload(null);
56763
- log11.info("dragCancel");
59296
+ log13.info("dragCancel");
56764
59297
  }, []);
56765
59298
  return /* @__PURE__ */ jsxRuntime.jsxs(
56766
59299
  core$1.DndContext,
@@ -58575,7 +61108,7 @@ init_AvlTransitionLane();
58575
61108
  init_AvlSwimLane();
58576
61109
  init_types();
58577
61110
  init_avl_elk_layout();
58578
- var log12 = logger.createLogger("almadar:ui:avl:trait-scene");
61111
+ var log14 = logger.createLogger("almadar:ui:avl:trait-scene");
58579
61112
  var SWIM_GUTTER2 = 120;
58580
61113
  var CENTER_W2 = 360;
58581
61114
  var AvlTraitScene = ({
@@ -58588,7 +61121,7 @@ var AvlTraitScene = ({
58588
61121
  const dataKey = React91.useMemo(() => JSON.stringify(data), [data]);
58589
61122
  React91.useEffect(() => {
58590
61123
  computeTraitLayout(data).then(setLayout).catch((error) => {
58591
- log12.error("computeTraitLayout failed", { error: error instanceof Error ? error : String(error) });
61124
+ log14.error("computeTraitLayout failed", { error: error instanceof Error ? error : String(error) });
58592
61125
  });
58593
61126
  }, [dataKey]);
58594
61127
  if (!layout) {