@almadar/ui 5.48.0 → 5.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/avl/index.cjs +2681 -148
  2. package/dist/avl/index.css +504 -0
  3. package/dist/avl/index.js +2681 -149
  4. package/dist/components/core/templates/index.d.ts +3 -0
  5. package/dist/components/game/atoms/ActionButton.d.ts +4 -1
  6. package/dist/components/game/atoms/ControlButton.d.ts +4 -2
  7. package/dist/components/game/atoms/DamageNumber.d.ts +4 -1
  8. package/dist/components/game/atoms/ScoreDisplay.d.ts +4 -1
  9. package/dist/components/game/atoms/TurnIndicator.d.ts +4 -1
  10. package/dist/components/game/molecules/EnemyPlate.d.ts +4 -1
  11. package/dist/components/game/molecules/StatBadge.d.ts +4 -1
  12. package/dist/components/game/molecules/three/index.cjs +1371 -283
  13. package/dist/components/game/molecules/three/index.js +1372 -284
  14. package/dist/components/game/organisms/GameBoard3D.d.ts +62 -0
  15. package/dist/components/game/organisms/PlatformerBoard.d.ts +51 -0
  16. package/dist/components/game/organisms/RoguelikeBoard.d.ts +59 -0
  17. package/dist/components/game/organisms/TowerDefenseBoard.d.ts +64 -0
  18. package/dist/components/game/organisms/index.d.ts +4 -0
  19. package/dist/components/game/templates/GameCanvas3DBattleTemplate.d.ts +22 -35
  20. package/dist/components/game/templates/GameCanvas3DCastleTemplate.d.ts +23 -23
  21. package/dist/components/game/templates/GameCanvas3DWorldMapTemplate.d.ts +27 -29
  22. package/dist/components/game/templates/PlatformerTemplate.d.ts +26 -0
  23. package/dist/components/game/templates/RoguelikeTemplate.d.ts +23 -0
  24. package/dist/components/game/templates/TowerDefenseTemplate.d.ts +43 -0
  25. package/dist/components/index.cjs +2674 -127
  26. package/dist/components/index.css +504 -0
  27. package/dist/components/index.js +2668 -129
  28. package/dist/docs/index.css +504 -0
  29. package/dist/providers/index.cjs +2664 -131
  30. package/dist/providers/index.css +504 -0
  31. package/dist/providers/index.js +2664 -132
  32. package/dist/runtime/index.cjs +2665 -132
  33. package/dist/runtime/index.css +504 -0
  34. package/dist/runtime/index.js +2665 -133
  35. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import * as React77 from 'react';
3
- import React77__default, { useContext, useMemo, useRef, useEffect, useCallback, useState, Suspense, lazy, createContext, useLayoutEffect, useId, useSyncExternalStore } from 'react';
3
+ import React77__default, { useContext, useMemo, useRef, useEffect, useCallback, useState, Suspense, lazy, createContext, useLayoutEffect, useId, forwardRef, useImperativeHandle, useSyncExternalStore, Component } from 'react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { EventBusContext, useTraitScope, TraitScopeProvider } from '@almadar/ui/providers';
@@ -41,6 +41,12 @@ import { DndContext, useSensors, useSensor, PointerSensor, KeyboardSensor, useDr
41
41
  import { useSortable, arrayMove, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
42
42
  import { CSS } from '@dnd-kit/utilities';
43
43
  import { useNodeId, ReactFlowProvider, Handle, Position } from '@xyflow/react';
44
+ import * as THREE from 'three';
45
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
46
+ import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
47
+ import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
48
+ import { Canvas, useThree } from '@react-three/fiber';
49
+ import { Grid as Grid$1, OrbitControls } from '@react-three/drei';
44
50
  import { useUISlots } from '@almadar/ui/context';
45
51
  import { getPatternDefinition as getPatternDefinition$1, getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
46
52
 
@@ -1276,9 +1282,9 @@ function resolvePhosphor(name, weight, family) {
1276
1282
  (lib) => {
1277
1283
  const PhosphorComp = lib[target];
1278
1284
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
1279
- const Component = PhosphorComp;
1285
+ const Component2 = PhosphorComp;
1280
1286
  const Adapter = (props) => /* @__PURE__ */ jsx(
1281
- Component,
1287
+ Component2,
1282
1288
  {
1283
1289
  weight,
1284
1290
  className: props.className,
@@ -1302,9 +1308,9 @@ function resolveTabler(name, family) {
1302
1308
  (lib) => {
1303
1309
  const TablerComp = lib[target];
1304
1310
  if (!TablerComp || typeof TablerComp !== "object") return null;
1305
- const Component = TablerComp;
1311
+ const Component2 = TablerComp;
1306
1312
  const Adapter = (props) => /* @__PURE__ */ jsx(
1307
- Component,
1313
+ Component2,
1308
1314
  {
1309
1315
  stroke: props.strokeWidth ?? 1.5,
1310
1316
  className: props.className,
@@ -1328,9 +1334,9 @@ function resolveFa(name, family) {
1328
1334
  (lib) => {
1329
1335
  const FaComp = lib[target];
1330
1336
  if (!FaComp || typeof FaComp !== "function") return null;
1331
- const Component = FaComp;
1337
+ const Component2 = FaComp;
1332
1338
  const Adapter = (props) => /* @__PURE__ */ jsx(
1333
- Component,
1339
+ Component2,
1334
1340
  {
1335
1341
  className: props.className,
1336
1342
  style: props.style,
@@ -3323,7 +3329,7 @@ var init_Box = __esm({
3323
3329
  position,
3324
3330
  className,
3325
3331
  children,
3326
- as: Component = "div",
3332
+ as: Component2 = "div",
3327
3333
  action,
3328
3334
  actionPayload,
3329
3335
  hoverEvent,
@@ -3355,7 +3361,7 @@ var init_Box = __esm({
3355
3361
  }, [hoverEvent, eventBus, onMouseLeave]);
3356
3362
  const isClickable = action || onClick;
3357
3363
  return React77__default.createElement(
3358
- Component,
3364
+ Component2,
3359
3365
  {
3360
3366
  ref,
3361
3367
  className: cn(
@@ -3404,10 +3410,10 @@ var init_Center = __esm({
3404
3410
  className,
3405
3411
  style,
3406
3412
  children,
3407
- as: Component = "div"
3413
+ as: Component2 = "div"
3408
3414
  }) => {
3409
3415
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
3410
- const Comp = Component;
3416
+ const Comp = Component2;
3411
3417
  return /* @__PURE__ */ jsx(
3412
3418
  Comp,
3413
3419
  {
@@ -4038,7 +4044,7 @@ var init_Stack = __esm({
4038
4044
  className,
4039
4045
  style,
4040
4046
  children,
4041
- as: Component = "div",
4047
+ as: Component2 = "div",
4042
4048
  onClick,
4043
4049
  onKeyDown,
4044
4050
  role,
@@ -4056,7 +4062,7 @@ var init_Stack = __esm({
4056
4062
  };
4057
4063
  const isHorizontal = direction === "horizontal";
4058
4064
  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";
4059
- const Comp = Component;
4065
+ const Comp = Component2;
4060
4066
  return /* @__PURE__ */ jsx(
4061
4067
  Comp,
4062
4068
  {
@@ -4247,8 +4253,8 @@ var init_Typography = __esm({
4247
4253
  children
4248
4254
  }) => {
4249
4255
  const variant = variantProp ?? (level ? `h${level}` : "body1");
4250
- const Component = as || defaultElements[variant];
4251
- const Comp = Component;
4256
+ const Component2 = as || defaultElements[variant];
4257
+ const Comp = Component2;
4252
4258
  return /* @__PURE__ */ jsx(
4253
4259
  Comp,
4254
4260
  {
@@ -7461,10 +7467,10 @@ var init_Container = __esm({
7461
7467
  center = true,
7462
7468
  className,
7463
7469
  children,
7464
- as: Component = "div"
7470
+ as: Component2 = "div"
7465
7471
  }) => {
7466
7472
  const resolvedSize = maxWidth ?? size ?? "lg";
7467
- const Comp = Component;
7473
+ const Comp = Component2;
7468
7474
  return /* @__PURE__ */ jsx(
7469
7475
  Comp,
7470
7476
  {
@@ -8179,6 +8185,7 @@ var init_Accordion = __esm({
8179
8185
  }
8180
8186
  });
8181
8187
  function ActionButton({
8188
+ assetUrl = DEFAULT_ASSET_URL,
8182
8189
  label = "Attack",
8183
8190
  icon,
8184
8191
  cooldown = 0,
@@ -8218,13 +8225,23 @@ function ActionButton({
8218
8225
  }
8219
8226
  }
8220
8227
  ),
8221
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8228
+ assetUrl ? /* @__PURE__ */ jsx(
8229
+ "img",
8230
+ {
8231
+ src: assetUrl,
8232
+ alt: "",
8233
+ width: 16,
8234
+ height: 16,
8235
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8236
+ className: cn("flex-shrink-0", sizes.icon)
8237
+ }
8238
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8222
8239
  const I = resolveIcon(icon);
8223
8240
  return I ? /* @__PURE__ */ jsx(I, { className: "w-4 h-4" }) : null;
8224
8241
  })() : /* @__PURE__ */ (() => {
8225
8242
  const I = icon;
8226
8243
  return /* @__PURE__ */ jsx(I, { className: "w-4 h-4" });
8227
- })() }),
8244
+ })() }) : null,
8228
8245
  /* @__PURE__ */ jsx("span", { className: "relative z-10", children: label }),
8229
8246
  hotkey && /* @__PURE__ */ jsx(
8230
8247
  "span",
@@ -8240,7 +8257,7 @@ function ActionButton({
8240
8257
  }
8241
8258
  );
8242
8259
  }
8243
- var sizeMap, variantStyles7;
8260
+ var sizeMap, variantStyles7, DEFAULT_ASSET_URL;
8244
8261
  var init_ActionButton = __esm({
8245
8262
  "components/game/atoms/ActionButton.tsx"() {
8246
8263
  init_cn();
@@ -8255,10 +8272,12 @@ var init_ActionButton = __esm({
8255
8272
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
8256
8273
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
8257
8274
  };
8275
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
8258
8276
  ActionButton.displayName = "ActionButton";
8259
8277
  }
8260
8278
  });
8261
8279
  function ControlButton({
8280
+ assetUrl = DEFAULT_ASSET_URL2,
8262
8281
  label,
8263
8282
  icon,
8264
8283
  size = "md",
@@ -8305,7 +8324,7 @@ function ControlButton({
8305
8324
  },
8306
8325
  [isPressed, releaseEvent, eventBus, onRelease]
8307
8326
  );
8308
- return /* @__PURE__ */ jsxs(
8327
+ return /* @__PURE__ */ jsx(
8309
8328
  "button",
8310
8329
  {
8311
8330
  type: "button",
@@ -8326,20 +8345,27 @@ function ControlButton({
8326
8345
  disabled && "opacity-50 cursor-not-allowed",
8327
8346
  className
8328
8347
  ),
8329
- children: [
8330
- icon && /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
8331
- const I = resolveIcon(icon);
8332
- return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
8333
- })() : icon : /* @__PURE__ */ (() => {
8334
- const I = icon;
8335
- return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
8336
- })() }),
8337
- label && !icon && /* @__PURE__ */ jsx("span", { children: label })
8338
- ]
8348
+ children: assetUrl ? /* @__PURE__ */ jsx(
8349
+ "img",
8350
+ {
8351
+ src: assetUrl,
8352
+ alt: "",
8353
+ width: 24,
8354
+ height: 24,
8355
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8356
+ className: "flex-shrink-0"
8357
+ }
8358
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
8359
+ const I = resolveIcon(icon);
8360
+ return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
8361
+ })() : icon : /* @__PURE__ */ (() => {
8362
+ const I = icon;
8363
+ return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
8364
+ })() }) : label ? /* @__PURE__ */ jsx("span", { children: label }) : null
8339
8365
  }
8340
8366
  );
8341
8367
  }
8342
- var sizeMap2, shapeMap, variantMap;
8368
+ var sizeMap2, shapeMap, variantMap, DEFAULT_ASSET_URL2;
8343
8369
  var init_ControlButton = __esm({
8344
8370
  "components/game/atoms/ControlButton.tsx"() {
8345
8371
  "use client";
@@ -8362,6 +8388,7 @@ var init_ControlButton = __esm({
8362
8388
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
8363
8389
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
8364
8390
  };
8391
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
8365
8392
  ControlButton.displayName = "ControlButton";
8366
8393
  }
8367
8394
  });
@@ -20171,7 +20198,7 @@ function getComboScale(combo) {
20171
20198
  return "";
20172
20199
  }
20173
20200
  function ComboCounter({
20174
- assetUrl = DEFAULT_ASSET_URL,
20201
+ assetUrl = DEFAULT_ASSET_URL3,
20175
20202
  combo = 5,
20176
20203
  multiplier,
20177
20204
  streak,
@@ -20216,11 +20243,11 @@ function ComboCounter({
20216
20243
  }
20217
20244
  );
20218
20245
  }
20219
- var DEFAULT_ASSET_URL, sizeMap4;
20246
+ var DEFAULT_ASSET_URL3, sizeMap4;
20220
20247
  var init_ComboCounter = __esm({
20221
20248
  "components/game/atoms/ComboCounter.tsx"() {
20222
20249
  init_cn();
20223
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
20250
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
20224
20251
  sizeMap4 = {
20225
20252
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
20226
20253
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -20629,7 +20656,7 @@ var init_CounterTemplate = __esm({
20629
20656
  }
20630
20657
  });
20631
20658
  function ItemSlot({
20632
- assetUrl = DEFAULT_ASSET_URL2,
20659
+ assetUrl = DEFAULT_ASSET_URL4,
20633
20660
  icon = "sword",
20634
20661
  label = "Iron Sword",
20635
20662
  quantity,
@@ -20688,7 +20715,7 @@ function ItemSlot({
20688
20715
  }
20689
20716
  );
20690
20717
  }
20691
- var sizeMap5, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL2, assetSizeMap;
20718
+ var sizeMap5, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL4, assetSizeMap;
20692
20719
  var init_ItemSlot = __esm({
20693
20720
  "components/game/atoms/ItemSlot.tsx"() {
20694
20721
  "use client";
@@ -20713,7 +20740,7 @@ var init_ItemSlot = __esm({
20713
20740
  epic: "shadow-lg",
20714
20741
  legendary: "shadow-lg"
20715
20742
  };
20716
- DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
20743
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
20717
20744
  assetSizeMap = {
20718
20745
  sm: 28,
20719
20746
  md: 40,
@@ -20892,6 +20919,7 @@ var init_DPad = __esm({
20892
20919
  }
20893
20920
  });
20894
20921
  function DamageNumber({
20922
+ assetUrl = DEFAULT_ASSET_URL5,
20895
20923
  value = 42,
20896
20924
  type = "damage",
20897
20925
  size = "md",
@@ -20900,22 +20928,35 @@ function DamageNumber({
20900
20928
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
20901
20929
  return /* @__PURE__ */ jsxs(Fragment, { children: [
20902
20930
  /* @__PURE__ */ jsx("style", { children: floatKeyframes }),
20903
- /* @__PURE__ */ jsx(
20931
+ /* @__PURE__ */ jsxs(
20904
20932
  "span",
20905
20933
  {
20906
20934
  className: cn(
20907
- "inline-block font-mono select-none pointer-events-none",
20935
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
20908
20936
  sizeMap7[size],
20909
20937
  typeStyles[type],
20910
20938
  className
20911
20939
  ),
20912
20940
  style: { animation: "damageFloat 1s ease-out forwards" },
20913
- children: displayText
20941
+ children: [
20942
+ assetUrl && /* @__PURE__ */ jsx(
20943
+ "img",
20944
+ {
20945
+ src: assetUrl,
20946
+ alt: "",
20947
+ width: 14,
20948
+ height: 14,
20949
+ style: { imageRendering: "pixelated", objectFit: "contain" },
20950
+ className: "flex-shrink-0"
20951
+ }
20952
+ ),
20953
+ displayText
20954
+ ]
20914
20955
  }
20915
20956
  )
20916
20957
  ] });
20917
20958
  }
20918
- var sizeMap7, typeStyles, floatKeyframes;
20959
+ var sizeMap7, typeStyles, floatKeyframes, DEFAULT_ASSET_URL5;
20919
20960
  var init_DamageNumber = __esm({
20920
20961
  "components/game/atoms/DamageNumber.tsx"() {
20921
20962
  init_cn();
@@ -20937,6 +20978,7 @@ var init_DamageNumber = __esm({
20937
20978
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
20938
20979
  }
20939
20980
  `;
20981
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
20940
20982
  DamageNumber.displayName = "DamageNumber";
20941
20983
  }
20942
20984
  });
@@ -24757,7 +24799,7 @@ var init_Flex = __esm({
24757
24799
  basis,
24758
24800
  className,
24759
24801
  children,
24760
- as: Component = "div"
24802
+ as: Component2 = "div"
24761
24803
  }) => {
24762
24804
  const flexStyle = {};
24763
24805
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -24769,7 +24811,7 @@ var init_Flex = __esm({
24769
24811
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
24770
24812
  }
24771
24813
  }
24772
- const Comp = Component;
24814
+ const Comp = Component2;
24773
24815
  return /* @__PURE__ */ jsx(
24774
24816
  Comp,
24775
24817
  {
@@ -24889,11 +24931,11 @@ var init_Grid = __esm({
24889
24931
  className,
24890
24932
  style,
24891
24933
  children,
24892
- as: Component = "div"
24934
+ as: Component2 = "div"
24893
24935
  }) => {
24894
24936
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
24895
24937
  return React77__default.createElement(
24896
- Component,
24938
+ Component2,
24897
24939
  {
24898
24940
  className: cn(
24899
24941
  "grid",
@@ -27042,6 +27084,7 @@ var init_HealthBar = __esm({
27042
27084
  }
27043
27085
  });
27044
27086
  function ScoreDisplay({
27087
+ assetUrl = DEFAULT_ASSET_URL6,
27045
27088
  value,
27046
27089
  label,
27047
27090
  icon,
@@ -27088,14 +27131,24 @@ function ScoreDisplay({
27088
27131
  className
27089
27132
  ),
27090
27133
  children: [
27091
- icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
27134
+ assetUrl ? /* @__PURE__ */ jsx(
27135
+ "img",
27136
+ {
27137
+ src: assetUrl,
27138
+ alt: "",
27139
+ width: 20,
27140
+ height: 20,
27141
+ style: { imageRendering: "pixelated", objectFit: "contain" },
27142
+ className: "flex-shrink-0"
27143
+ }
27144
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }) : null,
27092
27145
  label && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
27093
27146
  /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formattedValue })
27094
27147
  ]
27095
27148
  }
27096
27149
  );
27097
27150
  }
27098
- var sizeMap10;
27151
+ var sizeMap10, DEFAULT_ASSET_URL6;
27099
27152
  var init_ScoreDisplay = __esm({
27100
27153
  "components/game/atoms/ScoreDisplay.tsx"() {
27101
27154
  "use client";
@@ -27107,10 +27160,12 @@ var init_ScoreDisplay = __esm({
27107
27160
  lg: "text-2xl",
27108
27161
  xl: "text-4xl"
27109
27162
  };
27163
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
27110
27164
  ScoreDisplay.displayName = "ScoreDisplay";
27111
27165
  }
27112
27166
  });
27113
27167
  function StatBadge({
27168
+ assetUrl = DEFAULT_ASSET_URL7,
27114
27169
  label,
27115
27170
  value = 0,
27116
27171
  max,
@@ -27134,7 +27189,17 @@ function StatBadge({
27134
27189
  className
27135
27190
  ),
27136
27191
  children: [
27137
- 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" }) }),
27192
+ assetUrl ? /* @__PURE__ */ jsx(
27193
+ "img",
27194
+ {
27195
+ src: assetUrl,
27196
+ alt: "",
27197
+ width: 16,
27198
+ height: 16,
27199
+ style: { imageRendering: "pixelated", objectFit: "contain" },
27200
+ className: "flex-shrink-0"
27201
+ }
27202
+ ) : 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,
27138
27203
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-medium", children: label }),
27139
27204
  format === "hearts" && max && /* @__PURE__ */ jsx(
27140
27205
  HealthBar,
@@ -27167,7 +27232,7 @@ function StatBadge({
27167
27232
  }
27168
27233
  );
27169
27234
  }
27170
- var sizeMap11, variantMap2;
27235
+ var sizeMap11, variantMap2, DEFAULT_ASSET_URL7;
27171
27236
  var init_StatBadge = __esm({
27172
27237
  "components/game/molecules/StatBadge.tsx"() {
27173
27238
  init_cn();
@@ -27186,6 +27251,7 @@ var init_StatBadge = __esm({
27186
27251
  warning: "bg-warning/15 border-warning/40 text-foreground",
27187
27252
  danger: "bg-error/15 border-error/40 text-foreground"
27188
27253
  };
27254
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
27189
27255
  StatBadge.displayName = "StatBadge";
27190
27256
  }
27191
27257
  });
@@ -27269,7 +27335,7 @@ var init_InventoryGrid = __esm({
27269
27335
  }
27270
27336
  });
27271
27337
  function WaypointMarker({
27272
- assetUrl = DEFAULT_ASSET_URL3,
27338
+ assetUrl = DEFAULT_ASSET_URL8,
27273
27339
  label,
27274
27340
  icon,
27275
27341
  active = true,
@@ -27335,12 +27401,12 @@ function WaypointMarker({
27335
27401
  )
27336
27402
  ] });
27337
27403
  }
27338
- var DEFAULT_ASSET_URL3, sizeMap12, checkIcon;
27404
+ var DEFAULT_ASSET_URL8, sizeMap12, checkIcon;
27339
27405
  var init_WaypointMarker = __esm({
27340
27406
  "components/game/atoms/WaypointMarker.tsx"() {
27341
27407
  init_cn();
27342
27408
  init_Icon();
27343
- DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
27409
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
27344
27410
  sizeMap12 = {
27345
27411
  sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
27346
27412
  md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
@@ -27858,6 +27924,7 @@ var init_ResourceBar = __esm({
27858
27924
  }
27859
27925
  });
27860
27926
  function TurnIndicator({
27927
+ assetUrl = DEFAULT_ASSET_URL9,
27861
27928
  currentTurn = 1,
27862
27929
  maxTurns,
27863
27930
  activeTeam,
@@ -27890,14 +27957,24 @@ function TurnIndicator({
27890
27957
  ] }),
27891
27958
  activeTeam && /* @__PURE__ */ jsxs(Fragment, { children: [
27892
27959
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "|" }),
27893
- /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
27960
+ assetUrl ? /* @__PURE__ */ jsx(
27961
+ "img",
27962
+ {
27963
+ src: assetUrl,
27964
+ alt: "",
27965
+ width: 12,
27966
+ height: 12,
27967
+ style: { imageRendering: "pixelated", objectFit: "contain" },
27968
+ className: "flex-shrink-0"
27969
+ }
27970
+ ) : /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
27894
27971
  /* @__PURE__ */ jsx("span", { className: "text-success", children: activeTeam })
27895
27972
  ] })
27896
27973
  ]
27897
27974
  }
27898
27975
  );
27899
27976
  }
27900
- var sizeMap14;
27977
+ var sizeMap14, DEFAULT_ASSET_URL9;
27901
27978
  var init_TurnIndicator = __esm({
27902
27979
  "components/game/atoms/TurnIndicator.tsx"() {
27903
27980
  init_cn();
@@ -27906,6 +27983,7 @@ var init_TurnIndicator = __esm({
27906
27983
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
27907
27984
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
27908
27985
  };
27986
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
27909
27987
  TurnIndicator.displayName = "TurnIndicator";
27910
27988
  }
27911
27989
  });
@@ -27976,6 +28054,7 @@ var init_TurnPanel = __esm({
27976
28054
  }
27977
28055
  });
27978
28056
  function EnemyPlate({
28057
+ assetUrl = DEFAULT_ASSET_URL10,
27979
28058
  name = "Shadow Guard",
27980
28059
  health = 80,
27981
28060
  maxHealth = 100,
@@ -27993,15 +28072,28 @@ function EnemyPlate({
27993
28072
  ),
27994
28073
  children: [
27995
28074
  /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
27996
- /* @__PURE__ */ jsx(
27997
- Typography,
27998
- {
27999
- variant: "caption",
28000
- weight: "bold",
28001
- className: "text-[var(--color-foreground)] truncate",
28002
- children: name
28003
- }
28004
- ),
28075
+ /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
28076
+ assetUrl && /* @__PURE__ */ jsx(
28077
+ "img",
28078
+ {
28079
+ src: assetUrl,
28080
+ alt: name,
28081
+ width: 24,
28082
+ height: 24,
28083
+ style: { objectFit: "cover", borderRadius: "50%" },
28084
+ className: "flex-shrink-0"
28085
+ }
28086
+ ),
28087
+ /* @__PURE__ */ jsx(
28088
+ Typography,
28089
+ {
28090
+ variant: "caption",
28091
+ weight: "bold",
28092
+ className: "text-[var(--color-foreground)] truncate",
28093
+ children: name
28094
+ }
28095
+ )
28096
+ ] }),
28005
28097
  level != null && /* @__PURE__ */ jsxs(Badge, { variant: "neutral", size: "sm", children: [
28006
28098
  "Lv.",
28007
28099
  level
@@ -28046,7 +28138,7 @@ function EnemyPlate({
28046
28138
  }
28047
28139
  );
28048
28140
  }
28049
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
28141
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL10;
28050
28142
  var init_EnemyPlate = __esm({
28051
28143
  "components/game/molecules/EnemyPlate.tsx"() {
28052
28144
  "use client";
@@ -28063,6 +28155,7 @@ var init_EnemyPlate = __esm({
28063
28155
  DEFAULT_ENEMY_EFFECTS = [
28064
28156
  { icon: "flame", label: "Burn", variant: "debuff" }
28065
28157
  ];
28158
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
28066
28159
  EnemyPlate.displayName = "EnemyPlate";
28067
28160
  }
28068
28161
  });
@@ -29616,13 +29709,13 @@ var init_MapView = __esm({
29616
29709
  shadowSize: [41, 41]
29617
29710
  });
29618
29711
  L.Marker.prototype.options.icon = defaultIcon;
29619
- const { useEffect: useEffect74, useRef: useRef71, useCallback: useCallback113, useState: useState105 } = React77__default;
29712
+ const { useEffect: useEffect80, useRef: useRef77, useCallback: useCallback120, useState: useState110 } = React77__default;
29620
29713
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29621
- const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29714
+ const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29622
29715
  function MapUpdater({ centerLat, centerLng, zoom }) {
29623
29716
  const map = useMap();
29624
- const prevRef = useRef71({ centerLat, centerLng, zoom });
29625
- useEffect74(() => {
29717
+ const prevRef = useRef77({ centerLat, centerLng, zoom });
29718
+ useEffect80(() => {
29626
29719
  const prev = prevRef.current;
29627
29720
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
29628
29721
  map.setView([centerLat, centerLng], zoom);
@@ -29633,7 +29726,7 @@ var init_MapView = __esm({
29633
29726
  }
29634
29727
  function MapClickHandler({ onMapClick }) {
29635
29728
  const map = useMap();
29636
- useEffect74(() => {
29729
+ useEffect80(() => {
29637
29730
  if (!onMapClick) return;
29638
29731
  const handler = (e) => {
29639
29732
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -29660,9 +29753,9 @@ var init_MapView = __esm({
29660
29753
  className,
29661
29754
  showAttribution = true
29662
29755
  }) {
29663
- const eventBus = useEventBus2();
29664
- const [clickedPosition, setClickedPosition] = useState105(null);
29665
- const handleMapClick = useCallback113((lat, lng) => {
29756
+ const eventBus = useEventBus3();
29757
+ const [clickedPosition, setClickedPosition] = useState110(null);
29758
+ const handleMapClick = useCallback120((lat, lng) => {
29666
29759
  if (showClickedPin) {
29667
29760
  setClickedPosition({ lat, lng });
29668
29761
  }
@@ -29671,7 +29764,7 @@ var init_MapView = __esm({
29671
29764
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29672
29765
  }
29673
29766
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29674
- const handleMarkerClick = useCallback113((marker) => {
29767
+ const handleMarkerClick = useCallback120((marker) => {
29675
29768
  onMarkerClick?.(marker);
29676
29769
  if (markerClickEvent) {
29677
29770
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -36798,10 +36891,10 @@ var init_Section = __esm({
36798
36891
  children,
36799
36892
  headerClassName,
36800
36893
  contentClassName,
36801
- as: Component = "section"
36894
+ as: Component2 = "section"
36802
36895
  }) => {
36803
36896
  const hasHeader = title || description || action;
36804
- const Comp = Component;
36897
+ const Comp = Component2;
36805
36898
  return /* @__PURE__ */ jsxs(
36806
36899
  Comp,
36807
36900
  {
@@ -39802,7 +39895,7 @@ var init_DrawerSlot = __esm({
39802
39895
  }
39803
39896
  });
39804
39897
  function StateIndicator({
39805
- assetUrl = DEFAULT_ASSET_URL4,
39898
+ assetUrl = DEFAULT_ASSET_URL11,
39806
39899
  state = "idle",
39807
39900
  label,
39808
39901
  size = "md",
@@ -39841,13 +39934,13 @@ function StateIndicator({
39841
39934
  }
39842
39935
  );
39843
39936
  }
39844
- var DEFAULT_ASSET_URL4, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
39937
+ var DEFAULT_ASSET_URL11, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
39845
39938
  var init_StateIndicator = __esm({
39846
39939
  "components/game/atoms/StateIndicator.tsx"() {
39847
39940
  init_Box();
39848
39941
  init_Icon();
39849
39942
  init_cn();
39850
- DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
39943
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
39851
39944
  DEFAULT_STATE_STYLES = {
39852
39945
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
39853
39946
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -41693,6 +41786,1568 @@ var init_GameAudioToggle = __esm({
41693
41786
  GameAudioToggle.displayName = "GameAudioToggle";
41694
41787
  }
41695
41788
  });
41789
+ function detectAssetRoot(modelUrl) {
41790
+ const idx = modelUrl.indexOf("/3d/");
41791
+ if (idx !== -1) {
41792
+ return modelUrl.substring(0, idx + 4);
41793
+ }
41794
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41795
+ }
41796
+ function createGLTFLoaderForUrl(url) {
41797
+ const loader = new GLTFLoader();
41798
+ loader.setResourcePath(detectAssetRoot(url));
41799
+ return loader;
41800
+ }
41801
+ var AssetLoader;
41802
+ var init_AssetLoader = __esm({
41803
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
41804
+ AssetLoader = class {
41805
+ constructor() {
41806
+ __publicField(this, "objLoader");
41807
+ __publicField(this, "textureLoader");
41808
+ __publicField(this, "modelCache");
41809
+ __publicField(this, "textureCache");
41810
+ __publicField(this, "loadingPromises");
41811
+ this.objLoader = new OBJLoader();
41812
+ this.textureLoader = new THREE.TextureLoader();
41813
+ this.modelCache = /* @__PURE__ */ new Map();
41814
+ this.textureCache = /* @__PURE__ */ new Map();
41815
+ this.loadingPromises = /* @__PURE__ */ new Map();
41816
+ }
41817
+ /**
41818
+ * Load a GLB/GLTF model
41819
+ * @param url - URL to the .glb or .gltf file
41820
+ * @returns Promise with loaded model scene and animations
41821
+ */
41822
+ async loadModel(url) {
41823
+ if (this.modelCache.has(url)) {
41824
+ return this.modelCache.get(url);
41825
+ }
41826
+ if (this.loadingPromises.has(url)) {
41827
+ return this.loadingPromises.get(url);
41828
+ }
41829
+ const loader = createGLTFLoaderForUrl(url);
41830
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
41831
+ const result = {
41832
+ scene: gltf.scene,
41833
+ animations: gltf.animations || []
41834
+ };
41835
+ this.modelCache.set(url, result);
41836
+ this.loadingPromises.delete(url);
41837
+ return result;
41838
+ }).catch((error) => {
41839
+ this.loadingPromises.delete(url);
41840
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
41841
+ });
41842
+ this.loadingPromises.set(url, loadPromise);
41843
+ return loadPromise;
41844
+ }
41845
+ /**
41846
+ * Load an OBJ model (fallback for non-GLB assets)
41847
+ * @param url - URL to the .obj file
41848
+ * @returns Promise with loaded object group
41849
+ */
41850
+ async loadOBJ(url) {
41851
+ if (this.modelCache.has(url)) {
41852
+ return this.modelCache.get(url).scene;
41853
+ }
41854
+ if (this.loadingPromises.has(url)) {
41855
+ const result = await this.loadingPromises.get(url);
41856
+ return result.scene;
41857
+ }
41858
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
41859
+ const result = {
41860
+ scene: group,
41861
+ animations: []
41862
+ };
41863
+ this.modelCache.set(url, result);
41864
+ this.loadingPromises.delete(url);
41865
+ return result;
41866
+ }).catch((error) => {
41867
+ this.loadingPromises.delete(url);
41868
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
41869
+ });
41870
+ this.loadingPromises.set(url, loadPromise);
41871
+ return (await loadPromise).scene;
41872
+ }
41873
+ /**
41874
+ * Load a texture
41875
+ * @param url - URL to the texture image
41876
+ * @returns Promise with loaded texture
41877
+ */
41878
+ async loadTexture(url) {
41879
+ if (this.textureCache.has(url)) {
41880
+ return this.textureCache.get(url);
41881
+ }
41882
+ if (this.loadingPromises.has(`texture:${url}`)) {
41883
+ return this.loadingPromises.get(`texture:${url}`);
41884
+ }
41885
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
41886
+ texture.colorSpace = THREE.SRGBColorSpace;
41887
+ this.textureCache.set(url, texture);
41888
+ this.loadingPromises.delete(`texture:${url}`);
41889
+ return texture;
41890
+ }).catch((error) => {
41891
+ this.loadingPromises.delete(`texture:${url}`);
41892
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
41893
+ });
41894
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
41895
+ return loadPromise;
41896
+ }
41897
+ /**
41898
+ * Preload multiple assets
41899
+ * @param urls - Array of asset URLs to preload
41900
+ * @returns Promise that resolves when all assets are loaded
41901
+ */
41902
+ async preload(urls) {
41903
+ const promises = urls.map((url) => {
41904
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41905
+ return this.loadModel(url).catch(() => null);
41906
+ } else if (url.endsWith(".obj")) {
41907
+ return this.loadOBJ(url).catch(() => null);
41908
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41909
+ return this.loadTexture(url).catch(() => null);
41910
+ }
41911
+ return Promise.resolve(null);
41912
+ });
41913
+ await Promise.all(promises);
41914
+ }
41915
+ /**
41916
+ * Check if a model is cached
41917
+ * @param url - Model URL
41918
+ */
41919
+ hasModel(url) {
41920
+ return this.modelCache.has(url);
41921
+ }
41922
+ /**
41923
+ * Check if a texture is cached
41924
+ * @param url - Texture URL
41925
+ */
41926
+ hasTexture(url) {
41927
+ return this.textureCache.has(url);
41928
+ }
41929
+ /**
41930
+ * Get cached model (throws if not cached)
41931
+ * @param url - Model URL
41932
+ */
41933
+ getModel(url) {
41934
+ const model = this.modelCache.get(url);
41935
+ if (!model) {
41936
+ throw new Error(`Model ${url} not in cache`);
41937
+ }
41938
+ return model;
41939
+ }
41940
+ /**
41941
+ * Get cached texture (throws if not cached)
41942
+ * @param url - Texture URL
41943
+ */
41944
+ getTexture(url) {
41945
+ const texture = this.textureCache.get(url);
41946
+ if (!texture) {
41947
+ throw new Error(`Texture ${url} not in cache`);
41948
+ }
41949
+ return texture;
41950
+ }
41951
+ /**
41952
+ * Clear all caches
41953
+ */
41954
+ clearCache() {
41955
+ this.textureCache.forEach((texture) => {
41956
+ texture.dispose();
41957
+ });
41958
+ this.modelCache.forEach((model) => {
41959
+ model.scene.traverse((child) => {
41960
+ if (child instanceof THREE.Mesh) {
41961
+ child.geometry.dispose();
41962
+ if (Array.isArray(child.material)) {
41963
+ child.material.forEach((m) => m.dispose());
41964
+ } else {
41965
+ child.material.dispose();
41966
+ }
41967
+ }
41968
+ });
41969
+ });
41970
+ this.modelCache.clear();
41971
+ this.textureCache.clear();
41972
+ this.loadingPromises.clear();
41973
+ }
41974
+ /**
41975
+ * Get cache statistics
41976
+ */
41977
+ getStats() {
41978
+ return {
41979
+ models: this.modelCache.size,
41980
+ textures: this.textureCache.size,
41981
+ loading: this.loadingPromises.size
41982
+ };
41983
+ }
41984
+ };
41985
+ new AssetLoader();
41986
+ }
41987
+ });
41988
+ function useAssetLoader(options = {}) {
41989
+ const { preloadUrls = [], loader: customLoader } = options;
41990
+ const loaderRef = useRef(customLoader || new AssetLoader());
41991
+ const [state, setState] = useState({
41992
+ isLoading: false,
41993
+ progress: 0,
41994
+ loaded: 0,
41995
+ total: 0,
41996
+ errors: []
41997
+ });
41998
+ useEffect(() => {
41999
+ if (preloadUrls.length > 0) {
42000
+ preload(preloadUrls);
42001
+ }
42002
+ }, []);
42003
+ const updateProgress = useCallback((loaded, total) => {
42004
+ setState((prev) => ({
42005
+ ...prev,
42006
+ loaded,
42007
+ total,
42008
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
42009
+ }));
42010
+ }, []);
42011
+ const loadModel = useCallback(
42012
+ async (url) => {
42013
+ setState((prev) => ({ ...prev, isLoading: true }));
42014
+ try {
42015
+ const model = await loaderRef.current.loadModel(url);
42016
+ setState((prev) => ({
42017
+ ...prev,
42018
+ isLoading: false,
42019
+ loaded: prev.loaded + 1
42020
+ }));
42021
+ return model;
42022
+ } catch (error) {
42023
+ const errorMsg = error instanceof Error ? error.message : String(error);
42024
+ setState((prev) => ({
42025
+ ...prev,
42026
+ isLoading: false,
42027
+ errors: [...prev.errors, errorMsg]
42028
+ }));
42029
+ throw error;
42030
+ }
42031
+ },
42032
+ []
42033
+ );
42034
+ const loadOBJ = useCallback(
42035
+ async (url) => {
42036
+ setState((prev) => ({ ...prev, isLoading: true }));
42037
+ try {
42038
+ const model = await loaderRef.current.loadOBJ(url);
42039
+ setState((prev) => ({
42040
+ ...prev,
42041
+ isLoading: false,
42042
+ loaded: prev.loaded + 1
42043
+ }));
42044
+ return model;
42045
+ } catch (error) {
42046
+ const errorMsg = error instanceof Error ? error.message : String(error);
42047
+ setState((prev) => ({
42048
+ ...prev,
42049
+ isLoading: false,
42050
+ errors: [...prev.errors, errorMsg]
42051
+ }));
42052
+ throw error;
42053
+ }
42054
+ },
42055
+ []
42056
+ );
42057
+ const loadTexture = useCallback(
42058
+ async (url) => {
42059
+ setState((prev) => ({ ...prev, isLoading: true }));
42060
+ try {
42061
+ const texture = await loaderRef.current.loadTexture(url);
42062
+ setState((prev) => ({
42063
+ ...prev,
42064
+ isLoading: false,
42065
+ loaded: prev.loaded + 1
42066
+ }));
42067
+ return texture;
42068
+ } catch (error) {
42069
+ const errorMsg = error instanceof Error ? error.message : String(error);
42070
+ setState((prev) => ({
42071
+ ...prev,
42072
+ isLoading: false,
42073
+ errors: [...prev.errors, errorMsg]
42074
+ }));
42075
+ throw error;
42076
+ }
42077
+ },
42078
+ []
42079
+ );
42080
+ const preload = useCallback(
42081
+ async (urls) => {
42082
+ setState((prev) => ({
42083
+ ...prev,
42084
+ isLoading: true,
42085
+ total: urls.length,
42086
+ loaded: 0,
42087
+ errors: []
42088
+ }));
42089
+ let completed = 0;
42090
+ const errors = [];
42091
+ await Promise.all(
42092
+ urls.map(async (url) => {
42093
+ try {
42094
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
42095
+ await loaderRef.current.loadModel(url);
42096
+ } else if (url.endsWith(".obj")) {
42097
+ await loaderRef.current.loadOBJ(url);
42098
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
42099
+ await loaderRef.current.loadTexture(url);
42100
+ }
42101
+ completed++;
42102
+ updateProgress(completed, urls.length);
42103
+ } catch (error) {
42104
+ const errorMsg = error instanceof Error ? error.message : String(error);
42105
+ errors.push(`${url}: ${errorMsg}`);
42106
+ completed++;
42107
+ updateProgress(completed, urls.length);
42108
+ }
42109
+ })
42110
+ );
42111
+ setState((prev) => ({
42112
+ ...prev,
42113
+ isLoading: false,
42114
+ errors
42115
+ }));
42116
+ },
42117
+ [updateProgress]
42118
+ );
42119
+ const hasModel = useCallback((url) => {
42120
+ return loaderRef.current.hasModel(url);
42121
+ }, []);
42122
+ const hasTexture = useCallback((url) => {
42123
+ return loaderRef.current.hasTexture(url);
42124
+ }, []);
42125
+ const getModel = useCallback((url) => {
42126
+ try {
42127
+ return loaderRef.current.getModel(url);
42128
+ } catch {
42129
+ return void 0;
42130
+ }
42131
+ }, []);
42132
+ const getTexture = useCallback((url) => {
42133
+ try {
42134
+ return loaderRef.current.getTexture(url);
42135
+ } catch {
42136
+ return void 0;
42137
+ }
42138
+ }, []);
42139
+ const clearCache = useCallback(() => {
42140
+ loaderRef.current.clearCache();
42141
+ setState({
42142
+ isLoading: false,
42143
+ progress: 0,
42144
+ loaded: 0,
42145
+ total: 0,
42146
+ errors: []
42147
+ });
42148
+ }, []);
42149
+ return {
42150
+ ...state,
42151
+ loadModel,
42152
+ loadOBJ,
42153
+ loadTexture,
42154
+ preload,
42155
+ hasModel,
42156
+ hasTexture,
42157
+ getModel,
42158
+ getTexture,
42159
+ clearCache
42160
+ };
42161
+ }
42162
+ var init_useAssetLoader = __esm({
42163
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
42164
+ "use client";
42165
+ init_AssetLoader();
42166
+ }
42167
+ });
42168
+ function useGameCanvas3DEvents(options) {
42169
+ const {
42170
+ tileClickEvent,
42171
+ unitClickEvent,
42172
+ featureClickEvent,
42173
+ canvasClickEvent,
42174
+ tileHoverEvent,
42175
+ tileLeaveEvent,
42176
+ unitAnimationEvent,
42177
+ cameraChangeEvent,
42178
+ onTileClick,
42179
+ onUnitClick,
42180
+ onFeatureClick,
42181
+ onCanvasClick,
42182
+ onTileHover,
42183
+ onUnitAnimation
42184
+ } = options;
42185
+ const emit = useEmitEvent();
42186
+ const optionsRef = useRef(options);
42187
+ optionsRef.current = options;
42188
+ const handleTileClick = useCallback(
42189
+ (tile, event) => {
42190
+ if (tileClickEvent) {
42191
+ emit(tileClickEvent, {
42192
+ tileId: tile.id,
42193
+ x: tile.x,
42194
+ z: tile.z ?? tile.y ?? 0,
42195
+ type: tile.type,
42196
+ terrain: tile.terrain,
42197
+ elevation: tile.elevation
42198
+ });
42199
+ }
42200
+ optionsRef.current.onTileClick?.(tile, event);
42201
+ },
42202
+ [tileClickEvent, emit]
42203
+ );
42204
+ const handleUnitClick = useCallback(
42205
+ (unit, event) => {
42206
+ if (unitClickEvent) {
42207
+ emit(unitClickEvent, {
42208
+ unitId: unit.id,
42209
+ x: unit.x,
42210
+ z: unit.z ?? unit.y ?? 0,
42211
+ unitType: unit.unitType,
42212
+ name: unit.name,
42213
+ team: unit.team,
42214
+ faction: unit.faction,
42215
+ health: unit.health,
42216
+ maxHealth: unit.maxHealth
42217
+ });
42218
+ }
42219
+ optionsRef.current.onUnitClick?.(unit, event);
42220
+ },
42221
+ [unitClickEvent, emit]
42222
+ );
42223
+ const handleFeatureClick = useCallback(
42224
+ (feature, event) => {
42225
+ if (featureClickEvent) {
42226
+ emit(featureClickEvent, {
42227
+ featureId: feature.id,
42228
+ x: feature.x,
42229
+ z: feature.z ?? feature.y ?? 0,
42230
+ type: feature.type,
42231
+ elevation: feature.elevation
42232
+ });
42233
+ }
42234
+ optionsRef.current.onFeatureClick?.(feature, event);
42235
+ },
42236
+ [featureClickEvent, emit]
42237
+ );
42238
+ const handleCanvasClick = useCallback(
42239
+ (event) => {
42240
+ if (canvasClickEvent) {
42241
+ emit(canvasClickEvent, {
42242
+ clientX: event.clientX,
42243
+ clientY: event.clientY,
42244
+ button: event.button
42245
+ });
42246
+ }
42247
+ optionsRef.current.onCanvasClick?.(event);
42248
+ },
42249
+ [canvasClickEvent, emit]
42250
+ );
42251
+ const handleTileHover = useCallback(
42252
+ (tile, event) => {
42253
+ if (tile) {
42254
+ if (tileHoverEvent) {
42255
+ emit(tileHoverEvent, {
42256
+ tileId: tile.id,
42257
+ x: tile.x,
42258
+ z: tile.z ?? tile.y ?? 0,
42259
+ type: tile.type
42260
+ });
42261
+ }
42262
+ } else {
42263
+ if (tileLeaveEvent) {
42264
+ emit(tileLeaveEvent, {});
42265
+ }
42266
+ }
42267
+ optionsRef.current.onTileHover?.(tile, event);
42268
+ },
42269
+ [tileHoverEvent, tileLeaveEvent, emit]
42270
+ );
42271
+ const handleUnitAnimation = useCallback(
42272
+ (unitId, state) => {
42273
+ if (unitAnimationEvent) {
42274
+ emit(unitAnimationEvent, {
42275
+ unitId,
42276
+ state,
42277
+ timestamp: Date.now()
42278
+ });
42279
+ }
42280
+ optionsRef.current.onUnitAnimation?.(unitId, state);
42281
+ },
42282
+ [unitAnimationEvent, emit]
42283
+ );
42284
+ const handleCameraChange = useCallback(
42285
+ (position) => {
42286
+ if (cameraChangeEvent) {
42287
+ emit(cameraChangeEvent, {
42288
+ position,
42289
+ timestamp: Date.now()
42290
+ });
42291
+ }
42292
+ },
42293
+ [cameraChangeEvent, emit]
42294
+ );
42295
+ return {
42296
+ handleTileClick,
42297
+ handleUnitClick,
42298
+ handleFeatureClick,
42299
+ handleCanvasClick,
42300
+ handleTileHover,
42301
+ handleUnitAnimation,
42302
+ handleCameraChange
42303
+ };
42304
+ }
42305
+ var init_useGameCanvas3DEvents = __esm({
42306
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
42307
+ "use client";
42308
+ init_useEventBus();
42309
+ }
42310
+ });
42311
+
42312
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
42313
+ var init_Canvas3DLoadingState = __esm({
42314
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
42315
+ }
42316
+ });
42317
+ function Canvas3DLoadingState({
42318
+ progress = 0,
42319
+ loaded = 0,
42320
+ total = 0,
42321
+ message = "Loading 3D Scene...",
42322
+ details,
42323
+ showSpinner = true,
42324
+ className
42325
+ }) {
42326
+ const clampedProgress = Math.max(0, Math.min(100, progress));
42327
+ const hasProgress = total > 0;
42328
+ return /* @__PURE__ */ jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
42329
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__content", children: [
42330
+ showSpinner && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__spinner", children: [
42331
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring" }),
42332
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring spinner__ring--secondary" })
42333
+ ] }),
42334
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__message", children: message }),
42335
+ details && /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__details", children: details }),
42336
+ hasProgress && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__progress", children: [
42337
+ /* @__PURE__ */ jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsx(
42338
+ "div",
42339
+ {
42340
+ className: "progress__fill",
42341
+ style: { width: `${clampedProgress}%` }
42342
+ }
42343
+ ) }),
42344
+ /* @__PURE__ */ jsxs("div", { className: "progress__text", children: [
42345
+ /* @__PURE__ */ jsxs("span", { className: "progress__percentage", children: [
42346
+ clampedProgress,
42347
+ "%"
42348
+ ] }),
42349
+ /* @__PURE__ */ jsxs("span", { className: "progress__count", children: [
42350
+ "(",
42351
+ loaded,
42352
+ "/",
42353
+ total,
42354
+ ")"
42355
+ ] })
42356
+ ] })
42357
+ ] })
42358
+ ] }),
42359
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsx("div", { className: "bg__grid" }) })
42360
+ ] });
42361
+ }
42362
+ var init_Canvas3DLoadingState2 = __esm({
42363
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
42364
+ init_Canvas3DLoadingState();
42365
+ }
42366
+ });
42367
+
42368
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
42369
+ var init_Canvas3DErrorBoundary = __esm({
42370
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
42371
+ }
42372
+ });
42373
+ var log9, Canvas3DErrorBoundary;
42374
+ var init_Canvas3DErrorBoundary2 = __esm({
42375
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
42376
+ init_Canvas3DErrorBoundary();
42377
+ log9 = createLogger("almadar:ui:game:canvas3d:error-boundary");
42378
+ Canvas3DErrorBoundary = class extends Component {
42379
+ constructor(props) {
42380
+ super(props);
42381
+ __publicField(this, "handleReset", () => {
42382
+ this.setState({
42383
+ hasError: false,
42384
+ error: null,
42385
+ errorInfo: null
42386
+ });
42387
+ this.props.onReset?.();
42388
+ });
42389
+ this.state = {
42390
+ hasError: false,
42391
+ error: null,
42392
+ errorInfo: null
42393
+ };
42394
+ }
42395
+ static getDerivedStateFromError(error) {
42396
+ return {
42397
+ hasError: true,
42398
+ error,
42399
+ errorInfo: null
42400
+ };
42401
+ }
42402
+ componentDidCatch(error, errorInfo) {
42403
+ this.setState({ errorInfo });
42404
+ this.props.onError?.(error, errorInfo);
42405
+ log9.error("Error caught", { error });
42406
+ log9.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
42407
+ }
42408
+ render() {
42409
+ if (this.state.hasError) {
42410
+ if (this.props.fallback) {
42411
+ return this.props.fallback;
42412
+ }
42413
+ return /* @__PURE__ */ jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__content", children: [
42414
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
42415
+ /* @__PURE__ */ jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
42416
+ /* @__PURE__ */ jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
42417
+ this.state.error && /* @__PURE__ */ jsxs("details", { className: "canvas-3d-error__details", children: [
42418
+ /* @__PURE__ */ jsx("summary", { children: "Error Details" }),
42419
+ /* @__PURE__ */ jsxs("pre", { className: "error__stack", children: [
42420
+ this.state.error.message,
42421
+ "\n",
42422
+ this.state.error.stack
42423
+ ] }),
42424
+ this.state.errorInfo && /* @__PURE__ */ jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
42425
+ ] }),
42426
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__actions", children: [
42427
+ /* @__PURE__ */ jsx(
42428
+ "button",
42429
+ {
42430
+ className: "error__button error__button--primary",
42431
+ onClick: this.handleReset,
42432
+ children: "Try Again"
42433
+ }
42434
+ ),
42435
+ /* @__PURE__ */ jsx(
42436
+ "button",
42437
+ {
42438
+ className: "error__button error__button--secondary",
42439
+ onClick: () => window.location.reload(),
42440
+ children: "Reload Page"
42441
+ }
42442
+ )
42443
+ ] })
42444
+ ] }) });
42445
+ }
42446
+ return this.props.children;
42447
+ }
42448
+ };
42449
+ }
42450
+ });
42451
+ function detectAssetRoot2(modelUrl) {
42452
+ const idx = modelUrl.indexOf("/3d/");
42453
+ if (idx !== -1) {
42454
+ return modelUrl.substring(0, idx + 4);
42455
+ }
42456
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
42457
+ }
42458
+ function useGLTFModel(url, resourceBasePath) {
42459
+ const [state, setState] = useState({
42460
+ model: null,
42461
+ isLoading: false,
42462
+ error: null
42463
+ });
42464
+ useEffect(() => {
42465
+ if (!url) {
42466
+ setState({ model: null, isLoading: false, error: null });
42467
+ return;
42468
+ }
42469
+ log10.debug("Loading", { url });
42470
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
42471
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
42472
+ const loader = new GLTFLoader$1();
42473
+ loader.setResourcePath(assetRoot);
42474
+ loader.load(
42475
+ url,
42476
+ (gltf) => {
42477
+ log10.debug("Loaded", { url });
42478
+ setState({
42479
+ model: gltf.scene,
42480
+ isLoading: false,
42481
+ error: null
42482
+ });
42483
+ },
42484
+ void 0,
42485
+ (err) => {
42486
+ log10.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
42487
+ setState({
42488
+ model: null,
42489
+ isLoading: false,
42490
+ error: err instanceof Error ? err : new Error(String(err))
42491
+ });
42492
+ }
42493
+ );
42494
+ }, [url, resourceBasePath]);
42495
+ return state;
42496
+ }
42497
+ function ModelLoader({
42498
+ url,
42499
+ position = [0, 0, 0],
42500
+ scale = 1,
42501
+ rotation = [0, 0, 0],
42502
+ isSelected = false,
42503
+ isHovered = false,
42504
+ onClick,
42505
+ onHover,
42506
+ fallbackGeometry = "box",
42507
+ castShadow = true,
42508
+ receiveShadow = true,
42509
+ resourceBasePath
42510
+ }) {
42511
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
42512
+ const model = useMemo(() => {
42513
+ if (!loadedModel) return null;
42514
+ const cloned = loadedModel.clone();
42515
+ cloned.traverse((child) => {
42516
+ if (child instanceof THREE.Mesh) {
42517
+ child.castShadow = castShadow;
42518
+ child.receiveShadow = receiveShadow;
42519
+ }
42520
+ });
42521
+ return cloned;
42522
+ }, [loadedModel, castShadow, receiveShadow]);
42523
+ const scaleArray = useMemo(() => {
42524
+ if (typeof scale === "number") {
42525
+ return [scale, scale, scale];
42526
+ }
42527
+ return scale;
42528
+ }, [scale]);
42529
+ const rotationRad = useMemo(() => {
42530
+ return [
42531
+ rotation[0] * Math.PI / 180,
42532
+ rotation[1] * Math.PI / 180,
42533
+ rotation[2] * Math.PI / 180
42534
+ ];
42535
+ }, [rotation]);
42536
+ if (isLoading) {
42537
+ return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
42538
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
42539
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
42540
+ ] }) });
42541
+ }
42542
+ if (error || !model) {
42543
+ if (fallbackGeometry === "none") {
42544
+ return /* @__PURE__ */ jsx("group", { position });
42545
+ }
42546
+ const fallbackProps = {
42547
+ onClick,
42548
+ onPointerOver: () => onHover?.(true),
42549
+ onPointerOut: () => onHover?.(false)
42550
+ };
42551
+ return /* @__PURE__ */ jsxs("group", { position, children: [
42552
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42553
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
42554
+ /* @__PURE__ */ jsx(
42555
+ "meshBasicMaterial",
42556
+ {
42557
+ color: isSelected ? 16755200 : 16777215,
42558
+ transparent: true,
42559
+ opacity: 0.5
42560
+ }
42561
+ )
42562
+ ] }),
42563
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42564
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
42565
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42566
+ ] }),
42567
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42568
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.4, 16, 16] }),
42569
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42570
+ ] }),
42571
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42572
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
42573
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42574
+ ] })
42575
+ ] });
42576
+ }
42577
+ return /* @__PURE__ */ jsxs(
42578
+ "group",
42579
+ {
42580
+ position,
42581
+ rotation: rotationRad,
42582
+ onClick,
42583
+ onPointerOver: () => onHover?.(true),
42584
+ onPointerOut: () => onHover?.(false),
42585
+ children: [
42586
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42587
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
42588
+ /* @__PURE__ */ jsx(
42589
+ "meshBasicMaterial",
42590
+ {
42591
+ color: isSelected ? 16755200 : 16777215,
42592
+ transparent: true,
42593
+ opacity: 0.5
42594
+ }
42595
+ )
42596
+ ] }),
42597
+ /* @__PURE__ */ jsx("primitive", { object: model, scale: scaleArray })
42598
+ ]
42599
+ }
42600
+ );
42601
+ }
42602
+ var log10;
42603
+ var init_ModelLoader = __esm({
42604
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
42605
+ "use client";
42606
+ log10 = createLogger("almadar:ui:game:model-loader");
42607
+ }
42608
+ });
42609
+
42610
+ // components/game/molecules/GameCanvas3D.css
42611
+ var init_GameCanvas3D = __esm({
42612
+ "components/game/molecules/GameCanvas3D.css"() {
42613
+ }
42614
+ });
42615
+ function CameraController({
42616
+ onCameraChange
42617
+ }) {
42618
+ const { camera } = useThree();
42619
+ useEffect(() => {
42620
+ if (onCameraChange) {
42621
+ onCameraChange({
42622
+ x: camera.position.x,
42623
+ y: camera.position.y,
42624
+ z: camera.position.z
42625
+ });
42626
+ }
42627
+ }, [camera.position, onCameraChange]);
42628
+ return null;
42629
+ }
42630
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
42631
+ var init_GameCanvas3D2 = __esm({
42632
+ "components/game/molecules/GameCanvas3D.tsx"() {
42633
+ "use client";
42634
+ init_AssetLoader();
42635
+ init_useAssetLoader();
42636
+ init_useGameCanvas3DEvents();
42637
+ init_Canvas3DLoadingState2();
42638
+ init_Canvas3DErrorBoundary2();
42639
+ init_ModelLoader();
42640
+ init_cn();
42641
+ init_GameCanvas3D();
42642
+ DEFAULT_GRID_CONFIG = {
42643
+ cellSize: 1,
42644
+ offsetX: 0,
42645
+ offsetZ: 0
42646
+ };
42647
+ GameCanvas3D = forwardRef(
42648
+ ({
42649
+ tiles = [],
42650
+ units = [],
42651
+ features = [],
42652
+ events: events2 = [],
42653
+ orientation = "standard",
42654
+ cameraMode = "isometric",
42655
+ showGrid = true,
42656
+ showCoordinates = false,
42657
+ showTileInfo = false,
42658
+ overlay = "default",
42659
+ shadows = true,
42660
+ backgroundColor = "#1a1a2e",
42661
+ onTileClick,
42662
+ onUnitClick,
42663
+ onFeatureClick,
42664
+ onCanvasClick,
42665
+ onTileHover,
42666
+ onUnitAnimation,
42667
+ assetLoader: customAssetLoader,
42668
+ tileRenderer: CustomTileRenderer,
42669
+ unitRenderer: CustomUnitRenderer,
42670
+ featureRenderer: CustomFeatureRenderer,
42671
+ className,
42672
+ isLoading: externalLoading,
42673
+ error: externalError,
42674
+ entity,
42675
+ preloadAssets = [],
42676
+ tileClickEvent,
42677
+ unitClickEvent,
42678
+ featureClickEvent,
42679
+ canvasClickEvent,
42680
+ tileHoverEvent,
42681
+ tileLeaveEvent,
42682
+ unitAnimationEvent,
42683
+ cameraChangeEvent,
42684
+ loadingMessage = "Loading 3D Scene...",
42685
+ useInstancing = true,
42686
+ validMoves = [],
42687
+ attackTargets = [],
42688
+ selectedTileIds = [],
42689
+ selectedUnitId = null,
42690
+ children
42691
+ }, ref) => {
42692
+ const containerRef = useRef(null);
42693
+ const controlsRef = useRef(null);
42694
+ const [hoveredTile, setHoveredTile] = useState(null);
42695
+ const [internalError, setInternalError] = useState(null);
42696
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
42697
+ preloadUrls: preloadAssets,
42698
+ loader: customAssetLoader
42699
+ });
42700
+ const eventHandlers = useGameCanvas3DEvents({
42701
+ tileClickEvent,
42702
+ unitClickEvent,
42703
+ featureClickEvent,
42704
+ canvasClickEvent,
42705
+ tileHoverEvent,
42706
+ tileLeaveEvent,
42707
+ unitAnimationEvent,
42708
+ cameraChangeEvent,
42709
+ onTileClick,
42710
+ onUnitClick,
42711
+ onFeatureClick,
42712
+ onCanvasClick,
42713
+ onTileHover,
42714
+ onUnitAnimation
42715
+ });
42716
+ const gridBounds = useMemo(() => {
42717
+ if (tiles.length === 0) {
42718
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
42719
+ }
42720
+ const xs = tiles.map((t) => t.x);
42721
+ const zs = tiles.map((t) => t.z || t.y || 0);
42722
+ return {
42723
+ minX: Math.min(...xs),
42724
+ maxX: Math.max(...xs),
42725
+ minZ: Math.min(...zs),
42726
+ maxZ: Math.max(...zs)
42727
+ };
42728
+ }, [tiles]);
42729
+ const cameraTarget = useMemo(() => {
42730
+ return [
42731
+ (gridBounds.minX + gridBounds.maxX) / 2,
42732
+ 0,
42733
+ (gridBounds.minZ + gridBounds.maxZ) / 2
42734
+ ];
42735
+ }, [gridBounds]);
42736
+ const gridConfig = useMemo(
42737
+ () => ({
42738
+ ...DEFAULT_GRID_CONFIG,
42739
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
42740
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
42741
+ }),
42742
+ [gridBounds]
42743
+ );
42744
+ const gridToWorld = useCallback(
42745
+ (x, z, y = 0) => {
42746
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
42747
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
42748
+ return [worldX, y * gridConfig.cellSize, worldZ];
42749
+ },
42750
+ [gridBounds, gridConfig]
42751
+ );
42752
+ useImperativeHandle(ref, () => ({
42753
+ getCameraPosition: () => {
42754
+ if (controlsRef.current) {
42755
+ const pos = controlsRef.current.object.position;
42756
+ return new THREE.Vector3(pos.x, pos.y, pos.z);
42757
+ }
42758
+ return null;
42759
+ },
42760
+ setCameraPosition: (x, y, z) => {
42761
+ if (controlsRef.current) {
42762
+ controlsRef.current.object.position.set(x, y, z);
42763
+ controlsRef.current.update();
42764
+ }
42765
+ },
42766
+ lookAt: (x, y, z) => {
42767
+ if (controlsRef.current) {
42768
+ controlsRef.current.target.set(x, y, z);
42769
+ controlsRef.current.update();
42770
+ }
42771
+ },
42772
+ resetCamera: () => {
42773
+ if (controlsRef.current) {
42774
+ controlsRef.current.reset();
42775
+ }
42776
+ },
42777
+ screenshot: () => {
42778
+ const canvas = containerRef.current?.querySelector("canvas");
42779
+ if (canvas) {
42780
+ return canvas.toDataURL("image/png");
42781
+ }
42782
+ return null;
42783
+ },
42784
+ export: () => ({
42785
+ tiles,
42786
+ units,
42787
+ features
42788
+ })
42789
+ }));
42790
+ const handleTileClick = useCallback(
42791
+ (tile, event) => {
42792
+ eventHandlers.handleTileClick(tile, event);
42793
+ },
42794
+ [eventHandlers]
42795
+ );
42796
+ const handleUnitClick = useCallback(
42797
+ (unit, event) => {
42798
+ eventHandlers.handleUnitClick(unit, event);
42799
+ },
42800
+ [eventHandlers]
42801
+ );
42802
+ const handleFeatureClick = useCallback(
42803
+ (feature, event) => {
42804
+ if (event) {
42805
+ eventHandlers.handleFeatureClick(feature, event);
42806
+ }
42807
+ },
42808
+ [eventHandlers]
42809
+ );
42810
+ const handleTileHover = useCallback(
42811
+ (tile, event) => {
42812
+ setHoveredTile(tile);
42813
+ if (event) {
42814
+ eventHandlers.handleTileHover(tile, event);
42815
+ }
42816
+ },
42817
+ [eventHandlers]
42818
+ );
42819
+ const cameraConfig = useMemo(() => {
42820
+ const size = Math.max(
42821
+ gridBounds.maxX - gridBounds.minX,
42822
+ gridBounds.maxZ - gridBounds.minZ
42823
+ );
42824
+ const distance = size * 1.5;
42825
+ switch (cameraMode) {
42826
+ case "isometric":
42827
+ return {
42828
+ position: [distance, distance * 0.8, distance],
42829
+ fov: 45
42830
+ };
42831
+ case "top-down":
42832
+ return {
42833
+ position: [0, distance * 2, 0],
42834
+ fov: 45
42835
+ };
42836
+ case "perspective":
42837
+ default:
42838
+ return {
42839
+ position: [distance, distance, distance],
42840
+ fov: 45
42841
+ };
42842
+ }
42843
+ }, [cameraMode, gridBounds]);
42844
+ const DefaultTileRenderer = useCallback(
42845
+ ({ tile, position }) => {
42846
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
42847
+ const isHovered = hoveredTile?.id === tile.id;
42848
+ const isValidMove = validMoves.some(
42849
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
42850
+ );
42851
+ const isAttackTarget = attackTargets.some(
42852
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
42853
+ );
42854
+ let color = 8421504;
42855
+ if (tile.type === "water") color = 4491468;
42856
+ else if (tile.type === "grass") color = 4500036;
42857
+ else if (tile.type === "sand") color = 14535816;
42858
+ else if (tile.type === "rock") color = 8947848;
42859
+ else if (tile.type === "snow") color = 15658734;
42860
+ let emissive = 0;
42861
+ if (isSelected) emissive = 4473924;
42862
+ else if (isAttackTarget) emissive = 4456448;
42863
+ else if (isValidMove) emissive = 17408;
42864
+ else if (isHovered) emissive = 2236962;
42865
+ if (tile.modelUrl) {
42866
+ return /* @__PURE__ */ jsx(
42867
+ "group",
42868
+ {
42869
+ position,
42870
+ onClick: (e) => handleTileClick(tile, e),
42871
+ onPointerEnter: (e) => handleTileHover(tile, e),
42872
+ onPointerLeave: (e) => handleTileHover(null, e),
42873
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
42874
+ children: /* @__PURE__ */ jsx(
42875
+ ModelLoader,
42876
+ {
42877
+ url: tile.modelUrl,
42878
+ scale: 0.95,
42879
+ fallbackGeometry: "box",
42880
+ castShadow: true,
42881
+ receiveShadow: true
42882
+ }
42883
+ )
42884
+ }
42885
+ );
42886
+ }
42887
+ return /* @__PURE__ */ jsxs(
42888
+ "mesh",
42889
+ {
42890
+ position,
42891
+ onClick: (e) => handleTileClick(tile, e),
42892
+ onPointerEnter: (e) => handleTileHover(tile, e),
42893
+ onPointerLeave: (e) => handleTileHover(null, e),
42894
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
42895
+ children: [
42896
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
42897
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color, emissive })
42898
+ ]
42899
+ }
42900
+ );
42901
+ },
42902
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
42903
+ );
42904
+ const DefaultUnitRenderer = useCallback(
42905
+ ({ unit, position }) => {
42906
+ const isSelected = selectedUnitId === unit.id;
42907
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
42908
+ return /* @__PURE__ */ jsxs(
42909
+ "group",
42910
+ {
42911
+ position,
42912
+ onClick: (e) => handleUnitClick(unit, e),
42913
+ userData: { type: "unit", unitId: unit.id },
42914
+ children: [
42915
+ isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42916
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
42917
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
42918
+ ] }),
42919
+ unit.modelUrl ? (
42920
+ /* GLB unit model (box fallback while loading / on error) */
42921
+ /* @__PURE__ */ jsx(
42922
+ ModelLoader,
42923
+ {
42924
+ url: unit.modelUrl,
42925
+ scale: 0.5,
42926
+ fallbackGeometry: "box",
42927
+ castShadow: true
42928
+ }
42929
+ )
42930
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
42931
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.3, 0], children: [
42932
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
42933
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
42934
+ ] }),
42935
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.6, 0], children: [
42936
+ /* @__PURE__ */ jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
42937
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
42938
+ ] }),
42939
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
42940
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
42941
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
42942
+ ] })
42943
+ ] }),
42944
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxs("group", { position: [0, 1.2, 0], children: [
42945
+ /* @__PURE__ */ jsxs("mesh", { position: [-0.25, 0, 0], children: [
42946
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.05] }),
42947
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: 3355443 })
42948
+ ] }),
42949
+ /* @__PURE__ */ jsxs(
42950
+ "mesh",
42951
+ {
42952
+ position: [
42953
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
42954
+ 0,
42955
+ 0.01
42956
+ ],
42957
+ children: [
42958
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
42959
+ /* @__PURE__ */ jsx(
42960
+ "meshBasicMaterial",
42961
+ {
42962
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
42963
+ }
42964
+ )
42965
+ ]
42966
+ }
42967
+ )
42968
+ ] })
42969
+ ]
42970
+ }
42971
+ );
42972
+ },
42973
+ [selectedUnitId, handleUnitClick]
42974
+ );
42975
+ const DefaultFeatureRenderer = useCallback(
42976
+ ({
42977
+ feature,
42978
+ position
42979
+ }) => {
42980
+ if (feature.assetUrl) {
42981
+ return /* @__PURE__ */ jsx(
42982
+ ModelLoader,
42983
+ {
42984
+ url: feature.assetUrl,
42985
+ position,
42986
+ scale: 0.5,
42987
+ rotation: [0, feature.rotation ?? 0, 0],
42988
+ onClick: () => handleFeatureClick(feature, null),
42989
+ fallbackGeometry: "box"
42990
+ },
42991
+ feature.id
42992
+ );
42993
+ }
42994
+ if (feature.type === "tree") {
42995
+ return /* @__PURE__ */ jsxs(
42996
+ "group",
42997
+ {
42998
+ position,
42999
+ onClick: (e) => handleFeatureClick(feature, e),
43000
+ userData: { type: "feature", featureId: feature.id },
43001
+ children: [
43002
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.4, 0], children: [
43003
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
43004
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
43005
+ ] }),
43006
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
43007
+ /* @__PURE__ */ jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
43008
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 2263842 })
43009
+ ] })
43010
+ ]
43011
+ }
43012
+ );
43013
+ }
43014
+ if (feature.type === "rock") {
43015
+ return /* @__PURE__ */ jsxs(
43016
+ "mesh",
43017
+ {
43018
+ position: [position[0], position[1] + 0.3, position[2]],
43019
+ onClick: (e) => handleFeatureClick(feature, e),
43020
+ userData: { type: "feature", featureId: feature.id },
43021
+ children: [
43022
+ /* @__PURE__ */ jsx("dodecahedronGeometry", { args: [0.3, 0] }),
43023
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 8421504 })
43024
+ ]
43025
+ }
43026
+ );
43027
+ }
43028
+ return null;
43029
+ },
43030
+ [handleFeatureClick]
43031
+ );
43032
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
43033
+ return /* @__PURE__ */ jsx(
43034
+ Canvas3DLoadingState,
43035
+ {
43036
+ progress,
43037
+ loaded,
43038
+ total,
43039
+ message: loadingMessage,
43040
+ className
43041
+ }
43042
+ );
43043
+ }
43044
+ const displayError = externalError || internalError;
43045
+ if (displayError) {
43046
+ 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: [
43047
+ "Error: ",
43048
+ displayError
43049
+ ] }) }) });
43050
+ }
43051
+ return /* @__PURE__ */ jsx(
43052
+ Canvas3DErrorBoundary,
43053
+ {
43054
+ onError: (err) => setInternalError(err.message),
43055
+ onReset: () => setInternalError(null),
43056
+ children: /* @__PURE__ */ jsxs(
43057
+ "div",
43058
+ {
43059
+ ref: containerRef,
43060
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
43061
+ "data-orientation": orientation,
43062
+ "data-camera-mode": cameraMode,
43063
+ "data-overlay": overlay,
43064
+ children: [
43065
+ /* @__PURE__ */ jsxs(
43066
+ Canvas,
43067
+ {
43068
+ shadows,
43069
+ camera: {
43070
+ position: cameraConfig.position,
43071
+ fov: cameraConfig.fov,
43072
+ near: 0.1,
43073
+ far: 1e3
43074
+ },
43075
+ style: { background: backgroundColor },
43076
+ onClick: (e) => {
43077
+ if (e.target === e.currentTarget) {
43078
+ eventHandlers.handleCanvasClick(e);
43079
+ }
43080
+ },
43081
+ children: [
43082
+ /* @__PURE__ */ jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
43083
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 0.6 }),
43084
+ /* @__PURE__ */ jsx(
43085
+ "directionalLight",
43086
+ {
43087
+ position: [10, 20, 10],
43088
+ intensity: 0.8,
43089
+ castShadow: shadows,
43090
+ "shadow-mapSize": [2048, 2048]
43091
+ }
43092
+ ),
43093
+ /* @__PURE__ */ jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
43094
+ showGrid && /* @__PURE__ */ jsx(
43095
+ Grid$1,
43096
+ {
43097
+ args: [
43098
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
43099
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
43100
+ ],
43101
+ position: [
43102
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
43103
+ 0,
43104
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
43105
+ ],
43106
+ cellSize: 1,
43107
+ cellThickness: 1,
43108
+ cellColor: "#444444",
43109
+ sectionSize: 5,
43110
+ sectionThickness: 1.5,
43111
+ sectionColor: "#666666",
43112
+ fadeDistance: 50,
43113
+ fadeStrength: 1
43114
+ }
43115
+ ),
43116
+ tiles.map((tile, index) => {
43117
+ const position = gridToWorld(
43118
+ tile.x,
43119
+ tile.z ?? tile.y ?? 0,
43120
+ tile.elevation ?? 0
43121
+ );
43122
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
43123
+ return /* @__PURE__ */ jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
43124
+ }),
43125
+ features.map((feature, index) => {
43126
+ const position = gridToWorld(
43127
+ feature.x,
43128
+ feature.z ?? feature.y ?? 0,
43129
+ (feature.elevation ?? 0) + 0.5
43130
+ );
43131
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
43132
+ return /* @__PURE__ */ jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
43133
+ }),
43134
+ units.map((unit) => {
43135
+ const position = gridToWorld(
43136
+ unit.x ?? 0,
43137
+ unit.z ?? unit.y ?? 0,
43138
+ (unit.elevation ?? 0) + 0.5
43139
+ );
43140
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
43141
+ return /* @__PURE__ */ jsx(Renderer, { unit, position }, unit.id);
43142
+ }),
43143
+ children,
43144
+ /* @__PURE__ */ jsx(
43145
+ OrbitControls,
43146
+ {
43147
+ ref: controlsRef,
43148
+ target: cameraTarget,
43149
+ enableDamping: true,
43150
+ dampingFactor: 0.05,
43151
+ minDistance: 2,
43152
+ maxDistance: 100,
43153
+ maxPolarAngle: Math.PI / 2 - 0.1
43154
+ }
43155
+ )
43156
+ ]
43157
+ }
43158
+ ),
43159
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__coordinates", children: [
43160
+ "X: ",
43161
+ hoveredTile.x,
43162
+ ", Z: ",
43163
+ hoveredTile.z ?? hoveredTile.y ?? 0
43164
+ ] }),
43165
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__tile-info", children: [
43166
+ /* @__PURE__ */ jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
43167
+ hoveredTile.terrain && /* @__PURE__ */ jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
43168
+ ] })
43169
+ ]
43170
+ }
43171
+ )
43172
+ }
43173
+ );
43174
+ }
43175
+ );
43176
+ GameCanvas3D.displayName = "GameCanvas3D";
43177
+ }
43178
+ });
43179
+ function GameBoard3D({
43180
+ entity,
43181
+ tiles = [],
43182
+ features = [],
43183
+ cameraMode = "perspective",
43184
+ backgroundColor = "#2a1a1a",
43185
+ tileClickEvent,
43186
+ unitClickEvent,
43187
+ attackEvent,
43188
+ endTurnEvent,
43189
+ cancelEvent,
43190
+ playAgainEvent,
43191
+ gameEndEvent,
43192
+ className
43193
+ }) {
43194
+ const row = boardEntity(entity);
43195
+ const eventBus = useEventBus();
43196
+ const entityUnits = row ? rows(row.units) : [];
43197
+ const units = entityUnits;
43198
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
43199
+ const phase = row ? str(row.phase) : "observation";
43200
+ const result = row ? str(row.result) : "none";
43201
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
43202
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
43203
+ const turn = row ? num(row.turn) : 0;
43204
+ const currentTeam = row ? str(row.currentTeam) : "player";
43205
+ const isGameOver = result !== "none";
43206
+ const gameEndEmittedRef = useRef(false);
43207
+ useEffect(() => {
43208
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
43209
+ if (!gameEndEmittedRef.current) {
43210
+ gameEndEmittedRef.current = true;
43211
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
43212
+ }
43213
+ } else {
43214
+ gameEndEmittedRef.current = false;
43215
+ }
43216
+ }, [result, gameEndEvent, eventBus]);
43217
+ const checkGameEnd = useCallback(() => {
43218
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
43219
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
43220
+ if (alivePlayer.length === 0 && gameEndEvent) {
43221
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
43222
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
43223
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
43224
+ }
43225
+ }, [entityUnits, gameEndEvent, eventBus]);
43226
+ const handleUnitClickCallback = useCallback((isoUnit) => {
43227
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
43228
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
43229
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
43230
+ if (!attackerRow || !targetRow) return;
43231
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
43232
+ const ap = unitPosition(attackerRow);
43233
+ const tp = unitPosition(targetRow);
43234
+ const dx = Math.abs(ap.x - tp.x);
43235
+ const dy = Math.abs(ap.y - tp.y);
43236
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
43237
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
43238
+ eventBus.emit(`UI:${attackEvent}`, {
43239
+ attackerId: str(attackerRow.id),
43240
+ targetId: str(targetRow.id),
43241
+ damage
43242
+ });
43243
+ setTimeout(checkGameEnd, 100);
43244
+ }
43245
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
43246
+ const handleEndTurn = useCallback(() => {
43247
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
43248
+ }, [endTurnEvent, eventBus]);
43249
+ const handleCancel = useCallback(() => {
43250
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
43251
+ }, [cancelEvent, eventBus]);
43252
+ const handlePlayAgain = useCallback(() => {
43253
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
43254
+ }, [playAgainEvent, eventBus]);
43255
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
43256
+ /* @__PURE__ */ jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
43257
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
43258
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
43259
+ "\u2014 ",
43260
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
43261
+ ] }),
43262
+ /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
43263
+ "Turn ",
43264
+ turn
43265
+ ] })
43266
+ ] }),
43267
+ /* @__PURE__ */ jsx(
43268
+ GameCanvas3D,
43269
+ {
43270
+ tiles,
43271
+ units,
43272
+ features,
43273
+ cameraMode,
43274
+ showGrid: true,
43275
+ showCoordinates: false,
43276
+ showTileInfo: false,
43277
+ shadows: true,
43278
+ backgroundColor,
43279
+ tileClickEvent,
43280
+ unitClickEvent,
43281
+ onUnitClick: handleUnitClickCallback,
43282
+ selectedUnitId,
43283
+ validMoves,
43284
+ attackTargets,
43285
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
43286
+ }
43287
+ ),
43288
+ !isGameOver && /* @__PURE__ */ jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
43289
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsx(
43290
+ Button,
43291
+ {
43292
+ variant: "secondary",
43293
+ className: "shadow-xl",
43294
+ onClick: handleCancel,
43295
+ children: "Cancel"
43296
+ }
43297
+ ),
43298
+ phase !== "enemy_turn" && /* @__PURE__ */ jsx(
43299
+ Button,
43300
+ {
43301
+ variant: "primary",
43302
+ className: "shadow-xl",
43303
+ onClick: handleEndTurn,
43304
+ children: "End Turn"
43305
+ }
43306
+ )
43307
+ ] }),
43308
+ 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: [
43309
+ /* @__PURE__ */ jsx(
43310
+ Typography,
43311
+ {
43312
+ variant: "h2",
43313
+ className: cn(
43314
+ "overlay__result",
43315
+ result === "victory" ? "text-yellow-400" : "text-red-500"
43316
+ ),
43317
+ children: result === "victory" ? "Victory!" : "Defeat"
43318
+ }
43319
+ ),
43320
+ /* @__PURE__ */ jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
43321
+ "Completed in ",
43322
+ turn,
43323
+ " turn",
43324
+ turn !== 1 ? "s" : ""
43325
+ ] }),
43326
+ /* @__PURE__ */ jsx(
43327
+ Button,
43328
+ {
43329
+ variant: "primary",
43330
+ className: "px-8 py-3 font-semibold",
43331
+ onClick: handlePlayAgain,
43332
+ children: "Play Again"
43333
+ }
43334
+ )
43335
+ ] }) })
43336
+ ] });
43337
+ }
43338
+ var init_GameBoard3D = __esm({
43339
+ "components/game/organisms/GameBoard3D.tsx"() {
43340
+ "use client";
43341
+ init_cn();
43342
+ init_Button();
43343
+ init_Typography();
43344
+ init_Stack();
43345
+ init_useEventBus();
43346
+ init_GameCanvas3D2();
43347
+ init_boardEntity();
43348
+ GameBoard3D.displayName = "GameBoard3D";
43349
+ }
43350
+ });
41696
43351
  var GameShell;
41697
43352
  var init_GameShell = __esm({
41698
43353
  "components/game/templates/GameShell.tsx"() {
@@ -43343,6 +44998,236 @@ var init_NegotiatorBoard = __esm({
43343
44998
  NegotiatorBoard.displayName = "NegotiatorBoard";
43344
44999
  }
43345
45000
  });
45001
+ function resolveEntity(entity) {
45002
+ if (!entity) return {};
45003
+ if (Array.isArray(entity)) return entity[0] ?? {};
45004
+ return entity;
45005
+ }
45006
+ function numField(row, key, fallback) {
45007
+ const v = row[key];
45008
+ return typeof v === "number" ? v : fallback;
45009
+ }
45010
+ function strField(row, key, fallback) {
45011
+ const v = row[key];
45012
+ return typeof v === "string" ? v : fallback;
45013
+ }
45014
+ function PlatformerBoard({
45015
+ entity,
45016
+ player: propPlayer,
45017
+ platforms: propPlatforms,
45018
+ result: propResult,
45019
+ lives: propLives,
45020
+ score: propScore,
45021
+ level: propLevel,
45022
+ worldWidth = 800,
45023
+ worldHeight = 400,
45024
+ canvasWidth = 800,
45025
+ canvasHeight = 400,
45026
+ playerSprite,
45027
+ tileSprites,
45028
+ bgColor,
45029
+ leftEvent = "LEFT",
45030
+ rightEvent = "RIGHT",
45031
+ jumpEvent = "JUMP",
45032
+ stopEvent = "STOP",
45033
+ playAgainEvent = "PLAY_AGAIN",
45034
+ gameEndEvent = "GAME_END",
45035
+ className
45036
+ }) {
45037
+ const row = resolveEntity(entity);
45038
+ const eventBus = useEventBus();
45039
+ const { t } = useTranslate();
45040
+ const result = propResult ?? strField(row, "result", "none");
45041
+ propLives ?? numField(row, "lives", 3);
45042
+ const score = propScore ?? numField(row, "score", 0);
45043
+ const level = propLevel ?? numField(row, "level", 1);
45044
+ const entityPlayer = row["player"];
45045
+ const player = propPlayer ?? entityPlayer ?? {
45046
+ x: 80,
45047
+ y: 320,
45048
+ width: 32,
45049
+ height: 48,
45050
+ vx: 0,
45051
+ vy: 0,
45052
+ grounded: false,
45053
+ facingRight: true
45054
+ };
45055
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
45056
+ const platforms = propPlatforms ?? entityPlatforms ?? [
45057
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
45058
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
45059
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
45060
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
45061
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
45062
+ ];
45063
+ const handleRestart = useCallback(() => {
45064
+ if (playAgainEvent) {
45065
+ eventBus.emit(`UI:${playAgainEvent}`, {});
45066
+ }
45067
+ }, [playAgainEvent, eventBus]);
45068
+ const isGameOver = result === "won" || result === "lost";
45069
+ const gameEndEmittedRef = useRef(false);
45070
+ useEffect(() => {
45071
+ if ((result === "won" || result === "lost") && gameEndEvent) {
45072
+ if (!gameEndEmittedRef.current) {
45073
+ gameEndEmittedRef.current = true;
45074
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
45075
+ }
45076
+ } else {
45077
+ gameEndEmittedRef.current = false;
45078
+ }
45079
+ }, [result, gameEndEvent, eventBus]);
45080
+ return /* @__PURE__ */ jsx(
45081
+ VStack,
45082
+ {
45083
+ className: cn("platformer-board relative bg-background", className),
45084
+ gap: "none",
45085
+ children: /* @__PURE__ */ jsxs(Box, { className: "relative", children: [
45086
+ /* @__PURE__ */ jsx(
45087
+ PlatformerCanvas,
45088
+ {
45089
+ player,
45090
+ platforms,
45091
+ worldWidth,
45092
+ worldHeight,
45093
+ canvasWidth,
45094
+ canvasHeight,
45095
+ followCamera: true,
45096
+ bgColor,
45097
+ playerSprite,
45098
+ tileSprites,
45099
+ leftEvent,
45100
+ rightEvent,
45101
+ jumpEvent,
45102
+ stopEvent
45103
+ }
45104
+ ),
45105
+ 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: [
45106
+ /* @__PURE__ */ jsx(
45107
+ Typography,
45108
+ {
45109
+ variant: "h2",
45110
+ className: cn(
45111
+ "text-4xl font-black tracking-widest uppercase",
45112
+ result === "won" ? "text-warning" : "text-error"
45113
+ ),
45114
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
45115
+ }
45116
+ ),
45117
+ result === "won" && /* @__PURE__ */ jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
45118
+ t("platformer.level"),
45119
+ " ",
45120
+ level,
45121
+ " \u2014 ",
45122
+ t("platformer.score"),
45123
+ ": ",
45124
+ score
45125
+ ] }),
45126
+ result === "lost" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
45127
+ /* @__PURE__ */ jsx(
45128
+ Button,
45129
+ {
45130
+ variant: "primary",
45131
+ className: "px-8 py-3 font-semibold",
45132
+ onClick: handleRestart,
45133
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
45134
+ }
45135
+ )
45136
+ ] }) })
45137
+ ] })
45138
+ }
45139
+ );
45140
+ }
45141
+ var init_PlatformerBoard = __esm({
45142
+ "components/game/organisms/PlatformerBoard.tsx"() {
45143
+ "use client";
45144
+ init_cn();
45145
+ init_useEventBus();
45146
+ init_Box();
45147
+ init_Button();
45148
+ init_Typography();
45149
+ init_Stack();
45150
+ init_PlatformerCanvas();
45151
+ PlatformerBoard.displayName = "PlatformerBoard";
45152
+ }
45153
+ });
45154
+ function PlatformerTemplate({
45155
+ entity,
45156
+ platforms = DEFAULT_PLATFORMS,
45157
+ worldWidth = 800,
45158
+ worldHeight = 400,
45159
+ canvasWidth = 800,
45160
+ canvasHeight = 400,
45161
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
45162
+ tileSprites = DEFAULT_TILE_SPRITES,
45163
+ bgColor = "#5c94fc",
45164
+ gameEndEvent = "GAME_END",
45165
+ className
45166
+ }) {
45167
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
45168
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
45169
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
45170
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
45171
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
45172
+ /* @__PURE__ */ jsx(Box, { className: "relative", children: /* @__PURE__ */ jsx(
45173
+ GameHud,
45174
+ {
45175
+ position: "top",
45176
+ stats: [
45177
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
45178
+ { label: "Level", value: level, format: "number", variant: "default" }
45179
+ ]
45180
+ }
45181
+ ) }),
45182
+ /* @__PURE__ */ jsx(
45183
+ PlatformerBoard,
45184
+ {
45185
+ entity,
45186
+ platforms,
45187
+ worldWidth,
45188
+ worldHeight,
45189
+ canvasWidth,
45190
+ canvasHeight,
45191
+ playerSprite,
45192
+ tileSprites,
45193
+ bgColor,
45194
+ leftEvent: "LEFT",
45195
+ rightEvent: "RIGHT",
45196
+ jumpEvent: "JUMP",
45197
+ stopEvent: "STOP",
45198
+ playAgainEvent: "PLAY_AGAIN",
45199
+ gameEndEvent
45200
+ }
45201
+ ),
45202
+ /* @__PURE__ */ jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsx(ScoreBoard, { score, level }) })
45203
+ ] });
45204
+ }
45205
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
45206
+ var init_PlatformerTemplate = __esm({
45207
+ "components/game/templates/PlatformerTemplate.tsx"() {
45208
+ init_cn();
45209
+ init_Box();
45210
+ init_Stack();
45211
+ init_PlatformerBoard();
45212
+ init_GameHud();
45213
+ init_ScoreBoard();
45214
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
45215
+ DEFAULT_TILE_SPRITES = {
45216
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
45217
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
45218
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
45219
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
45220
+ };
45221
+ DEFAULT_PLATFORMS = [
45222
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
45223
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
45224
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
45225
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
45226
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
45227
+ ];
45228
+ PlatformerTemplate.displayName = "PlatformerTemplate";
45229
+ }
45230
+ });
43346
45231
  var PricingOrganism;
43347
45232
  var init_PricingOrganism = __esm({
43348
45233
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -43466,7 +45351,7 @@ var init_PricingPageTemplate = __esm({
43466
45351
  }
43467
45352
  });
43468
45353
  function ResourceCounter({
43469
- assetUrl = DEFAULT_ASSET_URL5,
45354
+ assetUrl = DEFAULT_ASSET_URL12,
43470
45355
  icon,
43471
45356
  label = "Gold",
43472
45357
  value = 250,
@@ -43509,7 +45394,7 @@ function ResourceCounter({
43509
45394
  }
43510
45395
  );
43511
45396
  }
43512
- var colorTokenClasses2, DEFAULT_ASSET_URL5, sizeMap15;
45397
+ var colorTokenClasses2, DEFAULT_ASSET_URL12, sizeMap15;
43513
45398
  var init_ResourceCounter = __esm({
43514
45399
  "components/game/atoms/ResourceCounter.tsx"() {
43515
45400
  init_cn();
@@ -43522,7 +45407,7 @@ var init_ResourceCounter = __esm({
43522
45407
  error: "text-error",
43523
45408
  muted: "text-muted-foreground"
43524
45409
  };
43525
- DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
45410
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
43526
45411
  sizeMap15 = {
43527
45412
  sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
43528
45413
  md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
@@ -43531,6 +45416,356 @@ var init_ResourceCounter = __esm({
43531
45416
  ResourceCounter.displayName = "ResourceCounter";
43532
45417
  }
43533
45418
  });
45419
+ function RoguelikeBoard({
45420
+ entity,
45421
+ tiles: propTiles,
45422
+ enemies: propEnemies,
45423
+ items: propItems,
45424
+ player: propPlayer,
45425
+ playerHp: propPlayerHp,
45426
+ playerMaxHp: propPlayerMaxHp,
45427
+ playerAttack: propPlayerAttack,
45428
+ depth: propDepth,
45429
+ result: propResult,
45430
+ phase: propPhase,
45431
+ stairsX: propStairsX,
45432
+ stairsY: propStairsY,
45433
+ assetManifest: propAssetManifest,
45434
+ scale = 0.45,
45435
+ unitScale = 1,
45436
+ moveEvent,
45437
+ playAgainEvent,
45438
+ gameEndEvent,
45439
+ className
45440
+ }) {
45441
+ const board = boardEntity(entity) ?? {};
45442
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
45443
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
45444
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
45445
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
45446
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
45447
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
45448
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
45449
+ const depth = propDepth ?? num(board.depth, 1);
45450
+ const result = propResult ?? (str(board.result) || "none");
45451
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
45452
+ propStairsX ?? num(board.stairsX, 3);
45453
+ propStairsY ?? num(board.stairsY, 3);
45454
+ const eventBus = useEventBus();
45455
+ const { t } = useTranslate();
45456
+ const [hoveredTile, setHoveredTile] = useState(null);
45457
+ const emitMove = useCallback((dx, dy) => {
45458
+ if (!moveEvent) return;
45459
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
45460
+ }, [moveEvent, eventBus]);
45461
+ useEffect(() => {
45462
+ const handler = (e) => {
45463
+ if (result !== "none" || phase !== "player_turn") return;
45464
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
45465
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
45466
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
45467
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
45468
+ };
45469
+ window.addEventListener("keydown", handler);
45470
+ return () => window.removeEventListener("keydown", handler);
45471
+ }, [result, phase, emitMove]);
45472
+ useEffect(() => {
45473
+ if ((result === "won" || result === "lost") && gameEndEvent) {
45474
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
45475
+ }
45476
+ }, [result, gameEndEvent, eventBus]);
45477
+ const handleTileClick = useCallback((x, y) => {
45478
+ if (result !== "none" || phase !== "player_turn") return;
45479
+ const dx = x - player.x;
45480
+ const dy = y - player.y;
45481
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
45482
+ emitMove(dx, dy);
45483
+ }
45484
+ }, [result, phase, player, emitMove]);
45485
+ const handleReset = useCallback(() => {
45486
+ if (!playAgainEvent) return;
45487
+ eventBus.emit(`UI:${playAgainEvent}`, {});
45488
+ }, [playAgainEvent, eventBus]);
45489
+ const isoUnits = useMemo(() => {
45490
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
45491
+ return [
45492
+ {
45493
+ id: "player",
45494
+ position: player,
45495
+ name: t("roguelike.player") || "Hero",
45496
+ team: "player",
45497
+ health: playerHp,
45498
+ maxHealth: playerMaxHp,
45499
+ unitType: "player",
45500
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
45501
+ },
45502
+ ...liveEnemies.map((e) => ({
45503
+ id: e.id,
45504
+ position: { x: e.x, y: e.y },
45505
+ name: t("roguelike.enemy") || "Enemy",
45506
+ team: "enemy",
45507
+ health: e.hp,
45508
+ maxHealth: 6,
45509
+ unitType: "enemy",
45510
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
45511
+ }))
45512
+ ];
45513
+ }, [player, enemies, playerHp, playerMaxHp, t]);
45514
+ const isoFeatures = useMemo(
45515
+ () => items.map((it) => ({
45516
+ id: it.id,
45517
+ x: it.x,
45518
+ y: it.y,
45519
+ type: it.kind,
45520
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
45521
+ })),
45522
+ [items]
45523
+ );
45524
+ const validMoves = useMemo(() => {
45525
+ if (result !== "none" || phase !== "player_turn") return [];
45526
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
45527
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
45528
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
45529
+ return tile?.passable ?? false;
45530
+ });
45531
+ }, [result, phase, player, tiles]);
45532
+ const isGameOver = result === "won" || result === "lost";
45533
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
45534
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
45535
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
45536
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
45537
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
45538
+ ] }),
45539
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
45540
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
45541
+ /* @__PURE__ */ jsxs(
45542
+ Typography,
45543
+ {
45544
+ variant: "body1",
45545
+ className: cn(
45546
+ "font-bold tabular-nums",
45547
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
45548
+ ),
45549
+ children: [
45550
+ playerHp,
45551
+ " / ",
45552
+ playerMaxHp
45553
+ ]
45554
+ }
45555
+ )
45556
+ ] }),
45557
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
45558
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
45559
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
45560
+ ] }),
45561
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
45562
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
45563
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
45564
+ ] })
45565
+ ] }),
45566
+ /* @__PURE__ */ jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsx(
45567
+ IsometricCanvas_default,
45568
+ {
45569
+ tiles,
45570
+ units: isoUnits,
45571
+ features: isoFeatures,
45572
+ selectedUnitId: null,
45573
+ validMoves,
45574
+ attackTargets: [],
45575
+ hoveredTile,
45576
+ onTileClick: handleTileClick,
45577
+ onUnitClick: () => void 0,
45578
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
45579
+ onTileLeave: () => setHoveredTile(null),
45580
+ scale,
45581
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
45582
+ assetManifest: propAssetManifest,
45583
+ unitScale
45584
+ }
45585
+ ) }),
45586
+ !isGameOver && /* @__PURE__ */ jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
45587
+ { label: "\u2191", dx: 0, dy: -1 },
45588
+ { label: "\u2193", dx: 0, dy: 1 },
45589
+ { label: "\u2190", dx: -1, dy: 0 },
45590
+ { label: "\u2192", dx: 1, dy: 0 }
45591
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsx(
45592
+ Button,
45593
+ {
45594
+ variant: "secondary",
45595
+ className: "w-9 h-9 p-0 text-base font-mono",
45596
+ onClick: () => emitMove(dx, dy),
45597
+ disabled: phase !== "player_turn",
45598
+ children: label
45599
+ },
45600
+ label
45601
+ )) }),
45602
+ 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: [
45603
+ /* @__PURE__ */ jsx(
45604
+ Typography,
45605
+ {
45606
+ variant: "h2",
45607
+ className: cn(
45608
+ "text-4xl font-black tracking-widest uppercase",
45609
+ result === "won" ? "text-warning" : "text-error"
45610
+ ),
45611
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
45612
+ }
45613
+ ),
45614
+ result === "won" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
45615
+ /* @__PURE__ */ jsx(
45616
+ Button,
45617
+ {
45618
+ variant: "primary",
45619
+ className: "px-8 py-3 font-semibold",
45620
+ onClick: handleReset,
45621
+ children: t("roguelike.playAgain") || "Play Again"
45622
+ }
45623
+ )
45624
+ ] }) })
45625
+ ] });
45626
+ }
45627
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
45628
+ var init_RoguelikeBoard = __esm({
45629
+ "components/game/organisms/RoguelikeBoard.tsx"() {
45630
+ "use client";
45631
+ init_cn();
45632
+ init_useEventBus();
45633
+ init_Box();
45634
+ init_Button();
45635
+ init_Typography();
45636
+ init_Stack();
45637
+ init_IsometricCanvas();
45638
+ init_boardEntity();
45639
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
45640
+ DEFAULT_TILES2 = [
45641
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45642
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45643
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45644
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45645
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45646
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45647
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45648
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45649
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45650
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45651
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45652
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45653
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45654
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45655
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45656
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45657
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45658
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45659
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
45660
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45661
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45662
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45663
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45664
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45665
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
45666
+ ];
45667
+ DEFAULT_ENEMIES = [
45668
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
45669
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
45670
+ ];
45671
+ DEFAULT_ITEMS = [
45672
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
45673
+ ];
45674
+ FEATURE_SPRITE = {
45675
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
45676
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
45677
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
45678
+ };
45679
+ RoguelikeBoard.displayName = "RoguelikeBoard";
45680
+ }
45681
+ });
45682
+ function RoguelikeTemplate({
45683
+ entity,
45684
+ scale = 0.45,
45685
+ unitScale = 1,
45686
+ tiles = DEFAULT_ROGUELIKE_TILES,
45687
+ enemies,
45688
+ items,
45689
+ stairsX,
45690
+ stairsY,
45691
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
45692
+ gameEndEvent,
45693
+ className
45694
+ }) {
45695
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
45696
+ if (!resolved && !tiles && !assetManifest) return null;
45697
+ return /* @__PURE__ */ jsx(
45698
+ RoguelikeBoard,
45699
+ {
45700
+ entity: resolved,
45701
+ tiles,
45702
+ enemies,
45703
+ items,
45704
+ stairsX,
45705
+ stairsY,
45706
+ assetManifest,
45707
+ scale,
45708
+ unitScale,
45709
+ moveEvent: "MOVE",
45710
+ playAgainEvent: "PLAY_AGAIN",
45711
+ gameEndEvent: gameEndEvent ?? "GAME_END",
45712
+ className
45713
+ }
45714
+ );
45715
+ }
45716
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
45717
+ var init_RoguelikeTemplate = __esm({
45718
+ "components/game/templates/RoguelikeTemplate.tsx"() {
45719
+ init_RoguelikeBoard();
45720
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
45721
+ DEFAULT_ROGUELIKE_TILES = [
45722
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45723
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45724
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45725
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45726
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45727
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45728
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45729
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45730
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45731
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45732
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45733
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45734
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45735
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45736
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45737
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45738
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45739
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45740
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
45741
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45742
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45743
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45744
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45745
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45746
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
45747
+ ];
45748
+ DEFAULT_ROGUELIKE_MANIFEST = {
45749
+ baseUrl: CDN5,
45750
+ terrains: {
45751
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
45752
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
45753
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
45754
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
45755
+ },
45756
+ units: {
45757
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
45758
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
45759
+ },
45760
+ features: {
45761
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
45762
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
45763
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
45764
+ }
45765
+ };
45766
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
45767
+ }
45768
+ });
43534
45769
 
43535
45770
  // lib/traitRegistry.ts
43536
45771
  function getAllTraits() {
@@ -44765,7 +47000,7 @@ function getAllEvents(traits2) {
44765
47000
  function EventDispatcherTab({ traits: traits2, schema }) {
44766
47001
  const eventBus = useEventBus();
44767
47002
  const { t } = useTranslate();
44768
- const [log9, setLog] = React77.useState([]);
47003
+ const [log11, setLog] = React77.useState([]);
44769
47004
  const prevStatesRef = React77.useRef(/* @__PURE__ */ new Map());
44770
47005
  React77.useEffect(() => {
44771
47006
  for (const trait of traits2) {
@@ -44829,9 +47064,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
44829
47064
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
44830
47065
  /* @__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)) })
44831
47066
  ] }),
44832
- log9.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
47067
+ log11.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
44833
47068
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
44834
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log9.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
47069
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log11.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
44835
47070
  /* @__PURE__ */ jsx("span", { className: "text-purple-400", children: entry.traitName }),
44836
47071
  " ",
44837
47072
  /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -46863,9 +49098,9 @@ function VariablePanel({
46863
49098
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
46864
49099
  variables.map((v) => {
46865
49100
  const name = v.name == null ? "" : String(v.name);
46866
- const value = numField(v.value);
46867
- const max = numField(v.max, 100);
46868
- const min = numField(v.min, 0);
49101
+ const value = numField2(v.value);
49102
+ const max = numField2(v.max, 100);
49103
+ const min = numField2(v.min, 0);
46869
49104
  const unit = v.unit == null ? "" : String(v.unit);
46870
49105
  const pct = Math.round((value - min) / (max - min) * 100);
46871
49106
  const isHigh = pct > 80;
@@ -46895,12 +49130,12 @@ function VariablePanel({
46895
49130
  })
46896
49131
  ] });
46897
49132
  }
46898
- var numField;
49133
+ var numField2;
46899
49134
  var init_VariablePanel = __esm({
46900
49135
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
46901
49136
  init_atoms2();
46902
49137
  init_cn();
46903
- numField = (v, fallback = 0) => {
49138
+ numField2 = (v, fallback = 0) => {
46904
49139
  const n = Number(v);
46905
49140
  return Number.isFinite(n) ? n : fallback;
46906
49141
  };
@@ -47327,7 +49562,7 @@ function formatDuration(seconds) {
47327
49562
  return `${Math.round(seconds)}s`;
47328
49563
  }
47329
49564
  function StatusEffect({
47330
- assetUrl = DEFAULT_ASSET_URL6,
49565
+ assetUrl = DEFAULT_ASSET_URL13,
47331
49566
  icon = "shield",
47332
49567
  label = "Shield",
47333
49568
  duration = 30,
@@ -47385,12 +49620,12 @@ function StatusEffect({
47385
49620
  label && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
47386
49621
  ] });
47387
49622
  }
47388
- var DEFAULT_ASSET_URL6, sizeMap16, variantStyles9;
49623
+ var DEFAULT_ASSET_URL13, sizeMap16, variantStyles9;
47389
49624
  var init_StatusEffect = __esm({
47390
49625
  "components/game/atoms/StatusEffect.tsx"() {
47391
49626
  init_cn();
47392
49627
  init_Icon();
47393
- DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
49628
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
47394
49629
  sizeMap16 = {
47395
49630
  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 },
47396
49631
  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 },
@@ -47887,6 +50122,289 @@ var init_ToastSlot = __esm({
47887
50122
  ToastSlot.displayName = "ToastSlot";
47888
50123
  }
47889
50124
  });
50125
+ function tilesToIso(tiles) {
50126
+ return tiles.map((t) => ({
50127
+ x: t.x,
50128
+ y: t.y,
50129
+ terrain: t.terrain,
50130
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
50131
+ passable: t.passable
50132
+ }));
50133
+ }
50134
+ function towersToUnits(towers) {
50135
+ return towers.map((t) => ({
50136
+ id: t.id,
50137
+ position: { x: t.x, y: t.y },
50138
+ name: "Tower",
50139
+ team: "player",
50140
+ sprite: TOWER_SPRITE,
50141
+ unitType: "guardian",
50142
+ health: 1,
50143
+ maxHealth: 1
50144
+ }));
50145
+ }
50146
+ function creepsToUnits(creeps) {
50147
+ return creeps.map((c) => ({
50148
+ id: c.id,
50149
+ position: { x: c.x, y: c.y },
50150
+ name: "Creep",
50151
+ team: "enemy",
50152
+ sprite: CREEP_SPRITE,
50153
+ unitType: "scrapper",
50154
+ health: c.hp,
50155
+ maxHealth: c.maxHp
50156
+ }));
50157
+ }
50158
+ function pathToFeatures(path) {
50159
+ return path.map((p2, i) => ({
50160
+ id: `path-${i}`,
50161
+ x: p2.x,
50162
+ y: p2.y,
50163
+ type: "path-marker",
50164
+ sprite: `${CDN6}world-map/road_straight.png`
50165
+ }));
50166
+ }
50167
+ function TowerDefenseBoard({
50168
+ entity,
50169
+ tiles: propTiles,
50170
+ path: propPath,
50171
+ towers: propTowers,
50172
+ creeps: propCreeps,
50173
+ gold: propGold,
50174
+ lives: propLives,
50175
+ wave: propWave,
50176
+ maxWaves: propMaxWaves,
50177
+ result: propResult,
50178
+ waveActive: propWaveActive,
50179
+ towerCost = 25,
50180
+ scale = 0.45,
50181
+ placeTowerEvent,
50182
+ startWaveEvent,
50183
+ playAgainEvent,
50184
+ gameEndEvent,
50185
+ className
50186
+ }) {
50187
+ const board = boardEntity(entity) ?? {};
50188
+ const eventBus = useEventBus();
50189
+ const { t } = useTranslate();
50190
+ const tiles = propTiles ?? rows(board.tiles);
50191
+ const path = propPath ?? rows(board.path);
50192
+ const towers = propTowers ?? rows(board.towers);
50193
+ const creeps = propCreeps ?? rows(board.creeps);
50194
+ const gold = propGold ?? num(board.gold, 100);
50195
+ const lives = propLives ?? num(board.lives, 20);
50196
+ const wave = propWave ?? num(board.wave, 1);
50197
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
50198
+ const result = propResult ?? (str(board.result) || "none");
50199
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
50200
+ const [hoveredTile, setHoveredTile] = useState(null);
50201
+ const emittedGameEnd = useRef(false);
50202
+ if (result !== "none" && !emittedGameEnd.current) {
50203
+ emittedGameEnd.current = true;
50204
+ if (gameEndEvent) {
50205
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
50206
+ }
50207
+ }
50208
+ if (result === "none") {
50209
+ emittedGameEnd.current = false;
50210
+ }
50211
+ const towerPositions = useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
50212
+ const pathPositions = useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
50213
+ const validMoves = useMemo(() => {
50214
+ if (result !== "none" || gold < towerCost) return [];
50215
+ return tiles.filter(
50216
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
50217
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
50218
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
50219
+ const isoTiles = useMemo(() => tilesToIso(tiles), [tiles]);
50220
+ const towerUnits = useMemo(() => towersToUnits(towers), [towers]);
50221
+ const creepUnits = useMemo(() => creepsToUnits(creeps), [creeps]);
50222
+ const isoUnits = useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
50223
+ const pathFeatures = useMemo(() => pathToFeatures(path), [path]);
50224
+ const handleTileClick = useCallback((x, y) => {
50225
+ if (result !== "none") return;
50226
+ if (pathPositions.has(`${x},${y}`)) return;
50227
+ if (towerPositions.has(`${x},${y}`)) return;
50228
+ if (gold < towerCost) return;
50229
+ if (placeTowerEvent) {
50230
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
50231
+ }
50232
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
50233
+ const handleStartWave = useCallback(() => {
50234
+ if (startWaveEvent) {
50235
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
50236
+ }
50237
+ }, [startWaveEvent, wave, eventBus]);
50238
+ const handlePlayAgain = useCallback(() => {
50239
+ if (playAgainEvent) {
50240
+ eventBus.emit(`UI:${playAgainEvent}`, {});
50241
+ }
50242
+ }, [playAgainEvent, eventBus]);
50243
+ const canPlaceTower = gold >= towerCost && result === "none";
50244
+ const canStartWave = !waveActive && result === "none";
50245
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
50246
+ /* @__PURE__ */ jsxs(
50247
+ HStack,
50248
+ {
50249
+ className: "px-4 py-2 border-b border-border bg-surface",
50250
+ gap: "lg",
50251
+ align: "center",
50252
+ children: [
50253
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
50254
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
50255
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
50256
+ ] }),
50257
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
50258
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
50259
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
50260
+ ] }),
50261
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
50262
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
50263
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", weight: "bold", children: [
50264
+ wave,
50265
+ " / ",
50266
+ maxWaves
50267
+ ] })
50268
+ ] }),
50269
+ /* @__PURE__ */ jsxs(Box, { className: "ml-auto", children: [
50270
+ canStartWave && /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
50271
+ waveActive && /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
50272
+ ] })
50273
+ ]
50274
+ }
50275
+ ),
50276
+ /* @__PURE__ */ jsxs(Box, { className: "relative flex-1", children: [
50277
+ /* @__PURE__ */ jsx(
50278
+ IsometricCanvas_default,
50279
+ {
50280
+ tiles: isoTiles,
50281
+ units: isoUnits,
50282
+ features: pathFeatures,
50283
+ validMoves,
50284
+ hoveredTile,
50285
+ onTileClick: handleTileClick,
50286
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
50287
+ onTileLeave: () => setHoveredTile(null),
50288
+ scale
50289
+ }
50290
+ ),
50291
+ 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)` }) })
50292
+ ] }),
50293
+ 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: [
50294
+ /* @__PURE__ */ jsx(
50295
+ Typography,
50296
+ {
50297
+ variant: "h2",
50298
+ className: cn(
50299
+ "text-4xl font-black tracking-widest uppercase",
50300
+ result === "won" ? "text-warning" : "text-error"
50301
+ ),
50302
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
50303
+ }
50304
+ ),
50305
+ /* @__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." }),
50306
+ /* @__PURE__ */ jsx(
50307
+ Button,
50308
+ {
50309
+ variant: "primary",
50310
+ className: "px-8 py-3 font-semibold",
50311
+ onClick: handlePlayAgain,
50312
+ children: t("td.playAgain") ?? "Play Again"
50313
+ }
50314
+ )
50315
+ ] }) })
50316
+ ] });
50317
+ }
50318
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
50319
+ var init_TowerDefenseBoard = __esm({
50320
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
50321
+ "use client";
50322
+ init_cn();
50323
+ init_useEventBus();
50324
+ init_Box();
50325
+ init_Button();
50326
+ init_Typography();
50327
+ init_Stack();
50328
+ init_IsometricCanvas();
50329
+ init_boardEntity();
50330
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
50331
+ TERRAIN_SPRITES = {
50332
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
50333
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
50334
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
50335
+ };
50336
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
50337
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
50338
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
50339
+ }
50340
+ });
50341
+ function TowerDefenseTemplate({
50342
+ entity,
50343
+ title = "Tower Defense",
50344
+ tiles,
50345
+ path,
50346
+ towers,
50347
+ gold,
50348
+ lives,
50349
+ maxWaves,
50350
+ towerCost,
50351
+ scale,
50352
+ placeTowerEvent,
50353
+ startWaveEvent,
50354
+ playAgainEvent,
50355
+ gameEndEvent,
50356
+ className
50357
+ }) {
50358
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
50359
+ return /* @__PURE__ */ jsxs(
50360
+ Box,
50361
+ {
50362
+ display: "flex",
50363
+ fullHeight: true,
50364
+ className: cn("tower-defense-template flex-col", className),
50365
+ children: [
50366
+ /* @__PURE__ */ jsx(
50367
+ HStack,
50368
+ {
50369
+ gap: "sm",
50370
+ align: "center",
50371
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
50372
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
50373
+ }
50374
+ ),
50375
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
50376
+ TowerDefenseBoard,
50377
+ {
50378
+ entity: resolved,
50379
+ tiles,
50380
+ path,
50381
+ towers,
50382
+ gold,
50383
+ lives,
50384
+ maxWaves,
50385
+ towerCost,
50386
+ scale,
50387
+ placeTowerEvent,
50388
+ startWaveEvent,
50389
+ playAgainEvent,
50390
+ gameEndEvent,
50391
+ className: "h-full"
50392
+ }
50393
+ ) })
50394
+ ]
50395
+ }
50396
+ );
50397
+ }
50398
+ var init_TowerDefenseTemplate = __esm({
50399
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
50400
+ init_cn();
50401
+ init_Box();
50402
+ init_Stack();
50403
+ init_Typography();
50404
+ init_TowerDefenseBoard();
50405
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
50406
+ }
50407
+ });
47890
50408
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
47891
50409
  const eventBus = useEventBus();
47892
50410
  const {
@@ -48396,48 +50914,48 @@ function WorldMapTemplate({
48396
50914
  }
48397
50915
  );
48398
50916
  }
48399
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
50917
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
48400
50918
  var init_WorldMapTemplate = __esm({
48401
50919
  "components/game/templates/WorldMapTemplate.tsx"() {
48402
50920
  init_WorldMapBoard();
48403
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
50921
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
48404
50922
  DEFAULT_WORLDMAP_TILES = [
48405
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48406
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48407
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
48408
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48409
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48410
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48411
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48412
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48413
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48414
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
48415
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
48416
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48417
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
48418
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48419
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48420
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48421
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48422
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48423
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
48424
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48425
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48426
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
48427
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48428
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48429
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
50923
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50924
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50925
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50926
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50927
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50928
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50929
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50930
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50931
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50932
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50933
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50934
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50935
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
50936
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50937
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50938
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50939
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50940
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50941
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
50942
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50943
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50944
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50945
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50946
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50947
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
48430
50948
  ];
48431
50949
  DEFAULT_WORLDMAP_UNITS = [
48432
- { 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` },
48433
- { 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` }
50950
+ { 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` },
50951
+ { 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` }
48434
50952
  ];
48435
50953
  DEFAULT_WORLDMAP_FEATURES = [
48436
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
48437
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
50954
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
50955
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
48438
50956
  ];
48439
50957
  DEFAULT_WORLDMAP_MANIFEST = {
48440
- baseUrl: CDN3,
50958
+ baseUrl: CDN7,
48441
50959
  terrains: {
48442
50960
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
48443
50961
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -48566,7 +51084,7 @@ function lazyThree(name, loader) {
48566
51084
  ThreeWrapper.displayName = `Lazy(${name})`;
48567
51085
  return ThreeWrapper;
48568
51086
  }
48569
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
51087
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
48570
51088
  var init_component_registry_generated = __esm({
48571
51089
  "components/core/organisms/component-registry.generated.ts"() {
48572
51090
  init_AboutPageTemplate();
@@ -48676,6 +51194,7 @@ var init_component_registry_generated = __esm({
48676
51194
  init_FormSectionHeader();
48677
51195
  init_GameAudioProvider();
48678
51196
  init_GameAudioToggle();
51197
+ init_GameBoard3D();
48679
51198
  init_GameCanvas2D();
48680
51199
  init_GameHud();
48681
51200
  init_GameMenu();
@@ -48733,7 +51252,9 @@ var init_component_registry_generated = __esm({
48733
51252
  init_PageHeader();
48734
51253
  init_Pagination();
48735
51254
  init_PatternTile();
51255
+ init_PlatformerBoard();
48736
51256
  init_PlatformerCanvas();
51257
+ init_PlatformerTemplate();
48737
51258
  init_Popover();
48738
51259
  init_PositionedCanvas();
48739
51260
  init_PowerupSlots();
@@ -48757,6 +51278,8 @@ var init_component_registry_generated = __esm({
48757
51278
  init_ResourceBar();
48758
51279
  init_ResourceCounter();
48759
51280
  init_RichBlockEditor();
51281
+ init_RoguelikeBoard();
51282
+ init_RoguelikeTemplate();
48760
51283
  init_RuntimeDebugger2();
48761
51284
  init_ScaledDiagram();
48762
51285
  init_ScoreBoard();
@@ -48829,6 +51352,8 @@ var init_component_registry_generated = __esm({
48829
51352
  init_Toast();
48830
51353
  init_ToastSlot();
48831
51354
  init_Tooltip();
51355
+ init_TowerDefenseBoard();
51356
+ init_TowerDefenseTemplate();
48832
51357
  init_TraitFrame();
48833
51358
  init_TraitSlot();
48834
51359
  init_TrendIndicator();
@@ -48873,7 +51398,7 @@ var init_component_registry_generated = __esm({
48873
51398
  }
48874
51399
  };
48875
51400
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
48876
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
51401
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
48877
51402
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
48878
51403
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
48879
51404
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -48992,8 +51517,9 @@ var init_component_registry_generated = __esm({
48992
51517
  "FormSectionHeader": FormSectionHeader,
48993
51518
  "GameAudioProvider": GameAudioProvider,
48994
51519
  "GameAudioToggle": GameAudioToggle,
51520
+ "GameBoard3D": GameBoard3D,
48995
51521
  "GameCanvas2D": GameCanvas2D,
48996
- "GameCanvas3D": GameCanvas3D,
51522
+ "GameCanvas3D": GameCanvas3D2,
48997
51523
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
48998
51524
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
48999
51525
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -49055,7 +51581,9 @@ var init_component_registry_generated = __esm({
49055
51581
  "PageHeader": PageHeader,
49056
51582
  "Pagination": Pagination,
49057
51583
  "PatternTile": PatternTile,
51584
+ "PlatformerBoard": PlatformerBoard,
49058
51585
  "PlatformerCanvas": PlatformerCanvas,
51586
+ "PlatformerTemplate": PlatformerTemplate,
49059
51587
  "Popover": Popover,
49060
51588
  "PositionedCanvas": PositionedCanvas,
49061
51589
  "PowerupSlots": PowerupSlots,
@@ -49079,6 +51607,8 @@ var init_component_registry_generated = __esm({
49079
51607
  "ResourceBar": ResourceBar,
49080
51608
  "ResourceCounter": ResourceCounter,
49081
51609
  "RichBlockEditor": RichBlockEditor,
51610
+ "RoguelikeBoard": RoguelikeBoard,
51611
+ "RoguelikeTemplate": RoguelikeTemplate,
49082
51612
  "RuntimeDebugger": RuntimeDebugger,
49083
51613
  "ScaledDiagram": ScaledDiagram,
49084
51614
  "ScoreBoard": ScoreBoard,
@@ -49154,6 +51684,8 @@ var init_component_registry_generated = __esm({
49154
51684
  "Toast": Toast,
49155
51685
  "ToastSlot": ToastSlot,
49156
51686
  "Tooltip": Tooltip,
51687
+ "TowerDefenseBoard": TowerDefenseBoard,
51688
+ "TowerDefenseTemplate": TowerDefenseTemplate,
49157
51689
  "TraitFrame": TraitFrame,
49158
51690
  "TraitSlot": TraitSlot,
49159
51691
  "TrendIndicator": TrendIndicator,
@@ -50716,6 +53248,10 @@ init_UncontrolledBattleBoard();
50716
53248
  init_useBattleState();
50717
53249
  init_WorldMapBoard();
50718
53250
  init_CastleBoard();
53251
+ init_PlatformerBoard();
53252
+ init_TowerDefenseBoard();
53253
+ init_RoguelikeBoard();
53254
+ init_GameBoard3D();
50719
53255
  init_TraitStateViewer();
50720
53256
  init_TraitSlot();
50721
53257
 
@@ -51288,6 +53824,9 @@ init_GameShell();
51288
53824
  init_BattleTemplate();
51289
53825
  init_CastleTemplate();
51290
53826
  init_WorldMapTemplate();
53827
+ init_PlatformerTemplate();
53828
+ init_TowerDefenseTemplate();
53829
+ init_RoguelikeTemplate();
51291
53830
  init_LandingPageTemplate();
51292
53831
  init_PricingPageTemplate();
51293
53832
  init_FeatureDetailPageTemplate();
@@ -51296,4 +53835,4 @@ init_AboutPageTemplate();
51296
53835
  // components/index.ts
51297
53836
  init_cn();
51298
53837
 
51299
- export { ALL_PRESETS, AR_BOOK_FIELDS, AboutPageTemplate, Accordion, ActionButton, ActionButtons, Card2 as ActionCard, ActionPalette, ActionTile, ActivationBlock, Alert, AnimatedCounter, AnimatedGraphic, AnimatedReveal, ArticleSection, Aside, AssetPicker, AuthLayout, Avatar, Badge, BattleBoard, BattleTemplate, BehaviorView, BloomQuizBlock, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, Box, BranchingLogicBuilder, Breadcrumb, BuilderBoard, Button, ButtonGroup, CTABanner, CalendarGrid, CanvasEffect, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, CaseStudyCard, CaseStudyOrganism, CastleBoard, CastleTemplate, Center, Chart, ChartLegend, Checkbox, ChoiceButton, ClassifierBoard, Coachmark, CodeBlock, CodeRunnerPanel, CollapsibleSection, CombatLog, ComboCounter, CommunityLinks, ConditionalWrapper, ConfettiEffect, ConfirmDialog, ConnectionBlock, Container, ContentRenderer, ContentSection, ControlButton, CounterTemplate, CraftingRecipe, DEFAULT_LIKERT_OPTIONS, DEFAULT_MATRIX_COLUMNS, DIAMOND_TOP_Y, DPad, DamageNumber, DashboardGrid, DashboardLayout, DataGrid, DataList, DataTable, DateRangePicker, DateRangeSelector, DayCell, DebuggerBoard, DetailPanel, Dialog, DialogueBox, DialogueBubble, Divider, DocBreadcrumb, DocPagination, DocSearch, DocSidebar, DocTOC, DocumentViewer, StateMachineView as DomStateMachineVisualizer, Drawer, DrawerSlot, EdgeDecoration, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmptyState, EnemyPlate, EntityDisplayEvents, ErrorBoundary, ErrorState, EventHandlerBoard, EventLog, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FeatureCard, FeatureDetailPageTemplate, FeatureGrid, FeatureGridOrganism, FileTree, FilterGroup, FilterPill, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, GameAudioContext, GameAudioProvider, GameAudioToggle, GameCanvas2D, GameHud, GameMenu, GameOverScreen, GameShell, GameTemplate, GenericAppTemplate, GeometricPattern, GradientDivider, GraphCanvas, GraphView, Grid, GridPicker, HStack, Header, HealthBar, HealthPanel, HeroOrganism, HeroSection, IDENTITY_BOOK_FIELDS, Icon, IconPicker, InfiniteScrollSentinel, Input, InputGroup, InstallBox, InventoryGrid, InventoryPanel, IsometricCanvas, ItemSlot, JazariStateMachine, JsonTreeEditor, Label, LandingPageTemplate, LawReferenceTooltip, Lightbox, LikertScale, LineChart2 as LineChart, List3 as List, LoadingState, MapView, MarkdownContent, MarketingFooter, MarketingStatCard, MasterDetail, MasterDetailLayout, MatrixQuestion, MediaGallery, Menu, Meter, MiniMap, Modal, ModalSlot, ModuleCard, Navigation, NegotiatorBoard, NodeSlotEditor, NotifyListener, NumberStepper, ObjectRulePanel, OnboardingSpotlight, OptionConstraintGroup, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, Overlay, PageHeader, Pagination, PatternTile, PhysicsManager, PlatformerCanvas, Popover, PositionedCanvas, PowerupSlots, PricingCard, PricingGrid, PricingOrganism, PricingPageTemplate, ProgressBar, ProgressDots, PropertyInspector, PullQuote, PullToRefresh, QrScanner, QuestTracker, QuizBlock, Radio, RangeSlider, ReflectionBlock, RelationSelect, RepeatableFormSection, ReplyTree, ResourceBar, ResourceCounter, RichBlockEditor, RuleEditor, RuntimeDebugger, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScaledDiagram, ScoreBoard, ScoreDisplay, SearchInput, Section, SectionHeader, SegmentRenderer, Select, SequenceBar, SequencerBoard, ServiceCatalog, ShowcaseCard, ShowcaseOrganism, SidePanel, Sidebar, SignaturePad, SimpleGrid, SimulationCanvas, SimulationControls, SimulationGraph, SimulatorBoard, Skeleton, SlotContentRenderer, SocialProof, SortableList, Spacer, Sparkline, Spinner, Split, SplitPane, SplitSection, Sprite, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateArchitectBoard, StateIndicator, StateJsonView, StateMachineView, StateNode2 as StateNode, StatsGrid, StatsOrganism, StatusBar, StatusDot, StatusEffect, StepFlow, StepFlowOrganism, SvgBranch, SvgConnection, SvgFlow, SvgGrid, SvgLobe, SvgMesh, SvgMorph, SvgNode, SvgPulse, SvgRing, SvgShield, SvgStack, SwipeableRow, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, TableView, Tabs, TagCloud, TagInput, TeamCard, TeamOrganism, TerrainPalette, TextHighlight, Textarea, ThemeSelector, ThemeToggle, TimeSlotCell, Timeline, TimerDisplay, Toast, ToastSlot, Tooltip, TraitFrame, TraitSlot, TraitStateViewer, TransitionArrow, TrendIndicator, TurnIndicator, TurnPanel, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UncontrolledBattleBoard, UnitCommandBar, UploadDropZone, VStack, VariablePanel, VersionDiff, ViolationAlert, VoteStack, WaypointMarker, WizardContainer, WizardNavigation, WizardProgress, WorldMapBoard, WorldMapTemplate, XPBar, applyTemporaryEffect, calculateAttackTargets, calculateDamage, calculateValidMoves, cn, combatAnimations, combatClasses, combatEffects, createInitialGameState, createUnitAnimationState, drawSprite, generateCombatMessage, getCurrentFrame, getTileDimensions, inferDirection, isoToScreen, mapBookData, parseLessonSegments, parseMarkdownWithCodeBlocks, pendulum, projectileMotion, resolveFieldMap, resolveFrame, resolveSheetDirection, screenToIso, springOscillator, tickAnimationState, toCodeLanguage, transitionAnimation, useAnchorRect, useBattleState, useCamera, useGameAudio, useGameAudioContext, useImageCache, usePhysics2D, useSpriteAnimations };
53838
+ export { ALL_PRESETS, AR_BOOK_FIELDS, AboutPageTemplate, Accordion, ActionButton, ActionButtons, Card2 as ActionCard, ActionPalette, ActionTile, ActivationBlock, Alert, AnimatedCounter, AnimatedGraphic, AnimatedReveal, ArticleSection, Aside, AssetPicker, AuthLayout, Avatar, Badge, BattleBoard, BattleTemplate, BehaviorView, BloomQuizBlock, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, Box, BranchingLogicBuilder, Breadcrumb, BuilderBoard, Button, ButtonGroup, CTABanner, CalendarGrid, CanvasEffect, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, CaseStudyCard, CaseStudyOrganism, CastleBoard, CastleTemplate, Center, Chart, ChartLegend, Checkbox, ChoiceButton, ClassifierBoard, Coachmark, CodeBlock, CodeRunnerPanel, CollapsibleSection, CombatLog, ComboCounter, CommunityLinks, ConditionalWrapper, ConfettiEffect, ConfirmDialog, ConnectionBlock, Container, ContentRenderer, ContentSection, ControlButton, CounterTemplate, CraftingRecipe, DEFAULT_LIKERT_OPTIONS, DEFAULT_MATRIX_COLUMNS, DIAMOND_TOP_Y, DPad, DamageNumber, DashboardGrid, DashboardLayout, DataGrid, DataList, DataTable, DateRangePicker, DateRangeSelector, DayCell, DebuggerBoard, DetailPanel, Dialog, DialogueBox, DialogueBubble, Divider, DocBreadcrumb, DocPagination, DocSearch, DocSidebar, DocTOC, DocumentViewer, StateMachineView as DomStateMachineVisualizer, Drawer, DrawerSlot, EdgeDecoration, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmptyState, EnemyPlate, EntityDisplayEvents, ErrorBoundary, ErrorState, EventHandlerBoard, EventLog, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FeatureCard, FeatureDetailPageTemplate, FeatureGrid, FeatureGridOrganism, FileTree, FilterGroup, FilterPill, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, GameAudioContext, GameAudioProvider, GameAudioToggle, GameBoard3D, GameCanvas2D, GameHud, GameMenu, GameOverScreen, GameShell, GameTemplate, GenericAppTemplate, GeometricPattern, GradientDivider, GraphCanvas, GraphView, Grid, GridPicker, HStack, Header, HealthBar, HealthPanel, HeroOrganism, HeroSection, IDENTITY_BOOK_FIELDS, Icon, IconPicker, InfiniteScrollSentinel, Input, InputGroup, InstallBox, InventoryGrid, InventoryPanel, IsometricCanvas, ItemSlot, JazariStateMachine, JsonTreeEditor, Label, LandingPageTemplate, LawReferenceTooltip, Lightbox, LikertScale, LineChart2 as LineChart, List3 as List, LoadingState, MapView, MarkdownContent, MarketingFooter, MarketingStatCard, MasterDetail, MasterDetailLayout, MatrixQuestion, MediaGallery, Menu, Meter, MiniMap, Modal, ModalSlot, ModuleCard, Navigation, NegotiatorBoard, NodeSlotEditor, NotifyListener, NumberStepper, ObjectRulePanel, OnboardingSpotlight, OptionConstraintGroup, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, Overlay, PageHeader, Pagination, PatternTile, PhysicsManager, PlatformerBoard, PlatformerCanvas, PlatformerTemplate, Popover, PositionedCanvas, PowerupSlots, PricingCard, PricingGrid, PricingOrganism, PricingPageTemplate, ProgressBar, ProgressDots, PropertyInspector, PullQuote, PullToRefresh, QrScanner, QuestTracker, QuizBlock, Radio, RangeSlider, ReflectionBlock, RelationSelect, RepeatableFormSection, ReplyTree, ResourceBar, ResourceCounter, RichBlockEditor, RoguelikeBoard, RoguelikeTemplate, RuleEditor, RuntimeDebugger, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScaledDiagram, ScoreBoard, ScoreDisplay, SearchInput, Section, SectionHeader, SegmentRenderer, Select, SequenceBar, SequencerBoard, ServiceCatalog, ShowcaseCard, ShowcaseOrganism, SidePanel, Sidebar, SignaturePad, SimpleGrid, SimulationCanvas, SimulationControls, SimulationGraph, SimulatorBoard, Skeleton, SlotContentRenderer, SocialProof, SortableList, Spacer, Sparkline, Spinner, Split, SplitPane, SplitSection, Sprite, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateArchitectBoard, StateIndicator, StateJsonView, StateMachineView, StateNode2 as StateNode, StatsGrid, StatsOrganism, StatusBar, StatusDot, StatusEffect, StepFlow, StepFlowOrganism, SvgBranch, SvgConnection, SvgFlow, SvgGrid, SvgLobe, SvgMesh, SvgMorph, SvgNode, SvgPulse, SvgRing, SvgShield, SvgStack, SwipeableRow, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, TableView, Tabs, TagCloud, TagInput, TeamCard, TeamOrganism, TerrainPalette, TextHighlight, Textarea, ThemeSelector, ThemeToggle, TimeSlotCell, Timeline, TimerDisplay, Toast, ToastSlot, Tooltip, TowerDefenseBoard, TowerDefenseTemplate, TraitFrame, TraitSlot, TraitStateViewer, TransitionArrow, TrendIndicator, TurnIndicator, TurnPanel, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UncontrolledBattleBoard, UnitCommandBar, UploadDropZone, VStack, VariablePanel, VersionDiff, ViolationAlert, VoteStack, WaypointMarker, WizardContainer, WizardNavigation, WizardProgress, WorldMapBoard, WorldMapTemplate, XPBar, applyTemporaryEffect, calculateAttackTargets, calculateDamage, calculateValidMoves, cn, combatAnimations, combatClasses, combatEffects, createInitialGameState, createUnitAnimationState, drawSprite, generateCombatMessage, getCurrentFrame, getTileDimensions, inferDirection, isoToScreen, mapBookData, parseLessonSegments, parseMarkdownWithCodeBlocks, pendulum, projectileMotion, resolveFieldMap, resolveFrame, resolveSheetDirection, screenToIso, springOscillator, tickAnimationState, toCodeLanguage, transitionAnimation, useAnchorRect, useBattleState, useCamera, useGameAudio, useGameAudioContext, useImageCache, usePhysics2D, useSpriteAnimations };