@almadar/ui 5.61.0 → 5.63.1

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 (61) hide show
  1. package/dist/avl/index.cjs +3599 -1810
  2. package/dist/avl/index.js +2431 -642
  3. package/dist/components/core/atoms/Box.d.ts +2 -0
  4. package/dist/components/core/molecules/CalendarGrid.d.ts +9 -1
  5. package/dist/components/core/molecules/GraphCanvas.d.ts +4 -0
  6. package/dist/components/core/molecules/PositionedCanvas.d.ts +14 -1
  7. package/dist/components/core/molecules/ReplyTree.d.ts +13 -1
  8. package/dist/components/core/templates/index.d.ts +4 -0
  9. package/dist/components/game/molecules/CardHand.d.ts +35 -0
  10. package/dist/components/game/molecules/DialogueBox.d.ts +8 -2
  11. package/dist/components/game/molecules/index.d.ts +1 -0
  12. package/dist/components/game/molecules/three/index.cjs +283 -115
  13. package/dist/components/game/molecules/three/index.js +283 -115
  14. package/dist/components/game/organisms/CardBattlerBoard.d.ts +46 -0
  15. package/dist/components/game/organisms/CityBuilderBoard.d.ts +63 -0
  16. package/dist/components/game/organisms/TopDownShooterBoard.d.ts +64 -0
  17. package/dist/components/game/organisms/TowerDefenseBoard.d.ts +10 -1
  18. package/dist/components/game/organisms/TraitSlot.d.ts +1 -3
  19. package/dist/components/game/organisms/VisualNovelBoard.d.ts +53 -0
  20. package/dist/components/game/organisms/hooks/useCamera.d.ts +16 -0
  21. package/dist/components/game/organisms/index.d.ts +4 -0
  22. package/dist/components/game/templates/CardBattlerTemplate.d.ts +35 -0
  23. package/dist/components/game/templates/CityBuilderTemplate.d.ts +40 -0
  24. package/dist/components/game/templates/GameCanvas3DBattleTemplate.d.ts +5 -2
  25. package/dist/components/game/templates/GameCanvas3DCastleTemplate.d.ts +5 -2
  26. package/dist/components/game/templates/GameCanvas3DWorldMapTemplate.d.ts +5 -2
  27. package/dist/components/game/templates/TopDownShooterTemplate.d.ts +42 -0
  28. package/dist/components/game/templates/VisualNovelTemplate.d.ts +28 -0
  29. package/dist/components/game/templates/game3dAssetManifest.d.ts +50 -0
  30. package/dist/components/game/templates/index.d.ts +4 -0
  31. package/dist/components/index.cjs +3465 -1659
  32. package/dist/components/index.js +2444 -647
  33. package/dist/components/marketing/organisms/CaseStudyOrganism.d.ts +10 -1
  34. package/dist/components/marketing/organisms/FeatureGridOrganism.d.ts +10 -1
  35. package/dist/components/marketing/organisms/HeroOrganism.d.ts +24 -1
  36. package/dist/components/marketing/organisms/PricingOrganism.d.ts +14 -1
  37. package/dist/components/marketing/organisms/ShowcaseOrganism.d.ts +14 -1
  38. package/dist/components/marketing/organisms/StatsOrganism.d.ts +6 -1
  39. package/dist/components/marketing/organisms/StepFlowOrganism.d.ts +9 -1
  40. package/dist/components/marketing/organisms/TeamOrganism.d.ts +10 -1
  41. package/dist/docs/index.cjs +169 -1
  42. package/dist/docs/index.d.cts +2 -0
  43. package/dist/docs/index.js +169 -1
  44. package/dist/hooks/index.cjs +253 -1
  45. package/dist/hooks/index.d.ts +2 -0
  46. package/dist/hooks/index.js +252 -2
  47. package/dist/hooks/useCanvasGestures.d.ts +44 -0
  48. package/dist/hooks/useTapReveal.d.ts +32 -0
  49. package/dist/locales/index.cjs +315 -3
  50. package/dist/locales/index.js +315 -3
  51. package/dist/marketing/index.cjs +59 -0
  52. package/dist/marketing/index.d.cts +2 -0
  53. package/dist/marketing/index.js +59 -0
  54. package/dist/providers/index.cjs +3408 -1619
  55. package/dist/providers/index.js +2407 -618
  56. package/dist/runtime/index.cjs +3464 -1675
  57. package/dist/runtime/index.js +2411 -622
  58. package/locales/ar.json +105 -1
  59. package/locales/en.json +105 -1
  60. package/locales/sl.json +105 -1
  61. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
- import * as React82 from 'react';
2
- import React82__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, useLayoutEffect, lazy, useId, useSyncExternalStore, forwardRef, useImperativeHandle, Component } from 'react';
1
+ import * as React85 from 'react';
2
+ import React85__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, useLayoutEffect, lazy, useId, useSyncExternalStore, forwardRef, useImperativeHandle, Component } from 'react';
3
3
  import { EventBusContext, useTraitScope, TraitScopeProvider } from '@almadar/ui/providers';
4
4
  import { createLogger, isLogLevelEnabled } from '@almadar/logger';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -246,12 +246,60 @@ var init_cn = __esm({
246
246
  "lib/cn.ts"() {
247
247
  }
248
248
  });
249
+ function useTapReveal(options = {}) {
250
+ const { onReveal, onDismiss, refs, enabled = true } = options;
251
+ const [revealed, setRevealed] = useState(false);
252
+ const onRevealRef = useRef(onReveal);
253
+ const onDismissRef = useRef(onDismiss);
254
+ onRevealRef.current = onReveal;
255
+ onDismissRef.current = onDismiss;
256
+ const reveal = useCallback(() => {
257
+ setRevealed((wasRevealed) => {
258
+ if (!wasRevealed) onRevealRef.current?.();
259
+ return true;
260
+ });
261
+ }, []);
262
+ const dismiss = useCallback(() => {
263
+ setRevealed((wasRevealed) => {
264
+ if (wasRevealed) onDismissRef.current?.();
265
+ return false;
266
+ });
267
+ }, []);
268
+ const onPointerDown = useCallback(
269
+ (e) => {
270
+ if (!enabled || e.pointerType === "mouse") return;
271
+ if (revealed) dismiss();
272
+ else reveal();
273
+ },
274
+ [enabled, revealed, reveal, dismiss]
275
+ );
276
+ useEffect(() => {
277
+ if (!revealed || typeof document === "undefined") return;
278
+ const onDocDown = (ev) => {
279
+ const target = ev.target;
280
+ if (!(target instanceof Node)) return;
281
+ const inside = (refs ?? []).some((r) => r.current?.contains(target));
282
+ if (!inside) dismiss();
283
+ };
284
+ const id = setTimeout(() => document.addEventListener("pointerdown", onDocDown), 0);
285
+ return () => {
286
+ clearTimeout(id);
287
+ document.removeEventListener("pointerdown", onDocDown);
288
+ };
289
+ }, [revealed, refs, dismiss]);
290
+ return { revealed, triggerProps: { onPointerDown }, reveal, dismiss };
291
+ }
292
+ var init_useTapReveal = __esm({
293
+ "hooks/useTapReveal.ts"() {
294
+ }
295
+ });
249
296
  var paddingStyles, paddingXStyles, paddingYStyles, marginStyles, marginXStyles, marginYStyles, bgStyles, roundedStyles, shadowStyles, displayStyles, overflowStyles, positionStyles, Box;
250
297
  var init_Box = __esm({
251
298
  "components/core/atoms/Box.tsx"() {
252
299
  "use client";
253
300
  init_cn();
254
301
  init_useEventBus();
302
+ init_useTapReveal();
255
303
  paddingStyles = {
256
304
  none: "p-0",
257
305
  xs: "p-1",
@@ -354,7 +402,7 @@ var init_Box = __esm({
354
402
  fixed: "fixed",
355
403
  sticky: "sticky"
356
404
  };
357
- Box = React82__default.forwardRef(
405
+ Box = React85__default.forwardRef(
358
406
  ({
359
407
  padding,
360
408
  paddingX,
@@ -377,10 +425,12 @@ var init_Box = __esm({
377
425
  action,
378
426
  actionPayload,
379
427
  hoverEvent,
428
+ tapReveal = true,
380
429
  maxWidth,
381
430
  onClick,
382
431
  onMouseEnter,
383
432
  onMouseLeave,
433
+ onPointerDown,
384
434
  ...rest
385
435
  }, ref) => {
386
436
  const eventBus = useEventBus();
@@ -403,8 +453,21 @@ var init_Box = __esm({
403
453
  }
404
454
  onMouseLeave?.(e);
405
455
  }, [hoverEvent, eventBus, onMouseLeave]);
456
+ const { triggerProps } = useTapReveal({
457
+ enabled: tapReveal && !!hoverEvent,
458
+ onReveal: useCallback(() => {
459
+ if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: true });
460
+ }, [hoverEvent, eventBus]),
461
+ onDismiss: useCallback(() => {
462
+ if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: false });
463
+ }, [hoverEvent, eventBus])
464
+ });
465
+ const handlePointerDown = useCallback((e) => {
466
+ if (hoverEvent && tapReveal) triggerProps.onPointerDown(e);
467
+ onPointerDown?.(e);
468
+ }, [hoverEvent, tapReveal, triggerProps, onPointerDown]);
406
469
  const isClickable = action || onClick;
407
- return React82__default.createElement(
470
+ return React85__default.createElement(
408
471
  Component2,
409
472
  {
410
473
  ref,
@@ -430,6 +493,7 @@ var init_Box = __esm({
430
493
  onClick: isClickable ? handleClick : void 0,
431
494
  onMouseEnter: hoverEvent || onMouseEnter ? handleMouseEnter : void 0,
432
495
  onMouseLeave: hoverEvent || onMouseLeave ? handleMouseLeave : void 0,
496
+ onPointerDown: hoverEvent && tapReveal || onPointerDown ? handlePointerDown : void 0,
433
497
  style: maxWidth ? { maxWidth, ...rest.style } : rest.style,
434
498
  ...rest
435
499
  },
@@ -499,7 +563,7 @@ function loadLib(key, importer) {
499
563
  return p2;
500
564
  }
501
565
  function lazyFamilyIcon(libKey, importer, pick, fallbackName, family) {
502
- const Lazy = React82__default.lazy(async () => {
566
+ const Lazy = React85__default.lazy(async () => {
503
567
  const lib = await loadLib(libKey, importer);
504
568
  const Comp = pick(lib);
505
569
  if (!Comp) {
@@ -509,7 +573,7 @@ function lazyFamilyIcon(libKey, importer, pick, fallbackName, family) {
509
573
  return { default: Comp };
510
574
  });
511
575
  const Wrapped = (props) => /* @__PURE__ */ jsx(
512
- React82__default.Suspense,
576
+ React85__default.Suspense,
513
577
  {
514
578
  fallback: /* @__PURE__ */ jsx(
515
579
  "span",
@@ -1235,7 +1299,7 @@ var init_Icon = __esm({
1235
1299
  const directIcon = typeof icon === "string" ? void 0 : icon;
1236
1300
  const effectiveName = typeof icon === "string" ? icon : name;
1237
1301
  const family = useIconFamily();
1238
- const RenderedComponent = React82__default.useMemo(() => {
1302
+ const RenderedComponent = React85__default.useMemo(() => {
1239
1303
  if (directIcon) return null;
1240
1304
  return effectiveName ? resolveIconForFamily(effectiveName, family) : null;
1241
1305
  }, [directIcon, effectiveName, family]);
@@ -1294,7 +1358,7 @@ function resolveIconProp(value, sizeClass) {
1294
1358
  const IconComp = value;
1295
1359
  return /* @__PURE__ */ jsx(IconComp, { className: sizeClass });
1296
1360
  }
1297
- if (React82__default.isValidElement(value)) {
1361
+ if (React85__default.isValidElement(value)) {
1298
1362
  return value;
1299
1363
  }
1300
1364
  if (typeof value === "object" && value !== null && "render" in value) {
@@ -1370,7 +1434,7 @@ var init_Button = __esm({
1370
1434
  md: "h-icon-default w-icon-default",
1371
1435
  lg: "h-icon-default w-icon-default"
1372
1436
  };
1373
- Button = React82__default.forwardRef(
1437
+ Button = React85__default.forwardRef(
1374
1438
  ({
1375
1439
  className,
1376
1440
  variant = "primary",
@@ -1435,7 +1499,7 @@ var Dialog;
1435
1499
  var init_Dialog = __esm({
1436
1500
  "components/core/atoms/Dialog.tsx"() {
1437
1501
  init_cn();
1438
- Dialog = React82__default.forwardRef(
1502
+ Dialog = React85__default.forwardRef(
1439
1503
  ({
1440
1504
  role = "dialog",
1441
1505
  "aria-modal": ariaModal = true,
@@ -2049,7 +2113,7 @@ var init_Badge = __esm({
2049
2113
  md: "px-2.5 py-1 text-sm",
2050
2114
  lg: "px-3 py-1.5 text-base"
2051
2115
  };
2052
- Badge = React82__default.forwardRef(
2116
+ Badge = React85__default.forwardRef(
2053
2117
  ({ className, variant = "default", size = "sm", amount, label, icon, children, onRemove, removeLabel, ...props }, ref) => {
2054
2118
  const iconSizes3 = {
2055
2119
  sm: "h-icon-default w-icon-default",
@@ -2385,7 +2449,7 @@ var init_SvgFlow = __esm({
2385
2449
  width = 100,
2386
2450
  height = 100
2387
2451
  }) => {
2388
- const markerId = React82__default.useMemo(() => {
2452
+ const markerId = React85__default.useMemo(() => {
2389
2453
  flowIdCounter += 1;
2390
2454
  return `almadar-flow-arrow-${flowIdCounter}`;
2391
2455
  }, []);
@@ -2978,7 +3042,7 @@ var init_SvgRing = __esm({
2978
3042
  width = 100,
2979
3043
  height = 100
2980
3044
  }) => {
2981
- const gradientId = React82__default.useMemo(() => {
3045
+ const gradientId = React85__default.useMemo(() => {
2982
3046
  ringIdCounter += 1;
2983
3047
  return `almadar-ring-glow-${ringIdCounter}`;
2984
3048
  }, []);
@@ -3159,7 +3223,7 @@ var init_Input = __esm({
3159
3223
  init_cn();
3160
3224
  init_Icon();
3161
3225
  init_useEventBus();
3162
- Input = React82__default.forwardRef(
3226
+ Input = React85__default.forwardRef(
3163
3227
  ({
3164
3228
  className,
3165
3229
  inputType,
@@ -3319,7 +3383,7 @@ var Label;
3319
3383
  var init_Label = __esm({
3320
3384
  "components/core/atoms/Label.tsx"() {
3321
3385
  init_cn();
3322
- Label = React82__default.forwardRef(
3386
+ Label = React85__default.forwardRef(
3323
3387
  ({ className, required, children, ...props }, ref) => {
3324
3388
  return /* @__PURE__ */ jsxs(
3325
3389
  "label",
@@ -3346,7 +3410,7 @@ var init_Textarea = __esm({
3346
3410
  "components/core/atoms/Textarea.tsx"() {
3347
3411
  init_cn();
3348
3412
  init_useEventBus();
3349
- Textarea = React82__default.forwardRef(
3413
+ Textarea = React85__default.forwardRef(
3350
3414
  ({ className, error, onChange, ...props }, ref) => {
3351
3415
  const eventBus = useEventBus();
3352
3416
  const handleChange = (e) => {
@@ -3585,7 +3649,7 @@ var init_Select = __esm({
3585
3649
  init_cn();
3586
3650
  init_Icon();
3587
3651
  init_useEventBus();
3588
- Select = React82__default.forwardRef(
3652
+ Select = React85__default.forwardRef(
3589
3653
  (props, _ref) => {
3590
3654
  const { multiple, searchable, clearable } = props;
3591
3655
  if (multiple || searchable || clearable) {
@@ -3602,7 +3666,7 @@ var init_Checkbox = __esm({
3602
3666
  "components/core/atoms/Checkbox.tsx"() {
3603
3667
  init_cn();
3604
3668
  init_useEventBus();
3605
- Checkbox = React82__default.forwardRef(
3669
+ Checkbox = React85__default.forwardRef(
3606
3670
  ({ className, label, id, onChange, ...props }, ref) => {
3607
3671
  const inputId = id || `checkbox-${Math.random().toString(36).substr(2, 9)}`;
3608
3672
  const eventBus = useEventBus();
@@ -3656,7 +3720,7 @@ var init_Spinner = __esm({
3656
3720
  md: "h-6 w-6",
3657
3721
  lg: "h-8 w-8"
3658
3722
  };
3659
- Spinner = React82__default.forwardRef(
3723
+ Spinner = React85__default.forwardRef(
3660
3724
  ({ className, size = "md", overlay, ...props }, ref) => {
3661
3725
  if (overlay) {
3662
3726
  return /* @__PURE__ */ jsx(
@@ -3746,7 +3810,7 @@ var init_Card = __esm({
3746
3810
  chip: "shadow-none rounded-pill border-[length:var(--border-width)] border-border",
3747
3811
  "tile-image-first": "p-0 overflow-hidden"
3748
3812
  };
3749
- Card = React82__default.forwardRef(
3813
+ Card = React85__default.forwardRef(
3750
3814
  ({
3751
3815
  className,
3752
3816
  variant = "bordered",
@@ -3794,9 +3858,9 @@ var init_Card = __esm({
3794
3858
  }
3795
3859
  );
3796
3860
  Card.displayName = "Card";
3797
- CardHeader = React82__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("mb-4", className), ...props }));
3861
+ CardHeader = React85__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("mb-4", className), ...props }));
3798
3862
  CardHeader.displayName = "CardHeader";
3799
- CardTitle = React82__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
3863
+ CardTitle = React85__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
3800
3864
  "h3",
3801
3865
  {
3802
3866
  ref,
@@ -3809,11 +3873,11 @@ var init_Card = __esm({
3809
3873
  }
3810
3874
  ));
3811
3875
  CardTitle.displayName = "CardTitle";
3812
- CardContent = React82__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("", className), ...props }));
3876
+ CardContent = React85__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("", className), ...props }));
3813
3877
  CardContent.displayName = "CardContent";
3814
3878
  CardBody = CardContent;
3815
3879
  CardBody.displayName = "CardBody";
3816
- CardFooter = React82__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
3880
+ CardFooter = React85__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
3817
3881
  "div",
3818
3882
  {
3819
3883
  ref,
@@ -3868,7 +3932,7 @@ var init_FilterPill = __esm({
3868
3932
  md: "w-3.5 h-3.5",
3869
3933
  lg: "w-4 h-4"
3870
3934
  };
3871
- FilterPill = React82__default.forwardRef(
3935
+ FilterPill = React85__default.forwardRef(
3872
3936
  ({
3873
3937
  className,
3874
3938
  variant = "default",
@@ -3997,8 +4061,8 @@ var init_Avatar = __esm({
3997
4061
  actionPayload
3998
4062
  }) => {
3999
4063
  const eventBus = useEventBus();
4000
- const [imgFailed, setImgFailed] = React82__default.useState(false);
4001
- React82__default.useEffect(() => {
4064
+ const [imgFailed, setImgFailed] = React85__default.useState(false);
4065
+ React85__default.useEffect(() => {
4002
4066
  setImgFailed(false);
4003
4067
  }, [src]);
4004
4068
  const initials = providedInitials ?? (name ? generateInitials(name) : void 0);
@@ -4383,7 +4447,7 @@ var init_Radio = __esm({
4383
4447
  md: "w-2.5 h-2.5",
4384
4448
  lg: "w-3 h-3"
4385
4449
  };
4386
- Radio = React82__default.forwardRef(
4450
+ Radio = React85__default.forwardRef(
4387
4451
  ({
4388
4452
  label,
4389
4453
  helperText,
@@ -4400,12 +4464,12 @@ var init_Radio = __esm({
4400
4464
  onChange,
4401
4465
  ...props
4402
4466
  }, ref) => {
4403
- const reactId = React82__default.useId();
4467
+ const reactId = React85__default.useId();
4404
4468
  const baseId = id || `radio-${reactId}`;
4405
4469
  const hasError = !!error;
4406
4470
  const eventBus = useEventBus();
4407
- const [selected, setSelected] = React82__default.useState(value);
4408
- React82__default.useEffect(() => {
4471
+ const [selected, setSelected] = React85__default.useState(value);
4472
+ React85__default.useEffect(() => {
4409
4473
  if (value !== void 0) setSelected(value);
4410
4474
  }, [value]);
4411
4475
  const pick = (next, e) => {
@@ -4587,7 +4651,7 @@ var init_Switch = __esm({
4587
4651
  "components/core/atoms/Switch.tsx"() {
4588
4652
  "use client";
4589
4653
  init_cn();
4590
- Switch = React82.forwardRef(
4654
+ Switch = React85.forwardRef(
4591
4655
  ({
4592
4656
  checked,
4593
4657
  defaultChecked = false,
@@ -4598,10 +4662,10 @@ var init_Switch = __esm({
4598
4662
  name,
4599
4663
  className
4600
4664
  }, ref) => {
4601
- const [isChecked, setIsChecked] = React82.useState(
4665
+ const [isChecked, setIsChecked] = React85.useState(
4602
4666
  checked !== void 0 ? checked : defaultChecked
4603
4667
  );
4604
- React82.useEffect(() => {
4668
+ React85.useEffect(() => {
4605
4669
  if (checked !== void 0) {
4606
4670
  setIsChecked(checked);
4607
4671
  }
@@ -4840,6 +4904,11 @@ var init_TextHighlight = __esm({
4840
4904
  if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: false, annotationId });
4841
4905
  onMouseLeave?.();
4842
4906
  },
4907
+ onPointerDown: (e) => {
4908
+ if (e.pointerType === "mouse") return;
4909
+ if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: true, annotationId });
4910
+ onMouseEnter?.();
4911
+ },
4843
4912
  role: "button",
4844
4913
  tabIndex: 0,
4845
4914
  onKeyDown: (e) => {
@@ -4956,7 +5025,7 @@ var Aside;
4956
5025
  var init_Aside = __esm({
4957
5026
  "components/core/atoms/Aside.tsx"() {
4958
5027
  init_cn();
4959
- Aside = React82__default.forwardRef(
5028
+ Aside = React85__default.forwardRef(
4960
5029
  ({ className, children, ...rest }, ref) => /* @__PURE__ */ jsx("aside", { ref, className: cn(className), ...rest, children })
4961
5030
  );
4962
5031
  Aside.displayName = "Aside";
@@ -5015,6 +5084,7 @@ var init_LawReferenceTooltip = __esm({
5015
5084
  init_Typography();
5016
5085
  init_Divider();
5017
5086
  init_cn();
5087
+ init_useTapReveal();
5018
5088
  positionStyles2 = {
5019
5089
  top: "bottom-full left-1/2 -translate-x-1/2 mb-2",
5020
5090
  bottom: "top-full left-1/2 -translate-x-1/2 mt-2",
@@ -5034,8 +5104,9 @@ var init_LawReferenceTooltip = __esm({
5034
5104
  className
5035
5105
  }) => {
5036
5106
  const { t } = useTranslate();
5037
- const [isVisible, setIsVisible] = React82__default.useState(false);
5038
- const timeoutRef = React82__default.useRef(null);
5107
+ const [isVisible, setIsVisible] = React85__default.useState(false);
5108
+ const timeoutRef = React85__default.useRef(null);
5109
+ const triggerRef = React85__default.useRef(null);
5039
5110
  const handleMouseEnter = () => {
5040
5111
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
5041
5112
  timeoutRef.current = setTimeout(() => setIsVisible(true), 200);
@@ -5044,7 +5115,9 @@ var init_LawReferenceTooltip = __esm({
5044
5115
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
5045
5116
  setIsVisible(false);
5046
5117
  };
5047
- React82__default.useEffect(() => {
5118
+ const { revealed, triggerProps } = useTapReveal({ refs: [triggerRef] });
5119
+ const open = isVisible || revealed;
5120
+ React85__default.useEffect(() => {
5048
5121
  return () => {
5049
5122
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
5050
5123
  };
@@ -5052,6 +5125,7 @@ var init_LawReferenceTooltip = __esm({
5052
5125
  return /* @__PURE__ */ jsxs(
5053
5126
  Box,
5054
5127
  {
5128
+ ref: triggerRef,
5055
5129
  as: "span",
5056
5130
  position: "relative",
5057
5131
  display: "inline-block",
@@ -5060,9 +5134,10 @@ var init_LawReferenceTooltip = __esm({
5060
5134
  onMouseLeave: handleMouseLeave,
5061
5135
  onFocus: handleMouseEnter,
5062
5136
  onBlur: handleMouseLeave,
5137
+ onPointerDown: triggerProps.onPointerDown,
5063
5138
  children: [
5064
5139
  children,
5065
- isVisible && /* @__PURE__ */ jsxs(
5140
+ open && /* @__PURE__ */ jsxs(
5066
5141
  Box,
5067
5142
  {
5068
5143
  padding: "sm",
@@ -5254,7 +5329,7 @@ var init_StatusDot = __esm({
5254
5329
  md: "w-2.5 h-2.5",
5255
5330
  lg: "w-3 h-3"
5256
5331
  };
5257
- StatusDot = React82__default.forwardRef(
5332
+ StatusDot = React85__default.forwardRef(
5258
5333
  ({ className, status = "offline", pulse = false, size = "md", label, ...props }, ref) => {
5259
5334
  return /* @__PURE__ */ jsx(
5260
5335
  "span",
@@ -5308,7 +5383,7 @@ var init_TrendIndicator = __esm({
5308
5383
  down: "trending-down",
5309
5384
  flat: "arrow-right"
5310
5385
  };
5311
- TrendIndicator = React82__default.forwardRef(
5386
+ TrendIndicator = React85__default.forwardRef(
5312
5387
  ({
5313
5388
  className,
5314
5389
  value,
@@ -5375,7 +5450,7 @@ var init_RangeSlider = __esm({
5375
5450
  md: "w-4 h-4",
5376
5451
  lg: "w-5 h-5"
5377
5452
  };
5378
- RangeSlider = React82__default.forwardRef(
5453
+ RangeSlider = React85__default.forwardRef(
5379
5454
  ({
5380
5455
  className,
5381
5456
  min = 0,
@@ -5883,7 +5958,7 @@ var init_ContentSection = __esm({
5883
5958
  md: "py-16",
5884
5959
  lg: "py-24"
5885
5960
  };
5886
- ContentSection = React82__default.forwardRef(
5961
+ ContentSection = React85__default.forwardRef(
5887
5962
  ({ children, background = "default", padding = "lg", id, className }, ref) => {
5888
5963
  return /* @__PURE__ */ jsx(
5889
5964
  Box,
@@ -6417,7 +6492,7 @@ var init_AnimatedReveal = __esm({
6417
6492
  "scale-up": { opacity: 1, transform: "scale(1) translateY(0)" },
6418
6493
  "none": {}
6419
6494
  };
6420
- AnimatedReveal = React82__default.forwardRef(
6495
+ AnimatedReveal = React85__default.forwardRef(
6421
6496
  ({
6422
6497
  trigger = "scroll",
6423
6498
  animation = "fade-up",
@@ -6577,7 +6652,7 @@ var init_AnimatedGraphic = __esm({
6577
6652
  "components/marketing/atoms/AnimatedGraphic.tsx"() {
6578
6653
  "use client";
6579
6654
  init_cn();
6580
- AnimatedGraphic = React82__default.forwardRef(
6655
+ AnimatedGraphic = React85__default.forwardRef(
6581
6656
  ({
6582
6657
  src,
6583
6658
  svgContent,
@@ -6600,7 +6675,7 @@ var init_AnimatedGraphic = __esm({
6600
6675
  const fetchedSvg = useFetchedSvg(svgContent ? void 0 : src);
6601
6676
  const resolvedSvg = svgContent ?? fetchedSvg;
6602
6677
  const prevAnimateRef = useRef(animate);
6603
- const setRef = React82__default.useCallback(
6678
+ const setRef = React85__default.useCallback(
6604
6679
  (node) => {
6605
6680
  containerRef.current = node;
6606
6681
  if (typeof ref === "function") ref(node);
@@ -6826,9 +6901,9 @@ function ScoreDisplay({
6826
6901
  ...rest
6827
6902
  }) {
6828
6903
  const resolvedValue = typeof value === "number" && !Number.isNaN(value) ? value : typeof rest.score === "number" && !Number.isNaN(rest.score) ? rest.score : 0;
6829
- const [displayValue, setDisplayValue] = React82.useState(resolvedValue);
6830
- const [isAnimating, setIsAnimating] = React82.useState(false);
6831
- React82.useEffect(() => {
6904
+ const [displayValue, setDisplayValue] = React85.useState(resolvedValue);
6905
+ const [isAnimating, setIsAnimating] = React85.useState(false);
6906
+ React85.useEffect(() => {
6832
6907
  if (!animated || displayValue === resolvedValue) {
6833
6908
  setDisplayValue(resolvedValue);
6834
6909
  return;
@@ -6911,9 +6986,9 @@ function ControlButton({
6911
6986
  className
6912
6987
  }) {
6913
6988
  const eventBus = useEventBus();
6914
- const [isPressed, setIsPressed] = React82.useState(false);
6989
+ const [isPressed, setIsPressed] = React85.useState(false);
6915
6990
  const actualPressed = pressed ?? isPressed;
6916
- const handlePointerDown = React82.useCallback(
6991
+ const handlePointerDown = React85.useCallback(
6917
6992
  (e) => {
6918
6993
  e.preventDefault();
6919
6994
  if (disabled) return;
@@ -6923,7 +6998,7 @@ function ControlButton({
6923
6998
  },
6924
6999
  [disabled, pressEvent, eventBus, onPress]
6925
7000
  );
6926
- const handlePointerUp = React82.useCallback(
7001
+ const handlePointerUp = React85.useCallback(
6927
7002
  (e) => {
6928
7003
  e.preventDefault();
6929
7004
  if (disabled) return;
@@ -6933,7 +7008,7 @@ function ControlButton({
6933
7008
  },
6934
7009
  [disabled, releaseEvent, eventBus, onRelease]
6935
7010
  );
6936
- const handlePointerLeave = React82.useCallback(
7011
+ const handlePointerLeave = React85.useCallback(
6937
7012
  (e) => {
6938
7013
  if (isPressed) {
6939
7014
  setIsPressed(false);
@@ -7554,8 +7629,8 @@ function XPBar({
7554
7629
  }) {
7555
7630
  const sizes = sizeMap9[size];
7556
7631
  const percentage = max > 0 ? Math.max(0, Math.min(100, current / max * 100)) : 0;
7557
- const [fillWidth, setFillWidth] = React82.useState(animated ? 0 : percentage);
7558
- React82.useEffect(() => {
7632
+ const [fillWidth, setFillWidth] = React85.useState(animated ? 0 : percentage);
7633
+ React85.useEffect(() => {
7559
7634
  if (!animated) {
7560
7635
  setFillWidth(percentage);
7561
7636
  return;
@@ -8043,9 +8118,9 @@ function MiniMap({
8043
8118
  viewportRect = DEFAULT_VIEWPORT,
8044
8119
  className
8045
8120
  }) {
8046
- const canvasRef = React82.useRef(null);
8047
- const frameRef = React82.useRef(0);
8048
- React82.useEffect(() => {
8121
+ const canvasRef = React85.useRef(null);
8122
+ const frameRef = React85.useRef(0);
8123
+ React85.useEffect(() => {
8049
8124
  const canvas = canvasRef.current;
8050
8125
  if (!canvas) return;
8051
8126
  const ctx = canvas.getContext("2d");
@@ -8234,7 +8309,7 @@ var init_ErrorBoundary = __esm({
8234
8309
  }
8235
8310
  );
8236
8311
  };
8237
- ErrorBoundary = class extends React82__default.Component {
8312
+ ErrorBoundary = class extends React85__default.Component {
8238
8313
  constructor(props) {
8239
8314
  super(props);
8240
8315
  __publicField(this, "reset", () => {
@@ -9625,8 +9700,8 @@ function ActionButtons({
9625
9700
  disabled
9626
9701
  }) {
9627
9702
  const eventBus = useEventBus();
9628
- const [activeButtons, setActiveButtons] = React82.useState(/* @__PURE__ */ new Set());
9629
- const handlePress = React82.useCallback(
9703
+ const [activeButtons, setActiveButtons] = React85.useState(/* @__PURE__ */ new Set());
9704
+ const handlePress = React85.useCallback(
9630
9705
  (id) => {
9631
9706
  setActiveButtons((prev) => new Set(prev).add(id));
9632
9707
  if (actionEvent) eventBus.emit(`UI:${actionEvent}`, { id, pressed: true });
@@ -9634,7 +9709,7 @@ function ActionButtons({
9634
9709
  },
9635
9710
  [actionEvent, eventBus, onAction]
9636
9711
  );
9637
- const handleRelease = React82.useCallback(
9712
+ const handleRelease = React85.useCallback(
9638
9713
  (id) => {
9639
9714
  setActiveButtons((prev) => {
9640
9715
  const next = new Set(prev);
@@ -10271,8 +10346,8 @@ var init_AnimatedCounter = __esm({
10271
10346
  const [displayValue, setDisplayValue] = useState("0");
10272
10347
  const [hasAnimated, setHasAnimated] = useState(false);
10273
10348
  const animate = useCallback(() => {
10274
- const { num: num2, prefix, suffix, decimals } = parseValue(value);
10275
- if (num2 === 0) {
10349
+ const { num: num3, prefix, suffix, decimals } = parseValue(value);
10350
+ if (num3 === 0) {
10276
10351
  setDisplayValue(String(value));
10277
10352
  return;
10278
10353
  }
@@ -10281,7 +10356,7 @@ var init_AnimatedCounter = __esm({
10281
10356
  const elapsed = now - startTime;
10282
10357
  const progress = Math.min(elapsed / duration, 1);
10283
10358
  const eased = 1 - Math.pow(1 - progress, 3);
10284
- const current = eased * num2;
10359
+ const current = eased * num3;
10285
10360
  setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
10286
10361
  if (progress < 1) {
10287
10362
  requestAnimationFrame(tick);
@@ -10822,6 +10897,36 @@ function useCamera() {
10822
10897
  cameraRef.current.zoom = Math.max(0.5, Math.min(3, cameraRef.current.zoom * zoomDelta));
10823
10898
  drawFn?.();
10824
10899
  }, []);
10900
+ const handlePointerDown = useCallback((e) => {
10901
+ handleMouseDown(e);
10902
+ }, [handleMouseDown]);
10903
+ const handlePointerUp = useCallback(() => {
10904
+ handleMouseUp();
10905
+ }, [handleMouseUp]);
10906
+ const handlePointerMove = useCallback((e, drawFn) => {
10907
+ return handleMouseMove(e, drawFn);
10908
+ }, [handleMouseMove]);
10909
+ const panBy = useCallback((dx, dy, drawFn) => {
10910
+ cameraRef.current.x -= dx;
10911
+ cameraRef.current.y -= dy;
10912
+ targetCameraRef.current = null;
10913
+ drawFn?.();
10914
+ }, []);
10915
+ const zoomAtPoint = useCallback((factor, centerX, centerY, viewportSize, drawFn) => {
10916
+ const cam = cameraRef.current;
10917
+ const oldZoom = cam.zoom;
10918
+ const newZoom = Math.max(0.5, Math.min(3, oldZoom * factor));
10919
+ if (newZoom === oldZoom) {
10920
+ drawFn?.();
10921
+ return;
10922
+ }
10923
+ const inv = 1 / oldZoom - 1 / newZoom;
10924
+ cam.x += (centerX - viewportSize.width / 2) * inv;
10925
+ cam.y += (centerY - viewportSize.height / 2) * inv;
10926
+ cam.zoom = newZoom;
10927
+ targetCameraRef.current = null;
10928
+ drawFn?.();
10929
+ }, []);
10825
10930
  const screenToWorld = useCallback((clientX, clientY, canvas, viewportSize) => {
10826
10931
  const rect = canvas.getBoundingClientRect();
10827
10932
  const screenX = clientX - rect.left;
@@ -10853,6 +10958,11 @@ function useCamera() {
10853
10958
  handleMouseMove,
10854
10959
  handleMouseLeave,
10855
10960
  handleWheel,
10961
+ handlePointerDown,
10962
+ handlePointerUp,
10963
+ handlePointerMove,
10964
+ panBy,
10965
+ zoomAtPoint,
10856
10966
  screenToWorld,
10857
10967
  lerpToTarget
10858
10968
  };
@@ -10862,6 +10972,113 @@ var init_useCamera = __esm({
10862
10972
  "use client";
10863
10973
  }
10864
10974
  });
10975
+ function localPoint(canvas, clientX, clientY) {
10976
+ if (!canvas) return { x: clientX, y: clientY };
10977
+ const rect = canvas.getBoundingClientRect();
10978
+ return { x: clientX - rect.left, y: clientY - rect.top };
10979
+ }
10980
+ function useCanvasGestures(options) {
10981
+ const {
10982
+ canvasRef,
10983
+ enabled = true,
10984
+ wheelStep = 1.1,
10985
+ onPointerDown,
10986
+ onPointerMove,
10987
+ onPointerUp,
10988
+ onZoom,
10989
+ onPanDelta,
10990
+ onMultiTouchStart
10991
+ } = options;
10992
+ const pointers = useRef(/* @__PURE__ */ new Map());
10993
+ const pinch = useRef(null);
10994
+ const computePinch = useCallback(() => {
10995
+ const pts = [...pointers.current.values()];
10996
+ const dx = pts[1].x - pts[0].x;
10997
+ const dy = pts[1].y - pts[0].y;
10998
+ return {
10999
+ distance: Math.hypot(dx, dy) || 1,
11000
+ centroid: { x: (pts[0].x + pts[1].x) / 2, y: (pts[0].y + pts[1].y) / 2 }
11001
+ };
11002
+ }, []);
11003
+ const handlePointerDown = useCallback(
11004
+ (e) => {
11005
+ if (!enabled) return;
11006
+ e.currentTarget.setPointerCapture(e.pointerId);
11007
+ pointers.current.set(e.pointerId, localPoint(canvasRef.current, e.clientX, e.clientY));
11008
+ if (pointers.current.size === 2) {
11009
+ onMultiTouchStart?.();
11010
+ pinch.current = computePinch();
11011
+ } else if (pointers.current.size === 1) {
11012
+ onPointerDown?.(e);
11013
+ }
11014
+ },
11015
+ [enabled, canvasRef, onMultiTouchStart, computePinch, onPointerDown]
11016
+ );
11017
+ const handlePointerMove = useCallback(
11018
+ (e) => {
11019
+ if (!enabled) return;
11020
+ if (pointers.current.has(e.pointerId)) {
11021
+ pointers.current.set(e.pointerId, localPoint(canvasRef.current, e.clientX, e.clientY));
11022
+ }
11023
+ if (pointers.current.size >= 2 && pinch.current) {
11024
+ const next = computePinch();
11025
+ const prev = pinch.current;
11026
+ if (next.distance !== prev.distance) {
11027
+ onZoom?.(next.distance / prev.distance, next.centroid.x, next.centroid.y);
11028
+ }
11029
+ onPanDelta?.(next.centroid.x - prev.centroid.x, next.centroid.y - prev.centroid.y);
11030
+ pinch.current = next;
11031
+ return;
11032
+ }
11033
+ onPointerMove?.(e);
11034
+ },
11035
+ [enabled, canvasRef, computePinch, onZoom, onPanDelta, onPointerMove]
11036
+ );
11037
+ const endPointer = useCallback(
11038
+ (e, fireUp) => {
11039
+ const wasMulti = pointers.current.size >= 2;
11040
+ pointers.current.delete(e.pointerId);
11041
+ if (pointers.current.size < 2) pinch.current = null;
11042
+ if (wasMulti && pointers.current.size === 1) return;
11043
+ if (pointers.current.size === 0 && fireUp) onPointerUp?.(e);
11044
+ },
11045
+ [onPointerUp]
11046
+ );
11047
+ const handlePointerUp = useCallback(
11048
+ (e) => {
11049
+ if (!enabled) return;
11050
+ endPointer(e, true);
11051
+ },
11052
+ [enabled, endPointer]
11053
+ );
11054
+ const handlePointerCancel = useCallback(
11055
+ (e) => {
11056
+ if (!enabled) return;
11057
+ endPointer(e, false);
11058
+ },
11059
+ [enabled, endPointer]
11060
+ );
11061
+ const handleWheel = useCallback(
11062
+ (e) => {
11063
+ if (!enabled) return;
11064
+ e.preventDefault();
11065
+ const { x, y } = localPoint(canvasRef.current, e.clientX, e.clientY);
11066
+ onZoom?.(e.deltaY < 0 ? wheelStep : 1 / wheelStep, x, y);
11067
+ },
11068
+ [enabled, canvasRef, wheelStep, onZoom]
11069
+ );
11070
+ return {
11071
+ onPointerDown: handlePointerDown,
11072
+ onPointerMove: handlePointerMove,
11073
+ onPointerUp: handlePointerUp,
11074
+ onPointerCancel: handlePointerCancel,
11075
+ onWheel: handleWheel
11076
+ };
11077
+ }
11078
+ var init_useCanvasGestures = __esm({
11079
+ "hooks/useCanvasGestures.ts"() {
11080
+ }
11081
+ });
10865
11082
  function unitAtlasUrl(unit) {
10866
11083
  if (unit.spriteSheet) return unit.spriteSheet;
10867
11084
  const sprite = unit.sprite;
@@ -11099,7 +11316,7 @@ function IsometricCanvas({
11099
11316
  // Tuning
11100
11317
  diamondTopY: diamondTopYProp,
11101
11318
  // Remote asset loading
11102
- assetBaseUrl = "https://almadar-kflow-assets.web.app/shared/",
11319
+ assetBaseUrl,
11103
11320
  assetManifest
11104
11321
  }) {
11105
11322
  const tilesProp = Array.isArray(_tilesPropRaw) ? _tilesPropRaw : [];
@@ -11178,7 +11395,7 @@ function IsometricCanvas({
11178
11395
  const attackTargetSet = useMemo(() => {
11179
11396
  return new Set(attackTargets.map((p2) => `${p2.x},${p2.y}`));
11180
11397
  }, [attackTargets]);
11181
- const resolveManifestUrl = useCallback((relativePath) => {
11398
+ const resolveManifestUrl7 = useCallback((relativePath) => {
11182
11399
  if (!relativePath) return void 0;
11183
11400
  if (assetBaseUrl) return `${assetBaseUrl.replace(/\/$/, "")}${relativePath.startsWith("/") ? "" : "/"}${relativePath}`;
11184
11401
  return relativePath;
@@ -11191,7 +11408,7 @@ function IsometricCanvas({
11191
11408
  const url = getTerrainSprite(tile.terrain ?? "");
11192
11409
  if (url) urls.push(url);
11193
11410
  } else {
11194
- const url = resolveManifestUrl(assetManifest?.terrains?.[tile.terrain ?? ""]);
11411
+ const url = resolveManifestUrl7(assetManifest?.terrains?.[tile.terrain ?? ""]);
11195
11412
  if (url) urls.push(url);
11196
11413
  }
11197
11414
  }
@@ -11201,7 +11418,7 @@ function IsometricCanvas({
11201
11418
  const url = getFeatureSprite(feature.type);
11202
11419
  if (url) urls.push(url);
11203
11420
  } else {
11204
- const url = resolveManifestUrl(assetManifest?.features?.[feature.type]);
11421
+ const url = resolveManifestUrl7(assetManifest?.features?.[feature.type]);
11205
11422
  if (url) urls.push(url);
11206
11423
  }
11207
11424
  }
@@ -11211,13 +11428,13 @@ function IsometricCanvas({
11211
11428
  const url = getUnitSprite(unit);
11212
11429
  if (url) urls.push(url);
11213
11430
  } else if (unit.unitType) {
11214
- const url = resolveManifestUrl(assetManifest?.units?.[unit.unitType]);
11431
+ const url = resolveManifestUrl7(assetManifest?.units?.[unit.unitType]);
11215
11432
  if (url) urls.push(url);
11216
11433
  }
11217
11434
  }
11218
11435
  if (assetManifest?.effects) {
11219
11436
  for (const path of Object.values(assetManifest.effects)) {
11220
- const url = resolveManifestUrl(path);
11437
+ const url = resolveManifestUrl7(path);
11221
11438
  if (url) urls.push(url);
11222
11439
  }
11223
11440
  }
@@ -11225,7 +11442,7 @@ function IsometricCanvas({
11225
11442
  if (atlasSheetUrls.length) urls.push(...atlasSheetUrls);
11226
11443
  if (backgroundImage) urls.push(backgroundImage);
11227
11444
  return [...new Set(urls.filter(Boolean))];
11228
- }, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, atlasSheetUrls, backgroundImage, assetManifest, resolveManifestUrl]);
11445
+ }, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, atlasSheetUrls, backgroundImage, assetManifest, resolveManifestUrl7]);
11229
11446
  const { getImage, pendingCount } = useImageCache(spriteUrls);
11230
11447
  useEffect(() => {
11231
11448
  if (typeof window === "undefined") return;
@@ -11240,23 +11457,24 @@ function IsometricCanvas({
11240
11457
  cameraRef,
11241
11458
  targetCameraRef,
11242
11459
  dragDistance,
11243
- handleMouseDown,
11244
- handleMouseUp,
11245
- handleMouseMove,
11246
11460
  handleMouseLeave,
11247
- handleWheel,
11461
+ handlePointerDown,
11462
+ handlePointerUp,
11463
+ handlePointerMove,
11464
+ panBy,
11465
+ zoomAtPoint,
11248
11466
  screenToWorld,
11249
11467
  lerpToTarget
11250
11468
  } = useCamera();
11251
11469
  const resolveTerrainSpriteUrl = useCallback((tile) => {
11252
- return tile.terrainSprite || getTerrainSprite?.(tile.terrain ?? "") || resolveManifestUrl(assetManifest?.terrains?.[tile.terrain ?? ""]);
11253
- }, [getTerrainSprite, assetManifest, resolveManifestUrl]);
11470
+ return tile.terrainSprite || getTerrainSprite?.(tile.terrain ?? "") || resolveManifestUrl7(assetManifest?.terrains?.[tile.terrain ?? ""]);
11471
+ }, [getTerrainSprite, assetManifest, resolveManifestUrl7]);
11254
11472
  const resolveFeatureSpriteUrl = useCallback((featureType) => {
11255
- return getFeatureSprite?.(featureType) || resolveManifestUrl(assetManifest?.features?.[featureType]);
11256
- }, [getFeatureSprite, assetManifest, resolveManifestUrl]);
11473
+ return getFeatureSprite?.(featureType) || resolveManifestUrl7(assetManifest?.features?.[featureType]);
11474
+ }, [getFeatureSprite, assetManifest, resolveManifestUrl7]);
11257
11475
  const resolveUnitSpriteUrl = useCallback((unit) => {
11258
- return unit.sprite || getUnitSprite?.(unit) || (unit.unitType ? resolveManifestUrl(assetManifest?.units?.[unit.unitType]) : void 0);
11259
- }, [getUnitSprite, assetManifest, resolveManifestUrl]);
11476
+ return unit.sprite || getUnitSprite?.(unit) || (unit.unitType ? resolveManifestUrl7(assetManifest?.units?.[unit.unitType]) : void 0);
11477
+ }, [getUnitSprite, assetManifest, resolveManifestUrl7]);
11260
11478
  const drawMinimap = useCallback(() => {
11261
11479
  if (!showMinimap) return;
11262
11480
  const miniCanvas = minimapRef.current;
@@ -11358,7 +11576,15 @@ function IsometricCanvas({
11358
11576
  const spriteUrl = resolveTerrainSpriteUrl(tile);
11359
11577
  const img = spriteUrl ? getImage(spriteUrl) : null;
11360
11578
  if (img) {
11361
- ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
11579
+ if (img.naturalWidth === 0) {
11580
+ ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
11581
+ } else {
11582
+ const drawW = scaledTileWidth;
11583
+ const drawH = scaledTileWidth * (img.naturalHeight / img.naturalWidth);
11584
+ const drawX = pos.x;
11585
+ const drawY = pos.y + scaledTileHeight - drawH;
11586
+ ctx.drawImage(img, drawX, drawY, drawW, drawH);
11587
+ }
11362
11588
  } else {
11363
11589
  const centerX = pos.x + scaledTileWidth / 2;
11364
11590
  const topY = pos.y + scaledDiamondTopY;
@@ -11671,11 +11897,16 @@ function IsometricCanvas({
11671
11897
  cancelAnimationFrame(rafIdRef.current);
11672
11898
  };
11673
11899
  }, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, atlasPending, lerpToTarget, targetCameraRef]);
11674
- const handleMouseMoveWithCamera = useCallback((e) => {
11675
- if (enableCamera) {
11676
- const wasPanning = handleMouseMove(e, () => draw(animTimeRef.current));
11677
- if (wasPanning) return;
11678
- }
11900
+ const singlePointerActiveRef = useRef(false);
11901
+ const handleCanvasPointerDown = useCallback((e) => {
11902
+ singlePointerActiveRef.current = true;
11903
+ if (enableCamera) handlePointerDown(e);
11904
+ }, [enableCamera, handlePointerDown]);
11905
+ const handleCanvasPointerMove = useCallback((e) => {
11906
+ if (enableCamera) handlePointerMove(e, () => draw(animTimeRef.current));
11907
+ }, [enableCamera, handlePointerMove, draw]);
11908
+ const handleCanvasHover = useCallback((e) => {
11909
+ if (singlePointerActiveRef.current) return;
11679
11910
  if (!onTileHover && !tileHoverEvent || !canvasRef.current) return;
11680
11911
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
11681
11912
  const adjustedX = world.x - scaledTileWidth / 2;
@@ -11686,26 +11917,10 @@ function IsometricCanvas({
11686
11917
  if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
11687
11918
  onTileHover?.(isoPos.x, isoPos.y);
11688
11919
  }
11689
- }, [enableCamera, handleMouseMove, draw, onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11690
- const handleMouseLeaveWithCamera = useCallback(() => {
11691
- handleMouseLeave();
11692
- if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
11693
- onTileLeave?.();
11694
- }, [handleMouseLeave, onTileLeave, tileLeaveEvent, eventBus]);
11695
- const handleWheelWithCamera = useCallback((e) => {
11696
- if (enableCamera) {
11697
- handleWheel(e, () => draw(animTimeRef.current));
11698
- }
11699
- }, [enableCamera, handleWheel, draw]);
11700
- useEffect(() => {
11701
- const canvas = canvasRef.current;
11702
- if (!canvas) return;
11703
- canvas.addEventListener("wheel", handleWheelWithCamera, { passive: false });
11704
- return () => {
11705
- canvas.removeEventListener("wheel", handleWheelWithCamera);
11706
- };
11707
- }, [handleWheelWithCamera]);
11708
- const handleClick = useCallback((e) => {
11920
+ }, [onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
11921
+ const handleCanvasPointerUp = useCallback((e) => {
11922
+ singlePointerActiveRef.current = false;
11923
+ if (enableCamera) handlePointerUp();
11709
11924
  if (dragDistance() > 5) return;
11710
11925
  if (!canvasRef.current) return;
11711
11926
  const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
@@ -11723,7 +11938,32 @@ function IsometricCanvas({
11723
11938
  onTileClick?.(isoPos.x, isoPos.y);
11724
11939
  }
11725
11940
  }
11726
- }, [dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
11941
+ }, [enableCamera, handlePointerUp, dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
11942
+ const handleCanvasPointerLeave = useCallback(() => {
11943
+ handleMouseLeave();
11944
+ if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
11945
+ onTileLeave?.();
11946
+ }, [handleMouseLeave, onTileLeave, tileLeaveEvent, eventBus]);
11947
+ const applyZoom = useCallback((factor, centerX, centerY) => {
11948
+ if (enableCamera) zoomAtPoint(factor, centerX, centerY, viewportSize, () => draw(animTimeRef.current));
11949
+ }, [enableCamera, zoomAtPoint, viewportSize, draw]);
11950
+ const applyPanDelta = useCallback((dx, dy) => {
11951
+ if (enableCamera) panBy(dx, dy, () => draw(animTimeRef.current));
11952
+ }, [enableCamera, panBy, draw]);
11953
+ const cancelSinglePointer = useCallback(() => {
11954
+ singlePointerActiveRef.current = false;
11955
+ if (enableCamera) handlePointerUp();
11956
+ }, [enableCamera, handlePointerUp]);
11957
+ const gestureHandlers = useCanvasGestures({
11958
+ canvasRef,
11959
+ enabled: enableCamera || !!onTileHover || !!tileHoverEvent || !!onTileClick || !!tileClickEvent || !!onUnitClick || !!unitClickEvent,
11960
+ onPointerDown: handleCanvasPointerDown,
11961
+ onPointerMove: handleCanvasPointerMove,
11962
+ onPointerUp: handleCanvasPointerUp,
11963
+ onZoom: applyZoom,
11964
+ onPanDelta: applyPanDelta,
11965
+ onMultiTouchStart: cancelSinglePointer
11966
+ });
11727
11967
  if (error) {
11728
11968
  return /* @__PURE__ */ jsx(ErrorState, { title: t("canvas.errorTitle"), message: error.message, className });
11729
11969
  }
@@ -11755,13 +11995,17 @@ function IsometricCanvas({
11755
11995
  {
11756
11996
  ref: canvasRef,
11757
11997
  "data-testid": "game-canvas",
11758
- onClick: handleClick,
11759
- onMouseDown: enableCamera ? handleMouseDown : void 0,
11760
- onMouseMove: handleMouseMoveWithCamera,
11761
- onMouseUp: enableCamera ? handleMouseUp : void 0,
11762
- onMouseLeave: handleMouseLeaveWithCamera,
11998
+ onPointerDown: gestureHandlers.onPointerDown,
11999
+ onPointerMove: (e) => {
12000
+ gestureHandlers.onPointerMove(e);
12001
+ handleCanvasHover(e);
12002
+ },
12003
+ onPointerUp: gestureHandlers.onPointerUp,
12004
+ onPointerCancel: gestureHandlers.onPointerCancel,
12005
+ onPointerLeave: handleCanvasPointerLeave,
12006
+ onWheel: gestureHandlers.onWheel,
11763
12007
  onContextMenu: (e) => e.preventDefault(),
11764
- className: "cursor-pointer",
12008
+ className: "cursor-pointer touch-none",
11765
12009
  style: {
11766
12010
  width: viewportSize.width,
11767
12011
  height: viewportSize.height
@@ -11815,6 +12059,7 @@ var init_IsometricCanvas = __esm({
11815
12059
  init_ErrorState();
11816
12060
  init_useImageCache();
11817
12061
  init_useCamera();
12062
+ init_useCanvasGestures();
11818
12063
  init_useUnitSpriteAtlas();
11819
12064
  init_verificationRegistry();
11820
12065
  init_isometric();
@@ -13617,7 +13862,7 @@ var init_CodeBlock = __esm({
13617
13862
  };
13618
13863
  LINE_PROPS_FN = (n) => ({ "data-line": String(n - 1) });
13619
13864
  HIDDEN_LINE_NUMBERS = { display: "none" };
13620
- CodeBlock = React82__default.memo(
13865
+ CodeBlock = React85__default.memo(
13621
13866
  ({
13622
13867
  code: rawCode,
13623
13868
  language = "text",
@@ -13786,24 +14031,24 @@ var init_CodeBlock = __esm({
13786
14031
  return;
13787
14032
  }
13788
14033
  lineEls.forEach((el) => {
13789
- const num2 = parseInt(el.getAttribute("data-line") ?? "-1", 10);
13790
- if (hiddenLines.has(num2)) {
14034
+ const num3 = parseInt(el.getAttribute("data-line") ?? "-1", 10);
14035
+ if (hiddenLines.has(num3)) {
13791
14036
  el.style.display = "none";
13792
14037
  return;
13793
14038
  }
13794
14039
  el.style.display = "";
13795
14040
  el.style.position = "relative";
13796
14041
  el.style.paddingLeft = "1.2em";
13797
- const region = foldStartMap.get(num2);
14042
+ const region = foldStartMap.get(num3);
13798
14043
  if (!region) return;
13799
- const isCollapsed = collapsed.has(num2);
14044
+ const isCollapsed = collapsed.has(num3);
13800
14045
  const toggle = document.createElement("span");
13801
14046
  toggle.className = "fold-toggle";
13802
14047
  toggle.textContent = isCollapsed ? "\u25B6" : "\u25BC";
13803
14048
  toggle.style.cssText = "position:absolute;left:0;top:0;width:1.2em;text-align:center;cursor:pointer;color:#858585;font-size:10px;user-select:none;line-height:inherit;height:100%";
13804
14049
  toggle.addEventListener("click", (e) => {
13805
14050
  e.stopPropagation();
13806
- toggleFoldRef.current(num2);
14051
+ toggleFoldRef.current(num3);
13807
14052
  });
13808
14053
  el.insertBefore(toggle, el.firstChild);
13809
14054
  if (isCollapsed) {
@@ -14204,7 +14449,7 @@ var init_MarkdownContent = __esm({
14204
14449
  init_Box();
14205
14450
  init_CodeBlock();
14206
14451
  init_cn();
14207
- MarkdownContent = React82__default.memo(
14452
+ MarkdownContent = React85__default.memo(
14208
14453
  ({ content, direction = "ltr", className }) => {
14209
14454
  const { t: _t } = useTranslate();
14210
14455
  const safeContent = typeof content === "string" ? content : String(content ?? "");
@@ -14930,6 +15175,10 @@ var init_StateMachineView = __esm({
14930
15175
  const handleMouseLeave2 = () => {
14931
15176
  onHover(null, 0, 0);
14932
15177
  };
15178
+ const handlePointerDown2 = (e) => {
15179
+ if (e.pointerType === "mouse") return;
15180
+ handleMouseEnter2();
15181
+ };
14933
15182
  return /* @__PURE__ */ jsxs(
14934
15183
  "g",
14935
15184
  {
@@ -14939,6 +15188,7 @@ var init_StateMachineView = __esm({
14939
15188
  onClick: () => onClick?.(bundle),
14940
15189
  onMouseEnter: handleMouseEnter2,
14941
15190
  onMouseLeave: handleMouseLeave2,
15191
+ onPointerDown: handlePointerDown2,
14942
15192
  style: { pointerEvents: "auto" },
14943
15193
  children: [
14944
15194
  /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
@@ -15056,6 +15306,10 @@ var init_StateMachineView = __esm({
15056
15306
  const handleMouseLeave = useCallback(() => {
15057
15307
  onHover(null, 0, 0);
15058
15308
  }, [onHover]);
15309
+ const handlePointerDown = useCallback((e) => {
15310
+ if (e.pointerType === "mouse") return;
15311
+ handleMouseEnter();
15312
+ }, [handleMouseEnter]);
15059
15313
  const uniqueMarkerId = `arrow-${bundle.id}`;
15060
15314
  const hasDetails = bundle.labels.some((l) => l.hasDetails);
15061
15315
  return /* @__PURE__ */ jsxs(
@@ -15067,6 +15321,7 @@ var init_StateMachineView = __esm({
15067
15321
  onClick: () => onClick?.(bundle),
15068
15322
  onMouseEnter: handleMouseEnter,
15069
15323
  onMouseLeave: handleMouseLeave,
15324
+ onPointerDown: handlePointerDown,
15070
15325
  style: { pointerEvents: "auto" },
15071
15326
  children: [
15072
15327
  /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
@@ -15521,7 +15776,7 @@ var init_StateMachineView = __esm({
15521
15776
  style: { top: title ? 30 : 0 },
15522
15777
  children: [
15523
15778
  entity && /* @__PURE__ */ jsx(EntityBox, { entity, config }),
15524
- states.map((state) => renderStateNode ? /* @__PURE__ */ jsx(React82__default.Fragment, { children: renderStateNode(state, config) }, state.id) : /* @__PURE__ */ jsx(
15779
+ states.map((state) => renderStateNode ? /* @__PURE__ */ jsx(React85__default.Fragment, { children: renderStateNode(state, config) }, state.id) : /* @__PURE__ */ jsx(
15525
15780
  StateNode,
15526
15781
  {
15527
15782
  state,
@@ -19422,8 +19677,8 @@ function EmojiEffect({
19422
19677
  );
19423
19678
  }
19424
19679
  function CanvasEffect({
19425
- effectSpriteUrl = "https://almadar-kflow-assets.web.app/shared/effects/gas/gas00.png",
19426
- assetBaseUrl = "https://almadar-kflow-assets.web.app/shared/effects/",
19680
+ effectSpriteUrl,
19681
+ assetBaseUrl,
19427
19682
  ...props
19428
19683
  }) {
19429
19684
  const eventBus = useEventBus();
@@ -19466,6 +19721,380 @@ var init_CanvasEffect = __esm({
19466
19721
  CanvasEffect.displayName = "CanvasEffect";
19467
19722
  }
19468
19723
  });
19724
+ function CardHand({
19725
+ cards = DEFAULT_CARDS,
19726
+ selectedId = "",
19727
+ size = "md",
19728
+ onCardClick,
19729
+ cardClickEvent,
19730
+ emptyLabel = "No cards in hand",
19731
+ className
19732
+ }) {
19733
+ const eventBus = useEventBus();
19734
+ const handleClick = React85.useCallback(
19735
+ (card) => {
19736
+ if (card.disabled) return;
19737
+ onCardClick?.(card.id);
19738
+ if (cardClickEvent) {
19739
+ eventBus.emit(`UI:${cardClickEvent}`, { cardId: card.id });
19740
+ }
19741
+ },
19742
+ [onCardClick, cardClickEvent, eventBus]
19743
+ );
19744
+ if (cards.length === 0) {
19745
+ return /* @__PURE__ */ jsx(Box, { className: cn("flex items-center justify-center p-4", className), children: /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: emptyLabel }) });
19746
+ }
19747
+ const art = artSizeMap[size];
19748
+ return /* @__PURE__ */ jsx(Box, { className: cn("flex flex-wrap gap-2 justify-center items-end", className), children: cards.map((card) => {
19749
+ const selected = selectedId === card.id;
19750
+ return /* @__PURE__ */ jsxs(
19751
+ "button",
19752
+ {
19753
+ type: "button",
19754
+ onClick: () => handleClick(card),
19755
+ disabled: card.disabled,
19756
+ title: card.title,
19757
+ className: cn(
19758
+ "card-hand-card relative flex flex-col items-center rounded-interactive border-2",
19759
+ "bg-card/90 px-1.5 pt-1.5 pb-1 transition-all duration-150",
19760
+ cardSizeMap[size],
19761
+ card.disabled ? "border-border opacity-50 cursor-not-allowed" : "border-accent hover:brightness-125 hover:-translate-y-1 cursor-pointer",
19762
+ selected && "ring-2 ring-foreground ring-offset-1 ring-offset-background -translate-y-1"
19763
+ ),
19764
+ children: [
19765
+ card.cost != null && /* @__PURE__ */ jsx(
19766
+ "span",
19767
+ {
19768
+ className: cn(
19769
+ "absolute -top-2 -left-2 flex items-center justify-center",
19770
+ "min-w-[22px] h-[22px] rounded-full px-1",
19771
+ "bg-info text-foreground text-xs font-bold border border-background"
19772
+ ),
19773
+ children: card.cost
19774
+ }
19775
+ ),
19776
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 flex items-center justify-center w-full", children: card.iconUrl ? /* @__PURE__ */ jsx(
19777
+ "img",
19778
+ {
19779
+ src: card.iconUrl,
19780
+ alt: card.title ?? card.id,
19781
+ width: art,
19782
+ height: art,
19783
+ style: { imageRendering: "pixelated", objectFit: "contain" },
19784
+ className: "flex-shrink-0"
19785
+ }
19786
+ ) : /* @__PURE__ */ jsx(
19787
+ Box,
19788
+ {
19789
+ className: "rounded bg-muted/40",
19790
+ style: { width: art, height: art }
19791
+ }
19792
+ ) }),
19793
+ card.title != null && /* @__PURE__ */ jsx(
19794
+ Typography,
19795
+ {
19796
+ variant: "small",
19797
+ className: "w-full truncate text-center text-[10px] leading-tight font-semibold",
19798
+ children: card.title
19799
+ }
19800
+ ),
19801
+ (card.attack != null || card.defense != null) && /* @__PURE__ */ jsxs(Box, { className: "flex w-full items-center justify-between px-0.5 text-[10px] font-bold leading-none", children: [
19802
+ /* @__PURE__ */ jsx("span", { className: "text-error", children: card.attack != null ? `\u2694${card.attack}` : "" }),
19803
+ /* @__PURE__ */ jsx("span", { className: "text-info", children: card.defense != null ? `\u{1F6E1}${card.defense}` : "" })
19804
+ ] })
19805
+ ]
19806
+ },
19807
+ card.id
19808
+ );
19809
+ }) });
19810
+ }
19811
+ var cardSizeMap, artSizeMap, DEFAULT_CARDS;
19812
+ var init_CardHand = __esm({
19813
+ "components/game/molecules/CardHand.tsx"() {
19814
+ "use client";
19815
+ init_cn();
19816
+ init_useEventBus();
19817
+ init_Box();
19818
+ init_Typography();
19819
+ cardSizeMap = {
19820
+ sm: "w-16 h-24",
19821
+ md: "w-20 h-28",
19822
+ lg: "w-24 h-36"
19823
+ };
19824
+ artSizeMap = {
19825
+ sm: 40,
19826
+ md: 52,
19827
+ lg: 64
19828
+ };
19829
+ DEFAULT_CARDS = [
19830
+ { id: "card-1", title: "Brawler", cost: 1, attack: 2, defense: 1 },
19831
+ { id: "card-2", title: "Guardian", cost: 2, attack: 1, defense: 4 },
19832
+ { id: "card-3", title: "Striker", cost: 3, attack: 5, defense: 2 }
19833
+ ];
19834
+ CardHand.displayName = "CardHand";
19835
+ }
19836
+ });
19837
+ function resolveManifestUrl(manifest, relative) {
19838
+ if (relative == null) return void 0;
19839
+ if (/^https?:\/\//.test(relative)) return relative;
19840
+ const base = manifest?.baseUrl;
19841
+ if (base == null) return relative;
19842
+ const cleanBase = base.replace(/\/$/, "");
19843
+ const cleanRel = relative.replace(/^\//, "");
19844
+ return `${cleanBase}/${cleanRel}`;
19845
+ }
19846
+ function rowToCard(r) {
19847
+ return {
19848
+ id: str(r.id),
19849
+ iconKey: r.iconKey == null ? void 0 : str(r.iconKey),
19850
+ title: r.title == null ? void 0 : str(r.title),
19851
+ cost: r.cost == null ? void 0 : num(r.cost),
19852
+ attack: r.attack == null ? void 0 : num(r.attack),
19853
+ defense: r.defense == null ? void 0 : num(r.defense)
19854
+ };
19855
+ }
19856
+ function toHandCards(cards, manifest, mana) {
19857
+ return cards.map((c) => {
19858
+ const key = c.iconKey ?? c.id;
19859
+ const iconUrl = resolveManifestUrl(manifest, manifest?.cards?.[key]);
19860
+ return {
19861
+ id: c.id,
19862
+ iconUrl,
19863
+ title: c.title,
19864
+ cost: c.cost,
19865
+ attack: c.attack,
19866
+ defense: c.defense,
19867
+ disabled: c.cost != null && c.cost > mana
19868
+ };
19869
+ });
19870
+ }
19871
+ function CardBattlerBoard({
19872
+ entity,
19873
+ deck: propDeck,
19874
+ hand: propHand,
19875
+ board: propBoard,
19876
+ mana: propMana,
19877
+ maxMana: propMaxMana,
19878
+ turn: propTurn,
19879
+ result: propResult,
19880
+ assetManifest: propAssetManifest,
19881
+ playCardEvent,
19882
+ endTurnEvent,
19883
+ playAgainEvent,
19884
+ gameEndEvent,
19885
+ className
19886
+ }) {
19887
+ const board = boardEntity(entity) ?? {};
19888
+ const eventBus = useEventBus();
19889
+ const { t } = useTranslate();
19890
+ const assetManifest = propAssetManifest ?? board.assetManifest;
19891
+ const entityHand = useMemo(() => rows(board.hand).map(rowToCard), [board.hand]);
19892
+ const rawHand = propHand ?? entityHand;
19893
+ const hand = rawHand.length > 0 ? rawHand : DEFAULT_HAND;
19894
+ const entityBoard = useMemo(() => rows(board.board).map(rowToCard), [board.board]);
19895
+ const playedCards = propBoard ?? entityBoard;
19896
+ const entityDeck = useMemo(() => rows(board.deck).map(rowToCard), [board.deck]);
19897
+ const deck = propDeck ?? entityDeck;
19898
+ const mana = propMana ?? num(board.mana, 3);
19899
+ const maxMana = propMaxMana ?? num(board.maxMana, 3);
19900
+ const turn = propTurn ?? num(board.turn, 1);
19901
+ const result = propResult ?? (str(board.result) || "none");
19902
+ const handCards = useMemo(() => toHandCards(hand, assetManifest, mana), [hand, assetManifest, mana]);
19903
+ const boardCards = useMemo(() => toHandCards(playedCards, assetManifest, mana), [playedCards, assetManifest, mana]);
19904
+ const emittedGameEnd = useRef(false);
19905
+ if (result !== "none" && !emittedGameEnd.current) {
19906
+ emittedGameEnd.current = true;
19907
+ if (gameEndEvent) {
19908
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
19909
+ }
19910
+ }
19911
+ if (result === "none") {
19912
+ emittedGameEnd.current = false;
19913
+ }
19914
+ const handlePlayCard = useCallback((cardId) => {
19915
+ if (result !== "none") return;
19916
+ if (playCardEvent) {
19917
+ eventBus.emit(`UI:${playCardEvent}`, { cardId });
19918
+ }
19919
+ }, [result, playCardEvent, eventBus]);
19920
+ const handleEndTurn = useCallback(() => {
19921
+ if (result !== "none") return;
19922
+ if (endTurnEvent) {
19923
+ eventBus.emit(`UI:${endTurnEvent}`, {});
19924
+ }
19925
+ }, [result, endTurnEvent, eventBus]);
19926
+ const handlePlayAgain = useCallback(() => {
19927
+ if (playAgainEvent) {
19928
+ eventBus.emit(`UI:${playAgainEvent}`, {});
19929
+ }
19930
+ }, [playAgainEvent, eventBus]);
19931
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("card-battler-board relative min-h-[600px] bg-background", className), gap: "none", children: [
19932
+ /* @__PURE__ */ jsxs(
19933
+ HStack,
19934
+ {
19935
+ className: "px-4 py-2 border-b border-border bg-surface",
19936
+ gap: "lg",
19937
+ align: "center",
19938
+ children: [
19939
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
19940
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.mana") ?? "Mana" }),
19941
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", weight: "bold", className: "text-info", children: [
19942
+ mana,
19943
+ " / ",
19944
+ maxMana
19945
+ ] })
19946
+ ] }),
19947
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
19948
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.turn") ?? "Turn" }),
19949
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", children: turn })
19950
+ ] }),
19951
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
19952
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.deck") ?? "Deck" }),
19953
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", children: deck.length })
19954
+ ] }),
19955
+ /* @__PURE__ */ jsx(Box, { className: "ml-auto", children: result === "none" && /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: handleEndTurn, children: t("cb.endTurn") ?? "End Turn" }) })
19956
+ ]
19957
+ }
19958
+ ),
19959
+ /* @__PURE__ */ jsxs(Box, { className: "flex-1 relative p-4 overflow-auto", children: [
19960
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", className: "mb-2 uppercase tracking-wide", children: t("cb.battlefield") ?? "Battlefield" }),
19961
+ boardCards.length > 0 ? /* @__PURE__ */ jsx(
19962
+ CardHand,
19963
+ {
19964
+ cards: boardCards,
19965
+ size: "md",
19966
+ className: "justify-start"
19967
+ }
19968
+ ) : /* @__PURE__ */ jsx(Box, { className: "flex items-center justify-center h-32 rounded-interactive border-2 border-dashed border-border", children: /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.emptyBoard") ?? "Play a card to begin" }) })
19969
+ ] }),
19970
+ /* @__PURE__ */ jsxs(Box, { className: "border-t border-border bg-surface px-4 py-3", children: [
19971
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", className: "mb-2 uppercase tracking-wide", children: t("cb.hand") ?? "Your Hand" }),
19972
+ /* @__PURE__ */ jsx(
19973
+ CardHand,
19974
+ {
19975
+ cards: handCards,
19976
+ size: "lg",
19977
+ onCardClick: handlePlayCard,
19978
+ emptyLabel: t("cb.emptyHand") ?? "No cards in hand"
19979
+ }
19980
+ )
19981
+ ] }),
19982
+ 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: [
19983
+ /* @__PURE__ */ jsx(
19984
+ Typography,
19985
+ {
19986
+ variant: "h2",
19987
+ className: cn(
19988
+ "text-4xl font-black tracking-widest uppercase",
19989
+ result === "won" ? "text-warning" : "text-error"
19990
+ ),
19991
+ children: result === "won" ? t("cb.victory") ?? "Victory!" : t("cb.defeat") ?? "Defeat!"
19992
+ }
19993
+ ),
19994
+ /* @__PURE__ */ jsx(
19995
+ Button,
19996
+ {
19997
+ variant: "primary",
19998
+ className: "px-8 py-3 font-semibold",
19999
+ onClick: handlePlayAgain,
20000
+ children: t("cb.playAgain") ?? "Play Again"
20001
+ }
20002
+ )
20003
+ ] }) })
20004
+ ] });
20005
+ }
20006
+ var DEFAULT_HAND;
20007
+ var init_CardBattlerBoard = __esm({
20008
+ "components/game/organisms/CardBattlerBoard.tsx"() {
20009
+ "use client";
20010
+ init_cn();
20011
+ init_useEventBus();
20012
+ init_Box();
20013
+ init_Button();
20014
+ init_Typography();
20015
+ init_Stack();
20016
+ init_CardHand();
20017
+ init_boardEntity();
20018
+ DEFAULT_HAND = [
20019
+ { id: "c1", iconKey: "bear", title: "Bear", cost: 3, attack: 5, defense: 4 },
20020
+ { id: "c2", iconKey: "owl", title: "Owl", cost: 1, attack: 2, defense: 1 },
20021
+ { id: "c3", iconKey: "gorilla", title: "Gorilla", cost: 4, attack: 6, defense: 5 },
20022
+ { id: "c4", iconKey: "frog", title: "Frog", cost: 1, attack: 1, defense: 2 },
20023
+ { id: "c5", iconKey: "snake", title: "Snake", cost: 2, attack: 4, defense: 1 }
20024
+ ];
20025
+ CardBattlerBoard.displayName = "CardBattlerBoard";
20026
+ }
20027
+ });
20028
+ function isEntityRow(value) {
20029
+ return value != null && typeof value === "object" && !Array.isArray(value);
20030
+ }
20031
+ function CardBattlerTemplate({
20032
+ entity,
20033
+ title = "Card Battler",
20034
+ deck,
20035
+ hand,
20036
+ board,
20037
+ mana,
20038
+ maxMana,
20039
+ turn,
20040
+ result,
20041
+ assetManifest,
20042
+ playCardEvent,
20043
+ endTurnEvent,
20044
+ playAgainEvent,
20045
+ gameEndEvent,
20046
+ className
20047
+ }) {
20048
+ const resolved = isEntityRow(entity) ? entity : void 0;
20049
+ return /* @__PURE__ */ jsxs(
20050
+ Box,
20051
+ {
20052
+ display: "flex",
20053
+ fullHeight: true,
20054
+ className: cn("card-battler-template flex-col", className),
20055
+ children: [
20056
+ /* @__PURE__ */ jsx(
20057
+ HStack,
20058
+ {
20059
+ gap: "sm",
20060
+ align: "center",
20061
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
20062
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
20063
+ }
20064
+ ),
20065
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
20066
+ CardBattlerBoard,
20067
+ {
20068
+ entity: resolved,
20069
+ deck,
20070
+ hand,
20071
+ board,
20072
+ mana,
20073
+ maxMana,
20074
+ turn,
20075
+ result,
20076
+ assetManifest,
20077
+ playCardEvent,
20078
+ endTurnEvent,
20079
+ playAgainEvent,
20080
+ gameEndEvent,
20081
+ className: "h-full"
20082
+ }
20083
+ ) })
20084
+ ]
20085
+ }
20086
+ );
20087
+ }
20088
+ var init_CardBattlerTemplate = __esm({
20089
+ "components/game/templates/CardBattlerTemplate.tsx"() {
20090
+ init_cn();
20091
+ init_Box();
20092
+ init_Stack();
20093
+ init_Typography();
20094
+ init_CardBattlerBoard();
20095
+ CardBattlerTemplate.displayName = "CardBattlerTemplate";
20096
+ }
20097
+ });
19469
20098
 
19470
20099
  // lib/getNestedValue.ts
19471
20100
  function getNestedValue(obj, path) {
@@ -21248,6 +21877,315 @@ var init_ChartLegend = __esm({
21248
21877
  ChartLegend.displayName = "ChartLegend";
21249
21878
  }
21250
21879
  });
21880
+ function resolveManifestUrl2(manifest, relative) {
21881
+ if (relative == null) return void 0;
21882
+ if (/^https?:\/\//.test(relative)) return relative;
21883
+ const base = manifest?.baseUrl;
21884
+ if (base == null) return relative;
21885
+ const cleanBase = base.replace(/\/$/, "");
21886
+ const cleanRel = relative.replace(/^\//, "");
21887
+ return `${cleanBase}/${cleanRel}`;
21888
+ }
21889
+ function buildDefaultCBTiles() {
21890
+ const tiles = [];
21891
+ for (let y = 0; y < CB_GRID_H; y++) {
21892
+ for (let x = 0; x < CB_GRID_W; x++) {
21893
+ const variant = (x * 3 + y * 5 + (x ^ y)) % 3;
21894
+ const terrainKey = ["grass", "stone", "dirt"][variant];
21895
+ tiles.push({ x, y, terrain: terrainKey, passable: true });
21896
+ }
21897
+ }
21898
+ return tiles;
21899
+ }
21900
+ function tilesToIso(tiles, manifest) {
21901
+ return tiles.map((t) => {
21902
+ const key = t.terrain ?? "grass";
21903
+ const sprite = t.terrainSprite ?? resolveManifestUrl2(manifest, manifest?.terrains?.[key]) ?? resolveManifestUrl2(manifest, manifest?.terrains?.grass);
21904
+ return {
21905
+ x: t.x,
21906
+ y: t.y,
21907
+ terrain: t.terrain,
21908
+ terrainSprite: sprite,
21909
+ passable: t.passable
21910
+ };
21911
+ });
21912
+ }
21913
+ function buildingsToFeatures(buildings, manifest) {
21914
+ return buildings.map((b) => ({
21915
+ id: b.id,
21916
+ x: b.x,
21917
+ y: b.y,
21918
+ type: b.type,
21919
+ sprite: resolveManifestUrl2(manifest, manifest?.features?.[b.type])
21920
+ }));
21921
+ }
21922
+ function CityBuilderBoard({
21923
+ entity,
21924
+ tiles: propTiles,
21925
+ buildings: propBuildings,
21926
+ assetManifest: propAssetManifest,
21927
+ buildTypes: propBuildTypes,
21928
+ pendingBuildType: propPendingBuildType,
21929
+ gold: propGold,
21930
+ wood: propWood,
21931
+ population: propPopulation,
21932
+ result: propResult,
21933
+ scale = 0.3,
21934
+ unitScale = 0.6,
21935
+ spriteHeightRatio = 1.5,
21936
+ spriteMaxWidthRatio = 0.7,
21937
+ selectBuildTypeEvent,
21938
+ placeBuildingEvent,
21939
+ gameEndEvent,
21940
+ className
21941
+ }) {
21942
+ const board = boardEntity(entity) ?? {};
21943
+ const eventBus = useEventBus();
21944
+ const { t } = useTranslate();
21945
+ const assetManifest = propAssetManifest ?? board.assetManifest;
21946
+ const entityTiles = useMemo(
21947
+ () => rows(board.tiles).map((r) => ({
21948
+ x: num(r.x),
21949
+ y: num(r.y),
21950
+ terrain: r.terrain == null ? void 0 : str(r.terrain),
21951
+ terrainSprite: r.terrainSprite == null ? void 0 : str(r.terrainSprite),
21952
+ passable: r.passable !== false
21953
+ })),
21954
+ [board.tiles]
21955
+ );
21956
+ const rawTiles = propTiles ?? entityTiles;
21957
+ const tiles = rawTiles.length >= CB_GRID_W * CB_GRID_H ? rawTiles : DEFAULT_CB_TILES;
21958
+ const entityBuildings = useMemo(
21959
+ () => rows(board.buildings).map((r) => ({
21960
+ id: str(r.id),
21961
+ x: num(r.x),
21962
+ y: num(r.y),
21963
+ type: str(r.type)
21964
+ })),
21965
+ [board.buildings]
21966
+ );
21967
+ const buildings = propBuildings ?? entityBuildings;
21968
+ const buildTypes = propBuildTypes ?? DEFAULT_BUILD_TYPES;
21969
+ const gold = propGold ?? num(board.gold, 100);
21970
+ const wood = propWood ?? num(board.wood, 80);
21971
+ const population = propPopulation ?? num(board.population, 0);
21972
+ const pendingBuildType = propPendingBuildType ?? (str(board.pendingBuildType) || buildTypes[0]?.type || "");
21973
+ const result = propResult ?? (str(board.result) || "none");
21974
+ const [hoveredTile, setHoveredTile] = useState(null);
21975
+ const emittedGameEnd = useRef(false);
21976
+ if (result !== "none" && !emittedGameEnd.current) {
21977
+ emittedGameEnd.current = true;
21978
+ if (gameEndEvent) {
21979
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
21980
+ }
21981
+ }
21982
+ if (result === "none") {
21983
+ emittedGameEnd.current = false;
21984
+ }
21985
+ const pendingCost = useMemo(
21986
+ () => buildTypes.find((b) => b.type === pendingBuildType)?.cost ?? 0,
21987
+ [buildTypes, pendingBuildType]
21988
+ );
21989
+ const buildingPositions = useMemo(
21990
+ () => new Set(buildings.map((b) => `${b.x},${b.y}`)),
21991
+ [buildings]
21992
+ );
21993
+ const validMoves = useMemo(() => {
21994
+ if (result !== "none" || gold < pendingCost) return [];
21995
+ return tiles.filter(
21996
+ (t2) => t2.passable !== false && !buildingPositions.has(`${t2.x},${t2.y}`)
21997
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
21998
+ }, [tiles, buildingPositions, result, gold, pendingCost]);
21999
+ const isoTiles = useMemo(() => tilesToIso(tiles, assetManifest), [tiles, assetManifest]);
22000
+ const buildingFeatures = useMemo(() => buildingsToFeatures(buildings, assetManifest), [buildings, assetManifest]);
22001
+ const handleTileClick = useCallback((x, y) => {
22002
+ if (result !== "none") return;
22003
+ if (buildingPositions.has(`${x},${y}`)) return;
22004
+ if (gold < pendingCost) return;
22005
+ if (placeBuildingEvent) {
22006
+ eventBus.emit(`UI:${placeBuildingEvent}`, { x, y });
22007
+ }
22008
+ }, [result, buildingPositions, gold, pendingCost, placeBuildingEvent, eventBus]);
22009
+ const handleSelectBuildType = useCallback((buildType) => {
22010
+ if (selectBuildTypeEvent) {
22011
+ eventBus.emit(`UI:${selectBuildTypeEvent}`, { buildType });
22012
+ }
22013
+ }, [selectBuildTypeEvent, eventBus]);
22014
+ const canBuild = gold >= pendingCost && result === "none";
22015
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("city-builder-board relative min-h-[600px] bg-background", className), gap: "none", children: [
22016
+ /* @__PURE__ */ jsxs(
22017
+ HStack,
22018
+ {
22019
+ className: "px-4 py-2 border-b border-border bg-surface",
22020
+ gap: "lg",
22021
+ align: "center",
22022
+ children: [
22023
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
22024
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.gold") ?? "Gold" }),
22025
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
22026
+ ] }),
22027
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
22028
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.wood") ?? "Wood" }),
22029
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-success", children: wood })
22030
+ ] }),
22031
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
22032
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.population") ?? "Population" }),
22033
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", children: population })
22034
+ ] }),
22035
+ /* @__PURE__ */ jsx(HStack, { gap: "xs", align: "center", className: "ml-auto", children: buildTypes.map((b) => /* @__PURE__ */ jsxs(
22036
+ Button,
22037
+ {
22038
+ variant: b.type === pendingBuildType ? "primary" : "secondary",
22039
+ size: "sm",
22040
+ disabled: gold < b.cost || result !== "none",
22041
+ onClick: () => handleSelectBuildType(b.type),
22042
+ children: [
22043
+ b.label,
22044
+ " (",
22045
+ b.cost,
22046
+ ")"
22047
+ ]
22048
+ },
22049
+ b.type
22050
+ )) })
22051
+ ]
22052
+ }
22053
+ ),
22054
+ /* @__PURE__ */ jsxs(Box, { className: "relative flex-1", children: [
22055
+ /* @__PURE__ */ jsx(
22056
+ IsometricCanvas_default,
22057
+ {
22058
+ tiles: isoTiles,
22059
+ features: buildingFeatures,
22060
+ assetBaseUrl: assetManifest?.baseUrl,
22061
+ assetManifest,
22062
+ validMoves,
22063
+ hoveredTile,
22064
+ onTileClick: handleTileClick,
22065
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
22066
+ onTileLeave: () => setHoveredTile(null),
22067
+ scale,
22068
+ unitScale,
22069
+ spriteHeightRatio,
22070
+ spriteMaxWidthRatio
22071
+ }
22072
+ ),
22073
+ hoveredTile && canBuild && 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("cb.placeBuilding") ?? `Build ${pendingBuildType} (${pendingCost} gold)` }) })
22074
+ ] }),
22075
+ 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: [
22076
+ /* @__PURE__ */ jsx(
22077
+ Typography,
22078
+ {
22079
+ variant: "h2",
22080
+ className: cn(
22081
+ "text-4xl font-black tracking-widest uppercase",
22082
+ result === "won" ? "text-warning" : "text-error"
22083
+ ),
22084
+ children: result === "won" ? t("cb.victory") ?? "City Thrives!" : t("cb.defeat") ?? "City Collapsed!"
22085
+ }
22086
+ ),
22087
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: result === "won" ? t("cb.reachedGoal") ?? `Your city reached a population of ${population}.` : t("cb.bankrupt") ?? "Your city ran out of resources." })
22088
+ ] }) })
22089
+ ] });
22090
+ }
22091
+ var CB_GRID_W, CB_GRID_H, DEFAULT_BUILD_TYPES, DEFAULT_CB_TILES;
22092
+ var init_CityBuilderBoard = __esm({
22093
+ "components/game/organisms/CityBuilderBoard.tsx"() {
22094
+ "use client";
22095
+ init_cn();
22096
+ init_useEventBus();
22097
+ init_Box();
22098
+ init_Button();
22099
+ init_Typography();
22100
+ init_Stack();
22101
+ init_IsometricCanvas();
22102
+ init_boardEntity();
22103
+ CB_GRID_W = 12;
22104
+ CB_GRID_H = 12;
22105
+ DEFAULT_BUILD_TYPES = [
22106
+ { type: "house", label: "House", cost: 20 },
22107
+ { type: "farm", label: "Farm", cost: 15 },
22108
+ { type: "mine", label: "Mine", cost: 25 },
22109
+ { type: "barracks", label: "Barracks", cost: 40 }
22110
+ ];
22111
+ DEFAULT_CB_TILES = buildDefaultCBTiles();
22112
+ CityBuilderBoard.displayName = "CityBuilderBoard";
22113
+ }
22114
+ });
22115
+ function isEntityRow2(value) {
22116
+ return value != null && typeof value === "object" && !Array.isArray(value);
22117
+ }
22118
+ function CityBuilderTemplate({
22119
+ entity,
22120
+ title = "City Builder",
22121
+ tiles,
22122
+ buildings,
22123
+ buildTypes,
22124
+ pendingBuildType,
22125
+ assetManifest,
22126
+ gold,
22127
+ wood,
22128
+ population,
22129
+ result,
22130
+ scale,
22131
+ unitScale,
22132
+ selectBuildTypeEvent,
22133
+ placeBuildingEvent,
22134
+ gameEndEvent,
22135
+ className
22136
+ }) {
22137
+ const resolved = isEntityRow2(entity) ? entity : void 0;
22138
+ return /* @__PURE__ */ jsxs(
22139
+ Box,
22140
+ {
22141
+ display: "flex",
22142
+ fullHeight: true,
22143
+ className: cn("city-builder-template flex-col", className),
22144
+ children: [
22145
+ /* @__PURE__ */ jsx(
22146
+ HStack,
22147
+ {
22148
+ gap: "sm",
22149
+ align: "center",
22150
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
22151
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
22152
+ }
22153
+ ),
22154
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
22155
+ CityBuilderBoard,
22156
+ {
22157
+ entity: resolved,
22158
+ tiles,
22159
+ buildings,
22160
+ buildTypes,
22161
+ pendingBuildType,
22162
+ assetManifest,
22163
+ gold,
22164
+ wood,
22165
+ population,
22166
+ result,
22167
+ scale,
22168
+ unitScale,
22169
+ selectBuildTypeEvent,
22170
+ placeBuildingEvent,
22171
+ gameEndEvent,
22172
+ className: "h-full"
22173
+ }
22174
+ ) })
22175
+ ]
22176
+ }
22177
+ );
22178
+ }
22179
+ var init_CityBuilderTemplate = __esm({
22180
+ "components/game/templates/CityBuilderTemplate.tsx"() {
22181
+ init_cn();
22182
+ init_Box();
22183
+ init_Stack();
22184
+ init_Typography();
22185
+ init_CityBuilderBoard();
22186
+ CityBuilderTemplate.displayName = "CityBuilderTemplate";
22187
+ }
22188
+ });
21251
22189
  function ClassifierBoard({
21252
22190
  entity,
21253
22191
  completeEvent = "PUZZLE_COMPLETE",
@@ -22062,7 +23000,7 @@ function CraftingRecipe({
22062
23000
  className
22063
23001
  }) {
22064
23002
  const eventBus = useEventBus();
22065
- const handleCraft = React82.useCallback(() => {
23003
+ const handleCraft = React85.useCallback(() => {
22066
23004
  onCraft?.();
22067
23005
  if (craftEvent) {
22068
23006
  eventBus.emit(craftEvent, { output: output.label });
@@ -22079,7 +23017,7 @@ function CraftingRecipe({
22079
23017
  children: [
22080
23018
  /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap items-center", children: inputs.map((ingredient, index) => {
22081
23019
  const hasSufficient = ingredient.available >= ingredient.required;
22082
- return /* @__PURE__ */ jsxs(React82.Fragment, { children: [
23020
+ return /* @__PURE__ */ jsxs(React85.Fragment, { children: [
22083
23021
  /* @__PURE__ */ jsx(Box, { className: "relative", children: /* @__PURE__ */ jsx(
22084
23022
  ItemSlot,
22085
23023
  {
@@ -22151,8 +23089,8 @@ function DPad({
22151
23089
  }) {
22152
23090
  const eventBus = useEventBus();
22153
23091
  const sizes = sizeMap15[size];
22154
- const [activeDirections, setActiveDirections] = React82.useState(/* @__PURE__ */ new Set());
22155
- const handlePress = React82.useCallback(
23092
+ const [activeDirections, setActiveDirections] = React85.useState(/* @__PURE__ */ new Set());
23093
+ const handlePress = React85.useCallback(
22156
23094
  (direction) => {
22157
23095
  setActiveDirections((prev) => new Set(prev).add(direction));
22158
23096
  if (directionEvent) eventBus.emit(`UI:${directionEvent}`, { direction, pressed: true });
@@ -22160,7 +23098,7 @@ function DPad({
22160
23098
  },
22161
23099
  [directionEvent, eventBus, onDirection]
22162
23100
  );
22163
- const handleRelease = React82.useCallback(
23101
+ const handleRelease = React85.useCallback(
22164
23102
  (direction) => {
22165
23103
  setActiveDirections((prev) => {
22166
23104
  const next = new Set(prev);
@@ -22882,10 +23820,84 @@ function SubMenu({
22882
23820
  );
22883
23821
  return typeof document !== "undefined" ? createPortal(panel, document.body) : panel;
22884
23822
  }
23823
+ function MenuItemRow({
23824
+ item,
23825
+ itemId,
23826
+ hasSubMenu,
23827
+ isDanger,
23828
+ direction,
23829
+ isSubMenuOpen,
23830
+ activeSubMenuRef,
23831
+ eventBus,
23832
+ onItemClick,
23833
+ openSubMenu
23834
+ }) {
23835
+ const rowRef = useRef(null);
23836
+ const { triggerProps } = useTapReveal({
23837
+ enabled: hasSubMenu,
23838
+ onReveal: () => openSubMenu(itemId, rowRef.current),
23839
+ refs: [rowRef]
23840
+ });
23841
+ return /* @__PURE__ */ jsxs(Box, { children: [
23842
+ /* @__PURE__ */ jsx(
23843
+ Box,
23844
+ {
23845
+ ref: rowRef,
23846
+ as: "button",
23847
+ onClick: () => onItemClick({ ...item, id: itemId }, itemId),
23848
+ "aria-disabled": item.disabled || void 0,
23849
+ onMouseEnter: (e) => {
23850
+ if (hasSubMenu) openSubMenu(itemId, e.currentTarget);
23851
+ },
23852
+ onPointerDown: hasSubMenu ? triggerProps.onPointerDown : void 0,
23853
+ "data-testid": item.event ? `action-${item.event}` : void 0,
23854
+ className: cn(
23855
+ "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
23856
+ "text-sm transition-colors",
23857
+ "hover:bg-muted",
23858
+ "focus:outline-none focus:bg-muted",
23859
+ "disabled:opacity-50 disabled:cursor-not-allowed",
23860
+ item.disabled && "cursor-not-allowed",
23861
+ isDanger && "text-error hover:bg-error/10"
23862
+ ),
23863
+ children: /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-3 flex-1 min-w-0", children: [
23864
+ item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsx(Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
23865
+ /* @__PURE__ */ jsx(
23866
+ Typography,
23867
+ {
23868
+ variant: "small",
23869
+ className: cn("flex-1", isDanger && "text-red-600"),
23870
+ children: item.label
23871
+ }
23872
+ ),
23873
+ item.badge !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", children: item.badge }),
23874
+ hasSubMenu && /* @__PURE__ */ jsx(
23875
+ Icon,
23876
+ {
23877
+ name: direction === "rtl" ? "chevron-left" : "chevron-right",
23878
+ size: "sm",
23879
+ className: "flex-shrink-0"
23880
+ }
23881
+ )
23882
+ ] })
23883
+ }
23884
+ ),
23885
+ hasSubMenu && isSubMenuOpen && item.subMenu && /* @__PURE__ */ jsx(
23886
+ SubMenu,
23887
+ {
23888
+ items: item.subMenu,
23889
+ itemRef: activeSubMenuRef,
23890
+ direction,
23891
+ eventBus
23892
+ }
23893
+ )
23894
+ ] });
23895
+ }
22885
23896
  var MENU_GAP, menuContainerStyles, Menu;
22886
23897
  var init_Menu = __esm({
22887
23898
  "components/core/molecules/Menu.tsx"() {
22888
23899
  "use client";
23900
+ init_useTapReveal();
22889
23901
  init_Box();
22890
23902
  init_Icon();
22891
23903
  init_Divider();
@@ -22940,6 +23952,10 @@ var init_Menu = __esm({
22940
23952
  setIsOpen(false);
22941
23953
  }
22942
23954
  };
23955
+ const openSubMenu = (itemId, el) => {
23956
+ setActiveSubMenu(itemId);
23957
+ setActiveSubMenuRef(el);
23958
+ };
22943
23959
  useEffect(() => {
22944
23960
  if (isOpen) {
22945
23961
  updatePosition();
@@ -22967,8 +23983,8 @@ var init_Menu = __esm({
22967
23983
  "bottom-end": "bottom-start"
22968
23984
  };
22969
23985
  const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
22970
- const triggerChild = React82__default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger });
22971
- const triggerElement = React82__default.cloneElement(
23986
+ const triggerChild = React85__default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger });
23987
+ const triggerElement = React85__default.cloneElement(
22972
23988
  triggerChild,
22973
23989
  {
22974
23990
  ref: triggerRef,
@@ -22983,61 +23999,22 @@ var init_Menu = __esm({
22983
23999
  if (isDivider) {
22984
24000
  return /* @__PURE__ */ jsx(Divider, { className: "my-1" }, `divider-${index}`);
22985
24001
  }
22986
- return /* @__PURE__ */ jsxs(Box, { children: [
22987
- /* @__PURE__ */ jsx(
22988
- Box,
22989
- {
22990
- as: "button",
22991
- onClick: () => handleItemClick({ ...item, id: itemId }, itemId),
22992
- "aria-disabled": item.disabled || void 0,
22993
- onMouseEnter: (e) => {
22994
- if (hasSubMenu) {
22995
- setActiveSubMenu(itemId);
22996
- setActiveSubMenuRef(e.currentTarget);
22997
- }
22998
- },
22999
- "data-testid": item.event ? `action-${item.event}` : void 0,
23000
- className: cn(
23001
- "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
23002
- "text-sm transition-colors",
23003
- "hover:bg-muted",
23004
- "focus:outline-none focus:bg-muted",
23005
- "disabled:opacity-50 disabled:cursor-not-allowed",
23006
- item.disabled && "cursor-not-allowed",
23007
- isDanger && "text-error hover:bg-error/10"
23008
- ),
23009
- children: /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-3 flex-1 min-w-0", children: [
23010
- item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsx(Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
23011
- /* @__PURE__ */ jsx(
23012
- Typography,
23013
- {
23014
- variant: "small",
23015
- className: cn("flex-1", isDanger && "text-red-600"),
23016
- children: item.label
23017
- }
23018
- ),
23019
- item.badge !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", children: item.badge }),
23020
- hasSubMenu && /* @__PURE__ */ jsx(
23021
- Icon,
23022
- {
23023
- name: direction === "rtl" ? "chevron-left" : "chevron-right",
23024
- size: "sm",
23025
- className: "flex-shrink-0"
23026
- }
23027
- )
23028
- ] })
23029
- }
23030
- ),
23031
- hasSubMenu && activeSubMenu === itemId && item.subMenu && /* @__PURE__ */ jsx(
23032
- SubMenu,
23033
- {
23034
- items: item.subMenu,
23035
- itemRef: activeSubMenuRef,
23036
- direction,
23037
- eventBus
23038
- }
23039
- )
23040
- ] }, itemId);
24002
+ return /* @__PURE__ */ jsx(
24003
+ MenuItemRow,
24004
+ {
24005
+ item,
24006
+ itemId,
24007
+ hasSubMenu,
24008
+ isDanger,
24009
+ direction,
24010
+ isSubMenuOpen: activeSubMenu === itemId,
24011
+ activeSubMenuRef,
24012
+ eventBus,
24013
+ onItemClick: handleItemClick,
24014
+ openSubMenu
24015
+ },
24016
+ itemId
24017
+ );
23041
24018
  });
23042
24019
  const panel = isOpen && triggerRect ? /* @__PURE__ */ jsxs(
23043
24020
  "div",
@@ -23102,14 +24079,14 @@ function useDataDnd(args) {
23102
24079
  const isZone = Boolean(dragGroup || accepts || sortable);
23103
24080
  const enabled = isZone || Boolean(dndRoot);
23104
24081
  const eventBus = useEventBus();
23105
- const parentRoot = React82__default.useContext(RootCtx);
24082
+ const parentRoot = React85__default.useContext(RootCtx);
23106
24083
  const isRoot = enabled && parentRoot === null;
23107
- const zoneId = React82__default.useId();
24084
+ const zoneId = React85__default.useId();
23108
24085
  const ownGroup = dragGroup ?? accepts ?? zoneId;
23109
- const [optimisticOrders, setOptimisticOrders] = React82__default.useState(() => /* @__PURE__ */ new Map());
23110
- const optimisticOrdersRef = React82__default.useRef(optimisticOrders);
24086
+ const [optimisticOrders, setOptimisticOrders] = React85__default.useState(() => /* @__PURE__ */ new Map());
24087
+ const optimisticOrdersRef = React85__default.useRef(optimisticOrders);
23111
24088
  optimisticOrdersRef.current = optimisticOrders;
23112
- const clearOptimisticOrder = React82__default.useCallback((group) => {
24089
+ const clearOptimisticOrder = React85__default.useCallback((group) => {
23113
24090
  setOptimisticOrders((prev) => {
23114
24091
  if (!prev.has(group)) return prev;
23115
24092
  const next = new Map(prev);
@@ -23134,7 +24111,7 @@ function useDataDnd(args) {
23134
24111
  const raw = it[dndItemIdField];
23135
24112
  return String(raw ?? `__idx_${idx}`);
23136
24113
  }).join("|");
23137
- const itemIds = React82__default.useMemo(
24114
+ const itemIds = React85__default.useMemo(
23138
24115
  () => orderedItems.map((it, idx) => {
23139
24116
  const raw = it[dndItemIdField];
23140
24117
  return raw ?? `__idx_${idx}`;
@@ -23142,7 +24119,7 @@ function useDataDnd(args) {
23142
24119
  [itemIdsSignature]
23143
24120
  );
23144
24121
  const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
23145
- React82__default.useEffect(() => {
24122
+ React85__default.useEffect(() => {
23146
24123
  const root = isRoot ? null : parentRoot;
23147
24124
  if (root) {
23148
24125
  root.clearOptimisticOrder(ownGroup);
@@ -23150,20 +24127,20 @@ function useDataDnd(args) {
23150
24127
  clearOptimisticOrder(ownGroup);
23151
24128
  }
23152
24129
  }, [itemsContentSig, ownGroup]);
23153
- const zonesRef = React82__default.useRef(/* @__PURE__ */ new Map());
23154
- const registerZone = React82__default.useCallback((zoneId2, meta2) => {
24130
+ const zonesRef = React85__default.useRef(/* @__PURE__ */ new Map());
24131
+ const registerZone = React85__default.useCallback((zoneId2, meta2) => {
23155
24132
  zonesRef.current.set(zoneId2, meta2);
23156
24133
  }, []);
23157
- const unregisterZone = React82__default.useCallback((zoneId2) => {
24134
+ const unregisterZone = React85__default.useCallback((zoneId2) => {
23158
24135
  zonesRef.current.delete(zoneId2);
23159
24136
  }, []);
23160
- const [activeDrag, setActiveDrag] = React82__default.useState(null);
23161
- const [overZoneGroup, setOverZoneGroup] = React82__default.useState(null);
23162
- const meta = React82__default.useMemo(
24137
+ const [activeDrag, setActiveDrag] = React85__default.useState(null);
24138
+ const [overZoneGroup, setOverZoneGroup] = React85__default.useState(null);
24139
+ const meta = React85__default.useMemo(
23163
24140
  () => ({ group: ownGroup, dropEvent, reorderEvent, positionEvent, itemIds, rawItems: items, idField: dndItemIdField }),
23164
24141
  [ownGroup, dropEvent, reorderEvent, positionEvent, itemIds, items, dndItemIdField]
23165
24142
  );
23166
- React82__default.useEffect(() => {
24143
+ React85__default.useEffect(() => {
23167
24144
  const target = isRoot ? null : parentRoot;
23168
24145
  if (!target) {
23169
24146
  zonesRef.current.set(zoneId, meta);
@@ -23182,7 +24159,7 @@ function useDataDnd(args) {
23182
24159
  }, [parentRoot, isRoot, zoneId, meta]);
23183
24160
  const sensors = useAlmadarDndSensors(true);
23184
24161
  const collisionDetection = almadarDndCollisionDetection;
23185
- const findZoneByItem = React82__default.useCallback(
24162
+ const findZoneByItem = React85__default.useCallback(
23186
24163
  (id) => {
23187
24164
  for (const z of zonesRef.current.values()) {
23188
24165
  if (z.itemIds.includes(id)) return z;
@@ -23191,7 +24168,7 @@ function useDataDnd(args) {
23191
24168
  },
23192
24169
  []
23193
24170
  );
23194
- React82__default.useCallback(
24171
+ React85__default.useCallback(
23195
24172
  (group) => {
23196
24173
  for (const z of zonesRef.current.values()) {
23197
24174
  if (z.group === group) return z;
@@ -23200,7 +24177,7 @@ function useDataDnd(args) {
23200
24177
  },
23201
24178
  []
23202
24179
  );
23203
- const handleDragEnd = React82__default.useCallback(
24180
+ const handleDragEnd = React85__default.useCallback(
23204
24181
  (event) => {
23205
24182
  const { active, over } = event;
23206
24183
  const activeIdStr = String(active.id);
@@ -23291,8 +24268,8 @@ function useDataDnd(args) {
23291
24268
  },
23292
24269
  [eventBus]
23293
24270
  );
23294
- const sortableData = React82__default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
23295
- const SortableItem = React82__default.useCallback(
24271
+ const sortableData = React85__default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
24272
+ const SortableItem = React85__default.useCallback(
23296
24273
  ({ id, children }) => {
23297
24274
  const {
23298
24275
  attributes,
@@ -23332,7 +24309,7 @@ function useDataDnd(args) {
23332
24309
  id: droppableId,
23333
24310
  data: sortableData
23334
24311
  });
23335
- const ctx = React82__default.useContext(RootCtx);
24312
+ const ctx = React85__default.useContext(RootCtx);
23336
24313
  const activeDrag2 = ctx?.activeDrag ?? null;
23337
24314
  const overZoneGroup2 = ctx?.overZoneGroup ?? null;
23338
24315
  const isThisZoneOver = overZoneGroup2 === ownGroup;
@@ -23347,7 +24324,7 @@ function useDataDnd(args) {
23347
24324
  showForeignPlaceholder,
23348
24325
  ctxAvailable: ctx != null
23349
24326
  });
23350
- React82__default.useEffect(() => {
24327
+ React85__default.useEffect(() => {
23351
24328
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
23352
24329
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
23353
24330
  return /* @__PURE__ */ jsx(
@@ -23361,11 +24338,11 @@ function useDataDnd(args) {
23361
24338
  }
23362
24339
  );
23363
24340
  };
23364
- const rootContextValue = React82__default.useMemo(
24341
+ const rootContextValue = React85__default.useMemo(
23365
24342
  () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
23366
24343
  [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
23367
24344
  );
23368
- const handleDragStart = React82__default.useCallback((event) => {
24345
+ const handleDragStart = React85__default.useCallback((event) => {
23369
24346
  const sourceZone = findZoneByItem(event.active.id);
23370
24347
  const rect = event.active.rect.current.initial;
23371
24348
  const height = rect?.height && rect.height > 0 ? rect.height : 64;
@@ -23384,7 +24361,7 @@ function useDataDnd(args) {
23384
24361
  isRoot
23385
24362
  });
23386
24363
  }, [findZoneByItem, isRoot, zoneId]);
23387
- const handleDragOver = React82__default.useCallback((event) => {
24364
+ const handleDragOver = React85__default.useCallback((event) => {
23388
24365
  const { active, over } = event;
23389
24366
  const overData = over?.data?.current;
23390
24367
  const overGroup = overData?.dndGroup ?? null;
@@ -23454,7 +24431,7 @@ function useDataDnd(args) {
23454
24431
  return next;
23455
24432
  });
23456
24433
  }, []);
23457
- const handleDragCancel = React82__default.useCallback((event) => {
24434
+ const handleDragCancel = React85__default.useCallback((event) => {
23458
24435
  setActiveDrag(null);
23459
24436
  setOverZoneGroup(null);
23460
24437
  dndLog.warn("dragCancel", {
@@ -23462,12 +24439,12 @@ function useDataDnd(args) {
23462
24439
  reason: "dnd-kit cancelled the drag (escape key, pointer interrupted, or external)"
23463
24440
  });
23464
24441
  }, []);
23465
- const handleDragEndWithCleanup = React82__default.useCallback((event) => {
24442
+ const handleDragEndWithCleanup = React85__default.useCallback((event) => {
23466
24443
  handleDragEnd(event);
23467
24444
  setActiveDrag(null);
23468
24445
  setOverZoneGroup(null);
23469
24446
  }, [handleDragEnd]);
23470
- const wrapContainer = React82__default.useCallback(
24447
+ const wrapContainer = React85__default.useCallback(
23471
24448
  (children) => {
23472
24449
  if (!enabled) return children;
23473
24450
  const strategy = layout === "grid" ? rectSortingStrategy : verticalListSortingStrategy;
@@ -23521,7 +24498,7 @@ var init_useDataDnd = __esm({
23521
24498
  init_useAlmadarDndCollision();
23522
24499
  init_Box();
23523
24500
  dndLog = createLogger("almadar:ui:dnd");
23524
- RootCtx = React82__default.createContext(null);
24501
+ RootCtx = React85__default.createContext(null);
23525
24502
  }
23526
24503
  });
23527
24504
  function renderIconInput(icon, props) {
@@ -24047,7 +25024,7 @@ function DataList({
24047
25024
  }) {
24048
25025
  const eventBus = useEventBus();
24049
25026
  const { t } = useTranslate();
24050
- const [visibleCount, setVisibleCount] = React82__default.useState(pageSize || Infinity);
25027
+ const [visibleCount, setVisibleCount] = React85__default.useState(pageSize || Infinity);
24051
25028
  const fieldDefs = fields ?? columns ?? [];
24052
25029
  const allDataRaw = Array.isArray(entity) ? entity : entity ? [entity] : [];
24053
25030
  const dnd = useDataDnd({
@@ -24066,7 +25043,7 @@ function DataList({
24066
25043
  const data = pageSize > 0 ? allData.slice(0, visibleCount) : allData;
24067
25044
  const hasMoreLocal = pageSize > 0 && visibleCount < allData.length;
24068
25045
  const hasRenderProp = typeof children === "function";
24069
- React82__default.useEffect(() => {
25046
+ React85__default.useEffect(() => {
24070
25047
  const renderItemTypeOf = typeof schemaRenderItem;
24071
25048
  const childrenTypeOf = typeof children;
24072
25049
  if (data.length > 0 && !hasRenderProp) {
@@ -24171,7 +25148,7 @@ function DataList({
24171
25148
  const items2 = data.map((item) => item);
24172
25149
  const groups2 = groupBy ? groupData(items2, groupBy) : [{ label: "", items: items2 }];
24173
25150
  const contentField = titleField?.name ?? fieldDefs[0]?.name ?? "";
24174
- return /* @__PURE__ */ jsx(VStack, { gap: "sm", className: cn("py-2", className), children: groups2.map((group, gi) => /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
25151
+ return /* @__PURE__ */ jsx(VStack, { gap: "sm", className: cn("py-2", className), children: groups2.map((group, gi) => /* @__PURE__ */ jsxs(React85__default.Fragment, { children: [
24175
25152
  group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: "my-2" }),
24176
25153
  group.items.map((itemData, index) => {
24177
25154
  const id = itemData.id || `${gi}-${index}`;
@@ -24312,7 +25289,7 @@ function DataList({
24312
25289
  className
24313
25290
  ),
24314
25291
  children: [
24315
- groups.map((group, gi) => /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
25292
+ groups.map((group, gi) => /* @__PURE__ */ jsxs(React85__default.Fragment, { children: [
24316
25293
  group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: gi > 0 ? "mt-4" : "mt-0" }),
24317
25294
  group.items.map(
24318
25295
  (itemData, index) => renderItem(itemData, index, gi === groups.length - 1 && index === group.items.length - 1)
@@ -25228,7 +26205,7 @@ var init_Grid = __esm({
25228
26205
  as: Component2 = "div"
25229
26206
  }) => {
25230
26207
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
25231
- return React82__default.createElement(
26208
+ return React85__default.createElement(
25232
26209
  Component2,
25233
26210
  {
25234
26211
  className: cn(
@@ -25340,6 +26317,7 @@ var init_Popover = __esm({
25340
26317
  "use client";
25341
26318
  init_Typography();
25342
26319
  init_cn();
26320
+ init_useTapReveal();
25343
26321
  arrowClasses = {
25344
26322
  top: "top-full left-1/2 -translate-x-1/2 border-t-white border-l-transparent border-r-transparent border-b-transparent",
25345
26323
  bottom: "bottom-full left-1/2 -translate-x-1/2 border-b-white border-l-transparent border-r-transparent border-t-transparent",
@@ -25410,18 +26388,32 @@ var init_Popover = __esm({
25410
26388
  document.addEventListener("mousedown", handleClickOutside);
25411
26389
  return () => document.removeEventListener("mousedown", handleClickOutside);
25412
26390
  }, [isOpen, trigger]);
25413
- const triggerProps = trigger === "click" ? {
26391
+ const { triggerProps: tapTriggerProps } = useTapReveal({
26392
+ enabled: trigger === "hover",
26393
+ onReveal: handleOpen,
26394
+ onDismiss: handleClose,
26395
+ refs: [triggerRef, popoverRef]
26396
+ });
26397
+ const handlerProps = trigger === "click" ? {
25414
26398
  onClick: handleToggle
25415
26399
  } : {
25416
26400
  onMouseEnter: handleOpen,
25417
- onMouseLeave: handleClose
26401
+ onMouseLeave: handleClose,
26402
+ onPointerDown: tapTriggerProps.onPointerDown
25418
26403
  };
25419
- const childElement = React82__default.isValidElement(children) ? children : /* @__PURE__ */ jsx("span", { children });
25420
- const triggerElement = React82__default.cloneElement(
26404
+ const childElement = React85__default.isValidElement(children) ? children : /* @__PURE__ */ jsx("span", { children });
26405
+ const childPointerDown = childElement.props.onPointerDown;
26406
+ const triggerElement = React85__default.cloneElement(
25421
26407
  childElement,
25422
26408
  {
25423
26409
  ref: triggerRef,
25424
- ...triggerProps
26410
+ ...handlerProps,
26411
+ ...trigger === "hover" ? {
26412
+ onPointerDown: (e) => {
26413
+ tapTriggerProps.onPointerDown(e);
26414
+ childPointerDown?.(e);
26415
+ }
26416
+ } : void 0
25425
26417
  }
25426
26418
  );
25427
26419
  const panel = isOpen && triggerRect ? /* @__PURE__ */ jsxs(
@@ -25952,6 +26944,7 @@ var init_Tooltip = __esm({
25952
26944
  "use client";
25953
26945
  init_Typography();
25954
26946
  init_cn();
26947
+ init_useTapReveal();
25955
26948
  TRIGGER_GAP2 = 8;
25956
26949
  arrowClasses2 = {
25957
26950
  top: "top-full left-1/2 -translate-x-1/2 border-t-primary border-l-transparent border-r-transparent border-b-transparent",
@@ -25996,6 +26989,11 @@ var init_Tooltip = __esm({
25996
26989
  setIsVisible(false);
25997
26990
  }, hideDelay);
25998
26991
  };
26992
+ const { triggerProps } = useTapReveal({
26993
+ onReveal: handleMouseEnter,
26994
+ onDismiss: handleMouseLeave,
26995
+ refs: [triggerRef, tooltipRef]
26996
+ });
25999
26997
  useLayoutEffect(() => {
26000
26998
  if (isVisible) {
26001
26999
  updatePosition();
@@ -26007,13 +27005,18 @@ var init_Tooltip = __esm({
26007
27005
  if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
26008
27006
  };
26009
27007
  }, []);
26010
- const triggerElement = React82__default.isValidElement(children) ? children : /* @__PURE__ */ jsx("span", { children });
26011
- const trigger = React82__default.cloneElement(triggerElement, {
27008
+ const triggerElement = React85__default.isValidElement(children) ? children : /* @__PURE__ */ jsx("span", { children });
27009
+ const childPointerDown = triggerElement.props.onPointerDown;
27010
+ const trigger = React85__default.cloneElement(triggerElement, {
26012
27011
  ref: triggerRef,
26013
27012
  onMouseEnter: handleMouseEnter,
26014
27013
  onMouseLeave: handleMouseLeave,
26015
27014
  onFocus: handleMouseEnter,
26016
- onBlur: handleMouseLeave
27015
+ onBlur: handleMouseLeave,
27016
+ onPointerDown: (e) => {
27017
+ triggerProps.onPointerDown(e);
27018
+ childPointerDown?.(e);
27019
+ }
26017
27020
  });
26018
27021
  const tooltipContent = isVisible && triggerRect ? /* @__PURE__ */ jsxs(
26019
27022
  "div",
@@ -26094,7 +27097,7 @@ var init_WizardProgress = __esm({
26094
27097
  children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: normalizedSteps.map((step, index) => {
26095
27098
  const isActive = index === currentStep;
26096
27099
  const isCompleted = index < currentStep;
26097
- return /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
27100
+ return /* @__PURE__ */ jsxs(React85__default.Fragment, { children: [
26098
27101
  /* @__PURE__ */ jsx(
26099
27102
  "button",
26100
27103
  {
@@ -27055,7 +28058,7 @@ var init_ProgressDots = __esm({
27055
28058
  }
27056
28059
  });
27057
28060
  function StatBadge({
27058
- assetUrl = DEFAULT_ASSET_URL12,
28061
+ assetUrl,
27059
28062
  label,
27060
28063
  value = 0,
27061
28064
  max,
@@ -27122,7 +28125,7 @@ function StatBadge({
27122
28125
  }
27123
28126
  );
27124
28127
  }
27125
- var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
28128
+ var sizeMap17, variantMap2;
27126
28129
  var init_StatBadge = __esm({
27127
28130
  "components/game/molecules/StatBadge.tsx"() {
27128
28131
  init_cn();
@@ -27141,7 +28144,6 @@ var init_StatBadge = __esm({
27141
28144
  warning: "bg-warning/15 border-warning/40 text-foreground",
27142
28145
  danger: "bg-error/15 border-error/40 text-foreground"
27143
28146
  };
27144
- DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
27145
28147
  StatBadge.displayName = "StatBadge";
27146
28148
  }
27147
28149
  });
@@ -27158,7 +28160,7 @@ function InventoryGrid({
27158
28160
  const eventBus = useEventBus();
27159
28161
  const slotCount = totalSlots ?? items.length;
27160
28162
  const emptySlotCount = Math.max(0, slotCount - items.length);
27161
- const handleSelect = React82.useCallback(
28163
+ const handleSelect = React85.useCallback(
27162
28164
  (id) => {
27163
28165
  onSelect?.(id);
27164
28166
  if (selectEvent) {
@@ -27389,35 +28391,37 @@ function GameCanvas2D({
27389
28391
  drawEvent,
27390
28392
  fps = 60,
27391
28393
  backgroundImage = "",
27392
- assetBaseUrl = "https://almadar-kflow-assets.web.app/shared/",
28394
+ assetBaseUrl,
27393
28395
  className
27394
28396
  }) {
27395
- const canvasRef = React82.useRef(null);
27396
- const rafRef = React82.useRef(0);
27397
- const frameRef = React82.useRef(0);
27398
- const lastTimeRef = React82.useRef(0);
27399
- const imageCache = React82.useRef(/* @__PURE__ */ new Map());
28397
+ const canvasRef = React85.useRef(null);
28398
+ const rafRef = React85.useRef(0);
28399
+ const frameRef = React85.useRef(0);
28400
+ const lastTimeRef = React85.useRef(0);
28401
+ const imageCache = React85.useRef(/* @__PURE__ */ new Map());
27400
28402
  const emit = useEmitEvent();
27401
- const onDrawRef = React82.useRef(onDraw);
28403
+ const onDrawRef = React85.useRef(onDraw);
27402
28404
  onDrawRef.current = onDraw;
27403
- const onTickRef = React82.useRef(onTick);
28405
+ const onTickRef = React85.useRef(onTick);
27404
28406
  onTickRef.current = onTick;
27405
- const tickEventRef = React82.useRef(tickEvent);
28407
+ const tickEventRef = React85.useRef(tickEvent);
27406
28408
  tickEventRef.current = tickEvent;
27407
- const drawEventRef = React82.useRef(drawEvent);
28409
+ const drawEventRef = React85.useRef(drawEvent);
27408
28410
  drawEventRef.current = drawEvent;
27409
- const emitRef = React82.useRef(emit);
28411
+ const emitRef = React85.useRef(emit);
27410
28412
  emitRef.current = emit;
27411
- const assetBaseUrlRef = React82.useRef(assetBaseUrl);
28413
+ const assetBaseUrlRef = React85.useRef(assetBaseUrl);
27412
28414
  assetBaseUrlRef.current = assetBaseUrl;
27413
- const backgroundImageRef = React82.useRef(backgroundImage);
28415
+ const backgroundImageRef = React85.useRef(backgroundImage);
27414
28416
  backgroundImageRef.current = backgroundImage;
27415
- const widthRef = React82.useRef(width);
28417
+ const widthRef = React85.useRef(width);
27416
28418
  widthRef.current = width;
27417
- const heightRef = React82.useRef(height);
28419
+ const heightRef = React85.useRef(height);
27418
28420
  heightRef.current = height;
27419
- const loadImage = React82.useCallback((url) => {
27420
- const fullUrl = url.startsWith("http") ? url : `${assetBaseUrlRef.current}${url}`;
28421
+ const loadImage = React85.useCallback((url) => {
28422
+ const base = assetBaseUrlRef.current;
28423
+ if (!url.startsWith("http") && !base) return null;
28424
+ const fullUrl = url.startsWith("http") ? url : `${base}${url}`;
27421
28425
  const cached = imageCache.current.get(fullUrl);
27422
28426
  if (cached?.complete && cached.naturalWidth > 0) return cached;
27423
28427
  if (!cached) {
@@ -27428,7 +28432,7 @@ function GameCanvas2D({
27428
28432
  }
27429
28433
  return null;
27430
28434
  }, []);
27431
- React82.useEffect(() => {
28435
+ React85.useEffect(() => {
27432
28436
  const canvas = canvasRef.current;
27433
28437
  if (!canvas) return;
27434
28438
  const ctx = canvas.getContext("2d");
@@ -27740,7 +28744,7 @@ function TurnPanel({
27740
28744
  className
27741
28745
  }) {
27742
28746
  const eventBus = useEventBus();
27743
- const handleAction = React82.useCallback(
28747
+ const handleAction = React85.useCallback(
27744
28748
  (event) => {
27745
28749
  if (event) {
27746
28750
  eventBus.emit(event, { turn: currentTurn, phase, activeTeam });
@@ -27798,7 +28802,7 @@ var init_TurnPanel = __esm({
27798
28802
  }
27799
28803
  });
27800
28804
  function EnemyPlate({
27801
- assetUrl = DEFAULT_ASSET_URL13,
28805
+ assetUrl,
27802
28806
  name = "Shadow Guard",
27803
28807
  health = 80,
27804
28808
  maxHealth = 100,
@@ -27882,7 +28886,7 @@ function EnemyPlate({
27882
28886
  }
27883
28887
  );
27884
28888
  }
27885
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
28889
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
27886
28890
  var init_EnemyPlate = __esm({
27887
28891
  "components/game/molecules/EnemyPlate.tsx"() {
27888
28892
  "use client";
@@ -27899,7 +28903,6 @@ var init_EnemyPlate = __esm({
27899
28903
  DEFAULT_ENEMY_EFFECTS = [
27900
28904
  { icon: "flame", label: "Burn", variant: "debuff" }
27901
28905
  ];
27902
- DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
27903
28906
  EnemyPlate.displayName = "EnemyPlate";
27904
28907
  }
27905
28908
  });
@@ -27909,7 +28912,7 @@ function UnitCommandBar({
27909
28912
  className
27910
28913
  }) {
27911
28914
  const eventBus = useEventBus();
27912
- const handleCommand = React82.useCallback(
28915
+ const handleCommand = React85.useCallback(
27913
28916
  (event) => {
27914
28917
  if (event) {
27915
28918
  eventBus.emit(event, { unitId: selectedUnitId });
@@ -28050,6 +29053,9 @@ function DialogueBox({
28050
29053
  completeEvent,
28051
29054
  choiceEvent,
28052
29055
  advanceEvent,
29056
+ backgroundImage,
29057
+ portraitUrl,
29058
+ portraitScale = 1,
28053
29059
  className
28054
29060
  }) {
28055
29061
  const eventBus = useEventBus();
@@ -28165,64 +29171,98 @@ function DialogueBox({
28165
29171
  }
28166
29172
  }, [isTyping, skipTypewriter, dialogue.choices, selectedChoice, onChoice, onAdvance, choiceEvent, advanceEvent, eventBus]);
28167
29173
  const enabledChoices = dialogue.choices?.filter((c) => !c.disabled) ?? [];
28168
- return /* @__PURE__ */ jsx(
28169
- "div",
28170
- {
28171
- className: cn(
28172
- "fixed left-0 right-0 z-40",
28173
- position === "top" ? "top-0" : "bottom-0",
28174
- className
28175
- ),
28176
- onClick: handleClick,
28177
- onKeyDown: handleKeyDown,
28178
- tabIndex: 0,
28179
- role: "dialog",
28180
- "aria-label": "Dialogue",
28181
- children: /* @__PURE__ */ jsx("div", { className: "mx-4 my-4 bg-[var(--color-card)] bg-opacity-95 border-2 border-border rounded-container overflow-hidden", children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
28182
- dialogue.portrait && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 p-4 border-r border-border", children: /* @__PURE__ */ jsx(
29174
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
29175
+ backgroundImage && /* @__PURE__ */ jsx(
29176
+ "div",
29177
+ {
29178
+ className: "fixed inset-0 z-20 bg-center bg-cover bg-no-repeat",
29179
+ style: { backgroundImage: `url(${backgroundImage})` },
29180
+ "aria-hidden": "true"
29181
+ }
29182
+ ),
29183
+ portraitUrl && /* @__PURE__ */ jsx(
29184
+ "div",
29185
+ {
29186
+ className: cn(
29187
+ "fixed left-1/2 -translate-x-1/2 z-30 pointer-events-none flex items-end",
29188
+ position === "top" ? "top-0" : "bottom-0"
29189
+ ),
29190
+ style: { height: `${60 * portraitScale}vh` },
29191
+ children: /* @__PURE__ */ jsx(
28183
29192
  "img",
28184
29193
  {
28185
- src: dialogue.portrait,
29194
+ src: portraitUrl,
28186
29195
  alt: dialogue.speaker,
28187
- className: "w-24 h-24 object-contain",
28188
- style: { imageRendering: "pixelated" }
29196
+ className: "h-full w-auto object-contain drop-shadow-2xl"
28189
29197
  }
28190
- ) }),
28191
- /* @__PURE__ */ jsxs("div", { className: "flex-1 p-4", children: [
28192
- /* @__PURE__ */ jsx("div", { className: "text-warning font-bold mb-2", children: dialogue.speaker }),
28193
- /* @__PURE__ */ jsxs("div", { className: "text-[var(--color-foreground)] text-lg leading-relaxed min-h-[60px]", children: [
28194
- displayedText,
28195
- isTyping && /* @__PURE__ */ jsx("span", { className: "animate-pulse", children: "\u258C" })
28196
- ] }),
28197
- !isTyping && enabledChoices.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: enabledChoices.map((choice, index) => /* @__PURE__ */ jsxs(
28198
- "button",
29198
+ )
29199
+ }
29200
+ ),
29201
+ /* @__PURE__ */ jsx(
29202
+ "div",
29203
+ {
29204
+ className: cn(
29205
+ "fixed left-0 right-0 z-40",
29206
+ position === "top" ? "top-0" : "bottom-0",
29207
+ className
29208
+ ),
29209
+ onClick: handleClick,
29210
+ onKeyDown: handleKeyDown,
29211
+ tabIndex: 0,
29212
+ role: "dialog",
29213
+ "aria-label": "Dialogue",
29214
+ children: /* @__PURE__ */ jsx("div", { className: cn(
29215
+ "mx-4 my-4 border-2 rounded-container overflow-hidden",
29216
+ backgroundImage ? "bg-black/80 backdrop-blur-sm text-white border-white/20" : "bg-[var(--color-card)] bg-opacity-95 border-border"
29217
+ ), children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
29218
+ dialogue.portrait && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 p-4 border-r border-border", children: /* @__PURE__ */ jsx(
29219
+ "img",
28199
29220
  {
28200
- type: "button",
28201
- className: cn(
28202
- "block w-full text-left px-4 py-2 rounded-interactive transition-colors",
28203
- "hover:bg-[var(--color-surface,#374151)] focus:outline-none focus:ring-2 focus:ring-warning",
28204
- selectedChoice === index ? "bg-[var(--color-surface,#374151)] text-warning" : "bg-[var(--color-card)] text-[var(--color-foreground)]"
28205
- ),
28206
- onClick: (e) => {
28207
- e.stopPropagation();
28208
- if (choiceEvent) eventBus.emit(`UI:${choiceEvent}`, { choice });
28209
- onChoice?.(choice);
29221
+ src: dialogue.portrait,
29222
+ alt: dialogue.speaker,
29223
+ className: "w-24 h-24 object-contain",
29224
+ style: { imageRendering: "pixelated" }
29225
+ }
29226
+ ) }),
29227
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 p-4", children: [
29228
+ /* @__PURE__ */ jsx("div", { className: "text-warning font-bold mb-2", children: dialogue.speaker }),
29229
+ /* @__PURE__ */ jsxs("div", { className: cn(
29230
+ "text-lg leading-relaxed min-h-[60px]",
29231
+ backgroundImage ? "text-white" : "text-[var(--color-foreground)]"
29232
+ ), children: [
29233
+ displayedText,
29234
+ isTyping && /* @__PURE__ */ jsx("span", { className: "animate-pulse", children: "\u258C" })
29235
+ ] }),
29236
+ !isTyping && enabledChoices.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: enabledChoices.map((choice, index) => /* @__PURE__ */ jsxs(
29237
+ "button",
29238
+ {
29239
+ type: "button",
29240
+ className: cn(
29241
+ "block w-full text-left px-4 py-2 rounded-interactive transition-colors",
29242
+ "hover:bg-[var(--color-surface,#374151)] focus:outline-none focus:ring-2 focus:ring-warning",
29243
+ selectedChoice === index ? "bg-[var(--color-surface,#374151)] text-warning" : backgroundImage ? "bg-white/10 text-white hover:bg-white/20" : "bg-[var(--color-card)] text-[var(--color-foreground)]"
29244
+ ),
29245
+ onClick: (e) => {
29246
+ e.stopPropagation();
29247
+ if (choiceEvent) eventBus.emit(`UI:${choiceEvent}`, { choice });
29248
+ onChoice?.(choice);
29249
+ },
29250
+ children: [
29251
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground mr-2", children: [
29252
+ index + 1,
29253
+ "."
29254
+ ] }),
29255
+ choice.text
29256
+ ]
28210
29257
  },
28211
- children: [
28212
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground mr-2", children: [
28213
- index + 1,
28214
- "."
28215
- ] }),
28216
- choice.text
28217
- ]
28218
- },
28219
- index
28220
- )) }),
28221
- !isTyping && !dialogue.choices?.length && /* @__PURE__ */ jsx("div", { className: "mt-4 text-muted-foreground text-sm animate-pulse", children: "Press SPACE or click to continue..." })
28222
- ] })
28223
- ] }) })
28224
- }
28225
- );
29258
+ index
29259
+ )) }),
29260
+ !isTyping && !dialogue.choices?.length && /* @__PURE__ */ jsx("div", { className: "mt-4 text-muted-foreground text-sm animate-pulse", children: "Press SPACE or click to continue..." })
29261
+ ] })
29262
+ ] }) })
29263
+ }
29264
+ )
29265
+ ] });
28226
29266
  }
28227
29267
  var DEFAULT_DIALOGUE;
28228
29268
  var init_DialogueBox = __esm({
@@ -28423,7 +29463,7 @@ function GameMenu({
28423
29463
  } catch {
28424
29464
  }
28425
29465
  const eventBus = eventBusProp || eventBusFromHook;
28426
- const handleOptionClick = React82.useCallback(
29466
+ const handleOptionClick = React85.useCallback(
28427
29467
  (option) => {
28428
29468
  if (option.event && eventBus) {
28429
29469
  eventBus.emit(`UI:${option.event}`, { option });
@@ -28542,7 +29582,7 @@ function GameOverScreen({
28542
29582
  } catch {
28543
29583
  }
28544
29584
  const eventBus = eventBusProp || eventBusFromHook;
28545
- const handleActionClick = React82.useCallback(
29585
+ const handleActionClick = React85.useCallback(
28546
29586
  (action) => {
28547
29587
  if (action.event && eventBus) {
28548
29588
  eventBus.emit(`UI:${action.event}`, { action });
@@ -28749,10 +29789,10 @@ function PlatformerCanvas({
28749
29789
  canvasHeight = 400,
28750
29790
  followCamera = true,
28751
29791
  bgColor = "#5c94fc",
28752
- playerSprite = "https://almadar-kflow-assets.web.app/shared/platformer/characters/platformChar_idle.png",
29792
+ playerSprite,
28753
29793
  tileSprites,
28754
29794
  backgroundImage = "",
28755
- assetBaseUrl = "https://almadar-kflow-assets.web.app/shared/platformer/",
29795
+ assetBaseUrl,
28756
29796
  leftEvent = "MOVE_LEFT",
28757
29797
  rightEvent = "MOVE_RIGHT",
28758
29798
  jumpEvent = "JUMP",
@@ -28766,6 +29806,7 @@ function PlatformerCanvas({
28766
29806
  const imageCache = useRef(/* @__PURE__ */ new Map());
28767
29807
  const [loadedImages, setLoadedImages] = useState(/* @__PURE__ */ new Set());
28768
29808
  const loadImage = useCallback((url) => {
29809
+ if (!url.startsWith("http") && !assetBaseUrl) return null;
28769
29810
  const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
28770
29811
  const cached = imageCache.current.get(fullUrl);
28771
29812
  if (cached?.complete && cached.naturalWidth > 0) {
@@ -29325,6 +30366,14 @@ var init_GraphView = __esm({
29325
30366
  },
29326
30367
  [onNodeClick]
29327
30368
  );
30369
+ const handleNodePointerDown = useCallback(
30370
+ (e, node) => {
30371
+ if (e.pointerType === "mouse") return;
30372
+ handleNodeMouseEnter(node);
30373
+ handleNodeClickInternal(node);
30374
+ },
30375
+ [handleNodeMouseEnter, handleNodeClickInternal]
30376
+ );
29328
30377
  if (nodes.length === 0) {
29329
30378
  return /* @__PURE__ */ jsx(Box, { className: cn("flex items-center justify-center", className), style: { width: w, height: h }, children: /* @__PURE__ */ jsx(Box, { className: "text-muted-foreground text-sm", children: t("display.noGraphData") }) });
29330
30379
  }
@@ -29384,6 +30433,7 @@ var init_GraphView = __esm({
29384
30433
  onMouseEnter: () => handleNodeMouseEnter(node),
29385
30434
  onMouseLeave: handleNodeMouseLeave,
29386
30435
  onClick: () => handleNodeClickInternal(node),
30436
+ onPointerDown: (e) => handleNodePointerDown(e, node),
29387
30437
  children: [
29388
30438
  /* @__PURE__ */ jsx(
29389
30439
  "circle",
@@ -29464,13 +30514,13 @@ var init_MapView = __esm({
29464
30514
  shadowSize: [41, 41]
29465
30515
  });
29466
30516
  L.Marker.prototype.options.icon = defaultIcon;
29467
- const { useEffect: useEffect80, useRef: useRef75, useCallback: useCallback118, useState: useState109 } = React82__default;
30517
+ const { useEffect: useEffect82, useRef: useRef80, useCallback: useCallback125, useState: useState111 } = React85__default;
29468
30518
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29469
30519
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29470
30520
  function MapUpdater({ centerLat, centerLng, zoom }) {
29471
30521
  const map = useMap();
29472
- const prevRef = useRef75({ centerLat, centerLng, zoom });
29473
- useEffect80(() => {
30522
+ const prevRef = useRef80({ centerLat, centerLng, zoom });
30523
+ useEffect82(() => {
29474
30524
  const prev = prevRef.current;
29475
30525
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
29476
30526
  map.setView([centerLat, centerLng], zoom);
@@ -29481,7 +30531,7 @@ var init_MapView = __esm({
29481
30531
  }
29482
30532
  function MapClickHandler({ onMapClick }) {
29483
30533
  const map = useMap();
29484
- useEffect80(() => {
30534
+ useEffect82(() => {
29485
30535
  if (!onMapClick) return;
29486
30536
  const handler = (e) => {
29487
30537
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -29509,8 +30559,8 @@ var init_MapView = __esm({
29509
30559
  showAttribution = true
29510
30560
  }) {
29511
30561
  const eventBus = useEventBus3();
29512
- const [clickedPosition, setClickedPosition] = useState109(null);
29513
- const handleMapClick = useCallback118((lat, lng) => {
30562
+ const [clickedPosition, setClickedPosition] = useState111(null);
30563
+ const handleMapClick = useCallback125((lat, lng) => {
29514
30564
  if (showClickedPin) {
29515
30565
  setClickedPosition({ lat, lng });
29516
30566
  }
@@ -29519,7 +30569,7 @@ var init_MapView = __esm({
29519
30569
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29520
30570
  }
29521
30571
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29522
- const handleMarkerClick = useCallback118((marker) => {
30572
+ const handleMarkerClick = useCallback125((marker) => {
29523
30573
  onMarkerClick?.(marker);
29524
30574
  if (markerClickEvent) {
29525
30575
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -29860,7 +30910,7 @@ var init_StarRating = __esm({
29860
30910
  "aria-valuenow": value,
29861
30911
  tabIndex: readOnly ? void 0 : 0,
29862
30912
  onKeyDown: handleKeyDown,
29863
- onMouseLeave: () => setHoverValue(null),
30913
+ onPointerLeave: () => setHoverValue(null),
29864
30914
  children: Array.from({ length: max }, (_, i) => {
29865
30915
  const fillLevel = Math.max(0, Math.min(1, displayValue - i));
29866
30916
  const isFull = fillLevel >= 1;
@@ -29870,7 +30920,7 @@ var init_StarRating = __esm({
29870
30920
  {
29871
30921
  className: "relative inline-block",
29872
30922
  onClick: () => handleStarClick(i, false),
29873
- onMouseMove: (e) => {
30923
+ onPointerMove: (e) => {
29874
30924
  if (readOnly) return;
29875
30925
  const rect = e.currentTarget.getBoundingClientRect();
29876
30926
  const isLeftHalf = e.clientX - rect.left < rect.width / 2;
@@ -30370,8 +31420,8 @@ function TableView({
30370
31420
  }) {
30371
31421
  const eventBus = useEventBus();
30372
31422
  const { t } = useTranslate();
30373
- const [visibleCount, setVisibleCount] = React82__default.useState(pageSize > 0 ? pageSize : Infinity);
30374
- const [localSelected, setLocalSelected] = React82__default.useState(/* @__PURE__ */ new Set());
31423
+ const [visibleCount, setVisibleCount] = React85__default.useState(pageSize > 0 ? pageSize : Infinity);
31424
+ const [localSelected, setLocalSelected] = React85__default.useState(/* @__PURE__ */ new Set());
30375
31425
  const colDefs = columns ?? fields ?? [];
30376
31426
  const allDataRaw = Array.isArray(entity) ? entity : entity ? [entity] : [];
30377
31427
  const dnd = useDataDnd({
@@ -30566,12 +31616,12 @@ function TableView({
30566
31616
  ]
30567
31617
  }
30568
31618
  );
30569
- return dnd.isZone ? /* @__PURE__ */ jsx(dnd.SortableItem, { id: row[idField] ?? id, children: rowInner }, id) : /* @__PURE__ */ jsx(React82__default.Fragment, { children: rowInner }, id);
31619
+ return dnd.isZone ? /* @__PURE__ */ jsx(dnd.SortableItem, { id: row[idField] ?? id, children: rowInner }, id) : /* @__PURE__ */ jsx(React85__default.Fragment, { children: rowInner }, id);
30570
31620
  };
30571
31621
  const items = data.map((row) => row);
30572
31622
  const groups = groupBy ? groupData2(items, groupBy) : [{ label: "", items }];
30573
31623
  let runningIndex = 0;
30574
- const body = /* @__PURE__ */ jsx(Box, { role: "rowgroup", children: groups.map((group, gi) => /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
31624
+ const body = /* @__PURE__ */ jsx(Box, { role: "rowgroup", children: groups.map((group, gi) => /* @__PURE__ */ jsxs(React85__default.Fragment, { children: [
30575
31625
  group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: gi > 0 ? "mt-3" : "mt-0" }),
30576
31626
  group.items.map((row) => renderRow(row, runningIndex++))
30577
31627
  ] }, gi)) });
@@ -31928,7 +32978,7 @@ var init_StepFlow = __esm({
31928
32978
  className
31929
32979
  }) => {
31930
32980
  if (orientation === "vertical") {
31931
- return /* @__PURE__ */ jsx(VStack, { gap: "none", className: cn("w-full", className), children: steps.map((step, index) => /* @__PURE__ */ jsx(React82__default.Fragment, { children: /* @__PURE__ */ jsxs(HStack, { gap: "md", align: "start", className: "w-full", children: [
32981
+ return /* @__PURE__ */ jsx(VStack, { gap: "none", className: cn("w-full", className), children: steps.map((step, index) => /* @__PURE__ */ jsx(React85__default.Fragment, { children: /* @__PURE__ */ jsxs(HStack, { gap: "md", align: "start", className: "w-full", children: [
31932
32982
  /* @__PURE__ */ jsxs(VStack, { gap: "none", align: "center", children: [
31933
32983
  /* @__PURE__ */ jsx(StepCircle, { step, index }),
31934
32984
  showConnectors && index < steps.length - 1 && /* @__PURE__ */ jsx(Box, { className: "w-px h-8 bg-border" })
@@ -31939,7 +32989,7 @@ var init_StepFlow = __esm({
31939
32989
  ] })
31940
32990
  ] }) }, index)) });
31941
32991
  }
31942
- return /* @__PURE__ */ jsx(Box, { className: cn("w-full flex flex-col md:flex-row items-start gap-0", className), children: steps.map((step, index) => /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
32992
+ return /* @__PURE__ */ jsx(Box, { className: cn("w-full flex flex-col md:flex-row items-start gap-0", className), children: steps.map((step, index) => /* @__PURE__ */ jsxs(React85__default.Fragment, { children: [
31943
32993
  /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", className: "flex-1 w-full md:w-auto", children: [
31944
32994
  /* @__PURE__ */ jsx(StepCircle, { step, index }),
31945
32995
  /* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-center", children: step.title }),
@@ -32924,7 +33974,7 @@ var init_LikertScale = __esm({
32924
33974
  md: "text-base",
32925
33975
  lg: "text-lg"
32926
33976
  };
32927
- LikertScale = React82__default.forwardRef(
33977
+ LikertScale = React85__default.forwardRef(
32928
33978
  ({
32929
33979
  question,
32930
33980
  options = DEFAULT_LIKERT_OPTIONS,
@@ -32936,7 +33986,7 @@ var init_LikertScale = __esm({
32936
33986
  variant = "radios",
32937
33987
  className
32938
33988
  }, ref) => {
32939
- const groupId = React82__default.useId();
33989
+ const groupId = React85__default.useId();
32940
33990
  const eventBus = useEventBus();
32941
33991
  const handleSelect = useCallback(
32942
33992
  (next) => {
@@ -33760,7 +34810,7 @@ var init_PositionedCanvas = __esm({
33760
34810
  {
33761
34811
  ref: containerRef,
33762
34812
  "data-testid": "positioned-canvas",
33763
- className: "relative bg-background border border-border rounded-container overflow-hidden",
34813
+ className: "relative bg-background border border-border rounded-container overflow-hidden touch-none",
33764
34814
  style: { width, height },
33765
34815
  onClick: handleContainerClick,
33766
34816
  children: items.map((item) => {
@@ -35218,7 +36268,7 @@ var init_DocBreadcrumb = __esm({
35218
36268
  "aria-label": t("aria.breadcrumb"),
35219
36269
  children: /* @__PURE__ */ jsx(HStack, { gap: "xs", align: "center", wrap: true, children: items.map((item, idx) => {
35220
36270
  const isLast = idx === items.length - 1;
35221
- return /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
36271
+ return /* @__PURE__ */ jsxs(React85__default.Fragment, { children: [
35222
36272
  idx > 0 && /* @__PURE__ */ jsx(
35223
36273
  Icon,
35224
36274
  {
@@ -36087,7 +37137,7 @@ var init_MiniStateMachine = __esm({
36087
37137
  const x = 2 + i * (NODE_W + GAP + ARROW_W + GAP);
36088
37138
  const tc = transitionCounts[s.name] ?? 0;
36089
37139
  const role = getStateRole(s.name, s.isInitial, s.isTerminal, tc, maxTC);
36090
- return /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
37140
+ return /* @__PURE__ */ jsxs(React85__default.Fragment, { children: [
36091
37141
  /* @__PURE__ */ jsx(
36092
37142
  AvlState,
36093
37143
  {
@@ -36291,7 +37341,7 @@ var init_PageHeader = __esm({
36291
37341
  info: "bg-info/10 text-info"
36292
37342
  };
36293
37343
  return /* @__PURE__ */ jsxs(Box, { className: cn("mb-6", className), children: [
36294
- breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx(Box, { as: "nav", className: "mb-4", children: /* @__PURE__ */ jsx(Box, { as: "ol", className: "flex items-center gap-2 text-sm", children: breadcrumbs.map((crumb, idx) => /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
37344
+ breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx(Box, { as: "nav", className: "mb-4", children: /* @__PURE__ */ jsx(Box, { as: "ol", className: "flex items-center gap-2 text-sm", children: breadcrumbs.map((crumb, idx) => /* @__PURE__ */ jsxs(React85__default.Fragment, { children: [
36295
37345
  idx > 0 && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: "/" }),
36296
37346
  crumb.href ? /* @__PURE__ */ jsx(
36297
37347
  "a",
@@ -36973,8 +38023,8 @@ var init_WizardContainer = __esm({
36973
38023
  return void 0;
36974
38024
  if (typeof controlledStep === "number") return controlledStep;
36975
38025
  if (typeof controlledStep === "string") return parseInt(controlledStep, 10);
36976
- const num2 = Number(controlledStep);
36977
- return isNaN(num2) ? void 0 : num2;
38026
+ const num3 = Number(controlledStep);
38027
+ return isNaN(num3) ? void 0 : num3;
36978
38028
  })();
36979
38029
  const currentStep = normalizedControlledStep !== void 0 ? normalizedControlledStep : internalStep;
36980
38030
  const totalSteps = steps.length;
@@ -37020,7 +38070,7 @@ var init_WizardContainer = __esm({
37020
38070
  const isCompleted = index < currentStep;
37021
38071
  const stepKey = step.id ?? step.tabId ?? `step-${index}`;
37022
38072
  const stepTitle = step.title ?? step.name ?? `Step ${index + 1}`;
37023
- return /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
38073
+ return /* @__PURE__ */ jsxs(React85__default.Fragment, { children: [
37024
38074
  /* @__PURE__ */ jsx(
37025
38075
  Button,
37026
38076
  {
@@ -37929,6 +38979,7 @@ var init_GraphCanvas = __esm({
37929
38979
  init_ErrorState();
37930
38980
  init_EmptyState();
37931
38981
  init_useEventBus();
38982
+ init_useCanvasGestures();
37932
38983
  GROUP_COLORS2 = [
37933
38984
  "var(--color-primary)",
37934
38985
  "var(--color-success)",
@@ -37952,6 +39003,8 @@ var init_GraphCanvas = __esm({
37952
39003
  selectedNodeId,
37953
39004
  repulsion = 800,
37954
39005
  linkDistance = 100,
39006
+ nodeSpacing = 28,
39007
+ linkOpacity = 0.18,
37955
39008
  layout = "force",
37956
39009
  entity,
37957
39010
  isLoading = false,
@@ -37964,6 +39017,10 @@ var init_GraphCanvas = __esm({
37964
39017
  const animRef = useRef(0);
37965
39018
  const [zoom, setZoom] = useState(1);
37966
39019
  const [offset, setOffset] = useState({ x: 0, y: 0 });
39020
+ const zoomRef = useRef(zoom);
39021
+ zoomRef.current = zoom;
39022
+ const offsetRef = useRef(offset);
39023
+ offsetRef.current = offset;
37967
39024
  const [hoveredNode, setHoveredNode] = useState(null);
37968
39025
  const nodesRef = useRef([]);
37969
39026
  const [, forceUpdate] = useState(0);
@@ -38111,6 +39168,25 @@ var init_GraphCanvas = __esm({
38111
39168
  node.x = Math.max(30, Math.min(w - 30, node.x));
38112
39169
  node.y = Math.max(30, Math.min(h - 30, node.y));
38113
39170
  }
39171
+ for (let i = 0; i < nodes.length; i++) {
39172
+ for (let j = i + 1; j < nodes.length; j++) {
39173
+ const a = nodes[i];
39174
+ const b = nodes[j];
39175
+ const dx = b.x - a.x;
39176
+ const dy = b.y - a.y;
39177
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
39178
+ const minDist = (a.size || 8) + (b.size || 8) + nodeSpacing;
39179
+ if (dist < minDist) {
39180
+ const push = (minDist - dist) / 2;
39181
+ const ux = dx / dist;
39182
+ const uy = dy / dist;
39183
+ a.x = Math.max(30, Math.min(w - 30, a.x - ux * push));
39184
+ a.y = Math.max(30, Math.min(h - 30, a.y - uy * push));
39185
+ b.x = Math.max(30, Math.min(w - 30, b.x + ux * push));
39186
+ b.y = Math.max(30, Math.min(h - 30, b.y + uy * push));
39187
+ }
39188
+ }
39189
+ }
38114
39190
  iterations++;
38115
39191
  forceUpdate((n) => n + 1);
38116
39192
  if (iterations < maxIterations) {
@@ -38124,7 +39200,7 @@ var init_GraphCanvas = __esm({
38124
39200
  return () => {
38125
39201
  cancelAnimationFrame(animRef.current);
38126
39202
  };
38127
- }, [propNodes, propEdges, layout, repulsion, linkDistance, logicalW, height]);
39203
+ }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, logicalW, height]);
38128
39204
  useEffect(() => {
38129
39205
  const canvas = canvasRef.current;
38130
39206
  if (!canvas) return;
@@ -38153,11 +39229,11 @@ var init_GraphCanvas = __esm({
38153
39229
  const target = nodes.find((n) => n.id === edge.target);
38154
39230
  if (!source || !target) continue;
38155
39231
  const incident = !!hoveredNode && (edge.source === hoveredNode || edge.target === hoveredNode);
38156
- ctx.globalAlpha = hoveredNode ? incident ? 1 : 0.12 : 1;
39232
+ ctx.globalAlpha = hoveredNode ? incident ? 1 : 0.05 : linkOpacity;
38157
39233
  ctx.beginPath();
38158
39234
  ctx.moveTo(source.x, source.y);
38159
39235
  ctx.lineTo(target.x, target.y);
38160
- ctx.strokeStyle = incident ? accentColor : edge.color || "#88888866";
39236
+ ctx.strokeStyle = incident ? accentColor : edge.color || "#888888";
38161
39237
  ctx.lineWidth = incident ? 2 : edge.weight ? Math.max(1, edge.weight) : 1;
38162
39238
  ctx.stroke();
38163
39239
  if (edge.label && showLabels) {
@@ -38204,25 +39280,27 @@ var init_GraphCanvas = __esm({
38204
39280
  setZoom(1);
38205
39281
  setOffset({ x: 0, y: 0 });
38206
39282
  }, []);
38207
- const handleWheel = useCallback(
38208
- (e) => {
38209
- if (!interactive) return;
38210
- e.preventDefault();
38211
- const coords = toCoords(e);
38212
- if (!coords) return;
38213
- const oldZoom = zoom;
38214
- const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
38215
- const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
38216
- if (newZoom === oldZoom) return;
38217
- setOffset((o) => ({
38218
- x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
38219
- y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
38220
- }));
38221
- setZoom(newZoom);
38222
- },
38223
- [interactive, toCoords, zoom]
38224
- );
38225
- const handleMouseDown = useCallback(
39283
+ const applyZoom = useCallback((factor, cx, cy) => {
39284
+ if (!interactive) return;
39285
+ const oldZoom = zoomRef.current;
39286
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
39287
+ if (newZoom === oldZoom) return;
39288
+ const o = offsetRef.current;
39289
+ setOffset({
39290
+ x: cx - (cx - o.x) * (newZoom / oldZoom),
39291
+ y: cy - (cy - o.y) * (newZoom / oldZoom)
39292
+ });
39293
+ setZoom(newZoom);
39294
+ }, [interactive]);
39295
+ const applyPanDelta = useCallback((dx, dy) => {
39296
+ if (!interactive) return;
39297
+ setOffset((o) => ({ x: o.x + dx, y: o.y + dy }));
39298
+ }, [interactive]);
39299
+ const cancelSinglePointer = useCallback(() => {
39300
+ interactionRef.current.mode = "none";
39301
+ interactionRef.current.dragNodeId = null;
39302
+ }, []);
39303
+ const handlePointerDown = useCallback(
38226
39304
  (e) => {
38227
39305
  const coords = toCoords(e);
38228
39306
  if (!coords) return;
@@ -38244,7 +39322,7 @@ var init_GraphCanvas = __esm({
38244
39322
  },
38245
39323
  [toCoords, nodeAt, draggable, interactive, offset]
38246
39324
  );
38247
- const handleMouseMove = useCallback(
39325
+ const handlePointerMove = useCallback(
38248
39326
  (e) => {
38249
39327
  const state = interactionRef.current;
38250
39328
  if (state.mode === "panning") {
@@ -38273,7 +39351,7 @@ var init_GraphCanvas = __esm({
38273
39351
  },
38274
39352
  [toCoords, nodeAt]
38275
39353
  );
38276
- const handleMouseUp = useCallback(
39354
+ const handlePointerUp = useCallback(
38277
39355
  (e) => {
38278
39356
  const state = interactionRef.current;
38279
39357
  const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
@@ -38290,11 +39368,19 @@ var init_GraphCanvas = __esm({
38290
39368
  },
38291
39369
  [toCoords, nodeAt, handleNodeClick]
38292
39370
  );
38293
- const handleMouseLeave = useCallback(() => {
38294
- interactionRef.current.mode = "none";
38295
- interactionRef.current.dragNodeId = null;
39371
+ const handlePointerLeave = useCallback(() => {
38296
39372
  setHoveredNode(null);
38297
39373
  }, []);
39374
+ const gestureHandlers = useCanvasGestures({
39375
+ canvasRef,
39376
+ enabled: interactive || draggable,
39377
+ onPointerDown: handlePointerDown,
39378
+ onPointerMove: handlePointerMove,
39379
+ onPointerUp: handlePointerUp,
39380
+ onZoom: applyZoom,
39381
+ onPanDelta: applyPanDelta,
39382
+ onMultiTouchStart: cancelSinglePointer
39383
+ });
38298
39384
  const handleDoubleClick = useCallback(
38299
39385
  (e) => {
38300
39386
  const coords = toCoords(e);
@@ -38363,13 +39449,14 @@ var init_GraphCanvas = __esm({
38363
39449
  ref: canvasRef,
38364
39450
  width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
38365
39451
  height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
38366
- className: "w-full cursor-grab active:cursor-grabbing",
39452
+ className: "w-full cursor-grab active:cursor-grabbing touch-none",
38367
39453
  style: { height },
38368
- onWheel: handleWheel,
38369
- onMouseDown: handleMouseDown,
38370
- onMouseMove: handleMouseMove,
38371
- onMouseUp: handleMouseUp,
38372
- onMouseLeave: handleMouseLeave,
39454
+ onPointerDown: gestureHandlers.onPointerDown,
39455
+ onPointerMove: gestureHandlers.onPointerMove,
39456
+ onPointerUp: gestureHandlers.onPointerUp,
39457
+ onPointerCancel: gestureHandlers.onPointerCancel,
39458
+ onPointerLeave: handlePointerLeave,
39459
+ onWheel: gestureHandlers.onWheel,
38373
39460
  onDoubleClick: handleDoubleClick
38374
39461
  }
38375
39462
  ) }),
@@ -39521,7 +40608,7 @@ var init_DetailPanel = __esm({
39521
40608
  }
39522
40609
  });
39523
40610
  function extractTitle(children) {
39524
- if (!React82__default.isValidElement(children)) return void 0;
40611
+ if (!React85__default.isValidElement(children)) return void 0;
39525
40612
  const props = children.props;
39526
40613
  if (typeof props.title === "string") {
39527
40614
  return props.title;
@@ -39576,7 +40663,7 @@ function LinearView({
39576
40663
  /* @__PURE__ */ jsx(HStack, { className: "flex-wrap items-center", gap: "xs", children: trait.states.map((state, i) => {
39577
40664
  const isDone = i < currentIdx;
39578
40665
  const isCurrent = i === currentIdx;
39579
- return /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
40666
+ return /* @__PURE__ */ jsxs(React85__default.Fragment, { children: [
39580
40667
  i > 0 && /* @__PURE__ */ jsx(
39581
40668
  Typography,
39582
40669
  {
@@ -40526,12 +41613,12 @@ var init_Form = __esm({
40526
41613
  const isSchemaEntity = isOrbitalEntitySchema(entity);
40527
41614
  const resolvedEntity = isSchemaEntity ? entity : void 0;
40528
41615
  const entityName = typeof entity === "string" ? entity : resolvedEntity?.name;
40529
- const normalizedInitialData = React82__default.useMemo(() => {
41616
+ const normalizedInitialData = React85__default.useMemo(() => {
40530
41617
  const entityRowAsInitial = isPlainEntityRow(entity) ? entity : void 0;
40531
41618
  const callerInitial = initialData !== null && typeof initialData === "object" && !Array.isArray(initialData) ? initialData : {};
40532
41619
  return entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
40533
41620
  }, [entity, initialData]);
40534
- const entityDerivedFields = React82__default.useMemo(() => {
41621
+ const entityDerivedFields = React85__default.useMemo(() => {
40535
41622
  if (fields && fields.length > 0) return void 0;
40536
41623
  if (!resolvedEntity) return void 0;
40537
41624
  return resolvedEntity.fields.map(
@@ -40551,16 +41638,16 @@ var init_Form = __esm({
40551
41638
  const conditionalFields = typeof conditionalFieldsRaw === "boolean" ? {} : conditionalFieldsRaw;
40552
41639
  const hiddenCalculations = typeof hiddenCalculationsRaw === "boolean" ? [] : hiddenCalculationsRaw;
40553
41640
  const violationTriggers = typeof violationTriggersRaw === "boolean" ? [] : violationTriggersRaw;
40554
- const [formData, setFormData] = React82__default.useState(
41641
+ const [formData, setFormData] = React85__default.useState(
40555
41642
  normalizedInitialData
40556
41643
  );
40557
- const [collapsedSections, setCollapsedSections] = React82__default.useState(
41644
+ const [collapsedSections, setCollapsedSections] = React85__default.useState(
40558
41645
  /* @__PURE__ */ new Set()
40559
41646
  );
40560
- const [submitError, setSubmitError] = React82__default.useState(null);
40561
- const formRef = React82__default.useRef(null);
41647
+ const [submitError, setSubmitError] = React85__default.useState(null);
41648
+ const formRef = React85__default.useRef(null);
40562
41649
  const formMode = props.mode;
40563
- const mountedRef = React82__default.useRef(false);
41650
+ const mountedRef = React85__default.useRef(false);
40564
41651
  if (!mountedRef.current) {
40565
41652
  mountedRef.current = true;
40566
41653
  debug("forms", "mount", {
@@ -40573,7 +41660,7 @@ var init_Form = __esm({
40573
41660
  });
40574
41661
  }
40575
41662
  const shouldShowCancel = showCancel ?? (fields && fields.length > 0);
40576
- const evalContext = React82__default.useMemo(
41663
+ const evalContext = React85__default.useMemo(
40577
41664
  () => ({
40578
41665
  formValues: formData,
40579
41666
  globalVariables: externalContext?.globalVariables ?? {},
@@ -40582,7 +41669,7 @@ var init_Form = __esm({
40582
41669
  }),
40583
41670
  [formData, externalContext]
40584
41671
  );
40585
- React82__default.useEffect(() => {
41672
+ React85__default.useEffect(() => {
40586
41673
  debug("forms", "initialData-sync", {
40587
41674
  mode: formMode,
40588
41675
  normalizedInitialData,
@@ -40593,7 +41680,7 @@ var init_Form = __esm({
40593
41680
  setFormData(normalizedInitialData);
40594
41681
  }
40595
41682
  }, [normalizedInitialData]);
40596
- const processCalculations = React82__default.useCallback(
41683
+ const processCalculations = React85__default.useCallback(
40597
41684
  (changedFieldId, newFormData) => {
40598
41685
  if (!hiddenCalculations.length) return;
40599
41686
  const context = {
@@ -40618,7 +41705,7 @@ var init_Form = __esm({
40618
41705
  },
40619
41706
  [hiddenCalculations, externalContext, eventBus]
40620
41707
  );
40621
- const checkViolations = React82__default.useCallback(
41708
+ const checkViolations = React85__default.useCallback(
40622
41709
  (changedFieldId, newFormData) => {
40623
41710
  if (!violationTriggers.length) return;
40624
41711
  const context = {
@@ -40656,7 +41743,7 @@ var init_Form = __esm({
40656
41743
  processCalculations(name, newFormData);
40657
41744
  checkViolations(name, newFormData);
40658
41745
  };
40659
- const isFieldVisible = React82__default.useCallback(
41746
+ const isFieldVisible = React85__default.useCallback(
40660
41747
  (fieldName) => {
40661
41748
  const condition = conditionalFields[fieldName];
40662
41749
  if (!condition) return true;
@@ -40664,7 +41751,7 @@ var init_Form = __esm({
40664
41751
  },
40665
41752
  [conditionalFields, evalContext]
40666
41753
  );
40667
- const isSectionVisible = React82__default.useCallback(
41754
+ const isSectionVisible = React85__default.useCallback(
40668
41755
  (section) => {
40669
41756
  if (!section.condition) return true;
40670
41757
  return Boolean(evaluateFormExpression(section.condition, evalContext));
@@ -40740,7 +41827,7 @@ var init_Form = __esm({
40740
41827
  eventBus.emit(`UI:${onCancel}`);
40741
41828
  }
40742
41829
  };
40743
- const renderField = React82__default.useCallback(
41830
+ const renderField = React85__default.useCallback(
40744
41831
  (field) => {
40745
41832
  const fieldName = field.name || field.field;
40746
41833
  if (!fieldName) return null;
@@ -40761,7 +41848,7 @@ var init_Form = __esm({
40761
41848
  [formData, isFieldVisible, relationsData, relationsLoading, isLoading]
40762
41849
  );
40763
41850
  const effectiveFields = entityDerivedFields ?? fields;
40764
- const normalizedFields = React82__default.useMemo(() => {
41851
+ const normalizedFields = React85__default.useMemo(() => {
40765
41852
  if (!effectiveFields || effectiveFields.length === 0) return [];
40766
41853
  return effectiveFields.map((field) => {
40767
41854
  if (typeof field === "string") {
@@ -40784,7 +41871,7 @@ var init_Form = __esm({
40784
41871
  return field;
40785
41872
  });
40786
41873
  }, [effectiveFields, resolvedEntity]);
40787
- const schemaFields = React82__default.useMemo(() => {
41874
+ const schemaFields = React85__default.useMemo(() => {
40788
41875
  if (normalizedFields.length === 0) return null;
40789
41876
  if (isDebugEnabled()) {
40790
41877
  debugGroup(`Form: ${entityName || "unknown"}`);
@@ -40794,7 +41881,7 @@ var init_Form = __esm({
40794
41881
  }
40795
41882
  return normalizedFields.map(renderField).filter(Boolean);
40796
41883
  }, [normalizedFields, renderField, entityName, conditionalFields]);
40797
- const sectionElements = React82__default.useMemo(() => {
41884
+ const sectionElements = React85__default.useMemo(() => {
40798
41885
  if (!sections || sections.length === 0) return null;
40799
41886
  return sections.map((section) => {
40800
41887
  if (!isSectionVisible(section)) {
@@ -42114,12 +43201,18 @@ function ModelLoader({
42114
43201
  });
42115
43202
  return cloned;
42116
43203
  }, [loadedModel, castShadow, receiveShadow]);
43204
+ const normFactor = useMemo(() => {
43205
+ if (!model) return 1;
43206
+ const box = new THREE3.Box3().setFromObject(model);
43207
+ const size = new THREE3.Vector3();
43208
+ box.getSize(size);
43209
+ const maxDim = Math.max(size.x, size.y, size.z);
43210
+ return maxDim > 0 && Number.isFinite(maxDim) ? 1 / maxDim : 1;
43211
+ }, [model]);
42117
43212
  const scaleArray = useMemo(() => {
42118
- if (typeof scale === "number") {
42119
- return [scale, scale, scale];
42120
- }
42121
- return scale;
42122
- }, [scale]);
43213
+ const base = typeof scale === "number" ? [scale, scale, scale] : scale;
43214
+ return [base[0] * normFactor, base[1] * normFactor, base[2] * normFactor];
43215
+ }, [scale, normFactor]);
42123
43216
  const rotationRad = useMemo(() => {
42124
43217
  return [
42125
43218
  rotation[0] * Math.PI / 180,
@@ -42330,6 +43423,29 @@ var init_GameCanvas3D2 = __esm({
42330
43423
  const controlsRef = useRef(null);
42331
43424
  const [hoveredTile, setHoveredTile] = useState(null);
42332
43425
  const [internalError, setInternalError] = useState(null);
43426
+ useEffect(() => {
43427
+ const el = containerRef.current;
43428
+ if (!el) return;
43429
+ let node = el;
43430
+ let depth = 0;
43431
+ while (node && depth < 8) {
43432
+ const cs = window.getComputedStyle(node);
43433
+ const rect = node.getBoundingClientRect();
43434
+ console.log("[almadar:ui:game:3d-height]", {
43435
+ depth,
43436
+ tag: node.tagName,
43437
+ id: node.id || void 0,
43438
+ className: node.className?.slice?.(0, 60) || void 0,
43439
+ rectHeight: rect.height,
43440
+ computedHeight: cs.height,
43441
+ computedMinHeight: cs.minHeight,
43442
+ computedFlex: cs.flex,
43443
+ computedOverflow: cs.overflow
43444
+ });
43445
+ node = node.parentElement;
43446
+ depth++;
43447
+ }
43448
+ }, []);
42333
43449
  const { sheetUrls: atlasSheetUrls, resolveUnitFrame } = useUnitSpriteAtlas(units);
42334
43450
  const preloadUrls = useMemo(() => [...preloadAssets, ...atlasSheetUrls], [preloadAssets, atlasSheetUrls]);
42335
43451
  const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
@@ -42719,7 +43835,7 @@ var init_GameCanvas3D2 = __esm({
42719
43835
  {
42720
43836
  ref: containerRef,
42721
43837
  className: cn("game-canvas-3d relative w-full overflow-hidden", className),
42722
- style: { minHeight: "85vh" },
43838
+ style: { height: "85vh" },
42723
43839
  "data-orientation": orientation,
42724
43840
  "data-camera-mode": cameraMode,
42725
43841
  "data-overlay": overlay,
@@ -42734,7 +43850,7 @@ var init_GameCanvas3D2 = __esm({
42734
43850
  near: 0.1,
42735
43851
  far: 1e3
42736
43852
  },
42737
- style: { background: backgroundColor, height: "85vh", width: "100%" },
43853
+ style: { background: backgroundColor, position: "absolute", inset: 0 },
42738
43854
  onClick: (e) => {
42739
43855
  if (e.target === e.currentTarget) {
42740
43856
  eventHandlers.handleCanvasClick(e);
@@ -42812,6 +43928,9 @@ var init_GameCanvas3D2 = __esm({
42812
43928
  target: cameraTarget,
42813
43929
  enableDamping: true,
42814
43930
  dampingFactor: 0.05,
43931
+ enableZoom: true,
43932
+ enablePan: true,
43933
+ touches: { ONE: THREE3.TOUCH.ROTATE, TWO: THREE3.TOUCH.DOLLY_PAN },
42815
43934
  minDistance: 2,
42816
43935
  maxDistance: 100,
42817
43936
  maxPolarAngle: Math.PI / 2 - 0.1
@@ -43705,7 +44824,7 @@ var init_List = __esm({
43705
44824
  if (entity && typeof entity === "object" && "id" in entity) return [entity];
43706
44825
  return [];
43707
44826
  }, [entity]);
43708
- const getItemActions = React82__default.useCallback(
44827
+ const getItemActions = React85__default.useCallback(
43709
44828
  (item) => {
43710
44829
  if (!itemActions) return [];
43711
44830
  if (typeof itemActions === "function") {
@@ -44181,7 +45300,7 @@ var init_MediaGallery = __esm({
44181
45300
  [selectable, selectedItems, selectionEvent, eventBus]
44182
45301
  );
44183
45302
  const entityData = Array.isArray(entity) ? entity : [];
44184
- const items = React82__default.useMemo(() => {
45303
+ const items = React85__default.useMemo(() => {
44185
45304
  if (propItems) return propItems;
44186
45305
  if (entityData.length === 0) return [];
44187
45306
  return entityData.map((record, idx) => ({
@@ -44342,7 +45461,7 @@ var init_MediaGallery = __esm({
44342
45461
  }
44343
45462
  });
44344
45463
  function extractTitle2(children) {
44345
- if (!React82__default.isValidElement(children)) return void 0;
45464
+ if (!React85__default.isValidElement(children)) return void 0;
44346
45465
  const props = children.props;
44347
45466
  if (typeof props.title === "string") {
44348
45467
  return props.title;
@@ -44918,31 +46037,33 @@ var init_PricingPageTemplate = __esm({
44918
46037
  PricingPageTemplate.displayName = "PricingPageTemplate";
44919
46038
  }
44920
46039
  });
44921
- function dungeonTerrain(x, y, stairsX, stairsY) {
46040
+ function resolveManifestUrl3(manifest, relative) {
46041
+ if (relative == null) return void 0;
46042
+ if (/^https?:\/\//.test(relative)) return relative;
46043
+ const base = manifest?.baseUrl;
46044
+ if (base == null) return relative;
46045
+ const cleanBase = base.replace(/\/$/, "");
46046
+ const cleanRel = relative.replace(/^\//, "");
46047
+ return `${cleanBase}/${cleanRel}`;
46048
+ }
46049
+ function dungeonTerrain(x, y, stairsX, stairsY, manifest) {
44922
46050
  const isBorder = x === 0 || y === 0 || x === DUNGEON_GRID_W - 1 || y === DUNGEON_GRID_H - 1;
44923
46051
  const isPillar = x % 4 === 0 && y % 4 === 0 && !isBorder;
44924
46052
  const isWall = isBorder || isPillar;
46053
+ const terrains = manifest?.terrains;
44925
46054
  if (x === stairsX && y === stairsY) {
44926
- return { terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` };
46055
+ return { terrain: "stairs", passable: true, terrainSprite: resolveManifestUrl3(manifest, terrains?.stairs) };
44927
46056
  }
44928
46057
  if (isWall) {
44929
- return { terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` };
44930
- }
44931
- const variant = (x * 3 + y * 7) % 5;
44932
- const sprites = [
44933
- `${CDN4}/isometric-dungeon/Isometric/dirt_E.png`,
44934
- `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png`,
44935
- `${CDN4}/isometric-dungeon/Isometric/planks_E.png`,
44936
- `${CDN4}/isometric-dungeon/Isometric/stoneTile_E.png`,
44937
- `${CDN4}/isometric-dungeon/Isometric/dirt_E.png`
44938
- ];
44939
- return { terrain: "floor", passable: true, terrainSprite: sprites[variant] };
46058
+ return { terrain: "wall", passable: false, terrainSprite: resolveManifestUrl3(manifest, terrains?.wall) };
46059
+ }
46060
+ return { terrain: "floor", passable: true, terrainSprite: resolveManifestUrl3(manifest, terrains?.floor) };
44940
46061
  }
44941
- function buildDefaultDungeonTiles(stairsX, stairsY) {
46062
+ function buildDefaultDungeonTiles(stairsX, stairsY, manifest) {
44942
46063
  const tiles = [];
44943
46064
  for (let y = 0; y < DUNGEON_GRID_H; y++) {
44944
46065
  for (let x = 0; x < DUNGEON_GRID_W; x++) {
44945
- tiles.push({ x, y, ...dungeonTerrain(x, y, stairsX, stairsY) });
46066
+ tiles.push({ x, y, ...dungeonTerrain(x, y, stairsX, stairsY, manifest) });
44946
46067
  }
44947
46068
  }
44948
46069
  return tiles;
@@ -44972,20 +46093,48 @@ function RoguelikeBoard({
44972
46093
  className
44973
46094
  }) {
44974
46095
  const board = boardEntity(entity) ?? {};
44975
- const resolvedStairsX = propStairsX ?? num(board.stairsX, 14);
44976
- const resolvedStairsY = propStairsY ?? num(board.stairsY, 14);
44977
- const tiles = propTiles ?? (Array.isArray(board.tiles) && board.tiles.length >= DUNGEON_GRID_W * DUNGEON_GRID_H ? board.tiles : buildDefaultDungeonTiles(resolvedStairsX, resolvedStairsY));
44978
- const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
44979
- const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
44980
- const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
46096
+ const assetManifest = propAssetManifest ?? board.assetManifest;
46097
+ const stairsX = propStairsX ?? num(board.stairsX, 14);
46098
+ const stairsY = propStairsY ?? num(board.stairsY, 14);
46099
+ const entityTiles = useMemo(
46100
+ () => rows(board.tiles).map((r) => ({
46101
+ x: num(r.x),
46102
+ y: num(r.y),
46103
+ terrain: r.terrain == null ? void 0 : str(r.terrain),
46104
+ terrainSprite: r.terrainSprite == null ? void 0 : str(r.terrainSprite),
46105
+ passable: r.passable !== false
46106
+ })),
46107
+ [board.tiles]
46108
+ );
46109
+ const tiles = propTiles ?? (entityTiles.length >= DUNGEON_GRID_W * DUNGEON_GRID_H ? entityTiles : buildDefaultDungeonTiles(stairsX, stairsY, assetManifest));
46110
+ const entityEnemies = useMemo(
46111
+ () => rows(board.enemies).map((r) => ({
46112
+ id: str(r.id),
46113
+ x: num(r.x),
46114
+ y: num(r.y),
46115
+ hp: num(r.hp),
46116
+ attack: num(r.attack)
46117
+ })),
46118
+ [board.enemies]
46119
+ );
46120
+ const enemies = propEnemies ?? (entityEnemies.length > 0 ? entityEnemies : DEFAULT_ENEMIES);
46121
+ const entityItems = useMemo(
46122
+ () => rows(board.items).map((r) => ({
46123
+ id: str(r.id),
46124
+ x: num(r.x),
46125
+ y: num(r.y),
46126
+ kind: str(r.kind) || "health_potion"
46127
+ })),
46128
+ [board.items]
46129
+ );
46130
+ const items = propItems ?? (entityItems.length > 0 ? entityItems : DEFAULT_ITEMS);
46131
+ const player = propPlayer ?? (board.player == null ? { x: 1, y: 1 } : vec2(board.player));
44981
46132
  const playerHp = propPlayerHp ?? num(board.playerHp, 10);
44982
46133
  const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
44983
46134
  const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
44984
46135
  const depth = propDepth ?? num(board.depth, 1);
44985
46136
  const result = propResult ?? (str(board.result) || "none");
44986
46137
  const phase = propPhase ?? (str(board.phase) || "player_turn");
44987
- propStairsX ?? num(board.stairsX, 14);
44988
- propStairsY ?? num(board.stairsY, 14);
44989
46138
  const eventBus = useEventBus();
44990
46139
  const { t } = useTranslate();
44991
46140
  const [hoveredTile, setHoveredTile] = useState(null);
@@ -45032,7 +46181,7 @@ function RoguelikeBoard({
45032
46181
  health: playerHp,
45033
46182
  maxHealth: playerMaxHp,
45034
46183
  unitType: "player",
45035
- sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
46184
+ sprite: resolveManifestUrl3(assetManifest, assetManifest?.units?.player)
45036
46185
  },
45037
46186
  ...liveEnemies.map((e) => ({
45038
46187
  id: e.id,
@@ -45042,19 +46191,19 @@ function RoguelikeBoard({
45042
46191
  health: e.hp,
45043
46192
  maxHealth: 6,
45044
46193
  unitType: "enemy",
45045
- sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
46194
+ sprite: resolveManifestUrl3(assetManifest, assetManifest?.units?.enemy)
45046
46195
  }))
45047
46196
  ];
45048
- }, [player, enemies, playerHp, playerMaxHp, t]);
46197
+ }, [player, enemies, playerHp, playerMaxHp, assetManifest, t]);
45049
46198
  const isoFeatures = useMemo(
45050
46199
  () => items.map((it) => ({
45051
46200
  id: it.id,
45052
46201
  x: it.x,
45053
46202
  y: it.y,
45054
46203
  type: it.kind,
45055
- sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
46204
+ sprite: resolveManifestUrl3(assetManifest, assetManifest?.features?.[it.kind])
45056
46205
  })),
45057
- [items]
46206
+ [items, assetManifest]
45058
46207
  );
45059
46208
  const validMoves = useMemo(() => {
45060
46209
  if (result !== "none" || phase !== "player_turn") return [];
@@ -45113,8 +46262,8 @@ function RoguelikeBoard({
45113
46262
  onTileHover: (x, y) => setHoveredTile({ x, y }),
45114
46263
  onTileLeave: () => setHoveredTile(null),
45115
46264
  scale,
45116
- assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
45117
- assetManifest: propAssetManifest,
46265
+ assetBaseUrl: assetManifest?.baseUrl,
46266
+ assetManifest,
45118
46267
  unitScale,
45119
46268
  spriteHeightRatio,
45120
46269
  spriteMaxWidthRatio
@@ -45161,7 +46310,7 @@ function RoguelikeBoard({
45161
46310
  ] }) })
45162
46311
  ] });
45163
46312
  }
45164
- var CDN4, DUNGEON_GRID_W, DUNGEON_GRID_H, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
46313
+ var DUNGEON_GRID_W, DUNGEON_GRID_H, DEFAULT_ENEMIES, DEFAULT_ITEMS;
45165
46314
  var init_RoguelikeBoard = __esm({
45166
46315
  "components/game/organisms/RoguelikeBoard.tsx"() {
45167
46316
  "use client";
@@ -45173,10 +46322,8 @@ var init_RoguelikeBoard = __esm({
45173
46322
  init_Stack();
45174
46323
  init_IsometricCanvas();
45175
46324
  init_boardEntity();
45176
- CDN4 = "https://almadar-kflow-assets.web.app/shared";
45177
46325
  DUNGEON_GRID_W = 16;
45178
46326
  DUNGEON_GRID_H = 16;
45179
- buildDefaultDungeonTiles(14, 14);
45180
46327
  DEFAULT_ENEMIES = [
45181
46328
  { id: "e1", x: 5, y: 2, hp: 5, attack: 2 },
45182
46329
  { id: "e2", x: 9, y: 6, hp: 4, attack: 1 },
@@ -45188,11 +46335,6 @@ var init_RoguelikeBoard = __esm({
45188
46335
  { id: "i2", x: 7, y: 9, kind: "sword" },
45189
46336
  { id: "i3", x: 11, y: 5, kind: "shield" }
45190
46337
  ];
45191
- FEATURE_SPRITE = {
45192
- health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
45193
- sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
45194
- shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
45195
- };
45196
46338
  RoguelikeBoard.displayName = "RoguelikeBoard";
45197
46339
  }
45198
46340
  });
@@ -45230,40 +46372,40 @@ function RoguelikeTemplate({
45230
46372
  }
45231
46373
  );
45232
46374
  }
45233
- var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
46375
+ var CDN4, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
45234
46376
  var init_RoguelikeTemplate = __esm({
45235
46377
  "components/game/templates/RoguelikeTemplate.tsx"() {
45236
46378
  init_RoguelikeBoard();
45237
- CDN5 = "https://almadar-kflow-assets.web.app/shared";
46379
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
45238
46380
  DEFAULT_ROGUELIKE_TILES = [
45239
- { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45240
- { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45241
- { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45242
- { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45243
- { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45244
- { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45245
- { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45246
- { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45247
- { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45248
- { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45249
- { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45250
- { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45251
- { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45252
- { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45253
- { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45254
- { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45255
- { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45256
- { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45257
- { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
45258
- { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45259
- { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45260
- { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45261
- { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45262
- { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45263
- { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
46381
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46382
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46383
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46384
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46385
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46386
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46387
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
46388
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
46389
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
46390
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46391
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46392
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
46393
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
46394
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
46395
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46396
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46397
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
46398
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
46399
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
46400
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46401
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46402
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46403
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46404
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46405
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
45264
46406
  ];
45265
46407
  DEFAULT_ROGUELIKE_MANIFEST = {
45266
- baseUrl: CDN5,
46408
+ baseUrl: CDN4,
45267
46409
  terrains: {
45268
46410
  floor: "/isometric-dungeon/Isometric/dirt_E.png",
45269
46411
  wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
@@ -45370,7 +46512,7 @@ var init_debugRegistry = __esm({
45370
46512
  }
45371
46513
  });
45372
46514
  function useDebugData() {
45373
- const [data, setData] = React82.useState(() => ({
46515
+ const [data, setData] = React85.useState(() => ({
45374
46516
  traits: [],
45375
46517
  ticks: [],
45376
46518
  guards: [],
@@ -45384,7 +46526,7 @@ function useDebugData() {
45384
46526
  },
45385
46527
  lastUpdate: Date.now()
45386
46528
  }));
45387
- React82.useEffect(() => {
46529
+ React85.useEffect(() => {
45388
46530
  const updateData = () => {
45389
46531
  setData({
45390
46532
  traits: getAllTraits(),
@@ -45493,12 +46635,12 @@ function layoutGraph(states, transitions, initialState, width, height) {
45493
46635
  return positions;
45494
46636
  }
45495
46637
  function WalkMinimap() {
45496
- const [walkStep, setWalkStep] = React82.useState(null);
45497
- const [traits2, setTraits] = React82.useState([]);
45498
- const [coveredEdges, setCoveredEdges] = React82.useState([]);
45499
- const [completedTraits, setCompletedTraits] = React82.useState(/* @__PURE__ */ new Set());
45500
- const prevTraitRef = React82.useRef(null);
45501
- React82.useEffect(() => {
46638
+ const [walkStep, setWalkStep] = React85.useState(null);
46639
+ const [traits2, setTraits] = React85.useState([]);
46640
+ const [coveredEdges, setCoveredEdges] = React85.useState([]);
46641
+ const [completedTraits, setCompletedTraits] = React85.useState(/* @__PURE__ */ new Set());
46642
+ const prevTraitRef = React85.useRef(null);
46643
+ React85.useEffect(() => {
45502
46644
  const interval = setInterval(() => {
45503
46645
  const w = window;
45504
46646
  const step = w.__orbitalWalkStep;
@@ -45934,15 +47076,15 @@ var init_EntitiesTab = __esm({
45934
47076
  });
45935
47077
  function EventFlowTab({ events: events2 }) {
45936
47078
  const { t } = useTranslate();
45937
- const [filter, setFilter] = React82.useState("all");
45938
- const containerRef = React82.useRef(null);
45939
- const [autoScroll, setAutoScroll] = React82.useState(true);
45940
- React82.useEffect(() => {
47079
+ const [filter, setFilter] = React85.useState("all");
47080
+ const containerRef = React85.useRef(null);
47081
+ const [autoScroll, setAutoScroll] = React85.useState(true);
47082
+ React85.useEffect(() => {
45941
47083
  if (autoScroll && containerRef.current) {
45942
47084
  containerRef.current.scrollTop = containerRef.current.scrollHeight;
45943
47085
  }
45944
47086
  }, [events2.length, autoScroll]);
45945
- const filteredEvents = React82.useMemo(() => {
47087
+ const filteredEvents = React85.useMemo(() => {
45946
47088
  if (filter === "all") return events2;
45947
47089
  return events2.filter((e) => e.type === filter);
45948
47090
  }, [events2, filter]);
@@ -46058,7 +47200,7 @@ var init_EventFlowTab = __esm({
46058
47200
  });
46059
47201
  function GuardsPanel({ guards }) {
46060
47202
  const { t } = useTranslate();
46061
- const [filter, setFilter] = React82.useState("all");
47203
+ const [filter, setFilter] = React85.useState("all");
46062
47204
  if (guards.length === 0) {
46063
47205
  return /* @__PURE__ */ jsx(
46064
47206
  EmptyState,
@@ -46071,7 +47213,7 @@ function GuardsPanel({ guards }) {
46071
47213
  }
46072
47214
  const passedCount = guards.filter((g) => g.result).length;
46073
47215
  const failedCount = guards.length - passedCount;
46074
- const filteredGuards = React82.useMemo(() => {
47216
+ const filteredGuards = React85.useMemo(() => {
46075
47217
  if (filter === "all") return guards;
46076
47218
  if (filter === "passed") return guards.filter((g) => g.result);
46077
47219
  return guards.filter((g) => !g.result);
@@ -46234,10 +47376,10 @@ function EffectBadge({ effect }) {
46234
47376
  }
46235
47377
  function TransitionTimeline({ transitions }) {
46236
47378
  const { t } = useTranslate();
46237
- const containerRef = React82.useRef(null);
46238
- const [autoScroll, setAutoScroll] = React82.useState(true);
46239
- const [expandedId, setExpandedId] = React82.useState(null);
46240
- React82.useEffect(() => {
47379
+ const containerRef = React85.useRef(null);
47380
+ const [autoScroll, setAutoScroll] = React85.useState(true);
47381
+ const [expandedId, setExpandedId] = React85.useState(null);
47382
+ React85.useEffect(() => {
46241
47383
  if (autoScroll && containerRef.current) {
46242
47384
  containerRef.current.scrollTop = containerRef.current.scrollHeight;
46243
47385
  }
@@ -46517,9 +47659,9 @@ function getAllEvents(traits2) {
46517
47659
  function EventDispatcherTab({ traits: traits2, schema }) {
46518
47660
  const eventBus = useEventBus();
46519
47661
  const { t } = useTranslate();
46520
- const [log13, setLog] = React82.useState([]);
46521
- const prevStatesRef = React82.useRef(/* @__PURE__ */ new Map());
46522
- React82.useEffect(() => {
47662
+ const [log13, setLog] = React85.useState([]);
47663
+ const prevStatesRef = React85.useRef(/* @__PURE__ */ new Map());
47664
+ React85.useEffect(() => {
46523
47665
  for (const trait of traits2) {
46524
47666
  const prev = prevStatesRef.current.get(trait.id);
46525
47667
  if (prev && prev !== trait.currentState) {
@@ -46688,10 +47830,10 @@ function VerifyModePanel({
46688
47830
  localCount
46689
47831
  }) {
46690
47832
  const { t } = useTranslate();
46691
- const [expanded, setExpanded] = React82.useState(true);
46692
- const scrollRef = React82.useRef(null);
46693
- const prevCountRef = React82.useRef(0);
46694
- React82.useEffect(() => {
47833
+ const [expanded, setExpanded] = React85.useState(true);
47834
+ const scrollRef = React85.useRef(null);
47835
+ const prevCountRef = React85.useRef(0);
47836
+ React85.useEffect(() => {
46695
47837
  if (expanded && transitions.length > prevCountRef.current && scrollRef.current) {
46696
47838
  scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
46697
47839
  }
@@ -46748,10 +47890,10 @@ function RuntimeDebugger({
46748
47890
  schema
46749
47891
  }) {
46750
47892
  const { t } = useTranslate();
46751
- const [isCollapsed, setIsCollapsed] = React82.useState(mode === "verify" ? true : defaultCollapsed);
46752
- const [isVisible, setIsVisible] = React82.useState(mode === "inline" || mode === "verify" || isDebugEnabled2());
47893
+ const [isCollapsed, setIsCollapsed] = React85.useState(mode === "verify" ? true : defaultCollapsed);
47894
+ const [isVisible, setIsVisible] = React85.useState(mode === "inline" || mode === "verify" || isDebugEnabled2());
46753
47895
  const debugData = useDebugData();
46754
- React82.useEffect(() => {
47896
+ React85.useEffect(() => {
46755
47897
  if (mode === "inline") return;
46756
47898
  return onDebugToggle((enabled) => {
46757
47899
  setIsVisible(enabled);
@@ -46760,7 +47902,7 @@ function RuntimeDebugger({
46760
47902
  }
46761
47903
  });
46762
47904
  }, [mode]);
46763
- React82.useEffect(() => {
47905
+ React85.useEffect(() => {
46764
47906
  if (mode === "inline") return;
46765
47907
  const handleKeyDown = (e) => {
46766
47908
  if (e.key === "`" && isVisible) {
@@ -47320,7 +48462,7 @@ function SequenceBar({
47320
48462
  onSlotRemove(index);
47321
48463
  }, [onSlotRemove, playing]);
47322
48464
  const paddedSlots = Array.from({ length: maxSlots }, (_, i) => slots[i]);
47323
- return /* @__PURE__ */ jsx(HStack, { className: cn("items-center", className), gap: "sm", children: paddedSlots.map((slot, i) => /* @__PURE__ */ jsxs(React82__default.Fragment, { children: [
48465
+ return /* @__PURE__ */ jsx(HStack, { className: cn("items-center", className), gap: "sm", children: paddedSlots.map((slot, i) => /* @__PURE__ */ jsxs(React85__default.Fragment, { children: [
47324
48466
  i > 0 && /* @__PURE__ */ jsx(
47325
48467
  Typography,
47326
48468
  {
@@ -48100,20 +49242,21 @@ var init_SplitPane = __esm({
48100
49242
  const [ratio, setRatio] = useState(initialRatio);
48101
49243
  const containerRef = useRef(null);
48102
49244
  const isDragging = useRef(false);
48103
- const handleMouseDown = useCallback(
49245
+ const handlePointerDown = useCallback(
48104
49246
  (e) => {
48105
49247
  if (!resizable) return;
48106
49248
  e.preventDefault();
48107
49249
  isDragging.current = true;
48108
- const handleMouseMove = (e2) => {
49250
+ e.currentTarget.setPointerCapture(e.pointerId);
49251
+ const handlePointerMove = (ev) => {
48109
49252
  if (!isDragging.current || !containerRef.current) return;
48110
49253
  const rect = containerRef.current.getBoundingClientRect();
48111
49254
  let newRatio;
48112
49255
  if (direction === "horizontal") {
48113
- const x = e2.clientX - rect.left;
49256
+ const x = ev.clientX - rect.left;
48114
49257
  newRatio = x / rect.width * 100;
48115
49258
  } else {
48116
- const y = e2.clientY - rect.top;
49259
+ const y = ev.clientY - rect.top;
48117
49260
  newRatio = y / rect.height * 100;
48118
49261
  }
48119
49262
  const minRatio = minSize / (direction === "horizontal" ? rect.width : rect.height) * 100;
@@ -48121,13 +49264,15 @@ var init_SplitPane = __esm({
48121
49264
  newRatio = Math.max(minRatio, Math.min(maxRatio, newRatio));
48122
49265
  setRatio(newRatio);
48123
49266
  };
48124
- const handleMouseUp = () => {
49267
+ const handlePointerUp = () => {
48125
49268
  isDragging.current = false;
48126
- document.removeEventListener("mousemove", handleMouseMove);
48127
- document.removeEventListener("mouseup", handleMouseUp);
49269
+ document.removeEventListener("pointermove", handlePointerMove);
49270
+ document.removeEventListener("pointerup", handlePointerUp);
49271
+ document.removeEventListener("pointercancel", handlePointerUp);
48128
49272
  };
48129
- document.addEventListener("mousemove", handleMouseMove);
48130
- document.addEventListener("mouseup", handleMouseUp);
49273
+ document.addEventListener("pointermove", handlePointerMove);
49274
+ document.addEventListener("pointerup", handlePointerUp);
49275
+ document.addEventListener("pointercancel", handlePointerUp);
48131
49276
  },
48132
49277
  [direction, minSize, resizable]
48133
49278
  );
@@ -48156,9 +49301,9 @@ var init_SplitPane = __esm({
48156
49301
  resizable && /* @__PURE__ */ jsx(
48157
49302
  "div",
48158
49303
  {
48159
- onMouseDown: handleMouseDown,
49304
+ onPointerDown: handlePointerDown,
48160
49305
  className: cn(
48161
- "flex-shrink-0 bg-border transition-colors",
49306
+ "flex-shrink-0 bg-border transition-colors touch-none",
48162
49307
  isHorizontal ? "w-1 cursor-col-resize hover:w-1.5 hover:bg-muted-foreground" : "h-1 cursor-row-resize hover:h-1.5 hover:bg-muted-foreground"
48163
49308
  )
48164
49309
  }
@@ -48209,7 +49354,7 @@ var init_StatCard = __esm({
48209
49354
  const labelToUse = propLabel ?? propTitle;
48210
49355
  const eventBus = useEventBus();
48211
49356
  const { t } = useTranslate();
48212
- const handleActionClick = React82__default.useCallback(() => {
49357
+ const handleActionClick = React85__default.useCallback(() => {
48213
49358
  if (action?.event) {
48214
49359
  eventBus.emit(`UI:${action.event}`, {});
48215
49360
  }
@@ -48220,7 +49365,7 @@ var init_StatCard = __esm({
48220
49365
  const data = Array.isArray(entity) ? entity : entity ? [entity] : [];
48221
49366
  const isLoading = externalLoading ?? false;
48222
49367
  const error = externalError;
48223
- const computeMetricValue = React82__default.useCallback(
49368
+ const computeMetricValue = React85__default.useCallback(
48224
49369
  (metric, items) => {
48225
49370
  if (metric.value !== void 0) {
48226
49371
  return metric.value;
@@ -48259,7 +49404,7 @@ var init_StatCard = __esm({
48259
49404
  },
48260
49405
  []
48261
49406
  );
48262
- const schemaStats = React82__default.useMemo(() => {
49407
+ const schemaStats = React85__default.useMemo(() => {
48263
49408
  if (!metrics || metrics.length === 0) return null;
48264
49409
  return metrics.map((metric) => ({
48265
49410
  label: metric.label,
@@ -48267,7 +49412,7 @@ var init_StatCard = __esm({
48267
49412
  format: metric.format
48268
49413
  }));
48269
49414
  }, [metrics, data, computeMetricValue]);
48270
- const calculatedTrend = React82__default.useMemo(() => {
49415
+ const calculatedTrend = React85__default.useMemo(() => {
48271
49416
  if (manualTrend !== void 0) return manualTrend;
48272
49417
  if (previousValue === void 0 || currentValue === void 0)
48273
49418
  return void 0;
@@ -49233,7 +50378,7 @@ var init_Timeline = __esm({
49233
50378
  }) => {
49234
50379
  const { t } = useTranslate();
49235
50380
  const entityData = Array.isArray(entity) ? entity : [];
49236
- const items = React82__default.useMemo(() => {
50381
+ const items = React85__default.useMemo(() => {
49237
50382
  if (propItems) return propItems;
49238
50383
  if (entityData.length === 0) return [];
49239
50384
  return entityData.map((record, idx) => {
@@ -49340,7 +50485,7 @@ var init_Timeline = __esm({
49340
50485
  }
49341
50486
  });
49342
50487
  function extractToastProps(children) {
49343
- if (!React82__default.isValidElement(children)) {
50488
+ if (!React85__default.isValidElement(children)) {
49344
50489
  if (typeof children === "string") {
49345
50490
  return { message: children };
49346
50491
  }
@@ -49378,7 +50523,7 @@ var init_ToastSlot = __esm({
49378
50523
  eventBus.emit("UI:CLOSE");
49379
50524
  };
49380
50525
  if (!isVisible) return null;
49381
- const isCustomContent = React82__default.isValidElement(children) && !message;
50526
+ const isCustomContent = React85__default.isValidElement(children) && !message;
49382
50527
  return /* @__PURE__ */ jsx(Box, { className: "fixed bottom-4 right-4 z-50", children: isCustomContent ? children : /* @__PURE__ */ jsx(
49383
50528
  Toast,
49384
50529
  {
@@ -49394,82 +50539,442 @@ var init_ToastSlot = __esm({
49394
50539
  ToastSlot.displayName = "ToastSlot";
49395
50540
  }
49396
50541
  });
50542
+ function resolveManifestUrl4(manifest, relative) {
50543
+ if (relative == null || relative === "") return void 0;
50544
+ if (/^https?:\/\//.test(relative)) return relative;
50545
+ const base = manifest?.baseUrl;
50546
+ if (base == null) return relative;
50547
+ const cleanBase = base.replace(/\/$/, "");
50548
+ const cleanRel = relative.replace(/^\//, "");
50549
+ return `${cleanBase}/${cleanRel}`;
50550
+ }
50551
+ function readPlayer(v) {
50552
+ if (v == null || typeof v !== "object") return void 0;
50553
+ const o = v;
50554
+ return { x: num(o.x), y: num(o.y) };
50555
+ }
50556
+ function TopDownShooterBoard({
50557
+ entity,
50558
+ player: propPlayer,
50559
+ enemies: propEnemies,
50560
+ projectiles: propProjectiles,
50561
+ score: propScore,
50562
+ wave: propWave,
50563
+ lives: propLives,
50564
+ result: propResult,
50565
+ assetManifest: propAssetManifest,
50566
+ width = DEFAULT_WIDTH,
50567
+ height = DEFAULT_HEIGHT,
50568
+ spriteSize = 32,
50569
+ moveEvent = "MOVE",
50570
+ fireEvent = "FIRE",
50571
+ playAgainEvent = "PLAY_AGAIN",
50572
+ gameEndEvent = "GAME_END",
50573
+ className
50574
+ }) {
50575
+ const board = boardEntity(entity) ?? {};
50576
+ const eventBus = useEventBus();
50577
+ const { t } = useTranslate();
50578
+ const assetManifest = propAssetManifest ?? board.assetManifest;
50579
+ const player = propPlayer ?? readPlayer(board.player) ?? { x: width / 2, y: height - spriteSize };
50580
+ const enemies = useMemo(
50581
+ () => propEnemies ?? rows(board.enemies).map((r) => ({
50582
+ id: str(r.id),
50583
+ x: num(r.x),
50584
+ y: num(r.y),
50585
+ hp: num(r.hp, 1)
50586
+ })),
50587
+ [propEnemies, board.enemies]
50588
+ );
50589
+ const projectiles = useMemo(
50590
+ () => propProjectiles ?? rows(board.projectiles).map((r) => ({
50591
+ id: str(r.id),
50592
+ x: num(r.x),
50593
+ y: num(r.y),
50594
+ vx: num(r.vx),
50595
+ vy: num(r.vy)
50596
+ })),
50597
+ [propProjectiles, board.projectiles]
50598
+ );
50599
+ const score = propScore ?? num(board.score, 0);
50600
+ const wave = propWave ?? num(board.wave, 1);
50601
+ const lives = propLives ?? num(board.lives, 3);
50602
+ const result = propResult ?? (str(board.result) || "none");
50603
+ const playerSprite = resolveManifestUrl4(assetManifest, assetManifest?.units?.player);
50604
+ const enemySprite = resolveManifestUrl4(assetManifest, assetManifest?.units?.enemy);
50605
+ const projectileSprite = resolveManifestUrl4(assetManifest, assetManifest?.features?.projectile);
50606
+ const backgroundSprite = resolveManifestUrl4(assetManifest, assetManifest?.background);
50607
+ const spriteUrls = useMemo(
50608
+ () => [playerSprite, enemySprite, projectileSprite].filter((u) => u != null),
50609
+ [playerSprite, enemySprite, projectileSprite]
50610
+ );
50611
+ const { getImage } = useImageCache(spriteUrls);
50612
+ const sceneRef = useRef({ player, enemies, projectiles, playerSprite, enemySprite, projectileSprite });
50613
+ sceneRef.current = { player, enemies, projectiles, playerSprite, enemySprite, projectileSprite };
50614
+ boardLog2.debug("shooter-resolve", {
50615
+ player: { x: player.x, y: player.y },
50616
+ enemyCount: enemies.length,
50617
+ projectileCount: projectiles.length,
50618
+ wave,
50619
+ lives,
50620
+ result,
50621
+ hasPlayerSprite: playerSprite != null
50622
+ });
50623
+ const onDraw = useCallback(
50624
+ (ctx, _frame) => {
50625
+ const scene = sceneRef.current;
50626
+ ctx.fillStyle = "#10141c";
50627
+ ctx.fillRect(0, 0, width, height);
50628
+ const half = spriteSize / 2;
50629
+ for (const p2 of scene.projectiles) {
50630
+ const img = scene.projectileSprite ? getImage(scene.projectileSprite) : void 0;
50631
+ if (img) {
50632
+ ctx.drawImage(img, p2.x - half / 2, p2.y - half / 2, spriteSize / 2, spriteSize / 2);
50633
+ } else {
50634
+ ctx.fillStyle = "#ffd34d";
50635
+ ctx.beginPath();
50636
+ ctx.arc(p2.x, p2.y, spriteSize / 6, 0, Math.PI * 2);
50637
+ ctx.fill();
50638
+ }
50639
+ }
50640
+ for (const e of scene.enemies) {
50641
+ const img = scene.enemySprite ? getImage(scene.enemySprite) : void 0;
50642
+ if (img) {
50643
+ ctx.drawImage(img, e.x - half, e.y - half, spriteSize, spriteSize);
50644
+ } else {
50645
+ ctx.fillStyle = "#e0524d";
50646
+ ctx.fillRect(e.x - half, e.y - half, spriteSize, spriteSize);
50647
+ }
50648
+ }
50649
+ const pImg = scene.playerSprite ? getImage(scene.playerSprite) : void 0;
50650
+ if (pImg) {
50651
+ ctx.drawImage(pImg, scene.player.x - half, scene.player.y - half, spriteSize, spriteSize);
50652
+ } else {
50653
+ ctx.fillStyle = "#4da3ff";
50654
+ ctx.beginPath();
50655
+ ctx.moveTo(scene.player.x, scene.player.y - half);
50656
+ ctx.lineTo(scene.player.x + half, scene.player.y + half);
50657
+ ctx.lineTo(scene.player.x - half, scene.player.y + half);
50658
+ ctx.closePath();
50659
+ ctx.fill();
50660
+ }
50661
+ },
50662
+ [getImage, width, height, spriteSize]
50663
+ );
50664
+ const moveEventRef = useRef(moveEvent);
50665
+ moveEventRef.current = moveEvent;
50666
+ const fireEventRef = useRef(fireEvent);
50667
+ fireEventRef.current = fireEvent;
50668
+ const resultRef = useRef(result);
50669
+ resultRef.current = result;
50670
+ const playerRef = useRef(player);
50671
+ playerRef.current = player;
50672
+ useEffect(() => {
50673
+ function onKeyDown(e) {
50674
+ if (resultRef.current !== "none") return;
50675
+ let dx = 0;
50676
+ let dy = 0;
50677
+ switch (e.key) {
50678
+ case "ArrowUp":
50679
+ case "w":
50680
+ dy = -1;
50681
+ break;
50682
+ case "ArrowDown":
50683
+ case "s":
50684
+ dy = 1;
50685
+ break;
50686
+ case "ArrowLeft":
50687
+ case "a":
50688
+ dx = -1;
50689
+ break;
50690
+ case "ArrowRight":
50691
+ case "d":
50692
+ dx = 1;
50693
+ break;
50694
+ case " ": {
50695
+ e.preventDefault();
50696
+ const evt = fireEventRef.current;
50697
+ if (evt) {
50698
+ eventBus.emit(`UI:${evt}`, { tx: playerRef.current.x, ty: 0 });
50699
+ }
50700
+ return;
50701
+ }
50702
+ default:
50703
+ return;
50704
+ }
50705
+ e.preventDefault();
50706
+ const moveEvt = moveEventRef.current;
50707
+ if (moveEvt) {
50708
+ eventBus.emit(`UI:${moveEvt}`, { dx, dy });
50709
+ }
50710
+ }
50711
+ window.addEventListener("keydown", onKeyDown);
50712
+ return () => window.removeEventListener("keydown", onKeyDown);
50713
+ }, [eventBus]);
50714
+ const handleFireClick = useCallback(
50715
+ (e) => {
50716
+ if (result !== "none") return;
50717
+ const evt = fireEventRef.current;
50718
+ if (!evt) return;
50719
+ const rect = e.currentTarget.getBoundingClientRect();
50720
+ const tx = e.clientX - rect.left;
50721
+ const ty = e.clientY - rect.top;
50722
+ eventBus.emit(`UI:${evt}`, { tx, ty });
50723
+ },
50724
+ [result, eventBus]
50725
+ );
50726
+ const handlePlayAgain = useCallback(() => {
50727
+ if (playAgainEvent) {
50728
+ eventBus.emit(`UI:${playAgainEvent}`, {});
50729
+ }
50730
+ }, [playAgainEvent, eventBus]);
50731
+ const gameEndEmittedRef = useRef(false);
50732
+ useEffect(() => {
50733
+ if ((result === "won" || result === "lost") && gameEndEvent) {
50734
+ if (!gameEndEmittedRef.current) {
50735
+ gameEndEmittedRef.current = true;
50736
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
50737
+ }
50738
+ } else {
50739
+ gameEndEmittedRef.current = false;
50740
+ }
50741
+ }, [result, gameEndEvent, eventBus]);
50742
+ const isGameOver = result === "won" || result === "lost";
50743
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("top-down-shooter-board relative bg-background", className), gap: "none", children: [
50744
+ /* @__PURE__ */ jsxs(HStack, { className: "px-4 py-2 border-b border-border bg-surface", gap: "lg", align: "center", children: [
50745
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
50746
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("shooter.score") ?? "Score" }),
50747
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: score })
50748
+ ] }),
50749
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
50750
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("shooter.wave") ?? "Wave" }),
50751
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", children: wave })
50752
+ ] }),
50753
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
50754
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("shooter.lives") ?? "Lives" }),
50755
+ /* @__PURE__ */ jsx(
50756
+ Typography,
50757
+ {
50758
+ variant: "body2",
50759
+ weight: "bold",
50760
+ className: lives <= 1 ? "text-error" : "text-success",
50761
+ children: lives
50762
+ }
50763
+ )
50764
+ ] })
50765
+ ] }),
50766
+ /* @__PURE__ */ jsxs(Box, { className: "relative", onClick: handleFireClick, children: [
50767
+ /* @__PURE__ */ jsx(
50768
+ GameCanvas2D,
50769
+ {
50770
+ width,
50771
+ height,
50772
+ backgroundImage: backgroundSprite,
50773
+ assetBaseUrl: assetManifest?.baseUrl,
50774
+ onDraw
50775
+ }
50776
+ ),
50777
+ 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: [
50778
+ /* @__PURE__ */ jsx(
50779
+ Typography,
50780
+ {
50781
+ variant: "h2",
50782
+ className: cn(
50783
+ "text-4xl font-black tracking-widest uppercase",
50784
+ result === "won" ? "text-warning" : "text-error"
50785
+ ),
50786
+ children: result === "won" ? t("shooter.victory") ?? "Victory!" : t("shooter.defeat") ?? "Defeat!"
50787
+ }
50788
+ ),
50789
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: result === "won" ? t("shooter.cleared") ?? `Cleared wave ${wave} \u2014 score ${score}` : t("shooter.overrun") ?? "You were overrun." }),
50790
+ /* @__PURE__ */ jsx(
50791
+ Button,
50792
+ {
50793
+ variant: "primary",
50794
+ className: "px-8 py-3 font-semibold",
50795
+ onClick: handlePlayAgain,
50796
+ children: t("shooter.playAgain") ?? "Play Again"
50797
+ }
50798
+ )
50799
+ ] }) })
50800
+ ] })
50801
+ ] });
50802
+ }
50803
+ var boardLog2, DEFAULT_WIDTH, DEFAULT_HEIGHT;
50804
+ var init_TopDownShooterBoard = __esm({
50805
+ "components/game/organisms/TopDownShooterBoard.tsx"() {
50806
+ "use client";
50807
+ init_cn();
50808
+ init_useEventBus();
50809
+ init_Box();
50810
+ init_Button();
50811
+ init_Typography();
50812
+ init_Stack();
50813
+ init_GameCanvas2D();
50814
+ init_useImageCache();
50815
+ init_boardEntity();
50816
+ boardLog2 = createLogger("almadar:ui:game:top-down-shooter-board");
50817
+ DEFAULT_WIDTH = 640;
50818
+ DEFAULT_HEIGHT = 480;
50819
+ TopDownShooterBoard.displayName = "TopDownShooterBoard";
50820
+ }
50821
+ });
50822
+ function isEntityRow3(value) {
50823
+ return value != null && typeof value === "object" && !Array.isArray(value);
50824
+ }
50825
+ function TopDownShooterTemplate({
50826
+ entity,
50827
+ title = "Arena Shooter",
50828
+ player,
50829
+ enemies,
50830
+ projectiles,
50831
+ score,
50832
+ wave,
50833
+ lives,
50834
+ result,
50835
+ assetManifest,
50836
+ width,
50837
+ height,
50838
+ spriteSize,
50839
+ moveEvent,
50840
+ fireEvent,
50841
+ playAgainEvent,
50842
+ gameEndEvent,
50843
+ className
50844
+ }) {
50845
+ const resolved = isEntityRow3(entity) ? entity : void 0;
50846
+ return /* @__PURE__ */ jsxs(
50847
+ Box,
50848
+ {
50849
+ display: "flex",
50850
+ fullHeight: true,
50851
+ className: cn("top-down-shooter-template flex-col", className),
50852
+ children: [
50853
+ /* @__PURE__ */ jsx(
50854
+ HStack,
50855
+ {
50856
+ gap: "sm",
50857
+ align: "center",
50858
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
50859
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
50860
+ }
50861
+ ),
50862
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
50863
+ TopDownShooterBoard,
50864
+ {
50865
+ entity: resolved,
50866
+ player,
50867
+ enemies,
50868
+ projectiles,
50869
+ score,
50870
+ wave,
50871
+ lives,
50872
+ result,
50873
+ assetManifest,
50874
+ width,
50875
+ height,
50876
+ spriteSize,
50877
+ moveEvent,
50878
+ fireEvent,
50879
+ playAgainEvent,
50880
+ gameEndEvent,
50881
+ className: "h-full"
50882
+ }
50883
+ ) })
50884
+ ]
50885
+ }
50886
+ );
50887
+ }
50888
+ var init_TopDownShooterTemplate = __esm({
50889
+ "components/game/templates/TopDownShooterTemplate.tsx"() {
50890
+ init_cn();
50891
+ init_Box();
50892
+ init_Stack();
50893
+ init_Typography();
50894
+ init_TopDownShooterBoard();
50895
+ TopDownShooterTemplate.displayName = "TopDownShooterTemplate";
50896
+ }
50897
+ });
50898
+ function resolveManifestUrl5(manifest, relative) {
50899
+ if (relative == null) return void 0;
50900
+ if (/^https?:\/\//.test(relative)) return relative;
50901
+ const base = manifest?.baseUrl;
50902
+ if (base == null) return relative;
50903
+ const cleanBase = base.replace(/\/$/, "");
50904
+ const cleanRel = relative.replace(/^\//, "");
50905
+ return `${cleanBase}/${cleanRel}`;
50906
+ }
49397
50907
  function buildDefaultTDTiles() {
49398
50908
  const pathSet = new Set(DEFAULT_TD_PATH.map((p2) => `${p2.x},${p2.y}`));
49399
50909
  const tiles = [];
49400
50910
  for (let y = 0; y < TD_GRID_H; y++) {
49401
50911
  for (let x = 0; x < TD_GRID_W; x++) {
49402
50912
  if (pathSet.has(`${x},${y}`)) {
49403
- tiles.push({ x, y, terrain: "path", passable: false, terrainSprite: TERRAIN_SPRITES.path });
50913
+ tiles.push({ x, y, terrain: "path", passable: false });
49404
50914
  } else {
49405
50915
  const variant = (x * 3 + y * 5 + (x ^ y)) % 3;
49406
- const terrainKey = ["ground", "grass", "dirt"][variant];
49407
- tiles.push({ x, y, terrain: terrainKey, passable: true, terrainSprite: TERRAIN_SPRITES[terrainKey] ?? TERRAIN_SPRITES.ground });
50916
+ const terrainKey = ["ground", "grass", "stone"][variant];
50917
+ tiles.push({ x, y, terrain: terrainKey, passable: true });
49408
50918
  }
49409
50919
  }
49410
50920
  }
49411
50921
  return tiles;
49412
50922
  }
49413
- function tilesToIso(tiles) {
49414
- return tiles.map((t) => ({
49415
- x: t.x,
49416
- y: t.y,
49417
- terrain: t.terrain,
49418
- terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
49419
- passable: t.passable
49420
- }));
49421
- }
49422
- function towersToUnits(towers) {
49423
- return towers.map((t) => ({
49424
- id: t.id,
49425
- position: { x: t.x, y: t.y },
49426
- name: "Tower",
49427
- team: "player",
49428
- sprite: TOWER_SPRITE,
49429
- unitType: "guardian",
49430
- health: 1,
49431
- maxHealth: 1
49432
- }));
50923
+ function tilesToIso2(tiles, manifest) {
50924
+ return tiles.map((t) => {
50925
+ const key = t.terrain ?? "ground";
50926
+ const sprite = t.terrainSprite ?? resolveManifestUrl5(manifest, manifest?.terrains?.[key]) ?? resolveManifestUrl5(manifest, manifest?.terrains?.ground);
50927
+ return {
50928
+ x: t.x,
50929
+ y: t.y,
50930
+ terrain: t.terrain,
50931
+ terrainSprite: sprite,
50932
+ passable: t.passable
50933
+ };
50934
+ });
49433
50935
  }
49434
- function creepsToUnits(creeps) {
50936
+ function creepsToUnits(creeps, manifest) {
50937
+ const sprite = resolveManifestUrl5(manifest, manifest?.units?.creep);
49435
50938
  return creeps.map((c) => ({
49436
50939
  id: c.id,
49437
50940
  position: { x: c.x, y: c.y },
49438
50941
  name: "Creep",
49439
50942
  team: "enemy",
49440
- sprite: CREEP_SPRITE,
49441
- unitType: "scrapper",
50943
+ sprite,
50944
+ unitType: "creep",
49442
50945
  health: c.hp,
49443
50946
  maxHealth: c.maxHp
49444
50947
  }));
49445
50948
  }
49446
- function pathToFeatures(path) {
49447
- return path.map((p2, i) => ({
49448
- id: `path-${i}`,
49449
- x: p2.x,
49450
- y: p2.y,
49451
- type: "path-marker",
49452
- sprite: `${CDN6}world-map/road_straight.png`
49453
- }));
49454
- }
49455
- function heroToUnit(hero) {
50949
+ function heroToUnit(hero, manifest) {
49456
50950
  return {
49457
50951
  id: hero.id,
49458
50952
  position: { x: hero.x, y: hero.y },
49459
50953
  name: "Hero",
49460
50954
  team: "player",
49461
- sprite: HERO_SPRITE,
49462
- unitType: "amir",
50955
+ sprite: resolveManifestUrl5(manifest, manifest?.units?.hero),
50956
+ unitType: "hero",
49463
50957
  health: 1,
49464
50958
  maxHealth: 1
49465
50959
  };
49466
50960
  }
50961
+ function towersToFeatures(towers, manifest) {
50962
+ const sprite = resolveManifestUrl5(manifest, manifest?.features?.tower);
50963
+ return towers.map((t) => ({
50964
+ id: t.id,
50965
+ x: t.x,
50966
+ y: t.y,
50967
+ type: "tower",
50968
+ sprite
50969
+ }));
50970
+ }
49467
50971
  function TowerDefenseBoard({
49468
50972
  entity,
49469
50973
  tiles: propTiles,
49470
50974
  path: propPath,
49471
50975
  towers: propTowers,
49472
50976
  creeps: propCreeps,
50977
+ assetManifest: propAssetManifest,
49473
50978
  hero: propHero,
49474
50979
  gold: propGold,
49475
50980
  lives: propLives,
@@ -49492,12 +50997,51 @@ function TowerDefenseBoard({
49492
50997
  const board = boardEntity(entity) ?? {};
49493
50998
  const eventBus = useEventBus();
49494
50999
  const { t } = useTranslate();
49495
- const rawTiles = propTiles ?? rows(board.tiles);
51000
+ const assetManifest = propAssetManifest ?? board.assetManifest;
51001
+ const entityTiles = useMemo(
51002
+ () => rows(board.tiles).map((r) => ({
51003
+ x: num(r.x),
51004
+ y: num(r.y),
51005
+ terrain: r.terrain == null ? void 0 : str(r.terrain),
51006
+ terrainSprite: r.terrainSprite == null ? void 0 : str(r.terrainSprite),
51007
+ passable: r.passable !== false
51008
+ })),
51009
+ [board.tiles]
51010
+ );
51011
+ const rawTiles = propTiles ?? entityTiles;
49496
51012
  const tiles = rawTiles.length >= TD_GRID_W * TD_GRID_H ? rawTiles : DEFAULT_TD_TILES;
49497
- const rawPath = propPath ?? rows(board.path);
51013
+ const entityPath = useMemo(
51014
+ () => rows(board.path).map((r) => ({ x: num(r.x), y: num(r.y) })),
51015
+ [board.path]
51016
+ );
51017
+ const rawPath = propPath ?? entityPath;
49498
51018
  const path = rawPath.length > 0 ? rawPath : DEFAULT_TD_PATH;
49499
- const towers = propTowers ?? rows(board.towers);
49500
- const creeps = propCreeps ?? rows(board.creeps);
51019
+ const entityTowers = useMemo(
51020
+ () => rows(board.towers).map((r) => ({
51021
+ id: str(r.id),
51022
+ x: num(r.x),
51023
+ y: num(r.y),
51024
+ range: num(r.range),
51025
+ damage: num(r.damage),
51026
+ cooldown: num(r.cooldown),
51027
+ lastFiredAt: r.lastFiredAt == null ? void 0 : num(r.lastFiredAt)
51028
+ })),
51029
+ [board.towers]
51030
+ );
51031
+ const towers = propTowers ?? entityTowers;
51032
+ const entityCreeps = useMemo(
51033
+ () => rows(board.creeps).map((r) => ({
51034
+ id: str(r.id),
51035
+ x: num(r.x),
51036
+ y: num(r.y),
51037
+ hp: num(r.hp),
51038
+ maxHp: num(r.maxHp),
51039
+ pathIndex: num(r.pathIndex),
51040
+ speed: num(r.speed)
51041
+ })),
51042
+ [board.creeps]
51043
+ );
51044
+ const creeps = propCreeps ?? entityCreeps;
49501
51045
  const hero = propHero ?? { id: "hero", x: 8, y: 8 };
49502
51046
  const gold = propGold ?? num(board.gold, 100);
49503
51047
  const lives = propLives ?? num(board.lives, 20);
@@ -49549,12 +51093,11 @@ function TowerDefenseBoard({
49549
51093
  (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
49550
51094
  ).map((t2) => ({ x: t2.x, y: t2.y }));
49551
51095
  }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
49552
- const isoTiles = useMemo(() => tilesToIso(tiles), [tiles]);
49553
- const towerUnits = useMemo(() => towersToUnits(towers), [towers]);
49554
- const creepUnits = useMemo(() => creepsToUnits(creeps), [creeps]);
49555
- const heroUnit = useMemo(() => heroToUnit(hero), [hero]);
49556
- const isoUnits = useMemo(() => [...towerUnits, ...creepUnits, heroUnit], [towerUnits, creepUnits, heroUnit]);
49557
- const pathFeatures = useMemo(() => pathToFeatures(path), [path]);
51096
+ const isoTiles = useMemo(() => tilesToIso2(tiles, assetManifest), [tiles, assetManifest]);
51097
+ const creepUnits = useMemo(() => creepsToUnits(creeps, assetManifest), [creeps, assetManifest]);
51098
+ const heroUnit = useMemo(() => heroToUnit(hero, assetManifest), [hero, assetManifest]);
51099
+ const isoUnits = useMemo(() => [...creepUnits, heroUnit], [creepUnits, heroUnit]);
51100
+ const towerFeatures = useMemo(() => towersToFeatures(towers, assetManifest), [towers, assetManifest]);
49558
51101
  const handleTileClick = useCallback((x, y) => {
49559
51102
  if (result !== "none") return;
49560
51103
  if (pathPositions.has(`${x},${y}`)) return;
@@ -49613,7 +51156,9 @@ function TowerDefenseBoard({
49613
51156
  {
49614
51157
  tiles: isoTiles,
49615
51158
  units: isoUnits,
49616
- features: pathFeatures,
51159
+ features: towerFeatures,
51160
+ assetBaseUrl: assetManifest?.baseUrl,
51161
+ assetManifest,
49617
51162
  validMoves,
49618
51163
  hoveredTile,
49619
51164
  onTileClick: handleTileClick,
@@ -49652,7 +51197,7 @@ function TowerDefenseBoard({
49652
51197
  ] }) })
49653
51198
  ] });
49654
51199
  }
49655
- var CDN6, TD_GRID_W, TD_GRID_H, TERRAIN_SPRITES, DEFAULT_TD_PATH, DEFAULT_TD_TILES, TOWER_SPRITE, CREEP_SPRITE, HERO_SPRITE;
51200
+ var TD_GRID_W, TD_GRID_H, DEFAULT_TD_PATH, DEFAULT_TD_TILES;
49656
51201
  var init_TowerDefenseBoard = __esm({
49657
51202
  "components/game/organisms/TowerDefenseBoard.tsx"() {
49658
51203
  "use client";
@@ -49664,16 +51209,8 @@ var init_TowerDefenseBoard = __esm({
49664
51209
  init_Stack();
49665
51210
  init_IsometricCanvas();
49666
51211
  init_boardEntity();
49667
- CDN6 = "https://almadar-kflow-assets.web.app/shared/";
49668
51212
  TD_GRID_W = 16;
49669
51213
  TD_GRID_H = 16;
49670
- TERRAIN_SPRITES = {
49671
- ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
49672
- path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
49673
- wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`,
49674
- grass: `${CDN6}isometric-dungeon/Isometric/dirt_E.png`,
49675
- dirt: `${CDN6}isometric-dungeon/Isometric/dirtTiles_E.png`
49676
- };
49677
51214
  DEFAULT_TD_PATH = [
49678
51215
  { x: 2, y: 0 },
49679
51216
  { x: 2, y: 1 },
@@ -49726,9 +51263,6 @@ var init_TowerDefenseBoard = __esm({
49726
51263
  { x: 13, y: 15 }
49727
51264
  ];
49728
51265
  DEFAULT_TD_TILES = buildDefaultTDTiles();
49729
- TOWER_SPRITE = `${CDN6}sprite-sheets/guardian-sprite-sheet-se.png`;
49730
- CREEP_SPRITE = `${CDN6}sprite-sheets/scrapper-sprite-sheet-se.png`;
49731
- HERO_SPRITE = `${CDN6}sprite-sheets/amir-sprite-sheet-se.png`;
49732
51266
  TowerDefenseBoard.displayName = "TowerDefenseBoard";
49733
51267
  }
49734
51268
  });
@@ -49996,6 +51530,214 @@ var init_UncontrolledBattleBoard = __esm({
49996
51530
  UncontrolledBattleBoard.displayName = "UncontrolledBattleBoard";
49997
51531
  }
49998
51532
  });
51533
+ function resolveManifestUrl6(manifest, relative) {
51534
+ if (relative == null) return void 0;
51535
+ if (/^https?:\/\//.test(relative)) return relative;
51536
+ const base = manifest?.baseUrl;
51537
+ if (base == null) return relative;
51538
+ const cleanBase = base.replace(/\/$/, "");
51539
+ const cleanRel = relative.replace(/^\//, "");
51540
+ return `${cleanBase}/${cleanRel}`;
51541
+ }
51542
+ function VisualNovelBoard({
51543
+ entity,
51544
+ nodes: propNodes,
51545
+ currentNodeId: propCurrentNodeId,
51546
+ assetManifest: propAssetManifest,
51547
+ typewriterSpeed = 30,
51548
+ portraitScale = 1,
51549
+ chooseEvent,
51550
+ advanceEvent,
51551
+ restartEvent,
51552
+ className
51553
+ }) {
51554
+ const board = boardEntity(entity) ?? {};
51555
+ const eventBus = useEventBus();
51556
+ const assetManifest = propAssetManifest ?? board.assetManifest;
51557
+ const entityNodes = useMemo(
51558
+ () => rows(board.nodes).map((r) => ({
51559
+ id: str(r.id),
51560
+ speaker: str(r.speaker),
51561
+ text: str(r.text),
51562
+ backgroundKey: r.backgroundKey == null ? void 0 : str(r.backgroundKey),
51563
+ portraitKey: r.portraitKey == null ? void 0 : str(r.portraitKey),
51564
+ choices: rows(r.choices).map((c) => ({
51565
+ label: str(c.label),
51566
+ nextId: str(c.nextId)
51567
+ }))
51568
+ })),
51569
+ [board.nodes]
51570
+ );
51571
+ const rawNodes = propNodes ?? entityNodes;
51572
+ const nodes = rawNodes.length > 0 ? rawNodes : DEFAULT_NODES;
51573
+ const currentNodeId = propCurrentNodeId ?? (str(board.currentNodeId) || nodes[0]?.id);
51574
+ const currentNode = nodes.find((n) => n.id === currentNodeId) ?? nodes[0];
51575
+ const backgroundImage = useMemo(
51576
+ () => resolveManifestUrl6(assetManifest, assetManifest?.backgrounds?.[currentNode?.backgroundKey ?? ""]),
51577
+ [assetManifest, currentNode?.backgroundKey]
51578
+ );
51579
+ const portraitUrl = useMemo(
51580
+ () => resolveManifestUrl6(assetManifest, assetManifest?.portraits?.[currentNode?.portraitKey ?? ""]),
51581
+ [assetManifest, currentNode?.portraitKey]
51582
+ );
51583
+ const dialogueChoices = useMemo(
51584
+ () => (currentNode?.choices ?? []).map((c) => ({ text: c.label, next: c.nextId })),
51585
+ [currentNode?.choices]
51586
+ );
51587
+ const handleChoice = useCallback(
51588
+ (choice) => {
51589
+ const index = dialogueChoices.findIndex((c) => c.next === choice.next && c.text === choice.text);
51590
+ if (chooseEvent) {
51591
+ eventBus.emit(`UI:${chooseEvent}`, { choiceIndex: index });
51592
+ }
51593
+ },
51594
+ [dialogueChoices, chooseEvent, eventBus]
51595
+ );
51596
+ const handleAdvance = useCallback(() => {
51597
+ if (advanceEvent) {
51598
+ eventBus.emit(`UI:${advanceEvent}`, {});
51599
+ }
51600
+ }, [advanceEvent, eventBus]);
51601
+ const handleRestart = useCallback(() => {
51602
+ if (restartEvent) {
51603
+ eventBus.emit(`UI:${restartEvent}`, {});
51604
+ }
51605
+ }, [restartEvent, eventBus]);
51606
+ if (!currentNode) {
51607
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("visual-novel-board relative min-h-[600px] bg-background items-center justify-center", className), gap: "lg", children: [
51608
+ /* @__PURE__ */ jsx(Typography, { variant: "h3", color: "muted", children: "No dialogue nodes" }),
51609
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleRestart, children: "Restart" })
51610
+ ] });
51611
+ }
51612
+ return /* @__PURE__ */ jsx(Box, { className: cn("visual-novel-board relative min-h-[600px] bg-background overflow-hidden", className), children: /* @__PURE__ */ jsx(
51613
+ DialogueBox,
51614
+ {
51615
+ dialogue: {
51616
+ speaker: currentNode.speaker,
51617
+ text: currentNode.text,
51618
+ choices: dialogueChoices
51619
+ },
51620
+ typewriterSpeed,
51621
+ position: "bottom",
51622
+ backgroundImage,
51623
+ portraitUrl,
51624
+ portraitScale,
51625
+ onChoice: handleChoice,
51626
+ onAdvance: handleAdvance
51627
+ }
51628
+ ) });
51629
+ }
51630
+ var DEFAULT_NODES;
51631
+ var init_VisualNovelBoard = __esm({
51632
+ "components/game/organisms/VisualNovelBoard.tsx"() {
51633
+ "use client";
51634
+ init_cn();
51635
+ init_useEventBus();
51636
+ init_Box();
51637
+ init_Typography();
51638
+ init_Stack();
51639
+ init_Button();
51640
+ init_DialogueBox();
51641
+ init_boardEntity();
51642
+ DEFAULT_NODES = [
51643
+ {
51644
+ id: "start",
51645
+ speaker: "Narrator",
51646
+ text: "The corridor stretches into shadow. A lone sentinel blocks your path.",
51647
+ backgroundKey: "corridor",
51648
+ portraitKey: "guide",
51649
+ choices: [
51650
+ { label: "Approach the sentinel.", nextId: "meet" },
51651
+ { label: "Turn back.", nextId: "retreat" }
51652
+ ]
51653
+ },
51654
+ {
51655
+ id: "meet",
51656
+ speaker: "Sentinel",
51657
+ text: "You carry the old marks. Speak your purpose, traveler.",
51658
+ backgroundKey: "forge",
51659
+ portraitKey: "rival",
51660
+ choices: [
51661
+ { label: "I seek the core.", nextId: "end" },
51662
+ { label: "I am only passing through.", nextId: "retreat" }
51663
+ ]
51664
+ },
51665
+ {
51666
+ id: "retreat",
51667
+ speaker: "Narrator",
51668
+ text: "You withdraw into the dark. The path will wait for braver feet.",
51669
+ backgroundKey: "corridor",
51670
+ choices: [{ label: "Begin again.", nextId: "start" }]
51671
+ },
51672
+ {
51673
+ id: "end",
51674
+ speaker: "Sentinel",
51675
+ text: "Then the way is open. Walk it well.",
51676
+ backgroundKey: "core",
51677
+ portraitKey: "rival",
51678
+ choices: [{ label: "Begin again.", nextId: "start" }]
51679
+ }
51680
+ ];
51681
+ VisualNovelBoard.displayName = "VisualNovelBoard";
51682
+ }
51683
+ });
51684
+ function VisualNovelTemplate({
51685
+ entity,
51686
+ title = "Visual Novel",
51687
+ nodes,
51688
+ currentNodeId,
51689
+ typewriterSpeed,
51690
+ portraitScale,
51691
+ chooseEvent,
51692
+ advanceEvent,
51693
+ restartEvent,
51694
+ className
51695
+ }) {
51696
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
51697
+ return /* @__PURE__ */ jsxs(
51698
+ Box,
51699
+ {
51700
+ display: "flex",
51701
+ fullHeight: true,
51702
+ className: cn("visual-novel-template flex-col", className),
51703
+ children: [
51704
+ /* @__PURE__ */ jsx(
51705
+ HStack,
51706
+ {
51707
+ gap: "sm",
51708
+ align: "center",
51709
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
51710
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
51711
+ }
51712
+ ),
51713
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
51714
+ VisualNovelBoard,
51715
+ {
51716
+ entity: resolved,
51717
+ nodes,
51718
+ currentNodeId,
51719
+ typewriterSpeed,
51720
+ portraitScale,
51721
+ chooseEvent,
51722
+ advanceEvent,
51723
+ restartEvent,
51724
+ className: "h-full"
51725
+ }
51726
+ ) })
51727
+ ]
51728
+ }
51729
+ );
51730
+ }
51731
+ var init_VisualNovelTemplate = __esm({
51732
+ "components/game/templates/VisualNovelTemplate.tsx"() {
51733
+ init_cn();
51734
+ init_Box();
51735
+ init_Stack();
51736
+ init_Typography();
51737
+ init_VisualNovelBoard();
51738
+ VisualNovelTemplate.displayName = "VisualNovelTemplate";
51739
+ }
51740
+ });
49999
51741
  function heroPosition(h) {
50000
51742
  if ("position" in h && h.position != null) return h.position;
50001
51743
  return vec2(h.position);
@@ -50336,48 +52078,48 @@ function WorldMapTemplate({
50336
52078
  }
50337
52079
  );
50338
52080
  }
50339
- var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
52081
+ var CDN5, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
50340
52082
  var init_WorldMapTemplate = __esm({
50341
52083
  "components/game/templates/WorldMapTemplate.tsx"() {
50342
52084
  init_WorldMapBoard();
50343
- CDN7 = "https://almadar-kflow-assets.web.app/shared";
52085
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
50344
52086
  DEFAULT_WORLDMAP_TILES = [
50345
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50346
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50347
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50348
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50349
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50350
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50351
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50352
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50353
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50354
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50355
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50356
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50357
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
50358
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50359
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50360
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50361
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50362
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50363
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
50364
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50365
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50366
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50367
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50368
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50369
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
52087
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52088
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52089
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52090
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52091
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52092
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52093
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52094
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52095
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52096
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52097
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52098
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52099
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
52100
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52101
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52102
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52103
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52104
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52105
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
52106
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52107
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52108
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52109
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52110
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52111
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
50370
52112
  ];
50371
52113
  DEFAULT_WORLDMAP_UNITS = [
50372
- { 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` },
50373
- { 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` }
52114
+ { id: "h1", position: { x: 1, y: 1 }, unitType: "hero", name: "Amir", team: "player", health: 10, maxHealth: 10, sprite: `${CDN5}/sprite-sheets/amir-sprite-sheet-se.png` },
52115
+ { id: "h2", position: { x: 3, y: 3 }, unitType: "scout", name: "Archivist", team: "player", health: 10, maxHealth: 10, sprite: `${CDN5}/sprite-sheets/archivist-sprite-sheet-se.png` }
50374
52116
  ];
50375
52117
  DEFAULT_WORLDMAP_FEATURES = [
50376
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
50377
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
52118
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN5}/scenes/world/capital.png` },
52119
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN5}/world-map/power_node.png` }
50378
52120
  ];
50379
52121
  DEFAULT_WORLDMAP_MANIFEST = {
50380
- baseUrl: CDN7,
52122
+ baseUrl: CDN5,
50381
52123
  terrains: {
50382
52124
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
50383
52125
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -50397,7 +52139,7 @@ var init_WorldMapTemplate = __esm({
50397
52139
  }
50398
52140
  });
50399
52141
  function lazyThree(name, loader) {
50400
- const Lazy = React82__default.lazy(
52142
+ const Lazy = React85__default.lazy(
50401
52143
  () => loader().then((m) => {
50402
52144
  const Resolved = m[name];
50403
52145
  if (!Resolved) {
@@ -50409,13 +52151,13 @@ function lazyThree(name, loader) {
50409
52151
  })
50410
52152
  );
50411
52153
  function ThreeWrapper(props) {
50412
- return React82__default.createElement(
52154
+ return React85__default.createElement(
50413
52155
  ThreeBoundary,
50414
52156
  { name },
50415
- React82__default.createElement(
50416
- React82__default.Suspense,
52157
+ React85__default.createElement(
52158
+ React85__default.Suspense,
50417
52159
  { fallback: null },
50418
- React82__default.createElement(Lazy, props)
52160
+ React85__default.createElement(Lazy, props)
50419
52161
  )
50420
52162
  );
50421
52163
  }
@@ -50459,7 +52201,10 @@ var init_component_registry_generated = __esm({
50459
52201
  init_CalendarGrid();
50460
52202
  init_CanvasEffect();
50461
52203
  init_Card();
52204
+ init_CardBattlerBoard();
52205
+ init_CardBattlerTemplate();
50462
52206
  init_CardGrid();
52207
+ init_CardHand();
50463
52208
  init_Carousel();
50464
52209
  init_CaseStudyCard();
50465
52210
  init_CaseStudyOrganism();
@@ -50470,6 +52215,8 @@ var init_component_registry_generated = __esm({
50470
52215
  init_ChartLegend();
50471
52216
  init_Checkbox();
50472
52217
  init_ChoiceButton();
52218
+ init_CityBuilderBoard();
52219
+ init_CityBuilderTemplate();
50473
52220
  init_ClassifierBoard();
50474
52221
  init_CodeBlock();
50475
52222
  init_CodeRunnerPanel();
@@ -50690,6 +52437,8 @@ var init_component_registry_generated = __esm({
50690
52437
  init_Toast();
50691
52438
  init_ToastSlot();
50692
52439
  init_Tooltip();
52440
+ init_TopDownShooterBoard();
52441
+ init_TopDownShooterTemplate();
50693
52442
  init_TowerDefenseBoard();
50694
52443
  init_TowerDefenseTemplate();
50695
52444
  init_TraitFrame();
@@ -50705,6 +52454,8 @@ var init_component_registry_generated = __esm({
50705
52454
  init_UploadDropZone();
50706
52455
  init_VersionDiff();
50707
52456
  init_ViolationAlert();
52457
+ init_VisualNovelBoard();
52458
+ init_VisualNovelTemplate();
50708
52459
  init_VoteStack();
50709
52460
  init_WaypointMarker();
50710
52461
  init_WizardContainer();
@@ -50713,7 +52464,7 @@ var init_component_registry_generated = __esm({
50713
52464
  init_WorldMapBoard();
50714
52465
  init_WorldMapTemplate();
50715
52466
  init_XPBar();
50716
- ThreeBoundary = class extends React82__default.Component {
52467
+ ThreeBoundary = class extends React85__default.Component {
50717
52468
  constructor() {
50718
52469
  super(...arguments);
50719
52470
  __publicField(this, "state", { failed: false });
@@ -50723,7 +52474,7 @@ var init_component_registry_generated = __esm({
50723
52474
  }
50724
52475
  render() {
50725
52476
  if (this.state.failed) {
50726
- return React82__default.createElement(
52477
+ return React85__default.createElement(
50727
52478
  "div",
50728
52479
  {
50729
52480
  "data-testid": "three-unavailable",
@@ -50779,7 +52530,10 @@ var init_component_registry_generated = __esm({
50779
52530
  "CalendarGrid": CalendarGrid,
50780
52531
  "CanvasEffect": CanvasEffect,
50781
52532
  "Card": Card,
52533
+ "CardBattlerBoard": CardBattlerBoard,
52534
+ "CardBattlerTemplate": CardBattlerTemplate,
50782
52535
  "CardGrid": CardGrid,
52536
+ "CardHand": CardHand,
50783
52537
  "Carousel": Carousel,
50784
52538
  "CaseStudyCard": CaseStudyCard,
50785
52539
  "CaseStudyOrganism": CaseStudyOrganism,
@@ -50790,6 +52544,8 @@ var init_component_registry_generated = __esm({
50790
52544
  "ChartLegend": ChartLegend,
50791
52545
  "Checkbox": Checkbox,
50792
52546
  "ChoiceButton": ChoiceButton,
52547
+ "CityBuilderBoard": CityBuilderBoard,
52548
+ "CityBuilderTemplate": CityBuilderTemplate,
50793
52549
  "ClassifierBoard": ClassifierBoard,
50794
52550
  "CodeBlock": CodeBlock,
50795
52551
  "CodeRunnerPanel": CodeRunnerPanel,
@@ -51022,6 +52778,8 @@ var init_component_registry_generated = __esm({
51022
52778
  "Toast": Toast,
51023
52779
  "ToastSlot": ToastSlot,
51024
52780
  "Tooltip": Tooltip,
52781
+ "TopDownShooterBoard": TopDownShooterBoard,
52782
+ "TopDownShooterTemplate": TopDownShooterTemplate,
51025
52783
  "TowerDefenseBoard": TowerDefenseBoard,
51026
52784
  "TowerDefenseTemplate": TowerDefenseTemplate,
51027
52785
  "TraitFrame": TraitFrame,
@@ -51038,6 +52796,8 @@ var init_component_registry_generated = __esm({
51038
52796
  "VStack": VStack,
51039
52797
  "VersionDiff": VersionDiff,
51040
52798
  "ViolationAlert": ViolationAlert,
52799
+ "VisualNovelBoard": VisualNovelBoard,
52800
+ "VisualNovelTemplate": VisualNovelTemplate,
51041
52801
  "VoteStack": VoteStack,
51042
52802
  "WaypointMarker": WaypointMarker,
51043
52803
  "WizardContainer": WizardContainer,
@@ -51064,7 +52824,7 @@ function SuspenseConfigProvider({
51064
52824
  config,
51065
52825
  children
51066
52826
  }) {
51067
- return React82__default.createElement(
52827
+ return React85__default.createElement(
51068
52828
  SuspenseConfigContext.Provider,
51069
52829
  { value: config },
51070
52830
  children
@@ -51554,7 +53314,7 @@ function renderPatternChildren(children, onDismiss, parentId = "root", parentPat
51554
53314
  const key = `${parentId}-${index}-trait:${traitName}`;
51555
53315
  return /* @__PURE__ */ jsx(TraitFrame, { traitName }, key);
51556
53316
  }
51557
- return /* @__PURE__ */ jsx(React82__default.Fragment, { children: child }, `${parentId}-${index}`);
53317
+ return /* @__PURE__ */ jsx(React85__default.Fragment, { children: child }, `${parentId}-${index}`);
51558
53318
  }
51559
53319
  if (!child || typeof child !== "object") return null;
51560
53320
  const childId = `${parentId}-${index}`;
@@ -51594,14 +53354,14 @@ function isPatternConfig(value) {
51594
53354
  if (value === null || value === void 0) return false;
51595
53355
  if (typeof value !== "object") return false;
51596
53356
  if (Array.isArray(value)) return false;
51597
- if (React82__default.isValidElement(value)) return false;
53357
+ if (React85__default.isValidElement(value)) return false;
51598
53358
  if (value instanceof Date) return false;
51599
53359
  if (typeof value === "function") return false;
51600
53360
  const record = value;
51601
53361
  return "type" in record && typeof record.type === "string" && getComponentForPattern$1(record.type) !== null;
51602
53362
  }
51603
53363
  function isPlainConfigObject(value) {
51604
- if (React82__default.isValidElement(value)) return false;
53364
+ if (React85__default.isValidElement(value)) return false;
51605
53365
  if (value instanceof Date) return false;
51606
53366
  const proto = Object.getPrototypeOf(value);
51607
53367
  return proto === Object.prototype || proto === null;
@@ -51722,6 +53482,22 @@ function SlotContentRenderer({
51722
53482
  const childrenIsRenderFn = typeof incomingChildren === "function";
51723
53483
  const { children: _childrenConfig, ...restPropsNoChildren } = content.props;
51724
53484
  const restProps = childrenIsRenderFn ? { ...restPropsNoChildren, children: incomingChildren } : restPropsNoChildren;
53485
+ const nodeSlotOverrides = {};
53486
+ for (const slotKey of CONTENT_NODE_SLOTS) {
53487
+ const slotVal = restProps[slotKey];
53488
+ if (slotVal === void 0 || slotVal === null) continue;
53489
+ if (React85__default.isValidElement(slotVal) || typeof slotVal === "string" || typeof slotVal === "number" || typeof slotVal === "boolean") continue;
53490
+ if (Array.isArray(slotVal) || typeof slotVal === "object" && "type" in slotVal) {
53491
+ nodeSlotOverrides[slotKey] = renderPatternChildren(
53492
+ slotVal,
53493
+ onDismiss,
53494
+ `${content.id}-${slotKey}`,
53495
+ `${myPath}.${slotKey}`,
53496
+ content.sourceTrait,
53497
+ { slot: content.slot, transitionEvent: content.transitionEvent, fromState: content.fromState, entity: content.entity }
53498
+ );
53499
+ }
53500
+ }
51725
53501
  const renderedProps = renderPatternProps(restProps, onDismiss);
51726
53502
  const patternDef = getPatternDefinition(content.pattern);
51727
53503
  const propsSchema = patternDef?.propsSchema;
@@ -51752,6 +53528,9 @@ function SlotContentRenderer({
51752
53528
  }
51753
53529
  }
51754
53530
  const finalProps = renderedProps;
53531
+ for (const [k, v] of Object.entries(nodeSlotOverrides)) {
53532
+ finalProps[k] = v;
53533
+ }
51755
53534
  const resolvedItems = Array.isArray(
51756
53535
  finalProps.entity
51757
53536
  ) ? finalProps.entity : null;
@@ -51872,7 +53651,7 @@ function UISlotRenderer({
51872
53651
  }
51873
53652
  return wrapped;
51874
53653
  }
51875
- var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, FORM_PATTERNS, SELF_OVERLAY_PATTERNS, PATTERNS_WITH_CHILDREN;
53654
+ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, FORM_PATTERNS, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN;
51876
53655
  var init_UISlotRenderer = __esm({
51877
53656
  "components/core/organisms/UISlotRenderer.tsx"() {
51878
53657
  "use client";
@@ -51909,6 +53688,16 @@ var init_UISlotRenderer = __esm({
51909
53688
  "wizard-step"
51910
53689
  ]);
51911
53690
  SELF_OVERLAY_PATTERNS = /* @__PURE__ */ new Set(["modal", "confirm-dialog"]);
53691
+ CONTENT_NODE_SLOTS = /* @__PURE__ */ new Set([
53692
+ "logo",
53693
+ "master",
53694
+ "detail",
53695
+ "trigger",
53696
+ "content",
53697
+ "addons",
53698
+ "hud",
53699
+ "fallback"
53700
+ ]);
51912
53701
  PATTERNS_WITH_CHILDREN = /* @__PURE__ */ new Set([
51913
53702
  "stack",
51914
53703
  "vstack",