@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
package/dist/avl/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import * as React91 from 'react';
3
- import React91__default, { createContext, useState, useMemo, useRef, useEffect, useContext, useCallback, Suspense, useLayoutEffect, Profiler, useReducer, useSyncExternalStore, lazy, useId } from 'react';
3
+ import React91__default, { createContext, useState, useMemo, useRef, useEffect, useContext, useCallback, Suspense, useLayoutEffect, Profiler, useReducer, useSyncExternalStore, lazy, useId, forwardRef, useImperativeHandle, Component } from 'react';
4
4
  import { OrbitalProvider, EventBusContext, useTraitScope, VerificationProvider, TraitScopeProvider } from '@almadar/ui/providers';
5
5
  import { createLogger, isLogLevelEnabled } from '@almadar/logger';
6
6
  import ELK from 'elkjs/lib/elk.bundled.js';
@@ -39,6 +39,12 @@ import { FieldTypeSchema, isInlineTrait, isPageReference, isEntityCall, schemaTo
39
39
  import { useDroppable, useDraggable, DndContext, DragOverlay, useSensors, useSensor, PointerSensor, KeyboardSensor, pointerWithin, rectIntersection, closestCorners } from '@dnd-kit/core';
40
40
  import { sortableKeyboardCoordinates, useSortable, arrayMove, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
41
41
  import { CSS } from '@dnd-kit/utilities';
42
+ import * as THREE from 'three';
43
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
44
+ import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
45
+ import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
46
+ import { Canvas, useThree } from '@react-three/fiber';
47
+ import { Grid as Grid$1, OrbitControls } from '@react-three/drei';
42
48
  import { getPatternDefinition, getComponentForPattern as getComponentForPattern$1, isEntityAwarePattern } from '@almadar/patterns';
43
49
  import { OrbitalServerRuntime } from '@almadar/runtime/OrbitalServerRuntime';
44
50
  import { InMemoryPersistence, StateMachineManager, createContextFromBindings, interpolateValue, collectDeclaredConfigDefaults, createServerEffectHandlers, EffectExecutor } from '@almadar/runtime';
@@ -3173,8 +3179,8 @@ var init_Typography = __esm({
3173
3179
  children
3174
3180
  }) => {
3175
3181
  const variant = variantProp ?? (level ? `h${level}` : "body1");
3176
- const Component = as || defaultElements[variant];
3177
- const Comp = Component;
3182
+ const Component2 = as || defaultElements[variant];
3183
+ const Comp = Component2;
3178
3184
  return /* @__PURE__ */ jsx(
3179
3185
  Comp,
3180
3186
  {
@@ -3483,7 +3489,7 @@ var init_Box = __esm({
3483
3489
  position,
3484
3490
  className,
3485
3491
  children,
3486
- as: Component = "div",
3492
+ as: Component2 = "div",
3487
3493
  action,
3488
3494
  actionPayload,
3489
3495
  hoverEvent,
@@ -3515,7 +3521,7 @@ var init_Box = __esm({
3515
3521
  }, [hoverEvent, eventBus, onMouseLeave]);
3516
3522
  const isClickable = action || onClick;
3517
3523
  return React91__default.createElement(
3518
- Component,
3524
+ Component2,
3519
3525
  {
3520
3526
  ref,
3521
3527
  className: cn(
@@ -3591,7 +3597,7 @@ var init_Stack = __esm({
3591
3597
  className,
3592
3598
  style,
3593
3599
  children,
3594
- as: Component = "div",
3600
+ as: Component2 = "div",
3595
3601
  onClick,
3596
3602
  onKeyDown,
3597
3603
  role,
@@ -3609,7 +3615,7 @@ var init_Stack = __esm({
3609
3615
  };
3610
3616
  const isHorizontal = direction === "horizontal";
3611
3617
  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";
3612
- const Comp = Component;
3618
+ const Comp = Component2;
3613
3619
  return /* @__PURE__ */ jsx(
3614
3620
  Comp,
3615
3621
  {
@@ -4523,9 +4529,9 @@ function resolvePhosphor(name, weight, family) {
4523
4529
  (lib) => {
4524
4530
  const PhosphorComp = lib[target];
4525
4531
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
4526
- const Component = PhosphorComp;
4532
+ const Component2 = PhosphorComp;
4527
4533
  const Adapter = (props) => /* @__PURE__ */ jsx(
4528
- Component,
4534
+ Component2,
4529
4535
  {
4530
4536
  weight,
4531
4537
  className: props.className,
@@ -4549,9 +4555,9 @@ function resolveTabler(name, family) {
4549
4555
  (lib) => {
4550
4556
  const TablerComp = lib[target];
4551
4557
  if (!TablerComp || typeof TablerComp !== "object") return null;
4552
- const Component = TablerComp;
4558
+ const Component2 = TablerComp;
4553
4559
  const Adapter = (props) => /* @__PURE__ */ jsx(
4554
- Component,
4560
+ Component2,
4555
4561
  {
4556
4562
  stroke: props.strokeWidth ?? 1.5,
4557
4563
  className: props.className,
@@ -4575,9 +4581,9 @@ function resolveFa(name, family) {
4575
4581
  (lib) => {
4576
4582
  const FaComp = lib[target];
4577
4583
  if (!FaComp || typeof FaComp !== "function") return null;
4578
- const Component = FaComp;
4584
+ const Component2 = FaComp;
4579
4585
  const Adapter = (props) => /* @__PURE__ */ jsx(
4580
- Component,
4586
+ Component2,
4581
4587
  {
4582
4588
  className: props.className,
4583
4589
  style: props.style,
@@ -7969,10 +7975,10 @@ var init_Center = __esm({
7969
7975
  className,
7970
7976
  style,
7971
7977
  children,
7972
- as: Component = "div"
7978
+ as: Component2 = "div"
7973
7979
  }) => {
7974
7980
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
7975
- const Comp = Component;
7981
+ const Comp = Component2;
7976
7982
  return /* @__PURE__ */ jsx(
7977
7983
  Comp,
7978
7984
  {
@@ -10766,6 +10772,7 @@ var init_HealthBar = __esm({
10766
10772
  }
10767
10773
  });
10768
10774
  function ScoreDisplay({
10775
+ assetUrl = DEFAULT_ASSET_URL,
10769
10776
  value,
10770
10777
  label,
10771
10778
  icon,
@@ -10812,14 +10819,24 @@ function ScoreDisplay({
10812
10819
  className
10813
10820
  ),
10814
10821
  children: [
10815
- icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
10822
+ assetUrl ? /* @__PURE__ */ jsx(
10823
+ "img",
10824
+ {
10825
+ src: assetUrl,
10826
+ alt: "",
10827
+ width: 20,
10828
+ height: 20,
10829
+ style: { imageRendering: "pixelated", objectFit: "contain" },
10830
+ className: "flex-shrink-0"
10831
+ }
10832
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }) : null,
10816
10833
  label && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
10817
10834
  /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formattedValue })
10818
10835
  ]
10819
10836
  }
10820
10837
  );
10821
10838
  }
10822
- var sizeMap2;
10839
+ var sizeMap2, DEFAULT_ASSET_URL;
10823
10840
  var init_ScoreDisplay = __esm({
10824
10841
  "components/game/atoms/ScoreDisplay.tsx"() {
10825
10842
  "use client";
@@ -10831,10 +10848,12 @@ var init_ScoreDisplay = __esm({
10831
10848
  lg: "text-2xl",
10832
10849
  xl: "text-4xl"
10833
10850
  };
10851
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
10834
10852
  ScoreDisplay.displayName = "ScoreDisplay";
10835
10853
  }
10836
10854
  });
10837
10855
  function ControlButton({
10856
+ assetUrl = DEFAULT_ASSET_URL2,
10838
10857
  label,
10839
10858
  icon,
10840
10859
  size = "md",
@@ -10881,7 +10900,7 @@ function ControlButton({
10881
10900
  },
10882
10901
  [isPressed, releaseEvent, eventBus, onRelease]
10883
10902
  );
10884
- return /* @__PURE__ */ jsxs(
10903
+ return /* @__PURE__ */ jsx(
10885
10904
  "button",
10886
10905
  {
10887
10906
  type: "button",
@@ -10902,20 +10921,27 @@ function ControlButton({
10902
10921
  disabled && "opacity-50 cursor-not-allowed",
10903
10922
  className
10904
10923
  ),
10905
- children: [
10906
- icon && /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
10907
- const I = resolveIcon(icon);
10908
- return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
10909
- })() : icon : /* @__PURE__ */ (() => {
10910
- const I = icon;
10911
- return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
10912
- })() }),
10913
- label && !icon && /* @__PURE__ */ jsx("span", { children: label })
10914
- ]
10924
+ children: assetUrl ? /* @__PURE__ */ jsx(
10925
+ "img",
10926
+ {
10927
+ src: assetUrl,
10928
+ alt: "",
10929
+ width: 24,
10930
+ height: 24,
10931
+ style: { imageRendering: "pixelated", objectFit: "contain" },
10932
+ className: "flex-shrink-0"
10933
+ }
10934
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
10935
+ const I = resolveIcon(icon);
10936
+ return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
10937
+ })() : icon : /* @__PURE__ */ (() => {
10938
+ const I = icon;
10939
+ return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
10940
+ })() }) : label ? /* @__PURE__ */ jsx("span", { children: label }) : null
10915
10941
  }
10916
10942
  );
10917
10943
  }
10918
- var sizeMap3, shapeMap, variantMap;
10944
+ var sizeMap3, shapeMap, variantMap, DEFAULT_ASSET_URL2;
10919
10945
  var init_ControlButton = __esm({
10920
10946
  "components/game/atoms/ControlButton.tsx"() {
10921
10947
  "use client";
@@ -10938,6 +10964,7 @@ var init_ControlButton = __esm({
10938
10964
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
10939
10965
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
10940
10966
  };
10967
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
10941
10968
  ControlButton.displayName = "ControlButton";
10942
10969
  }
10943
10970
  });
@@ -11017,7 +11044,7 @@ var init_Sprite = __esm({
11017
11044
  }
11018
11045
  });
11019
11046
  function StateIndicator({
11020
- assetUrl = DEFAULT_ASSET_URL,
11047
+ assetUrl = DEFAULT_ASSET_URL3,
11021
11048
  state = "idle",
11022
11049
  label,
11023
11050
  size = "md",
@@ -11056,13 +11083,13 @@ function StateIndicator({
11056
11083
  }
11057
11084
  );
11058
11085
  }
11059
- var DEFAULT_ASSET_URL, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
11086
+ var DEFAULT_ASSET_URL3, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
11060
11087
  var init_StateIndicator = __esm({
11061
11088
  "components/game/atoms/StateIndicator.tsx"() {
11062
11089
  init_Box();
11063
11090
  init_Icon();
11064
11091
  init_cn();
11065
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11092
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11066
11093
  DEFAULT_STATE_STYLES = {
11067
11094
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
11068
11095
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -11137,7 +11164,7 @@ var init_TimerDisplay = __esm({
11137
11164
  }
11138
11165
  });
11139
11166
  function ResourceCounter({
11140
- assetUrl = DEFAULT_ASSET_URL2,
11167
+ assetUrl = DEFAULT_ASSET_URL4,
11141
11168
  icon,
11142
11169
  label = "Gold",
11143
11170
  value = 250,
@@ -11180,7 +11207,7 @@ function ResourceCounter({
11180
11207
  }
11181
11208
  );
11182
11209
  }
11183
- var colorTokenClasses2, DEFAULT_ASSET_URL2, sizeMap5;
11210
+ var colorTokenClasses2, DEFAULT_ASSET_URL4, sizeMap5;
11184
11211
  var init_ResourceCounter = __esm({
11185
11212
  "components/game/atoms/ResourceCounter.tsx"() {
11186
11213
  init_cn();
@@ -11193,7 +11220,7 @@ var init_ResourceCounter = __esm({
11193
11220
  error: "text-error",
11194
11221
  muted: "text-muted-foreground"
11195
11222
  };
11196
- DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
11223
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
11197
11224
  sizeMap5 = {
11198
11225
  sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
11199
11226
  md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
@@ -11203,7 +11230,7 @@ var init_ResourceCounter = __esm({
11203
11230
  }
11204
11231
  });
11205
11232
  function ItemSlot({
11206
- assetUrl = DEFAULT_ASSET_URL3,
11233
+ assetUrl = DEFAULT_ASSET_URL5,
11207
11234
  icon = "sword",
11208
11235
  label = "Iron Sword",
11209
11236
  quantity,
@@ -11262,7 +11289,7 @@ function ItemSlot({
11262
11289
  }
11263
11290
  );
11264
11291
  }
11265
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL3, assetSizeMap;
11292
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL5, assetSizeMap;
11266
11293
  var init_ItemSlot = __esm({
11267
11294
  "components/game/atoms/ItemSlot.tsx"() {
11268
11295
  "use client";
@@ -11287,7 +11314,7 @@ var init_ItemSlot = __esm({
11287
11314
  epic: "shadow-lg",
11288
11315
  legendary: "shadow-lg"
11289
11316
  };
11290
- DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11317
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11291
11318
  assetSizeMap = {
11292
11319
  sm: 28,
11293
11320
  md: 40,
@@ -11297,6 +11324,7 @@ var init_ItemSlot = __esm({
11297
11324
  }
11298
11325
  });
11299
11326
  function TurnIndicator({
11327
+ assetUrl = DEFAULT_ASSET_URL6,
11300
11328
  currentTurn = 1,
11301
11329
  maxTurns,
11302
11330
  activeTeam,
@@ -11329,14 +11357,24 @@ function TurnIndicator({
11329
11357
  ] }),
11330
11358
  activeTeam && /* @__PURE__ */ jsxs(Fragment, { children: [
11331
11359
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "|" }),
11332
- /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
11360
+ assetUrl ? /* @__PURE__ */ jsx(
11361
+ "img",
11362
+ {
11363
+ src: assetUrl,
11364
+ alt: "",
11365
+ width: 12,
11366
+ height: 12,
11367
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11368
+ className: "flex-shrink-0"
11369
+ }
11370
+ ) : /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
11333
11371
  /* @__PURE__ */ jsx("span", { className: "text-success", children: activeTeam })
11334
11372
  ] })
11335
11373
  ]
11336
11374
  }
11337
11375
  );
11338
11376
  }
11339
- var sizeMap7;
11377
+ var sizeMap7, DEFAULT_ASSET_URL6;
11340
11378
  var init_TurnIndicator = __esm({
11341
11379
  "components/game/atoms/TurnIndicator.tsx"() {
11342
11380
  init_cn();
@@ -11345,6 +11383,7 @@ var init_TurnIndicator = __esm({
11345
11383
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
11346
11384
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
11347
11385
  };
11386
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
11348
11387
  TurnIndicator.displayName = "TurnIndicator";
11349
11388
  }
11350
11389
  });
@@ -11360,7 +11399,7 @@ function getComboScale(combo) {
11360
11399
  return "";
11361
11400
  }
11362
11401
  function ComboCounter({
11363
- assetUrl = DEFAULT_ASSET_URL4,
11402
+ assetUrl = DEFAULT_ASSET_URL7,
11364
11403
  combo = 5,
11365
11404
  multiplier,
11366
11405
  streak,
@@ -11405,11 +11444,11 @@ function ComboCounter({
11405
11444
  }
11406
11445
  );
11407
11446
  }
11408
- var DEFAULT_ASSET_URL4, sizeMap8;
11447
+ var DEFAULT_ASSET_URL7, sizeMap8;
11409
11448
  var init_ComboCounter = __esm({
11410
11449
  "components/game/atoms/ComboCounter.tsx"() {
11411
11450
  init_cn();
11412
- DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
11451
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
11413
11452
  sizeMap8 = {
11414
11453
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
11415
11454
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -11503,7 +11542,7 @@ var init_XPBar = __esm({
11503
11542
  }
11504
11543
  });
11505
11544
  function WaypointMarker({
11506
- assetUrl = DEFAULT_ASSET_URL5,
11545
+ assetUrl = DEFAULT_ASSET_URL8,
11507
11546
  label,
11508
11547
  icon,
11509
11548
  active = true,
@@ -11569,12 +11608,12 @@ function WaypointMarker({
11569
11608
  )
11570
11609
  ] });
11571
11610
  }
11572
- var DEFAULT_ASSET_URL5, sizeMap10, checkIcon;
11611
+ var DEFAULT_ASSET_URL8, sizeMap10, checkIcon;
11573
11612
  var init_WaypointMarker = __esm({
11574
11613
  "components/game/atoms/WaypointMarker.tsx"() {
11575
11614
  init_cn();
11576
11615
  init_Icon();
11577
- DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
11616
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
11578
11617
  sizeMap10 = {
11579
11618
  sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
11580
11619
  md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
@@ -11591,7 +11630,7 @@ function formatDuration(seconds) {
11591
11630
  return `${Math.round(seconds)}s`;
11592
11631
  }
11593
11632
  function StatusEffect({
11594
- assetUrl = DEFAULT_ASSET_URL6,
11633
+ assetUrl = DEFAULT_ASSET_URL9,
11595
11634
  icon = "shield",
11596
11635
  label = "Shield",
11597
11636
  duration = 30,
@@ -11649,12 +11688,12 @@ function StatusEffect({
11649
11688
  label && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
11650
11689
  ] });
11651
11690
  }
11652
- var DEFAULT_ASSET_URL6, sizeMap11, variantStyles7;
11691
+ var DEFAULT_ASSET_URL9, sizeMap11, variantStyles7;
11653
11692
  var init_StatusEffect = __esm({
11654
11693
  "components/game/atoms/StatusEffect.tsx"() {
11655
11694
  init_cn();
11656
11695
  init_Icon();
11657
- DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
11696
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
11658
11697
  sizeMap11 = {
11659
11698
  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 },
11660
11699
  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 },
@@ -11669,6 +11708,7 @@ var init_StatusEffect = __esm({
11669
11708
  }
11670
11709
  });
11671
11710
  function DamageNumber({
11711
+ assetUrl = DEFAULT_ASSET_URL10,
11672
11712
  value = 42,
11673
11713
  type = "damage",
11674
11714
  size = "md",
@@ -11677,22 +11717,35 @@ function DamageNumber({
11677
11717
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
11678
11718
  return /* @__PURE__ */ jsxs(Fragment, { children: [
11679
11719
  /* @__PURE__ */ jsx("style", { children: floatKeyframes }),
11680
- /* @__PURE__ */ jsx(
11720
+ /* @__PURE__ */ jsxs(
11681
11721
  "span",
11682
11722
  {
11683
11723
  className: cn(
11684
- "inline-block font-mono select-none pointer-events-none",
11724
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
11685
11725
  sizeMap12[size],
11686
11726
  typeStyles[type],
11687
11727
  className
11688
11728
  ),
11689
11729
  style: { animation: "damageFloat 1s ease-out forwards" },
11690
- children: displayText
11730
+ children: [
11731
+ assetUrl && /* @__PURE__ */ jsx(
11732
+ "img",
11733
+ {
11734
+ src: assetUrl,
11735
+ alt: "",
11736
+ width: 14,
11737
+ height: 14,
11738
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11739
+ className: "flex-shrink-0"
11740
+ }
11741
+ ),
11742
+ displayText
11743
+ ]
11691
11744
  }
11692
11745
  )
11693
11746
  ] });
11694
11747
  }
11695
- var sizeMap12, typeStyles, floatKeyframes;
11748
+ var sizeMap12, typeStyles, floatKeyframes, DEFAULT_ASSET_URL10;
11696
11749
  var init_DamageNumber = __esm({
11697
11750
  "components/game/atoms/DamageNumber.tsx"() {
11698
11751
  init_cn();
@@ -11714,6 +11767,7 @@ var init_DamageNumber = __esm({
11714
11767
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
11715
11768
  }
11716
11769
  `;
11770
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
11717
11771
  DamageNumber.displayName = "DamageNumber";
11718
11772
  }
11719
11773
  });
@@ -11802,6 +11856,7 @@ var init_ChoiceButton = __esm({
11802
11856
  }
11803
11857
  });
11804
11858
  function ActionButton({
11859
+ assetUrl = DEFAULT_ASSET_URL11,
11805
11860
  label = "Attack",
11806
11861
  icon,
11807
11862
  cooldown = 0,
@@ -11841,13 +11896,23 @@ function ActionButton({
11841
11896
  }
11842
11897
  }
11843
11898
  ),
11844
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
11899
+ assetUrl ? /* @__PURE__ */ jsx(
11900
+ "img",
11901
+ {
11902
+ src: assetUrl,
11903
+ alt: "",
11904
+ width: 16,
11905
+ height: 16,
11906
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11907
+ className: cn("flex-shrink-0", sizes.icon)
11908
+ }
11909
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
11845
11910
  const I = resolveIcon(icon);
11846
11911
  return I ? /* @__PURE__ */ jsx(I, { className: "w-4 h-4" }) : null;
11847
11912
  })() : /* @__PURE__ */ (() => {
11848
11913
  const I = icon;
11849
11914
  return /* @__PURE__ */ jsx(I, { className: "w-4 h-4" });
11850
- })() }),
11915
+ })() }) : null,
11851
11916
  /* @__PURE__ */ jsx("span", { className: "relative z-10", children: label }),
11852
11917
  hotkey && /* @__PURE__ */ jsx(
11853
11918
  "span",
@@ -11863,7 +11928,7 @@ function ActionButton({
11863
11928
  }
11864
11929
  );
11865
11930
  }
11866
- var sizeMap13, variantStyles8;
11931
+ var sizeMap13, variantStyles8, DEFAULT_ASSET_URL11;
11867
11932
  var init_ActionButton = __esm({
11868
11933
  "components/game/atoms/ActionButton.tsx"() {
11869
11934
  init_cn();
@@ -11878,6 +11943,7 @@ var init_ActionButton = __esm({
11878
11943
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
11879
11944
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
11880
11945
  };
11946
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
11881
11947
  ActionButton.displayName = "ActionButton";
11882
11948
  }
11883
11949
  });
@@ -12390,10 +12456,10 @@ var init_Container = __esm({
12390
12456
  center = true,
12391
12457
  className,
12392
12458
  children,
12393
- as: Component = "div"
12459
+ as: Component2 = "div"
12394
12460
  }) => {
12395
12461
  const resolvedSize = maxWidth ?? size ?? "lg";
12396
- const Comp = Component;
12462
+ const Comp = Component2;
12397
12463
  return /* @__PURE__ */ jsx(
12398
12464
  Comp,
12399
12465
  {
@@ -27570,7 +27636,7 @@ var init_Flex = __esm({
27570
27636
  basis,
27571
27637
  className,
27572
27638
  children,
27573
- as: Component = "div"
27639
+ as: Component2 = "div"
27574
27640
  }) => {
27575
27641
  const flexStyle = {};
27576
27642
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -27582,7 +27648,7 @@ var init_Flex = __esm({
27582
27648
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
27583
27649
  }
27584
27650
  }
27585
- const Comp = Component;
27651
+ const Comp = Component2;
27586
27652
  return /* @__PURE__ */ jsx(
27587
27653
  Comp,
27588
27654
  {
@@ -27702,11 +27768,11 @@ var init_Grid = __esm({
27702
27768
  className,
27703
27769
  style,
27704
27770
  children,
27705
- as: Component = "div"
27771
+ as: Component2 = "div"
27706
27772
  }) => {
27707
27773
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
27708
27774
  return React91__default.createElement(
27709
- Component,
27775
+ Component2,
27710
27776
  {
27711
27777
  className: cn(
27712
27778
  "grid",
@@ -29532,6 +29598,7 @@ var init_ProgressDots = __esm({
29532
29598
  }
29533
29599
  });
29534
29600
  function StatBadge({
29601
+ assetUrl = DEFAULT_ASSET_URL12,
29535
29602
  label,
29536
29603
  value = 0,
29537
29604
  max,
@@ -29555,7 +29622,17 @@ function StatBadge({
29555
29622
  className
29556
29623
  ),
29557
29624
  children: [
29558
- 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" }) }),
29625
+ assetUrl ? /* @__PURE__ */ jsx(
29626
+ "img",
29627
+ {
29628
+ src: assetUrl,
29629
+ alt: "",
29630
+ width: 16,
29631
+ height: 16,
29632
+ style: { imageRendering: "pixelated", objectFit: "contain" },
29633
+ className: "flex-shrink-0"
29634
+ }
29635
+ ) : 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,
29559
29636
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-medium", children: label }),
29560
29637
  format === "hearts" && max && /* @__PURE__ */ jsx(
29561
29638
  HealthBar,
@@ -29588,7 +29665,7 @@ function StatBadge({
29588
29665
  }
29589
29666
  );
29590
29667
  }
29591
- var sizeMap17, variantMap2;
29668
+ var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
29592
29669
  var init_StatBadge = __esm({
29593
29670
  "components/game/molecules/StatBadge.tsx"() {
29594
29671
  init_cn();
@@ -29607,6 +29684,7 @@ var init_StatBadge = __esm({
29607
29684
  warning: "bg-warning/15 border-warning/40 text-foreground",
29608
29685
  danger: "bg-error/15 border-error/40 text-foreground"
29609
29686
  };
29687
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
29610
29688
  StatBadge.displayName = "StatBadge";
29611
29689
  }
29612
29690
  });
@@ -30263,6 +30341,7 @@ var init_TurnPanel = __esm({
30263
30341
  }
30264
30342
  });
30265
30343
  function EnemyPlate({
30344
+ assetUrl = DEFAULT_ASSET_URL13,
30266
30345
  name = "Shadow Guard",
30267
30346
  health = 80,
30268
30347
  maxHealth = 100,
@@ -30280,15 +30359,28 @@ function EnemyPlate({
30280
30359
  ),
30281
30360
  children: [
30282
30361
  /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
30283
- /* @__PURE__ */ jsx(
30284
- Typography,
30285
- {
30286
- variant: "caption",
30287
- weight: "bold",
30288
- className: "text-[var(--color-foreground)] truncate",
30289
- children: name
30290
- }
30291
- ),
30362
+ /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
30363
+ assetUrl && /* @__PURE__ */ jsx(
30364
+ "img",
30365
+ {
30366
+ src: assetUrl,
30367
+ alt: name,
30368
+ width: 24,
30369
+ height: 24,
30370
+ style: { objectFit: "cover", borderRadius: "50%" },
30371
+ className: "flex-shrink-0"
30372
+ }
30373
+ ),
30374
+ /* @__PURE__ */ jsx(
30375
+ Typography,
30376
+ {
30377
+ variant: "caption",
30378
+ weight: "bold",
30379
+ className: "text-[var(--color-foreground)] truncate",
30380
+ children: name
30381
+ }
30382
+ )
30383
+ ] }),
30292
30384
  level != null && /* @__PURE__ */ jsxs(Badge, { variant: "neutral", size: "sm", children: [
30293
30385
  "Lv.",
30294
30386
  level
@@ -30333,7 +30425,7 @@ function EnemyPlate({
30333
30425
  }
30334
30426
  );
30335
30427
  }
30336
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
30428
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
30337
30429
  var init_EnemyPlate = __esm({
30338
30430
  "components/game/molecules/EnemyPlate.tsx"() {
30339
30431
  "use client";
@@ -30350,6 +30442,7 @@ var init_EnemyPlate = __esm({
30350
30442
  DEFAULT_ENEMY_EFFECTS = [
30351
30443
  { icon: "flame", label: "Burn", variant: "debuff" }
30352
30444
  ];
30445
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
30353
30446
  EnemyPlate.displayName = "EnemyPlate";
30354
30447
  }
30355
30448
  });
@@ -31881,13 +31974,13 @@ var init_MapView = __esm({
31881
31974
  shadowSize: [41, 41]
31882
31975
  });
31883
31976
  L.Marker.prototype.options.icon = defaultIcon;
31884
- const { useEffect: useEffect78, useRef: useRef70, useCallback: useCallback117, useState: useState115 } = React91__default;
31977
+ const { useEffect: useEffect84, useRef: useRef76, useCallback: useCallback124, useState: useState120 } = React91__default;
31885
31978
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31886
- const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31979
+ const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31887
31980
  function MapUpdater({ centerLat, centerLng, zoom }) {
31888
31981
  const map = useMap();
31889
- const prevRef = useRef70({ centerLat, centerLng, zoom });
31890
- useEffect78(() => {
31982
+ const prevRef = useRef76({ centerLat, centerLng, zoom });
31983
+ useEffect84(() => {
31891
31984
  const prev = prevRef.current;
31892
31985
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
31893
31986
  map.setView([centerLat, centerLng], zoom);
@@ -31898,7 +31991,7 @@ var init_MapView = __esm({
31898
31991
  }
31899
31992
  function MapClickHandler({ onMapClick }) {
31900
31993
  const map = useMap();
31901
- useEffect78(() => {
31994
+ useEffect84(() => {
31902
31995
  if (!onMapClick) return;
31903
31996
  const handler = (e) => {
31904
31997
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -31925,9 +32018,9 @@ var init_MapView = __esm({
31925
32018
  className,
31926
32019
  showAttribution = true
31927
32020
  }) {
31928
- const eventBus = useEventBus3();
31929
- const [clickedPosition, setClickedPosition] = useState115(null);
31930
- const handleMapClick = useCallback117((lat, lng) => {
32021
+ const eventBus = useEventBus4();
32022
+ const [clickedPosition, setClickedPosition] = useState120(null);
32023
+ const handleMapClick = useCallback124((lat, lng) => {
31931
32024
  if (showClickedPin) {
31932
32025
  setClickedPosition({ lat, lng });
31933
32026
  }
@@ -31936,7 +32029,7 @@ var init_MapView = __esm({
31936
32029
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
31937
32030
  }
31938
32031
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
31939
- const handleMarkerClick = useCallback117((marker) => {
32032
+ const handleMarkerClick = useCallback124((marker) => {
31940
32033
  onMarkerClick?.(marker);
31941
32034
  if (markerClickEvent) {
31942
32035
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -38654,10 +38747,10 @@ var init_Section = __esm({
38654
38747
  children,
38655
38748
  headerClassName,
38656
38749
  contentClassName,
38657
- as: Component = "section"
38750
+ as: Component2 = "section"
38658
38751
  }) => {
38659
38752
  const hasHeader = title || description || action;
38660
- const Comp = Component;
38753
+ const Comp = Component2;
38661
38754
  return /* @__PURE__ */ jsxs(
38662
38755
  Comp,
38663
38756
  {
@@ -43222,6 +43315,1568 @@ var init_GameAudioToggle = __esm({
43222
43315
  GameAudioToggle.displayName = "GameAudioToggle";
43223
43316
  }
43224
43317
  });
43318
+ function detectAssetRoot(modelUrl) {
43319
+ const idx = modelUrl.indexOf("/3d/");
43320
+ if (idx !== -1) {
43321
+ return modelUrl.substring(0, idx + 4);
43322
+ }
43323
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
43324
+ }
43325
+ function createGLTFLoaderForUrl(url) {
43326
+ const loader = new GLTFLoader();
43327
+ loader.setResourcePath(detectAssetRoot(url));
43328
+ return loader;
43329
+ }
43330
+ var AssetLoader;
43331
+ var init_AssetLoader = __esm({
43332
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
43333
+ AssetLoader = class {
43334
+ constructor() {
43335
+ __publicField(this, "objLoader");
43336
+ __publicField(this, "textureLoader");
43337
+ __publicField(this, "modelCache");
43338
+ __publicField(this, "textureCache");
43339
+ __publicField(this, "loadingPromises");
43340
+ this.objLoader = new OBJLoader();
43341
+ this.textureLoader = new THREE.TextureLoader();
43342
+ this.modelCache = /* @__PURE__ */ new Map();
43343
+ this.textureCache = /* @__PURE__ */ new Map();
43344
+ this.loadingPromises = /* @__PURE__ */ new Map();
43345
+ }
43346
+ /**
43347
+ * Load a GLB/GLTF model
43348
+ * @param url - URL to the .glb or .gltf file
43349
+ * @returns Promise with loaded model scene and animations
43350
+ */
43351
+ async loadModel(url) {
43352
+ if (this.modelCache.has(url)) {
43353
+ return this.modelCache.get(url);
43354
+ }
43355
+ if (this.loadingPromises.has(url)) {
43356
+ return this.loadingPromises.get(url);
43357
+ }
43358
+ const loader = createGLTFLoaderForUrl(url);
43359
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
43360
+ const result = {
43361
+ scene: gltf.scene,
43362
+ animations: gltf.animations || []
43363
+ };
43364
+ this.modelCache.set(url, result);
43365
+ this.loadingPromises.delete(url);
43366
+ return result;
43367
+ }).catch((error) => {
43368
+ this.loadingPromises.delete(url);
43369
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
43370
+ });
43371
+ this.loadingPromises.set(url, loadPromise);
43372
+ return loadPromise;
43373
+ }
43374
+ /**
43375
+ * Load an OBJ model (fallback for non-GLB assets)
43376
+ * @param url - URL to the .obj file
43377
+ * @returns Promise with loaded object group
43378
+ */
43379
+ async loadOBJ(url) {
43380
+ if (this.modelCache.has(url)) {
43381
+ return this.modelCache.get(url).scene;
43382
+ }
43383
+ if (this.loadingPromises.has(url)) {
43384
+ const result = await this.loadingPromises.get(url);
43385
+ return result.scene;
43386
+ }
43387
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
43388
+ const result = {
43389
+ scene: group,
43390
+ animations: []
43391
+ };
43392
+ this.modelCache.set(url, result);
43393
+ this.loadingPromises.delete(url);
43394
+ return result;
43395
+ }).catch((error) => {
43396
+ this.loadingPromises.delete(url);
43397
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
43398
+ });
43399
+ this.loadingPromises.set(url, loadPromise);
43400
+ return (await loadPromise).scene;
43401
+ }
43402
+ /**
43403
+ * Load a texture
43404
+ * @param url - URL to the texture image
43405
+ * @returns Promise with loaded texture
43406
+ */
43407
+ async loadTexture(url) {
43408
+ if (this.textureCache.has(url)) {
43409
+ return this.textureCache.get(url);
43410
+ }
43411
+ if (this.loadingPromises.has(`texture:${url}`)) {
43412
+ return this.loadingPromises.get(`texture:${url}`);
43413
+ }
43414
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
43415
+ texture.colorSpace = THREE.SRGBColorSpace;
43416
+ this.textureCache.set(url, texture);
43417
+ this.loadingPromises.delete(`texture:${url}`);
43418
+ return texture;
43419
+ }).catch((error) => {
43420
+ this.loadingPromises.delete(`texture:${url}`);
43421
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
43422
+ });
43423
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
43424
+ return loadPromise;
43425
+ }
43426
+ /**
43427
+ * Preload multiple assets
43428
+ * @param urls - Array of asset URLs to preload
43429
+ * @returns Promise that resolves when all assets are loaded
43430
+ */
43431
+ async preload(urls) {
43432
+ const promises = urls.map((url) => {
43433
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
43434
+ return this.loadModel(url).catch(() => null);
43435
+ } else if (url.endsWith(".obj")) {
43436
+ return this.loadOBJ(url).catch(() => null);
43437
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
43438
+ return this.loadTexture(url).catch(() => null);
43439
+ }
43440
+ return Promise.resolve(null);
43441
+ });
43442
+ await Promise.all(promises);
43443
+ }
43444
+ /**
43445
+ * Check if a model is cached
43446
+ * @param url - Model URL
43447
+ */
43448
+ hasModel(url) {
43449
+ return this.modelCache.has(url);
43450
+ }
43451
+ /**
43452
+ * Check if a texture is cached
43453
+ * @param url - Texture URL
43454
+ */
43455
+ hasTexture(url) {
43456
+ return this.textureCache.has(url);
43457
+ }
43458
+ /**
43459
+ * Get cached model (throws if not cached)
43460
+ * @param url - Model URL
43461
+ */
43462
+ getModel(url) {
43463
+ const model = this.modelCache.get(url);
43464
+ if (!model) {
43465
+ throw new Error(`Model ${url} not in cache`);
43466
+ }
43467
+ return model;
43468
+ }
43469
+ /**
43470
+ * Get cached texture (throws if not cached)
43471
+ * @param url - Texture URL
43472
+ */
43473
+ getTexture(url) {
43474
+ const texture = this.textureCache.get(url);
43475
+ if (!texture) {
43476
+ throw new Error(`Texture ${url} not in cache`);
43477
+ }
43478
+ return texture;
43479
+ }
43480
+ /**
43481
+ * Clear all caches
43482
+ */
43483
+ clearCache() {
43484
+ this.textureCache.forEach((texture) => {
43485
+ texture.dispose();
43486
+ });
43487
+ this.modelCache.forEach((model) => {
43488
+ model.scene.traverse((child) => {
43489
+ if (child instanceof THREE.Mesh) {
43490
+ child.geometry.dispose();
43491
+ if (Array.isArray(child.material)) {
43492
+ child.material.forEach((m) => m.dispose());
43493
+ } else {
43494
+ child.material.dispose();
43495
+ }
43496
+ }
43497
+ });
43498
+ });
43499
+ this.modelCache.clear();
43500
+ this.textureCache.clear();
43501
+ this.loadingPromises.clear();
43502
+ }
43503
+ /**
43504
+ * Get cache statistics
43505
+ */
43506
+ getStats() {
43507
+ return {
43508
+ models: this.modelCache.size,
43509
+ textures: this.textureCache.size,
43510
+ loading: this.loadingPromises.size
43511
+ };
43512
+ }
43513
+ };
43514
+ new AssetLoader();
43515
+ }
43516
+ });
43517
+ function useAssetLoader(options = {}) {
43518
+ const { preloadUrls = [], loader: customLoader } = options;
43519
+ const loaderRef = useRef(customLoader || new AssetLoader());
43520
+ const [state, setState] = useState({
43521
+ isLoading: false,
43522
+ progress: 0,
43523
+ loaded: 0,
43524
+ total: 0,
43525
+ errors: []
43526
+ });
43527
+ useEffect(() => {
43528
+ if (preloadUrls.length > 0) {
43529
+ preload(preloadUrls);
43530
+ }
43531
+ }, []);
43532
+ const updateProgress = useCallback((loaded, total) => {
43533
+ setState((prev) => ({
43534
+ ...prev,
43535
+ loaded,
43536
+ total,
43537
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
43538
+ }));
43539
+ }, []);
43540
+ const loadModel = useCallback(
43541
+ async (url) => {
43542
+ setState((prev) => ({ ...prev, isLoading: true }));
43543
+ try {
43544
+ const model = await loaderRef.current.loadModel(url);
43545
+ setState((prev) => ({
43546
+ ...prev,
43547
+ isLoading: false,
43548
+ loaded: prev.loaded + 1
43549
+ }));
43550
+ return model;
43551
+ } catch (error) {
43552
+ const errorMsg = error instanceof Error ? error.message : String(error);
43553
+ setState((prev) => ({
43554
+ ...prev,
43555
+ isLoading: false,
43556
+ errors: [...prev.errors, errorMsg]
43557
+ }));
43558
+ throw error;
43559
+ }
43560
+ },
43561
+ []
43562
+ );
43563
+ const loadOBJ = useCallback(
43564
+ async (url) => {
43565
+ setState((prev) => ({ ...prev, isLoading: true }));
43566
+ try {
43567
+ const model = await loaderRef.current.loadOBJ(url);
43568
+ setState((prev) => ({
43569
+ ...prev,
43570
+ isLoading: false,
43571
+ loaded: prev.loaded + 1
43572
+ }));
43573
+ return model;
43574
+ } catch (error) {
43575
+ const errorMsg = error instanceof Error ? error.message : String(error);
43576
+ setState((prev) => ({
43577
+ ...prev,
43578
+ isLoading: false,
43579
+ errors: [...prev.errors, errorMsg]
43580
+ }));
43581
+ throw error;
43582
+ }
43583
+ },
43584
+ []
43585
+ );
43586
+ const loadTexture = useCallback(
43587
+ async (url) => {
43588
+ setState((prev) => ({ ...prev, isLoading: true }));
43589
+ try {
43590
+ const texture = await loaderRef.current.loadTexture(url);
43591
+ setState((prev) => ({
43592
+ ...prev,
43593
+ isLoading: false,
43594
+ loaded: prev.loaded + 1
43595
+ }));
43596
+ return texture;
43597
+ } catch (error) {
43598
+ const errorMsg = error instanceof Error ? error.message : String(error);
43599
+ setState((prev) => ({
43600
+ ...prev,
43601
+ isLoading: false,
43602
+ errors: [...prev.errors, errorMsg]
43603
+ }));
43604
+ throw error;
43605
+ }
43606
+ },
43607
+ []
43608
+ );
43609
+ const preload = useCallback(
43610
+ async (urls) => {
43611
+ setState((prev) => ({
43612
+ ...prev,
43613
+ isLoading: true,
43614
+ total: urls.length,
43615
+ loaded: 0,
43616
+ errors: []
43617
+ }));
43618
+ let completed = 0;
43619
+ const errors = [];
43620
+ await Promise.all(
43621
+ urls.map(async (url) => {
43622
+ try {
43623
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
43624
+ await loaderRef.current.loadModel(url);
43625
+ } else if (url.endsWith(".obj")) {
43626
+ await loaderRef.current.loadOBJ(url);
43627
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
43628
+ await loaderRef.current.loadTexture(url);
43629
+ }
43630
+ completed++;
43631
+ updateProgress(completed, urls.length);
43632
+ } catch (error) {
43633
+ const errorMsg = error instanceof Error ? error.message : String(error);
43634
+ errors.push(`${url}: ${errorMsg}`);
43635
+ completed++;
43636
+ updateProgress(completed, urls.length);
43637
+ }
43638
+ })
43639
+ );
43640
+ setState((prev) => ({
43641
+ ...prev,
43642
+ isLoading: false,
43643
+ errors
43644
+ }));
43645
+ },
43646
+ [updateProgress]
43647
+ );
43648
+ const hasModel = useCallback((url) => {
43649
+ return loaderRef.current.hasModel(url);
43650
+ }, []);
43651
+ const hasTexture = useCallback((url) => {
43652
+ return loaderRef.current.hasTexture(url);
43653
+ }, []);
43654
+ const getModel = useCallback((url) => {
43655
+ try {
43656
+ return loaderRef.current.getModel(url);
43657
+ } catch {
43658
+ return void 0;
43659
+ }
43660
+ }, []);
43661
+ const getTexture = useCallback((url) => {
43662
+ try {
43663
+ return loaderRef.current.getTexture(url);
43664
+ } catch {
43665
+ return void 0;
43666
+ }
43667
+ }, []);
43668
+ const clearCache = useCallback(() => {
43669
+ loaderRef.current.clearCache();
43670
+ setState({
43671
+ isLoading: false,
43672
+ progress: 0,
43673
+ loaded: 0,
43674
+ total: 0,
43675
+ errors: []
43676
+ });
43677
+ }, []);
43678
+ return {
43679
+ ...state,
43680
+ loadModel,
43681
+ loadOBJ,
43682
+ loadTexture,
43683
+ preload,
43684
+ hasModel,
43685
+ hasTexture,
43686
+ getModel,
43687
+ getTexture,
43688
+ clearCache
43689
+ };
43690
+ }
43691
+ var init_useAssetLoader = __esm({
43692
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
43693
+ "use client";
43694
+ init_AssetLoader();
43695
+ }
43696
+ });
43697
+ function useGameCanvas3DEvents(options) {
43698
+ const {
43699
+ tileClickEvent,
43700
+ unitClickEvent,
43701
+ featureClickEvent,
43702
+ canvasClickEvent,
43703
+ tileHoverEvent,
43704
+ tileLeaveEvent,
43705
+ unitAnimationEvent,
43706
+ cameraChangeEvent,
43707
+ onTileClick,
43708
+ onUnitClick,
43709
+ onFeatureClick,
43710
+ onCanvasClick,
43711
+ onTileHover,
43712
+ onUnitAnimation
43713
+ } = options;
43714
+ const emit = useEmitEvent();
43715
+ const optionsRef = useRef(options);
43716
+ optionsRef.current = options;
43717
+ const handleTileClick = useCallback(
43718
+ (tile, event) => {
43719
+ if (tileClickEvent) {
43720
+ emit(tileClickEvent, {
43721
+ tileId: tile.id,
43722
+ x: tile.x,
43723
+ z: tile.z ?? tile.y ?? 0,
43724
+ type: tile.type,
43725
+ terrain: tile.terrain,
43726
+ elevation: tile.elevation
43727
+ });
43728
+ }
43729
+ optionsRef.current.onTileClick?.(tile, event);
43730
+ },
43731
+ [tileClickEvent, emit]
43732
+ );
43733
+ const handleUnitClick = useCallback(
43734
+ (unit, event) => {
43735
+ if (unitClickEvent) {
43736
+ emit(unitClickEvent, {
43737
+ unitId: unit.id,
43738
+ x: unit.x,
43739
+ z: unit.z ?? unit.y ?? 0,
43740
+ unitType: unit.unitType,
43741
+ name: unit.name,
43742
+ team: unit.team,
43743
+ faction: unit.faction,
43744
+ health: unit.health,
43745
+ maxHealth: unit.maxHealth
43746
+ });
43747
+ }
43748
+ optionsRef.current.onUnitClick?.(unit, event);
43749
+ },
43750
+ [unitClickEvent, emit]
43751
+ );
43752
+ const handleFeatureClick = useCallback(
43753
+ (feature, event) => {
43754
+ if (featureClickEvent) {
43755
+ emit(featureClickEvent, {
43756
+ featureId: feature.id,
43757
+ x: feature.x,
43758
+ z: feature.z ?? feature.y ?? 0,
43759
+ type: feature.type,
43760
+ elevation: feature.elevation
43761
+ });
43762
+ }
43763
+ optionsRef.current.onFeatureClick?.(feature, event);
43764
+ },
43765
+ [featureClickEvent, emit]
43766
+ );
43767
+ const handleCanvasClick = useCallback(
43768
+ (event) => {
43769
+ if (canvasClickEvent) {
43770
+ emit(canvasClickEvent, {
43771
+ clientX: event.clientX,
43772
+ clientY: event.clientY,
43773
+ button: event.button
43774
+ });
43775
+ }
43776
+ optionsRef.current.onCanvasClick?.(event);
43777
+ },
43778
+ [canvasClickEvent, emit]
43779
+ );
43780
+ const handleTileHover = useCallback(
43781
+ (tile, event) => {
43782
+ if (tile) {
43783
+ if (tileHoverEvent) {
43784
+ emit(tileHoverEvent, {
43785
+ tileId: tile.id,
43786
+ x: tile.x,
43787
+ z: tile.z ?? tile.y ?? 0,
43788
+ type: tile.type
43789
+ });
43790
+ }
43791
+ } else {
43792
+ if (tileLeaveEvent) {
43793
+ emit(tileLeaveEvent, {});
43794
+ }
43795
+ }
43796
+ optionsRef.current.onTileHover?.(tile, event);
43797
+ },
43798
+ [tileHoverEvent, tileLeaveEvent, emit]
43799
+ );
43800
+ const handleUnitAnimation = useCallback(
43801
+ (unitId, state) => {
43802
+ if (unitAnimationEvent) {
43803
+ emit(unitAnimationEvent, {
43804
+ unitId,
43805
+ state,
43806
+ timestamp: Date.now()
43807
+ });
43808
+ }
43809
+ optionsRef.current.onUnitAnimation?.(unitId, state);
43810
+ },
43811
+ [unitAnimationEvent, emit]
43812
+ );
43813
+ const handleCameraChange = useCallback(
43814
+ (position) => {
43815
+ if (cameraChangeEvent) {
43816
+ emit(cameraChangeEvent, {
43817
+ position,
43818
+ timestamp: Date.now()
43819
+ });
43820
+ }
43821
+ },
43822
+ [cameraChangeEvent, emit]
43823
+ );
43824
+ return {
43825
+ handleTileClick,
43826
+ handleUnitClick,
43827
+ handleFeatureClick,
43828
+ handleCanvasClick,
43829
+ handleTileHover,
43830
+ handleUnitAnimation,
43831
+ handleCameraChange
43832
+ };
43833
+ }
43834
+ var init_useGameCanvas3DEvents = __esm({
43835
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
43836
+ "use client";
43837
+ init_useEventBus();
43838
+ }
43839
+ });
43840
+
43841
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
43842
+ var init_Canvas3DLoadingState = __esm({
43843
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
43844
+ }
43845
+ });
43846
+ function Canvas3DLoadingState({
43847
+ progress = 0,
43848
+ loaded = 0,
43849
+ total = 0,
43850
+ message = "Loading 3D Scene...",
43851
+ details,
43852
+ showSpinner = true,
43853
+ className
43854
+ }) {
43855
+ const clampedProgress = Math.max(0, Math.min(100, progress));
43856
+ const hasProgress = total > 0;
43857
+ return /* @__PURE__ */ jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
43858
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__content", children: [
43859
+ showSpinner && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__spinner", children: [
43860
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring" }),
43861
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring spinner__ring--secondary" })
43862
+ ] }),
43863
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__message", children: message }),
43864
+ details && /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__details", children: details }),
43865
+ hasProgress && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__progress", children: [
43866
+ /* @__PURE__ */ jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsx(
43867
+ "div",
43868
+ {
43869
+ className: "progress__fill",
43870
+ style: { width: `${clampedProgress}%` }
43871
+ }
43872
+ ) }),
43873
+ /* @__PURE__ */ jsxs("div", { className: "progress__text", children: [
43874
+ /* @__PURE__ */ jsxs("span", { className: "progress__percentage", children: [
43875
+ clampedProgress,
43876
+ "%"
43877
+ ] }),
43878
+ /* @__PURE__ */ jsxs("span", { className: "progress__count", children: [
43879
+ "(",
43880
+ loaded,
43881
+ "/",
43882
+ total,
43883
+ ")"
43884
+ ] })
43885
+ ] })
43886
+ ] })
43887
+ ] }),
43888
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsx("div", { className: "bg__grid" }) })
43889
+ ] });
43890
+ }
43891
+ var init_Canvas3DLoadingState2 = __esm({
43892
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
43893
+ init_Canvas3DLoadingState();
43894
+ }
43895
+ });
43896
+
43897
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
43898
+ var init_Canvas3DErrorBoundary = __esm({
43899
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
43900
+ }
43901
+ });
43902
+ var log9, Canvas3DErrorBoundary;
43903
+ var init_Canvas3DErrorBoundary2 = __esm({
43904
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
43905
+ init_Canvas3DErrorBoundary();
43906
+ log9 = createLogger("almadar:ui:game:canvas3d:error-boundary");
43907
+ Canvas3DErrorBoundary = class extends Component {
43908
+ constructor(props) {
43909
+ super(props);
43910
+ __publicField(this, "handleReset", () => {
43911
+ this.setState({
43912
+ hasError: false,
43913
+ error: null,
43914
+ errorInfo: null
43915
+ });
43916
+ this.props.onReset?.();
43917
+ });
43918
+ this.state = {
43919
+ hasError: false,
43920
+ error: null,
43921
+ errorInfo: null
43922
+ };
43923
+ }
43924
+ static getDerivedStateFromError(error) {
43925
+ return {
43926
+ hasError: true,
43927
+ error,
43928
+ errorInfo: null
43929
+ };
43930
+ }
43931
+ componentDidCatch(error, errorInfo) {
43932
+ this.setState({ errorInfo });
43933
+ this.props.onError?.(error, errorInfo);
43934
+ log9.error("Error caught", { error });
43935
+ log9.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
43936
+ }
43937
+ render() {
43938
+ if (this.state.hasError) {
43939
+ if (this.props.fallback) {
43940
+ return this.props.fallback;
43941
+ }
43942
+ return /* @__PURE__ */ jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__content", children: [
43943
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
43944
+ /* @__PURE__ */ jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
43945
+ /* @__PURE__ */ jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
43946
+ this.state.error && /* @__PURE__ */ jsxs("details", { className: "canvas-3d-error__details", children: [
43947
+ /* @__PURE__ */ jsx("summary", { children: "Error Details" }),
43948
+ /* @__PURE__ */ jsxs("pre", { className: "error__stack", children: [
43949
+ this.state.error.message,
43950
+ "\n",
43951
+ this.state.error.stack
43952
+ ] }),
43953
+ this.state.errorInfo && /* @__PURE__ */ jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
43954
+ ] }),
43955
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__actions", children: [
43956
+ /* @__PURE__ */ jsx(
43957
+ "button",
43958
+ {
43959
+ className: "error__button error__button--primary",
43960
+ onClick: this.handleReset,
43961
+ children: "Try Again"
43962
+ }
43963
+ ),
43964
+ /* @__PURE__ */ jsx(
43965
+ "button",
43966
+ {
43967
+ className: "error__button error__button--secondary",
43968
+ onClick: () => window.location.reload(),
43969
+ children: "Reload Page"
43970
+ }
43971
+ )
43972
+ ] })
43973
+ ] }) });
43974
+ }
43975
+ return this.props.children;
43976
+ }
43977
+ };
43978
+ }
43979
+ });
43980
+ function detectAssetRoot2(modelUrl) {
43981
+ const idx = modelUrl.indexOf("/3d/");
43982
+ if (idx !== -1) {
43983
+ return modelUrl.substring(0, idx + 4);
43984
+ }
43985
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
43986
+ }
43987
+ function useGLTFModel(url, resourceBasePath) {
43988
+ const [state, setState] = useState({
43989
+ model: null,
43990
+ isLoading: false,
43991
+ error: null
43992
+ });
43993
+ useEffect(() => {
43994
+ if (!url) {
43995
+ setState({ model: null, isLoading: false, error: null });
43996
+ return;
43997
+ }
43998
+ log10.debug("Loading", { url });
43999
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
44000
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
44001
+ const loader = new GLTFLoader$1();
44002
+ loader.setResourcePath(assetRoot);
44003
+ loader.load(
44004
+ url,
44005
+ (gltf) => {
44006
+ log10.debug("Loaded", { url });
44007
+ setState({
44008
+ model: gltf.scene,
44009
+ isLoading: false,
44010
+ error: null
44011
+ });
44012
+ },
44013
+ void 0,
44014
+ (err) => {
44015
+ log10.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
44016
+ setState({
44017
+ model: null,
44018
+ isLoading: false,
44019
+ error: err instanceof Error ? err : new Error(String(err))
44020
+ });
44021
+ }
44022
+ );
44023
+ }, [url, resourceBasePath]);
44024
+ return state;
44025
+ }
44026
+ function ModelLoader({
44027
+ url,
44028
+ position = [0, 0, 0],
44029
+ scale = 1,
44030
+ rotation = [0, 0, 0],
44031
+ isSelected = false,
44032
+ isHovered = false,
44033
+ onClick,
44034
+ onHover,
44035
+ fallbackGeometry = "box",
44036
+ castShadow = true,
44037
+ receiveShadow = true,
44038
+ resourceBasePath
44039
+ }) {
44040
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
44041
+ const model = useMemo(() => {
44042
+ if (!loadedModel) return null;
44043
+ const cloned = loadedModel.clone();
44044
+ cloned.traverse((child) => {
44045
+ if (child instanceof THREE.Mesh) {
44046
+ child.castShadow = castShadow;
44047
+ child.receiveShadow = receiveShadow;
44048
+ }
44049
+ });
44050
+ return cloned;
44051
+ }, [loadedModel, castShadow, receiveShadow]);
44052
+ const scaleArray = useMemo(() => {
44053
+ if (typeof scale === "number") {
44054
+ return [scale, scale, scale];
44055
+ }
44056
+ return scale;
44057
+ }, [scale]);
44058
+ const rotationRad = useMemo(() => {
44059
+ return [
44060
+ rotation[0] * Math.PI / 180,
44061
+ rotation[1] * Math.PI / 180,
44062
+ rotation[2] * Math.PI / 180
44063
+ ];
44064
+ }, [rotation]);
44065
+ if (isLoading) {
44066
+ return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
44067
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
44068
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
44069
+ ] }) });
44070
+ }
44071
+ if (error || !model) {
44072
+ if (fallbackGeometry === "none") {
44073
+ return /* @__PURE__ */ jsx("group", { position });
44074
+ }
44075
+ const fallbackProps = {
44076
+ onClick,
44077
+ onPointerOver: () => onHover?.(true),
44078
+ onPointerOut: () => onHover?.(false)
44079
+ };
44080
+ return /* @__PURE__ */ jsxs("group", { position, children: [
44081
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
44082
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
44083
+ /* @__PURE__ */ jsx(
44084
+ "meshBasicMaterial",
44085
+ {
44086
+ color: isSelected ? 16755200 : 16777215,
44087
+ transparent: true,
44088
+ opacity: 0.5
44089
+ }
44090
+ )
44091
+ ] }),
44092
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
44093
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
44094
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
44095
+ ] }),
44096
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
44097
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.4, 16, 16] }),
44098
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
44099
+ ] }),
44100
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
44101
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
44102
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
44103
+ ] })
44104
+ ] });
44105
+ }
44106
+ return /* @__PURE__ */ jsxs(
44107
+ "group",
44108
+ {
44109
+ position,
44110
+ rotation: rotationRad,
44111
+ onClick,
44112
+ onPointerOver: () => onHover?.(true),
44113
+ onPointerOut: () => onHover?.(false),
44114
+ children: [
44115
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
44116
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
44117
+ /* @__PURE__ */ jsx(
44118
+ "meshBasicMaterial",
44119
+ {
44120
+ color: isSelected ? 16755200 : 16777215,
44121
+ transparent: true,
44122
+ opacity: 0.5
44123
+ }
44124
+ )
44125
+ ] }),
44126
+ /* @__PURE__ */ jsx("primitive", { object: model, scale: scaleArray })
44127
+ ]
44128
+ }
44129
+ );
44130
+ }
44131
+ var log10;
44132
+ var init_ModelLoader = __esm({
44133
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
44134
+ "use client";
44135
+ log10 = createLogger("almadar:ui:game:model-loader");
44136
+ }
44137
+ });
44138
+
44139
+ // components/game/molecules/GameCanvas3D.css
44140
+ var init_GameCanvas3D = __esm({
44141
+ "components/game/molecules/GameCanvas3D.css"() {
44142
+ }
44143
+ });
44144
+ function CameraController({
44145
+ onCameraChange
44146
+ }) {
44147
+ const { camera } = useThree();
44148
+ useEffect(() => {
44149
+ if (onCameraChange) {
44150
+ onCameraChange({
44151
+ x: camera.position.x,
44152
+ y: camera.position.y,
44153
+ z: camera.position.z
44154
+ });
44155
+ }
44156
+ }, [camera.position, onCameraChange]);
44157
+ return null;
44158
+ }
44159
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
44160
+ var init_GameCanvas3D2 = __esm({
44161
+ "components/game/molecules/GameCanvas3D.tsx"() {
44162
+ "use client";
44163
+ init_AssetLoader();
44164
+ init_useAssetLoader();
44165
+ init_useGameCanvas3DEvents();
44166
+ init_Canvas3DLoadingState2();
44167
+ init_Canvas3DErrorBoundary2();
44168
+ init_ModelLoader();
44169
+ init_cn();
44170
+ init_GameCanvas3D();
44171
+ DEFAULT_GRID_CONFIG = {
44172
+ cellSize: 1,
44173
+ offsetX: 0,
44174
+ offsetZ: 0
44175
+ };
44176
+ GameCanvas3D = forwardRef(
44177
+ ({
44178
+ tiles = [],
44179
+ units = [],
44180
+ features = [],
44181
+ events: events2 = [],
44182
+ orientation = "standard",
44183
+ cameraMode = "isometric",
44184
+ showGrid = true,
44185
+ showCoordinates = false,
44186
+ showTileInfo = false,
44187
+ overlay = "default",
44188
+ shadows = true,
44189
+ backgroundColor = "#1a1a2e",
44190
+ onTileClick,
44191
+ onUnitClick,
44192
+ onFeatureClick,
44193
+ onCanvasClick,
44194
+ onTileHover,
44195
+ onUnitAnimation,
44196
+ assetLoader: customAssetLoader,
44197
+ tileRenderer: CustomTileRenderer,
44198
+ unitRenderer: CustomUnitRenderer,
44199
+ featureRenderer: CustomFeatureRenderer,
44200
+ className,
44201
+ isLoading: externalLoading,
44202
+ error: externalError,
44203
+ entity,
44204
+ preloadAssets = [],
44205
+ tileClickEvent,
44206
+ unitClickEvent,
44207
+ featureClickEvent,
44208
+ canvasClickEvent,
44209
+ tileHoverEvent,
44210
+ tileLeaveEvent,
44211
+ unitAnimationEvent,
44212
+ cameraChangeEvent,
44213
+ loadingMessage = "Loading 3D Scene...",
44214
+ useInstancing = true,
44215
+ validMoves = [],
44216
+ attackTargets = [],
44217
+ selectedTileIds = [],
44218
+ selectedUnitId = null,
44219
+ children
44220
+ }, ref) => {
44221
+ const containerRef = useRef(null);
44222
+ const controlsRef = useRef(null);
44223
+ const [hoveredTile, setHoveredTile] = useState(null);
44224
+ const [internalError, setInternalError] = useState(null);
44225
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
44226
+ preloadUrls: preloadAssets,
44227
+ loader: customAssetLoader
44228
+ });
44229
+ const eventHandlers = useGameCanvas3DEvents({
44230
+ tileClickEvent,
44231
+ unitClickEvent,
44232
+ featureClickEvent,
44233
+ canvasClickEvent,
44234
+ tileHoverEvent,
44235
+ tileLeaveEvent,
44236
+ unitAnimationEvent,
44237
+ cameraChangeEvent,
44238
+ onTileClick,
44239
+ onUnitClick,
44240
+ onFeatureClick,
44241
+ onCanvasClick,
44242
+ onTileHover,
44243
+ onUnitAnimation
44244
+ });
44245
+ const gridBounds = useMemo(() => {
44246
+ if (tiles.length === 0) {
44247
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
44248
+ }
44249
+ const xs = tiles.map((t) => t.x);
44250
+ const zs = tiles.map((t) => t.z || t.y || 0);
44251
+ return {
44252
+ minX: Math.min(...xs),
44253
+ maxX: Math.max(...xs),
44254
+ minZ: Math.min(...zs),
44255
+ maxZ: Math.max(...zs)
44256
+ };
44257
+ }, [tiles]);
44258
+ const cameraTarget = useMemo(() => {
44259
+ return [
44260
+ (gridBounds.minX + gridBounds.maxX) / 2,
44261
+ 0,
44262
+ (gridBounds.minZ + gridBounds.maxZ) / 2
44263
+ ];
44264
+ }, [gridBounds]);
44265
+ const gridConfig = useMemo(
44266
+ () => ({
44267
+ ...DEFAULT_GRID_CONFIG,
44268
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
44269
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
44270
+ }),
44271
+ [gridBounds]
44272
+ );
44273
+ const gridToWorld = useCallback(
44274
+ (x, z, y = 0) => {
44275
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
44276
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
44277
+ return [worldX, y * gridConfig.cellSize, worldZ];
44278
+ },
44279
+ [gridBounds, gridConfig]
44280
+ );
44281
+ useImperativeHandle(ref, () => ({
44282
+ getCameraPosition: () => {
44283
+ if (controlsRef.current) {
44284
+ const pos = controlsRef.current.object.position;
44285
+ return new THREE.Vector3(pos.x, pos.y, pos.z);
44286
+ }
44287
+ return null;
44288
+ },
44289
+ setCameraPosition: (x, y, z) => {
44290
+ if (controlsRef.current) {
44291
+ controlsRef.current.object.position.set(x, y, z);
44292
+ controlsRef.current.update();
44293
+ }
44294
+ },
44295
+ lookAt: (x, y, z) => {
44296
+ if (controlsRef.current) {
44297
+ controlsRef.current.target.set(x, y, z);
44298
+ controlsRef.current.update();
44299
+ }
44300
+ },
44301
+ resetCamera: () => {
44302
+ if (controlsRef.current) {
44303
+ controlsRef.current.reset();
44304
+ }
44305
+ },
44306
+ screenshot: () => {
44307
+ const canvas = containerRef.current?.querySelector("canvas");
44308
+ if (canvas) {
44309
+ return canvas.toDataURL("image/png");
44310
+ }
44311
+ return null;
44312
+ },
44313
+ export: () => ({
44314
+ tiles,
44315
+ units,
44316
+ features
44317
+ })
44318
+ }));
44319
+ const handleTileClick = useCallback(
44320
+ (tile, event) => {
44321
+ eventHandlers.handleTileClick(tile, event);
44322
+ },
44323
+ [eventHandlers]
44324
+ );
44325
+ const handleUnitClick = useCallback(
44326
+ (unit, event) => {
44327
+ eventHandlers.handleUnitClick(unit, event);
44328
+ },
44329
+ [eventHandlers]
44330
+ );
44331
+ const handleFeatureClick = useCallback(
44332
+ (feature, event) => {
44333
+ if (event) {
44334
+ eventHandlers.handleFeatureClick(feature, event);
44335
+ }
44336
+ },
44337
+ [eventHandlers]
44338
+ );
44339
+ const handleTileHover = useCallback(
44340
+ (tile, event) => {
44341
+ setHoveredTile(tile);
44342
+ if (event) {
44343
+ eventHandlers.handleTileHover(tile, event);
44344
+ }
44345
+ },
44346
+ [eventHandlers]
44347
+ );
44348
+ const cameraConfig = useMemo(() => {
44349
+ const size = Math.max(
44350
+ gridBounds.maxX - gridBounds.minX,
44351
+ gridBounds.maxZ - gridBounds.minZ
44352
+ );
44353
+ const distance = size * 1.5;
44354
+ switch (cameraMode) {
44355
+ case "isometric":
44356
+ return {
44357
+ position: [distance, distance * 0.8, distance],
44358
+ fov: 45
44359
+ };
44360
+ case "top-down":
44361
+ return {
44362
+ position: [0, distance * 2, 0],
44363
+ fov: 45
44364
+ };
44365
+ case "perspective":
44366
+ default:
44367
+ return {
44368
+ position: [distance, distance, distance],
44369
+ fov: 45
44370
+ };
44371
+ }
44372
+ }, [cameraMode, gridBounds]);
44373
+ const DefaultTileRenderer = useCallback(
44374
+ ({ tile, position }) => {
44375
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
44376
+ const isHovered = hoveredTile?.id === tile.id;
44377
+ const isValidMove = validMoves.some(
44378
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
44379
+ );
44380
+ const isAttackTarget = attackTargets.some(
44381
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
44382
+ );
44383
+ let color = 8421504;
44384
+ if (tile.type === "water") color = 4491468;
44385
+ else if (tile.type === "grass") color = 4500036;
44386
+ else if (tile.type === "sand") color = 14535816;
44387
+ else if (tile.type === "rock") color = 8947848;
44388
+ else if (tile.type === "snow") color = 15658734;
44389
+ let emissive = 0;
44390
+ if (isSelected) emissive = 4473924;
44391
+ else if (isAttackTarget) emissive = 4456448;
44392
+ else if (isValidMove) emissive = 17408;
44393
+ else if (isHovered) emissive = 2236962;
44394
+ if (tile.modelUrl) {
44395
+ return /* @__PURE__ */ jsx(
44396
+ "group",
44397
+ {
44398
+ position,
44399
+ onClick: (e) => handleTileClick(tile, e),
44400
+ onPointerEnter: (e) => handleTileHover(tile, e),
44401
+ onPointerLeave: (e) => handleTileHover(null, e),
44402
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
44403
+ children: /* @__PURE__ */ jsx(
44404
+ ModelLoader,
44405
+ {
44406
+ url: tile.modelUrl,
44407
+ scale: 0.95,
44408
+ fallbackGeometry: "box",
44409
+ castShadow: true,
44410
+ receiveShadow: true
44411
+ }
44412
+ )
44413
+ }
44414
+ );
44415
+ }
44416
+ return /* @__PURE__ */ jsxs(
44417
+ "mesh",
44418
+ {
44419
+ position,
44420
+ onClick: (e) => handleTileClick(tile, e),
44421
+ onPointerEnter: (e) => handleTileHover(tile, e),
44422
+ onPointerLeave: (e) => handleTileHover(null, e),
44423
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
44424
+ children: [
44425
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
44426
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color, emissive })
44427
+ ]
44428
+ }
44429
+ );
44430
+ },
44431
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
44432
+ );
44433
+ const DefaultUnitRenderer = useCallback(
44434
+ ({ unit, position }) => {
44435
+ const isSelected = selectedUnitId === unit.id;
44436
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
44437
+ return /* @__PURE__ */ jsxs(
44438
+ "group",
44439
+ {
44440
+ position,
44441
+ onClick: (e) => handleUnitClick(unit, e),
44442
+ userData: { type: "unit", unitId: unit.id },
44443
+ children: [
44444
+ isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
44445
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
44446
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
44447
+ ] }),
44448
+ unit.modelUrl ? (
44449
+ /* GLB unit model (box fallback while loading / on error) */
44450
+ /* @__PURE__ */ jsx(
44451
+ ModelLoader,
44452
+ {
44453
+ url: unit.modelUrl,
44454
+ scale: 0.5,
44455
+ fallbackGeometry: "box",
44456
+ castShadow: true
44457
+ }
44458
+ )
44459
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
44460
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.3, 0], children: [
44461
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
44462
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
44463
+ ] }),
44464
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.6, 0], children: [
44465
+ /* @__PURE__ */ jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
44466
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
44467
+ ] }),
44468
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
44469
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
44470
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
44471
+ ] })
44472
+ ] }),
44473
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxs("group", { position: [0, 1.2, 0], children: [
44474
+ /* @__PURE__ */ jsxs("mesh", { position: [-0.25, 0, 0], children: [
44475
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.05] }),
44476
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: 3355443 })
44477
+ ] }),
44478
+ /* @__PURE__ */ jsxs(
44479
+ "mesh",
44480
+ {
44481
+ position: [
44482
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
44483
+ 0,
44484
+ 0.01
44485
+ ],
44486
+ children: [
44487
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
44488
+ /* @__PURE__ */ jsx(
44489
+ "meshBasicMaterial",
44490
+ {
44491
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
44492
+ }
44493
+ )
44494
+ ]
44495
+ }
44496
+ )
44497
+ ] })
44498
+ ]
44499
+ }
44500
+ );
44501
+ },
44502
+ [selectedUnitId, handleUnitClick]
44503
+ );
44504
+ const DefaultFeatureRenderer = useCallback(
44505
+ ({
44506
+ feature,
44507
+ position
44508
+ }) => {
44509
+ if (feature.assetUrl) {
44510
+ return /* @__PURE__ */ jsx(
44511
+ ModelLoader,
44512
+ {
44513
+ url: feature.assetUrl,
44514
+ position,
44515
+ scale: 0.5,
44516
+ rotation: [0, feature.rotation ?? 0, 0],
44517
+ onClick: () => handleFeatureClick(feature, null),
44518
+ fallbackGeometry: "box"
44519
+ },
44520
+ feature.id
44521
+ );
44522
+ }
44523
+ if (feature.type === "tree") {
44524
+ return /* @__PURE__ */ jsxs(
44525
+ "group",
44526
+ {
44527
+ position,
44528
+ onClick: (e) => handleFeatureClick(feature, e),
44529
+ userData: { type: "feature", featureId: feature.id },
44530
+ children: [
44531
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.4, 0], children: [
44532
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
44533
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
44534
+ ] }),
44535
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
44536
+ /* @__PURE__ */ jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
44537
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 2263842 })
44538
+ ] })
44539
+ ]
44540
+ }
44541
+ );
44542
+ }
44543
+ if (feature.type === "rock") {
44544
+ return /* @__PURE__ */ jsxs(
44545
+ "mesh",
44546
+ {
44547
+ position: [position[0], position[1] + 0.3, position[2]],
44548
+ onClick: (e) => handleFeatureClick(feature, e),
44549
+ userData: { type: "feature", featureId: feature.id },
44550
+ children: [
44551
+ /* @__PURE__ */ jsx("dodecahedronGeometry", { args: [0.3, 0] }),
44552
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 8421504 })
44553
+ ]
44554
+ }
44555
+ );
44556
+ }
44557
+ return null;
44558
+ },
44559
+ [handleFeatureClick]
44560
+ );
44561
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
44562
+ return /* @__PURE__ */ jsx(
44563
+ Canvas3DLoadingState,
44564
+ {
44565
+ progress,
44566
+ loaded,
44567
+ total,
44568
+ message: loadingMessage,
44569
+ className
44570
+ }
44571
+ );
44572
+ }
44573
+ const displayError = externalError || internalError;
44574
+ if (displayError) {
44575
+ 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: [
44576
+ "Error: ",
44577
+ displayError
44578
+ ] }) }) });
44579
+ }
44580
+ return /* @__PURE__ */ jsx(
44581
+ Canvas3DErrorBoundary,
44582
+ {
44583
+ onError: (err) => setInternalError(err.message),
44584
+ onReset: () => setInternalError(null),
44585
+ children: /* @__PURE__ */ jsxs(
44586
+ "div",
44587
+ {
44588
+ ref: containerRef,
44589
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
44590
+ "data-orientation": orientation,
44591
+ "data-camera-mode": cameraMode,
44592
+ "data-overlay": overlay,
44593
+ children: [
44594
+ /* @__PURE__ */ jsxs(
44595
+ Canvas,
44596
+ {
44597
+ shadows,
44598
+ camera: {
44599
+ position: cameraConfig.position,
44600
+ fov: cameraConfig.fov,
44601
+ near: 0.1,
44602
+ far: 1e3
44603
+ },
44604
+ style: { background: backgroundColor },
44605
+ onClick: (e) => {
44606
+ if (e.target === e.currentTarget) {
44607
+ eventHandlers.handleCanvasClick(e);
44608
+ }
44609
+ },
44610
+ children: [
44611
+ /* @__PURE__ */ jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
44612
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 0.6 }),
44613
+ /* @__PURE__ */ jsx(
44614
+ "directionalLight",
44615
+ {
44616
+ position: [10, 20, 10],
44617
+ intensity: 0.8,
44618
+ castShadow: shadows,
44619
+ "shadow-mapSize": [2048, 2048]
44620
+ }
44621
+ ),
44622
+ /* @__PURE__ */ jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
44623
+ showGrid && /* @__PURE__ */ jsx(
44624
+ Grid$1,
44625
+ {
44626
+ args: [
44627
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
44628
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
44629
+ ],
44630
+ position: [
44631
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
44632
+ 0,
44633
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
44634
+ ],
44635
+ cellSize: 1,
44636
+ cellThickness: 1,
44637
+ cellColor: "#444444",
44638
+ sectionSize: 5,
44639
+ sectionThickness: 1.5,
44640
+ sectionColor: "#666666",
44641
+ fadeDistance: 50,
44642
+ fadeStrength: 1
44643
+ }
44644
+ ),
44645
+ tiles.map((tile, index) => {
44646
+ const position = gridToWorld(
44647
+ tile.x,
44648
+ tile.z ?? tile.y ?? 0,
44649
+ tile.elevation ?? 0
44650
+ );
44651
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
44652
+ return /* @__PURE__ */ jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
44653
+ }),
44654
+ features.map((feature, index) => {
44655
+ const position = gridToWorld(
44656
+ feature.x,
44657
+ feature.z ?? feature.y ?? 0,
44658
+ (feature.elevation ?? 0) + 0.5
44659
+ );
44660
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
44661
+ return /* @__PURE__ */ jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
44662
+ }),
44663
+ units.map((unit) => {
44664
+ const position = gridToWorld(
44665
+ unit.x ?? 0,
44666
+ unit.z ?? unit.y ?? 0,
44667
+ (unit.elevation ?? 0) + 0.5
44668
+ );
44669
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
44670
+ return /* @__PURE__ */ jsx(Renderer, { unit, position }, unit.id);
44671
+ }),
44672
+ children,
44673
+ /* @__PURE__ */ jsx(
44674
+ OrbitControls,
44675
+ {
44676
+ ref: controlsRef,
44677
+ target: cameraTarget,
44678
+ enableDamping: true,
44679
+ dampingFactor: 0.05,
44680
+ minDistance: 2,
44681
+ maxDistance: 100,
44682
+ maxPolarAngle: Math.PI / 2 - 0.1
44683
+ }
44684
+ )
44685
+ ]
44686
+ }
44687
+ ),
44688
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__coordinates", children: [
44689
+ "X: ",
44690
+ hoveredTile.x,
44691
+ ", Z: ",
44692
+ hoveredTile.z ?? hoveredTile.y ?? 0
44693
+ ] }),
44694
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__tile-info", children: [
44695
+ /* @__PURE__ */ jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
44696
+ hoveredTile.terrain && /* @__PURE__ */ jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
44697
+ ] })
44698
+ ]
44699
+ }
44700
+ )
44701
+ }
44702
+ );
44703
+ }
44704
+ );
44705
+ GameCanvas3D.displayName = "GameCanvas3D";
44706
+ }
44707
+ });
44708
+ function GameBoard3D({
44709
+ entity,
44710
+ tiles = [],
44711
+ features = [],
44712
+ cameraMode = "perspective",
44713
+ backgroundColor = "#2a1a1a",
44714
+ tileClickEvent,
44715
+ unitClickEvent,
44716
+ attackEvent,
44717
+ endTurnEvent,
44718
+ cancelEvent,
44719
+ playAgainEvent,
44720
+ gameEndEvent,
44721
+ className
44722
+ }) {
44723
+ const row = boardEntity(entity);
44724
+ const eventBus = useEventBus();
44725
+ const entityUnits = row ? rows(row.units) : [];
44726
+ const units = entityUnits;
44727
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
44728
+ const phase = row ? str(row.phase) : "observation";
44729
+ const result = row ? str(row.result) : "none";
44730
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
44731
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
44732
+ const turn = row ? num(row.turn) : 0;
44733
+ const currentTeam = row ? str(row.currentTeam) : "player";
44734
+ const isGameOver = result !== "none";
44735
+ const gameEndEmittedRef = useRef(false);
44736
+ useEffect(() => {
44737
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
44738
+ if (!gameEndEmittedRef.current) {
44739
+ gameEndEmittedRef.current = true;
44740
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44741
+ }
44742
+ } else {
44743
+ gameEndEmittedRef.current = false;
44744
+ }
44745
+ }, [result, gameEndEvent, eventBus]);
44746
+ const checkGameEnd = useCallback(() => {
44747
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
44748
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
44749
+ if (alivePlayer.length === 0 && gameEndEvent) {
44750
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
44751
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
44752
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
44753
+ }
44754
+ }, [entityUnits, gameEndEvent, eventBus]);
44755
+ const handleUnitClickCallback = useCallback((isoUnit) => {
44756
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
44757
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
44758
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
44759
+ if (!attackerRow || !targetRow) return;
44760
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
44761
+ const ap = unitPosition(attackerRow);
44762
+ const tp = unitPosition(targetRow);
44763
+ const dx = Math.abs(ap.x - tp.x);
44764
+ const dy = Math.abs(ap.y - tp.y);
44765
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
44766
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
44767
+ eventBus.emit(`UI:${attackEvent}`, {
44768
+ attackerId: str(attackerRow.id),
44769
+ targetId: str(targetRow.id),
44770
+ damage
44771
+ });
44772
+ setTimeout(checkGameEnd, 100);
44773
+ }
44774
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
44775
+ const handleEndTurn = useCallback(() => {
44776
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
44777
+ }, [endTurnEvent, eventBus]);
44778
+ const handleCancel = useCallback(() => {
44779
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
44780
+ }, [cancelEvent, eventBus]);
44781
+ const handlePlayAgain = useCallback(() => {
44782
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
44783
+ }, [playAgainEvent, eventBus]);
44784
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
44785
+ /* @__PURE__ */ jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
44786
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
44787
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
44788
+ "\u2014 ",
44789
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
44790
+ ] }),
44791
+ /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
44792
+ "Turn ",
44793
+ turn
44794
+ ] })
44795
+ ] }),
44796
+ /* @__PURE__ */ jsx(
44797
+ GameCanvas3D,
44798
+ {
44799
+ tiles,
44800
+ units,
44801
+ features,
44802
+ cameraMode,
44803
+ showGrid: true,
44804
+ showCoordinates: false,
44805
+ showTileInfo: false,
44806
+ shadows: true,
44807
+ backgroundColor,
44808
+ tileClickEvent,
44809
+ unitClickEvent,
44810
+ onUnitClick: handleUnitClickCallback,
44811
+ selectedUnitId,
44812
+ validMoves,
44813
+ attackTargets,
44814
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
44815
+ }
44816
+ ),
44817
+ !isGameOver && /* @__PURE__ */ jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
44818
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsx(
44819
+ Button,
44820
+ {
44821
+ variant: "secondary",
44822
+ className: "shadow-xl",
44823
+ onClick: handleCancel,
44824
+ children: "Cancel"
44825
+ }
44826
+ ),
44827
+ phase !== "enemy_turn" && /* @__PURE__ */ jsx(
44828
+ Button,
44829
+ {
44830
+ variant: "primary",
44831
+ className: "shadow-xl",
44832
+ onClick: handleEndTurn,
44833
+ children: "End Turn"
44834
+ }
44835
+ )
44836
+ ] }),
44837
+ 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: [
44838
+ /* @__PURE__ */ jsx(
44839
+ Typography,
44840
+ {
44841
+ variant: "h2",
44842
+ className: cn(
44843
+ "overlay__result",
44844
+ result === "victory" ? "text-yellow-400" : "text-red-500"
44845
+ ),
44846
+ children: result === "victory" ? "Victory!" : "Defeat"
44847
+ }
44848
+ ),
44849
+ /* @__PURE__ */ jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
44850
+ "Completed in ",
44851
+ turn,
44852
+ " turn",
44853
+ turn !== 1 ? "s" : ""
44854
+ ] }),
44855
+ /* @__PURE__ */ jsx(
44856
+ Button,
44857
+ {
44858
+ variant: "primary",
44859
+ className: "px-8 py-3 font-semibold",
44860
+ onClick: handlePlayAgain,
44861
+ children: "Play Again"
44862
+ }
44863
+ )
44864
+ ] }) })
44865
+ ] });
44866
+ }
44867
+ var init_GameBoard3D = __esm({
44868
+ "components/game/organisms/GameBoard3D.tsx"() {
44869
+ "use client";
44870
+ init_cn();
44871
+ init_Button();
44872
+ init_Typography();
44873
+ init_Stack();
44874
+ init_useEventBus();
44875
+ init_GameCanvas3D2();
44876
+ init_boardEntity();
44877
+ GameBoard3D.displayName = "GameBoard3D";
44878
+ }
44879
+ });
43225
44880
  var GameShell;
43226
44881
  var init_GameShell = __esm({
43227
44882
  "components/game/templates/GameShell.tsx"() {
@@ -44764,6 +46419,236 @@ var init_NegotiatorBoard = __esm({
44764
46419
  NegotiatorBoard.displayName = "NegotiatorBoard";
44765
46420
  }
44766
46421
  });
46422
+ function resolveEntity(entity) {
46423
+ if (!entity) return {};
46424
+ if (Array.isArray(entity)) return entity[0] ?? {};
46425
+ return entity;
46426
+ }
46427
+ function numField(row, key, fallback) {
46428
+ const v = row[key];
46429
+ return typeof v === "number" ? v : fallback;
46430
+ }
46431
+ function strField(row, key, fallback) {
46432
+ const v = row[key];
46433
+ return typeof v === "string" ? v : fallback;
46434
+ }
46435
+ function PlatformerBoard({
46436
+ entity,
46437
+ player: propPlayer,
46438
+ platforms: propPlatforms,
46439
+ result: propResult,
46440
+ lives: propLives,
46441
+ score: propScore,
46442
+ level: propLevel,
46443
+ worldWidth = 800,
46444
+ worldHeight = 400,
46445
+ canvasWidth = 800,
46446
+ canvasHeight = 400,
46447
+ playerSprite,
46448
+ tileSprites,
46449
+ bgColor,
46450
+ leftEvent = "LEFT",
46451
+ rightEvent = "RIGHT",
46452
+ jumpEvent = "JUMP",
46453
+ stopEvent = "STOP",
46454
+ playAgainEvent = "PLAY_AGAIN",
46455
+ gameEndEvent = "GAME_END",
46456
+ className
46457
+ }) {
46458
+ const row = resolveEntity(entity);
46459
+ const eventBus = useEventBus();
46460
+ const { t } = useTranslate();
46461
+ const result = propResult ?? strField(row, "result", "none");
46462
+ propLives ?? numField(row, "lives", 3);
46463
+ const score = propScore ?? numField(row, "score", 0);
46464
+ const level = propLevel ?? numField(row, "level", 1);
46465
+ const entityPlayer = row["player"];
46466
+ const player = propPlayer ?? entityPlayer ?? {
46467
+ x: 80,
46468
+ y: 320,
46469
+ width: 32,
46470
+ height: 48,
46471
+ vx: 0,
46472
+ vy: 0,
46473
+ grounded: false,
46474
+ facingRight: true
46475
+ };
46476
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
46477
+ const platforms = propPlatforms ?? entityPlatforms ?? [
46478
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
46479
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
46480
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
46481
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
46482
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
46483
+ ];
46484
+ const handleRestart = useCallback(() => {
46485
+ if (playAgainEvent) {
46486
+ eventBus.emit(`UI:${playAgainEvent}`, {});
46487
+ }
46488
+ }, [playAgainEvent, eventBus]);
46489
+ const isGameOver = result === "won" || result === "lost";
46490
+ const gameEndEmittedRef = useRef(false);
46491
+ useEffect(() => {
46492
+ if ((result === "won" || result === "lost") && gameEndEvent) {
46493
+ if (!gameEndEmittedRef.current) {
46494
+ gameEndEmittedRef.current = true;
46495
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
46496
+ }
46497
+ } else {
46498
+ gameEndEmittedRef.current = false;
46499
+ }
46500
+ }, [result, gameEndEvent, eventBus]);
46501
+ return /* @__PURE__ */ jsx(
46502
+ VStack,
46503
+ {
46504
+ className: cn("platformer-board relative bg-background", className),
46505
+ gap: "none",
46506
+ children: /* @__PURE__ */ jsxs(Box, { className: "relative", children: [
46507
+ /* @__PURE__ */ jsx(
46508
+ PlatformerCanvas,
46509
+ {
46510
+ player,
46511
+ platforms,
46512
+ worldWidth,
46513
+ worldHeight,
46514
+ canvasWidth,
46515
+ canvasHeight,
46516
+ followCamera: true,
46517
+ bgColor,
46518
+ playerSprite,
46519
+ tileSprites,
46520
+ leftEvent,
46521
+ rightEvent,
46522
+ jumpEvent,
46523
+ stopEvent
46524
+ }
46525
+ ),
46526
+ 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: [
46527
+ /* @__PURE__ */ jsx(
46528
+ Typography,
46529
+ {
46530
+ variant: "h2",
46531
+ className: cn(
46532
+ "text-4xl font-black tracking-widest uppercase",
46533
+ result === "won" ? "text-warning" : "text-error"
46534
+ ),
46535
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
46536
+ }
46537
+ ),
46538
+ result === "won" && /* @__PURE__ */ jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
46539
+ t("platformer.level"),
46540
+ " ",
46541
+ level,
46542
+ " \u2014 ",
46543
+ t("platformer.score"),
46544
+ ": ",
46545
+ score
46546
+ ] }),
46547
+ result === "lost" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
46548
+ /* @__PURE__ */ jsx(
46549
+ Button,
46550
+ {
46551
+ variant: "primary",
46552
+ className: "px-8 py-3 font-semibold",
46553
+ onClick: handleRestart,
46554
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
46555
+ }
46556
+ )
46557
+ ] }) })
46558
+ ] })
46559
+ }
46560
+ );
46561
+ }
46562
+ var init_PlatformerBoard = __esm({
46563
+ "components/game/organisms/PlatformerBoard.tsx"() {
46564
+ "use client";
46565
+ init_cn();
46566
+ init_useEventBus();
46567
+ init_Box();
46568
+ init_Button();
46569
+ init_Typography();
46570
+ init_Stack();
46571
+ init_PlatformerCanvas();
46572
+ PlatformerBoard.displayName = "PlatformerBoard";
46573
+ }
46574
+ });
46575
+ function PlatformerTemplate({
46576
+ entity,
46577
+ platforms = DEFAULT_PLATFORMS,
46578
+ worldWidth = 800,
46579
+ worldHeight = 400,
46580
+ canvasWidth = 800,
46581
+ canvasHeight = 400,
46582
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
46583
+ tileSprites = DEFAULT_TILE_SPRITES,
46584
+ bgColor = "#5c94fc",
46585
+ gameEndEvent = "GAME_END",
46586
+ className
46587
+ }) {
46588
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
46589
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
46590
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
46591
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
46592
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
46593
+ /* @__PURE__ */ jsx(Box, { className: "relative", children: /* @__PURE__ */ jsx(
46594
+ GameHud,
46595
+ {
46596
+ position: "top",
46597
+ stats: [
46598
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
46599
+ { label: "Level", value: level, format: "number", variant: "default" }
46600
+ ]
46601
+ }
46602
+ ) }),
46603
+ /* @__PURE__ */ jsx(
46604
+ PlatformerBoard,
46605
+ {
46606
+ entity,
46607
+ platforms,
46608
+ worldWidth,
46609
+ worldHeight,
46610
+ canvasWidth,
46611
+ canvasHeight,
46612
+ playerSprite,
46613
+ tileSprites,
46614
+ bgColor,
46615
+ leftEvent: "LEFT",
46616
+ rightEvent: "RIGHT",
46617
+ jumpEvent: "JUMP",
46618
+ stopEvent: "STOP",
46619
+ playAgainEvent: "PLAY_AGAIN",
46620
+ gameEndEvent
46621
+ }
46622
+ ),
46623
+ /* @__PURE__ */ jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsx(ScoreBoard, { score, level }) })
46624
+ ] });
46625
+ }
46626
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
46627
+ var init_PlatformerTemplate = __esm({
46628
+ "components/game/templates/PlatformerTemplate.tsx"() {
46629
+ init_cn();
46630
+ init_Box();
46631
+ init_Stack();
46632
+ init_PlatformerBoard();
46633
+ init_GameHud();
46634
+ init_ScoreBoard();
46635
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
46636
+ DEFAULT_TILE_SPRITES = {
46637
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
46638
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
46639
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
46640
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
46641
+ };
46642
+ DEFAULT_PLATFORMS = [
46643
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
46644
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
46645
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
46646
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
46647
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
46648
+ ];
46649
+ PlatformerTemplate.displayName = "PlatformerTemplate";
46650
+ }
46651
+ });
44767
46652
  var PricingOrganism;
44768
46653
  var init_PricingOrganism = __esm({
44769
46654
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -44886,6 +46771,356 @@ var init_PricingPageTemplate = __esm({
44886
46771
  PricingPageTemplate.displayName = "PricingPageTemplate";
44887
46772
  }
44888
46773
  });
46774
+ function RoguelikeBoard({
46775
+ entity,
46776
+ tiles: propTiles,
46777
+ enemies: propEnemies,
46778
+ items: propItems,
46779
+ player: propPlayer,
46780
+ playerHp: propPlayerHp,
46781
+ playerMaxHp: propPlayerMaxHp,
46782
+ playerAttack: propPlayerAttack,
46783
+ depth: propDepth,
46784
+ result: propResult,
46785
+ phase: propPhase,
46786
+ stairsX: propStairsX,
46787
+ stairsY: propStairsY,
46788
+ assetManifest: propAssetManifest,
46789
+ scale = 0.45,
46790
+ unitScale = 1,
46791
+ moveEvent,
46792
+ playAgainEvent,
46793
+ gameEndEvent,
46794
+ className
46795
+ }) {
46796
+ const board = boardEntity(entity) ?? {};
46797
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
46798
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
46799
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
46800
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
46801
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
46802
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
46803
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
46804
+ const depth = propDepth ?? num(board.depth, 1);
46805
+ const result = propResult ?? (str(board.result) || "none");
46806
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
46807
+ propStairsX ?? num(board.stairsX, 3);
46808
+ propStairsY ?? num(board.stairsY, 3);
46809
+ const eventBus = useEventBus();
46810
+ const { t } = useTranslate();
46811
+ const [hoveredTile, setHoveredTile] = useState(null);
46812
+ const emitMove = useCallback((dx, dy) => {
46813
+ if (!moveEvent) return;
46814
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
46815
+ }, [moveEvent, eventBus]);
46816
+ useEffect(() => {
46817
+ const handler = (e) => {
46818
+ if (result !== "none" || phase !== "player_turn") return;
46819
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
46820
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
46821
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
46822
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
46823
+ };
46824
+ window.addEventListener("keydown", handler);
46825
+ return () => window.removeEventListener("keydown", handler);
46826
+ }, [result, phase, emitMove]);
46827
+ useEffect(() => {
46828
+ if ((result === "won" || result === "lost") && gameEndEvent) {
46829
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
46830
+ }
46831
+ }, [result, gameEndEvent, eventBus]);
46832
+ const handleTileClick = useCallback((x, y) => {
46833
+ if (result !== "none" || phase !== "player_turn") return;
46834
+ const dx = x - player.x;
46835
+ const dy = y - player.y;
46836
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
46837
+ emitMove(dx, dy);
46838
+ }
46839
+ }, [result, phase, player, emitMove]);
46840
+ const handleReset = useCallback(() => {
46841
+ if (!playAgainEvent) return;
46842
+ eventBus.emit(`UI:${playAgainEvent}`, {});
46843
+ }, [playAgainEvent, eventBus]);
46844
+ const isoUnits = useMemo(() => {
46845
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
46846
+ return [
46847
+ {
46848
+ id: "player",
46849
+ position: player,
46850
+ name: t("roguelike.player") || "Hero",
46851
+ team: "player",
46852
+ health: playerHp,
46853
+ maxHealth: playerMaxHp,
46854
+ unitType: "player",
46855
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
46856
+ },
46857
+ ...liveEnemies.map((e) => ({
46858
+ id: e.id,
46859
+ position: { x: e.x, y: e.y },
46860
+ name: t("roguelike.enemy") || "Enemy",
46861
+ team: "enemy",
46862
+ health: e.hp,
46863
+ maxHealth: 6,
46864
+ unitType: "enemy",
46865
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
46866
+ }))
46867
+ ];
46868
+ }, [player, enemies, playerHp, playerMaxHp, t]);
46869
+ const isoFeatures = useMemo(
46870
+ () => items.map((it) => ({
46871
+ id: it.id,
46872
+ x: it.x,
46873
+ y: it.y,
46874
+ type: it.kind,
46875
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
46876
+ })),
46877
+ [items]
46878
+ );
46879
+ const validMoves = useMemo(() => {
46880
+ if (result !== "none" || phase !== "player_turn") return [];
46881
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
46882
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
46883
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
46884
+ return tile?.passable ?? false;
46885
+ });
46886
+ }, [result, phase, player, tiles]);
46887
+ const isGameOver = result === "won" || result === "lost";
46888
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
46889
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
46890
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
46891
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
46892
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
46893
+ ] }),
46894
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
46895
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
46896
+ /* @__PURE__ */ jsxs(
46897
+ Typography,
46898
+ {
46899
+ variant: "body1",
46900
+ className: cn(
46901
+ "font-bold tabular-nums",
46902
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
46903
+ ),
46904
+ children: [
46905
+ playerHp,
46906
+ " / ",
46907
+ playerMaxHp
46908
+ ]
46909
+ }
46910
+ )
46911
+ ] }),
46912
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
46913
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
46914
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
46915
+ ] }),
46916
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
46917
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
46918
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
46919
+ ] })
46920
+ ] }),
46921
+ /* @__PURE__ */ jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsx(
46922
+ IsometricCanvas_default,
46923
+ {
46924
+ tiles,
46925
+ units: isoUnits,
46926
+ features: isoFeatures,
46927
+ selectedUnitId: null,
46928
+ validMoves,
46929
+ attackTargets: [],
46930
+ hoveredTile,
46931
+ onTileClick: handleTileClick,
46932
+ onUnitClick: () => void 0,
46933
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
46934
+ onTileLeave: () => setHoveredTile(null),
46935
+ scale,
46936
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
46937
+ assetManifest: propAssetManifest,
46938
+ unitScale
46939
+ }
46940
+ ) }),
46941
+ !isGameOver && /* @__PURE__ */ jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
46942
+ { label: "\u2191", dx: 0, dy: -1 },
46943
+ { label: "\u2193", dx: 0, dy: 1 },
46944
+ { label: "\u2190", dx: -1, dy: 0 },
46945
+ { label: "\u2192", dx: 1, dy: 0 }
46946
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsx(
46947
+ Button,
46948
+ {
46949
+ variant: "secondary",
46950
+ className: "w-9 h-9 p-0 text-base font-mono",
46951
+ onClick: () => emitMove(dx, dy),
46952
+ disabled: phase !== "player_turn",
46953
+ children: label
46954
+ },
46955
+ label
46956
+ )) }),
46957
+ 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: [
46958
+ /* @__PURE__ */ jsx(
46959
+ Typography,
46960
+ {
46961
+ variant: "h2",
46962
+ className: cn(
46963
+ "text-4xl font-black tracking-widest uppercase",
46964
+ result === "won" ? "text-warning" : "text-error"
46965
+ ),
46966
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
46967
+ }
46968
+ ),
46969
+ result === "won" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
46970
+ /* @__PURE__ */ jsx(
46971
+ Button,
46972
+ {
46973
+ variant: "primary",
46974
+ className: "px-8 py-3 font-semibold",
46975
+ onClick: handleReset,
46976
+ children: t("roguelike.playAgain") || "Play Again"
46977
+ }
46978
+ )
46979
+ ] }) })
46980
+ ] });
46981
+ }
46982
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
46983
+ var init_RoguelikeBoard = __esm({
46984
+ "components/game/organisms/RoguelikeBoard.tsx"() {
46985
+ "use client";
46986
+ init_cn();
46987
+ init_useEventBus();
46988
+ init_Box();
46989
+ init_Button();
46990
+ init_Typography();
46991
+ init_Stack();
46992
+ init_IsometricCanvas();
46993
+ init_boardEntity();
46994
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
46995
+ DEFAULT_TILES2 = [
46996
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46997
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46998
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46999
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47000
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47001
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47002
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47003
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47004
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47005
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47006
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47007
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47008
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47009
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47010
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47011
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47012
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47013
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47014
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
47015
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47016
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47017
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47018
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47019
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47020
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
47021
+ ];
47022
+ DEFAULT_ENEMIES = [
47023
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
47024
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
47025
+ ];
47026
+ DEFAULT_ITEMS = [
47027
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
47028
+ ];
47029
+ FEATURE_SPRITE = {
47030
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
47031
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
47032
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
47033
+ };
47034
+ RoguelikeBoard.displayName = "RoguelikeBoard";
47035
+ }
47036
+ });
47037
+ function RoguelikeTemplate({
47038
+ entity,
47039
+ scale = 0.45,
47040
+ unitScale = 1,
47041
+ tiles = DEFAULT_ROGUELIKE_TILES,
47042
+ enemies,
47043
+ items,
47044
+ stairsX,
47045
+ stairsY,
47046
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
47047
+ gameEndEvent,
47048
+ className
47049
+ }) {
47050
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
47051
+ if (!resolved && !tiles && !assetManifest) return null;
47052
+ return /* @__PURE__ */ jsx(
47053
+ RoguelikeBoard,
47054
+ {
47055
+ entity: resolved,
47056
+ tiles,
47057
+ enemies,
47058
+ items,
47059
+ stairsX,
47060
+ stairsY,
47061
+ assetManifest,
47062
+ scale,
47063
+ unitScale,
47064
+ moveEvent: "MOVE",
47065
+ playAgainEvent: "PLAY_AGAIN",
47066
+ gameEndEvent: gameEndEvent ?? "GAME_END",
47067
+ className
47068
+ }
47069
+ );
47070
+ }
47071
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
47072
+ var init_RoguelikeTemplate = __esm({
47073
+ "components/game/templates/RoguelikeTemplate.tsx"() {
47074
+ init_RoguelikeBoard();
47075
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
47076
+ DEFAULT_ROGUELIKE_TILES = [
47077
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47078
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47079
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47080
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47081
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47082
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47083
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47084
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47085
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47086
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47087
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47088
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47089
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47090
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47091
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47092
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47093
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47094
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47095
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
47096
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47097
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47098
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47099
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47100
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47101
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
47102
+ ];
47103
+ DEFAULT_ROGUELIKE_MANIFEST = {
47104
+ baseUrl: CDN5,
47105
+ terrains: {
47106
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
47107
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
47108
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
47109
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
47110
+ },
47111
+ units: {
47112
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
47113
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
47114
+ },
47115
+ features: {
47116
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
47117
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
47118
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
47119
+ }
47120
+ };
47121
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
47122
+ }
47123
+ });
44889
47124
 
44890
47125
  // lib/traitRegistry.ts
44891
47126
  function notifyListeners2() {
@@ -46139,7 +48374,7 @@ function getAllEvents(traits2) {
46139
48374
  function EventDispatcherTab({ traits: traits2, schema }) {
46140
48375
  const eventBus = useEventBus();
46141
48376
  const { t } = useTranslate();
46142
- const [log13, setLog] = React91.useState([]);
48377
+ const [log15, setLog] = React91.useState([]);
46143
48378
  const prevStatesRef = React91.useRef(/* @__PURE__ */ new Map());
46144
48379
  React91.useEffect(() => {
46145
48380
  for (const trait of traits2) {
@@ -46203,9 +48438,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
46203
48438
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
46204
48439
  /* @__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)) })
46205
48440
  ] }),
46206
- log13.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
48441
+ log15.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
46207
48442
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
46208
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log13.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
48443
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log15.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
46209
48444
  /* @__PURE__ */ jsx("span", { className: "text-purple-400", children: entry.traitName }),
46210
48445
  " ",
46211
48446
  /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -48126,9 +50361,9 @@ function VariablePanel({
48126
50361
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
48127
50362
  variables.map((v) => {
48128
50363
  const name = v.name == null ? "" : String(v.name);
48129
- const value = numField(v.value);
48130
- const max = numField(v.max, 100);
48131
- const min = numField(v.min, 0);
50364
+ const value = numField2(v.value);
50365
+ const max = numField2(v.max, 100);
50366
+ const min = numField2(v.min, 0);
48132
50367
  const unit = v.unit == null ? "" : String(v.unit);
48133
50368
  const pct = Math.round((value - min) / (max - min) * 100);
48134
50369
  const isHigh = pct > 80;
@@ -48158,12 +50393,12 @@ function VariablePanel({
48158
50393
  })
48159
50394
  ] });
48160
50395
  }
48161
- var numField;
50396
+ var numField2;
48162
50397
  var init_VariablePanel = __esm({
48163
50398
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
48164
50399
  init_atoms2();
48165
50400
  init_cn();
48166
- numField = (v, fallback = 0) => {
50401
+ numField2 = (v, fallback = 0) => {
48167
50402
  const n = Number(v);
48168
50403
  return Number.isFinite(n) ? n : fallback;
48169
50404
  };
@@ -49016,6 +51251,289 @@ var init_ToastSlot = __esm({
49016
51251
  ToastSlot.displayName = "ToastSlot";
49017
51252
  }
49018
51253
  });
51254
+ function tilesToIso(tiles) {
51255
+ return tiles.map((t) => ({
51256
+ x: t.x,
51257
+ y: t.y,
51258
+ terrain: t.terrain,
51259
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
51260
+ passable: t.passable
51261
+ }));
51262
+ }
51263
+ function towersToUnits(towers) {
51264
+ return towers.map((t) => ({
51265
+ id: t.id,
51266
+ position: { x: t.x, y: t.y },
51267
+ name: "Tower",
51268
+ team: "player",
51269
+ sprite: TOWER_SPRITE,
51270
+ unitType: "guardian",
51271
+ health: 1,
51272
+ maxHealth: 1
51273
+ }));
51274
+ }
51275
+ function creepsToUnits(creeps) {
51276
+ return creeps.map((c) => ({
51277
+ id: c.id,
51278
+ position: { x: c.x, y: c.y },
51279
+ name: "Creep",
51280
+ team: "enemy",
51281
+ sprite: CREEP_SPRITE,
51282
+ unitType: "scrapper",
51283
+ health: c.hp,
51284
+ maxHealth: c.maxHp
51285
+ }));
51286
+ }
51287
+ function pathToFeatures(path) {
51288
+ return path.map((p2, i) => ({
51289
+ id: `path-${i}`,
51290
+ x: p2.x,
51291
+ y: p2.y,
51292
+ type: "path-marker",
51293
+ sprite: `${CDN6}world-map/road_straight.png`
51294
+ }));
51295
+ }
51296
+ function TowerDefenseBoard({
51297
+ entity,
51298
+ tiles: propTiles,
51299
+ path: propPath,
51300
+ towers: propTowers,
51301
+ creeps: propCreeps,
51302
+ gold: propGold,
51303
+ lives: propLives,
51304
+ wave: propWave,
51305
+ maxWaves: propMaxWaves,
51306
+ result: propResult,
51307
+ waveActive: propWaveActive,
51308
+ towerCost = 25,
51309
+ scale = 0.45,
51310
+ placeTowerEvent,
51311
+ startWaveEvent,
51312
+ playAgainEvent,
51313
+ gameEndEvent,
51314
+ className
51315
+ }) {
51316
+ const board = boardEntity(entity) ?? {};
51317
+ const eventBus = useEventBus();
51318
+ const { t } = useTranslate();
51319
+ const tiles = propTiles ?? rows(board.tiles);
51320
+ const path = propPath ?? rows(board.path);
51321
+ const towers = propTowers ?? rows(board.towers);
51322
+ const creeps = propCreeps ?? rows(board.creeps);
51323
+ const gold = propGold ?? num(board.gold, 100);
51324
+ const lives = propLives ?? num(board.lives, 20);
51325
+ const wave = propWave ?? num(board.wave, 1);
51326
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
51327
+ const result = propResult ?? (str(board.result) || "none");
51328
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
51329
+ const [hoveredTile, setHoveredTile] = useState(null);
51330
+ const emittedGameEnd = useRef(false);
51331
+ if (result !== "none" && !emittedGameEnd.current) {
51332
+ emittedGameEnd.current = true;
51333
+ if (gameEndEvent) {
51334
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
51335
+ }
51336
+ }
51337
+ if (result === "none") {
51338
+ emittedGameEnd.current = false;
51339
+ }
51340
+ const towerPositions = useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
51341
+ const pathPositions = useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
51342
+ const validMoves = useMemo(() => {
51343
+ if (result !== "none" || gold < towerCost) return [];
51344
+ return tiles.filter(
51345
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
51346
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
51347
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
51348
+ const isoTiles = useMemo(() => tilesToIso(tiles), [tiles]);
51349
+ const towerUnits = useMemo(() => towersToUnits(towers), [towers]);
51350
+ const creepUnits = useMemo(() => creepsToUnits(creeps), [creeps]);
51351
+ const isoUnits = useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
51352
+ const pathFeatures = useMemo(() => pathToFeatures(path), [path]);
51353
+ const handleTileClick = useCallback((x, y) => {
51354
+ if (result !== "none") return;
51355
+ if (pathPositions.has(`${x},${y}`)) return;
51356
+ if (towerPositions.has(`${x},${y}`)) return;
51357
+ if (gold < towerCost) return;
51358
+ if (placeTowerEvent) {
51359
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
51360
+ }
51361
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
51362
+ const handleStartWave = useCallback(() => {
51363
+ if (startWaveEvent) {
51364
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
51365
+ }
51366
+ }, [startWaveEvent, wave, eventBus]);
51367
+ const handlePlayAgain = useCallback(() => {
51368
+ if (playAgainEvent) {
51369
+ eventBus.emit(`UI:${playAgainEvent}`, {});
51370
+ }
51371
+ }, [playAgainEvent, eventBus]);
51372
+ const canPlaceTower = gold >= towerCost && result === "none";
51373
+ const canStartWave = !waveActive && result === "none";
51374
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
51375
+ /* @__PURE__ */ jsxs(
51376
+ HStack,
51377
+ {
51378
+ className: "px-4 py-2 border-b border-border bg-surface",
51379
+ gap: "lg",
51380
+ align: "center",
51381
+ children: [
51382
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
51383
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
51384
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
51385
+ ] }),
51386
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
51387
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
51388
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
51389
+ ] }),
51390
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
51391
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
51392
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", weight: "bold", children: [
51393
+ wave,
51394
+ " / ",
51395
+ maxWaves
51396
+ ] })
51397
+ ] }),
51398
+ /* @__PURE__ */ jsxs(Box, { className: "ml-auto", children: [
51399
+ canStartWave && /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
51400
+ waveActive && /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
51401
+ ] })
51402
+ ]
51403
+ }
51404
+ ),
51405
+ /* @__PURE__ */ jsxs(Box, { className: "relative flex-1", children: [
51406
+ /* @__PURE__ */ jsx(
51407
+ IsometricCanvas_default,
51408
+ {
51409
+ tiles: isoTiles,
51410
+ units: isoUnits,
51411
+ features: pathFeatures,
51412
+ validMoves,
51413
+ hoveredTile,
51414
+ onTileClick: handleTileClick,
51415
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
51416
+ onTileLeave: () => setHoveredTile(null),
51417
+ scale
51418
+ }
51419
+ ),
51420
+ 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)` }) })
51421
+ ] }),
51422
+ 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: [
51423
+ /* @__PURE__ */ jsx(
51424
+ Typography,
51425
+ {
51426
+ variant: "h2",
51427
+ className: cn(
51428
+ "text-4xl font-black tracking-widest uppercase",
51429
+ result === "won" ? "text-warning" : "text-error"
51430
+ ),
51431
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
51432
+ }
51433
+ ),
51434
+ /* @__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." }),
51435
+ /* @__PURE__ */ jsx(
51436
+ Button,
51437
+ {
51438
+ variant: "primary",
51439
+ className: "px-8 py-3 font-semibold",
51440
+ onClick: handlePlayAgain,
51441
+ children: t("td.playAgain") ?? "Play Again"
51442
+ }
51443
+ )
51444
+ ] }) })
51445
+ ] });
51446
+ }
51447
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
51448
+ var init_TowerDefenseBoard = __esm({
51449
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
51450
+ "use client";
51451
+ init_cn();
51452
+ init_useEventBus();
51453
+ init_Box();
51454
+ init_Button();
51455
+ init_Typography();
51456
+ init_Stack();
51457
+ init_IsometricCanvas();
51458
+ init_boardEntity();
51459
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
51460
+ TERRAIN_SPRITES = {
51461
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
51462
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
51463
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
51464
+ };
51465
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
51466
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
51467
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
51468
+ }
51469
+ });
51470
+ function TowerDefenseTemplate({
51471
+ entity,
51472
+ title = "Tower Defense",
51473
+ tiles,
51474
+ path,
51475
+ towers,
51476
+ gold,
51477
+ lives,
51478
+ maxWaves,
51479
+ towerCost,
51480
+ scale,
51481
+ placeTowerEvent,
51482
+ startWaveEvent,
51483
+ playAgainEvent,
51484
+ gameEndEvent,
51485
+ className
51486
+ }) {
51487
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
51488
+ return /* @__PURE__ */ jsxs(
51489
+ Box,
51490
+ {
51491
+ display: "flex",
51492
+ fullHeight: true,
51493
+ className: cn("tower-defense-template flex-col", className),
51494
+ children: [
51495
+ /* @__PURE__ */ jsx(
51496
+ HStack,
51497
+ {
51498
+ gap: "sm",
51499
+ align: "center",
51500
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
51501
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
51502
+ }
51503
+ ),
51504
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
51505
+ TowerDefenseBoard,
51506
+ {
51507
+ entity: resolved,
51508
+ tiles,
51509
+ path,
51510
+ towers,
51511
+ gold,
51512
+ lives,
51513
+ maxWaves,
51514
+ towerCost,
51515
+ scale,
51516
+ placeTowerEvent,
51517
+ startWaveEvent,
51518
+ playAgainEvent,
51519
+ gameEndEvent,
51520
+ className: "h-full"
51521
+ }
51522
+ ) })
51523
+ ]
51524
+ }
51525
+ );
51526
+ }
51527
+ var init_TowerDefenseTemplate = __esm({
51528
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
51529
+ init_cn();
51530
+ init_Box();
51531
+ init_Stack();
51532
+ init_Typography();
51533
+ init_TowerDefenseBoard();
51534
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
51535
+ }
51536
+ });
49019
51537
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
49020
51538
  const eventBus = useEventBus();
49021
51539
  const {
@@ -49525,48 +52043,48 @@ function WorldMapTemplate({
49525
52043
  }
49526
52044
  );
49527
52045
  }
49528
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
52046
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
49529
52047
  var init_WorldMapTemplate = __esm({
49530
52048
  "components/game/templates/WorldMapTemplate.tsx"() {
49531
52049
  init_WorldMapBoard();
49532
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
52050
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
49533
52051
  DEFAULT_WORLDMAP_TILES = [
49534
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49535
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49536
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49537
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49538
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49539
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49540
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49541
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49542
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49543
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49544
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49545
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49546
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49547
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49548
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49549
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49550
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49551
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49552
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49553
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49554
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49555
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49556
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49557
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49558
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
52052
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52053
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52054
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52055
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52056
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52057
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52058
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52059
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52060
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52061
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52062
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52063
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52064
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
52065
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52066
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52067
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52068
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52069
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52070
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
52071
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52072
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52073
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52074
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52075
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52076
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
49559
52077
  ];
49560
52078
  DEFAULT_WORLDMAP_UNITS = [
49561
- { 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` },
49562
- { 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` }
52079
+ { 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` },
52080
+ { 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` }
49563
52081
  ];
49564
52082
  DEFAULT_WORLDMAP_FEATURES = [
49565
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
49566
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
52083
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
52084
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
49567
52085
  ];
49568
52086
  DEFAULT_WORLDMAP_MANIFEST = {
49569
- baseUrl: CDN3,
52087
+ baseUrl: CDN7,
49570
52088
  terrains: {
49571
52089
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
49572
52090
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -49611,7 +52129,7 @@ function lazyThree(name, loader) {
49611
52129
  ThreeWrapper.displayName = `Lazy(${name})`;
49612
52130
  return ThreeWrapper;
49613
52131
  }
49614
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
52132
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
49615
52133
  var init_component_registry_generated = __esm({
49616
52134
  "components/core/organisms/component-registry.generated.ts"() {
49617
52135
  init_AboutPageTemplate();
@@ -49721,6 +52239,7 @@ var init_component_registry_generated = __esm({
49721
52239
  init_FormSectionHeader();
49722
52240
  init_GameAudioProvider();
49723
52241
  init_GameAudioToggle();
52242
+ init_GameBoard3D();
49724
52243
  init_GameCanvas2D();
49725
52244
  init_GameHud();
49726
52245
  init_GameMenu();
@@ -49778,7 +52297,9 @@ var init_component_registry_generated = __esm({
49778
52297
  init_PageHeader();
49779
52298
  init_Pagination();
49780
52299
  init_PatternTile();
52300
+ init_PlatformerBoard();
49781
52301
  init_PlatformerCanvas();
52302
+ init_PlatformerTemplate();
49782
52303
  init_Popover();
49783
52304
  init_PositionedCanvas();
49784
52305
  init_PowerupSlots();
@@ -49802,6 +52323,8 @@ var init_component_registry_generated = __esm({
49802
52323
  init_ResourceBar();
49803
52324
  init_ResourceCounter();
49804
52325
  init_RichBlockEditor();
52326
+ init_RoguelikeBoard();
52327
+ init_RoguelikeTemplate();
49805
52328
  init_RuntimeDebugger2();
49806
52329
  init_ScaledDiagram();
49807
52330
  init_ScoreBoard();
@@ -49874,6 +52397,8 @@ var init_component_registry_generated = __esm({
49874
52397
  init_Toast();
49875
52398
  init_ToastSlot();
49876
52399
  init_Tooltip();
52400
+ init_TowerDefenseBoard();
52401
+ init_TowerDefenseTemplate();
49877
52402
  init_TraitFrame();
49878
52403
  init_TraitSlot();
49879
52404
  init_TrendIndicator();
@@ -49918,7 +52443,7 @@ var init_component_registry_generated = __esm({
49918
52443
  }
49919
52444
  };
49920
52445
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
49921
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
52446
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
49922
52447
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
49923
52448
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
49924
52449
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -50037,8 +52562,9 @@ var init_component_registry_generated = __esm({
50037
52562
  "FormSectionHeader": FormSectionHeader,
50038
52563
  "GameAudioProvider": GameAudioProvider,
50039
52564
  "GameAudioToggle": GameAudioToggle,
52565
+ "GameBoard3D": GameBoard3D,
50040
52566
  "GameCanvas2D": GameCanvas2D,
50041
- "GameCanvas3D": GameCanvas3D,
52567
+ "GameCanvas3D": GameCanvas3D2,
50042
52568
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
50043
52569
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
50044
52570
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -50100,7 +52626,9 @@ var init_component_registry_generated = __esm({
50100
52626
  "PageHeader": PageHeader,
50101
52627
  "Pagination": Pagination,
50102
52628
  "PatternTile": PatternTile,
52629
+ "PlatformerBoard": PlatformerBoard,
50103
52630
  "PlatformerCanvas": PlatformerCanvas,
52631
+ "PlatformerTemplate": PlatformerTemplate,
50104
52632
  "Popover": Popover,
50105
52633
  "PositionedCanvas": PositionedCanvas,
50106
52634
  "PowerupSlots": PowerupSlots,
@@ -50124,6 +52652,8 @@ var init_component_registry_generated = __esm({
50124
52652
  "ResourceBar": ResourceBar,
50125
52653
  "ResourceCounter": ResourceCounter,
50126
52654
  "RichBlockEditor": RichBlockEditor,
52655
+ "RoguelikeBoard": RoguelikeBoard,
52656
+ "RoguelikeTemplate": RoguelikeTemplate,
50127
52657
  "RuntimeDebugger": RuntimeDebugger,
50128
52658
  "ScaledDiagram": ScaledDiagram,
50129
52659
  "ScoreBoard": ScoreBoard,
@@ -50199,6 +52729,8 @@ var init_component_registry_generated = __esm({
50199
52729
  "Toast": Toast,
50200
52730
  "ToastSlot": ToastSlot,
50201
52731
  "Tooltip": Tooltip,
52732
+ "TowerDefenseBoard": TowerDefenseBoard,
52733
+ "TowerDefenseTemplate": TowerDefenseTemplate,
50202
52734
  "TraitFrame": TraitFrame,
50203
52735
  "TraitSlot": TraitSlot,
50204
52736
  "TrendIndicator": TrendIndicator,
@@ -54685,7 +57217,7 @@ function convertObjectProps(props) {
54685
57217
  function convertFnFormLambdasInProps(props) {
54686
57218
  return convertObjectProps(props);
54687
57219
  }
54688
- var log9 = createLogger("almadar:ui:effects:client-handlers");
57220
+ var log11 = createLogger("almadar:ui:effects:client-handlers");
54689
57221
  function createClientEffectHandlers(options) {
54690
57222
  const { eventBus, slotSetter, navigate, notify, callService } = options;
54691
57223
  return {
@@ -54694,10 +57226,10 @@ function createClientEffectHandlers(options) {
54694
57226
  eventBus.emit(prefixedEvent, payload);
54695
57227
  },
54696
57228
  persist: async () => {
54697
- log9.warn("persist is server-side only, ignored on client");
57229
+ log11.warn("persist is server-side only, ignored on client");
54698
57230
  },
54699
57231
  set: () => {
54700
- log9.warn("set is server-side only, ignored on client");
57232
+ log11.warn("set is server-side only, ignored on client");
54701
57233
  },
54702
57234
  callService: async (service, action, params) => {
54703
57235
  if (callService) return callService(service, action, params);
@@ -54726,10 +57258,10 @@ function createClientEffectHandlers(options) {
54726
57258
  slotSetter.addPattern(slot, pattern, props);
54727
57259
  },
54728
57260
  navigate: navigate ?? ((path) => {
54729
- log9.warn("No navigate handler, ignoring", { path });
57261
+ log11.warn("No navigate handler, ignoring", { path });
54730
57262
  }),
54731
57263
  notify: notify ?? ((msg, type) => {
54732
- log9.debug("notify", { type, message: msg });
57264
+ log11.debug("notify", { type, message: msg });
54733
57265
  })
54734
57266
  };
54735
57267
  }
@@ -55894,7 +58426,7 @@ OrbitalThemeProvider.displayName = "OrbitalThemeProvider";
55894
58426
  init_navigation();
55895
58427
  init_verificationRegistry();
55896
58428
  var PERF_NAMESPACE = "almadar:perf:canvas";
55897
- var log10 = createLogger(PERF_NAMESPACE);
58429
+ var log12 = createLogger(PERF_NAMESPACE);
55898
58430
  var RING_SIZE = 50;
55899
58431
  var ring = [];
55900
58432
  var writeIdx = 0;
@@ -55945,7 +58477,7 @@ function perfEnd(name, startToken, detail) {
55945
58477
  }
55946
58478
  }
55947
58479
  push({ name, durationMs, ts: endTs, detail });
55948
- log10.debug(name, () => ({ durationMs, ...detail ?? {} }));
58480
+ log12.debug(name, () => ({ durationMs, ...detail ?? {} }));
55949
58481
  }
55950
58482
  var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, commitTime) => {
55951
58483
  if (!isEnabled()) return;
@@ -55955,7 +58487,7 @@ var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, com
55955
58487
  ts: commitTime,
55956
58488
  detail: { baseDuration }
55957
58489
  });
55958
- log10.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
58490
+ log12.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
55959
58491
  };
55960
58492
 
55961
58493
  // runtime/prepareSchemaForPreview.ts
@@ -56598,7 +59130,7 @@ init_useEventBus();
56598
59130
  // components/avl/molecules/useCanvasDnd.tsx
56599
59131
  init_useEventBus();
56600
59132
  init_useAlmadarDndCollision();
56601
- var log11 = createLogger("almadar:ui:canvas-dnd");
59133
+ var log13 = createLogger("almadar:ui:canvas-dnd");
56602
59134
  function useCanvasDraggable({
56603
59135
  id,
56604
59136
  payload,
@@ -56637,7 +59169,7 @@ function defaultEmit(eventBus, drop) {
56637
59169
  if (payload.kind === "pattern") {
56638
59170
  const patternType = payload.data.type;
56639
59171
  if (typeof patternType !== "string") {
56640
- log11.warn("default-emit:pattern:missing-type");
59172
+ log13.warn("default-emit:pattern:missing-type");
56641
59173
  return;
56642
59174
  }
56643
59175
  const out = { patternType, containerNode: target.containerNode };
@@ -56646,23 +59178,23 @@ function defaultEmit(eventBus, drop) {
56646
59178
  out.index = resolved.index;
56647
59179
  }
56648
59180
  eventBus.emit("UI:PATTERN_DROP", out);
56649
- log11.info("default-emit:pattern", { patternType, level: target.level });
59181
+ log13.info("default-emit:pattern", { patternType, level: target.level });
56650
59182
  return;
56651
59183
  }
56652
59184
  if (payload.kind === "behavior") {
56653
59185
  const behaviorName = payload.data.name;
56654
59186
  if (typeof behaviorName !== "string") {
56655
- log11.warn("default-emit:behavior:missing-name");
59187
+ log13.warn("default-emit:behavior:missing-name");
56656
59188
  return;
56657
59189
  }
56658
59190
  eventBus.emit("UI:BEHAVIOR_DROP", {
56659
59191
  behaviorName,
56660
59192
  containerNode: target.containerNode
56661
59193
  });
56662
- log11.info("default-emit:behavior", { behaviorName, level: target.level });
59194
+ log13.info("default-emit:behavior", { behaviorName, level: target.level });
56663
59195
  return;
56664
59196
  }
56665
- log11.debug("default-emit:unhandled-kind", { kind: payload.kind });
59197
+ log13.debug("default-emit:unhandled-kind", { kind: payload.kind });
56666
59198
  }
56667
59199
  function CanvasDndProvider({
56668
59200
  children,
@@ -56678,9 +59210,9 @@ function CanvasDndProvider({
56678
59210
  if (payload) {
56679
59211
  setActivePayload(payload);
56680
59212
  eventBus.emit("UI:DRAG_START", { kind: payload.kind, data: payload.data });
56681
- log11.info("dragStart", { id: e.active.id, kind: payload.kind });
59213
+ log13.info("dragStart", { id: e.active.id, kind: payload.kind });
56682
59214
  } else {
56683
- log11.warn("dragStart:missing-payload", { id: e.active.id });
59215
+ log13.warn("dragStart:missing-payload", { id: e.active.id });
56684
59216
  }
56685
59217
  }, [eventBus]);
56686
59218
  const handleDragEnd = React91__default.useCallback((e) => {
@@ -56690,7 +59222,7 @@ function CanvasDndProvider({
56690
59222
  const overData = e.over?.data.current;
56691
59223
  const target = overData?.target;
56692
59224
  const accepts = overData?.accepts;
56693
- log11.info("dragEnd", {
59225
+ log13.info("dragEnd", {
56694
59226
  activeId: e.active.id,
56695
59227
  overId: e.over?.id,
56696
59228
  hasPayload: !!payload,
@@ -56702,7 +59234,7 @@ function CanvasDndProvider({
56702
59234
  }
56703
59235
  if (!payload || !target) return;
56704
59236
  if (accepts && !accepts.includes(payload.kind)) {
56705
- log11.debug("dragEnd:rejected:kind", { kind: payload.kind, accepts: [...accepts] });
59237
+ log13.debug("dragEnd:rejected:kind", { kind: payload.kind, accepts: [...accepts] });
56706
59238
  return;
56707
59239
  }
56708
59240
  const activator = e.activatorEvent;
@@ -56714,7 +59246,7 @@ function CanvasDndProvider({
56714
59246
  }, [eventBus, onDrop]);
56715
59247
  const handleDragCancel = React91__default.useCallback(() => {
56716
59248
  setActivePayload(null);
56717
- log11.info("dragCancel");
59249
+ log13.info("dragCancel");
56718
59250
  }, []);
56719
59251
  return /* @__PURE__ */ jsxs(
56720
59252
  DndContext,
@@ -58529,7 +61061,7 @@ init_AvlTransitionLane();
58529
61061
  init_AvlSwimLane();
58530
61062
  init_types();
58531
61063
  init_avl_elk_layout();
58532
- var log12 = createLogger("almadar:ui:avl:trait-scene");
61064
+ var log14 = createLogger("almadar:ui:avl:trait-scene");
58533
61065
  var SWIM_GUTTER2 = 120;
58534
61066
  var CENTER_W2 = 360;
58535
61067
  var AvlTraitScene = ({
@@ -58542,7 +61074,7 @@ var AvlTraitScene = ({
58542
61074
  const dataKey = useMemo(() => JSON.stringify(data), [data]);
58543
61075
  useEffect(() => {
58544
61076
  computeTraitLayout(data).then(setLayout).catch((error) => {
58545
- log12.error("computeTraitLayout failed", { error: error instanceof Error ? error : String(error) });
61077
+ log14.error("computeTraitLayout failed", { error: error instanceof Error ? error : String(error) });
58546
61078
  });
58547
61079
  }, [dataKey]);
58548
61080
  if (!layout) {