@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
  var runtime = require('@almadar/runtime');
46
52
  var OrbitalServerRuntime = require('@almadar/runtime/OrbitalServerRuntime');
@@ -90,6 +96,7 @@ var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown);
90
96
  var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
91
97
  var remarkMath__default = /*#__PURE__*/_interopDefault(remarkMath);
92
98
  var rehypeKatex__default = /*#__PURE__*/_interopDefault(rehypeKatex);
99
+ var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
93
100
 
94
101
  var __defProp = Object.defineProperty;
95
102
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -455,7 +462,7 @@ var init_Box = __esm({
455
462
  position,
456
463
  className,
457
464
  children,
458
- as: Component = "div",
465
+ as: Component2 = "div",
459
466
  action,
460
467
  actionPayload,
461
468
  hoverEvent,
@@ -487,7 +494,7 @@ var init_Box = __esm({
487
494
  }, [hoverEvent, eventBus, onMouseLeave]);
488
495
  const isClickable = action || onClick;
489
496
  return React82__namespace.default.createElement(
490
- Component,
497
+ Component2,
491
498
  {
492
499
  ref,
493
500
  className: cn(
@@ -625,9 +632,9 @@ function resolvePhosphor(name, weight, family) {
625
632
  (lib) => {
626
633
  const PhosphorComp = lib[target];
627
634
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
628
- const Component = PhosphorComp;
635
+ const Component2 = PhosphorComp;
629
636
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
630
- Component,
637
+ Component2,
631
638
  {
632
639
  weight,
633
640
  className: props.className,
@@ -651,9 +658,9 @@ function resolveTabler(name, family) {
651
658
  (lib) => {
652
659
  const TablerComp = lib[target];
653
660
  if (!TablerComp || typeof TablerComp !== "object") return null;
654
- const Component = TablerComp;
661
+ const Component2 = TablerComp;
655
662
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
656
- Component,
663
+ Component2,
657
664
  {
658
665
  stroke: props.strokeWidth ?? 1.5,
659
666
  className: props.className,
@@ -677,9 +684,9 @@ function resolveFa(name, family) {
677
684
  (lib) => {
678
685
  const FaComp = lib[target];
679
686
  if (!FaComp || typeof FaComp !== "function") return null;
680
- const Component = FaComp;
687
+ const Component2 = FaComp;
681
688
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
682
- Component,
689
+ Component2,
683
690
  {
684
691
  className: props.className,
685
692
  style: props.style,
@@ -1634,8 +1641,8 @@ var init_Typography = __esm({
1634
1641
  children
1635
1642
  }) => {
1636
1643
  const variant = variantProp ?? (level ? `h${level}` : "body1");
1637
- const Component = as || defaultElements[variant];
1638
- const Comp = Component;
1644
+ const Component2 = as || defaultElements[variant];
1645
+ const Comp = Component2;
1639
1646
  return /* @__PURE__ */ jsxRuntime.jsx(
1640
1647
  Comp,
1641
1648
  {
@@ -4190,10 +4197,10 @@ var init_Center = __esm({
4190
4197
  className,
4191
4198
  style,
4192
4199
  children,
4193
- as: Component = "div"
4200
+ as: Component2 = "div"
4194
4201
  }) => {
4195
4202
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
4196
- const Comp = Component;
4203
+ const Comp = Component2;
4197
4204
  return /* @__PURE__ */ jsxRuntime.jsx(
4198
4205
  Comp,
4199
4206
  {
@@ -4824,7 +4831,7 @@ var init_Stack = __esm({
4824
4831
  className,
4825
4832
  style,
4826
4833
  children,
4827
- as: Component = "div",
4834
+ as: Component2 = "div",
4828
4835
  onClick,
4829
4836
  onKeyDown,
4830
4837
  role,
@@ -4842,7 +4849,7 @@ var init_Stack = __esm({
4842
4849
  };
4843
4850
  const isHorizontal = direction === "horizontal";
4844
4851
  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";
4845
- const Comp = Component;
4852
+ const Comp = Component2;
4846
4853
  return /* @__PURE__ */ jsxRuntime.jsx(
4847
4854
  Comp,
4848
4855
  {
@@ -7073,6 +7080,7 @@ var init_HealthBar = __esm({
7073
7080
  }
7074
7081
  });
7075
7082
  function ScoreDisplay({
7083
+ assetUrl = DEFAULT_ASSET_URL,
7076
7084
  value,
7077
7085
  label,
7078
7086
  icon,
@@ -7119,14 +7127,24 @@ function ScoreDisplay({
7119
7127
  className
7120
7128
  ),
7121
7129
  children: [
7122
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) }),
7130
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7131
+ "img",
7132
+ {
7133
+ src: assetUrl,
7134
+ alt: "",
7135
+ width: 20,
7136
+ height: 20,
7137
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7138
+ className: "flex-shrink-0"
7139
+ }
7140
+ ) : 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,
7123
7141
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: label }),
7124
7142
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: formattedValue })
7125
7143
  ]
7126
7144
  }
7127
7145
  );
7128
7146
  }
7129
- var sizeMap2;
7147
+ var sizeMap2, DEFAULT_ASSET_URL;
7130
7148
  var init_ScoreDisplay = __esm({
7131
7149
  "components/game/atoms/ScoreDisplay.tsx"() {
7132
7150
  "use client";
@@ -7138,10 +7156,12 @@ var init_ScoreDisplay = __esm({
7138
7156
  lg: "text-2xl",
7139
7157
  xl: "text-4xl"
7140
7158
  };
7159
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
7141
7160
  ScoreDisplay.displayName = "ScoreDisplay";
7142
7161
  }
7143
7162
  });
7144
7163
  function ControlButton({
7164
+ assetUrl = DEFAULT_ASSET_URL2,
7145
7165
  label,
7146
7166
  icon,
7147
7167
  size = "md",
@@ -7188,7 +7208,7 @@ function ControlButton({
7188
7208
  },
7189
7209
  [isPressed, releaseEvent, eventBus, onRelease]
7190
7210
  );
7191
- return /* @__PURE__ */ jsxRuntime.jsxs(
7211
+ return /* @__PURE__ */ jsxRuntime.jsx(
7192
7212
  "button",
7193
7213
  {
7194
7214
  type: "button",
@@ -7209,20 +7229,27 @@ function ControlButton({
7209
7229
  disabled && "opacity-50 cursor-not-allowed",
7210
7230
  className
7211
7231
  ),
7212
- children: [
7213
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7214
- const I = resolveIcon(icon);
7215
- return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" }) : null;
7216
- })() : icon : /* @__PURE__ */ (() => {
7217
- const I = icon;
7218
- return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" });
7219
- })() }),
7220
- label && !icon && /* @__PURE__ */ jsxRuntime.jsx("span", { children: label })
7221
- ]
7232
+ children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7233
+ "img",
7234
+ {
7235
+ src: assetUrl,
7236
+ alt: "",
7237
+ width: 24,
7238
+ height: 24,
7239
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7240
+ className: "flex-shrink-0"
7241
+ }
7242
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7243
+ const I = resolveIcon(icon);
7244
+ return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" }) : null;
7245
+ })() : icon : /* @__PURE__ */ (() => {
7246
+ const I = icon;
7247
+ return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" });
7248
+ })() }) : label ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }) : null
7222
7249
  }
7223
7250
  );
7224
7251
  }
7225
- var sizeMap3, shapeMap, variantMap;
7252
+ var sizeMap3, shapeMap, variantMap, DEFAULT_ASSET_URL2;
7226
7253
  var init_ControlButton = __esm({
7227
7254
  "components/game/atoms/ControlButton.tsx"() {
7228
7255
  "use client";
@@ -7245,6 +7272,7 @@ var init_ControlButton = __esm({
7245
7272
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
7246
7273
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
7247
7274
  };
7275
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
7248
7276
  ControlButton.displayName = "ControlButton";
7249
7277
  }
7250
7278
  });
@@ -7324,7 +7352,7 @@ var init_Sprite = __esm({
7324
7352
  }
7325
7353
  });
7326
7354
  function StateIndicator({
7327
- assetUrl = DEFAULT_ASSET_URL,
7355
+ assetUrl = DEFAULT_ASSET_URL3,
7328
7356
  state = "idle",
7329
7357
  label,
7330
7358
  size = "md",
@@ -7363,13 +7391,13 @@ function StateIndicator({
7363
7391
  }
7364
7392
  );
7365
7393
  }
7366
- var DEFAULT_ASSET_URL, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7394
+ var DEFAULT_ASSET_URL3, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7367
7395
  var init_StateIndicator = __esm({
7368
7396
  "components/game/atoms/StateIndicator.tsx"() {
7369
7397
  init_Box();
7370
7398
  init_Icon();
7371
7399
  init_cn();
7372
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7400
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7373
7401
  DEFAULT_STATE_STYLES = {
7374
7402
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
7375
7403
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -7444,7 +7472,7 @@ var init_TimerDisplay = __esm({
7444
7472
  }
7445
7473
  });
7446
7474
  function ResourceCounter({
7447
- assetUrl = DEFAULT_ASSET_URL2,
7475
+ assetUrl = DEFAULT_ASSET_URL4,
7448
7476
  icon,
7449
7477
  label = "Gold",
7450
7478
  value = 250,
@@ -7487,7 +7515,7 @@ function ResourceCounter({
7487
7515
  }
7488
7516
  );
7489
7517
  }
7490
- var colorTokenClasses2, DEFAULT_ASSET_URL2, sizeMap5;
7518
+ var colorTokenClasses2, DEFAULT_ASSET_URL4, sizeMap5;
7491
7519
  var init_ResourceCounter = __esm({
7492
7520
  "components/game/atoms/ResourceCounter.tsx"() {
7493
7521
  init_cn();
@@ -7500,7 +7528,7 @@ var init_ResourceCounter = __esm({
7500
7528
  error: "text-error",
7501
7529
  muted: "text-muted-foreground"
7502
7530
  };
7503
- DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7531
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7504
7532
  sizeMap5 = {
7505
7533
  sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
7506
7534
  md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
@@ -7510,7 +7538,7 @@ var init_ResourceCounter = __esm({
7510
7538
  }
7511
7539
  });
7512
7540
  function ItemSlot({
7513
- assetUrl = DEFAULT_ASSET_URL3,
7541
+ assetUrl = DEFAULT_ASSET_URL5,
7514
7542
  icon = "sword",
7515
7543
  label = "Iron Sword",
7516
7544
  quantity,
@@ -7569,7 +7597,7 @@ function ItemSlot({
7569
7597
  }
7570
7598
  );
7571
7599
  }
7572
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL3, assetSizeMap;
7600
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL5, assetSizeMap;
7573
7601
  var init_ItemSlot = __esm({
7574
7602
  "components/game/atoms/ItemSlot.tsx"() {
7575
7603
  "use client";
@@ -7594,7 +7622,7 @@ var init_ItemSlot = __esm({
7594
7622
  epic: "shadow-lg",
7595
7623
  legendary: "shadow-lg"
7596
7624
  };
7597
- DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7625
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7598
7626
  assetSizeMap = {
7599
7627
  sm: 28,
7600
7628
  md: 40,
@@ -7604,6 +7632,7 @@ var init_ItemSlot = __esm({
7604
7632
  }
7605
7633
  });
7606
7634
  function TurnIndicator({
7635
+ assetUrl = DEFAULT_ASSET_URL6,
7607
7636
  currentTurn = 1,
7608
7637
  maxTurns,
7609
7638
  activeTeam,
@@ -7636,14 +7665,24 @@ function TurnIndicator({
7636
7665
  ] }),
7637
7666
  activeTeam && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7638
7667
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "|" }),
7639
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7668
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7669
+ "img",
7670
+ {
7671
+ src: assetUrl,
7672
+ alt: "",
7673
+ width: 12,
7674
+ height: 12,
7675
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7676
+ className: "flex-shrink-0"
7677
+ }
7678
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7640
7679
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-success", children: activeTeam })
7641
7680
  ] })
7642
7681
  ]
7643
7682
  }
7644
7683
  );
7645
7684
  }
7646
- var sizeMap7;
7685
+ var sizeMap7, DEFAULT_ASSET_URL6;
7647
7686
  var init_TurnIndicator = __esm({
7648
7687
  "components/game/atoms/TurnIndicator.tsx"() {
7649
7688
  init_cn();
@@ -7652,6 +7691,7 @@ var init_TurnIndicator = __esm({
7652
7691
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
7653
7692
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
7654
7693
  };
7694
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
7655
7695
  TurnIndicator.displayName = "TurnIndicator";
7656
7696
  }
7657
7697
  });
@@ -7667,7 +7707,7 @@ function getComboScale(combo) {
7667
7707
  return "";
7668
7708
  }
7669
7709
  function ComboCounter({
7670
- assetUrl = DEFAULT_ASSET_URL4,
7710
+ assetUrl = DEFAULT_ASSET_URL7,
7671
7711
  combo = 5,
7672
7712
  multiplier,
7673
7713
  streak,
@@ -7712,11 +7752,11 @@ function ComboCounter({
7712
7752
  }
7713
7753
  );
7714
7754
  }
7715
- var DEFAULT_ASSET_URL4, sizeMap8;
7755
+ var DEFAULT_ASSET_URL7, sizeMap8;
7716
7756
  var init_ComboCounter = __esm({
7717
7757
  "components/game/atoms/ComboCounter.tsx"() {
7718
7758
  init_cn();
7719
- DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7759
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7720
7760
  sizeMap8 = {
7721
7761
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
7722
7762
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -7810,7 +7850,7 @@ var init_XPBar = __esm({
7810
7850
  }
7811
7851
  });
7812
7852
  function WaypointMarker({
7813
- assetUrl = DEFAULT_ASSET_URL5,
7853
+ assetUrl = DEFAULT_ASSET_URL8,
7814
7854
  label,
7815
7855
  icon,
7816
7856
  active = true,
@@ -7876,12 +7916,12 @@ function WaypointMarker({
7876
7916
  )
7877
7917
  ] });
7878
7918
  }
7879
- var DEFAULT_ASSET_URL5, sizeMap10, checkIcon;
7919
+ var DEFAULT_ASSET_URL8, sizeMap10, checkIcon;
7880
7920
  var init_WaypointMarker = __esm({
7881
7921
  "components/game/atoms/WaypointMarker.tsx"() {
7882
7922
  init_cn();
7883
7923
  init_Icon();
7884
- DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7924
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7885
7925
  sizeMap10 = {
7886
7926
  sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
7887
7927
  md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
@@ -7898,7 +7938,7 @@ function formatDuration(seconds) {
7898
7938
  return `${Math.round(seconds)}s`;
7899
7939
  }
7900
7940
  function StatusEffect({
7901
- assetUrl = DEFAULT_ASSET_URL6,
7941
+ assetUrl = DEFAULT_ASSET_URL9,
7902
7942
  icon = "shield",
7903
7943
  label = "Shield",
7904
7944
  duration = 30,
@@ -7956,12 +7996,12 @@ function StatusEffect({
7956
7996
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
7957
7997
  ] });
7958
7998
  }
7959
- var DEFAULT_ASSET_URL6, sizeMap11, variantStyles7;
7999
+ var DEFAULT_ASSET_URL9, sizeMap11, variantStyles7;
7960
8000
  var init_StatusEffect = __esm({
7961
8001
  "components/game/atoms/StatusEffect.tsx"() {
7962
8002
  init_cn();
7963
8003
  init_Icon();
7964
- DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
8004
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
7965
8005
  sizeMap11 = {
7966
8006
  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 },
7967
8007
  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 },
@@ -7976,6 +8016,7 @@ var init_StatusEffect = __esm({
7976
8016
  }
7977
8017
  });
7978
8018
  function DamageNumber({
8019
+ assetUrl = DEFAULT_ASSET_URL10,
7979
8020
  value = 42,
7980
8021
  type = "damage",
7981
8022
  size = "md",
@@ -7984,22 +8025,35 @@ function DamageNumber({
7984
8025
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
7985
8026
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7986
8027
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: floatKeyframes }),
7987
- /* @__PURE__ */ jsxRuntime.jsx(
8028
+ /* @__PURE__ */ jsxRuntime.jsxs(
7988
8029
  "span",
7989
8030
  {
7990
8031
  className: cn(
7991
- "inline-block font-mono select-none pointer-events-none",
8032
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
7992
8033
  sizeMap12[size],
7993
8034
  typeStyles[type],
7994
8035
  className
7995
8036
  ),
7996
8037
  style: { animation: "damageFloat 1s ease-out forwards" },
7997
- children: displayText
8038
+ children: [
8039
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
8040
+ "img",
8041
+ {
8042
+ src: assetUrl,
8043
+ alt: "",
8044
+ width: 14,
8045
+ height: 14,
8046
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8047
+ className: "flex-shrink-0"
8048
+ }
8049
+ ),
8050
+ displayText
8051
+ ]
7998
8052
  }
7999
8053
  )
8000
8054
  ] });
8001
8055
  }
8002
- var sizeMap12, typeStyles, floatKeyframes;
8056
+ var sizeMap12, typeStyles, floatKeyframes, DEFAULT_ASSET_URL10;
8003
8057
  var init_DamageNumber = __esm({
8004
8058
  "components/game/atoms/DamageNumber.tsx"() {
8005
8059
  init_cn();
@@ -8021,6 +8075,7 @@ var init_DamageNumber = __esm({
8021
8075
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
8022
8076
  }
8023
8077
  `;
8078
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
8024
8079
  DamageNumber.displayName = "DamageNumber";
8025
8080
  }
8026
8081
  });
@@ -8109,6 +8164,7 @@ var init_ChoiceButton = __esm({
8109
8164
  }
8110
8165
  });
8111
8166
  function ActionButton({
8167
+ assetUrl = DEFAULT_ASSET_URL11,
8112
8168
  label = "Attack",
8113
8169
  icon,
8114
8170
  cooldown = 0,
@@ -8148,13 +8204,23 @@ function ActionButton({
8148
8204
  }
8149
8205
  }
8150
8206
  ),
8151
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8207
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
8208
+ "img",
8209
+ {
8210
+ src: assetUrl,
8211
+ alt: "",
8212
+ width: 16,
8213
+ height: 16,
8214
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8215
+ className: cn("flex-shrink-0", sizes.icon)
8216
+ }
8217
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8152
8218
  const I = resolveIcon(icon);
8153
8219
  return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-4 h-4" }) : null;
8154
8220
  })() : /* @__PURE__ */ (() => {
8155
8221
  const I = icon;
8156
8222
  return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-4 h-4" });
8157
- })() }),
8223
+ })() }) : null,
8158
8224
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative z-10", children: label }),
8159
8225
  hotkey && /* @__PURE__ */ jsxRuntime.jsx(
8160
8226
  "span",
@@ -8170,7 +8236,7 @@ function ActionButton({
8170
8236
  }
8171
8237
  );
8172
8238
  }
8173
- var sizeMap13, variantStyles8;
8239
+ var sizeMap13, variantStyles8, DEFAULT_ASSET_URL11;
8174
8240
  var init_ActionButton = __esm({
8175
8241
  "components/game/atoms/ActionButton.tsx"() {
8176
8242
  init_cn();
@@ -8185,6 +8251,7 @@ var init_ActionButton = __esm({
8185
8251
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
8186
8252
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
8187
8253
  };
8254
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
8188
8255
  ActionButton.displayName = "ActionButton";
8189
8256
  }
8190
8257
  });
@@ -8697,10 +8764,10 @@ var init_Container = __esm({
8697
8764
  center = true,
8698
8765
  className,
8699
8766
  children,
8700
- as: Component = "div"
8767
+ as: Component2 = "div"
8701
8768
  }) => {
8702
8769
  const resolvedSize = maxWidth ?? size ?? "lg";
8703
- const Comp = Component;
8770
+ const Comp = Component2;
8704
8771
  return /* @__PURE__ */ jsxRuntime.jsx(
8705
8772
  Comp,
8706
8773
  {
@@ -24706,7 +24773,7 @@ var init_Flex = __esm({
24706
24773
  basis,
24707
24774
  className,
24708
24775
  children,
24709
- as: Component = "div"
24776
+ as: Component2 = "div"
24710
24777
  }) => {
24711
24778
  const flexStyle = {};
24712
24779
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -24718,7 +24785,7 @@ var init_Flex = __esm({
24718
24785
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
24719
24786
  }
24720
24787
  }
24721
- const Comp = Component;
24788
+ const Comp = Component2;
24722
24789
  return /* @__PURE__ */ jsxRuntime.jsx(
24723
24790
  Comp,
24724
24791
  {
@@ -24838,11 +24905,11 @@ var init_Grid = __esm({
24838
24905
  className,
24839
24906
  style,
24840
24907
  children,
24841
- as: Component = "div"
24908
+ as: Component2 = "div"
24842
24909
  }) => {
24843
24910
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
24844
24911
  return React82__namespace.default.createElement(
24845
- Component,
24912
+ Component2,
24846
24913
  {
24847
24914
  className: cn(
24848
24915
  "grid",
@@ -26668,6 +26735,7 @@ var init_ProgressDots = __esm({
26668
26735
  }
26669
26736
  });
26670
26737
  function StatBadge({
26738
+ assetUrl = DEFAULT_ASSET_URL12,
26671
26739
  label,
26672
26740
  value = 0,
26673
26741
  max,
@@ -26691,7 +26759,17 @@ function StatBadge({
26691
26759
  className
26692
26760
  ),
26693
26761
  children: [
26694
- 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" }) }),
26762
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
26763
+ "img",
26764
+ {
26765
+ src: assetUrl,
26766
+ alt: "",
26767
+ width: 16,
26768
+ height: 16,
26769
+ style: { imageRendering: "pixelated", objectFit: "contain" },
26770
+ className: "flex-shrink-0"
26771
+ }
26772
+ ) : 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,
26695
26773
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground font-medium", children: label }),
26696
26774
  format === "hearts" && max && /* @__PURE__ */ jsxRuntime.jsx(
26697
26775
  HealthBar,
@@ -26724,7 +26802,7 @@ function StatBadge({
26724
26802
  }
26725
26803
  );
26726
26804
  }
26727
- var sizeMap17, variantMap2;
26805
+ var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
26728
26806
  var init_StatBadge = __esm({
26729
26807
  "components/game/molecules/StatBadge.tsx"() {
26730
26808
  init_cn();
@@ -26743,6 +26821,7 @@ var init_StatBadge = __esm({
26743
26821
  warning: "bg-warning/15 border-warning/40 text-foreground",
26744
26822
  danger: "bg-error/15 border-error/40 text-foreground"
26745
26823
  };
26824
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
26746
26825
  StatBadge.displayName = "StatBadge";
26747
26826
  }
26748
26827
  });
@@ -27399,6 +27478,7 @@ var init_TurnPanel = __esm({
27399
27478
  }
27400
27479
  });
27401
27480
  function EnemyPlate({
27481
+ assetUrl = DEFAULT_ASSET_URL13,
27402
27482
  name = "Shadow Guard",
27403
27483
  health = 80,
27404
27484
  maxHealth = 100,
@@ -27416,15 +27496,28 @@ function EnemyPlate({
27416
27496
  ),
27417
27497
  children: [
27418
27498
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
27419
- /* @__PURE__ */ jsxRuntime.jsx(
27420
- Typography,
27421
- {
27422
- variant: "caption",
27423
- weight: "bold",
27424
- className: "text-[var(--color-foreground)] truncate",
27425
- children: name
27426
- }
27427
- ),
27499
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
27500
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
27501
+ "img",
27502
+ {
27503
+ src: assetUrl,
27504
+ alt: name,
27505
+ width: 24,
27506
+ height: 24,
27507
+ style: { objectFit: "cover", borderRadius: "50%" },
27508
+ className: "flex-shrink-0"
27509
+ }
27510
+ ),
27511
+ /* @__PURE__ */ jsxRuntime.jsx(
27512
+ Typography,
27513
+ {
27514
+ variant: "caption",
27515
+ weight: "bold",
27516
+ className: "text-[var(--color-foreground)] truncate",
27517
+ children: name
27518
+ }
27519
+ )
27520
+ ] }),
27428
27521
  level != null && /* @__PURE__ */ jsxRuntime.jsxs(Badge, { variant: "neutral", size: "sm", children: [
27429
27522
  "Lv.",
27430
27523
  level
@@ -27469,7 +27562,7 @@ function EnemyPlate({
27469
27562
  }
27470
27563
  );
27471
27564
  }
27472
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
27565
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
27473
27566
  var init_EnemyPlate = __esm({
27474
27567
  "components/game/molecules/EnemyPlate.tsx"() {
27475
27568
  "use client";
@@ -27486,6 +27579,7 @@ var init_EnemyPlate = __esm({
27486
27579
  DEFAULT_ENEMY_EFFECTS = [
27487
27580
  { icon: "flame", label: "Burn", variant: "debuff" }
27488
27581
  ];
27582
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
27489
27583
  EnemyPlate.displayName = "EnemyPlate";
27490
27584
  }
27491
27585
  });
@@ -29017,13 +29111,13 @@ var init_MapView = __esm({
29017
29111
  shadowSize: [41, 41]
29018
29112
  });
29019
29113
  L.Marker.prototype.options.icon = defaultIcon;
29020
- const { useEffect: useEffect74, useRef: useRef68, useCallback: useCallback111, useState: useState107 } = React82__namespace.default;
29114
+ const { useEffect: useEffect80, useRef: useRef74, useCallback: useCallback118, useState: useState112 } = React82__namespace.default;
29021
29115
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29022
- const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29116
+ const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29023
29117
  function MapUpdater({ centerLat, centerLng, zoom }) {
29024
29118
  const map = useMap();
29025
- const prevRef = useRef68({ centerLat, centerLng, zoom });
29026
- useEffect74(() => {
29119
+ const prevRef = useRef74({ centerLat, centerLng, zoom });
29120
+ useEffect80(() => {
29027
29121
  const prev = prevRef.current;
29028
29122
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
29029
29123
  map.setView([centerLat, centerLng], zoom);
@@ -29034,7 +29128,7 @@ var init_MapView = __esm({
29034
29128
  }
29035
29129
  function MapClickHandler({ onMapClick }) {
29036
29130
  const map = useMap();
29037
- useEffect74(() => {
29131
+ useEffect80(() => {
29038
29132
  if (!onMapClick) return;
29039
29133
  const handler = (e) => {
29040
29134
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -29061,9 +29155,9 @@ var init_MapView = __esm({
29061
29155
  className,
29062
29156
  showAttribution = true
29063
29157
  }) {
29064
- const eventBus = useEventBus3();
29065
- const [clickedPosition, setClickedPosition] = useState107(null);
29066
- const handleMapClick = useCallback111((lat, lng) => {
29158
+ const eventBus = useEventBus4();
29159
+ const [clickedPosition, setClickedPosition] = useState112(null);
29160
+ const handleMapClick = useCallback118((lat, lng) => {
29067
29161
  if (showClickedPin) {
29068
29162
  setClickedPosition({ lat, lng });
29069
29163
  }
@@ -29072,7 +29166,7 @@ var init_MapView = __esm({
29072
29166
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29073
29167
  }
29074
29168
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29075
- const handleMarkerClick = useCallback111((marker) => {
29169
+ const handleMarkerClick = useCallback118((marker) => {
29076
29170
  onMarkerClick?.(marker);
29077
29171
  if (markerClickEvent) {
29078
29172
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -36199,10 +36293,10 @@ var init_Section = __esm({
36199
36293
  children,
36200
36294
  headerClassName,
36201
36295
  contentClassName,
36202
- as: Component = "section"
36296
+ as: Component2 = "section"
36203
36297
  }) => {
36204
36298
  const hasHeader = title || description || action;
36205
- const Comp = Component;
36299
+ const Comp = Component2;
36206
36300
  return /* @__PURE__ */ jsxRuntime.jsxs(
36207
36301
  Comp,
36208
36302
  {
@@ -40767,6 +40861,1568 @@ var init_GameAudioToggle = __esm({
40767
40861
  GameAudioToggle.displayName = "GameAudioToggle";
40768
40862
  }
40769
40863
  });
40864
+ function detectAssetRoot(modelUrl) {
40865
+ const idx = modelUrl.indexOf("/3d/");
40866
+ if (idx !== -1) {
40867
+ return modelUrl.substring(0, idx + 4);
40868
+ }
40869
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
40870
+ }
40871
+ function createGLTFLoaderForUrl(url) {
40872
+ const loader = new GLTFLoader_js.GLTFLoader();
40873
+ loader.setResourcePath(detectAssetRoot(url));
40874
+ return loader;
40875
+ }
40876
+ var AssetLoader;
40877
+ var init_AssetLoader = __esm({
40878
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
40879
+ AssetLoader = class {
40880
+ constructor() {
40881
+ __publicField(this, "objLoader");
40882
+ __publicField(this, "textureLoader");
40883
+ __publicField(this, "modelCache");
40884
+ __publicField(this, "textureCache");
40885
+ __publicField(this, "loadingPromises");
40886
+ this.objLoader = new OBJLoader_js.OBJLoader();
40887
+ this.textureLoader = new THREE__namespace.TextureLoader();
40888
+ this.modelCache = /* @__PURE__ */ new Map();
40889
+ this.textureCache = /* @__PURE__ */ new Map();
40890
+ this.loadingPromises = /* @__PURE__ */ new Map();
40891
+ }
40892
+ /**
40893
+ * Load a GLB/GLTF model
40894
+ * @param url - URL to the .glb or .gltf file
40895
+ * @returns Promise with loaded model scene and animations
40896
+ */
40897
+ async loadModel(url) {
40898
+ if (this.modelCache.has(url)) {
40899
+ return this.modelCache.get(url);
40900
+ }
40901
+ if (this.loadingPromises.has(url)) {
40902
+ return this.loadingPromises.get(url);
40903
+ }
40904
+ const loader = createGLTFLoaderForUrl(url);
40905
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
40906
+ const result = {
40907
+ scene: gltf.scene,
40908
+ animations: gltf.animations || []
40909
+ };
40910
+ this.modelCache.set(url, result);
40911
+ this.loadingPromises.delete(url);
40912
+ return result;
40913
+ }).catch((error) => {
40914
+ this.loadingPromises.delete(url);
40915
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
40916
+ });
40917
+ this.loadingPromises.set(url, loadPromise);
40918
+ return loadPromise;
40919
+ }
40920
+ /**
40921
+ * Load an OBJ model (fallback for non-GLB assets)
40922
+ * @param url - URL to the .obj file
40923
+ * @returns Promise with loaded object group
40924
+ */
40925
+ async loadOBJ(url) {
40926
+ if (this.modelCache.has(url)) {
40927
+ return this.modelCache.get(url).scene;
40928
+ }
40929
+ if (this.loadingPromises.has(url)) {
40930
+ const result = await this.loadingPromises.get(url);
40931
+ return result.scene;
40932
+ }
40933
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
40934
+ const result = {
40935
+ scene: group,
40936
+ animations: []
40937
+ };
40938
+ this.modelCache.set(url, result);
40939
+ this.loadingPromises.delete(url);
40940
+ return result;
40941
+ }).catch((error) => {
40942
+ this.loadingPromises.delete(url);
40943
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
40944
+ });
40945
+ this.loadingPromises.set(url, loadPromise);
40946
+ return (await loadPromise).scene;
40947
+ }
40948
+ /**
40949
+ * Load a texture
40950
+ * @param url - URL to the texture image
40951
+ * @returns Promise with loaded texture
40952
+ */
40953
+ async loadTexture(url) {
40954
+ if (this.textureCache.has(url)) {
40955
+ return this.textureCache.get(url);
40956
+ }
40957
+ if (this.loadingPromises.has(`texture:${url}`)) {
40958
+ return this.loadingPromises.get(`texture:${url}`);
40959
+ }
40960
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
40961
+ texture.colorSpace = THREE__namespace.SRGBColorSpace;
40962
+ this.textureCache.set(url, texture);
40963
+ this.loadingPromises.delete(`texture:${url}`);
40964
+ return texture;
40965
+ }).catch((error) => {
40966
+ this.loadingPromises.delete(`texture:${url}`);
40967
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
40968
+ });
40969
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
40970
+ return loadPromise;
40971
+ }
40972
+ /**
40973
+ * Preload multiple assets
40974
+ * @param urls - Array of asset URLs to preload
40975
+ * @returns Promise that resolves when all assets are loaded
40976
+ */
40977
+ async preload(urls) {
40978
+ const promises = urls.map((url) => {
40979
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
40980
+ return this.loadModel(url).catch(() => null);
40981
+ } else if (url.endsWith(".obj")) {
40982
+ return this.loadOBJ(url).catch(() => null);
40983
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
40984
+ return this.loadTexture(url).catch(() => null);
40985
+ }
40986
+ return Promise.resolve(null);
40987
+ });
40988
+ await Promise.all(promises);
40989
+ }
40990
+ /**
40991
+ * Check if a model is cached
40992
+ * @param url - Model URL
40993
+ */
40994
+ hasModel(url) {
40995
+ return this.modelCache.has(url);
40996
+ }
40997
+ /**
40998
+ * Check if a texture is cached
40999
+ * @param url - Texture URL
41000
+ */
41001
+ hasTexture(url) {
41002
+ return this.textureCache.has(url);
41003
+ }
41004
+ /**
41005
+ * Get cached model (throws if not cached)
41006
+ * @param url - Model URL
41007
+ */
41008
+ getModel(url) {
41009
+ const model = this.modelCache.get(url);
41010
+ if (!model) {
41011
+ throw new Error(`Model ${url} not in cache`);
41012
+ }
41013
+ return model;
41014
+ }
41015
+ /**
41016
+ * Get cached texture (throws if not cached)
41017
+ * @param url - Texture URL
41018
+ */
41019
+ getTexture(url) {
41020
+ const texture = this.textureCache.get(url);
41021
+ if (!texture) {
41022
+ throw new Error(`Texture ${url} not in cache`);
41023
+ }
41024
+ return texture;
41025
+ }
41026
+ /**
41027
+ * Clear all caches
41028
+ */
41029
+ clearCache() {
41030
+ this.textureCache.forEach((texture) => {
41031
+ texture.dispose();
41032
+ });
41033
+ this.modelCache.forEach((model) => {
41034
+ model.scene.traverse((child) => {
41035
+ if (child instanceof THREE__namespace.Mesh) {
41036
+ child.geometry.dispose();
41037
+ if (Array.isArray(child.material)) {
41038
+ child.material.forEach((m) => m.dispose());
41039
+ } else {
41040
+ child.material.dispose();
41041
+ }
41042
+ }
41043
+ });
41044
+ });
41045
+ this.modelCache.clear();
41046
+ this.textureCache.clear();
41047
+ this.loadingPromises.clear();
41048
+ }
41049
+ /**
41050
+ * Get cache statistics
41051
+ */
41052
+ getStats() {
41053
+ return {
41054
+ models: this.modelCache.size,
41055
+ textures: this.textureCache.size,
41056
+ loading: this.loadingPromises.size
41057
+ };
41058
+ }
41059
+ };
41060
+ new AssetLoader();
41061
+ }
41062
+ });
41063
+ function useAssetLoader(options = {}) {
41064
+ const { preloadUrls = [], loader: customLoader } = options;
41065
+ const loaderRef = React82.useRef(customLoader || new AssetLoader());
41066
+ const [state, setState] = React82.useState({
41067
+ isLoading: false,
41068
+ progress: 0,
41069
+ loaded: 0,
41070
+ total: 0,
41071
+ errors: []
41072
+ });
41073
+ React82.useEffect(() => {
41074
+ if (preloadUrls.length > 0) {
41075
+ preload(preloadUrls);
41076
+ }
41077
+ }, []);
41078
+ const updateProgress = React82.useCallback((loaded, total) => {
41079
+ setState((prev) => ({
41080
+ ...prev,
41081
+ loaded,
41082
+ total,
41083
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
41084
+ }));
41085
+ }, []);
41086
+ const loadModel = React82.useCallback(
41087
+ async (url) => {
41088
+ setState((prev) => ({ ...prev, isLoading: true }));
41089
+ try {
41090
+ const model = await loaderRef.current.loadModel(url);
41091
+ setState((prev) => ({
41092
+ ...prev,
41093
+ isLoading: false,
41094
+ loaded: prev.loaded + 1
41095
+ }));
41096
+ return model;
41097
+ } catch (error) {
41098
+ const errorMsg = error instanceof Error ? error.message : String(error);
41099
+ setState((prev) => ({
41100
+ ...prev,
41101
+ isLoading: false,
41102
+ errors: [...prev.errors, errorMsg]
41103
+ }));
41104
+ throw error;
41105
+ }
41106
+ },
41107
+ []
41108
+ );
41109
+ const loadOBJ = React82.useCallback(
41110
+ async (url) => {
41111
+ setState((prev) => ({ ...prev, isLoading: true }));
41112
+ try {
41113
+ const model = await loaderRef.current.loadOBJ(url);
41114
+ setState((prev) => ({
41115
+ ...prev,
41116
+ isLoading: false,
41117
+ loaded: prev.loaded + 1
41118
+ }));
41119
+ return model;
41120
+ } catch (error) {
41121
+ const errorMsg = error instanceof Error ? error.message : String(error);
41122
+ setState((prev) => ({
41123
+ ...prev,
41124
+ isLoading: false,
41125
+ errors: [...prev.errors, errorMsg]
41126
+ }));
41127
+ throw error;
41128
+ }
41129
+ },
41130
+ []
41131
+ );
41132
+ const loadTexture = React82.useCallback(
41133
+ async (url) => {
41134
+ setState((prev) => ({ ...prev, isLoading: true }));
41135
+ try {
41136
+ const texture = await loaderRef.current.loadTexture(url);
41137
+ setState((prev) => ({
41138
+ ...prev,
41139
+ isLoading: false,
41140
+ loaded: prev.loaded + 1
41141
+ }));
41142
+ return texture;
41143
+ } catch (error) {
41144
+ const errorMsg = error instanceof Error ? error.message : String(error);
41145
+ setState((prev) => ({
41146
+ ...prev,
41147
+ isLoading: false,
41148
+ errors: [...prev.errors, errorMsg]
41149
+ }));
41150
+ throw error;
41151
+ }
41152
+ },
41153
+ []
41154
+ );
41155
+ const preload = React82.useCallback(
41156
+ async (urls) => {
41157
+ setState((prev) => ({
41158
+ ...prev,
41159
+ isLoading: true,
41160
+ total: urls.length,
41161
+ loaded: 0,
41162
+ errors: []
41163
+ }));
41164
+ let completed = 0;
41165
+ const errors = [];
41166
+ await Promise.all(
41167
+ urls.map(async (url) => {
41168
+ try {
41169
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41170
+ await loaderRef.current.loadModel(url);
41171
+ } else if (url.endsWith(".obj")) {
41172
+ await loaderRef.current.loadOBJ(url);
41173
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41174
+ await loaderRef.current.loadTexture(url);
41175
+ }
41176
+ completed++;
41177
+ updateProgress(completed, urls.length);
41178
+ } catch (error) {
41179
+ const errorMsg = error instanceof Error ? error.message : String(error);
41180
+ errors.push(`${url}: ${errorMsg}`);
41181
+ completed++;
41182
+ updateProgress(completed, urls.length);
41183
+ }
41184
+ })
41185
+ );
41186
+ setState((prev) => ({
41187
+ ...prev,
41188
+ isLoading: false,
41189
+ errors
41190
+ }));
41191
+ },
41192
+ [updateProgress]
41193
+ );
41194
+ const hasModel = React82.useCallback((url) => {
41195
+ return loaderRef.current.hasModel(url);
41196
+ }, []);
41197
+ const hasTexture = React82.useCallback((url) => {
41198
+ return loaderRef.current.hasTexture(url);
41199
+ }, []);
41200
+ const getModel = React82.useCallback((url) => {
41201
+ try {
41202
+ return loaderRef.current.getModel(url);
41203
+ } catch {
41204
+ return void 0;
41205
+ }
41206
+ }, []);
41207
+ const getTexture = React82.useCallback((url) => {
41208
+ try {
41209
+ return loaderRef.current.getTexture(url);
41210
+ } catch {
41211
+ return void 0;
41212
+ }
41213
+ }, []);
41214
+ const clearCache = React82.useCallback(() => {
41215
+ loaderRef.current.clearCache();
41216
+ setState({
41217
+ isLoading: false,
41218
+ progress: 0,
41219
+ loaded: 0,
41220
+ total: 0,
41221
+ errors: []
41222
+ });
41223
+ }, []);
41224
+ return {
41225
+ ...state,
41226
+ loadModel,
41227
+ loadOBJ,
41228
+ loadTexture,
41229
+ preload,
41230
+ hasModel,
41231
+ hasTexture,
41232
+ getModel,
41233
+ getTexture,
41234
+ clearCache
41235
+ };
41236
+ }
41237
+ var init_useAssetLoader = __esm({
41238
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
41239
+ "use client";
41240
+ init_AssetLoader();
41241
+ }
41242
+ });
41243
+ function useGameCanvas3DEvents(options) {
41244
+ const {
41245
+ tileClickEvent,
41246
+ unitClickEvent,
41247
+ featureClickEvent,
41248
+ canvasClickEvent,
41249
+ tileHoverEvent,
41250
+ tileLeaveEvent,
41251
+ unitAnimationEvent,
41252
+ cameraChangeEvent,
41253
+ onTileClick,
41254
+ onUnitClick,
41255
+ onFeatureClick,
41256
+ onCanvasClick,
41257
+ onTileHover,
41258
+ onUnitAnimation
41259
+ } = options;
41260
+ const emit = useEmitEvent();
41261
+ const optionsRef = React82.useRef(options);
41262
+ optionsRef.current = options;
41263
+ const handleTileClick = React82.useCallback(
41264
+ (tile, event) => {
41265
+ if (tileClickEvent) {
41266
+ emit(tileClickEvent, {
41267
+ tileId: tile.id,
41268
+ x: tile.x,
41269
+ z: tile.z ?? tile.y ?? 0,
41270
+ type: tile.type,
41271
+ terrain: tile.terrain,
41272
+ elevation: tile.elevation
41273
+ });
41274
+ }
41275
+ optionsRef.current.onTileClick?.(tile, event);
41276
+ },
41277
+ [tileClickEvent, emit]
41278
+ );
41279
+ const handleUnitClick = React82.useCallback(
41280
+ (unit, event) => {
41281
+ if (unitClickEvent) {
41282
+ emit(unitClickEvent, {
41283
+ unitId: unit.id,
41284
+ x: unit.x,
41285
+ z: unit.z ?? unit.y ?? 0,
41286
+ unitType: unit.unitType,
41287
+ name: unit.name,
41288
+ team: unit.team,
41289
+ faction: unit.faction,
41290
+ health: unit.health,
41291
+ maxHealth: unit.maxHealth
41292
+ });
41293
+ }
41294
+ optionsRef.current.onUnitClick?.(unit, event);
41295
+ },
41296
+ [unitClickEvent, emit]
41297
+ );
41298
+ const handleFeatureClick = React82.useCallback(
41299
+ (feature, event) => {
41300
+ if (featureClickEvent) {
41301
+ emit(featureClickEvent, {
41302
+ featureId: feature.id,
41303
+ x: feature.x,
41304
+ z: feature.z ?? feature.y ?? 0,
41305
+ type: feature.type,
41306
+ elevation: feature.elevation
41307
+ });
41308
+ }
41309
+ optionsRef.current.onFeatureClick?.(feature, event);
41310
+ },
41311
+ [featureClickEvent, emit]
41312
+ );
41313
+ const handleCanvasClick = React82.useCallback(
41314
+ (event) => {
41315
+ if (canvasClickEvent) {
41316
+ emit(canvasClickEvent, {
41317
+ clientX: event.clientX,
41318
+ clientY: event.clientY,
41319
+ button: event.button
41320
+ });
41321
+ }
41322
+ optionsRef.current.onCanvasClick?.(event);
41323
+ },
41324
+ [canvasClickEvent, emit]
41325
+ );
41326
+ const handleTileHover = React82.useCallback(
41327
+ (tile, event) => {
41328
+ if (tile) {
41329
+ if (tileHoverEvent) {
41330
+ emit(tileHoverEvent, {
41331
+ tileId: tile.id,
41332
+ x: tile.x,
41333
+ z: tile.z ?? tile.y ?? 0,
41334
+ type: tile.type
41335
+ });
41336
+ }
41337
+ } else {
41338
+ if (tileLeaveEvent) {
41339
+ emit(tileLeaveEvent, {});
41340
+ }
41341
+ }
41342
+ optionsRef.current.onTileHover?.(tile, event);
41343
+ },
41344
+ [tileHoverEvent, tileLeaveEvent, emit]
41345
+ );
41346
+ const handleUnitAnimation = React82.useCallback(
41347
+ (unitId, state) => {
41348
+ if (unitAnimationEvent) {
41349
+ emit(unitAnimationEvent, {
41350
+ unitId,
41351
+ state,
41352
+ timestamp: Date.now()
41353
+ });
41354
+ }
41355
+ optionsRef.current.onUnitAnimation?.(unitId, state);
41356
+ },
41357
+ [unitAnimationEvent, emit]
41358
+ );
41359
+ const handleCameraChange = React82.useCallback(
41360
+ (position) => {
41361
+ if (cameraChangeEvent) {
41362
+ emit(cameraChangeEvent, {
41363
+ position,
41364
+ timestamp: Date.now()
41365
+ });
41366
+ }
41367
+ },
41368
+ [cameraChangeEvent, emit]
41369
+ );
41370
+ return {
41371
+ handleTileClick,
41372
+ handleUnitClick,
41373
+ handleFeatureClick,
41374
+ handleCanvasClick,
41375
+ handleTileHover,
41376
+ handleUnitAnimation,
41377
+ handleCameraChange
41378
+ };
41379
+ }
41380
+ var init_useGameCanvas3DEvents = __esm({
41381
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
41382
+ "use client";
41383
+ init_useEventBus();
41384
+ }
41385
+ });
41386
+
41387
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
41388
+ var init_Canvas3DLoadingState = __esm({
41389
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
41390
+ }
41391
+ });
41392
+ function Canvas3DLoadingState({
41393
+ progress = 0,
41394
+ loaded = 0,
41395
+ total = 0,
41396
+ message = "Loading 3D Scene...",
41397
+ details,
41398
+ showSpinner = true,
41399
+ className
41400
+ }) {
41401
+ const clampedProgress = Math.max(0, Math.min(100, progress));
41402
+ const hasProgress = total > 0;
41403
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
41404
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__content", children: [
41405
+ showSpinner && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__spinner", children: [
41406
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner__ring" }),
41407
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner__ring spinner__ring--secondary" })
41408
+ ] }),
41409
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__message", children: message }),
41410
+ details && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__details", children: details }),
41411
+ hasProgress && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__progress", children: [
41412
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsxRuntime.jsx(
41413
+ "div",
41414
+ {
41415
+ className: "progress__fill",
41416
+ style: { width: `${clampedProgress}%` }
41417
+ }
41418
+ ) }),
41419
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "progress__text", children: [
41420
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "progress__percentage", children: [
41421
+ clampedProgress,
41422
+ "%"
41423
+ ] }),
41424
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "progress__count", children: [
41425
+ "(",
41426
+ loaded,
41427
+ "/",
41428
+ total,
41429
+ ")"
41430
+ ] })
41431
+ ] })
41432
+ ] })
41433
+ ] }),
41434
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg__grid" }) })
41435
+ ] });
41436
+ }
41437
+ var init_Canvas3DLoadingState2 = __esm({
41438
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
41439
+ init_Canvas3DLoadingState();
41440
+ }
41441
+ });
41442
+
41443
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
41444
+ var init_Canvas3DErrorBoundary = __esm({
41445
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
41446
+ }
41447
+ });
41448
+ var log10, Canvas3DErrorBoundary;
41449
+ var init_Canvas3DErrorBoundary2 = __esm({
41450
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
41451
+ init_Canvas3DErrorBoundary();
41452
+ log10 = logger.createLogger("almadar:ui:game:canvas3d:error-boundary");
41453
+ Canvas3DErrorBoundary = class extends React82.Component {
41454
+ constructor(props) {
41455
+ super(props);
41456
+ __publicField(this, "handleReset", () => {
41457
+ this.setState({
41458
+ hasError: false,
41459
+ error: null,
41460
+ errorInfo: null
41461
+ });
41462
+ this.props.onReset?.();
41463
+ });
41464
+ this.state = {
41465
+ hasError: false,
41466
+ error: null,
41467
+ errorInfo: null
41468
+ };
41469
+ }
41470
+ static getDerivedStateFromError(error) {
41471
+ return {
41472
+ hasError: true,
41473
+ error,
41474
+ errorInfo: null
41475
+ };
41476
+ }
41477
+ componentDidCatch(error, errorInfo) {
41478
+ this.setState({ errorInfo });
41479
+ this.props.onError?.(error, errorInfo);
41480
+ log10.error("Error caught", { error });
41481
+ log10.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
41482
+ }
41483
+ render() {
41484
+ if (this.state.hasError) {
41485
+ if (this.props.fallback) {
41486
+ return this.props.fallback;
41487
+ }
41488
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-error__content", children: [
41489
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
41490
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
41491
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
41492
+ this.state.error && /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "canvas-3d-error__details", children: [
41493
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Error Details" }),
41494
+ /* @__PURE__ */ jsxRuntime.jsxs("pre", { className: "error__stack", children: [
41495
+ this.state.error.message,
41496
+ "\n",
41497
+ this.state.error.stack
41498
+ ] }),
41499
+ this.state.errorInfo && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
41500
+ ] }),
41501
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-error__actions", children: [
41502
+ /* @__PURE__ */ jsxRuntime.jsx(
41503
+ "button",
41504
+ {
41505
+ className: "error__button error__button--primary",
41506
+ onClick: this.handleReset,
41507
+ children: "Try Again"
41508
+ }
41509
+ ),
41510
+ /* @__PURE__ */ jsxRuntime.jsx(
41511
+ "button",
41512
+ {
41513
+ className: "error__button error__button--secondary",
41514
+ onClick: () => window.location.reload(),
41515
+ children: "Reload Page"
41516
+ }
41517
+ )
41518
+ ] })
41519
+ ] }) });
41520
+ }
41521
+ return this.props.children;
41522
+ }
41523
+ };
41524
+ }
41525
+ });
41526
+ function detectAssetRoot2(modelUrl) {
41527
+ const idx = modelUrl.indexOf("/3d/");
41528
+ if (idx !== -1) {
41529
+ return modelUrl.substring(0, idx + 4);
41530
+ }
41531
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41532
+ }
41533
+ function useGLTFModel(url, resourceBasePath) {
41534
+ const [state, setState] = React82.useState({
41535
+ model: null,
41536
+ isLoading: false,
41537
+ error: null
41538
+ });
41539
+ React82.useEffect(() => {
41540
+ if (!url) {
41541
+ setState({ model: null, isLoading: false, error: null });
41542
+ return;
41543
+ }
41544
+ log11.debug("Loading", { url });
41545
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
41546
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
41547
+ const loader = new GLTFLoader.GLTFLoader();
41548
+ loader.setResourcePath(assetRoot);
41549
+ loader.load(
41550
+ url,
41551
+ (gltf) => {
41552
+ log11.debug("Loaded", { url });
41553
+ setState({
41554
+ model: gltf.scene,
41555
+ isLoading: false,
41556
+ error: null
41557
+ });
41558
+ },
41559
+ void 0,
41560
+ (err) => {
41561
+ log11.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
41562
+ setState({
41563
+ model: null,
41564
+ isLoading: false,
41565
+ error: err instanceof Error ? err : new Error(String(err))
41566
+ });
41567
+ }
41568
+ );
41569
+ }, [url, resourceBasePath]);
41570
+ return state;
41571
+ }
41572
+ function ModelLoader({
41573
+ url,
41574
+ position = [0, 0, 0],
41575
+ scale = 1,
41576
+ rotation = [0, 0, 0],
41577
+ isSelected = false,
41578
+ isHovered = false,
41579
+ onClick,
41580
+ onHover,
41581
+ fallbackGeometry = "box",
41582
+ castShadow = true,
41583
+ receiveShadow = true,
41584
+ resourceBasePath
41585
+ }) {
41586
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
41587
+ const model = React82.useMemo(() => {
41588
+ if (!loadedModel) return null;
41589
+ const cloned = loadedModel.clone();
41590
+ cloned.traverse((child) => {
41591
+ if (child instanceof THREE__namespace.Mesh) {
41592
+ child.castShadow = castShadow;
41593
+ child.receiveShadow = receiveShadow;
41594
+ }
41595
+ });
41596
+ return cloned;
41597
+ }, [loadedModel, castShadow, receiveShadow]);
41598
+ const scaleArray = React82.useMemo(() => {
41599
+ if (typeof scale === "number") {
41600
+ return [scale, scale, scale];
41601
+ }
41602
+ return scale;
41603
+ }, [scale]);
41604
+ const rotationRad = React82.useMemo(() => {
41605
+ return [
41606
+ rotation[0] * Math.PI / 180,
41607
+ rotation[1] * Math.PI / 180,
41608
+ rotation[2] * Math.PI / 180
41609
+ ];
41610
+ }, [rotation]);
41611
+ if (isLoading) {
41612
+ return /* @__PURE__ */ jsxRuntime.jsx("group", { position, children: /* @__PURE__ */ jsxRuntime.jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
41613
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
41614
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
41615
+ ] }) });
41616
+ }
41617
+ if (error || !model) {
41618
+ if (fallbackGeometry === "none") {
41619
+ return /* @__PURE__ */ jsxRuntime.jsx("group", { position });
41620
+ }
41621
+ const fallbackProps = {
41622
+ onClick,
41623
+ onPointerOver: () => onHover?.(true),
41624
+ onPointerOut: () => onHover?.(false)
41625
+ };
41626
+ return /* @__PURE__ */ jsxRuntime.jsxs("group", { position, children: [
41627
+ (isSelected || isHovered) && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
41628
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
41629
+ /* @__PURE__ */ jsxRuntime.jsx(
41630
+ "meshBasicMaterial",
41631
+ {
41632
+ color: isSelected ? 16755200 : 16777215,
41633
+ transparent: true,
41634
+ opacity: 0.5
41635
+ }
41636
+ )
41637
+ ] }),
41638
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
41639
+ /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
41640
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
41641
+ ] }),
41642
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
41643
+ /* @__PURE__ */ jsxRuntime.jsx("sphereGeometry", { args: [0.4, 16, 16] }),
41644
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
41645
+ ] }),
41646
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
41647
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
41648
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
41649
+ ] })
41650
+ ] });
41651
+ }
41652
+ return /* @__PURE__ */ jsxRuntime.jsxs(
41653
+ "group",
41654
+ {
41655
+ position,
41656
+ rotation: rotationRad,
41657
+ onClick,
41658
+ onPointerOver: () => onHover?.(true),
41659
+ onPointerOut: () => onHover?.(false),
41660
+ children: [
41661
+ (isSelected || isHovered) && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
41662
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
41663
+ /* @__PURE__ */ jsxRuntime.jsx(
41664
+ "meshBasicMaterial",
41665
+ {
41666
+ color: isSelected ? 16755200 : 16777215,
41667
+ transparent: true,
41668
+ opacity: 0.5
41669
+ }
41670
+ )
41671
+ ] }),
41672
+ /* @__PURE__ */ jsxRuntime.jsx("primitive", { object: model, scale: scaleArray })
41673
+ ]
41674
+ }
41675
+ );
41676
+ }
41677
+ var log11;
41678
+ var init_ModelLoader = __esm({
41679
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
41680
+ "use client";
41681
+ log11 = logger.createLogger("almadar:ui:game:model-loader");
41682
+ }
41683
+ });
41684
+
41685
+ // components/game/molecules/GameCanvas3D.css
41686
+ var init_GameCanvas3D = __esm({
41687
+ "components/game/molecules/GameCanvas3D.css"() {
41688
+ }
41689
+ });
41690
+ function CameraController({
41691
+ onCameraChange
41692
+ }) {
41693
+ const { camera } = fiber.useThree();
41694
+ React82.useEffect(() => {
41695
+ if (onCameraChange) {
41696
+ onCameraChange({
41697
+ x: camera.position.x,
41698
+ y: camera.position.y,
41699
+ z: camera.position.z
41700
+ });
41701
+ }
41702
+ }, [camera.position, onCameraChange]);
41703
+ return null;
41704
+ }
41705
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
41706
+ var init_GameCanvas3D2 = __esm({
41707
+ "components/game/molecules/GameCanvas3D.tsx"() {
41708
+ "use client";
41709
+ init_AssetLoader();
41710
+ init_useAssetLoader();
41711
+ init_useGameCanvas3DEvents();
41712
+ init_Canvas3DLoadingState2();
41713
+ init_Canvas3DErrorBoundary2();
41714
+ init_ModelLoader();
41715
+ init_cn();
41716
+ init_GameCanvas3D();
41717
+ DEFAULT_GRID_CONFIG = {
41718
+ cellSize: 1,
41719
+ offsetX: 0,
41720
+ offsetZ: 0
41721
+ };
41722
+ GameCanvas3D = React82.forwardRef(
41723
+ ({
41724
+ tiles = [],
41725
+ units = [],
41726
+ features = [],
41727
+ events: events2 = [],
41728
+ orientation = "standard",
41729
+ cameraMode = "isometric",
41730
+ showGrid = true,
41731
+ showCoordinates = false,
41732
+ showTileInfo = false,
41733
+ overlay = "default",
41734
+ shadows = true,
41735
+ backgroundColor = "#1a1a2e",
41736
+ onTileClick,
41737
+ onUnitClick,
41738
+ onFeatureClick,
41739
+ onCanvasClick,
41740
+ onTileHover,
41741
+ onUnitAnimation,
41742
+ assetLoader: customAssetLoader,
41743
+ tileRenderer: CustomTileRenderer,
41744
+ unitRenderer: CustomUnitRenderer,
41745
+ featureRenderer: CustomFeatureRenderer,
41746
+ className,
41747
+ isLoading: externalLoading,
41748
+ error: externalError,
41749
+ entity,
41750
+ preloadAssets = [],
41751
+ tileClickEvent,
41752
+ unitClickEvent,
41753
+ featureClickEvent,
41754
+ canvasClickEvent,
41755
+ tileHoverEvent,
41756
+ tileLeaveEvent,
41757
+ unitAnimationEvent,
41758
+ cameraChangeEvent,
41759
+ loadingMessage = "Loading 3D Scene...",
41760
+ useInstancing = true,
41761
+ validMoves = [],
41762
+ attackTargets = [],
41763
+ selectedTileIds = [],
41764
+ selectedUnitId = null,
41765
+ children
41766
+ }, ref) => {
41767
+ const containerRef = React82.useRef(null);
41768
+ const controlsRef = React82.useRef(null);
41769
+ const [hoveredTile, setHoveredTile] = React82.useState(null);
41770
+ const [internalError, setInternalError] = React82.useState(null);
41771
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
41772
+ preloadUrls: preloadAssets,
41773
+ loader: customAssetLoader
41774
+ });
41775
+ const eventHandlers = useGameCanvas3DEvents({
41776
+ tileClickEvent,
41777
+ unitClickEvent,
41778
+ featureClickEvent,
41779
+ canvasClickEvent,
41780
+ tileHoverEvent,
41781
+ tileLeaveEvent,
41782
+ unitAnimationEvent,
41783
+ cameraChangeEvent,
41784
+ onTileClick,
41785
+ onUnitClick,
41786
+ onFeatureClick,
41787
+ onCanvasClick,
41788
+ onTileHover,
41789
+ onUnitAnimation
41790
+ });
41791
+ const gridBounds = React82.useMemo(() => {
41792
+ if (tiles.length === 0) {
41793
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
41794
+ }
41795
+ const xs = tiles.map((t) => t.x);
41796
+ const zs = tiles.map((t) => t.z || t.y || 0);
41797
+ return {
41798
+ minX: Math.min(...xs),
41799
+ maxX: Math.max(...xs),
41800
+ minZ: Math.min(...zs),
41801
+ maxZ: Math.max(...zs)
41802
+ };
41803
+ }, [tiles]);
41804
+ const cameraTarget = React82.useMemo(() => {
41805
+ return [
41806
+ (gridBounds.minX + gridBounds.maxX) / 2,
41807
+ 0,
41808
+ (gridBounds.minZ + gridBounds.maxZ) / 2
41809
+ ];
41810
+ }, [gridBounds]);
41811
+ const gridConfig = React82.useMemo(
41812
+ () => ({
41813
+ ...DEFAULT_GRID_CONFIG,
41814
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
41815
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
41816
+ }),
41817
+ [gridBounds]
41818
+ );
41819
+ const gridToWorld = React82.useCallback(
41820
+ (x, z, y = 0) => {
41821
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
41822
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
41823
+ return [worldX, y * gridConfig.cellSize, worldZ];
41824
+ },
41825
+ [gridBounds, gridConfig]
41826
+ );
41827
+ React82.useImperativeHandle(ref, () => ({
41828
+ getCameraPosition: () => {
41829
+ if (controlsRef.current) {
41830
+ const pos = controlsRef.current.object.position;
41831
+ return new THREE__namespace.Vector3(pos.x, pos.y, pos.z);
41832
+ }
41833
+ return null;
41834
+ },
41835
+ setCameraPosition: (x, y, z) => {
41836
+ if (controlsRef.current) {
41837
+ controlsRef.current.object.position.set(x, y, z);
41838
+ controlsRef.current.update();
41839
+ }
41840
+ },
41841
+ lookAt: (x, y, z) => {
41842
+ if (controlsRef.current) {
41843
+ controlsRef.current.target.set(x, y, z);
41844
+ controlsRef.current.update();
41845
+ }
41846
+ },
41847
+ resetCamera: () => {
41848
+ if (controlsRef.current) {
41849
+ controlsRef.current.reset();
41850
+ }
41851
+ },
41852
+ screenshot: () => {
41853
+ const canvas = containerRef.current?.querySelector("canvas");
41854
+ if (canvas) {
41855
+ return canvas.toDataURL("image/png");
41856
+ }
41857
+ return null;
41858
+ },
41859
+ export: () => ({
41860
+ tiles,
41861
+ units,
41862
+ features
41863
+ })
41864
+ }));
41865
+ const handleTileClick = React82.useCallback(
41866
+ (tile, event) => {
41867
+ eventHandlers.handleTileClick(tile, event);
41868
+ },
41869
+ [eventHandlers]
41870
+ );
41871
+ const handleUnitClick = React82.useCallback(
41872
+ (unit, event) => {
41873
+ eventHandlers.handleUnitClick(unit, event);
41874
+ },
41875
+ [eventHandlers]
41876
+ );
41877
+ const handleFeatureClick = React82.useCallback(
41878
+ (feature, event) => {
41879
+ if (event) {
41880
+ eventHandlers.handleFeatureClick(feature, event);
41881
+ }
41882
+ },
41883
+ [eventHandlers]
41884
+ );
41885
+ const handleTileHover = React82.useCallback(
41886
+ (tile, event) => {
41887
+ setHoveredTile(tile);
41888
+ if (event) {
41889
+ eventHandlers.handleTileHover(tile, event);
41890
+ }
41891
+ },
41892
+ [eventHandlers]
41893
+ );
41894
+ const cameraConfig = React82.useMemo(() => {
41895
+ const size = Math.max(
41896
+ gridBounds.maxX - gridBounds.minX,
41897
+ gridBounds.maxZ - gridBounds.minZ
41898
+ );
41899
+ const distance = size * 1.5;
41900
+ switch (cameraMode) {
41901
+ case "isometric":
41902
+ return {
41903
+ position: [distance, distance * 0.8, distance],
41904
+ fov: 45
41905
+ };
41906
+ case "top-down":
41907
+ return {
41908
+ position: [0, distance * 2, 0],
41909
+ fov: 45
41910
+ };
41911
+ case "perspective":
41912
+ default:
41913
+ return {
41914
+ position: [distance, distance, distance],
41915
+ fov: 45
41916
+ };
41917
+ }
41918
+ }, [cameraMode, gridBounds]);
41919
+ const DefaultTileRenderer = React82.useCallback(
41920
+ ({ tile, position }) => {
41921
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
41922
+ const isHovered = hoveredTile?.id === tile.id;
41923
+ const isValidMove = validMoves.some(
41924
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
41925
+ );
41926
+ const isAttackTarget = attackTargets.some(
41927
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
41928
+ );
41929
+ let color = 8421504;
41930
+ if (tile.type === "water") color = 4491468;
41931
+ else if (tile.type === "grass") color = 4500036;
41932
+ else if (tile.type === "sand") color = 14535816;
41933
+ else if (tile.type === "rock") color = 8947848;
41934
+ else if (tile.type === "snow") color = 15658734;
41935
+ let emissive = 0;
41936
+ if (isSelected) emissive = 4473924;
41937
+ else if (isAttackTarget) emissive = 4456448;
41938
+ else if (isValidMove) emissive = 17408;
41939
+ else if (isHovered) emissive = 2236962;
41940
+ if (tile.modelUrl) {
41941
+ return /* @__PURE__ */ jsxRuntime.jsx(
41942
+ "group",
41943
+ {
41944
+ position,
41945
+ onClick: (e) => handleTileClick(tile, e),
41946
+ onPointerEnter: (e) => handleTileHover(tile, e),
41947
+ onPointerLeave: (e) => handleTileHover(null, e),
41948
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
41949
+ children: /* @__PURE__ */ jsxRuntime.jsx(
41950
+ ModelLoader,
41951
+ {
41952
+ url: tile.modelUrl,
41953
+ scale: 0.95,
41954
+ fallbackGeometry: "box",
41955
+ castShadow: true,
41956
+ receiveShadow: true
41957
+ }
41958
+ )
41959
+ }
41960
+ );
41961
+ }
41962
+ return /* @__PURE__ */ jsxRuntime.jsxs(
41963
+ "mesh",
41964
+ {
41965
+ position,
41966
+ onClick: (e) => handleTileClick(tile, e),
41967
+ onPointerEnter: (e) => handleTileHover(tile, e),
41968
+ onPointerLeave: (e) => handleTileHover(null, e),
41969
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
41970
+ children: [
41971
+ /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
41972
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color, emissive })
41973
+ ]
41974
+ }
41975
+ );
41976
+ },
41977
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
41978
+ );
41979
+ const DefaultUnitRenderer = React82.useCallback(
41980
+ ({ unit, position }) => {
41981
+ const isSelected = selectedUnitId === unit.id;
41982
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
41983
+ return /* @__PURE__ */ jsxRuntime.jsxs(
41984
+ "group",
41985
+ {
41986
+ position,
41987
+ onClick: (e) => handleUnitClick(unit, e),
41988
+ userData: { type: "unit", unitId: unit.id },
41989
+ children: [
41990
+ isSelected && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
41991
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
41992
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
41993
+ ] }),
41994
+ unit.modelUrl ? (
41995
+ /* GLB unit model (box fallback while loading / on error) */
41996
+ /* @__PURE__ */ jsxRuntime.jsx(
41997
+ ModelLoader,
41998
+ {
41999
+ url: unit.modelUrl,
42000
+ scale: 0.5,
42001
+ fallbackGeometry: "box",
42002
+ castShadow: true
42003
+ }
42004
+ )
42005
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
42006
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.3, 0], children: [
42007
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
42008
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
42009
+ ] }),
42010
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.6, 0], children: [
42011
+ /* @__PURE__ */ jsxRuntime.jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
42012
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
42013
+ ] }),
42014
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
42015
+ /* @__PURE__ */ jsxRuntime.jsx("sphereGeometry", { args: [0.12, 8, 8] }),
42016
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
42017
+ ] })
42018
+ ] }),
42019
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("group", { position: [0, 1.2, 0], children: [
42020
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [-0.25, 0, 0], children: [
42021
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5, 0.05] }),
42022
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: 3355443 })
42023
+ ] }),
42024
+ /* @__PURE__ */ jsxRuntime.jsxs(
42025
+ "mesh",
42026
+ {
42027
+ position: [
42028
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
42029
+ 0,
42030
+ 0.01
42031
+ ],
42032
+ children: [
42033
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
42034
+ /* @__PURE__ */ jsxRuntime.jsx(
42035
+ "meshBasicMaterial",
42036
+ {
42037
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
42038
+ }
42039
+ )
42040
+ ]
42041
+ }
42042
+ )
42043
+ ] })
42044
+ ]
42045
+ }
42046
+ );
42047
+ },
42048
+ [selectedUnitId, handleUnitClick]
42049
+ );
42050
+ const DefaultFeatureRenderer = React82.useCallback(
42051
+ ({
42052
+ feature,
42053
+ position
42054
+ }) => {
42055
+ if (feature.assetUrl) {
42056
+ return /* @__PURE__ */ jsxRuntime.jsx(
42057
+ ModelLoader,
42058
+ {
42059
+ url: feature.assetUrl,
42060
+ position,
42061
+ scale: 0.5,
42062
+ rotation: [0, feature.rotation ?? 0, 0],
42063
+ onClick: () => handleFeatureClick(feature, null),
42064
+ fallbackGeometry: "box"
42065
+ },
42066
+ feature.id
42067
+ );
42068
+ }
42069
+ if (feature.type === "tree") {
42070
+ return /* @__PURE__ */ jsxRuntime.jsxs(
42071
+ "group",
42072
+ {
42073
+ position,
42074
+ onClick: (e) => handleFeatureClick(feature, e),
42075
+ userData: { type: "feature", featureId: feature.id },
42076
+ children: [
42077
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.4, 0], children: [
42078
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
42079
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 9127187 })
42080
+ ] }),
42081
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
42082
+ /* @__PURE__ */ jsxRuntime.jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
42083
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 2263842 })
42084
+ ] })
42085
+ ]
42086
+ }
42087
+ );
42088
+ }
42089
+ if (feature.type === "rock") {
42090
+ return /* @__PURE__ */ jsxRuntime.jsxs(
42091
+ "mesh",
42092
+ {
42093
+ position: [position[0], position[1] + 0.3, position[2]],
42094
+ onClick: (e) => handleFeatureClick(feature, e),
42095
+ userData: { type: "feature", featureId: feature.id },
42096
+ children: [
42097
+ /* @__PURE__ */ jsxRuntime.jsx("dodecahedronGeometry", { args: [0.3, 0] }),
42098
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 8421504 })
42099
+ ]
42100
+ }
42101
+ );
42102
+ }
42103
+ return null;
42104
+ },
42105
+ [handleFeatureClick]
42106
+ );
42107
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
42108
+ return /* @__PURE__ */ jsxRuntime.jsx(
42109
+ Canvas3DLoadingState,
42110
+ {
42111
+ progress,
42112
+ loaded,
42113
+ total,
42114
+ message: loadingMessage,
42115
+ className
42116
+ }
42117
+ );
42118
+ }
42119
+ const displayError = externalError || internalError;
42120
+ if (displayError) {
42121
+ 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: [
42122
+ "Error: ",
42123
+ displayError
42124
+ ] }) }) });
42125
+ }
42126
+ return /* @__PURE__ */ jsxRuntime.jsx(
42127
+ Canvas3DErrorBoundary,
42128
+ {
42129
+ onError: (err) => setInternalError(err.message),
42130
+ onReset: () => setInternalError(null),
42131
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
42132
+ "div",
42133
+ {
42134
+ ref: containerRef,
42135
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
42136
+ "data-orientation": orientation,
42137
+ "data-camera-mode": cameraMode,
42138
+ "data-overlay": overlay,
42139
+ children: [
42140
+ /* @__PURE__ */ jsxRuntime.jsxs(
42141
+ fiber.Canvas,
42142
+ {
42143
+ shadows,
42144
+ camera: {
42145
+ position: cameraConfig.position,
42146
+ fov: cameraConfig.fov,
42147
+ near: 0.1,
42148
+ far: 1e3
42149
+ },
42150
+ style: { background: backgroundColor },
42151
+ onClick: (e) => {
42152
+ if (e.target === e.currentTarget) {
42153
+ eventHandlers.handleCanvasClick(e);
42154
+ }
42155
+ },
42156
+ children: [
42157
+ /* @__PURE__ */ jsxRuntime.jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
42158
+ /* @__PURE__ */ jsxRuntime.jsx("ambientLight", { intensity: 0.6 }),
42159
+ /* @__PURE__ */ jsxRuntime.jsx(
42160
+ "directionalLight",
42161
+ {
42162
+ position: [10, 20, 10],
42163
+ intensity: 0.8,
42164
+ castShadow: shadows,
42165
+ "shadow-mapSize": [2048, 2048]
42166
+ }
42167
+ ),
42168
+ /* @__PURE__ */ jsxRuntime.jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
42169
+ showGrid && /* @__PURE__ */ jsxRuntime.jsx(
42170
+ drei.Grid,
42171
+ {
42172
+ args: [
42173
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
42174
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
42175
+ ],
42176
+ position: [
42177
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
42178
+ 0,
42179
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
42180
+ ],
42181
+ cellSize: 1,
42182
+ cellThickness: 1,
42183
+ cellColor: "#444444",
42184
+ sectionSize: 5,
42185
+ sectionThickness: 1.5,
42186
+ sectionColor: "#666666",
42187
+ fadeDistance: 50,
42188
+ fadeStrength: 1
42189
+ }
42190
+ ),
42191
+ tiles.map((tile, index) => {
42192
+ const position = gridToWorld(
42193
+ tile.x,
42194
+ tile.z ?? tile.y ?? 0,
42195
+ tile.elevation ?? 0
42196
+ );
42197
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
42198
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
42199
+ }),
42200
+ features.map((feature, index) => {
42201
+ const position = gridToWorld(
42202
+ feature.x,
42203
+ feature.z ?? feature.y ?? 0,
42204
+ (feature.elevation ?? 0) + 0.5
42205
+ );
42206
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
42207
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
42208
+ }),
42209
+ units.map((unit) => {
42210
+ const position = gridToWorld(
42211
+ unit.x ?? 0,
42212
+ unit.z ?? unit.y ?? 0,
42213
+ (unit.elevation ?? 0) + 0.5
42214
+ );
42215
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
42216
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { unit, position }, unit.id);
42217
+ }),
42218
+ children,
42219
+ /* @__PURE__ */ jsxRuntime.jsx(
42220
+ drei.OrbitControls,
42221
+ {
42222
+ ref: controlsRef,
42223
+ target: cameraTarget,
42224
+ enableDamping: true,
42225
+ dampingFactor: 0.05,
42226
+ minDistance: 2,
42227
+ maxDistance: 100,
42228
+ maxPolarAngle: Math.PI / 2 - 0.1
42229
+ }
42230
+ )
42231
+ ]
42232
+ }
42233
+ ),
42234
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__coordinates", children: [
42235
+ "X: ",
42236
+ hoveredTile.x,
42237
+ ", Z: ",
42238
+ hoveredTile.z ?? hoveredTile.y ?? 0
42239
+ ] }),
42240
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__tile-info", children: [
42241
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
42242
+ hoveredTile.terrain && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
42243
+ ] })
42244
+ ]
42245
+ }
42246
+ )
42247
+ }
42248
+ );
42249
+ }
42250
+ );
42251
+ GameCanvas3D.displayName = "GameCanvas3D";
42252
+ }
42253
+ });
42254
+ function GameBoard3D({
42255
+ entity,
42256
+ tiles = [],
42257
+ features = [],
42258
+ cameraMode = "perspective",
42259
+ backgroundColor = "#2a1a1a",
42260
+ tileClickEvent,
42261
+ unitClickEvent,
42262
+ attackEvent,
42263
+ endTurnEvent,
42264
+ cancelEvent,
42265
+ playAgainEvent,
42266
+ gameEndEvent,
42267
+ className
42268
+ }) {
42269
+ const row = boardEntity(entity);
42270
+ const eventBus = useEventBus();
42271
+ const entityUnits = row ? rows(row.units) : [];
42272
+ const units = entityUnits;
42273
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
42274
+ const phase = row ? str(row.phase) : "observation";
42275
+ const result = row ? str(row.result) : "none";
42276
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
42277
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
42278
+ const turn = row ? num(row.turn) : 0;
42279
+ const currentTeam = row ? str(row.currentTeam) : "player";
42280
+ const isGameOver = result !== "none";
42281
+ const gameEndEmittedRef = React82.useRef(false);
42282
+ React82.useEffect(() => {
42283
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
42284
+ if (!gameEndEmittedRef.current) {
42285
+ gameEndEmittedRef.current = true;
42286
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
42287
+ }
42288
+ } else {
42289
+ gameEndEmittedRef.current = false;
42290
+ }
42291
+ }, [result, gameEndEvent, eventBus]);
42292
+ const checkGameEnd = React82.useCallback(() => {
42293
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
42294
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
42295
+ if (alivePlayer.length === 0 && gameEndEvent) {
42296
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
42297
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
42298
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
42299
+ }
42300
+ }, [entityUnits, gameEndEvent, eventBus]);
42301
+ const handleUnitClickCallback = React82.useCallback((isoUnit) => {
42302
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
42303
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
42304
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
42305
+ if (!attackerRow || !targetRow) return;
42306
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
42307
+ const ap = unitPosition(attackerRow);
42308
+ const tp = unitPosition(targetRow);
42309
+ const dx = Math.abs(ap.x - tp.x);
42310
+ const dy = Math.abs(ap.y - tp.y);
42311
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
42312
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
42313
+ eventBus.emit(`UI:${attackEvent}`, {
42314
+ attackerId: str(attackerRow.id),
42315
+ targetId: str(targetRow.id),
42316
+ damage
42317
+ });
42318
+ setTimeout(checkGameEnd, 100);
42319
+ }
42320
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
42321
+ const handleEndTurn = React82.useCallback(() => {
42322
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
42323
+ }, [endTurnEvent, eventBus]);
42324
+ const handleCancel = React82.useCallback(() => {
42325
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
42326
+ }, [cancelEvent, eventBus]);
42327
+ const handlePlayAgain = React82.useCallback(() => {
42328
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
42329
+ }, [playAgainEvent, eventBus]);
42330
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
42331
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
42332
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
42333
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
42334
+ "\u2014 ",
42335
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
42336
+ ] }),
42337
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
42338
+ "Turn ",
42339
+ turn
42340
+ ] })
42341
+ ] }),
42342
+ /* @__PURE__ */ jsxRuntime.jsx(
42343
+ GameCanvas3D,
42344
+ {
42345
+ tiles,
42346
+ units,
42347
+ features,
42348
+ cameraMode,
42349
+ showGrid: true,
42350
+ showCoordinates: false,
42351
+ showTileInfo: false,
42352
+ shadows: true,
42353
+ backgroundColor,
42354
+ tileClickEvent,
42355
+ unitClickEvent,
42356
+ onUnitClick: handleUnitClickCallback,
42357
+ selectedUnitId,
42358
+ validMoves,
42359
+ attackTargets,
42360
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
42361
+ }
42362
+ ),
42363
+ !isGameOver && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
42364
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsxRuntime.jsx(
42365
+ Button,
42366
+ {
42367
+ variant: "secondary",
42368
+ className: "shadow-xl",
42369
+ onClick: handleCancel,
42370
+ children: "Cancel"
42371
+ }
42372
+ ),
42373
+ phase !== "enemy_turn" && /* @__PURE__ */ jsxRuntime.jsx(
42374
+ Button,
42375
+ {
42376
+ variant: "primary",
42377
+ className: "shadow-xl",
42378
+ onClick: handleEndTurn,
42379
+ children: "End Turn"
42380
+ }
42381
+ )
42382
+ ] }),
42383
+ 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: [
42384
+ /* @__PURE__ */ jsxRuntime.jsx(
42385
+ Typography,
42386
+ {
42387
+ variant: "h2",
42388
+ className: cn(
42389
+ "overlay__result",
42390
+ result === "victory" ? "text-yellow-400" : "text-red-500"
42391
+ ),
42392
+ children: result === "victory" ? "Victory!" : "Defeat"
42393
+ }
42394
+ ),
42395
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
42396
+ "Completed in ",
42397
+ turn,
42398
+ " turn",
42399
+ turn !== 1 ? "s" : ""
42400
+ ] }),
42401
+ /* @__PURE__ */ jsxRuntime.jsx(
42402
+ Button,
42403
+ {
42404
+ variant: "primary",
42405
+ className: "px-8 py-3 font-semibold",
42406
+ onClick: handlePlayAgain,
42407
+ children: "Play Again"
42408
+ }
42409
+ )
42410
+ ] }) })
42411
+ ] });
42412
+ }
42413
+ var init_GameBoard3D = __esm({
42414
+ "components/game/organisms/GameBoard3D.tsx"() {
42415
+ "use client";
42416
+ init_cn();
42417
+ init_Button();
42418
+ init_Typography();
42419
+ init_Stack();
42420
+ init_useEventBus();
42421
+ init_GameCanvas3D2();
42422
+ init_boardEntity();
42423
+ GameBoard3D.displayName = "GameBoard3D";
42424
+ }
42425
+ });
40770
42426
  var GameShell;
40771
42427
  var init_GameShell = __esm({
40772
42428
  "components/game/templates/GameShell.tsx"() {
@@ -42309,6 +43965,236 @@ var init_NegotiatorBoard = __esm({
42309
43965
  NegotiatorBoard.displayName = "NegotiatorBoard";
42310
43966
  }
42311
43967
  });
43968
+ function resolveEntity(entity) {
43969
+ if (!entity) return {};
43970
+ if (Array.isArray(entity)) return entity[0] ?? {};
43971
+ return entity;
43972
+ }
43973
+ function numField(row, key, fallback) {
43974
+ const v = row[key];
43975
+ return typeof v === "number" ? v : fallback;
43976
+ }
43977
+ function strField(row, key, fallback) {
43978
+ const v = row[key];
43979
+ return typeof v === "string" ? v : fallback;
43980
+ }
43981
+ function PlatformerBoard({
43982
+ entity,
43983
+ player: propPlayer,
43984
+ platforms: propPlatforms,
43985
+ result: propResult,
43986
+ lives: propLives,
43987
+ score: propScore,
43988
+ level: propLevel,
43989
+ worldWidth = 800,
43990
+ worldHeight = 400,
43991
+ canvasWidth = 800,
43992
+ canvasHeight = 400,
43993
+ playerSprite,
43994
+ tileSprites,
43995
+ bgColor,
43996
+ leftEvent = "LEFT",
43997
+ rightEvent = "RIGHT",
43998
+ jumpEvent = "JUMP",
43999
+ stopEvent = "STOP",
44000
+ playAgainEvent = "PLAY_AGAIN",
44001
+ gameEndEvent = "GAME_END",
44002
+ className
44003
+ }) {
44004
+ const row = resolveEntity(entity);
44005
+ const eventBus = useEventBus();
44006
+ const { t } = hooks.useTranslate();
44007
+ const result = propResult ?? strField(row, "result", "none");
44008
+ propLives ?? numField(row, "lives", 3);
44009
+ const score = propScore ?? numField(row, "score", 0);
44010
+ const level = propLevel ?? numField(row, "level", 1);
44011
+ const entityPlayer = row["player"];
44012
+ const player = propPlayer ?? entityPlayer ?? {
44013
+ x: 80,
44014
+ y: 320,
44015
+ width: 32,
44016
+ height: 48,
44017
+ vx: 0,
44018
+ vy: 0,
44019
+ grounded: false,
44020
+ facingRight: true
44021
+ };
44022
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
44023
+ const platforms = propPlatforms ?? entityPlatforms ?? [
44024
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44025
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44026
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44027
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44028
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44029
+ ];
44030
+ const handleRestart = React82.useCallback(() => {
44031
+ if (playAgainEvent) {
44032
+ eventBus.emit(`UI:${playAgainEvent}`, {});
44033
+ }
44034
+ }, [playAgainEvent, eventBus]);
44035
+ const isGameOver = result === "won" || result === "lost";
44036
+ const gameEndEmittedRef = React82.useRef(false);
44037
+ React82.useEffect(() => {
44038
+ if ((result === "won" || result === "lost") && gameEndEvent) {
44039
+ if (!gameEndEmittedRef.current) {
44040
+ gameEndEmittedRef.current = true;
44041
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44042
+ }
44043
+ } else {
44044
+ gameEndEmittedRef.current = false;
44045
+ }
44046
+ }, [result, gameEndEvent, eventBus]);
44047
+ return /* @__PURE__ */ jsxRuntime.jsx(
44048
+ VStack,
44049
+ {
44050
+ className: cn("platformer-board relative bg-background", className),
44051
+ gap: "none",
44052
+ children: /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative", children: [
44053
+ /* @__PURE__ */ jsxRuntime.jsx(
44054
+ PlatformerCanvas,
44055
+ {
44056
+ player,
44057
+ platforms,
44058
+ worldWidth,
44059
+ worldHeight,
44060
+ canvasWidth,
44061
+ canvasHeight,
44062
+ followCamera: true,
44063
+ bgColor,
44064
+ playerSprite,
44065
+ tileSprites,
44066
+ leftEvent,
44067
+ rightEvent,
44068
+ jumpEvent,
44069
+ stopEvent
44070
+ }
44071
+ ),
44072
+ 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: [
44073
+ /* @__PURE__ */ jsxRuntime.jsx(
44074
+ Typography,
44075
+ {
44076
+ variant: "h2",
44077
+ className: cn(
44078
+ "text-4xl font-black tracking-widest uppercase",
44079
+ result === "won" ? "text-warning" : "text-error"
44080
+ ),
44081
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
44082
+ }
44083
+ ),
44084
+ result === "won" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
44085
+ t("platformer.level"),
44086
+ " ",
44087
+ level,
44088
+ " \u2014 ",
44089
+ t("platformer.score"),
44090
+ ": ",
44091
+ score
44092
+ ] }),
44093
+ result === "lost" && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
44094
+ /* @__PURE__ */ jsxRuntime.jsx(
44095
+ Button,
44096
+ {
44097
+ variant: "primary",
44098
+ className: "px-8 py-3 font-semibold",
44099
+ onClick: handleRestart,
44100
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
44101
+ }
44102
+ )
44103
+ ] }) })
44104
+ ] })
44105
+ }
44106
+ );
44107
+ }
44108
+ var init_PlatformerBoard = __esm({
44109
+ "components/game/organisms/PlatformerBoard.tsx"() {
44110
+ "use client";
44111
+ init_cn();
44112
+ init_useEventBus();
44113
+ init_Box();
44114
+ init_Button();
44115
+ init_Typography();
44116
+ init_Stack();
44117
+ init_PlatformerCanvas();
44118
+ PlatformerBoard.displayName = "PlatformerBoard";
44119
+ }
44120
+ });
44121
+ function PlatformerTemplate({
44122
+ entity,
44123
+ platforms = DEFAULT_PLATFORMS,
44124
+ worldWidth = 800,
44125
+ worldHeight = 400,
44126
+ canvasWidth = 800,
44127
+ canvasHeight = 400,
44128
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
44129
+ tileSprites = DEFAULT_TILE_SPRITES,
44130
+ bgColor = "#5c94fc",
44131
+ gameEndEvent = "GAME_END",
44132
+ className
44133
+ }) {
44134
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
44135
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
44136
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
44137
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
44138
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
44139
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsx(
44140
+ GameHud,
44141
+ {
44142
+ position: "top",
44143
+ stats: [
44144
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
44145
+ { label: "Level", value: level, format: "number", variant: "default" }
44146
+ ]
44147
+ }
44148
+ ) }),
44149
+ /* @__PURE__ */ jsxRuntime.jsx(
44150
+ PlatformerBoard,
44151
+ {
44152
+ entity,
44153
+ platforms,
44154
+ worldWidth,
44155
+ worldHeight,
44156
+ canvasWidth,
44157
+ canvasHeight,
44158
+ playerSprite,
44159
+ tileSprites,
44160
+ bgColor,
44161
+ leftEvent: "LEFT",
44162
+ rightEvent: "RIGHT",
44163
+ jumpEvent: "JUMP",
44164
+ stopEvent: "STOP",
44165
+ playAgainEvent: "PLAY_AGAIN",
44166
+ gameEndEvent
44167
+ }
44168
+ ),
44169
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(ScoreBoard, { score, level }) })
44170
+ ] });
44171
+ }
44172
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
44173
+ var init_PlatformerTemplate = __esm({
44174
+ "components/game/templates/PlatformerTemplate.tsx"() {
44175
+ init_cn();
44176
+ init_Box();
44177
+ init_Stack();
44178
+ init_PlatformerBoard();
44179
+ init_GameHud();
44180
+ init_ScoreBoard();
44181
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
44182
+ DEFAULT_TILE_SPRITES = {
44183
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
44184
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
44185
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
44186
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
44187
+ };
44188
+ DEFAULT_PLATFORMS = [
44189
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44190
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44191
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44192
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44193
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44194
+ ];
44195
+ PlatformerTemplate.displayName = "PlatformerTemplate";
44196
+ }
44197
+ });
42312
44198
  var PricingOrganism;
42313
44199
  var init_PricingOrganism = __esm({
42314
44200
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -42431,6 +44317,356 @@ var init_PricingPageTemplate = __esm({
42431
44317
  PricingPageTemplate.displayName = "PricingPageTemplate";
42432
44318
  }
42433
44319
  });
44320
+ function RoguelikeBoard({
44321
+ entity,
44322
+ tiles: propTiles,
44323
+ enemies: propEnemies,
44324
+ items: propItems,
44325
+ player: propPlayer,
44326
+ playerHp: propPlayerHp,
44327
+ playerMaxHp: propPlayerMaxHp,
44328
+ playerAttack: propPlayerAttack,
44329
+ depth: propDepth,
44330
+ result: propResult,
44331
+ phase: propPhase,
44332
+ stairsX: propStairsX,
44333
+ stairsY: propStairsY,
44334
+ assetManifest: propAssetManifest,
44335
+ scale = 0.45,
44336
+ unitScale = 1,
44337
+ moveEvent,
44338
+ playAgainEvent,
44339
+ gameEndEvent,
44340
+ className
44341
+ }) {
44342
+ const board = boardEntity(entity) ?? {};
44343
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
44344
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
44345
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
44346
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
44347
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
44348
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
44349
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
44350
+ const depth = propDepth ?? num(board.depth, 1);
44351
+ const result = propResult ?? (str(board.result) || "none");
44352
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
44353
+ propStairsX ?? num(board.stairsX, 3);
44354
+ propStairsY ?? num(board.stairsY, 3);
44355
+ const eventBus = useEventBus();
44356
+ const { t } = hooks.useTranslate();
44357
+ const [hoveredTile, setHoveredTile] = React82.useState(null);
44358
+ const emitMove = React82.useCallback((dx, dy) => {
44359
+ if (!moveEvent) return;
44360
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
44361
+ }, [moveEvent, eventBus]);
44362
+ React82.useEffect(() => {
44363
+ const handler = (e) => {
44364
+ if (result !== "none" || phase !== "player_turn") return;
44365
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
44366
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
44367
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
44368
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
44369
+ };
44370
+ window.addEventListener("keydown", handler);
44371
+ return () => window.removeEventListener("keydown", handler);
44372
+ }, [result, phase, emitMove]);
44373
+ React82.useEffect(() => {
44374
+ if ((result === "won" || result === "lost") && gameEndEvent) {
44375
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44376
+ }
44377
+ }, [result, gameEndEvent, eventBus]);
44378
+ const handleTileClick = React82.useCallback((x, y) => {
44379
+ if (result !== "none" || phase !== "player_turn") return;
44380
+ const dx = x - player.x;
44381
+ const dy = y - player.y;
44382
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
44383
+ emitMove(dx, dy);
44384
+ }
44385
+ }, [result, phase, player, emitMove]);
44386
+ const handleReset = React82.useCallback(() => {
44387
+ if (!playAgainEvent) return;
44388
+ eventBus.emit(`UI:${playAgainEvent}`, {});
44389
+ }, [playAgainEvent, eventBus]);
44390
+ const isoUnits = React82.useMemo(() => {
44391
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
44392
+ return [
44393
+ {
44394
+ id: "player",
44395
+ position: player,
44396
+ name: t("roguelike.player") || "Hero",
44397
+ team: "player",
44398
+ health: playerHp,
44399
+ maxHealth: playerMaxHp,
44400
+ unitType: "player",
44401
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
44402
+ },
44403
+ ...liveEnemies.map((e) => ({
44404
+ id: e.id,
44405
+ position: { x: e.x, y: e.y },
44406
+ name: t("roguelike.enemy") || "Enemy",
44407
+ team: "enemy",
44408
+ health: e.hp,
44409
+ maxHealth: 6,
44410
+ unitType: "enemy",
44411
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
44412
+ }))
44413
+ ];
44414
+ }, [player, enemies, playerHp, playerMaxHp, t]);
44415
+ const isoFeatures = React82.useMemo(
44416
+ () => items.map((it) => ({
44417
+ id: it.id,
44418
+ x: it.x,
44419
+ y: it.y,
44420
+ type: it.kind,
44421
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
44422
+ })),
44423
+ [items]
44424
+ );
44425
+ const validMoves = React82.useMemo(() => {
44426
+ if (result !== "none" || phase !== "player_turn") return [];
44427
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
44428
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
44429
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
44430
+ return tile?.passable ?? false;
44431
+ });
44432
+ }, [result, phase, player, tiles]);
44433
+ const isGameOver = result === "won" || result === "lost";
44434
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
44435
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
44436
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
44437
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
44438
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
44439
+ ] }),
44440
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
44441
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
44442
+ /* @__PURE__ */ jsxRuntime.jsxs(
44443
+ Typography,
44444
+ {
44445
+ variant: "body1",
44446
+ className: cn(
44447
+ "font-bold tabular-nums",
44448
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
44449
+ ),
44450
+ children: [
44451
+ playerHp,
44452
+ " / ",
44453
+ playerMaxHp
44454
+ ]
44455
+ }
44456
+ )
44457
+ ] }),
44458
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
44459
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
44460
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
44461
+ ] }),
44462
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
44463
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
44464
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
44465
+ ] })
44466
+ ] }),
44467
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
44468
+ IsometricCanvas_default,
44469
+ {
44470
+ tiles,
44471
+ units: isoUnits,
44472
+ features: isoFeatures,
44473
+ selectedUnitId: null,
44474
+ validMoves,
44475
+ attackTargets: [],
44476
+ hoveredTile,
44477
+ onTileClick: handleTileClick,
44478
+ onUnitClick: () => void 0,
44479
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
44480
+ onTileLeave: () => setHoveredTile(null),
44481
+ scale,
44482
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
44483
+ assetManifest: propAssetManifest,
44484
+ unitScale
44485
+ }
44486
+ ) }),
44487
+ !isGameOver && /* @__PURE__ */ jsxRuntime.jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
44488
+ { label: "\u2191", dx: 0, dy: -1 },
44489
+ { label: "\u2193", dx: 0, dy: 1 },
44490
+ { label: "\u2190", dx: -1, dy: 0 },
44491
+ { label: "\u2192", dx: 1, dy: 0 }
44492
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsxRuntime.jsx(
44493
+ Button,
44494
+ {
44495
+ variant: "secondary",
44496
+ className: "w-9 h-9 p-0 text-base font-mono",
44497
+ onClick: () => emitMove(dx, dy),
44498
+ disabled: phase !== "player_turn",
44499
+ children: label
44500
+ },
44501
+ label
44502
+ )) }),
44503
+ 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: [
44504
+ /* @__PURE__ */ jsxRuntime.jsx(
44505
+ Typography,
44506
+ {
44507
+ variant: "h2",
44508
+ className: cn(
44509
+ "text-4xl font-black tracking-widest uppercase",
44510
+ result === "won" ? "text-warning" : "text-error"
44511
+ ),
44512
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
44513
+ }
44514
+ ),
44515
+ result === "won" && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
44516
+ /* @__PURE__ */ jsxRuntime.jsx(
44517
+ Button,
44518
+ {
44519
+ variant: "primary",
44520
+ className: "px-8 py-3 font-semibold",
44521
+ onClick: handleReset,
44522
+ children: t("roguelike.playAgain") || "Play Again"
44523
+ }
44524
+ )
44525
+ ] }) })
44526
+ ] });
44527
+ }
44528
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
44529
+ var init_RoguelikeBoard = __esm({
44530
+ "components/game/organisms/RoguelikeBoard.tsx"() {
44531
+ "use client";
44532
+ init_cn();
44533
+ init_useEventBus();
44534
+ init_Box();
44535
+ init_Button();
44536
+ init_Typography();
44537
+ init_Stack();
44538
+ init_IsometricCanvas();
44539
+ init_boardEntity();
44540
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
44541
+ DEFAULT_TILES2 = [
44542
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44543
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44544
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44545
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44546
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44547
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44548
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44549
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44550
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44551
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44552
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44553
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44554
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44555
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44556
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44557
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44558
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44559
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44560
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
44561
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44562
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44563
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44564
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44565
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44566
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
44567
+ ];
44568
+ DEFAULT_ENEMIES = [
44569
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
44570
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
44571
+ ];
44572
+ DEFAULT_ITEMS = [
44573
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
44574
+ ];
44575
+ FEATURE_SPRITE = {
44576
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
44577
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
44578
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
44579
+ };
44580
+ RoguelikeBoard.displayName = "RoguelikeBoard";
44581
+ }
44582
+ });
44583
+ function RoguelikeTemplate({
44584
+ entity,
44585
+ scale = 0.45,
44586
+ unitScale = 1,
44587
+ tiles = DEFAULT_ROGUELIKE_TILES,
44588
+ enemies,
44589
+ items,
44590
+ stairsX,
44591
+ stairsY,
44592
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
44593
+ gameEndEvent,
44594
+ className
44595
+ }) {
44596
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
44597
+ if (!resolved && !tiles && !assetManifest) return null;
44598
+ return /* @__PURE__ */ jsxRuntime.jsx(
44599
+ RoguelikeBoard,
44600
+ {
44601
+ entity: resolved,
44602
+ tiles,
44603
+ enemies,
44604
+ items,
44605
+ stairsX,
44606
+ stairsY,
44607
+ assetManifest,
44608
+ scale,
44609
+ unitScale,
44610
+ moveEvent: "MOVE",
44611
+ playAgainEvent: "PLAY_AGAIN",
44612
+ gameEndEvent: gameEndEvent ?? "GAME_END",
44613
+ className
44614
+ }
44615
+ );
44616
+ }
44617
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
44618
+ var init_RoguelikeTemplate = __esm({
44619
+ "components/game/templates/RoguelikeTemplate.tsx"() {
44620
+ init_RoguelikeBoard();
44621
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
44622
+ DEFAULT_ROGUELIKE_TILES = [
44623
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44624
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44625
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44626
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44627
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44628
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44629
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44630
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44631
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44632
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44633
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44634
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44635
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44636
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44637
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44638
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44639
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44640
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44641
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
44642
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44643
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44644
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44645
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44646
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44647
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
44648
+ ];
44649
+ DEFAULT_ROGUELIKE_MANIFEST = {
44650
+ baseUrl: CDN5,
44651
+ terrains: {
44652
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
44653
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
44654
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
44655
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
44656
+ },
44657
+ units: {
44658
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
44659
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
44660
+ },
44661
+ features: {
44662
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
44663
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
44664
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
44665
+ }
44666
+ };
44667
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
44668
+ }
44669
+ });
42434
44670
 
42435
44671
  // lib/traitRegistry.ts
42436
44672
  function notifyListeners2() {
@@ -43684,7 +45920,7 @@ function getAllEvents(traits2) {
43684
45920
  function EventDispatcherTab({ traits: traits2, schema }) {
43685
45921
  const eventBus = useEventBus();
43686
45922
  const { t } = hooks.useTranslate();
43687
- const [log12, setLog] = React82__namespace.useState([]);
45923
+ const [log14, setLog] = React82__namespace.useState([]);
43688
45924
  const prevStatesRef = React82__namespace.useRef(/* @__PURE__ */ new Map());
43689
45925
  React82__namespace.useEffect(() => {
43690
45926
  for (const trait of traits2) {
@@ -43748,9 +45984,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
43748
45984
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
43749
45985
  /* @__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)) })
43750
45986
  ] }),
43751
- log12.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
45987
+ log14.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
43752
45988
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
43753
- /* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log12.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
45989
+ /* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log14.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
43754
45990
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-purple-400", children: entry.traitName }),
43755
45991
  " ",
43756
45992
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -45671,9 +47907,9 @@ function VariablePanel({
45671
47907
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
45672
47908
  variables.map((v) => {
45673
47909
  const name = v.name == null ? "" : String(v.name);
45674
- const value = numField(v.value);
45675
- const max = numField(v.max, 100);
45676
- const min = numField(v.min, 0);
47910
+ const value = numField2(v.value);
47911
+ const max = numField2(v.max, 100);
47912
+ const min = numField2(v.min, 0);
45677
47913
  const unit = v.unit == null ? "" : String(v.unit);
45678
47914
  const pct = Math.round((value - min) / (max - min) * 100);
45679
47915
  const isHigh = pct > 80;
@@ -45703,12 +47939,12 @@ function VariablePanel({
45703
47939
  })
45704
47940
  ] });
45705
47941
  }
45706
- var numField;
47942
+ var numField2;
45707
47943
  var init_VariablePanel = __esm({
45708
47944
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
45709
47945
  init_atoms2();
45710
47946
  init_cn();
45711
- numField = (v, fallback = 0) => {
47947
+ numField2 = (v, fallback = 0) => {
45712
47948
  const n = Number(v);
45713
47949
  return Number.isFinite(n) ? n : fallback;
45714
47950
  };
@@ -46561,6 +48797,289 @@ var init_ToastSlot = __esm({
46561
48797
  ToastSlot.displayName = "ToastSlot";
46562
48798
  }
46563
48799
  });
48800
+ function tilesToIso(tiles) {
48801
+ return tiles.map((t) => ({
48802
+ x: t.x,
48803
+ y: t.y,
48804
+ terrain: t.terrain,
48805
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
48806
+ passable: t.passable
48807
+ }));
48808
+ }
48809
+ function towersToUnits(towers) {
48810
+ return towers.map((t) => ({
48811
+ id: t.id,
48812
+ position: { x: t.x, y: t.y },
48813
+ name: "Tower",
48814
+ team: "player",
48815
+ sprite: TOWER_SPRITE,
48816
+ unitType: "guardian",
48817
+ health: 1,
48818
+ maxHealth: 1
48819
+ }));
48820
+ }
48821
+ function creepsToUnits(creeps) {
48822
+ return creeps.map((c) => ({
48823
+ id: c.id,
48824
+ position: { x: c.x, y: c.y },
48825
+ name: "Creep",
48826
+ team: "enemy",
48827
+ sprite: CREEP_SPRITE,
48828
+ unitType: "scrapper",
48829
+ health: c.hp,
48830
+ maxHealth: c.maxHp
48831
+ }));
48832
+ }
48833
+ function pathToFeatures(path) {
48834
+ return path.map((p2, i) => ({
48835
+ id: `path-${i}`,
48836
+ x: p2.x,
48837
+ y: p2.y,
48838
+ type: "path-marker",
48839
+ sprite: `${CDN6}world-map/road_straight.png`
48840
+ }));
48841
+ }
48842
+ function TowerDefenseBoard({
48843
+ entity,
48844
+ tiles: propTiles,
48845
+ path: propPath,
48846
+ towers: propTowers,
48847
+ creeps: propCreeps,
48848
+ gold: propGold,
48849
+ lives: propLives,
48850
+ wave: propWave,
48851
+ maxWaves: propMaxWaves,
48852
+ result: propResult,
48853
+ waveActive: propWaveActive,
48854
+ towerCost = 25,
48855
+ scale = 0.45,
48856
+ placeTowerEvent,
48857
+ startWaveEvent,
48858
+ playAgainEvent,
48859
+ gameEndEvent,
48860
+ className
48861
+ }) {
48862
+ const board = boardEntity(entity) ?? {};
48863
+ const eventBus = useEventBus();
48864
+ const { t } = hooks.useTranslate();
48865
+ const tiles = propTiles ?? rows(board.tiles);
48866
+ const path = propPath ?? rows(board.path);
48867
+ const towers = propTowers ?? rows(board.towers);
48868
+ const creeps = propCreeps ?? rows(board.creeps);
48869
+ const gold = propGold ?? num(board.gold, 100);
48870
+ const lives = propLives ?? num(board.lives, 20);
48871
+ const wave = propWave ?? num(board.wave, 1);
48872
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
48873
+ const result = propResult ?? (str(board.result) || "none");
48874
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
48875
+ const [hoveredTile, setHoveredTile] = React82.useState(null);
48876
+ const emittedGameEnd = React82.useRef(false);
48877
+ if (result !== "none" && !emittedGameEnd.current) {
48878
+ emittedGameEnd.current = true;
48879
+ if (gameEndEvent) {
48880
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
48881
+ }
48882
+ }
48883
+ if (result === "none") {
48884
+ emittedGameEnd.current = false;
48885
+ }
48886
+ const towerPositions = React82.useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
48887
+ const pathPositions = React82.useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
48888
+ const validMoves = React82.useMemo(() => {
48889
+ if (result !== "none" || gold < towerCost) return [];
48890
+ return tiles.filter(
48891
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
48892
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
48893
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
48894
+ const isoTiles = React82.useMemo(() => tilesToIso(tiles), [tiles]);
48895
+ const towerUnits = React82.useMemo(() => towersToUnits(towers), [towers]);
48896
+ const creepUnits = React82.useMemo(() => creepsToUnits(creeps), [creeps]);
48897
+ const isoUnits = React82.useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
48898
+ const pathFeatures = React82.useMemo(() => pathToFeatures(path), [path]);
48899
+ const handleTileClick = React82.useCallback((x, y) => {
48900
+ if (result !== "none") return;
48901
+ if (pathPositions.has(`${x},${y}`)) return;
48902
+ if (towerPositions.has(`${x},${y}`)) return;
48903
+ if (gold < towerCost) return;
48904
+ if (placeTowerEvent) {
48905
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
48906
+ }
48907
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
48908
+ const handleStartWave = React82.useCallback(() => {
48909
+ if (startWaveEvent) {
48910
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
48911
+ }
48912
+ }, [startWaveEvent, wave, eventBus]);
48913
+ const handlePlayAgain = React82.useCallback(() => {
48914
+ if (playAgainEvent) {
48915
+ eventBus.emit(`UI:${playAgainEvent}`, {});
48916
+ }
48917
+ }, [playAgainEvent, eventBus]);
48918
+ const canPlaceTower = gold >= towerCost && result === "none";
48919
+ const canStartWave = !waveActive && result === "none";
48920
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
48921
+ /* @__PURE__ */ jsxRuntime.jsxs(
48922
+ HStack,
48923
+ {
48924
+ className: "px-4 py-2 border-b border-border bg-surface",
48925
+ gap: "lg",
48926
+ align: "center",
48927
+ children: [
48928
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
48929
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
48930
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
48931
+ ] }),
48932
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
48933
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
48934
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
48935
+ ] }),
48936
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
48937
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
48938
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body2", weight: "bold", children: [
48939
+ wave,
48940
+ " / ",
48941
+ maxWaves
48942
+ ] })
48943
+ ] }),
48944
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "ml-auto", children: [
48945
+ canStartWave && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
48946
+ waveActive && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
48947
+ ] })
48948
+ ]
48949
+ }
48950
+ ),
48951
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative flex-1", children: [
48952
+ /* @__PURE__ */ jsxRuntime.jsx(
48953
+ IsometricCanvas_default,
48954
+ {
48955
+ tiles: isoTiles,
48956
+ units: isoUnits,
48957
+ features: pathFeatures,
48958
+ validMoves,
48959
+ hoveredTile,
48960
+ onTileClick: handleTileClick,
48961
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
48962
+ onTileLeave: () => setHoveredTile(null),
48963
+ scale
48964
+ }
48965
+ ),
48966
+ 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)` }) })
48967
+ ] }),
48968
+ 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: [
48969
+ /* @__PURE__ */ jsxRuntime.jsx(
48970
+ Typography,
48971
+ {
48972
+ variant: "h2",
48973
+ className: cn(
48974
+ "text-4xl font-black tracking-widest uppercase",
48975
+ result === "won" ? "text-warning" : "text-error"
48976
+ ),
48977
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
48978
+ }
48979
+ ),
48980
+ /* @__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." }),
48981
+ /* @__PURE__ */ jsxRuntime.jsx(
48982
+ Button,
48983
+ {
48984
+ variant: "primary",
48985
+ className: "px-8 py-3 font-semibold",
48986
+ onClick: handlePlayAgain,
48987
+ children: t("td.playAgain") ?? "Play Again"
48988
+ }
48989
+ )
48990
+ ] }) })
48991
+ ] });
48992
+ }
48993
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
48994
+ var init_TowerDefenseBoard = __esm({
48995
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
48996
+ "use client";
48997
+ init_cn();
48998
+ init_useEventBus();
48999
+ init_Box();
49000
+ init_Button();
49001
+ init_Typography();
49002
+ init_Stack();
49003
+ init_IsometricCanvas();
49004
+ init_boardEntity();
49005
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
49006
+ TERRAIN_SPRITES = {
49007
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
49008
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
49009
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
49010
+ };
49011
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
49012
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
49013
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
49014
+ }
49015
+ });
49016
+ function TowerDefenseTemplate({
49017
+ entity,
49018
+ title = "Tower Defense",
49019
+ tiles,
49020
+ path,
49021
+ towers,
49022
+ gold,
49023
+ lives,
49024
+ maxWaves,
49025
+ towerCost,
49026
+ scale,
49027
+ placeTowerEvent,
49028
+ startWaveEvent,
49029
+ playAgainEvent,
49030
+ gameEndEvent,
49031
+ className
49032
+ }) {
49033
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
49034
+ return /* @__PURE__ */ jsxRuntime.jsxs(
49035
+ Box,
49036
+ {
49037
+ display: "flex",
49038
+ fullHeight: true,
49039
+ className: cn("tower-defense-template flex-col", className),
49040
+ children: [
49041
+ /* @__PURE__ */ jsxRuntime.jsx(
49042
+ HStack,
49043
+ {
49044
+ gap: "sm",
49045
+ align: "center",
49046
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
49047
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title })
49048
+ }
49049
+ ),
49050
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
49051
+ TowerDefenseBoard,
49052
+ {
49053
+ entity: resolved,
49054
+ tiles,
49055
+ path,
49056
+ towers,
49057
+ gold,
49058
+ lives,
49059
+ maxWaves,
49060
+ towerCost,
49061
+ scale,
49062
+ placeTowerEvent,
49063
+ startWaveEvent,
49064
+ playAgainEvent,
49065
+ gameEndEvent,
49066
+ className: "h-full"
49067
+ }
49068
+ ) })
49069
+ ]
49070
+ }
49071
+ );
49072
+ }
49073
+ var init_TowerDefenseTemplate = __esm({
49074
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
49075
+ init_cn();
49076
+ init_Box();
49077
+ init_Stack();
49078
+ init_Typography();
49079
+ init_TowerDefenseBoard();
49080
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
49081
+ }
49082
+ });
46564
49083
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
46565
49084
  const eventBus = useEventBus();
46566
49085
  const {
@@ -47070,48 +49589,48 @@ function WorldMapTemplate({
47070
49589
  }
47071
49590
  );
47072
49591
  }
47073
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
49592
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
47074
49593
  var init_WorldMapTemplate = __esm({
47075
49594
  "components/game/templates/WorldMapTemplate.tsx"() {
47076
49595
  init_WorldMapBoard();
47077
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
49596
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
47078
49597
  DEFAULT_WORLDMAP_TILES = [
47079
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47080
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47081
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47082
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47083
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47084
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47085
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47086
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47087
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47088
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47089
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47090
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47091
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47092
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47093
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47094
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47095
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47096
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47097
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47098
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47099
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47100
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47101
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47102
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47103
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
49598
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49599
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49600
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49601
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49602
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49603
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49604
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49605
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49606
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49607
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49608
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49609
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49610
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49611
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49612
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49613
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49614
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49615
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49616
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49617
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49618
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49619
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49620
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49621
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49622
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
47104
49623
  ];
47105
49624
  DEFAULT_WORLDMAP_UNITS = [
47106
- { 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` },
47107
- { 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` }
49625
+ { 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` },
49626
+ { 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` }
47108
49627
  ];
47109
49628
  DEFAULT_WORLDMAP_FEATURES = [
47110
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
47111
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
49629
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
49630
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
47112
49631
  ];
47113
49632
  DEFAULT_WORLDMAP_MANIFEST = {
47114
- baseUrl: CDN3,
49633
+ baseUrl: CDN7,
47115
49634
  terrains: {
47116
49635
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
47117
49636
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -47156,7 +49675,7 @@ function lazyThree(name, loader) {
47156
49675
  ThreeWrapper.displayName = `Lazy(${name})`;
47157
49676
  return ThreeWrapper;
47158
49677
  }
47159
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
49678
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
47160
49679
  var init_component_registry_generated = __esm({
47161
49680
  "components/core/organisms/component-registry.generated.ts"() {
47162
49681
  init_AboutPageTemplate();
@@ -47266,6 +49785,7 @@ var init_component_registry_generated = __esm({
47266
49785
  init_FormSectionHeader();
47267
49786
  init_GameAudioProvider();
47268
49787
  init_GameAudioToggle();
49788
+ init_GameBoard3D();
47269
49789
  init_GameCanvas2D();
47270
49790
  init_GameHud();
47271
49791
  init_GameMenu();
@@ -47323,7 +49843,9 @@ var init_component_registry_generated = __esm({
47323
49843
  init_PageHeader();
47324
49844
  init_Pagination();
47325
49845
  init_PatternTile();
49846
+ init_PlatformerBoard();
47326
49847
  init_PlatformerCanvas();
49848
+ init_PlatformerTemplate();
47327
49849
  init_Popover();
47328
49850
  init_PositionedCanvas();
47329
49851
  init_PowerupSlots();
@@ -47347,6 +49869,8 @@ var init_component_registry_generated = __esm({
47347
49869
  init_ResourceBar();
47348
49870
  init_ResourceCounter();
47349
49871
  init_RichBlockEditor();
49872
+ init_RoguelikeBoard();
49873
+ init_RoguelikeTemplate();
47350
49874
  init_RuntimeDebugger2();
47351
49875
  init_ScaledDiagram();
47352
49876
  init_ScoreBoard();
@@ -47419,6 +49943,8 @@ var init_component_registry_generated = __esm({
47419
49943
  init_Toast();
47420
49944
  init_ToastSlot();
47421
49945
  init_Tooltip();
49946
+ init_TowerDefenseBoard();
49947
+ init_TowerDefenseTemplate();
47422
49948
  init_TraitFrame();
47423
49949
  init_TraitSlot();
47424
49950
  init_TrendIndicator();
@@ -47463,7 +49989,7 @@ var init_component_registry_generated = __esm({
47463
49989
  }
47464
49990
  };
47465
49991
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
47466
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
49992
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
47467
49993
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47468
49994
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47469
49995
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -47582,8 +50108,9 @@ var init_component_registry_generated = __esm({
47582
50108
  "FormSectionHeader": FormSectionHeader,
47583
50109
  "GameAudioProvider": GameAudioProvider,
47584
50110
  "GameAudioToggle": GameAudioToggle,
50111
+ "GameBoard3D": GameBoard3D,
47585
50112
  "GameCanvas2D": GameCanvas2D,
47586
- "GameCanvas3D": GameCanvas3D,
50113
+ "GameCanvas3D": GameCanvas3D2,
47587
50114
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
47588
50115
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
47589
50116
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -47645,7 +50172,9 @@ var init_component_registry_generated = __esm({
47645
50172
  "PageHeader": PageHeader,
47646
50173
  "Pagination": Pagination,
47647
50174
  "PatternTile": PatternTile,
50175
+ "PlatformerBoard": PlatformerBoard,
47648
50176
  "PlatformerCanvas": PlatformerCanvas,
50177
+ "PlatformerTemplate": PlatformerTemplate,
47649
50178
  "Popover": Popover,
47650
50179
  "PositionedCanvas": PositionedCanvas,
47651
50180
  "PowerupSlots": PowerupSlots,
@@ -47669,6 +50198,8 @@ var init_component_registry_generated = __esm({
47669
50198
  "ResourceBar": ResourceBar,
47670
50199
  "ResourceCounter": ResourceCounter,
47671
50200
  "RichBlockEditor": RichBlockEditor,
50201
+ "RoguelikeBoard": RoguelikeBoard,
50202
+ "RoguelikeTemplate": RoguelikeTemplate,
47672
50203
  "RuntimeDebugger": RuntimeDebugger,
47673
50204
  "ScaledDiagram": ScaledDiagram,
47674
50205
  "ScoreBoard": ScoreBoard,
@@ -47744,6 +50275,8 @@ var init_component_registry_generated = __esm({
47744
50275
  "Toast": Toast,
47745
50276
  "ToastSlot": ToastSlot,
47746
50277
  "Tooltip": Tooltip,
50278
+ "TowerDefenseBoard": TowerDefenseBoard,
50279
+ "TowerDefenseTemplate": TowerDefenseTemplate,
47747
50280
  "TraitFrame": TraitFrame,
47748
50281
  "TraitSlot": TraitSlot,
47749
50282
  "TrendIndicator": TrendIndicator,
@@ -49640,7 +52173,7 @@ function clearSchemaCache() {
49640
52173
 
49641
52174
  // runtime/index.ts
49642
52175
  init_EntitySchemaContext();
49643
- var log10 = logger.createLogger("almadar:ui:trait-provider");
52176
+ var log12 = logger.createLogger("almadar:ui:trait-provider");
49644
52177
  var TraitContext = React82.createContext(null);
49645
52178
  function TraitProvider({
49646
52179
  traits: traitBindings,
@@ -49657,7 +52190,7 @@ function TraitProvider({
49657
52190
  currentState: stateName,
49658
52191
  availableEvents: trait.transitions.filter((t) => t.from === stateName).map((t) => t.event),
49659
52192
  dispatch: (eventKey, payload) => {
49660
- log10.debug("Dispatch", () => ({
52193
+ log12.debug("Dispatch", () => ({
49661
52194
  trait: trait.name,
49662
52195
  event: eventKey,
49663
52196
  payloadKeys: payload ? Object.keys(payload) : []
@@ -50241,7 +52774,7 @@ OrbitalThemeProvider.displayName = "OrbitalThemeProvider";
50241
52774
  init_navigation();
50242
52775
  init_verificationRegistry();
50243
52776
  var PERF_NAMESPACE = "almadar:perf:canvas";
50244
- var log11 = logger.createLogger(PERF_NAMESPACE);
52777
+ var log13 = logger.createLogger(PERF_NAMESPACE);
50245
52778
  var RING_SIZE = 50;
50246
52779
  var ring = [];
50247
52780
  var writeIdx = 0;
@@ -50296,7 +52829,7 @@ function perfEnd(name, startToken, detail) {
50296
52829
  }
50297
52830
  }
50298
52831
  push({ name, durationMs, ts: endTs, detail });
50299
- log11.debug(name, () => ({ durationMs, ...detail ?? {} }));
52832
+ log13.debug(name, () => ({ durationMs, ...detail ?? {} }));
50300
52833
  }
50301
52834
  function perfTime(name, fn, detail) {
50302
52835
  const t = perfStart(name);
@@ -50314,7 +52847,7 @@ var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, com
50314
52847
  ts: commitTime,
50315
52848
  detail: { baseDuration }
50316
52849
  });
50317
- log11.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
52850
+ log13.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
50318
52851
  };
50319
52852
  function getPerfSnapshot() {
50320
52853
  if (ring.length < RING_SIZE) return ring.slice();