@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 React82 from 'react';
2
- import React82__default, { createContext, useMemo, useContext, useRef, useEffect, useCallback, Suspense, useState, useSyncExternalStore, useLayoutEffect, lazy, useId } from 'react';
2
+ import React82__default, { createContext, useMemo, useContext, useRef, useEffect, useCallback, Suspense, useState, useSyncExternalStore, useLayoutEffect, lazy, useId, forwardRef, useImperativeHandle, Component } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
@@ -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
  import { StateMachineManager, createContextFromBindings, interpolateValue, collectDeclaredConfigDefaults, createServerEffectHandlers, EffectExecutor, InMemoryPersistence } from '@almadar/runtime';
46
52
  import { OrbitalServerRuntime } from '@almadar/runtime/OrbitalServerRuntime';
@@ -409,7 +415,7 @@ var init_Box = __esm({
409
415
  position,
410
416
  className,
411
417
  children,
412
- as: Component = "div",
418
+ as: Component2 = "div",
413
419
  action,
414
420
  actionPayload,
415
421
  hoverEvent,
@@ -441,7 +447,7 @@ var init_Box = __esm({
441
447
  }, [hoverEvent, eventBus, onMouseLeave]);
442
448
  const isClickable = action || onClick;
443
449
  return React82__default.createElement(
444
- Component,
450
+ Component2,
445
451
  {
446
452
  ref,
447
453
  className: cn(
@@ -579,9 +585,9 @@ function resolvePhosphor(name, weight, family) {
579
585
  (lib) => {
580
586
  const PhosphorComp = lib[target];
581
587
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
582
- const Component = PhosphorComp;
588
+ const Component2 = PhosphorComp;
583
589
  const Adapter = (props) => /* @__PURE__ */ jsx(
584
- Component,
590
+ Component2,
585
591
  {
586
592
  weight,
587
593
  className: props.className,
@@ -605,9 +611,9 @@ function resolveTabler(name, family) {
605
611
  (lib) => {
606
612
  const TablerComp = lib[target];
607
613
  if (!TablerComp || typeof TablerComp !== "object") return null;
608
- const Component = TablerComp;
614
+ const Component2 = TablerComp;
609
615
  const Adapter = (props) => /* @__PURE__ */ jsx(
610
- Component,
616
+ Component2,
611
617
  {
612
618
  stroke: props.strokeWidth ?? 1.5,
613
619
  className: props.className,
@@ -631,9 +637,9 @@ function resolveFa(name, family) {
631
637
  (lib) => {
632
638
  const FaComp = lib[target];
633
639
  if (!FaComp || typeof FaComp !== "function") return null;
634
- const Component = FaComp;
640
+ const Component2 = FaComp;
635
641
  const Adapter = (props) => /* @__PURE__ */ jsx(
636
- Component,
642
+ Component2,
637
643
  {
638
644
  className: props.className,
639
645
  style: props.style,
@@ -1588,8 +1594,8 @@ var init_Typography = __esm({
1588
1594
  children
1589
1595
  }) => {
1590
1596
  const variant = variantProp ?? (level ? `h${level}` : "body1");
1591
- const Component = as || defaultElements[variant];
1592
- const Comp = Component;
1597
+ const Component2 = as || defaultElements[variant];
1598
+ const Comp = Component2;
1593
1599
  return /* @__PURE__ */ jsx(
1594
1600
  Comp,
1595
1601
  {
@@ -4144,10 +4150,10 @@ var init_Center = __esm({
4144
4150
  className,
4145
4151
  style,
4146
4152
  children,
4147
- as: Component = "div"
4153
+ as: Component2 = "div"
4148
4154
  }) => {
4149
4155
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
4150
- const Comp = Component;
4156
+ const Comp = Component2;
4151
4157
  return /* @__PURE__ */ jsx(
4152
4158
  Comp,
4153
4159
  {
@@ -4778,7 +4784,7 @@ var init_Stack = __esm({
4778
4784
  className,
4779
4785
  style,
4780
4786
  children,
4781
- as: Component = "div",
4787
+ as: Component2 = "div",
4782
4788
  onClick,
4783
4789
  onKeyDown,
4784
4790
  role,
@@ -4796,7 +4802,7 @@ var init_Stack = __esm({
4796
4802
  };
4797
4803
  const isHorizontal = direction === "horizontal";
4798
4804
  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";
4799
- const Comp = Component;
4805
+ const Comp = Component2;
4800
4806
  return /* @__PURE__ */ jsx(
4801
4807
  Comp,
4802
4808
  {
@@ -7027,6 +7033,7 @@ var init_HealthBar = __esm({
7027
7033
  }
7028
7034
  });
7029
7035
  function ScoreDisplay({
7036
+ assetUrl = DEFAULT_ASSET_URL,
7030
7037
  value,
7031
7038
  label,
7032
7039
  icon,
@@ -7073,14 +7080,24 @@ function ScoreDisplay({
7073
7080
  className
7074
7081
  ),
7075
7082
  children: [
7076
- icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
7083
+ assetUrl ? /* @__PURE__ */ jsx(
7084
+ "img",
7085
+ {
7086
+ src: assetUrl,
7087
+ alt: "",
7088
+ width: 20,
7089
+ height: 20,
7090
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7091
+ className: "flex-shrink-0"
7092
+ }
7093
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }) : null,
7077
7094
  label && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
7078
7095
  /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formattedValue })
7079
7096
  ]
7080
7097
  }
7081
7098
  );
7082
7099
  }
7083
- var sizeMap2;
7100
+ var sizeMap2, DEFAULT_ASSET_URL;
7084
7101
  var init_ScoreDisplay = __esm({
7085
7102
  "components/game/atoms/ScoreDisplay.tsx"() {
7086
7103
  "use client";
@@ -7092,10 +7109,12 @@ var init_ScoreDisplay = __esm({
7092
7109
  lg: "text-2xl",
7093
7110
  xl: "text-4xl"
7094
7111
  };
7112
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
7095
7113
  ScoreDisplay.displayName = "ScoreDisplay";
7096
7114
  }
7097
7115
  });
7098
7116
  function ControlButton({
7117
+ assetUrl = DEFAULT_ASSET_URL2,
7099
7118
  label,
7100
7119
  icon,
7101
7120
  size = "md",
@@ -7142,7 +7161,7 @@ function ControlButton({
7142
7161
  },
7143
7162
  [isPressed, releaseEvent, eventBus, onRelease]
7144
7163
  );
7145
- return /* @__PURE__ */ jsxs(
7164
+ return /* @__PURE__ */ jsx(
7146
7165
  "button",
7147
7166
  {
7148
7167
  type: "button",
@@ -7163,20 +7182,27 @@ function ControlButton({
7163
7182
  disabled && "opacity-50 cursor-not-allowed",
7164
7183
  className
7165
7184
  ),
7166
- children: [
7167
- icon && /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7168
- const I = resolveIcon(icon);
7169
- return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
7170
- })() : icon : /* @__PURE__ */ (() => {
7171
- const I = icon;
7172
- return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
7173
- })() }),
7174
- label && !icon && /* @__PURE__ */ jsx("span", { children: label })
7175
- ]
7185
+ children: assetUrl ? /* @__PURE__ */ jsx(
7186
+ "img",
7187
+ {
7188
+ src: assetUrl,
7189
+ alt: "",
7190
+ width: 24,
7191
+ height: 24,
7192
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7193
+ className: "flex-shrink-0"
7194
+ }
7195
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7196
+ const I = resolveIcon(icon);
7197
+ return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
7198
+ })() : icon : /* @__PURE__ */ (() => {
7199
+ const I = icon;
7200
+ return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
7201
+ })() }) : label ? /* @__PURE__ */ jsx("span", { children: label }) : null
7176
7202
  }
7177
7203
  );
7178
7204
  }
7179
- var sizeMap3, shapeMap, variantMap;
7205
+ var sizeMap3, shapeMap, variantMap, DEFAULT_ASSET_URL2;
7180
7206
  var init_ControlButton = __esm({
7181
7207
  "components/game/atoms/ControlButton.tsx"() {
7182
7208
  "use client";
@@ -7199,6 +7225,7 @@ var init_ControlButton = __esm({
7199
7225
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
7200
7226
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
7201
7227
  };
7228
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
7202
7229
  ControlButton.displayName = "ControlButton";
7203
7230
  }
7204
7231
  });
@@ -7278,7 +7305,7 @@ var init_Sprite = __esm({
7278
7305
  }
7279
7306
  });
7280
7307
  function StateIndicator({
7281
- assetUrl = DEFAULT_ASSET_URL,
7308
+ assetUrl = DEFAULT_ASSET_URL3,
7282
7309
  state = "idle",
7283
7310
  label,
7284
7311
  size = "md",
@@ -7317,13 +7344,13 @@ function StateIndicator({
7317
7344
  }
7318
7345
  );
7319
7346
  }
7320
- var DEFAULT_ASSET_URL, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7347
+ var DEFAULT_ASSET_URL3, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7321
7348
  var init_StateIndicator = __esm({
7322
7349
  "components/game/atoms/StateIndicator.tsx"() {
7323
7350
  init_Box();
7324
7351
  init_Icon();
7325
7352
  init_cn();
7326
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7353
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7327
7354
  DEFAULT_STATE_STYLES = {
7328
7355
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
7329
7356
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -7398,7 +7425,7 @@ var init_TimerDisplay = __esm({
7398
7425
  }
7399
7426
  });
7400
7427
  function ResourceCounter({
7401
- assetUrl = DEFAULT_ASSET_URL2,
7428
+ assetUrl = DEFAULT_ASSET_URL4,
7402
7429
  icon,
7403
7430
  label = "Gold",
7404
7431
  value = 250,
@@ -7441,7 +7468,7 @@ function ResourceCounter({
7441
7468
  }
7442
7469
  );
7443
7470
  }
7444
- var colorTokenClasses2, DEFAULT_ASSET_URL2, sizeMap5;
7471
+ var colorTokenClasses2, DEFAULT_ASSET_URL4, sizeMap5;
7445
7472
  var init_ResourceCounter = __esm({
7446
7473
  "components/game/atoms/ResourceCounter.tsx"() {
7447
7474
  init_cn();
@@ -7454,7 +7481,7 @@ var init_ResourceCounter = __esm({
7454
7481
  error: "text-error",
7455
7482
  muted: "text-muted-foreground"
7456
7483
  };
7457
- DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7484
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7458
7485
  sizeMap5 = {
7459
7486
  sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
7460
7487
  md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
@@ -7464,7 +7491,7 @@ var init_ResourceCounter = __esm({
7464
7491
  }
7465
7492
  });
7466
7493
  function ItemSlot({
7467
- assetUrl = DEFAULT_ASSET_URL3,
7494
+ assetUrl = DEFAULT_ASSET_URL5,
7468
7495
  icon = "sword",
7469
7496
  label = "Iron Sword",
7470
7497
  quantity,
@@ -7523,7 +7550,7 @@ function ItemSlot({
7523
7550
  }
7524
7551
  );
7525
7552
  }
7526
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL3, assetSizeMap;
7553
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL5, assetSizeMap;
7527
7554
  var init_ItemSlot = __esm({
7528
7555
  "components/game/atoms/ItemSlot.tsx"() {
7529
7556
  "use client";
@@ -7548,7 +7575,7 @@ var init_ItemSlot = __esm({
7548
7575
  epic: "shadow-lg",
7549
7576
  legendary: "shadow-lg"
7550
7577
  };
7551
- DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7578
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7552
7579
  assetSizeMap = {
7553
7580
  sm: 28,
7554
7581
  md: 40,
@@ -7558,6 +7585,7 @@ var init_ItemSlot = __esm({
7558
7585
  }
7559
7586
  });
7560
7587
  function TurnIndicator({
7588
+ assetUrl = DEFAULT_ASSET_URL6,
7561
7589
  currentTurn = 1,
7562
7590
  maxTurns,
7563
7591
  activeTeam,
@@ -7590,14 +7618,24 @@ function TurnIndicator({
7590
7618
  ] }),
7591
7619
  activeTeam && /* @__PURE__ */ jsxs(Fragment, { children: [
7592
7620
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "|" }),
7593
- /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7621
+ assetUrl ? /* @__PURE__ */ jsx(
7622
+ "img",
7623
+ {
7624
+ src: assetUrl,
7625
+ alt: "",
7626
+ width: 12,
7627
+ height: 12,
7628
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7629
+ className: "flex-shrink-0"
7630
+ }
7631
+ ) : /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7594
7632
  /* @__PURE__ */ jsx("span", { className: "text-success", children: activeTeam })
7595
7633
  ] })
7596
7634
  ]
7597
7635
  }
7598
7636
  );
7599
7637
  }
7600
- var sizeMap7;
7638
+ var sizeMap7, DEFAULT_ASSET_URL6;
7601
7639
  var init_TurnIndicator = __esm({
7602
7640
  "components/game/atoms/TurnIndicator.tsx"() {
7603
7641
  init_cn();
@@ -7606,6 +7644,7 @@ var init_TurnIndicator = __esm({
7606
7644
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
7607
7645
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
7608
7646
  };
7647
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
7609
7648
  TurnIndicator.displayName = "TurnIndicator";
7610
7649
  }
7611
7650
  });
@@ -7621,7 +7660,7 @@ function getComboScale(combo) {
7621
7660
  return "";
7622
7661
  }
7623
7662
  function ComboCounter({
7624
- assetUrl = DEFAULT_ASSET_URL4,
7663
+ assetUrl = DEFAULT_ASSET_URL7,
7625
7664
  combo = 5,
7626
7665
  multiplier,
7627
7666
  streak,
@@ -7666,11 +7705,11 @@ function ComboCounter({
7666
7705
  }
7667
7706
  );
7668
7707
  }
7669
- var DEFAULT_ASSET_URL4, sizeMap8;
7708
+ var DEFAULT_ASSET_URL7, sizeMap8;
7670
7709
  var init_ComboCounter = __esm({
7671
7710
  "components/game/atoms/ComboCounter.tsx"() {
7672
7711
  init_cn();
7673
- DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7712
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7674
7713
  sizeMap8 = {
7675
7714
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
7676
7715
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -7764,7 +7803,7 @@ var init_XPBar = __esm({
7764
7803
  }
7765
7804
  });
7766
7805
  function WaypointMarker({
7767
- assetUrl = DEFAULT_ASSET_URL5,
7806
+ assetUrl = DEFAULT_ASSET_URL8,
7768
7807
  label,
7769
7808
  icon,
7770
7809
  active = true,
@@ -7830,12 +7869,12 @@ function WaypointMarker({
7830
7869
  )
7831
7870
  ] });
7832
7871
  }
7833
- var DEFAULT_ASSET_URL5, sizeMap10, checkIcon;
7872
+ var DEFAULT_ASSET_URL8, sizeMap10, checkIcon;
7834
7873
  var init_WaypointMarker = __esm({
7835
7874
  "components/game/atoms/WaypointMarker.tsx"() {
7836
7875
  init_cn();
7837
7876
  init_Icon();
7838
- DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7877
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7839
7878
  sizeMap10 = {
7840
7879
  sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
7841
7880
  md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
@@ -7852,7 +7891,7 @@ function formatDuration(seconds) {
7852
7891
  return `${Math.round(seconds)}s`;
7853
7892
  }
7854
7893
  function StatusEffect({
7855
- assetUrl = DEFAULT_ASSET_URL6,
7894
+ assetUrl = DEFAULT_ASSET_URL9,
7856
7895
  icon = "shield",
7857
7896
  label = "Shield",
7858
7897
  duration = 30,
@@ -7910,12 +7949,12 @@ function StatusEffect({
7910
7949
  label && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
7911
7950
  ] });
7912
7951
  }
7913
- var DEFAULT_ASSET_URL6, sizeMap11, variantStyles7;
7952
+ var DEFAULT_ASSET_URL9, sizeMap11, variantStyles7;
7914
7953
  var init_StatusEffect = __esm({
7915
7954
  "components/game/atoms/StatusEffect.tsx"() {
7916
7955
  init_cn();
7917
7956
  init_Icon();
7918
- DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
7957
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
7919
7958
  sizeMap11 = {
7920
7959
  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 },
7921
7960
  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 },
@@ -7930,6 +7969,7 @@ var init_StatusEffect = __esm({
7930
7969
  }
7931
7970
  });
7932
7971
  function DamageNumber({
7972
+ assetUrl = DEFAULT_ASSET_URL10,
7933
7973
  value = 42,
7934
7974
  type = "damage",
7935
7975
  size = "md",
@@ -7938,22 +7978,35 @@ function DamageNumber({
7938
7978
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
7939
7979
  return /* @__PURE__ */ jsxs(Fragment, { children: [
7940
7980
  /* @__PURE__ */ jsx("style", { children: floatKeyframes }),
7941
- /* @__PURE__ */ jsx(
7981
+ /* @__PURE__ */ jsxs(
7942
7982
  "span",
7943
7983
  {
7944
7984
  className: cn(
7945
- "inline-block font-mono select-none pointer-events-none",
7985
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
7946
7986
  sizeMap12[size],
7947
7987
  typeStyles[type],
7948
7988
  className
7949
7989
  ),
7950
7990
  style: { animation: "damageFloat 1s ease-out forwards" },
7951
- children: displayText
7991
+ children: [
7992
+ assetUrl && /* @__PURE__ */ jsx(
7993
+ "img",
7994
+ {
7995
+ src: assetUrl,
7996
+ alt: "",
7997
+ width: 14,
7998
+ height: 14,
7999
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8000
+ className: "flex-shrink-0"
8001
+ }
8002
+ ),
8003
+ displayText
8004
+ ]
7952
8005
  }
7953
8006
  )
7954
8007
  ] });
7955
8008
  }
7956
- var sizeMap12, typeStyles, floatKeyframes;
8009
+ var sizeMap12, typeStyles, floatKeyframes, DEFAULT_ASSET_URL10;
7957
8010
  var init_DamageNumber = __esm({
7958
8011
  "components/game/atoms/DamageNumber.tsx"() {
7959
8012
  init_cn();
@@ -7975,6 +8028,7 @@ var init_DamageNumber = __esm({
7975
8028
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
7976
8029
  }
7977
8030
  `;
8031
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
7978
8032
  DamageNumber.displayName = "DamageNumber";
7979
8033
  }
7980
8034
  });
@@ -8063,6 +8117,7 @@ var init_ChoiceButton = __esm({
8063
8117
  }
8064
8118
  });
8065
8119
  function ActionButton({
8120
+ assetUrl = DEFAULT_ASSET_URL11,
8066
8121
  label = "Attack",
8067
8122
  icon,
8068
8123
  cooldown = 0,
@@ -8102,13 +8157,23 @@ function ActionButton({
8102
8157
  }
8103
8158
  }
8104
8159
  ),
8105
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8160
+ assetUrl ? /* @__PURE__ */ jsx(
8161
+ "img",
8162
+ {
8163
+ src: assetUrl,
8164
+ alt: "",
8165
+ width: 16,
8166
+ height: 16,
8167
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8168
+ className: cn("flex-shrink-0", sizes.icon)
8169
+ }
8170
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8106
8171
  const I = resolveIcon(icon);
8107
8172
  return I ? /* @__PURE__ */ jsx(I, { className: "w-4 h-4" }) : null;
8108
8173
  })() : /* @__PURE__ */ (() => {
8109
8174
  const I = icon;
8110
8175
  return /* @__PURE__ */ jsx(I, { className: "w-4 h-4" });
8111
- })() }),
8176
+ })() }) : null,
8112
8177
  /* @__PURE__ */ jsx("span", { className: "relative z-10", children: label }),
8113
8178
  hotkey && /* @__PURE__ */ jsx(
8114
8179
  "span",
@@ -8124,7 +8189,7 @@ function ActionButton({
8124
8189
  }
8125
8190
  );
8126
8191
  }
8127
- var sizeMap13, variantStyles8;
8192
+ var sizeMap13, variantStyles8, DEFAULT_ASSET_URL11;
8128
8193
  var init_ActionButton = __esm({
8129
8194
  "components/game/atoms/ActionButton.tsx"() {
8130
8195
  init_cn();
@@ -8139,6 +8204,7 @@ var init_ActionButton = __esm({
8139
8204
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
8140
8205
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
8141
8206
  };
8207
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
8142
8208
  ActionButton.displayName = "ActionButton";
8143
8209
  }
8144
8210
  });
@@ -8651,10 +8717,10 @@ var init_Container = __esm({
8651
8717
  center = true,
8652
8718
  className,
8653
8719
  children,
8654
- as: Component = "div"
8720
+ as: Component2 = "div"
8655
8721
  }) => {
8656
8722
  const resolvedSize = maxWidth ?? size ?? "lg";
8657
- const Comp = Component;
8723
+ const Comp = Component2;
8658
8724
  return /* @__PURE__ */ jsx(
8659
8725
  Comp,
8660
8726
  {
@@ -24660,7 +24726,7 @@ var init_Flex = __esm({
24660
24726
  basis,
24661
24727
  className,
24662
24728
  children,
24663
- as: Component = "div"
24729
+ as: Component2 = "div"
24664
24730
  }) => {
24665
24731
  const flexStyle = {};
24666
24732
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -24672,7 +24738,7 @@ var init_Flex = __esm({
24672
24738
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
24673
24739
  }
24674
24740
  }
24675
- const Comp = Component;
24741
+ const Comp = Component2;
24676
24742
  return /* @__PURE__ */ jsx(
24677
24743
  Comp,
24678
24744
  {
@@ -24792,11 +24858,11 @@ var init_Grid = __esm({
24792
24858
  className,
24793
24859
  style,
24794
24860
  children,
24795
- as: Component = "div"
24861
+ as: Component2 = "div"
24796
24862
  }) => {
24797
24863
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
24798
24864
  return React82__default.createElement(
24799
- Component,
24865
+ Component2,
24800
24866
  {
24801
24867
  className: cn(
24802
24868
  "grid",
@@ -26622,6 +26688,7 @@ var init_ProgressDots = __esm({
26622
26688
  }
26623
26689
  });
26624
26690
  function StatBadge({
26691
+ assetUrl = DEFAULT_ASSET_URL12,
26625
26692
  label,
26626
26693
  value = 0,
26627
26694
  max,
@@ -26645,7 +26712,17 @@ function StatBadge({
26645
26712
  className
26646
26713
  ),
26647
26714
  children: [
26648
- 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" }) }),
26715
+ assetUrl ? /* @__PURE__ */ jsx(
26716
+ "img",
26717
+ {
26718
+ src: assetUrl,
26719
+ alt: "",
26720
+ width: 16,
26721
+ height: 16,
26722
+ style: { imageRendering: "pixelated", objectFit: "contain" },
26723
+ className: "flex-shrink-0"
26724
+ }
26725
+ ) : 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,
26649
26726
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-medium", children: label }),
26650
26727
  format === "hearts" && max && /* @__PURE__ */ jsx(
26651
26728
  HealthBar,
@@ -26678,7 +26755,7 @@ function StatBadge({
26678
26755
  }
26679
26756
  );
26680
26757
  }
26681
- var sizeMap17, variantMap2;
26758
+ var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
26682
26759
  var init_StatBadge = __esm({
26683
26760
  "components/game/molecules/StatBadge.tsx"() {
26684
26761
  init_cn();
@@ -26697,6 +26774,7 @@ var init_StatBadge = __esm({
26697
26774
  warning: "bg-warning/15 border-warning/40 text-foreground",
26698
26775
  danger: "bg-error/15 border-error/40 text-foreground"
26699
26776
  };
26777
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
26700
26778
  StatBadge.displayName = "StatBadge";
26701
26779
  }
26702
26780
  });
@@ -27353,6 +27431,7 @@ var init_TurnPanel = __esm({
27353
27431
  }
27354
27432
  });
27355
27433
  function EnemyPlate({
27434
+ assetUrl = DEFAULT_ASSET_URL13,
27356
27435
  name = "Shadow Guard",
27357
27436
  health = 80,
27358
27437
  maxHealth = 100,
@@ -27370,15 +27449,28 @@ function EnemyPlate({
27370
27449
  ),
27371
27450
  children: [
27372
27451
  /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
27373
- /* @__PURE__ */ jsx(
27374
- Typography,
27375
- {
27376
- variant: "caption",
27377
- weight: "bold",
27378
- className: "text-[var(--color-foreground)] truncate",
27379
- children: name
27380
- }
27381
- ),
27452
+ /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
27453
+ assetUrl && /* @__PURE__ */ jsx(
27454
+ "img",
27455
+ {
27456
+ src: assetUrl,
27457
+ alt: name,
27458
+ width: 24,
27459
+ height: 24,
27460
+ style: { objectFit: "cover", borderRadius: "50%" },
27461
+ className: "flex-shrink-0"
27462
+ }
27463
+ ),
27464
+ /* @__PURE__ */ jsx(
27465
+ Typography,
27466
+ {
27467
+ variant: "caption",
27468
+ weight: "bold",
27469
+ className: "text-[var(--color-foreground)] truncate",
27470
+ children: name
27471
+ }
27472
+ )
27473
+ ] }),
27382
27474
  level != null && /* @__PURE__ */ jsxs(Badge, { variant: "neutral", size: "sm", children: [
27383
27475
  "Lv.",
27384
27476
  level
@@ -27423,7 +27515,7 @@ function EnemyPlate({
27423
27515
  }
27424
27516
  );
27425
27517
  }
27426
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
27518
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
27427
27519
  var init_EnemyPlate = __esm({
27428
27520
  "components/game/molecules/EnemyPlate.tsx"() {
27429
27521
  "use client";
@@ -27440,6 +27532,7 @@ var init_EnemyPlate = __esm({
27440
27532
  DEFAULT_ENEMY_EFFECTS = [
27441
27533
  { icon: "flame", label: "Burn", variant: "debuff" }
27442
27534
  ];
27535
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
27443
27536
  EnemyPlate.displayName = "EnemyPlate";
27444
27537
  }
27445
27538
  });
@@ -28971,13 +29064,13 @@ var init_MapView = __esm({
28971
29064
  shadowSize: [41, 41]
28972
29065
  });
28973
29066
  L.Marker.prototype.options.icon = defaultIcon;
28974
- const { useEffect: useEffect74, useRef: useRef68, useCallback: useCallback111, useState: useState107 } = React82__default;
29067
+ const { useEffect: useEffect80, useRef: useRef74, useCallback: useCallback118, useState: useState112 } = React82__default;
28975
29068
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
28976
- const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29069
+ const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
28977
29070
  function MapUpdater({ centerLat, centerLng, zoom }) {
28978
29071
  const map = useMap();
28979
- const prevRef = useRef68({ centerLat, centerLng, zoom });
28980
- useEffect74(() => {
29072
+ const prevRef = useRef74({ centerLat, centerLng, zoom });
29073
+ useEffect80(() => {
28981
29074
  const prev = prevRef.current;
28982
29075
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
28983
29076
  map.setView([centerLat, centerLng], zoom);
@@ -28988,7 +29081,7 @@ var init_MapView = __esm({
28988
29081
  }
28989
29082
  function MapClickHandler({ onMapClick }) {
28990
29083
  const map = useMap();
28991
- useEffect74(() => {
29084
+ useEffect80(() => {
28992
29085
  if (!onMapClick) return;
28993
29086
  const handler = (e) => {
28994
29087
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -29015,9 +29108,9 @@ var init_MapView = __esm({
29015
29108
  className,
29016
29109
  showAttribution = true
29017
29110
  }) {
29018
- const eventBus = useEventBus3();
29019
- const [clickedPosition, setClickedPosition] = useState107(null);
29020
- const handleMapClick = useCallback111((lat, lng) => {
29111
+ const eventBus = useEventBus4();
29112
+ const [clickedPosition, setClickedPosition] = useState112(null);
29113
+ const handleMapClick = useCallback118((lat, lng) => {
29021
29114
  if (showClickedPin) {
29022
29115
  setClickedPosition({ lat, lng });
29023
29116
  }
@@ -29026,7 +29119,7 @@ var init_MapView = __esm({
29026
29119
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29027
29120
  }
29028
29121
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29029
- const handleMarkerClick = useCallback111((marker) => {
29122
+ const handleMarkerClick = useCallback118((marker) => {
29030
29123
  onMarkerClick?.(marker);
29031
29124
  if (markerClickEvent) {
29032
29125
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -36153,10 +36246,10 @@ var init_Section = __esm({
36153
36246
  children,
36154
36247
  headerClassName,
36155
36248
  contentClassName,
36156
- as: Component = "section"
36249
+ as: Component2 = "section"
36157
36250
  }) => {
36158
36251
  const hasHeader = title || description || action;
36159
- const Comp = Component;
36252
+ const Comp = Component2;
36160
36253
  return /* @__PURE__ */ jsxs(
36161
36254
  Comp,
36162
36255
  {
@@ -40721,6 +40814,1568 @@ var init_GameAudioToggle = __esm({
40721
40814
  GameAudioToggle.displayName = "GameAudioToggle";
40722
40815
  }
40723
40816
  });
40817
+ function detectAssetRoot(modelUrl) {
40818
+ const idx = modelUrl.indexOf("/3d/");
40819
+ if (idx !== -1) {
40820
+ return modelUrl.substring(0, idx + 4);
40821
+ }
40822
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
40823
+ }
40824
+ function createGLTFLoaderForUrl(url) {
40825
+ const loader = new GLTFLoader();
40826
+ loader.setResourcePath(detectAssetRoot(url));
40827
+ return loader;
40828
+ }
40829
+ var AssetLoader;
40830
+ var init_AssetLoader = __esm({
40831
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
40832
+ AssetLoader = class {
40833
+ constructor() {
40834
+ __publicField(this, "objLoader");
40835
+ __publicField(this, "textureLoader");
40836
+ __publicField(this, "modelCache");
40837
+ __publicField(this, "textureCache");
40838
+ __publicField(this, "loadingPromises");
40839
+ this.objLoader = new OBJLoader();
40840
+ this.textureLoader = new THREE.TextureLoader();
40841
+ this.modelCache = /* @__PURE__ */ new Map();
40842
+ this.textureCache = /* @__PURE__ */ new Map();
40843
+ this.loadingPromises = /* @__PURE__ */ new Map();
40844
+ }
40845
+ /**
40846
+ * Load a GLB/GLTF model
40847
+ * @param url - URL to the .glb or .gltf file
40848
+ * @returns Promise with loaded model scene and animations
40849
+ */
40850
+ async loadModel(url) {
40851
+ if (this.modelCache.has(url)) {
40852
+ return this.modelCache.get(url);
40853
+ }
40854
+ if (this.loadingPromises.has(url)) {
40855
+ return this.loadingPromises.get(url);
40856
+ }
40857
+ const loader = createGLTFLoaderForUrl(url);
40858
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
40859
+ const result = {
40860
+ scene: gltf.scene,
40861
+ animations: gltf.animations || []
40862
+ };
40863
+ this.modelCache.set(url, result);
40864
+ this.loadingPromises.delete(url);
40865
+ return result;
40866
+ }).catch((error) => {
40867
+ this.loadingPromises.delete(url);
40868
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
40869
+ });
40870
+ this.loadingPromises.set(url, loadPromise);
40871
+ return loadPromise;
40872
+ }
40873
+ /**
40874
+ * Load an OBJ model (fallback for non-GLB assets)
40875
+ * @param url - URL to the .obj file
40876
+ * @returns Promise with loaded object group
40877
+ */
40878
+ async loadOBJ(url) {
40879
+ if (this.modelCache.has(url)) {
40880
+ return this.modelCache.get(url).scene;
40881
+ }
40882
+ if (this.loadingPromises.has(url)) {
40883
+ const result = await this.loadingPromises.get(url);
40884
+ return result.scene;
40885
+ }
40886
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
40887
+ const result = {
40888
+ scene: group,
40889
+ animations: []
40890
+ };
40891
+ this.modelCache.set(url, result);
40892
+ this.loadingPromises.delete(url);
40893
+ return result;
40894
+ }).catch((error) => {
40895
+ this.loadingPromises.delete(url);
40896
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
40897
+ });
40898
+ this.loadingPromises.set(url, loadPromise);
40899
+ return (await loadPromise).scene;
40900
+ }
40901
+ /**
40902
+ * Load a texture
40903
+ * @param url - URL to the texture image
40904
+ * @returns Promise with loaded texture
40905
+ */
40906
+ async loadTexture(url) {
40907
+ if (this.textureCache.has(url)) {
40908
+ return this.textureCache.get(url);
40909
+ }
40910
+ if (this.loadingPromises.has(`texture:${url}`)) {
40911
+ return this.loadingPromises.get(`texture:${url}`);
40912
+ }
40913
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
40914
+ texture.colorSpace = THREE.SRGBColorSpace;
40915
+ this.textureCache.set(url, texture);
40916
+ this.loadingPromises.delete(`texture:${url}`);
40917
+ return texture;
40918
+ }).catch((error) => {
40919
+ this.loadingPromises.delete(`texture:${url}`);
40920
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
40921
+ });
40922
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
40923
+ return loadPromise;
40924
+ }
40925
+ /**
40926
+ * Preload multiple assets
40927
+ * @param urls - Array of asset URLs to preload
40928
+ * @returns Promise that resolves when all assets are loaded
40929
+ */
40930
+ async preload(urls) {
40931
+ const promises = urls.map((url) => {
40932
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
40933
+ return this.loadModel(url).catch(() => null);
40934
+ } else if (url.endsWith(".obj")) {
40935
+ return this.loadOBJ(url).catch(() => null);
40936
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
40937
+ return this.loadTexture(url).catch(() => null);
40938
+ }
40939
+ return Promise.resolve(null);
40940
+ });
40941
+ await Promise.all(promises);
40942
+ }
40943
+ /**
40944
+ * Check if a model is cached
40945
+ * @param url - Model URL
40946
+ */
40947
+ hasModel(url) {
40948
+ return this.modelCache.has(url);
40949
+ }
40950
+ /**
40951
+ * Check if a texture is cached
40952
+ * @param url - Texture URL
40953
+ */
40954
+ hasTexture(url) {
40955
+ return this.textureCache.has(url);
40956
+ }
40957
+ /**
40958
+ * Get cached model (throws if not cached)
40959
+ * @param url - Model URL
40960
+ */
40961
+ getModel(url) {
40962
+ const model = this.modelCache.get(url);
40963
+ if (!model) {
40964
+ throw new Error(`Model ${url} not in cache`);
40965
+ }
40966
+ return model;
40967
+ }
40968
+ /**
40969
+ * Get cached texture (throws if not cached)
40970
+ * @param url - Texture URL
40971
+ */
40972
+ getTexture(url) {
40973
+ const texture = this.textureCache.get(url);
40974
+ if (!texture) {
40975
+ throw new Error(`Texture ${url} not in cache`);
40976
+ }
40977
+ return texture;
40978
+ }
40979
+ /**
40980
+ * Clear all caches
40981
+ */
40982
+ clearCache() {
40983
+ this.textureCache.forEach((texture) => {
40984
+ texture.dispose();
40985
+ });
40986
+ this.modelCache.forEach((model) => {
40987
+ model.scene.traverse((child) => {
40988
+ if (child instanceof THREE.Mesh) {
40989
+ child.geometry.dispose();
40990
+ if (Array.isArray(child.material)) {
40991
+ child.material.forEach((m) => m.dispose());
40992
+ } else {
40993
+ child.material.dispose();
40994
+ }
40995
+ }
40996
+ });
40997
+ });
40998
+ this.modelCache.clear();
40999
+ this.textureCache.clear();
41000
+ this.loadingPromises.clear();
41001
+ }
41002
+ /**
41003
+ * Get cache statistics
41004
+ */
41005
+ getStats() {
41006
+ return {
41007
+ models: this.modelCache.size,
41008
+ textures: this.textureCache.size,
41009
+ loading: this.loadingPromises.size
41010
+ };
41011
+ }
41012
+ };
41013
+ new AssetLoader();
41014
+ }
41015
+ });
41016
+ function useAssetLoader(options = {}) {
41017
+ const { preloadUrls = [], loader: customLoader } = options;
41018
+ const loaderRef = useRef(customLoader || new AssetLoader());
41019
+ const [state, setState] = useState({
41020
+ isLoading: false,
41021
+ progress: 0,
41022
+ loaded: 0,
41023
+ total: 0,
41024
+ errors: []
41025
+ });
41026
+ useEffect(() => {
41027
+ if (preloadUrls.length > 0) {
41028
+ preload(preloadUrls);
41029
+ }
41030
+ }, []);
41031
+ const updateProgress = useCallback((loaded, total) => {
41032
+ setState((prev) => ({
41033
+ ...prev,
41034
+ loaded,
41035
+ total,
41036
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
41037
+ }));
41038
+ }, []);
41039
+ const loadModel = useCallback(
41040
+ async (url) => {
41041
+ setState((prev) => ({ ...prev, isLoading: true }));
41042
+ try {
41043
+ const model = await loaderRef.current.loadModel(url);
41044
+ setState((prev) => ({
41045
+ ...prev,
41046
+ isLoading: false,
41047
+ loaded: prev.loaded + 1
41048
+ }));
41049
+ return model;
41050
+ } catch (error) {
41051
+ const errorMsg = error instanceof Error ? error.message : String(error);
41052
+ setState((prev) => ({
41053
+ ...prev,
41054
+ isLoading: false,
41055
+ errors: [...prev.errors, errorMsg]
41056
+ }));
41057
+ throw error;
41058
+ }
41059
+ },
41060
+ []
41061
+ );
41062
+ const loadOBJ = useCallback(
41063
+ async (url) => {
41064
+ setState((prev) => ({ ...prev, isLoading: true }));
41065
+ try {
41066
+ const model = await loaderRef.current.loadOBJ(url);
41067
+ setState((prev) => ({
41068
+ ...prev,
41069
+ isLoading: false,
41070
+ loaded: prev.loaded + 1
41071
+ }));
41072
+ return model;
41073
+ } catch (error) {
41074
+ const errorMsg = error instanceof Error ? error.message : String(error);
41075
+ setState((prev) => ({
41076
+ ...prev,
41077
+ isLoading: false,
41078
+ errors: [...prev.errors, errorMsg]
41079
+ }));
41080
+ throw error;
41081
+ }
41082
+ },
41083
+ []
41084
+ );
41085
+ const loadTexture = useCallback(
41086
+ async (url) => {
41087
+ setState((prev) => ({ ...prev, isLoading: true }));
41088
+ try {
41089
+ const texture = await loaderRef.current.loadTexture(url);
41090
+ setState((prev) => ({
41091
+ ...prev,
41092
+ isLoading: false,
41093
+ loaded: prev.loaded + 1
41094
+ }));
41095
+ return texture;
41096
+ } catch (error) {
41097
+ const errorMsg = error instanceof Error ? error.message : String(error);
41098
+ setState((prev) => ({
41099
+ ...prev,
41100
+ isLoading: false,
41101
+ errors: [...prev.errors, errorMsg]
41102
+ }));
41103
+ throw error;
41104
+ }
41105
+ },
41106
+ []
41107
+ );
41108
+ const preload = useCallback(
41109
+ async (urls) => {
41110
+ setState((prev) => ({
41111
+ ...prev,
41112
+ isLoading: true,
41113
+ total: urls.length,
41114
+ loaded: 0,
41115
+ errors: []
41116
+ }));
41117
+ let completed = 0;
41118
+ const errors = [];
41119
+ await Promise.all(
41120
+ urls.map(async (url) => {
41121
+ try {
41122
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41123
+ await loaderRef.current.loadModel(url);
41124
+ } else if (url.endsWith(".obj")) {
41125
+ await loaderRef.current.loadOBJ(url);
41126
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41127
+ await loaderRef.current.loadTexture(url);
41128
+ }
41129
+ completed++;
41130
+ updateProgress(completed, urls.length);
41131
+ } catch (error) {
41132
+ const errorMsg = error instanceof Error ? error.message : String(error);
41133
+ errors.push(`${url}: ${errorMsg}`);
41134
+ completed++;
41135
+ updateProgress(completed, urls.length);
41136
+ }
41137
+ })
41138
+ );
41139
+ setState((prev) => ({
41140
+ ...prev,
41141
+ isLoading: false,
41142
+ errors
41143
+ }));
41144
+ },
41145
+ [updateProgress]
41146
+ );
41147
+ const hasModel = useCallback((url) => {
41148
+ return loaderRef.current.hasModel(url);
41149
+ }, []);
41150
+ const hasTexture = useCallback((url) => {
41151
+ return loaderRef.current.hasTexture(url);
41152
+ }, []);
41153
+ const getModel = useCallback((url) => {
41154
+ try {
41155
+ return loaderRef.current.getModel(url);
41156
+ } catch {
41157
+ return void 0;
41158
+ }
41159
+ }, []);
41160
+ const getTexture = useCallback((url) => {
41161
+ try {
41162
+ return loaderRef.current.getTexture(url);
41163
+ } catch {
41164
+ return void 0;
41165
+ }
41166
+ }, []);
41167
+ const clearCache = useCallback(() => {
41168
+ loaderRef.current.clearCache();
41169
+ setState({
41170
+ isLoading: false,
41171
+ progress: 0,
41172
+ loaded: 0,
41173
+ total: 0,
41174
+ errors: []
41175
+ });
41176
+ }, []);
41177
+ return {
41178
+ ...state,
41179
+ loadModel,
41180
+ loadOBJ,
41181
+ loadTexture,
41182
+ preload,
41183
+ hasModel,
41184
+ hasTexture,
41185
+ getModel,
41186
+ getTexture,
41187
+ clearCache
41188
+ };
41189
+ }
41190
+ var init_useAssetLoader = __esm({
41191
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
41192
+ "use client";
41193
+ init_AssetLoader();
41194
+ }
41195
+ });
41196
+ function useGameCanvas3DEvents(options) {
41197
+ const {
41198
+ tileClickEvent,
41199
+ unitClickEvent,
41200
+ featureClickEvent,
41201
+ canvasClickEvent,
41202
+ tileHoverEvent,
41203
+ tileLeaveEvent,
41204
+ unitAnimationEvent,
41205
+ cameraChangeEvent,
41206
+ onTileClick,
41207
+ onUnitClick,
41208
+ onFeatureClick,
41209
+ onCanvasClick,
41210
+ onTileHover,
41211
+ onUnitAnimation
41212
+ } = options;
41213
+ const emit = useEmitEvent();
41214
+ const optionsRef = useRef(options);
41215
+ optionsRef.current = options;
41216
+ const handleTileClick = useCallback(
41217
+ (tile, event) => {
41218
+ if (tileClickEvent) {
41219
+ emit(tileClickEvent, {
41220
+ tileId: tile.id,
41221
+ x: tile.x,
41222
+ z: tile.z ?? tile.y ?? 0,
41223
+ type: tile.type,
41224
+ terrain: tile.terrain,
41225
+ elevation: tile.elevation
41226
+ });
41227
+ }
41228
+ optionsRef.current.onTileClick?.(tile, event);
41229
+ },
41230
+ [tileClickEvent, emit]
41231
+ );
41232
+ const handleUnitClick = useCallback(
41233
+ (unit, event) => {
41234
+ if (unitClickEvent) {
41235
+ emit(unitClickEvent, {
41236
+ unitId: unit.id,
41237
+ x: unit.x,
41238
+ z: unit.z ?? unit.y ?? 0,
41239
+ unitType: unit.unitType,
41240
+ name: unit.name,
41241
+ team: unit.team,
41242
+ faction: unit.faction,
41243
+ health: unit.health,
41244
+ maxHealth: unit.maxHealth
41245
+ });
41246
+ }
41247
+ optionsRef.current.onUnitClick?.(unit, event);
41248
+ },
41249
+ [unitClickEvent, emit]
41250
+ );
41251
+ const handleFeatureClick = useCallback(
41252
+ (feature, event) => {
41253
+ if (featureClickEvent) {
41254
+ emit(featureClickEvent, {
41255
+ featureId: feature.id,
41256
+ x: feature.x,
41257
+ z: feature.z ?? feature.y ?? 0,
41258
+ type: feature.type,
41259
+ elevation: feature.elevation
41260
+ });
41261
+ }
41262
+ optionsRef.current.onFeatureClick?.(feature, event);
41263
+ },
41264
+ [featureClickEvent, emit]
41265
+ );
41266
+ const handleCanvasClick = useCallback(
41267
+ (event) => {
41268
+ if (canvasClickEvent) {
41269
+ emit(canvasClickEvent, {
41270
+ clientX: event.clientX,
41271
+ clientY: event.clientY,
41272
+ button: event.button
41273
+ });
41274
+ }
41275
+ optionsRef.current.onCanvasClick?.(event);
41276
+ },
41277
+ [canvasClickEvent, emit]
41278
+ );
41279
+ const handleTileHover = useCallback(
41280
+ (tile, event) => {
41281
+ if (tile) {
41282
+ if (tileHoverEvent) {
41283
+ emit(tileHoverEvent, {
41284
+ tileId: tile.id,
41285
+ x: tile.x,
41286
+ z: tile.z ?? tile.y ?? 0,
41287
+ type: tile.type
41288
+ });
41289
+ }
41290
+ } else {
41291
+ if (tileLeaveEvent) {
41292
+ emit(tileLeaveEvent, {});
41293
+ }
41294
+ }
41295
+ optionsRef.current.onTileHover?.(tile, event);
41296
+ },
41297
+ [tileHoverEvent, tileLeaveEvent, emit]
41298
+ );
41299
+ const handleUnitAnimation = useCallback(
41300
+ (unitId, state) => {
41301
+ if (unitAnimationEvent) {
41302
+ emit(unitAnimationEvent, {
41303
+ unitId,
41304
+ state,
41305
+ timestamp: Date.now()
41306
+ });
41307
+ }
41308
+ optionsRef.current.onUnitAnimation?.(unitId, state);
41309
+ },
41310
+ [unitAnimationEvent, emit]
41311
+ );
41312
+ const handleCameraChange = useCallback(
41313
+ (position) => {
41314
+ if (cameraChangeEvent) {
41315
+ emit(cameraChangeEvent, {
41316
+ position,
41317
+ timestamp: Date.now()
41318
+ });
41319
+ }
41320
+ },
41321
+ [cameraChangeEvent, emit]
41322
+ );
41323
+ return {
41324
+ handleTileClick,
41325
+ handleUnitClick,
41326
+ handleFeatureClick,
41327
+ handleCanvasClick,
41328
+ handleTileHover,
41329
+ handleUnitAnimation,
41330
+ handleCameraChange
41331
+ };
41332
+ }
41333
+ var init_useGameCanvas3DEvents = __esm({
41334
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
41335
+ "use client";
41336
+ init_useEventBus();
41337
+ }
41338
+ });
41339
+
41340
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
41341
+ var init_Canvas3DLoadingState = __esm({
41342
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
41343
+ }
41344
+ });
41345
+ function Canvas3DLoadingState({
41346
+ progress = 0,
41347
+ loaded = 0,
41348
+ total = 0,
41349
+ message = "Loading 3D Scene...",
41350
+ details,
41351
+ showSpinner = true,
41352
+ className
41353
+ }) {
41354
+ const clampedProgress = Math.max(0, Math.min(100, progress));
41355
+ const hasProgress = total > 0;
41356
+ return /* @__PURE__ */ jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
41357
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__content", children: [
41358
+ showSpinner && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__spinner", children: [
41359
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring" }),
41360
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring spinner__ring--secondary" })
41361
+ ] }),
41362
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__message", children: message }),
41363
+ details && /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__details", children: details }),
41364
+ hasProgress && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__progress", children: [
41365
+ /* @__PURE__ */ jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsx(
41366
+ "div",
41367
+ {
41368
+ className: "progress__fill",
41369
+ style: { width: `${clampedProgress}%` }
41370
+ }
41371
+ ) }),
41372
+ /* @__PURE__ */ jsxs("div", { className: "progress__text", children: [
41373
+ /* @__PURE__ */ jsxs("span", { className: "progress__percentage", children: [
41374
+ clampedProgress,
41375
+ "%"
41376
+ ] }),
41377
+ /* @__PURE__ */ jsxs("span", { className: "progress__count", children: [
41378
+ "(",
41379
+ loaded,
41380
+ "/",
41381
+ total,
41382
+ ")"
41383
+ ] })
41384
+ ] })
41385
+ ] })
41386
+ ] }),
41387
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsx("div", { className: "bg__grid" }) })
41388
+ ] });
41389
+ }
41390
+ var init_Canvas3DLoadingState2 = __esm({
41391
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
41392
+ init_Canvas3DLoadingState();
41393
+ }
41394
+ });
41395
+
41396
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
41397
+ var init_Canvas3DErrorBoundary = __esm({
41398
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
41399
+ }
41400
+ });
41401
+ var log10, Canvas3DErrorBoundary;
41402
+ var init_Canvas3DErrorBoundary2 = __esm({
41403
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
41404
+ init_Canvas3DErrorBoundary();
41405
+ log10 = createLogger("almadar:ui:game:canvas3d:error-boundary");
41406
+ Canvas3DErrorBoundary = class extends Component {
41407
+ constructor(props) {
41408
+ super(props);
41409
+ __publicField(this, "handleReset", () => {
41410
+ this.setState({
41411
+ hasError: false,
41412
+ error: null,
41413
+ errorInfo: null
41414
+ });
41415
+ this.props.onReset?.();
41416
+ });
41417
+ this.state = {
41418
+ hasError: false,
41419
+ error: null,
41420
+ errorInfo: null
41421
+ };
41422
+ }
41423
+ static getDerivedStateFromError(error) {
41424
+ return {
41425
+ hasError: true,
41426
+ error,
41427
+ errorInfo: null
41428
+ };
41429
+ }
41430
+ componentDidCatch(error, errorInfo) {
41431
+ this.setState({ errorInfo });
41432
+ this.props.onError?.(error, errorInfo);
41433
+ log10.error("Error caught", { error });
41434
+ log10.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
41435
+ }
41436
+ render() {
41437
+ if (this.state.hasError) {
41438
+ if (this.props.fallback) {
41439
+ return this.props.fallback;
41440
+ }
41441
+ return /* @__PURE__ */ jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__content", children: [
41442
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
41443
+ /* @__PURE__ */ jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
41444
+ /* @__PURE__ */ jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
41445
+ this.state.error && /* @__PURE__ */ jsxs("details", { className: "canvas-3d-error__details", children: [
41446
+ /* @__PURE__ */ jsx("summary", { children: "Error Details" }),
41447
+ /* @__PURE__ */ jsxs("pre", { className: "error__stack", children: [
41448
+ this.state.error.message,
41449
+ "\n",
41450
+ this.state.error.stack
41451
+ ] }),
41452
+ this.state.errorInfo && /* @__PURE__ */ jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
41453
+ ] }),
41454
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__actions", children: [
41455
+ /* @__PURE__ */ jsx(
41456
+ "button",
41457
+ {
41458
+ className: "error__button error__button--primary",
41459
+ onClick: this.handleReset,
41460
+ children: "Try Again"
41461
+ }
41462
+ ),
41463
+ /* @__PURE__ */ jsx(
41464
+ "button",
41465
+ {
41466
+ className: "error__button error__button--secondary",
41467
+ onClick: () => window.location.reload(),
41468
+ children: "Reload Page"
41469
+ }
41470
+ )
41471
+ ] })
41472
+ ] }) });
41473
+ }
41474
+ return this.props.children;
41475
+ }
41476
+ };
41477
+ }
41478
+ });
41479
+ function detectAssetRoot2(modelUrl) {
41480
+ const idx = modelUrl.indexOf("/3d/");
41481
+ if (idx !== -1) {
41482
+ return modelUrl.substring(0, idx + 4);
41483
+ }
41484
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41485
+ }
41486
+ function useGLTFModel(url, resourceBasePath) {
41487
+ const [state, setState] = useState({
41488
+ model: null,
41489
+ isLoading: false,
41490
+ error: null
41491
+ });
41492
+ useEffect(() => {
41493
+ if (!url) {
41494
+ setState({ model: null, isLoading: false, error: null });
41495
+ return;
41496
+ }
41497
+ log11.debug("Loading", { url });
41498
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
41499
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
41500
+ const loader = new GLTFLoader$1();
41501
+ loader.setResourcePath(assetRoot);
41502
+ loader.load(
41503
+ url,
41504
+ (gltf) => {
41505
+ log11.debug("Loaded", { url });
41506
+ setState({
41507
+ model: gltf.scene,
41508
+ isLoading: false,
41509
+ error: null
41510
+ });
41511
+ },
41512
+ void 0,
41513
+ (err) => {
41514
+ log11.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
41515
+ setState({
41516
+ model: null,
41517
+ isLoading: false,
41518
+ error: err instanceof Error ? err : new Error(String(err))
41519
+ });
41520
+ }
41521
+ );
41522
+ }, [url, resourceBasePath]);
41523
+ return state;
41524
+ }
41525
+ function ModelLoader({
41526
+ url,
41527
+ position = [0, 0, 0],
41528
+ scale = 1,
41529
+ rotation = [0, 0, 0],
41530
+ isSelected = false,
41531
+ isHovered = false,
41532
+ onClick,
41533
+ onHover,
41534
+ fallbackGeometry = "box",
41535
+ castShadow = true,
41536
+ receiveShadow = true,
41537
+ resourceBasePath
41538
+ }) {
41539
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
41540
+ const model = useMemo(() => {
41541
+ if (!loadedModel) return null;
41542
+ const cloned = loadedModel.clone();
41543
+ cloned.traverse((child) => {
41544
+ if (child instanceof THREE.Mesh) {
41545
+ child.castShadow = castShadow;
41546
+ child.receiveShadow = receiveShadow;
41547
+ }
41548
+ });
41549
+ return cloned;
41550
+ }, [loadedModel, castShadow, receiveShadow]);
41551
+ const scaleArray = useMemo(() => {
41552
+ if (typeof scale === "number") {
41553
+ return [scale, scale, scale];
41554
+ }
41555
+ return scale;
41556
+ }, [scale]);
41557
+ const rotationRad = useMemo(() => {
41558
+ return [
41559
+ rotation[0] * Math.PI / 180,
41560
+ rotation[1] * Math.PI / 180,
41561
+ rotation[2] * Math.PI / 180
41562
+ ];
41563
+ }, [rotation]);
41564
+ if (isLoading) {
41565
+ return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
41566
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
41567
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
41568
+ ] }) });
41569
+ }
41570
+ if (error || !model) {
41571
+ if (fallbackGeometry === "none") {
41572
+ return /* @__PURE__ */ jsx("group", { position });
41573
+ }
41574
+ const fallbackProps = {
41575
+ onClick,
41576
+ onPointerOver: () => onHover?.(true),
41577
+ onPointerOut: () => onHover?.(false)
41578
+ };
41579
+ return /* @__PURE__ */ jsxs("group", { position, children: [
41580
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
41581
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
41582
+ /* @__PURE__ */ jsx(
41583
+ "meshBasicMaterial",
41584
+ {
41585
+ color: isSelected ? 16755200 : 16777215,
41586
+ transparent: true,
41587
+ opacity: 0.5
41588
+ }
41589
+ )
41590
+ ] }),
41591
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
41592
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
41593
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
41594
+ ] }),
41595
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
41596
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.4, 16, 16] }),
41597
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
41598
+ ] }),
41599
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
41600
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
41601
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
41602
+ ] })
41603
+ ] });
41604
+ }
41605
+ return /* @__PURE__ */ jsxs(
41606
+ "group",
41607
+ {
41608
+ position,
41609
+ rotation: rotationRad,
41610
+ onClick,
41611
+ onPointerOver: () => onHover?.(true),
41612
+ onPointerOut: () => onHover?.(false),
41613
+ children: [
41614
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
41615
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
41616
+ /* @__PURE__ */ jsx(
41617
+ "meshBasicMaterial",
41618
+ {
41619
+ color: isSelected ? 16755200 : 16777215,
41620
+ transparent: true,
41621
+ opacity: 0.5
41622
+ }
41623
+ )
41624
+ ] }),
41625
+ /* @__PURE__ */ jsx("primitive", { object: model, scale: scaleArray })
41626
+ ]
41627
+ }
41628
+ );
41629
+ }
41630
+ var log11;
41631
+ var init_ModelLoader = __esm({
41632
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
41633
+ "use client";
41634
+ log11 = createLogger("almadar:ui:game:model-loader");
41635
+ }
41636
+ });
41637
+
41638
+ // components/game/molecules/GameCanvas3D.css
41639
+ var init_GameCanvas3D = __esm({
41640
+ "components/game/molecules/GameCanvas3D.css"() {
41641
+ }
41642
+ });
41643
+ function CameraController({
41644
+ onCameraChange
41645
+ }) {
41646
+ const { camera } = useThree();
41647
+ useEffect(() => {
41648
+ if (onCameraChange) {
41649
+ onCameraChange({
41650
+ x: camera.position.x,
41651
+ y: camera.position.y,
41652
+ z: camera.position.z
41653
+ });
41654
+ }
41655
+ }, [camera.position, onCameraChange]);
41656
+ return null;
41657
+ }
41658
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
41659
+ var init_GameCanvas3D2 = __esm({
41660
+ "components/game/molecules/GameCanvas3D.tsx"() {
41661
+ "use client";
41662
+ init_AssetLoader();
41663
+ init_useAssetLoader();
41664
+ init_useGameCanvas3DEvents();
41665
+ init_Canvas3DLoadingState2();
41666
+ init_Canvas3DErrorBoundary2();
41667
+ init_ModelLoader();
41668
+ init_cn();
41669
+ init_GameCanvas3D();
41670
+ DEFAULT_GRID_CONFIG = {
41671
+ cellSize: 1,
41672
+ offsetX: 0,
41673
+ offsetZ: 0
41674
+ };
41675
+ GameCanvas3D = forwardRef(
41676
+ ({
41677
+ tiles = [],
41678
+ units = [],
41679
+ features = [],
41680
+ events: events2 = [],
41681
+ orientation = "standard",
41682
+ cameraMode = "isometric",
41683
+ showGrid = true,
41684
+ showCoordinates = false,
41685
+ showTileInfo = false,
41686
+ overlay = "default",
41687
+ shadows = true,
41688
+ backgroundColor = "#1a1a2e",
41689
+ onTileClick,
41690
+ onUnitClick,
41691
+ onFeatureClick,
41692
+ onCanvasClick,
41693
+ onTileHover,
41694
+ onUnitAnimation,
41695
+ assetLoader: customAssetLoader,
41696
+ tileRenderer: CustomTileRenderer,
41697
+ unitRenderer: CustomUnitRenderer,
41698
+ featureRenderer: CustomFeatureRenderer,
41699
+ className,
41700
+ isLoading: externalLoading,
41701
+ error: externalError,
41702
+ entity,
41703
+ preloadAssets = [],
41704
+ tileClickEvent,
41705
+ unitClickEvent,
41706
+ featureClickEvent,
41707
+ canvasClickEvent,
41708
+ tileHoverEvent,
41709
+ tileLeaveEvent,
41710
+ unitAnimationEvent,
41711
+ cameraChangeEvent,
41712
+ loadingMessage = "Loading 3D Scene...",
41713
+ useInstancing = true,
41714
+ validMoves = [],
41715
+ attackTargets = [],
41716
+ selectedTileIds = [],
41717
+ selectedUnitId = null,
41718
+ children
41719
+ }, ref) => {
41720
+ const containerRef = useRef(null);
41721
+ const controlsRef = useRef(null);
41722
+ const [hoveredTile, setHoveredTile] = useState(null);
41723
+ const [internalError, setInternalError] = useState(null);
41724
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
41725
+ preloadUrls: preloadAssets,
41726
+ loader: customAssetLoader
41727
+ });
41728
+ const eventHandlers = useGameCanvas3DEvents({
41729
+ tileClickEvent,
41730
+ unitClickEvent,
41731
+ featureClickEvent,
41732
+ canvasClickEvent,
41733
+ tileHoverEvent,
41734
+ tileLeaveEvent,
41735
+ unitAnimationEvent,
41736
+ cameraChangeEvent,
41737
+ onTileClick,
41738
+ onUnitClick,
41739
+ onFeatureClick,
41740
+ onCanvasClick,
41741
+ onTileHover,
41742
+ onUnitAnimation
41743
+ });
41744
+ const gridBounds = useMemo(() => {
41745
+ if (tiles.length === 0) {
41746
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
41747
+ }
41748
+ const xs = tiles.map((t) => t.x);
41749
+ const zs = tiles.map((t) => t.z || t.y || 0);
41750
+ return {
41751
+ minX: Math.min(...xs),
41752
+ maxX: Math.max(...xs),
41753
+ minZ: Math.min(...zs),
41754
+ maxZ: Math.max(...zs)
41755
+ };
41756
+ }, [tiles]);
41757
+ const cameraTarget = useMemo(() => {
41758
+ return [
41759
+ (gridBounds.minX + gridBounds.maxX) / 2,
41760
+ 0,
41761
+ (gridBounds.minZ + gridBounds.maxZ) / 2
41762
+ ];
41763
+ }, [gridBounds]);
41764
+ const gridConfig = useMemo(
41765
+ () => ({
41766
+ ...DEFAULT_GRID_CONFIG,
41767
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
41768
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
41769
+ }),
41770
+ [gridBounds]
41771
+ );
41772
+ const gridToWorld = useCallback(
41773
+ (x, z, y = 0) => {
41774
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
41775
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
41776
+ return [worldX, y * gridConfig.cellSize, worldZ];
41777
+ },
41778
+ [gridBounds, gridConfig]
41779
+ );
41780
+ useImperativeHandle(ref, () => ({
41781
+ getCameraPosition: () => {
41782
+ if (controlsRef.current) {
41783
+ const pos = controlsRef.current.object.position;
41784
+ return new THREE.Vector3(pos.x, pos.y, pos.z);
41785
+ }
41786
+ return null;
41787
+ },
41788
+ setCameraPosition: (x, y, z) => {
41789
+ if (controlsRef.current) {
41790
+ controlsRef.current.object.position.set(x, y, z);
41791
+ controlsRef.current.update();
41792
+ }
41793
+ },
41794
+ lookAt: (x, y, z) => {
41795
+ if (controlsRef.current) {
41796
+ controlsRef.current.target.set(x, y, z);
41797
+ controlsRef.current.update();
41798
+ }
41799
+ },
41800
+ resetCamera: () => {
41801
+ if (controlsRef.current) {
41802
+ controlsRef.current.reset();
41803
+ }
41804
+ },
41805
+ screenshot: () => {
41806
+ const canvas = containerRef.current?.querySelector("canvas");
41807
+ if (canvas) {
41808
+ return canvas.toDataURL("image/png");
41809
+ }
41810
+ return null;
41811
+ },
41812
+ export: () => ({
41813
+ tiles,
41814
+ units,
41815
+ features
41816
+ })
41817
+ }));
41818
+ const handleTileClick = useCallback(
41819
+ (tile, event) => {
41820
+ eventHandlers.handleTileClick(tile, event);
41821
+ },
41822
+ [eventHandlers]
41823
+ );
41824
+ const handleUnitClick = useCallback(
41825
+ (unit, event) => {
41826
+ eventHandlers.handleUnitClick(unit, event);
41827
+ },
41828
+ [eventHandlers]
41829
+ );
41830
+ const handleFeatureClick = useCallback(
41831
+ (feature, event) => {
41832
+ if (event) {
41833
+ eventHandlers.handleFeatureClick(feature, event);
41834
+ }
41835
+ },
41836
+ [eventHandlers]
41837
+ );
41838
+ const handleTileHover = useCallback(
41839
+ (tile, event) => {
41840
+ setHoveredTile(tile);
41841
+ if (event) {
41842
+ eventHandlers.handleTileHover(tile, event);
41843
+ }
41844
+ },
41845
+ [eventHandlers]
41846
+ );
41847
+ const cameraConfig = useMemo(() => {
41848
+ const size = Math.max(
41849
+ gridBounds.maxX - gridBounds.minX,
41850
+ gridBounds.maxZ - gridBounds.minZ
41851
+ );
41852
+ const distance = size * 1.5;
41853
+ switch (cameraMode) {
41854
+ case "isometric":
41855
+ return {
41856
+ position: [distance, distance * 0.8, distance],
41857
+ fov: 45
41858
+ };
41859
+ case "top-down":
41860
+ return {
41861
+ position: [0, distance * 2, 0],
41862
+ fov: 45
41863
+ };
41864
+ case "perspective":
41865
+ default:
41866
+ return {
41867
+ position: [distance, distance, distance],
41868
+ fov: 45
41869
+ };
41870
+ }
41871
+ }, [cameraMode, gridBounds]);
41872
+ const DefaultTileRenderer = useCallback(
41873
+ ({ tile, position }) => {
41874
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
41875
+ const isHovered = hoveredTile?.id === tile.id;
41876
+ const isValidMove = validMoves.some(
41877
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
41878
+ );
41879
+ const isAttackTarget = attackTargets.some(
41880
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
41881
+ );
41882
+ let color = 8421504;
41883
+ if (tile.type === "water") color = 4491468;
41884
+ else if (tile.type === "grass") color = 4500036;
41885
+ else if (tile.type === "sand") color = 14535816;
41886
+ else if (tile.type === "rock") color = 8947848;
41887
+ else if (tile.type === "snow") color = 15658734;
41888
+ let emissive = 0;
41889
+ if (isSelected) emissive = 4473924;
41890
+ else if (isAttackTarget) emissive = 4456448;
41891
+ else if (isValidMove) emissive = 17408;
41892
+ else if (isHovered) emissive = 2236962;
41893
+ if (tile.modelUrl) {
41894
+ return /* @__PURE__ */ jsx(
41895
+ "group",
41896
+ {
41897
+ position,
41898
+ onClick: (e) => handleTileClick(tile, e),
41899
+ onPointerEnter: (e) => handleTileHover(tile, e),
41900
+ onPointerLeave: (e) => handleTileHover(null, e),
41901
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
41902
+ children: /* @__PURE__ */ jsx(
41903
+ ModelLoader,
41904
+ {
41905
+ url: tile.modelUrl,
41906
+ scale: 0.95,
41907
+ fallbackGeometry: "box",
41908
+ castShadow: true,
41909
+ receiveShadow: true
41910
+ }
41911
+ )
41912
+ }
41913
+ );
41914
+ }
41915
+ return /* @__PURE__ */ jsxs(
41916
+ "mesh",
41917
+ {
41918
+ position,
41919
+ onClick: (e) => handleTileClick(tile, e),
41920
+ onPointerEnter: (e) => handleTileHover(tile, e),
41921
+ onPointerLeave: (e) => handleTileHover(null, e),
41922
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
41923
+ children: [
41924
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
41925
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color, emissive })
41926
+ ]
41927
+ }
41928
+ );
41929
+ },
41930
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
41931
+ );
41932
+ const DefaultUnitRenderer = useCallback(
41933
+ ({ unit, position }) => {
41934
+ const isSelected = selectedUnitId === unit.id;
41935
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
41936
+ return /* @__PURE__ */ jsxs(
41937
+ "group",
41938
+ {
41939
+ position,
41940
+ onClick: (e) => handleUnitClick(unit, e),
41941
+ userData: { type: "unit", unitId: unit.id },
41942
+ children: [
41943
+ isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
41944
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
41945
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
41946
+ ] }),
41947
+ unit.modelUrl ? (
41948
+ /* GLB unit model (box fallback while loading / on error) */
41949
+ /* @__PURE__ */ jsx(
41950
+ ModelLoader,
41951
+ {
41952
+ url: unit.modelUrl,
41953
+ scale: 0.5,
41954
+ fallbackGeometry: "box",
41955
+ castShadow: true
41956
+ }
41957
+ )
41958
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
41959
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.3, 0], children: [
41960
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
41961
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
41962
+ ] }),
41963
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.6, 0], children: [
41964
+ /* @__PURE__ */ jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
41965
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
41966
+ ] }),
41967
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
41968
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
41969
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
41970
+ ] })
41971
+ ] }),
41972
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxs("group", { position: [0, 1.2, 0], children: [
41973
+ /* @__PURE__ */ jsxs("mesh", { position: [-0.25, 0, 0], children: [
41974
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.05] }),
41975
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: 3355443 })
41976
+ ] }),
41977
+ /* @__PURE__ */ jsxs(
41978
+ "mesh",
41979
+ {
41980
+ position: [
41981
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
41982
+ 0,
41983
+ 0.01
41984
+ ],
41985
+ children: [
41986
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
41987
+ /* @__PURE__ */ jsx(
41988
+ "meshBasicMaterial",
41989
+ {
41990
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
41991
+ }
41992
+ )
41993
+ ]
41994
+ }
41995
+ )
41996
+ ] })
41997
+ ]
41998
+ }
41999
+ );
42000
+ },
42001
+ [selectedUnitId, handleUnitClick]
42002
+ );
42003
+ const DefaultFeatureRenderer = useCallback(
42004
+ ({
42005
+ feature,
42006
+ position
42007
+ }) => {
42008
+ if (feature.assetUrl) {
42009
+ return /* @__PURE__ */ jsx(
42010
+ ModelLoader,
42011
+ {
42012
+ url: feature.assetUrl,
42013
+ position,
42014
+ scale: 0.5,
42015
+ rotation: [0, feature.rotation ?? 0, 0],
42016
+ onClick: () => handleFeatureClick(feature, null),
42017
+ fallbackGeometry: "box"
42018
+ },
42019
+ feature.id
42020
+ );
42021
+ }
42022
+ if (feature.type === "tree") {
42023
+ return /* @__PURE__ */ jsxs(
42024
+ "group",
42025
+ {
42026
+ position,
42027
+ onClick: (e) => handleFeatureClick(feature, e),
42028
+ userData: { type: "feature", featureId: feature.id },
42029
+ children: [
42030
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.4, 0], children: [
42031
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
42032
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
42033
+ ] }),
42034
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
42035
+ /* @__PURE__ */ jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
42036
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 2263842 })
42037
+ ] })
42038
+ ]
42039
+ }
42040
+ );
42041
+ }
42042
+ if (feature.type === "rock") {
42043
+ return /* @__PURE__ */ jsxs(
42044
+ "mesh",
42045
+ {
42046
+ position: [position[0], position[1] + 0.3, position[2]],
42047
+ onClick: (e) => handleFeatureClick(feature, e),
42048
+ userData: { type: "feature", featureId: feature.id },
42049
+ children: [
42050
+ /* @__PURE__ */ jsx("dodecahedronGeometry", { args: [0.3, 0] }),
42051
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 8421504 })
42052
+ ]
42053
+ }
42054
+ );
42055
+ }
42056
+ return null;
42057
+ },
42058
+ [handleFeatureClick]
42059
+ );
42060
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
42061
+ return /* @__PURE__ */ jsx(
42062
+ Canvas3DLoadingState,
42063
+ {
42064
+ progress,
42065
+ loaded,
42066
+ total,
42067
+ message: loadingMessage,
42068
+ className
42069
+ }
42070
+ );
42071
+ }
42072
+ const displayError = externalError || internalError;
42073
+ if (displayError) {
42074
+ 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: [
42075
+ "Error: ",
42076
+ displayError
42077
+ ] }) }) });
42078
+ }
42079
+ return /* @__PURE__ */ jsx(
42080
+ Canvas3DErrorBoundary,
42081
+ {
42082
+ onError: (err) => setInternalError(err.message),
42083
+ onReset: () => setInternalError(null),
42084
+ children: /* @__PURE__ */ jsxs(
42085
+ "div",
42086
+ {
42087
+ ref: containerRef,
42088
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
42089
+ "data-orientation": orientation,
42090
+ "data-camera-mode": cameraMode,
42091
+ "data-overlay": overlay,
42092
+ children: [
42093
+ /* @__PURE__ */ jsxs(
42094
+ Canvas,
42095
+ {
42096
+ shadows,
42097
+ camera: {
42098
+ position: cameraConfig.position,
42099
+ fov: cameraConfig.fov,
42100
+ near: 0.1,
42101
+ far: 1e3
42102
+ },
42103
+ style: { background: backgroundColor },
42104
+ onClick: (e) => {
42105
+ if (e.target === e.currentTarget) {
42106
+ eventHandlers.handleCanvasClick(e);
42107
+ }
42108
+ },
42109
+ children: [
42110
+ /* @__PURE__ */ jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
42111
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 0.6 }),
42112
+ /* @__PURE__ */ jsx(
42113
+ "directionalLight",
42114
+ {
42115
+ position: [10, 20, 10],
42116
+ intensity: 0.8,
42117
+ castShadow: shadows,
42118
+ "shadow-mapSize": [2048, 2048]
42119
+ }
42120
+ ),
42121
+ /* @__PURE__ */ jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
42122
+ showGrid && /* @__PURE__ */ jsx(
42123
+ Grid$1,
42124
+ {
42125
+ args: [
42126
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
42127
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
42128
+ ],
42129
+ position: [
42130
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
42131
+ 0,
42132
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
42133
+ ],
42134
+ cellSize: 1,
42135
+ cellThickness: 1,
42136
+ cellColor: "#444444",
42137
+ sectionSize: 5,
42138
+ sectionThickness: 1.5,
42139
+ sectionColor: "#666666",
42140
+ fadeDistance: 50,
42141
+ fadeStrength: 1
42142
+ }
42143
+ ),
42144
+ tiles.map((tile, index) => {
42145
+ const position = gridToWorld(
42146
+ tile.x,
42147
+ tile.z ?? tile.y ?? 0,
42148
+ tile.elevation ?? 0
42149
+ );
42150
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
42151
+ return /* @__PURE__ */ jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
42152
+ }),
42153
+ features.map((feature, index) => {
42154
+ const position = gridToWorld(
42155
+ feature.x,
42156
+ feature.z ?? feature.y ?? 0,
42157
+ (feature.elevation ?? 0) + 0.5
42158
+ );
42159
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
42160
+ return /* @__PURE__ */ jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
42161
+ }),
42162
+ units.map((unit) => {
42163
+ const position = gridToWorld(
42164
+ unit.x ?? 0,
42165
+ unit.z ?? unit.y ?? 0,
42166
+ (unit.elevation ?? 0) + 0.5
42167
+ );
42168
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
42169
+ return /* @__PURE__ */ jsx(Renderer, { unit, position }, unit.id);
42170
+ }),
42171
+ children,
42172
+ /* @__PURE__ */ jsx(
42173
+ OrbitControls,
42174
+ {
42175
+ ref: controlsRef,
42176
+ target: cameraTarget,
42177
+ enableDamping: true,
42178
+ dampingFactor: 0.05,
42179
+ minDistance: 2,
42180
+ maxDistance: 100,
42181
+ maxPolarAngle: Math.PI / 2 - 0.1
42182
+ }
42183
+ )
42184
+ ]
42185
+ }
42186
+ ),
42187
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__coordinates", children: [
42188
+ "X: ",
42189
+ hoveredTile.x,
42190
+ ", Z: ",
42191
+ hoveredTile.z ?? hoveredTile.y ?? 0
42192
+ ] }),
42193
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__tile-info", children: [
42194
+ /* @__PURE__ */ jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
42195
+ hoveredTile.terrain && /* @__PURE__ */ jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
42196
+ ] })
42197
+ ]
42198
+ }
42199
+ )
42200
+ }
42201
+ );
42202
+ }
42203
+ );
42204
+ GameCanvas3D.displayName = "GameCanvas3D";
42205
+ }
42206
+ });
42207
+ function GameBoard3D({
42208
+ entity,
42209
+ tiles = [],
42210
+ features = [],
42211
+ cameraMode = "perspective",
42212
+ backgroundColor = "#2a1a1a",
42213
+ tileClickEvent,
42214
+ unitClickEvent,
42215
+ attackEvent,
42216
+ endTurnEvent,
42217
+ cancelEvent,
42218
+ playAgainEvent,
42219
+ gameEndEvent,
42220
+ className
42221
+ }) {
42222
+ const row = boardEntity(entity);
42223
+ const eventBus = useEventBus();
42224
+ const entityUnits = row ? rows(row.units) : [];
42225
+ const units = entityUnits;
42226
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
42227
+ const phase = row ? str(row.phase) : "observation";
42228
+ const result = row ? str(row.result) : "none";
42229
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
42230
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
42231
+ const turn = row ? num(row.turn) : 0;
42232
+ const currentTeam = row ? str(row.currentTeam) : "player";
42233
+ const isGameOver = result !== "none";
42234
+ const gameEndEmittedRef = useRef(false);
42235
+ useEffect(() => {
42236
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
42237
+ if (!gameEndEmittedRef.current) {
42238
+ gameEndEmittedRef.current = true;
42239
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
42240
+ }
42241
+ } else {
42242
+ gameEndEmittedRef.current = false;
42243
+ }
42244
+ }, [result, gameEndEvent, eventBus]);
42245
+ const checkGameEnd = useCallback(() => {
42246
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
42247
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
42248
+ if (alivePlayer.length === 0 && gameEndEvent) {
42249
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
42250
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
42251
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
42252
+ }
42253
+ }, [entityUnits, gameEndEvent, eventBus]);
42254
+ const handleUnitClickCallback = useCallback((isoUnit) => {
42255
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
42256
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
42257
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
42258
+ if (!attackerRow || !targetRow) return;
42259
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
42260
+ const ap = unitPosition(attackerRow);
42261
+ const tp = unitPosition(targetRow);
42262
+ const dx = Math.abs(ap.x - tp.x);
42263
+ const dy = Math.abs(ap.y - tp.y);
42264
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
42265
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
42266
+ eventBus.emit(`UI:${attackEvent}`, {
42267
+ attackerId: str(attackerRow.id),
42268
+ targetId: str(targetRow.id),
42269
+ damage
42270
+ });
42271
+ setTimeout(checkGameEnd, 100);
42272
+ }
42273
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
42274
+ const handleEndTurn = useCallback(() => {
42275
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
42276
+ }, [endTurnEvent, eventBus]);
42277
+ const handleCancel = useCallback(() => {
42278
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
42279
+ }, [cancelEvent, eventBus]);
42280
+ const handlePlayAgain = useCallback(() => {
42281
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
42282
+ }, [playAgainEvent, eventBus]);
42283
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
42284
+ /* @__PURE__ */ jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
42285
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
42286
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
42287
+ "\u2014 ",
42288
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
42289
+ ] }),
42290
+ /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
42291
+ "Turn ",
42292
+ turn
42293
+ ] })
42294
+ ] }),
42295
+ /* @__PURE__ */ jsx(
42296
+ GameCanvas3D,
42297
+ {
42298
+ tiles,
42299
+ units,
42300
+ features,
42301
+ cameraMode,
42302
+ showGrid: true,
42303
+ showCoordinates: false,
42304
+ showTileInfo: false,
42305
+ shadows: true,
42306
+ backgroundColor,
42307
+ tileClickEvent,
42308
+ unitClickEvent,
42309
+ onUnitClick: handleUnitClickCallback,
42310
+ selectedUnitId,
42311
+ validMoves,
42312
+ attackTargets,
42313
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
42314
+ }
42315
+ ),
42316
+ !isGameOver && /* @__PURE__ */ jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
42317
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsx(
42318
+ Button,
42319
+ {
42320
+ variant: "secondary",
42321
+ className: "shadow-xl",
42322
+ onClick: handleCancel,
42323
+ children: "Cancel"
42324
+ }
42325
+ ),
42326
+ phase !== "enemy_turn" && /* @__PURE__ */ jsx(
42327
+ Button,
42328
+ {
42329
+ variant: "primary",
42330
+ className: "shadow-xl",
42331
+ onClick: handleEndTurn,
42332
+ children: "End Turn"
42333
+ }
42334
+ )
42335
+ ] }),
42336
+ 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: [
42337
+ /* @__PURE__ */ jsx(
42338
+ Typography,
42339
+ {
42340
+ variant: "h2",
42341
+ className: cn(
42342
+ "overlay__result",
42343
+ result === "victory" ? "text-yellow-400" : "text-red-500"
42344
+ ),
42345
+ children: result === "victory" ? "Victory!" : "Defeat"
42346
+ }
42347
+ ),
42348
+ /* @__PURE__ */ jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
42349
+ "Completed in ",
42350
+ turn,
42351
+ " turn",
42352
+ turn !== 1 ? "s" : ""
42353
+ ] }),
42354
+ /* @__PURE__ */ jsx(
42355
+ Button,
42356
+ {
42357
+ variant: "primary",
42358
+ className: "px-8 py-3 font-semibold",
42359
+ onClick: handlePlayAgain,
42360
+ children: "Play Again"
42361
+ }
42362
+ )
42363
+ ] }) })
42364
+ ] });
42365
+ }
42366
+ var init_GameBoard3D = __esm({
42367
+ "components/game/organisms/GameBoard3D.tsx"() {
42368
+ "use client";
42369
+ init_cn();
42370
+ init_Button();
42371
+ init_Typography();
42372
+ init_Stack();
42373
+ init_useEventBus();
42374
+ init_GameCanvas3D2();
42375
+ init_boardEntity();
42376
+ GameBoard3D.displayName = "GameBoard3D";
42377
+ }
42378
+ });
40724
42379
  var GameShell;
40725
42380
  var init_GameShell = __esm({
40726
42381
  "components/game/templates/GameShell.tsx"() {
@@ -42263,6 +43918,236 @@ var init_NegotiatorBoard = __esm({
42263
43918
  NegotiatorBoard.displayName = "NegotiatorBoard";
42264
43919
  }
42265
43920
  });
43921
+ function resolveEntity(entity) {
43922
+ if (!entity) return {};
43923
+ if (Array.isArray(entity)) return entity[0] ?? {};
43924
+ return entity;
43925
+ }
43926
+ function numField(row, key, fallback) {
43927
+ const v = row[key];
43928
+ return typeof v === "number" ? v : fallback;
43929
+ }
43930
+ function strField(row, key, fallback) {
43931
+ const v = row[key];
43932
+ return typeof v === "string" ? v : fallback;
43933
+ }
43934
+ function PlatformerBoard({
43935
+ entity,
43936
+ player: propPlayer,
43937
+ platforms: propPlatforms,
43938
+ result: propResult,
43939
+ lives: propLives,
43940
+ score: propScore,
43941
+ level: propLevel,
43942
+ worldWidth = 800,
43943
+ worldHeight = 400,
43944
+ canvasWidth = 800,
43945
+ canvasHeight = 400,
43946
+ playerSprite,
43947
+ tileSprites,
43948
+ bgColor,
43949
+ leftEvent = "LEFT",
43950
+ rightEvent = "RIGHT",
43951
+ jumpEvent = "JUMP",
43952
+ stopEvent = "STOP",
43953
+ playAgainEvent = "PLAY_AGAIN",
43954
+ gameEndEvent = "GAME_END",
43955
+ className
43956
+ }) {
43957
+ const row = resolveEntity(entity);
43958
+ const eventBus = useEventBus();
43959
+ const { t } = useTranslate();
43960
+ const result = propResult ?? strField(row, "result", "none");
43961
+ propLives ?? numField(row, "lives", 3);
43962
+ const score = propScore ?? numField(row, "score", 0);
43963
+ const level = propLevel ?? numField(row, "level", 1);
43964
+ const entityPlayer = row["player"];
43965
+ const player = propPlayer ?? entityPlayer ?? {
43966
+ x: 80,
43967
+ y: 320,
43968
+ width: 32,
43969
+ height: 48,
43970
+ vx: 0,
43971
+ vy: 0,
43972
+ grounded: false,
43973
+ facingRight: true
43974
+ };
43975
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
43976
+ const platforms = propPlatforms ?? entityPlatforms ?? [
43977
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
43978
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
43979
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
43980
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
43981
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
43982
+ ];
43983
+ const handleRestart = useCallback(() => {
43984
+ if (playAgainEvent) {
43985
+ eventBus.emit(`UI:${playAgainEvent}`, {});
43986
+ }
43987
+ }, [playAgainEvent, eventBus]);
43988
+ const isGameOver = result === "won" || result === "lost";
43989
+ const gameEndEmittedRef = useRef(false);
43990
+ useEffect(() => {
43991
+ if ((result === "won" || result === "lost") && gameEndEvent) {
43992
+ if (!gameEndEmittedRef.current) {
43993
+ gameEndEmittedRef.current = true;
43994
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
43995
+ }
43996
+ } else {
43997
+ gameEndEmittedRef.current = false;
43998
+ }
43999
+ }, [result, gameEndEvent, eventBus]);
44000
+ return /* @__PURE__ */ jsx(
44001
+ VStack,
44002
+ {
44003
+ className: cn("platformer-board relative bg-background", className),
44004
+ gap: "none",
44005
+ children: /* @__PURE__ */ jsxs(Box, { className: "relative", children: [
44006
+ /* @__PURE__ */ jsx(
44007
+ PlatformerCanvas,
44008
+ {
44009
+ player,
44010
+ platforms,
44011
+ worldWidth,
44012
+ worldHeight,
44013
+ canvasWidth,
44014
+ canvasHeight,
44015
+ followCamera: true,
44016
+ bgColor,
44017
+ playerSprite,
44018
+ tileSprites,
44019
+ leftEvent,
44020
+ rightEvent,
44021
+ jumpEvent,
44022
+ stopEvent
44023
+ }
44024
+ ),
44025
+ 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: [
44026
+ /* @__PURE__ */ jsx(
44027
+ Typography,
44028
+ {
44029
+ variant: "h2",
44030
+ className: cn(
44031
+ "text-4xl font-black tracking-widest uppercase",
44032
+ result === "won" ? "text-warning" : "text-error"
44033
+ ),
44034
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
44035
+ }
44036
+ ),
44037
+ result === "won" && /* @__PURE__ */ jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
44038
+ t("platformer.level"),
44039
+ " ",
44040
+ level,
44041
+ " \u2014 ",
44042
+ t("platformer.score"),
44043
+ ": ",
44044
+ score
44045
+ ] }),
44046
+ result === "lost" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
44047
+ /* @__PURE__ */ jsx(
44048
+ Button,
44049
+ {
44050
+ variant: "primary",
44051
+ className: "px-8 py-3 font-semibold",
44052
+ onClick: handleRestart,
44053
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
44054
+ }
44055
+ )
44056
+ ] }) })
44057
+ ] })
44058
+ }
44059
+ );
44060
+ }
44061
+ var init_PlatformerBoard = __esm({
44062
+ "components/game/organisms/PlatformerBoard.tsx"() {
44063
+ "use client";
44064
+ init_cn();
44065
+ init_useEventBus();
44066
+ init_Box();
44067
+ init_Button();
44068
+ init_Typography();
44069
+ init_Stack();
44070
+ init_PlatformerCanvas();
44071
+ PlatformerBoard.displayName = "PlatformerBoard";
44072
+ }
44073
+ });
44074
+ function PlatformerTemplate({
44075
+ entity,
44076
+ platforms = DEFAULT_PLATFORMS,
44077
+ worldWidth = 800,
44078
+ worldHeight = 400,
44079
+ canvasWidth = 800,
44080
+ canvasHeight = 400,
44081
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
44082
+ tileSprites = DEFAULT_TILE_SPRITES,
44083
+ bgColor = "#5c94fc",
44084
+ gameEndEvent = "GAME_END",
44085
+ className
44086
+ }) {
44087
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
44088
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
44089
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
44090
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
44091
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
44092
+ /* @__PURE__ */ jsx(Box, { className: "relative", children: /* @__PURE__ */ jsx(
44093
+ GameHud,
44094
+ {
44095
+ position: "top",
44096
+ stats: [
44097
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
44098
+ { label: "Level", value: level, format: "number", variant: "default" }
44099
+ ]
44100
+ }
44101
+ ) }),
44102
+ /* @__PURE__ */ jsx(
44103
+ PlatformerBoard,
44104
+ {
44105
+ entity,
44106
+ platforms,
44107
+ worldWidth,
44108
+ worldHeight,
44109
+ canvasWidth,
44110
+ canvasHeight,
44111
+ playerSprite,
44112
+ tileSprites,
44113
+ bgColor,
44114
+ leftEvent: "LEFT",
44115
+ rightEvent: "RIGHT",
44116
+ jumpEvent: "JUMP",
44117
+ stopEvent: "STOP",
44118
+ playAgainEvent: "PLAY_AGAIN",
44119
+ gameEndEvent
44120
+ }
44121
+ ),
44122
+ /* @__PURE__ */ jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsx(ScoreBoard, { score, level }) })
44123
+ ] });
44124
+ }
44125
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
44126
+ var init_PlatformerTemplate = __esm({
44127
+ "components/game/templates/PlatformerTemplate.tsx"() {
44128
+ init_cn();
44129
+ init_Box();
44130
+ init_Stack();
44131
+ init_PlatformerBoard();
44132
+ init_GameHud();
44133
+ init_ScoreBoard();
44134
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
44135
+ DEFAULT_TILE_SPRITES = {
44136
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
44137
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
44138
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
44139
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
44140
+ };
44141
+ DEFAULT_PLATFORMS = [
44142
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44143
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44144
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44145
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44146
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44147
+ ];
44148
+ PlatformerTemplate.displayName = "PlatformerTemplate";
44149
+ }
44150
+ });
42266
44151
  var PricingOrganism;
42267
44152
  var init_PricingOrganism = __esm({
42268
44153
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -42385,6 +44270,356 @@ var init_PricingPageTemplate = __esm({
42385
44270
  PricingPageTemplate.displayName = "PricingPageTemplate";
42386
44271
  }
42387
44272
  });
44273
+ function RoguelikeBoard({
44274
+ entity,
44275
+ tiles: propTiles,
44276
+ enemies: propEnemies,
44277
+ items: propItems,
44278
+ player: propPlayer,
44279
+ playerHp: propPlayerHp,
44280
+ playerMaxHp: propPlayerMaxHp,
44281
+ playerAttack: propPlayerAttack,
44282
+ depth: propDepth,
44283
+ result: propResult,
44284
+ phase: propPhase,
44285
+ stairsX: propStairsX,
44286
+ stairsY: propStairsY,
44287
+ assetManifest: propAssetManifest,
44288
+ scale = 0.45,
44289
+ unitScale = 1,
44290
+ moveEvent,
44291
+ playAgainEvent,
44292
+ gameEndEvent,
44293
+ className
44294
+ }) {
44295
+ const board = boardEntity(entity) ?? {};
44296
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
44297
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
44298
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
44299
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
44300
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
44301
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
44302
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
44303
+ const depth = propDepth ?? num(board.depth, 1);
44304
+ const result = propResult ?? (str(board.result) || "none");
44305
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
44306
+ propStairsX ?? num(board.stairsX, 3);
44307
+ propStairsY ?? num(board.stairsY, 3);
44308
+ const eventBus = useEventBus();
44309
+ const { t } = useTranslate();
44310
+ const [hoveredTile, setHoveredTile] = useState(null);
44311
+ const emitMove = useCallback((dx, dy) => {
44312
+ if (!moveEvent) return;
44313
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
44314
+ }, [moveEvent, eventBus]);
44315
+ useEffect(() => {
44316
+ const handler = (e) => {
44317
+ if (result !== "none" || phase !== "player_turn") return;
44318
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
44319
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
44320
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
44321
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
44322
+ };
44323
+ window.addEventListener("keydown", handler);
44324
+ return () => window.removeEventListener("keydown", handler);
44325
+ }, [result, phase, emitMove]);
44326
+ useEffect(() => {
44327
+ if ((result === "won" || result === "lost") && gameEndEvent) {
44328
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44329
+ }
44330
+ }, [result, gameEndEvent, eventBus]);
44331
+ const handleTileClick = useCallback((x, y) => {
44332
+ if (result !== "none" || phase !== "player_turn") return;
44333
+ const dx = x - player.x;
44334
+ const dy = y - player.y;
44335
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
44336
+ emitMove(dx, dy);
44337
+ }
44338
+ }, [result, phase, player, emitMove]);
44339
+ const handleReset = useCallback(() => {
44340
+ if (!playAgainEvent) return;
44341
+ eventBus.emit(`UI:${playAgainEvent}`, {});
44342
+ }, [playAgainEvent, eventBus]);
44343
+ const isoUnits = useMemo(() => {
44344
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
44345
+ return [
44346
+ {
44347
+ id: "player",
44348
+ position: player,
44349
+ name: t("roguelike.player") || "Hero",
44350
+ team: "player",
44351
+ health: playerHp,
44352
+ maxHealth: playerMaxHp,
44353
+ unitType: "player",
44354
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
44355
+ },
44356
+ ...liveEnemies.map((e) => ({
44357
+ id: e.id,
44358
+ position: { x: e.x, y: e.y },
44359
+ name: t("roguelike.enemy") || "Enemy",
44360
+ team: "enemy",
44361
+ health: e.hp,
44362
+ maxHealth: 6,
44363
+ unitType: "enemy",
44364
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
44365
+ }))
44366
+ ];
44367
+ }, [player, enemies, playerHp, playerMaxHp, t]);
44368
+ const isoFeatures = useMemo(
44369
+ () => items.map((it) => ({
44370
+ id: it.id,
44371
+ x: it.x,
44372
+ y: it.y,
44373
+ type: it.kind,
44374
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
44375
+ })),
44376
+ [items]
44377
+ );
44378
+ const validMoves = useMemo(() => {
44379
+ if (result !== "none" || phase !== "player_turn") return [];
44380
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
44381
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
44382
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
44383
+ return tile?.passable ?? false;
44384
+ });
44385
+ }, [result, phase, player, tiles]);
44386
+ const isGameOver = result === "won" || result === "lost";
44387
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
44388
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
44389
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44390
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
44391
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
44392
+ ] }),
44393
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44394
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
44395
+ /* @__PURE__ */ jsxs(
44396
+ Typography,
44397
+ {
44398
+ variant: "body1",
44399
+ className: cn(
44400
+ "font-bold tabular-nums",
44401
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
44402
+ ),
44403
+ children: [
44404
+ playerHp,
44405
+ " / ",
44406
+ playerMaxHp
44407
+ ]
44408
+ }
44409
+ )
44410
+ ] }),
44411
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44412
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
44413
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
44414
+ ] }),
44415
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44416
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
44417
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
44418
+ ] })
44419
+ ] }),
44420
+ /* @__PURE__ */ jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsx(
44421
+ IsometricCanvas_default,
44422
+ {
44423
+ tiles,
44424
+ units: isoUnits,
44425
+ features: isoFeatures,
44426
+ selectedUnitId: null,
44427
+ validMoves,
44428
+ attackTargets: [],
44429
+ hoveredTile,
44430
+ onTileClick: handleTileClick,
44431
+ onUnitClick: () => void 0,
44432
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
44433
+ onTileLeave: () => setHoveredTile(null),
44434
+ scale,
44435
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
44436
+ assetManifest: propAssetManifest,
44437
+ unitScale
44438
+ }
44439
+ ) }),
44440
+ !isGameOver && /* @__PURE__ */ jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
44441
+ { label: "\u2191", dx: 0, dy: -1 },
44442
+ { label: "\u2193", dx: 0, dy: 1 },
44443
+ { label: "\u2190", dx: -1, dy: 0 },
44444
+ { label: "\u2192", dx: 1, dy: 0 }
44445
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsx(
44446
+ Button,
44447
+ {
44448
+ variant: "secondary",
44449
+ className: "w-9 h-9 p-0 text-base font-mono",
44450
+ onClick: () => emitMove(dx, dy),
44451
+ disabled: phase !== "player_turn",
44452
+ children: label
44453
+ },
44454
+ label
44455
+ )) }),
44456
+ 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: [
44457
+ /* @__PURE__ */ jsx(
44458
+ Typography,
44459
+ {
44460
+ variant: "h2",
44461
+ className: cn(
44462
+ "text-4xl font-black tracking-widest uppercase",
44463
+ result === "won" ? "text-warning" : "text-error"
44464
+ ),
44465
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
44466
+ }
44467
+ ),
44468
+ result === "won" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
44469
+ /* @__PURE__ */ jsx(
44470
+ Button,
44471
+ {
44472
+ variant: "primary",
44473
+ className: "px-8 py-3 font-semibold",
44474
+ onClick: handleReset,
44475
+ children: t("roguelike.playAgain") || "Play Again"
44476
+ }
44477
+ )
44478
+ ] }) })
44479
+ ] });
44480
+ }
44481
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
44482
+ var init_RoguelikeBoard = __esm({
44483
+ "components/game/organisms/RoguelikeBoard.tsx"() {
44484
+ "use client";
44485
+ init_cn();
44486
+ init_useEventBus();
44487
+ init_Box();
44488
+ init_Button();
44489
+ init_Typography();
44490
+ init_Stack();
44491
+ init_IsometricCanvas();
44492
+ init_boardEntity();
44493
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
44494
+ DEFAULT_TILES2 = [
44495
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44496
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44497
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44498
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44499
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44500
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44501
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44502
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44503
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44504
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44505
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44506
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44507
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44508
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44509
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44510
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44511
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44512
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44513
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
44514
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44515
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44516
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44517
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44518
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44519
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
44520
+ ];
44521
+ DEFAULT_ENEMIES = [
44522
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
44523
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
44524
+ ];
44525
+ DEFAULT_ITEMS = [
44526
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
44527
+ ];
44528
+ FEATURE_SPRITE = {
44529
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
44530
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
44531
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
44532
+ };
44533
+ RoguelikeBoard.displayName = "RoguelikeBoard";
44534
+ }
44535
+ });
44536
+ function RoguelikeTemplate({
44537
+ entity,
44538
+ scale = 0.45,
44539
+ unitScale = 1,
44540
+ tiles = DEFAULT_ROGUELIKE_TILES,
44541
+ enemies,
44542
+ items,
44543
+ stairsX,
44544
+ stairsY,
44545
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
44546
+ gameEndEvent,
44547
+ className
44548
+ }) {
44549
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
44550
+ if (!resolved && !tiles && !assetManifest) return null;
44551
+ return /* @__PURE__ */ jsx(
44552
+ RoguelikeBoard,
44553
+ {
44554
+ entity: resolved,
44555
+ tiles,
44556
+ enemies,
44557
+ items,
44558
+ stairsX,
44559
+ stairsY,
44560
+ assetManifest,
44561
+ scale,
44562
+ unitScale,
44563
+ moveEvent: "MOVE",
44564
+ playAgainEvent: "PLAY_AGAIN",
44565
+ gameEndEvent: gameEndEvent ?? "GAME_END",
44566
+ className
44567
+ }
44568
+ );
44569
+ }
44570
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
44571
+ var init_RoguelikeTemplate = __esm({
44572
+ "components/game/templates/RoguelikeTemplate.tsx"() {
44573
+ init_RoguelikeBoard();
44574
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
44575
+ DEFAULT_ROGUELIKE_TILES = [
44576
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44577
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44578
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44579
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44580
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44581
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44582
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44583
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44584
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44585
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44586
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44587
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44588
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44589
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44590
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44591
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44592
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44593
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44594
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
44595
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44596
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44597
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44598
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44599
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44600
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
44601
+ ];
44602
+ DEFAULT_ROGUELIKE_MANIFEST = {
44603
+ baseUrl: CDN5,
44604
+ terrains: {
44605
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
44606
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
44607
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
44608
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
44609
+ },
44610
+ units: {
44611
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
44612
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
44613
+ },
44614
+ features: {
44615
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
44616
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
44617
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
44618
+ }
44619
+ };
44620
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
44621
+ }
44622
+ });
42388
44623
 
42389
44624
  // lib/traitRegistry.ts
42390
44625
  function notifyListeners2() {
@@ -43638,7 +45873,7 @@ function getAllEvents(traits2) {
43638
45873
  function EventDispatcherTab({ traits: traits2, schema }) {
43639
45874
  const eventBus = useEventBus();
43640
45875
  const { t } = useTranslate();
43641
- const [log12, setLog] = React82.useState([]);
45876
+ const [log14, setLog] = React82.useState([]);
43642
45877
  const prevStatesRef = React82.useRef(/* @__PURE__ */ new Map());
43643
45878
  React82.useEffect(() => {
43644
45879
  for (const trait of traits2) {
@@ -43702,9 +45937,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
43702
45937
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
43703
45938
  /* @__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)) })
43704
45939
  ] }),
43705
- log12.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
45940
+ log14.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
43706
45941
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
43707
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log12.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
45942
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log14.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
43708
45943
  /* @__PURE__ */ jsx("span", { className: "text-purple-400", children: entry.traitName }),
43709
45944
  " ",
43710
45945
  /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -45625,9 +47860,9 @@ function VariablePanel({
45625
47860
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
45626
47861
  variables.map((v) => {
45627
47862
  const name = v.name == null ? "" : String(v.name);
45628
- const value = numField(v.value);
45629
- const max = numField(v.max, 100);
45630
- const min = numField(v.min, 0);
47863
+ const value = numField2(v.value);
47864
+ const max = numField2(v.max, 100);
47865
+ const min = numField2(v.min, 0);
45631
47866
  const unit = v.unit == null ? "" : String(v.unit);
45632
47867
  const pct = Math.round((value - min) / (max - min) * 100);
45633
47868
  const isHigh = pct > 80;
@@ -45657,12 +47892,12 @@ function VariablePanel({
45657
47892
  })
45658
47893
  ] });
45659
47894
  }
45660
- var numField;
47895
+ var numField2;
45661
47896
  var init_VariablePanel = __esm({
45662
47897
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
45663
47898
  init_atoms2();
45664
47899
  init_cn();
45665
- numField = (v, fallback = 0) => {
47900
+ numField2 = (v, fallback = 0) => {
45666
47901
  const n = Number(v);
45667
47902
  return Number.isFinite(n) ? n : fallback;
45668
47903
  };
@@ -46515,6 +48750,289 @@ var init_ToastSlot = __esm({
46515
48750
  ToastSlot.displayName = "ToastSlot";
46516
48751
  }
46517
48752
  });
48753
+ function tilesToIso(tiles) {
48754
+ return tiles.map((t) => ({
48755
+ x: t.x,
48756
+ y: t.y,
48757
+ terrain: t.terrain,
48758
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
48759
+ passable: t.passable
48760
+ }));
48761
+ }
48762
+ function towersToUnits(towers) {
48763
+ return towers.map((t) => ({
48764
+ id: t.id,
48765
+ position: { x: t.x, y: t.y },
48766
+ name: "Tower",
48767
+ team: "player",
48768
+ sprite: TOWER_SPRITE,
48769
+ unitType: "guardian",
48770
+ health: 1,
48771
+ maxHealth: 1
48772
+ }));
48773
+ }
48774
+ function creepsToUnits(creeps) {
48775
+ return creeps.map((c) => ({
48776
+ id: c.id,
48777
+ position: { x: c.x, y: c.y },
48778
+ name: "Creep",
48779
+ team: "enemy",
48780
+ sprite: CREEP_SPRITE,
48781
+ unitType: "scrapper",
48782
+ health: c.hp,
48783
+ maxHealth: c.maxHp
48784
+ }));
48785
+ }
48786
+ function pathToFeatures(path) {
48787
+ return path.map((p2, i) => ({
48788
+ id: `path-${i}`,
48789
+ x: p2.x,
48790
+ y: p2.y,
48791
+ type: "path-marker",
48792
+ sprite: `${CDN6}world-map/road_straight.png`
48793
+ }));
48794
+ }
48795
+ function TowerDefenseBoard({
48796
+ entity,
48797
+ tiles: propTiles,
48798
+ path: propPath,
48799
+ towers: propTowers,
48800
+ creeps: propCreeps,
48801
+ gold: propGold,
48802
+ lives: propLives,
48803
+ wave: propWave,
48804
+ maxWaves: propMaxWaves,
48805
+ result: propResult,
48806
+ waveActive: propWaveActive,
48807
+ towerCost = 25,
48808
+ scale = 0.45,
48809
+ placeTowerEvent,
48810
+ startWaveEvent,
48811
+ playAgainEvent,
48812
+ gameEndEvent,
48813
+ className
48814
+ }) {
48815
+ const board = boardEntity(entity) ?? {};
48816
+ const eventBus = useEventBus();
48817
+ const { t } = useTranslate();
48818
+ const tiles = propTiles ?? rows(board.tiles);
48819
+ const path = propPath ?? rows(board.path);
48820
+ const towers = propTowers ?? rows(board.towers);
48821
+ const creeps = propCreeps ?? rows(board.creeps);
48822
+ const gold = propGold ?? num(board.gold, 100);
48823
+ const lives = propLives ?? num(board.lives, 20);
48824
+ const wave = propWave ?? num(board.wave, 1);
48825
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
48826
+ const result = propResult ?? (str(board.result) || "none");
48827
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
48828
+ const [hoveredTile, setHoveredTile] = useState(null);
48829
+ const emittedGameEnd = useRef(false);
48830
+ if (result !== "none" && !emittedGameEnd.current) {
48831
+ emittedGameEnd.current = true;
48832
+ if (gameEndEvent) {
48833
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
48834
+ }
48835
+ }
48836
+ if (result === "none") {
48837
+ emittedGameEnd.current = false;
48838
+ }
48839
+ const towerPositions = useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
48840
+ const pathPositions = useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
48841
+ const validMoves = useMemo(() => {
48842
+ if (result !== "none" || gold < towerCost) return [];
48843
+ return tiles.filter(
48844
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
48845
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
48846
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
48847
+ const isoTiles = useMemo(() => tilesToIso(tiles), [tiles]);
48848
+ const towerUnits = useMemo(() => towersToUnits(towers), [towers]);
48849
+ const creepUnits = useMemo(() => creepsToUnits(creeps), [creeps]);
48850
+ const isoUnits = useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
48851
+ const pathFeatures = useMemo(() => pathToFeatures(path), [path]);
48852
+ const handleTileClick = useCallback((x, y) => {
48853
+ if (result !== "none") return;
48854
+ if (pathPositions.has(`${x},${y}`)) return;
48855
+ if (towerPositions.has(`${x},${y}`)) return;
48856
+ if (gold < towerCost) return;
48857
+ if (placeTowerEvent) {
48858
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
48859
+ }
48860
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
48861
+ const handleStartWave = useCallback(() => {
48862
+ if (startWaveEvent) {
48863
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
48864
+ }
48865
+ }, [startWaveEvent, wave, eventBus]);
48866
+ const handlePlayAgain = useCallback(() => {
48867
+ if (playAgainEvent) {
48868
+ eventBus.emit(`UI:${playAgainEvent}`, {});
48869
+ }
48870
+ }, [playAgainEvent, eventBus]);
48871
+ const canPlaceTower = gold >= towerCost && result === "none";
48872
+ const canStartWave = !waveActive && result === "none";
48873
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
48874
+ /* @__PURE__ */ jsxs(
48875
+ HStack,
48876
+ {
48877
+ className: "px-4 py-2 border-b border-border bg-surface",
48878
+ gap: "lg",
48879
+ align: "center",
48880
+ children: [
48881
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
48882
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
48883
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
48884
+ ] }),
48885
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
48886
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
48887
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
48888
+ ] }),
48889
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
48890
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
48891
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", weight: "bold", children: [
48892
+ wave,
48893
+ " / ",
48894
+ maxWaves
48895
+ ] })
48896
+ ] }),
48897
+ /* @__PURE__ */ jsxs(Box, { className: "ml-auto", children: [
48898
+ canStartWave && /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
48899
+ waveActive && /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
48900
+ ] })
48901
+ ]
48902
+ }
48903
+ ),
48904
+ /* @__PURE__ */ jsxs(Box, { className: "relative flex-1", children: [
48905
+ /* @__PURE__ */ jsx(
48906
+ IsometricCanvas_default,
48907
+ {
48908
+ tiles: isoTiles,
48909
+ units: isoUnits,
48910
+ features: pathFeatures,
48911
+ validMoves,
48912
+ hoveredTile,
48913
+ onTileClick: handleTileClick,
48914
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
48915
+ onTileLeave: () => setHoveredTile(null),
48916
+ scale
48917
+ }
48918
+ ),
48919
+ 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)` }) })
48920
+ ] }),
48921
+ 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: [
48922
+ /* @__PURE__ */ jsx(
48923
+ Typography,
48924
+ {
48925
+ variant: "h2",
48926
+ className: cn(
48927
+ "text-4xl font-black tracking-widest uppercase",
48928
+ result === "won" ? "text-warning" : "text-error"
48929
+ ),
48930
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
48931
+ }
48932
+ ),
48933
+ /* @__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." }),
48934
+ /* @__PURE__ */ jsx(
48935
+ Button,
48936
+ {
48937
+ variant: "primary",
48938
+ className: "px-8 py-3 font-semibold",
48939
+ onClick: handlePlayAgain,
48940
+ children: t("td.playAgain") ?? "Play Again"
48941
+ }
48942
+ )
48943
+ ] }) })
48944
+ ] });
48945
+ }
48946
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
48947
+ var init_TowerDefenseBoard = __esm({
48948
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
48949
+ "use client";
48950
+ init_cn();
48951
+ init_useEventBus();
48952
+ init_Box();
48953
+ init_Button();
48954
+ init_Typography();
48955
+ init_Stack();
48956
+ init_IsometricCanvas();
48957
+ init_boardEntity();
48958
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
48959
+ TERRAIN_SPRITES = {
48960
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
48961
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
48962
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
48963
+ };
48964
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
48965
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
48966
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
48967
+ }
48968
+ });
48969
+ function TowerDefenseTemplate({
48970
+ entity,
48971
+ title = "Tower Defense",
48972
+ tiles,
48973
+ path,
48974
+ towers,
48975
+ gold,
48976
+ lives,
48977
+ maxWaves,
48978
+ towerCost,
48979
+ scale,
48980
+ placeTowerEvent,
48981
+ startWaveEvent,
48982
+ playAgainEvent,
48983
+ gameEndEvent,
48984
+ className
48985
+ }) {
48986
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
48987
+ return /* @__PURE__ */ jsxs(
48988
+ Box,
48989
+ {
48990
+ display: "flex",
48991
+ fullHeight: true,
48992
+ className: cn("tower-defense-template flex-col", className),
48993
+ children: [
48994
+ /* @__PURE__ */ jsx(
48995
+ HStack,
48996
+ {
48997
+ gap: "sm",
48998
+ align: "center",
48999
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
49000
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
49001
+ }
49002
+ ),
49003
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
49004
+ TowerDefenseBoard,
49005
+ {
49006
+ entity: resolved,
49007
+ tiles,
49008
+ path,
49009
+ towers,
49010
+ gold,
49011
+ lives,
49012
+ maxWaves,
49013
+ towerCost,
49014
+ scale,
49015
+ placeTowerEvent,
49016
+ startWaveEvent,
49017
+ playAgainEvent,
49018
+ gameEndEvent,
49019
+ className: "h-full"
49020
+ }
49021
+ ) })
49022
+ ]
49023
+ }
49024
+ );
49025
+ }
49026
+ var init_TowerDefenseTemplate = __esm({
49027
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
49028
+ init_cn();
49029
+ init_Box();
49030
+ init_Stack();
49031
+ init_Typography();
49032
+ init_TowerDefenseBoard();
49033
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
49034
+ }
49035
+ });
46518
49036
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
46519
49037
  const eventBus = useEventBus();
46520
49038
  const {
@@ -47024,48 +49542,48 @@ function WorldMapTemplate({
47024
49542
  }
47025
49543
  );
47026
49544
  }
47027
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
49545
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
47028
49546
  var init_WorldMapTemplate = __esm({
47029
49547
  "components/game/templates/WorldMapTemplate.tsx"() {
47030
49548
  init_WorldMapBoard();
47031
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
49549
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
47032
49550
  DEFAULT_WORLDMAP_TILES = [
47033
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47034
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47035
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47036
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47037
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47038
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47039
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47040
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47041
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47042
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47043
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47044
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47045
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47046
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47047
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47048
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47049
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47050
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47051
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47052
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47053
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47054
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47055
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47056
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47057
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
49551
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49552
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49553
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49554
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49555
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49556
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49557
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49558
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49559
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49560
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49561
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49562
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49563
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49564
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49565
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49566
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49567
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49568
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49569
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49570
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49571
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49572
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49573
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49574
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49575
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
47058
49576
  ];
47059
49577
  DEFAULT_WORLDMAP_UNITS = [
47060
- { 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` },
47061
- { 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` }
49578
+ { 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` },
49579
+ { 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` }
47062
49580
  ];
47063
49581
  DEFAULT_WORLDMAP_FEATURES = [
47064
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
47065
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
49582
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
49583
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
47066
49584
  ];
47067
49585
  DEFAULT_WORLDMAP_MANIFEST = {
47068
- baseUrl: CDN3,
49586
+ baseUrl: CDN7,
47069
49587
  terrains: {
47070
49588
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
47071
49589
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -47110,7 +49628,7 @@ function lazyThree(name, loader) {
47110
49628
  ThreeWrapper.displayName = `Lazy(${name})`;
47111
49629
  return ThreeWrapper;
47112
49630
  }
47113
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
49631
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
47114
49632
  var init_component_registry_generated = __esm({
47115
49633
  "components/core/organisms/component-registry.generated.ts"() {
47116
49634
  init_AboutPageTemplate();
@@ -47220,6 +49738,7 @@ var init_component_registry_generated = __esm({
47220
49738
  init_FormSectionHeader();
47221
49739
  init_GameAudioProvider();
47222
49740
  init_GameAudioToggle();
49741
+ init_GameBoard3D();
47223
49742
  init_GameCanvas2D();
47224
49743
  init_GameHud();
47225
49744
  init_GameMenu();
@@ -47277,7 +49796,9 @@ var init_component_registry_generated = __esm({
47277
49796
  init_PageHeader();
47278
49797
  init_Pagination();
47279
49798
  init_PatternTile();
49799
+ init_PlatformerBoard();
47280
49800
  init_PlatformerCanvas();
49801
+ init_PlatformerTemplate();
47281
49802
  init_Popover();
47282
49803
  init_PositionedCanvas();
47283
49804
  init_PowerupSlots();
@@ -47301,6 +49822,8 @@ var init_component_registry_generated = __esm({
47301
49822
  init_ResourceBar();
47302
49823
  init_ResourceCounter();
47303
49824
  init_RichBlockEditor();
49825
+ init_RoguelikeBoard();
49826
+ init_RoguelikeTemplate();
47304
49827
  init_RuntimeDebugger2();
47305
49828
  init_ScaledDiagram();
47306
49829
  init_ScoreBoard();
@@ -47373,6 +49896,8 @@ var init_component_registry_generated = __esm({
47373
49896
  init_Toast();
47374
49897
  init_ToastSlot();
47375
49898
  init_Tooltip();
49899
+ init_TowerDefenseBoard();
49900
+ init_TowerDefenseTemplate();
47376
49901
  init_TraitFrame();
47377
49902
  init_TraitSlot();
47378
49903
  init_TrendIndicator();
@@ -47417,7 +49942,7 @@ var init_component_registry_generated = __esm({
47417
49942
  }
47418
49943
  };
47419
49944
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
47420
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
49945
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
47421
49946
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47422
49947
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47423
49948
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -47536,8 +50061,9 @@ var init_component_registry_generated = __esm({
47536
50061
  "FormSectionHeader": FormSectionHeader,
47537
50062
  "GameAudioProvider": GameAudioProvider,
47538
50063
  "GameAudioToggle": GameAudioToggle,
50064
+ "GameBoard3D": GameBoard3D,
47539
50065
  "GameCanvas2D": GameCanvas2D,
47540
- "GameCanvas3D": GameCanvas3D,
50066
+ "GameCanvas3D": GameCanvas3D2,
47541
50067
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
47542
50068
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
47543
50069
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -47599,7 +50125,9 @@ var init_component_registry_generated = __esm({
47599
50125
  "PageHeader": PageHeader,
47600
50126
  "Pagination": Pagination,
47601
50127
  "PatternTile": PatternTile,
50128
+ "PlatformerBoard": PlatformerBoard,
47602
50129
  "PlatformerCanvas": PlatformerCanvas,
50130
+ "PlatformerTemplate": PlatformerTemplate,
47603
50131
  "Popover": Popover,
47604
50132
  "PositionedCanvas": PositionedCanvas,
47605
50133
  "PowerupSlots": PowerupSlots,
@@ -47623,6 +50151,8 @@ var init_component_registry_generated = __esm({
47623
50151
  "ResourceBar": ResourceBar,
47624
50152
  "ResourceCounter": ResourceCounter,
47625
50153
  "RichBlockEditor": RichBlockEditor,
50154
+ "RoguelikeBoard": RoguelikeBoard,
50155
+ "RoguelikeTemplate": RoguelikeTemplate,
47626
50156
  "RuntimeDebugger": RuntimeDebugger,
47627
50157
  "ScaledDiagram": ScaledDiagram,
47628
50158
  "ScoreBoard": ScoreBoard,
@@ -47698,6 +50228,8 @@ var init_component_registry_generated = __esm({
47698
50228
  "Toast": Toast,
47699
50229
  "ToastSlot": ToastSlot,
47700
50230
  "Tooltip": Tooltip,
50231
+ "TowerDefenseBoard": TowerDefenseBoard,
50232
+ "TowerDefenseTemplate": TowerDefenseTemplate,
47701
50233
  "TraitFrame": TraitFrame,
47702
50234
  "TraitSlot": TraitSlot,
47703
50235
  "TrendIndicator": TrendIndicator,
@@ -49594,7 +52126,7 @@ function clearSchemaCache() {
49594
52126
 
49595
52127
  // runtime/index.ts
49596
52128
  init_EntitySchemaContext();
49597
- var log10 = createLogger("almadar:ui:trait-provider");
52129
+ var log12 = createLogger("almadar:ui:trait-provider");
49598
52130
  var TraitContext = createContext(null);
49599
52131
  function TraitProvider({
49600
52132
  traits: traitBindings,
@@ -49611,7 +52143,7 @@ function TraitProvider({
49611
52143
  currentState: stateName,
49612
52144
  availableEvents: trait.transitions.filter((t) => t.from === stateName).map((t) => t.event),
49613
52145
  dispatch: (eventKey, payload) => {
49614
- log10.debug("Dispatch", () => ({
52146
+ log12.debug("Dispatch", () => ({
49615
52147
  trait: trait.name,
49616
52148
  event: eventKey,
49617
52149
  payloadKeys: payload ? Object.keys(payload) : []
@@ -50195,7 +52727,7 @@ OrbitalThemeProvider.displayName = "OrbitalThemeProvider";
50195
52727
  init_navigation();
50196
52728
  init_verificationRegistry();
50197
52729
  var PERF_NAMESPACE = "almadar:perf:canvas";
50198
- var log11 = createLogger(PERF_NAMESPACE);
52730
+ var log13 = createLogger(PERF_NAMESPACE);
50199
52731
  var RING_SIZE = 50;
50200
52732
  var ring = [];
50201
52733
  var writeIdx = 0;
@@ -50250,7 +52782,7 @@ function perfEnd(name, startToken, detail) {
50250
52782
  }
50251
52783
  }
50252
52784
  push({ name, durationMs, ts: endTs, detail });
50253
- log11.debug(name, () => ({ durationMs, ...detail ?? {} }));
52785
+ log13.debug(name, () => ({ durationMs, ...detail ?? {} }));
50254
52786
  }
50255
52787
  function perfTime(name, fn, detail) {
50256
52788
  const t = perfStart(name);
@@ -50268,7 +52800,7 @@ var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, com
50268
52800
  ts: commitTime,
50269
52801
  detail: { baseDuration }
50270
52802
  });
50271
- log11.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
52803
+ log13.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
50272
52804
  };
50273
52805
  function getPerfSnapshot() {
50274
52806
  if (ring.length < RING_SIZE) return ring.slice();