@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
@@ -41,6 +41,12 @@ var core$1 = require('@dnd-kit/core');
41
41
  var sortable = require('@dnd-kit/sortable');
42
42
  var utilities = require('@dnd-kit/utilities');
43
43
  var react = require('@xyflow/react');
44
+ var THREE = require('three');
45
+ var GLTFLoader_js = require('three/examples/jsm/loaders/GLTFLoader.js');
46
+ var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
47
+ var GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader');
48
+ var fiber = require('@react-three/fiber');
49
+ var drei = require('@react-three/drei');
44
50
  var patterns = require('@almadar/patterns');
45
51
 
46
52
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -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;
@@ -719,7 +726,7 @@ var init_Box = __esm({
719
726
  position,
720
727
  className,
721
728
  children,
722
- as: Component = "div",
729
+ as: Component2 = "div",
723
730
  action,
724
731
  actionPayload,
725
732
  hoverEvent,
@@ -751,7 +758,7 @@ var init_Box = __esm({
751
758
  }, [hoverEvent, eventBus, onMouseLeave]);
752
759
  const isClickable = action || onClick;
753
760
  return React83__namespace.default.createElement(
754
- Component,
761
+ Component2,
755
762
  {
756
763
  ref,
757
764
  className: cn(
@@ -889,9 +896,9 @@ function resolvePhosphor(name, weight, family) {
889
896
  (lib) => {
890
897
  const PhosphorComp = lib[target];
891
898
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
892
- const Component = PhosphorComp;
899
+ const Component2 = PhosphorComp;
893
900
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
894
- Component,
901
+ Component2,
895
902
  {
896
903
  weight,
897
904
  className: props.className,
@@ -915,9 +922,9 @@ function resolveTabler(name, family) {
915
922
  (lib) => {
916
923
  const TablerComp = lib[target];
917
924
  if (!TablerComp || typeof TablerComp !== "object") return null;
918
- const Component = TablerComp;
925
+ const Component2 = TablerComp;
919
926
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
920
- Component,
927
+ Component2,
921
928
  {
922
929
  stroke: props.strokeWidth ?? 1.5,
923
930
  className: props.className,
@@ -941,9 +948,9 @@ function resolveFa(name, family) {
941
948
  (lib) => {
942
949
  const FaComp = lib[target];
943
950
  if (!FaComp || typeof FaComp !== "function") return null;
944
- const Component = FaComp;
951
+ const Component2 = FaComp;
945
952
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
946
- Component,
953
+ Component2,
947
954
  {
948
955
  className: props.className,
949
956
  style: props.style,
@@ -1898,8 +1905,8 @@ var init_Typography = __esm({
1898
1905
  children
1899
1906
  }) => {
1900
1907
  const variant = variantProp ?? (level ? `h${level}` : "body1");
1901
- const Component = as || defaultElements[variant];
1902
- const Comp = Component;
1908
+ const Component2 = as || defaultElements[variant];
1909
+ const Comp = Component2;
1903
1910
  return /* @__PURE__ */ jsxRuntime.jsx(
1904
1911
  Comp,
1905
1912
  {
@@ -4454,10 +4461,10 @@ var init_Center = __esm({
4454
4461
  className,
4455
4462
  style,
4456
4463
  children,
4457
- as: Component = "div"
4464
+ as: Component2 = "div"
4458
4465
  }) => {
4459
4466
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
4460
- const Comp = Component;
4467
+ const Comp = Component2;
4461
4468
  return /* @__PURE__ */ jsxRuntime.jsx(
4462
4469
  Comp,
4463
4470
  {
@@ -5088,7 +5095,7 @@ var init_Stack = __esm({
5088
5095
  className,
5089
5096
  style,
5090
5097
  children,
5091
- as: Component = "div",
5098
+ as: Component2 = "div",
5092
5099
  onClick,
5093
5100
  onKeyDown,
5094
5101
  role,
@@ -5106,7 +5113,7 @@ var init_Stack = __esm({
5106
5113
  };
5107
5114
  const isHorizontal = direction === "horizontal";
5108
5115
  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";
5109
- const Comp = Component;
5116
+ const Comp = Component2;
5110
5117
  return /* @__PURE__ */ jsxRuntime.jsx(
5111
5118
  Comp,
5112
5119
  {
@@ -7162,6 +7169,7 @@ var init_HealthBar = __esm({
7162
7169
  }
7163
7170
  });
7164
7171
  function ScoreDisplay({
7172
+ assetUrl = DEFAULT_ASSET_URL,
7165
7173
  value,
7166
7174
  label,
7167
7175
  icon,
@@ -7208,14 +7216,24 @@ function ScoreDisplay({
7208
7216
  className
7209
7217
  ),
7210
7218
  children: [
7211
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) }),
7219
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7220
+ "img",
7221
+ {
7222
+ src: assetUrl,
7223
+ alt: "",
7224
+ width: 20,
7225
+ height: 20,
7226
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7227
+ className: "flex-shrink-0"
7228
+ }
7229
+ ) : 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,
7212
7230
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: label }),
7213
7231
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: formattedValue })
7214
7232
  ]
7215
7233
  }
7216
7234
  );
7217
7235
  }
7218
- var sizeMap2;
7236
+ var sizeMap2, DEFAULT_ASSET_URL;
7219
7237
  var init_ScoreDisplay = __esm({
7220
7238
  "components/game/atoms/ScoreDisplay.tsx"() {
7221
7239
  "use client";
@@ -7227,10 +7245,12 @@ var init_ScoreDisplay = __esm({
7227
7245
  lg: "text-2xl",
7228
7246
  xl: "text-4xl"
7229
7247
  };
7248
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
7230
7249
  ScoreDisplay.displayName = "ScoreDisplay";
7231
7250
  }
7232
7251
  });
7233
7252
  function ControlButton({
7253
+ assetUrl = DEFAULT_ASSET_URL2,
7234
7254
  label,
7235
7255
  icon,
7236
7256
  size = "md",
@@ -7277,7 +7297,7 @@ function ControlButton({
7277
7297
  },
7278
7298
  [isPressed, releaseEvent, eventBus, onRelease]
7279
7299
  );
7280
- return /* @__PURE__ */ jsxRuntime.jsxs(
7300
+ return /* @__PURE__ */ jsxRuntime.jsx(
7281
7301
  "button",
7282
7302
  {
7283
7303
  type: "button",
@@ -7298,20 +7318,27 @@ function ControlButton({
7298
7318
  disabled && "opacity-50 cursor-not-allowed",
7299
7319
  className
7300
7320
  ),
7301
- children: [
7302
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7303
- const I = resolveIcon(icon);
7304
- return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" }) : null;
7305
- })() : icon : /* @__PURE__ */ (() => {
7306
- const I = icon;
7307
- return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" });
7308
- })() }),
7309
- label && !icon && /* @__PURE__ */ jsxRuntime.jsx("span", { children: label })
7310
- ]
7321
+ children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7322
+ "img",
7323
+ {
7324
+ src: assetUrl,
7325
+ alt: "",
7326
+ width: 24,
7327
+ height: 24,
7328
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7329
+ className: "flex-shrink-0"
7330
+ }
7331
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7332
+ const I = resolveIcon(icon);
7333
+ return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" }) : null;
7334
+ })() : icon : /* @__PURE__ */ (() => {
7335
+ const I = icon;
7336
+ return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" });
7337
+ })() }) : label ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }) : null
7311
7338
  }
7312
7339
  );
7313
7340
  }
7314
- var sizeMap3, shapeMap, variantMap;
7341
+ var sizeMap3, shapeMap, variantMap, DEFAULT_ASSET_URL2;
7315
7342
  var init_ControlButton = __esm({
7316
7343
  "components/game/atoms/ControlButton.tsx"() {
7317
7344
  "use client";
@@ -7334,6 +7361,7 @@ var init_ControlButton = __esm({
7334
7361
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
7335
7362
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
7336
7363
  };
7364
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
7337
7365
  ControlButton.displayName = "ControlButton";
7338
7366
  }
7339
7367
  });
@@ -7413,7 +7441,7 @@ var init_Sprite = __esm({
7413
7441
  }
7414
7442
  });
7415
7443
  function StateIndicator({
7416
- assetUrl = DEFAULT_ASSET_URL,
7444
+ assetUrl = DEFAULT_ASSET_URL3,
7417
7445
  state = "idle",
7418
7446
  label,
7419
7447
  size = "md",
@@ -7452,13 +7480,13 @@ function StateIndicator({
7452
7480
  }
7453
7481
  );
7454
7482
  }
7455
- var DEFAULT_ASSET_URL, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7483
+ var DEFAULT_ASSET_URL3, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7456
7484
  var init_StateIndicator = __esm({
7457
7485
  "components/game/atoms/StateIndicator.tsx"() {
7458
7486
  init_Box();
7459
7487
  init_Icon();
7460
7488
  init_cn();
7461
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7489
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7462
7490
  DEFAULT_STATE_STYLES = {
7463
7491
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
7464
7492
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -7533,7 +7561,7 @@ var init_TimerDisplay = __esm({
7533
7561
  }
7534
7562
  });
7535
7563
  function ResourceCounter({
7536
- assetUrl = DEFAULT_ASSET_URL2,
7564
+ assetUrl = DEFAULT_ASSET_URL4,
7537
7565
  icon,
7538
7566
  label = "Gold",
7539
7567
  value = 250,
@@ -7576,7 +7604,7 @@ function ResourceCounter({
7576
7604
  }
7577
7605
  );
7578
7606
  }
7579
- var colorTokenClasses2, DEFAULT_ASSET_URL2, sizeMap5;
7607
+ var colorTokenClasses2, DEFAULT_ASSET_URL4, sizeMap5;
7580
7608
  var init_ResourceCounter = __esm({
7581
7609
  "components/game/atoms/ResourceCounter.tsx"() {
7582
7610
  init_cn();
@@ -7589,7 +7617,7 @@ var init_ResourceCounter = __esm({
7589
7617
  error: "text-error",
7590
7618
  muted: "text-muted-foreground"
7591
7619
  };
7592
- DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7620
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7593
7621
  sizeMap5 = {
7594
7622
  sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
7595
7623
  md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
@@ -7599,7 +7627,7 @@ var init_ResourceCounter = __esm({
7599
7627
  }
7600
7628
  });
7601
7629
  function ItemSlot({
7602
- assetUrl = DEFAULT_ASSET_URL3,
7630
+ assetUrl = DEFAULT_ASSET_URL5,
7603
7631
  icon = "sword",
7604
7632
  label = "Iron Sword",
7605
7633
  quantity,
@@ -7658,7 +7686,7 @@ function ItemSlot({
7658
7686
  }
7659
7687
  );
7660
7688
  }
7661
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL3, assetSizeMap;
7689
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL5, assetSizeMap;
7662
7690
  var init_ItemSlot = __esm({
7663
7691
  "components/game/atoms/ItemSlot.tsx"() {
7664
7692
  "use client";
@@ -7683,7 +7711,7 @@ var init_ItemSlot = __esm({
7683
7711
  epic: "shadow-lg",
7684
7712
  legendary: "shadow-lg"
7685
7713
  };
7686
- DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7714
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7687
7715
  assetSizeMap = {
7688
7716
  sm: 28,
7689
7717
  md: 40,
@@ -7693,6 +7721,7 @@ var init_ItemSlot = __esm({
7693
7721
  }
7694
7722
  });
7695
7723
  function TurnIndicator({
7724
+ assetUrl = DEFAULT_ASSET_URL6,
7696
7725
  currentTurn = 1,
7697
7726
  maxTurns,
7698
7727
  activeTeam,
@@ -7725,14 +7754,24 @@ function TurnIndicator({
7725
7754
  ] }),
7726
7755
  activeTeam && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7727
7756
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "|" }),
7728
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7757
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7758
+ "img",
7759
+ {
7760
+ src: assetUrl,
7761
+ alt: "",
7762
+ width: 12,
7763
+ height: 12,
7764
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7765
+ className: "flex-shrink-0"
7766
+ }
7767
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7729
7768
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-success", children: activeTeam })
7730
7769
  ] })
7731
7770
  ]
7732
7771
  }
7733
7772
  );
7734
7773
  }
7735
- var sizeMap7;
7774
+ var sizeMap7, DEFAULT_ASSET_URL6;
7736
7775
  var init_TurnIndicator = __esm({
7737
7776
  "components/game/atoms/TurnIndicator.tsx"() {
7738
7777
  init_cn();
@@ -7741,6 +7780,7 @@ var init_TurnIndicator = __esm({
7741
7780
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
7742
7781
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
7743
7782
  };
7783
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
7744
7784
  TurnIndicator.displayName = "TurnIndicator";
7745
7785
  }
7746
7786
  });
@@ -7756,7 +7796,7 @@ function getComboScale(combo) {
7756
7796
  return "";
7757
7797
  }
7758
7798
  function ComboCounter({
7759
- assetUrl = DEFAULT_ASSET_URL4,
7799
+ assetUrl = DEFAULT_ASSET_URL7,
7760
7800
  combo = 5,
7761
7801
  multiplier,
7762
7802
  streak,
@@ -7801,11 +7841,11 @@ function ComboCounter({
7801
7841
  }
7802
7842
  );
7803
7843
  }
7804
- var DEFAULT_ASSET_URL4, sizeMap8;
7844
+ var DEFAULT_ASSET_URL7, sizeMap8;
7805
7845
  var init_ComboCounter = __esm({
7806
7846
  "components/game/atoms/ComboCounter.tsx"() {
7807
7847
  init_cn();
7808
- DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7848
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7809
7849
  sizeMap8 = {
7810
7850
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
7811
7851
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -7899,7 +7939,7 @@ var init_XPBar = __esm({
7899
7939
  }
7900
7940
  });
7901
7941
  function WaypointMarker({
7902
- assetUrl = DEFAULT_ASSET_URL5,
7942
+ assetUrl = DEFAULT_ASSET_URL8,
7903
7943
  label,
7904
7944
  icon,
7905
7945
  active = true,
@@ -7965,12 +8005,12 @@ function WaypointMarker({
7965
8005
  )
7966
8006
  ] });
7967
8007
  }
7968
- var DEFAULT_ASSET_URL5, sizeMap10, checkIcon;
8008
+ var DEFAULT_ASSET_URL8, sizeMap10, checkIcon;
7969
8009
  var init_WaypointMarker = __esm({
7970
8010
  "components/game/atoms/WaypointMarker.tsx"() {
7971
8011
  init_cn();
7972
8012
  init_Icon();
7973
- DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
8013
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7974
8014
  sizeMap10 = {
7975
8015
  sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
7976
8016
  md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
@@ -7987,7 +8027,7 @@ function formatDuration(seconds) {
7987
8027
  return `${Math.round(seconds)}s`;
7988
8028
  }
7989
8029
  function StatusEffect({
7990
- assetUrl = DEFAULT_ASSET_URL6,
8030
+ assetUrl = DEFAULT_ASSET_URL9,
7991
8031
  icon = "shield",
7992
8032
  label = "Shield",
7993
8033
  duration = 30,
@@ -8045,12 +8085,12 @@ function StatusEffect({
8045
8085
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
8046
8086
  ] });
8047
8087
  }
8048
- var DEFAULT_ASSET_URL6, sizeMap11, variantStyles7;
8088
+ var DEFAULT_ASSET_URL9, sizeMap11, variantStyles7;
8049
8089
  var init_StatusEffect = __esm({
8050
8090
  "components/game/atoms/StatusEffect.tsx"() {
8051
8091
  init_cn();
8052
8092
  init_Icon();
8053
- DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
8093
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
8054
8094
  sizeMap11 = {
8055
8095
  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 },
8056
8096
  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 },
@@ -8065,6 +8105,7 @@ var init_StatusEffect = __esm({
8065
8105
  }
8066
8106
  });
8067
8107
  function DamageNumber({
8108
+ assetUrl = DEFAULT_ASSET_URL10,
8068
8109
  value = 42,
8069
8110
  type = "damage",
8070
8111
  size = "md",
@@ -8073,22 +8114,35 @@ function DamageNumber({
8073
8114
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
8074
8115
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8075
8116
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: floatKeyframes }),
8076
- /* @__PURE__ */ jsxRuntime.jsx(
8117
+ /* @__PURE__ */ jsxRuntime.jsxs(
8077
8118
  "span",
8078
8119
  {
8079
8120
  className: cn(
8080
- "inline-block font-mono select-none pointer-events-none",
8121
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
8081
8122
  sizeMap12[size],
8082
8123
  typeStyles[type],
8083
8124
  className
8084
8125
  ),
8085
8126
  style: { animation: "damageFloat 1s ease-out forwards" },
8086
- children: displayText
8127
+ children: [
8128
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
8129
+ "img",
8130
+ {
8131
+ src: assetUrl,
8132
+ alt: "",
8133
+ width: 14,
8134
+ height: 14,
8135
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8136
+ className: "flex-shrink-0"
8137
+ }
8138
+ ),
8139
+ displayText
8140
+ ]
8087
8141
  }
8088
8142
  )
8089
8143
  ] });
8090
8144
  }
8091
- var sizeMap12, typeStyles, floatKeyframes;
8145
+ var sizeMap12, typeStyles, floatKeyframes, DEFAULT_ASSET_URL10;
8092
8146
  var init_DamageNumber = __esm({
8093
8147
  "components/game/atoms/DamageNumber.tsx"() {
8094
8148
  init_cn();
@@ -8110,6 +8164,7 @@ var init_DamageNumber = __esm({
8110
8164
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
8111
8165
  }
8112
8166
  `;
8167
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
8113
8168
  DamageNumber.displayName = "DamageNumber";
8114
8169
  }
8115
8170
  });
@@ -8198,6 +8253,7 @@ var init_ChoiceButton = __esm({
8198
8253
  }
8199
8254
  });
8200
8255
  function ActionButton({
8256
+ assetUrl = DEFAULT_ASSET_URL11,
8201
8257
  label = "Attack",
8202
8258
  icon,
8203
8259
  cooldown = 0,
@@ -8237,13 +8293,23 @@ function ActionButton({
8237
8293
  }
8238
8294
  }
8239
8295
  ),
8240
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8296
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
8297
+ "img",
8298
+ {
8299
+ src: assetUrl,
8300
+ alt: "",
8301
+ width: 16,
8302
+ height: 16,
8303
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8304
+ className: cn("flex-shrink-0", sizes.icon)
8305
+ }
8306
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8241
8307
  const I = resolveIcon(icon);
8242
8308
  return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-4 h-4" }) : null;
8243
8309
  })() : /* @__PURE__ */ (() => {
8244
8310
  const I = icon;
8245
8311
  return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-4 h-4" });
8246
- })() }),
8312
+ })() }) : null,
8247
8313
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative z-10", children: label }),
8248
8314
  hotkey && /* @__PURE__ */ jsxRuntime.jsx(
8249
8315
  "span",
@@ -8259,7 +8325,7 @@ function ActionButton({
8259
8325
  }
8260
8326
  );
8261
8327
  }
8262
- var sizeMap13, variantStyles8;
8328
+ var sizeMap13, variantStyles8, DEFAULT_ASSET_URL11;
8263
8329
  var init_ActionButton = __esm({
8264
8330
  "components/game/atoms/ActionButton.tsx"() {
8265
8331
  init_cn();
@@ -8274,6 +8340,7 @@ var init_ActionButton = __esm({
8274
8340
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
8275
8341
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
8276
8342
  };
8343
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
8277
8344
  ActionButton.displayName = "ActionButton";
8278
8345
  }
8279
8346
  });
@@ -9142,10 +9209,10 @@ var init_Container = __esm({
9142
9209
  center = true,
9143
9210
  className,
9144
9211
  children,
9145
- as: Component = "div"
9212
+ as: Component2 = "div"
9146
9213
  }) => {
9147
9214
  const resolvedSize = maxWidth ?? size ?? "lg";
9148
- const Comp = Component;
9215
+ const Comp = Component2;
9149
9216
  return /* @__PURE__ */ jsxRuntime.jsx(
9150
9217
  Comp,
9151
9218
  {
@@ -25139,7 +25206,7 @@ var init_Flex = __esm({
25139
25206
  basis,
25140
25207
  className,
25141
25208
  children,
25142
- as: Component = "div"
25209
+ as: Component2 = "div"
25143
25210
  }) => {
25144
25211
  const flexStyle = {};
25145
25212
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -25151,7 +25218,7 @@ var init_Flex = __esm({
25151
25218
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
25152
25219
  }
25153
25220
  }
25154
- const Comp = Component;
25221
+ const Comp = Component2;
25155
25222
  return /* @__PURE__ */ jsxRuntime.jsx(
25156
25223
  Comp,
25157
25224
  {
@@ -25271,11 +25338,11 @@ var init_Grid = __esm({
25271
25338
  className,
25272
25339
  style,
25273
25340
  children,
25274
- as: Component = "div"
25341
+ as: Component2 = "div"
25275
25342
  }) => {
25276
25343
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
25277
25344
  return React83__namespace.default.createElement(
25278
- Component,
25345
+ Component2,
25279
25346
  {
25280
25347
  className: cn(
25281
25348
  "grid",
@@ -27101,6 +27168,7 @@ var init_ProgressDots = __esm({
27101
27168
  }
27102
27169
  });
27103
27170
  function StatBadge({
27171
+ assetUrl = DEFAULT_ASSET_URL12,
27104
27172
  label,
27105
27173
  value = 0,
27106
27174
  max,
@@ -27124,7 +27192,17 @@ function StatBadge({
27124
27192
  className
27125
27193
  ),
27126
27194
  children: [
27127
- 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" }) }),
27195
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
27196
+ "img",
27197
+ {
27198
+ src: assetUrl,
27199
+ alt: "",
27200
+ width: 16,
27201
+ height: 16,
27202
+ style: { imageRendering: "pixelated", objectFit: "contain" },
27203
+ className: "flex-shrink-0"
27204
+ }
27205
+ ) : 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,
27128
27206
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground font-medium", children: label }),
27129
27207
  format === "hearts" && max && /* @__PURE__ */ jsxRuntime.jsx(
27130
27208
  HealthBar,
@@ -27157,7 +27235,7 @@ function StatBadge({
27157
27235
  }
27158
27236
  );
27159
27237
  }
27160
- var sizeMap17, variantMap2;
27238
+ var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
27161
27239
  var init_StatBadge = __esm({
27162
27240
  "components/game/molecules/StatBadge.tsx"() {
27163
27241
  init_cn();
@@ -27176,6 +27254,7 @@ var init_StatBadge = __esm({
27176
27254
  warning: "bg-warning/15 border-warning/40 text-foreground",
27177
27255
  danger: "bg-error/15 border-error/40 text-foreground"
27178
27256
  };
27257
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
27179
27258
  StatBadge.displayName = "StatBadge";
27180
27259
  }
27181
27260
  });
@@ -27832,6 +27911,7 @@ var init_TurnPanel = __esm({
27832
27911
  }
27833
27912
  });
27834
27913
  function EnemyPlate({
27914
+ assetUrl = DEFAULT_ASSET_URL13,
27835
27915
  name = "Shadow Guard",
27836
27916
  health = 80,
27837
27917
  maxHealth = 100,
@@ -27849,15 +27929,28 @@ function EnemyPlate({
27849
27929
  ),
27850
27930
  children: [
27851
27931
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
27852
- /* @__PURE__ */ jsxRuntime.jsx(
27853
- Typography,
27854
- {
27855
- variant: "caption",
27856
- weight: "bold",
27857
- className: "text-[var(--color-foreground)] truncate",
27858
- children: name
27859
- }
27860
- ),
27932
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
27933
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
27934
+ "img",
27935
+ {
27936
+ src: assetUrl,
27937
+ alt: name,
27938
+ width: 24,
27939
+ height: 24,
27940
+ style: { objectFit: "cover", borderRadius: "50%" },
27941
+ className: "flex-shrink-0"
27942
+ }
27943
+ ),
27944
+ /* @__PURE__ */ jsxRuntime.jsx(
27945
+ Typography,
27946
+ {
27947
+ variant: "caption",
27948
+ weight: "bold",
27949
+ className: "text-[var(--color-foreground)] truncate",
27950
+ children: name
27951
+ }
27952
+ )
27953
+ ] }),
27861
27954
  level != null && /* @__PURE__ */ jsxRuntime.jsxs(Badge, { variant: "neutral", size: "sm", children: [
27862
27955
  "Lv.",
27863
27956
  level
@@ -27902,7 +27995,7 @@ function EnemyPlate({
27902
27995
  }
27903
27996
  );
27904
27997
  }
27905
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
27998
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
27906
27999
  var init_EnemyPlate = __esm({
27907
28000
  "components/game/molecules/EnemyPlate.tsx"() {
27908
28001
  "use client";
@@ -27919,6 +28012,7 @@ var init_EnemyPlate = __esm({
27919
28012
  DEFAULT_ENEMY_EFFECTS = [
27920
28013
  { icon: "flame", label: "Burn", variant: "debuff" }
27921
28014
  ];
28015
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
27922
28016
  EnemyPlate.displayName = "EnemyPlate";
27923
28017
  }
27924
28018
  });
@@ -29450,13 +29544,13 @@ var init_MapView = __esm({
29450
29544
  shadowSize: [41, 41]
29451
29545
  });
29452
29546
  L.Marker.prototype.options.icon = defaultIcon;
29453
- const { useEffect: useEffect73, useRef: useRef68, useCallback: useCallback111, useState: useState104 } = React83__namespace.default;
29547
+ const { useEffect: useEffect79, useRef: useRef74, useCallback: useCallback118, useState: useState109 } = React83__namespace.default;
29454
29548
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29455
- const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29549
+ const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29456
29550
  function MapUpdater({ centerLat, centerLng, zoom }) {
29457
29551
  const map = useMap();
29458
- const prevRef = useRef68({ centerLat, centerLng, zoom });
29459
- useEffect73(() => {
29552
+ const prevRef = useRef74({ centerLat, centerLng, zoom });
29553
+ useEffect79(() => {
29460
29554
  const prev = prevRef.current;
29461
29555
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
29462
29556
  map.setView([centerLat, centerLng], zoom);
@@ -29467,7 +29561,7 @@ var init_MapView = __esm({
29467
29561
  }
29468
29562
  function MapClickHandler({ onMapClick }) {
29469
29563
  const map = useMap();
29470
- useEffect73(() => {
29564
+ useEffect79(() => {
29471
29565
  if (!onMapClick) return;
29472
29566
  const handler = (e) => {
29473
29567
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -29494,9 +29588,9 @@ var init_MapView = __esm({
29494
29588
  className,
29495
29589
  showAttribution = true
29496
29590
  }) {
29497
- const eventBus = useEventBus2();
29498
- const [clickedPosition, setClickedPosition] = useState104(null);
29499
- const handleMapClick = useCallback111((lat, lng) => {
29591
+ const eventBus = useEventBus3();
29592
+ const [clickedPosition, setClickedPosition] = useState109(null);
29593
+ const handleMapClick = useCallback118((lat, lng) => {
29500
29594
  if (showClickedPin) {
29501
29595
  setClickedPosition({ lat, lng });
29502
29596
  }
@@ -29505,7 +29599,7 @@ var init_MapView = __esm({
29505
29599
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29506
29600
  }
29507
29601
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29508
- const handleMarkerClick = useCallback111((marker) => {
29602
+ const handleMarkerClick = useCallback118((marker) => {
29509
29603
  onMarkerClick?.(marker);
29510
29604
  if (markerClickEvent) {
29511
29605
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -36632,10 +36726,10 @@ var init_Section = __esm({
36632
36726
  children,
36633
36727
  headerClassName,
36634
36728
  contentClassName,
36635
- as: Component = "section"
36729
+ as: Component2 = "section"
36636
36730
  }) => {
36637
36731
  const hasHeader = title || description || action;
36638
- const Comp = Component;
36732
+ const Comp = Component2;
36639
36733
  return /* @__PURE__ */ jsxRuntime.jsxs(
36640
36734
  Comp,
36641
36735
  {
@@ -41200,6 +41294,1568 @@ var init_GameAudioToggle = __esm({
41200
41294
  GameAudioToggle.displayName = "GameAudioToggle";
41201
41295
  }
41202
41296
  });
41297
+ function detectAssetRoot(modelUrl) {
41298
+ const idx = modelUrl.indexOf("/3d/");
41299
+ if (idx !== -1) {
41300
+ return modelUrl.substring(0, idx + 4);
41301
+ }
41302
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41303
+ }
41304
+ function createGLTFLoaderForUrl(url) {
41305
+ const loader = new GLTFLoader_js.GLTFLoader();
41306
+ loader.setResourcePath(detectAssetRoot(url));
41307
+ return loader;
41308
+ }
41309
+ var AssetLoader;
41310
+ var init_AssetLoader = __esm({
41311
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
41312
+ AssetLoader = class {
41313
+ constructor() {
41314
+ __publicField(this, "objLoader");
41315
+ __publicField(this, "textureLoader");
41316
+ __publicField(this, "modelCache");
41317
+ __publicField(this, "textureCache");
41318
+ __publicField(this, "loadingPromises");
41319
+ this.objLoader = new OBJLoader_js.OBJLoader();
41320
+ this.textureLoader = new THREE__namespace.TextureLoader();
41321
+ this.modelCache = /* @__PURE__ */ new Map();
41322
+ this.textureCache = /* @__PURE__ */ new Map();
41323
+ this.loadingPromises = /* @__PURE__ */ new Map();
41324
+ }
41325
+ /**
41326
+ * Load a GLB/GLTF model
41327
+ * @param url - URL to the .glb or .gltf file
41328
+ * @returns Promise with loaded model scene and animations
41329
+ */
41330
+ async loadModel(url) {
41331
+ if (this.modelCache.has(url)) {
41332
+ return this.modelCache.get(url);
41333
+ }
41334
+ if (this.loadingPromises.has(url)) {
41335
+ return this.loadingPromises.get(url);
41336
+ }
41337
+ const loader = createGLTFLoaderForUrl(url);
41338
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
41339
+ const result = {
41340
+ scene: gltf.scene,
41341
+ animations: gltf.animations || []
41342
+ };
41343
+ this.modelCache.set(url, result);
41344
+ this.loadingPromises.delete(url);
41345
+ return result;
41346
+ }).catch((error) => {
41347
+ this.loadingPromises.delete(url);
41348
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
41349
+ });
41350
+ this.loadingPromises.set(url, loadPromise);
41351
+ return loadPromise;
41352
+ }
41353
+ /**
41354
+ * Load an OBJ model (fallback for non-GLB assets)
41355
+ * @param url - URL to the .obj file
41356
+ * @returns Promise with loaded object group
41357
+ */
41358
+ async loadOBJ(url) {
41359
+ if (this.modelCache.has(url)) {
41360
+ return this.modelCache.get(url).scene;
41361
+ }
41362
+ if (this.loadingPromises.has(url)) {
41363
+ const result = await this.loadingPromises.get(url);
41364
+ return result.scene;
41365
+ }
41366
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
41367
+ const result = {
41368
+ scene: group,
41369
+ animations: []
41370
+ };
41371
+ this.modelCache.set(url, result);
41372
+ this.loadingPromises.delete(url);
41373
+ return result;
41374
+ }).catch((error) => {
41375
+ this.loadingPromises.delete(url);
41376
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
41377
+ });
41378
+ this.loadingPromises.set(url, loadPromise);
41379
+ return (await loadPromise).scene;
41380
+ }
41381
+ /**
41382
+ * Load a texture
41383
+ * @param url - URL to the texture image
41384
+ * @returns Promise with loaded texture
41385
+ */
41386
+ async loadTexture(url) {
41387
+ if (this.textureCache.has(url)) {
41388
+ return this.textureCache.get(url);
41389
+ }
41390
+ if (this.loadingPromises.has(`texture:${url}`)) {
41391
+ return this.loadingPromises.get(`texture:${url}`);
41392
+ }
41393
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
41394
+ texture.colorSpace = THREE__namespace.SRGBColorSpace;
41395
+ this.textureCache.set(url, texture);
41396
+ this.loadingPromises.delete(`texture:${url}`);
41397
+ return texture;
41398
+ }).catch((error) => {
41399
+ this.loadingPromises.delete(`texture:${url}`);
41400
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
41401
+ });
41402
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
41403
+ return loadPromise;
41404
+ }
41405
+ /**
41406
+ * Preload multiple assets
41407
+ * @param urls - Array of asset URLs to preload
41408
+ * @returns Promise that resolves when all assets are loaded
41409
+ */
41410
+ async preload(urls) {
41411
+ const promises = urls.map((url) => {
41412
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41413
+ return this.loadModel(url).catch(() => null);
41414
+ } else if (url.endsWith(".obj")) {
41415
+ return this.loadOBJ(url).catch(() => null);
41416
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41417
+ return this.loadTexture(url).catch(() => null);
41418
+ }
41419
+ return Promise.resolve(null);
41420
+ });
41421
+ await Promise.all(promises);
41422
+ }
41423
+ /**
41424
+ * Check if a model is cached
41425
+ * @param url - Model URL
41426
+ */
41427
+ hasModel(url) {
41428
+ return this.modelCache.has(url);
41429
+ }
41430
+ /**
41431
+ * Check if a texture is cached
41432
+ * @param url - Texture URL
41433
+ */
41434
+ hasTexture(url) {
41435
+ return this.textureCache.has(url);
41436
+ }
41437
+ /**
41438
+ * Get cached model (throws if not cached)
41439
+ * @param url - Model URL
41440
+ */
41441
+ getModel(url) {
41442
+ const model = this.modelCache.get(url);
41443
+ if (!model) {
41444
+ throw new Error(`Model ${url} not in cache`);
41445
+ }
41446
+ return model;
41447
+ }
41448
+ /**
41449
+ * Get cached texture (throws if not cached)
41450
+ * @param url - Texture URL
41451
+ */
41452
+ getTexture(url) {
41453
+ const texture = this.textureCache.get(url);
41454
+ if (!texture) {
41455
+ throw new Error(`Texture ${url} not in cache`);
41456
+ }
41457
+ return texture;
41458
+ }
41459
+ /**
41460
+ * Clear all caches
41461
+ */
41462
+ clearCache() {
41463
+ this.textureCache.forEach((texture) => {
41464
+ texture.dispose();
41465
+ });
41466
+ this.modelCache.forEach((model) => {
41467
+ model.scene.traverse((child) => {
41468
+ if (child instanceof THREE__namespace.Mesh) {
41469
+ child.geometry.dispose();
41470
+ if (Array.isArray(child.material)) {
41471
+ child.material.forEach((m) => m.dispose());
41472
+ } else {
41473
+ child.material.dispose();
41474
+ }
41475
+ }
41476
+ });
41477
+ });
41478
+ this.modelCache.clear();
41479
+ this.textureCache.clear();
41480
+ this.loadingPromises.clear();
41481
+ }
41482
+ /**
41483
+ * Get cache statistics
41484
+ */
41485
+ getStats() {
41486
+ return {
41487
+ models: this.modelCache.size,
41488
+ textures: this.textureCache.size,
41489
+ loading: this.loadingPromises.size
41490
+ };
41491
+ }
41492
+ };
41493
+ new AssetLoader();
41494
+ }
41495
+ });
41496
+ function useAssetLoader(options = {}) {
41497
+ const { preloadUrls = [], loader: customLoader } = options;
41498
+ const loaderRef = React83.useRef(customLoader || new AssetLoader());
41499
+ const [state, setState] = React83.useState({
41500
+ isLoading: false,
41501
+ progress: 0,
41502
+ loaded: 0,
41503
+ total: 0,
41504
+ errors: []
41505
+ });
41506
+ React83.useEffect(() => {
41507
+ if (preloadUrls.length > 0) {
41508
+ preload(preloadUrls);
41509
+ }
41510
+ }, []);
41511
+ const updateProgress = React83.useCallback((loaded, total) => {
41512
+ setState((prev) => ({
41513
+ ...prev,
41514
+ loaded,
41515
+ total,
41516
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
41517
+ }));
41518
+ }, []);
41519
+ const loadModel = React83.useCallback(
41520
+ async (url) => {
41521
+ setState((prev) => ({ ...prev, isLoading: true }));
41522
+ try {
41523
+ const model = await loaderRef.current.loadModel(url);
41524
+ setState((prev) => ({
41525
+ ...prev,
41526
+ isLoading: false,
41527
+ loaded: prev.loaded + 1
41528
+ }));
41529
+ return model;
41530
+ } catch (error) {
41531
+ const errorMsg = error instanceof Error ? error.message : String(error);
41532
+ setState((prev) => ({
41533
+ ...prev,
41534
+ isLoading: false,
41535
+ errors: [...prev.errors, errorMsg]
41536
+ }));
41537
+ throw error;
41538
+ }
41539
+ },
41540
+ []
41541
+ );
41542
+ const loadOBJ = React83.useCallback(
41543
+ async (url) => {
41544
+ setState((prev) => ({ ...prev, isLoading: true }));
41545
+ try {
41546
+ const model = await loaderRef.current.loadOBJ(url);
41547
+ setState((prev) => ({
41548
+ ...prev,
41549
+ isLoading: false,
41550
+ loaded: prev.loaded + 1
41551
+ }));
41552
+ return model;
41553
+ } catch (error) {
41554
+ const errorMsg = error instanceof Error ? error.message : String(error);
41555
+ setState((prev) => ({
41556
+ ...prev,
41557
+ isLoading: false,
41558
+ errors: [...prev.errors, errorMsg]
41559
+ }));
41560
+ throw error;
41561
+ }
41562
+ },
41563
+ []
41564
+ );
41565
+ const loadTexture = React83.useCallback(
41566
+ async (url) => {
41567
+ setState((prev) => ({ ...prev, isLoading: true }));
41568
+ try {
41569
+ const texture = await loaderRef.current.loadTexture(url);
41570
+ setState((prev) => ({
41571
+ ...prev,
41572
+ isLoading: false,
41573
+ loaded: prev.loaded + 1
41574
+ }));
41575
+ return texture;
41576
+ } catch (error) {
41577
+ const errorMsg = error instanceof Error ? error.message : String(error);
41578
+ setState((prev) => ({
41579
+ ...prev,
41580
+ isLoading: false,
41581
+ errors: [...prev.errors, errorMsg]
41582
+ }));
41583
+ throw error;
41584
+ }
41585
+ },
41586
+ []
41587
+ );
41588
+ const preload = React83.useCallback(
41589
+ async (urls) => {
41590
+ setState((prev) => ({
41591
+ ...prev,
41592
+ isLoading: true,
41593
+ total: urls.length,
41594
+ loaded: 0,
41595
+ errors: []
41596
+ }));
41597
+ let completed = 0;
41598
+ const errors = [];
41599
+ await Promise.all(
41600
+ urls.map(async (url) => {
41601
+ try {
41602
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41603
+ await loaderRef.current.loadModel(url);
41604
+ } else if (url.endsWith(".obj")) {
41605
+ await loaderRef.current.loadOBJ(url);
41606
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41607
+ await loaderRef.current.loadTexture(url);
41608
+ }
41609
+ completed++;
41610
+ updateProgress(completed, urls.length);
41611
+ } catch (error) {
41612
+ const errorMsg = error instanceof Error ? error.message : String(error);
41613
+ errors.push(`${url}: ${errorMsg}`);
41614
+ completed++;
41615
+ updateProgress(completed, urls.length);
41616
+ }
41617
+ })
41618
+ );
41619
+ setState((prev) => ({
41620
+ ...prev,
41621
+ isLoading: false,
41622
+ errors
41623
+ }));
41624
+ },
41625
+ [updateProgress]
41626
+ );
41627
+ const hasModel = React83.useCallback((url) => {
41628
+ return loaderRef.current.hasModel(url);
41629
+ }, []);
41630
+ const hasTexture = React83.useCallback((url) => {
41631
+ return loaderRef.current.hasTexture(url);
41632
+ }, []);
41633
+ const getModel = React83.useCallback((url) => {
41634
+ try {
41635
+ return loaderRef.current.getModel(url);
41636
+ } catch {
41637
+ return void 0;
41638
+ }
41639
+ }, []);
41640
+ const getTexture = React83.useCallback((url) => {
41641
+ try {
41642
+ return loaderRef.current.getTexture(url);
41643
+ } catch {
41644
+ return void 0;
41645
+ }
41646
+ }, []);
41647
+ const clearCache = React83.useCallback(() => {
41648
+ loaderRef.current.clearCache();
41649
+ setState({
41650
+ isLoading: false,
41651
+ progress: 0,
41652
+ loaded: 0,
41653
+ total: 0,
41654
+ errors: []
41655
+ });
41656
+ }, []);
41657
+ return {
41658
+ ...state,
41659
+ loadModel,
41660
+ loadOBJ,
41661
+ loadTexture,
41662
+ preload,
41663
+ hasModel,
41664
+ hasTexture,
41665
+ getModel,
41666
+ getTexture,
41667
+ clearCache
41668
+ };
41669
+ }
41670
+ var init_useAssetLoader = __esm({
41671
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
41672
+ "use client";
41673
+ init_AssetLoader();
41674
+ }
41675
+ });
41676
+ function useGameCanvas3DEvents(options) {
41677
+ const {
41678
+ tileClickEvent,
41679
+ unitClickEvent,
41680
+ featureClickEvent,
41681
+ canvasClickEvent,
41682
+ tileHoverEvent,
41683
+ tileLeaveEvent,
41684
+ unitAnimationEvent,
41685
+ cameraChangeEvent,
41686
+ onTileClick,
41687
+ onUnitClick,
41688
+ onFeatureClick,
41689
+ onCanvasClick,
41690
+ onTileHover,
41691
+ onUnitAnimation
41692
+ } = options;
41693
+ const emit = useEmitEvent();
41694
+ const optionsRef = React83.useRef(options);
41695
+ optionsRef.current = options;
41696
+ const handleTileClick = React83.useCallback(
41697
+ (tile, event) => {
41698
+ if (tileClickEvent) {
41699
+ emit(tileClickEvent, {
41700
+ tileId: tile.id,
41701
+ x: tile.x,
41702
+ z: tile.z ?? tile.y ?? 0,
41703
+ type: tile.type,
41704
+ terrain: tile.terrain,
41705
+ elevation: tile.elevation
41706
+ });
41707
+ }
41708
+ optionsRef.current.onTileClick?.(tile, event);
41709
+ },
41710
+ [tileClickEvent, emit]
41711
+ );
41712
+ const handleUnitClick = React83.useCallback(
41713
+ (unit, event) => {
41714
+ if (unitClickEvent) {
41715
+ emit(unitClickEvent, {
41716
+ unitId: unit.id,
41717
+ x: unit.x,
41718
+ z: unit.z ?? unit.y ?? 0,
41719
+ unitType: unit.unitType,
41720
+ name: unit.name,
41721
+ team: unit.team,
41722
+ faction: unit.faction,
41723
+ health: unit.health,
41724
+ maxHealth: unit.maxHealth
41725
+ });
41726
+ }
41727
+ optionsRef.current.onUnitClick?.(unit, event);
41728
+ },
41729
+ [unitClickEvent, emit]
41730
+ );
41731
+ const handleFeatureClick = React83.useCallback(
41732
+ (feature, event) => {
41733
+ if (featureClickEvent) {
41734
+ emit(featureClickEvent, {
41735
+ featureId: feature.id,
41736
+ x: feature.x,
41737
+ z: feature.z ?? feature.y ?? 0,
41738
+ type: feature.type,
41739
+ elevation: feature.elevation
41740
+ });
41741
+ }
41742
+ optionsRef.current.onFeatureClick?.(feature, event);
41743
+ },
41744
+ [featureClickEvent, emit]
41745
+ );
41746
+ const handleCanvasClick = React83.useCallback(
41747
+ (event) => {
41748
+ if (canvasClickEvent) {
41749
+ emit(canvasClickEvent, {
41750
+ clientX: event.clientX,
41751
+ clientY: event.clientY,
41752
+ button: event.button
41753
+ });
41754
+ }
41755
+ optionsRef.current.onCanvasClick?.(event);
41756
+ },
41757
+ [canvasClickEvent, emit]
41758
+ );
41759
+ const handleTileHover = React83.useCallback(
41760
+ (tile, event) => {
41761
+ if (tile) {
41762
+ if (tileHoverEvent) {
41763
+ emit(tileHoverEvent, {
41764
+ tileId: tile.id,
41765
+ x: tile.x,
41766
+ z: tile.z ?? tile.y ?? 0,
41767
+ type: tile.type
41768
+ });
41769
+ }
41770
+ } else {
41771
+ if (tileLeaveEvent) {
41772
+ emit(tileLeaveEvent, {});
41773
+ }
41774
+ }
41775
+ optionsRef.current.onTileHover?.(tile, event);
41776
+ },
41777
+ [tileHoverEvent, tileLeaveEvent, emit]
41778
+ );
41779
+ const handleUnitAnimation = React83.useCallback(
41780
+ (unitId, state) => {
41781
+ if (unitAnimationEvent) {
41782
+ emit(unitAnimationEvent, {
41783
+ unitId,
41784
+ state,
41785
+ timestamp: Date.now()
41786
+ });
41787
+ }
41788
+ optionsRef.current.onUnitAnimation?.(unitId, state);
41789
+ },
41790
+ [unitAnimationEvent, emit]
41791
+ );
41792
+ const handleCameraChange = React83.useCallback(
41793
+ (position) => {
41794
+ if (cameraChangeEvent) {
41795
+ emit(cameraChangeEvent, {
41796
+ position,
41797
+ timestamp: Date.now()
41798
+ });
41799
+ }
41800
+ },
41801
+ [cameraChangeEvent, emit]
41802
+ );
41803
+ return {
41804
+ handleTileClick,
41805
+ handleUnitClick,
41806
+ handleFeatureClick,
41807
+ handleCanvasClick,
41808
+ handleTileHover,
41809
+ handleUnitAnimation,
41810
+ handleCameraChange
41811
+ };
41812
+ }
41813
+ var init_useGameCanvas3DEvents = __esm({
41814
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
41815
+ "use client";
41816
+ init_useEventBus();
41817
+ }
41818
+ });
41819
+
41820
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
41821
+ var init_Canvas3DLoadingState = __esm({
41822
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
41823
+ }
41824
+ });
41825
+ function Canvas3DLoadingState({
41826
+ progress = 0,
41827
+ loaded = 0,
41828
+ total = 0,
41829
+ message = "Loading 3D Scene...",
41830
+ details,
41831
+ showSpinner = true,
41832
+ className
41833
+ }) {
41834
+ const clampedProgress = Math.max(0, Math.min(100, progress));
41835
+ const hasProgress = total > 0;
41836
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
41837
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__content", children: [
41838
+ showSpinner && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__spinner", children: [
41839
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner__ring" }),
41840
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner__ring spinner__ring--secondary" })
41841
+ ] }),
41842
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__message", children: message }),
41843
+ details && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__details", children: details }),
41844
+ hasProgress && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__progress", children: [
41845
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsxRuntime.jsx(
41846
+ "div",
41847
+ {
41848
+ className: "progress__fill",
41849
+ style: { width: `${clampedProgress}%` }
41850
+ }
41851
+ ) }),
41852
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "progress__text", children: [
41853
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "progress__percentage", children: [
41854
+ clampedProgress,
41855
+ "%"
41856
+ ] }),
41857
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "progress__count", children: [
41858
+ "(",
41859
+ loaded,
41860
+ "/",
41861
+ total,
41862
+ ")"
41863
+ ] })
41864
+ ] })
41865
+ ] })
41866
+ ] }),
41867
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg__grid" }) })
41868
+ ] });
41869
+ }
41870
+ var init_Canvas3DLoadingState2 = __esm({
41871
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
41872
+ init_Canvas3DLoadingState();
41873
+ }
41874
+ });
41875
+
41876
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
41877
+ var init_Canvas3DErrorBoundary = __esm({
41878
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
41879
+ }
41880
+ });
41881
+ var log11, Canvas3DErrorBoundary;
41882
+ var init_Canvas3DErrorBoundary2 = __esm({
41883
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
41884
+ init_Canvas3DErrorBoundary();
41885
+ log11 = logger.createLogger("almadar:ui:game:canvas3d:error-boundary");
41886
+ Canvas3DErrorBoundary = class extends React83.Component {
41887
+ constructor(props) {
41888
+ super(props);
41889
+ __publicField(this, "handleReset", () => {
41890
+ this.setState({
41891
+ hasError: false,
41892
+ error: null,
41893
+ errorInfo: null
41894
+ });
41895
+ this.props.onReset?.();
41896
+ });
41897
+ this.state = {
41898
+ hasError: false,
41899
+ error: null,
41900
+ errorInfo: null
41901
+ };
41902
+ }
41903
+ static getDerivedStateFromError(error) {
41904
+ return {
41905
+ hasError: true,
41906
+ error,
41907
+ errorInfo: null
41908
+ };
41909
+ }
41910
+ componentDidCatch(error, errorInfo) {
41911
+ this.setState({ errorInfo });
41912
+ this.props.onError?.(error, errorInfo);
41913
+ log11.error("Error caught", { error });
41914
+ log11.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
41915
+ }
41916
+ render() {
41917
+ if (this.state.hasError) {
41918
+ if (this.props.fallback) {
41919
+ return this.props.fallback;
41920
+ }
41921
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-error__content", children: [
41922
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
41923
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
41924
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
41925
+ this.state.error && /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "canvas-3d-error__details", children: [
41926
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Error Details" }),
41927
+ /* @__PURE__ */ jsxRuntime.jsxs("pre", { className: "error__stack", children: [
41928
+ this.state.error.message,
41929
+ "\n",
41930
+ this.state.error.stack
41931
+ ] }),
41932
+ this.state.errorInfo && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
41933
+ ] }),
41934
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-error__actions", children: [
41935
+ /* @__PURE__ */ jsxRuntime.jsx(
41936
+ "button",
41937
+ {
41938
+ className: "error__button error__button--primary",
41939
+ onClick: this.handleReset,
41940
+ children: "Try Again"
41941
+ }
41942
+ ),
41943
+ /* @__PURE__ */ jsxRuntime.jsx(
41944
+ "button",
41945
+ {
41946
+ className: "error__button error__button--secondary",
41947
+ onClick: () => window.location.reload(),
41948
+ children: "Reload Page"
41949
+ }
41950
+ )
41951
+ ] })
41952
+ ] }) });
41953
+ }
41954
+ return this.props.children;
41955
+ }
41956
+ };
41957
+ }
41958
+ });
41959
+ function detectAssetRoot2(modelUrl) {
41960
+ const idx = modelUrl.indexOf("/3d/");
41961
+ if (idx !== -1) {
41962
+ return modelUrl.substring(0, idx + 4);
41963
+ }
41964
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41965
+ }
41966
+ function useGLTFModel(url, resourceBasePath) {
41967
+ const [state, setState] = React83.useState({
41968
+ model: null,
41969
+ isLoading: false,
41970
+ error: null
41971
+ });
41972
+ React83.useEffect(() => {
41973
+ if (!url) {
41974
+ setState({ model: null, isLoading: false, error: null });
41975
+ return;
41976
+ }
41977
+ log12.debug("Loading", { url });
41978
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
41979
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
41980
+ const loader = new GLTFLoader.GLTFLoader();
41981
+ loader.setResourcePath(assetRoot);
41982
+ loader.load(
41983
+ url,
41984
+ (gltf) => {
41985
+ log12.debug("Loaded", { url });
41986
+ setState({
41987
+ model: gltf.scene,
41988
+ isLoading: false,
41989
+ error: null
41990
+ });
41991
+ },
41992
+ void 0,
41993
+ (err) => {
41994
+ log12.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
41995
+ setState({
41996
+ model: null,
41997
+ isLoading: false,
41998
+ error: err instanceof Error ? err : new Error(String(err))
41999
+ });
42000
+ }
42001
+ );
42002
+ }, [url, resourceBasePath]);
42003
+ return state;
42004
+ }
42005
+ function ModelLoader({
42006
+ url,
42007
+ position = [0, 0, 0],
42008
+ scale = 1,
42009
+ rotation = [0, 0, 0],
42010
+ isSelected = false,
42011
+ isHovered = false,
42012
+ onClick,
42013
+ onHover,
42014
+ fallbackGeometry = "box",
42015
+ castShadow = true,
42016
+ receiveShadow = true,
42017
+ resourceBasePath
42018
+ }) {
42019
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
42020
+ const model = React83.useMemo(() => {
42021
+ if (!loadedModel) return null;
42022
+ const cloned = loadedModel.clone();
42023
+ cloned.traverse((child) => {
42024
+ if (child instanceof THREE__namespace.Mesh) {
42025
+ child.castShadow = castShadow;
42026
+ child.receiveShadow = receiveShadow;
42027
+ }
42028
+ });
42029
+ return cloned;
42030
+ }, [loadedModel, castShadow, receiveShadow]);
42031
+ const scaleArray = React83.useMemo(() => {
42032
+ if (typeof scale === "number") {
42033
+ return [scale, scale, scale];
42034
+ }
42035
+ return scale;
42036
+ }, [scale]);
42037
+ const rotationRad = React83.useMemo(() => {
42038
+ return [
42039
+ rotation[0] * Math.PI / 180,
42040
+ rotation[1] * Math.PI / 180,
42041
+ rotation[2] * Math.PI / 180
42042
+ ];
42043
+ }, [rotation]);
42044
+ if (isLoading) {
42045
+ return /* @__PURE__ */ jsxRuntime.jsx("group", { position, children: /* @__PURE__ */ jsxRuntime.jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
42046
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
42047
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
42048
+ ] }) });
42049
+ }
42050
+ if (error || !model) {
42051
+ if (fallbackGeometry === "none") {
42052
+ return /* @__PURE__ */ jsxRuntime.jsx("group", { position });
42053
+ }
42054
+ const fallbackProps = {
42055
+ onClick,
42056
+ onPointerOver: () => onHover?.(true),
42057
+ onPointerOut: () => onHover?.(false)
42058
+ };
42059
+ return /* @__PURE__ */ jsxRuntime.jsxs("group", { position, children: [
42060
+ (isSelected || isHovered) && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42061
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
42062
+ /* @__PURE__ */ jsxRuntime.jsx(
42063
+ "meshBasicMaterial",
42064
+ {
42065
+ color: isSelected ? 16755200 : 16777215,
42066
+ transparent: true,
42067
+ opacity: 0.5
42068
+ }
42069
+ )
42070
+ ] }),
42071
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42072
+ /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
42073
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42074
+ ] }),
42075
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42076
+ /* @__PURE__ */ jsxRuntime.jsx("sphereGeometry", { args: [0.4, 16, 16] }),
42077
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42078
+ ] }),
42079
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42080
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
42081
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42082
+ ] })
42083
+ ] });
42084
+ }
42085
+ return /* @__PURE__ */ jsxRuntime.jsxs(
42086
+ "group",
42087
+ {
42088
+ position,
42089
+ rotation: rotationRad,
42090
+ onClick,
42091
+ onPointerOver: () => onHover?.(true),
42092
+ onPointerOut: () => onHover?.(false),
42093
+ children: [
42094
+ (isSelected || isHovered) && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42095
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
42096
+ /* @__PURE__ */ jsxRuntime.jsx(
42097
+ "meshBasicMaterial",
42098
+ {
42099
+ color: isSelected ? 16755200 : 16777215,
42100
+ transparent: true,
42101
+ opacity: 0.5
42102
+ }
42103
+ )
42104
+ ] }),
42105
+ /* @__PURE__ */ jsxRuntime.jsx("primitive", { object: model, scale: scaleArray })
42106
+ ]
42107
+ }
42108
+ );
42109
+ }
42110
+ var log12;
42111
+ var init_ModelLoader = __esm({
42112
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
42113
+ "use client";
42114
+ log12 = logger.createLogger("almadar:ui:game:model-loader");
42115
+ }
42116
+ });
42117
+
42118
+ // components/game/molecules/GameCanvas3D.css
42119
+ var init_GameCanvas3D = __esm({
42120
+ "components/game/molecules/GameCanvas3D.css"() {
42121
+ }
42122
+ });
42123
+ function CameraController({
42124
+ onCameraChange
42125
+ }) {
42126
+ const { camera } = fiber.useThree();
42127
+ React83.useEffect(() => {
42128
+ if (onCameraChange) {
42129
+ onCameraChange({
42130
+ x: camera.position.x,
42131
+ y: camera.position.y,
42132
+ z: camera.position.z
42133
+ });
42134
+ }
42135
+ }, [camera.position, onCameraChange]);
42136
+ return null;
42137
+ }
42138
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
42139
+ var init_GameCanvas3D2 = __esm({
42140
+ "components/game/molecules/GameCanvas3D.tsx"() {
42141
+ "use client";
42142
+ init_AssetLoader();
42143
+ init_useAssetLoader();
42144
+ init_useGameCanvas3DEvents();
42145
+ init_Canvas3DLoadingState2();
42146
+ init_Canvas3DErrorBoundary2();
42147
+ init_ModelLoader();
42148
+ init_cn();
42149
+ init_GameCanvas3D();
42150
+ DEFAULT_GRID_CONFIG = {
42151
+ cellSize: 1,
42152
+ offsetX: 0,
42153
+ offsetZ: 0
42154
+ };
42155
+ GameCanvas3D = React83.forwardRef(
42156
+ ({
42157
+ tiles = [],
42158
+ units = [],
42159
+ features = [],
42160
+ events: events2 = [],
42161
+ orientation = "standard",
42162
+ cameraMode = "isometric",
42163
+ showGrid = true,
42164
+ showCoordinates = false,
42165
+ showTileInfo = false,
42166
+ overlay = "default",
42167
+ shadows = true,
42168
+ backgroundColor = "#1a1a2e",
42169
+ onTileClick,
42170
+ onUnitClick,
42171
+ onFeatureClick,
42172
+ onCanvasClick,
42173
+ onTileHover,
42174
+ onUnitAnimation,
42175
+ assetLoader: customAssetLoader,
42176
+ tileRenderer: CustomTileRenderer,
42177
+ unitRenderer: CustomUnitRenderer,
42178
+ featureRenderer: CustomFeatureRenderer,
42179
+ className,
42180
+ isLoading: externalLoading,
42181
+ error: externalError,
42182
+ entity,
42183
+ preloadAssets = [],
42184
+ tileClickEvent,
42185
+ unitClickEvent,
42186
+ featureClickEvent,
42187
+ canvasClickEvent,
42188
+ tileHoverEvent,
42189
+ tileLeaveEvent,
42190
+ unitAnimationEvent,
42191
+ cameraChangeEvent,
42192
+ loadingMessage = "Loading 3D Scene...",
42193
+ useInstancing = true,
42194
+ validMoves = [],
42195
+ attackTargets = [],
42196
+ selectedTileIds = [],
42197
+ selectedUnitId = null,
42198
+ children
42199
+ }, ref) => {
42200
+ const containerRef = React83.useRef(null);
42201
+ const controlsRef = React83.useRef(null);
42202
+ const [hoveredTile, setHoveredTile] = React83.useState(null);
42203
+ const [internalError, setInternalError] = React83.useState(null);
42204
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
42205
+ preloadUrls: preloadAssets,
42206
+ loader: customAssetLoader
42207
+ });
42208
+ const eventHandlers = useGameCanvas3DEvents({
42209
+ tileClickEvent,
42210
+ unitClickEvent,
42211
+ featureClickEvent,
42212
+ canvasClickEvent,
42213
+ tileHoverEvent,
42214
+ tileLeaveEvent,
42215
+ unitAnimationEvent,
42216
+ cameraChangeEvent,
42217
+ onTileClick,
42218
+ onUnitClick,
42219
+ onFeatureClick,
42220
+ onCanvasClick,
42221
+ onTileHover,
42222
+ onUnitAnimation
42223
+ });
42224
+ const gridBounds = React83.useMemo(() => {
42225
+ if (tiles.length === 0) {
42226
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
42227
+ }
42228
+ const xs = tiles.map((t) => t.x);
42229
+ const zs = tiles.map((t) => t.z || t.y || 0);
42230
+ return {
42231
+ minX: Math.min(...xs),
42232
+ maxX: Math.max(...xs),
42233
+ minZ: Math.min(...zs),
42234
+ maxZ: Math.max(...zs)
42235
+ };
42236
+ }, [tiles]);
42237
+ const cameraTarget = React83.useMemo(() => {
42238
+ return [
42239
+ (gridBounds.minX + gridBounds.maxX) / 2,
42240
+ 0,
42241
+ (gridBounds.minZ + gridBounds.maxZ) / 2
42242
+ ];
42243
+ }, [gridBounds]);
42244
+ const gridConfig = React83.useMemo(
42245
+ () => ({
42246
+ ...DEFAULT_GRID_CONFIG,
42247
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
42248
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
42249
+ }),
42250
+ [gridBounds]
42251
+ );
42252
+ const gridToWorld = React83.useCallback(
42253
+ (x, z, y = 0) => {
42254
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
42255
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
42256
+ return [worldX, y * gridConfig.cellSize, worldZ];
42257
+ },
42258
+ [gridBounds, gridConfig]
42259
+ );
42260
+ React83.useImperativeHandle(ref, () => ({
42261
+ getCameraPosition: () => {
42262
+ if (controlsRef.current) {
42263
+ const pos = controlsRef.current.object.position;
42264
+ return new THREE__namespace.Vector3(pos.x, pos.y, pos.z);
42265
+ }
42266
+ return null;
42267
+ },
42268
+ setCameraPosition: (x, y, z) => {
42269
+ if (controlsRef.current) {
42270
+ controlsRef.current.object.position.set(x, y, z);
42271
+ controlsRef.current.update();
42272
+ }
42273
+ },
42274
+ lookAt: (x, y, z) => {
42275
+ if (controlsRef.current) {
42276
+ controlsRef.current.target.set(x, y, z);
42277
+ controlsRef.current.update();
42278
+ }
42279
+ },
42280
+ resetCamera: () => {
42281
+ if (controlsRef.current) {
42282
+ controlsRef.current.reset();
42283
+ }
42284
+ },
42285
+ screenshot: () => {
42286
+ const canvas = containerRef.current?.querySelector("canvas");
42287
+ if (canvas) {
42288
+ return canvas.toDataURL("image/png");
42289
+ }
42290
+ return null;
42291
+ },
42292
+ export: () => ({
42293
+ tiles,
42294
+ units,
42295
+ features
42296
+ })
42297
+ }));
42298
+ const handleTileClick = React83.useCallback(
42299
+ (tile, event) => {
42300
+ eventHandlers.handleTileClick(tile, event);
42301
+ },
42302
+ [eventHandlers]
42303
+ );
42304
+ const handleUnitClick = React83.useCallback(
42305
+ (unit, event) => {
42306
+ eventHandlers.handleUnitClick(unit, event);
42307
+ },
42308
+ [eventHandlers]
42309
+ );
42310
+ const handleFeatureClick = React83.useCallback(
42311
+ (feature, event) => {
42312
+ if (event) {
42313
+ eventHandlers.handleFeatureClick(feature, event);
42314
+ }
42315
+ },
42316
+ [eventHandlers]
42317
+ );
42318
+ const handleTileHover = React83.useCallback(
42319
+ (tile, event) => {
42320
+ setHoveredTile(tile);
42321
+ if (event) {
42322
+ eventHandlers.handleTileHover(tile, event);
42323
+ }
42324
+ },
42325
+ [eventHandlers]
42326
+ );
42327
+ const cameraConfig = React83.useMemo(() => {
42328
+ const size = Math.max(
42329
+ gridBounds.maxX - gridBounds.minX,
42330
+ gridBounds.maxZ - gridBounds.minZ
42331
+ );
42332
+ const distance = size * 1.5;
42333
+ switch (cameraMode) {
42334
+ case "isometric":
42335
+ return {
42336
+ position: [distance, distance * 0.8, distance],
42337
+ fov: 45
42338
+ };
42339
+ case "top-down":
42340
+ return {
42341
+ position: [0, distance * 2, 0],
42342
+ fov: 45
42343
+ };
42344
+ case "perspective":
42345
+ default:
42346
+ return {
42347
+ position: [distance, distance, distance],
42348
+ fov: 45
42349
+ };
42350
+ }
42351
+ }, [cameraMode, gridBounds]);
42352
+ const DefaultTileRenderer = React83.useCallback(
42353
+ ({ tile, position }) => {
42354
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
42355
+ const isHovered = hoveredTile?.id === tile.id;
42356
+ const isValidMove = validMoves.some(
42357
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
42358
+ );
42359
+ const isAttackTarget = attackTargets.some(
42360
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
42361
+ );
42362
+ let color = 8421504;
42363
+ if (tile.type === "water") color = 4491468;
42364
+ else if (tile.type === "grass") color = 4500036;
42365
+ else if (tile.type === "sand") color = 14535816;
42366
+ else if (tile.type === "rock") color = 8947848;
42367
+ else if (tile.type === "snow") color = 15658734;
42368
+ let emissive = 0;
42369
+ if (isSelected) emissive = 4473924;
42370
+ else if (isAttackTarget) emissive = 4456448;
42371
+ else if (isValidMove) emissive = 17408;
42372
+ else if (isHovered) emissive = 2236962;
42373
+ if (tile.modelUrl) {
42374
+ return /* @__PURE__ */ jsxRuntime.jsx(
42375
+ "group",
42376
+ {
42377
+ position,
42378
+ onClick: (e) => handleTileClick(tile, e),
42379
+ onPointerEnter: (e) => handleTileHover(tile, e),
42380
+ onPointerLeave: (e) => handleTileHover(null, e),
42381
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
42382
+ children: /* @__PURE__ */ jsxRuntime.jsx(
42383
+ ModelLoader,
42384
+ {
42385
+ url: tile.modelUrl,
42386
+ scale: 0.95,
42387
+ fallbackGeometry: "box",
42388
+ castShadow: true,
42389
+ receiveShadow: true
42390
+ }
42391
+ )
42392
+ }
42393
+ );
42394
+ }
42395
+ return /* @__PURE__ */ jsxRuntime.jsxs(
42396
+ "mesh",
42397
+ {
42398
+ position,
42399
+ onClick: (e) => handleTileClick(tile, e),
42400
+ onPointerEnter: (e) => handleTileHover(tile, e),
42401
+ onPointerLeave: (e) => handleTileHover(null, e),
42402
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
42403
+ children: [
42404
+ /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
42405
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color, emissive })
42406
+ ]
42407
+ }
42408
+ );
42409
+ },
42410
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
42411
+ );
42412
+ const DefaultUnitRenderer = React83.useCallback(
42413
+ ({ unit, position }) => {
42414
+ const isSelected = selectedUnitId === unit.id;
42415
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
42416
+ return /* @__PURE__ */ jsxRuntime.jsxs(
42417
+ "group",
42418
+ {
42419
+ position,
42420
+ onClick: (e) => handleUnitClick(unit, e),
42421
+ userData: { type: "unit", unitId: unit.id },
42422
+ children: [
42423
+ isSelected && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42424
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
42425
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
42426
+ ] }),
42427
+ unit.modelUrl ? (
42428
+ /* GLB unit model (box fallback while loading / on error) */
42429
+ /* @__PURE__ */ jsxRuntime.jsx(
42430
+ ModelLoader,
42431
+ {
42432
+ url: unit.modelUrl,
42433
+ scale: 0.5,
42434
+ fallbackGeometry: "box",
42435
+ castShadow: true
42436
+ }
42437
+ )
42438
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
42439
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.3, 0], children: [
42440
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
42441
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
42442
+ ] }),
42443
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.6, 0], children: [
42444
+ /* @__PURE__ */ jsxRuntime.jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
42445
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
42446
+ ] }),
42447
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
42448
+ /* @__PURE__ */ jsxRuntime.jsx("sphereGeometry", { args: [0.12, 8, 8] }),
42449
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
42450
+ ] })
42451
+ ] }),
42452
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("group", { position: [0, 1.2, 0], children: [
42453
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [-0.25, 0, 0], children: [
42454
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5, 0.05] }),
42455
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: 3355443 })
42456
+ ] }),
42457
+ /* @__PURE__ */ jsxRuntime.jsxs(
42458
+ "mesh",
42459
+ {
42460
+ position: [
42461
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
42462
+ 0,
42463
+ 0.01
42464
+ ],
42465
+ children: [
42466
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
42467
+ /* @__PURE__ */ jsxRuntime.jsx(
42468
+ "meshBasicMaterial",
42469
+ {
42470
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
42471
+ }
42472
+ )
42473
+ ]
42474
+ }
42475
+ )
42476
+ ] })
42477
+ ]
42478
+ }
42479
+ );
42480
+ },
42481
+ [selectedUnitId, handleUnitClick]
42482
+ );
42483
+ const DefaultFeatureRenderer = React83.useCallback(
42484
+ ({
42485
+ feature,
42486
+ position
42487
+ }) => {
42488
+ if (feature.assetUrl) {
42489
+ return /* @__PURE__ */ jsxRuntime.jsx(
42490
+ ModelLoader,
42491
+ {
42492
+ url: feature.assetUrl,
42493
+ position,
42494
+ scale: 0.5,
42495
+ rotation: [0, feature.rotation ?? 0, 0],
42496
+ onClick: () => handleFeatureClick(feature, null),
42497
+ fallbackGeometry: "box"
42498
+ },
42499
+ feature.id
42500
+ );
42501
+ }
42502
+ if (feature.type === "tree") {
42503
+ return /* @__PURE__ */ jsxRuntime.jsxs(
42504
+ "group",
42505
+ {
42506
+ position,
42507
+ onClick: (e) => handleFeatureClick(feature, e),
42508
+ userData: { type: "feature", featureId: feature.id },
42509
+ children: [
42510
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.4, 0], children: [
42511
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
42512
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 9127187 })
42513
+ ] }),
42514
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
42515
+ /* @__PURE__ */ jsxRuntime.jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
42516
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 2263842 })
42517
+ ] })
42518
+ ]
42519
+ }
42520
+ );
42521
+ }
42522
+ if (feature.type === "rock") {
42523
+ return /* @__PURE__ */ jsxRuntime.jsxs(
42524
+ "mesh",
42525
+ {
42526
+ position: [position[0], position[1] + 0.3, position[2]],
42527
+ onClick: (e) => handleFeatureClick(feature, e),
42528
+ userData: { type: "feature", featureId: feature.id },
42529
+ children: [
42530
+ /* @__PURE__ */ jsxRuntime.jsx("dodecahedronGeometry", { args: [0.3, 0] }),
42531
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 8421504 })
42532
+ ]
42533
+ }
42534
+ );
42535
+ }
42536
+ return null;
42537
+ },
42538
+ [handleFeatureClick]
42539
+ );
42540
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
42541
+ return /* @__PURE__ */ jsxRuntime.jsx(
42542
+ Canvas3DLoadingState,
42543
+ {
42544
+ progress,
42545
+ loaded,
42546
+ total,
42547
+ message: loadingMessage,
42548
+ className
42549
+ }
42550
+ );
42551
+ }
42552
+ const displayError = externalError || internalError;
42553
+ if (displayError) {
42554
+ 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: [
42555
+ "Error: ",
42556
+ displayError
42557
+ ] }) }) });
42558
+ }
42559
+ return /* @__PURE__ */ jsxRuntime.jsx(
42560
+ Canvas3DErrorBoundary,
42561
+ {
42562
+ onError: (err) => setInternalError(err.message),
42563
+ onReset: () => setInternalError(null),
42564
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
42565
+ "div",
42566
+ {
42567
+ ref: containerRef,
42568
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
42569
+ "data-orientation": orientation,
42570
+ "data-camera-mode": cameraMode,
42571
+ "data-overlay": overlay,
42572
+ children: [
42573
+ /* @__PURE__ */ jsxRuntime.jsxs(
42574
+ fiber.Canvas,
42575
+ {
42576
+ shadows,
42577
+ camera: {
42578
+ position: cameraConfig.position,
42579
+ fov: cameraConfig.fov,
42580
+ near: 0.1,
42581
+ far: 1e3
42582
+ },
42583
+ style: { background: backgroundColor },
42584
+ onClick: (e) => {
42585
+ if (e.target === e.currentTarget) {
42586
+ eventHandlers.handleCanvasClick(e);
42587
+ }
42588
+ },
42589
+ children: [
42590
+ /* @__PURE__ */ jsxRuntime.jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
42591
+ /* @__PURE__ */ jsxRuntime.jsx("ambientLight", { intensity: 0.6 }),
42592
+ /* @__PURE__ */ jsxRuntime.jsx(
42593
+ "directionalLight",
42594
+ {
42595
+ position: [10, 20, 10],
42596
+ intensity: 0.8,
42597
+ castShadow: shadows,
42598
+ "shadow-mapSize": [2048, 2048]
42599
+ }
42600
+ ),
42601
+ /* @__PURE__ */ jsxRuntime.jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
42602
+ showGrid && /* @__PURE__ */ jsxRuntime.jsx(
42603
+ drei.Grid,
42604
+ {
42605
+ args: [
42606
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
42607
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
42608
+ ],
42609
+ position: [
42610
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
42611
+ 0,
42612
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
42613
+ ],
42614
+ cellSize: 1,
42615
+ cellThickness: 1,
42616
+ cellColor: "#444444",
42617
+ sectionSize: 5,
42618
+ sectionThickness: 1.5,
42619
+ sectionColor: "#666666",
42620
+ fadeDistance: 50,
42621
+ fadeStrength: 1
42622
+ }
42623
+ ),
42624
+ tiles.map((tile, index) => {
42625
+ const position = gridToWorld(
42626
+ tile.x,
42627
+ tile.z ?? tile.y ?? 0,
42628
+ tile.elevation ?? 0
42629
+ );
42630
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
42631
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
42632
+ }),
42633
+ features.map((feature, index) => {
42634
+ const position = gridToWorld(
42635
+ feature.x,
42636
+ feature.z ?? feature.y ?? 0,
42637
+ (feature.elevation ?? 0) + 0.5
42638
+ );
42639
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
42640
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
42641
+ }),
42642
+ units.map((unit) => {
42643
+ const position = gridToWorld(
42644
+ unit.x ?? 0,
42645
+ unit.z ?? unit.y ?? 0,
42646
+ (unit.elevation ?? 0) + 0.5
42647
+ );
42648
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
42649
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { unit, position }, unit.id);
42650
+ }),
42651
+ children,
42652
+ /* @__PURE__ */ jsxRuntime.jsx(
42653
+ drei.OrbitControls,
42654
+ {
42655
+ ref: controlsRef,
42656
+ target: cameraTarget,
42657
+ enableDamping: true,
42658
+ dampingFactor: 0.05,
42659
+ minDistance: 2,
42660
+ maxDistance: 100,
42661
+ maxPolarAngle: Math.PI / 2 - 0.1
42662
+ }
42663
+ )
42664
+ ]
42665
+ }
42666
+ ),
42667
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__coordinates", children: [
42668
+ "X: ",
42669
+ hoveredTile.x,
42670
+ ", Z: ",
42671
+ hoveredTile.z ?? hoveredTile.y ?? 0
42672
+ ] }),
42673
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__tile-info", children: [
42674
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
42675
+ hoveredTile.terrain && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
42676
+ ] })
42677
+ ]
42678
+ }
42679
+ )
42680
+ }
42681
+ );
42682
+ }
42683
+ );
42684
+ GameCanvas3D.displayName = "GameCanvas3D";
42685
+ }
42686
+ });
42687
+ function GameBoard3D({
42688
+ entity,
42689
+ tiles = [],
42690
+ features = [],
42691
+ cameraMode = "perspective",
42692
+ backgroundColor = "#2a1a1a",
42693
+ tileClickEvent,
42694
+ unitClickEvent,
42695
+ attackEvent,
42696
+ endTurnEvent,
42697
+ cancelEvent,
42698
+ playAgainEvent,
42699
+ gameEndEvent,
42700
+ className
42701
+ }) {
42702
+ const row = boardEntity(entity);
42703
+ const eventBus = useEventBus();
42704
+ const entityUnits = row ? rows(row.units) : [];
42705
+ const units = entityUnits;
42706
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
42707
+ const phase = row ? str(row.phase) : "observation";
42708
+ const result = row ? str(row.result) : "none";
42709
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
42710
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
42711
+ const turn = row ? num(row.turn) : 0;
42712
+ const currentTeam = row ? str(row.currentTeam) : "player";
42713
+ const isGameOver = result !== "none";
42714
+ const gameEndEmittedRef = React83.useRef(false);
42715
+ React83.useEffect(() => {
42716
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
42717
+ if (!gameEndEmittedRef.current) {
42718
+ gameEndEmittedRef.current = true;
42719
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
42720
+ }
42721
+ } else {
42722
+ gameEndEmittedRef.current = false;
42723
+ }
42724
+ }, [result, gameEndEvent, eventBus]);
42725
+ const checkGameEnd = React83.useCallback(() => {
42726
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
42727
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
42728
+ if (alivePlayer.length === 0 && gameEndEvent) {
42729
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
42730
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
42731
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
42732
+ }
42733
+ }, [entityUnits, gameEndEvent, eventBus]);
42734
+ const handleUnitClickCallback = React83.useCallback((isoUnit) => {
42735
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
42736
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
42737
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
42738
+ if (!attackerRow || !targetRow) return;
42739
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
42740
+ const ap = unitPosition(attackerRow);
42741
+ const tp = unitPosition(targetRow);
42742
+ const dx = Math.abs(ap.x - tp.x);
42743
+ const dy = Math.abs(ap.y - tp.y);
42744
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
42745
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
42746
+ eventBus.emit(`UI:${attackEvent}`, {
42747
+ attackerId: str(attackerRow.id),
42748
+ targetId: str(targetRow.id),
42749
+ damage
42750
+ });
42751
+ setTimeout(checkGameEnd, 100);
42752
+ }
42753
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
42754
+ const handleEndTurn = React83.useCallback(() => {
42755
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
42756
+ }, [endTurnEvent, eventBus]);
42757
+ const handleCancel = React83.useCallback(() => {
42758
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
42759
+ }, [cancelEvent, eventBus]);
42760
+ const handlePlayAgain = React83.useCallback(() => {
42761
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
42762
+ }, [playAgainEvent, eventBus]);
42763
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
42764
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
42765
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
42766
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
42767
+ "\u2014 ",
42768
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
42769
+ ] }),
42770
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
42771
+ "Turn ",
42772
+ turn
42773
+ ] })
42774
+ ] }),
42775
+ /* @__PURE__ */ jsxRuntime.jsx(
42776
+ GameCanvas3D,
42777
+ {
42778
+ tiles,
42779
+ units,
42780
+ features,
42781
+ cameraMode,
42782
+ showGrid: true,
42783
+ showCoordinates: false,
42784
+ showTileInfo: false,
42785
+ shadows: true,
42786
+ backgroundColor,
42787
+ tileClickEvent,
42788
+ unitClickEvent,
42789
+ onUnitClick: handleUnitClickCallback,
42790
+ selectedUnitId,
42791
+ validMoves,
42792
+ attackTargets,
42793
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
42794
+ }
42795
+ ),
42796
+ !isGameOver && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
42797
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsxRuntime.jsx(
42798
+ Button,
42799
+ {
42800
+ variant: "secondary",
42801
+ className: "shadow-xl",
42802
+ onClick: handleCancel,
42803
+ children: "Cancel"
42804
+ }
42805
+ ),
42806
+ phase !== "enemy_turn" && /* @__PURE__ */ jsxRuntime.jsx(
42807
+ Button,
42808
+ {
42809
+ variant: "primary",
42810
+ className: "shadow-xl",
42811
+ onClick: handleEndTurn,
42812
+ children: "End Turn"
42813
+ }
42814
+ )
42815
+ ] }),
42816
+ 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: [
42817
+ /* @__PURE__ */ jsxRuntime.jsx(
42818
+ Typography,
42819
+ {
42820
+ variant: "h2",
42821
+ className: cn(
42822
+ "overlay__result",
42823
+ result === "victory" ? "text-yellow-400" : "text-red-500"
42824
+ ),
42825
+ children: result === "victory" ? "Victory!" : "Defeat"
42826
+ }
42827
+ ),
42828
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
42829
+ "Completed in ",
42830
+ turn,
42831
+ " turn",
42832
+ turn !== 1 ? "s" : ""
42833
+ ] }),
42834
+ /* @__PURE__ */ jsxRuntime.jsx(
42835
+ Button,
42836
+ {
42837
+ variant: "primary",
42838
+ className: "px-8 py-3 font-semibold",
42839
+ onClick: handlePlayAgain,
42840
+ children: "Play Again"
42841
+ }
42842
+ )
42843
+ ] }) })
42844
+ ] });
42845
+ }
42846
+ var init_GameBoard3D = __esm({
42847
+ "components/game/organisms/GameBoard3D.tsx"() {
42848
+ "use client";
42849
+ init_cn();
42850
+ init_Button();
42851
+ init_Typography();
42852
+ init_Stack();
42853
+ init_useEventBus();
42854
+ init_GameCanvas3D2();
42855
+ init_boardEntity();
42856
+ GameBoard3D.displayName = "GameBoard3D";
42857
+ }
42858
+ });
41203
42859
  var GameShell;
41204
42860
  var init_GameShell = __esm({
41205
42861
  "components/game/templates/GameShell.tsx"() {
@@ -42742,6 +44398,236 @@ var init_NegotiatorBoard = __esm({
42742
44398
  NegotiatorBoard.displayName = "NegotiatorBoard";
42743
44399
  }
42744
44400
  });
44401
+ function resolveEntity(entity) {
44402
+ if (!entity) return {};
44403
+ if (Array.isArray(entity)) return entity[0] ?? {};
44404
+ return entity;
44405
+ }
44406
+ function numField(row, key, fallback) {
44407
+ const v = row[key];
44408
+ return typeof v === "number" ? v : fallback;
44409
+ }
44410
+ function strField(row, key, fallback) {
44411
+ const v = row[key];
44412
+ return typeof v === "string" ? v : fallback;
44413
+ }
44414
+ function PlatformerBoard({
44415
+ entity,
44416
+ player: propPlayer,
44417
+ platforms: propPlatforms,
44418
+ result: propResult,
44419
+ lives: propLives,
44420
+ score: propScore,
44421
+ level: propLevel,
44422
+ worldWidth = 800,
44423
+ worldHeight = 400,
44424
+ canvasWidth = 800,
44425
+ canvasHeight = 400,
44426
+ playerSprite,
44427
+ tileSprites,
44428
+ bgColor,
44429
+ leftEvent = "LEFT",
44430
+ rightEvent = "RIGHT",
44431
+ jumpEvent = "JUMP",
44432
+ stopEvent = "STOP",
44433
+ playAgainEvent = "PLAY_AGAIN",
44434
+ gameEndEvent = "GAME_END",
44435
+ className
44436
+ }) {
44437
+ const row = resolveEntity(entity);
44438
+ const eventBus = useEventBus();
44439
+ const { t } = hooks.useTranslate();
44440
+ const result = propResult ?? strField(row, "result", "none");
44441
+ propLives ?? numField(row, "lives", 3);
44442
+ const score = propScore ?? numField(row, "score", 0);
44443
+ const level = propLevel ?? numField(row, "level", 1);
44444
+ const entityPlayer = row["player"];
44445
+ const player = propPlayer ?? entityPlayer ?? {
44446
+ x: 80,
44447
+ y: 320,
44448
+ width: 32,
44449
+ height: 48,
44450
+ vx: 0,
44451
+ vy: 0,
44452
+ grounded: false,
44453
+ facingRight: true
44454
+ };
44455
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
44456
+ const platforms = propPlatforms ?? entityPlatforms ?? [
44457
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44458
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44459
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44460
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44461
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44462
+ ];
44463
+ const handleRestart = React83.useCallback(() => {
44464
+ if (playAgainEvent) {
44465
+ eventBus.emit(`UI:${playAgainEvent}`, {});
44466
+ }
44467
+ }, [playAgainEvent, eventBus]);
44468
+ const isGameOver = result === "won" || result === "lost";
44469
+ const gameEndEmittedRef = React83.useRef(false);
44470
+ React83.useEffect(() => {
44471
+ if ((result === "won" || result === "lost") && gameEndEvent) {
44472
+ if (!gameEndEmittedRef.current) {
44473
+ gameEndEmittedRef.current = true;
44474
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44475
+ }
44476
+ } else {
44477
+ gameEndEmittedRef.current = false;
44478
+ }
44479
+ }, [result, gameEndEvent, eventBus]);
44480
+ return /* @__PURE__ */ jsxRuntime.jsx(
44481
+ VStack,
44482
+ {
44483
+ className: cn("platformer-board relative bg-background", className),
44484
+ gap: "none",
44485
+ children: /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative", children: [
44486
+ /* @__PURE__ */ jsxRuntime.jsx(
44487
+ PlatformerCanvas,
44488
+ {
44489
+ player,
44490
+ platforms,
44491
+ worldWidth,
44492
+ worldHeight,
44493
+ canvasWidth,
44494
+ canvasHeight,
44495
+ followCamera: true,
44496
+ bgColor,
44497
+ playerSprite,
44498
+ tileSprites,
44499
+ leftEvent,
44500
+ rightEvent,
44501
+ jumpEvent,
44502
+ stopEvent
44503
+ }
44504
+ ),
44505
+ 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: [
44506
+ /* @__PURE__ */ jsxRuntime.jsx(
44507
+ Typography,
44508
+ {
44509
+ variant: "h2",
44510
+ className: cn(
44511
+ "text-4xl font-black tracking-widest uppercase",
44512
+ result === "won" ? "text-warning" : "text-error"
44513
+ ),
44514
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
44515
+ }
44516
+ ),
44517
+ result === "won" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
44518
+ t("platformer.level"),
44519
+ " ",
44520
+ level,
44521
+ " \u2014 ",
44522
+ t("platformer.score"),
44523
+ ": ",
44524
+ score
44525
+ ] }),
44526
+ result === "lost" && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
44527
+ /* @__PURE__ */ jsxRuntime.jsx(
44528
+ Button,
44529
+ {
44530
+ variant: "primary",
44531
+ className: "px-8 py-3 font-semibold",
44532
+ onClick: handleRestart,
44533
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
44534
+ }
44535
+ )
44536
+ ] }) })
44537
+ ] })
44538
+ }
44539
+ );
44540
+ }
44541
+ var init_PlatformerBoard = __esm({
44542
+ "components/game/organisms/PlatformerBoard.tsx"() {
44543
+ "use client";
44544
+ init_cn();
44545
+ init_useEventBus();
44546
+ init_Box();
44547
+ init_Button();
44548
+ init_Typography();
44549
+ init_Stack();
44550
+ init_PlatformerCanvas();
44551
+ PlatformerBoard.displayName = "PlatformerBoard";
44552
+ }
44553
+ });
44554
+ function PlatformerTemplate({
44555
+ entity,
44556
+ platforms = DEFAULT_PLATFORMS,
44557
+ worldWidth = 800,
44558
+ worldHeight = 400,
44559
+ canvasWidth = 800,
44560
+ canvasHeight = 400,
44561
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
44562
+ tileSprites = DEFAULT_TILE_SPRITES,
44563
+ bgColor = "#5c94fc",
44564
+ gameEndEvent = "GAME_END",
44565
+ className
44566
+ }) {
44567
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
44568
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
44569
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
44570
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
44571
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
44572
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsx(
44573
+ GameHud,
44574
+ {
44575
+ position: "top",
44576
+ stats: [
44577
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
44578
+ { label: "Level", value: level, format: "number", variant: "default" }
44579
+ ]
44580
+ }
44581
+ ) }),
44582
+ /* @__PURE__ */ jsxRuntime.jsx(
44583
+ PlatformerBoard,
44584
+ {
44585
+ entity,
44586
+ platforms,
44587
+ worldWidth,
44588
+ worldHeight,
44589
+ canvasWidth,
44590
+ canvasHeight,
44591
+ playerSprite,
44592
+ tileSprites,
44593
+ bgColor,
44594
+ leftEvent: "LEFT",
44595
+ rightEvent: "RIGHT",
44596
+ jumpEvent: "JUMP",
44597
+ stopEvent: "STOP",
44598
+ playAgainEvent: "PLAY_AGAIN",
44599
+ gameEndEvent
44600
+ }
44601
+ ),
44602
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(ScoreBoard, { score, level }) })
44603
+ ] });
44604
+ }
44605
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
44606
+ var init_PlatformerTemplate = __esm({
44607
+ "components/game/templates/PlatformerTemplate.tsx"() {
44608
+ init_cn();
44609
+ init_Box();
44610
+ init_Stack();
44611
+ init_PlatformerBoard();
44612
+ init_GameHud();
44613
+ init_ScoreBoard();
44614
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
44615
+ DEFAULT_TILE_SPRITES = {
44616
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
44617
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
44618
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
44619
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
44620
+ };
44621
+ DEFAULT_PLATFORMS = [
44622
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44623
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44624
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44625
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44626
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44627
+ ];
44628
+ PlatformerTemplate.displayName = "PlatformerTemplate";
44629
+ }
44630
+ });
42745
44631
  var PricingOrganism;
42746
44632
  var init_PricingOrganism = __esm({
42747
44633
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -42864,6 +44750,356 @@ var init_PricingPageTemplate = __esm({
42864
44750
  PricingPageTemplate.displayName = "PricingPageTemplate";
42865
44751
  }
42866
44752
  });
44753
+ function RoguelikeBoard({
44754
+ entity,
44755
+ tiles: propTiles,
44756
+ enemies: propEnemies,
44757
+ items: propItems,
44758
+ player: propPlayer,
44759
+ playerHp: propPlayerHp,
44760
+ playerMaxHp: propPlayerMaxHp,
44761
+ playerAttack: propPlayerAttack,
44762
+ depth: propDepth,
44763
+ result: propResult,
44764
+ phase: propPhase,
44765
+ stairsX: propStairsX,
44766
+ stairsY: propStairsY,
44767
+ assetManifest: propAssetManifest,
44768
+ scale = 0.45,
44769
+ unitScale = 1,
44770
+ moveEvent,
44771
+ playAgainEvent,
44772
+ gameEndEvent,
44773
+ className
44774
+ }) {
44775
+ const board = boardEntity(entity) ?? {};
44776
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
44777
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
44778
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
44779
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
44780
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
44781
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
44782
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
44783
+ const depth = propDepth ?? num(board.depth, 1);
44784
+ const result = propResult ?? (str(board.result) || "none");
44785
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
44786
+ propStairsX ?? num(board.stairsX, 3);
44787
+ propStairsY ?? num(board.stairsY, 3);
44788
+ const eventBus = useEventBus();
44789
+ const { t } = hooks.useTranslate();
44790
+ const [hoveredTile, setHoveredTile] = React83.useState(null);
44791
+ const emitMove = React83.useCallback((dx, dy) => {
44792
+ if (!moveEvent) return;
44793
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
44794
+ }, [moveEvent, eventBus]);
44795
+ React83.useEffect(() => {
44796
+ const handler = (e) => {
44797
+ if (result !== "none" || phase !== "player_turn") return;
44798
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
44799
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
44800
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
44801
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
44802
+ };
44803
+ window.addEventListener("keydown", handler);
44804
+ return () => window.removeEventListener("keydown", handler);
44805
+ }, [result, phase, emitMove]);
44806
+ React83.useEffect(() => {
44807
+ if ((result === "won" || result === "lost") && gameEndEvent) {
44808
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44809
+ }
44810
+ }, [result, gameEndEvent, eventBus]);
44811
+ const handleTileClick = React83.useCallback((x, y) => {
44812
+ if (result !== "none" || phase !== "player_turn") return;
44813
+ const dx = x - player.x;
44814
+ const dy = y - player.y;
44815
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
44816
+ emitMove(dx, dy);
44817
+ }
44818
+ }, [result, phase, player, emitMove]);
44819
+ const handleReset = React83.useCallback(() => {
44820
+ if (!playAgainEvent) return;
44821
+ eventBus.emit(`UI:${playAgainEvent}`, {});
44822
+ }, [playAgainEvent, eventBus]);
44823
+ const isoUnits = React83.useMemo(() => {
44824
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
44825
+ return [
44826
+ {
44827
+ id: "player",
44828
+ position: player,
44829
+ name: t("roguelike.player") || "Hero",
44830
+ team: "player",
44831
+ health: playerHp,
44832
+ maxHealth: playerMaxHp,
44833
+ unitType: "player",
44834
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
44835
+ },
44836
+ ...liveEnemies.map((e) => ({
44837
+ id: e.id,
44838
+ position: { x: e.x, y: e.y },
44839
+ name: t("roguelike.enemy") || "Enemy",
44840
+ team: "enemy",
44841
+ health: e.hp,
44842
+ maxHealth: 6,
44843
+ unitType: "enemy",
44844
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
44845
+ }))
44846
+ ];
44847
+ }, [player, enemies, playerHp, playerMaxHp, t]);
44848
+ const isoFeatures = React83.useMemo(
44849
+ () => items.map((it) => ({
44850
+ id: it.id,
44851
+ x: it.x,
44852
+ y: it.y,
44853
+ type: it.kind,
44854
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
44855
+ })),
44856
+ [items]
44857
+ );
44858
+ const validMoves = React83.useMemo(() => {
44859
+ if (result !== "none" || phase !== "player_turn") return [];
44860
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
44861
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
44862
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
44863
+ return tile?.passable ?? false;
44864
+ });
44865
+ }, [result, phase, player, tiles]);
44866
+ const isGameOver = result === "won" || result === "lost";
44867
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
44868
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
44869
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
44870
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
44871
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
44872
+ ] }),
44873
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
44874
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
44875
+ /* @__PURE__ */ jsxRuntime.jsxs(
44876
+ Typography,
44877
+ {
44878
+ variant: "body1",
44879
+ className: cn(
44880
+ "font-bold tabular-nums",
44881
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
44882
+ ),
44883
+ children: [
44884
+ playerHp,
44885
+ " / ",
44886
+ playerMaxHp
44887
+ ]
44888
+ }
44889
+ )
44890
+ ] }),
44891
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
44892
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
44893
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
44894
+ ] }),
44895
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
44896
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
44897
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
44898
+ ] })
44899
+ ] }),
44900
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
44901
+ IsometricCanvas_default,
44902
+ {
44903
+ tiles,
44904
+ units: isoUnits,
44905
+ features: isoFeatures,
44906
+ selectedUnitId: null,
44907
+ validMoves,
44908
+ attackTargets: [],
44909
+ hoveredTile,
44910
+ onTileClick: handleTileClick,
44911
+ onUnitClick: () => void 0,
44912
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
44913
+ onTileLeave: () => setHoveredTile(null),
44914
+ scale,
44915
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
44916
+ assetManifest: propAssetManifest,
44917
+ unitScale
44918
+ }
44919
+ ) }),
44920
+ !isGameOver && /* @__PURE__ */ jsxRuntime.jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
44921
+ { label: "\u2191", dx: 0, dy: -1 },
44922
+ { label: "\u2193", dx: 0, dy: 1 },
44923
+ { label: "\u2190", dx: -1, dy: 0 },
44924
+ { label: "\u2192", dx: 1, dy: 0 }
44925
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsxRuntime.jsx(
44926
+ Button,
44927
+ {
44928
+ variant: "secondary",
44929
+ className: "w-9 h-9 p-0 text-base font-mono",
44930
+ onClick: () => emitMove(dx, dy),
44931
+ disabled: phase !== "player_turn",
44932
+ children: label
44933
+ },
44934
+ label
44935
+ )) }),
44936
+ 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: [
44937
+ /* @__PURE__ */ jsxRuntime.jsx(
44938
+ Typography,
44939
+ {
44940
+ variant: "h2",
44941
+ className: cn(
44942
+ "text-4xl font-black tracking-widest uppercase",
44943
+ result === "won" ? "text-warning" : "text-error"
44944
+ ),
44945
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
44946
+ }
44947
+ ),
44948
+ result === "won" && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
44949
+ /* @__PURE__ */ jsxRuntime.jsx(
44950
+ Button,
44951
+ {
44952
+ variant: "primary",
44953
+ className: "px-8 py-3 font-semibold",
44954
+ onClick: handleReset,
44955
+ children: t("roguelike.playAgain") || "Play Again"
44956
+ }
44957
+ )
44958
+ ] }) })
44959
+ ] });
44960
+ }
44961
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
44962
+ var init_RoguelikeBoard = __esm({
44963
+ "components/game/organisms/RoguelikeBoard.tsx"() {
44964
+ "use client";
44965
+ init_cn();
44966
+ init_useEventBus();
44967
+ init_Box();
44968
+ init_Button();
44969
+ init_Typography();
44970
+ init_Stack();
44971
+ init_IsometricCanvas();
44972
+ init_boardEntity();
44973
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
44974
+ DEFAULT_TILES2 = [
44975
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44976
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44977
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44978
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44979
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44980
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44981
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44982
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44983
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44984
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44985
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44986
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44987
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44988
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44989
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44990
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44991
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44992
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44993
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
44994
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44995
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44996
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44997
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44998
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44999
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
45000
+ ];
45001
+ DEFAULT_ENEMIES = [
45002
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
45003
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
45004
+ ];
45005
+ DEFAULT_ITEMS = [
45006
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
45007
+ ];
45008
+ FEATURE_SPRITE = {
45009
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
45010
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
45011
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
45012
+ };
45013
+ RoguelikeBoard.displayName = "RoguelikeBoard";
45014
+ }
45015
+ });
45016
+ function RoguelikeTemplate({
45017
+ entity,
45018
+ scale = 0.45,
45019
+ unitScale = 1,
45020
+ tiles = DEFAULT_ROGUELIKE_TILES,
45021
+ enemies,
45022
+ items,
45023
+ stairsX,
45024
+ stairsY,
45025
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
45026
+ gameEndEvent,
45027
+ className
45028
+ }) {
45029
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
45030
+ if (!resolved && !tiles && !assetManifest) return null;
45031
+ return /* @__PURE__ */ jsxRuntime.jsx(
45032
+ RoguelikeBoard,
45033
+ {
45034
+ entity: resolved,
45035
+ tiles,
45036
+ enemies,
45037
+ items,
45038
+ stairsX,
45039
+ stairsY,
45040
+ assetManifest,
45041
+ scale,
45042
+ unitScale,
45043
+ moveEvent: "MOVE",
45044
+ playAgainEvent: "PLAY_AGAIN",
45045
+ gameEndEvent: gameEndEvent ?? "GAME_END",
45046
+ className
45047
+ }
45048
+ );
45049
+ }
45050
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
45051
+ var init_RoguelikeTemplate = __esm({
45052
+ "components/game/templates/RoguelikeTemplate.tsx"() {
45053
+ init_RoguelikeBoard();
45054
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
45055
+ DEFAULT_ROGUELIKE_TILES = [
45056
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45057
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45058
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45059
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45060
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45061
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45062
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45063
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45064
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45065
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45066
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45067
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45068
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45069
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45070
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45071
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45072
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45073
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45074
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
45075
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45076
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45077
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45078
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45079
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45080
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
45081
+ ];
45082
+ DEFAULT_ROGUELIKE_MANIFEST = {
45083
+ baseUrl: CDN5,
45084
+ terrains: {
45085
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
45086
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
45087
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
45088
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
45089
+ },
45090
+ units: {
45091
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
45092
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
45093
+ },
45094
+ features: {
45095
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
45096
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
45097
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
45098
+ }
45099
+ };
45100
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
45101
+ }
45102
+ });
42867
45103
 
42868
45104
  // lib/traitRegistry.ts
42869
45105
  function getAllTraits() {
@@ -44098,7 +46334,7 @@ function getAllEvents(traits2) {
44098
46334
  function EventDispatcherTab({ traits: traits2, schema }) {
44099
46335
  const eventBus = useEventBus();
44100
46336
  const { t } = hooks.useTranslate();
44101
- const [log12, setLog] = React83__namespace.useState([]);
46337
+ const [log14, setLog] = React83__namespace.useState([]);
44102
46338
  const prevStatesRef = React83__namespace.useRef(/* @__PURE__ */ new Map());
44103
46339
  React83__namespace.useEffect(() => {
44104
46340
  for (const trait of traits2) {
@@ -44162,9 +46398,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
44162
46398
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
44163
46399
  /* @__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)) })
44164
46400
  ] }),
44165
- log12.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
46401
+ log14.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
44166
46402
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
44167
- /* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log12.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
46403
+ /* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log14.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
44168
46404
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-purple-400", children: entry.traitName }),
44169
46405
  " ",
44170
46406
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -46085,9 +48321,9 @@ function VariablePanel({
46085
48321
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
46086
48322
  variables.map((v) => {
46087
48323
  const name = v.name == null ? "" : String(v.name);
46088
- const value = numField(v.value);
46089
- const max = numField(v.max, 100);
46090
- const min = numField(v.min, 0);
48324
+ const value = numField2(v.value);
48325
+ const max = numField2(v.max, 100);
48326
+ const min = numField2(v.min, 0);
46091
48327
  const unit = v.unit == null ? "" : String(v.unit);
46092
48328
  const pct = Math.round((value - min) / (max - min) * 100);
46093
48329
  const isHigh = pct > 80;
@@ -46117,12 +48353,12 @@ function VariablePanel({
46117
48353
  })
46118
48354
  ] });
46119
48355
  }
46120
- var numField;
48356
+ var numField2;
46121
48357
  var init_VariablePanel = __esm({
46122
48358
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
46123
48359
  init_atoms2();
46124
48360
  init_cn();
46125
- numField = (v, fallback = 0) => {
48361
+ numField2 = (v, fallback = 0) => {
46126
48362
  const n = Number(v);
46127
48363
  return Number.isFinite(n) ? n : fallback;
46128
48364
  };
@@ -46975,6 +49211,289 @@ var init_ToastSlot = __esm({
46975
49211
  ToastSlot.displayName = "ToastSlot";
46976
49212
  }
46977
49213
  });
49214
+ function tilesToIso(tiles) {
49215
+ return tiles.map((t) => ({
49216
+ x: t.x,
49217
+ y: t.y,
49218
+ terrain: t.terrain,
49219
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
49220
+ passable: t.passable
49221
+ }));
49222
+ }
49223
+ function towersToUnits(towers) {
49224
+ return towers.map((t) => ({
49225
+ id: t.id,
49226
+ position: { x: t.x, y: t.y },
49227
+ name: "Tower",
49228
+ team: "player",
49229
+ sprite: TOWER_SPRITE,
49230
+ unitType: "guardian",
49231
+ health: 1,
49232
+ maxHealth: 1
49233
+ }));
49234
+ }
49235
+ function creepsToUnits(creeps) {
49236
+ return creeps.map((c) => ({
49237
+ id: c.id,
49238
+ position: { x: c.x, y: c.y },
49239
+ name: "Creep",
49240
+ team: "enemy",
49241
+ sprite: CREEP_SPRITE,
49242
+ unitType: "scrapper",
49243
+ health: c.hp,
49244
+ maxHealth: c.maxHp
49245
+ }));
49246
+ }
49247
+ function pathToFeatures(path) {
49248
+ return path.map((p2, i) => ({
49249
+ id: `path-${i}`,
49250
+ x: p2.x,
49251
+ y: p2.y,
49252
+ type: "path-marker",
49253
+ sprite: `${CDN6}world-map/road_straight.png`
49254
+ }));
49255
+ }
49256
+ function TowerDefenseBoard({
49257
+ entity,
49258
+ tiles: propTiles,
49259
+ path: propPath,
49260
+ towers: propTowers,
49261
+ creeps: propCreeps,
49262
+ gold: propGold,
49263
+ lives: propLives,
49264
+ wave: propWave,
49265
+ maxWaves: propMaxWaves,
49266
+ result: propResult,
49267
+ waveActive: propWaveActive,
49268
+ towerCost = 25,
49269
+ scale = 0.45,
49270
+ placeTowerEvent,
49271
+ startWaveEvent,
49272
+ playAgainEvent,
49273
+ gameEndEvent,
49274
+ className
49275
+ }) {
49276
+ const board = boardEntity(entity) ?? {};
49277
+ const eventBus = useEventBus();
49278
+ const { t } = hooks.useTranslate();
49279
+ const tiles = propTiles ?? rows(board.tiles);
49280
+ const path = propPath ?? rows(board.path);
49281
+ const towers = propTowers ?? rows(board.towers);
49282
+ const creeps = propCreeps ?? rows(board.creeps);
49283
+ const gold = propGold ?? num(board.gold, 100);
49284
+ const lives = propLives ?? num(board.lives, 20);
49285
+ const wave = propWave ?? num(board.wave, 1);
49286
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
49287
+ const result = propResult ?? (str(board.result) || "none");
49288
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
49289
+ const [hoveredTile, setHoveredTile] = React83.useState(null);
49290
+ const emittedGameEnd = React83.useRef(false);
49291
+ if (result !== "none" && !emittedGameEnd.current) {
49292
+ emittedGameEnd.current = true;
49293
+ if (gameEndEvent) {
49294
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
49295
+ }
49296
+ }
49297
+ if (result === "none") {
49298
+ emittedGameEnd.current = false;
49299
+ }
49300
+ const towerPositions = React83.useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
49301
+ const pathPositions = React83.useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
49302
+ const validMoves = React83.useMemo(() => {
49303
+ if (result !== "none" || gold < towerCost) return [];
49304
+ return tiles.filter(
49305
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
49306
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
49307
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
49308
+ const isoTiles = React83.useMemo(() => tilesToIso(tiles), [tiles]);
49309
+ const towerUnits = React83.useMemo(() => towersToUnits(towers), [towers]);
49310
+ const creepUnits = React83.useMemo(() => creepsToUnits(creeps), [creeps]);
49311
+ const isoUnits = React83.useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
49312
+ const pathFeatures = React83.useMemo(() => pathToFeatures(path), [path]);
49313
+ const handleTileClick = React83.useCallback((x, y) => {
49314
+ if (result !== "none") return;
49315
+ if (pathPositions.has(`${x},${y}`)) return;
49316
+ if (towerPositions.has(`${x},${y}`)) return;
49317
+ if (gold < towerCost) return;
49318
+ if (placeTowerEvent) {
49319
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
49320
+ }
49321
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
49322
+ const handleStartWave = React83.useCallback(() => {
49323
+ if (startWaveEvent) {
49324
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
49325
+ }
49326
+ }, [startWaveEvent, wave, eventBus]);
49327
+ const handlePlayAgain = React83.useCallback(() => {
49328
+ if (playAgainEvent) {
49329
+ eventBus.emit(`UI:${playAgainEvent}`, {});
49330
+ }
49331
+ }, [playAgainEvent, eventBus]);
49332
+ const canPlaceTower = gold >= towerCost && result === "none";
49333
+ const canStartWave = !waveActive && result === "none";
49334
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
49335
+ /* @__PURE__ */ jsxRuntime.jsxs(
49336
+ HStack,
49337
+ {
49338
+ className: "px-4 py-2 border-b border-border bg-surface",
49339
+ gap: "lg",
49340
+ align: "center",
49341
+ children: [
49342
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
49343
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
49344
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
49345
+ ] }),
49346
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
49347
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
49348
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
49349
+ ] }),
49350
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
49351
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
49352
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body2", weight: "bold", children: [
49353
+ wave,
49354
+ " / ",
49355
+ maxWaves
49356
+ ] })
49357
+ ] }),
49358
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "ml-auto", children: [
49359
+ canStartWave && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
49360
+ waveActive && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
49361
+ ] })
49362
+ ]
49363
+ }
49364
+ ),
49365
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative flex-1", children: [
49366
+ /* @__PURE__ */ jsxRuntime.jsx(
49367
+ IsometricCanvas_default,
49368
+ {
49369
+ tiles: isoTiles,
49370
+ units: isoUnits,
49371
+ features: pathFeatures,
49372
+ validMoves,
49373
+ hoveredTile,
49374
+ onTileClick: handleTileClick,
49375
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
49376
+ onTileLeave: () => setHoveredTile(null),
49377
+ scale
49378
+ }
49379
+ ),
49380
+ 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)` }) })
49381
+ ] }),
49382
+ 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: [
49383
+ /* @__PURE__ */ jsxRuntime.jsx(
49384
+ Typography,
49385
+ {
49386
+ variant: "h2",
49387
+ className: cn(
49388
+ "text-4xl font-black tracking-widest uppercase",
49389
+ result === "won" ? "text-warning" : "text-error"
49390
+ ),
49391
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
49392
+ }
49393
+ ),
49394
+ /* @__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." }),
49395
+ /* @__PURE__ */ jsxRuntime.jsx(
49396
+ Button,
49397
+ {
49398
+ variant: "primary",
49399
+ className: "px-8 py-3 font-semibold",
49400
+ onClick: handlePlayAgain,
49401
+ children: t("td.playAgain") ?? "Play Again"
49402
+ }
49403
+ )
49404
+ ] }) })
49405
+ ] });
49406
+ }
49407
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
49408
+ var init_TowerDefenseBoard = __esm({
49409
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
49410
+ "use client";
49411
+ init_cn();
49412
+ init_useEventBus();
49413
+ init_Box();
49414
+ init_Button();
49415
+ init_Typography();
49416
+ init_Stack();
49417
+ init_IsometricCanvas();
49418
+ init_boardEntity();
49419
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
49420
+ TERRAIN_SPRITES = {
49421
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
49422
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
49423
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
49424
+ };
49425
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
49426
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
49427
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
49428
+ }
49429
+ });
49430
+ function TowerDefenseTemplate({
49431
+ entity,
49432
+ title = "Tower Defense",
49433
+ tiles,
49434
+ path,
49435
+ towers,
49436
+ gold,
49437
+ lives,
49438
+ maxWaves,
49439
+ towerCost,
49440
+ scale,
49441
+ placeTowerEvent,
49442
+ startWaveEvent,
49443
+ playAgainEvent,
49444
+ gameEndEvent,
49445
+ className
49446
+ }) {
49447
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
49448
+ return /* @__PURE__ */ jsxRuntime.jsxs(
49449
+ Box,
49450
+ {
49451
+ display: "flex",
49452
+ fullHeight: true,
49453
+ className: cn("tower-defense-template flex-col", className),
49454
+ children: [
49455
+ /* @__PURE__ */ jsxRuntime.jsx(
49456
+ HStack,
49457
+ {
49458
+ gap: "sm",
49459
+ align: "center",
49460
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
49461
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title })
49462
+ }
49463
+ ),
49464
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
49465
+ TowerDefenseBoard,
49466
+ {
49467
+ entity: resolved,
49468
+ tiles,
49469
+ path,
49470
+ towers,
49471
+ gold,
49472
+ lives,
49473
+ maxWaves,
49474
+ towerCost,
49475
+ scale,
49476
+ placeTowerEvent,
49477
+ startWaveEvent,
49478
+ playAgainEvent,
49479
+ gameEndEvent,
49480
+ className: "h-full"
49481
+ }
49482
+ ) })
49483
+ ]
49484
+ }
49485
+ );
49486
+ }
49487
+ var init_TowerDefenseTemplate = __esm({
49488
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
49489
+ init_cn();
49490
+ init_Box();
49491
+ init_Stack();
49492
+ init_Typography();
49493
+ init_TowerDefenseBoard();
49494
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
49495
+ }
49496
+ });
46978
49497
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
46979
49498
  const eventBus = useEventBus();
46980
49499
  const {
@@ -47484,48 +50003,48 @@ function WorldMapTemplate({
47484
50003
  }
47485
50004
  );
47486
50005
  }
47487
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
50006
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
47488
50007
  var init_WorldMapTemplate = __esm({
47489
50008
  "components/game/templates/WorldMapTemplate.tsx"() {
47490
50009
  init_WorldMapBoard();
47491
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
50010
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
47492
50011
  DEFAULT_WORLDMAP_TILES = [
47493
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47494
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47495
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47496
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47497
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47498
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47499
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47500
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47501
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47502
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47503
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47504
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47505
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47506
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47507
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47508
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47509
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47510
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47511
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47512
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47513
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47514
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47515
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47516
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47517
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
50012
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50013
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50014
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50015
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50016
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50017
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50018
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50019
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50020
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50021
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50022
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50023
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50024
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
50025
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50026
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50027
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50028
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50029
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50030
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
50031
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50032
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50033
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50034
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50035
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50036
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
47518
50037
  ];
47519
50038
  DEFAULT_WORLDMAP_UNITS = [
47520
- { 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` },
47521
- { 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` }
50039
+ { 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` },
50040
+ { 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` }
47522
50041
  ];
47523
50042
  DEFAULT_WORLDMAP_FEATURES = [
47524
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
47525
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
50043
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
50044
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
47526
50045
  ];
47527
50046
  DEFAULT_WORLDMAP_MANIFEST = {
47528
- baseUrl: CDN3,
50047
+ baseUrl: CDN7,
47529
50048
  terrains: {
47530
50049
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
47531
50050
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -47570,7 +50089,7 @@ function lazyThree(name, loader) {
47570
50089
  ThreeWrapper.displayName = `Lazy(${name})`;
47571
50090
  return ThreeWrapper;
47572
50091
  }
47573
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
50092
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
47574
50093
  var init_component_registry_generated = __esm({
47575
50094
  "components/core/organisms/component-registry.generated.ts"() {
47576
50095
  init_AboutPageTemplate();
@@ -47680,6 +50199,7 @@ var init_component_registry_generated = __esm({
47680
50199
  init_FormSectionHeader();
47681
50200
  init_GameAudioProvider();
47682
50201
  init_GameAudioToggle();
50202
+ init_GameBoard3D();
47683
50203
  init_GameCanvas2D();
47684
50204
  init_GameHud();
47685
50205
  init_GameMenu();
@@ -47737,7 +50257,9 @@ var init_component_registry_generated = __esm({
47737
50257
  init_PageHeader();
47738
50258
  init_Pagination();
47739
50259
  init_PatternTile();
50260
+ init_PlatformerBoard();
47740
50261
  init_PlatformerCanvas();
50262
+ init_PlatformerTemplate();
47741
50263
  init_Popover();
47742
50264
  init_PositionedCanvas();
47743
50265
  init_PowerupSlots();
@@ -47761,6 +50283,8 @@ var init_component_registry_generated = __esm({
47761
50283
  init_ResourceBar();
47762
50284
  init_ResourceCounter();
47763
50285
  init_RichBlockEditor();
50286
+ init_RoguelikeBoard();
50287
+ init_RoguelikeTemplate();
47764
50288
  init_RuntimeDebugger2();
47765
50289
  init_ScaledDiagram();
47766
50290
  init_ScoreBoard();
@@ -47833,6 +50357,8 @@ var init_component_registry_generated = __esm({
47833
50357
  init_Toast();
47834
50358
  init_ToastSlot();
47835
50359
  init_Tooltip();
50360
+ init_TowerDefenseBoard();
50361
+ init_TowerDefenseTemplate();
47836
50362
  init_TraitFrame();
47837
50363
  init_TraitSlot();
47838
50364
  init_TrendIndicator();
@@ -47877,7 +50403,7 @@ var init_component_registry_generated = __esm({
47877
50403
  }
47878
50404
  };
47879
50405
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
47880
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
50406
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
47881
50407
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47882
50408
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47883
50409
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -47996,8 +50522,9 @@ var init_component_registry_generated = __esm({
47996
50522
  "FormSectionHeader": FormSectionHeader,
47997
50523
  "GameAudioProvider": GameAudioProvider,
47998
50524
  "GameAudioToggle": GameAudioToggle,
50525
+ "GameBoard3D": GameBoard3D,
47999
50526
  "GameCanvas2D": GameCanvas2D,
48000
- "GameCanvas3D": GameCanvas3D,
50527
+ "GameCanvas3D": GameCanvas3D2,
48001
50528
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
48002
50529
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
48003
50530
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -48059,7 +50586,9 @@ var init_component_registry_generated = __esm({
48059
50586
  "PageHeader": PageHeader,
48060
50587
  "Pagination": Pagination,
48061
50588
  "PatternTile": PatternTile,
50589
+ "PlatformerBoard": PlatformerBoard,
48062
50590
  "PlatformerCanvas": PlatformerCanvas,
50591
+ "PlatformerTemplate": PlatformerTemplate,
48063
50592
  "Popover": Popover,
48064
50593
  "PositionedCanvas": PositionedCanvas,
48065
50594
  "PowerupSlots": PowerupSlots,
@@ -48083,6 +50612,8 @@ var init_component_registry_generated = __esm({
48083
50612
  "ResourceBar": ResourceBar,
48084
50613
  "ResourceCounter": ResourceCounter,
48085
50614
  "RichBlockEditor": RichBlockEditor,
50615
+ "RoguelikeBoard": RoguelikeBoard,
50616
+ "RoguelikeTemplate": RoguelikeTemplate,
48086
50617
  "RuntimeDebugger": RuntimeDebugger,
48087
50618
  "ScaledDiagram": ScaledDiagram,
48088
50619
  "ScoreBoard": ScoreBoard,
@@ -48158,6 +50689,8 @@ var init_component_registry_generated = __esm({
48158
50689
  "Toast": Toast,
48159
50690
  "ToastSlot": ToastSlot,
48160
50691
  "Tooltip": Tooltip,
50692
+ "TowerDefenseBoard": TowerDefenseBoard,
50693
+ "TowerDefenseTemplate": TowerDefenseTemplate,
48161
50694
  "TraitFrame": TraitFrame,
48162
50695
  "TraitSlot": TraitSlot,
48163
50696
  "TrendIndicator": TrendIndicator,
@@ -49329,7 +51862,7 @@ init_UISlotRenderer();
49329
51862
  // providers/VerificationProvider.tsx
49330
51863
  init_useEventBus();
49331
51864
  init_verificationRegistry();
49332
- var log11 = logger.createLogger("almadar:verify");
51865
+ var log13 = logger.createLogger("almadar:verify");
49333
51866
  var DISPATCH_SUFFIX = ":DISPATCH";
49334
51867
  var SUCCESS_SUFFIX = ":SUCCESS";
49335
51868
  var ERROR_SUFFIX = ":ERROR";
@@ -49376,7 +51909,7 @@ function VerificationProvider({
49376
51909
  const verificationProviderLifecycleListener = (evt) => {
49377
51910
  const parsed = parseLifecycleEvent(evt.type);
49378
51911
  if (!parsed) return;
49379
- log11.debug("lifecycle:event", { kind: parsed.kind, traitName: parsed.traitName, event: parsed.event, type: evt.type });
51912
+ log13.debug("lifecycle:event", { kind: parsed.kind, traitName: parsed.traitName, event: parsed.event, type: evt.type });
49380
51913
  const payload = evt.payload ?? {};
49381
51914
  if (parsed.kind === "dispatch") {
49382
51915
  const key = `${parsed.traitName}:${String(payload["event"] ?? "")}`;
@@ -49431,7 +51964,7 @@ function VerificationProvider({
49431
51964
  },
49432
51965
  timestamp: Date.now()
49433
51966
  });
49434
- log11.debug("transition:success", { trait: parsed.traitName, event: parsed.event, from: pending?.from, to: newState, effectCount: effects.length });
51967
+ log13.debug("transition:success", { trait: parsed.traitName, event: parsed.event, from: pending?.from, to: newState, effectCount: effects.length });
49435
51968
  } else if (parsed.kind === "error" && parsed.event) {
49436
51969
  const key = `${parsed.traitName}:${parsed.event}`;
49437
51970
  const pending = pendingRef.current.get(key);
@@ -49461,7 +51994,7 @@ function VerificationProvider({
49461
51994
  },
49462
51995
  timestamp: Date.now()
49463
51996
  });
49464
- log11.warn("transition:error", { trait: parsed.traitName, event: parsed.event, from: fromState, error: errorMsg });
51997
+ log13.warn("transition:error", { trait: parsed.traitName, event: parsed.event, from: fromState, error: errorMsg });
49465
51998
  }
49466
51999
  };
49467
52000
  Object.defineProperty(verificationProviderLifecycleListener, "name", {