@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
@@ -1,5 +1,5 @@
1
1
  import * as React83 from 'react';
2
- import React83__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, useLayoutEffect, lazy, useId, useSyncExternalStore } from 'react';
2
+ import React83__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, useLayoutEffect, lazy, useId, useSyncExternalStore, forwardRef, useImperativeHandle, Component } from 'react';
3
3
  import { createLogger, isLogLevelEnabled } from '@almadar/logger';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { EventBusContext, useTraitScope, TraitScopeProvider } from '@almadar/ui/providers';
@@ -41,6 +41,12 @@ import { DndContext, pointerWithin, rectIntersection, closestCorners, useSensors
41
41
  import { useSortable, arrayMove, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
42
42
  import { CSS } from '@dnd-kit/utilities';
43
43
  import { useNodeId, ReactFlowProvider, Handle, Position } from '@xyflow/react';
44
+ import * as THREE from 'three';
45
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
46
+ import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
47
+ import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
48
+ import { Canvas, useThree } from '@react-three/fiber';
49
+ import { Grid as Grid$1, OrbitControls } from '@react-three/drei';
44
50
  import { getPatternDefinition, getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
45
51
 
46
52
  var __defProp = Object.defineProperty;
@@ -673,7 +679,7 @@ var init_Box = __esm({
673
679
  position,
674
680
  className,
675
681
  children,
676
- as: Component = "div",
682
+ as: Component2 = "div",
677
683
  action,
678
684
  actionPayload,
679
685
  hoverEvent,
@@ -705,7 +711,7 @@ var init_Box = __esm({
705
711
  }, [hoverEvent, eventBus, onMouseLeave]);
706
712
  const isClickable = action || onClick;
707
713
  return React83__default.createElement(
708
- Component,
714
+ Component2,
709
715
  {
710
716
  ref,
711
717
  className: cn(
@@ -843,9 +849,9 @@ function resolvePhosphor(name, weight, family) {
843
849
  (lib) => {
844
850
  const PhosphorComp = lib[target];
845
851
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
846
- const Component = PhosphorComp;
852
+ const Component2 = PhosphorComp;
847
853
  const Adapter = (props) => /* @__PURE__ */ jsx(
848
- Component,
854
+ Component2,
849
855
  {
850
856
  weight,
851
857
  className: props.className,
@@ -869,9 +875,9 @@ function resolveTabler(name, family) {
869
875
  (lib) => {
870
876
  const TablerComp = lib[target];
871
877
  if (!TablerComp || typeof TablerComp !== "object") return null;
872
- const Component = TablerComp;
878
+ const Component2 = TablerComp;
873
879
  const Adapter = (props) => /* @__PURE__ */ jsx(
874
- Component,
880
+ Component2,
875
881
  {
876
882
  stroke: props.strokeWidth ?? 1.5,
877
883
  className: props.className,
@@ -895,9 +901,9 @@ function resolveFa(name, family) {
895
901
  (lib) => {
896
902
  const FaComp = lib[target];
897
903
  if (!FaComp || typeof FaComp !== "function") return null;
898
- const Component = FaComp;
904
+ const Component2 = FaComp;
899
905
  const Adapter = (props) => /* @__PURE__ */ jsx(
900
- Component,
906
+ Component2,
901
907
  {
902
908
  className: props.className,
903
909
  style: props.style,
@@ -1852,8 +1858,8 @@ var init_Typography = __esm({
1852
1858
  children
1853
1859
  }) => {
1854
1860
  const variant = variantProp ?? (level ? `h${level}` : "body1");
1855
- const Component = as || defaultElements[variant];
1856
- const Comp = Component;
1861
+ const Component2 = as || defaultElements[variant];
1862
+ const Comp = Component2;
1857
1863
  return /* @__PURE__ */ jsx(
1858
1864
  Comp,
1859
1865
  {
@@ -4408,10 +4414,10 @@ var init_Center = __esm({
4408
4414
  className,
4409
4415
  style,
4410
4416
  children,
4411
- as: Component = "div"
4417
+ as: Component2 = "div"
4412
4418
  }) => {
4413
4419
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
4414
- const Comp = Component;
4420
+ const Comp = Component2;
4415
4421
  return /* @__PURE__ */ jsx(
4416
4422
  Comp,
4417
4423
  {
@@ -5042,7 +5048,7 @@ var init_Stack = __esm({
5042
5048
  className,
5043
5049
  style,
5044
5050
  children,
5045
- as: Component = "div",
5051
+ as: Component2 = "div",
5046
5052
  onClick,
5047
5053
  onKeyDown,
5048
5054
  role,
@@ -5060,7 +5066,7 @@ var init_Stack = __esm({
5060
5066
  };
5061
5067
  const isHorizontal = direction === "horizontal";
5062
5068
  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";
5063
- const Comp = Component;
5069
+ const Comp = Component2;
5064
5070
  return /* @__PURE__ */ jsx(
5065
5071
  Comp,
5066
5072
  {
@@ -7116,6 +7122,7 @@ var init_HealthBar = __esm({
7116
7122
  }
7117
7123
  });
7118
7124
  function ScoreDisplay({
7125
+ assetUrl = DEFAULT_ASSET_URL,
7119
7126
  value,
7120
7127
  label,
7121
7128
  icon,
@@ -7162,14 +7169,24 @@ function ScoreDisplay({
7162
7169
  className
7163
7170
  ),
7164
7171
  children: [
7165
- icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
7172
+ assetUrl ? /* @__PURE__ */ jsx(
7173
+ "img",
7174
+ {
7175
+ src: assetUrl,
7176
+ alt: "",
7177
+ width: 20,
7178
+ height: 20,
7179
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7180
+ className: "flex-shrink-0"
7181
+ }
7182
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }) : null,
7166
7183
  label && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
7167
7184
  /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formattedValue })
7168
7185
  ]
7169
7186
  }
7170
7187
  );
7171
7188
  }
7172
- var sizeMap2;
7189
+ var sizeMap2, DEFAULT_ASSET_URL;
7173
7190
  var init_ScoreDisplay = __esm({
7174
7191
  "components/game/atoms/ScoreDisplay.tsx"() {
7175
7192
  "use client";
@@ -7181,10 +7198,12 @@ var init_ScoreDisplay = __esm({
7181
7198
  lg: "text-2xl",
7182
7199
  xl: "text-4xl"
7183
7200
  };
7201
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
7184
7202
  ScoreDisplay.displayName = "ScoreDisplay";
7185
7203
  }
7186
7204
  });
7187
7205
  function ControlButton({
7206
+ assetUrl = DEFAULT_ASSET_URL2,
7188
7207
  label,
7189
7208
  icon,
7190
7209
  size = "md",
@@ -7231,7 +7250,7 @@ function ControlButton({
7231
7250
  },
7232
7251
  [isPressed, releaseEvent, eventBus, onRelease]
7233
7252
  );
7234
- return /* @__PURE__ */ jsxs(
7253
+ return /* @__PURE__ */ jsx(
7235
7254
  "button",
7236
7255
  {
7237
7256
  type: "button",
@@ -7252,20 +7271,27 @@ function ControlButton({
7252
7271
  disabled && "opacity-50 cursor-not-allowed",
7253
7272
  className
7254
7273
  ),
7255
- children: [
7256
- icon && /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7257
- const I = resolveIcon(icon);
7258
- return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
7259
- })() : icon : /* @__PURE__ */ (() => {
7260
- const I = icon;
7261
- return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
7262
- })() }),
7263
- label && !icon && /* @__PURE__ */ jsx("span", { children: label })
7264
- ]
7274
+ children: assetUrl ? /* @__PURE__ */ jsx(
7275
+ "img",
7276
+ {
7277
+ src: assetUrl,
7278
+ alt: "",
7279
+ width: 24,
7280
+ height: 24,
7281
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7282
+ className: "flex-shrink-0"
7283
+ }
7284
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7285
+ const I = resolveIcon(icon);
7286
+ return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
7287
+ })() : icon : /* @__PURE__ */ (() => {
7288
+ const I = icon;
7289
+ return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
7290
+ })() }) : label ? /* @__PURE__ */ jsx("span", { children: label }) : null
7265
7291
  }
7266
7292
  );
7267
7293
  }
7268
- var sizeMap3, shapeMap, variantMap;
7294
+ var sizeMap3, shapeMap, variantMap, DEFAULT_ASSET_URL2;
7269
7295
  var init_ControlButton = __esm({
7270
7296
  "components/game/atoms/ControlButton.tsx"() {
7271
7297
  "use client";
@@ -7288,6 +7314,7 @@ var init_ControlButton = __esm({
7288
7314
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
7289
7315
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
7290
7316
  };
7317
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
7291
7318
  ControlButton.displayName = "ControlButton";
7292
7319
  }
7293
7320
  });
@@ -7367,7 +7394,7 @@ var init_Sprite = __esm({
7367
7394
  }
7368
7395
  });
7369
7396
  function StateIndicator({
7370
- assetUrl = DEFAULT_ASSET_URL,
7397
+ assetUrl = DEFAULT_ASSET_URL3,
7371
7398
  state = "idle",
7372
7399
  label,
7373
7400
  size = "md",
@@ -7406,13 +7433,13 @@ function StateIndicator({
7406
7433
  }
7407
7434
  );
7408
7435
  }
7409
- var DEFAULT_ASSET_URL, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7436
+ var DEFAULT_ASSET_URL3, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7410
7437
  var init_StateIndicator = __esm({
7411
7438
  "components/game/atoms/StateIndicator.tsx"() {
7412
7439
  init_Box();
7413
7440
  init_Icon();
7414
7441
  init_cn();
7415
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7442
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7416
7443
  DEFAULT_STATE_STYLES = {
7417
7444
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
7418
7445
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -7487,7 +7514,7 @@ var init_TimerDisplay = __esm({
7487
7514
  }
7488
7515
  });
7489
7516
  function ResourceCounter({
7490
- assetUrl = DEFAULT_ASSET_URL2,
7517
+ assetUrl = DEFAULT_ASSET_URL4,
7491
7518
  icon,
7492
7519
  label = "Gold",
7493
7520
  value = 250,
@@ -7530,7 +7557,7 @@ function ResourceCounter({
7530
7557
  }
7531
7558
  );
7532
7559
  }
7533
- var colorTokenClasses2, DEFAULT_ASSET_URL2, sizeMap5;
7560
+ var colorTokenClasses2, DEFAULT_ASSET_URL4, sizeMap5;
7534
7561
  var init_ResourceCounter = __esm({
7535
7562
  "components/game/atoms/ResourceCounter.tsx"() {
7536
7563
  init_cn();
@@ -7543,7 +7570,7 @@ var init_ResourceCounter = __esm({
7543
7570
  error: "text-error",
7544
7571
  muted: "text-muted-foreground"
7545
7572
  };
7546
- DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7573
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7547
7574
  sizeMap5 = {
7548
7575
  sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
7549
7576
  md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
@@ -7553,7 +7580,7 @@ var init_ResourceCounter = __esm({
7553
7580
  }
7554
7581
  });
7555
7582
  function ItemSlot({
7556
- assetUrl = DEFAULT_ASSET_URL3,
7583
+ assetUrl = DEFAULT_ASSET_URL5,
7557
7584
  icon = "sword",
7558
7585
  label = "Iron Sword",
7559
7586
  quantity,
@@ -7612,7 +7639,7 @@ function ItemSlot({
7612
7639
  }
7613
7640
  );
7614
7641
  }
7615
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL3, assetSizeMap;
7642
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL5, assetSizeMap;
7616
7643
  var init_ItemSlot = __esm({
7617
7644
  "components/game/atoms/ItemSlot.tsx"() {
7618
7645
  "use client";
@@ -7637,7 +7664,7 @@ var init_ItemSlot = __esm({
7637
7664
  epic: "shadow-lg",
7638
7665
  legendary: "shadow-lg"
7639
7666
  };
7640
- DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7667
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7641
7668
  assetSizeMap = {
7642
7669
  sm: 28,
7643
7670
  md: 40,
@@ -7647,6 +7674,7 @@ var init_ItemSlot = __esm({
7647
7674
  }
7648
7675
  });
7649
7676
  function TurnIndicator({
7677
+ assetUrl = DEFAULT_ASSET_URL6,
7650
7678
  currentTurn = 1,
7651
7679
  maxTurns,
7652
7680
  activeTeam,
@@ -7679,14 +7707,24 @@ function TurnIndicator({
7679
7707
  ] }),
7680
7708
  activeTeam && /* @__PURE__ */ jsxs(Fragment, { children: [
7681
7709
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "|" }),
7682
- /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7710
+ assetUrl ? /* @__PURE__ */ jsx(
7711
+ "img",
7712
+ {
7713
+ src: assetUrl,
7714
+ alt: "",
7715
+ width: 12,
7716
+ height: 12,
7717
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7718
+ className: "flex-shrink-0"
7719
+ }
7720
+ ) : /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7683
7721
  /* @__PURE__ */ jsx("span", { className: "text-success", children: activeTeam })
7684
7722
  ] })
7685
7723
  ]
7686
7724
  }
7687
7725
  );
7688
7726
  }
7689
- var sizeMap7;
7727
+ var sizeMap7, DEFAULT_ASSET_URL6;
7690
7728
  var init_TurnIndicator = __esm({
7691
7729
  "components/game/atoms/TurnIndicator.tsx"() {
7692
7730
  init_cn();
@@ -7695,6 +7733,7 @@ var init_TurnIndicator = __esm({
7695
7733
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
7696
7734
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
7697
7735
  };
7736
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
7698
7737
  TurnIndicator.displayName = "TurnIndicator";
7699
7738
  }
7700
7739
  });
@@ -7710,7 +7749,7 @@ function getComboScale(combo) {
7710
7749
  return "";
7711
7750
  }
7712
7751
  function ComboCounter({
7713
- assetUrl = DEFAULT_ASSET_URL4,
7752
+ assetUrl = DEFAULT_ASSET_URL7,
7714
7753
  combo = 5,
7715
7754
  multiplier,
7716
7755
  streak,
@@ -7755,11 +7794,11 @@ function ComboCounter({
7755
7794
  }
7756
7795
  );
7757
7796
  }
7758
- var DEFAULT_ASSET_URL4, sizeMap8;
7797
+ var DEFAULT_ASSET_URL7, sizeMap8;
7759
7798
  var init_ComboCounter = __esm({
7760
7799
  "components/game/atoms/ComboCounter.tsx"() {
7761
7800
  init_cn();
7762
- DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7801
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7763
7802
  sizeMap8 = {
7764
7803
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
7765
7804
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -7853,7 +7892,7 @@ var init_XPBar = __esm({
7853
7892
  }
7854
7893
  });
7855
7894
  function WaypointMarker({
7856
- assetUrl = DEFAULT_ASSET_URL5,
7895
+ assetUrl = DEFAULT_ASSET_URL8,
7857
7896
  label,
7858
7897
  icon,
7859
7898
  active = true,
@@ -7919,12 +7958,12 @@ function WaypointMarker({
7919
7958
  )
7920
7959
  ] });
7921
7960
  }
7922
- var DEFAULT_ASSET_URL5, sizeMap10, checkIcon;
7961
+ var DEFAULT_ASSET_URL8, sizeMap10, checkIcon;
7923
7962
  var init_WaypointMarker = __esm({
7924
7963
  "components/game/atoms/WaypointMarker.tsx"() {
7925
7964
  init_cn();
7926
7965
  init_Icon();
7927
- DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7966
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7928
7967
  sizeMap10 = {
7929
7968
  sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
7930
7969
  md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
@@ -7941,7 +7980,7 @@ function formatDuration(seconds) {
7941
7980
  return `${Math.round(seconds)}s`;
7942
7981
  }
7943
7982
  function StatusEffect({
7944
- assetUrl = DEFAULT_ASSET_URL6,
7983
+ assetUrl = DEFAULT_ASSET_URL9,
7945
7984
  icon = "shield",
7946
7985
  label = "Shield",
7947
7986
  duration = 30,
@@ -7999,12 +8038,12 @@ function StatusEffect({
7999
8038
  label && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
8000
8039
  ] });
8001
8040
  }
8002
- var DEFAULT_ASSET_URL6, sizeMap11, variantStyles7;
8041
+ var DEFAULT_ASSET_URL9, sizeMap11, variantStyles7;
8003
8042
  var init_StatusEffect = __esm({
8004
8043
  "components/game/atoms/StatusEffect.tsx"() {
8005
8044
  init_cn();
8006
8045
  init_Icon();
8007
- DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
8046
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
8008
8047
  sizeMap11 = {
8009
8048
  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 },
8010
8049
  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 },
@@ -8019,6 +8058,7 @@ var init_StatusEffect = __esm({
8019
8058
  }
8020
8059
  });
8021
8060
  function DamageNumber({
8061
+ assetUrl = DEFAULT_ASSET_URL10,
8022
8062
  value = 42,
8023
8063
  type = "damage",
8024
8064
  size = "md",
@@ -8027,22 +8067,35 @@ function DamageNumber({
8027
8067
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
8028
8068
  return /* @__PURE__ */ jsxs(Fragment, { children: [
8029
8069
  /* @__PURE__ */ jsx("style", { children: floatKeyframes }),
8030
- /* @__PURE__ */ jsx(
8070
+ /* @__PURE__ */ jsxs(
8031
8071
  "span",
8032
8072
  {
8033
8073
  className: cn(
8034
- "inline-block font-mono select-none pointer-events-none",
8074
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
8035
8075
  sizeMap12[size],
8036
8076
  typeStyles[type],
8037
8077
  className
8038
8078
  ),
8039
8079
  style: { animation: "damageFloat 1s ease-out forwards" },
8040
- children: displayText
8080
+ children: [
8081
+ assetUrl && /* @__PURE__ */ jsx(
8082
+ "img",
8083
+ {
8084
+ src: assetUrl,
8085
+ alt: "",
8086
+ width: 14,
8087
+ height: 14,
8088
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8089
+ className: "flex-shrink-0"
8090
+ }
8091
+ ),
8092
+ displayText
8093
+ ]
8041
8094
  }
8042
8095
  )
8043
8096
  ] });
8044
8097
  }
8045
- var sizeMap12, typeStyles, floatKeyframes;
8098
+ var sizeMap12, typeStyles, floatKeyframes, DEFAULT_ASSET_URL10;
8046
8099
  var init_DamageNumber = __esm({
8047
8100
  "components/game/atoms/DamageNumber.tsx"() {
8048
8101
  init_cn();
@@ -8064,6 +8117,7 @@ var init_DamageNumber = __esm({
8064
8117
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
8065
8118
  }
8066
8119
  `;
8120
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
8067
8121
  DamageNumber.displayName = "DamageNumber";
8068
8122
  }
8069
8123
  });
@@ -8152,6 +8206,7 @@ var init_ChoiceButton = __esm({
8152
8206
  }
8153
8207
  });
8154
8208
  function ActionButton({
8209
+ assetUrl = DEFAULT_ASSET_URL11,
8155
8210
  label = "Attack",
8156
8211
  icon,
8157
8212
  cooldown = 0,
@@ -8191,13 +8246,23 @@ function ActionButton({
8191
8246
  }
8192
8247
  }
8193
8248
  ),
8194
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8249
+ assetUrl ? /* @__PURE__ */ jsx(
8250
+ "img",
8251
+ {
8252
+ src: assetUrl,
8253
+ alt: "",
8254
+ width: 16,
8255
+ height: 16,
8256
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8257
+ className: cn("flex-shrink-0", sizes.icon)
8258
+ }
8259
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8195
8260
  const I = resolveIcon(icon);
8196
8261
  return I ? /* @__PURE__ */ jsx(I, { className: "w-4 h-4" }) : null;
8197
8262
  })() : /* @__PURE__ */ (() => {
8198
8263
  const I = icon;
8199
8264
  return /* @__PURE__ */ jsx(I, { className: "w-4 h-4" });
8200
- })() }),
8265
+ })() }) : null,
8201
8266
  /* @__PURE__ */ jsx("span", { className: "relative z-10", children: label }),
8202
8267
  hotkey && /* @__PURE__ */ jsx(
8203
8268
  "span",
@@ -8213,7 +8278,7 @@ function ActionButton({
8213
8278
  }
8214
8279
  );
8215
8280
  }
8216
- var sizeMap13, variantStyles8;
8281
+ var sizeMap13, variantStyles8, DEFAULT_ASSET_URL11;
8217
8282
  var init_ActionButton = __esm({
8218
8283
  "components/game/atoms/ActionButton.tsx"() {
8219
8284
  init_cn();
@@ -8228,6 +8293,7 @@ var init_ActionButton = __esm({
8228
8293
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
8229
8294
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
8230
8295
  };
8296
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
8231
8297
  ActionButton.displayName = "ActionButton";
8232
8298
  }
8233
8299
  });
@@ -9096,10 +9162,10 @@ var init_Container = __esm({
9096
9162
  center = true,
9097
9163
  className,
9098
9164
  children,
9099
- as: Component = "div"
9165
+ as: Component2 = "div"
9100
9166
  }) => {
9101
9167
  const resolvedSize = maxWidth ?? size ?? "lg";
9102
- const Comp = Component;
9168
+ const Comp = Component2;
9103
9169
  return /* @__PURE__ */ jsx(
9104
9170
  Comp,
9105
9171
  {
@@ -25093,7 +25159,7 @@ var init_Flex = __esm({
25093
25159
  basis,
25094
25160
  className,
25095
25161
  children,
25096
- as: Component = "div"
25162
+ as: Component2 = "div"
25097
25163
  }) => {
25098
25164
  const flexStyle = {};
25099
25165
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -25105,7 +25171,7 @@ var init_Flex = __esm({
25105
25171
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
25106
25172
  }
25107
25173
  }
25108
- const Comp = Component;
25174
+ const Comp = Component2;
25109
25175
  return /* @__PURE__ */ jsx(
25110
25176
  Comp,
25111
25177
  {
@@ -25225,11 +25291,11 @@ var init_Grid = __esm({
25225
25291
  className,
25226
25292
  style,
25227
25293
  children,
25228
- as: Component = "div"
25294
+ as: Component2 = "div"
25229
25295
  }) => {
25230
25296
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
25231
25297
  return React83__default.createElement(
25232
- Component,
25298
+ Component2,
25233
25299
  {
25234
25300
  className: cn(
25235
25301
  "grid",
@@ -27055,6 +27121,7 @@ var init_ProgressDots = __esm({
27055
27121
  }
27056
27122
  });
27057
27123
  function StatBadge({
27124
+ assetUrl = DEFAULT_ASSET_URL12,
27058
27125
  label,
27059
27126
  value = 0,
27060
27127
  max,
@@ -27078,7 +27145,17 @@ function StatBadge({
27078
27145
  className
27079
27146
  ),
27080
27147
  children: [
27081
- icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 text-lg", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, className: "w-4 h-4" }) : /* @__PURE__ */ jsx(Icon, { icon, className: "w-4 h-4" }) }),
27148
+ assetUrl ? /* @__PURE__ */ jsx(
27149
+ "img",
27150
+ {
27151
+ src: assetUrl,
27152
+ alt: "",
27153
+ width: 16,
27154
+ height: 16,
27155
+ style: { imageRendering: "pixelated", objectFit: "contain" },
27156
+ className: "flex-shrink-0"
27157
+ }
27158
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 text-lg", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, className: "w-4 h-4" }) : /* @__PURE__ */ jsx(Icon, { icon, className: "w-4 h-4" }) }) : null,
27082
27159
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-medium", children: label }),
27083
27160
  format === "hearts" && max && /* @__PURE__ */ jsx(
27084
27161
  HealthBar,
@@ -27111,7 +27188,7 @@ function StatBadge({
27111
27188
  }
27112
27189
  );
27113
27190
  }
27114
- var sizeMap17, variantMap2;
27191
+ var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
27115
27192
  var init_StatBadge = __esm({
27116
27193
  "components/game/molecules/StatBadge.tsx"() {
27117
27194
  init_cn();
@@ -27130,6 +27207,7 @@ var init_StatBadge = __esm({
27130
27207
  warning: "bg-warning/15 border-warning/40 text-foreground",
27131
27208
  danger: "bg-error/15 border-error/40 text-foreground"
27132
27209
  };
27210
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
27133
27211
  StatBadge.displayName = "StatBadge";
27134
27212
  }
27135
27213
  });
@@ -27786,6 +27864,7 @@ var init_TurnPanel = __esm({
27786
27864
  }
27787
27865
  });
27788
27866
  function EnemyPlate({
27867
+ assetUrl = DEFAULT_ASSET_URL13,
27789
27868
  name = "Shadow Guard",
27790
27869
  health = 80,
27791
27870
  maxHealth = 100,
@@ -27803,15 +27882,28 @@ function EnemyPlate({
27803
27882
  ),
27804
27883
  children: [
27805
27884
  /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
27806
- /* @__PURE__ */ jsx(
27807
- Typography,
27808
- {
27809
- variant: "caption",
27810
- weight: "bold",
27811
- className: "text-[var(--color-foreground)] truncate",
27812
- children: name
27813
- }
27814
- ),
27885
+ /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
27886
+ assetUrl && /* @__PURE__ */ jsx(
27887
+ "img",
27888
+ {
27889
+ src: assetUrl,
27890
+ alt: name,
27891
+ width: 24,
27892
+ height: 24,
27893
+ style: { objectFit: "cover", borderRadius: "50%" },
27894
+ className: "flex-shrink-0"
27895
+ }
27896
+ ),
27897
+ /* @__PURE__ */ jsx(
27898
+ Typography,
27899
+ {
27900
+ variant: "caption",
27901
+ weight: "bold",
27902
+ className: "text-[var(--color-foreground)] truncate",
27903
+ children: name
27904
+ }
27905
+ )
27906
+ ] }),
27815
27907
  level != null && /* @__PURE__ */ jsxs(Badge, { variant: "neutral", size: "sm", children: [
27816
27908
  "Lv.",
27817
27909
  level
@@ -27856,7 +27948,7 @@ function EnemyPlate({
27856
27948
  }
27857
27949
  );
27858
27950
  }
27859
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
27951
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
27860
27952
  var init_EnemyPlate = __esm({
27861
27953
  "components/game/molecules/EnemyPlate.tsx"() {
27862
27954
  "use client";
@@ -27873,6 +27965,7 @@ var init_EnemyPlate = __esm({
27873
27965
  DEFAULT_ENEMY_EFFECTS = [
27874
27966
  { icon: "flame", label: "Burn", variant: "debuff" }
27875
27967
  ];
27968
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
27876
27969
  EnemyPlate.displayName = "EnemyPlate";
27877
27970
  }
27878
27971
  });
@@ -29404,13 +29497,13 @@ var init_MapView = __esm({
29404
29497
  shadowSize: [41, 41]
29405
29498
  });
29406
29499
  L.Marker.prototype.options.icon = defaultIcon;
29407
- const { useEffect: useEffect73, useRef: useRef68, useCallback: useCallback111, useState: useState104 } = React83__default;
29500
+ const { useEffect: useEffect79, useRef: useRef74, useCallback: useCallback118, useState: useState109 } = React83__default;
29408
29501
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29409
- const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29502
+ const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29410
29503
  function MapUpdater({ centerLat, centerLng, zoom }) {
29411
29504
  const map = useMap();
29412
- const prevRef = useRef68({ centerLat, centerLng, zoom });
29413
- useEffect73(() => {
29505
+ const prevRef = useRef74({ centerLat, centerLng, zoom });
29506
+ useEffect79(() => {
29414
29507
  const prev = prevRef.current;
29415
29508
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
29416
29509
  map.setView([centerLat, centerLng], zoom);
@@ -29421,7 +29514,7 @@ var init_MapView = __esm({
29421
29514
  }
29422
29515
  function MapClickHandler({ onMapClick }) {
29423
29516
  const map = useMap();
29424
- useEffect73(() => {
29517
+ useEffect79(() => {
29425
29518
  if (!onMapClick) return;
29426
29519
  const handler = (e) => {
29427
29520
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -29448,9 +29541,9 @@ var init_MapView = __esm({
29448
29541
  className,
29449
29542
  showAttribution = true
29450
29543
  }) {
29451
- const eventBus = useEventBus2();
29452
- const [clickedPosition, setClickedPosition] = useState104(null);
29453
- const handleMapClick = useCallback111((lat, lng) => {
29544
+ const eventBus = useEventBus3();
29545
+ const [clickedPosition, setClickedPosition] = useState109(null);
29546
+ const handleMapClick = useCallback118((lat, lng) => {
29454
29547
  if (showClickedPin) {
29455
29548
  setClickedPosition({ lat, lng });
29456
29549
  }
@@ -29459,7 +29552,7 @@ var init_MapView = __esm({
29459
29552
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29460
29553
  }
29461
29554
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29462
- const handleMarkerClick = useCallback111((marker) => {
29555
+ const handleMarkerClick = useCallback118((marker) => {
29463
29556
  onMarkerClick?.(marker);
29464
29557
  if (markerClickEvent) {
29465
29558
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -36586,10 +36679,10 @@ var init_Section = __esm({
36586
36679
  children,
36587
36680
  headerClassName,
36588
36681
  contentClassName,
36589
- as: Component = "section"
36682
+ as: Component2 = "section"
36590
36683
  }) => {
36591
36684
  const hasHeader = title || description || action;
36592
- const Comp = Component;
36685
+ const Comp = Component2;
36593
36686
  return /* @__PURE__ */ jsxs(
36594
36687
  Comp,
36595
36688
  {
@@ -41154,6 +41247,1568 @@ var init_GameAudioToggle = __esm({
41154
41247
  GameAudioToggle.displayName = "GameAudioToggle";
41155
41248
  }
41156
41249
  });
41250
+ function detectAssetRoot(modelUrl) {
41251
+ const idx = modelUrl.indexOf("/3d/");
41252
+ if (idx !== -1) {
41253
+ return modelUrl.substring(0, idx + 4);
41254
+ }
41255
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41256
+ }
41257
+ function createGLTFLoaderForUrl(url) {
41258
+ const loader = new GLTFLoader();
41259
+ loader.setResourcePath(detectAssetRoot(url));
41260
+ return loader;
41261
+ }
41262
+ var AssetLoader;
41263
+ var init_AssetLoader = __esm({
41264
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
41265
+ AssetLoader = class {
41266
+ constructor() {
41267
+ __publicField(this, "objLoader");
41268
+ __publicField(this, "textureLoader");
41269
+ __publicField(this, "modelCache");
41270
+ __publicField(this, "textureCache");
41271
+ __publicField(this, "loadingPromises");
41272
+ this.objLoader = new OBJLoader();
41273
+ this.textureLoader = new THREE.TextureLoader();
41274
+ this.modelCache = /* @__PURE__ */ new Map();
41275
+ this.textureCache = /* @__PURE__ */ new Map();
41276
+ this.loadingPromises = /* @__PURE__ */ new Map();
41277
+ }
41278
+ /**
41279
+ * Load a GLB/GLTF model
41280
+ * @param url - URL to the .glb or .gltf file
41281
+ * @returns Promise with loaded model scene and animations
41282
+ */
41283
+ async loadModel(url) {
41284
+ if (this.modelCache.has(url)) {
41285
+ return this.modelCache.get(url);
41286
+ }
41287
+ if (this.loadingPromises.has(url)) {
41288
+ return this.loadingPromises.get(url);
41289
+ }
41290
+ const loader = createGLTFLoaderForUrl(url);
41291
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
41292
+ const result = {
41293
+ scene: gltf.scene,
41294
+ animations: gltf.animations || []
41295
+ };
41296
+ this.modelCache.set(url, result);
41297
+ this.loadingPromises.delete(url);
41298
+ return result;
41299
+ }).catch((error) => {
41300
+ this.loadingPromises.delete(url);
41301
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
41302
+ });
41303
+ this.loadingPromises.set(url, loadPromise);
41304
+ return loadPromise;
41305
+ }
41306
+ /**
41307
+ * Load an OBJ model (fallback for non-GLB assets)
41308
+ * @param url - URL to the .obj file
41309
+ * @returns Promise with loaded object group
41310
+ */
41311
+ async loadOBJ(url) {
41312
+ if (this.modelCache.has(url)) {
41313
+ return this.modelCache.get(url).scene;
41314
+ }
41315
+ if (this.loadingPromises.has(url)) {
41316
+ const result = await this.loadingPromises.get(url);
41317
+ return result.scene;
41318
+ }
41319
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
41320
+ const result = {
41321
+ scene: group,
41322
+ animations: []
41323
+ };
41324
+ this.modelCache.set(url, result);
41325
+ this.loadingPromises.delete(url);
41326
+ return result;
41327
+ }).catch((error) => {
41328
+ this.loadingPromises.delete(url);
41329
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
41330
+ });
41331
+ this.loadingPromises.set(url, loadPromise);
41332
+ return (await loadPromise).scene;
41333
+ }
41334
+ /**
41335
+ * Load a texture
41336
+ * @param url - URL to the texture image
41337
+ * @returns Promise with loaded texture
41338
+ */
41339
+ async loadTexture(url) {
41340
+ if (this.textureCache.has(url)) {
41341
+ return this.textureCache.get(url);
41342
+ }
41343
+ if (this.loadingPromises.has(`texture:${url}`)) {
41344
+ return this.loadingPromises.get(`texture:${url}`);
41345
+ }
41346
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
41347
+ texture.colorSpace = THREE.SRGBColorSpace;
41348
+ this.textureCache.set(url, texture);
41349
+ this.loadingPromises.delete(`texture:${url}`);
41350
+ return texture;
41351
+ }).catch((error) => {
41352
+ this.loadingPromises.delete(`texture:${url}`);
41353
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
41354
+ });
41355
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
41356
+ return loadPromise;
41357
+ }
41358
+ /**
41359
+ * Preload multiple assets
41360
+ * @param urls - Array of asset URLs to preload
41361
+ * @returns Promise that resolves when all assets are loaded
41362
+ */
41363
+ async preload(urls) {
41364
+ const promises = urls.map((url) => {
41365
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41366
+ return this.loadModel(url).catch(() => null);
41367
+ } else if (url.endsWith(".obj")) {
41368
+ return this.loadOBJ(url).catch(() => null);
41369
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41370
+ return this.loadTexture(url).catch(() => null);
41371
+ }
41372
+ return Promise.resolve(null);
41373
+ });
41374
+ await Promise.all(promises);
41375
+ }
41376
+ /**
41377
+ * Check if a model is cached
41378
+ * @param url - Model URL
41379
+ */
41380
+ hasModel(url) {
41381
+ return this.modelCache.has(url);
41382
+ }
41383
+ /**
41384
+ * Check if a texture is cached
41385
+ * @param url - Texture URL
41386
+ */
41387
+ hasTexture(url) {
41388
+ return this.textureCache.has(url);
41389
+ }
41390
+ /**
41391
+ * Get cached model (throws if not cached)
41392
+ * @param url - Model URL
41393
+ */
41394
+ getModel(url) {
41395
+ const model = this.modelCache.get(url);
41396
+ if (!model) {
41397
+ throw new Error(`Model ${url} not in cache`);
41398
+ }
41399
+ return model;
41400
+ }
41401
+ /**
41402
+ * Get cached texture (throws if not cached)
41403
+ * @param url - Texture URL
41404
+ */
41405
+ getTexture(url) {
41406
+ const texture = this.textureCache.get(url);
41407
+ if (!texture) {
41408
+ throw new Error(`Texture ${url} not in cache`);
41409
+ }
41410
+ return texture;
41411
+ }
41412
+ /**
41413
+ * Clear all caches
41414
+ */
41415
+ clearCache() {
41416
+ this.textureCache.forEach((texture) => {
41417
+ texture.dispose();
41418
+ });
41419
+ this.modelCache.forEach((model) => {
41420
+ model.scene.traverse((child) => {
41421
+ if (child instanceof THREE.Mesh) {
41422
+ child.geometry.dispose();
41423
+ if (Array.isArray(child.material)) {
41424
+ child.material.forEach((m) => m.dispose());
41425
+ } else {
41426
+ child.material.dispose();
41427
+ }
41428
+ }
41429
+ });
41430
+ });
41431
+ this.modelCache.clear();
41432
+ this.textureCache.clear();
41433
+ this.loadingPromises.clear();
41434
+ }
41435
+ /**
41436
+ * Get cache statistics
41437
+ */
41438
+ getStats() {
41439
+ return {
41440
+ models: this.modelCache.size,
41441
+ textures: this.textureCache.size,
41442
+ loading: this.loadingPromises.size
41443
+ };
41444
+ }
41445
+ };
41446
+ new AssetLoader();
41447
+ }
41448
+ });
41449
+ function useAssetLoader(options = {}) {
41450
+ const { preloadUrls = [], loader: customLoader } = options;
41451
+ const loaderRef = useRef(customLoader || new AssetLoader());
41452
+ const [state, setState] = useState({
41453
+ isLoading: false,
41454
+ progress: 0,
41455
+ loaded: 0,
41456
+ total: 0,
41457
+ errors: []
41458
+ });
41459
+ useEffect(() => {
41460
+ if (preloadUrls.length > 0) {
41461
+ preload(preloadUrls);
41462
+ }
41463
+ }, []);
41464
+ const updateProgress = useCallback((loaded, total) => {
41465
+ setState((prev) => ({
41466
+ ...prev,
41467
+ loaded,
41468
+ total,
41469
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
41470
+ }));
41471
+ }, []);
41472
+ const loadModel = useCallback(
41473
+ async (url) => {
41474
+ setState((prev) => ({ ...prev, isLoading: true }));
41475
+ try {
41476
+ const model = await loaderRef.current.loadModel(url);
41477
+ setState((prev) => ({
41478
+ ...prev,
41479
+ isLoading: false,
41480
+ loaded: prev.loaded + 1
41481
+ }));
41482
+ return model;
41483
+ } catch (error) {
41484
+ const errorMsg = error instanceof Error ? error.message : String(error);
41485
+ setState((prev) => ({
41486
+ ...prev,
41487
+ isLoading: false,
41488
+ errors: [...prev.errors, errorMsg]
41489
+ }));
41490
+ throw error;
41491
+ }
41492
+ },
41493
+ []
41494
+ );
41495
+ const loadOBJ = useCallback(
41496
+ async (url) => {
41497
+ setState((prev) => ({ ...prev, isLoading: true }));
41498
+ try {
41499
+ const model = await loaderRef.current.loadOBJ(url);
41500
+ setState((prev) => ({
41501
+ ...prev,
41502
+ isLoading: false,
41503
+ loaded: prev.loaded + 1
41504
+ }));
41505
+ return model;
41506
+ } catch (error) {
41507
+ const errorMsg = error instanceof Error ? error.message : String(error);
41508
+ setState((prev) => ({
41509
+ ...prev,
41510
+ isLoading: false,
41511
+ errors: [...prev.errors, errorMsg]
41512
+ }));
41513
+ throw error;
41514
+ }
41515
+ },
41516
+ []
41517
+ );
41518
+ const loadTexture = useCallback(
41519
+ async (url) => {
41520
+ setState((prev) => ({ ...prev, isLoading: true }));
41521
+ try {
41522
+ const texture = await loaderRef.current.loadTexture(url);
41523
+ setState((prev) => ({
41524
+ ...prev,
41525
+ isLoading: false,
41526
+ loaded: prev.loaded + 1
41527
+ }));
41528
+ return texture;
41529
+ } catch (error) {
41530
+ const errorMsg = error instanceof Error ? error.message : String(error);
41531
+ setState((prev) => ({
41532
+ ...prev,
41533
+ isLoading: false,
41534
+ errors: [...prev.errors, errorMsg]
41535
+ }));
41536
+ throw error;
41537
+ }
41538
+ },
41539
+ []
41540
+ );
41541
+ const preload = useCallback(
41542
+ async (urls) => {
41543
+ setState((prev) => ({
41544
+ ...prev,
41545
+ isLoading: true,
41546
+ total: urls.length,
41547
+ loaded: 0,
41548
+ errors: []
41549
+ }));
41550
+ let completed = 0;
41551
+ const errors = [];
41552
+ await Promise.all(
41553
+ urls.map(async (url) => {
41554
+ try {
41555
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41556
+ await loaderRef.current.loadModel(url);
41557
+ } else if (url.endsWith(".obj")) {
41558
+ await loaderRef.current.loadOBJ(url);
41559
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41560
+ await loaderRef.current.loadTexture(url);
41561
+ }
41562
+ completed++;
41563
+ updateProgress(completed, urls.length);
41564
+ } catch (error) {
41565
+ const errorMsg = error instanceof Error ? error.message : String(error);
41566
+ errors.push(`${url}: ${errorMsg}`);
41567
+ completed++;
41568
+ updateProgress(completed, urls.length);
41569
+ }
41570
+ })
41571
+ );
41572
+ setState((prev) => ({
41573
+ ...prev,
41574
+ isLoading: false,
41575
+ errors
41576
+ }));
41577
+ },
41578
+ [updateProgress]
41579
+ );
41580
+ const hasModel = useCallback((url) => {
41581
+ return loaderRef.current.hasModel(url);
41582
+ }, []);
41583
+ const hasTexture = useCallback((url) => {
41584
+ return loaderRef.current.hasTexture(url);
41585
+ }, []);
41586
+ const getModel = useCallback((url) => {
41587
+ try {
41588
+ return loaderRef.current.getModel(url);
41589
+ } catch {
41590
+ return void 0;
41591
+ }
41592
+ }, []);
41593
+ const getTexture = useCallback((url) => {
41594
+ try {
41595
+ return loaderRef.current.getTexture(url);
41596
+ } catch {
41597
+ return void 0;
41598
+ }
41599
+ }, []);
41600
+ const clearCache = useCallback(() => {
41601
+ loaderRef.current.clearCache();
41602
+ setState({
41603
+ isLoading: false,
41604
+ progress: 0,
41605
+ loaded: 0,
41606
+ total: 0,
41607
+ errors: []
41608
+ });
41609
+ }, []);
41610
+ return {
41611
+ ...state,
41612
+ loadModel,
41613
+ loadOBJ,
41614
+ loadTexture,
41615
+ preload,
41616
+ hasModel,
41617
+ hasTexture,
41618
+ getModel,
41619
+ getTexture,
41620
+ clearCache
41621
+ };
41622
+ }
41623
+ var init_useAssetLoader = __esm({
41624
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
41625
+ "use client";
41626
+ init_AssetLoader();
41627
+ }
41628
+ });
41629
+ function useGameCanvas3DEvents(options) {
41630
+ const {
41631
+ tileClickEvent,
41632
+ unitClickEvent,
41633
+ featureClickEvent,
41634
+ canvasClickEvent,
41635
+ tileHoverEvent,
41636
+ tileLeaveEvent,
41637
+ unitAnimationEvent,
41638
+ cameraChangeEvent,
41639
+ onTileClick,
41640
+ onUnitClick,
41641
+ onFeatureClick,
41642
+ onCanvasClick,
41643
+ onTileHover,
41644
+ onUnitAnimation
41645
+ } = options;
41646
+ const emit = useEmitEvent();
41647
+ const optionsRef = useRef(options);
41648
+ optionsRef.current = options;
41649
+ const handleTileClick = useCallback(
41650
+ (tile, event) => {
41651
+ if (tileClickEvent) {
41652
+ emit(tileClickEvent, {
41653
+ tileId: tile.id,
41654
+ x: tile.x,
41655
+ z: tile.z ?? tile.y ?? 0,
41656
+ type: tile.type,
41657
+ terrain: tile.terrain,
41658
+ elevation: tile.elevation
41659
+ });
41660
+ }
41661
+ optionsRef.current.onTileClick?.(tile, event);
41662
+ },
41663
+ [tileClickEvent, emit]
41664
+ );
41665
+ const handleUnitClick = useCallback(
41666
+ (unit, event) => {
41667
+ if (unitClickEvent) {
41668
+ emit(unitClickEvent, {
41669
+ unitId: unit.id,
41670
+ x: unit.x,
41671
+ z: unit.z ?? unit.y ?? 0,
41672
+ unitType: unit.unitType,
41673
+ name: unit.name,
41674
+ team: unit.team,
41675
+ faction: unit.faction,
41676
+ health: unit.health,
41677
+ maxHealth: unit.maxHealth
41678
+ });
41679
+ }
41680
+ optionsRef.current.onUnitClick?.(unit, event);
41681
+ },
41682
+ [unitClickEvent, emit]
41683
+ );
41684
+ const handleFeatureClick = useCallback(
41685
+ (feature, event) => {
41686
+ if (featureClickEvent) {
41687
+ emit(featureClickEvent, {
41688
+ featureId: feature.id,
41689
+ x: feature.x,
41690
+ z: feature.z ?? feature.y ?? 0,
41691
+ type: feature.type,
41692
+ elevation: feature.elevation
41693
+ });
41694
+ }
41695
+ optionsRef.current.onFeatureClick?.(feature, event);
41696
+ },
41697
+ [featureClickEvent, emit]
41698
+ );
41699
+ const handleCanvasClick = useCallback(
41700
+ (event) => {
41701
+ if (canvasClickEvent) {
41702
+ emit(canvasClickEvent, {
41703
+ clientX: event.clientX,
41704
+ clientY: event.clientY,
41705
+ button: event.button
41706
+ });
41707
+ }
41708
+ optionsRef.current.onCanvasClick?.(event);
41709
+ },
41710
+ [canvasClickEvent, emit]
41711
+ );
41712
+ const handleTileHover = useCallback(
41713
+ (tile, event) => {
41714
+ if (tile) {
41715
+ if (tileHoverEvent) {
41716
+ emit(tileHoverEvent, {
41717
+ tileId: tile.id,
41718
+ x: tile.x,
41719
+ z: tile.z ?? tile.y ?? 0,
41720
+ type: tile.type
41721
+ });
41722
+ }
41723
+ } else {
41724
+ if (tileLeaveEvent) {
41725
+ emit(tileLeaveEvent, {});
41726
+ }
41727
+ }
41728
+ optionsRef.current.onTileHover?.(tile, event);
41729
+ },
41730
+ [tileHoverEvent, tileLeaveEvent, emit]
41731
+ );
41732
+ const handleUnitAnimation = useCallback(
41733
+ (unitId, state) => {
41734
+ if (unitAnimationEvent) {
41735
+ emit(unitAnimationEvent, {
41736
+ unitId,
41737
+ state,
41738
+ timestamp: Date.now()
41739
+ });
41740
+ }
41741
+ optionsRef.current.onUnitAnimation?.(unitId, state);
41742
+ },
41743
+ [unitAnimationEvent, emit]
41744
+ );
41745
+ const handleCameraChange = useCallback(
41746
+ (position) => {
41747
+ if (cameraChangeEvent) {
41748
+ emit(cameraChangeEvent, {
41749
+ position,
41750
+ timestamp: Date.now()
41751
+ });
41752
+ }
41753
+ },
41754
+ [cameraChangeEvent, emit]
41755
+ );
41756
+ return {
41757
+ handleTileClick,
41758
+ handleUnitClick,
41759
+ handleFeatureClick,
41760
+ handleCanvasClick,
41761
+ handleTileHover,
41762
+ handleUnitAnimation,
41763
+ handleCameraChange
41764
+ };
41765
+ }
41766
+ var init_useGameCanvas3DEvents = __esm({
41767
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
41768
+ "use client";
41769
+ init_useEventBus();
41770
+ }
41771
+ });
41772
+
41773
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
41774
+ var init_Canvas3DLoadingState = __esm({
41775
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
41776
+ }
41777
+ });
41778
+ function Canvas3DLoadingState({
41779
+ progress = 0,
41780
+ loaded = 0,
41781
+ total = 0,
41782
+ message = "Loading 3D Scene...",
41783
+ details,
41784
+ showSpinner = true,
41785
+ className
41786
+ }) {
41787
+ const clampedProgress = Math.max(0, Math.min(100, progress));
41788
+ const hasProgress = total > 0;
41789
+ return /* @__PURE__ */ jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
41790
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__content", children: [
41791
+ showSpinner && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__spinner", children: [
41792
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring" }),
41793
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring spinner__ring--secondary" })
41794
+ ] }),
41795
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__message", children: message }),
41796
+ details && /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__details", children: details }),
41797
+ hasProgress && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__progress", children: [
41798
+ /* @__PURE__ */ jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsx(
41799
+ "div",
41800
+ {
41801
+ className: "progress__fill",
41802
+ style: { width: `${clampedProgress}%` }
41803
+ }
41804
+ ) }),
41805
+ /* @__PURE__ */ jsxs("div", { className: "progress__text", children: [
41806
+ /* @__PURE__ */ jsxs("span", { className: "progress__percentage", children: [
41807
+ clampedProgress,
41808
+ "%"
41809
+ ] }),
41810
+ /* @__PURE__ */ jsxs("span", { className: "progress__count", children: [
41811
+ "(",
41812
+ loaded,
41813
+ "/",
41814
+ total,
41815
+ ")"
41816
+ ] })
41817
+ ] })
41818
+ ] })
41819
+ ] }),
41820
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsx("div", { className: "bg__grid" }) })
41821
+ ] });
41822
+ }
41823
+ var init_Canvas3DLoadingState2 = __esm({
41824
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
41825
+ init_Canvas3DLoadingState();
41826
+ }
41827
+ });
41828
+
41829
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
41830
+ var init_Canvas3DErrorBoundary = __esm({
41831
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
41832
+ }
41833
+ });
41834
+ var log11, Canvas3DErrorBoundary;
41835
+ var init_Canvas3DErrorBoundary2 = __esm({
41836
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
41837
+ init_Canvas3DErrorBoundary();
41838
+ log11 = createLogger("almadar:ui:game:canvas3d:error-boundary");
41839
+ Canvas3DErrorBoundary = class extends Component {
41840
+ constructor(props) {
41841
+ super(props);
41842
+ __publicField(this, "handleReset", () => {
41843
+ this.setState({
41844
+ hasError: false,
41845
+ error: null,
41846
+ errorInfo: null
41847
+ });
41848
+ this.props.onReset?.();
41849
+ });
41850
+ this.state = {
41851
+ hasError: false,
41852
+ error: null,
41853
+ errorInfo: null
41854
+ };
41855
+ }
41856
+ static getDerivedStateFromError(error) {
41857
+ return {
41858
+ hasError: true,
41859
+ error,
41860
+ errorInfo: null
41861
+ };
41862
+ }
41863
+ componentDidCatch(error, errorInfo) {
41864
+ this.setState({ errorInfo });
41865
+ this.props.onError?.(error, errorInfo);
41866
+ log11.error("Error caught", { error });
41867
+ log11.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
41868
+ }
41869
+ render() {
41870
+ if (this.state.hasError) {
41871
+ if (this.props.fallback) {
41872
+ return this.props.fallback;
41873
+ }
41874
+ return /* @__PURE__ */ jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__content", children: [
41875
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
41876
+ /* @__PURE__ */ jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
41877
+ /* @__PURE__ */ jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
41878
+ this.state.error && /* @__PURE__ */ jsxs("details", { className: "canvas-3d-error__details", children: [
41879
+ /* @__PURE__ */ jsx("summary", { children: "Error Details" }),
41880
+ /* @__PURE__ */ jsxs("pre", { className: "error__stack", children: [
41881
+ this.state.error.message,
41882
+ "\n",
41883
+ this.state.error.stack
41884
+ ] }),
41885
+ this.state.errorInfo && /* @__PURE__ */ jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
41886
+ ] }),
41887
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__actions", children: [
41888
+ /* @__PURE__ */ jsx(
41889
+ "button",
41890
+ {
41891
+ className: "error__button error__button--primary",
41892
+ onClick: this.handleReset,
41893
+ children: "Try Again"
41894
+ }
41895
+ ),
41896
+ /* @__PURE__ */ jsx(
41897
+ "button",
41898
+ {
41899
+ className: "error__button error__button--secondary",
41900
+ onClick: () => window.location.reload(),
41901
+ children: "Reload Page"
41902
+ }
41903
+ )
41904
+ ] })
41905
+ ] }) });
41906
+ }
41907
+ return this.props.children;
41908
+ }
41909
+ };
41910
+ }
41911
+ });
41912
+ function detectAssetRoot2(modelUrl) {
41913
+ const idx = modelUrl.indexOf("/3d/");
41914
+ if (idx !== -1) {
41915
+ return modelUrl.substring(0, idx + 4);
41916
+ }
41917
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41918
+ }
41919
+ function useGLTFModel(url, resourceBasePath) {
41920
+ const [state, setState] = useState({
41921
+ model: null,
41922
+ isLoading: false,
41923
+ error: null
41924
+ });
41925
+ useEffect(() => {
41926
+ if (!url) {
41927
+ setState({ model: null, isLoading: false, error: null });
41928
+ return;
41929
+ }
41930
+ log12.debug("Loading", { url });
41931
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
41932
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
41933
+ const loader = new GLTFLoader$1();
41934
+ loader.setResourcePath(assetRoot);
41935
+ loader.load(
41936
+ url,
41937
+ (gltf) => {
41938
+ log12.debug("Loaded", { url });
41939
+ setState({
41940
+ model: gltf.scene,
41941
+ isLoading: false,
41942
+ error: null
41943
+ });
41944
+ },
41945
+ void 0,
41946
+ (err) => {
41947
+ log12.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
41948
+ setState({
41949
+ model: null,
41950
+ isLoading: false,
41951
+ error: err instanceof Error ? err : new Error(String(err))
41952
+ });
41953
+ }
41954
+ );
41955
+ }, [url, resourceBasePath]);
41956
+ return state;
41957
+ }
41958
+ function ModelLoader({
41959
+ url,
41960
+ position = [0, 0, 0],
41961
+ scale = 1,
41962
+ rotation = [0, 0, 0],
41963
+ isSelected = false,
41964
+ isHovered = false,
41965
+ onClick,
41966
+ onHover,
41967
+ fallbackGeometry = "box",
41968
+ castShadow = true,
41969
+ receiveShadow = true,
41970
+ resourceBasePath
41971
+ }) {
41972
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
41973
+ const model = useMemo(() => {
41974
+ if (!loadedModel) return null;
41975
+ const cloned = loadedModel.clone();
41976
+ cloned.traverse((child) => {
41977
+ if (child instanceof THREE.Mesh) {
41978
+ child.castShadow = castShadow;
41979
+ child.receiveShadow = receiveShadow;
41980
+ }
41981
+ });
41982
+ return cloned;
41983
+ }, [loadedModel, castShadow, receiveShadow]);
41984
+ const scaleArray = useMemo(() => {
41985
+ if (typeof scale === "number") {
41986
+ return [scale, scale, scale];
41987
+ }
41988
+ return scale;
41989
+ }, [scale]);
41990
+ const rotationRad = useMemo(() => {
41991
+ return [
41992
+ rotation[0] * Math.PI / 180,
41993
+ rotation[1] * Math.PI / 180,
41994
+ rotation[2] * Math.PI / 180
41995
+ ];
41996
+ }, [rotation]);
41997
+ if (isLoading) {
41998
+ return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
41999
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
42000
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
42001
+ ] }) });
42002
+ }
42003
+ if (error || !model) {
42004
+ if (fallbackGeometry === "none") {
42005
+ return /* @__PURE__ */ jsx("group", { position });
42006
+ }
42007
+ const fallbackProps = {
42008
+ onClick,
42009
+ onPointerOver: () => onHover?.(true),
42010
+ onPointerOut: () => onHover?.(false)
42011
+ };
42012
+ return /* @__PURE__ */ jsxs("group", { position, children: [
42013
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42014
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
42015
+ /* @__PURE__ */ jsx(
42016
+ "meshBasicMaterial",
42017
+ {
42018
+ color: isSelected ? 16755200 : 16777215,
42019
+ transparent: true,
42020
+ opacity: 0.5
42021
+ }
42022
+ )
42023
+ ] }),
42024
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42025
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
42026
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42027
+ ] }),
42028
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42029
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.4, 16, 16] }),
42030
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42031
+ ] }),
42032
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42033
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
42034
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42035
+ ] })
42036
+ ] });
42037
+ }
42038
+ return /* @__PURE__ */ jsxs(
42039
+ "group",
42040
+ {
42041
+ position,
42042
+ rotation: rotationRad,
42043
+ onClick,
42044
+ onPointerOver: () => onHover?.(true),
42045
+ onPointerOut: () => onHover?.(false),
42046
+ children: [
42047
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42048
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
42049
+ /* @__PURE__ */ jsx(
42050
+ "meshBasicMaterial",
42051
+ {
42052
+ color: isSelected ? 16755200 : 16777215,
42053
+ transparent: true,
42054
+ opacity: 0.5
42055
+ }
42056
+ )
42057
+ ] }),
42058
+ /* @__PURE__ */ jsx("primitive", { object: model, scale: scaleArray })
42059
+ ]
42060
+ }
42061
+ );
42062
+ }
42063
+ var log12;
42064
+ var init_ModelLoader = __esm({
42065
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
42066
+ "use client";
42067
+ log12 = createLogger("almadar:ui:game:model-loader");
42068
+ }
42069
+ });
42070
+
42071
+ // components/game/molecules/GameCanvas3D.css
42072
+ var init_GameCanvas3D = __esm({
42073
+ "components/game/molecules/GameCanvas3D.css"() {
42074
+ }
42075
+ });
42076
+ function CameraController({
42077
+ onCameraChange
42078
+ }) {
42079
+ const { camera } = useThree();
42080
+ useEffect(() => {
42081
+ if (onCameraChange) {
42082
+ onCameraChange({
42083
+ x: camera.position.x,
42084
+ y: camera.position.y,
42085
+ z: camera.position.z
42086
+ });
42087
+ }
42088
+ }, [camera.position, onCameraChange]);
42089
+ return null;
42090
+ }
42091
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
42092
+ var init_GameCanvas3D2 = __esm({
42093
+ "components/game/molecules/GameCanvas3D.tsx"() {
42094
+ "use client";
42095
+ init_AssetLoader();
42096
+ init_useAssetLoader();
42097
+ init_useGameCanvas3DEvents();
42098
+ init_Canvas3DLoadingState2();
42099
+ init_Canvas3DErrorBoundary2();
42100
+ init_ModelLoader();
42101
+ init_cn();
42102
+ init_GameCanvas3D();
42103
+ DEFAULT_GRID_CONFIG = {
42104
+ cellSize: 1,
42105
+ offsetX: 0,
42106
+ offsetZ: 0
42107
+ };
42108
+ GameCanvas3D = forwardRef(
42109
+ ({
42110
+ tiles = [],
42111
+ units = [],
42112
+ features = [],
42113
+ events: events2 = [],
42114
+ orientation = "standard",
42115
+ cameraMode = "isometric",
42116
+ showGrid = true,
42117
+ showCoordinates = false,
42118
+ showTileInfo = false,
42119
+ overlay = "default",
42120
+ shadows = true,
42121
+ backgroundColor = "#1a1a2e",
42122
+ onTileClick,
42123
+ onUnitClick,
42124
+ onFeatureClick,
42125
+ onCanvasClick,
42126
+ onTileHover,
42127
+ onUnitAnimation,
42128
+ assetLoader: customAssetLoader,
42129
+ tileRenderer: CustomTileRenderer,
42130
+ unitRenderer: CustomUnitRenderer,
42131
+ featureRenderer: CustomFeatureRenderer,
42132
+ className,
42133
+ isLoading: externalLoading,
42134
+ error: externalError,
42135
+ entity,
42136
+ preloadAssets = [],
42137
+ tileClickEvent,
42138
+ unitClickEvent,
42139
+ featureClickEvent,
42140
+ canvasClickEvent,
42141
+ tileHoverEvent,
42142
+ tileLeaveEvent,
42143
+ unitAnimationEvent,
42144
+ cameraChangeEvent,
42145
+ loadingMessage = "Loading 3D Scene...",
42146
+ useInstancing = true,
42147
+ validMoves = [],
42148
+ attackTargets = [],
42149
+ selectedTileIds = [],
42150
+ selectedUnitId = null,
42151
+ children
42152
+ }, ref) => {
42153
+ const containerRef = useRef(null);
42154
+ const controlsRef = useRef(null);
42155
+ const [hoveredTile, setHoveredTile] = useState(null);
42156
+ const [internalError, setInternalError] = useState(null);
42157
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
42158
+ preloadUrls: preloadAssets,
42159
+ loader: customAssetLoader
42160
+ });
42161
+ const eventHandlers = useGameCanvas3DEvents({
42162
+ tileClickEvent,
42163
+ unitClickEvent,
42164
+ featureClickEvent,
42165
+ canvasClickEvent,
42166
+ tileHoverEvent,
42167
+ tileLeaveEvent,
42168
+ unitAnimationEvent,
42169
+ cameraChangeEvent,
42170
+ onTileClick,
42171
+ onUnitClick,
42172
+ onFeatureClick,
42173
+ onCanvasClick,
42174
+ onTileHover,
42175
+ onUnitAnimation
42176
+ });
42177
+ const gridBounds = useMemo(() => {
42178
+ if (tiles.length === 0) {
42179
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
42180
+ }
42181
+ const xs = tiles.map((t) => t.x);
42182
+ const zs = tiles.map((t) => t.z || t.y || 0);
42183
+ return {
42184
+ minX: Math.min(...xs),
42185
+ maxX: Math.max(...xs),
42186
+ minZ: Math.min(...zs),
42187
+ maxZ: Math.max(...zs)
42188
+ };
42189
+ }, [tiles]);
42190
+ const cameraTarget = useMemo(() => {
42191
+ return [
42192
+ (gridBounds.minX + gridBounds.maxX) / 2,
42193
+ 0,
42194
+ (gridBounds.minZ + gridBounds.maxZ) / 2
42195
+ ];
42196
+ }, [gridBounds]);
42197
+ const gridConfig = useMemo(
42198
+ () => ({
42199
+ ...DEFAULT_GRID_CONFIG,
42200
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
42201
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
42202
+ }),
42203
+ [gridBounds]
42204
+ );
42205
+ const gridToWorld = useCallback(
42206
+ (x, z, y = 0) => {
42207
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
42208
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
42209
+ return [worldX, y * gridConfig.cellSize, worldZ];
42210
+ },
42211
+ [gridBounds, gridConfig]
42212
+ );
42213
+ useImperativeHandle(ref, () => ({
42214
+ getCameraPosition: () => {
42215
+ if (controlsRef.current) {
42216
+ const pos = controlsRef.current.object.position;
42217
+ return new THREE.Vector3(pos.x, pos.y, pos.z);
42218
+ }
42219
+ return null;
42220
+ },
42221
+ setCameraPosition: (x, y, z) => {
42222
+ if (controlsRef.current) {
42223
+ controlsRef.current.object.position.set(x, y, z);
42224
+ controlsRef.current.update();
42225
+ }
42226
+ },
42227
+ lookAt: (x, y, z) => {
42228
+ if (controlsRef.current) {
42229
+ controlsRef.current.target.set(x, y, z);
42230
+ controlsRef.current.update();
42231
+ }
42232
+ },
42233
+ resetCamera: () => {
42234
+ if (controlsRef.current) {
42235
+ controlsRef.current.reset();
42236
+ }
42237
+ },
42238
+ screenshot: () => {
42239
+ const canvas = containerRef.current?.querySelector("canvas");
42240
+ if (canvas) {
42241
+ return canvas.toDataURL("image/png");
42242
+ }
42243
+ return null;
42244
+ },
42245
+ export: () => ({
42246
+ tiles,
42247
+ units,
42248
+ features
42249
+ })
42250
+ }));
42251
+ const handleTileClick = useCallback(
42252
+ (tile, event) => {
42253
+ eventHandlers.handleTileClick(tile, event);
42254
+ },
42255
+ [eventHandlers]
42256
+ );
42257
+ const handleUnitClick = useCallback(
42258
+ (unit, event) => {
42259
+ eventHandlers.handleUnitClick(unit, event);
42260
+ },
42261
+ [eventHandlers]
42262
+ );
42263
+ const handleFeatureClick = useCallback(
42264
+ (feature, event) => {
42265
+ if (event) {
42266
+ eventHandlers.handleFeatureClick(feature, event);
42267
+ }
42268
+ },
42269
+ [eventHandlers]
42270
+ );
42271
+ const handleTileHover = useCallback(
42272
+ (tile, event) => {
42273
+ setHoveredTile(tile);
42274
+ if (event) {
42275
+ eventHandlers.handleTileHover(tile, event);
42276
+ }
42277
+ },
42278
+ [eventHandlers]
42279
+ );
42280
+ const cameraConfig = useMemo(() => {
42281
+ const size = Math.max(
42282
+ gridBounds.maxX - gridBounds.minX,
42283
+ gridBounds.maxZ - gridBounds.minZ
42284
+ );
42285
+ const distance = size * 1.5;
42286
+ switch (cameraMode) {
42287
+ case "isometric":
42288
+ return {
42289
+ position: [distance, distance * 0.8, distance],
42290
+ fov: 45
42291
+ };
42292
+ case "top-down":
42293
+ return {
42294
+ position: [0, distance * 2, 0],
42295
+ fov: 45
42296
+ };
42297
+ case "perspective":
42298
+ default:
42299
+ return {
42300
+ position: [distance, distance, distance],
42301
+ fov: 45
42302
+ };
42303
+ }
42304
+ }, [cameraMode, gridBounds]);
42305
+ const DefaultTileRenderer = useCallback(
42306
+ ({ tile, position }) => {
42307
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
42308
+ const isHovered = hoveredTile?.id === tile.id;
42309
+ const isValidMove = validMoves.some(
42310
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
42311
+ );
42312
+ const isAttackTarget = attackTargets.some(
42313
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
42314
+ );
42315
+ let color = 8421504;
42316
+ if (tile.type === "water") color = 4491468;
42317
+ else if (tile.type === "grass") color = 4500036;
42318
+ else if (tile.type === "sand") color = 14535816;
42319
+ else if (tile.type === "rock") color = 8947848;
42320
+ else if (tile.type === "snow") color = 15658734;
42321
+ let emissive = 0;
42322
+ if (isSelected) emissive = 4473924;
42323
+ else if (isAttackTarget) emissive = 4456448;
42324
+ else if (isValidMove) emissive = 17408;
42325
+ else if (isHovered) emissive = 2236962;
42326
+ if (tile.modelUrl) {
42327
+ return /* @__PURE__ */ jsx(
42328
+ "group",
42329
+ {
42330
+ position,
42331
+ onClick: (e) => handleTileClick(tile, e),
42332
+ onPointerEnter: (e) => handleTileHover(tile, e),
42333
+ onPointerLeave: (e) => handleTileHover(null, e),
42334
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
42335
+ children: /* @__PURE__ */ jsx(
42336
+ ModelLoader,
42337
+ {
42338
+ url: tile.modelUrl,
42339
+ scale: 0.95,
42340
+ fallbackGeometry: "box",
42341
+ castShadow: true,
42342
+ receiveShadow: true
42343
+ }
42344
+ )
42345
+ }
42346
+ );
42347
+ }
42348
+ return /* @__PURE__ */ jsxs(
42349
+ "mesh",
42350
+ {
42351
+ position,
42352
+ onClick: (e) => handleTileClick(tile, e),
42353
+ onPointerEnter: (e) => handleTileHover(tile, e),
42354
+ onPointerLeave: (e) => handleTileHover(null, e),
42355
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
42356
+ children: [
42357
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
42358
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color, emissive })
42359
+ ]
42360
+ }
42361
+ );
42362
+ },
42363
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
42364
+ );
42365
+ const DefaultUnitRenderer = useCallback(
42366
+ ({ unit, position }) => {
42367
+ const isSelected = selectedUnitId === unit.id;
42368
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
42369
+ return /* @__PURE__ */ jsxs(
42370
+ "group",
42371
+ {
42372
+ position,
42373
+ onClick: (e) => handleUnitClick(unit, e),
42374
+ userData: { type: "unit", unitId: unit.id },
42375
+ children: [
42376
+ isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42377
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
42378
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
42379
+ ] }),
42380
+ unit.modelUrl ? (
42381
+ /* GLB unit model (box fallback while loading / on error) */
42382
+ /* @__PURE__ */ jsx(
42383
+ ModelLoader,
42384
+ {
42385
+ url: unit.modelUrl,
42386
+ scale: 0.5,
42387
+ fallbackGeometry: "box",
42388
+ castShadow: true
42389
+ }
42390
+ )
42391
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
42392
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.3, 0], children: [
42393
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
42394
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
42395
+ ] }),
42396
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.6, 0], children: [
42397
+ /* @__PURE__ */ jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
42398
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
42399
+ ] }),
42400
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
42401
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
42402
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
42403
+ ] })
42404
+ ] }),
42405
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxs("group", { position: [0, 1.2, 0], children: [
42406
+ /* @__PURE__ */ jsxs("mesh", { position: [-0.25, 0, 0], children: [
42407
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.05] }),
42408
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: 3355443 })
42409
+ ] }),
42410
+ /* @__PURE__ */ jsxs(
42411
+ "mesh",
42412
+ {
42413
+ position: [
42414
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
42415
+ 0,
42416
+ 0.01
42417
+ ],
42418
+ children: [
42419
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
42420
+ /* @__PURE__ */ jsx(
42421
+ "meshBasicMaterial",
42422
+ {
42423
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
42424
+ }
42425
+ )
42426
+ ]
42427
+ }
42428
+ )
42429
+ ] })
42430
+ ]
42431
+ }
42432
+ );
42433
+ },
42434
+ [selectedUnitId, handleUnitClick]
42435
+ );
42436
+ const DefaultFeatureRenderer = useCallback(
42437
+ ({
42438
+ feature,
42439
+ position
42440
+ }) => {
42441
+ if (feature.assetUrl) {
42442
+ return /* @__PURE__ */ jsx(
42443
+ ModelLoader,
42444
+ {
42445
+ url: feature.assetUrl,
42446
+ position,
42447
+ scale: 0.5,
42448
+ rotation: [0, feature.rotation ?? 0, 0],
42449
+ onClick: () => handleFeatureClick(feature, null),
42450
+ fallbackGeometry: "box"
42451
+ },
42452
+ feature.id
42453
+ );
42454
+ }
42455
+ if (feature.type === "tree") {
42456
+ return /* @__PURE__ */ jsxs(
42457
+ "group",
42458
+ {
42459
+ position,
42460
+ onClick: (e) => handleFeatureClick(feature, e),
42461
+ userData: { type: "feature", featureId: feature.id },
42462
+ children: [
42463
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.4, 0], children: [
42464
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
42465
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
42466
+ ] }),
42467
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
42468
+ /* @__PURE__ */ jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
42469
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 2263842 })
42470
+ ] })
42471
+ ]
42472
+ }
42473
+ );
42474
+ }
42475
+ if (feature.type === "rock") {
42476
+ return /* @__PURE__ */ jsxs(
42477
+ "mesh",
42478
+ {
42479
+ position: [position[0], position[1] + 0.3, position[2]],
42480
+ onClick: (e) => handleFeatureClick(feature, e),
42481
+ userData: { type: "feature", featureId: feature.id },
42482
+ children: [
42483
+ /* @__PURE__ */ jsx("dodecahedronGeometry", { args: [0.3, 0] }),
42484
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 8421504 })
42485
+ ]
42486
+ }
42487
+ );
42488
+ }
42489
+ return null;
42490
+ },
42491
+ [handleFeatureClick]
42492
+ );
42493
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
42494
+ return /* @__PURE__ */ jsx(
42495
+ Canvas3DLoadingState,
42496
+ {
42497
+ progress,
42498
+ loaded,
42499
+ total,
42500
+ message: loadingMessage,
42501
+ className
42502
+ }
42503
+ );
42504
+ }
42505
+ const displayError = externalError || internalError;
42506
+ if (displayError) {
42507
+ return /* @__PURE__ */ jsx(Canvas3DErrorBoundary, { children: /* @__PURE__ */ jsx("div", { className: "game-canvas-3d game-canvas-3d--error", children: /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__error", children: [
42508
+ "Error: ",
42509
+ displayError
42510
+ ] }) }) });
42511
+ }
42512
+ return /* @__PURE__ */ jsx(
42513
+ Canvas3DErrorBoundary,
42514
+ {
42515
+ onError: (err) => setInternalError(err.message),
42516
+ onReset: () => setInternalError(null),
42517
+ children: /* @__PURE__ */ jsxs(
42518
+ "div",
42519
+ {
42520
+ ref: containerRef,
42521
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
42522
+ "data-orientation": orientation,
42523
+ "data-camera-mode": cameraMode,
42524
+ "data-overlay": overlay,
42525
+ children: [
42526
+ /* @__PURE__ */ jsxs(
42527
+ Canvas,
42528
+ {
42529
+ shadows,
42530
+ camera: {
42531
+ position: cameraConfig.position,
42532
+ fov: cameraConfig.fov,
42533
+ near: 0.1,
42534
+ far: 1e3
42535
+ },
42536
+ style: { background: backgroundColor },
42537
+ onClick: (e) => {
42538
+ if (e.target === e.currentTarget) {
42539
+ eventHandlers.handleCanvasClick(e);
42540
+ }
42541
+ },
42542
+ children: [
42543
+ /* @__PURE__ */ jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
42544
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 0.6 }),
42545
+ /* @__PURE__ */ jsx(
42546
+ "directionalLight",
42547
+ {
42548
+ position: [10, 20, 10],
42549
+ intensity: 0.8,
42550
+ castShadow: shadows,
42551
+ "shadow-mapSize": [2048, 2048]
42552
+ }
42553
+ ),
42554
+ /* @__PURE__ */ jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
42555
+ showGrid && /* @__PURE__ */ jsx(
42556
+ Grid$1,
42557
+ {
42558
+ args: [
42559
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
42560
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
42561
+ ],
42562
+ position: [
42563
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
42564
+ 0,
42565
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
42566
+ ],
42567
+ cellSize: 1,
42568
+ cellThickness: 1,
42569
+ cellColor: "#444444",
42570
+ sectionSize: 5,
42571
+ sectionThickness: 1.5,
42572
+ sectionColor: "#666666",
42573
+ fadeDistance: 50,
42574
+ fadeStrength: 1
42575
+ }
42576
+ ),
42577
+ tiles.map((tile, index) => {
42578
+ const position = gridToWorld(
42579
+ tile.x,
42580
+ tile.z ?? tile.y ?? 0,
42581
+ tile.elevation ?? 0
42582
+ );
42583
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
42584
+ return /* @__PURE__ */ jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
42585
+ }),
42586
+ features.map((feature, index) => {
42587
+ const position = gridToWorld(
42588
+ feature.x,
42589
+ feature.z ?? feature.y ?? 0,
42590
+ (feature.elevation ?? 0) + 0.5
42591
+ );
42592
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
42593
+ return /* @__PURE__ */ jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
42594
+ }),
42595
+ units.map((unit) => {
42596
+ const position = gridToWorld(
42597
+ unit.x ?? 0,
42598
+ unit.z ?? unit.y ?? 0,
42599
+ (unit.elevation ?? 0) + 0.5
42600
+ );
42601
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
42602
+ return /* @__PURE__ */ jsx(Renderer, { unit, position }, unit.id);
42603
+ }),
42604
+ children,
42605
+ /* @__PURE__ */ jsx(
42606
+ OrbitControls,
42607
+ {
42608
+ ref: controlsRef,
42609
+ target: cameraTarget,
42610
+ enableDamping: true,
42611
+ dampingFactor: 0.05,
42612
+ minDistance: 2,
42613
+ maxDistance: 100,
42614
+ maxPolarAngle: Math.PI / 2 - 0.1
42615
+ }
42616
+ )
42617
+ ]
42618
+ }
42619
+ ),
42620
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__coordinates", children: [
42621
+ "X: ",
42622
+ hoveredTile.x,
42623
+ ", Z: ",
42624
+ hoveredTile.z ?? hoveredTile.y ?? 0
42625
+ ] }),
42626
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__tile-info", children: [
42627
+ /* @__PURE__ */ jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
42628
+ hoveredTile.terrain && /* @__PURE__ */ jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
42629
+ ] })
42630
+ ]
42631
+ }
42632
+ )
42633
+ }
42634
+ );
42635
+ }
42636
+ );
42637
+ GameCanvas3D.displayName = "GameCanvas3D";
42638
+ }
42639
+ });
42640
+ function GameBoard3D({
42641
+ entity,
42642
+ tiles = [],
42643
+ features = [],
42644
+ cameraMode = "perspective",
42645
+ backgroundColor = "#2a1a1a",
42646
+ tileClickEvent,
42647
+ unitClickEvent,
42648
+ attackEvent,
42649
+ endTurnEvent,
42650
+ cancelEvent,
42651
+ playAgainEvent,
42652
+ gameEndEvent,
42653
+ className
42654
+ }) {
42655
+ const row = boardEntity(entity);
42656
+ const eventBus = useEventBus();
42657
+ const entityUnits = row ? rows(row.units) : [];
42658
+ const units = entityUnits;
42659
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
42660
+ const phase = row ? str(row.phase) : "observation";
42661
+ const result = row ? str(row.result) : "none";
42662
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
42663
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
42664
+ const turn = row ? num(row.turn) : 0;
42665
+ const currentTeam = row ? str(row.currentTeam) : "player";
42666
+ const isGameOver = result !== "none";
42667
+ const gameEndEmittedRef = useRef(false);
42668
+ useEffect(() => {
42669
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
42670
+ if (!gameEndEmittedRef.current) {
42671
+ gameEndEmittedRef.current = true;
42672
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
42673
+ }
42674
+ } else {
42675
+ gameEndEmittedRef.current = false;
42676
+ }
42677
+ }, [result, gameEndEvent, eventBus]);
42678
+ const checkGameEnd = useCallback(() => {
42679
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
42680
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
42681
+ if (alivePlayer.length === 0 && gameEndEvent) {
42682
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
42683
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
42684
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
42685
+ }
42686
+ }, [entityUnits, gameEndEvent, eventBus]);
42687
+ const handleUnitClickCallback = useCallback((isoUnit) => {
42688
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
42689
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
42690
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
42691
+ if (!attackerRow || !targetRow) return;
42692
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
42693
+ const ap = unitPosition(attackerRow);
42694
+ const tp = unitPosition(targetRow);
42695
+ const dx = Math.abs(ap.x - tp.x);
42696
+ const dy = Math.abs(ap.y - tp.y);
42697
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
42698
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
42699
+ eventBus.emit(`UI:${attackEvent}`, {
42700
+ attackerId: str(attackerRow.id),
42701
+ targetId: str(targetRow.id),
42702
+ damage
42703
+ });
42704
+ setTimeout(checkGameEnd, 100);
42705
+ }
42706
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
42707
+ const handleEndTurn = useCallback(() => {
42708
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
42709
+ }, [endTurnEvent, eventBus]);
42710
+ const handleCancel = useCallback(() => {
42711
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
42712
+ }, [cancelEvent, eventBus]);
42713
+ const handlePlayAgain = useCallback(() => {
42714
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
42715
+ }, [playAgainEvent, eventBus]);
42716
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
42717
+ /* @__PURE__ */ jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
42718
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
42719
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
42720
+ "\u2014 ",
42721
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
42722
+ ] }),
42723
+ /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
42724
+ "Turn ",
42725
+ turn
42726
+ ] })
42727
+ ] }),
42728
+ /* @__PURE__ */ jsx(
42729
+ GameCanvas3D,
42730
+ {
42731
+ tiles,
42732
+ units,
42733
+ features,
42734
+ cameraMode,
42735
+ showGrid: true,
42736
+ showCoordinates: false,
42737
+ showTileInfo: false,
42738
+ shadows: true,
42739
+ backgroundColor,
42740
+ tileClickEvent,
42741
+ unitClickEvent,
42742
+ onUnitClick: handleUnitClickCallback,
42743
+ selectedUnitId,
42744
+ validMoves,
42745
+ attackTargets,
42746
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
42747
+ }
42748
+ ),
42749
+ !isGameOver && /* @__PURE__ */ jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
42750
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsx(
42751
+ Button,
42752
+ {
42753
+ variant: "secondary",
42754
+ className: "shadow-xl",
42755
+ onClick: handleCancel,
42756
+ children: "Cancel"
42757
+ }
42758
+ ),
42759
+ phase !== "enemy_turn" && /* @__PURE__ */ jsx(
42760
+ Button,
42761
+ {
42762
+ variant: "primary",
42763
+ className: "shadow-xl",
42764
+ onClick: handleEndTurn,
42765
+ children: "End Turn"
42766
+ }
42767
+ )
42768
+ ] }),
42769
+ isGameOver && /* @__PURE__ */ jsx("div", { className: "game-board-3d__overlay absolute inset-0 z-20 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxs(VStack, { align: "center", gap: "md", className: "overlay__card p-8 rounded-xl bg-gray-900/90 shadow-2xl", children: [
42770
+ /* @__PURE__ */ jsx(
42771
+ Typography,
42772
+ {
42773
+ variant: "h2",
42774
+ className: cn(
42775
+ "overlay__result",
42776
+ result === "victory" ? "text-yellow-400" : "text-red-500"
42777
+ ),
42778
+ children: result === "victory" ? "Victory!" : "Defeat"
42779
+ }
42780
+ ),
42781
+ /* @__PURE__ */ jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
42782
+ "Completed in ",
42783
+ turn,
42784
+ " turn",
42785
+ turn !== 1 ? "s" : ""
42786
+ ] }),
42787
+ /* @__PURE__ */ jsx(
42788
+ Button,
42789
+ {
42790
+ variant: "primary",
42791
+ className: "px-8 py-3 font-semibold",
42792
+ onClick: handlePlayAgain,
42793
+ children: "Play Again"
42794
+ }
42795
+ )
42796
+ ] }) })
42797
+ ] });
42798
+ }
42799
+ var init_GameBoard3D = __esm({
42800
+ "components/game/organisms/GameBoard3D.tsx"() {
42801
+ "use client";
42802
+ init_cn();
42803
+ init_Button();
42804
+ init_Typography();
42805
+ init_Stack();
42806
+ init_useEventBus();
42807
+ init_GameCanvas3D2();
42808
+ init_boardEntity();
42809
+ GameBoard3D.displayName = "GameBoard3D";
42810
+ }
42811
+ });
41157
42812
  var GameShell;
41158
42813
  var init_GameShell = __esm({
41159
42814
  "components/game/templates/GameShell.tsx"() {
@@ -42696,6 +44351,236 @@ var init_NegotiatorBoard = __esm({
42696
44351
  NegotiatorBoard.displayName = "NegotiatorBoard";
42697
44352
  }
42698
44353
  });
44354
+ function resolveEntity(entity) {
44355
+ if (!entity) return {};
44356
+ if (Array.isArray(entity)) return entity[0] ?? {};
44357
+ return entity;
44358
+ }
44359
+ function numField(row, key, fallback) {
44360
+ const v = row[key];
44361
+ return typeof v === "number" ? v : fallback;
44362
+ }
44363
+ function strField(row, key, fallback) {
44364
+ const v = row[key];
44365
+ return typeof v === "string" ? v : fallback;
44366
+ }
44367
+ function PlatformerBoard({
44368
+ entity,
44369
+ player: propPlayer,
44370
+ platforms: propPlatforms,
44371
+ result: propResult,
44372
+ lives: propLives,
44373
+ score: propScore,
44374
+ level: propLevel,
44375
+ worldWidth = 800,
44376
+ worldHeight = 400,
44377
+ canvasWidth = 800,
44378
+ canvasHeight = 400,
44379
+ playerSprite,
44380
+ tileSprites,
44381
+ bgColor,
44382
+ leftEvent = "LEFT",
44383
+ rightEvent = "RIGHT",
44384
+ jumpEvent = "JUMP",
44385
+ stopEvent = "STOP",
44386
+ playAgainEvent = "PLAY_AGAIN",
44387
+ gameEndEvent = "GAME_END",
44388
+ className
44389
+ }) {
44390
+ const row = resolveEntity(entity);
44391
+ const eventBus = useEventBus();
44392
+ const { t } = useTranslate();
44393
+ const result = propResult ?? strField(row, "result", "none");
44394
+ propLives ?? numField(row, "lives", 3);
44395
+ const score = propScore ?? numField(row, "score", 0);
44396
+ const level = propLevel ?? numField(row, "level", 1);
44397
+ const entityPlayer = row["player"];
44398
+ const player = propPlayer ?? entityPlayer ?? {
44399
+ x: 80,
44400
+ y: 320,
44401
+ width: 32,
44402
+ height: 48,
44403
+ vx: 0,
44404
+ vy: 0,
44405
+ grounded: false,
44406
+ facingRight: true
44407
+ };
44408
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
44409
+ const platforms = propPlatforms ?? entityPlatforms ?? [
44410
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44411
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44412
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44413
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44414
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44415
+ ];
44416
+ const handleRestart = useCallback(() => {
44417
+ if (playAgainEvent) {
44418
+ eventBus.emit(`UI:${playAgainEvent}`, {});
44419
+ }
44420
+ }, [playAgainEvent, eventBus]);
44421
+ const isGameOver = result === "won" || result === "lost";
44422
+ const gameEndEmittedRef = useRef(false);
44423
+ useEffect(() => {
44424
+ if ((result === "won" || result === "lost") && gameEndEvent) {
44425
+ if (!gameEndEmittedRef.current) {
44426
+ gameEndEmittedRef.current = true;
44427
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44428
+ }
44429
+ } else {
44430
+ gameEndEmittedRef.current = false;
44431
+ }
44432
+ }, [result, gameEndEvent, eventBus]);
44433
+ return /* @__PURE__ */ jsx(
44434
+ VStack,
44435
+ {
44436
+ className: cn("platformer-board relative bg-background", className),
44437
+ gap: "none",
44438
+ children: /* @__PURE__ */ jsxs(Box, { className: "relative", children: [
44439
+ /* @__PURE__ */ jsx(
44440
+ PlatformerCanvas,
44441
+ {
44442
+ player,
44443
+ platforms,
44444
+ worldWidth,
44445
+ worldHeight,
44446
+ canvasWidth,
44447
+ canvasHeight,
44448
+ followCamera: true,
44449
+ bgColor,
44450
+ playerSprite,
44451
+ tileSprites,
44452
+ leftEvent,
44453
+ rightEvent,
44454
+ jumpEvent,
44455
+ stopEvent
44456
+ }
44457
+ ),
44458
+ isGameOver && /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
44459
+ /* @__PURE__ */ jsx(
44460
+ Typography,
44461
+ {
44462
+ variant: "h2",
44463
+ className: cn(
44464
+ "text-4xl font-black tracking-widest uppercase",
44465
+ result === "won" ? "text-warning" : "text-error"
44466
+ ),
44467
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
44468
+ }
44469
+ ),
44470
+ result === "won" && /* @__PURE__ */ jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
44471
+ t("platformer.level"),
44472
+ " ",
44473
+ level,
44474
+ " \u2014 ",
44475
+ t("platformer.score"),
44476
+ ": ",
44477
+ score
44478
+ ] }),
44479
+ result === "lost" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
44480
+ /* @__PURE__ */ jsx(
44481
+ Button,
44482
+ {
44483
+ variant: "primary",
44484
+ className: "px-8 py-3 font-semibold",
44485
+ onClick: handleRestart,
44486
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
44487
+ }
44488
+ )
44489
+ ] }) })
44490
+ ] })
44491
+ }
44492
+ );
44493
+ }
44494
+ var init_PlatformerBoard = __esm({
44495
+ "components/game/organisms/PlatformerBoard.tsx"() {
44496
+ "use client";
44497
+ init_cn();
44498
+ init_useEventBus();
44499
+ init_Box();
44500
+ init_Button();
44501
+ init_Typography();
44502
+ init_Stack();
44503
+ init_PlatformerCanvas();
44504
+ PlatformerBoard.displayName = "PlatformerBoard";
44505
+ }
44506
+ });
44507
+ function PlatformerTemplate({
44508
+ entity,
44509
+ platforms = DEFAULT_PLATFORMS,
44510
+ worldWidth = 800,
44511
+ worldHeight = 400,
44512
+ canvasWidth = 800,
44513
+ canvasHeight = 400,
44514
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
44515
+ tileSprites = DEFAULT_TILE_SPRITES,
44516
+ bgColor = "#5c94fc",
44517
+ gameEndEvent = "GAME_END",
44518
+ className
44519
+ }) {
44520
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
44521
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
44522
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
44523
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
44524
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
44525
+ /* @__PURE__ */ jsx(Box, { className: "relative", children: /* @__PURE__ */ jsx(
44526
+ GameHud,
44527
+ {
44528
+ position: "top",
44529
+ stats: [
44530
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
44531
+ { label: "Level", value: level, format: "number", variant: "default" }
44532
+ ]
44533
+ }
44534
+ ) }),
44535
+ /* @__PURE__ */ jsx(
44536
+ PlatformerBoard,
44537
+ {
44538
+ entity,
44539
+ platforms,
44540
+ worldWidth,
44541
+ worldHeight,
44542
+ canvasWidth,
44543
+ canvasHeight,
44544
+ playerSprite,
44545
+ tileSprites,
44546
+ bgColor,
44547
+ leftEvent: "LEFT",
44548
+ rightEvent: "RIGHT",
44549
+ jumpEvent: "JUMP",
44550
+ stopEvent: "STOP",
44551
+ playAgainEvent: "PLAY_AGAIN",
44552
+ gameEndEvent
44553
+ }
44554
+ ),
44555
+ /* @__PURE__ */ jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsx(ScoreBoard, { score, level }) })
44556
+ ] });
44557
+ }
44558
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
44559
+ var init_PlatformerTemplate = __esm({
44560
+ "components/game/templates/PlatformerTemplate.tsx"() {
44561
+ init_cn();
44562
+ init_Box();
44563
+ init_Stack();
44564
+ init_PlatformerBoard();
44565
+ init_GameHud();
44566
+ init_ScoreBoard();
44567
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
44568
+ DEFAULT_TILE_SPRITES = {
44569
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
44570
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
44571
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
44572
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
44573
+ };
44574
+ DEFAULT_PLATFORMS = [
44575
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44576
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44577
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44578
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44579
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44580
+ ];
44581
+ PlatformerTemplate.displayName = "PlatformerTemplate";
44582
+ }
44583
+ });
42699
44584
  var PricingOrganism;
42700
44585
  var init_PricingOrganism = __esm({
42701
44586
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -42818,6 +44703,356 @@ var init_PricingPageTemplate = __esm({
42818
44703
  PricingPageTemplate.displayName = "PricingPageTemplate";
42819
44704
  }
42820
44705
  });
44706
+ function RoguelikeBoard({
44707
+ entity,
44708
+ tiles: propTiles,
44709
+ enemies: propEnemies,
44710
+ items: propItems,
44711
+ player: propPlayer,
44712
+ playerHp: propPlayerHp,
44713
+ playerMaxHp: propPlayerMaxHp,
44714
+ playerAttack: propPlayerAttack,
44715
+ depth: propDepth,
44716
+ result: propResult,
44717
+ phase: propPhase,
44718
+ stairsX: propStairsX,
44719
+ stairsY: propStairsY,
44720
+ assetManifest: propAssetManifest,
44721
+ scale = 0.45,
44722
+ unitScale = 1,
44723
+ moveEvent,
44724
+ playAgainEvent,
44725
+ gameEndEvent,
44726
+ className
44727
+ }) {
44728
+ const board = boardEntity(entity) ?? {};
44729
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
44730
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
44731
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
44732
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
44733
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
44734
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
44735
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
44736
+ const depth = propDepth ?? num(board.depth, 1);
44737
+ const result = propResult ?? (str(board.result) || "none");
44738
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
44739
+ propStairsX ?? num(board.stairsX, 3);
44740
+ propStairsY ?? num(board.stairsY, 3);
44741
+ const eventBus = useEventBus();
44742
+ const { t } = useTranslate();
44743
+ const [hoveredTile, setHoveredTile] = useState(null);
44744
+ const emitMove = useCallback((dx, dy) => {
44745
+ if (!moveEvent) return;
44746
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
44747
+ }, [moveEvent, eventBus]);
44748
+ useEffect(() => {
44749
+ const handler = (e) => {
44750
+ if (result !== "none" || phase !== "player_turn") return;
44751
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
44752
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
44753
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
44754
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
44755
+ };
44756
+ window.addEventListener("keydown", handler);
44757
+ return () => window.removeEventListener("keydown", handler);
44758
+ }, [result, phase, emitMove]);
44759
+ useEffect(() => {
44760
+ if ((result === "won" || result === "lost") && gameEndEvent) {
44761
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44762
+ }
44763
+ }, [result, gameEndEvent, eventBus]);
44764
+ const handleTileClick = useCallback((x, y) => {
44765
+ if (result !== "none" || phase !== "player_turn") return;
44766
+ const dx = x - player.x;
44767
+ const dy = y - player.y;
44768
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
44769
+ emitMove(dx, dy);
44770
+ }
44771
+ }, [result, phase, player, emitMove]);
44772
+ const handleReset = useCallback(() => {
44773
+ if (!playAgainEvent) return;
44774
+ eventBus.emit(`UI:${playAgainEvent}`, {});
44775
+ }, [playAgainEvent, eventBus]);
44776
+ const isoUnits = useMemo(() => {
44777
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
44778
+ return [
44779
+ {
44780
+ id: "player",
44781
+ position: player,
44782
+ name: t("roguelike.player") || "Hero",
44783
+ team: "player",
44784
+ health: playerHp,
44785
+ maxHealth: playerMaxHp,
44786
+ unitType: "player",
44787
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
44788
+ },
44789
+ ...liveEnemies.map((e) => ({
44790
+ id: e.id,
44791
+ position: { x: e.x, y: e.y },
44792
+ name: t("roguelike.enemy") || "Enemy",
44793
+ team: "enemy",
44794
+ health: e.hp,
44795
+ maxHealth: 6,
44796
+ unitType: "enemy",
44797
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
44798
+ }))
44799
+ ];
44800
+ }, [player, enemies, playerHp, playerMaxHp, t]);
44801
+ const isoFeatures = useMemo(
44802
+ () => items.map((it) => ({
44803
+ id: it.id,
44804
+ x: it.x,
44805
+ y: it.y,
44806
+ type: it.kind,
44807
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
44808
+ })),
44809
+ [items]
44810
+ );
44811
+ const validMoves = useMemo(() => {
44812
+ if (result !== "none" || phase !== "player_turn") return [];
44813
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
44814
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
44815
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
44816
+ return tile?.passable ?? false;
44817
+ });
44818
+ }, [result, phase, player, tiles]);
44819
+ const isGameOver = result === "won" || result === "lost";
44820
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
44821
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
44822
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44823
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
44824
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
44825
+ ] }),
44826
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44827
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
44828
+ /* @__PURE__ */ jsxs(
44829
+ Typography,
44830
+ {
44831
+ variant: "body1",
44832
+ className: cn(
44833
+ "font-bold tabular-nums",
44834
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
44835
+ ),
44836
+ children: [
44837
+ playerHp,
44838
+ " / ",
44839
+ playerMaxHp
44840
+ ]
44841
+ }
44842
+ )
44843
+ ] }),
44844
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44845
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
44846
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
44847
+ ] }),
44848
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44849
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
44850
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
44851
+ ] })
44852
+ ] }),
44853
+ /* @__PURE__ */ jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsx(
44854
+ IsometricCanvas_default,
44855
+ {
44856
+ tiles,
44857
+ units: isoUnits,
44858
+ features: isoFeatures,
44859
+ selectedUnitId: null,
44860
+ validMoves,
44861
+ attackTargets: [],
44862
+ hoveredTile,
44863
+ onTileClick: handleTileClick,
44864
+ onUnitClick: () => void 0,
44865
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
44866
+ onTileLeave: () => setHoveredTile(null),
44867
+ scale,
44868
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
44869
+ assetManifest: propAssetManifest,
44870
+ unitScale
44871
+ }
44872
+ ) }),
44873
+ !isGameOver && /* @__PURE__ */ jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
44874
+ { label: "\u2191", dx: 0, dy: -1 },
44875
+ { label: "\u2193", dx: 0, dy: 1 },
44876
+ { label: "\u2190", dx: -1, dy: 0 },
44877
+ { label: "\u2192", dx: 1, dy: 0 }
44878
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsx(
44879
+ Button,
44880
+ {
44881
+ variant: "secondary",
44882
+ className: "w-9 h-9 p-0 text-base font-mono",
44883
+ onClick: () => emitMove(dx, dy),
44884
+ disabled: phase !== "player_turn",
44885
+ children: label
44886
+ },
44887
+ label
44888
+ )) }),
44889
+ isGameOver && /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/75 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
44890
+ /* @__PURE__ */ jsx(
44891
+ Typography,
44892
+ {
44893
+ variant: "h2",
44894
+ className: cn(
44895
+ "text-4xl font-black tracking-widest uppercase",
44896
+ result === "won" ? "text-warning" : "text-error"
44897
+ ),
44898
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
44899
+ }
44900
+ ),
44901
+ result === "won" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
44902
+ /* @__PURE__ */ jsx(
44903
+ Button,
44904
+ {
44905
+ variant: "primary",
44906
+ className: "px-8 py-3 font-semibold",
44907
+ onClick: handleReset,
44908
+ children: t("roguelike.playAgain") || "Play Again"
44909
+ }
44910
+ )
44911
+ ] }) })
44912
+ ] });
44913
+ }
44914
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
44915
+ var init_RoguelikeBoard = __esm({
44916
+ "components/game/organisms/RoguelikeBoard.tsx"() {
44917
+ "use client";
44918
+ init_cn();
44919
+ init_useEventBus();
44920
+ init_Box();
44921
+ init_Button();
44922
+ init_Typography();
44923
+ init_Stack();
44924
+ init_IsometricCanvas();
44925
+ init_boardEntity();
44926
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
44927
+ DEFAULT_TILES2 = [
44928
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44929
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44930
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44931
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44932
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44933
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44934
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44935
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44936
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44937
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44938
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44939
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44940
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44941
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44942
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44943
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44944
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44945
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44946
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
44947
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44948
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44949
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44950
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44951
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44952
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
44953
+ ];
44954
+ DEFAULT_ENEMIES = [
44955
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
44956
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
44957
+ ];
44958
+ DEFAULT_ITEMS = [
44959
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
44960
+ ];
44961
+ FEATURE_SPRITE = {
44962
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
44963
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
44964
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
44965
+ };
44966
+ RoguelikeBoard.displayName = "RoguelikeBoard";
44967
+ }
44968
+ });
44969
+ function RoguelikeTemplate({
44970
+ entity,
44971
+ scale = 0.45,
44972
+ unitScale = 1,
44973
+ tiles = DEFAULT_ROGUELIKE_TILES,
44974
+ enemies,
44975
+ items,
44976
+ stairsX,
44977
+ stairsY,
44978
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
44979
+ gameEndEvent,
44980
+ className
44981
+ }) {
44982
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
44983
+ if (!resolved && !tiles && !assetManifest) return null;
44984
+ return /* @__PURE__ */ jsx(
44985
+ RoguelikeBoard,
44986
+ {
44987
+ entity: resolved,
44988
+ tiles,
44989
+ enemies,
44990
+ items,
44991
+ stairsX,
44992
+ stairsY,
44993
+ assetManifest,
44994
+ scale,
44995
+ unitScale,
44996
+ moveEvent: "MOVE",
44997
+ playAgainEvent: "PLAY_AGAIN",
44998
+ gameEndEvent: gameEndEvent ?? "GAME_END",
44999
+ className
45000
+ }
45001
+ );
45002
+ }
45003
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
45004
+ var init_RoguelikeTemplate = __esm({
45005
+ "components/game/templates/RoguelikeTemplate.tsx"() {
45006
+ init_RoguelikeBoard();
45007
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
45008
+ DEFAULT_ROGUELIKE_TILES = [
45009
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45010
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45011
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45012
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45013
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45014
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45015
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45016
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45017
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45018
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45019
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45020
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45021
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45022
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45023
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45024
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45025
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45026
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45027
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
45028
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45029
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45030
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45031
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45032
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45033
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
45034
+ ];
45035
+ DEFAULT_ROGUELIKE_MANIFEST = {
45036
+ baseUrl: CDN5,
45037
+ terrains: {
45038
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
45039
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
45040
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
45041
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
45042
+ },
45043
+ units: {
45044
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
45045
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
45046
+ },
45047
+ features: {
45048
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
45049
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
45050
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
45051
+ }
45052
+ };
45053
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
45054
+ }
45055
+ });
42821
45056
 
42822
45057
  // lib/traitRegistry.ts
42823
45058
  function getAllTraits() {
@@ -44052,7 +46287,7 @@ function getAllEvents(traits2) {
44052
46287
  function EventDispatcherTab({ traits: traits2, schema }) {
44053
46288
  const eventBus = useEventBus();
44054
46289
  const { t } = useTranslate();
44055
- const [log12, setLog] = React83.useState([]);
46290
+ const [log14, setLog] = React83.useState([]);
44056
46291
  const prevStatesRef = React83.useRef(/* @__PURE__ */ new Map());
44057
46292
  React83.useEffect(() => {
44058
46293
  for (const trait of traits2) {
@@ -44116,9 +46351,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
44116
46351
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
44117
46352
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: unavailableEvents.map((event) => /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", className: "opacity-50", children: event }, event)) })
44118
46353
  ] }),
44119
- log12.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
46354
+ log14.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
44120
46355
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
44121
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log12.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
46356
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log14.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
44122
46357
  /* @__PURE__ */ jsx("span", { className: "text-purple-400", children: entry.traitName }),
44123
46358
  " ",
44124
46359
  /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -46039,9 +48274,9 @@ function VariablePanel({
46039
48274
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
46040
48275
  variables.map((v) => {
46041
48276
  const name = v.name == null ? "" : String(v.name);
46042
- const value = numField(v.value);
46043
- const max = numField(v.max, 100);
46044
- const min = numField(v.min, 0);
48277
+ const value = numField2(v.value);
48278
+ const max = numField2(v.max, 100);
48279
+ const min = numField2(v.min, 0);
46045
48280
  const unit = v.unit == null ? "" : String(v.unit);
46046
48281
  const pct = Math.round((value - min) / (max - min) * 100);
46047
48282
  const isHigh = pct > 80;
@@ -46071,12 +48306,12 @@ function VariablePanel({
46071
48306
  })
46072
48307
  ] });
46073
48308
  }
46074
- var numField;
48309
+ var numField2;
46075
48310
  var init_VariablePanel = __esm({
46076
48311
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
46077
48312
  init_atoms2();
46078
48313
  init_cn();
46079
- numField = (v, fallback = 0) => {
48314
+ numField2 = (v, fallback = 0) => {
46080
48315
  const n = Number(v);
46081
48316
  return Number.isFinite(n) ? n : fallback;
46082
48317
  };
@@ -46929,6 +49164,289 @@ var init_ToastSlot = __esm({
46929
49164
  ToastSlot.displayName = "ToastSlot";
46930
49165
  }
46931
49166
  });
49167
+ function tilesToIso(tiles) {
49168
+ return tiles.map((t) => ({
49169
+ x: t.x,
49170
+ y: t.y,
49171
+ terrain: t.terrain,
49172
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
49173
+ passable: t.passable
49174
+ }));
49175
+ }
49176
+ function towersToUnits(towers) {
49177
+ return towers.map((t) => ({
49178
+ id: t.id,
49179
+ position: { x: t.x, y: t.y },
49180
+ name: "Tower",
49181
+ team: "player",
49182
+ sprite: TOWER_SPRITE,
49183
+ unitType: "guardian",
49184
+ health: 1,
49185
+ maxHealth: 1
49186
+ }));
49187
+ }
49188
+ function creepsToUnits(creeps) {
49189
+ return creeps.map((c) => ({
49190
+ id: c.id,
49191
+ position: { x: c.x, y: c.y },
49192
+ name: "Creep",
49193
+ team: "enemy",
49194
+ sprite: CREEP_SPRITE,
49195
+ unitType: "scrapper",
49196
+ health: c.hp,
49197
+ maxHealth: c.maxHp
49198
+ }));
49199
+ }
49200
+ function pathToFeatures(path) {
49201
+ return path.map((p2, i) => ({
49202
+ id: `path-${i}`,
49203
+ x: p2.x,
49204
+ y: p2.y,
49205
+ type: "path-marker",
49206
+ sprite: `${CDN6}world-map/road_straight.png`
49207
+ }));
49208
+ }
49209
+ function TowerDefenseBoard({
49210
+ entity,
49211
+ tiles: propTiles,
49212
+ path: propPath,
49213
+ towers: propTowers,
49214
+ creeps: propCreeps,
49215
+ gold: propGold,
49216
+ lives: propLives,
49217
+ wave: propWave,
49218
+ maxWaves: propMaxWaves,
49219
+ result: propResult,
49220
+ waveActive: propWaveActive,
49221
+ towerCost = 25,
49222
+ scale = 0.45,
49223
+ placeTowerEvent,
49224
+ startWaveEvent,
49225
+ playAgainEvent,
49226
+ gameEndEvent,
49227
+ className
49228
+ }) {
49229
+ const board = boardEntity(entity) ?? {};
49230
+ const eventBus = useEventBus();
49231
+ const { t } = useTranslate();
49232
+ const tiles = propTiles ?? rows(board.tiles);
49233
+ const path = propPath ?? rows(board.path);
49234
+ const towers = propTowers ?? rows(board.towers);
49235
+ const creeps = propCreeps ?? rows(board.creeps);
49236
+ const gold = propGold ?? num(board.gold, 100);
49237
+ const lives = propLives ?? num(board.lives, 20);
49238
+ const wave = propWave ?? num(board.wave, 1);
49239
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
49240
+ const result = propResult ?? (str(board.result) || "none");
49241
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
49242
+ const [hoveredTile, setHoveredTile] = useState(null);
49243
+ const emittedGameEnd = useRef(false);
49244
+ if (result !== "none" && !emittedGameEnd.current) {
49245
+ emittedGameEnd.current = true;
49246
+ if (gameEndEvent) {
49247
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
49248
+ }
49249
+ }
49250
+ if (result === "none") {
49251
+ emittedGameEnd.current = false;
49252
+ }
49253
+ const towerPositions = useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
49254
+ const pathPositions = useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
49255
+ const validMoves = useMemo(() => {
49256
+ if (result !== "none" || gold < towerCost) return [];
49257
+ return tiles.filter(
49258
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
49259
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
49260
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
49261
+ const isoTiles = useMemo(() => tilesToIso(tiles), [tiles]);
49262
+ const towerUnits = useMemo(() => towersToUnits(towers), [towers]);
49263
+ const creepUnits = useMemo(() => creepsToUnits(creeps), [creeps]);
49264
+ const isoUnits = useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
49265
+ const pathFeatures = useMemo(() => pathToFeatures(path), [path]);
49266
+ const handleTileClick = useCallback((x, y) => {
49267
+ if (result !== "none") return;
49268
+ if (pathPositions.has(`${x},${y}`)) return;
49269
+ if (towerPositions.has(`${x},${y}`)) return;
49270
+ if (gold < towerCost) return;
49271
+ if (placeTowerEvent) {
49272
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
49273
+ }
49274
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
49275
+ const handleStartWave = useCallback(() => {
49276
+ if (startWaveEvent) {
49277
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
49278
+ }
49279
+ }, [startWaveEvent, wave, eventBus]);
49280
+ const handlePlayAgain = useCallback(() => {
49281
+ if (playAgainEvent) {
49282
+ eventBus.emit(`UI:${playAgainEvent}`, {});
49283
+ }
49284
+ }, [playAgainEvent, eventBus]);
49285
+ const canPlaceTower = gold >= towerCost && result === "none";
49286
+ const canStartWave = !waveActive && result === "none";
49287
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
49288
+ /* @__PURE__ */ jsxs(
49289
+ HStack,
49290
+ {
49291
+ className: "px-4 py-2 border-b border-border bg-surface",
49292
+ gap: "lg",
49293
+ align: "center",
49294
+ children: [
49295
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
49296
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
49297
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
49298
+ ] }),
49299
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
49300
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
49301
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
49302
+ ] }),
49303
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
49304
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
49305
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", weight: "bold", children: [
49306
+ wave,
49307
+ " / ",
49308
+ maxWaves
49309
+ ] })
49310
+ ] }),
49311
+ /* @__PURE__ */ jsxs(Box, { className: "ml-auto", children: [
49312
+ canStartWave && /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
49313
+ waveActive && /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
49314
+ ] })
49315
+ ]
49316
+ }
49317
+ ),
49318
+ /* @__PURE__ */ jsxs(Box, { className: "relative flex-1", children: [
49319
+ /* @__PURE__ */ jsx(
49320
+ IsometricCanvas_default,
49321
+ {
49322
+ tiles: isoTiles,
49323
+ units: isoUnits,
49324
+ features: pathFeatures,
49325
+ validMoves,
49326
+ hoveredTile,
49327
+ onTileClick: handleTileClick,
49328
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
49329
+ onTileLeave: () => setHoveredTile(null),
49330
+ scale
49331
+ }
49332
+ ),
49333
+ hoveredTile && canPlaceTower && validMoves.some((m) => m.x === hoveredTile.x && m.y === hoveredTile.y) && /* @__PURE__ */ 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__ */ jsx(Typography, { variant: "small", children: t("td.placeTower") ?? `Place tower (${towerCost} gold)` }) })
49334
+ ] }),
49335
+ result !== "none" && /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm", children: /* @__PURE__ */ jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
49336
+ /* @__PURE__ */ jsx(
49337
+ Typography,
49338
+ {
49339
+ variant: "h2",
49340
+ className: cn(
49341
+ "text-4xl font-black tracking-widest uppercase",
49342
+ result === "won" ? "text-warning" : "text-error"
49343
+ ),
49344
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
49345
+ }
49346
+ ),
49347
+ /* @__PURE__ */ 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." }),
49348
+ /* @__PURE__ */ jsx(
49349
+ Button,
49350
+ {
49351
+ variant: "primary",
49352
+ className: "px-8 py-3 font-semibold",
49353
+ onClick: handlePlayAgain,
49354
+ children: t("td.playAgain") ?? "Play Again"
49355
+ }
49356
+ )
49357
+ ] }) })
49358
+ ] });
49359
+ }
49360
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
49361
+ var init_TowerDefenseBoard = __esm({
49362
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
49363
+ "use client";
49364
+ init_cn();
49365
+ init_useEventBus();
49366
+ init_Box();
49367
+ init_Button();
49368
+ init_Typography();
49369
+ init_Stack();
49370
+ init_IsometricCanvas();
49371
+ init_boardEntity();
49372
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
49373
+ TERRAIN_SPRITES = {
49374
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
49375
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
49376
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
49377
+ };
49378
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
49379
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
49380
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
49381
+ }
49382
+ });
49383
+ function TowerDefenseTemplate({
49384
+ entity,
49385
+ title = "Tower Defense",
49386
+ tiles,
49387
+ path,
49388
+ towers,
49389
+ gold,
49390
+ lives,
49391
+ maxWaves,
49392
+ towerCost,
49393
+ scale,
49394
+ placeTowerEvent,
49395
+ startWaveEvent,
49396
+ playAgainEvent,
49397
+ gameEndEvent,
49398
+ className
49399
+ }) {
49400
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
49401
+ return /* @__PURE__ */ jsxs(
49402
+ Box,
49403
+ {
49404
+ display: "flex",
49405
+ fullHeight: true,
49406
+ className: cn("tower-defense-template flex-col", className),
49407
+ children: [
49408
+ /* @__PURE__ */ jsx(
49409
+ HStack,
49410
+ {
49411
+ gap: "sm",
49412
+ align: "center",
49413
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
49414
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
49415
+ }
49416
+ ),
49417
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
49418
+ TowerDefenseBoard,
49419
+ {
49420
+ entity: resolved,
49421
+ tiles,
49422
+ path,
49423
+ towers,
49424
+ gold,
49425
+ lives,
49426
+ maxWaves,
49427
+ towerCost,
49428
+ scale,
49429
+ placeTowerEvent,
49430
+ startWaveEvent,
49431
+ playAgainEvent,
49432
+ gameEndEvent,
49433
+ className: "h-full"
49434
+ }
49435
+ ) })
49436
+ ]
49437
+ }
49438
+ );
49439
+ }
49440
+ var init_TowerDefenseTemplate = __esm({
49441
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
49442
+ init_cn();
49443
+ init_Box();
49444
+ init_Stack();
49445
+ init_Typography();
49446
+ init_TowerDefenseBoard();
49447
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
49448
+ }
49449
+ });
46932
49450
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
46933
49451
  const eventBus = useEventBus();
46934
49452
  const {
@@ -47438,48 +49956,48 @@ function WorldMapTemplate({
47438
49956
  }
47439
49957
  );
47440
49958
  }
47441
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
49959
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
47442
49960
  var init_WorldMapTemplate = __esm({
47443
49961
  "components/game/templates/WorldMapTemplate.tsx"() {
47444
49962
  init_WorldMapBoard();
47445
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
49963
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
47446
49964
  DEFAULT_WORLDMAP_TILES = [
47447
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47448
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47449
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47450
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47451
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47452
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47453
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47454
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47455
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47456
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47457
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47458
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47459
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47460
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47461
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47462
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47463
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47464
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47465
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47466
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47467
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47468
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47469
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47470
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47471
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
49965
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49966
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49967
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49968
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49969
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49970
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49971
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49972
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49973
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49974
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49975
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49976
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49977
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49978
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49979
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49980
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49981
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49982
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49983
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49984
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49985
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49986
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49987
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49988
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49989
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
47472
49990
  ];
47473
49991
  DEFAULT_WORLDMAP_UNITS = [
47474
- { 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` },
47475
- { 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` }
49992
+ { 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` },
49993
+ { 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` }
47476
49994
  ];
47477
49995
  DEFAULT_WORLDMAP_FEATURES = [
47478
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
47479
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
49996
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
49997
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
47480
49998
  ];
47481
49999
  DEFAULT_WORLDMAP_MANIFEST = {
47482
- baseUrl: CDN3,
50000
+ baseUrl: CDN7,
47483
50001
  terrains: {
47484
50002
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
47485
50003
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -47524,7 +50042,7 @@ function lazyThree(name, loader) {
47524
50042
  ThreeWrapper.displayName = `Lazy(${name})`;
47525
50043
  return ThreeWrapper;
47526
50044
  }
47527
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
50045
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
47528
50046
  var init_component_registry_generated = __esm({
47529
50047
  "components/core/organisms/component-registry.generated.ts"() {
47530
50048
  init_AboutPageTemplate();
@@ -47634,6 +50152,7 @@ var init_component_registry_generated = __esm({
47634
50152
  init_FormSectionHeader();
47635
50153
  init_GameAudioProvider();
47636
50154
  init_GameAudioToggle();
50155
+ init_GameBoard3D();
47637
50156
  init_GameCanvas2D();
47638
50157
  init_GameHud();
47639
50158
  init_GameMenu();
@@ -47691,7 +50210,9 @@ var init_component_registry_generated = __esm({
47691
50210
  init_PageHeader();
47692
50211
  init_Pagination();
47693
50212
  init_PatternTile();
50213
+ init_PlatformerBoard();
47694
50214
  init_PlatformerCanvas();
50215
+ init_PlatformerTemplate();
47695
50216
  init_Popover();
47696
50217
  init_PositionedCanvas();
47697
50218
  init_PowerupSlots();
@@ -47715,6 +50236,8 @@ var init_component_registry_generated = __esm({
47715
50236
  init_ResourceBar();
47716
50237
  init_ResourceCounter();
47717
50238
  init_RichBlockEditor();
50239
+ init_RoguelikeBoard();
50240
+ init_RoguelikeTemplate();
47718
50241
  init_RuntimeDebugger2();
47719
50242
  init_ScaledDiagram();
47720
50243
  init_ScoreBoard();
@@ -47787,6 +50310,8 @@ var init_component_registry_generated = __esm({
47787
50310
  init_Toast();
47788
50311
  init_ToastSlot();
47789
50312
  init_Tooltip();
50313
+ init_TowerDefenseBoard();
50314
+ init_TowerDefenseTemplate();
47790
50315
  init_TraitFrame();
47791
50316
  init_TraitSlot();
47792
50317
  init_TrendIndicator();
@@ -47831,7 +50356,7 @@ var init_component_registry_generated = __esm({
47831
50356
  }
47832
50357
  };
47833
50358
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
47834
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
50359
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
47835
50360
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47836
50361
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47837
50362
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -47950,8 +50475,9 @@ var init_component_registry_generated = __esm({
47950
50475
  "FormSectionHeader": FormSectionHeader,
47951
50476
  "GameAudioProvider": GameAudioProvider,
47952
50477
  "GameAudioToggle": GameAudioToggle,
50478
+ "GameBoard3D": GameBoard3D,
47953
50479
  "GameCanvas2D": GameCanvas2D,
47954
- "GameCanvas3D": GameCanvas3D,
50480
+ "GameCanvas3D": GameCanvas3D2,
47955
50481
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
47956
50482
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
47957
50483
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -48013,7 +50539,9 @@ var init_component_registry_generated = __esm({
48013
50539
  "PageHeader": PageHeader,
48014
50540
  "Pagination": Pagination,
48015
50541
  "PatternTile": PatternTile,
50542
+ "PlatformerBoard": PlatformerBoard,
48016
50543
  "PlatformerCanvas": PlatformerCanvas,
50544
+ "PlatformerTemplate": PlatformerTemplate,
48017
50545
  "Popover": Popover,
48018
50546
  "PositionedCanvas": PositionedCanvas,
48019
50547
  "PowerupSlots": PowerupSlots,
@@ -48037,6 +50565,8 @@ var init_component_registry_generated = __esm({
48037
50565
  "ResourceBar": ResourceBar,
48038
50566
  "ResourceCounter": ResourceCounter,
48039
50567
  "RichBlockEditor": RichBlockEditor,
50568
+ "RoguelikeBoard": RoguelikeBoard,
50569
+ "RoguelikeTemplate": RoguelikeTemplate,
48040
50570
  "RuntimeDebugger": RuntimeDebugger,
48041
50571
  "ScaledDiagram": ScaledDiagram,
48042
50572
  "ScoreBoard": ScoreBoard,
@@ -48112,6 +50642,8 @@ var init_component_registry_generated = __esm({
48112
50642
  "Toast": Toast,
48113
50643
  "ToastSlot": ToastSlot,
48114
50644
  "Tooltip": Tooltip,
50645
+ "TowerDefenseBoard": TowerDefenseBoard,
50646
+ "TowerDefenseTemplate": TowerDefenseTemplate,
48115
50647
  "TraitFrame": TraitFrame,
48116
50648
  "TraitSlot": TraitSlot,
48117
50649
  "TrendIndicator": TrendIndicator,
@@ -49283,7 +51815,7 @@ init_UISlotRenderer();
49283
51815
  // providers/VerificationProvider.tsx
49284
51816
  init_useEventBus();
49285
51817
  init_verificationRegistry();
49286
- var log11 = createLogger("almadar:verify");
51818
+ var log13 = createLogger("almadar:verify");
49287
51819
  var DISPATCH_SUFFIX = ":DISPATCH";
49288
51820
  var SUCCESS_SUFFIX = ":SUCCESS";
49289
51821
  var ERROR_SUFFIX = ":ERROR";
@@ -49330,7 +51862,7 @@ function VerificationProvider({
49330
51862
  const verificationProviderLifecycleListener = (evt) => {
49331
51863
  const parsed = parseLifecycleEvent(evt.type);
49332
51864
  if (!parsed) return;
49333
- log11.debug("lifecycle:event", { kind: parsed.kind, traitName: parsed.traitName, event: parsed.event, type: evt.type });
51865
+ log13.debug("lifecycle:event", { kind: parsed.kind, traitName: parsed.traitName, event: parsed.event, type: evt.type });
49334
51866
  const payload = evt.payload ?? {};
49335
51867
  if (parsed.kind === "dispatch") {
49336
51868
  const key = `${parsed.traitName}:${String(payload["event"] ?? "")}`;
@@ -49385,7 +51917,7 @@ function VerificationProvider({
49385
51917
  },
49386
51918
  timestamp: Date.now()
49387
51919
  });
49388
- log11.debug("transition:success", { trait: parsed.traitName, event: parsed.event, from: pending?.from, to: newState, effectCount: effects.length });
51920
+ log13.debug("transition:success", { trait: parsed.traitName, event: parsed.event, from: pending?.from, to: newState, effectCount: effects.length });
49389
51921
  } else if (parsed.kind === "error" && parsed.event) {
49390
51922
  const key = `${parsed.traitName}:${parsed.event}`;
49391
51923
  const pending = pendingRef.current.get(key);
@@ -49415,7 +51947,7 @@ function VerificationProvider({
49415
51947
  },
49416
51948
  timestamp: Date.now()
49417
51949
  });
49418
- log11.warn("transition:error", { trait: parsed.traitName, event: parsed.event, from: fromState, error: errorMsg });
51950
+ log13.warn("transition:error", { trait: parsed.traitName, event: parsed.event, from: fromState, error: errorMsg });
49419
51951
  }
49420
51952
  };
49421
51953
  Object.defineProperty(verificationProviderLifecycleListener, "name", {