@almadar/ui 5.60.0 → 5.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/avl/index.cjs +3233 -1757
  2. package/dist/avl/index.js +2057 -581
  3. package/dist/components/core/molecules/GraphCanvas.d.ts +6 -0
  4. package/dist/components/core/templates/index.d.ts +4 -0
  5. package/dist/components/game/molecules/CardHand.d.ts +35 -0
  6. package/dist/components/game/molecules/DialogueBox.d.ts +8 -2
  7. package/dist/components/game/molecules/index.d.ts +1 -0
  8. package/dist/components/game/molecules/three/index.cjs +229 -120
  9. package/dist/components/game/molecules/three/index.js +229 -120
  10. package/dist/components/game/organisms/CardBattlerBoard.d.ts +46 -0
  11. package/dist/components/game/organisms/CityBuilderBoard.d.ts +63 -0
  12. package/dist/components/game/organisms/TopDownShooterBoard.d.ts +64 -0
  13. package/dist/components/game/organisms/TowerDefenseBoard.d.ts +10 -1
  14. package/dist/components/game/organisms/TraitSlot.d.ts +1 -3
  15. package/dist/components/game/organisms/VisualNovelBoard.d.ts +53 -0
  16. package/dist/components/game/organisms/index.d.ts +4 -0
  17. package/dist/components/game/templates/CardBattlerTemplate.d.ts +35 -0
  18. package/dist/components/game/templates/CityBuilderTemplate.d.ts +40 -0
  19. package/dist/components/game/templates/GameCanvas3DBattleTemplate.d.ts +5 -2
  20. package/dist/components/game/templates/GameCanvas3DCastleTemplate.d.ts +5 -2
  21. package/dist/components/game/templates/GameCanvas3DWorldMapTemplate.d.ts +5 -2
  22. package/dist/components/game/templates/TopDownShooterTemplate.d.ts +42 -0
  23. package/dist/components/game/templates/VisualNovelTemplate.d.ts +28 -0
  24. package/dist/components/game/templates/game3dAssetManifest.d.ts +50 -0
  25. package/dist/components/game/templates/index.d.ts +4 -0
  26. package/dist/components/index.cjs +3048 -1547
  27. package/dist/components/index.js +2019 -527
  28. package/dist/docs/index.cjs +105 -1
  29. package/dist/docs/index.js +105 -1
  30. package/dist/hooks/index.cjs +105 -1
  31. package/dist/hooks/index.js +105 -1
  32. package/dist/locales/index.cjs +315 -3
  33. package/dist/locales/index.js +315 -3
  34. package/dist/providers/index.cjs +3034 -1558
  35. package/dist/providers/index.js +2025 -549
  36. package/dist/runtime/index.cjs +3085 -1609
  37. package/dist/runtime/index.js +2024 -548
  38. package/locales/ar.json +105 -1
  39. package/locales/en.json +105 -1
  40. package/locales/sl.json +105 -1
  41. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- import * as React81 from 'react';
2
- import React81__default, { createContext, useMemo, useContext, useRef, useEffect, useCallback, Suspense, useState, useSyncExternalStore, useLayoutEffect, lazy, useId, forwardRef, useImperativeHandle, Component } from 'react';
1
+ import * as React84 from 'react';
2
+ import React84__default, { createContext, useMemo, useContext, useRef, useEffect, useCallback, Suspense, useState, useSyncExternalStore, useLayoutEffect, lazy, useId, forwardRef, useImperativeHandle, Component } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
@@ -396,7 +396,7 @@ var init_Box = __esm({
396
396
  fixed: "fixed",
397
397
  sticky: "sticky"
398
398
  };
399
- Box = React81__default.forwardRef(
399
+ Box = React84__default.forwardRef(
400
400
  ({
401
401
  padding,
402
402
  paddingX,
@@ -446,7 +446,7 @@ var init_Box = __esm({
446
446
  onMouseLeave?.(e);
447
447
  }, [hoverEvent, eventBus, onMouseLeave]);
448
448
  const isClickable = action || onClick;
449
- return React81__default.createElement(
449
+ return React84__default.createElement(
450
450
  Component2,
451
451
  {
452
452
  ref,
@@ -541,7 +541,7 @@ function loadLib(key, importer) {
541
541
  return p2;
542
542
  }
543
543
  function lazyFamilyIcon(libKey, importer, pick, fallbackName, family) {
544
- const Lazy = React81__default.lazy(async () => {
544
+ const Lazy = React84__default.lazy(async () => {
545
545
  const lib = await loadLib(libKey, importer);
546
546
  const Comp = pick(lib);
547
547
  if (!Comp) {
@@ -551,7 +551,7 @@ function lazyFamilyIcon(libKey, importer, pick, fallbackName, family) {
551
551
  return { default: Comp };
552
552
  });
553
553
  const Wrapped = (props) => /* @__PURE__ */ jsx(
554
- React81__default.Suspense,
554
+ React84__default.Suspense,
555
555
  {
556
556
  fallback: /* @__PURE__ */ jsx(
557
557
  "span",
@@ -1277,7 +1277,7 @@ var init_Icon = __esm({
1277
1277
  const directIcon = typeof icon === "string" ? void 0 : icon;
1278
1278
  const effectiveName = typeof icon === "string" ? icon : name;
1279
1279
  const family = useIconFamily();
1280
- const RenderedComponent = React81__default.useMemo(() => {
1280
+ const RenderedComponent = React84__default.useMemo(() => {
1281
1281
  if (directIcon) return null;
1282
1282
  return effectiveName ? resolveIconForFamily(effectiveName, family) : null;
1283
1283
  }, [directIcon, effectiveName, family]);
@@ -1336,7 +1336,7 @@ function resolveIconProp(value, sizeClass) {
1336
1336
  const IconComp = value;
1337
1337
  return /* @__PURE__ */ jsx(IconComp, { className: sizeClass });
1338
1338
  }
1339
- if (React81__default.isValidElement(value)) {
1339
+ if (React84__default.isValidElement(value)) {
1340
1340
  return value;
1341
1341
  }
1342
1342
  if (typeof value === "object" && value !== null && "render" in value) {
@@ -1412,7 +1412,7 @@ var init_Button = __esm({
1412
1412
  md: "h-icon-default w-icon-default",
1413
1413
  lg: "h-icon-default w-icon-default"
1414
1414
  };
1415
- Button = React81__default.forwardRef(
1415
+ Button = React84__default.forwardRef(
1416
1416
  ({
1417
1417
  className,
1418
1418
  variant = "primary",
@@ -1477,7 +1477,7 @@ var Dialog;
1477
1477
  var init_Dialog = __esm({
1478
1478
  "components/core/atoms/Dialog.tsx"() {
1479
1479
  init_cn();
1480
- Dialog = React81__default.forwardRef(
1480
+ Dialog = React84__default.forwardRef(
1481
1481
  ({
1482
1482
  role = "dialog",
1483
1483
  "aria-modal": ariaModal = true,
@@ -2091,7 +2091,7 @@ var init_Badge = __esm({
2091
2091
  md: "px-2.5 py-1 text-sm",
2092
2092
  lg: "px-3 py-1.5 text-base"
2093
2093
  };
2094
- Badge = React81__default.forwardRef(
2094
+ Badge = React84__default.forwardRef(
2095
2095
  ({ className, variant = "default", size = "sm", amount, label, icon, children, onRemove, removeLabel, ...props }, ref) => {
2096
2096
  const iconSizes3 = {
2097
2097
  sm: "h-icon-default w-icon-default",
@@ -2427,7 +2427,7 @@ var init_SvgFlow = __esm({
2427
2427
  width = 100,
2428
2428
  height = 100
2429
2429
  }) => {
2430
- const markerId = React81__default.useMemo(() => {
2430
+ const markerId = React84__default.useMemo(() => {
2431
2431
  flowIdCounter += 1;
2432
2432
  return `almadar-flow-arrow-${flowIdCounter}`;
2433
2433
  }, []);
@@ -3020,7 +3020,7 @@ var init_SvgRing = __esm({
3020
3020
  width = 100,
3021
3021
  height = 100
3022
3022
  }) => {
3023
- const gradientId = React81__default.useMemo(() => {
3023
+ const gradientId = React84__default.useMemo(() => {
3024
3024
  ringIdCounter += 1;
3025
3025
  return `almadar-ring-glow-${ringIdCounter}`;
3026
3026
  }, []);
@@ -3201,7 +3201,7 @@ var init_Input = __esm({
3201
3201
  init_cn();
3202
3202
  init_Icon();
3203
3203
  init_useEventBus();
3204
- Input = React81__default.forwardRef(
3204
+ Input = React84__default.forwardRef(
3205
3205
  ({
3206
3206
  className,
3207
3207
  inputType,
@@ -3361,7 +3361,7 @@ var Label;
3361
3361
  var init_Label = __esm({
3362
3362
  "components/core/atoms/Label.tsx"() {
3363
3363
  init_cn();
3364
- Label = React81__default.forwardRef(
3364
+ Label = React84__default.forwardRef(
3365
3365
  ({ className, required, children, ...props }, ref) => {
3366
3366
  return /* @__PURE__ */ jsxs(
3367
3367
  "label",
@@ -3388,7 +3388,7 @@ var init_Textarea = __esm({
3388
3388
  "components/core/atoms/Textarea.tsx"() {
3389
3389
  init_cn();
3390
3390
  init_useEventBus();
3391
- Textarea = React81__default.forwardRef(
3391
+ Textarea = React84__default.forwardRef(
3392
3392
  ({ className, error, onChange, ...props }, ref) => {
3393
3393
  const eventBus = useEventBus();
3394
3394
  const handleChange = (e) => {
@@ -3627,7 +3627,7 @@ var init_Select = __esm({
3627
3627
  init_cn();
3628
3628
  init_Icon();
3629
3629
  init_useEventBus();
3630
- Select = React81__default.forwardRef(
3630
+ Select = React84__default.forwardRef(
3631
3631
  (props, _ref) => {
3632
3632
  const { multiple, searchable, clearable } = props;
3633
3633
  if (multiple || searchable || clearable) {
@@ -3644,7 +3644,7 @@ var init_Checkbox = __esm({
3644
3644
  "components/core/atoms/Checkbox.tsx"() {
3645
3645
  init_cn();
3646
3646
  init_useEventBus();
3647
- Checkbox = React81__default.forwardRef(
3647
+ Checkbox = React84__default.forwardRef(
3648
3648
  ({ className, label, id, onChange, ...props }, ref) => {
3649
3649
  const inputId = id || `checkbox-${Math.random().toString(36).substr(2, 9)}`;
3650
3650
  const eventBus = useEventBus();
@@ -3698,7 +3698,7 @@ var init_Spinner = __esm({
3698
3698
  md: "h-6 w-6",
3699
3699
  lg: "h-8 w-8"
3700
3700
  };
3701
- Spinner = React81__default.forwardRef(
3701
+ Spinner = React84__default.forwardRef(
3702
3702
  ({ className, size = "md", overlay, ...props }, ref) => {
3703
3703
  if (overlay) {
3704
3704
  return /* @__PURE__ */ jsx(
@@ -3788,7 +3788,7 @@ var init_Card = __esm({
3788
3788
  chip: "shadow-none rounded-pill border-[length:var(--border-width)] border-border",
3789
3789
  "tile-image-first": "p-0 overflow-hidden"
3790
3790
  };
3791
- Card = React81__default.forwardRef(
3791
+ Card = React84__default.forwardRef(
3792
3792
  ({
3793
3793
  className,
3794
3794
  variant = "bordered",
@@ -3836,9 +3836,9 @@ var init_Card = __esm({
3836
3836
  }
3837
3837
  );
3838
3838
  Card.displayName = "Card";
3839
- CardHeader = React81__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("mb-4", className), ...props }));
3839
+ CardHeader = React84__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("mb-4", className), ...props }));
3840
3840
  CardHeader.displayName = "CardHeader";
3841
- CardTitle = React81__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
3841
+ CardTitle = React84__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
3842
3842
  "h3",
3843
3843
  {
3844
3844
  ref,
@@ -3851,11 +3851,11 @@ var init_Card = __esm({
3851
3851
  }
3852
3852
  ));
3853
3853
  CardTitle.displayName = "CardTitle";
3854
- CardContent = React81__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("", className), ...props }));
3854
+ CardContent = React84__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("", className), ...props }));
3855
3855
  CardContent.displayName = "CardContent";
3856
3856
  CardBody = CardContent;
3857
3857
  CardBody.displayName = "CardBody";
3858
- CardFooter = React81__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
3858
+ CardFooter = React84__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
3859
3859
  "div",
3860
3860
  {
3861
3861
  ref,
@@ -3910,7 +3910,7 @@ var init_FilterPill = __esm({
3910
3910
  md: "w-3.5 h-3.5",
3911
3911
  lg: "w-4 h-4"
3912
3912
  };
3913
- FilterPill = React81__default.forwardRef(
3913
+ FilterPill = React84__default.forwardRef(
3914
3914
  ({
3915
3915
  className,
3916
3916
  variant = "default",
@@ -4039,8 +4039,8 @@ var init_Avatar = __esm({
4039
4039
  actionPayload
4040
4040
  }) => {
4041
4041
  const eventBus = useEventBus();
4042
- const [imgFailed, setImgFailed] = React81__default.useState(false);
4043
- React81__default.useEffect(() => {
4042
+ const [imgFailed, setImgFailed] = React84__default.useState(false);
4043
+ React84__default.useEffect(() => {
4044
4044
  setImgFailed(false);
4045
4045
  }, [src]);
4046
4046
  const initials = providedInitials ?? (name ? generateInitials(name) : void 0);
@@ -4425,7 +4425,7 @@ var init_Radio = __esm({
4425
4425
  md: "w-2.5 h-2.5",
4426
4426
  lg: "w-3 h-3"
4427
4427
  };
4428
- Radio = React81__default.forwardRef(
4428
+ Radio = React84__default.forwardRef(
4429
4429
  ({
4430
4430
  label,
4431
4431
  helperText,
@@ -4442,12 +4442,12 @@ var init_Radio = __esm({
4442
4442
  onChange,
4443
4443
  ...props
4444
4444
  }, ref) => {
4445
- const reactId = React81__default.useId();
4445
+ const reactId = React84__default.useId();
4446
4446
  const baseId = id || `radio-${reactId}`;
4447
4447
  const hasError = !!error;
4448
4448
  const eventBus = useEventBus();
4449
- const [selected, setSelected] = React81__default.useState(value);
4450
- React81__default.useEffect(() => {
4449
+ const [selected, setSelected] = React84__default.useState(value);
4450
+ React84__default.useEffect(() => {
4451
4451
  if (value !== void 0) setSelected(value);
4452
4452
  }, [value]);
4453
4453
  const pick = (next, e) => {
@@ -4629,7 +4629,7 @@ var init_Switch = __esm({
4629
4629
  "components/core/atoms/Switch.tsx"() {
4630
4630
  "use client";
4631
4631
  init_cn();
4632
- Switch = React81.forwardRef(
4632
+ Switch = React84.forwardRef(
4633
4633
  ({
4634
4634
  checked,
4635
4635
  defaultChecked = false,
@@ -4640,10 +4640,10 @@ var init_Switch = __esm({
4640
4640
  name,
4641
4641
  className
4642
4642
  }, ref) => {
4643
- const [isChecked, setIsChecked] = React81.useState(
4643
+ const [isChecked, setIsChecked] = React84.useState(
4644
4644
  checked !== void 0 ? checked : defaultChecked
4645
4645
  );
4646
- React81.useEffect(() => {
4646
+ React84.useEffect(() => {
4647
4647
  if (checked !== void 0) {
4648
4648
  setIsChecked(checked);
4649
4649
  }
@@ -4998,7 +4998,7 @@ var Aside;
4998
4998
  var init_Aside = __esm({
4999
4999
  "components/core/atoms/Aside.tsx"() {
5000
5000
  init_cn();
5001
- Aside = React81__default.forwardRef(
5001
+ Aside = React84__default.forwardRef(
5002
5002
  ({ className, children, ...rest }, ref) => /* @__PURE__ */ jsx("aside", { ref, className: cn(className), ...rest, children })
5003
5003
  );
5004
5004
  Aside.displayName = "Aside";
@@ -5076,8 +5076,8 @@ var init_LawReferenceTooltip = __esm({
5076
5076
  className
5077
5077
  }) => {
5078
5078
  const { t } = useTranslate();
5079
- const [isVisible, setIsVisible] = React81__default.useState(false);
5080
- const timeoutRef = React81__default.useRef(null);
5079
+ const [isVisible, setIsVisible] = React84__default.useState(false);
5080
+ const timeoutRef = React84__default.useRef(null);
5081
5081
  const handleMouseEnter = () => {
5082
5082
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
5083
5083
  timeoutRef.current = setTimeout(() => setIsVisible(true), 200);
@@ -5086,7 +5086,7 @@ var init_LawReferenceTooltip = __esm({
5086
5086
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
5087
5087
  setIsVisible(false);
5088
5088
  };
5089
- React81__default.useEffect(() => {
5089
+ React84__default.useEffect(() => {
5090
5090
  return () => {
5091
5091
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
5092
5092
  };
@@ -5296,7 +5296,7 @@ var init_StatusDot = __esm({
5296
5296
  md: "w-2.5 h-2.5",
5297
5297
  lg: "w-3 h-3"
5298
5298
  };
5299
- StatusDot = React81__default.forwardRef(
5299
+ StatusDot = React84__default.forwardRef(
5300
5300
  ({ className, status = "offline", pulse = false, size = "md", label, ...props }, ref) => {
5301
5301
  return /* @__PURE__ */ jsx(
5302
5302
  "span",
@@ -5350,7 +5350,7 @@ var init_TrendIndicator = __esm({
5350
5350
  down: "trending-down",
5351
5351
  flat: "arrow-right"
5352
5352
  };
5353
- TrendIndicator = React81__default.forwardRef(
5353
+ TrendIndicator = React84__default.forwardRef(
5354
5354
  ({
5355
5355
  className,
5356
5356
  value,
@@ -5417,7 +5417,7 @@ var init_RangeSlider = __esm({
5417
5417
  md: "w-4 h-4",
5418
5418
  lg: "w-5 h-5"
5419
5419
  };
5420
- RangeSlider = React81__default.forwardRef(
5420
+ RangeSlider = React84__default.forwardRef(
5421
5421
  ({
5422
5422
  className,
5423
5423
  min = 0,
@@ -5925,7 +5925,7 @@ var init_ContentSection = __esm({
5925
5925
  md: "py-16",
5926
5926
  lg: "py-24"
5927
5927
  };
5928
- ContentSection = React81__default.forwardRef(
5928
+ ContentSection = React84__default.forwardRef(
5929
5929
  ({ children, background = "default", padding = "lg", id, className }, ref) => {
5930
5930
  return /* @__PURE__ */ jsx(
5931
5931
  Box,
@@ -6459,7 +6459,7 @@ var init_AnimatedReveal = __esm({
6459
6459
  "scale-up": { opacity: 1, transform: "scale(1) translateY(0)" },
6460
6460
  "none": {}
6461
6461
  };
6462
- AnimatedReveal = React81__default.forwardRef(
6462
+ AnimatedReveal = React84__default.forwardRef(
6463
6463
  ({
6464
6464
  trigger = "scroll",
6465
6465
  animation = "fade-up",
@@ -6619,7 +6619,7 @@ var init_AnimatedGraphic = __esm({
6619
6619
  "components/marketing/atoms/AnimatedGraphic.tsx"() {
6620
6620
  "use client";
6621
6621
  init_cn();
6622
- AnimatedGraphic = React81__default.forwardRef(
6622
+ AnimatedGraphic = React84__default.forwardRef(
6623
6623
  ({
6624
6624
  src,
6625
6625
  svgContent,
@@ -6642,7 +6642,7 @@ var init_AnimatedGraphic = __esm({
6642
6642
  const fetchedSvg = useFetchedSvg(svgContent ? void 0 : src);
6643
6643
  const resolvedSvg = svgContent ?? fetchedSvg;
6644
6644
  const prevAnimateRef = useRef(animate);
6645
- const setRef = React81__default.useCallback(
6645
+ const setRef = React84__default.useCallback(
6646
6646
  (node) => {
6647
6647
  containerRef.current = node;
6648
6648
  if (typeof ref === "function") ref(node);
@@ -6868,9 +6868,9 @@ function ScoreDisplay({
6868
6868
  ...rest
6869
6869
  }) {
6870
6870
  const resolvedValue = typeof value === "number" && !Number.isNaN(value) ? value : typeof rest.score === "number" && !Number.isNaN(rest.score) ? rest.score : 0;
6871
- const [displayValue, setDisplayValue] = React81.useState(resolvedValue);
6872
- const [isAnimating, setIsAnimating] = React81.useState(false);
6873
- React81.useEffect(() => {
6871
+ const [displayValue, setDisplayValue] = React84.useState(resolvedValue);
6872
+ const [isAnimating, setIsAnimating] = React84.useState(false);
6873
+ React84.useEffect(() => {
6874
6874
  if (!animated || displayValue === resolvedValue) {
6875
6875
  setDisplayValue(resolvedValue);
6876
6876
  return;
@@ -6953,9 +6953,9 @@ function ControlButton({
6953
6953
  className
6954
6954
  }) {
6955
6955
  const eventBus = useEventBus();
6956
- const [isPressed, setIsPressed] = React81.useState(false);
6956
+ const [isPressed, setIsPressed] = React84.useState(false);
6957
6957
  const actualPressed = pressed ?? isPressed;
6958
- const handlePointerDown = React81.useCallback(
6958
+ const handlePointerDown = React84.useCallback(
6959
6959
  (e) => {
6960
6960
  e.preventDefault();
6961
6961
  if (disabled) return;
@@ -6965,7 +6965,7 @@ function ControlButton({
6965
6965
  },
6966
6966
  [disabled, pressEvent, eventBus, onPress]
6967
6967
  );
6968
- const handlePointerUp = React81.useCallback(
6968
+ const handlePointerUp = React84.useCallback(
6969
6969
  (e) => {
6970
6970
  e.preventDefault();
6971
6971
  if (disabled) return;
@@ -6975,7 +6975,7 @@ function ControlButton({
6975
6975
  },
6976
6976
  [disabled, releaseEvent, eventBus, onRelease]
6977
6977
  );
6978
- const handlePointerLeave = React81.useCallback(
6978
+ const handlePointerLeave = React84.useCallback(
6979
6979
  (e) => {
6980
6980
  if (isPressed) {
6981
6981
  setIsPressed(false);
@@ -7596,8 +7596,8 @@ function XPBar({
7596
7596
  }) {
7597
7597
  const sizes = sizeMap9[size];
7598
7598
  const percentage = max > 0 ? Math.max(0, Math.min(100, current / max * 100)) : 0;
7599
- const [fillWidth, setFillWidth] = React81.useState(animated ? 0 : percentage);
7600
- React81.useEffect(() => {
7599
+ const [fillWidth, setFillWidth] = React84.useState(animated ? 0 : percentage);
7600
+ React84.useEffect(() => {
7601
7601
  if (!animated) {
7602
7602
  setFillWidth(percentage);
7603
7603
  return;
@@ -8085,9 +8085,9 @@ function MiniMap({
8085
8085
  viewportRect = DEFAULT_VIEWPORT,
8086
8086
  className
8087
8087
  }) {
8088
- const canvasRef = React81.useRef(null);
8089
- const frameRef = React81.useRef(0);
8090
- React81.useEffect(() => {
8088
+ const canvasRef = React84.useRef(null);
8089
+ const frameRef = React84.useRef(0);
8090
+ React84.useEffect(() => {
8091
8091
  const canvas = canvasRef.current;
8092
8092
  if (!canvas) return;
8093
8093
  const ctx = canvas.getContext("2d");
@@ -8276,7 +8276,7 @@ var init_ErrorBoundary = __esm({
8276
8276
  }
8277
8277
  );
8278
8278
  };
8279
- ErrorBoundary = class extends React81__default.Component {
8279
+ ErrorBoundary = class extends React84__default.Component {
8280
8280
  constructor(props) {
8281
8281
  super(props);
8282
8282
  __publicField(this, "reset", () => {
@@ -8421,16 +8421,6 @@ var init_Skeleton = __esm({
8421
8421
  Skeleton.displayName = "Skeleton";
8422
8422
  }
8423
8423
  });
8424
- function isKnownPattern(type) {
8425
- return type in componentMapping;
8426
- }
8427
- var componentMapping;
8428
- var init_pattern_resolver = __esm({
8429
- "renderer/pattern-resolver.ts"() {
8430
- createLogger("almadar:ui:pattern-resolver");
8431
- componentMapping = {};
8432
- }
8433
- });
8434
8424
 
8435
8425
  // renderer/slot-definitions.ts
8436
8426
  function isPortalSlot(slot) {
@@ -8539,7 +8529,6 @@ var init_navigation = __esm({
8539
8529
  // renderer/index.ts
8540
8530
  var init_renderer = __esm({
8541
8531
  "renderer/index.ts"() {
8542
- init_pattern_resolver();
8543
8532
  init_slot_definitions();
8544
8533
  }
8545
8534
  });
@@ -9322,8 +9311,8 @@ function ActionButtons({
9322
9311
  disabled
9323
9312
  }) {
9324
9313
  const eventBus = useEventBus();
9325
- const [activeButtons, setActiveButtons] = React81.useState(/* @__PURE__ */ new Set());
9326
- const handlePress = React81.useCallback(
9314
+ const [activeButtons, setActiveButtons] = React84.useState(/* @__PURE__ */ new Set());
9315
+ const handlePress = React84.useCallback(
9327
9316
  (id) => {
9328
9317
  setActiveButtons((prev) => new Set(prev).add(id));
9329
9318
  if (actionEvent) eventBus.emit(`UI:${actionEvent}`, { id, pressed: true });
@@ -9331,7 +9320,7 @@ function ActionButtons({
9331
9320
  },
9332
9321
  [actionEvent, eventBus, onAction]
9333
9322
  );
9334
- const handleRelease = React81.useCallback(
9323
+ const handleRelease = React84.useCallback(
9335
9324
  (id) => {
9336
9325
  setActiveButtons((prev) => {
9337
9326
  const next = new Set(prev);
@@ -9968,8 +9957,8 @@ var init_AnimatedCounter = __esm({
9968
9957
  const [displayValue, setDisplayValue] = useState("0");
9969
9958
  const [hasAnimated, setHasAnimated] = useState(false);
9970
9959
  const animate = useCallback(() => {
9971
- const { num: num2, prefix, suffix, decimals } = parseValue(value);
9972
- if (num2 === 0) {
9960
+ const { num: num3, prefix, suffix, decimals } = parseValue(value);
9961
+ if (num3 === 0) {
9973
9962
  setDisplayValue(String(value));
9974
9963
  return;
9975
9964
  }
@@ -9978,7 +9967,7 @@ var init_AnimatedCounter = __esm({
9978
9967
  const elapsed = now2 - startTime;
9979
9968
  const progress = Math.min(elapsed / duration, 1);
9980
9969
  const eased = 1 - Math.pow(1 - progress, 3);
9981
- const current = eased * num2;
9970
+ const current = eased * num3;
9982
9971
  setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
9983
9972
  if (progress < 1) {
9984
9973
  requestAnimationFrame(tick);
@@ -10243,7 +10232,7 @@ function recordTransition(trace) {
10243
10232
  ...trace,
10244
10233
  id: `t-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
10245
10234
  };
10246
- log5.debug("transition:recorded", { trait: trace.traitName, from: trace.from, to: trace.to, event: trace.event, effectCount: trace.effects.length });
10235
+ log4.debug("transition:recorded", { trait: trace.traitName, from: trace.from, to: trace.to, event: trace.event, effectCount: trace.effects.length });
10247
10236
  getState().transitions.push(entry);
10248
10237
  if (getState().transitions.length > MAX_TRANSITIONS) {
10249
10238
  getState().transitions.shift();
@@ -10342,7 +10331,7 @@ function getTraitSnapshots() {
10342
10331
  try {
10343
10332
  snapshots.push(getter());
10344
10333
  } catch (err) {
10345
- log5.error("traitSnapshot getter failed", { trait: traitName, err: String(err) });
10334
+ log4.error("traitSnapshot getter failed", { trait: traitName, err: String(err) });
10346
10335
  }
10347
10336
  }
10348
10337
  return snapshots;
@@ -10441,10 +10430,10 @@ function updateAssetStatus(url, status) {
10441
10430
  window.__orbitalVerification.assetStatus[url] = status;
10442
10431
  }
10443
10432
  }
10444
- var log5, MAX_TRANSITIONS;
10433
+ var log4, MAX_TRANSITIONS;
10445
10434
  var init_verificationRegistry = __esm({
10446
10435
  "lib/verificationRegistry.ts"() {
10447
- log5 = createLogger("almadar:bridge");
10436
+ log4 = createLogger("almadar:bridge");
10448
10437
  MAX_TRANSITIONS = 500;
10449
10438
  exposeOnWindow();
10450
10439
  }
@@ -10808,7 +10797,7 @@ function IsometricCanvas({
10808
10797
  // Tuning
10809
10798
  diamondTopY: diamondTopYProp,
10810
10799
  // Remote asset loading
10811
- assetBaseUrl = "https://almadar-kflow-assets.web.app/shared/",
10800
+ assetBaseUrl,
10812
10801
  assetManifest
10813
10802
  }) {
10814
10803
  const tilesProp = Array.isArray(_tilesPropRaw) ? _tilesPropRaw : [];
@@ -10887,7 +10876,7 @@ function IsometricCanvas({
10887
10876
  const attackTargetSet = useMemo(() => {
10888
10877
  return new Set(attackTargets.map((p2) => `${p2.x},${p2.y}`));
10889
10878
  }, [attackTargets]);
10890
- const resolveManifestUrl = useCallback((relativePath) => {
10879
+ const resolveManifestUrl7 = useCallback((relativePath) => {
10891
10880
  if (!relativePath) return void 0;
10892
10881
  if (assetBaseUrl) return `${assetBaseUrl.replace(/\/$/, "")}${relativePath.startsWith("/") ? "" : "/"}${relativePath}`;
10893
10882
  return relativePath;
@@ -10900,7 +10889,7 @@ function IsometricCanvas({
10900
10889
  const url = getTerrainSprite(tile.terrain ?? "");
10901
10890
  if (url) urls.push(url);
10902
10891
  } else {
10903
- const url = resolveManifestUrl(assetManifest?.terrains?.[tile.terrain ?? ""]);
10892
+ const url = resolveManifestUrl7(assetManifest?.terrains?.[tile.terrain ?? ""]);
10904
10893
  if (url) urls.push(url);
10905
10894
  }
10906
10895
  }
@@ -10910,7 +10899,7 @@ function IsometricCanvas({
10910
10899
  const url = getFeatureSprite(feature.type);
10911
10900
  if (url) urls.push(url);
10912
10901
  } else {
10913
- const url = resolveManifestUrl(assetManifest?.features?.[feature.type]);
10902
+ const url = resolveManifestUrl7(assetManifest?.features?.[feature.type]);
10914
10903
  if (url) urls.push(url);
10915
10904
  }
10916
10905
  }
@@ -10920,13 +10909,13 @@ function IsometricCanvas({
10920
10909
  const url = getUnitSprite(unit);
10921
10910
  if (url) urls.push(url);
10922
10911
  } else if (unit.unitType) {
10923
- const url = resolveManifestUrl(assetManifest?.units?.[unit.unitType]);
10912
+ const url = resolveManifestUrl7(assetManifest?.units?.[unit.unitType]);
10924
10913
  if (url) urls.push(url);
10925
10914
  }
10926
10915
  }
10927
10916
  if (assetManifest?.effects) {
10928
10917
  for (const path of Object.values(assetManifest.effects)) {
10929
- const url = resolveManifestUrl(path);
10918
+ const url = resolveManifestUrl7(path);
10930
10919
  if (url) urls.push(url);
10931
10920
  }
10932
10921
  }
@@ -10934,7 +10923,7 @@ function IsometricCanvas({
10934
10923
  if (atlasSheetUrls.length) urls.push(...atlasSheetUrls);
10935
10924
  if (backgroundImage) urls.push(backgroundImage);
10936
10925
  return [...new Set(urls.filter(Boolean))];
10937
- }, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, atlasSheetUrls, backgroundImage, assetManifest, resolveManifestUrl]);
10926
+ }, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, atlasSheetUrls, backgroundImage, assetManifest, resolveManifestUrl7]);
10938
10927
  const { getImage, pendingCount } = useImageCache(spriteUrls);
10939
10928
  useEffect(() => {
10940
10929
  if (typeof window === "undefined") return;
@@ -10958,14 +10947,14 @@ function IsometricCanvas({
10958
10947
  lerpToTarget
10959
10948
  } = useCamera();
10960
10949
  const resolveTerrainSpriteUrl = useCallback((tile) => {
10961
- return tile.terrainSprite || getTerrainSprite?.(tile.terrain ?? "") || resolveManifestUrl(assetManifest?.terrains?.[tile.terrain ?? ""]);
10962
- }, [getTerrainSprite, assetManifest, resolveManifestUrl]);
10950
+ return tile.terrainSprite || getTerrainSprite?.(tile.terrain ?? "") || resolveManifestUrl7(assetManifest?.terrains?.[tile.terrain ?? ""]);
10951
+ }, [getTerrainSprite, assetManifest, resolveManifestUrl7]);
10963
10952
  const resolveFeatureSpriteUrl = useCallback((featureType) => {
10964
- return getFeatureSprite?.(featureType) || resolveManifestUrl(assetManifest?.features?.[featureType]);
10965
- }, [getFeatureSprite, assetManifest, resolveManifestUrl]);
10953
+ return getFeatureSprite?.(featureType) || resolveManifestUrl7(assetManifest?.features?.[featureType]);
10954
+ }, [getFeatureSprite, assetManifest, resolveManifestUrl7]);
10966
10955
  const resolveUnitSpriteUrl = useCallback((unit) => {
10967
- return unit.sprite || getUnitSprite?.(unit) || (unit.unitType ? resolveManifestUrl(assetManifest?.units?.[unit.unitType]) : void 0);
10968
- }, [getUnitSprite, assetManifest, resolveManifestUrl]);
10956
+ return unit.sprite || getUnitSprite?.(unit) || (unit.unitType ? resolveManifestUrl7(assetManifest?.units?.[unit.unitType]) : void 0);
10957
+ }, [getUnitSprite, assetManifest, resolveManifestUrl7]);
10969
10958
  const drawMinimap = useCallback(() => {
10970
10959
  if (!showMinimap) return;
10971
10960
  const miniCanvas = minimapRef.current;
@@ -11067,7 +11056,15 @@ function IsometricCanvas({
11067
11056
  const spriteUrl = resolveTerrainSpriteUrl(tile);
11068
11057
  const img = spriteUrl ? getImage(spriteUrl) : null;
11069
11058
  if (img) {
11070
- ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
11059
+ if (img.naturalWidth === 0) {
11060
+ ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
11061
+ } else {
11062
+ const drawW = scaledTileWidth;
11063
+ const drawH = scaledTileWidth * (img.naturalHeight / img.naturalWidth);
11064
+ const drawX = pos.x;
11065
+ const drawY = pos.y + scaledTileHeight - drawH;
11066
+ ctx.drawImage(img, drawX, drawY, drawW, drawH);
11067
+ }
11071
11068
  } else {
11072
11069
  const centerX = pos.x + scaledTileWidth / 2;
11073
11070
  const topY = pos.y + scaledDiamondTopY;
@@ -12773,7 +12770,7 @@ var init_avl_elk_layout = __esm({
12773
12770
  elk = new ELK();
12774
12771
  }
12775
12772
  });
12776
- var log6, SWIM_GUTTER, CENTER_W, BehaviorView;
12773
+ var log5, SWIM_GUTTER, CENTER_W, BehaviorView;
12777
12774
  var init_BehaviorView = __esm({
12778
12775
  "components/avl/molecules/BehaviorView.tsx"() {
12779
12776
  "use client";
@@ -12782,7 +12779,7 @@ var init_BehaviorView = __esm({
12782
12779
  init_AvlSwimLane();
12783
12780
  init_types();
12784
12781
  init_avl_elk_layout();
12785
- log6 = createLogger("almadar:ui:avl:behavior-view");
12782
+ log5 = createLogger("almadar:ui:avl:behavior-view");
12786
12783
  SWIM_GUTTER = 120;
12787
12784
  CENTER_W = 360;
12788
12785
  BehaviorView = ({ data }) => {
@@ -12793,7 +12790,7 @@ var init_BehaviorView = __esm({
12793
12790
  const dataKey = useMemo(() => JSON.stringify(traitData), [traitData]);
12794
12791
  useEffect(() => {
12795
12792
  if (!traitData) return;
12796
- computeTraitLayout(traitData).then(setLayout).catch((err) => log6.error("compute-trait-layout-failed", { error: err instanceof Error ? err : String(err) }));
12793
+ computeTraitLayout(traitData).then(setLayout).catch((err) => log5.error("compute-trait-layout-failed", { error: err instanceof Error ? err : String(err) }));
12797
12794
  }, [dataKey]);
12798
12795
  if (!traitData) {
12799
12796
  return /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] p-4 text-center text-[var(--color-muted-foreground)] text-sm", children: t("avl.noTraitData") });
@@ -13204,7 +13201,7 @@ function generateDiff(oldVal, newVal) {
13204
13201
  }
13205
13202
  return diff;
13206
13203
  }
13207
- var orbStyleOverrides, orbStyle, loloStyleOverrides, loloStyle, log7, CODE_LANGUAGES, CODE_LANGUAGE_SET, DIFF_STYLES, LINE_PROPS_FN, HIDDEN_LINE_NUMBERS, CodeBlock;
13204
+ var orbStyleOverrides, orbStyle, loloStyleOverrides, loloStyle, log6, CODE_LANGUAGES, CODE_LANGUAGE_SET, DIFF_STYLES, LINE_PROPS_FN, HIDDEN_LINE_NUMBERS, CodeBlock;
13208
13205
  var init_CodeBlock = __esm({
13209
13206
  "components/core/molecules/markdown/CodeBlock.tsx"() {
13210
13207
  init_cn();
@@ -13287,7 +13284,7 @@ var init_CodeBlock = __esm({
13287
13284
  "lolo-op-async": { color: ORB_COLORS.dark.async }
13288
13285
  };
13289
13286
  loloStyle = { ...dark, ...loloStyleOverrides };
13290
- log7 = createLogger("almadar:ui:markdown-code");
13287
+ log6 = createLogger("almadar:ui:markdown-code");
13291
13288
  CODE_LANGUAGES = [
13292
13289
  "text",
13293
13290
  "json",
@@ -13326,7 +13323,7 @@ var init_CodeBlock = __esm({
13326
13323
  };
13327
13324
  LINE_PROPS_FN = (n) => ({ "data-line": String(n - 1) });
13328
13325
  HIDDEN_LINE_NUMBERS = { display: "none" };
13329
- CodeBlock = React81__default.memo(
13326
+ CodeBlock = React84__default.memo(
13330
13327
  ({
13331
13328
  code: rawCode,
13332
13329
  language = "text",
@@ -13495,24 +13492,24 @@ var init_CodeBlock = __esm({
13495
13492
  return;
13496
13493
  }
13497
13494
  lineEls.forEach((el) => {
13498
- const num2 = parseInt(el.getAttribute("data-line") ?? "-1", 10);
13499
- if (hiddenLines.has(num2)) {
13495
+ const num3 = parseInt(el.getAttribute("data-line") ?? "-1", 10);
13496
+ if (hiddenLines.has(num3)) {
13500
13497
  el.style.display = "none";
13501
13498
  return;
13502
13499
  }
13503
13500
  el.style.display = "";
13504
13501
  el.style.position = "relative";
13505
13502
  el.style.paddingLeft = "1.2em";
13506
- const region = foldStartMap.get(num2);
13503
+ const region = foldStartMap.get(num3);
13507
13504
  if (!region) return;
13508
- const isCollapsed = collapsed.has(num2);
13505
+ const isCollapsed = collapsed.has(num3);
13509
13506
  const toggle = document.createElement("span");
13510
13507
  toggle.className = "fold-toggle";
13511
13508
  toggle.textContent = isCollapsed ? "\u25B6" : "\u25BC";
13512
13509
  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%";
13513
13510
  toggle.addEventListener("click", (e) => {
13514
13511
  e.stopPropagation();
13515
- toggleFoldRef.current(num2);
13512
+ toggleFoldRef.current(num3);
13516
13513
  });
13517
13514
  el.insertBefore(toggle, el.firstChild);
13518
13515
  if (isCollapsed) {
@@ -13551,7 +13548,7 @@ var init_CodeBlock = __esm({
13551
13548
  eventBus.emit("UI:COPY_CODE", { language: activeLanguage, success: true });
13552
13549
  setTimeout(() => setCopied(false), 2e3);
13553
13550
  } catch (err) {
13554
- log7.error("Failed to copy code", { error: err instanceof Error ? err : String(err) });
13551
+ log6.error("Failed to copy code", { error: err instanceof Error ? err : String(err) });
13555
13552
  eventBus.emit("UI:COPY_CODE", { language: activeLanguage, success: false });
13556
13553
  }
13557
13554
  };
@@ -13913,7 +13910,7 @@ var init_MarkdownContent = __esm({
13913
13910
  init_Box();
13914
13911
  init_CodeBlock();
13915
13912
  init_cn();
13916
- MarkdownContent = React81__default.memo(
13913
+ MarkdownContent = React84__default.memo(
13917
13914
  ({ content, direction = "ltr", className }) => {
13918
13915
  const { t: _t } = useTranslate();
13919
13916
  const safeContent = typeof content === "string" ? content : String(content ?? "");
@@ -15230,7 +15227,7 @@ var init_StateMachineView = __esm({
15230
15227
  style: { top: title ? 30 : 0 },
15231
15228
  children: [
15232
15229
  entity && /* @__PURE__ */ jsx(EntityBox, { entity, config }),
15233
- states.map((state) => renderStateNode ? /* @__PURE__ */ jsx(React81__default.Fragment, { children: renderStateNode(state, config) }, state.id) : /* @__PURE__ */ jsx(
15230
+ states.map((state) => renderStateNode ? /* @__PURE__ */ jsx(React84__default.Fragment, { children: renderStateNode(state, config) }, state.id) : /* @__PURE__ */ jsx(
15234
15231
  StateNode,
15235
15232
  {
15236
15233
  state,
@@ -17432,14 +17429,14 @@ function useSafeEventBus2() {
17432
17429
  } };
17433
17430
  }
17434
17431
  }
17435
- var log8, lookStyles4, ButtonGroup;
17432
+ var log7, lookStyles4, ButtonGroup;
17436
17433
  var init_ButtonGroup = __esm({
17437
17434
  "components/core/molecules/ButtonGroup.tsx"() {
17438
17435
  "use client";
17439
17436
  init_cn();
17440
17437
  init_atoms2();
17441
17438
  init_useEventBus();
17442
- log8 = createLogger("almadar:ui:button-group");
17439
+ log7 = createLogger("almadar:ui:button-group");
17443
17440
  lookStyles4 = {
17444
17441
  "right-aligned-buttons": "",
17445
17442
  "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
@@ -17520,7 +17517,7 @@ var init_ButtonGroup = __esm({
17520
17517
  {
17521
17518
  variant: "ghost",
17522
17519
  onClick: () => {
17523
- log8.debug("Filter clicked", { field: filter.field });
17520
+ log7.debug("Filter clicked", { field: filter.field });
17524
17521
  },
17525
17522
  children: filter.label
17526
17523
  },
@@ -19131,8 +19128,8 @@ function EmojiEffect({
19131
19128
  );
19132
19129
  }
19133
19130
  function CanvasEffect({
19134
- effectSpriteUrl = "https://almadar-kflow-assets.web.app/shared/effects/gas/gas00.png",
19135
- assetBaseUrl = "https://almadar-kflow-assets.web.app/shared/effects/",
19131
+ effectSpriteUrl,
19132
+ assetBaseUrl,
19136
19133
  ...props
19137
19134
  }) {
19138
19135
  const eventBus = useEventBus();
@@ -19175,6 +19172,380 @@ var init_CanvasEffect = __esm({
19175
19172
  CanvasEffect.displayName = "CanvasEffect";
19176
19173
  }
19177
19174
  });
19175
+ function CardHand({
19176
+ cards = DEFAULT_CARDS,
19177
+ selectedId = "",
19178
+ size = "md",
19179
+ onCardClick,
19180
+ cardClickEvent,
19181
+ emptyLabel = "No cards in hand",
19182
+ className
19183
+ }) {
19184
+ const eventBus = useEventBus();
19185
+ const handleClick = React84.useCallback(
19186
+ (card) => {
19187
+ if (card.disabled) return;
19188
+ onCardClick?.(card.id);
19189
+ if (cardClickEvent) {
19190
+ eventBus.emit(`UI:${cardClickEvent}`, { cardId: card.id });
19191
+ }
19192
+ },
19193
+ [onCardClick, cardClickEvent, eventBus]
19194
+ );
19195
+ if (cards.length === 0) {
19196
+ return /* @__PURE__ */ jsx(Box, { className: cn("flex items-center justify-center p-4", className), children: /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: emptyLabel }) });
19197
+ }
19198
+ const art = artSizeMap[size];
19199
+ return /* @__PURE__ */ jsx(Box, { className: cn("flex flex-wrap gap-2 justify-center items-end", className), children: cards.map((card) => {
19200
+ const selected = selectedId === card.id;
19201
+ return /* @__PURE__ */ jsxs(
19202
+ "button",
19203
+ {
19204
+ type: "button",
19205
+ onClick: () => handleClick(card),
19206
+ disabled: card.disabled,
19207
+ title: card.title,
19208
+ className: cn(
19209
+ "card-hand-card relative flex flex-col items-center rounded-interactive border-2",
19210
+ "bg-card/90 px-1.5 pt-1.5 pb-1 transition-all duration-150",
19211
+ cardSizeMap[size],
19212
+ card.disabled ? "border-border opacity-50 cursor-not-allowed" : "border-accent hover:brightness-125 hover:-translate-y-1 cursor-pointer",
19213
+ selected && "ring-2 ring-foreground ring-offset-1 ring-offset-background -translate-y-1"
19214
+ ),
19215
+ children: [
19216
+ card.cost != null && /* @__PURE__ */ jsx(
19217
+ "span",
19218
+ {
19219
+ className: cn(
19220
+ "absolute -top-2 -left-2 flex items-center justify-center",
19221
+ "min-w-[22px] h-[22px] rounded-full px-1",
19222
+ "bg-info text-foreground text-xs font-bold border border-background"
19223
+ ),
19224
+ children: card.cost
19225
+ }
19226
+ ),
19227
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 flex items-center justify-center w-full", children: card.iconUrl ? /* @__PURE__ */ jsx(
19228
+ "img",
19229
+ {
19230
+ src: card.iconUrl,
19231
+ alt: card.title ?? card.id,
19232
+ width: art,
19233
+ height: art,
19234
+ style: { imageRendering: "pixelated", objectFit: "contain" },
19235
+ className: "flex-shrink-0"
19236
+ }
19237
+ ) : /* @__PURE__ */ jsx(
19238
+ Box,
19239
+ {
19240
+ className: "rounded bg-muted/40",
19241
+ style: { width: art, height: art }
19242
+ }
19243
+ ) }),
19244
+ card.title != null && /* @__PURE__ */ jsx(
19245
+ Typography,
19246
+ {
19247
+ variant: "small",
19248
+ className: "w-full truncate text-center text-[10px] leading-tight font-semibold",
19249
+ children: card.title
19250
+ }
19251
+ ),
19252
+ (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: [
19253
+ /* @__PURE__ */ jsx("span", { className: "text-error", children: card.attack != null ? `\u2694${card.attack}` : "" }),
19254
+ /* @__PURE__ */ jsx("span", { className: "text-info", children: card.defense != null ? `\u{1F6E1}${card.defense}` : "" })
19255
+ ] })
19256
+ ]
19257
+ },
19258
+ card.id
19259
+ );
19260
+ }) });
19261
+ }
19262
+ var cardSizeMap, artSizeMap, DEFAULT_CARDS;
19263
+ var init_CardHand = __esm({
19264
+ "components/game/molecules/CardHand.tsx"() {
19265
+ "use client";
19266
+ init_cn();
19267
+ init_useEventBus();
19268
+ init_Box();
19269
+ init_Typography();
19270
+ cardSizeMap = {
19271
+ sm: "w-16 h-24",
19272
+ md: "w-20 h-28",
19273
+ lg: "w-24 h-36"
19274
+ };
19275
+ artSizeMap = {
19276
+ sm: 40,
19277
+ md: 52,
19278
+ lg: 64
19279
+ };
19280
+ DEFAULT_CARDS = [
19281
+ { id: "card-1", title: "Brawler", cost: 1, attack: 2, defense: 1 },
19282
+ { id: "card-2", title: "Guardian", cost: 2, attack: 1, defense: 4 },
19283
+ { id: "card-3", title: "Striker", cost: 3, attack: 5, defense: 2 }
19284
+ ];
19285
+ CardHand.displayName = "CardHand";
19286
+ }
19287
+ });
19288
+ function resolveManifestUrl(manifest, relative) {
19289
+ if (relative == null) return void 0;
19290
+ if (/^https?:\/\//.test(relative)) return relative;
19291
+ const base = manifest?.baseUrl;
19292
+ if (base == null) return relative;
19293
+ const cleanBase = base.replace(/\/$/, "");
19294
+ const cleanRel = relative.replace(/^\//, "");
19295
+ return `${cleanBase}/${cleanRel}`;
19296
+ }
19297
+ function rowToCard(r) {
19298
+ return {
19299
+ id: str(r.id),
19300
+ iconKey: r.iconKey == null ? void 0 : str(r.iconKey),
19301
+ title: r.title == null ? void 0 : str(r.title),
19302
+ cost: r.cost == null ? void 0 : num(r.cost),
19303
+ attack: r.attack == null ? void 0 : num(r.attack),
19304
+ defense: r.defense == null ? void 0 : num(r.defense)
19305
+ };
19306
+ }
19307
+ function toHandCards(cards, manifest, mana) {
19308
+ return cards.map((c) => {
19309
+ const key = c.iconKey ?? c.id;
19310
+ const iconUrl = resolveManifestUrl(manifest, manifest?.cards?.[key]);
19311
+ return {
19312
+ id: c.id,
19313
+ iconUrl,
19314
+ title: c.title,
19315
+ cost: c.cost,
19316
+ attack: c.attack,
19317
+ defense: c.defense,
19318
+ disabled: c.cost != null && c.cost > mana
19319
+ };
19320
+ });
19321
+ }
19322
+ function CardBattlerBoard({
19323
+ entity,
19324
+ deck: propDeck,
19325
+ hand: propHand,
19326
+ board: propBoard,
19327
+ mana: propMana,
19328
+ maxMana: propMaxMana,
19329
+ turn: propTurn,
19330
+ result: propResult,
19331
+ assetManifest: propAssetManifest,
19332
+ playCardEvent,
19333
+ endTurnEvent,
19334
+ playAgainEvent,
19335
+ gameEndEvent,
19336
+ className
19337
+ }) {
19338
+ const board = boardEntity(entity) ?? {};
19339
+ const eventBus = useEventBus();
19340
+ const { t } = useTranslate();
19341
+ const assetManifest = propAssetManifest ?? board.assetManifest;
19342
+ const entityHand = useMemo(() => rows(board.hand).map(rowToCard), [board.hand]);
19343
+ const rawHand = propHand ?? entityHand;
19344
+ const hand = rawHand.length > 0 ? rawHand : DEFAULT_HAND;
19345
+ const entityBoard = useMemo(() => rows(board.board).map(rowToCard), [board.board]);
19346
+ const playedCards = propBoard ?? entityBoard;
19347
+ const entityDeck = useMemo(() => rows(board.deck).map(rowToCard), [board.deck]);
19348
+ const deck = propDeck ?? entityDeck;
19349
+ const mana = propMana ?? num(board.mana, 3);
19350
+ const maxMana = propMaxMana ?? num(board.maxMana, 3);
19351
+ const turn = propTurn ?? num(board.turn, 1);
19352
+ const result = propResult ?? (str(board.result) || "none");
19353
+ const handCards = useMemo(() => toHandCards(hand, assetManifest, mana), [hand, assetManifest, mana]);
19354
+ const boardCards = useMemo(() => toHandCards(playedCards, assetManifest, mana), [playedCards, assetManifest, mana]);
19355
+ const emittedGameEnd = useRef(false);
19356
+ if (result !== "none" && !emittedGameEnd.current) {
19357
+ emittedGameEnd.current = true;
19358
+ if (gameEndEvent) {
19359
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
19360
+ }
19361
+ }
19362
+ if (result === "none") {
19363
+ emittedGameEnd.current = false;
19364
+ }
19365
+ const handlePlayCard = useCallback((cardId) => {
19366
+ if (result !== "none") return;
19367
+ if (playCardEvent) {
19368
+ eventBus.emit(`UI:${playCardEvent}`, { cardId });
19369
+ }
19370
+ }, [result, playCardEvent, eventBus]);
19371
+ const handleEndTurn = useCallback(() => {
19372
+ if (result !== "none") return;
19373
+ if (endTurnEvent) {
19374
+ eventBus.emit(`UI:${endTurnEvent}`, {});
19375
+ }
19376
+ }, [result, endTurnEvent, eventBus]);
19377
+ const handlePlayAgain = useCallback(() => {
19378
+ if (playAgainEvent) {
19379
+ eventBus.emit(`UI:${playAgainEvent}`, {});
19380
+ }
19381
+ }, [playAgainEvent, eventBus]);
19382
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("card-battler-board relative min-h-[600px] bg-background", className), gap: "none", children: [
19383
+ /* @__PURE__ */ jsxs(
19384
+ HStack,
19385
+ {
19386
+ className: "px-4 py-2 border-b border-border bg-surface",
19387
+ gap: "lg",
19388
+ align: "center",
19389
+ children: [
19390
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
19391
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.mana") ?? "Mana" }),
19392
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", weight: "bold", className: "text-info", children: [
19393
+ mana,
19394
+ " / ",
19395
+ maxMana
19396
+ ] })
19397
+ ] }),
19398
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
19399
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.turn") ?? "Turn" }),
19400
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", children: turn })
19401
+ ] }),
19402
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
19403
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.deck") ?? "Deck" }),
19404
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", children: deck.length })
19405
+ ] }),
19406
+ /* @__PURE__ */ jsx(Box, { className: "ml-auto", children: result === "none" && /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: handleEndTurn, children: t("cb.endTurn") ?? "End Turn" }) })
19407
+ ]
19408
+ }
19409
+ ),
19410
+ /* @__PURE__ */ jsxs(Box, { className: "flex-1 relative p-4 overflow-auto", children: [
19411
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", className: "mb-2 uppercase tracking-wide", children: t("cb.battlefield") ?? "Battlefield" }),
19412
+ boardCards.length > 0 ? /* @__PURE__ */ jsx(
19413
+ CardHand,
19414
+ {
19415
+ cards: boardCards,
19416
+ size: "md",
19417
+ className: "justify-start"
19418
+ }
19419
+ ) : /* @__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" }) })
19420
+ ] }),
19421
+ /* @__PURE__ */ jsxs(Box, { className: "border-t border-border bg-surface px-4 py-3", children: [
19422
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", className: "mb-2 uppercase tracking-wide", children: t("cb.hand") ?? "Your Hand" }),
19423
+ /* @__PURE__ */ jsx(
19424
+ CardHand,
19425
+ {
19426
+ cards: handCards,
19427
+ size: "lg",
19428
+ onCardClick: handlePlayCard,
19429
+ emptyLabel: t("cb.emptyHand") ?? "No cards in hand"
19430
+ }
19431
+ )
19432
+ ] }),
19433
+ 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: [
19434
+ /* @__PURE__ */ jsx(
19435
+ Typography,
19436
+ {
19437
+ variant: "h2",
19438
+ className: cn(
19439
+ "text-4xl font-black tracking-widest uppercase",
19440
+ result === "won" ? "text-warning" : "text-error"
19441
+ ),
19442
+ children: result === "won" ? t("cb.victory") ?? "Victory!" : t("cb.defeat") ?? "Defeat!"
19443
+ }
19444
+ ),
19445
+ /* @__PURE__ */ jsx(
19446
+ Button,
19447
+ {
19448
+ variant: "primary",
19449
+ className: "px-8 py-3 font-semibold",
19450
+ onClick: handlePlayAgain,
19451
+ children: t("cb.playAgain") ?? "Play Again"
19452
+ }
19453
+ )
19454
+ ] }) })
19455
+ ] });
19456
+ }
19457
+ var DEFAULT_HAND;
19458
+ var init_CardBattlerBoard = __esm({
19459
+ "components/game/organisms/CardBattlerBoard.tsx"() {
19460
+ "use client";
19461
+ init_cn();
19462
+ init_useEventBus();
19463
+ init_Box();
19464
+ init_Button();
19465
+ init_Typography();
19466
+ init_Stack();
19467
+ init_CardHand();
19468
+ init_boardEntity();
19469
+ DEFAULT_HAND = [
19470
+ { id: "c1", iconKey: "bear", title: "Bear", cost: 3, attack: 5, defense: 4 },
19471
+ { id: "c2", iconKey: "owl", title: "Owl", cost: 1, attack: 2, defense: 1 },
19472
+ { id: "c3", iconKey: "gorilla", title: "Gorilla", cost: 4, attack: 6, defense: 5 },
19473
+ { id: "c4", iconKey: "frog", title: "Frog", cost: 1, attack: 1, defense: 2 },
19474
+ { id: "c5", iconKey: "snake", title: "Snake", cost: 2, attack: 4, defense: 1 }
19475
+ ];
19476
+ CardBattlerBoard.displayName = "CardBattlerBoard";
19477
+ }
19478
+ });
19479
+ function isEntityRow(value) {
19480
+ return value != null && typeof value === "object" && !Array.isArray(value);
19481
+ }
19482
+ function CardBattlerTemplate({
19483
+ entity,
19484
+ title = "Card Battler",
19485
+ deck,
19486
+ hand,
19487
+ board,
19488
+ mana,
19489
+ maxMana,
19490
+ turn,
19491
+ result,
19492
+ assetManifest,
19493
+ playCardEvent,
19494
+ endTurnEvent,
19495
+ playAgainEvent,
19496
+ gameEndEvent,
19497
+ className
19498
+ }) {
19499
+ const resolved = isEntityRow(entity) ? entity : void 0;
19500
+ return /* @__PURE__ */ jsxs(
19501
+ Box,
19502
+ {
19503
+ display: "flex",
19504
+ fullHeight: true,
19505
+ className: cn("card-battler-template flex-col", className),
19506
+ children: [
19507
+ /* @__PURE__ */ jsx(
19508
+ HStack,
19509
+ {
19510
+ gap: "sm",
19511
+ align: "center",
19512
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
19513
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
19514
+ }
19515
+ ),
19516
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
19517
+ CardBattlerBoard,
19518
+ {
19519
+ entity: resolved,
19520
+ deck,
19521
+ hand,
19522
+ board,
19523
+ mana,
19524
+ maxMana,
19525
+ turn,
19526
+ result,
19527
+ assetManifest,
19528
+ playCardEvent,
19529
+ endTurnEvent,
19530
+ playAgainEvent,
19531
+ gameEndEvent,
19532
+ className: "h-full"
19533
+ }
19534
+ ) })
19535
+ ]
19536
+ }
19537
+ );
19538
+ }
19539
+ var init_CardBattlerTemplate = __esm({
19540
+ "components/game/templates/CardBattlerTemplate.tsx"() {
19541
+ init_cn();
19542
+ init_Box();
19543
+ init_Stack();
19544
+ init_Typography();
19545
+ init_CardBattlerBoard();
19546
+ CardBattlerTemplate.displayName = "CardBattlerTemplate";
19547
+ }
19548
+ });
19178
19549
 
19179
19550
  // lib/getNestedValue.ts
19180
19551
  function getNestedValue(obj, path) {
@@ -20957,6 +21328,315 @@ var init_ChartLegend = __esm({
20957
21328
  ChartLegend.displayName = "ChartLegend";
20958
21329
  }
20959
21330
  });
21331
+ function resolveManifestUrl2(manifest, relative) {
21332
+ if (relative == null) return void 0;
21333
+ if (/^https?:\/\//.test(relative)) return relative;
21334
+ const base = manifest?.baseUrl;
21335
+ if (base == null) return relative;
21336
+ const cleanBase = base.replace(/\/$/, "");
21337
+ const cleanRel = relative.replace(/^\//, "");
21338
+ return `${cleanBase}/${cleanRel}`;
21339
+ }
21340
+ function buildDefaultCBTiles() {
21341
+ const tiles = [];
21342
+ for (let y = 0; y < CB_GRID_H; y++) {
21343
+ for (let x = 0; x < CB_GRID_W; x++) {
21344
+ const variant = (x * 3 + y * 5 + (x ^ y)) % 3;
21345
+ const terrainKey = ["grass", "stone", "dirt"][variant];
21346
+ tiles.push({ x, y, terrain: terrainKey, passable: true });
21347
+ }
21348
+ }
21349
+ return tiles;
21350
+ }
21351
+ function tilesToIso(tiles, manifest) {
21352
+ return tiles.map((t) => {
21353
+ const key = t.terrain ?? "grass";
21354
+ const sprite = t.terrainSprite ?? resolveManifestUrl2(manifest, manifest?.terrains?.[key]) ?? resolveManifestUrl2(manifest, manifest?.terrains?.grass);
21355
+ return {
21356
+ x: t.x,
21357
+ y: t.y,
21358
+ terrain: t.terrain,
21359
+ terrainSprite: sprite,
21360
+ passable: t.passable
21361
+ };
21362
+ });
21363
+ }
21364
+ function buildingsToFeatures(buildings, manifest) {
21365
+ return buildings.map((b) => ({
21366
+ id: b.id,
21367
+ x: b.x,
21368
+ y: b.y,
21369
+ type: b.type,
21370
+ sprite: resolveManifestUrl2(manifest, manifest?.features?.[b.type])
21371
+ }));
21372
+ }
21373
+ function CityBuilderBoard({
21374
+ entity,
21375
+ tiles: propTiles,
21376
+ buildings: propBuildings,
21377
+ assetManifest: propAssetManifest,
21378
+ buildTypes: propBuildTypes,
21379
+ pendingBuildType: propPendingBuildType,
21380
+ gold: propGold,
21381
+ wood: propWood,
21382
+ population: propPopulation,
21383
+ result: propResult,
21384
+ scale = 0.3,
21385
+ unitScale = 0.6,
21386
+ spriteHeightRatio = 1.5,
21387
+ spriteMaxWidthRatio = 0.7,
21388
+ selectBuildTypeEvent,
21389
+ placeBuildingEvent,
21390
+ gameEndEvent,
21391
+ className
21392
+ }) {
21393
+ const board = boardEntity(entity) ?? {};
21394
+ const eventBus = useEventBus();
21395
+ const { t } = useTranslate();
21396
+ const assetManifest = propAssetManifest ?? board.assetManifest;
21397
+ const entityTiles = useMemo(
21398
+ () => rows(board.tiles).map((r) => ({
21399
+ x: num(r.x),
21400
+ y: num(r.y),
21401
+ terrain: r.terrain == null ? void 0 : str(r.terrain),
21402
+ terrainSprite: r.terrainSprite == null ? void 0 : str(r.terrainSprite),
21403
+ passable: r.passable !== false
21404
+ })),
21405
+ [board.tiles]
21406
+ );
21407
+ const rawTiles = propTiles ?? entityTiles;
21408
+ const tiles = rawTiles.length >= CB_GRID_W * CB_GRID_H ? rawTiles : DEFAULT_CB_TILES;
21409
+ const entityBuildings = useMemo(
21410
+ () => rows(board.buildings).map((r) => ({
21411
+ id: str(r.id),
21412
+ x: num(r.x),
21413
+ y: num(r.y),
21414
+ type: str(r.type)
21415
+ })),
21416
+ [board.buildings]
21417
+ );
21418
+ const buildings = propBuildings ?? entityBuildings;
21419
+ const buildTypes = propBuildTypes ?? DEFAULT_BUILD_TYPES;
21420
+ const gold = propGold ?? num(board.gold, 100);
21421
+ const wood = propWood ?? num(board.wood, 80);
21422
+ const population = propPopulation ?? num(board.population, 0);
21423
+ const pendingBuildType = propPendingBuildType ?? (str(board.pendingBuildType) || buildTypes[0]?.type || "");
21424
+ const result = propResult ?? (str(board.result) || "none");
21425
+ const [hoveredTile, setHoveredTile] = useState(null);
21426
+ const emittedGameEnd = useRef(false);
21427
+ if (result !== "none" && !emittedGameEnd.current) {
21428
+ emittedGameEnd.current = true;
21429
+ if (gameEndEvent) {
21430
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
21431
+ }
21432
+ }
21433
+ if (result === "none") {
21434
+ emittedGameEnd.current = false;
21435
+ }
21436
+ const pendingCost = useMemo(
21437
+ () => buildTypes.find((b) => b.type === pendingBuildType)?.cost ?? 0,
21438
+ [buildTypes, pendingBuildType]
21439
+ );
21440
+ const buildingPositions = useMemo(
21441
+ () => new Set(buildings.map((b) => `${b.x},${b.y}`)),
21442
+ [buildings]
21443
+ );
21444
+ const validMoves = useMemo(() => {
21445
+ if (result !== "none" || gold < pendingCost) return [];
21446
+ return tiles.filter(
21447
+ (t2) => t2.passable !== false && !buildingPositions.has(`${t2.x},${t2.y}`)
21448
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
21449
+ }, [tiles, buildingPositions, result, gold, pendingCost]);
21450
+ const isoTiles = useMemo(() => tilesToIso(tiles, assetManifest), [tiles, assetManifest]);
21451
+ const buildingFeatures = useMemo(() => buildingsToFeatures(buildings, assetManifest), [buildings, assetManifest]);
21452
+ const handleTileClick = useCallback((x, y) => {
21453
+ if (result !== "none") return;
21454
+ if (buildingPositions.has(`${x},${y}`)) return;
21455
+ if (gold < pendingCost) return;
21456
+ if (placeBuildingEvent) {
21457
+ eventBus.emit(`UI:${placeBuildingEvent}`, { x, y });
21458
+ }
21459
+ }, [result, buildingPositions, gold, pendingCost, placeBuildingEvent, eventBus]);
21460
+ const handleSelectBuildType = useCallback((buildType) => {
21461
+ if (selectBuildTypeEvent) {
21462
+ eventBus.emit(`UI:${selectBuildTypeEvent}`, { buildType });
21463
+ }
21464
+ }, [selectBuildTypeEvent, eventBus]);
21465
+ const canBuild = gold >= pendingCost && result === "none";
21466
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("city-builder-board relative min-h-[600px] bg-background", className), gap: "none", children: [
21467
+ /* @__PURE__ */ jsxs(
21468
+ HStack,
21469
+ {
21470
+ className: "px-4 py-2 border-b border-border bg-surface",
21471
+ gap: "lg",
21472
+ align: "center",
21473
+ children: [
21474
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
21475
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.gold") ?? "Gold" }),
21476
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
21477
+ ] }),
21478
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
21479
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.wood") ?? "Wood" }),
21480
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-success", children: wood })
21481
+ ] }),
21482
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
21483
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("cb.population") ?? "Population" }),
21484
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", children: population })
21485
+ ] }),
21486
+ /* @__PURE__ */ jsx(HStack, { gap: "xs", align: "center", className: "ml-auto", children: buildTypes.map((b) => /* @__PURE__ */ jsxs(
21487
+ Button,
21488
+ {
21489
+ variant: b.type === pendingBuildType ? "primary" : "secondary",
21490
+ size: "sm",
21491
+ disabled: gold < b.cost || result !== "none",
21492
+ onClick: () => handleSelectBuildType(b.type),
21493
+ children: [
21494
+ b.label,
21495
+ " (",
21496
+ b.cost,
21497
+ ")"
21498
+ ]
21499
+ },
21500
+ b.type
21501
+ )) })
21502
+ ]
21503
+ }
21504
+ ),
21505
+ /* @__PURE__ */ jsxs(Box, { className: "relative flex-1", children: [
21506
+ /* @__PURE__ */ jsx(
21507
+ IsometricCanvas_default,
21508
+ {
21509
+ tiles: isoTiles,
21510
+ features: buildingFeatures,
21511
+ assetBaseUrl: assetManifest?.baseUrl,
21512
+ assetManifest,
21513
+ validMoves,
21514
+ hoveredTile,
21515
+ onTileClick: handleTileClick,
21516
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
21517
+ onTileLeave: () => setHoveredTile(null),
21518
+ scale,
21519
+ unitScale,
21520
+ spriteHeightRatio,
21521
+ spriteMaxWidthRatio
21522
+ }
21523
+ ),
21524
+ 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)` }) })
21525
+ ] }),
21526
+ 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: [
21527
+ /* @__PURE__ */ jsx(
21528
+ Typography,
21529
+ {
21530
+ variant: "h2",
21531
+ className: cn(
21532
+ "text-4xl font-black tracking-widest uppercase",
21533
+ result === "won" ? "text-warning" : "text-error"
21534
+ ),
21535
+ children: result === "won" ? t("cb.victory") ?? "City Thrives!" : t("cb.defeat") ?? "City Collapsed!"
21536
+ }
21537
+ ),
21538
+ /* @__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." })
21539
+ ] }) })
21540
+ ] });
21541
+ }
21542
+ var CB_GRID_W, CB_GRID_H, DEFAULT_BUILD_TYPES, DEFAULT_CB_TILES;
21543
+ var init_CityBuilderBoard = __esm({
21544
+ "components/game/organisms/CityBuilderBoard.tsx"() {
21545
+ "use client";
21546
+ init_cn();
21547
+ init_useEventBus();
21548
+ init_Box();
21549
+ init_Button();
21550
+ init_Typography();
21551
+ init_Stack();
21552
+ init_IsometricCanvas();
21553
+ init_boardEntity();
21554
+ CB_GRID_W = 12;
21555
+ CB_GRID_H = 12;
21556
+ DEFAULT_BUILD_TYPES = [
21557
+ { type: "house", label: "House", cost: 20 },
21558
+ { type: "farm", label: "Farm", cost: 15 },
21559
+ { type: "mine", label: "Mine", cost: 25 },
21560
+ { type: "barracks", label: "Barracks", cost: 40 }
21561
+ ];
21562
+ DEFAULT_CB_TILES = buildDefaultCBTiles();
21563
+ CityBuilderBoard.displayName = "CityBuilderBoard";
21564
+ }
21565
+ });
21566
+ function isEntityRow2(value) {
21567
+ return value != null && typeof value === "object" && !Array.isArray(value);
21568
+ }
21569
+ function CityBuilderTemplate({
21570
+ entity,
21571
+ title = "City Builder",
21572
+ tiles,
21573
+ buildings,
21574
+ buildTypes,
21575
+ pendingBuildType,
21576
+ assetManifest,
21577
+ gold,
21578
+ wood,
21579
+ population,
21580
+ result,
21581
+ scale,
21582
+ unitScale,
21583
+ selectBuildTypeEvent,
21584
+ placeBuildingEvent,
21585
+ gameEndEvent,
21586
+ className
21587
+ }) {
21588
+ const resolved = isEntityRow2(entity) ? entity : void 0;
21589
+ return /* @__PURE__ */ jsxs(
21590
+ Box,
21591
+ {
21592
+ display: "flex",
21593
+ fullHeight: true,
21594
+ className: cn("city-builder-template flex-col", className),
21595
+ children: [
21596
+ /* @__PURE__ */ jsx(
21597
+ HStack,
21598
+ {
21599
+ gap: "sm",
21600
+ align: "center",
21601
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
21602
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
21603
+ }
21604
+ ),
21605
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
21606
+ CityBuilderBoard,
21607
+ {
21608
+ entity: resolved,
21609
+ tiles,
21610
+ buildings,
21611
+ buildTypes,
21612
+ pendingBuildType,
21613
+ assetManifest,
21614
+ gold,
21615
+ wood,
21616
+ population,
21617
+ result,
21618
+ scale,
21619
+ unitScale,
21620
+ selectBuildTypeEvent,
21621
+ placeBuildingEvent,
21622
+ gameEndEvent,
21623
+ className: "h-full"
21624
+ }
21625
+ ) })
21626
+ ]
21627
+ }
21628
+ );
21629
+ }
21630
+ var init_CityBuilderTemplate = __esm({
21631
+ "components/game/templates/CityBuilderTemplate.tsx"() {
21632
+ init_cn();
21633
+ init_Box();
21634
+ init_Stack();
21635
+ init_Typography();
21636
+ init_CityBuilderBoard();
21637
+ CityBuilderTemplate.displayName = "CityBuilderTemplate";
21638
+ }
21639
+ });
20960
21640
  function ClassifierBoard({
20961
21641
  entity,
20962
21642
  completeEvent = "PUZZLE_COMPLETE",
@@ -21771,7 +22451,7 @@ function CraftingRecipe({
21771
22451
  className
21772
22452
  }) {
21773
22453
  const eventBus = useEventBus();
21774
- const handleCraft = React81.useCallback(() => {
22454
+ const handleCraft = React84.useCallback(() => {
21775
22455
  onCraft?.();
21776
22456
  if (craftEvent) {
21777
22457
  eventBus.emit(craftEvent, { output: output.label });
@@ -21788,7 +22468,7 @@ function CraftingRecipe({
21788
22468
  children: [
21789
22469
  /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap items-center", children: inputs.map((ingredient, index) => {
21790
22470
  const hasSufficient = ingredient.available >= ingredient.required;
21791
- return /* @__PURE__ */ jsxs(React81.Fragment, { children: [
22471
+ return /* @__PURE__ */ jsxs(React84.Fragment, { children: [
21792
22472
  /* @__PURE__ */ jsx(Box, { className: "relative", children: /* @__PURE__ */ jsx(
21793
22473
  ItemSlot,
21794
22474
  {
@@ -21860,8 +22540,8 @@ function DPad({
21860
22540
  }) {
21861
22541
  const eventBus = useEventBus();
21862
22542
  const sizes = sizeMap15[size];
21863
- const [activeDirections, setActiveDirections] = React81.useState(/* @__PURE__ */ new Set());
21864
- const handlePress = React81.useCallback(
22543
+ const [activeDirections, setActiveDirections] = React84.useState(/* @__PURE__ */ new Set());
22544
+ const handlePress = React84.useCallback(
21865
22545
  (direction) => {
21866
22546
  setActiveDirections((prev) => new Set(prev).add(direction));
21867
22547
  if (directionEvent) eventBus.emit(`UI:${directionEvent}`, { direction, pressed: true });
@@ -21869,7 +22549,7 @@ function DPad({
21869
22549
  },
21870
22550
  [directionEvent, eventBus, onDirection]
21871
22551
  );
21872
- const handleRelease = React81.useCallback(
22552
+ const handleRelease = React84.useCallback(
21873
22553
  (direction) => {
21874
22554
  setActiveDirections((prev) => {
21875
22555
  const next = new Set(prev);
@@ -22676,8 +23356,8 @@ var init_Menu = __esm({
22676
23356
  "bottom-end": "bottom-start"
22677
23357
  };
22678
23358
  const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
22679
- const triggerChild = React81__default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger });
22680
- const triggerElement = React81__default.cloneElement(
23359
+ const triggerChild = React84__default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger });
23360
+ const triggerElement = React84__default.cloneElement(
22681
23361
  triggerChild,
22682
23362
  {
22683
23363
  ref: triggerRef,
@@ -22811,14 +23491,14 @@ function useDataDnd(args) {
22811
23491
  const isZone = Boolean(dragGroup || accepts || sortable);
22812
23492
  const enabled = isZone || Boolean(dndRoot);
22813
23493
  const eventBus = useEventBus();
22814
- const parentRoot = React81__default.useContext(RootCtx);
23494
+ const parentRoot = React84__default.useContext(RootCtx);
22815
23495
  const isRoot = enabled && parentRoot === null;
22816
- const zoneId = React81__default.useId();
23496
+ const zoneId = React84__default.useId();
22817
23497
  const ownGroup = dragGroup ?? accepts ?? zoneId;
22818
- const [optimisticOrders, setOptimisticOrders] = React81__default.useState(() => /* @__PURE__ */ new Map());
22819
- const optimisticOrdersRef = React81__default.useRef(optimisticOrders);
23498
+ const [optimisticOrders, setOptimisticOrders] = React84__default.useState(() => /* @__PURE__ */ new Map());
23499
+ const optimisticOrdersRef = React84__default.useRef(optimisticOrders);
22820
23500
  optimisticOrdersRef.current = optimisticOrders;
22821
- const clearOptimisticOrder = React81__default.useCallback((group) => {
23501
+ const clearOptimisticOrder = React84__default.useCallback((group) => {
22822
23502
  setOptimisticOrders((prev) => {
22823
23503
  if (!prev.has(group)) return prev;
22824
23504
  const next = new Map(prev);
@@ -22843,7 +23523,7 @@ function useDataDnd(args) {
22843
23523
  const raw = it[dndItemIdField];
22844
23524
  return String(raw ?? `__idx_${idx}`);
22845
23525
  }).join("|");
22846
- const itemIds = React81__default.useMemo(
23526
+ const itemIds = React84__default.useMemo(
22847
23527
  () => orderedItems.map((it, idx) => {
22848
23528
  const raw = it[dndItemIdField];
22849
23529
  return raw ?? `__idx_${idx}`;
@@ -22851,7 +23531,7 @@ function useDataDnd(args) {
22851
23531
  [itemIdsSignature]
22852
23532
  );
22853
23533
  const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
22854
- React81__default.useEffect(() => {
23534
+ React84__default.useEffect(() => {
22855
23535
  const root = isRoot ? null : parentRoot;
22856
23536
  if (root) {
22857
23537
  root.clearOptimisticOrder(ownGroup);
@@ -22859,20 +23539,20 @@ function useDataDnd(args) {
22859
23539
  clearOptimisticOrder(ownGroup);
22860
23540
  }
22861
23541
  }, [itemsContentSig, ownGroup]);
22862
- const zonesRef = React81__default.useRef(/* @__PURE__ */ new Map());
22863
- const registerZone = React81__default.useCallback((zoneId2, meta2) => {
23542
+ const zonesRef = React84__default.useRef(/* @__PURE__ */ new Map());
23543
+ const registerZone = React84__default.useCallback((zoneId2, meta2) => {
22864
23544
  zonesRef.current.set(zoneId2, meta2);
22865
23545
  }, []);
22866
- const unregisterZone = React81__default.useCallback((zoneId2) => {
23546
+ const unregisterZone = React84__default.useCallback((zoneId2) => {
22867
23547
  zonesRef.current.delete(zoneId2);
22868
23548
  }, []);
22869
- const [activeDrag, setActiveDrag] = React81__default.useState(null);
22870
- const [overZoneGroup, setOverZoneGroup] = React81__default.useState(null);
22871
- const meta = React81__default.useMemo(
23549
+ const [activeDrag, setActiveDrag] = React84__default.useState(null);
23550
+ const [overZoneGroup, setOverZoneGroup] = React84__default.useState(null);
23551
+ const meta = React84__default.useMemo(
22872
23552
  () => ({ group: ownGroup, dropEvent, reorderEvent, positionEvent, itemIds, rawItems: items, idField: dndItemIdField }),
22873
23553
  [ownGroup, dropEvent, reorderEvent, positionEvent, itemIds, items, dndItemIdField]
22874
23554
  );
22875
- React81__default.useEffect(() => {
23555
+ React84__default.useEffect(() => {
22876
23556
  const target = isRoot ? null : parentRoot;
22877
23557
  if (!target) {
22878
23558
  zonesRef.current.set(zoneId, meta);
@@ -22891,7 +23571,7 @@ function useDataDnd(args) {
22891
23571
  }, [parentRoot, isRoot, zoneId, meta]);
22892
23572
  const sensors = useAlmadarDndSensors(true);
22893
23573
  const collisionDetection = almadarDndCollisionDetection;
22894
- const findZoneByItem = React81__default.useCallback(
23574
+ const findZoneByItem = React84__default.useCallback(
22895
23575
  (id) => {
22896
23576
  for (const z of zonesRef.current.values()) {
22897
23577
  if (z.itemIds.includes(id)) return z;
@@ -22900,7 +23580,7 @@ function useDataDnd(args) {
22900
23580
  },
22901
23581
  []
22902
23582
  );
22903
- React81__default.useCallback(
23583
+ React84__default.useCallback(
22904
23584
  (group) => {
22905
23585
  for (const z of zonesRef.current.values()) {
22906
23586
  if (z.group === group) return z;
@@ -22909,7 +23589,7 @@ function useDataDnd(args) {
22909
23589
  },
22910
23590
  []
22911
23591
  );
22912
- const handleDragEnd = React81__default.useCallback(
23592
+ const handleDragEnd = React84__default.useCallback(
22913
23593
  (event) => {
22914
23594
  const { active, over } = event;
22915
23595
  const activeIdStr = String(active.id);
@@ -23000,8 +23680,8 @@ function useDataDnd(args) {
23000
23680
  },
23001
23681
  [eventBus]
23002
23682
  );
23003
- const sortableData = React81__default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
23004
- const SortableItem = React81__default.useCallback(
23683
+ const sortableData = React84__default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
23684
+ const SortableItem = React84__default.useCallback(
23005
23685
  ({ id, children }) => {
23006
23686
  const {
23007
23687
  attributes,
@@ -23041,7 +23721,7 @@ function useDataDnd(args) {
23041
23721
  id: droppableId,
23042
23722
  data: sortableData
23043
23723
  });
23044
- const ctx = React81__default.useContext(RootCtx);
23724
+ const ctx = React84__default.useContext(RootCtx);
23045
23725
  const activeDrag2 = ctx?.activeDrag ?? null;
23046
23726
  const overZoneGroup2 = ctx?.overZoneGroup ?? null;
23047
23727
  const isThisZoneOver = overZoneGroup2 === ownGroup;
@@ -23056,7 +23736,7 @@ function useDataDnd(args) {
23056
23736
  showForeignPlaceholder,
23057
23737
  ctxAvailable: ctx != null
23058
23738
  });
23059
- React81__default.useEffect(() => {
23739
+ React84__default.useEffect(() => {
23060
23740
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
23061
23741
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
23062
23742
  return /* @__PURE__ */ jsx(
@@ -23070,11 +23750,11 @@ function useDataDnd(args) {
23070
23750
  }
23071
23751
  );
23072
23752
  };
23073
- const rootContextValue = React81__default.useMemo(
23753
+ const rootContextValue = React84__default.useMemo(
23074
23754
  () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
23075
23755
  [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
23076
23756
  );
23077
- const handleDragStart = React81__default.useCallback((event) => {
23757
+ const handleDragStart = React84__default.useCallback((event) => {
23078
23758
  const sourceZone = findZoneByItem(event.active.id);
23079
23759
  const rect = event.active.rect.current.initial;
23080
23760
  const height = rect?.height && rect.height > 0 ? rect.height : 64;
@@ -23093,7 +23773,7 @@ function useDataDnd(args) {
23093
23773
  isRoot
23094
23774
  });
23095
23775
  }, [findZoneByItem, isRoot, zoneId]);
23096
- const handleDragOver = React81__default.useCallback((event) => {
23776
+ const handleDragOver = React84__default.useCallback((event) => {
23097
23777
  const { active, over } = event;
23098
23778
  const overData = over?.data?.current;
23099
23779
  const overGroup = overData?.dndGroup ?? null;
@@ -23163,7 +23843,7 @@ function useDataDnd(args) {
23163
23843
  return next;
23164
23844
  });
23165
23845
  }, []);
23166
- const handleDragCancel = React81__default.useCallback((event) => {
23846
+ const handleDragCancel = React84__default.useCallback((event) => {
23167
23847
  setActiveDrag(null);
23168
23848
  setOverZoneGroup(null);
23169
23849
  dndLog.warn("dragCancel", {
@@ -23171,12 +23851,12 @@ function useDataDnd(args) {
23171
23851
  reason: "dnd-kit cancelled the drag (escape key, pointer interrupted, or external)"
23172
23852
  });
23173
23853
  }, []);
23174
- const handleDragEndWithCleanup = React81__default.useCallback((event) => {
23854
+ const handleDragEndWithCleanup = React84__default.useCallback((event) => {
23175
23855
  handleDragEnd(event);
23176
23856
  setActiveDrag(null);
23177
23857
  setOverZoneGroup(null);
23178
23858
  }, [handleDragEnd]);
23179
- const wrapContainer = React81__default.useCallback(
23859
+ const wrapContainer = React84__default.useCallback(
23180
23860
  (children) => {
23181
23861
  if (!enabled) return children;
23182
23862
  const strategy = layout === "grid" ? rectSortingStrategy : verticalListSortingStrategy;
@@ -23230,7 +23910,7 @@ var init_useDataDnd = __esm({
23230
23910
  init_useAlmadarDndCollision();
23231
23911
  init_Box();
23232
23912
  dndLog = createLogger("almadar:ui:dnd");
23233
- RootCtx = React81__default.createContext(null);
23913
+ RootCtx = React84__default.createContext(null);
23234
23914
  }
23235
23915
  });
23236
23916
  function renderIconInput(icon, props) {
@@ -23756,7 +24436,7 @@ function DataList({
23756
24436
  }) {
23757
24437
  const eventBus = useEventBus();
23758
24438
  const { t } = useTranslate();
23759
- const [visibleCount, setVisibleCount] = React81__default.useState(pageSize || Infinity);
24439
+ const [visibleCount, setVisibleCount] = React84__default.useState(pageSize || Infinity);
23760
24440
  const fieldDefs = fields ?? columns ?? [];
23761
24441
  const allDataRaw = Array.isArray(entity) ? entity : entity ? [entity] : [];
23762
24442
  const dnd = useDataDnd({
@@ -23775,7 +24455,7 @@ function DataList({
23775
24455
  const data = pageSize > 0 ? allData.slice(0, visibleCount) : allData;
23776
24456
  const hasMoreLocal = pageSize > 0 && visibleCount < allData.length;
23777
24457
  const hasRenderProp = typeof children === "function";
23778
- React81__default.useEffect(() => {
24458
+ React84__default.useEffect(() => {
23779
24459
  const renderItemTypeOf = typeof schemaRenderItem;
23780
24460
  const childrenTypeOf = typeof children;
23781
24461
  if (data.length > 0 && !hasRenderProp) {
@@ -23880,7 +24560,7 @@ function DataList({
23880
24560
  const items2 = data.map((item) => item);
23881
24561
  const groups2 = groupBy ? groupData(items2, groupBy) : [{ label: "", items: items2 }];
23882
24562
  const contentField = titleField?.name ?? fieldDefs[0]?.name ?? "";
23883
- return /* @__PURE__ */ jsx(VStack, { gap: "sm", className: cn("py-2", className), children: groups2.map((group, gi) => /* @__PURE__ */ jsxs(React81__default.Fragment, { children: [
24563
+ return /* @__PURE__ */ jsx(VStack, { gap: "sm", className: cn("py-2", className), children: groups2.map((group, gi) => /* @__PURE__ */ jsxs(React84__default.Fragment, { children: [
23884
24564
  group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: "my-2" }),
23885
24565
  group.items.map((itemData, index) => {
23886
24566
  const id = itemData.id || `${gi}-${index}`;
@@ -24021,7 +24701,7 @@ function DataList({
24021
24701
  className
24022
24702
  ),
24023
24703
  children: [
24024
- groups.map((group, gi) => /* @__PURE__ */ jsxs(React81__default.Fragment, { children: [
24704
+ groups.map((group, gi) => /* @__PURE__ */ jsxs(React84__default.Fragment, { children: [
24025
24705
  group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: gi > 0 ? "mt-4" : "mt-0" }),
24026
24706
  group.items.map(
24027
24707
  (itemData, index) => renderItem(itemData, index, gi === groups.length - 1 && index === group.items.length - 1)
@@ -24937,7 +25617,7 @@ var init_Grid = __esm({
24937
25617
  as: Component2 = "div"
24938
25618
  }) => {
24939
25619
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
24940
- return React81__default.createElement(
25620
+ return React84__default.createElement(
24941
25621
  Component2,
24942
25622
  {
24943
25623
  className: cn(
@@ -25125,8 +25805,8 @@ var init_Popover = __esm({
25125
25805
  onMouseEnter: handleOpen,
25126
25806
  onMouseLeave: handleClose
25127
25807
  };
25128
- const childElement = React81__default.isValidElement(children) ? children : /* @__PURE__ */ jsx("span", { children });
25129
- const triggerElement = React81__default.cloneElement(
25808
+ const childElement = React84__default.isValidElement(children) ? children : /* @__PURE__ */ jsx("span", { children });
25809
+ const triggerElement = React84__default.cloneElement(
25130
25810
  childElement,
25131
25811
  {
25132
25812
  ref: triggerRef,
@@ -25182,9 +25862,9 @@ function debug(...args) {
25182
25862
  const [first, ...rest] = args;
25183
25863
  const message = typeof first === "string" ? first : "<debug>";
25184
25864
  if (rest.length === 0 && typeof first === "string") {
25185
- log9.debug(message);
25865
+ log8.debug(message);
25186
25866
  } else {
25187
- log9.debug(message, { args: rest.length > 0 ? formatArgs(rest) : formatArgs([first]) });
25867
+ log8.debug(message, { args: rest.length > 0 ? formatArgs(rest) : formatArgs([first]) });
25188
25868
  }
25189
25869
  }
25190
25870
  function debugGroup(label) {
@@ -25212,11 +25892,11 @@ function toLogMetaValue(v) {
25212
25892
  }
25213
25893
  return String(v);
25214
25894
  }
25215
- var NAMESPACE, log9;
25895
+ var NAMESPACE, log8;
25216
25896
  var init_debug = __esm({
25217
25897
  "lib/debug.ts"() {
25218
25898
  NAMESPACE = "almadar:ui:debug";
25219
- log9 = createLogger(NAMESPACE);
25899
+ log8 = createLogger(NAMESPACE);
25220
25900
  createLogger("almadar:ui:debug:input");
25221
25901
  createLogger("almadar:ui:debug:collision");
25222
25902
  createLogger("almadar:ui:debug:physics");
@@ -25716,8 +26396,8 @@ var init_Tooltip = __esm({
25716
26396
  if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
25717
26397
  };
25718
26398
  }, []);
25719
- const triggerElement = React81__default.isValidElement(children) ? children : /* @__PURE__ */ jsx("span", { children });
25720
- const trigger = React81__default.cloneElement(triggerElement, {
26399
+ const triggerElement = React84__default.isValidElement(children) ? children : /* @__PURE__ */ jsx("span", { children });
26400
+ const trigger = React84__default.cloneElement(triggerElement, {
25721
26401
  ref: triggerRef,
25722
26402
  onMouseEnter: handleMouseEnter,
25723
26403
  onMouseLeave: handleMouseLeave,
@@ -25803,7 +26483,7 @@ var init_WizardProgress = __esm({
25803
26483
  children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: normalizedSteps.map((step, index) => {
25804
26484
  const isActive = index === currentStep;
25805
26485
  const isCompleted = index < currentStep;
25806
- return /* @__PURE__ */ jsxs(React81__default.Fragment, { children: [
26486
+ return /* @__PURE__ */ jsxs(React84__default.Fragment, { children: [
25807
26487
  /* @__PURE__ */ jsx(
25808
26488
  "button",
25809
26489
  {
@@ -26764,7 +27444,7 @@ var init_ProgressDots = __esm({
26764
27444
  }
26765
27445
  });
26766
27446
  function StatBadge({
26767
- assetUrl = DEFAULT_ASSET_URL12,
27447
+ assetUrl,
26768
27448
  label,
26769
27449
  value = 0,
26770
27450
  max,
@@ -26831,7 +27511,7 @@ function StatBadge({
26831
27511
  }
26832
27512
  );
26833
27513
  }
26834
- var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
27514
+ var sizeMap17, variantMap2;
26835
27515
  var init_StatBadge = __esm({
26836
27516
  "components/game/molecules/StatBadge.tsx"() {
26837
27517
  init_cn();
@@ -26850,7 +27530,6 @@ var init_StatBadge = __esm({
26850
27530
  warning: "bg-warning/15 border-warning/40 text-foreground",
26851
27531
  danger: "bg-error/15 border-error/40 text-foreground"
26852
27532
  };
26853
- DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
26854
27533
  StatBadge.displayName = "StatBadge";
26855
27534
  }
26856
27535
  });
@@ -26867,7 +27546,7 @@ function InventoryGrid({
26867
27546
  const eventBus = useEventBus();
26868
27547
  const slotCount = totalSlots ?? items.length;
26869
27548
  const emptySlotCount = Math.max(0, slotCount - items.length);
26870
- const handleSelect = React81.useCallback(
27549
+ const handleSelect = React84.useCallback(
26871
27550
  (id) => {
26872
27551
  onSelect?.(id);
26873
27552
  if (selectEvent) {
@@ -27098,35 +27777,37 @@ function GameCanvas2D({
27098
27777
  drawEvent,
27099
27778
  fps = 60,
27100
27779
  backgroundImage = "",
27101
- assetBaseUrl = "https://almadar-kflow-assets.web.app/shared/",
27780
+ assetBaseUrl,
27102
27781
  className
27103
27782
  }) {
27104
- const canvasRef = React81.useRef(null);
27105
- const rafRef = React81.useRef(0);
27106
- const frameRef = React81.useRef(0);
27107
- const lastTimeRef = React81.useRef(0);
27108
- const imageCache = React81.useRef(/* @__PURE__ */ new Map());
27783
+ const canvasRef = React84.useRef(null);
27784
+ const rafRef = React84.useRef(0);
27785
+ const frameRef = React84.useRef(0);
27786
+ const lastTimeRef = React84.useRef(0);
27787
+ const imageCache = React84.useRef(/* @__PURE__ */ new Map());
27109
27788
  const emit = useEmitEvent();
27110
- const onDrawRef = React81.useRef(onDraw);
27789
+ const onDrawRef = React84.useRef(onDraw);
27111
27790
  onDrawRef.current = onDraw;
27112
- const onTickRef = React81.useRef(onTick);
27791
+ const onTickRef = React84.useRef(onTick);
27113
27792
  onTickRef.current = onTick;
27114
- const tickEventRef = React81.useRef(tickEvent);
27793
+ const tickEventRef = React84.useRef(tickEvent);
27115
27794
  tickEventRef.current = tickEvent;
27116
- const drawEventRef = React81.useRef(drawEvent);
27795
+ const drawEventRef = React84.useRef(drawEvent);
27117
27796
  drawEventRef.current = drawEvent;
27118
- const emitRef = React81.useRef(emit);
27797
+ const emitRef = React84.useRef(emit);
27119
27798
  emitRef.current = emit;
27120
- const assetBaseUrlRef = React81.useRef(assetBaseUrl);
27799
+ const assetBaseUrlRef = React84.useRef(assetBaseUrl);
27121
27800
  assetBaseUrlRef.current = assetBaseUrl;
27122
- const backgroundImageRef = React81.useRef(backgroundImage);
27801
+ const backgroundImageRef = React84.useRef(backgroundImage);
27123
27802
  backgroundImageRef.current = backgroundImage;
27124
- const widthRef = React81.useRef(width);
27803
+ const widthRef = React84.useRef(width);
27125
27804
  widthRef.current = width;
27126
- const heightRef = React81.useRef(height);
27805
+ const heightRef = React84.useRef(height);
27127
27806
  heightRef.current = height;
27128
- const loadImage = React81.useCallback((url) => {
27129
- const fullUrl = url.startsWith("http") ? url : `${assetBaseUrlRef.current}${url}`;
27807
+ const loadImage = React84.useCallback((url) => {
27808
+ const base = assetBaseUrlRef.current;
27809
+ if (!url.startsWith("http") && !base) return null;
27810
+ const fullUrl = url.startsWith("http") ? url : `${base}${url}`;
27130
27811
  const cached = imageCache.current.get(fullUrl);
27131
27812
  if (cached?.complete && cached.naturalWidth > 0) return cached;
27132
27813
  if (!cached) {
@@ -27137,7 +27818,7 @@ function GameCanvas2D({
27137
27818
  }
27138
27819
  return null;
27139
27820
  }, []);
27140
- React81.useEffect(() => {
27821
+ React84.useEffect(() => {
27141
27822
  const canvas = canvasRef.current;
27142
27823
  if (!canvas) return;
27143
27824
  const ctx = canvas.getContext("2d");
@@ -27449,7 +28130,7 @@ function TurnPanel({
27449
28130
  className
27450
28131
  }) {
27451
28132
  const eventBus = useEventBus();
27452
- const handleAction = React81.useCallback(
28133
+ const handleAction = React84.useCallback(
27453
28134
  (event) => {
27454
28135
  if (event) {
27455
28136
  eventBus.emit(event, { turn: currentTurn, phase, activeTeam });
@@ -27507,7 +28188,7 @@ var init_TurnPanel = __esm({
27507
28188
  }
27508
28189
  });
27509
28190
  function EnemyPlate({
27510
- assetUrl = DEFAULT_ASSET_URL13,
28191
+ assetUrl,
27511
28192
  name = "Shadow Guard",
27512
28193
  health = 80,
27513
28194
  maxHealth = 100,
@@ -27591,7 +28272,7 @@ function EnemyPlate({
27591
28272
  }
27592
28273
  );
27593
28274
  }
27594
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
28275
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
27595
28276
  var init_EnemyPlate = __esm({
27596
28277
  "components/game/molecules/EnemyPlate.tsx"() {
27597
28278
  "use client";
@@ -27608,7 +28289,6 @@ var init_EnemyPlate = __esm({
27608
28289
  DEFAULT_ENEMY_EFFECTS = [
27609
28290
  { icon: "flame", label: "Burn", variant: "debuff" }
27610
28291
  ];
27611
- DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
27612
28292
  EnemyPlate.displayName = "EnemyPlate";
27613
28293
  }
27614
28294
  });
@@ -27618,7 +28298,7 @@ function UnitCommandBar({
27618
28298
  className
27619
28299
  }) {
27620
28300
  const eventBus = useEventBus();
27621
- const handleCommand = React81.useCallback(
28301
+ const handleCommand = React84.useCallback(
27622
28302
  (event) => {
27623
28303
  if (event) {
27624
28304
  eventBus.emit(event, { unitId: selectedUnitId });
@@ -27759,6 +28439,9 @@ function DialogueBox({
27759
28439
  completeEvent,
27760
28440
  choiceEvent,
27761
28441
  advanceEvent,
28442
+ backgroundImage,
28443
+ portraitUrl,
28444
+ portraitScale = 1,
27762
28445
  className
27763
28446
  }) {
27764
28447
  const eventBus = useEventBus();
@@ -27874,64 +28557,98 @@ function DialogueBox({
27874
28557
  }
27875
28558
  }, [isTyping, skipTypewriter, dialogue.choices, selectedChoice, onChoice, onAdvance, choiceEvent, advanceEvent, eventBus]);
27876
28559
  const enabledChoices = dialogue.choices?.filter((c) => !c.disabled) ?? [];
27877
- return /* @__PURE__ */ jsx(
27878
- "div",
27879
- {
27880
- className: cn(
27881
- "fixed left-0 right-0 z-40",
27882
- position === "top" ? "top-0" : "bottom-0",
27883
- className
27884
- ),
27885
- onClick: handleClick,
27886
- onKeyDown: handleKeyDown,
27887
- tabIndex: 0,
27888
- role: "dialog",
27889
- "aria-label": "Dialogue",
27890
- 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: [
27891
- dialogue.portrait && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 p-4 border-r border-border", children: /* @__PURE__ */ jsx(
28560
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
28561
+ backgroundImage && /* @__PURE__ */ jsx(
28562
+ "div",
28563
+ {
28564
+ className: "fixed inset-0 z-20 bg-center bg-cover bg-no-repeat",
28565
+ style: { backgroundImage: `url(${backgroundImage})` },
28566
+ "aria-hidden": "true"
28567
+ }
28568
+ ),
28569
+ portraitUrl && /* @__PURE__ */ jsx(
28570
+ "div",
28571
+ {
28572
+ className: cn(
28573
+ "fixed left-1/2 -translate-x-1/2 z-30 pointer-events-none flex items-end",
28574
+ position === "top" ? "top-0" : "bottom-0"
28575
+ ),
28576
+ style: { height: `${60 * portraitScale}vh` },
28577
+ children: /* @__PURE__ */ jsx(
27892
28578
  "img",
27893
28579
  {
27894
- src: dialogue.portrait,
28580
+ src: portraitUrl,
27895
28581
  alt: dialogue.speaker,
27896
- className: "w-24 h-24 object-contain",
27897
- style: { imageRendering: "pixelated" }
28582
+ className: "h-full w-auto object-contain drop-shadow-2xl"
27898
28583
  }
27899
- ) }),
27900
- /* @__PURE__ */ jsxs("div", { className: "flex-1 p-4", children: [
27901
- /* @__PURE__ */ jsx("div", { className: "text-warning font-bold mb-2", children: dialogue.speaker }),
27902
- /* @__PURE__ */ jsxs("div", { className: "text-[var(--color-foreground)] text-lg leading-relaxed min-h-[60px]", children: [
27903
- displayedText,
27904
- isTyping && /* @__PURE__ */ jsx("span", { className: "animate-pulse", children: "\u258C" })
27905
- ] }),
27906
- !isTyping && enabledChoices.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: enabledChoices.map((choice, index) => /* @__PURE__ */ jsxs(
27907
- "button",
28584
+ )
28585
+ }
28586
+ ),
28587
+ /* @__PURE__ */ jsx(
28588
+ "div",
28589
+ {
28590
+ className: cn(
28591
+ "fixed left-0 right-0 z-40",
28592
+ position === "top" ? "top-0" : "bottom-0",
28593
+ className
28594
+ ),
28595
+ onClick: handleClick,
28596
+ onKeyDown: handleKeyDown,
28597
+ tabIndex: 0,
28598
+ role: "dialog",
28599
+ "aria-label": "Dialogue",
28600
+ children: /* @__PURE__ */ jsx("div", { className: cn(
28601
+ "mx-4 my-4 border-2 rounded-container overflow-hidden",
28602
+ backgroundImage ? "bg-black/80 backdrop-blur-sm text-white border-white/20" : "bg-[var(--color-card)] bg-opacity-95 border-border"
28603
+ ), children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
28604
+ dialogue.portrait && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 p-4 border-r border-border", children: /* @__PURE__ */ jsx(
28605
+ "img",
27908
28606
  {
27909
- type: "button",
27910
- className: cn(
27911
- "block w-full text-left px-4 py-2 rounded-interactive transition-colors",
27912
- "hover:bg-[var(--color-surface,#374151)] focus:outline-none focus:ring-2 focus:ring-warning",
27913
- selectedChoice === index ? "bg-[var(--color-surface,#374151)] text-warning" : "bg-[var(--color-card)] text-[var(--color-foreground)]"
27914
- ),
27915
- onClick: (e) => {
27916
- e.stopPropagation();
27917
- if (choiceEvent) eventBus.emit(`UI:${choiceEvent}`, { choice });
27918
- onChoice?.(choice);
28607
+ src: dialogue.portrait,
28608
+ alt: dialogue.speaker,
28609
+ className: "w-24 h-24 object-contain",
28610
+ style: { imageRendering: "pixelated" }
28611
+ }
28612
+ ) }),
28613
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 p-4", children: [
28614
+ /* @__PURE__ */ jsx("div", { className: "text-warning font-bold mb-2", children: dialogue.speaker }),
28615
+ /* @__PURE__ */ jsxs("div", { className: cn(
28616
+ "text-lg leading-relaxed min-h-[60px]",
28617
+ backgroundImage ? "text-white" : "text-[var(--color-foreground)]"
28618
+ ), children: [
28619
+ displayedText,
28620
+ isTyping && /* @__PURE__ */ jsx("span", { className: "animate-pulse", children: "\u258C" })
28621
+ ] }),
28622
+ !isTyping && enabledChoices.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: enabledChoices.map((choice, index) => /* @__PURE__ */ jsxs(
28623
+ "button",
28624
+ {
28625
+ type: "button",
28626
+ className: cn(
28627
+ "block w-full text-left px-4 py-2 rounded-interactive transition-colors",
28628
+ "hover:bg-[var(--color-surface,#374151)] focus:outline-none focus:ring-2 focus:ring-warning",
28629
+ 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)]"
28630
+ ),
28631
+ onClick: (e) => {
28632
+ e.stopPropagation();
28633
+ if (choiceEvent) eventBus.emit(`UI:${choiceEvent}`, { choice });
28634
+ onChoice?.(choice);
28635
+ },
28636
+ children: [
28637
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground mr-2", children: [
28638
+ index + 1,
28639
+ "."
28640
+ ] }),
28641
+ choice.text
28642
+ ]
27919
28643
  },
27920
- children: [
27921
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground mr-2", children: [
27922
- index + 1,
27923
- "."
27924
- ] }),
27925
- choice.text
27926
- ]
27927
- },
27928
- index
27929
- )) }),
27930
- !isTyping && !dialogue.choices?.length && /* @__PURE__ */ jsx("div", { className: "mt-4 text-muted-foreground text-sm animate-pulse", children: "Press SPACE or click to continue..." })
27931
- ] })
27932
- ] }) })
27933
- }
27934
- );
28644
+ index
28645
+ )) }),
28646
+ !isTyping && !dialogue.choices?.length && /* @__PURE__ */ jsx("div", { className: "mt-4 text-muted-foreground text-sm animate-pulse", children: "Press SPACE or click to continue..." })
28647
+ ] })
28648
+ ] }) })
28649
+ }
28650
+ )
28651
+ ] });
27935
28652
  }
27936
28653
  var DEFAULT_DIALOGUE;
27937
28654
  var init_DialogueBox = __esm({
@@ -28132,7 +28849,7 @@ function GameMenu({
28132
28849
  } catch {
28133
28850
  }
28134
28851
  const eventBus = eventBusProp || eventBusFromHook;
28135
- const handleOptionClick = React81.useCallback(
28852
+ const handleOptionClick = React84.useCallback(
28136
28853
  (option) => {
28137
28854
  if (option.event && eventBus) {
28138
28855
  eventBus.emit(`UI:${option.event}`, { option });
@@ -28251,7 +28968,7 @@ function GameOverScreen({
28251
28968
  } catch {
28252
28969
  }
28253
28970
  const eventBus = eventBusProp || eventBusFromHook;
28254
- const handleActionClick = React81.useCallback(
28971
+ const handleActionClick = React84.useCallback(
28255
28972
  (action) => {
28256
28973
  if (action.event && eventBus) {
28257
28974
  eventBus.emit(`UI:${action.event}`, { action });
@@ -28458,10 +29175,10 @@ function PlatformerCanvas({
28458
29175
  canvasHeight = 400,
28459
29176
  followCamera = true,
28460
29177
  bgColor = "#5c94fc",
28461
- playerSprite = "https://almadar-kflow-assets.web.app/shared/platformer/characters/platformChar_idle.png",
29178
+ playerSprite,
28462
29179
  tileSprites,
28463
29180
  backgroundImage = "",
28464
- assetBaseUrl = "https://almadar-kflow-assets.web.app/shared/platformer/",
29181
+ assetBaseUrl,
28465
29182
  leftEvent = "MOVE_LEFT",
28466
29183
  rightEvent = "MOVE_RIGHT",
28467
29184
  jumpEvent = "JUMP",
@@ -28475,6 +29192,7 @@ function PlatformerCanvas({
28475
29192
  const imageCache = useRef(/* @__PURE__ */ new Map());
28476
29193
  const [loadedImages, setLoadedImages] = useState(/* @__PURE__ */ new Set());
28477
29194
  const loadImage = useCallback((url) => {
29195
+ if (!url.startsWith("http") && !assetBaseUrl) return null;
28478
29196
  const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
28479
29197
  const cached = imageCache.current.get(fullUrl);
28480
29198
  if (cached?.complete && cached.naturalWidth > 0) {
@@ -29173,13 +29891,13 @@ var init_MapView = __esm({
29173
29891
  shadowSize: [41, 41]
29174
29892
  });
29175
29893
  L.Marker.prototype.options.icon = defaultIcon;
29176
- const { useEffect: useEffect82, useRef: useRef75, useCallback: useCallback119, useState: useState113 } = React81__default;
29894
+ const { useEffect: useEffect83, useRef: useRef78, useCallback: useCallback124, useState: useState114 } = React84__default;
29177
29895
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29178
29896
  const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29179
29897
  function MapUpdater({ centerLat, centerLng, zoom }) {
29180
29898
  const map = useMap();
29181
- const prevRef = useRef75({ centerLat, centerLng, zoom });
29182
- useEffect82(() => {
29899
+ const prevRef = useRef78({ centerLat, centerLng, zoom });
29900
+ useEffect83(() => {
29183
29901
  const prev = prevRef.current;
29184
29902
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
29185
29903
  map.setView([centerLat, centerLng], zoom);
@@ -29190,7 +29908,7 @@ var init_MapView = __esm({
29190
29908
  }
29191
29909
  function MapClickHandler({ onMapClick }) {
29192
29910
  const map = useMap();
29193
- useEffect82(() => {
29911
+ useEffect83(() => {
29194
29912
  if (!onMapClick) return;
29195
29913
  const handler = (e) => {
29196
29914
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -29218,8 +29936,8 @@ var init_MapView = __esm({
29218
29936
  showAttribution = true
29219
29937
  }) {
29220
29938
  const eventBus = useEventBus4();
29221
- const [clickedPosition, setClickedPosition] = useState113(null);
29222
- const handleMapClick = useCallback119((lat, lng) => {
29939
+ const [clickedPosition, setClickedPosition] = useState114(null);
29940
+ const handleMapClick = useCallback124((lat, lng) => {
29223
29941
  if (showClickedPin) {
29224
29942
  setClickedPosition({ lat, lng });
29225
29943
  }
@@ -29228,7 +29946,7 @@ var init_MapView = __esm({
29228
29946
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29229
29947
  }
29230
29948
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29231
- const handleMarkerClick = useCallback119((marker) => {
29949
+ const handleMarkerClick = useCallback124((marker) => {
29232
29950
  onMarkerClick?.(marker);
29233
29951
  if (markerClickEvent) {
29234
29952
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -30079,8 +30797,8 @@ function TableView({
30079
30797
  }) {
30080
30798
  const eventBus = useEventBus();
30081
30799
  const { t } = useTranslate();
30082
- const [visibleCount, setVisibleCount] = React81__default.useState(pageSize > 0 ? pageSize : Infinity);
30083
- const [localSelected, setLocalSelected] = React81__default.useState(/* @__PURE__ */ new Set());
30800
+ const [visibleCount, setVisibleCount] = React84__default.useState(pageSize > 0 ? pageSize : Infinity);
30801
+ const [localSelected, setLocalSelected] = React84__default.useState(/* @__PURE__ */ new Set());
30084
30802
  const colDefs = columns ?? fields ?? [];
30085
30803
  const allDataRaw = Array.isArray(entity) ? entity : entity ? [entity] : [];
30086
30804
  const dnd = useDataDnd({
@@ -30275,12 +30993,12 @@ function TableView({
30275
30993
  ]
30276
30994
  }
30277
30995
  );
30278
- return dnd.isZone ? /* @__PURE__ */ jsx(dnd.SortableItem, { id: row[idField] ?? id, children: rowInner }, id) : /* @__PURE__ */ jsx(React81__default.Fragment, { children: rowInner }, id);
30996
+ return dnd.isZone ? /* @__PURE__ */ jsx(dnd.SortableItem, { id: row[idField] ?? id, children: rowInner }, id) : /* @__PURE__ */ jsx(React84__default.Fragment, { children: rowInner }, id);
30279
30997
  };
30280
30998
  const items = data.map((row) => row);
30281
30999
  const groups = groupBy ? groupData2(items, groupBy) : [{ label: "", items }];
30282
31000
  let runningIndex = 0;
30283
- const body = /* @__PURE__ */ jsx(Box, { role: "rowgroup", children: groups.map((group, gi) => /* @__PURE__ */ jsxs(React81__default.Fragment, { children: [
31001
+ const body = /* @__PURE__ */ jsx(Box, { role: "rowgroup", children: groups.map((group, gi) => /* @__PURE__ */ jsxs(React84__default.Fragment, { children: [
30284
31002
  group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: gi > 0 ? "mt-3" : "mt-0" }),
30285
31003
  group.items.map((row) => renderRow(row, runningIndex++))
30286
31004
  ] }, gi)) });
@@ -31637,7 +32355,7 @@ var init_StepFlow = __esm({
31637
32355
  className
31638
32356
  }) => {
31639
32357
  if (orientation === "vertical") {
31640
- return /* @__PURE__ */ jsx(VStack, { gap: "none", className: cn("w-full", className), children: steps.map((step, index) => /* @__PURE__ */ jsx(React81__default.Fragment, { children: /* @__PURE__ */ jsxs(HStack, { gap: "md", align: "start", className: "w-full", children: [
32358
+ return /* @__PURE__ */ jsx(VStack, { gap: "none", className: cn("w-full", className), children: steps.map((step, index) => /* @__PURE__ */ jsx(React84__default.Fragment, { children: /* @__PURE__ */ jsxs(HStack, { gap: "md", align: "start", className: "w-full", children: [
31641
32359
  /* @__PURE__ */ jsxs(VStack, { gap: "none", align: "center", children: [
31642
32360
  /* @__PURE__ */ jsx(StepCircle, { step, index }),
31643
32361
  showConnectors && index < steps.length - 1 && /* @__PURE__ */ jsx(Box, { className: "w-px h-8 bg-border" })
@@ -31648,7 +32366,7 @@ var init_StepFlow = __esm({
31648
32366
  ] })
31649
32367
  ] }) }, index)) });
31650
32368
  }
31651
- 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(React81__default.Fragment, { children: [
32369
+ 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(React84__default.Fragment, { children: [
31652
32370
  /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", className: "flex-1 w-full md:w-auto", children: [
31653
32371
  /* @__PURE__ */ jsx(StepCircle, { step, index }),
31654
32372
  /* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-center", children: step.title }),
@@ -32633,7 +33351,7 @@ var init_LikertScale = __esm({
32633
33351
  md: "text-base",
32634
33352
  lg: "text-lg"
32635
33353
  };
32636
- LikertScale = React81__default.forwardRef(
33354
+ LikertScale = React84__default.forwardRef(
32637
33355
  ({
32638
33356
  question,
32639
33357
  options = DEFAULT_LIKERT_OPTIONS,
@@ -32645,7 +33363,7 @@ var init_LikertScale = __esm({
32645
33363
  variant = "radios",
32646
33364
  className
32647
33365
  }, ref) => {
32648
- const groupId = React81__default.useId();
33366
+ const groupId = React84__default.useId();
32649
33367
  const eventBus = useEventBus();
32650
33368
  const handleSelect = useCallback(
32651
33369
  (next) => {
@@ -34927,7 +35645,7 @@ var init_DocBreadcrumb = __esm({
34927
35645
  "aria-label": t("aria.breadcrumb"),
34928
35646
  children: /* @__PURE__ */ jsx(HStack, { gap: "xs", align: "center", wrap: true, children: items.map((item, idx) => {
34929
35647
  const isLast = idx === items.length - 1;
34930
- return /* @__PURE__ */ jsxs(React81__default.Fragment, { children: [
35648
+ return /* @__PURE__ */ jsxs(React84__default.Fragment, { children: [
34931
35649
  idx > 0 && /* @__PURE__ */ jsx(
34932
35650
  Icon,
34933
35651
  {
@@ -35796,7 +36514,7 @@ var init_MiniStateMachine = __esm({
35796
36514
  const x = 2 + i * (NODE_W + GAP + ARROW_W + GAP);
35797
36515
  const tc = transitionCounts[s.name] ?? 0;
35798
36516
  const role = getStateRole(s.name, s.isInitial, s.isTerminal, tc, maxTC);
35799
- return /* @__PURE__ */ jsxs(React81__default.Fragment, { children: [
36517
+ return /* @__PURE__ */ jsxs(React84__default.Fragment, { children: [
35800
36518
  /* @__PURE__ */ jsx(
35801
36519
  AvlState,
35802
36520
  {
@@ -36000,7 +36718,7 @@ var init_PageHeader = __esm({
36000
36718
  info: "bg-info/10 text-info"
36001
36719
  };
36002
36720
  return /* @__PURE__ */ jsxs(Box, { className: cn("mb-6", className), children: [
36003
- 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(React81__default.Fragment, { children: [
36721
+ 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(React84__default.Fragment, { children: [
36004
36722
  idx > 0 && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: "/" }),
36005
36723
  crumb.href ? /* @__PURE__ */ jsx(
36006
36724
  "a",
@@ -36682,8 +37400,8 @@ var init_WizardContainer = __esm({
36682
37400
  return void 0;
36683
37401
  if (typeof controlledStep === "number") return controlledStep;
36684
37402
  if (typeof controlledStep === "string") return parseInt(controlledStep, 10);
36685
- const num2 = Number(controlledStep);
36686
- return isNaN(num2) ? void 0 : num2;
37403
+ const num3 = Number(controlledStep);
37404
+ return isNaN(num3) ? void 0 : num3;
36687
37405
  })();
36688
37406
  const currentStep = normalizedControlledStep !== void 0 ? normalizedControlledStep : internalStep;
36689
37407
  const totalSteps = steps.length;
@@ -36729,7 +37447,7 @@ var init_WizardContainer = __esm({
36729
37447
  const isCompleted = index < currentStep;
36730
37448
  const stepKey = step.id ?? step.tabId ?? `step-${index}`;
36731
37449
  const stepTitle = step.title ?? step.name ?? `Step ${index + 1}`;
36732
- return /* @__PURE__ */ jsxs(React81__default.Fragment, { children: [
37450
+ return /* @__PURE__ */ jsxs(React84__default.Fragment, { children: [
36733
37451
  /* @__PURE__ */ jsx(
36734
37452
  Button,
36735
37453
  {
@@ -37656,10 +38374,13 @@ var init_GraphCanvas = __esm({
37656
38374
  draggable = true,
37657
38375
  actions,
37658
38376
  onNodeClick,
38377
+ onNodeDoubleClick,
37659
38378
  nodeClickEvent,
37660
38379
  selectedNodeId,
37661
38380
  repulsion = 800,
37662
38381
  linkDistance = 100,
38382
+ nodeSpacing = 28,
38383
+ linkOpacity = 0.18,
37663
38384
  layout = "force",
37664
38385
  entity,
37665
38386
  isLoading = false,
@@ -37675,6 +38396,19 @@ var init_GraphCanvas = __esm({
37675
38396
  const [hoveredNode, setHoveredNode] = useState(null);
37676
38397
  const nodesRef = useRef([]);
37677
38398
  const [, forceUpdate] = useState(0);
38399
+ const [logicalW, setLogicalW] = useState(800);
38400
+ useEffect(() => {
38401
+ const canvas = canvasRef.current;
38402
+ if (!canvas || typeof ResizeObserver === "undefined") return;
38403
+ const measure = () => {
38404
+ const width = Math.round(canvas.clientWidth);
38405
+ if (width > 0) setLogicalW(width);
38406
+ };
38407
+ measure();
38408
+ const ro = new ResizeObserver(measure);
38409
+ ro.observe(canvas);
38410
+ return () => ro.disconnect();
38411
+ }, []);
37678
38412
  const interactionRef = useRef({
37679
38413
  mode: "none",
37680
38414
  dragNodeId: null,
@@ -37687,10 +38421,8 @@ var init_GraphCanvas = __esm({
37687
38421
  const canvas = canvasRef.current;
37688
38422
  if (!canvas) return null;
37689
38423
  const rect = canvas.getBoundingClientRect();
37690
- const scaleX = rect.width > 0 ? canvas.width / rect.width : 1;
37691
- const scaleY = rect.height > 0 ? canvas.height / rect.height : 1;
37692
- const screenX = (e.clientX - rect.left) * scaleX;
37693
- const screenY = (e.clientY - rect.top) * scaleY;
38424
+ const screenX = e.clientX - rect.left;
38425
+ const screenY = e.clientY - rect.top;
37694
38426
  return {
37695
38427
  screenX,
37696
38428
  screenY,
@@ -37730,8 +38462,8 @@ var init_GraphCanvas = __esm({
37730
38462
  useEffect(() => {
37731
38463
  const canvas = canvasRef.current;
37732
38464
  if (!canvas || propNodes.length === 0) return;
37733
- const w = canvas.width;
37734
- const h = canvas.height;
38465
+ const w = logicalW;
38466
+ const h = height;
37735
38467
  const simNodes = propNodes.map((n, idx) => {
37736
38468
  let x = n.x ?? 0;
37737
38469
  let y = n.y ?? 0;
@@ -37808,6 +38540,25 @@ var init_GraphCanvas = __esm({
37808
38540
  node.x = Math.max(30, Math.min(w - 30, node.x));
37809
38541
  node.y = Math.max(30, Math.min(h - 30, node.y));
37810
38542
  }
38543
+ for (let i = 0; i < nodes.length; i++) {
38544
+ for (let j = i + 1; j < nodes.length; j++) {
38545
+ const a = nodes[i];
38546
+ const b = nodes[j];
38547
+ const dx = b.x - a.x;
38548
+ const dy = b.y - a.y;
38549
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
38550
+ const minDist = (a.size || 8) + (b.size || 8) + nodeSpacing;
38551
+ if (dist < minDist) {
38552
+ const push2 = (minDist - dist) / 2;
38553
+ const ux = dx / dist;
38554
+ const uy = dy / dist;
38555
+ a.x = Math.max(30, Math.min(w - 30, a.x - ux * push2));
38556
+ a.y = Math.max(30, Math.min(h - 30, a.y - uy * push2));
38557
+ b.x = Math.max(30, Math.min(w - 30, b.x + ux * push2));
38558
+ b.y = Math.max(30, Math.min(h - 30, b.y + uy * push2));
38559
+ }
38560
+ }
38561
+ }
37811
38562
  iterations++;
37812
38563
  forceUpdate((n) => n + 1);
37813
38564
  if (iterations < maxIterations) {
@@ -37821,29 +38572,41 @@ var init_GraphCanvas = __esm({
37821
38572
  return () => {
37822
38573
  cancelAnimationFrame(animRef.current);
37823
38574
  };
37824
- }, [propNodes, propEdges, layout, repulsion, linkDistance]);
38575
+ }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, logicalW, height]);
37825
38576
  useEffect(() => {
37826
38577
  const canvas = canvasRef.current;
37827
38578
  if (!canvas) return;
37828
38579
  const ctx = canvas.getContext("2d");
37829
38580
  if (!ctx) return;
37830
- const w = canvas.width;
37831
- const h = canvas.height;
38581
+ const w = logicalW;
38582
+ const h = height;
37832
38583
  const nodes = nodesRef.current;
37833
38584
  const accentColor = resolveColor2("var(--color-accent)", canvas);
38585
+ const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
38586
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
37834
38587
  ctx.clearRect(0, 0, w, h);
37835
38588
  ctx.save();
37836
38589
  ctx.translate(offset.x, offset.y);
37837
38590
  ctx.scale(zoom, zoom);
38591
+ const neighbors = /* @__PURE__ */ new Set();
38592
+ if (hoveredNode) {
38593
+ neighbors.add(hoveredNode);
38594
+ for (const edge of propEdges) {
38595
+ if (edge.source === hoveredNode) neighbors.add(String(edge.target));
38596
+ else if (edge.target === hoveredNode) neighbors.add(String(edge.source));
38597
+ }
38598
+ }
37838
38599
  for (const edge of propEdges) {
37839
38600
  const source = nodes.find((n) => n.id === edge.source);
37840
38601
  const target = nodes.find((n) => n.id === edge.target);
37841
38602
  if (!source || !target) continue;
38603
+ const incident = !!hoveredNode && (edge.source === hoveredNode || edge.target === hoveredNode);
38604
+ ctx.globalAlpha = hoveredNode ? incident ? 1 : 0.05 : linkOpacity;
37842
38605
  ctx.beginPath();
37843
38606
  ctx.moveTo(source.x, source.y);
37844
38607
  ctx.lineTo(target.x, target.y);
37845
- ctx.strokeStyle = edge.color || "#88888866";
37846
- ctx.lineWidth = edge.weight ? Math.max(1, edge.weight) : 1;
38608
+ ctx.strokeStyle = incident ? accentColor : edge.color || "#888888";
38609
+ ctx.lineWidth = incident ? 2 : edge.weight ? Math.max(1, edge.weight) : 1;
37847
38610
  ctx.stroke();
37848
38611
  if (edge.label && showLabels) {
37849
38612
  const mx = (source.x + target.x) / 2;
@@ -37854,11 +38617,13 @@ var init_GraphCanvas = __esm({
37854
38617
  ctx.fillText(edge.label, mx, my - 4);
37855
38618
  }
37856
38619
  }
38620
+ ctx.globalAlpha = 1;
37857
38621
  for (const node of nodes) {
37858
38622
  const size = node.size || 8;
37859
38623
  const color = resolveColor2(node.color || getGroupColor(node.group, groups), canvas);
37860
38624
  const isHovered = hoveredNode === node.id;
37861
38625
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
38626
+ ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
37862
38627
  const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
37863
38628
  ctx.beginPath();
37864
38629
  ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
@@ -37978,6 +38743,15 @@ var init_GraphCanvas = __esm({
37978
38743
  interactionRef.current.dragNodeId = null;
37979
38744
  setHoveredNode(null);
37980
38745
  }, []);
38746
+ const handleDoubleClick = useCallback(
38747
+ (e) => {
38748
+ const coords = toCoords(e);
38749
+ if (!coords) return;
38750
+ const node = nodeAt(coords.graphX, coords.graphY);
38751
+ if (node) onNodeDoubleClick?.(node);
38752
+ },
38753
+ [toCoords, nodeAt, onNodeDoubleClick]
38754
+ );
37981
38755
  if (isLoading) {
37982
38756
  return /* @__PURE__ */ jsx(LoadingState, { message: t("common.loading"), className });
37983
38757
  }
@@ -38035,15 +38809,16 @@ var init_GraphCanvas = __esm({
38035
38809
  "canvas",
38036
38810
  {
38037
38811
  ref: canvasRef,
38038
- width: 800,
38039
- height,
38812
+ width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
38813
+ height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
38040
38814
  className: "w-full cursor-grab active:cursor-grabbing",
38041
38815
  style: { height },
38042
38816
  onWheel: handleWheel,
38043
38817
  onMouseDown: handleMouseDown,
38044
38818
  onMouseMove: handleMouseMove,
38045
38819
  onMouseUp: handleMouseUp,
38046
- onMouseLeave: handleMouseLeave
38820
+ onMouseLeave: handleMouseLeave,
38821
+ onDoubleClick: handleDoubleClick
38047
38822
  }
38048
38823
  ) }),
38049
38824
  groups.length > 1 && /* @__PURE__ */ jsx(HStack, { gap: "md", className: "px-4 py-2 border-t border-border", wrap: true, children: groups.map((group, idx) => /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
@@ -39194,7 +39969,7 @@ var init_DetailPanel = __esm({
39194
39969
  }
39195
39970
  });
39196
39971
  function extractTitle(children) {
39197
- if (!React81__default.isValidElement(children)) return void 0;
39972
+ if (!React84__default.isValidElement(children)) return void 0;
39198
39973
  const props = children.props;
39199
39974
  if (typeof props.title === "string") {
39200
39975
  return props.title;
@@ -39249,7 +40024,7 @@ function LinearView({
39249
40024
  /* @__PURE__ */ jsx(HStack, { className: "flex-wrap items-center", gap: "xs", children: trait.states.map((state, i) => {
39250
40025
  const isDone = i < currentIdx;
39251
40026
  const isCurrent = i === currentIdx;
39252
- return /* @__PURE__ */ jsxs(React81__default.Fragment, { children: [
40027
+ return /* @__PURE__ */ jsxs(React84__default.Fragment, { children: [
39253
40028
  i > 0 && /* @__PURE__ */ jsx(
39254
40029
  Typography,
39255
40030
  {
@@ -40199,12 +40974,12 @@ var init_Form = __esm({
40199
40974
  const isSchemaEntity = isOrbitalEntitySchema(entity);
40200
40975
  const resolvedEntity = isSchemaEntity ? entity : void 0;
40201
40976
  const entityName = typeof entity === "string" ? entity : resolvedEntity?.name;
40202
- const normalizedInitialData = React81__default.useMemo(() => {
40977
+ const normalizedInitialData = React84__default.useMemo(() => {
40203
40978
  const entityRowAsInitial = isPlainEntityRow(entity) ? entity : void 0;
40204
40979
  const callerInitial = initialData !== null && typeof initialData === "object" && !Array.isArray(initialData) ? initialData : {};
40205
40980
  return entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
40206
40981
  }, [entity, initialData]);
40207
- const entityDerivedFields = React81__default.useMemo(() => {
40982
+ const entityDerivedFields = React84__default.useMemo(() => {
40208
40983
  if (fields && fields.length > 0) return void 0;
40209
40984
  if (!resolvedEntity) return void 0;
40210
40985
  return resolvedEntity.fields.map(
@@ -40224,16 +40999,16 @@ var init_Form = __esm({
40224
40999
  const conditionalFields = typeof conditionalFieldsRaw === "boolean" ? {} : conditionalFieldsRaw;
40225
41000
  const hiddenCalculations = typeof hiddenCalculationsRaw === "boolean" ? [] : hiddenCalculationsRaw;
40226
41001
  const violationTriggers = typeof violationTriggersRaw === "boolean" ? [] : violationTriggersRaw;
40227
- const [formData, setFormData] = React81__default.useState(
41002
+ const [formData, setFormData] = React84__default.useState(
40228
41003
  normalizedInitialData
40229
41004
  );
40230
- const [collapsedSections, setCollapsedSections] = React81__default.useState(
41005
+ const [collapsedSections, setCollapsedSections] = React84__default.useState(
40231
41006
  /* @__PURE__ */ new Set()
40232
41007
  );
40233
- const [submitError, setSubmitError] = React81__default.useState(null);
40234
- const formRef = React81__default.useRef(null);
41008
+ const [submitError, setSubmitError] = React84__default.useState(null);
41009
+ const formRef = React84__default.useRef(null);
40235
41010
  const formMode = props.mode;
40236
- const mountedRef = React81__default.useRef(false);
41011
+ const mountedRef = React84__default.useRef(false);
40237
41012
  if (!mountedRef.current) {
40238
41013
  mountedRef.current = true;
40239
41014
  debug("forms", "mount", {
@@ -40246,7 +41021,7 @@ var init_Form = __esm({
40246
41021
  });
40247
41022
  }
40248
41023
  const shouldShowCancel = showCancel ?? (fields && fields.length > 0);
40249
- const evalContext = React81__default.useMemo(
41024
+ const evalContext = React84__default.useMemo(
40250
41025
  () => ({
40251
41026
  formValues: formData,
40252
41027
  globalVariables: externalContext?.globalVariables ?? {},
@@ -40255,7 +41030,7 @@ var init_Form = __esm({
40255
41030
  }),
40256
41031
  [formData, externalContext]
40257
41032
  );
40258
- React81__default.useEffect(() => {
41033
+ React84__default.useEffect(() => {
40259
41034
  debug("forms", "initialData-sync", {
40260
41035
  mode: formMode,
40261
41036
  normalizedInitialData,
@@ -40266,7 +41041,7 @@ var init_Form = __esm({
40266
41041
  setFormData(normalizedInitialData);
40267
41042
  }
40268
41043
  }, [normalizedInitialData]);
40269
- const processCalculations = React81__default.useCallback(
41044
+ const processCalculations = React84__default.useCallback(
40270
41045
  (changedFieldId, newFormData) => {
40271
41046
  if (!hiddenCalculations.length) return;
40272
41047
  const context = {
@@ -40291,7 +41066,7 @@ var init_Form = __esm({
40291
41066
  },
40292
41067
  [hiddenCalculations, externalContext, eventBus]
40293
41068
  );
40294
- const checkViolations = React81__default.useCallback(
41069
+ const checkViolations = React84__default.useCallback(
40295
41070
  (changedFieldId, newFormData) => {
40296
41071
  if (!violationTriggers.length) return;
40297
41072
  const context = {
@@ -40329,7 +41104,7 @@ var init_Form = __esm({
40329
41104
  processCalculations(name, newFormData);
40330
41105
  checkViolations(name, newFormData);
40331
41106
  };
40332
- const isFieldVisible = React81__default.useCallback(
41107
+ const isFieldVisible = React84__default.useCallback(
40333
41108
  (fieldName) => {
40334
41109
  const condition = conditionalFields[fieldName];
40335
41110
  if (!condition) return true;
@@ -40337,7 +41112,7 @@ var init_Form = __esm({
40337
41112
  },
40338
41113
  [conditionalFields, evalContext]
40339
41114
  );
40340
- const isSectionVisible = React81__default.useCallback(
41115
+ const isSectionVisible = React84__default.useCallback(
40341
41116
  (section) => {
40342
41117
  if (!section.condition) return true;
40343
41118
  return Boolean(evaluateFormExpression(section.condition, evalContext));
@@ -40413,7 +41188,7 @@ var init_Form = __esm({
40413
41188
  eventBus.emit(`UI:${onCancel}`);
40414
41189
  }
40415
41190
  };
40416
- const renderField = React81__default.useCallback(
41191
+ const renderField = React84__default.useCallback(
40417
41192
  (field) => {
40418
41193
  const fieldName = field.name || field.field;
40419
41194
  if (!fieldName) return null;
@@ -40434,7 +41209,7 @@ var init_Form = __esm({
40434
41209
  [formData, isFieldVisible, relationsData, relationsLoading, isLoading]
40435
41210
  );
40436
41211
  const effectiveFields = entityDerivedFields ?? fields;
40437
- const normalizedFields = React81__default.useMemo(() => {
41212
+ const normalizedFields = React84__default.useMemo(() => {
40438
41213
  if (!effectiveFields || effectiveFields.length === 0) return [];
40439
41214
  return effectiveFields.map((field) => {
40440
41215
  if (typeof field === "string") {
@@ -40457,7 +41232,7 @@ var init_Form = __esm({
40457
41232
  return field;
40458
41233
  });
40459
41234
  }, [effectiveFields, resolvedEntity]);
40460
- const schemaFields = React81__default.useMemo(() => {
41235
+ const schemaFields = React84__default.useMemo(() => {
40461
41236
  if (normalizedFields.length === 0) return null;
40462
41237
  if (isDebugEnabled()) {
40463
41238
  debugGroup(`Form: ${entityName || "unknown"}`);
@@ -40467,7 +41242,7 @@ var init_Form = __esm({
40467
41242
  }
40468
41243
  return normalizedFields.map(renderField).filter(Boolean);
40469
41244
  }, [normalizedFields, renderField, entityName, conditionalFields]);
40470
- const sectionElements = React81__default.useMemo(() => {
41245
+ const sectionElements = React84__default.useMemo(() => {
40471
41246
  if (!sections || sections.length === 0) return null;
40472
41247
  return sections.map((section) => {
40473
41248
  if (!isSectionVisible(section)) {
@@ -41637,11 +42412,11 @@ var init_Canvas3DErrorBoundary = __esm({
41637
42412
  "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
41638
42413
  }
41639
42414
  });
41640
- var log10, Canvas3DErrorBoundary;
42415
+ var log9, Canvas3DErrorBoundary;
41641
42416
  var init_Canvas3DErrorBoundary2 = __esm({
41642
42417
  "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
41643
42418
  init_Canvas3DErrorBoundary();
41644
- log10 = createLogger("almadar:ui:game:canvas3d:error-boundary");
42419
+ log9 = createLogger("almadar:ui:game:canvas3d:error-boundary");
41645
42420
  Canvas3DErrorBoundary = class extends Component {
41646
42421
  constructor(props) {
41647
42422
  super(props);
@@ -41669,8 +42444,8 @@ var init_Canvas3DErrorBoundary2 = __esm({
41669
42444
  componentDidCatch(error, errorInfo) {
41670
42445
  this.setState({ errorInfo });
41671
42446
  this.props.onError?.(error, errorInfo);
41672
- log10.error("Error caught", { error });
41673
- log10.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
42447
+ log9.error("Error caught", { error });
42448
+ log9.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
41674
42449
  }
41675
42450
  render() {
41676
42451
  if (this.state.hasError) {
@@ -41733,7 +42508,7 @@ function useGLTFModel(url, resourceBasePath) {
41733
42508
  setState({ model: null, isLoading: false, error: null });
41734
42509
  return;
41735
42510
  }
41736
- log11.debug("Loading", { url });
42511
+ log10.debug("Loading", { url });
41737
42512
  setState((prev) => ({ ...prev, isLoading: true, error: null }));
41738
42513
  const assetRoot = resourceBasePath || detectAssetRoot2(url);
41739
42514
  const loader = new GLTFLoader$1();
@@ -41741,7 +42516,7 @@ function useGLTFModel(url, resourceBasePath) {
41741
42516
  loader.load(
41742
42517
  url,
41743
42518
  (gltf) => {
41744
- log11.debug("Loaded", { url });
42519
+ log10.debug("Loaded", { url });
41745
42520
  setState({
41746
42521
  model: gltf.scene,
41747
42522
  isLoading: false,
@@ -41750,7 +42525,7 @@ function useGLTFModel(url, resourceBasePath) {
41750
42525
  },
41751
42526
  void 0,
41752
42527
  (err) => {
41753
- log11.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
42528
+ log10.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
41754
42529
  setState({
41755
42530
  model: null,
41756
42531
  isLoading: false,
@@ -41787,12 +42562,18 @@ function ModelLoader({
41787
42562
  });
41788
42563
  return cloned;
41789
42564
  }, [loadedModel, castShadow, receiveShadow]);
42565
+ const normFactor = useMemo(() => {
42566
+ if (!model) return 1;
42567
+ const box = new THREE3.Box3().setFromObject(model);
42568
+ const size = new THREE3.Vector3();
42569
+ box.getSize(size);
42570
+ const maxDim = Math.max(size.x, size.y, size.z);
42571
+ return maxDim > 0 && Number.isFinite(maxDim) ? 1 / maxDim : 1;
42572
+ }, [model]);
41790
42573
  const scaleArray = useMemo(() => {
41791
- if (typeof scale === "number") {
41792
- return [scale, scale, scale];
41793
- }
41794
- return scale;
41795
- }, [scale]);
42574
+ const base = typeof scale === "number" ? [scale, scale, scale] : scale;
42575
+ return [base[0] * normFactor, base[1] * normFactor, base[2] * normFactor];
42576
+ }, [scale, normFactor]);
41796
42577
  const rotationRad = useMemo(() => {
41797
42578
  return [
41798
42579
  rotation[0] * Math.PI / 180,
@@ -41866,11 +42647,11 @@ function ModelLoader({
41866
42647
  }
41867
42648
  );
41868
42649
  }
41869
- var log11;
42650
+ var log10;
41870
42651
  var init_ModelLoader = __esm({
41871
42652
  "components/game/molecules/three/components/ModelLoader.tsx"() {
41872
42653
  "use client";
41873
- log11 = createLogger("almadar:ui:game:model-loader");
42654
+ log10 = createLogger("almadar:ui:game:model-loader");
41874
42655
  }
41875
42656
  });
41876
42657
 
@@ -42003,6 +42784,29 @@ var init_GameCanvas3D2 = __esm({
42003
42784
  const controlsRef = useRef(null);
42004
42785
  const [hoveredTile, setHoveredTile] = useState(null);
42005
42786
  const [internalError, setInternalError] = useState(null);
42787
+ useEffect(() => {
42788
+ const el = containerRef.current;
42789
+ if (!el) return;
42790
+ let node = el;
42791
+ let depth = 0;
42792
+ while (node && depth < 8) {
42793
+ const cs = window.getComputedStyle(node);
42794
+ const rect = node.getBoundingClientRect();
42795
+ console.log("[almadar:ui:game:3d-height]", {
42796
+ depth,
42797
+ tag: node.tagName,
42798
+ id: node.id || void 0,
42799
+ className: node.className?.slice?.(0, 60) || void 0,
42800
+ rectHeight: rect.height,
42801
+ computedHeight: cs.height,
42802
+ computedMinHeight: cs.minHeight,
42803
+ computedFlex: cs.flex,
42804
+ computedOverflow: cs.overflow
42805
+ });
42806
+ node = node.parentElement;
42807
+ depth++;
42808
+ }
42809
+ }, []);
42006
42810
  const { sheetUrls: atlasSheetUrls, resolveUnitFrame } = useUnitSpriteAtlas(units);
42007
42811
  const preloadUrls = useMemo(() => [...preloadAssets, ...atlasSheetUrls], [preloadAssets, atlasSheetUrls]);
42008
42812
  const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
@@ -42131,24 +42935,28 @@ var init_GameCanvas3D2 = __esm({
42131
42935
  const cameraConfig = useMemo(() => {
42132
42936
  const size = Math.max(
42133
42937
  gridBounds.maxX - gridBounds.minX,
42134
- gridBounds.maxZ - gridBounds.minZ
42938
+ gridBounds.maxZ - gridBounds.minZ,
42939
+ 4
42940
+ // minimum framing distance so a tiny board isn't zoomed in
42135
42941
  );
42136
- const distance = size * 1.5;
42942
+ const cx = (gridBounds.minX + gridBounds.maxX) / 2;
42943
+ const cz = (gridBounds.minZ + gridBounds.maxZ) / 2;
42944
+ const d = size * 1.5;
42137
42945
  switch (cameraMode) {
42138
42946
  case "isometric":
42139
42947
  return {
42140
- position: [distance, distance * 0.8, distance],
42948
+ position: [cx + d, d * 0.8, cz + d],
42141
42949
  fov: 45
42142
42950
  };
42143
42951
  case "top-down":
42144
42952
  return {
42145
- position: [0, distance * 2, 0],
42953
+ position: [cx, d * 2, cz],
42146
42954
  fov: 45
42147
42955
  };
42148
42956
  case "perspective":
42149
42957
  default:
42150
42958
  return {
42151
- position: [distance, distance, distance],
42959
+ position: [cx + d, d, cz + d],
42152
42960
  fov: 45
42153
42961
  };
42154
42962
  }
@@ -42387,7 +43195,8 @@ var init_GameCanvas3D2 = __esm({
42387
43195
  "div",
42388
43196
  {
42389
43197
  ref: containerRef,
42390
- className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
43198
+ className: cn("game-canvas-3d relative w-full overflow-hidden", className),
43199
+ style: { height: "85vh" },
42391
43200
  "data-orientation": orientation,
42392
43201
  "data-camera-mode": cameraMode,
42393
43202
  "data-overlay": overlay,
@@ -42402,7 +43211,7 @@ var init_GameCanvas3D2 = __esm({
42402
43211
  near: 0.1,
42403
43212
  far: 1e3
42404
43213
  },
42405
- style: { background: backgroundColor },
43214
+ style: { background: backgroundColor, position: "absolute", inset: 0 },
42406
43215
  onClick: (e) => {
42407
43216
  if (e.target === e.currentTarget) {
42408
43217
  eventHandlers.handleCanvasClick(e);
@@ -43373,7 +44182,7 @@ var init_List = __esm({
43373
44182
  if (entity && typeof entity === "object" && "id" in entity) return [entity];
43374
44183
  return [];
43375
44184
  }, [entity]);
43376
- const getItemActions = React81__default.useCallback(
44185
+ const getItemActions = React84__default.useCallback(
43377
44186
  (item) => {
43378
44187
  if (!itemActions) return [];
43379
44188
  if (typeof itemActions === "function") {
@@ -43849,7 +44658,7 @@ var init_MediaGallery = __esm({
43849
44658
  [selectable, selectedItems, selectionEvent, eventBus]
43850
44659
  );
43851
44660
  const entityData = Array.isArray(entity) ? entity : [];
43852
- const items = React81__default.useMemo(() => {
44661
+ const items = React84__default.useMemo(() => {
43853
44662
  if (propItems) return propItems;
43854
44663
  if (entityData.length === 0) return [];
43855
44664
  return entityData.map((record, idx) => ({
@@ -44010,7 +44819,7 @@ var init_MediaGallery = __esm({
44010
44819
  }
44011
44820
  });
44012
44821
  function extractTitle2(children) {
44013
- if (!React81__default.isValidElement(children)) return void 0;
44822
+ if (!React84__default.isValidElement(children)) return void 0;
44014
44823
  const props = children.props;
44015
44824
  if (typeof props.title === "string") {
44016
44825
  return props.title;
@@ -44586,31 +45395,33 @@ var init_PricingPageTemplate = __esm({
44586
45395
  PricingPageTemplate.displayName = "PricingPageTemplate";
44587
45396
  }
44588
45397
  });
44589
- function dungeonTerrain(x, y, stairsX, stairsY) {
45398
+ function resolveManifestUrl3(manifest, relative) {
45399
+ if (relative == null) return void 0;
45400
+ if (/^https?:\/\//.test(relative)) return relative;
45401
+ const base = manifest?.baseUrl;
45402
+ if (base == null) return relative;
45403
+ const cleanBase = base.replace(/\/$/, "");
45404
+ const cleanRel = relative.replace(/^\//, "");
45405
+ return `${cleanBase}/${cleanRel}`;
45406
+ }
45407
+ function dungeonTerrain(x, y, stairsX, stairsY, manifest) {
44590
45408
  const isBorder = x === 0 || y === 0 || x === DUNGEON_GRID_W - 1 || y === DUNGEON_GRID_H - 1;
44591
45409
  const isPillar = x % 4 === 0 && y % 4 === 0 && !isBorder;
44592
45410
  const isWall = isBorder || isPillar;
45411
+ const terrains = manifest?.terrains;
44593
45412
  if (x === stairsX && y === stairsY) {
44594
- return { terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` };
45413
+ return { terrain: "stairs", passable: true, terrainSprite: resolveManifestUrl3(manifest, terrains?.stairs) };
44595
45414
  }
44596
45415
  if (isWall) {
44597
- return { terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` };
44598
- }
44599
- const variant = (x * 3 + y * 7) % 5;
44600
- const sprites = [
44601
- `${CDN4}/isometric-dungeon/Isometric/dirt_E.png`,
44602
- `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png`,
44603
- `${CDN4}/isometric-dungeon/Isometric/planks_E.png`,
44604
- `${CDN4}/isometric-dungeon/Isometric/stoneTile_E.png`,
44605
- `${CDN4}/isometric-dungeon/Isometric/dirt_E.png`
44606
- ];
44607
- return { terrain: "floor", passable: true, terrainSprite: sprites[variant] };
45416
+ return { terrain: "wall", passable: false, terrainSprite: resolveManifestUrl3(manifest, terrains?.wall) };
45417
+ }
45418
+ return { terrain: "floor", passable: true, terrainSprite: resolveManifestUrl3(manifest, terrains?.floor) };
44608
45419
  }
44609
- function buildDefaultDungeonTiles(stairsX, stairsY) {
45420
+ function buildDefaultDungeonTiles(stairsX, stairsY, manifest) {
44610
45421
  const tiles = [];
44611
45422
  for (let y = 0; y < DUNGEON_GRID_H; y++) {
44612
45423
  for (let x = 0; x < DUNGEON_GRID_W; x++) {
44613
- tiles.push({ x, y, ...dungeonTerrain(x, y, stairsX, stairsY) });
45424
+ tiles.push({ x, y, ...dungeonTerrain(x, y, stairsX, stairsY, manifest) });
44614
45425
  }
44615
45426
  }
44616
45427
  return tiles;
@@ -44640,20 +45451,48 @@ function RoguelikeBoard({
44640
45451
  className
44641
45452
  }) {
44642
45453
  const board = boardEntity(entity) ?? {};
44643
- const resolvedStairsX = propStairsX ?? num(board.stairsX, 14);
44644
- const resolvedStairsY = propStairsY ?? num(board.stairsY, 14);
44645
- const tiles = propTiles ?? (Array.isArray(board.tiles) && board.tiles.length >= DUNGEON_GRID_W * DUNGEON_GRID_H ? board.tiles : buildDefaultDungeonTiles(resolvedStairsX, resolvedStairsY));
44646
- const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
44647
- const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
44648
- const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
45454
+ const assetManifest = propAssetManifest ?? board.assetManifest;
45455
+ const stairsX = propStairsX ?? num(board.stairsX, 14);
45456
+ const stairsY = propStairsY ?? num(board.stairsY, 14);
45457
+ const entityTiles = useMemo(
45458
+ () => rows(board.tiles).map((r) => ({
45459
+ x: num(r.x),
45460
+ y: num(r.y),
45461
+ terrain: r.terrain == null ? void 0 : str(r.terrain),
45462
+ terrainSprite: r.terrainSprite == null ? void 0 : str(r.terrainSprite),
45463
+ passable: r.passable !== false
45464
+ })),
45465
+ [board.tiles]
45466
+ );
45467
+ const tiles = propTiles ?? (entityTiles.length >= DUNGEON_GRID_W * DUNGEON_GRID_H ? entityTiles : buildDefaultDungeonTiles(stairsX, stairsY, assetManifest));
45468
+ const entityEnemies = useMemo(
45469
+ () => rows(board.enemies).map((r) => ({
45470
+ id: str(r.id),
45471
+ x: num(r.x),
45472
+ y: num(r.y),
45473
+ hp: num(r.hp),
45474
+ attack: num(r.attack)
45475
+ })),
45476
+ [board.enemies]
45477
+ );
45478
+ const enemies = propEnemies ?? (entityEnemies.length > 0 ? entityEnemies : DEFAULT_ENEMIES);
45479
+ const entityItems = useMemo(
45480
+ () => rows(board.items).map((r) => ({
45481
+ id: str(r.id),
45482
+ x: num(r.x),
45483
+ y: num(r.y),
45484
+ kind: str(r.kind) || "health_potion"
45485
+ })),
45486
+ [board.items]
45487
+ );
45488
+ const items = propItems ?? (entityItems.length > 0 ? entityItems : DEFAULT_ITEMS);
45489
+ const player = propPlayer ?? (board.player == null ? { x: 1, y: 1 } : vec2(board.player));
44649
45490
  const playerHp = propPlayerHp ?? num(board.playerHp, 10);
44650
45491
  const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
44651
45492
  const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
44652
45493
  const depth = propDepth ?? num(board.depth, 1);
44653
45494
  const result = propResult ?? (str(board.result) || "none");
44654
45495
  const phase = propPhase ?? (str(board.phase) || "player_turn");
44655
- propStairsX ?? num(board.stairsX, 14);
44656
- propStairsY ?? num(board.stairsY, 14);
44657
45496
  const eventBus = useEventBus();
44658
45497
  const { t } = useTranslate();
44659
45498
  const [hoveredTile, setHoveredTile] = useState(null);
@@ -44700,7 +45539,7 @@ function RoguelikeBoard({
44700
45539
  health: playerHp,
44701
45540
  maxHealth: playerMaxHp,
44702
45541
  unitType: "player",
44703
- sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
45542
+ sprite: resolveManifestUrl3(assetManifest, assetManifest?.units?.player)
44704
45543
  },
44705
45544
  ...liveEnemies.map((e) => ({
44706
45545
  id: e.id,
@@ -44710,19 +45549,19 @@ function RoguelikeBoard({
44710
45549
  health: e.hp,
44711
45550
  maxHealth: 6,
44712
45551
  unitType: "enemy",
44713
- sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
45552
+ sprite: resolveManifestUrl3(assetManifest, assetManifest?.units?.enemy)
44714
45553
  }))
44715
45554
  ];
44716
- }, [player, enemies, playerHp, playerMaxHp, t]);
45555
+ }, [player, enemies, playerHp, playerMaxHp, assetManifest, t]);
44717
45556
  const isoFeatures = useMemo(
44718
45557
  () => items.map((it) => ({
44719
45558
  id: it.id,
44720
45559
  x: it.x,
44721
45560
  y: it.y,
44722
45561
  type: it.kind,
44723
- sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
45562
+ sprite: resolveManifestUrl3(assetManifest, assetManifest?.features?.[it.kind])
44724
45563
  })),
44725
- [items]
45564
+ [items, assetManifest]
44726
45565
  );
44727
45566
  const validMoves = useMemo(() => {
44728
45567
  if (result !== "none" || phase !== "player_turn") return [];
@@ -44781,8 +45620,8 @@ function RoguelikeBoard({
44781
45620
  onTileHover: (x, y) => setHoveredTile({ x, y }),
44782
45621
  onTileLeave: () => setHoveredTile(null),
44783
45622
  scale,
44784
- assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
44785
- assetManifest: propAssetManifest,
45623
+ assetBaseUrl: assetManifest?.baseUrl,
45624
+ assetManifest,
44786
45625
  unitScale,
44787
45626
  spriteHeightRatio,
44788
45627
  spriteMaxWidthRatio
@@ -44829,7 +45668,7 @@ function RoguelikeBoard({
44829
45668
  ] }) })
44830
45669
  ] });
44831
45670
  }
44832
- var CDN4, DUNGEON_GRID_W, DUNGEON_GRID_H, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
45671
+ var DUNGEON_GRID_W, DUNGEON_GRID_H, DEFAULT_ENEMIES, DEFAULT_ITEMS;
44833
45672
  var init_RoguelikeBoard = __esm({
44834
45673
  "components/game/organisms/RoguelikeBoard.tsx"() {
44835
45674
  "use client";
@@ -44841,10 +45680,8 @@ var init_RoguelikeBoard = __esm({
44841
45680
  init_Stack();
44842
45681
  init_IsometricCanvas();
44843
45682
  init_boardEntity();
44844
- CDN4 = "https://almadar-kflow-assets.web.app/shared";
44845
45683
  DUNGEON_GRID_W = 16;
44846
45684
  DUNGEON_GRID_H = 16;
44847
- buildDefaultDungeonTiles(14, 14);
44848
45685
  DEFAULT_ENEMIES = [
44849
45686
  { id: "e1", x: 5, y: 2, hp: 5, attack: 2 },
44850
45687
  { id: "e2", x: 9, y: 6, hp: 4, attack: 1 },
@@ -44856,11 +45693,6 @@ var init_RoguelikeBoard = __esm({
44856
45693
  { id: "i2", x: 7, y: 9, kind: "sword" },
44857
45694
  { id: "i3", x: 11, y: 5, kind: "shield" }
44858
45695
  ];
44859
- FEATURE_SPRITE = {
44860
- health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
44861
- sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
44862
- shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
44863
- };
44864
45696
  RoguelikeBoard.displayName = "RoguelikeBoard";
44865
45697
  }
44866
45698
  });
@@ -44898,40 +45730,40 @@ function RoguelikeTemplate({
44898
45730
  }
44899
45731
  );
44900
45732
  }
44901
- var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
45733
+ var CDN4, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
44902
45734
  var init_RoguelikeTemplate = __esm({
44903
45735
  "components/game/templates/RoguelikeTemplate.tsx"() {
44904
45736
  init_RoguelikeBoard();
44905
- CDN5 = "https://almadar-kflow-assets.web.app/shared";
45737
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
44906
45738
  DEFAULT_ROGUELIKE_TILES = [
44907
- { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44908
- { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44909
- { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44910
- { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44911
- { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44912
- { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44913
- { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44914
- { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44915
- { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44916
- { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44917
- { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44918
- { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44919
- { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44920
- { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44921
- { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44922
- { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44923
- { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44924
- { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44925
- { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
44926
- { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44927
- { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44928
- { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44929
- { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44930
- { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44931
- { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
45739
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45740
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45741
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45742
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45743
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45744
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45745
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45746
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45747
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45748
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45749
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45750
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45751
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45752
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45753
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45754
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45755
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45756
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45757
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
45758
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45759
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45760
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45761
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45762
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45763
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
44932
45764
  ];
44933
45765
  DEFAULT_ROGUELIKE_MANIFEST = {
44934
- baseUrl: CDN5,
45766
+ baseUrl: CDN4,
44935
45767
  terrains: {
44936
45768
  floor: "/isometric-dungeon/Isometric/dirt_E.png",
44937
45769
  wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
@@ -45057,7 +45889,7 @@ var init_debugRegistry = __esm({
45057
45889
  }
45058
45890
  });
45059
45891
  function useDebugData() {
45060
- const [data, setData] = React81.useState(() => ({
45892
+ const [data, setData] = React84.useState(() => ({
45061
45893
  traits: [],
45062
45894
  ticks: [],
45063
45895
  guards: [],
@@ -45071,7 +45903,7 @@ function useDebugData() {
45071
45903
  },
45072
45904
  lastUpdate: Date.now()
45073
45905
  }));
45074
- React81.useEffect(() => {
45906
+ React84.useEffect(() => {
45075
45907
  const updateData = () => {
45076
45908
  setData({
45077
45909
  traits: getAllTraits(),
@@ -45180,12 +46012,12 @@ function layoutGraph(states, transitions, initialState, width, height) {
45180
46012
  return positions;
45181
46013
  }
45182
46014
  function WalkMinimap() {
45183
- const [walkStep, setWalkStep] = React81.useState(null);
45184
- const [traits2, setTraits] = React81.useState([]);
45185
- const [coveredEdges, setCoveredEdges] = React81.useState([]);
45186
- const [completedTraits, setCompletedTraits] = React81.useState(/* @__PURE__ */ new Set());
45187
- const prevTraitRef = React81.useRef(null);
45188
- React81.useEffect(() => {
46015
+ const [walkStep, setWalkStep] = React84.useState(null);
46016
+ const [traits2, setTraits] = React84.useState([]);
46017
+ const [coveredEdges, setCoveredEdges] = React84.useState([]);
46018
+ const [completedTraits, setCompletedTraits] = React84.useState(/* @__PURE__ */ new Set());
46019
+ const prevTraitRef = React84.useRef(null);
46020
+ React84.useEffect(() => {
45189
46021
  const interval = setInterval(() => {
45190
46022
  const w = window;
45191
46023
  const step = w.__orbitalWalkStep;
@@ -45621,15 +46453,15 @@ var init_EntitiesTab = __esm({
45621
46453
  });
45622
46454
  function EventFlowTab({ events: events2 }) {
45623
46455
  const { t } = useTranslate();
45624
- const [filter, setFilter] = React81.useState("all");
45625
- const containerRef = React81.useRef(null);
45626
- const [autoScroll, setAutoScroll] = React81.useState(true);
45627
- React81.useEffect(() => {
46456
+ const [filter, setFilter] = React84.useState("all");
46457
+ const containerRef = React84.useRef(null);
46458
+ const [autoScroll, setAutoScroll] = React84.useState(true);
46459
+ React84.useEffect(() => {
45628
46460
  if (autoScroll && containerRef.current) {
45629
46461
  containerRef.current.scrollTop = containerRef.current.scrollHeight;
45630
46462
  }
45631
46463
  }, [events2.length, autoScroll]);
45632
- const filteredEvents = React81.useMemo(() => {
46464
+ const filteredEvents = React84.useMemo(() => {
45633
46465
  if (filter === "all") return events2;
45634
46466
  return events2.filter((e) => e.type === filter);
45635
46467
  }, [events2, filter]);
@@ -45745,7 +46577,7 @@ var init_EventFlowTab = __esm({
45745
46577
  });
45746
46578
  function GuardsPanel({ guards }) {
45747
46579
  const { t } = useTranslate();
45748
- const [filter, setFilter] = React81.useState("all");
46580
+ const [filter, setFilter] = React84.useState("all");
45749
46581
  if (guards.length === 0) {
45750
46582
  return /* @__PURE__ */ jsx(
45751
46583
  EmptyState,
@@ -45758,7 +46590,7 @@ function GuardsPanel({ guards }) {
45758
46590
  }
45759
46591
  const passedCount = guards.filter((g) => g.result).length;
45760
46592
  const failedCount = guards.length - passedCount;
45761
- const filteredGuards = React81.useMemo(() => {
46593
+ const filteredGuards = React84.useMemo(() => {
45762
46594
  if (filter === "all") return guards;
45763
46595
  if (filter === "passed") return guards.filter((g) => g.result);
45764
46596
  return guards.filter((g) => !g.result);
@@ -45921,10 +46753,10 @@ function EffectBadge({ effect }) {
45921
46753
  }
45922
46754
  function TransitionTimeline({ transitions }) {
45923
46755
  const { t } = useTranslate();
45924
- const containerRef = React81.useRef(null);
45925
- const [autoScroll, setAutoScroll] = React81.useState(true);
45926
- const [expandedId, setExpandedId] = React81.useState(null);
45927
- React81.useEffect(() => {
46756
+ const containerRef = React84.useRef(null);
46757
+ const [autoScroll, setAutoScroll] = React84.useState(true);
46758
+ const [expandedId, setExpandedId] = React84.useState(null);
46759
+ React84.useEffect(() => {
45928
46760
  if (autoScroll && containerRef.current) {
45929
46761
  containerRef.current.scrollTop = containerRef.current.scrollHeight;
45930
46762
  }
@@ -46204,9 +47036,9 @@ function getAllEvents(traits2) {
46204
47036
  function EventDispatcherTab({ traits: traits2, schema }) {
46205
47037
  const eventBus = useEventBus();
46206
47038
  const { t } = useTranslate();
46207
- const [log15, setLog] = React81.useState([]);
46208
- const prevStatesRef = React81.useRef(/* @__PURE__ */ new Map());
46209
- React81.useEffect(() => {
47039
+ const [log14, setLog] = React84.useState([]);
47040
+ const prevStatesRef = React84.useRef(/* @__PURE__ */ new Map());
47041
+ React84.useEffect(() => {
46210
47042
  for (const trait of traits2) {
46211
47043
  const prev = prevStatesRef.current.get(trait.id);
46212
47044
  if (prev && prev !== trait.currentState) {
@@ -46268,9 +47100,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
46268
47100
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
46269
47101
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: unavailableEvents.map((event) => /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", className: "opacity-50", children: event }, event)) })
46270
47102
  ] }),
46271
- log15.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
47103
+ log14.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
46272
47104
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
46273
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log15.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
47105
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log14.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
46274
47106
  /* @__PURE__ */ jsx("span", { className: "text-purple-400", children: entry.traitName }),
46275
47107
  " ",
46276
47108
  /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -46375,10 +47207,10 @@ function VerifyModePanel({
46375
47207
  localCount
46376
47208
  }) {
46377
47209
  const { t } = useTranslate();
46378
- const [expanded, setExpanded] = React81.useState(true);
46379
- const scrollRef = React81.useRef(null);
46380
- const prevCountRef = React81.useRef(0);
46381
- React81.useEffect(() => {
47210
+ const [expanded, setExpanded] = React84.useState(true);
47211
+ const scrollRef = React84.useRef(null);
47212
+ const prevCountRef = React84.useRef(0);
47213
+ React84.useEffect(() => {
46382
47214
  if (expanded && transitions.length > prevCountRef.current && scrollRef.current) {
46383
47215
  scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
46384
47216
  }
@@ -46435,10 +47267,10 @@ function RuntimeDebugger({
46435
47267
  schema
46436
47268
  }) {
46437
47269
  const { t } = useTranslate();
46438
- const [isCollapsed, setIsCollapsed] = React81.useState(mode === "verify" ? true : defaultCollapsed);
46439
- const [isVisible, setIsVisible] = React81.useState(mode === "inline" || mode === "verify" || isDebugEnabled2());
47270
+ const [isCollapsed, setIsCollapsed] = React84.useState(mode === "verify" ? true : defaultCollapsed);
47271
+ const [isVisible, setIsVisible] = React84.useState(mode === "inline" || mode === "verify" || isDebugEnabled2());
46440
47272
  const debugData = useDebugData();
46441
- React81.useEffect(() => {
47273
+ React84.useEffect(() => {
46442
47274
  if (mode === "inline") return;
46443
47275
  return onDebugToggle((enabled) => {
46444
47276
  setIsVisible(enabled);
@@ -46447,7 +47279,7 @@ function RuntimeDebugger({
46447
47279
  }
46448
47280
  });
46449
47281
  }, [mode]);
46450
- React81.useEffect(() => {
47282
+ React84.useEffect(() => {
46451
47283
  if (mode === "inline") return;
46452
47284
  const handleKeyDown = (e) => {
46453
47285
  if (e.key === "`" && isVisible) {
@@ -47007,7 +47839,7 @@ function SequenceBar({
47007
47839
  onSlotRemove(index);
47008
47840
  }, [onSlotRemove, playing]);
47009
47841
  const paddedSlots = Array.from({ length: maxSlots }, (_, i) => slots[i]);
47010
- return /* @__PURE__ */ jsx(HStack, { className: cn("items-center", className), gap: "sm", children: paddedSlots.map((slot, i) => /* @__PURE__ */ jsxs(React81__default.Fragment, { children: [
47842
+ return /* @__PURE__ */ jsx(HStack, { className: cn("items-center", className), gap: "sm", children: paddedSlots.map((slot, i) => /* @__PURE__ */ jsxs(React84__default.Fragment, { children: [
47011
47843
  i > 0 && /* @__PURE__ */ jsx(
47012
47844
  Typography,
47013
47845
  {
@@ -47896,7 +48728,7 @@ var init_StatCard = __esm({
47896
48728
  const labelToUse = propLabel ?? propTitle;
47897
48729
  const eventBus = useEventBus();
47898
48730
  const { t } = useTranslate();
47899
- const handleActionClick = React81__default.useCallback(() => {
48731
+ const handleActionClick = React84__default.useCallback(() => {
47900
48732
  if (action?.event) {
47901
48733
  eventBus.emit(`UI:${action.event}`, {});
47902
48734
  }
@@ -47907,7 +48739,7 @@ var init_StatCard = __esm({
47907
48739
  const data = Array.isArray(entity) ? entity : entity ? [entity] : [];
47908
48740
  const isLoading = externalLoading ?? false;
47909
48741
  const error = externalError;
47910
- const computeMetricValue = React81__default.useCallback(
48742
+ const computeMetricValue = React84__default.useCallback(
47911
48743
  (metric, items) => {
47912
48744
  if (metric.value !== void 0) {
47913
48745
  return metric.value;
@@ -47946,7 +48778,7 @@ var init_StatCard = __esm({
47946
48778
  },
47947
48779
  []
47948
48780
  );
47949
- const schemaStats = React81__default.useMemo(() => {
48781
+ const schemaStats = React84__default.useMemo(() => {
47950
48782
  if (!metrics || metrics.length === 0) return null;
47951
48783
  return metrics.map((metric) => ({
47952
48784
  label: metric.label,
@@ -47954,7 +48786,7 @@ var init_StatCard = __esm({
47954
48786
  format: metric.format
47955
48787
  }));
47956
48788
  }, [metrics, data, computeMetricValue]);
47957
- const calculatedTrend = React81__default.useMemo(() => {
48789
+ const calculatedTrend = React84__default.useMemo(() => {
47958
48790
  if (manualTrend !== void 0) return manualTrend;
47959
48791
  if (previousValue === void 0 || currentValue === void 0)
47960
48792
  return void 0;
@@ -48920,7 +49752,7 @@ var init_Timeline = __esm({
48920
49752
  }) => {
48921
49753
  const { t } = useTranslate();
48922
49754
  const entityData = Array.isArray(entity) ? entity : [];
48923
- const items = React81__default.useMemo(() => {
49755
+ const items = React84__default.useMemo(() => {
48924
49756
  if (propItems) return propItems;
48925
49757
  if (entityData.length === 0) return [];
48926
49758
  return entityData.map((record, idx) => {
@@ -49027,7 +49859,7 @@ var init_Timeline = __esm({
49027
49859
  }
49028
49860
  });
49029
49861
  function extractToastProps(children) {
49030
- if (!React81__default.isValidElement(children)) {
49862
+ if (!React84__default.isValidElement(children)) {
49031
49863
  if (typeof children === "string") {
49032
49864
  return { message: children };
49033
49865
  }
@@ -49065,7 +49897,7 @@ var init_ToastSlot = __esm({
49065
49897
  eventBus.emit("UI:CLOSE");
49066
49898
  };
49067
49899
  if (!isVisible) return null;
49068
- const isCustomContent = React81__default.isValidElement(children) && !message;
49900
+ const isCustomContent = React84__default.isValidElement(children) && !message;
49069
49901
  return /* @__PURE__ */ jsx(Box, { className: "fixed bottom-4 right-4 z-50", children: isCustomContent ? children : /* @__PURE__ */ jsx(
49070
49902
  Toast,
49071
49903
  {
@@ -49081,82 +49913,442 @@ var init_ToastSlot = __esm({
49081
49913
  ToastSlot.displayName = "ToastSlot";
49082
49914
  }
49083
49915
  });
49916
+ function resolveManifestUrl4(manifest, relative) {
49917
+ if (relative == null || relative === "") return void 0;
49918
+ if (/^https?:\/\//.test(relative)) return relative;
49919
+ const base = manifest?.baseUrl;
49920
+ if (base == null) return relative;
49921
+ const cleanBase = base.replace(/\/$/, "");
49922
+ const cleanRel = relative.replace(/^\//, "");
49923
+ return `${cleanBase}/${cleanRel}`;
49924
+ }
49925
+ function readPlayer(v) {
49926
+ if (v == null || typeof v !== "object") return void 0;
49927
+ const o = v;
49928
+ return { x: num(o.x), y: num(o.y) };
49929
+ }
49930
+ function TopDownShooterBoard({
49931
+ entity,
49932
+ player: propPlayer,
49933
+ enemies: propEnemies,
49934
+ projectiles: propProjectiles,
49935
+ score: propScore,
49936
+ wave: propWave,
49937
+ lives: propLives,
49938
+ result: propResult,
49939
+ assetManifest: propAssetManifest,
49940
+ width = DEFAULT_WIDTH,
49941
+ height = DEFAULT_HEIGHT,
49942
+ spriteSize = 32,
49943
+ moveEvent = "MOVE",
49944
+ fireEvent = "FIRE",
49945
+ playAgainEvent = "PLAY_AGAIN",
49946
+ gameEndEvent = "GAME_END",
49947
+ className
49948
+ }) {
49949
+ const board = boardEntity(entity) ?? {};
49950
+ const eventBus = useEventBus();
49951
+ const { t } = useTranslate();
49952
+ const assetManifest = propAssetManifest ?? board.assetManifest;
49953
+ const player = propPlayer ?? readPlayer(board.player) ?? { x: width / 2, y: height - spriteSize };
49954
+ const enemies = useMemo(
49955
+ () => propEnemies ?? rows(board.enemies).map((r) => ({
49956
+ id: str(r.id),
49957
+ x: num(r.x),
49958
+ y: num(r.y),
49959
+ hp: num(r.hp, 1)
49960
+ })),
49961
+ [propEnemies, board.enemies]
49962
+ );
49963
+ const projectiles = useMemo(
49964
+ () => propProjectiles ?? rows(board.projectiles).map((r) => ({
49965
+ id: str(r.id),
49966
+ x: num(r.x),
49967
+ y: num(r.y),
49968
+ vx: num(r.vx),
49969
+ vy: num(r.vy)
49970
+ })),
49971
+ [propProjectiles, board.projectiles]
49972
+ );
49973
+ const score = propScore ?? num(board.score, 0);
49974
+ const wave = propWave ?? num(board.wave, 1);
49975
+ const lives = propLives ?? num(board.lives, 3);
49976
+ const result = propResult ?? (str(board.result) || "none");
49977
+ const playerSprite = resolveManifestUrl4(assetManifest, assetManifest?.units?.player);
49978
+ const enemySprite = resolveManifestUrl4(assetManifest, assetManifest?.units?.enemy);
49979
+ const projectileSprite = resolveManifestUrl4(assetManifest, assetManifest?.features?.projectile);
49980
+ const backgroundSprite = resolveManifestUrl4(assetManifest, assetManifest?.background);
49981
+ const spriteUrls = useMemo(
49982
+ () => [playerSprite, enemySprite, projectileSprite].filter((u) => u != null),
49983
+ [playerSprite, enemySprite, projectileSprite]
49984
+ );
49985
+ const { getImage } = useImageCache(spriteUrls);
49986
+ const sceneRef = useRef({ player, enemies, projectiles, playerSprite, enemySprite, projectileSprite });
49987
+ sceneRef.current = { player, enemies, projectiles, playerSprite, enemySprite, projectileSprite };
49988
+ boardLog2.debug("shooter-resolve", {
49989
+ player: { x: player.x, y: player.y },
49990
+ enemyCount: enemies.length,
49991
+ projectileCount: projectiles.length,
49992
+ wave,
49993
+ lives,
49994
+ result,
49995
+ hasPlayerSprite: playerSprite != null
49996
+ });
49997
+ const onDraw = useCallback(
49998
+ (ctx, _frame) => {
49999
+ const scene = sceneRef.current;
50000
+ ctx.fillStyle = "#10141c";
50001
+ ctx.fillRect(0, 0, width, height);
50002
+ const half = spriteSize / 2;
50003
+ for (const p2 of scene.projectiles) {
50004
+ const img = scene.projectileSprite ? getImage(scene.projectileSprite) : void 0;
50005
+ if (img) {
50006
+ ctx.drawImage(img, p2.x - half / 2, p2.y - half / 2, spriteSize / 2, spriteSize / 2);
50007
+ } else {
50008
+ ctx.fillStyle = "#ffd34d";
50009
+ ctx.beginPath();
50010
+ ctx.arc(p2.x, p2.y, spriteSize / 6, 0, Math.PI * 2);
50011
+ ctx.fill();
50012
+ }
50013
+ }
50014
+ for (const e of scene.enemies) {
50015
+ const img = scene.enemySprite ? getImage(scene.enemySprite) : void 0;
50016
+ if (img) {
50017
+ ctx.drawImage(img, e.x - half, e.y - half, spriteSize, spriteSize);
50018
+ } else {
50019
+ ctx.fillStyle = "#e0524d";
50020
+ ctx.fillRect(e.x - half, e.y - half, spriteSize, spriteSize);
50021
+ }
50022
+ }
50023
+ const pImg = scene.playerSprite ? getImage(scene.playerSprite) : void 0;
50024
+ if (pImg) {
50025
+ ctx.drawImage(pImg, scene.player.x - half, scene.player.y - half, spriteSize, spriteSize);
50026
+ } else {
50027
+ ctx.fillStyle = "#4da3ff";
50028
+ ctx.beginPath();
50029
+ ctx.moveTo(scene.player.x, scene.player.y - half);
50030
+ ctx.lineTo(scene.player.x + half, scene.player.y + half);
50031
+ ctx.lineTo(scene.player.x - half, scene.player.y + half);
50032
+ ctx.closePath();
50033
+ ctx.fill();
50034
+ }
50035
+ },
50036
+ [getImage, width, height, spriteSize]
50037
+ );
50038
+ const moveEventRef = useRef(moveEvent);
50039
+ moveEventRef.current = moveEvent;
50040
+ const fireEventRef = useRef(fireEvent);
50041
+ fireEventRef.current = fireEvent;
50042
+ const resultRef = useRef(result);
50043
+ resultRef.current = result;
50044
+ const playerRef = useRef(player);
50045
+ playerRef.current = player;
50046
+ useEffect(() => {
50047
+ function onKeyDown(e) {
50048
+ if (resultRef.current !== "none") return;
50049
+ let dx = 0;
50050
+ let dy = 0;
50051
+ switch (e.key) {
50052
+ case "ArrowUp":
50053
+ case "w":
50054
+ dy = -1;
50055
+ break;
50056
+ case "ArrowDown":
50057
+ case "s":
50058
+ dy = 1;
50059
+ break;
50060
+ case "ArrowLeft":
50061
+ case "a":
50062
+ dx = -1;
50063
+ break;
50064
+ case "ArrowRight":
50065
+ case "d":
50066
+ dx = 1;
50067
+ break;
50068
+ case " ": {
50069
+ e.preventDefault();
50070
+ const evt = fireEventRef.current;
50071
+ if (evt) {
50072
+ eventBus.emit(`UI:${evt}`, { tx: playerRef.current.x, ty: 0 });
50073
+ }
50074
+ return;
50075
+ }
50076
+ default:
50077
+ return;
50078
+ }
50079
+ e.preventDefault();
50080
+ const moveEvt = moveEventRef.current;
50081
+ if (moveEvt) {
50082
+ eventBus.emit(`UI:${moveEvt}`, { dx, dy });
50083
+ }
50084
+ }
50085
+ window.addEventListener("keydown", onKeyDown);
50086
+ return () => window.removeEventListener("keydown", onKeyDown);
50087
+ }, [eventBus]);
50088
+ const handleFireClick = useCallback(
50089
+ (e) => {
50090
+ if (result !== "none") return;
50091
+ const evt = fireEventRef.current;
50092
+ if (!evt) return;
50093
+ const rect = e.currentTarget.getBoundingClientRect();
50094
+ const tx = e.clientX - rect.left;
50095
+ const ty = e.clientY - rect.top;
50096
+ eventBus.emit(`UI:${evt}`, { tx, ty });
50097
+ },
50098
+ [result, eventBus]
50099
+ );
50100
+ const handlePlayAgain = useCallback(() => {
50101
+ if (playAgainEvent) {
50102
+ eventBus.emit(`UI:${playAgainEvent}`, {});
50103
+ }
50104
+ }, [playAgainEvent, eventBus]);
50105
+ const gameEndEmittedRef = useRef(false);
50106
+ useEffect(() => {
50107
+ if ((result === "won" || result === "lost") && gameEndEvent) {
50108
+ if (!gameEndEmittedRef.current) {
50109
+ gameEndEmittedRef.current = true;
50110
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
50111
+ }
50112
+ } else {
50113
+ gameEndEmittedRef.current = false;
50114
+ }
50115
+ }, [result, gameEndEvent, eventBus]);
50116
+ const isGameOver = result === "won" || result === "lost";
50117
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("top-down-shooter-board relative bg-background", className), gap: "none", children: [
50118
+ /* @__PURE__ */ jsxs(HStack, { className: "px-4 py-2 border-b border-border bg-surface", gap: "lg", align: "center", children: [
50119
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
50120
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("shooter.score") ?? "Score" }),
50121
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: score })
50122
+ ] }),
50123
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
50124
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("shooter.wave") ?? "Wave" }),
50125
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", children: wave })
50126
+ ] }),
50127
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
50128
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("shooter.lives") ?? "Lives" }),
50129
+ /* @__PURE__ */ jsx(
50130
+ Typography,
50131
+ {
50132
+ variant: "body2",
50133
+ weight: "bold",
50134
+ className: lives <= 1 ? "text-error" : "text-success",
50135
+ children: lives
50136
+ }
50137
+ )
50138
+ ] })
50139
+ ] }),
50140
+ /* @__PURE__ */ jsxs(Box, { className: "relative", onClick: handleFireClick, children: [
50141
+ /* @__PURE__ */ jsx(
50142
+ GameCanvas2D,
50143
+ {
50144
+ width,
50145
+ height,
50146
+ backgroundImage: backgroundSprite,
50147
+ assetBaseUrl: assetManifest?.baseUrl,
50148
+ onDraw
50149
+ }
50150
+ ),
50151
+ 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: [
50152
+ /* @__PURE__ */ jsx(
50153
+ Typography,
50154
+ {
50155
+ variant: "h2",
50156
+ className: cn(
50157
+ "text-4xl font-black tracking-widest uppercase",
50158
+ result === "won" ? "text-warning" : "text-error"
50159
+ ),
50160
+ children: result === "won" ? t("shooter.victory") ?? "Victory!" : t("shooter.defeat") ?? "Defeat!"
50161
+ }
50162
+ ),
50163
+ /* @__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." }),
50164
+ /* @__PURE__ */ jsx(
50165
+ Button,
50166
+ {
50167
+ variant: "primary",
50168
+ className: "px-8 py-3 font-semibold",
50169
+ onClick: handlePlayAgain,
50170
+ children: t("shooter.playAgain") ?? "Play Again"
50171
+ }
50172
+ )
50173
+ ] }) })
50174
+ ] })
50175
+ ] });
50176
+ }
50177
+ var boardLog2, DEFAULT_WIDTH, DEFAULT_HEIGHT;
50178
+ var init_TopDownShooterBoard = __esm({
50179
+ "components/game/organisms/TopDownShooterBoard.tsx"() {
50180
+ "use client";
50181
+ init_cn();
50182
+ init_useEventBus();
50183
+ init_Box();
50184
+ init_Button();
50185
+ init_Typography();
50186
+ init_Stack();
50187
+ init_GameCanvas2D();
50188
+ init_useImageCache();
50189
+ init_boardEntity();
50190
+ boardLog2 = createLogger("almadar:ui:game:top-down-shooter-board");
50191
+ DEFAULT_WIDTH = 640;
50192
+ DEFAULT_HEIGHT = 480;
50193
+ TopDownShooterBoard.displayName = "TopDownShooterBoard";
50194
+ }
50195
+ });
50196
+ function isEntityRow3(value) {
50197
+ return value != null && typeof value === "object" && !Array.isArray(value);
50198
+ }
50199
+ function TopDownShooterTemplate({
50200
+ entity,
50201
+ title = "Arena Shooter",
50202
+ player,
50203
+ enemies,
50204
+ projectiles,
50205
+ score,
50206
+ wave,
50207
+ lives,
50208
+ result,
50209
+ assetManifest,
50210
+ width,
50211
+ height,
50212
+ spriteSize,
50213
+ moveEvent,
50214
+ fireEvent,
50215
+ playAgainEvent,
50216
+ gameEndEvent,
50217
+ className
50218
+ }) {
50219
+ const resolved = isEntityRow3(entity) ? entity : void 0;
50220
+ return /* @__PURE__ */ jsxs(
50221
+ Box,
50222
+ {
50223
+ display: "flex",
50224
+ fullHeight: true,
50225
+ className: cn("top-down-shooter-template flex-col", className),
50226
+ children: [
50227
+ /* @__PURE__ */ jsx(
50228
+ HStack,
50229
+ {
50230
+ gap: "sm",
50231
+ align: "center",
50232
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
50233
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
50234
+ }
50235
+ ),
50236
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
50237
+ TopDownShooterBoard,
50238
+ {
50239
+ entity: resolved,
50240
+ player,
50241
+ enemies,
50242
+ projectiles,
50243
+ score,
50244
+ wave,
50245
+ lives,
50246
+ result,
50247
+ assetManifest,
50248
+ width,
50249
+ height,
50250
+ spriteSize,
50251
+ moveEvent,
50252
+ fireEvent,
50253
+ playAgainEvent,
50254
+ gameEndEvent,
50255
+ className: "h-full"
50256
+ }
50257
+ ) })
50258
+ ]
50259
+ }
50260
+ );
50261
+ }
50262
+ var init_TopDownShooterTemplate = __esm({
50263
+ "components/game/templates/TopDownShooterTemplate.tsx"() {
50264
+ init_cn();
50265
+ init_Box();
50266
+ init_Stack();
50267
+ init_Typography();
50268
+ init_TopDownShooterBoard();
50269
+ TopDownShooterTemplate.displayName = "TopDownShooterTemplate";
50270
+ }
50271
+ });
50272
+ function resolveManifestUrl5(manifest, relative) {
50273
+ if (relative == null) return void 0;
50274
+ if (/^https?:\/\//.test(relative)) return relative;
50275
+ const base = manifest?.baseUrl;
50276
+ if (base == null) return relative;
50277
+ const cleanBase = base.replace(/\/$/, "");
50278
+ const cleanRel = relative.replace(/^\//, "");
50279
+ return `${cleanBase}/${cleanRel}`;
50280
+ }
49084
50281
  function buildDefaultTDTiles() {
49085
50282
  const pathSet = new Set(DEFAULT_TD_PATH.map((p2) => `${p2.x},${p2.y}`));
49086
50283
  const tiles = [];
49087
50284
  for (let y = 0; y < TD_GRID_H; y++) {
49088
50285
  for (let x = 0; x < TD_GRID_W; x++) {
49089
50286
  if (pathSet.has(`${x},${y}`)) {
49090
- tiles.push({ x, y, terrain: "path", passable: false, terrainSprite: TERRAIN_SPRITES.path });
50287
+ tiles.push({ x, y, terrain: "path", passable: false });
49091
50288
  } else {
49092
50289
  const variant = (x * 3 + y * 5 + (x ^ y)) % 3;
49093
- const terrainKey = ["ground", "grass", "dirt"][variant];
49094
- tiles.push({ x, y, terrain: terrainKey, passable: true, terrainSprite: TERRAIN_SPRITES[terrainKey] ?? TERRAIN_SPRITES.ground });
50290
+ const terrainKey = ["ground", "grass", "stone"][variant];
50291
+ tiles.push({ x, y, terrain: terrainKey, passable: true });
49095
50292
  }
49096
50293
  }
49097
50294
  }
49098
50295
  return tiles;
49099
50296
  }
49100
- function tilesToIso(tiles) {
49101
- return tiles.map((t) => ({
49102
- x: t.x,
49103
- y: t.y,
49104
- terrain: t.terrain,
49105
- terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
49106
- passable: t.passable
49107
- }));
49108
- }
49109
- function towersToUnits(towers) {
49110
- return towers.map((t) => ({
49111
- id: t.id,
49112
- position: { x: t.x, y: t.y },
49113
- name: "Tower",
49114
- team: "player",
49115
- sprite: TOWER_SPRITE,
49116
- unitType: "guardian",
49117
- health: 1,
49118
- maxHealth: 1
49119
- }));
50297
+ function tilesToIso2(tiles, manifest) {
50298
+ return tiles.map((t) => {
50299
+ const key = t.terrain ?? "ground";
50300
+ const sprite = t.terrainSprite ?? resolveManifestUrl5(manifest, manifest?.terrains?.[key]) ?? resolveManifestUrl5(manifest, manifest?.terrains?.ground);
50301
+ return {
50302
+ x: t.x,
50303
+ y: t.y,
50304
+ terrain: t.terrain,
50305
+ terrainSprite: sprite,
50306
+ passable: t.passable
50307
+ };
50308
+ });
49120
50309
  }
49121
- function creepsToUnits(creeps) {
50310
+ function creepsToUnits(creeps, manifest) {
50311
+ const sprite = resolveManifestUrl5(manifest, manifest?.units?.creep);
49122
50312
  return creeps.map((c) => ({
49123
50313
  id: c.id,
49124
50314
  position: { x: c.x, y: c.y },
49125
50315
  name: "Creep",
49126
50316
  team: "enemy",
49127
- sprite: CREEP_SPRITE,
49128
- unitType: "scrapper",
50317
+ sprite,
50318
+ unitType: "creep",
49129
50319
  health: c.hp,
49130
50320
  maxHealth: c.maxHp
49131
50321
  }));
49132
50322
  }
49133
- function pathToFeatures(path) {
49134
- return path.map((p2, i) => ({
49135
- id: `path-${i}`,
49136
- x: p2.x,
49137
- y: p2.y,
49138
- type: "path-marker",
49139
- sprite: `${CDN6}world-map/road_straight.png`
49140
- }));
49141
- }
49142
- function heroToUnit(hero) {
50323
+ function heroToUnit(hero, manifest) {
49143
50324
  return {
49144
50325
  id: hero.id,
49145
50326
  position: { x: hero.x, y: hero.y },
49146
50327
  name: "Hero",
49147
50328
  team: "player",
49148
- sprite: HERO_SPRITE,
49149
- unitType: "amir",
50329
+ sprite: resolveManifestUrl5(manifest, manifest?.units?.hero),
50330
+ unitType: "hero",
49150
50331
  health: 1,
49151
50332
  maxHealth: 1
49152
50333
  };
49153
50334
  }
50335
+ function towersToFeatures(towers, manifest) {
50336
+ const sprite = resolveManifestUrl5(manifest, manifest?.features?.tower);
50337
+ return towers.map((t) => ({
50338
+ id: t.id,
50339
+ x: t.x,
50340
+ y: t.y,
50341
+ type: "tower",
50342
+ sprite
50343
+ }));
50344
+ }
49154
50345
  function TowerDefenseBoard({
49155
50346
  entity,
49156
50347
  tiles: propTiles,
49157
50348
  path: propPath,
49158
50349
  towers: propTowers,
49159
50350
  creeps: propCreeps,
50351
+ assetManifest: propAssetManifest,
49160
50352
  hero: propHero,
49161
50353
  gold: propGold,
49162
50354
  lives: propLives,
@@ -49179,12 +50371,51 @@ function TowerDefenseBoard({
49179
50371
  const board = boardEntity(entity) ?? {};
49180
50372
  const eventBus = useEventBus();
49181
50373
  const { t } = useTranslate();
49182
- const rawTiles = propTiles ?? rows(board.tiles);
50374
+ const assetManifest = propAssetManifest ?? board.assetManifest;
50375
+ const entityTiles = useMemo(
50376
+ () => rows(board.tiles).map((r) => ({
50377
+ x: num(r.x),
50378
+ y: num(r.y),
50379
+ terrain: r.terrain == null ? void 0 : str(r.terrain),
50380
+ terrainSprite: r.terrainSprite == null ? void 0 : str(r.terrainSprite),
50381
+ passable: r.passable !== false
50382
+ })),
50383
+ [board.tiles]
50384
+ );
50385
+ const rawTiles = propTiles ?? entityTiles;
49183
50386
  const tiles = rawTiles.length >= TD_GRID_W * TD_GRID_H ? rawTiles : DEFAULT_TD_TILES;
49184
- const rawPath = propPath ?? rows(board.path);
50387
+ const entityPath = useMemo(
50388
+ () => rows(board.path).map((r) => ({ x: num(r.x), y: num(r.y) })),
50389
+ [board.path]
50390
+ );
50391
+ const rawPath = propPath ?? entityPath;
49185
50392
  const path = rawPath.length > 0 ? rawPath : DEFAULT_TD_PATH;
49186
- const towers = propTowers ?? rows(board.towers);
49187
- const creeps = propCreeps ?? rows(board.creeps);
50393
+ const entityTowers = useMemo(
50394
+ () => rows(board.towers).map((r) => ({
50395
+ id: str(r.id),
50396
+ x: num(r.x),
50397
+ y: num(r.y),
50398
+ range: num(r.range),
50399
+ damage: num(r.damage),
50400
+ cooldown: num(r.cooldown),
50401
+ lastFiredAt: r.lastFiredAt == null ? void 0 : num(r.lastFiredAt)
50402
+ })),
50403
+ [board.towers]
50404
+ );
50405
+ const towers = propTowers ?? entityTowers;
50406
+ const entityCreeps = useMemo(
50407
+ () => rows(board.creeps).map((r) => ({
50408
+ id: str(r.id),
50409
+ x: num(r.x),
50410
+ y: num(r.y),
50411
+ hp: num(r.hp),
50412
+ maxHp: num(r.maxHp),
50413
+ pathIndex: num(r.pathIndex),
50414
+ speed: num(r.speed)
50415
+ })),
50416
+ [board.creeps]
50417
+ );
50418
+ const creeps = propCreeps ?? entityCreeps;
49188
50419
  const hero = propHero ?? { id: "hero", x: 8, y: 8 };
49189
50420
  const gold = propGold ?? num(board.gold, 100);
49190
50421
  const lives = propLives ?? num(board.lives, 20);
@@ -49236,12 +50467,11 @@ function TowerDefenseBoard({
49236
50467
  (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
49237
50468
  ).map((t2) => ({ x: t2.x, y: t2.y }));
49238
50469
  }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
49239
- const isoTiles = useMemo(() => tilesToIso(tiles), [tiles]);
49240
- const towerUnits = useMemo(() => towersToUnits(towers), [towers]);
49241
- const creepUnits = useMemo(() => creepsToUnits(creeps), [creeps]);
49242
- const heroUnit = useMemo(() => heroToUnit(hero), [hero]);
49243
- const isoUnits = useMemo(() => [...towerUnits, ...creepUnits, heroUnit], [towerUnits, creepUnits, heroUnit]);
49244
- const pathFeatures = useMemo(() => pathToFeatures(path), [path]);
50470
+ const isoTiles = useMemo(() => tilesToIso2(tiles, assetManifest), [tiles, assetManifest]);
50471
+ const creepUnits = useMemo(() => creepsToUnits(creeps, assetManifest), [creeps, assetManifest]);
50472
+ const heroUnit = useMemo(() => heroToUnit(hero, assetManifest), [hero, assetManifest]);
50473
+ const isoUnits = useMemo(() => [...creepUnits, heroUnit], [creepUnits, heroUnit]);
50474
+ const towerFeatures = useMemo(() => towersToFeatures(towers, assetManifest), [towers, assetManifest]);
49245
50475
  const handleTileClick = useCallback((x, y) => {
49246
50476
  if (result !== "none") return;
49247
50477
  if (pathPositions.has(`${x},${y}`)) return;
@@ -49300,7 +50530,9 @@ function TowerDefenseBoard({
49300
50530
  {
49301
50531
  tiles: isoTiles,
49302
50532
  units: isoUnits,
49303
- features: pathFeatures,
50533
+ features: towerFeatures,
50534
+ assetBaseUrl: assetManifest?.baseUrl,
50535
+ assetManifest,
49304
50536
  validMoves,
49305
50537
  hoveredTile,
49306
50538
  onTileClick: handleTileClick,
@@ -49339,7 +50571,7 @@ function TowerDefenseBoard({
49339
50571
  ] }) })
49340
50572
  ] });
49341
50573
  }
49342
- var CDN6, TD_GRID_W, TD_GRID_H, TERRAIN_SPRITES, DEFAULT_TD_PATH, DEFAULT_TD_TILES, TOWER_SPRITE, CREEP_SPRITE, HERO_SPRITE;
50574
+ var TD_GRID_W, TD_GRID_H, DEFAULT_TD_PATH, DEFAULT_TD_TILES;
49343
50575
  var init_TowerDefenseBoard = __esm({
49344
50576
  "components/game/organisms/TowerDefenseBoard.tsx"() {
49345
50577
  "use client";
@@ -49351,16 +50583,8 @@ var init_TowerDefenseBoard = __esm({
49351
50583
  init_Stack();
49352
50584
  init_IsometricCanvas();
49353
50585
  init_boardEntity();
49354
- CDN6 = "https://almadar-kflow-assets.web.app/shared/";
49355
50586
  TD_GRID_W = 16;
49356
50587
  TD_GRID_H = 16;
49357
- TERRAIN_SPRITES = {
49358
- ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
49359
- path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
49360
- wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`,
49361
- grass: `${CDN6}isometric-dungeon/Isometric/dirt_E.png`,
49362
- dirt: `${CDN6}isometric-dungeon/Isometric/dirtTiles_E.png`
49363
- };
49364
50588
  DEFAULT_TD_PATH = [
49365
50589
  { x: 2, y: 0 },
49366
50590
  { x: 2, y: 1 },
@@ -49413,9 +50637,6 @@ var init_TowerDefenseBoard = __esm({
49413
50637
  { x: 13, y: 15 }
49414
50638
  ];
49415
50639
  DEFAULT_TD_TILES = buildDefaultTDTiles();
49416
- TOWER_SPRITE = `${CDN6}sprite-sheets/guardian-sprite-sheet-se.png`;
49417
- CREEP_SPRITE = `${CDN6}sprite-sheets/scrapper-sprite-sheet-se.png`;
49418
- HERO_SPRITE = `${CDN6}sprite-sheets/amir-sprite-sheet-se.png`;
49419
50640
  TowerDefenseBoard.displayName = "TowerDefenseBoard";
49420
50641
  }
49421
50642
  });
@@ -49683,6 +50904,214 @@ var init_UncontrolledBattleBoard = __esm({
49683
50904
  UncontrolledBattleBoard.displayName = "UncontrolledBattleBoard";
49684
50905
  }
49685
50906
  });
50907
+ function resolveManifestUrl6(manifest, relative) {
50908
+ if (relative == null) return void 0;
50909
+ if (/^https?:\/\//.test(relative)) return relative;
50910
+ const base = manifest?.baseUrl;
50911
+ if (base == null) return relative;
50912
+ const cleanBase = base.replace(/\/$/, "");
50913
+ const cleanRel = relative.replace(/^\//, "");
50914
+ return `${cleanBase}/${cleanRel}`;
50915
+ }
50916
+ function VisualNovelBoard({
50917
+ entity,
50918
+ nodes: propNodes,
50919
+ currentNodeId: propCurrentNodeId,
50920
+ assetManifest: propAssetManifest,
50921
+ typewriterSpeed = 30,
50922
+ portraitScale = 1,
50923
+ chooseEvent,
50924
+ advanceEvent,
50925
+ restartEvent,
50926
+ className
50927
+ }) {
50928
+ const board = boardEntity(entity) ?? {};
50929
+ const eventBus = useEventBus();
50930
+ const assetManifest = propAssetManifest ?? board.assetManifest;
50931
+ const entityNodes = useMemo(
50932
+ () => rows(board.nodes).map((r) => ({
50933
+ id: str(r.id),
50934
+ speaker: str(r.speaker),
50935
+ text: str(r.text),
50936
+ backgroundKey: r.backgroundKey == null ? void 0 : str(r.backgroundKey),
50937
+ portraitKey: r.portraitKey == null ? void 0 : str(r.portraitKey),
50938
+ choices: rows(r.choices).map((c) => ({
50939
+ label: str(c.label),
50940
+ nextId: str(c.nextId)
50941
+ }))
50942
+ })),
50943
+ [board.nodes]
50944
+ );
50945
+ const rawNodes = propNodes ?? entityNodes;
50946
+ const nodes = rawNodes.length > 0 ? rawNodes : DEFAULT_NODES;
50947
+ const currentNodeId = propCurrentNodeId ?? (str(board.currentNodeId) || nodes[0]?.id);
50948
+ const currentNode = nodes.find((n) => n.id === currentNodeId) ?? nodes[0];
50949
+ const backgroundImage = useMemo(
50950
+ () => resolveManifestUrl6(assetManifest, assetManifest?.backgrounds?.[currentNode?.backgroundKey ?? ""]),
50951
+ [assetManifest, currentNode?.backgroundKey]
50952
+ );
50953
+ const portraitUrl = useMemo(
50954
+ () => resolveManifestUrl6(assetManifest, assetManifest?.portraits?.[currentNode?.portraitKey ?? ""]),
50955
+ [assetManifest, currentNode?.portraitKey]
50956
+ );
50957
+ const dialogueChoices = useMemo(
50958
+ () => (currentNode?.choices ?? []).map((c) => ({ text: c.label, next: c.nextId })),
50959
+ [currentNode?.choices]
50960
+ );
50961
+ const handleChoice = useCallback(
50962
+ (choice) => {
50963
+ const index = dialogueChoices.findIndex((c) => c.next === choice.next && c.text === choice.text);
50964
+ if (chooseEvent) {
50965
+ eventBus.emit(`UI:${chooseEvent}`, { choiceIndex: index });
50966
+ }
50967
+ },
50968
+ [dialogueChoices, chooseEvent, eventBus]
50969
+ );
50970
+ const handleAdvance = useCallback(() => {
50971
+ if (advanceEvent) {
50972
+ eventBus.emit(`UI:${advanceEvent}`, {});
50973
+ }
50974
+ }, [advanceEvent, eventBus]);
50975
+ const handleRestart = useCallback(() => {
50976
+ if (restartEvent) {
50977
+ eventBus.emit(`UI:${restartEvent}`, {});
50978
+ }
50979
+ }, [restartEvent, eventBus]);
50980
+ if (!currentNode) {
50981
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("visual-novel-board relative min-h-[600px] bg-background items-center justify-center", className), gap: "lg", children: [
50982
+ /* @__PURE__ */ jsx(Typography, { variant: "h3", color: "muted", children: "No dialogue nodes" }),
50983
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleRestart, children: "Restart" })
50984
+ ] });
50985
+ }
50986
+ return /* @__PURE__ */ jsx(Box, { className: cn("visual-novel-board relative min-h-[600px] bg-background overflow-hidden", className), children: /* @__PURE__ */ jsx(
50987
+ DialogueBox,
50988
+ {
50989
+ dialogue: {
50990
+ speaker: currentNode.speaker,
50991
+ text: currentNode.text,
50992
+ choices: dialogueChoices
50993
+ },
50994
+ typewriterSpeed,
50995
+ position: "bottom",
50996
+ backgroundImage,
50997
+ portraitUrl,
50998
+ portraitScale,
50999
+ onChoice: handleChoice,
51000
+ onAdvance: handleAdvance
51001
+ }
51002
+ ) });
51003
+ }
51004
+ var DEFAULT_NODES;
51005
+ var init_VisualNovelBoard = __esm({
51006
+ "components/game/organisms/VisualNovelBoard.tsx"() {
51007
+ "use client";
51008
+ init_cn();
51009
+ init_useEventBus();
51010
+ init_Box();
51011
+ init_Typography();
51012
+ init_Stack();
51013
+ init_Button();
51014
+ init_DialogueBox();
51015
+ init_boardEntity();
51016
+ DEFAULT_NODES = [
51017
+ {
51018
+ id: "start",
51019
+ speaker: "Narrator",
51020
+ text: "The corridor stretches into shadow. A lone sentinel blocks your path.",
51021
+ backgroundKey: "corridor",
51022
+ portraitKey: "guide",
51023
+ choices: [
51024
+ { label: "Approach the sentinel.", nextId: "meet" },
51025
+ { label: "Turn back.", nextId: "retreat" }
51026
+ ]
51027
+ },
51028
+ {
51029
+ id: "meet",
51030
+ speaker: "Sentinel",
51031
+ text: "You carry the old marks. Speak your purpose, traveler.",
51032
+ backgroundKey: "forge",
51033
+ portraitKey: "rival",
51034
+ choices: [
51035
+ { label: "I seek the core.", nextId: "end" },
51036
+ { label: "I am only passing through.", nextId: "retreat" }
51037
+ ]
51038
+ },
51039
+ {
51040
+ id: "retreat",
51041
+ speaker: "Narrator",
51042
+ text: "You withdraw into the dark. The path will wait for braver feet.",
51043
+ backgroundKey: "corridor",
51044
+ choices: [{ label: "Begin again.", nextId: "start" }]
51045
+ },
51046
+ {
51047
+ id: "end",
51048
+ speaker: "Sentinel",
51049
+ text: "Then the way is open. Walk it well.",
51050
+ backgroundKey: "core",
51051
+ portraitKey: "rival",
51052
+ choices: [{ label: "Begin again.", nextId: "start" }]
51053
+ }
51054
+ ];
51055
+ VisualNovelBoard.displayName = "VisualNovelBoard";
51056
+ }
51057
+ });
51058
+ function VisualNovelTemplate({
51059
+ entity,
51060
+ title = "Visual Novel",
51061
+ nodes,
51062
+ currentNodeId,
51063
+ typewriterSpeed,
51064
+ portraitScale,
51065
+ chooseEvent,
51066
+ advanceEvent,
51067
+ restartEvent,
51068
+ className
51069
+ }) {
51070
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
51071
+ return /* @__PURE__ */ jsxs(
51072
+ Box,
51073
+ {
51074
+ display: "flex",
51075
+ fullHeight: true,
51076
+ className: cn("visual-novel-template flex-col", className),
51077
+ children: [
51078
+ /* @__PURE__ */ jsx(
51079
+ HStack,
51080
+ {
51081
+ gap: "sm",
51082
+ align: "center",
51083
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
51084
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
51085
+ }
51086
+ ),
51087
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
51088
+ VisualNovelBoard,
51089
+ {
51090
+ entity: resolved,
51091
+ nodes,
51092
+ currentNodeId,
51093
+ typewriterSpeed,
51094
+ portraitScale,
51095
+ chooseEvent,
51096
+ advanceEvent,
51097
+ restartEvent,
51098
+ className: "h-full"
51099
+ }
51100
+ ) })
51101
+ ]
51102
+ }
51103
+ );
51104
+ }
51105
+ var init_VisualNovelTemplate = __esm({
51106
+ "components/game/templates/VisualNovelTemplate.tsx"() {
51107
+ init_cn();
51108
+ init_Box();
51109
+ init_Stack();
51110
+ init_Typography();
51111
+ init_VisualNovelBoard();
51112
+ VisualNovelTemplate.displayName = "VisualNovelTemplate";
51113
+ }
51114
+ });
49686
51115
  function heroPosition(h) {
49687
51116
  if ("position" in h && h.position != null) return h.position;
49688
51117
  return vec2(h.position);
@@ -50023,48 +51452,48 @@ function WorldMapTemplate({
50023
51452
  }
50024
51453
  );
50025
51454
  }
50026
- var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
51455
+ var CDN5, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
50027
51456
  var init_WorldMapTemplate = __esm({
50028
51457
  "components/game/templates/WorldMapTemplate.tsx"() {
50029
51458
  init_WorldMapBoard();
50030
- CDN7 = "https://almadar-kflow-assets.web.app/shared";
51459
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
50031
51460
  DEFAULT_WORLDMAP_TILES = [
50032
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50033
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50034
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50035
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50036
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50037
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50038
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50039
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50040
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50041
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50042
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50043
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50044
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
50045
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50046
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50047
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50048
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50049
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50050
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
50051
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50052
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50053
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50054
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50055
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50056
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
51461
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
51462
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
51463
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
51464
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
51465
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
51466
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
51467
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
51468
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
51469
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
51470
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
51471
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
51472
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
51473
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
51474
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
51475
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
51476
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
51477
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
51478
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
51479
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
51480
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
51481
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
51482
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
51483
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
51484
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
51485
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN5}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
50057
51486
  ];
50058
51487
  DEFAULT_WORLDMAP_UNITS = [
50059
- { 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` },
50060
- { 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` }
51488
+ { 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` },
51489
+ { 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` }
50061
51490
  ];
50062
51491
  DEFAULT_WORLDMAP_FEATURES = [
50063
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
50064
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
51492
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN5}/scenes/world/capital.png` },
51493
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN5}/world-map/power_node.png` }
50065
51494
  ];
50066
51495
  DEFAULT_WORLDMAP_MANIFEST = {
50067
- baseUrl: CDN7,
51496
+ baseUrl: CDN5,
50068
51497
  terrains: {
50069
51498
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
50070
51499
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -50084,7 +51513,7 @@ var init_WorldMapTemplate = __esm({
50084
51513
  }
50085
51514
  });
50086
51515
  function lazyThree(name, loader) {
50087
- const Lazy = React81__default.lazy(
51516
+ const Lazy = React84__default.lazy(
50088
51517
  () => loader().then((m) => {
50089
51518
  const Resolved = m[name];
50090
51519
  if (!Resolved) {
@@ -50096,13 +51525,13 @@ function lazyThree(name, loader) {
50096
51525
  })
50097
51526
  );
50098
51527
  function ThreeWrapper(props) {
50099
- return React81__default.createElement(
51528
+ return React84__default.createElement(
50100
51529
  ThreeBoundary,
50101
51530
  { name },
50102
- React81__default.createElement(
50103
- React81__default.Suspense,
51531
+ React84__default.createElement(
51532
+ React84__default.Suspense,
50104
51533
  { fallback: null },
50105
- React81__default.createElement(Lazy, props)
51534
+ React84__default.createElement(Lazy, props)
50106
51535
  )
50107
51536
  );
50108
51537
  }
@@ -50146,7 +51575,10 @@ var init_component_registry_generated = __esm({
50146
51575
  init_CalendarGrid();
50147
51576
  init_CanvasEffect();
50148
51577
  init_Card();
51578
+ init_CardBattlerBoard();
51579
+ init_CardBattlerTemplate();
50149
51580
  init_CardGrid();
51581
+ init_CardHand();
50150
51582
  init_Carousel();
50151
51583
  init_CaseStudyCard();
50152
51584
  init_CaseStudyOrganism();
@@ -50157,6 +51589,8 @@ var init_component_registry_generated = __esm({
50157
51589
  init_ChartLegend();
50158
51590
  init_Checkbox();
50159
51591
  init_ChoiceButton();
51592
+ init_CityBuilderBoard();
51593
+ init_CityBuilderTemplate();
50160
51594
  init_ClassifierBoard();
50161
51595
  init_CodeBlock();
50162
51596
  init_CodeRunnerPanel();
@@ -50377,6 +51811,8 @@ var init_component_registry_generated = __esm({
50377
51811
  init_Toast();
50378
51812
  init_ToastSlot();
50379
51813
  init_Tooltip();
51814
+ init_TopDownShooterBoard();
51815
+ init_TopDownShooterTemplate();
50380
51816
  init_TowerDefenseBoard();
50381
51817
  init_TowerDefenseTemplate();
50382
51818
  init_TraitFrame();
@@ -50392,6 +51828,8 @@ var init_component_registry_generated = __esm({
50392
51828
  init_UploadDropZone();
50393
51829
  init_VersionDiff();
50394
51830
  init_ViolationAlert();
51831
+ init_VisualNovelBoard();
51832
+ init_VisualNovelTemplate();
50395
51833
  init_VoteStack();
50396
51834
  init_WaypointMarker();
50397
51835
  init_WizardContainer();
@@ -50400,7 +51838,7 @@ var init_component_registry_generated = __esm({
50400
51838
  init_WorldMapBoard();
50401
51839
  init_WorldMapTemplate();
50402
51840
  init_XPBar();
50403
- ThreeBoundary = class extends React81__default.Component {
51841
+ ThreeBoundary = class extends React84__default.Component {
50404
51842
  constructor() {
50405
51843
  super(...arguments);
50406
51844
  __publicField(this, "state", { failed: false });
@@ -50410,7 +51848,7 @@ var init_component_registry_generated = __esm({
50410
51848
  }
50411
51849
  render() {
50412
51850
  if (this.state.failed) {
50413
- return React81__default.createElement(
51851
+ return React84__default.createElement(
50414
51852
  "div",
50415
51853
  {
50416
51854
  "data-testid": "three-unavailable",
@@ -50466,7 +51904,10 @@ var init_component_registry_generated = __esm({
50466
51904
  "CalendarGrid": CalendarGrid,
50467
51905
  "CanvasEffect": CanvasEffect,
50468
51906
  "Card": Card,
51907
+ "CardBattlerBoard": CardBattlerBoard,
51908
+ "CardBattlerTemplate": CardBattlerTemplate,
50469
51909
  "CardGrid": CardGrid,
51910
+ "CardHand": CardHand,
50470
51911
  "Carousel": Carousel,
50471
51912
  "CaseStudyCard": CaseStudyCard,
50472
51913
  "CaseStudyOrganism": CaseStudyOrganism,
@@ -50477,6 +51918,8 @@ var init_component_registry_generated = __esm({
50477
51918
  "ChartLegend": ChartLegend,
50478
51919
  "Checkbox": Checkbox,
50479
51920
  "ChoiceButton": ChoiceButton,
51921
+ "CityBuilderBoard": CityBuilderBoard,
51922
+ "CityBuilderTemplate": CityBuilderTemplate,
50480
51923
  "ClassifierBoard": ClassifierBoard,
50481
51924
  "CodeBlock": CodeBlock,
50482
51925
  "CodeRunnerPanel": CodeRunnerPanel,
@@ -50709,6 +52152,8 @@ var init_component_registry_generated = __esm({
50709
52152
  "Toast": Toast,
50710
52153
  "ToastSlot": ToastSlot,
50711
52154
  "Tooltip": Tooltip,
52155
+ "TopDownShooterBoard": TopDownShooterBoard,
52156
+ "TopDownShooterTemplate": TopDownShooterTemplate,
50712
52157
  "TowerDefenseBoard": TowerDefenseBoard,
50713
52158
  "TowerDefenseTemplate": TowerDefenseTemplate,
50714
52159
  "TraitFrame": TraitFrame,
@@ -50725,6 +52170,8 @@ var init_component_registry_generated = __esm({
50725
52170
  "VStack": VStack,
50726
52171
  "VersionDiff": VersionDiff,
50727
52172
  "ViolationAlert": ViolationAlert,
52173
+ "VisualNovelBoard": VisualNovelBoard,
52174
+ "VisualNovelTemplate": VisualNovelTemplate,
50728
52175
  "VoteStack": VoteStack,
50729
52176
  "WaypointMarker": WaypointMarker,
50730
52177
  "WizardContainer": WizardContainer,
@@ -50751,7 +52198,7 @@ function SuspenseConfigProvider({
50751
52198
  config,
50752
52199
  children
50753
52200
  }) {
50754
- return React81__default.createElement(
52201
+ return React84__default.createElement(
50755
52202
  SuspenseConfigContext.Provider,
50756
52203
  { value: config },
50757
52204
  children
@@ -51241,7 +52688,7 @@ function renderPatternChildren(children, onDismiss, parentId = "root", parentPat
51241
52688
  const key = `${parentId}-${index}-trait:${traitName}`;
51242
52689
  return /* @__PURE__ */ jsx(TraitFrame, { traitName }, key);
51243
52690
  }
51244
- return /* @__PURE__ */ jsx(React81__default.Fragment, { children: child }, `${parentId}-${index}`);
52691
+ return /* @__PURE__ */ jsx(React84__default.Fragment, { children: child }, `${parentId}-${index}`);
51245
52692
  }
51246
52693
  if (!child || typeof child !== "object") return null;
51247
52694
  const childId = `${parentId}-${index}`;
@@ -51281,14 +52728,14 @@ function isPatternConfig(value) {
51281
52728
  if (value === null || value === void 0) return false;
51282
52729
  if (typeof value !== "object") return false;
51283
52730
  if (Array.isArray(value)) return false;
51284
- if (React81__default.isValidElement(value)) return false;
52731
+ if (React84__default.isValidElement(value)) return false;
51285
52732
  if (value instanceof Date) return false;
51286
52733
  if (typeof value === "function") return false;
51287
52734
  const record = value;
51288
- return "type" in record && typeof record.type === "string" && isKnownPattern(record.type);
52735
+ return "type" in record && typeof record.type === "string" && getComponentForPattern$1(record.type) !== null;
51289
52736
  }
51290
52737
  function isPlainConfigObject(value) {
51291
- if (React81__default.isValidElement(value)) return false;
52738
+ if (React84__default.isValidElement(value)) return false;
51292
52739
  if (value instanceof Date) return false;
51293
52740
  const proto = Object.getPrototypeOf(value);
51294
52741
  return proto === Object.prototype || proto === null;
@@ -51409,6 +52856,22 @@ function SlotContentRenderer({
51409
52856
  const childrenIsRenderFn = typeof incomingChildren === "function";
51410
52857
  const { children: _childrenConfig, ...restPropsNoChildren } = content.props;
51411
52858
  const restProps = childrenIsRenderFn ? { ...restPropsNoChildren, children: incomingChildren } : restPropsNoChildren;
52859
+ const nodeSlotOverrides = {};
52860
+ for (const slotKey of CONTENT_NODE_SLOTS) {
52861
+ const slotVal = restProps[slotKey];
52862
+ if (slotVal === void 0 || slotVal === null) continue;
52863
+ if (React84__default.isValidElement(slotVal) || typeof slotVal === "string" || typeof slotVal === "number" || typeof slotVal === "boolean") continue;
52864
+ if (Array.isArray(slotVal) || typeof slotVal === "object" && "type" in slotVal) {
52865
+ nodeSlotOverrides[slotKey] = renderPatternChildren(
52866
+ slotVal,
52867
+ onDismiss,
52868
+ `${content.id}-${slotKey}`,
52869
+ `${myPath}.${slotKey}`,
52870
+ content.sourceTrait,
52871
+ { slot: content.slot, transitionEvent: content.transitionEvent, fromState: content.fromState, entity: content.entity }
52872
+ );
52873
+ }
52874
+ }
51412
52875
  const renderedProps = renderPatternProps(restProps, onDismiss);
51413
52876
  const patternDef = getPatternDefinition(content.pattern);
51414
52877
  const propsSchema = patternDef?.propsSchema;
@@ -51439,6 +52902,9 @@ function SlotContentRenderer({
51439
52902
  }
51440
52903
  }
51441
52904
  const finalProps = renderedProps;
52905
+ for (const [k, v] of Object.entries(nodeSlotOverrides)) {
52906
+ finalProps[k] = v;
52907
+ }
51442
52908
  const resolvedItems = Array.isArray(
51443
52909
  finalProps.entity
51444
52910
  ) ? finalProps.entity : null;
@@ -51559,7 +53025,7 @@ function UISlotRenderer({
51559
53025
  }
51560
53026
  return wrapped;
51561
53027
  }
51562
- var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, FORM_PATTERNS, SELF_OVERLAY_PATTERNS, PATTERNS_WITH_CHILDREN;
53028
+ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, FORM_PATTERNS, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN;
51563
53029
  var init_UISlotRenderer = __esm({
51564
53030
  "components/core/organisms/UISlotRenderer.tsx"() {
51565
53031
  "use client";
@@ -51596,6 +53062,16 @@ var init_UISlotRenderer = __esm({
51596
53062
  "wizard-step"
51597
53063
  ]);
51598
53064
  SELF_OVERLAY_PATTERNS = /* @__PURE__ */ new Set(["modal", "confirm-dialog"]);
53065
+ CONTENT_NODE_SLOTS = /* @__PURE__ */ new Set([
53066
+ "logo",
53067
+ "master",
53068
+ "detail",
53069
+ "trigger",
53070
+ "content",
53071
+ "addons",
53072
+ "hud",
53073
+ "fallback"
53074
+ ]);
51599
53075
  PATTERNS_WITH_CHILDREN = /* @__PURE__ */ new Set([
51600
53076
  "stack",
51601
53077
  "vstack",
@@ -51720,7 +53196,7 @@ function resolveLambdaBindings(body, params, item, index) {
51720
53196
  if (Array.isArray(body)) {
51721
53197
  return body.map((b) => recur(b));
51722
53198
  }
51723
- if (body !== null && typeof body === "object" && !React81__default.isValidElement(body) && !(body instanceof Date) && typeof body !== "function") {
53199
+ if (body !== null && typeof body === "object" && !React84__default.isValidElement(body) && !(body instanceof Date) && typeof body !== "function") {
51724
53200
  const out = {};
51725
53201
  for (const [k, v] of Object.entries(body)) {
51726
53202
  out[k] = recur(v);
@@ -51739,7 +53215,7 @@ function getSlotContentRenderer2() {
51739
53215
  function makeLambdaFn(params, lambdaBody, callerKey) {
51740
53216
  return (item, index) => {
51741
53217
  const resolvedBody = resolveLambdaBindings(lambdaBody, params, item, index);
51742
- if (resolvedBody === null || typeof resolvedBody !== "object" || Array.isArray(resolvedBody) || typeof resolvedBody === "function" || React81__default.isValidElement(resolvedBody) || resolvedBody instanceof Date) {
53218
+ if (resolvedBody === null || typeof resolvedBody !== "object" || Array.isArray(resolvedBody) || typeof resolvedBody === "function" || React84__default.isValidElement(resolvedBody) || resolvedBody instanceof Date) {
51743
53219
  return null;
51744
53220
  }
51745
53221
  const record = resolvedBody;
@@ -51758,7 +53234,7 @@ function makeLambdaFn(params, lambdaBody, callerKey) {
51758
53234
  props: childProps,
51759
53235
  priority: 0
51760
53236
  };
51761
- return React81__default.createElement(SlotContentRenderer2, { content: childContent });
53237
+ return React84__default.createElement(SlotContentRenderer2, { content: childContent });
51762
53238
  };
51763
53239
  }
51764
53240
  function convertNode(node, callerKey) {
@@ -51777,7 +53253,7 @@ function convertNode(node, callerKey) {
51777
53253
  });
51778
53254
  return anyChanged ? mapped : node;
51779
53255
  }
51780
- if (typeof node === "object" && !React81__default.isValidElement(node) && !(node instanceof Date)) {
53256
+ if (typeof node === "object" && !React84__default.isValidElement(node) && !(node instanceof Date)) {
51781
53257
  return convertObjectProps(node);
51782
53258
  }
51783
53259
  return node;
@@ -52032,7 +53508,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
52032
53508
  };
52033
53509
  }, [traitBindings]);
52034
53510
  const executeTransitionEffects = useCallback(async (params) => {
52035
- const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log15 } = params;
53511
+ const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log14 } = params;
52036
53512
  const traitName = binding.trait.name;
52037
53513
  const linkedEntity = binding.linkedEntity || "";
52038
53514
  const entityId = payload?.entityId;
@@ -52120,7 +53596,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
52120
53596
  ...handlers,
52121
53597
  set: async (targetId, field, value) => {
52122
53598
  if (baseSet) await baseSet(targetId, field, value);
52123
- log15.debug("set:write", {
53599
+ log14.debug("set:write", {
52124
53600
  traitName,
52125
53601
  field,
52126
53602
  value: JSON.stringify(value),
@@ -52160,7 +53636,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
52160
53636
  const executor = new EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
52161
53637
  try {
52162
53638
  await executor.executeAll(effects);
52163
- log15.debug("effects:executed", () => ({
53639
+ log14.debug("effects:executed", () => ({
52164
53640
  traitName,
52165
53641
  transition: `${previousState}->${newState}`,
52166
53642
  event: flushEvent,
@@ -52170,7 +53646,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
52170
53646
  slotsTouched: Array.from(pendingSlots.keys()).join(",")
52171
53647
  }));
52172
53648
  for (const [slot, patterns] of pendingSlots) {
52173
- log15.debug("flush:slot", {
53649
+ log14.debug("flush:slot", {
52174
53650
  traitName,
52175
53651
  slot,
52176
53652
  patternCount: patterns.length,
@@ -52185,7 +53661,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
52185
53661
  });
52186
53662
  }
52187
53663
  } catch (error) {
52188
- log15.error("effects:error", {
53664
+ log14.error("effects:error", {
52189
53665
  traitName,
52190
53666
  transition: `${previousState}->${newState}`,
52191
53667
  event: flushEvent,
@@ -52660,7 +54136,7 @@ function clearSchemaCache() {
52660
54136
 
52661
54137
  // runtime/index.ts
52662
54138
  init_EntitySchemaContext();
52663
- var log12 = createLogger("almadar:ui:trait-provider");
54139
+ var log11 = createLogger("almadar:ui:trait-provider");
52664
54140
  var TraitContext = createContext(null);
52665
54141
  function TraitProvider({
52666
54142
  traits: traitBindings,
@@ -52677,7 +54153,7 @@ function TraitProvider({
52677
54153
  currentState: stateName,
52678
54154
  availableEvents: trait.transitions.filter((t) => t.from === stateName).map((t) => t.event),
52679
54155
  dispatch: (eventKey, payload) => {
52680
- log12.debug("Dispatch", () => ({
54156
+ log11.debug("Dispatch", () => ({
52681
54157
  trait: trait.name,
52682
54158
  event: eventKey,
52683
54159
  payloadKeys: payload ? Object.keys(payload) : []
@@ -53427,7 +54903,7 @@ OrbitalThemeProvider.displayName = "OrbitalThemeProvider";
53427
54903
  init_navigation();
53428
54904
  init_verificationRegistry();
53429
54905
  var PERF_NAMESPACE = "almadar:perf:canvas";
53430
- var log14 = createLogger(PERF_NAMESPACE);
54906
+ var log13 = createLogger(PERF_NAMESPACE);
53431
54907
  var RING_SIZE = 50;
53432
54908
  var ring = [];
53433
54909
  var writeIdx = 0;
@@ -53482,7 +54958,7 @@ function perfEnd(name, startToken, detail) {
53482
54958
  }
53483
54959
  }
53484
54960
  push({ name, durationMs, ts: endTs, detail });
53485
- log14.debug(name, () => ({ durationMs, ...detail ?? {} }));
54961
+ log13.debug(name, () => ({ durationMs, ...detail ?? {} }));
53486
54962
  }
53487
54963
  function perfTime(name, fn, detail) {
53488
54964
  const t = perfStart(name);
@@ -53500,7 +54976,7 @@ var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, com
53500
54976
  ts: commitTime,
53501
54977
  detail: { baseDuration }
53502
54978
  });
53503
- log14.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
54979
+ log13.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
53504
54980
  };
53505
54981
  function getPerfSnapshot() {
53506
54982
  if (ring.length < RING_SIZE) return ring.slice();